Python進(jìn)階(五)

博客鏈接: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è)生命周期:

  1. 內(nèi)存分配,malloc
  2. 調(diào)用構(gòu)造函數(shù)初始化對(duì)象,A::A()
  3. 調(diào)用析構(gòu)函數(shù)清理對(duì)象,A::~A()
  4. 釋放內(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è)部分:

  1. 內(nèi)存分配,__new__方法;
  2. 調(diào)用初始化(initializer),__init__方法;
  3. 調(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日于杭州家中

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,776評(píng)論 0 9
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評(píng)論 19 139
  • 兩本不錯(cuò)的書: 《Python參考手冊(cè)》:對(duì)Python各個(gè)標(biāo)準(zhǔn)模塊,特性介紹的比較詳細(xì)。 《Python核心編程...
    靜熙老師哈哈哈閱讀 3,388評(píng)論 0 80
  • 教程總綱:http://www.runoob.com/python/python-tutorial.html 進(jìn)階...
    健康哥哥閱讀 2,077評(píng)論 1 3
  • 凌晨3點(diǎn)半,未眠。不知為何,絲毫沒(méi)有睡意。躺在床上,回想起了自己從初中以來(lái)到現(xiàn)在所經(jīng)歷的一些事,一些對(duì)我來(lái)說(shuō)永遠(yuǎn)在...
    半夜微涼閱讀 247評(píng)論 0 4