0.說明
作爲《Python核心編程》核心部分的最後一章,這篇的內容也相當重要。對於高級部分的整理,將採用《Python核心編程》第三版,但是,方式會以之前的完全不一樣了。
1.可調用對象
可調用對象即可通過函數操作符“()”來調用的對象,也可以通過函數式編程接口來進行調用,如apply()、filter()、map()和reduce()。Python有4種可調用對象:函數、方法、類和一些類的實例。
(1)函數
Python中有三種不同類型的函數:內建函數(BIF)、用戶定義的函數(UDF)和lambda表達式。
內建函數(BIF)
用C/C++寫的,編譯過後放入Python解釋器中,然後把它們作爲第一(內建)名稱空間的一部分加載進系統,這些函數在__builtin__模塊裏,並作爲__builtins__模塊導入到解釋器中。
可以使用dir()來列出函數的所有屬性,另外從內部機制來看,BIF和內建方法屬於相同的類型,所以全長type()調用的結果是:
>>> type(dir) <type 'builtin_function_or_method'> >>> >>> type('xpleaf'.upper) <type 'builtin_function_or_method'>
但是應用於工廠函數,得到的結果是不一樣的,雖然這些工廠函數本身是內建函數:
>>> type(int) <type 'type'>
那是因爲,本質上這些工廠函數是類。
用戶定義的函數(UDF)
用戶定義的函數通常是用Python寫的,調用type()的結果如下:
>>> def foo():pass ... >>> type(foo) <type 'function'>
UDF本身也有很多屬性:
>>> dir(foo) ['__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']
lambda表達式(名爲“<lambdda>”的函數)
lambda返回一個函數對象,只是lambda表達式沒有給命令綁定的代碼提供基礎結構,所以要通過函數式編程接口來調用,或把它們的引用賦值給一個變量,通過該變量來進行調用,只是需要注意的是,這個變量僅僅是個別名,並不是函數對象的名字。
通過lambda來創建的函數對象除了沒有命名外,和用戶定義的函數是有相同的屬性的,當然,對於__name__屬性,值爲"<lambda>":
>>> lambdaFunc = lambda x: x*2 >>> lambdaFunc(100) 200 >>> type(lambdaFunc) <type 'function'> >>> >>> lambdaFunc.__name__ '<lambda>' >>> >>> foo.__name__ 'foo'
(2)方法
用戶自定義方法是被定義爲類的一部分函數,而許多Python數據類型本身也有方法,這些方法被稱爲內建方法。
內建方法(BIM)
只有內建類型有內建方法,如下:
>>> type([].append) <type 'builtin_function_or_method'>
BIM和BIF兩者享有相同的屬性,只是不同的是,BIM的__self__屬性指向一個Python對象,而BIF指向None:
>>> dir([].append) ['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] >>> [].append.__self__ [] >>> dir.__self__ >>> print [].append.__self__ [] >>> print dir.__self__ None
用戶定義的方法(UDM)
所有的UDM都是相同的類型,即“實例方法”,無論該方法是否有綁定的實例:
>>> class C(object): ... def foo(self): ... pass ... >>> c = C() >>> >>> type(C.foo) <type 'instancemethod'> >>> type(c.foo) <type 'instancemethod'>
只是訪問對象本身會揭示該方法是否綁定:
>>> c.foo <bound method C.foo of <__main__.C object at 0x7f37a3c45810>> >>> C.foo <unbound method C.foo>
(3)類
調用類,其實就是創建一個實例對象。類有默認構造器,但是這個函數什麼都不做,可以通過實現__init__()方法來自定義實例化過程。
(4)類的實例
Python給類提供了名爲__call__的特別方法,該方法允許程序創建可調用的對象(實例)。默認情況下沒有實現該方法,因此實例是不可調用的。
在定義類時覆蓋__call__方法即可達到實例可調用的效果,這樣,調用實例對象就等同於調用__call__()方法,參數的設置跟類的構造器原理是一樣的:
>>> class C(object): ... def __call__(self, *args): ... print "I'm callable! Called with args:\n", args ... >>> c = C() >>> c <__main__.C object at 0x7f37a3c2d090> >>> callable(c) # 實例是可調用的 True >>> c() # 調用實例 I'm callable! Called with args: () >>> c(3) # 傳入參數 I'm callable! Called with args: (3,) >>> c(3, 'no more , no less') I'm callable! Called with args: (3, 'no more , no less') >>> >>> c.__call__(3) # 與c(3)的調用原理是一樣的 I'm callable! Called with args: (3,) >>> c.__call__(3, 'no more, no less') I'm callable! Called with args: (3, 'no more, no less')
2.代碼對象
每個可調用物的核心都是代碼對象,由語句、賦值、表達式和其他可調用物組成。查看一個模塊意味着觀察一個較大的、包含了模塊中所有代碼的對象。然後代碼可以分成語句、賦值、表達式,以及可調用物。可調用物又可以遞歸分解到下一層,那兒有自己的代碼對象。
一般來說,代碼對象可以作爲函數或者方法調用的一部分來執行(比如語句或賦值等),也可用exec語句或內建函數eval()來執行。從整體上看,一個Python模塊的代碼對象是構成該模塊的全部代碼。
如果要執行Python代碼,那麼該代碼必須先要轉換成字節編譯的代碼(又稱字節碼)。這纔是真正的代碼對象。然而,它們不包含任何關於它們執行環境的信息,這便是可調用物存在的原因,它被用來包裝一個代碼對象並提供額外的信息(如屬性等相關信息)。
函數對象僅是代碼對象的包裝,方法則是給函數對象的包裝,只是不同於單純的代碼對象,它們還提供了一些額外的信息。當研究到最底層,便會發現這是一個代碼對象。
在函數對象中,有一個func_code屬性,其實就是指代碼對象:
>>> def foo(): pass ... >>> foo.func_code <code object foo at 0x7f37a5daceb0, file "<stdin>", line 1>
3.可執行的對象聲明和內建函數
常見的相關函數如下:
可執行的對象聲明和內建函數 | |
內建函數和語句 | 描述 |
callable(obj) | 如果obj可調用,返回True,否則返回False |
compile(string, file, type) | 從type類型中創建代碼對象;file是代碼存放的地方(通常設爲"") |
eval(obj, globals=globals(), locals=locals()) | 對obj進行求值,obj是已編譯爲代碼對象的表達式,或是一個字符串表達式;可以給出全局或者/和局部的名稱空間 |
exec obj | 執行obj、單一的Python語句或者語句的集合,也就是說格式是代碼對象或者字符串;obj也可以是一個文件對象(已經打開的有效的Python腳本中) |
input(prompt='') | 等同於eval(raw_input(prompt='')) |
(1)callable()
callable()是一個布爾函數,確定一個對象是否可以通過函數操作符(())來調用:
>>> callable(dir) True >>> callable(1) False >>> def foo(): pass ... >>> callable(foo) True
(2)compile()
compile()函數允許程序員在運行時刻迅速生成代碼對象,然後就可以用exec語句或者內建函數eval()來執行這些代碼對象或者對它們進行求值。一個很重要的觀點是:exec和eval()都可以執行字符串格式的Python代碼。當執行字符串形式的代碼時,每次都必須對這些代碼進行字節編譯處理。compile()函數提供了一次性字節代碼預編譯,以後每次調用的時候,就不用編譯了。
compile的三個參數都是必需的,第一參數藉助了要編譯的Python代碼。第二個字符串,雖然是必需的,但通常被設置爲空串。該參數代表了存放代碼對象的文件的名字(字符串類型)。compile的通常用法是動態生成字符串形式的Python代碼,然後生成一個代碼對象——代碼顯然沒有存放在任何文件,所以文件名就通常設置爲空了。
最後的參數是個字符串,它用來表明代碼對象的類型,有三個可能值:
'eval' | 可求值的表達式,和eval()一起使用 |
'single' | 單一可執行語句,和exec一起使用 |
'exec' | 可執行語句組,和exec一起使用 |
可求值表達式
>>> eval_code = compile('100+200', '', 'eval') >>> eval_code <code object <module> at 0x7f37a5db3cb0, file "", line 1> >>> eval(eval_code) 300
單一可執行語句
>>> single_code = compile('print "Hello world!"', '', 'single') >>> single_code <code object <module> at 0x7f37a3c2a0b0, file "", line 1> >>> exec single_code Hello world!
可執行語句組
>>> exec_code = compile(""" ... req = input('Count how many numbers?') ... for eachNum in range(req): ... print eachNum ... """, '', 'exec') >>> >>> exec exec_code Count how many numbers?6 0 1 2 3 4 5
(3)eval()
eval()對表達式求值,第一個參數可以爲字符串或compile()創建的預編譯代碼對象。第二個和第三個參數爲可選的,分別默認爲globals()和locals()返回的對象:
>>> a = 3 >>> eval('a') 3 >>> def test_eval(): ... a = 6 ... print eval('a') ... >>> test_eval() 6 >> >>> scope={} >>> scope['a'] = 3 >>> scope['b'] = 4 >>> result = eval('a+b',scope) >>> result 7
(4)exec
exec語句只接受一個參數,即exec obj,obj除了可以是原始的字符串(單一語句或語句組)或是compile()編譯的代碼對象外,也可以是文件對象:
>>> f = open('xcount.py') >>> for eachLine in f: ... print eachLine, ... x = 0 print 'x is currently:', x while x < 5: x += 1 print 'incrementing x to:', x >>> >>> f.tell() 86 >>> f.seek(0) >>> >>> exec f x is currently: 0 incrementing x to: 1 incrementing x to: 2 incrementing x to: 3 incrementing x to: 4 incrementing x to: 5 >>> >>> f.tell() # 執行完之後,就指針就停留在文件末尾(end-of-line,EOF) 86
(5)input()
內建函數input()是eval()和raw_input()的組合,等價於eval(raw_input()),這說明input()和raw_input()本身還是有區別的。
從功能上看,input不同於raw_input(),因爲raw_input()總是以字符串的形式返回用戶的輸入;input()履行相同的任務,而且它還把輸入作爲Python表達式進行求值。這意味着input()返回的數據是對輸入表達式求值的結果:一個Python對象。
舉例如下:
>>> aString = raw_input('Enter a list: ') Enter a list: ['xpleaf', 'clyyh'] >>> aString "['xpleaf', 'clyyh']" >>> type(aString) <type 'str'> >>> >>> aList = input('Enter a list: ') Enter a list: ['xpleaf', 'clyyh'] >>> aList ['xpleaf', 'clyyh'] >>> type(aList) <type 'list'>
(6)使用Python在運行時生成和執行Python代碼
有需要時可以參考書本上的例子。