Python函數屬性和PyCodeObject

函數屬性

python中的函數是一種對象,它有屬於對象的屬性。除此以外,函數還能夠自定義本身的屬性。注意,屬性是和對象相關的,和做用域無關。python

自定義屬性

自定義函數本身的屬性方式很簡單。假設函數名稱爲myfunc,那麼爲這個函數添加一個屬性var1:閉包

myfunc.var1="abc"

那麼這個屬性var1就像是全局變量同樣被訪問、修改。但它並非全局變量。函數

能夠跨模塊自定義函數的屬性。例如,在b.py中有一個函數b_func(),而後在a.py中導入這個b.py模塊,能夠直接在a.py中設置並訪問來自b.py中的b_func()的屬性。spa

import b
b.b_func.var1="hello"
print(b.b_func.var1)  # 輸出hello

查看函數對象屬性

python函數是一種對象,是對象就會有對象的屬性。能夠經過以下方式查看函數對象的屬性:code

dir(func_name)

例如,有一個屬性__name__,它表示函數的名稱:對象

def f(x):
    y=10
    def g(z):
        return x+y+z
    return g

print(f.__name__)   # 輸出f

還有一個屬性__code__,這個屬性是本文的重點,它表示函數代碼對象:內存

print(f.__code__)

輸出:作用域

<code object f at 0x0335B180, file "a.py", line 2>

上面的輸出結果已經指明瞭__code__也是對象,既然是對象,它就有本身的屬性:編譯器

print( dir(f.__code__) )

如今,就能夠看到函數代碼對象相關的屬性,其中有一類屬性都以co_開頭,表示字節碼的意思,後文會詳細解釋這些屬性的意義。實際上,並不是只有函數具備這些屬性,全部的代碼塊(code block)都有這些屬性。源碼

[...省略其它非co_屬性...
'co_argcount', 'co_cellvars',
'co_code', 'co_consts',
'co_filename', 'co_firstlineno',
'co_flags', 'co_freevars',
'co_kwonlyargcount', 'co_lnotab',
'co_name', 'co_names', 'co_nlocals',
'co_stacksize', 'co_varnames']

如何查看這些__code__的屬性?使用f.__code__.co_XXX便可。因爲dir()返回的是屬性列表,因此下面使用循環將co_開頭的屬性都輸出出來:

for i in dir(f.__code__):
    if i.startswith("co"):
        print(i+":",eval("f.__code__."+i))

輸出結果:

co_argcount: 1
co_cellvars: ('x', 'y')
co_code: b'd\x01\x89\x01\x87\x00\x87\x01f\x02d\x02d\x03\x84\x08}\x01|\x01S\x00'
co_consts: (None, 10, <code object g at 0x02FB7338, file "g:/pycode/b.py", line 3>, 'f.<locals>.g')
co_filename: g:/pycode/b.py
co_firstlineno: 1
co_flags: 3
co_freevars: ()
co_kwonlyargcount: 0
co_lnotab: b'\x00\x01\x04\x01\x0e\x02'
co_name: f
co_names: ()
co_nlocals: 2
co_stacksize: 3
co_varnames: ('x', 'g')

此外,還可使用dis模塊的show_code()函數來輸出這些信息的整理:

import dis
def f(x):
    y=10
    def g(z):
        return x+y+z
    return g

print(dis.show_code(f))

輸出結果:

Name:              f
Filename:          g:/pycode/b.py
Argument count:    1
Kw-only arguments: 0
Number of locals:  2
Stack size:        3
Flags:             OPTIMIZED, NEWLOCALS
Constants:
   0: None
   1: 10
   2: <code object g at 0x00A89338, file "g:/pycode/b.py", line 4>
   3: 'f.<locals>.g'
Variable names:
   0: x
   1: g
Cell variables:
   0: x
   1: y
None

__code__屬性的解釋

這些屬性定義在python源碼包的Include/code.h文件中,若有須要,可自行去查看。

另外,這些屬性是代碼塊(code block)的,不限於函數。但此處以函數爲例進行說明。

因爲這些屬性中涉及到了閉包屬性(或者嵌套函數的屬性),因此如下面這個a.py文件中的嵌套函數爲例:

import dis
x=3
def f(a,b,*args,c):
    a=3
    y=10
    print(a,b,c,x,y)
    def g(z):
        return a+b+c+x+z
    return g

如下是查看函數f()和閉包函數g()的方式:

# f()的show_code結果
dis.show_code(f)

# f()的co_XXX屬性
for i in dir(f.__code__):
    if i.startswith("co"):
        print(i+":",eval("f.__code__."+i))

# 閉包函數,注意,傳遞了*args參數
f1=f(3,4,"arg1","arg2",c=5)

# f1()的show_code結果
dis.show_code(f1)

# f1()的co_XXX屬性
for i in dir(f1.__code__):
    if i.startswith("co"):
        print(i+":",eval("f1.__code__."+i))

下面將根據上面查看的結果解釋各屬性:

co_name
函數的名稱。

上例中該屬性的值爲外層函數f和閉包函數g,注意不是f1。

co_filename
函數定義在哪一個文件名中。

上例中爲a.py

co_firstlineno
函數聲明語句在文件中的第幾行。即def關鍵字所在的行號。

上例中f()的行號爲3,g()的行號爲7。

co_consts
該函數中使用的常量有哪些。python中並無專門的常量概念,全部字面意義的數據都是常量。

如下是show_code()獲得的f()中的常量:

Constants:
   0: None
   1: 3
   2: 10
   3: <code object g at 0x0326B7B0, file "a.py", line 7>
   4: 'f.<locals>.g'

而內層函數g()中沒有常量。

co_kwonlyargcount
keyword-only的參數個數。

f()的keyword-only的參數只有c,因此個數爲1
g()中沒有keyword-only類的參數,因此爲0

co_argcount
除去*args以外的變量總數。其實是除去***所收集的參數以及keyword-only類的參數以後剩餘的參數個數。換句話說,是***前面的位置參數個數。

f()中屬於此類參數的有a和b,因此co_argcount數值爲2
g()中只有一個位置參數,因此co_argcount數值爲1

co_nlocals
co_varnames
本地變量個數和它們的名稱,變量名稱收集在元組中。

f()的本地變量個數爲6,元組的內容爲:('a', 'b', 'c', 'args', 'y', 'g')
g()的本地變量個數爲1,元組的內容爲:('z',)

co_stacksize
本段函數須要在棧空間評估的記錄個數。換句話說,就是棧空間個數。

這個怎麼計算的,我也不知道。如下是本示例的結果:
f()的棧空間個數爲6
g()的棧空間個數爲2

co_names
函數中保存的名稱符號,通常除了本地變量外,其它須要查找的變量(如其它文件中的函數名,全局變量等)都須要保存起來。

f()的co_names:

Names:
   0: print
   1: x

g()的co_names:

Names:
   0: x

co_cellvars
co_freevars
這兩個屬性和嵌套函數(或者閉包有關),它們是互相對應的,因此內容徹底相同,它們以元組形式存在。

co_cellvars是外層函數的哪些本地變量被內層函數所引用
co_freevars是內層函數引用了哪些外層函數的本地變量

對外層函數來講,co_freevars必定是空元組,對內層函數來講,co_cellvars則必定是空元組。

若是知道自由變量的概念,這個很容易理解。

f()的co_cellvars內容: ('a', 'b', 'c', 'y')
f()的co_freevars內容: ('a', 'b', 'c', 'y')

co_code
co_flags
co_lnotab
這3個屬性和python函數的源代碼編譯成字節碼有關,本文不解釋它們。

屬性和字節碼對象PyCodeObject

對於python,一般都認爲它是一種解釋型語言。但實際上它在進行解釋以前,會先進行編譯,會將python源代碼編譯成python的字節碼(bytecode),而後在python virtual machine(PVM)中運行這段字節碼,就像Java同樣。可是PVM相比JVM而言,要更"高級"一些,這個高級的意思和高級語言的意思同樣:離物理機(處理機器碼)的距離更遠,或者說要更加抽象。

源代碼被python編譯器編譯的結果會保存在內存中一個名爲PyCodeObject的對象中,當須要運行時,python解釋器開始將其放進PVM中解釋執行,執行完畢後解釋器會"根據須要"將這個編譯的結果對象持久化到二進制文件*.pyc中。下次若是再執行,將首先從文件中加載(若是存在的話)。

所謂"根據須要"是指該py文件是否只運行一次,若是不是,則寫入pyc文件。至少,對於那些模塊文件,都會生成pyc二進制文件。另外,使用compileall模塊,能夠強制讓py文件編譯後生成pyc文件。

但須要注意,pyc雖然是字節碼文件,但並不意味着比py文件執行效率更高,它們是同樣的,都是一行行地讀取、解釋、執行。pyc惟一比py快的地方在導入,由於它無需編譯的過程,而是直接從文件中加載對象。

py文件中的每個代碼塊(code block)都有一個屬於本身的PyCodeObject對象。每一個代碼塊除了被編譯獲得的字節碼數據,還包含這個代碼塊中的常量、變量、棧空間等內容,也就是前面解釋的各類co_XXX屬性信息。

pyc文件包含3部分:

  • 4字節的Magic int,表示pyc的版本信息
  • 4字節的int,是pyc的產生時間,若是與py文件修改時間不一樣,則會從新生成
  • PycodeObject對象序列化的內容
相關文章
相關標籤/搜索