博客鏈接:http://inarrater.com/2016/07/10/pythonadvance5/
7. Function
作為Callable部分第一個(gè)別提到,但是最后來(lái)分析它。原因其實(shí)很簡(jiǎn)單,我們已經(jīng)發(fā)現(xiàn)前面的很多內(nèi)容,包括bound method也好,static method也好,甚至operators,本質(zhì)上都是function。
那么,如何去探究一個(gè)Python的Function的屬性呢,閱讀過(guò)前面內(nèi)容的讀者應(yīng)該很明白了,通過(guò)一些簡(jiǎn)單的代碼加上print就可以看到不少東西了。
我們先用dirf來(lái)看下一個(gè)function對(duì)象身上有什么。
def foo(a, b = 10):
print a + b
print dir(foo)
輸出結(jié)果:
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
不管雙下劃線開(kāi)頭的部分屬性,我們只看對(duì)外開(kāi)放的部分,先來(lái)看一看func_name屬性吧。
print foo #<function foo at 0x0235A2B0>
foo.func_name = 'abc'
print foo <function abc at 0x0235A2B0>
func_name看上只是用來(lái)記錄信息的一個(gè)名稱而已,修改它并不會(huì)影響函數(shù)對(duì)象的調(diào)用。
我們?cè)賮?lái)看下func_defaults屬性,看名稱它是和默認(rèn)值相關(guān)。
foo(1) #11
print foo.func_defaults #(10,)
foo.func_defaults = (100, )
foo(1) #101
依然是把輸出的結(jié)果放在代碼行的后面以注釋的形式給出,我們看到函數(shù)對(duì)象的func_defaults屬性是一個(gè)元組,依次列出了所有默認(rèn)參數(shù)。我們可以通過(guò)改變這個(gè)屬性來(lái)改變已經(jīng)被定義了的函數(shù)的默認(rèn)參數(shù)。
為了可以看到func_closture的內(nèi)容,我們構(gòu)建一個(gè)閉包:
def bar(n):
def f(x):
return x + n
return f
f = bar(1)
g = bar("abc")
print f(2) # 3
print g("def") # defabc
print foo.func_closure # None
print f.func_closure # (<cell at 0x029655F0: int object at 0x029378E0>,)
print g.func_closure # (<cell at 0x02A31130: str object at 0x02972AE8>,)
f和g是兩個(gè)閉包對(duì)象,可以看到foo這個(gè)函數(shù)對(duì)象身上的func_closure屬性為None,f和g身上分別是兩個(gè)cell對(duì)象,這部分內(nèi)容和閉包中講的部分就契合在了一起。
func_code屬性我們也來(lái)看一下
def bar(a, b):
print a * b
print bar.func_code
print dir(bar.func_code)
foo(6, 2)
foo.func_code = bar.func_code
foo(6, 2)
輸出的結(jié)果由于比較長(zhǎng),寫在了下面:
<code object bar at 02516C80, file "C:\Users\David-PC\Desktop\Advanced Course on Python 2016\a014.py", line 27>
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
8
12
我們可以看到,func_code是一個(gè)code object,它有的屬性有很多,基本以co_開(kāi)頭,如果有興趣,可以自己一點(diǎn)點(diǎn)去把他們print出來(lái)看,也可以發(fā)現(xiàn)很多有趣的東西,這里暫時(shí)不進(jìn)行展開(kāi)。例子的另外一部分展示了func_code是可以被替換的。
擴(kuò)展:關(guān)于code對(duì)象,可以閱讀Exploring Python Code Objects,了解怎樣通過(guò)compile方法來(lái)生成code object,以及code類的一些屬性。
屬性名稱 | 描述 |
---|---|
func_name | 函數(shù)名稱 |
func_defaults | 函數(shù)的默認(rèn)值列表 |
func_code | 函數(shù)體的code對(duì)象 |
func_globals | 函數(shù)的全局命名空間 |
func_closure | 函數(shù)的cell對(duì)象 |
關(guān)于function中比較重要的幾個(gè)屬性,以表格的形式總結(jié)出來(lái)。
屬性名稱 | 描述 |
---|---|
func_name | 函數(shù)名稱 |
func_defaults | 函數(shù)的默認(rèn)值列表 |
func_code | 函數(shù)體的code對(duì)象 |
func_globals | 函數(shù)的全局命名空間 |
func_closure | 函數(shù)的cell對(duì)象 |
看了function的這些屬性,對(duì)于之前討論過(guò)的hotfix是不是有了更深的了解呢?只需要替換一個(gè)函數(shù)對(duì)象的func_code,func_defaults,func_closure等屬性,這個(gè)函數(shù)的行為就可以被改變了,結(jié)合上bound method和unbound method的動(dòng)態(tài)生成的特性,對(duì)Python語(yǔ)言的動(dòng)態(tài)性的原理的理解是否有更深入了一步呢?
8. Classes
關(guān)于類,其實(shí)有很多可以討論的內(nèi)容,從生命周期的角度來(lái)看,一個(gè)C++類的對(duì)象包含如下四個(gè)生命周期:
- 內(nèi)存分配,malloc
- 調(diào)用構(gòu)造函數(shù)初始化對(duì)象,A::A()
- 調(diào)用析構(gòu)函數(shù)清理對(duì)象,A::~A()
- 釋放內(nèi)存,free
不同的編譯器在內(nèi)存分配或者釋放的時(shí)候具體使用的函數(shù)可能不同,但這四個(gè)步驟是都有的,而且在C++中,1和4兩個(gè)步驟是隱式的,即開(kāi)發(fā)者通常不需要去關(guān)心(當(dāng)然也有可以去操作的方法),它們分別隱含在了構(gòu)造函數(shù)和析構(gòu)函數(shù)當(dāng)中。
對(duì)于Python的對(duì)象來(lái)說(shuō),其生命周期包含如下三個(gè)部分:
- 內(nèi)存分配,
__new__
方法; - 調(diào)用初始化(initializer),
__init__
方法; - 調(diào)用終結(jié)器(finalizer),
__del__
方法;
對(duì)于自定義的類,上述的過(guò)程可以通過(guò)重載對(duì)應(yīng)的方法來(lái)實(shí)現(xiàn)對(duì)于其過(guò)程的控制。可以看到,這里并沒(méi)有內(nèi)存釋放的過(guò)程,也就是說(shuō)開(kāi)發(fā)者無(wú)法主動(dòng)控制對(duì)象所占用過(guò)的內(nèi)存的釋放,這一部分是方便開(kāi)發(fā)者不需要進(jìn)行內(nèi)存的管理,另外也利于Python語(yǔ)言本身進(jìn)行對(duì)象緩存池的設(shè)計(jì)與實(shí)現(xiàn)。這部分內(nèi)容在bound method的部分已經(jīng)看到了緩存池在Python的應(yīng)用,更多的討論放在內(nèi)存管理的部分來(lái)進(jìn)行。
思考:Python中如何實(shí)現(xiàn)單例模式?是否可以通過(guò)重載
__new__
方法,在每次分配內(nèi)存的時(shí)候返回同一個(gè)對(duì)象來(lái)實(shí)現(xiàn)呢?
答案是否定的,因?yàn)镻ython對(duì)于生命周期的控制決定了在__new__
方法被調(diào)用,內(nèi)存分配完畢之后會(huì)主動(dòng)調(diào)用__init__
方法,這樣雖然分配的是同一個(gè)對(duì)象,但是多次__init__
方法的調(diào)用可能會(huì)導(dǎo)致對(duì)象的屬性被修改,可能會(huì)引發(fā)意料之外的bug,比如已經(jīng)被修改過(guò)屬性又被__init__
方法改變等。
說(shuō)了這些之后,對(duì)于Class我們返回Callable的主題,Python中的Class也是一個(gè)Callable的對(duì)象,調(diào)用一個(gè)類的結(jié)果很簡(jiǎn)單,就是獲得一個(gè)類的實(shí)例化對(duì)象,我們來(lái)看一個(gè)簡(jiǎn)單的例子。
class Foo(object):
pass
print Foo # <class '__main__.Foo'>
print Foo.__call__ # <method-wrapper '__call__' of type object at 0x029D33E0>
print Foo() # <__main__.Foo object at 0x02AAEED0>
def func():
pass
print func.__call__ # <method-wrapper '__call__' of function object at 0x029C77F0>
類Foo是一個(gè)class對(duì)象,它是可以訪問(wèn)到__call__
屬性的,它是一個(gè)id為0x029D33E0的type對(duì)象的'call'方法的method-wrapper,如果打印print id(Foo)
的話,你可以發(fā)現(xiàn)它的十六進(jìn)制結(jié)果就是0x029D33E0。這容易理解,而對(duì)于method-wrapper,我把它理解為一個(gè)方法的封裝。查了一些資料,但是沒(méi)有找到官方的精準(zhǔn)答案,這里我猜測(cè)對(duì)象的創(chuàng)建、函數(shù)的執(zhí)行等過(guò)程,在Python中可能是一種C的實(shí)現(xiàn),因此在Python層給出的是一個(gè)wrapper,可以讓他們的行為像一個(gè)Python的函數(shù)一樣。當(dāng)然這是我的猜測(cè),如果有知道準(zhǔn)確答案的朋友歡迎指導(dǎo)。
思考:Class作為一個(gè)Callable的類型,對(duì)于我們開(kāi)發(fā)中有什么好處嗎?
想象一下工廠方法在C++中的實(shí)現(xiàn),通常需要通過(guò)一個(gè)函數(shù)來(lái)封裝一個(gè)對(duì)象的創(chuàng)建過(guò)程,而在Python中,我們可以把對(duì)象類型存儲(chǔ)在一個(gè)字典或者列表中,通過(guò)映射關(guān)系,可以直接生成對(duì)象。某種程度上說(shuō),這也是一種“反射”機(jī)制。具體的代碼由于比較簡(jiǎn)單,此處就不列舉了。
總結(jié):關(guān)于Callable的部分已經(jīng)聊得差不多了,從我們分析的過(guò)程看,我們通常使用dir和print在加上一些分析能力就可以看出Python語(yǔ)言設(shè)計(jì)上的一些原理和思路,一切皆對(duì)象的理念被應(yīng)用的淋漓盡致,而為了實(shí)現(xiàn)其動(dòng)態(tài)特性,Python語(yǔ)言做了很多特殊的設(shè)計(jì)和方法。
2016年7月10日于杭州家中