天天看點

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)

  下面我們看下圖:

python筆試題(3)

   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

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

python筆試題(3)

   也就是說:

    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)
      

   按照位 異或:

python筆試題(3)
0^0 ->0

1^0 ->1

0^1 ->1

1^1 ->0
      

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

python筆試題(3)

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

5,Python的位運算

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

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

python筆試題(3)

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

python筆試題(3)

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

python筆試題(3)

左移運算

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

  規律簡單來說就是:

python筆試題(3)

  舉個例子:

# 運算數的二進制左移若幹位,高位丢棄,低維補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。

python筆試題(3)
# 把“>>” 左邊的運算數的各二進位全部右移若幹位,“>>” 右邊的數指定移動的位數。
# 正數補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在解釋源碼程式時分為兩步:

  如下圖所示:第一步:将源碼編譯為位元組碼;第二步:将位元組碼解釋為機器碼。

python筆試題(3)

  當我們的程式沒有修改過,那麼下次運作程式的時候,就可以跳過從源碼到位元組碼的過程,直接加載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。

python筆試題(3)

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

7.6   Python中pyc檔案的批量生成

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

python筆試題(3)

  這樣就可以批量生成所有檔案的 pyc。

python筆試題(3)

   指令行為:

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
      

   如下:

python筆試題(3)

   源代碼如下:

python筆試題(3)

  感覺反編譯對于簡單的檔案還是很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,表達空集時,不會使得上界小于下界

不經一番徹骨寒 怎得梅花撲鼻香

繼續閱讀