1. 程式人生 > 實用技巧 >python筆試題(3)

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,表達空集時,不會使得上界小於下界