python筆試題(3)

  這裏的幾道題都是自己在學習Python中常遇到的幾個問題,這裏整理出來,防止自己忘記。

1,python中@property的用法

  (學習於廖雪峯:https://www.liaoxuefeng.com/wiki/1016959663602400/1017502538658208)

  在Python中,可以通過@property (裝飾器)將一個方法轉換爲屬性,從而實現用於計算的屬性。將方法轉換爲屬性後,可以直接通過方法名來訪問方法,而不需要一對小括號“()”,這樣可以讓代碼更加簡潔。

  在綁定屬性時,如果我們直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查參數,導致可以隨便賦值給對象,比如:

s = Student()
s.score = 9999

  這顯然不合邏輯,爲了限制score的範圍,可以通過一個 set_score() 方法來設置成績,再通過一個 get_score() 來獲取成績,這樣,在 set_score() 方法裏,就可以檢查參數:

class Student(object):

    def get_score(self):
        return self._score

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError("score must be an integer")
        if value < 0 or value > 100:
            raise ValueError("score must between 0~100")
        self._score = value

  現在,對任意的Student 實例進行操作,就不能隨心所欲的設置score了。

s = Student()
s.set_score(60)
print(s.get_score())
s.set_score(2123)
print(s.get_score())
'''
60
Traceback (most recent call last):
    ...  ... 
    raise ValueError("score must between 0~100")
ValueError: score must between 0~100'''

  但是,上面的調用方法又略顯複雜,沒有直接用屬性這麼直接簡單 。

  有沒有既能檢查參數,又可以用類似屬性這樣簡單的方法來訪問類的變量呢?對於追求完美的Python程序員來說,這是必須做到的。

  還記得裝飾器(decorator)可以給函數動態加上功能嗎?對於類的方法,裝飾器一樣起作用。Python內置的@property裝飾器就是負責把一個方法編程屬性調用的。

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError("score must be an integer")
        if value < 0 or value > 100:
            raise ValueError("score must between 0~100")
        self._score = value

  @property 的實現比較複雜,我們先考察如何使用。把一個 getter方法編程屬性,只需要加上 @property 就可以了,此時,@property本身又創建了另一個裝飾器 @score.setter ,負責把一個 setter 方法變成屬性賦值,於是,我們就擁有一個可控的屬性操作:

s = Student()
s.score = 60
print(s.score)
s.score = 1232
print(s.score)
'''
60
Traceback (most recent call last):
    ...  ... 
    raise ValueError("score must between 0~100")
ValueError: score must between 0~100'''

  注意到這個神奇的 @property ,我們在對實例屬性操作的時,就知道該屬性很可能不是直接暴露的,而是通過 getter 和 setter 方法來實現的。

  還可以定義只讀屬性,只定義 getter 方法,不定義 setter 方法就是一個只讀屬性:

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

  上面的 birth 是可讀寫屬性,而 age 就是一個只讀屬性,因爲 age 可以根據  birth 和當前時間計算出來的。

  所以 @property 廣泛應用在類的定義中,可以讓調用者寫出簡短的代碼,同時保證對參數進行必要的檢查,這樣,程序運行時就減少了出錯的可能性。

@property爲屬性添加安全保護機制

  在Python中,默認情況下,創建的類屬性或者實例是可以在類外進行修改的,如果想要限制其不能再類體外修改,可以將其設置爲私有的,但設置爲私有後,在類體外也不能獲取他的值,如果想要創建一個可以讀但不能修改的屬性,那麼也可以用@property 實現只讀屬性。

  注意:私有屬性__name   爲單下劃線,當然也可以定義爲 name,但是爲什麼定義_name?

  以一個下劃線開頭的實例變量名,比如 _age,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當看到這樣的變量時,意思是:雖然可以被訪問,但是,請視爲私有變量,不要隨意訪問

 

2,Python中類繼承object和不繼承object的區別

  我們寫代碼,常發現,定義class的時候,有些代碼繼承了object,有些代碼沒有繼承object,那麼有object和沒有object的區別是什麼呢?

  下面我們查看,在Python2.x 中和Python3.x中,通過分別繼承object和不繼承object定義不同的類,之後通過dir() 和 type 分別查看該類的所有方法和類型:

  Python2.X

>>> class test(object):
...     pass
...
>>> dir(test)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '_
_init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__size
of__', '__str__', '__subclasshook__', '__weakref__']
>>> type(test)
<type 'type'>


>>> class test2():
...     pass
...
>>> dir(test2)
['__doc__', '__module__']
>>> type(test2)
<type 'classobj'>

  Python3.X

>>> class test(object):
    pass

>>> class test2():
    pass

>>> type(test)

<class 'type'>

>>> type(test2)

<class 'type'>

>>> dir(test)

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

>>> dir(test2)

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

  所以:在Python3.X中,繼承和不繼承object對象,我們創建的類,均擁有好多可操作對象。所以不用管。而python2.X中,卻不一樣。那這是爲什麼呢?

  這就需要從新式類和經典類說起了。

#新式類是指繼承object的類
class A(obect):
      pass

#經典類是指沒有繼承object的類
class A:
     pass

  Python中推薦大家使用新式類(新式類已經兼容經典類,修復了經典類中多繼承出現的bug)

那經典類中多繼承bug是什麼呢?

  (參考文獻:https://www.cnblogs.com/attitudeY/p/6789370.html)

  下面我們看下圖:

   BC爲A的子類,D爲BC的子類,A中有save方法,C對其進行了重寫。

  在經典類中,調用D的save 方法,搜索按深度優先,路徑 B-A-C,執行的爲A中save ,顯然不合理

  在新式類中,調用D的save 方法,搜索按廣度優先,路徑B-C-A,執行的爲C中save

  代碼:

#經典類
class A:
    def __init__(self):
        print 'this is A'

    def save(self):
        print 'come from A'

class B(A):
    def __init__(self):
        print 'this is B'

class C(A):
    def __init__(self):
        print 'this is C'
    def save(self):
        print 'come from C'

class D(B,C):
    def __init__(self):
        print 'this is D'

d1=D()
d1.save()  #結果爲'come from A


#新式類
class A(object):
    def __init__(self):
        print 'this is A'

    def save(self):
        print 'come from A'

class B(A):
    def __init__(self):
        print 'this is B'

class C(A):
    def __init__(self):
        print 'this is C'
    def save(self):
        print 'come from C'

class D(B,C):
    def __init__(self):
        print 'this is D'

d1=D()
d1.save()   #結果爲'come from C'

  所以,Python3.X 中不支持經典類了,雖然還是可以不帶object,但是均統一使用新式類,所以在python3.X 中不需要考慮object的繼承與否。但是在 Python 2.7 中這種差異仍然存在,因此還是推薦使用新式類,要繼承 object 類。

3,深度優先遍歷和廣度優先遍歷

  圖的遍歷是指從圖中某一頂點除法訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷。

  深度優先遍歷常用的數據結構爲棧,廣度優先遍歷常用的數據結構爲隊列。

3.1 深度優先遍歷Depth First Search(DFS)

  深度優先遍歷的思想是從上至下,對每一個分支一直往下一層遍歷直到這個分支結束,然後返回上一層,對上一層的右子樹這個分支繼續深搜,直到一整棵樹完全遍歷,因此深搜的步驟符合棧後進先出的特點。

  深度優先搜索算法:不全部保留節點,佔用空間少;有回溯操作(即有入棧,出棧操作),運行速度慢。

  其實二叉樹的前序,中序,後序遍歷,本質上也可以認爲是深度優先遍歷。

3.2 廣度優先遍歷Breadth First Search(BFS)

  廣度優先遍歷的思想是從左到右,對樹的每一層所有節點依次遍歷,當一層的節點遍歷完全後,對下一層開始遍歷,而下一層節點又恰好是上一層的子結點。因此廣搜的步驟符合隊列先進先出的思想。

  廣度優先搜索算法:保留全部節點,佔用空間大;無回溯操作(即無入棧,出棧操作),運行速度快。

  其實二叉樹的層次遍歷,本質上也可以認爲是廣度優先遍歷。

4,Python 邏輯運算符 異或 xor

  首先,讓我們用真值表來看一下異或的運算邏輯:

   也就是說:

    AB有一個爲真,但不同時爲真的運算稱作異或

  看起來,簡單明瞭,但是如果我們將布爾值之間的異或換成數字之間的異或會發生什麼呢?

  讓我們來試一下

0 ^ 0
0 ^ 1
1 ^ 0
1 ^ 1

>>>0
1
1
0

   結果告訴我們,數字相同異或值爲0  數字不相同異或值爲1

  讓我們再試試0, 1 除外的數字

5 ^ 3
>>>6

   爲什麼答案是6呢?(其中這裏經歷了幾次計算,下面學習一下)

  異或是基於二進制基礎上按位異或的結果計算 5^3 的過程,其實是將5和3分別轉成二進制,然後進行異或計算。

5 = 0101(b)

3 = 0011(b)

   按照位 異或:

0^0 ->0

1^0 ->1

0^1 ->1

1^1 ->0

   排起來就是 0110(b) 轉換爲十進制就是 6

   注意上面:如果a,b兩個值不相同,則異或結果爲1,如果兩個值相同,則異或結果爲0

 

5,Python的位運算

  位運算:就是對該數據的二進制形式進行運算操作。

  位運算分爲六種情況,如下:

   我們可以看一下1~10的十進制轉二進制結果表示:

  注意:這個 bin() 函數對於負數的二進制解析要多加註意。而且 Python裏面的內置函數 bin() 是解析爲二進制,和實際的在計算機裏面的二進制表示有點不一樣,bin() 函數的輸出通常不考慮最高位的運算符,而是拿 - 來代替!

左移運算

  左移運算符:運算數的各二進制全部左移若干位,由“<<” 右邊的數指定移動的位數,高位丟棄,低位補0。

  規律簡單來說就是:

  舉個例子:

# 運算數的二進制左移若干位,高位丟棄,低維補0

a = 2                    其二進制爲:0b10

a << 2 :                 將a移動兩位: ob1000   
a << n = a*(2**n) =8     8的二進制爲: 0b1000

a << 4 :                 將a移動兩位: ob100000   
a << n = a*(2**n) =32    32的二進制爲: 0b10000

a << 5 :                 將a移動兩位: ob1000000   
a << n = a*(2**n) =64    64的二進制爲: 0b100000

 

右移運算

  右移運算符:把“>>” 左邊的運算數的各二進制全部右移若干位,“>>” 右邊的數指定移動的位數。正數補0,負數補1。

  規律簡單來說就是:

  舉個例子:

# 把“>>” 左邊的運算數的各二進位全部右移若干位,“>>” 右邊的數指定移動的位數。
# 正數補0,負數補1

a = 64                    其二進制爲:0b1000000

a >> 2 :                 將a移動兩位:  ob10000   
a >> n = a%(2**n) =16    16的二進制爲: 0b10000

a >> 4 :                 將a移動兩位: ob100   
a >> n = a%(2**n) =4     4的二進制爲: 0b100

a >> 5 :                 將a移動兩位: ob10   
a >> n = a%(2**n) =2     2的二進制爲: 0b10

   注意:左移右移不一樣,右移複雜一些。

按位與

  按位與運算符:參與運算的兩個值,如果兩個相應位都是1,則該位的結果爲1,否則爲0。

  舉個例子:

# 參與運算的兩個值,如果兩個相應位都爲1,則該位的結果爲1,否則爲0。

a = 2                    其二進制爲:0b10
b = 4                    其二進制爲:0b100

a & b    010 & 100     000 
print(int('0', 2))   0   # 0的二進制爲 ob0

 

按位或

  按位或運算符:只要對應的二個二進位有一個爲1時,結果就爲1,否則爲0。

  舉個例子:

# 按或運算的兩個值,只要對應的兩個二進位有一個爲1時,則該位的結果爲1,否則爲0。

a = 2                    其二進制爲:0b10
b = 4                    其二進制爲:0b100

a | b    010 | 100     110 
print(int('110', 2))   6   # 6的二進制爲 ob110

 

按位異或

  按位異或運算符:當兩對應的二進位相異時,結果爲1,否則爲0。

  舉個例子:

# 按位異或運算的兩個值,當對應的兩個二進位相異時,則該位的結果爲1,否則爲0。

a = 2                    其二進制爲:0b10
b = 4                    其二進制爲:0b100

a ^ b    010 ^ 100     110 
print(int('110', 2))   6   # 0的二進制爲 ob110

 

按位取反

  按位取反運算符:對數據的每個二進制值加1再取反, ~x 類似於 -x-1

  舉個例子:

# 按位取反運算符:對數據的每個二進制位取反,即把 1 變爲 0,把 0 變爲 1。

a = 2                          其二進制爲:0b10
(a+1)*(-1) = -3                其二進制爲:-0b11

res = ~a         

 

二進制轉十進制

  二進制轉十進制方法很簡單:即每一位都乘以2的(位數-1)次方。

# 二進制轉爲十進制,每一位乘以2的(位數-1)次方

# 如下:
    比如4,其二進制爲 ob100, 那麼 
        100 = 1*2**(3-1) + 0*2**(2-1) + 0*2**(1-1) = 4

   即將各個位拆開,然後每個位乘以 2 的(位數-1)次方。

十進制轉二進制

  十進制轉二進制時,採用“除2取餘,逆序排列”法。

  1. 用 2  整除十進制數,得到商和餘數
  2. 再用 2 整數商,得到新的商和餘數
  3. 重複第一步和第二步,直到商爲0
  4. 將先得到的餘數作爲二進制數的高位,後得到的餘數作爲二進制數的低位,依次排序

  如下:

# 十進制轉二進制  採用 除2取餘,逆序排列
# 比如 101 的二進制 '0b1100101'

101 % 2 = 50 餘 1
50 % 2 = 25  餘 0
25 % 2 = 12  餘 1
12 % 2 = 6   餘 0
6 % 2 = 3    餘 0
3 % 2 = 1    餘 1
1 % 2 = 0    餘 1

# 所以 逆序排列即二進制中的從高位到低位排序:得到7位數的二進制數爲 ob1100101

 

6,collections.defaltdict()的用法

  我們先看看其源碼解釋:

class defaultdict(dict):
    """
    defaultdict(default_factory[, ...]) --> dict with default factory
    
    The default factory is called without arguments to produce
    a new value when a key is not present, in __getitem__ only.
    A defaultdict compares equal to a dict with the same items.
    All remaining arguments are treated the same as if they were
    passed to the dict constructor, including keyword arguments.
    """

   就是說collections類中的 defaultdict() 方法爲字典提供默認值。和字典的功能是一樣的。函數返回與字典類似的對象。只不過defaultdict() 是Python內建字典(dict)的一個子類,它重寫了方法_missing_(key) ,增加了一個可寫的實例變量  default_factory,實例變量  default_factory 被 missing() 方法使用,如果該變量存在,則用以初始化構造器,如果沒有,則爲None。其他的功能和 dict一樣。

  而它比字典好的一點就是,使用dict的時候,如果引入的Key不存在,就會拋出 KeyError。如果希望Key不存在時,返回一個默認值,就可以用 defaultdict。

from collections import defaultdict

res = defaultdict(lambda: 1)
res['key1'] = 'value1'   # key1存在  key2不存在
print(res['key1'], res['key2'])
# value1 1
# 從結果來看,key2不存在,則返回默認值
# 注意默認值是在調用函數返回的,而函數在創建defaultdict對象時就已經傳入了。
# 除了在Key不存在返回默認值,defaultdict的其他行爲與dict是完全一致的

 

7,Python中 pyc文件學習

  參考地址:https://blog.csdn.net/newchitu/article/details/82840767

  這個需要從頭說起。

7.1  Python爲什麼是一門解釋型語言?

  初學Python時,聽到的關於 Python的第一句話就是,Python是一門解釋型語言,直到發現了 *.pyc 文件的存在。如果是解釋型語言,那麼生成的 *.pyc 文件是什麼呢?

  c 應該是 compiled 的縮寫,那麼Python是解釋型語言如何解釋呢?下面先來看看編譯型語言與解釋型語言的區別。

7.2  編譯型語言與解釋型語言

  計算機是不能識別高級語言的,所以當我們運行一個高級語言程序的時候就需要一個“翻譯機”來從事把高級語言轉變爲計算機能讀懂的機器語言的過程。這個過程分爲兩類,第一種是編譯,第二種是解釋。

  編譯型語言在程序執行之前,先會通過編譯器對程序執行一個編譯的過程,把程序轉變成機器語言。運行時就不需要翻譯,而直接執行就可以了。最典型的例子就是C語言。

  解釋型語言就沒有這個編譯的過程,而程序執行的時候,通過解釋器對程序逐行做出解釋,然後直接運行,最典型的例子就是Ruby。

  所以說,編譯型語言在程序運行之前就已經對程序做出了“翻譯”,所以在運行時就少掉了“翻譯”的過程,所以效率比較高。但是我們也不能一概而論,一些解釋型語言也可以通過解釋器的優化來對程序做出翻譯時對整個程序做出優化,從而在效率上接近編譯型語言。

  此外,隨着Java等基於虛擬機的語言的興起,我們又不能把語言純粹的分爲解釋型和編譯型兩種,用Java來說,java就是首先通過編譯器編譯成字節碼文件,然後在運行時通過解釋器給解釋成及其文件,所以說java是一種先編譯後解釋的語言。

7.3  Python是什麼呢?

  其實Python和Java/ C# 一樣,也是一門基於虛擬機的語言。

  當我們在命令行中輸入 python test.py 的時候,其實是激活了 Python的“解釋器”,告訴“解釋器”: 要開始工作了。

  可是在“解釋”之前,其實執行的第一項工作和java一樣,是編譯。

  所以Python是一門先編譯後解釋的語言。

  Python在解釋源碼程序時分爲兩步:

  如下圖所示:第一步:將源碼編譯爲字節碼;第二步:將字節碼解釋爲機器碼。

  當我們的程序沒有修改過,那麼下次運行程序的時候,就可以跳過從源碼到字節碼的過程,直接加載pyc文件,這樣可以加快啓動速度。

7.4  簡述Python的運行過程

  在學習Python的運行過程之前,先說兩個概念,PyCodeObject和pyc文件。

  其實PyCodeObject 是Python編譯器真正編譯成的結果。

  當Python程序運行時,編譯的結果則是保存在位於內存中的 PyCodeObject中,當Python程序運行結束時,Python解釋器則將 PyCodeObject寫回pyc文件中。

  當Python程序第二次運行時,首先程序會在硬盤中尋找pyc文件,如果找到,則直接載入,否則就重複上面的過程。

  所以我們應該這樣定位PyCodeObject和pyc文件,我們說 Pyc文件其實是PyCodeObject的一種持久化保存方式。

7.5   Python中單個pyc文件的生成

  pyc文件是當我們 import 別的 py文件時,那個 py文件會被存一份 pyc 加速下次裝載,而主文件因爲只需要裝載一次就沒有存 pyc。

  比如上面的例子,從上圖我們可以看出 test_b.py 是被引用的文件,所以它會在__pycache_ 下生成 test_b.py 對應的 pyc 文件,若下次執行腳本時,若解釋器發現你的 *.py 腳本沒有變更,便會跳出編譯這一步,直接運行保存在 __pycache__ 目錄下的 *.pyc文件。

7.6   Python中pyc文件的批量生成

  針對一個目錄下所有的 py文件進行編譯,Python提供一個模板叫 compileall,代碼如下:

  這樣就可以批量生成所有文件的 pyc。

   命令行爲:

python -m compileall <dir>

7.7   Python中pyc文件的注意點

  使用 pyc文件可以跨平臺部署,這樣就不會暴露源碼。

  • 1,import 過的文件纔會自動生成 pyc文件
  • 2,pyc文件不可以直接看到源碼,但是可以被反編譯
  • 3,pyc的內容,是跟-Python版本相關的,不同版本編譯後的pyc文件是不同的,即3.6編譯的文件在 3.5中無法執行。

7.8   Python中pyc文件的反編譯

  使用 uncompyle6, 可以將 Python字節碼轉換回等效的 Python源代碼,它接受python1.3  到 3.8 版本的字節碼。

  安裝代碼:

pip install uncompyle6

   使用示例:

# 反編譯 test_a.pyc 文件,輸出爲 test_a.py 源碼文件

uncompyle6 test_a.py test_a.cpython-36.pyc

   如下:

   源代碼如下:

  感覺反編譯對於簡單的文件還是很OK的。我將複雜代碼進行反編譯,發現也可以出現,但是註釋等一些代碼就不會出現。

8,super() 函數

  參考地址:https://www.runoob.com/python/python-func-super.html

8.1 super()函數描述

  super() 函數是用於調用父類(超類)的一個方法。根據官方文檔,super函數返回一個委託類 type 的父類或者兄弟類方法調用的代理對象。super 函數用來調用已經在子類中重寫過的父類方法。

  python3是直接調用 super(),這其實是 super(type, obj)的簡寫方式。

  super 是用來解決多重繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查找順序(MRO),重複調用(鑽石繼承)等種種問題。

8.2 單繼承

  在單繼承中,super().__init__() 與 Parent.__init__() 是一樣的。super()避免了基類的顯式調用。

  下面看一個實例來驗證結果是否一致:

# -*- coding:utf-8 -*-

# 下面兩種class 的方法一樣,現在建議使用第二種,畢竟使用新式類比較方便
# class FooParent(object): 
class FooParent:
    def __init__(self):
        print('this is parent')

    def bar(self, message):
        print('%s from parent'%message)
        print('parent bar function')


class FooChild(FooParent):
    def __init__(self):
        # 方法一
        # super(FooChild, self).__init__()  # 在Python3中寫成 super().__init()
        # 方法二
        FooParent.__init__(self)
        print('this is child')


if __name__ == '__main__':
    foochild = FooChild()
    foochild.bar('message')
    '''
        方法1結果:
            this is parent
            this is child
            message from parent
            parent bar function

        方法2結果:
            this is parent
            this is child
            message from parent
            parent bar function
    '''

 

8.3 多繼承

  super 與父類沒有實質性的關聯。在單繼承時,super獲取的類剛好是父類,但多繼承時,super獲取的是繼承順序中的下一個類。

  以下面繼承方式爲例:

         Parent
          /  \
         /    \
    child1     child2
         \    /
          \  /
       grandchild

   使用super,代碼如下:

# -*- coding:utf-8 -*-

# 下面兩種class 的方法一樣,現在建議使用第二種,畢竟使用新式類比較方便
# class FooParent(object): 
class FooParent:
    def __init__(self):
        print('this is parent')

    def bar(self, message):
        print('%s from parent'%message)
        print('parent bar function')


class FooChild1(FooParent):
    def __init__(self):
        # 方法一
        # super(FooChild1, self).__init__()  # 在Python3中寫成 super().__init()
        # 方法二
        FooParent().__init__()
        print('this is child1111')


class FooChild2(FooParent):
    def __init__(self):
        # 方法一
        # super(FooChild2, self).__init__()  # 在Python3中寫成 super().__init()
        # 方法二
        FooParent().__init__()
        print('this is child2222')


class FooGrandchild(FooChild1, FooChild2):
    def __init__(self):
        # 方法一
        #super(FooGrandchild, self).__init__()  # 在Python3中寫成 super().__init()
        # 方法二
        FooChild1().__init__()
        FooChild2().__init__()
        print('this is FooGrandchild')

if __name__ == '__main__':
    FooChild1 = FooChild1()
    FooChild1.bar('message')
    '''
        方法1結果:
            this is parent
            this is child2222
            this is child1111
            this is FooGrandchild
            message from parent
            parent bar function

        方法2結果:
            this is parent
            this is parent
            this is child1111
            this is parent
            this is parent
            this is child1111
            this is parent
            this is parent
            this is child2222
            this is parent
            this is parent
            this is child2222
            this is FooGrandchild
            message from parent
            parent bar function
    爲了方便查看,我們打印child1的結果如下:
            this is parent
            this is parent
            this is child1111
            message from parent
            parent bar function
    '''

   可以看出如果不使用 super,會導致基類被多次調用,開銷非常大。

  最後一個例子:

# -*- coding:utf-8 -*-

# 下面兩種class 的方法一樣,現在建議使用第二種,畢竟使用新式類比較方便
# class FooParent(object): 
class FooParent:
    def __init__(self):
        self.parent = "I'm the parent"
        print('this is parent')

    def bar(self, message):
        print('%s from parent'%message)


class FooChild(FooParent):
    def __init__(self):
        # super(FooChild, self)首先找到FooChild的父類
        # 就是類 FooParent,然後把類 FooChild的對象轉換爲類 FooParent 的對象
        super(FooChild, self).__init__()
        print('this is child')

    def bar(self, message):
        super(FooChild, self).bar(message)
        print('Child bar function')


if __name__ == '__main__':
    foochild = FooChild()
    foochild.bar('durant is my favorite player')
    '''
        this is parent
        this is child
        durant is my favorite player from parent
        Child bar function
    '''

   這裏解釋一下,我們直接繼承父類的bar方法,但是我們不需要其方法內容,所以使用super() 來修改。最後執行,我們也看到了效果。

  注意:如果我們在__init__()裏面寫入:

FooParent.bar.__init__(self)

   意思就是說,需要重構父類的所有方法,假設我們沒有重構,則繼承父類,如果重構了這裏需要與父類的名稱一樣。

 

9, Python3的 int 類型詳解(爲什麼int不存在溢出問題?)

  參考地址:https://www.cnblogs.com/ChangAn223/p/11495690.html

  在Python內部對整數的處理分爲普通整數和長整數,普通整數長度是機器位長,通常都是 32 位,超過這個範圍的整數就自動當長整數處理,而長整數的範圍幾乎沒有限制,所以long類型運算內部使用大數字算法實現,可以做到無長度限制。

  在以前的 python2中,整型分爲 int 和 Long,也就是整型和長整型,長整型不存在溢出問題,即可以存放任意大小的數值,理論上支持無線大數字。因此在Python3中,統一使用長整型,用 int 表示,在Python3中不存在 long,只有 int。

  長整形 int 結構其實也很簡單,在 longinterpr.h 中定義:

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

  ob_digit  是一個數組指針,digit可認爲是 Int 的別名。

  python中整型結構中的數組,每個元素最大存儲 15位的二進制數(不同位數操作系統有差異 32 位系統存 16位,64位系統是 32位)。

  如 64位系統最大存儲32位的二進制數,即存儲的最大十進制數爲 2^31-1 = 2147483647,也就是說上面例子中數組的一個元素存儲的最大值是 2147483647。

  需要注意的是:實際存儲是以二進制形式存儲,而非我們寫的十進制

  有人說:一個數組元素所需要的內存大小是4字節即 32 位,但是其實存儲數字的有效位是30個(64位系統中)。其原因是:指數運算中要求位移量需要是 5的倍數,可能是某種優化算法,這裏不做深究。

10,爲什麼Python的Range要設計成左開右閉?

  Python的Range是左開右閉的,而且除了Python的Range,還有各種語言也有類似的設計。關於Range爲什麼要設計這個問題,Edsger W.Dijkstra在1982年寫過一篇短文中分析了一下其中的原因,當然那時候沒有Python,E.W.Dijkstra當年以其他語言爲例,但是思路是相通的,這裏做摘抄和翻譯如下:

  爲了表示2,3,...,12這樣一個序列,有四種方法

  • 2 ≤ i < 13(左閉右開區間)
  • 1 < i ≤ 12(左開右閉區間)
  • 2 ≤ i ≤ 12(閉區間)
  • 1 < i < 13(開區間)

  其中有沒有哪一種是最好的表示法呢?有的,前兩種表示法的兩端數字的差剛好是序列的長度,而且在這兩種的任何一個表示法中,兩個相鄰子序列的其中一個子序列的上界就是就是另一個子序列的下界,這只是讓我們跳出了前兩種,而不能讓我們從前兩種中選出最好的一種方法來,讓我們繼續分析。

  注意到自然數是有最小值的,當我們在下界取<(像第二和第四那樣),如果我們想表示從最小的自然數開始的序列,那這種表示法的下界就會是非自然數(比如0,1,....,5會被表示爲 -1 < i ≤ 5),這種表示法顯得太醜了,所以對於下界,我們喜歡<=。

  那我們再來看看上界,在下界使用<=的時候,如果我們對上界也使用<=會發生什麼呢?考慮一下當我們想要表示一個空集時,比如0 ≤ i ≤ -1上界會小於下界。顯然,這也是很難令人接受的,太反直覺了,而如果上界使用<,就會方便很多,同樣表示空集:0 ≤ i < 0。所以,對於上界,我們喜歡 <。

  好的,我們通過這些分析發現,第一種表示法是最直接的,我們再來看下標問題,到底我們應該給第一個元素什麼值呢?0還是1?對於含有N個元素的序列,使用第一種表示法:

  • 當從 1 開始時,下標範圍是 1 ≤ i < N+1;
  • 而如果從零開始,下標範圍是 0 ≤ i < N;
    讓我們的下標從零開始吧,這樣,一個元素的下標就等於當前元素之前的元素的數量了。(an element's subscript equals the number of elements preceding it in the sequence. )
所以總結一下爲什麼選擇第一種表示法(左閉右開區間):
  1,上下界之差等於元素的數量
  2,易於表示兩個相鄰子序列,一個子序列的上界就是另一個子序列的下界
  3,序列從零(最小自然數)開始計數時,下界的下標不是-1(非自然數)
  4,表達空集時,不會使得上界小於下界
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章