精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

函數(shù)是怎么創(chuàng)建的,背后經(jīng)歷了哪些過程?

開發(fā) 前端
因?yàn)橐粋€ PyCodeObject 是對一段代碼的靜態(tài)表示,Python 編譯器將源代碼編譯之后,針對里面的每一個代碼塊(code block)都會生成相應(yīng)的 PyCodeObject 對象,該對象包含了這個代碼塊的一些靜態(tài)信息,也就是可以從源代碼中看到的信息。

楔子

前面我們介紹了函數(shù)的基本結(jié)構(gòu),它在底層由 PyFunctionObject 結(jié)構(gòu)體表示,那么本篇文章來看看函數(shù)的創(chuàng)建過程。

正好明天周六日,可以慢慢看[doge]。

函數(shù)是何時(shí)創(chuàng)建的

介紹函數(shù)結(jié)構(gòu)時(shí),我們看到內(nèi)部有一個 func_code 字段,指向一個 PyCodeObject 對象,而函數(shù)就是根據(jù) PyCodeObject 對象創(chuàng)建的。

因?yàn)橐粋€ PyCodeObject 是對一段代碼的靜態(tài)表示,Python 編譯器將源代碼編譯之后,針對里面的每一個代碼塊(code block)都會生成相應(yīng)的 PyCodeObject 對象,該對象包含了這個代碼塊的一些靜態(tài)信息,也就是可以從源代碼中看到的信息。

比如某個函數(shù)對應(yīng)的代碼塊里面有一個 a = 1 這樣的表達(dá)式,那么符號 a 和整數(shù) 1、以及它們之間的聯(lián)系就是靜態(tài)信息。這些信息會被靜態(tài)存儲起來,符號 a 被存在符號表 co_varnames 中,整數(shù) 1 被存在常量池 co_consts 中。然后 a = 1 是一條賦值語句,因此會有兩條指令 LOAD_CONST 和 STORE_FAST 存在字節(jié)碼指令序列 co_code 中。

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

但 PyFunctionObject 對象是何時(shí)產(chǎn)生的呢?顯然它是 Python 代碼在運(yùn)行時(shí)動態(tài)產(chǎn)生的,更準(zhǔn)確的說,是在執(zhí)行一個 def 語句的時(shí)候創(chuàng)建的。當(dāng)虛擬機(jī)發(fā)現(xiàn)了 def 語句,那么就代表發(fā)現(xiàn)了新的 PyCodeObject 對象,因?yàn)樗鼈兪强梢詫訉忧短椎摹?/p>

然后虛擬機(jī)會根據(jù)這個 PyCodeObject 對象創(chuàng)建對應(yīng)的 PyFunctionObject 對象,并將變量名和 PyFunctionObject 對象(函數(shù)體)組成鍵值對放在當(dāng)前的 local 空間中。

而在 PyFunctionObject 對象中,也需要拿到相關(guān)的靜態(tài)信息,因此會有一個 func_code 字段指向 PyCodeObject。

除此之外,PyFunctionObject 對象還包含了一些函數(shù)在執(zhí)行時(shí)所必需的動態(tài)信息,即上下文信息。比如 func_globals,就是函數(shù)在執(zhí)行時(shí)關(guān)聯(lián)的 global 名字空間,如果沒有這個空間的話,函數(shù)就無法訪問全局變量了。

由于 global 作用域中的符號和值必須在運(yùn)行時(shí)才能確定,所以這部分必須在運(yùn)行時(shí)動態(tài)創(chuàng)建,無法靜態(tài)存儲在 PyCodeObject 中。因此要基于 PyCodeObject 對象和 global 名字空間來創(chuàng)建 PyFunctionObject 對象,相當(dāng)于一個封裝。總之一切的目的,都是為了更好地執(zhí)行字節(jié)碼。

我們舉個例子:

# 首先虛擬機(jī)從上到下執(zhí)行字節(jié)碼
name = "古明地覺"
age = 17

# 啪,很快啊,出現(xiàn)了一個 def
def foo():
    pass

# 出現(xiàn)了 def,虛擬機(jī)就知道源代碼進(jìn)入了一個新的作用域了
# 也就是遇到一個新的 PyCodeObject 對象了
# 而通過 def 關(guān)鍵字知道這是一個函數(shù),于是會進(jìn)行封裝
# 將 PyCodeObject 封裝成 PyFunctionObject,同時(shí)包含了全局名字空間
# 所以當(dāng)執(zhí)行完 def 語句之后,一個函數(shù)就被創(chuàng)建了
# 然后將變量名 foo 和函數(shù)體(PyFunctionObject)組成鍵值對存放在當(dāng)前的 local 空間中
# 當(dāng)然對于模塊而言,local 空間也是 global 空間
print({k: v for k, v in locals().items() if k == "foo"})
"""
{'foo': <function foo at 0x102d5bf40>}
"""

# 函數(shù)內(nèi)部也保存了 global 空間
print(foo.__globals__ is globals() is locals())
"""
True
"""
print(foo.__globals__["foo"] is foo is locals()["foo"])
"""
True
"""

調(diào)用的時(shí)候,會從 local 空間中取出符號 foo 對應(yīng)的 PyFunctionObject 對象(函數(shù)對象)。然后根據(jù)函數(shù)對象創(chuàng)建棧幀對象,也就是為函數(shù)創(chuàng)建一個棧幀,隨后將執(zhí)行權(quán)交給新創(chuàng)建的棧幀,并在新創(chuàng)建的棧幀中執(zhí)行字節(jié)碼。

函數(shù)是怎么創(chuàng)建的

經(jīng)過分析我們知道,當(dāng)執(zhí)行到 def 語句時(shí)會創(chuàng)建函數(shù),并保存在 local 空間中。而通過函數(shù)名()進(jìn)行調(diào)用時(shí),會從 local 空間取出和函數(shù)名綁定的函數(shù)對象,然后執(zhí)行。

那么問題來了,函數(shù)(對象)是怎么創(chuàng)建的呢?或者說虛擬機(jī)是如何完成 PyCodeObject 對象到 PyFunctionObject 對象之間的轉(zhuǎn)變呢?顯然想了解這其中的奧秘,就必須從字節(jié)碼入手。

import dis

code_string = """
name = "satori"
def foo(a, b):
    print(a, b)

foo(1, 2)
"""

dis.dis(compile(code_string, "<func>", "exec"))

源代碼很簡單,定義一個變量 name 和一個函數(shù) foo,然后調(diào)用函數(shù)。顯然這里面會產(chǎn)生兩個 PyCodeObject,我們來看一下。

0 RESUME                   0

 # name = "satori"
 2 LOAD_CONST               0 ('satori')
 4 STORE_NAME               0 (name)

 # 我們看到 PyCodeObject 也會作為常量被靜態(tài)收集
 # 這里是將常量池中索引為 1 的 PyCodeObject 壓入運(yùn)行時(shí)棧
 6 LOAD_CONST               1 (<code object foo at 0x7f5d...>)
 # 從棧中彈出 PyCodeObject,然后構(gòu)建函數(shù)對象
 # 將函數(shù)對象(的指針)再壓入運(yùn)行時(shí)棧
 8 MAKE_FUNCTION            0
 # 從棧中彈出函數(shù)對象,并用符號 foo 綁定起來
 # 到此函數(shù)就創(chuàng)建完畢了
10 STORE_NAME               1 (foo)
 
 # 以下是 foo(1, 2) 對應(yīng)的字節(jié)碼
12 PUSH_NULL
 # 加載全局變量 foo 并壓棧
14 LOAD_NAME                1 (foo)
 # 加載常量 1 和 2 并壓棧
16 LOAD_CONST               2 (1)
18 LOAD_CONST               3 (2)
 # 從棧中彈出函數(shù)和參數(shù),然后調(diào)用
 # 將調(diào)用結(jié)果、即函數(shù)的返回值壓入棧中
20 CALL                     2
 # 從棧頂彈出返回值,因?yàn)槲覀儧]有使用變量保存,所以會直接丟棄
 # 如果使用變量保存了,比如 res = foo(1, 2),那么這里的字節(jié)碼就是 STORE_NAME
28 POP_TOP
30 RETURN_CONST             4 (None)
 
 # 以上是模塊對應(yīng)的字節(jié)碼指令,下面是函數(shù)對應(yīng)的字節(jié)碼指令
Disassembly of <code object foo at 0x7f5d..., file "<func>", line 3>:
 0 RESUME                   0
 
 # 比較簡單,就是 print(a, b) 對應(yīng)的字節(jié)碼
 2 LOAD_GLOBAL              1 (NULL + print)
12 LOAD_FAST                0 (a)
14 LOAD_FAST                1 (b)
16 CALL                     2
24 POP_TOP
26 RETURN_CONST             0 (None)

通過字節(jié)碼我們看到,def 關(guān)鍵字實(shí)際上還是在定義變量,正所謂函數(shù)即變量,我們可以把函數(shù)當(dāng)成普通的變量來處理。函數(shù)名就是變量名,它位于模塊對應(yīng)的 PyCodeObject 的符號表中。函數(shù)體就是變量指向的值,它是基于一個獨(dú)立的 PyCodeObject 構(gòu)建的。

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

分析完結(jié)構(gòu)之后,重點(diǎn)就要落在 MAKE_FUNCTION 指令上了,我們說當(dāng)遇到 def 關(guān)鍵字的時(shí)候,就知道要創(chuàng)建函數(shù)了。在語法上這是函數(shù)的聲明語句,但從虛擬機(jī)的角度來看,這其實(shí)是函數(shù)對象的創(chuàng)建語句。

所以函數(shù)是怎么創(chuàng)建的,就是執(zhí)行 MAKE_FUNCTION 指令創(chuàng)建的,該指令執(zhí)行完畢后,一個函數(shù)對象就被壓入了運(yùn)行時(shí)棧。等到 STORE_NAME 執(zhí)行時(shí),再將它從棧中彈出,然后和變量(函數(shù)名)綁定起來。

MAKE_FUNCTION 指令

下面我們就來分析一下 MAKE_FUNCTION 指令,看看它是怎么將一個 PyCodeObject 對象變成一個 PyFunctionObject 對象的。

TARGET(MAKE_FUNCTION) {
    // 獲取 PyCodeObject 對象
    PyObject *codeobj = stack_pointer[-1];

    // 編譯時(shí),解釋器能夠靜態(tài)檢測出函數(shù)有沒有閉包變量、類型注解等屬性,并體現(xiàn)在 oparg 中
    // 構(gòu)建函數(shù)時(shí),通過 oparg 和一系列標(biāo)志位做按位與,來判斷函數(shù)是否包含指定屬性
    // 由于 oparg 是指令參數(shù),所以這些屬性是否存在、以及如何訪問,在編譯階段就已經(jīng)確定了
    PyObject *closure = (oparg & 0x08) ? stack_pointer[...] : NULL;
    PyObject *annotations = (oparg & 0x04) ? stack_pointer[...] : NULL;
    PyObject *kwdefaults = (oparg & 0x02) ? stack_pointer[...] : NULL;
    PyObject *defaults = (oparg & 0x01) ? stack_pointer[...] : NULL;
    PyObject *func;
    #line 3267 "Python/bytecodes.c"
    
    // 基于 PyCodeObject 和全局名字空間,來構(gòu)建 PyFunctionObject
    PyFunctionObject *func_obj = (PyFunctionObject *)
        PyFunction_New(codeobj, GLOBALS());

    Py_DECREF(codeobj);
    if (func_obj == NULL) {
        goto error;
    }
    // 設(shè)置閉包變量、類型注解、默認(rèn)值等屬性
    if (oparg & 0x08) {
        assert(PyTuple_CheckExact(closure));
        func_obj->func_closure = closure;
    }
    if (oparg & 0x04) {
        assert(PyTuple_CheckExact(annotations));
        func_obj->func_annotations = annotations;
    }
    if (oparg & 0x02) {
        assert(PyDict_CheckExact(kwdefaults));
        func_obj->func_kwdefaults = kwdefaults;
    }
    if (oparg & 0x01) {
        assert(PyTuple_CheckExact(defaults));
        func_obj->func_defaults = defaults;
    }

    func_obj->func_version = ((PyCodeObject *)codeobj)->co_version;
    // 函數(shù)創(chuàng)建之后,將棧里的元素彈出,然后將函數(shù)對象壓入棧中
    func = (PyObject *)func_obj;
    #line 4534 "Python/generated_cases.c.h"
    STACK_SHRINK(((oparg & 0x01) ? 1 : 0) + ((oparg & 0x02) ? 1 : 0) + 
        ((oparg & 0x04) ? 1 : 0) + ((oparg & 0x08) ? 1 : 0));
    stack_pointer[-1] = func;
    DISPATCH();
}

整個步驟很好理解,然后創(chuàng)建函數(shù)體用的是 PyFunction_New,看一下它的邏輯。

// Objects/funcobject.c
PyObject *
PyFunction_New(PyObject *code, PyObject *globals)
{
    return PyFunction_NewWithQualName(code, globals, NULL);
}


PyObject *
PyFunction_NewWithQualName(PyObject *code, 
                           PyObject *globals, 
                           PyObject *qualname)
{
    assert(globals != NULL);
    assert(PyDict_Check(globals));
    // 給全局名字空間增加引用計(jì)數(shù)
    Py_INCREF(globals);
    // 獲取線程狀態(tài)對象
    PyThreadState *tstate = _PyThreadState_GET();
    // 給 PyCodeObject 對象增加引用計(jì)數(shù)
    PyCodeObject *code_obj = (PyCodeObject *)Py_NewRef(code);

    assert(code_obj->co_name != NULL);
    // 獲取 co_name 并增加引用計(jì)數(shù)
    PyObject *name = Py_NewRef(code_obj->co_name);
    
    // 獲取 co_qualname,如果存在,增加引用計(jì)數(shù)
    if (!qualname) {
        qualname = code_obj->co_qualname;
    }
    assert(qualname != NULL);
    Py_INCREF(qualname);
    
    // 獲取常量池
    PyObject *consts = code_obj->co_consts;
    assert(PyTuple_Check(consts));
    // 函數(shù)的 docstring 也會被收集到常量池中,并且是常量池的第一個元素
    // 如果函數(shù)沒有 docstring,那么常量池的第一個元素會是 None
    PyObject *doc;
    if (PyTuple_Size(consts) >= 1) {
        doc = PyTuple_GetItem(consts, 0);
        // 如果第一個元素不是字符串,則說明函數(shù)沒有 docstring
        if (!PyUnicode_Check(doc)) {
            doc = Py_None;
        }
    }
    else {
        doc = Py_None;
    }
    Py_INCREF(doc);

    // 獲取 __module__,并增加引用計(jì)數(shù)
    PyObject *module = PyDict_GetItemWithError(
            globals, &_Py_ID(__name__));
    PyObject *builtins = NULL;
    if (module == NULL && _PyErr_Occurred(tstate)) {
        goto error;
    }
    Py_XINCREF(module);
    // 獲取 __builtins__,并增加引用計(jì)數(shù)
    builtins = _PyEval_BuiltinsFromGlobals(tstate, globals);
    if (builtins == NULL) {
        goto error;
    }
    Py_INCREF(builtins);
    
    // 為函數(shù)對象申請內(nèi)存空間
    PyFunctionObject *op = PyObject_GC_New(
            PyFunctionObject, &PyFunction_Type);
    if (op == NULL) {
        goto error;
    }
    
    // 初始化函數(shù)對象的內(nèi)部屬性
    op->func_globals = globals;
    op->func_builtins = builtins;
    op->func_name = name;
    op->func_qualname = qualname;
    op->func_code = (PyObject*)code_obj;
    op->func_defaults = NULL;    
    op->func_kwdefaults = NULL;  
    op->func_closure = NULL;
    op->func_doc = doc;
    op->func_dict = NULL;
    op->func_weakreflist = NULL;
    op->func_module = module;
    op->func_annotations = NULL;
    op->func_typeparams = NULL;
    op->vectorcall = _PyFunction_Vectorcall;
    op->func_version = 0;
    _PyObject_GC_TRACK(op);
    handle_func_event(PyFunction_EVENT_CREATE, op, NULL);
    return (PyObject *)op;

error:
    Py_DECREF(globals);
    Py_DECREF(code_obj);
    Py_DECREF(name);
    Py_DECREF(qualname);
    Py_DECREF(doc);
    Py_XDECREF(module);
    Py_XDECREF(builtins);
    return NULL;
}

以上就是函數(shù)對象的創(chuàng)建過程,說白了就是對 PyCodeObject 進(jìn)行了一個封裝。等函數(shù)對象創(chuàng)建完畢后會回到 MAKE_FUNCTION,然后設(shè)置閉包、注解等屬性,并將函數(shù)對象壓入棧中。接著執(zhí)行 STORE_NAME 從符號表中加載符號(函數(shù)名),并從棧頂彈出函數(shù)對象,然后將兩者組成鍵值對存儲在當(dāng)前棧幀的 local 名字空間中,整體還是比較簡單的。

但如果再加上類型注解、以及默認(rèn)值,會有什么效果呢?

import dis

code_string = """
name = "satori"
def foo(a: int = 1, b: int = 2):
    print(a, b)
"""

dis.dis(compile(code_string, "<func>", "exec"))

我們看看加上了類型注解和默認(rèn)值之后,它的字節(jié)碼指令會有什么變化?

0 RESUME                   0

 2 LOAD_CONST               0 ('satori')
 4 STORE_NAME               0 (name)

 6 LOAD_CONST               5 ((1, 2))
 8 LOAD_CONST               1 ('a')
10 LOAD_NAME                1 (int)
12 LOAD_CONST               2 ('b')
14 LOAD_NAME                1 (int)
16 BUILD_TUPLE              4
18 LOAD_CONST               3 (<code object foo at 0x7f...>)
20 MAKE_FUNCTION            5 (defaults, annotations)
22 STORE_NAME               2 (foo)
......

不難發(fā)現(xiàn),在構(gòu)建函數(shù)時(shí)會先將默認(rèn)值以元組的形式壓入運(yùn)行時(shí)棧;然后再將使用了類型注解的參數(shù)和類型也構(gòu)建一個元組,并壓入運(yùn)行時(shí)棧。

后續(xù)創(chuàng)建函數(shù)的時(shí)候,會將默認(rèn)值保存在 func_defaults 字段中,類型注解對應(yīng)的字典會保存在 func_annotations 字段中。

def foo(a: int = 1, b: int = 2):
    print(a, b)

print(foo.__defaults__)
"""
(1, 2)
"""
print(foo.__annotations__)
"""
{'a': <class 'int'>, 'b': <class 'int'>}
"""

基于類型注解,我們便可以額外施加一些手段,讓 Python 像靜態(tài)語言一樣,實(shí)現(xiàn)函數(shù)參數(shù)的類型約束。

聊一聊函數(shù)名

這里再說一下函數(shù)名,舉個例子。

def foo():
    pass

print(foo.__name__)  # foo

bar = foo
print(bar.__name__)  # foo

我們定義了一個函數(shù) foo,那么函數(shù)名就是 foo,這是沒問題的,但怎么理解 bar 呢?

所以嚴(yán)格意義上講,代碼中的 foo 應(yīng)該是一個變量。之前說過,定義函數(shù)、類、導(dǎo)入模塊,其實(shí)都是創(chuàng)建了一個變量。所以代碼中的 foo 也是一個變量,它指向了函數(shù)對象,而函數(shù)的名字是保存在函數(shù)對象里面的。

code_string = """
def foo():
    pass
"""

code_obj = compile(code_string, "<func>", "exec")
# 我們是以模塊的形式編譯的,它里面只有一個變量 foo
# 所以符號表就是 ('foo',)
print(code_obj.co_names)  # ('foo',)

# 然后常量池里面存在一個 PyCodeObject
# 這個 PyCodeObject 便是函數(shù)對應(yīng)的 PyCodeObject
print(code_obj.co_consts[0])  # <code object foo ...>
print(code_obj.co_consts[0].co_name)  # foo

# 構(gòu)建函數(shù)時(shí),PyCodeObject 的 co_name 會被賦值給函數(shù)的 func_name
# 所以嚴(yán)格意義上講,def foo() 中的 foo 只能算做是變量名
# 而真正的函數(shù)名是函數(shù)對象的 func_name,它來自于 co_name
# 只不過在編譯成 PyCodeObject 對象時(shí),會進(jìn)行詞法分析
# 因?yàn)?def 后面是 foo,所以編譯之后的 PyCodeObject 的 co_name 也是 foo

# 當(dāng)然其它對象也是如此
class A:
    pass

# 這里的 A 指向了類型對象,但類型對象的名稱是保存在類型對象里面的
print(A.__name__)  # A
# A.__name__ 才是類名,class 后面的 A 只是一個變量名

# 這里同樣創(chuàng)建了一個類
B = type("B1", (object,), {})
print(B.__name__)  # B1
# 但是我們看到類名不是 B,而是 B1
# 所以我們需要明白,不管是變量賦值、還是定義函數(shù)、類、方法,導(dǎo)入模塊
# 我們得到的只是一個變量,這個變量指向了具體的對象(它們是字典中的一個鍵值對)
# 而對象的名稱、類型等信息,都保存在對象里面,和變量無關(guān)
# 因?yàn)樽兞恐皇且粋€符號,或者理解為代號,每個對象都可以有不同的代號

def foo():
    pass

# 名稱也可以自由更改
foo.__name__ = "foo1"
# 在更改過后,函數(shù)的名字就變成了 foo1
print(foo.__name__)  # foo1

# bar = foo 之后,這個函數(shù)對象就有了兩個代號,你通過 foo 和 bar 都可以找到它
# 但函數(shù)對象的名字是不變的,還是 foo1,因?yàn)樗?__name__ 屬性的值是 foo1
bar = foo
print(bar.__name__)  # foo1

我們之前說變量只是一個和對象綁定的符號,或者說代號,運(yùn)行時(shí)會和某個對象(的地址)組成鍵值對保存在字典中。虛擬機(jī)通過變量可以找到它代表的對象,本質(zhì)上就是將變量名作為 key,去字典中檢索 value。至于獲取到的對象叫什么名字,是保存在對象里面的。

如果變量指向的是整數(shù)、字符串等,那么該對象就沒有名字。如果指向的是函數(shù)、類、模塊,那么對象的 __name__ 就是對象的名字。只不過在默認(rèn)情況下,定義函數(shù)(以及類)時(shí),變量名默認(rèn)和函數(shù)名是一樣的,所以我們會把指向函數(shù)對象的變量的名稱也叫做函數(shù)名。

關(guān)于這一點(diǎn),大家一定要清晰。

name = "古明地覺"

def foo():
    pass

class A:
    pass

import os

print("name" in locals())  # True
print("foo" in locals())  # True
print("A" in locals())  # True
print("os" in locals())  # True

這里的 name、foo、A、os 都是變量,站在虛擬機(jī)的角度,它們沒有任何的不同,只不過指向的對象不同罷了。而站在 Python 的角度,它們也是一樣的,其名稱都是字典里的一個 key,只不過關(guān)聯(lián)的 value 不同罷了。

比如 name 指向的是字符串對象,foo 指向的是函數(shù)對象,A 指向的是類對象,os 指向的是模塊對象。但我們也可以改變指向,比如讓 foo 指向類對象,A 指向字符串對像等等,都是可以的。

總結(jié):變量只是一個指針,可以保存任意對象的地址,也就是可以指向任意的對象。而對象的名字、類型等一切信息,都保存在對象中,和變量無關(guān)。

當(dāng)然這些都是之前說過的內(nèi)容,再來回顧一下,總之一定要了解 Python 變量的本質(zhì)。

函數(shù)的一些騷操作

我們通過一些騷操作,來更好地理解一下函數(shù)。

之前說 <class 'function'> 是函數(shù)的類型對象,而這個類底層沒有暴露給我們,但我們依舊可以通過曲線救國的方式進(jìn)行獲取。

def foo():
    pass

print(type(foo))  # <class 'function'>
# lambda 匿名函數(shù)的類型也是 function
print(type(lambda: None))  # <class 'function'>

那么下面就來創(chuàng)建函數(shù):

gender = "female"

def foo(name, age):
    return f"name: {name}, age: {age}, gender: {gender}"

# 得到 PyCodeObject 對象
code = foo.__code__
# 根據(jù) class function 創(chuàng)建函數(shù)對象
# 接收三個參數(shù): PyCodeObject 對象、名字空間、函數(shù)名
new_foo = type(foo)(code, globals(), "根據(jù) foo 創(chuàng)建的 new_foo")

# 打印函數(shù)名
print(new_foo.__name__)
"""
根據(jù) foo 創(chuàng)建的 new_foo
"""

# 調(diào)用函數(shù)
print(new_foo("古明地覺", 17))
"""
name: 古明地覺, age: 17, gender: female
"""

是不是很神奇呢?另外函數(shù)之所以能訪問全局變量,是因?yàn)樵趧?chuàng)建函數(shù)的時(shí)候?qū)?global 名字空間傳進(jìn)去了,如果我們不傳遞呢?

gender = "female"

def foo(name, age):
    return f"name: {name}, age: {age}, gender: {gender}"

code = foo.__code__
# 第二個參數(shù)必須是一個字典,不能傳 None
new_foo = type(foo)(code, {}, "根據(jù) foo 創(chuàng)建的 new_foo")

try:
    print(new_foo("古明地覺", 17))
except NameError as e:
    print(e)  # name 'gender' is not defined

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

gender = "female"

def foo(name, age):
    return f"name: {name}, age: {age}, gender: {gender}"

code = foo.__code__
new_foo = type(foo)(code, {"gender": "萌妹子"}, "根據(jù) foo 創(chuàng)建的 new_foo")

# 我們可以手動傳遞一個字典進(jìn)去
# 此時(shí)傳遞的字典對于函數(shù)來說就是 global 名字空間
print(new_foo("古明地覺", 17))
"""
name: 古明地覺, age: 17, gender: 萌妹子
"""
# 所以此時(shí)的 gender 不再是外部的 "female", 而是我們指定的 "萌妹子"

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

def foo(name, age, gender):
    return f"name: {name}, age: {age}, gender: {gender}"

# 必須接收一個 PyTupleObject 對象
foo.__defaults__ = ("古明地覺", 17, "female")
print(foo())
"""
name: 古明地覺, age: 17, gender: female
"""

我們看到函數(shù) foo 明明接收三個參數(shù),但是調(diào)用時(shí)不傳遞居然也不會報(bào)錯,原因就在于我們指定了默認(rèn)值。而默認(rèn)值可以在定義函數(shù)的時(shí)候指定,也可以通過 __defaults__ 指定,但很明顯我們應(yīng)該通過前者來指定。

如果你使用的是 PyCharm,那么會在 foo() 這個位置給你加波浪線,提示你參數(shù)沒有傳遞。但我們知道,由于通過 __defaults__ 設(shè)置了默認(rèn)值,所以這里是不會報(bào)錯的。只不過 PyCharm 沒有檢測到,當(dāng)然基本上所有的 IDE 都無法做到這一點(diǎn),畢竟動態(tài)語言。

另外如果 __defaults__ 接收的元組里面的元素個數(shù)和參數(shù)個數(shù)不匹配怎么辦?

def foo(name, age, gender):
    return f"name: {name}, age: {age}, gender: {gender}"

foo.__defaults__ = (15, "female")
print(foo("古明地戀"))
"""
name: 古明地戀, age: 15, gender: female
"""

由于元組里面只有兩個元素,意味著我們在調(diào)用時(shí)需要至少傳遞一個參數(shù),而這個參數(shù)會賦值給 name。原因就是在設(shè)置默認(rèn)值的時(shí)候是從后往前設(shè)置的,也就是 "female" 會賦值給 gender,15 會賦值給 age。而 name 沒有得到默認(rèn)值,那么它就需要調(diào)用者顯式傳遞了。

如果返回值從前往后設(shè)置的話,會出現(xiàn)什么后果?顯然 15 會賦值給 name,"female" 會賦值給 age,此時(shí)函數(shù)就等價(jià)于如下:

def foo(name=15, age="female", gender):
    return f"name: {name}, age: {age}, gender: {gender}"

這樣的函數(shù)顯然無法通過編譯,因?yàn)槟J(rèn)參數(shù)必須在非默認(rèn)參數(shù)的后面。所以 Python 的這個做法是完全正確的,必須要從后往前進(jìn)行設(shè)置。

另外我們知道默認(rèn)值的個數(shù)是小于等于參數(shù)個數(shù)的,如果大于會怎么樣呢?

def foo(name, age, gender):
    return f"name: {name}, age: {age}, gender: {gender}"

foo.__defaults__ = ("古明地覺", "古明地戀", 15, "female")
print(foo())
"""
name: 古明地戀, age: 15, gender: female
"""

依舊是從后往前進(jìn)行設(shè)置,當(dāng)所有參數(shù)都有默認(rèn)值時(shí),就結(jié)束了,多余的默認(rèn)值會丟棄。當(dāng)然,如果不使用 __defaults__,是不可能出現(xiàn)默認(rèn)值個數(shù)大于參數(shù)個數(shù)的。可要是 __defaults__ 指向的元組先結(jié)束,那么沒有得到默認(rèn)值的參數(shù)就必須由調(diào)用者顯式傳遞了。

最后,再來說一下如何深拷貝一個函數(shù)。首先如果是你的話,你會怎么拷貝一個函數(shù)呢?不出意外的話,你應(yīng)該會使用 copy 模塊。

import copy

def foo(a, b):
    return [a, b]

# 但是問題來了,這樣能否實(shí)現(xiàn)深度拷貝呢?
new_foo = copy.deepcopy(foo)
# 修改 foo 的默認(rèn)值
foo.__defaults__ = (2, 3)
# 但是 new_foo 也會受到影響
print(new_foo())  # [2, 3]

打印結(jié)果提示我們并沒有實(shí)現(xiàn)函數(shù)的深度拷貝,事實(shí)上 copy 模塊無法對函數(shù)、方法、回溯棧、棧幀、模塊、文件、套接字等類型實(shí)現(xiàn)深度拷貝。那我們應(yīng)該怎么做呢?

from types import FunctionType

def foo(a, b):
    return "result"

# FunctionType 就是函數(shù)的類型對象
# 它也是通過 type 得到的
new_foo = FunctionType(foo.__code__,
                       foo.__globals__,
                       foo.__name__,
                       foo.__defaults__,
                       foo.__closure__)
# 顯然 function 還可以接收第四個參數(shù)和第五個參數(shù)
# 分別是函數(shù)的默認(rèn)值和閉包

# 然后別忘記將屬性字典也拷貝一份
# 由于函數(shù)的屬性字典幾乎用不上,這里就淺拷貝了
new_foo.__dict__.update(foo.__dict__)

foo.__defaults__ = (2, 3)
print(foo.__defaults__)  # (2, 3)
print(new_foo.__defaults__)  # None

此時(shí)修改 foo 不會影響 new_foo,當(dāng)然在拷貝的時(shí)候也可以自定義屬性。

其實(shí)上面實(shí)現(xiàn)的深拷貝,本質(zhì)上就是定義了一個新的函數(shù)。由于是兩個不同的函數(shù),那么自然就沒有聯(lián)系了。

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

最后再來看看如何檢測一個函數(shù)有哪些參數(shù),首先函數(shù)的局部變量(包括參數(shù))在編譯時(shí)就已經(jīng)確定,會存在符號表 co_varnames 中。

def foo(a, b, /, c, d, *args, e, f, **kwargs):
    g = 1
    h = 2

print(foo.__code__.co_varnames)
"""
('a', 'b', 'c', 'd', 'e', 'f', 'args', 'kwargs', 'g', 'h')
"""

在定義函數(shù)的時(shí)候,* 和 ** 最多只能出現(xiàn)一次。然后這里的 a 和 b 必須通過位置參數(shù)傳遞,c 和 d 可以通過位置參數(shù)或者關(guān)鍵字參數(shù)傳遞,e 和 f 必須通過關(guān)鍵字參數(shù)傳遞。

而從打印的符號表來看,里面的符號是有順序的。參數(shù)永遠(yuǎn)在函數(shù)內(nèi)部定義的局部變量的前面,比如 g 和 h 就是函數(shù)內(nèi)部定義的局部變量,所以它在所有參數(shù)的后面。而對于參數(shù),* 和 ** 會位于最后面,其它參數(shù)位置不變。所以除了 g 和 h,最后面的就是 args 和 kwargs。

有了這些信息,我們就可以進(jìn)行檢測了。

def foo(a, b, /, c, d, *args, e, f, **kwargs):
    g = 1
    h = 2

varnames = foo.__code__.co_varnames
# 1. 尋找必須通過位置參數(shù)傳遞的參數(shù)
posonlyargcount = foo.__code__.co_posonlyargcount
print(posonlyargcount)  # 2
print(varnames[: posonlyargcount])  # ('a', 'b')

# 2. 尋找可以通過位置參數(shù)或者關(guān)鍵字參數(shù)傳遞的參數(shù)
argcount = foo.__code__.co_argcount
print(argcount)  # 4
print(varnames[: argcount])  # ('a', 'b', 'c', 'd')
print(varnames[posonlyargcount: argcount])  # ('c', 'd')

# 3. 尋找必須通過關(guān)鍵字參數(shù)傳遞的參數(shù)
kwonlyargcount = foo.__code__.co_kwonlyargcount
print(kwonlyargcount)  # 2
print(varnames[argcount: argcount + kwonlyargcount])  # ('e', 'f')

# 4. 尋找 *args 和 **kwargs
flags = foo.__code__.co_flags
# 在介紹 PyCodeObject 的時(shí)候,我們說里面有一個 co_flags 成員
# 它是函數(shù)的標(biāo)識,可以對函數(shù)類型和參數(shù)進(jìn)行檢測
# 如果 co_flags 和 4 按位與之后為真,那么就代表有 *args,否則沒有
# 如果 co_flags 和 8 按位與之后為真,那么就代表有 **kwargs,否則沒有
step = argcount + kwonlyargcount
if flags & 0x04:
    print(varnames[step])  # args
    step += 1

if flags & 0x08:
    print(varnames[step])  # kwargs

以上我們就檢測出了函數(shù)都有哪些參數(shù),你也可以將其封裝成一個函數(shù),實(shí)現(xiàn)代碼的復(fù)用。然后還要注意一點(diǎn),如果我們定義的時(shí)候不是 *args,而只是一個 *,那么它就不是參數(shù)了。

def f(a, b, *, c):
    pass


# 符號表里面只有 a、b、c
print(f.__code__.co_varnames)  # ('a', 'b', 'c')

# 顯然此時(shí)也都為假
print(f.__code__.co_flags & 0x04)  # 0
print(f.__code__.co_flags & 0x08)  # 0

單獨(dú)的一個 * 只是為了強(qiáng)制要求后面的參數(shù)必須通過關(guān)鍵字參數(shù)的方式傳遞。

小結(jié)

這一次我們簡單地分析了一下函數(shù)是如何創(chuàng)建的,并且還在 Python 的層面上做了一些小 trick。最后我們也分析了如何通過 PyCodeObject 對象來檢索函數(shù)的參數(shù),以及相關(guān)種類,標(biāo)準(zhǔn)庫中的 inspect 模塊也是這么做的。準(zhǔn)確的說,是我們模仿人家的思路做的。

現(xiàn)在你是不是對函數(shù)有了一個更深刻的認(rèn)識了呢?當(dāng)然目前介紹的只是函數(shù)的一部分內(nèi)容,還有更多內(nèi)容等待我們挖掘,比如:

  • 函數(shù)如何調(diào)用。
  • 位置參數(shù)和關(guān)鍵字參數(shù)如何解析。
  • 對于有默認(rèn)值的參數(shù),如何在不傳參的時(shí)候使用默認(rèn)值、在傳參的時(shí)候使用我們傳遞的值。
  • *args 和 **kwargs 如何解析。
  • 閉包怎么實(shí)現(xiàn)。
  • 裝飾器怎么實(shí)現(xiàn)
  • ......
責(zé)任編輯:武曉燕 來源: 古明地覺的編程教室
相關(guān)推薦

2024-10-20 13:28:47

虛擬機(jī)字節(jié)碼CPU

2024-05-21 12:51:06

Python對象PyObject

2024-08-29 12:37:11

2024-05-22 13:04:46

Python對象關(guān)系

2023-10-30 23:14:57

瀏覽器URL網(wǎng)頁

2016-01-29 10:32:32

KDEKDE PlatforQt 框架

2017-03-29 15:50:09

AndroidApp框架

2018-09-14 14:20:43

人肉智能運(yùn)維

2025-10-09 07:25:00

2020-12-09 08:12:30

系統(tǒng)架構(gòu)

2020-10-27 07:29:43

架構(gòu)系統(tǒng)流量

2020-08-26 09:05:03

函數(shù)編譯詞法

2019-08-26 09:15:09

設(shè)計(jì)技術(shù)人生第一份工作

2020-05-28 16:54:51

自動駕駛谷歌華為

2020-01-02 08:29:14

互聯(lián)網(wǎng)網(wǎng)約車海康威視

2010-04-16 10:11:20

Oracle存儲過程

2010-11-19 09:48:48

ORACLE創(chuàng)建實(shí)例

2025-02-12 10:06:25

2022-05-12 13:03:00

DLT分布式賬本加密貨幣

2025-06-13 08:40:00

ShuffleSpark大數(shù)據(jù)
點(diǎn)贊
收藏

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

午夜精品毛片| 欧美第一视频| 成人国产精品视频| 热re99久久精品国产66热| 30一40一50老女人毛片| 色豆豆成人网| 亚洲精品第一国产综合野| 国产日韩二区| 最新黄色网址在线观看| 欧美精品一卡| 国产一区二区日韩| 丰满少妇中文字幕| 中文字幕 在线观看| 国产精品理论片在线观看| 成人欧美一区二区三区视频 | 午夜精品一区二| 久久中文字幕av| 亚洲激情在线观看视频免费| 色片在线免费观看| 国产资源在线观看入口av| 国产精品天天摸av网| 国产精品一区二| 一级黄色免费片| 亚洲国产高清一区二区三区| 中文字幕亚洲欧美日韩2019| 国产精品无码在线| 精品91福利视频| 欧美视频一二三区| 3d动漫一区二区三区| 18+激情视频在线| 国产精品色噜噜| 欧美日韩国产不卡在线看| 精品久久久免费视频| 青青草伊人久久| 69视频在线播放| 久久这里只有精品免费| 99久久综合| 国产午夜精品视频免费不卡69堂| 99热超碰在线| 亚洲一区二区三区四区电影| 欧美男男青年gay1069videost | 欧美成人免费观看视频| 日韩中文在线电影| 亚洲天堂视频在线观看| 国产黑丝一区二区| www.成人网| 欧美一区二区视频观看视频| 亚洲精品20p| 精品国产欧美日韩一区二区三区| 欧美日韩亚洲成人| www..com日韩| 97超碰免费在线| 亚洲电影在线播放| 免费网站永久免费观看| 日本三级在线观看网站| 亚洲精选视频在线| 成人在线观看毛片| 91网在线看| 亚洲精品视频自拍| 欧美另类videosbestsex日本| 免费在线看黄色| 国产精品久久久久一区二区三区共| 日韩精品欧美一区二区三区| 国产爆初菊在线观看免费视频网站 | 国产精品对白交换视频| 制服诱惑一区| 成人黄色在线电影| 一区二区三区欧美| 久操网在线观看| 新版的欧美在线视频| 色婷婷综合久久久中文一区二区| 日本va中文字幕| 成人在线高清| 制服丝袜av成人在线看| 老女人性生活视频| 99精品中文字幕在线不卡| 精品成人佐山爱一区二区| 中文字幕人妻一区二区三区| 亚洲第一二三区| 亚洲欧美制服丝袜| 国产乱子轮xxx农村| 中文字幕人成人乱码| 久久久亚洲影院你懂的| 国产成人精品片| 日韩福利视频导航| 亚洲一区中文字幕| 日本精品久久久久| 欧美高清在线一区二区| 久久久成人精品一区二区三区| 黄页在线观看免费| 日韩欧美在线观看视频| 在线观看av免费观看| 国产丝袜一区| 综合久久五月天| 国产亚洲欧美精品久久久www| 日韩午夜免费| 国产日韩在线亚洲字幕中文| 欧美 中文字幕| 国产欧美日韩中文久久| 国产精品国产三级国产专区51| 欧亚在线中文字幕免费| 欧美浪妇xxxx高跟鞋交| a级一a一级在线观看| 成人3d动漫在线观看| 欧美激情欧美激情在线五月| 色老头一区二区| 国产成人av一区二区三区在线| 免费av在线一区二区| 国产美女av在线| 欧美性高跟鞋xxxxhd| 在线观看中文av| 国产亚洲电影| 国内偷自视频区视频综合| 中文字幕在线播放日韩| 99在线精品观看| 免费看污污视频| 精品免费av一区二区三区| 精品国产一区二区三区四区四| 精品日韩在线视频| 一本综合精品| 亚洲在线观看视频| 337p日本欧洲亚洲大胆鲁鲁| 亚洲444eee在线观看| 中文字幕剧情在线观看| 精品久久久久久久| 69av在线播放| 丰满少妇被猛烈进入| 成人免费在线观看入口| 男人插女人下面免费视频| 日韩高清一级| 久久久久久国产精品| 国产精品人人爽| 国产三级欧美三级日产三级99| 免费不卡av在线| 日本超碰一区二区| 久久精品久久久久久国产 免费| 波多野结衣毛片| 久久免费偷拍视频| 日韩中文字幕在线视频观看| 北条麻妃一区二区三区在线| 美女少妇精品视频| 国产精品久久免费| 国产精品久久久久久久久免费樱桃| 色综合av综合无码综合网站| 日韩高清一级| 欧美在线观看视频| 日韩a在线观看| 岛国精品视频在线播放| 国产精品无码一区二区三区免费| 亚洲国产国产亚洲一二三| 国产精品一区免费观看| av美女在线观看| 亚洲电影av在线| 好吊妞视频一区二区三区| 99久久婷婷国产| 精品久久久久久久久久中文字幕| 欧美大胆视频| 日本精品久久久| 毛片网站在线观看| 欧美亚洲禁片免费| 91动漫免费网站| 国产一区激情在线| www.国产亚洲| 国产在线播放精品| 欧美综合第一页| 成人福利在线| 欧美福利一区二区| 久久这里只有精品国产| av网站免费线看精品| 男人操女人免费| 日韩成人免费| 亚洲自拍欧美另类| 波多野结衣乳巨码无在线观看| 精品粉嫩超白一线天av| 毛片视频网站在线观看| 国产欧美日韩精品在线| 亚洲精品视频三区| 在线观看一区| 欧美日韩一区二区视频在线| 欧美成人毛片| 欧美日韩国产999| 日中文字幕在线| 欧美手机在线视频| 久久午夜无码鲁丝片午夜精品| 99re这里都是精品| 日韩av片网站| 国产综合精品一区| 秋霞在线观看一区二区三区| 亚洲一区导航| 91福利视频在线观看| 香蕉视频网站在线观看| 精品欧美一区二区在线观看| 中文字幕在线看人| 亚洲人成精品久久久久久| 中文字幕一区二区三区乱码不卡| 日韩电影在线一区| 成人在线视频一区二区三区| 亚洲影院天堂中文av色| 91九色精品视频| 亚洲最新无码中文字幕久久| 久久亚洲国产精品成人av秋霞| 色婷婷av一区二区三区之红樱桃| 在线观看视频91| 国产精品9191| 国产精品国产三级国产aⅴ中文| jjzzjjzz欧美69巨大| 麻豆精品国产91久久久久久| 日韩a∨精品日韩在线观看| 日韩久久精品网| 久久精品日产第一区二区三区乱码| 男人亚洲天堂| 日韩av成人在线观看| 日本在线视频中文有码| 中日韩美女免费视频网址在线观看| 免费观看国产精品| 欧美区在线观看| youjizz在线视频| 亚洲激情av在线| jizz18女人高潮| 久久丝袜美腿综合| 亚洲男女在线观看| 东方aⅴ免费观看久久av| 91看片破解版| 免费的国产精品| 精品一区二区中文字幕| 韩国一区二区三区在线观看| 色香蕉在线观看| 日韩成人精品一区二区| 欧美少妇一区| 亚州国产精品| 国产日韩二区| 久久99偷拍| 国产精品一区二区欧美| 欧美一级大片在线视频| 国产欧美va欧美va香蕉在| 国产另类xxxxhd高清| 欧美综合一区第一页| 日韩伦理在线一区| 91黑丝在线观看| 91在线三级| 久久久久亚洲精品国产| 污视频网站免费在线观看| 久久影视免费观看| 黄色在线视频网站| 久久夜色精品国产亚洲aⅴ| 日本高清中文字幕在线| 日韩视频在线观看免费| 天堂地址在线www| 中文字幕亚洲综合| 午夜在线小视频| 久久久精品在线| 久久久久久久久免费视频| 久久久av免费| 91精选在线| 九色精品免费永久在线| 女人天堂av在线播放| 欧美国产日韩在线| 成入视频在线观看| 2019中文在线观看| 亚洲va中文在线播放免费| 国产成人免费av| 黄色成人小视频| 成人免费淫片aa视频免费| 精品国产亚洲一区二区三区大结局 | 亚洲精品自在在线观看| 图片小说视频色综合| 欧美日韩激情四射| 国产亚洲精品v| 久久久久久久久久久久久国产精品 | 欧美日产国产成人免费图片| 日韩精品分区| 欧洲一区二区视频| 久久人体av| 97av自拍| 亚洲精品国模| 在线观看成人av| 狠狠色狠狠色综合日日tαg| 国产日产欧美视频| 国产在线观看一区二区| 中文字幕一区二区人妻电影丶| 久久精品视频免费| 国产天堂av在线| 偷拍一区二区三区| 亚洲天堂aaa| 精品国产伦一区二区三区免费| 亚洲 欧美 激情 小说 另类| 色多多国产成人永久免费网站| 神马午夜伦理不卡| 欧美自拍大量在线观看| 国产电影一区| 欧美成人一区二区在线| 91精品在线观看国产| 国产av天堂无码一区二区三区| 秋霞国产午夜精品免费视频| 69亚洲乱人伦| 中文在线免费一区三区高中清不卡| 久久精品99国产精| 欧美亚洲动漫精品| 亚洲女同志亚洲女同女播放| 在线观看中文字幕亚洲| 欧美bbbxxxxx| 国产日韩在线视频| 夜夜躁狠狠躁日日躁2021日韩| 欧美三级午夜理伦三级老人| 亚洲欧美成人| 熟妇女人妻丰满少妇中文字幕| 久久久久久久久久久电影| 麻豆一区产品精品蜜桃的特点| 色综合久久六月婷婷中文字幕| 精品国产av 无码一区二区三区 | 欧美在线视频不卡| 粉嫩小泬无遮挡久久久久久| 日日摸夜夜添一区| 自拍视频在线看| 国产a一区二区| 天天影视欧美综合在线观看| www.玖玖玖| 成人福利电影精品一区二区在线观看| 992在线观看| 色综合天天综合色综合av| 成人午夜免费在线观看| 久久综合国产精品台湾中文娱乐网| 日本精品不卡| 久久大香伊蕉在人线观看热2| 综合久久十次| 国产3p在线播放| 国产精品狼人久久影院观看方式| 国产精品久久久久久久久夜色| 亚洲韩国欧洲国产日产av| 日本理论片午伦夜理片在线观看| 成人激情视频在线播放| 日韩精品一区二区久久| 国内外免费激情视频| 97se狠狠狠综合亚洲狠狠| 国产一级视频在线观看| 日韩女优电影在线观看| 中文在线字幕免费观看| 91精品视频大全| 91精品国产91久久综合| 亚洲人视频在线| 国产精品乱码人人做人人爱| 亚洲在线观看av| 日韩在线免费av| 欧美午夜三级| 资源网第一页久久久| 久久99久久99精品免视看婷婷| 国产精品69久久久久孕妇欧美| 欧美色视频在线| 夜级特黄日本大片_在线 | 我要色综合中文字幕| 色撸撸在线观看| 国产精品一色哟哟哟| 男女做暖暖视频| 日韩一区二区免费在线电影| 国产鲁鲁视频在线观看特色| 亚洲自拍偷拍网址| 激情丁香综合| 国产精品一级黄片| 色噜噜偷拍精品综合在线| 国产剧情在线观看| 国产一区二区丝袜高跟鞋图片| 午夜激情久久| 日本女人性视频| 天天影视涩香欲综合网| 美女欧美视频在线观看免费 | 无码人妻丰满熟妇区五十路百度| www日韩大片| 中文字幕精品一区二| 久久精品国产欧美激情| 日韩中文字幕视频网| 欧美在线一区视频| 国产喂奶挤奶一区二区三区| 中文在线字幕av| 欧美成年人视频网站| 久久99精品国产自在现线| 欧美国产日韩在线播放| 亚洲色图在线看| 全国男人的天堂网| 国产精品va在线| 91精品99| 国产又爽又黄无码无遮挡在线观看| 在线观看视频91| 久草在线视频资源| 日韩hmxxxx| 国产盗摄女厕一区二区三区| 男人日女人网站| 久热爱精品视频线路一| 欧美日韩一本| 亚洲精品成人在线播放| 亚洲丰满少妇videoshd| 95在线视频| 精品国产乱码久久久久软件 | 99精品欧美一区二区三区| 亚洲欧美日韩国产| 久久久久亚洲av片无码| 国产丝袜一区二区| 美女精品视频在线| 久草综合在线观看| 亚洲不卡一区二区三区| 日本免费在线视频|