Sharing

2011年10月31日 星期一

Python 學習手冊第三版筆記 (六)

CH.22 OOP: 大藍圖

  • 可多重繼承,我不一向不太喜歡這個,比較喜歡 Java 的單一繼承,事情有時會簡單一點
  • 每個函式必須加上 self ,我也覺得這個很多餘

CH.23 類別撰碼基礎

  • 和 def 一樣是可執行敘述,執行完後就打包成一個 class 物件,而且是可以動態增加元素
  • >>> class rec: pass    # 一開始裡面什麼都沒有
    
    >>> rec.name = 'Bob'      # 動態增加了一個欄位
    >>> x = rec()
    >>> x.name                      #  x 是 rec 的物件,可以存取到 name
    'Bob'
    >>> x.age                         # 想要存取 age 這個欄位會失敗,因為不存在
    
    Traceback (most recent call last):
      File "< pyshell#20>", line 1, in < module>
        x.age
    AttributeError: rec instance has no attribute 'age'
    >>> rec.age = 3              # 那我們就幫他增加 age 這個欄位吧!
    >>> x.age                        #  可以成功增加
    3
    
    除此之外,我們甚至可以動態增加物件裡面的欄位,不過這就不會影響到原生 Class
    >>> class rec:
     name = 'BoB'
     age = 3
    
    >>> x = rec()
    >>> x.length = 180
    >>> x.length                # 成功增加了 length 這個欄位
    180
    >>> rec.length            # 原生的 rec 仍然沒有這個欄位
    
    Traceback (most recent call last):
      File "< pyshell#34>", line 1, in < module>
        rec.length
    AttributeError: class rec has no attribute 'length'
  • __init__ 是建構子,__del__是解構子,在學C++時有聽過三一律,不知道 Python 有沒有符合這樣的特性
  • 可以利用 __add__、__mul__ 覆蓋運算子

另外函式也是可以輕易的被動態換掉,只要第一個參數是 Self 即可,我想這樣稍微解釋了為什麼他規定要每個函式第一個參數都是 Self,這樣的寫法其實很像我們用 C 在模擬 OOP 時,利用第一個參數來傳送 Instance 的道理是一樣的,所以要說 Python 是很先進的 OOP 的語法,好像又不是那麼一回事,只能說他反璞歸真吧~~

>>> def upperName(self):
 return self.name.upper()

>>> rec.method = upperName
>>> x.method()
'BOB'

CH.24 類別撰碼細節

我突然領悟到為什麼書一開頭有講到 Python 的 Class 類別長的像其它 OOP 語言但其實內裡完全不是那麼一回事。一般 OOP 的 Class 是一種宣告,存在於 ROM 之中,而且在一般狀況下會事先 loading 到 Memory 當中,然後 Instance 是 Class 的實作,每份 Instance 都是獨立的個體、獨立的 Memory,換一種方式來說 Class 的宣告是死的,Instance 是活的。然而 Python 的設計我覺得是一個很偷懶的設計,Class 本身就是一個活著的物件,而每個 Instance 看似是獨立的個體,但其實是障眼法,它其實只做了個身體,然後等你要用到右手的時候,它才做個右手給你,等你要用到左手的時候,它才做個左手給你,不然平常它都是直接偷用 Class 內的東西,所以如果你改了 Class 內的值,也會間接影響到這些 Instance,我看到書裡面這個範例時,快笑翻了,怎麼會有這麼偷懶的語言。

>>> class SharedData:
 spam = 42

>>> x = SharedData()
>>> y = SharedData()
>>> x.spam, y.spam
(42, 42)
>>> SharedData.spam = 99
>>> x.spam, y.spam, SharedData.spam
(99, 99, 99)
>>> x.spam = 88
>>> x.spam, y.spam, SharedData.spam
(88, 99, 99)

而且 Class 的宣告和 Instance 的生成,兩者之間看似很有關係,但這關係很容易被破壞,你除了可以在 Instance 上面加上原來就該有的手、腳外,你高興的話也可以臨時在 Class 上增加個翅膀,也可以臨時在 Instance 上增加個尾巴,然後也可以任意的把眼睛的功能變成透視眼功能,在這樣惡搞之下,Instance 其實可能會長的和 Class 一點也不像,不過幸好的是只能做"加法",而不能做"減法",所以 Instance 至少會保有 Class 所有的欄位及功能。

繼承的部份和 Instance 也很像,關係看似很緊,但其實也是很薄弱,Super class 可以動態亂改,Inheritor 也可以動態亂改,看了半天,我覺得繼承其實也是一種 Instance,只是他是一個有名字、可以方便再利用的 Instance。

Java 中有 Interface,C++ 中有 virtual function,Python 裡面沒有這樣的概念,但在實作上卻可以做的出來,只是如果忘了實作的話要等到要用到時才會發現。(這就是所謂了要吃飯時才發現飯忘了煮嗎?!天呀!真的是偷懶到極點的語言)

>>> class Super:
 def delegate(self):
  self.action()

  
>>> class Provider(Super):
 def action(self):
  print 'in Provider'

  
>>> x = Super()
>>> x.delegate()

Traceback (most recent call last):
  File "< pyshell#75>", line 1, in < module>
    x.delegate()
  File "< pyshell#69>", line 3, in delegate
    self.action()
AttributeError: Super instance has no attribute 'action'
>>> x = Provider()
>>> x.delegate()
in Provider


看到這裡其實也突然讓我發現一個事實,Python 的 method binding 在 def / class 中是不會即時檢查的,也不會事先 linking,而是動態的 linking,所以即使用了一個沒有宣告的函式或是物件,也必須要等到真的有被用到時,才會發現這個事情,但也因為這樣的設計,當你要寫兩個會互相用到的函式(放在不同的模組內),而必須互相 import 時卻不會出事

import modb

def funcA(x):
    if x == 1:
        return 0
    return 1 + modb.funcB(x /2)

class ClassA:
    def getInterClass():
        x = modb.ClassB()
        return x


import moda

def funcB(x):
    if x == 1:
        return 1
    return 1 + moda.funcA(x + 1)

class ClassB:
    def getInnerClass():
        x = moda.ClassA()
        return x



接下來看到 __getitem__ 的應用,書中寫到"買一送一堆",真的是太傳神了,而且我覺得這真的很強大,和__iter__不同的是,反覆器只能繞行一次,而索引運算式則可以一直重覆使用,不過反覆器的用法在寫程式上比較直覺。要解決這樣的問題可以做一個反覆器生成器出來。

__getattr__ 和 __setattr__ 就先跳過不看,太複雜了,竟然是用在實作 private 特性。



CH.25 類別的設計

getattr(X,N) 和 X.__dict__[N] 差異在於,前者也會執行繼承搜尋,但後者不會!

有幾個內建的屬性我覺得還滿重要的,雖然少用但在重要時刻可能會派上用場,特別是 Debug 時

  • 每個類別都有內建 __name__來顯示類別的名字
  • 每個類別都有內建 __bases__ 來顯示其繼承的類別
  • >>> class A: pass
    >>> class B: pass
    >>> class C(A,B): pass
    >>> C.__name__
    'C'
    >>> C.__bases__
    (< class __main__.A at 0x000000000279EF48>, < class __main__.B at 0x000000000279EFA8>)
    >>> C.__bases__[0].__name__
    'A'
    
    
  • __class__ ,每一個實體都可以藉此來辨別他的類別
  • >>> class sample:
     pass
    
    >>> x = sample()
    >>> x.__class__
    < class __main__.sample at 0x000000000279EEE8>
    
  • 每個類別及實體都有 __dict__ 來表示其擁有的屬性,但不包含繼承來的
  • 可以使用 dir 函式來找出所有的屬性,包括自動繼承的部份
  • >>> class C(A,B): pass
    >>> C.__dict__
    {'__module__': '__main__', '__doc__': None}
    >>> dir(C)
    ['__doc__', '__module__']
    >>> x = C()
    >>> x.__dict__
    {}
    >>> dir(x)
    ['__doc__', '__module__']
    
無綁束類別方法物件:必須要明確提供實體物件作為第一個引數 綁束實體方法物件: self + 函式配對,不必再另外傳入 self 兩種除了在使用方法上不一樣外,其它內建的屬性幾乎都一樣,只有 im_self 不同

>>> class Spam():
 def doit(): pass
>>> Spam.doit
< unbound method Spam.doit>
>>> x = Spam()
>>> x.doit
< bound method Spam.doit of <__main__.Spam instance at 0x00000000027A3CC8>>
>>> x.doit.__dict__
{}
>>> Spam.doit.__dict__
{}
>>> dir(x.doit)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'im_class', 'im_func', 'im_self']
>>> dir(Spam.doit)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'im_class', 'im_func', 'im_self']

>>> x.doit.im_self
<__main__.Spam instance at 0x00000000027A3188>
>>> Spam.doit.im_self

__doc__ 可以取得註解,註解必須寫下類別或是函式宣告的下一行



CH.26 高等類別議題
類別內以 "__" 開頭的屬性會被重新命名成 _[Classname]__[原始名稱],不論是一般變數或是函式,但在類別內的宣告時仍然可以用原始名稱來存取,但我覺得 Python 的作者發明這樣的東西其實也不是真的私有化,如果真的要解決書中的問題,可以發明關鍵字 "private" ,不是更乾脆?! 再來是靜態方法及類別方法之間的轉換可以靠 staticmethod 及 classmethod 我覺得也滿醜的,為什麼不用 static 這樣的關鍵字來處理? 更何況打從一開始,我就覺得 Python 是給懶人用的,根本不需要私有化及靜態方法,硬加上這些功能有點四不像。
__slots__ 可能會造成實體沒有 __dict__,以下是一個例子,不但影響了實體,其實連類別的 __dict__ 也產生了變化
>>> class limiter(object):
 __slots__  = ['age', 'name']
>>> x = limiter()
>>> limiter.__dict__
< dictproxy object at 0x00000000027A9108>
>>> x.__dict__

Traceback (most recent call last):
  File "< pyshell#169>", line 1, in < module>
    x.__dict__
AttributeError: 'limiter' object has no attribute '__dict__'
>>> dir(limiter)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'age', 'name']
>>> dir(x)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'age', 'name']
靜態方法、類別方法、一般方法的定義都不太一樣,尤其是"類別方法" ,被宣告成靜態方法後,不管是透過類別或是透過實體去呼叫,都只是一般的函式而己,不需要管是否有綁定,再來是實體內的函式都是有被綁定,透過類別的函式,如果有宣告 classmethod ,那就變成綁定的函式,但不管呼叫的是類別或是實體,都是綁定 class。
class Multi:
    def imeth(self, x):
        print self, x
    def smeth(x):
        print x
    def cmeth(cls, x):
        print cls, x
    smeth = staticmethod(smeth)
    cmeth = classmethod(cmeth)

>>> Multi.imeth
< unbound method Multi.imeth>                         # 非綁定
>>> Multi.smeth
< function smeth at 0x0000000002773E48>     # 一般函式
>>> Multi.cmeth
< bound method classobj.cmeth of < class __main__.Multi at 0x000000000276E6A8>>   # 綁定 class

>>> y = Multi()
>>> y.imeth
< bound method Multi.imeth of <__main__.Multi instance at 0x0000000002774D88>>   # 綁定 instance
>>> y.smeth
< function smeth at 0x0000000002773E48>   
>>> y.cmeth
< bound method classobj.cmeth of < class __main__.Multi at 0x000000000276E6A8>>   # 綁定 class

>>> Multi.imeth.im_self
>>> Multi.smeth.im_self

Traceback (most recent call last):
  File "< pyshell#297>", line 1, in < module>
    Multi.smeth.im_self
AttributeError: 'function' object has no attribute 'im_self'
>>> Multi.cmeth.im_self
< class __main__.Multi at 0x000000000270E6A8>


>>> y.imeth.im_self
<__main__.Multi instance at 0x0000000002713D08>
>>> y.smeth.im_self

Traceback (most recent call last):
  File "< pyshell#293>", line 1, in < module>
    y.smeth.im_self
AttributeError: 'function' object has no attribute 'im_self'
>>> y.cmeth.im_self
< class __main__.Multi at 0x000000000270E6A8>


沒有留言: