天天看點

(XWZ)的Python學習筆記Ⅲ——面向對象進階程式設計面向對象進階程式設計

目錄

面向對象進階程式設計

__slots__

@property

定制類

 使用枚舉類

使用元類

面向對象進階程式設計

__slots__

  1. 在上一篇文章講到過,我們可以通過簡單的操作就能夠給執行個體或類綁定屬性。這裡我們來讨論如何給執行個體和類綁定方法,先來看個例子:
    from types import MethodType
    
    class Dog():
        def __init__(self, name):
            self.Name = name
    
    def show(self):
        print('my name\'s %s' % self.Name)
    
    d = Dog('Dolly')
               
     需要注意的是:若是通過下面這種方式給執行個體綁定方法則在調用方法時,解釋器就不會自動把本執行個體作為一個參數傳入self中了,也就是不會将執行個體和方法進行綁定:
    In [2]: d.show = show
    
    In [3]: d.show()
    Traceback (most recent call last):
    
      File "<ipython-input-3-73a424ac9a82>", line 1, in <module>
        d.show()
    
    TypeError: show() missing 1 required positional argument: 'self'
               
     正确的綁定操作應該是這樣:
    In [5]: d.show = MethodType(show, d) #通過MethodType函數給執行個體d綁定方法
    
    In [6]: d.show() #調用剛剛綁定的方法
    my name's Dolly
               
    從例子中可以看到,我們通過MethodType函數來給執行個體綁定方法。這裡我們僅僅是給執行個體d綁定了show方法,也就是說其它的執行個體并沒有show方法:
    In [8]: d2 = Dog('Mike')
    
    In [9]: d2.show()
    Traceback (most recent call last):
    
      File "<ipython-input-9-714779789114>", line 1, in <module>
        d2.show()
    
    AttributeError: 'Dog' object has no attribute 'show'
               
    為了解決這個問題,我們可以嘗試給類綁定方法,因為類的方法是所有執行個體所共有的,給類綁定方法非常簡單:
    In [10]: def bark(self):
        ...:     print('wang wang wang !')
        ...:     
    
    In [11]: Dog.bark = bark
               
    現在我們來試試:
    In [12]: d.bark()
    wang wang wang !
    
    In [13]: d2.bark()
    wang wang wang !
               
  2. 有時我們想給執行個體綁定的屬性添加一個限定,使得屬性的綁定不能是任意的,而隻能綁定我們規定的幾種屬性。這就要使用一個特殊的變量——__slots__:
    class  Dog():
        __slots__ = ('name', 'gender') #限定Dog的執行個體隻能夠綁定name和gender這兩個屬性
               
    現在我們來試試給執行個體綁定屬性:
    In [2]: d = Dog()
    
    In [3]: d.name = 'Dolly' #給d綁定name屬性
    
    In [4]: d.name
    Out[4]: 'Dolly'
    
    In [5]: d.gender = 'femal' #給d綁定gender屬性
    
    In [6]: d.gender
    Out[6]: 'femal'
    
    In [7]: d.color = 'yello' #給d綁定color屬性
    Traceback (most recent call last):
    
      File "<ipython-input-7-dd28abd9292c>", line 1, in <module>
        d.color = 'yello'
    
    AttributeError: 'Dog' object has no attribute 'color'
               
    我們看到在給執行個體d綁定name和gender這兩個屬性時沒問題,在綁定屬性color時就抛出了錯誤,因為color這個屬性是不被允許的!如果再定義一個Dog的子類Husky,并且Husky類中沒有對__slots__進行聲明,那麼Husky的執行個體對屬性的綁定是不受其父類Dog影響的,可以任意綁定;如果Husky中對__slots__也進行了聲明,那麼Husky執行個體所允許綁定的屬性是兩個聲明的__slots__屬性之和。

@property

  1. 通過對前面知識的學習,我們在給執行個體綁定屬性時一般進行類似以下操作:
    In [13]: class Dog():
        ...:     def __init__(self, name):
        ...:         self._name = name #綁定屬性_name
        ...:     
    
    In [14]: d = Dog('Dolly')
    
    In [16]: d.age = 999999 #綁定屬性age
               
    但是這樣會産生的一個問題是:如果沒有任何措施加以限制的話,屬性值是可以改成任意值的,就比如上述例子中将age設為999999,這明顯不符合實際!也許我們可以定義一個set_age()方法用于修改屬性值,在方法中對屬性的取值加以限制:
    class Dog():
        def get_age(self):
            return self.age
        
        def set_age(self, val):
            if not isinstance(val, int): #檢查val的類型
                raise ValueError('年齡隻能為整數!')
            elif val < 0 or val > 30: #檢查val的取值
                raise ValueError('年齡隻能在0~30之間!')
            self.age = val
               
    現在我們來試試:
    In [18]: d = Dog()
    
    In [19]: d.set_age('六歲')
    Traceback (most recent call last):
    
      File "<ipython-input-19-8626661de8b1>", line 1, in <module>
        d.set_age('六歲')
    
      File "C:/Users/Whisky/.spyder-py3/temp.py", line 10, in set_age
        raise ValueError('年齡隻能為整數!')
    
    ValueError: 年齡隻能為整數!
    
    
    In [20]: d.set_age(35)
    Traceback (most recent call last):
    
      File "<ipython-input-20-b611f54c4999>", line 1, in <module>
        d.set_age(35)
    
      File "C:/Users/Whisky/.spyder-py3/temp.py", line 12, in set_age
        raise ValueError('年齡隻能在0~30之間!')
    
    ValueError: 年齡隻能在0~30之間!
    
    
    In [21]: d.set_age(6)
    
    In [22]: d.get_age()
    Out[22]: 6
               
    其實心細的同學會發現這沒從根本上解決問題,我們還是可以随意更改age的值,隻要不通過調用set_age()方法就行了:
    In [24]: d.age = 100
    
    In [25]: d.age
    Out[25]: 100
               
    那麼到底怎麼解決這個問題呢?這就要使用Pyhton提供的@property裝飾器了!先來看下面的例子:
    class Dog():
        @property
        def age(self):
            return self._age ##這裡不要寫成self.age
        
        @age.setter
        def age(self, val):
            if not isinstance(val, int):
                raise ValueError('年齡必須為整數值!')
            elif val < 0 or val > 30:
                raise ValueError('年齡隻能在0~30間')
            self._age = val ##這裡不要寫成self.age = val
               
    Python内置的@property裝飾器能夠把方法轉成屬性的調用,而另一個裝飾器@age.setter能夠把方法轉成屬性的指派,來看下怎麼使用:
    In [21]: d = Dog()
    
    In [22]: d.age = 6 #實際上是轉化為d.set_age(6)
    
    In [23]: d.age #實際上是轉化為d.get_age()
    Out[23]: 6
    
    In [27]: d.age = 999 #實際上是轉化為d.set_age(999)
    Traceback (most recent call last):
    
      File "<ipython-input-27-8e0eec63a531>", line 1, in <module>
        d.age = 999
    
      File "C:/Users/Whisky/.spyder-py3/temp.py", line 14, in age
        raise ValueError('年齡隻能在0~30間')
    
    ValueError: 年齡隻能在0~30間
               
    我們看到,在使用d.age = 999對age的值進行修改時,實際上是轉化為執行d.set_age(999),而999是age不能接受的取值故抛出錯誤。_在兩個方法中我們使用的屬性是_age而非age,其實将_age改成其他的名字也行,但就是不能寫成和age一樣!!!自己試試看會出現什麼情況!這實際上是給屬性age取了個别名_age,試試看直接通路_age會是什麼結果:
    In [24]: d._age
    Out[24]: 6
    
    In [25]: d._age = 999
    
    In [26]: d.age
    Out[26]: 999
               
    可以看到,我們其實還是可以直接修改age,你非要給age胡亂的設值也沒辦法,是以隻能要求你遵守程式設計規範,按規矩辦事😅。從上面這些例子我們可以到,@property修飾的方法是用于“讀”,而@屬性.setter修飾的方法是用于“寫”。是以如果要建立一個隻讀屬性那麼就隻需要添加@property裝飾器:
    class Dog():
        @property
        def age(self):
            return self._age
        
        @age.setter
        def age(self, val):
            if not isinstance(val, int):
                raise ValueError('年齡必須為整數值!')
            elif val < 0 or val > 30:
                raise ValueError('年齡隻能在0~30間')
            self._age = val
        
        @property 
        def birth(self): #birth為隻讀屬性
            return 2020 - self.age
               
    In [58]: print(Dog.birth)
    <property object at 0x000002976BDCE868>
    
    In [59]: d.birth
    Out[59]: 2014
    
    In [60]: d.birth = 1996 #嘗試修改隻讀屬性的值
    Traceback (most recent call last):
    
      File "<ipython-input-60-d4aa4bab9960>", line 1, in <module>
        d.birth = 1996
    
    AttributeError: can't set attribute
               
    我們可以用一句話來總結這兩個裝飾器的功能:@property能夠使我們在通路屬性時按照我們自己規定的方式進行,@屬性.setter能夠使我們在設定屬性的值時按照我們自己規定的方式進行。

定制類

  1. __str__()。當我們在類中實作了__str__()方法後,在使用print()函數列印某個對象時,會自動調用__str__()函數:
    class Dog():
        def __init__(self, name):
            self._name = name
        
        def __str__(self):
            return 'hello, my name\'s %s' % self._name
               
    In [70]: d = Dog('Dolly')
    
    In [71]: print(d)
    hello, my name's Dolly
               
    那麼直接在控制台輸入d會列印出什麼結果呢?試試看:
    In [72]: d
    Out[72]: <__main__.Dog at 0x2976bdb2f28>
               
    我們希望輸出的和print(d)結果一樣,實際上這兩者理應是一樣的。這時就需要實作__repr__()方法了,也許你會這樣實作:
    class Dog():
        def __init__(self, name):
            self._name = name
        
        def __str__(self):
            return 'hello, my name\'s %s' % self._name
        
        def __repr__(self):
            return 'hello, my name\'s %s' % self._name
               
    但其實上隻需要這樣就行了:
    class Dog():
        def __init__(self, name):
            self._name = name
        
        def __str__(self):
            return 'hello, my name\'s %s' % self._name
        
        __repr__ = __str__
               
    實作__repr__()是針對開發者,也就是說,

    __repr__()

    是為調試服務的;而__str__()是針對使用者的,傳回使用者能夠靠的字元串。
  2. __iter__()。在第一篇文章中講到過:凡是能用于for循環的就是Iterable類型,凡是能夠調用__next__()方法的就是Iterator類型,Iterator也屬于是Iterable類型。如果想要自己寫的類也能用于for...in,那就要在類中實作__iter__()方法,這樣,在使用for循環時就會不斷調用__next__()方法不斷生成下一個元素,直到抛出

    StopIteration為止。

    class Fib():
        def __init__(self):
            self.a, self.b = 0, 1
        
        def __iter__(self):
            return self
        
        def __next__(self):
            if self.b >= 500:
                raise StopIteration()
            self.a, self.b = self.b, self.a + self.b
            return self.b
               
    現在來試試:
    In [117]: f = Fib()
    
    In [118]: for x in f:
         ...:     print(x)
         ...:     
    1
    2
    3
    5
    8
    13
    21
    34
    55
    89
    144
    233
    377
    610
               
    那麼f是否是Iterable和Iterator類型的呢:
    In [119]: from collections import Iterable
    
    In [123]: from collections import Iterator
    
    In [124]: isinstance(f, Iterable)
    Out[124]: True
    
    In [125]: isinstance(f, Iterator)
    Out[125]: True
    
    
               
  3. __getitem__()。如果要讓我們的類能像list,str那樣能用下标對元素進行通路,那麼就要實作__getitem__()方法:
    class Fib():
        def __getitem__(self, idx):
            a, b = 1, 1
            for i in range(idx):
                a, b = b, a + b
            return a
               
    In [139]: f = Fib()
    
    In [140]: f[10]
    Out[140]: 89
    
    In [141]: f[100]
    Out[141]: 573147844013817084101
               
    如果要能夠像list,str那樣能使用切片呢?那麼就要對__getitem__()再進行修改:
    class Fib():
        def __getitem__(self, oj):
            if isinstance(oj, int): #判斷傳入的對象是否是int類型
                a, b = 1, 1
                for i in range(oj):
                    a, b = b, a + b
                return a
            elif isinstance(oj, slice): #判斷傳入的類型是否是slice類型
                a, b = 1, 1
                L = []           #定義一個list用于儲存結果
                start = oj.start
                stop = oj.stop
                if start == None:
                    start = 0
                if stop == None: #預設設定stop最大為1000
                    stop = 1000
                for i in range(start):
                    a, b = b, a + b
                for i in range(start, stop):
                    L.append(a)
                    a, b = b, a + b
                return L
               
    試試看:
    In [147]: f[0]
    Out[147]: 1
    
    In [148]: f[2]
    Out[148]: 2
    
    In [149]: f[:20]
    Out[149]: 
    [1,
     1,
     2,
     3,
     5,
     8,
     13,
     21,
     34,
     55,
     89,
     144,
     233,
     377,
     610,
     987,
     1597,
     2584,
     4181,
     6765]
               
    這裡隻是實作了簡單的切片操作,沒有對步長進行處理,也沒有考慮一些異常情況。
  4. __getattr__()。我們先定義一個類,并建立個執行個體:
    class Dog():
        def __init__(self, name):
            self._name = name
    
    
    d = Dog('Dolly')
               
    當通路該執行個體已綁定的屬性時自然沒有問題:
    In [186]: d._name
    Out[186]: 'Dolly'
               
    而通路不存在的屬性時就會抛出錯誤:
    In [187]: d._gender
    Traceback (most recent call last):
    
      File "<ipython-input-187-984b0f00d8a6>", line 1, in <module>
        d._gender
    
    AttributeError: 'Dog' object has no attribute '_gender'
               
    為此,python中提供了__getattr__()應對此問題。我們可以在類中實作該方法,這樣,在通路到不存在的屬性時就會自動調用該方法生成屬性,比如:
    class Dog():
        def __init__(self, name):
            self._name = name
        
        def __getattr__(self, attr): #調用該方法,自動生成屬性,注意!這裡的attr是字元串(經傳入的屬性名轉成了字元串形式)
            if attr == '_gender':
                return 'male' #會将_gender指派為'male'
               
    In [226]: Dog('Dolly')._gender
    Out[226]: 'male'
               
    該方法預設響應所有的屬性,如果屬性在__getattr__()中沒經過處理,預設傳回的是None,試試看:
    In [239]: hasattr(d, 'age') #測試是否有age屬性
    Out[239]: True
    
    In [240]: hasattr(d, 'color') #測試是否有color屬性
    Out[240]: True
    
    In [241]: d.age #預設傳回None,在控制台None是不顯示的
    
    In [242]: d.color #預設傳回None,在控制台None是不顯示的
    
    In [243]: d._gender #經過處理,傳回給定的值
    Out[243]: 'male'
               
    若要規定隻響應規定的幾個屬性,那麼就要這樣做:
    class Dog():
        def __init__(self, name):
            self._name = name
        
        def __getattr__(self, attr):
            if attr == '_gender':
                return 'male'
            raise AttributeError("'Dog'的執行個體沒有綁定該屬性!")
               
    In [246]: d = Dog('Mike')
    
    In [247]: d._gender
    Out[247]: 'male'
    
    In [248]: hasattr(d, 'color') #測試是否有color屬性
    Out[248]: False
    
    In [249]: d.color #通路未綁定的color屬性
    Traceback (most recent call last):
    
      File "<ipython-input-249-6063e1808d7c>", line 1, in <module>
        d.color
    
      File "C:/Users/Whisky/.spyder-py3/temp.py", line 10, in __getattr__
        raise AttributeError("'Dog'的執行個體沒有綁定該屬性!")
    
    AttributeError: 'Dog'的執行個體沒有綁定該屬性!
    
    In [250]: d.age #通路未綁定的age屬性
    Traceback (most recent call last):
    
      File "<ipython-input-250-ec62f7b57bf8>", line 1, in <module>
        d.age
    
      File "C:/Users/Whisky/.spyder-py3/temp.py", line 10, in __getattr__
        raise AttributeError("'Dog'的執行個體沒有綁定該屬性!")
    
    AttributeError: 'Dog'的執行個體沒有綁定該屬性!
               
  5. __call__()。python中還提供了一個很有趣的方法__call__(),我們在類中實作這個方法就能夠像調用函數一般來調用對象了:
    class Dog():
        def __call__(self, breed):
            print('I\'m %s!' % breed)
               
    In [268]: d = Dog()
    
    In [269]: d('Husky')
    I'm Husky!
               
    這樣看起來好像對象和函數就沒什麼差別了,是以我們就可以将對象看成是函數,将函數看成是對象,實際上這兩者本來就沒啥差別!既然如此,我們怎麼判斷一個對象是否能夠像函數那樣被調用呢?可以通過判斷該對象是否是Callable類型,能被調用就是Callable類型,比如函數和實作了__call__()方法的類執行個體:
    In [270]: callable(d)
    Out[270]: True
    
    In [271]: callable(abs)
    Out[271]: True
    
    In [272]: callable(123)
    Out[272]: False
    
    In [273]: callable('hello')
    Out[273]: False
               
  6. 在本節的最後來看個鍊式調用的例子:
    class Chain():
        def __init__(self, info):
            self._info = info
        
        def __str__(self):
            return self._info
        
        def __call__(self):
            return Chain(self._info)
        
        def __getattr__(self, attr):
            return  Chain(self._info)
        
        def test(self):
            return Chain(self._info)
               
    In [283]: print(Chain('鍊式調用測試!').test().user1.user2())
    鍊式調用測試!
               
    相信大家已經看出來了,就這麼一句代碼将類中定義的方法全部都用上了!!現在我們來對這句代碼進行剖析。首先“Chain('鍊式調用測試!')”是建立個Chain執行個體(為了方面叙述,将這裡的執行個體記為INST1),“.test()”是調用該執行個體的test的方法,而該方法傳回的是Chain執行個體(将該執行個體記為INS2),這裡相信大家都能夠看得懂。接下來“.user1”是通路INS2的屬性user1,但是INS2并沒有預先綁定該屬性,是以這裡就調用了INS2的__getattr__()方法,該方法得到的也是一個Chain執行個體(将該執行個體記為INS3),接着,同樣也先是通路INS3的user2屬性,但INS3沒有預先綁定該屬性,則調用__getattr__()傳回一個執行個體(記為INS4),大家發現沒,這裡對該執行個體是以函數的方式調用的,是以調用INS4的__call__()方法,同樣傳回一個執行個體(記為INS5),最後回到最外層的print()函數對INS5進行列印調用INS5的__str__()方法,該方法傳回INS5的info屬性。至此代碼運作完畢!!簡單的一行代碼藏了很深的知識!!!了解這個鍊式調用栗子,那麼這一節的知識就算是弄通了!!!!

 使用枚舉類

  1. 枚舉類型可以看成是一些标簽的集合,比如:月份包含一到十二個月,星期包含周一到周日,顔色包含紅橙黃綠青藍紫等等。python中提供了Enum類來實作枚舉類型,而我們可以通過繼承Enum來定制我們自己的枚舉類型,比如:
    from enum import Enum, unique
    
    class Month(Enum):
        Jan =  1
        Feb = 2
        Mar = 3
        Apr = 4
        May = 5
        Jun = 6
        Jul = 7
        Aug = 8
        Sep = 9
        Oct = 10
        Nov = 11
        Dec = 12
               
    這裡要注意,①我們無法直接執行個體化枚舉類,枚舉類中的成員稱為單例,都是枚舉類型的;②每個标簽都被賦予一個固定的值,标簽的值也是不能夠更改的:
    In [336]: m = Month() #嘗試執行個體化枚舉類
    Traceback (most recent call last):
    
      File "<ipython-input-336-1c51a47c3f35>", line 1, in <module>
        m = Month()
    
    TypeError: __call__() missing 1 required positional argument: 'value'
    
    
    In [337]: isinstance(Month.Jan, Month) 
    Out[337]: True
    
    In [338]: Month.Jan = 13
    Traceback (most recent call last):
    
      File "<ipython-input-337-27c71d0b74c9>", line 1, in <module>
        Month.Jan = 13
    
      File "E:\Anaconda3\lib\enum.py", line 386, in __setattr__
        raise AttributeError('Cannot reassign members.')
    
    AttributeError: Cannot reassign members.
               
    标簽中的值可以重複,值重複的多個标簽會被認為是具有多個名字的同一個标簽,若要限定不同标簽值不能重複則要使用@unique裝飾器:
    from enum import Enum, unique
    
    @unique
    class Month(Enum):
        Jan =  1
        Feb = 2
        Mar = 3
        Apr = 4
        May = 5
        Jun = 6 #值重複
        Jul = 6 #值重複
        Aug = 6 #值重複
        Sep = 9
        Oct = 10
        Nov = 11
        Dec = 12
               
    由于标簽Jun、Jul、Aug三個标簽的值重複,抛出以下錯誤:
    ValueError: duplicate values found in <enum 'Month'>: Jul -> Jun, Aug -> Jun
               
    枚舉類型通路方式有多種:
    In [354]: Month.Feb #由标簽通路值
    Out[354]: <Month.Feb: 2>
    
    In [355]: Month['Nov'] #由标簽通路值
    Out[355]: <Month.Nov: 11>
    
    In [356]: Month(12) #由值通路标簽
    Out[356]: <Month.Dec: 12>
    
    In [357]: for m in Month:  #這種方式對于值相同的标簽隻列印一次
         ...:     print(m)
         ...:     
    Month.Jan
    Month.Feb
    Month.Mar
    Month.Apr
    Month.May
    Month.Jun
    Month.Jul
    Month.Aug
    Month.Sep
    Month.Oct
    Month.Nov
    Month.Dec
    
    In [358]: for m in Month.__members__: #這種方式會列印出所有标簽,包括值相同的标簽
         ...:     print(m)
         ...:     
    Jan
    Feb
    Mar
    Apr
    May
    Jun
    Jul
    Aug
    Sep
    Oct
    Nov
    Dec
    
    In [359]: for name, member in Month.__members__.items():
         ...:     print(name, '=>', member)
         ...:     
    Jan => Month.Jan
    Feb => Month.Feb
    Mar => Month.Mar
    Apr => Month.Apr
    May => Month.May
    Jun => Month.Jun
    Jul => Month.Jul
    Aug => Month.Aug
    Sep => Month.Sep
    Oct => Month.Oct
    Nov => Month.Nov
    Dec => Month.Dec
               
    這裡的特殊屬性__members__是一個将名稱映射到成員的有序字典,也可以通過它來完成周遊,大家可以自己試試周遊__members__.keys()、__members__.values()和__members__.items()看看會得到什麼結果。

使用元類

  1. 前面我們用到過type()這個函數,可以用來判斷一個對象的類型:
    In [43]: type(1)
    Out[43]: int
    
    In [44]: type('hello')
    Out[44]: str
    
    In [45]: type((1,))
    Out[45]: tuple
    
    In [46]: type({})
    Out[46]: dict
               
    它還有另一個功能——能夠建立類。之前我們建立類都是用下面這種形式:
    class Dog():
        def __init__(self, name):
            self._name = name
               
    其實在運作過程中,解釋器隻是掃描了下文法,最終還是要通過調用type()函數來建立,下面來看下如何用type來建立類:
    In [63]: def fun(self): #先定義函數
        ...:    print('hello world!')
        ...:    
    
    In [64]: x = 1 
    
    In [66]: A = type('A', (object,), dict(show = fun, _x = x)) #調用type函數建立類A
    
    In [67]: A._x #通路A的類屬型
    Out[67]: 1
    
    In [68]: A().show() #調用方法
    hello world!
               
    type()需要傳入三個參數:①類名;②所繼承的父類; ③綁定的類屬型和方法。
  2. 除了用type函數建立類,我們還可用元類(metaclass)定制我們自己的類,metaclass就是類的模闆。我們知道,對象是類的執行個體,其實我們也可以将類了解為metaclass的“執行個體”。下面看看元類怎麼使用:
    class MyMetaclass(type): #定義元類
        def __new__(cls, name, bases, attr):
            attr['show'] = lambda self: print('hello world!') #給類添加show方法
            attr['x'] = 7 #給類添加屬性x
            bases = (A,) #讓類繼承A
            return type.__new__(cls, name, bases, attr)
    
    
    class A(): #類A
        def __print__(self):
            print('I\'m \'A\'')
               
    定義元類和定義一般的類是一樣的方式,但是記得要繼承type(其實所有的類都是type類型的),接着我們要在元類中實作一個__new__方法,__new__方法中的參數含義分别是:①目前要建立的類,相當于我們在定義一般類時的self; ②類名; ③類需要繼承的父類集合;④dict類型,類方法和集屬性集合。接下來建立類B,同時傳入關鍵字參數metaclass,表明使用我們定義的MyMetaclass來定制類:
    class B(metaclass = MyMetaclass):
        pass
               
    接下來進行測試:
    In [78]: b = B()
    
    In [79]: B.x
    Out[79]: 7
    
    In [80]: b.show()
    hello world!
    
    In [81]: b.__print__()
    I'm 'A'