Python里的原型編程

最近在看《松本行弘的程序世界》,其中講到Ruby和Javascript里的原型編程,覺得非常靈活。我記得之前看Python教程的時候也有提到過類似的機制,無奈當時不求甚解,現(xiàn)在只好補補課。

在原型編程中,對象的成員變量和方法都可以動態(tài)修改,通過復制已有的對象來實現(xiàn)代碼的重用。這樣不需要事先定義好的類就可以實現(xiàn)面向?qū)ο缶幊?。要實現(xiàn)原型編程,首要條件就是對象的成員變量和方法可以動態(tài)修改。Python提供了setattr()和delattr()兩個函數(shù)來動態(tài)修改對象的屬性,也就是成員變量和方法。先看下面一段代碼:

class Foo(object):
    def do(self):
        print("done")

o = Foo()
try:
    print(o.bar)
except:
    print("no attribute bar found")

setattr(o, "bar", 1)
try:
    print(o.bar)
except:
    print("no attribute bar found")

delattr(o, "bar")
try:
    print(o.bar)
except:
    print("no attribute bar found")

第一次print(o.bar)的時候,因為Foo類型的對象沒有這個屬性,所以會拋出異常;第二次打印的時候,已經(jīng)通過setattr()添加了bar屬性,所以可以成功;之后又用delattr()刪除了屬性,所以第三次打印又是異常。事實上,還有更簡單的寫法:

o.bar = 1   # 等價于setattr(o, "bar", 1)
del o.bar   # 等價與delattr(o, "bar")

在Python里,每個自定義類的對象都有一個__dict__成員變量,這個字典里記錄了這個對象的動態(tài)屬性(在類定義之外添加的屬性)。對于Foo類型的對象o,打印出來就是這樣:

>>> o = Foo()
>>> o.bar = 1
>>> o.__dict__
{'bar': 1}

動態(tài)添加的bar在__dict__中,而類中定義的do不在。__dict__在對象外部是可讀可寫的,相當于一個public類型的成員變量。通過修改對象的__dict__,就可以修改對象的屬性。所以前面的代碼還有第三種寫法:

o.__dict__["bar"] = 1   # 等價于setattr(o, "bar", 1)
del o.__dict__["bar"]   # 等價與delattr(o, "bar")

Python的內(nèi)建類型,比如int、str或者object是沒有這個成員變量的,因此這些類型的對象是不能動態(tài)添加屬性的。既然可以對象的屬性可以動態(tài)增加,那么把一個函數(shù)賦值給o的一個成員變量,是不是就成了一個方法了呢?

def func():
    print("hello")

o.say = func
o.say()

看起來好像問題已經(jīng)解決了,不過其實沒有那么簡單。在類的成員方法中,第一個參數(shù)都是self(比如前面的do方法),通過它可以訪問對象的成員變量,相當于C++的this。但是這里的func()函數(shù)并沒有self參數(shù),因此也沒有辦法引用成員變量。我們試試給func()添加一個self參數(shù):

def func(self):
    print("hello")

再把它設置為o的屬性:

>>> o.say = func
>>> o.say()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 1 argument (0 given)

這次直接拋出異常,告訴我們func()函數(shù)缺少一個參數(shù)。而對于正常的成員方法,調(diào)用的時候是不需要顯式的傳遞self參數(shù)的,這說明func()函數(shù)距離真正成為成員函數(shù)還差了那么一丟丟。用type()函數(shù)查看一下類型,可以發(fā)現(xiàn)do和say原來是不同的類型:

>>> type(o.do)
<type 'instancemethod'>
>>> type(o.say)
<type 'function'>

看起來只要把func轉(zhuǎn)換instancemethod類型就可以了。Python的types包中提供了MethodType來完成這種轉(zhuǎn)換[1]

>>> from types import MethodType
>>> o.say = MethodType(func, o, Foo)
>>> o.say()
hello

到這里我們的目的已經(jīng)達到了。不過我還想補充一點兒關(guān)于開放類的內(nèi)容。《松本行弘的程序世界》第6章中講解了Ruby的開放類。在Ruby中,已經(jīng)聲明的類可以在代碼中動態(tài)的修改,可以添加和刪除方法,或者給方法起別名。這種方法很有用,比如RoR里就通過AcitveSupport對Ruby的基本類型進行了擴展,所以可以像下面這樣寫代碼:

2.weeks.ago

Python中其實也有類似的機制。在Python里,一切皆對象。所謂的類,也不過是一種特殊類型的對象罷了。先看看上面的Foo類,其實也有__dict__屬性:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, '__module__': 'test', '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})

不過這個__dict__的類型有點不一樣,不是字典,而是dict_proxy,這個我們暫時不討論,只要知道這里的__dict__是不能修改的就好了。

>>> Foo.__dict__['a'] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'dictproxy' object does not support item assignment

雖然這種方式不能用了,但是setattr()和Foo.bar = 1這兩種寫法還是可以的,因此類的屬性也是可以動態(tài)增加的。動態(tài)增加Foo類的屬性,是否會影響已經(jīng)生成的Foo類對象呢?

>>> o = Foo()
>>> Foo.bar = 1
>>> o.bar
1
>>> Foo.bar = 2
>>> o.bar
2

答案是肯定的。反過來,修改對象o的屬性,是否會影響到Foo呢?接著前面的代碼繼續(xù)執(zhí)行:

>>> o.bar = 3
>>> Foo.bar
2
>>> Foo.bar = 4
>>> o.bar

修改對象o的bar屬性不會影響類Foo的bar屬性,并且當o的bar屬性被修改之后,再次修改Foo.bar,就不會對o.bar產(chǎn)生影響了。這有點類似與copy-on-write機制。在沒有修改o.bar的時候,對象o其實是共享了Foo類的bar屬性,因此修改Foo.bar,o.bar也隨之改變。一旦修改了o.bar之后,就創(chuàng)建了一個bar屬性的副本,再修改Foo.bar就不會影響o.bar了。其實只要把整個過程中對象o的__dict__屬性打印出來就很清楚了:

>>> Foo.bar = 2
>>> o.__dict__
{}
>>> o.bar = 3
>>> o.__dict__
{'bar': 3}
>>> Foo.bar = 4
>>> o.__dict__
{'bar': 3}

給Foo類增加bar屬性的時候,o.__dict__是空。這是打印o.bar,由于o本身沒有bar屬性,就會找到Foo類的bar屬性。而對o.bar進行了修改之后,o.__dict__里就多了一個key為“bar”的項目,也就是給o本身增加了一個bar屬性,此后再打印o.bar,訪問的就是這個屬性,而不是Foo.bar了。

前面給類添加了成員變量,要添加方法也是一樣的,先用types.MethodType()將函數(shù)轉(zhuǎn)換成instancemethod類型,然后復制給某個屬性。給類添加方法時,MethodType()的第二個參數(shù)可以填None,不指定任何對象。

總結(jié)一下,Python還是很靈活的,不過美中不足的是,前面講的東西僅僅適用于自定義類型。對于Python的內(nèi)建類型,比如int、str和object,不論是類型本身還是對象,都是不能動態(tài)修改的。所以Python也沒有辦法像前面RoR那么靈活的去編程。另外,這種靈活性其實是雙刃劍,濫用的話會讓代碼變得很難讀,因為讀者找不到某些類或者對象的屬性都是在哪里添加的,也就很難理解代碼。


  1. 這個方法是在https://segmentfault.com/q/1010000004087006看到的,里面還提供另一種使用partial的實現(xiàn)方式,這里暫不討論。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.元類 1.1.1類也是對象 在大多數(shù)編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在Python中這...
    TENG書閱讀 1,417評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 前言 人生苦多,快來 Kotlin ,快速學習Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,669評論 9 118
  • 我是一個全職媽媽,帶了兩個娃,并拒絕老人們的幫助,因為,我不希望娃成為智能玩具,也不希望娃被慣的人嫌狗棄,所以,寧...
    青杏路涂閱讀 157評論 4 7
  • 今天,和我家兩個小侄女分享思維導圖這個工具。 她們倆的性格特質(zhì)很明顯,一個是I,一個是S。 我分享完之后讓她們畫思...
    鄧男神AI賦能教練閱讀 654評論 0 0

友情鏈接更多精彩內(nèi)容