標籤:

Python進階課程筆記(五)

7. Function

作為Callable部分第一個別提到,但是最後來分析它。原因其實很簡單,我們已經發現前面的很多內容,包括bound method也好,static method也好,甚至operators,本質上都是function。

那麼,如何去探究一個Python的Function的屬性呢,閱讀過前面內容的讀者應該很明白了,通過一些簡單的代碼加上print就可以看到不少東西了。

我們先用dirf來看下一個function對象身上有什麼。

def foo(a, b = 10):n print a + bnnprint dir(foo)n

輸出結果:

[__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]n

不管雙下劃線開頭的部分屬性,我們只看對外開放的部分,先來看一看func_name屬性吧。

print foo #<function foo at 0x0235A2B0>nfoo.func_name = abcnprint foo <function abc at 0x0235A2B0>n

func_name看上只是用來記錄信息的一個名稱而已,修改它並不會影響函數對象的調用。

我們再來看下func_defaults屬性,看名稱它是和默認值相關。

foo(1) #11nprint foo.func_defaults #(10,)nfoo.func_defaults = (100, )nfoo(1) #101n

依然是把輸出的結果放在代碼行的後面以注釋的形式給出,我們看到函數對象的func_defaults屬性是一個元組,依次列出了所有默認參數。我們可以通過改變這個屬性來改變已經被定義了的函數的默認參數。

為了可以看到func_closture的內容,我們構建一個閉包:

def bar(n):n def f(x):n return x + nnn return fnnf = bar(1)ng = bar("abc")nprint f(2) # 3nprint g("def") # defabcnprint foo.func_closure # Nonenprint f.func_closure # (<cell at 0x029655F0: int object at 0x029378E0>,)nprint g.func_closure # (<cell at 0x02A31130: str object at 0x02972AE8>,)n

f和g是兩個閉包對象,可以看到foo這個函數對象身上的func_closure屬性為None,f和g身上分別是兩個cell對象,這部分內容和閉包中講的部分就契合在了一起。

func_code屬性我們也來看一下

def bar(a, b):n print a * bnnprint bar.func_codenprint dir(bar.func_code)nnfoo(6, 2)nfoo.func_code = bar.func_codenfoo(6, 2)n

輸出的結果由於比較長,寫在了下面:

<code object bar at 02516C80, file "C:UsersDavid-PCDesktopAdvanced Course on Python 2016a014.py", line 27>n[__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]n8n12n

我們可以看到,func_code是一個code object,它有的屬性有很多,基本以co_開頭,如果有興趣,可以自己一點點去把他們print出來看,也可以發現很多有趣的東西,這裡暫時不進行展開。例子的另外一部分展示了func_code是可以被替換的。

擴展:關於code對象,可以閱讀Exploring Python Code Objects,了解怎樣通過compile方法來生成code object,以及code類的一些屬性。

關於function中比較重要的幾個屬性,以表格的形式總結出來。

| 屬性名稱 | 描述 |

| -------- | -----: |

| func_name | 函數名稱 |

| func_defaults | 函數的默認值列表 |

| func_code | 函數體的code對象 |

| func_globals | 函數的全局命名空間 |

| func_closure | 函數的cell對象 |

看了function的這些屬性,對於之前討論過的hotfix是不是有了更深的了解呢?只需要替換一個函數對象的func_code,func_defaults,func_closure等屬性,這個函數的行為就可以被改變了,結合上bound method和unbound method的動態生成的特性,對Python語言的動態性的原理的理解是否有更深入了一步呢?

8. Classes

關於類,其實有很多可以討論的內容,從生命周期的角度來看,一個C++類的對象包含如下四個生命周期:

  1. 內存分配,malloc
  2. 調用構造函數初始化對象,A::A()
  3. 調用析構函數清理對象,A::~A()
  4. 釋放內存,free

不同的編譯器在內存分配或者釋放的時候具體使用的函數可能不同,但這四個步驟是都有的,而且在C++中,1和4兩個步驟是隱式的,即開發者通常不需要去關心(當然也有可以去操作的方法),它們分別隱含在了構造函數和析構函數當中。

對於Python的對象來說,其生命周期包含如下三個部分:

  1. 內存分配,__new__方法;
  2. 調用初始化(initializer),__init__方法;
  3. 調用終結器(finalizer),__del__方法;

對於自定義的類,上述的過程可以通過重載對應的方法來實現對於其過程的控制。可以看到,這裡並沒有內存釋放的過程,也就是說開發者無法主動控制對象所佔用過的內存的釋放,這一部分是方便開發者不需要進行內存的管理,另外也利於Python語言本身進行對象緩存池的設計與實現。這部分內容在bound method的部分已經看到了緩存池在Python的應用,更多的討論放在內存管理的部分來進行。

思考:Python中如何實現單例模式?是否可以通過重載__new__方法,在每次分配內存的時候返回同一個對象來實現呢?

答案是否定的,因為Python對於生命周期的控制決定了在__new__方法被調用,內存分配完畢之後會主動調用__init__方法,這樣雖然分配的是同一個對象,但是多次__init__方法的調用可能會導致對象的屬性被修改,可能會引發意料之外的bug,比如已經被修改過屬性又被__init__方法改變等。

說了這些之後,對於Class我們返回Callable的主題,Python中的Class也是一個Callable的對象,調用一個類的結果很簡單,就是獲得一個類的實例化對象,我們來看一個簡單的例子。

class Foo(object):n passnnprint Foo # <class __main__.Foo>nprint Foo.__call__ # <method-wrapper __call__ of type object at 0x029D33E0>nprint Foo() # <__main__.Foo object at 0x02AAEED0>nndef func():n passnnprint func.__call__ # <method-wrapper __call__ of function object at 0x029C77F0>n

類Foo是一個class對象,它是可以訪問到__call__屬性的,它是一個id為0x029D33E0的type對象的call方法的method-wrapper,如果列印print id(Foo)的話,你可以發現它的十六進位結果就是0x029D33E0。這容易理解,而對於method-wrapper,我把它理解為一個方法的封裝。查了一些資料,但是沒有找到官方的精準答案,這裡我猜測對象的創建、函數的執行等過程,在Python中可能是一種C的實現,因此在Python層給出的是一個wrapper,可以讓他們的行為像一個Python的函數一樣。當然這是我的猜測,如果有知道準確答案的朋友歡迎指導。

思考:Class作為一個Callable的類型,對於我們開發中有什麼好處嗎?

想像一下工廠方法在C++中的實現,通常需要通過一個函數來封裝一個對象的創建過程,而在Python中,我們可以把對象類型存儲在一個字典或者列表中,通過映射關係,可以直接生成對象。某種程度上說,這也是一種「反射」機制。具體的代碼由於比較簡單,此處就不列舉了。

總結:關於Callable的部分已經聊得差不多了,從我們分析的過程看,我們通常使用dir和print在加上一些分析能力就可以看出Python語言設計上的一些原理和思路,一切皆對象的理念被應用的淋漓盡致,而為了實現其動態特性,Python語言做了很多特殊的設計和方法。

2016年7月10日於杭州家中


推薦閱讀:

TAG:Python |