函數屬性 python中的函數是一種對象,它有屬於對象的屬性。除此之外,函數還可以自定義自己的屬性。註意,屬性是和對象相關的,和作用域無關。 自定義屬性 自定義函數自己的屬性方式很簡單。假設函數名稱為myfunc,那麼為這個函數添加一個屬性var1: 那麼這個屬性var1就像是全局變數一樣被訪問、修 ...
函數屬性
python中的函數是一種對象,它有屬於對象的屬性。除此之外,函數還可以自定義自己的屬性。註意,屬性是和對象相關的,和作用域無關。
自定義屬性
自定義函數自己的屬性方式很簡單。假設函數名稱為myfunc,那麼為這個函數添加一個屬性var1:
myfunc.var1="abc"
那麼這個屬性var1就像是全局變數一樣被訪問、修改。但它並不是全局變數。
可以跨模塊自定義函數的屬性。例如,在b.py中有一個函數b_func()
,然後在a.py中導入這個b.py模塊,可以直接在a.py中設置並訪問來自b.py中的b_func()
的屬性。
import b
b.b_func.var1="hello"
print(b.b_func.var1) # 輸出hello
查看函數對象屬性
python函數是一種對象,是對象就會有對象的屬性。可以通過如下方式查看函數對象的屬性:
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: 1co_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二進位文件。
py文件中的每一個代碼塊(code block)都有一個屬於自己的PyCodeObject對象。每個代碼塊除了被編譯得到的位元組碼數據,還包含這個代碼塊中的常量、變數、棧空間等內容,也就是前面解釋的各種co_XXX
屬性信息。
pyc文件包含3部分:
- 4位元組的Magic int,表示pyc的版本信息
- 4位元組的int,是pyc的產生時間,如果與py文件修改時間不同,則會重新生成
- PycodeObject對象序列化的內容
參考文章:https://blog.csdn.net/efeics/article/details/9255193