自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python 函數(shù)在底層長(zhǎng)什么樣子?

開(kāi)發(fā) 前端
Python 一切皆對(duì)象,函數(shù)也不例外,函數(shù)這種抽象機(jī)制在底層是通過(guò) PyFunctionObject 結(jié)構(gòu)體實(shí)現(xiàn)的。

楔子

函數(shù)是任何一門(mén)編程語(yǔ)言都具備的基本元素,它可以將多個(gè)動(dòng)作組合起來(lái),一個(gè)函數(shù)代表了一系列的動(dòng)作。而且在調(diào)用函數(shù)時(shí)會(huì)干什么來(lái)著,沒(méi)錯(cuò),要?jiǎng)?chuàng)建棧幀,用于函數(shù)的執(zhí)行。

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

PyFunctionObject

Python 一切皆對(duì)象,函數(shù)也不例外,函數(shù)這種抽象機(jī)制在底層是通過(guò) PyFunctionObject 結(jié)構(gòu)體實(shí)現(xiàn)的。

// Include/cpython/funcobject.h
#define COMMON_FIELDS(PREFIX) \
    PyObject *PREFIX ## globals; \
    PyObject *PREFIX ## builtins; \
    PyObject *PREFIX ## name; \
    PyObject *PREFIX ## qualname; \
    PyObject *PREFIX ## code; \
    PyObject *PREFIX ## defaults; \
    PyObject *PREFIX ## kwdefaults; \
    PyObject *PREFIX ## closure;

typedef struct {
    PyObject_HEAD
    COMMON_FIELDS(func_)
    PyObject *func_doc;         
    PyObject *func_dict;        
    PyObject *func_weakreflist; 
    PyObject *func_module;      
    PyObject *func_annotations; 
    PyObject *func_typeparams;  
    vectorcallfunc vectorcall;
    uint32_t func_version;
} PyFunctionObject;

如果將宏展開(kāi)的話,結(jié)構(gòu)體就是下面這個(gè)樣子。

typedef struct {
    PyObject_HEAD
    PyObject *func_globals; 
    PyObject *func_builtins; 
    PyObject *func_name; 
    PyObject *func_qualname; 
    PyObject *func_code; 
    PyObject *func_defaults; 
    PyObject *func_kwdefaults; 
    PyObject *func_closure;
    PyObject *func_doc;         
    PyObject *func_dict;        
    PyObject *func_weakreflist; 
    PyObject *func_module;      
    PyObject *func_annotations; 
    PyObject *func_typeparams;  
    vectorcallfunc vectorcall;
    uint32_t func_version;
} PyFunctionObject;

我們來(lái)解釋一下這些字段,并實(shí)際獲取一下,看看它們?cè)?Python 中是如何表現(xiàn)的。

func_globals:global 名字空間

def foo(a, b, c):
    pass

name = "古明地覺(jué)"
print(foo.__globals__)  # {..., 'name': '古明地覺(jué)'}
# 拿到的其實(shí)就是外部的 global名字空間
print(foo.__globals__ is globals())  # True

函數(shù)內(nèi)部之所以可以訪問(wèn)全局變量,就是因?yàn)樗4媪巳置挚臻g。

func_builtins:builtin 名字空間

def foo(a, b, c):
    pass

print(foo.__builtins__ is __builtins__.__dict__)  # True

注意:在之前的版本中,函數(shù)內(nèi)部是沒(méi)有這個(gè)字段的。

func_name:函數(shù)的名字

def foo(name, age):
    pass

print(foo.__name__)  # foo

當(dāng)然不光是函數(shù),方法、類、模塊都有自己的名字。

import numpy as np

print(np.__name__)  # numpy
print(np.ndarray.__name__)  # ndarray
print(np.array([1, 2, 3]).transpose.__name__)  # transpose

除了 func_name 之外,函數(shù)還有一個(gè) func_qualname 字段。

func_qualname:函數(shù)的全限定名

print(str.join.__name__)  # join
print(str.join.__qualname__)  # str.join

函數(shù)如果定義在類里面,那么它就叫類的成員函數(shù),但它本質(zhì)上依舊是個(gè)函數(shù),和普通函數(shù)并無(wú)區(qū)別。只是在獲取全限定名的時(shí)候,會(huì)帶上類名。

func_code:函數(shù)對(duì)應(yīng)的 PyCodeObject 對(duì)象

def foo(a, b, c):
    pass

code = foo.__code__
print(code)  # <code object foo at ......>
print(code.co_varnames)  # ('a', 'b', 'c')

函數(shù)便是基于 PyCodeObject 構(gòu)建的。

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

def foo(name="古明地覺(jué)", age=16):
    pass
# 打印的是默認(rèn)值
print(foo.__defaults__)  # ('古明地覺(jué)', 16)

def bar():
    pass
# 沒(méi)有默認(rèn)值的話,__defaults__ 為 None
print(bar.__defaults__)  # None

注:默認(rèn)值只會(huì)創(chuàng)建一次,所以默認(rèn)值不應(yīng)該是可變對(duì)象。

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

def foo(name="古明地覺(jué)", age=16):
    pass
# 打印為 None,這是因?yàn)殡m然有默認(rèn)值
# 但并不要求必須通過(guò)關(guān)鍵字參數(shù)的方式傳遞
print(foo.__kwdefaults__)  # None

def bar(name="古明地覺(jué)", *, age=16):
    pass
print(bar.__kwdefaults__)  # {'age': 16}

加上一個(gè) * 表示后面的參數(shù)必須通過(guò)關(guān)鍵字的方式傳遞。

func_closure:一個(gè)元組,包含了內(nèi)層函數(shù)使用的外層作用域的變量,即 cell 變量。

def foo():
    name = "古明地覺(jué)"
    age = 17

    def bar():
        print(name, age)

    return bar


# 內(nèi)層函數(shù) bar 使用了外層作用域中的 name、age 變量
print(foo().__closure__)
"""
(<cell at 0x000001FD1D3B02B0: int object at 0x7efe79d4a1c8>,
 <cell at 0x000001FD1D42E310: str object at 0x7efe7921bc30>)
"""

print(foo().__closure__[0].cell_contents)  # 17
print(foo().__closure__[1].cell_contents)  # 古明地覺(jué)

注意:查看閉包屬性我們使用的是內(nèi)層函數(shù)。

func_doc:函數(shù)的 docstring

def foo():
    """
    hi,歡迎來(lái)到我的小屋
    遇見(jiàn)你真好
    """
    pass

print(foo.__doc__)
"""
    hi,歡迎來(lái)到我的小屋
    遇見(jiàn)你真好
"""

當(dāng)我們?cè)趯?xiě) Python 擴(kuò)展的時(shí)候,由于編譯之后是一個(gè) pyd,那么就會(huì)通過(guò) docstring 來(lái)描述函數(shù)的相關(guān)信息。

func_dict:函數(shù)的屬性字典

def foo(name, age):
    pass

print(foo.__dict__)  # {}

函數(shù)在底層也是由一個(gè)類實(shí)例化得到的,所以它也可以有自己的屬性字典,只不過(guò)這個(gè)字典一般為空。

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

import numpy as np

print(np.array.__module__)  # numpy

除了函數(shù),類、方法、協(xié)程也有 __module__ 屬性。

func_annotations:類型注解

def foo(name: str, age: int):
    pass

# Python3.5 新增的語(yǔ)法,但只能用于函數(shù)參數(shù)
# 而在 3.6 的時(shí)候,聲明變量也可以使用這種方式
# 特別是當(dāng) IDE 無(wú)法得知返回值類型時(shí),便可通過(guò)類型注解的方式告知 IDE
# 這樣就又能使用 IDE 的智能提示了
print(
    foo.__annotations__
)  # {'name': <class 'str'>, 'age': <class 'int'>}

像 FastAPI、Pydantic 等框架,都大量應(yīng)用了類型注解。

func_typeparams:類型參數(shù)

from typing import TypeVar

T = TypeVar('T')
S = TypeVar('S')


def foo[T, S](x: T, y: S) -> list[S, T]:
    return (y, x)

print(foo.__type_params__)  # (T, S)


class A[T, S]:
    def __init__(self, x: T, y: S):
        self.x: T = x
        self.y: S = y

a1 = A[int, float](3, 2.71)
a2 = A[str, dict]("hello", {})
print(A.__type_params__)  # (T, S)
print(a1.__type_params__)  # (T, S)
print(a2.__type_params__)  # (T, S)

關(guān)于類型參數(shù)的更具體用法,可以查閱相關(guān)文檔,說(shuō)實(shí)話如果是在 Python 里面,這種語(yǔ)法我估計(jì)一輩子都不會(huì)用。

vectorcallfunc vectorcall:矢量調(diào)用協(xié)議

函數(shù)本質(zhì)上也是一個(gè)實(shí)例對(duì)象,在調(diào)用時(shí)會(huì)執(zhí)行類型對(duì)象的 tp_call,對(duì)應(yīng) Python 里的 __call__。但 tp_call 屬于通用邏輯,而通用往往也意味著平庸,tp_call 在執(zhí)行時(shí)需要?jiǎng)?chuàng)建臨時(shí)元組和臨時(shí)字典來(lái)存儲(chǔ)位置參數(shù)、關(guān)鍵字參數(shù),這些臨時(shí)對(duì)象增加了內(nèi)存分配和垃圾回收的開(kāi)銷。

如果只是一般的實(shí)例對(duì)象倒也沒(méi)什么,但函數(shù)不同,它作為實(shí)例對(duì)象注定是要被調(diào)用的。所以底層對(duì)它進(jìn)行了優(yōu)化,引入了速度更快的 vectorcall,即矢量調(diào)用。

關(guān)于普通調(diào)用(tp_call)和矢量調(diào)用(vectorcall)的具體細(xì)節(jié),后續(xù)會(huì)詳細(xì)說(shuō)明??傊粋€(gè)實(shí)例對(duì)象如果支持矢量調(diào)用,那么它也必須支持普通調(diào)用,并且兩者的結(jié)果是一致的,當(dāng)對(duì)象不支持矢量調(diào)用時(shí),會(huì)退化成普通調(diào)用。

uint32_t func_version:版本號(hào),用于函數(shù)特化

函數(shù)特化是指根據(jù)函數(shù)的調(diào)用模式,生成更高效的特定版本代碼,特別是針對(duì)那些頻繁調(diào)用的函數(shù)。但函數(shù)特化有一個(gè)前提,就是函數(shù)本身不能夠發(fā)生改變,于是引入了 func_version 字段。

當(dāng)函數(shù)的某些字段的值發(fā)生改變時(shí),func_version 會(huì)重置為 0,而當(dāng)?shù)讓涌吹?func_version 為 0 時(shí),就知道函數(shù)發(fā)生改變了,特化失效。

def foo(x, y=10):
    return x + y

# 以下操作會(huì)將 func_version 重置為 0

# 1. 修改默認(rèn)參數(shù)
foo.__defaults__ = (20,)

# 2. 修改關(guān)鍵字默認(rèn)參數(shù)
# 注:必須是指向一個(gè)新的字典,版本號(hào)才會(huì)重置
foo.__kwdefaults__ = {"z": 30}

# 3. 修改代碼對(duì)象
# (幾乎不可能直接修改,但可以通過(guò)某些高級(jí)技巧)

# 4. 修改注解
foo.__annotations__["return"] = int

# 5. 修改 vectorcall 函數(shù)指針
# (這是 C 級(jí)別的操作,Python 代碼通常無(wú)法直接觸及)

所以只要函數(shù)保持不變,Python 就會(huì)用特化版本來(lái)優(yōu)化執(zhí)行,而我們?cè)诠ぷ髦谢疽膊粫?huì)修改上面這幾個(gè)字段。

小結(jié)

以上就是函數(shù)的底層結(jié)構(gòu),在 Python 里面是由 <class 'function'> 實(shí)例化得到的。

def foo(name, age):
    pass

# <class 'function'> 就是 C 里面的 PyFunction_Type
print(foo.__class__)  # <class 'function'>

但這個(gè)類底層沒(méi)有暴露給我們,我們不能直接用,因?yàn)楹瘮?shù)通過(guò) def 創(chuàng)建即可,不需要通過(guò)類型對(duì)象來(lái)創(chuàng)建。

責(zé)任編輯:武曉燕 來(lái)源: 古明地覺(jué)的編程教室
相關(guān)推薦

2024-08-08 11:05:22

2024-05-07 09:24:12

Python源碼Java

2021-08-09 18:42:57

React VueSvelte

2024-07-24 09:34:27

2020-11-16 09:28:41

函數(shù)內(nèi)存

2020-04-09 11:00:20

Java虛擬機(jī)對(duì)象

2011-10-10 11:04:54

2022-03-15 16:19:13

物聯(lián)網(wǎng)物聯(lián)網(wǎng) 2.0IoT

2022-05-30 18:54:12

元宇宙Web3數(shù)據(jù)量

2022-10-10 08:47:49

ITCIO數(shù)據(jù)

2021-02-19 10:14:49

云計(jì)算公共云

2018-04-11 15:22:58

2021-09-14 16:32:11

物聯(lián)網(wǎng)IOT

2022-04-08 09:59:03

物聯(lián)網(wǎng)2.0物聯(lián)網(wǎng)

2018-11-28 14:53:44

物聯(lián)網(wǎng)網(wǎng)關(guān)物聯(lián)網(wǎng)IOT

2021-05-08 13:11:58

物聯(lián)網(wǎng)IOT物聯(lián)網(wǎng)技術(shù)

2018-01-16 15:02:20

存儲(chǔ)RAIDSAN

2014-04-08 09:56:30

銷售易CRM

2020-11-04 11:17:20

好代碼程序員整潔

2021-11-29 07:42:44

CSS 技巧CSS 繪圖技巧
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)