Python回顧與整理12:執行環境

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代碼

    有需要時可以參考書本上的例子。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章