成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

再探泛型 API,感受 Python 對象的設計哲學

開發 前端
如果一個對象支持迭代器操作,那么它的類型對象一定實現了 __iter__,通過 type(data) 可以獲取到類型對象,然后將 data 作為參數調用 __iter__ 即可。

之前我們提到了泛型 API,這類 API 的特點是可以處理任意類型的對象,舉個例子。

// 返回對象的長度
PyObject_Size
// 返回對象的某個屬性
PyObject_GetAttr
// 返回對象的哈希值
PyObject_Hash
// 將對象轉成字符串返回
PyObject_Str

對應到 Python 代碼中,就是下面這個樣子。

# PyObject_Size
print(len("古明地覺"))
print(len([1, 2, 3]))
"""
4
3
"""

# PyObject_GetAttr
print(getattr("古明地覺", "lower"))
print(getattr([1, 2, 3], "append"))
print(getattr({}, "update"))
"""
<built-in method lower of str object at 0x7f081aa7e920>
<built-in method append of list object at 0x7f081adc1100>
<built-in method update of dict object at 0x7f081aa8fd80>
"""

# PyObject_Hash
print(hash("古明地覺"))
print(hash(2.71))
print(hash(123))
"""
8152506393378233203
1637148536541722626
123
"""

# PyObject_Str
print(str("古明地覺"))
print(str(object()))
"""
古明地覺
<object object at 0x7fdfa0209d10>
"""

這些 API 能處理任意類型的對象,這究竟是怎么辦到的?要想搞清楚這一點,還是要從 PyObject 入手。

我們知道對象在 C 看來就是一個結構體實例,并且結構體嵌套了 PyObject。

# 創建一個列表,讓變量 var 指向它
var = [1, 2, 3]
# 創建一個浮點數,讓變量 var 指向它
var = 2.71

列表對應的結構體是 PyListObject,浮點數對應的結構體是 PyFloatObject,變量 var 是指向對象的指針。那么問題來了,憑啥一個變量可以指向不同類型的對象呢?或者說變量和容器里面為什么可以保存不同對象的指針呢?

原因在前面的文章中解釋的很詳細了,因為對象的指針會統一轉成 PyObject * 之后再交給變量保存,以創建列表為例。

圖片圖片

當然創建浮點數也是同理,因此變量和容器里的元素本質上就是一個泛型指針 PyObject *。而對象的指針在交給變量保存的時候,也都會先轉成 PyObject *,因為不管什么對象,它底層的結構體都嵌套了 PyObject。正是因為這個設計,變量才能指向任意的對象。

圖片圖片

所以 Python 變量相當于一個便利貼,可以貼在任意對象上。

不過問題來了,由于對象的指針會統一轉成 PyObject * 之后再交給變量保存,那么變量怎么知道自己指向的是哪種類型的對象呢?相信你肯定知道答案:通過 ob_type 字段。

圖片圖片

對象對應的結構體可以有很多個字段,比如 PyListObject,但變量能看到的只有前兩個字段。至于之后的字段是什么,則取決于對象的類型,總之對變量來說是不可見的,因為它是 PyObject *。

所以變量會先通過 ob_type 字段獲取對象的類型,如果 ob_type 字段的值為 &PyList_Type,那么變量指向的就是 PyListObject。如果 ob_type 字段的值為 &PyFloat_Type,那么變量指向的就是 PyFloatObject,其它類型同理。

當得到了對象的類型,那么再轉成相應的指針即可,假設 ob_type 是 &PyList_Type,那么變量會再轉成 PyListObject *,這樣就可以操作列表的其它字段了。

所以我們再總結一下:

圖片圖片

變量和容器里的元素只能保存相同的指針類型,而不同類型的對象,其底層的結構體是不同的。但這些結構體無一例外都嵌套了 PyObject,因此它們的指針會統一轉成 PyObject * 之后再交給變量保存。

然后變量在操作對象時,會先通過 ob_type 判斷對象的類型,假如是 &PyList_Type,那么會再轉成 PyListObject *,其它類型同理。

圖片圖片

相信你已經知道為什么泛型 API 可以處理任意類型的對象了,以 PyObject_GetAttr 為例,它內部會調用類型對象的 tp_getattro。

// 等價于 getattr(v, name)
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{   
    // 獲取對象 v 的類型對象
    PyTypeObject *tp = Py_TYPE(v);
    PyObject* result = NULL;
    // 如果類型對象實現了 tp_getattro,那么進行調用
    // 等價于 Python 中的 type(v).__getattr__(v, name)
    if (tp->tp_getattro != NULL) {
        result = (*tp->tp_getattro)(v, name);
    }
    // 否則會退化為 tp_getattr,它要求屬性名稱必須是 C 字符串
    // 不過 tp_getattr 已經廢棄,應該使用 tp_getattro
    else if (tp->tp_getattr != NULL) {
        const char *name_str = PyUnicode_AsUTF8(name);
        if (name_str == NULL) {
            return NULL;
        }
        result = (*tp->tp_getattr)(v, (char *)name_str);
    }
    // 否則說明對象 v 沒有指定屬性
    else {
        PyErr_Format(PyExc_AttributeError,
                    "'%.100s' object has no attribute '%U'",
                    tp->tp_name, name);
    }
    return result;
}

函數先通過 ob_type 找到對象的類型,然后通過類型對象的 tp_getattro 調用對應的屬性查找函數。所以 PyObject_GetAttr 會根據對象的類型,調用不同的屬性查找函數,因此這就是泛型 API 能處理任意對象的秘密。

我們再以 Python 為例:

class A:

    def __getattr__(self, item):
        return f"class:A,item:{item}"

class B:

    def __getattr__(self, item):
        return f"class:B,item:{item}"

a = A()
b = B()
print(getattr(a, "some_attr"))
print(getattr(b, "some_attr"))
"""
class:A,item:some_attr
class:B,item:some_attr
"""
# 以上等價于
print(type(a).__getattr__(a, "some_attr"))
print(type(b).__getattr__(b, "some_attr"))
"""
class:A,item:some_attr
class:B,item:some_attr
"""

在 Python 里的表現和源碼是一致的,我們再舉個 iter 的例子:

data = [1, 2, 3]
print(iter(data))
print(type(data).__iter__(data))
"""
<list_iterator object at 0x7fb8200f29a0>
<list_iterator object at 0x7fb8200f29a0>
"""

如果一個對象支持迭代器操作,那么它的類型對象一定實現了 __iter__,通過 type(data) 可以獲取到類型對象,然后將 data 作為參數調用 __iter__ 即可。

所以通過 ob_type 字段,這些泛型 API 實現了類似多態的效果,一個函數,多種實現。

責任編輯:武曉燕 來源: 古明地覺的編程教室
相關推薦

2022-04-15 09:55:59

Go 泛型Go 程序函數

2020-11-24 13:05:35

Go語言泛型

2021-09-29 18:17:30

Go泛型語言

2024-09-18 13:49:42

2013-05-08 09:12:44

2012-04-24 09:55:29

.NET

2021-06-17 06:51:32

Java泛型Java編程

2023-12-10 22:00:47

STLC++編程

2015-09-24 17:22:10

內容家社交

2017-03-06 16:51:52

Java泛型實現

2016-09-09 12:51:23

PhxSQL原則局限性

2009-09-25 10:03:51

Java泛型

2009-06-24 10:25:25

C#泛型

2009-08-25 14:03:17

2009-08-24 14:43:35

C# 泛型

2009-06-12 15:32:01

Hibernate H

2017-03-03 10:37:07

Java泛型設計

2022-01-05 07:07:37

Go核心設計

2024-04-23 08:23:36

TypeScript泛型Generics

2009-09-09 14:11:58

Scala泛型
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人在线一区二区 | 伊人看片 | 99热视| 国产精品视频久久久久久 | 亚洲一区二区三区视频免费观看 | 久久国产精品色av免费观看 | 综合九九| 日韩中文字幕在线观看 | 黄色一级毛片 | 一区二区三区在线观看视频 | 亚洲欧美日本国产 | 久久久久免费精品国产 | 国产一区亚洲 | 成人伊人网 | jizz在线免费观看 | 欧美13videosex性极品 | 亚洲毛片网站 | 久久av一区 | 手机av免费在线 | 中文字幕欧美一区 | 欧美精品成人一区二区三区四区 | 精品亚洲一区二区三区四区五区 | 国产aaaaav久久久一区二区 | 亚洲第一福利网 | 久久综合一区 | 91精品国产综合久久久动漫日韩 | 国产日产精品一区二区三区四区 | 国产福利在线小视频 | 成人a视频 | 中文字幕一区二区三区不卡 | 男女羞羞视频在线观看 | 国产精品一区二区欧美黑人喷潮水 | 日本免费一区二区三区 | 毛片视频网站 | a网站在线观看 | 99精品免费久久久久久久久日本 | 精产国产伦理一二三区 | 亚洲天堂成人在线视频 | 国产精品久久久久免费 | 欧美1—12sexvideos | 日韩在线小视频 |