2015/10/9 Python基礎(21):可調用和可執行對象


在Python中有多種運行外部程序的方法,比如,運行操作系統命令或另外的Python腳本,或執行一個磁盤上的文件,或通過網絡來運行文件。這完全取決於想要干什么。特定的環境包括:
  在當前腳本繼續運行
  創建和管理子進程
  執行外部命令或程序
  執行需要輸入的命令
  通過網絡來調用命令
  執行命令來創建需要處理的輸出
  執行其他的Python腳本
  執行一系列動態生成的Python語句
  導入Python模塊
  Python中,內建和外部模塊都可以提供上述各種功能。程序員得根據實現的需要,從這些模塊中選擇合適的處理方法。

可調用對象

許多Python對象都是我們所說的可調用的,即是任何通過函數操作符()來調用的對象。Python有4中可調用對象:函數,方法,類,以及一些類的實例。

1.函數
Python有3中不同類型的函數對象,第一種是內建函數。
內建函數(BIFs)
BIF是用C/CPP寫的,編譯過后放入Python解釋器,然后把它們作為第一(內建)名字空間的一部分加載進系統。這些函數在_builtin_模塊里,並作為__builtins__模塊導入到解釋器中。
可以用dir()列出函數的所有屬性:

>>> dir(type)
['__abstractmethods__', '__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__delattr__', '__dict__', '__dictoffset__', '__doc__', '__eq__', '__flags__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__instancecheck__', '__itemsize__', '__le__', '__lt__', '__module__', '__mro__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasscheck__', '__subclasses__', '__subclasshook__', '__weakrefoffset__', 'mro']


從內部機制來看,因為BIFs和內建方法(BIMs)屬於相同類型,所以對BIF或者BIM調用type()的結果是:

>>> type(dir)
<type 'builtin_function_or_method'>

 

用戶定義的函數(UDF)
UDF通常是用Python寫的,定義在模塊的最高級,因此會作為全局名字空間的一部分裝置到系統中。函數也可以在其他函數體內定義,我們可以對多重嵌套作用域中的屬性進行訪問。
從內部機制來看,用戶自定義的函數是“函數”類型的:

>>> def foo():pass

>>> type(foo)
<type 'function'>

 

lambda表達式
lambda表達式和用戶自定義函數相比,略有不同。雖然它們也是返回一個函數對象,但是不是用def語句創建的,而是用lambda關鍵字:
因為lambda表達式沒有給命名綁定的代碼提供基礎結構,所以要通過函數式編程接口來調用,或把它們的引用賦值給一個變量,然后就可以直接調用或者再通過函數來調用。變量僅是個別名,並不是函數對象的名字。
通過lambda來創建函數的對象除了沒有命名之外,和UDF有相同的屬性;__name__或者func_name屬性給定位字符串"<lambda>"

>>> lambdaFunc = lambda x: x * 2
>>> lambdaFunc(12)
24
>>> type(lambdaFunc)
<type 'function'>
>>> lambdaFunc.__name__
'<lambda>'

 


如果是UDF的名字,則是這樣:

>>> def foo():pass

>>> foo.__name__
'foo'

 

以上是三種函數對象

2.方法
用戶自定義方法是被定義為類的一部分的函數。許多Python的數據類型,比如列表和字典,也有方法,被稱為內建方法。為了說明所有權的類型,方法通過對象的名字和句點屬性標識符命名。

內建方法(BIMs)
剛剛我們說了BIF和BIM的類似之處。只有內建類型有內建方法。對於內建方法,type()工廠函數給出了和BIF一樣的輸出。

>>> type([].append)
<type 'builtin_function_or_method'>

 

此外BIM和BIF兩者有相同屬性。不同之處在於BIM的__self__屬性指向一個Python對象,BIF指向None。

用戶定義的方法(UDM)
UDM包含在類定義之中,只是擁有標准函數的包裝,僅有定義他們的類可以使用。如果沒有在子類定義中被覆蓋,也可以通過子類實例來調用它們。

3.類
調用類的結果就是創建了實例,也就是實例化。

4.類的實例
Python給類提供了名為__call__的特別方法,該方法允許程序員創建可調用的對象(實例)。默認情況下,__call__()方法是沒有實現的,這意味着大多數情況下實例是不可調用的。然而,如果在類中覆蓋了這個方法,那么這個類的實例就成為可調用的了。調用這樣的實例對象等同於調用__call__()方法。如:foo()和foo.__call__(foo)的效果相同,這里的foo也作為參數出現,因為是對自己的引用,實例將自動成為每次方法調用的第一個參數,如果__call__()有參數,那么foo(arg)就和foo.__call__(foo, arg)一樣。

 

代碼對象

可調用對象是Python執行環境里最重要的部分,然而這並不是全部。Python語句,賦值,表達式,甚至還有模塊構成了更宏大的場面。這些可執行對象無法像可調用物那樣被調用。這些代碼塊被稱為代碼對象。

每個可調用物的核心都是代碼對象,由語句,賦值,表達式,以及其他可調用物組成。查看一個模塊意味着觀察一個較大的、包含了模塊中所有代碼的對象。然后代碼分成語句,賦值,表達式,以及可調用物。可調用物又可以遞歸分解到下一層,那里有它自己的代碼對象。
一般來說,代碼對象可以作為函數或者方法調用的一部分來執行,也可用exec語句或內建函數eval()來執行。從整體上看,一個Python模塊的代碼對象是構成該模塊的全部代碼。
如果要執行Python代碼,那么該代碼必須先要轉換成字節編譯的代碼(又稱字節碼)。這才是真正的代碼對象。然而,它們不包含任何關於它們執行環境的信息,這便是可調用物存在的原因,它被用來包裝一個代碼對象並提供額外的信息。
UDF有 udf.func_code 屬性就是代碼對象。UDM的udm.im_func也是一個函數對象,他同樣有它自己的udm.im_func.func_code代碼對象。這樣的話,你會發現,函數對象僅是代碼對象的包裝,方法則是給函數對象的包裝。當研究到最底層,便是一個代碼對象。

 


可執行的對象聲明和內建函數

Python提供了大量的BIF來支持可調用/可執行對象。

1.callable()
callable()是一個布爾函數,確定一個對象是否可以用函數操作符()來調用。如果可調用便返回True,否則便是False。

2.compile()
compile()函數允許程序員在運行時刻迅速生成代碼對象,然后就可以用exec語句或者內建函數eval()來執行這些對象或者他們進行求值。
compile的三個參數都是必需的,第一參數代表了要編譯的Python代碼。第二個參數是字符串,雖然是必需的,但通常被置為空串,該參數代表了存放代碼對象的文件的名字(字符串類型)。compile的通常用法是動態生成字符串形式的Python代碼,然后生成一個代碼對象——代碼顯然沒有存放在任何文件。最后的參數是個字符串,用來表明代碼的類型。有三個可能值:
'eval' 可求值的表達式[和eval()一起使用]
'single' 單一可執行語句[和exec一起使用]
'exec' 可執行與劇組[和exec一起使用]

可求值表達式

>>> eval_code = compile('10-2','','eval')
>>> eval(eval_code)
8

 

單一可執行語句

>>> single_code = compile('print "Hello world"','','single')
>>> single_code
<code object <module> at 024DC698, 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()返回的對象。如果只傳入了一個全局字典,那么該字典也作為局部參數傳入。
這是eval()的一個例子。

>>> eval('123')
123
>>> int('123')
123
>>> eval('123+234')
357
>>> int('123+234')

Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
int('123+234')
ValueError: invalid literal for int() with base 10: '123+234'

 

開始,我們傳入'123'給eval()和int()的時候,返回了相同的結果,但是方式是不盡相同的,eval()接受引號內的字符串把它作為Python表達式求值,int()接受代表整數的字符串並把它轉換為整數。而當我們輸入'123+234'時,情況就不一樣了。int()調用就失敗了。可以認為eval()函數對表達式兩端的引號視而不見,將它執行在解釋器上,返回結果。

4.exec
和eval()相似,exec語句執行代碼對象或字符串形式的Python代碼。類似地,用compile()預編譯重復代碼有助於改善性能,因為在調用時不必經過字節編譯處理。exec只接受一個參數,語法是:

exec obj

obj可以是原始的字符串,比如單一語句或語句組,也可以預編譯層一個代碼對象。

>>> exec """
x = 0
print 'x is currently:',x
while x < 5:
  x += 1
  print 'incrementing x to:',x
"""
x is currently: 0
incrementing x to: 1
incrementing x to: 2
incrementing x to: 3
incrementing x to: 4
incrementing x to: 5

 


exec還可以接受有效的Python文件對象。如果我們用上面的多行代碼創建一個xcount.py的文件,那么也可以這樣執行相同代碼:

>>> f = open('xcount.py') # open the file
>>> exec f # execute the file
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,如果在完成后繼續調用它

>>> exec f
>>>


調用會失敗。並不是真正的失敗,只是不再做任何事。事實上,exec已從文件中讀取了全部數據且停留在文件末尾(EOF)。當用相同的文件對象對exec進行調用的時候,沒有可執行的代碼了,所以exec什么都不做。
我們可以用tell()方法來告訴我們處於文件的何處,然后用os.path.getsize()來告訴我們腳本由多大。然后就會發現,這兩個數字完全一樣:

>>> f.tell()
116
>>> f.close()
>>> from os.path import getsize
>>> getsize('xcount.py')
116

 

如果想在不關閉和重新打開文件的情況下再次運行它,可以用seek()到文件最開頭並再次調用exec。假定我們還沒有調用f.close(),那么:

>>> 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.close()

 

5.input()
之前用到的內建函數input()是eval()和raw_input()的組合,等價於eval(raw_input()),input()和raw_input()一樣有一個可選的參數給用戶字符串提示。
input不同於raw_input(),input()返回的數據是對輸入表達式求值的結果,是一個Python對象。

6.使用Python在運行時生成和執行Python代碼
書上提供了兩個例子,這兩個例子在運行時吧Python代碼作為字符串並執行。
第一個例子是loopmake.py腳本。一個簡單迅速和執行循環的計算機輔助軟件工程。提示用戶給出各種參數,生成代碼字符串,並執行它。

dashes = '\n' + '-' * 50
exec_dict = {
'f':'''               #for loop
for %s in %s:
    print %s
''',

's':'''               # sequence while loop
%s = 0
%s = %s
while %s < len(%s):
    print %s[%s]
    %s = %s + 1
''',

'n':'''                # counting while loop
%s = %d
while %s < %d:
    print %s
    %s = %s + %d
'''
}

def main():

    ltype = raw_input('Loop type? (For/While)')
    dtype = raw_input('Data type? (Number/Sequence)')

    if dtype == 'n':
        start = input('Starting value? ')
        stop = input('Ending value (non-inclusive)? ')
        step = input('Stepping value? ')
        seq = str(range(start, stop, step))

    else:
        seq = raw_input('Enter sequence:')

    var = raw_input('Iterative variable name?')

    if ltype == 'f':
        exec_str = exec_dict['f'] % (var, seq, var)

    elif ltype == 'w':
        if dtype == 's':
            svar = raw_input('Enter sequence name? ')
            exec_str = exec_dict['s'] % \
                       (var, svar, seq, var, svar, svar, var, var, var)
        elif dtype == 'n':
            exec_str = exec_dict['n'] % \
                       (var, start, var, stop, var, var, var, step)

    print dashes
    print 'The custom-generated code for you is:' + dashes
    print exec_str + dashes
    print 'The execution of the code:' + dashes
    exec exec_str
    print dashes

if __name__ == '__main__':
    main()

    

 

有興趣的人可以執行一下這段代碼,十分有趣,可以幫助你生成代碼並執行。反正我寫這段代碼的時候感覺到了exec和input的強大。

第二個例子是有條件地執行代碼
這是代碼:

def foo():
    return True

def bar():
    'bar() does not do much'
    return True

foo.__doc__ = 'foo() does not do much'
foo.tester = '''
if foo():
    print 'PASSED'
else:
    print 'FAILED'
'''

for eachAttr in dir():
    obj = eval(eachAttr)
    if isinstance(obj, type(foo)):
        if hasattr(obj, '__doc__'):
            print '\nFunction "%s" has a doc string:\n\t%s'\
                  % (eachAttr, obj.__doc__)
        if hasattr(obj, 'tester'):
            print 'Function "%s" has a tester... executing'\
                  % eachAttr
            exec obj.tester
        else:
            print 'Function "%s" has no tester... skipping'\
                  % eachAttr
    else:
        print '"%s" is not a function' % eachAttr

 

下面是執行后的結果:

>>> 
"__builtins__" is not a function
"__doc__" is not a function
"__file__" is not a function
"__name__" is not a function
"__package__" is not a function

Function "bar" has a doc string:
bar() does not do much
Function "bar" has no tester... skipping

Function "foo" has a doc string:
foo() does not do much
Function "foo" has a tester... executing
PASSED

 

代碼並不難理解,但其所做的事的確很有趣不是么?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM