九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
14. Python函數(shù)對(duì)象的深度解析(第一部分): 函數(shù)在底層的數(shù)據(jù)結(jié)構(gòu)、以及它的創(chuàng)建方式

楔子

函數(shù)是任何一門(mén)編程語(yǔ)言都具備的基本元素,它可以將多個(gè)動(dòng)作組合起來(lái),一個(gè)函數(shù)代表了一系列的動(dòng)作。當(dāng)然我們之前說(shuō)函數(shù)也是一個(gè)變量,該變量指向一個(gè)函數(shù)。而且在調(diào)用函數(shù)時(shí)會(huì)干什么來(lái)著,沒(méi)錯(cuò),要在運(yùn)行時(shí)棧中創(chuàng)建棧幀,用于函數(shù)的執(zhí)行。

那么下面就來(lái)看看函數(shù)在C中是如何實(shí)現(xiàn)的,生得一副什么模樣。

PyFunctionObject對(duì)象

我們說(shuō)過(guò)Python中一切皆對(duì)象,函數(shù)也不例外。在Python中,函數(shù)這種抽象機(jī)制是通過(guò)PyFunctionObject對(duì)象實(shí)現(xiàn)的,位于 Include/funcobject.h 中。

typedef struct {    PyObject_HEAD               /* 頭部信息, 不用多說(shuō) */    PyObject *func_code;        /* 函數(shù)的PyCodeObject對(duì)象, 因?yàn)楹瘮?shù)就是根據(jù)該P(yáng)yCodeObject對(duì)象創(chuàng)建的 */    PyObject *func_globals;     /* 函數(shù)的global名字空間 */    PyObject *func_defaults;    /* 函數(shù)參數(shù)的默認(rèn)值, 一個(gè)元組或者空 */    PyObject *func_kwdefaults;  /* 只能通過(guò)關(guān)鍵字參數(shù)傳遞的"參數(shù)"和"該參數(shù)的默認(rèn)值", 一個(gè)字典或者空 */    PyObject *func_closure;     /* 獲取閉包對(duì)象 */    PyObject *func_doc;         /* 函數(shù)的doc */    PyObject *func_name;        /* 函數(shù)名 */    PyObject *func_dict;        /* 屬性字典, 一般為空 */    PyObject *func_weakreflist; /* 弱引用列表 */    PyObject *func_module;      /* 函數(shù)所在的模塊 */    PyObject *func_annotations; /* 函數(shù)參數(shù)的注解, 一個(gè)字典或者空 */    PyObject *func_qualname;    /* 函數(shù)的全限定名, 我們后面會(huì)說(shuō)它和func_name之間的區(qū)別 */    vectorcallfunc vectorcall;    /* Invariant:     *     func_closure contains the bindings for func_code->co_freevars, so     *     PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)     *     (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).     */} PyFunctionObject;

PyFunctionObject的這些成員都是以func開(kāi)頭的,比如:func_name,但是我們?cè)赑ython中獲取的時(shí)候直接通過(guò)__name__獲取即可。

func_code:函數(shù)的字節(jié)碼

def foo(a, b, c):    passcode = foo.__code__print(code)  # <code object foo at 0x000001D250B9D3A0, file "C:/Users/satori/Desktop/三無(wú)少女/2.py", line 1>print(code.co_varnames)  # ('a', 'b', 'c')

func_globals:global命名空間

def foo(a, b, c):    passname = "夏色祭"# __globals__其實(shí)就是外部的global名字空間print(foo.__globals__)  # {......, 'name': '夏色祭'}print(foo.__globals__ == globals())  # True

func_defaults:函數(shù)參數(shù)的默認(rèn)值

def foo(name="夏色祭", age=-1):    pass# 打印的是默認(rèn)值print(foo.__defaults__)  # ('夏色祭', -1)def bar():     pass# 沒(méi)有默認(rèn)值的話(huà), __defaults__為Noneprint(bar.__defaults__)  # None

func_kwdefaults:只能通過(guò)關(guān)鍵字參數(shù)傳遞的"參數(shù)"和"該參數(shù)的默認(rèn)值"

def foo(name="夏色祭", age=-1):    pass# 打印是為None的, 這是因?yàn)殡m然有默認(rèn)值, 但是它并不要求必須通過(guò)關(guān)鍵字的方式傳遞print(foo.__kwdefaults__)  # None# 如果在前面加上一個(gè)*, 表示后面的參數(shù)就必須通過(guò)關(guān)鍵字的方式傳遞# 因?yàn)槿绻煌ㄟ^(guò)關(guān)鍵字的話(huà), 那么無(wú)論多少個(gè)位置參數(shù)都會(huì)被*給吸收掉, 無(wú)論如何也不可能傳遞給name、age# 我們經(jīng)常會(huì)看到*args, 這是因?yàn)槲覀冃枰瘮?shù)調(diào)用時(shí)傳遞過(guò)來(lái)的值, 所以可以通過(guò)args以元組的形式來(lái)拿到這些值# 但是這里我們不需要, 我們只是希望后面的參數(shù)必須通過(guò)關(guān)鍵字參數(shù)傳遞, 因此前面寫(xiě)一個(gè)*即可# 當(dāng)然寫(xiě)*args或者其他的也可以, 但是我們用不到, 所以寫(xiě)一個(gè)*即可def bar(*, name="夏色祭", age=-1):    pass# 此時(shí)就打印了默認(rèn)值,因?yàn)檫@是只能通過(guò)kw(關(guān)鍵字)傳遞的參數(shù)的默認(rèn)值print(bar.__kwdefaults__)  # {'name': 'satori', 'age': 16}

func_closure:閉包對(duì)象

def foo():    name = "夏色祭"    age = -1    def bar():        nonlocal name        nonlocal age    return bar# 查看的是閉包里面nonlocal的值# 這里有兩個(gè)nonlocal,所以foo().__closure__是一個(gè)有兩個(gè)元素的元組print(foo().__closure__)  # (<cell at 0x000001FD1D3B02B0: int object at 0x00007FFDE559D660>,                          # <cell at 0x000001FD1D42E310: str object at 0x000001FD1D3DA090>)print(foo().__closure__[0].cell_contents)  # -1print(foo().__closure__[1].cell_contents)  # 夏色祭# 注意:查看閉包屬性我們使用的是內(nèi)層函數(shù),不是外層的foo

func_doc:函數(shù)的文檔

def foo(name, age):    """    接收一個(gè)name和age,    返回一句話(huà)    my name is $name, age is $age    """    return f"my name is {name}, age is {age}"print(foo.__doc__)"""    接收一個(gè)name和age,    返回一句話(huà)    my name is $name, age is $age    """

func_name:函數(shù)名

def foo(name, age):    passprint(foo.__name__)  # foo

func_dict:屬性字典

def foo(name, age):    pass# 一般函數(shù)的屬性字典都會(huì)空,屬性字典基本上在類(lèi)里面使用print(foo.__dict__)  # {}

func_weakreflist:弱引用列表

Python無(wú)法獲取這個(gè)屬性,底層沒(méi)有提供相應(yīng)的接口。

func_module:函數(shù)所在的模塊

 def foo(name, age):    passprint(foo.__module__)  # __main__

func_annotations:注解

def foo(name: str, age: int):    pass# Python3.5的時(shí)候新增的語(yǔ)法print(foo.__annotations__)  # {'name': <class 'str'>, 'age': <class 'int'>}

func_qualname:全限定名

def foo():    passprint(foo.__name__, foo.__qualname__)  # foo fooclass A:    def foo(self):        passprint(A.foo.__name__, A.foo.__qualname__)  # foo A.foo

在PyFunctionObject的定義中,我們看到一個(gè)func_code成員,指向了一個(gè)PyCodeObject對(duì)象,我們說(shuō)函數(shù)就是根據(jù)這個(gè)PyCodeObject對(duì)象創(chuàng)建的。因?yàn)槲覀冎酪粋€(gè)PyCodeObject對(duì)象是對(duì)一段代碼的靜態(tài)表示,Python編譯器在將源代碼進(jìn)行編譯之后,對(duì)里面的每一個(gè)代碼塊(code block)都會(huì)生成一個(gè)、并且是唯一一個(gè)PyCodeObject對(duì)象,這個(gè)PyCodeObject對(duì)象中包含了這個(gè)代碼塊中的一些靜態(tài)信息,也就是可以從源代碼中看到的信息。

比如:某個(gè)函數(shù)對(duì)應(yīng)的code block中有一個(gè) name = "夏色祭" 這樣的表達(dá)式,那么符號(hào)"a"和對(duì)應(yīng)的值1、以及它們之間的聯(lián)系就是靜態(tài)信息。這些信息會(huì)被靜態(tài)存儲(chǔ)起來(lái),符號(hào)"a"會(huì)被存在符號(hào)表co_varnames中、值1會(huì)被存在常量池co_consts中、這兩者之間是一個(gè)賦值,因此會(huì)有兩條指令LOAD_CONSTS和STORE_FAST存在字節(jié)碼指令序列co_code中。

這些信息是編譯的時(shí)候就可以得到的,因此PyCodeObject對(duì)象是編譯時(shí)候的結(jié)果。

但是PyFunctionObject對(duì)象是何時(shí)產(chǎn)生的呢?實(shí)際上PyFunctionObject對(duì)象是Python代碼在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生的,更準(zhǔn)確的說(shuō),是在執(zhí)行一個(gè)def語(yǔ)句的時(shí)候創(chuàng)建的。

當(dāng)Python虛擬機(jī)在當(dāng)前棧幀中執(zhí)行字節(jié)碼時(shí)發(fā)現(xiàn)了def語(yǔ)句,那么就代表發(fā)現(xiàn)了新的PyCodeObject對(duì)象,因?yàn)樗鼈兪强梢詫訉忧短椎?。所以虛擬機(jī)會(huì)根據(jù)這個(gè)PyCodeObject對(duì)象創(chuàng)建對(duì)應(yīng)的PyFunctionObject對(duì)象,然后將函數(shù)名和函數(shù)體對(duì)應(yīng)的PyFunctionObject對(duì)象組成鍵值對(duì)放在當(dāng)前的local空間中。

顯然在PyFunctionObject對(duì)象中,也會(huì)包含這些函數(shù)的靜態(tài)信息,這些信息存儲(chǔ)在func_code中,實(shí)際上,func_code一定會(huì)指向與函數(shù)對(duì)應(yīng)的PyCodeObject對(duì)象。除此之外,PyFunctionObject對(duì)象中還包含了一些函數(shù)在執(zhí)行時(shí)所必須的動(dòng)態(tài)信息,即上下文信息。比如func_globals,就是函數(shù)在執(zhí)行時(shí)關(guān)聯(lián)的global作用域(globals),說(shuō)白了就是讓你在局部變量找不到的時(shí)候能夠找全局變量,可如果連global空間都沒(méi)有的話(huà),那即便想找也無(wú)從下手呀。而global作用域中的符號(hào)和值必須在運(yùn)行時(shí)才能確定,所以這部分必須在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建,無(wú)法存儲(chǔ)在PyCodeObject中,所以要根據(jù)PyCodeObject對(duì)象創(chuàng)建PyFunctionObject對(duì)象,相當(dāng)于一個(gè)封裝??傊磺械哪康?,都是為了更好的執(zhí)行字節(jié)碼。

我們舉個(gè)栗子:

# 首先虛擬機(jī)從上到下執(zhí)行字節(jié)碼name = "夏色祭"age = -1# pia, 出現(xiàn)了一個(gè)defdef foo():    pass# 那么知道源代碼進(jìn)入了一個(gè)新的作用域了, 這里遇到一個(gè)新的PyCodeObject對(duì)象了# 而通過(guò)def知道這是一個(gè)函數(shù), 所以會(huì)進(jìn)行封裝, 將PyCodeObject對(duì)象封裝成PyFunctionObject# 所以當(dāng)執(zhí)行完def語(yǔ)句之后, 一個(gè)函數(shù)就被創(chuàng)建了, 放在當(dāng)前的local空間中, 當(dāng)然對(duì)于模塊來(lái)說(shuō): local空間也是global空間print(locals())  # {......, 'foo': <function foo at 0x000001B299FAF3A0>}# 函數(shù)的類(lèi)型是<class 'function'>, 當(dāng)然這個(gè)類(lèi)Python沒(méi)有暴露給我們# 當(dāng)我們調(diào)用函數(shù)foo的時(shí)候, 會(huì)從local空間中取出符號(hào)"foo"對(duì)應(yīng)的PyFunctionObject對(duì)象# 然后根據(jù)這個(gè)PyFunctionObject對(duì)象創(chuàng)建PyFrameObject對(duì)象, 也就是為函數(shù)創(chuàng)建一個(gè)棧幀# 然后將執(zhí)行權(quán)交給新創(chuàng)建的棧幀, 在新創(chuàng)建的棧幀中執(zhí)行字節(jié)碼

函數(shù)對(duì)象如何創(chuàng)建

我們現(xiàn)在已經(jīng)看清了函數(shù)的模樣,它在底層對(duì)應(yīng)PyFunctionObject對(duì)象,并且它和PyCodeObject對(duì)象關(guān)系密切。那么Python底層又是如何完成PyCodeObject對(duì)象到PyFunctionObject對(duì)象之間的轉(zhuǎn)變呢?想了解這其中的奧秘,就必須要從字節(jié)碼入手。

s = """name = "夏色祭"def foo(a, b):    print(a, b)foo(1, 2)"""import disdis.dis(compile(s, "func", "exec"))
  2           0 LOAD_CONST               0 ('夏色祭')              2 STORE_NAME               0 (name)  3           4 LOAD_CONST               1 (<code object foo at 0x000001EE0CBA72F0, file "func", line 3>)              6 LOAD_CONST               2 ('foo')              8 MAKE_FUNCTION            0             10 STORE_NAME               1 (foo)  6          12 LOAD_NAME                1 (foo)             14 LOAD_CONST               3 (1)             16 LOAD_CONST               4 (2)             18 CALL_FUNCTION            2             20 POP_TOP             22 LOAD_CONST               5 (None)             24 RETURN_VALUEDisassembly of <code object foo at 0x000001EE0CBA72F0, file "func", line 3>:  4           0 LOAD_GLOBAL              0 (print)              2 LOAD_FAST                0 (a)              4 LOAD_FAST                1 (b)              6 CALL_FUNCTION            2              8 POP_TOP             10 LOAD_CONST               0 (None)             12 RETURN_VALUE

顯然這個(gè)代碼中出現(xiàn)了兩個(gè)PyCodeObject對(duì)象,一個(gè)對(duì)應(yīng)整個(gè)py文件,另一個(gè)則是對(duì)應(yīng)函數(shù)foo。

s = """name = "夏色祭"def foo(a, b):    print(a, b)foo(1, 2)"""# 把字符串當(dāng)成是一個(gè)py文件來(lái)進(jìn)行編譯co = compile(s, "func", "exec")print(co.co_consts)  # ('夏色祭', <code object foo at 0x00000183F9101450, file "func", line 3>, 'foo', 1, 2, None)print(co.co_name)  # <module>print(co.co_consts[1].co_name)  # foo

可以看到,"函數(shù)foo對(duì)應(yīng)的PyCodeObject對(duì)象"是"py文件對(duì)應(yīng)的PyCodeObject對(duì)象"的常量池co_consts中的一個(gè)元素。因?yàn)樵趯?duì)py文件創(chuàng)建PyCodeObject對(duì)象的時(shí)候,發(fā)現(xiàn)了一個(gè)函數(shù)代碼塊foo,那么會(huì)對(duì)函數(shù)代碼塊foo繼續(xù)創(chuàng)建一個(gè)PyCodeObject對(duì)象(每一個(gè)代碼塊都會(huì)對(duì)應(yīng)一個(gè)PyCodeObject對(duì)象),而函數(shù)foo對(duì)應(yīng)的PyCodeObject對(duì)象則是py文件對(duì)應(yīng)的PyCodeObject對(duì)象的co_consts常量池當(dāng)中的一個(gè)元素。

通過(guò)以上例子,我們發(fā)現(xiàn)PyCodeObject對(duì)象是嵌套的。之前我們我們說(shuō)過(guò),每一個(gè)code block(函數(shù)、類(lèi)等等)都會(huì)對(duì)應(yīng)一個(gè)PyCodeObject對(duì)象?,F(xiàn)在我們又看到了,根據(jù)層級(jí)來(lái)分的話(huà),"內(nèi)層代碼塊對(duì)應(yīng)的PyCodeObject對(duì)象"是"最近的外層代碼塊對(duì)應(yīng)的PyCodeObject對(duì)象"的常量池co_consts中的一個(gè)元素。而最外層則是模塊對(duì)應(yīng)的PyCodeObject對(duì)象,因此這就意味著我們通過(guò)最外層的PyCodeObject對(duì)象可以找到所有的PyCodeObject對(duì)象,顯然這是毋庸置疑的。而這里和棧幀也是對(duì)應(yīng)的,棧幀我們說(shuō)過(guò)也是層層嵌套的,而內(nèi)層棧幀通過(guò)f_back可以找到外層、也就是調(diào)用者對(duì)應(yīng)的棧幀,當(dāng)然這里我們之前的章節(jié)已經(jīng)說(shuō)過(guò)了,這里再提一遍。

這里再來(lái)重新看一下上面的字節(jié)碼:

  2           0 LOAD_CONST               0 ('夏色祭')              2 STORE_NAME               0 (name)  3           4 LOAD_CONST               1 (<code object foo at 0x000001EE0CBA72F0, file "func", line 3>)              6 LOAD_CONST               2 ('foo')              8 MAKE_FUNCTION            0             10 STORE_NAME               1 (foo)  6          12 LOAD_NAME                1 (foo)             14 LOAD_CONST               3 (1)             16 LOAD_CONST               4 (2)             18 CALL_FUNCTION            2             20 POP_TOP             22 LOAD_CONST               5 (None)             24 RETURN_VALUEDisassembly of <code object foo at 0x000001EE0CBA72F0, file "func", line 3>:  4           0 LOAD_GLOBAL              0 (print)              2 LOAD_FAST                0 (a)              4 LOAD_FAST                1 (b)              6 CALL_FUNCTION            2              8 POP_TOP             10 LOAD_CONST               0 (None)             12 RETURN_VALUE

顯然dis模塊自動(dòng)幫我們分成了兩部分,上面是模塊的字節(jié)碼,下面是函數(shù)的字節(jié)碼。首先函數(shù)很簡(jiǎn)單我們就不看了,直接看模塊的。

首先開(kāi)頭的LOAD_CONST和STORE_NAME顯然是 name = "夏色祭" 對(duì)應(yīng)的指令。然后我們看4?LOAD_CONST,這條指令也是加載了一個(gè)常量,但這個(gè)常量是一個(gè)PyCodeObject對(duì)象;6?LOAD_CONST則是將字符串常量"foo"、即函數(shù)名加載了進(jìn)來(lái),然后通過(guò)MAKE_FUNCTION指令構(gòu)建一個(gè)PyFunctionObject對(duì)象;然后10?STORE_NAME,讓符號(hào)foo指向這個(gè)PyFunctionObject對(duì)象。再下面就是函數(shù)調(diào)用了,函數(shù)調(diào)用的具體細(xì)節(jié)我們之后會(huì)詳細(xì)說(shuō)。

并且我們還看到一個(gè)有趣的現(xiàn)象,那就是源代碼的行號(hào)。我們發(fā)現(xiàn)之前看到源代碼的行號(hào)都是從上往下、依次增大的,這很好理解,畢竟一條一條解釋嘛。但是這里卻發(fā)生了變化,先執(zhí)行了第6行,之后再執(zhí)行第4行。如果是從Python層面的函數(shù)調(diào)用來(lái)理解的話(huà),很容易一句話(huà)就解釋了,因?yàn)楹瘮?shù)只有在調(diào)用的時(shí)候才會(huì)執(zhí)行。但是從字節(jié)碼的角度來(lái)理解的話(huà),我們發(fā)現(xiàn)函數(shù)的聲明和實(shí)現(xiàn)是分離的,是在不同的PyCodeObject對(duì)象中。確實(shí)如此,雖然一個(gè)函數(shù)名和函數(shù)體是一個(gè)整體,但是Python虛擬機(jī)在實(shí)現(xiàn)這個(gè)函數(shù)的時(shí)候,卻在物理上將它們分離開(kāi)了,構(gòu)建函數(shù)的字節(jié)碼指令序列必須在模塊對(duì)應(yīng)的PyCodeObject對(duì)象中。

我們之前說(shuō)過(guò),函數(shù)即變量。我們是可以把函數(shù)當(dāng)成是普通的變量來(lái)處理的,函數(shù)名就相當(dāng)于變量名,函數(shù)體就相當(dāng)于是變量指向的值。而foo函數(shù)顯然是在全局中定義的一個(gè)函數(shù),那么foo是不是要出現(xiàn)在py文件對(duì)應(yīng)的PyCodeObject對(duì)象的符號(hào)表co_names里面呢?foo對(duì)應(yīng)的PyCodeObject對(duì)象是不是要出現(xiàn)在py文件對(duì)應(yīng)的PyCodeObject對(duì)象的常量池co_consts里面呢?

至此,函數(shù)的結(jié)構(gòu)就已經(jīng)非常清晰了。

所以函數(shù)名和函數(shù)體是分離的,它們存在不同的PyCodeObject對(duì)象當(dāng)中。分析完結(jié)構(gòu)之后,我們的重點(diǎn)就在于那個(gè)MAKE_FUNCTION指令了,我們說(shuō)當(dāng)遇到def?foo(a, b)的時(shí)候,在語(yǔ)法上將這是函數(shù)的聲明語(yǔ)句,但是從虛擬機(jī)的角度來(lái)看這其實(shí)是函數(shù)對(duì)象的創(chuàng)建語(yǔ)句。所以下面我們就要分析一下這個(gè)指令,看看它到底是怎么將一個(gè)PyCodeObject對(duì)象變成一個(gè)PyFunctionObject對(duì)象的。

        case TARGET(MAKE_FUNCTION): {            PyObject *qualname = POP(); //彈出符號(hào)表中的函數(shù)名            PyObject *codeobj = POP();  //彈出對(duì)應(yīng)的字節(jié)碼對(duì)象            //創(chuàng)建PyFunctionObject對(duì)象, 接收三個(gè)參數(shù), 首先第一個(gè)參數(shù)和第三個(gè)參數(shù)很好理解, 但重點(diǎn)是第二個(gè)參數(shù)            //首先f(wàn)指的就是當(dāng)前所在的棧幀, 對(duì)于我們目前這個(gè)里而言就是模塊、或者py文件對(duì)應(yīng)的棧幀            //然后將f_globals、也就是global名字空間傳遞了進(jìn)去, 所以我們現(xiàn)在明白了為什么函數(shù)可以調(diào)用__globals__了            //當(dāng)然也明白為什么函數(shù)可以在局部變量找不到的時(shí)候去找全局變量了            PyFunctionObject *func = (PyFunctionObject *)                PyFunction_NewWithQualName(codeobj, f->f_globals, qualname);            Py_DECREF(codeobj);            Py_DECREF(qualname);            if (func == NULL) {                goto error;            }            //下面是設(shè)置閉包、注解、參數(shù)默認(rèn)值等屬性            if (oparg & 0x08) {                assert(PyTuple_CheckExact(TOP()));                func ->func_closure = POP();            }            if (oparg & 0x04) {                assert(PyDict_CheckExact(TOP()));                func->func_annotations = POP();            }            if (oparg & 0x02) {                assert(PyDict_CheckExact(TOP()));                func->func_kwdefaults = POP();            }            if (oparg & 0x01) {                assert(PyTuple_CheckExact(TOP()));                func->func_defaults = POP();            }            //將函數(shù)或者說(shuō)函數(shù)對(duì)象壓入運(yùn)行時(shí)棧            PUSH((PyObject *)func);            DISPATCH();        }

我們看到在MAKE FUNCTION之前,先進(jìn)行了LOAD CONST,顯然是將foo對(duì)應(yīng)的字節(jié)碼對(duì)象和符號(hào)foo壓入到了棧中。所以在執(zhí)行MAKE FUNCTION的時(shí)候,首先就是將這個(gè)字節(jié)碼對(duì)象以及對(duì)應(yīng)符號(hào)彈出棧,然后再加上當(dāng)前PyFrameObject對(duì)象中維護(hù)的global名字空間f_globals對(duì)象,三者作為參數(shù)傳入PyFunction_NewWithQualName函數(shù)中,從而構(gòu)建出相應(yīng)的PyFunctionObject對(duì)象。

下面我們來(lái)看看PyFunction_NewWithQualName是如何構(gòu)造出一個(gè)函數(shù)的,它位于 Objects/funcobject.c 中。

PyObject *PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname){    //要返回的PyFunctionObject *, 這里先聲明一下    PyFunctionObject *op;    //函數(shù)的doc、PyCodeObject的co_consts、函數(shù)所在的模塊    PyObject *doc, *consts, *module;    static PyObject *__name__ = NULL;    if (__name__ == NULL) {        __name__ = PyUnicode_InternFromString("__name__");        if (__name__ == NULL)            return NULL;    }    //通過(guò)PyObject_GC_New為函數(shù)對(duì)象申請(qǐng)空間    op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);    if (op == NULL)        return NULL;    //下面就是設(shè)置PyFunctionObject對(duì)象的成員屬性了    op->func_weakreflist = NULL;    Py_INCREF(code);    op->func_code = code;    Py_INCREF(globals);    op->func_globals = globals;    op->func_name = ((PyCodeObject *)code)->co_name;    Py_INCREF(op->func_name);    op->func_defaults = NULL; /* No default arguments */    op->func_kwdefaults = NULL; /* No keyword only defaults */    op->func_closure = NULL;    op->vectorcall = _PyFunction_Vectorcall;    //通過(guò)PyCodeObject對(duì)象獲取常量池    consts = ((PyCodeObject *)code)->co_consts;        //我們知道函數(shù)的doc其實(shí)就是一個(gè)字符串, 顯然它也是常量池的一個(gè)常量, 并且是常量池的第一個(gè)元素    //否則的話(huà)它就是不能成為doc    if (PyTuple_Size(consts) >= 1) {        //所以如果consts>=1, 并且第一個(gè)元素是字符串, 那么它就是函數(shù)的doc        doc = PyTuple_GetItem(consts, 0);        if (!PyUnicode_Check(doc))            doc = Py_None;    }    else //否則doc就是None        doc = Py_None;    Py_INCREF(doc);    //下面也是設(shè)置PyFunctionObject對(duì)象的成員    op->func_doc = doc;    op->func_dict = NULL;    op->func_module = NULL;    op->func_annotations = NULL;    /* __module__: If module name is in globals, use it.       Otherwise, use None. */    module = PyDict_GetItemWithError(globals, __name__);    if (module) {        Py_INCREF(module);        op->func_module = module;    }    else if (PyErr_Occurred()) {        Py_DECREF(op);        return NULL;    }    if (qualname)        op->func_qualname = qualname;    else        op->func_qualname = op->func_name;    Py_INCREF(op->func_qualname);    _PyObject_GC_TRACK(op);    return (PyObject *)op;}

所以通過(guò)MAKE_FUNCTION我們便創(chuàng)建了PyFunctionObject對(duì)象,然后它會(huì)被壓入棧中,再通過(guò)STORE_NAME將符號(hào)foo和PyFunctionObject對(duì)象組成一個(gè)entry,存儲(chǔ)在當(dāng)前棧幀的local名字空間中,當(dāng)然也是global名字空間。只不過(guò)為了和函數(shù)保持統(tǒng)一,我們都說(shuō)成local名字空間,只不過(guò)不同的作用域?qū)?yīng)的local空間是不一樣的。

當(dāng)然了我們說(shuō)函數(shù)對(duì)象的類(lèi)型是<class 'function'>,但是這個(gè)類(lèi)底層沒(méi)有暴露給我們,但是我們依舊可以通過(guò)曲線(xiàn)救國(guó)的方式進(jìn)行獲取。

def f():    passprint(type(f))  # <class 'function'># lambda匿名函數(shù)的類(lèi)型也是<class 'function'>print(type(lambda: None))  # <class 'function'>

所以我們可以仿照底層的思路,通過(guò)<class 'function'>來(lái)創(chuàng)建一個(gè)函數(shù)對(duì)象。

gender = "female"def f(name, age):    return f"name: {name}, age: {age}, gender: {gender}"# 得到PyCodeObject對(duì)象code = f.__code__# 根據(jù)class function創(chuàng)建函數(shù)對(duì)象, 接收三個(gè)參數(shù): PyCodeObject對(duì)象、名字空間、函數(shù)名new_f = type(f)(code, globals(), "根據(jù)f創(chuàng)建的new_f")# 打印函數(shù)名print(new_f.__name__)  # 根據(jù)f創(chuàng)建的new_f# 調(diào)用函數(shù)print(new_f("夏色祭", -1))  # name: 夏色祭, age: -1, gender: female

是不是很神奇呢?另外我們說(shuō)函數(shù)在訪問(wèn)gender指向的對(duì)象時(shí),顯然先從自身的符號(hào)表中找,如果沒(méi)有那么回去找全局變量。這是因?yàn)?,我們?cè)趧?chuàng)建函數(shù)的時(shí)候?qū)lobal名字空間傳進(jìn)去了,如果我們不傳遞呢?

gender = "female"def f(name, age):    return f"name: {name}, age: {age}, gender: {gender}"code = f.__code__try:    new_f = type(f)(code, None, "根據(jù)f創(chuàng)建的new_f")except TypeError as e:    print(e)  # function() argument 'globals' must be dict, not None# 這里告訴我們function的第二個(gè)參數(shù)globals必須是一個(gè)字典# 我們傳遞一個(gè)空字典new_f1 = type(f)(code, {}, "根據(jù)f創(chuàng)建的new_f1")# 打印函數(shù)名print(new_f1.__name__)  # 根據(jù)f創(chuàng)建的new_f1# 調(diào)用函數(shù)try:    print(new_f1("夏色祭", -1))except NameError as e:    print(e)  # name 'gender' is not defined# 我們看到告訴我們gender沒(méi)有定義

因此現(xiàn)在我們又在Python的角度上理解了一遍,為什么Python中的函數(shù)能夠在局部變量找不到的時(shí)候,去找全局變量,原因就在于構(gòu)建函數(shù)的時(shí)候,將global名字空間交給了函數(shù)。使得函數(shù)可以在global空間進(jìn)行變量查找,所以它才能夠找到全局變量。而我們這里給了一個(gè)空字典,那么顯然就找不到gender這個(gè)變量了。

gender = "female"def f(name, age):    return f"name: {name}, age: {age}, gender: {gender}"code = f.__code__new_f = type(f)(code, {"gender": "萌妹子"}, "根據(jù)f創(chuàng)建的new_f")# 我們可以手動(dòng)傳遞一個(gè)字典進(jìn)去, 此時(shí)我們傳遞的字典對(duì)于函數(shù)來(lái)說(shuō)就是global名字空間# 所以在函數(shù)內(nèi)部找不到某個(gè)變量的時(shí)候, 就會(huì)去我們指定的名字空間中找print(new_f("夏色祭", -1))  # name: 夏色祭, age: -1, gender: 萌妹子# 所以此時(shí)的gender不再是外部的"female", 而是我們指定的"萌妹子"

此外我們還可以為函數(shù)指定默認(rèn)值:

gender = "female"def f(name, age):    return f"name: {name}, age: {age}, gender: {gender}"code = f.__code__new_f = type(f)(code, {"gender": "屑女仆"}, "根據(jù)f創(chuàng)建的new_f")# 必須接收一個(gè)PyTupleObject對(duì)象new_f.__defaults__ = ("神樂(lè)mea", 38)# 即使我們不傳遞參數(shù), 也是完全可以的, 因?yàn)橐呀?jīng)有默認(rèn)值了print(new_f())  # name: 神樂(lè)mea, age: 38, gender: 屑女仆# 我們也可以指定部分默認(rèn)參數(shù)new_f1 = type(f)(code, {"gender": "屑女仆"}, "根據(jù)f創(chuàng)建的new_f1")# 這里的在設(shè)置默認(rèn)值的時(shí)候是從后往前設(shè)置的, 比如: ("神樂(lè)mea", 38)# 是將38設(shè)置為age的默認(rèn)值, "神樂(lè)mea"設(shè)置為name的默認(rèn)值# 所以這里的(38,) , 會(huì)將38設(shè)置為age的默認(rèn)值, 不是name# 那name怎么辦? 如果沒(méi)有對(duì)應(yīng)的默認(rèn)值了, 那么它就必須在函數(shù)調(diào)用的時(shí)候由我們顯式的傳遞new_f1.__defaults__ = (38,)try:    new_f1()except TypeError as e:    print(e)  # f() missing 1 required positional argument: 'name'print(new_f1("神楽めあ"))  # name: 神楽めあ, age: 38, gender: 屑女仆"""但是問(wèn)題來(lái)了, 為什么在設(shè)置默認(rèn)值的時(shí)候要從后往前呢?首先如果默認(rèn)值的個(gè)數(shù)和參數(shù)的個(gè)數(shù)正好匹配, 那么相安無(wú)事, 如果不匹配那么只能是默認(rèn)值的個(gè)數(shù)小于參數(shù)個(gè)數(shù)如果是從后往前, 那么(38,)就意味著38設(shè)置為age的默認(rèn)值, name就必須由我們?cè)谡{(diào)用的時(shí)候傳遞但如果是從前往后, 那么(38,)就意味著38設(shè)置為name的默認(rèn)值, age就必須由我們?cè)谡{(diào)用的時(shí)候來(lái)傳遞但是問(wèn)題來(lái)了, 如果38設(shè)置為name的默認(rèn)值, 這會(huì)是什么情況? 顯然等價(jià)于:def new_f1(name=38, age):    ......    你認(rèn)為這樣的函數(shù)能夠通過(guò)編譯嗎?顯然是不行的, 因?yàn)槟J(rèn)參數(shù)必須在非默認(rèn)參數(shù)的后面"""# 所以Python的這個(gè)做法是完全正確的, 必須要從后往前進(jìn)行設(shè)置

當(dāng)然,這種設(shè)置默認(rèn)值的方式顯然也可以使用于通過(guò)def定義的函數(shù),因?yàn)槲覀兩厦娴膎ew_f、new_f1和f都是<class 'function'>對(duì)象。

gender = "female"def f(name, age):    return f"name: {name}, age: {age}, gender: {gender}"print(f.__defaults__)  # None# 設(shè)置默認(rèn)值f.__defaults__ = ("夏色祭", -1)# 如果你用的是pycharm, 那么會(huì)在f()這個(gè)位置給你做上標(biāo)記, 提示你參數(shù)沒(méi)有傳遞# 但我們知道由于使用__defaults__已經(jīng)設(shè)置了默認(rèn)值, 所以這里是不會(huì)報(bào)錯(cuò)的, 只不過(guò)pycharm沒(méi)有檢測(cè)到, 當(dāng)然基本上所有的ide都無(wú)法做到這一點(diǎn)print(f())  # name: 夏色祭, age: -1, gender: female

另外我們說(shuō),默認(rèn)值的個(gè)數(shù)一定要小于等于參數(shù)的個(gè)數(shù),但如果大于呢?

gender = "female"def f(name, age):    return f"name: {name}, age: {age}, gender: {gender}"print(f.__defaults__)  # Nonef.__defaults__ = ("夏色祭", -1, "神樂(lè)mea", 38)print(f())  # name: 神樂(lè)mea, age: 38, gender: female# 依舊從后往前, 38給age、"神樂(lè)mea"給name# 參數(shù)都有默認(rèn)值了, 那么就結(jié)束了# 當(dāng)然如果是__defaults__指向的元組先結(jié)束, 那么沒(méi)有得到默認(rèn)值的參數(shù)就必須由我們來(lái)傳遞了

想不到Python中的函數(shù)可以玩出這么多新花樣,現(xiàn)在你是不是對(duì)函數(shù)有了一個(gè)更深刻的認(rèn)識(shí)了呢?當(dāng)然目前介紹的只是函數(shù)的一小部分內(nèi)容,還有函數(shù)如何調(diào)用、位置參數(shù)和關(guān)鍵字參數(shù)如何解析、對(duì)于有默認(rèn)值的參數(shù)如何在我們不傳遞的時(shí)候使用默認(rèn)值以及在我們傳遞的時(shí)候使用我們傳遞的值、*args和**kwargs又如何解析、閉包怎么做到的、還有裝飾器等等等等,這些我們接下來(lái)會(huì)單獨(dú)用幾篇博客詳細(xì)說(shuō)。

因?yàn)榉旁谝黄┛屠锩娴脑?huà),字?jǐn)?shù)至少要好幾萬(wàn),而我使用的Markdown編輯器typora在字?jǐn)?shù)達(dá)到一萬(wàn)五的時(shí)候就會(huì)出現(xiàn)明顯卡頓,要是一下子都寫(xiě)完的話(huà),絕對(duì)卡到爆,而且越往后越卡,這對(duì)我而言也是個(gè)痛苦。而且函數(shù)的內(nèi)容也比較多,我們就多用一些篇幅去介紹它吧。

判斷函數(shù)都有哪些參數(shù)

最后我們?cè)賮?lái)看看我們?nèi)绾螜z測(cè)一個(gè)函數(shù)有哪些參數(shù),首先函數(shù)的局部變量(包括參數(shù))在編譯是就已經(jīng)確定,會(huì)存在符號(hào)表co_varnames中。

# 注意: 在定義函數(shù)的時(shí)候*和**最多只能出現(xiàn)一次# 顯然a和b必須通過(guò)位置參數(shù)傳遞# c和d可以通過(guò)位置參數(shù)或者關(guān)鍵字參數(shù)傳遞# e和f必須通過(guò)關(guān)鍵字參數(shù)傳遞def f(a, b, /, c, d, *args, e, f, **kwargs):    g = 1    h = 2varnames = f.__code__.co_varnamesprint(varnames)# ('a', 'b', 'c', 'd', 'e', 'f', 'args', 'kwargs', 'g', 'h')"""首先co_varnames打印的符號(hào)表是有順序的, 參數(shù)永遠(yuǎn)在函數(shù)內(nèi)部定義的局部變量的前面g和h就是函數(shù)內(nèi)部定義的局部變量, 所以它在所有的后面如果是參數(shù)的話(huà), 那么*和**會(huì)位于最后面, 其它參數(shù)位置不變, 所以除了g和h, 最后面的就是args和kwargs"""# 接下來(lái), 我們就可以進(jìn)行判斷了# 1. 尋找必須通過(guò)位置參數(shù)傳遞的參數(shù)posonlyargcount = f.__code__.co_posonlyargcountprint(posonlyargcount)  # 2print(varnames[: posonlyargcount])  # ('a', 'b')# 2. 尋找可以通過(guò)位置參數(shù)傳遞或者關(guān)鍵字參數(shù)傳遞的參數(shù)argcount = f.__code__.co_argcountprint(argcount)  # 4print(varnames[: 4])  # ('a', 'b', 'c', 'd')print(varnames[posonlyargcount: 4])  # ('c', 'd')# 3. 尋找必須通過(guò)關(guān)鍵字參數(shù)傳遞的參數(shù)kwonlyargcount = f.__code__.co_kwonlyargcountprint(kwonlyargcount)  # 2print(varnames[argcount: argcount   kwonlyargcount])  # ('e', 'f')# 4. 尋找*args和**kwargs"""在介紹PyCodeObject對(duì)象的時(shí)候, 我們說(shuō)里面有一個(gè)co_flags成員它是專(zhuān)門(mén)用來(lái)判斷參數(shù)中是否有*args和**kwargs的"""flags = f.__code__.co_flags# 如果flags和4進(jìn)行按位與之后為真, 那么就代表有*args, 否則沒(méi)有# 如果flags和8進(jìn)行按位與之后為真, 那么就代表有**kwargs, 否則沒(méi)有step = argcount   kwonlyargcountif flags & 0x04:    print(varnames[step])  # args    step  = 1if flags & 0x08:    print(varnames[step])  # kwargs    # 雖然我們這里打印的是args和kwargs, 但主要取決定義的時(shí)候使用的名字# 如果定義的時(shí)候是*ARGS和**KWARGS, 那么這里就是ARGS和KWARGS, 只不過(guò)一般我們都叫做*args和**kwargs

如果我們定義的不是*args,只是一個(gè)*,那么它就不是參數(shù)了。

def f(a, b, *, c):    pass# 我們看到此時(shí)只有a、b、cprint(f.__code__.co_varnames)  # ('a', 'b', 'c')print(f.__code__.co_flags & 0x04)  # 0print(f.__code__.co_flags & 0x08)  # 0# 顯然此時(shí)也都為假

小結(jié)

這一次我們簡(jiǎn)單的分析了一下函數(shù)在底層對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),以及如何創(chuàng)建一個(gè)函數(shù),并且還在Python的層面上做了一些小trick。最后我們也分析了如何通過(guò)PyCodeObject對(duì)象來(lái)檢索Python中的參數(shù),以及相關(guān)種類(lèi),當(dāng)然標(biāo)準(zhǔn)庫(kù)中的inspect模塊也是這么做的。當(dāng)然說(shuō)白了,其實(shí)是我們模仿人家的思路做的。

來(lái)源:https://www.icode9.com/content-1-729601.html
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶(hù)發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
《源碼探秘 CPython》57. 函數(shù)是怎么創(chuàng)建的?
AS3[1]基礎(chǔ)教程
ES6 中變量的解構(gòu)賦值
ES6解構(gòu)賦值實(shí)例詳解
es6新增特性之函數(shù)參數(shù)默認(rèn)值的優(yōu)勢(shì)-筆記
Python陷阱:為什么不能用可變對(duì)象作為函數(shù)的默認(rèn)參數(shù)值
更多類(lèi)似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服