欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Python虛擬機(jī)中描述器的實(shí)現(xiàn)原理

 更新時(shí)間:2023年05月07日 08:51:32   作者:一無(wú)是處的研究僧  
這篇文章主要給大家介紹一個(gè)我們?cè)谑褂妙?lèi)的時(shí)候經(jīng)常使用但是卻很少在意的黑科技——描述器的實(shí)現(xiàn)原理,文中的示例代碼講解詳細(xì),需要的可以參考一下

在本篇文章當(dāng)中主要給大家介紹一個(gè)我們?cè)谑褂妙?lèi)的時(shí)候經(jīng)常使用但是卻很少在意的黑科技——描述器,在本篇文章當(dāng)中主要分析描述器的原理,以及介紹使用描述器實(shí)現(xiàn)屬性訪問(wèn)控制和 orm 映射等等功能!在后面的文章當(dāng)中我們將繼續(xù)去分析描述器的實(shí)現(xiàn)原理。

描述器的基本用法

描述器是一個(gè)實(shí)現(xiàn)了 __get__、__set____delete__ 中至少一個(gè)方法的 Python 類(lèi)。這些方法分別用于在屬性被訪問(wèn)、設(shè)置或刪除時(shí)調(diào)用。當(dāng)一個(gè)描述器被定義為一個(gè)類(lèi)的屬性時(shí),它可以控制該屬性的訪問(wèn)、修改和刪除。

下面是一個(gè)示例,演示了如何定義一個(gè)簡(jiǎn)單的描述器:

class Descriptor:
    def __get__(self, instance, owner):
        print(f"Getting {self.__class__.__name__}")
        return instance.__dict__.get(self.attrname)

    def __set__(self, instance, value):
        print(f"Setting {self.__class__.__name__}")
        instance.__dict__[self.attrname] = value

    def __delete__(self, instance):
        print(f"Deleting {self.__class__.__name__}")
        del instance.__dict__[self.attrname]

    def __set_name__(self, owner, name):
        self.attrname = name

在這個(gè)例子中,我們定義了一個(gè)名為 Descriptor 的描述器類(lèi),它有三個(gè)方法:__get____set____delete__。當(dāng)我們?cè)诹硪粋€(gè)類(lèi)中使用這個(gè)描述器時(shí),這些方法將被調(diào)用,以控制該類(lèi)的屬性的訪問(wèn)和修改。

要使用這個(gè)描述器,我們可以在另一個(gè)類(lèi)中將其定義為一個(gè)類(lèi)屬性:

class MyClass:
    x = Descriptor()

現(xiàn)在,我們可以創(chuàng)建一個(gè) MyClass 對(duì)象并訪問(wèn)其屬性:

>>> obj = MyClass()
>>> obj.x = 1
Setting Descriptor
>>> obj.x
Getting Descriptor
1
>>> del obj.x
Deleting Descriptor
>>> obj.x
Getting Descriptor

在這個(gè)例子中,我們首先創(chuàng)建了一個(gè) MyClass 對(duì)象,并將其 x 屬性設(shè)置為 1。然后,我們?cè)俅卧L問(wèn) x 屬性時(shí),會(huì)調(diào)用 __get__ 方法并返回 1。最后,我們刪除了 x 屬性,并再次訪問(wèn)它時(shí),會(huì)調(diào)用 __get__ 方法并返回 None。從上面的輸出結(jié)果可以看到對(duì)應(yīng)的方法都被調(diào)用了,這是符合上面對(duì)描述器的定義的。如果一個(gè)類(lèi)對(duì)象不是描述器,那么在使用對(duì)應(yīng)的屬性的時(shí)候是不會(huì)調(diào)用__get__、__set____delete__三個(gè)方法的。比如下面的代碼:

class NonDescriptor(object):
    pass


class MyClass():

    nd = NonDescriptor()


if __name__ == '__main__':
    a = MyClass()
    print(a.nd)

上面的代碼輸出結(jié)果如下所示:

<__main__.NonDescriptor object at 0x1012cce20>

從上面程序的輸出結(jié)果可以知道,當(dāng)使用一個(gè)非描述器的類(lèi)屬性的時(shí)候是不會(huì)調(diào)用對(duì)應(yīng)的方法的,而是直接得到對(duì)應(yīng)的對(duì)象。

描述器的實(shí)現(xiàn)原理

描述器的實(shí)現(xiàn)原理可以用以下三個(gè)步驟來(lái)概括:

  • 當(dāng)一個(gè)類(lèi)的屬性被訪問(wèn)時(shí),Python 解釋器會(huì)檢查該屬性是否是一個(gè)描述器。如果是,它會(huì)調(diào)用描述器的 __get__ 方法,并將該類(lèi)的實(shí)例作為第一個(gè)參數(shù),該實(shí)例所屬的類(lèi)作為第二個(gè)參數(shù),并將屬性名稱(chēng)作為第三個(gè)參數(shù)傳遞給 __get__ 方法。
  • 當(dāng)一個(gè)類(lèi)的屬性被設(shè)置時(shí),Python 解釋器會(huì)檢查該屬性是否是一個(gè)描述器。如果是,它會(huì)調(diào)用描述器的 __set__ 方法,并將該類(lèi)的實(shí)例作為第一個(gè)參數(shù),設(shè)置的值作為第二個(gè)參數(shù),并將屬性名稱(chēng)作為第三個(gè)參數(shù)傳遞給 __set__ 方法。
  • 當(dāng)一個(gè)類(lèi)的屬性被刪除時(shí),Python 解釋器會(huì)檢查該屬性是否是一個(gè)描述器。如果是,它會(huì)調(diào)用描述器的 __delete__ 方法,并將該類(lèi)的實(shí)例作為第一個(gè)參數(shù)和屬性名稱(chēng)作為第二個(gè)參數(shù)傳遞給 __delete__ 方法。

在描述器的實(shí)現(xiàn)中,通常還會(huì)使用 __set_name__ 方法來(lái)在描述器被綁定到類(lèi)屬性時(shí)設(shè)置屬性名稱(chēng)。這使得描述器可以在被多個(gè)屬性使用時(shí),正確地識(shí)別每個(gè)屬性的名稱(chēng)。

現(xiàn)在來(lái)仔細(xì)了解一下上面的幾個(gè)函數(shù)的參數(shù),我們以下面的代碼為例子進(jìn)行說(shuō)明:

class Descriptor(object):
    def __set_name__(self, obj_type, attr_name):
        print(f"__set_name__ : {obj_type } {attr_name = }")
        return "__set_name__"
    def __get__(self, obj, obj_type):
        print(f"__get__ : {obj = } { obj_type = }")
        return "__get__"
    def __set__(self, instance, value):
        print(f"__set__ : {instance = } {value = }")
        return "__set__"
    def __delete__(self, obj):
        print(f"__delete__ : {obj = }")
        return "__delete__"
class MyClass(object):
    des = Descriptor()
if __name__ == '__main__':
    a = MyClass()
    _ = MyClass.des
    _ = a.des
    a.des = "hello"
    del a.des

上面的代碼輸入結(jié)果如下所示:

__set_name__ : <class '__main__.MyClass'> attr_name = 'des'
__get__ : obj = None  obj_type = <class '__main__.MyClass'>
__get__ : obj = <__main__.MyClass object at 0x1054abeb0>  obj_type = <class '__main__.MyClass'>
__set__ : instance = <__main__.MyClass object at 0x1054abeb0> value = 'hello'
__delete__ : obj = <__main__.MyClass object at 0x1054abeb0>

  • __set_name__ 這個(gè)函數(shù)一共有兩個(gè)參數(shù)傳入的參數(shù)第一個(gè)參數(shù)是使用描述器的類(lèi),第二個(gè)參數(shù)是使用這個(gè)描述器的類(lèi)當(dāng)中使用的屬性名字,在上面的例子當(dāng)中就是 "des" 。
  • __get__,這個(gè)函數(shù)主要有兩個(gè)參數(shù),一個(gè)是使用屬性的對(duì)象,另外一個(gè)是對(duì)象的類(lèi)型,如果是直接使用類(lèi)名使用屬性的話(huà),obj 就是 None,比如上面的 MyClass.des 。
  • __set__,這個(gè)函數(shù)主要有兩個(gè)參數(shù)一個(gè)是對(duì)象,另外一個(gè)是需要設(shè)置的值。
  • __delete__,這函數(shù)有一個(gè)參數(shù),就是傳入的對(duì)象,比如 del a.des 傳入的就是對(duì)象 a 。

描述器的應(yīng)用場(chǎng)景

描述器在 Python 中有很多應(yīng)用場(chǎng)景。以下是其中的一些示例:

實(shí)現(xiàn)屬性訪問(wèn)控制

通過(guò)使用描述器,可以實(shí)現(xiàn)對(duì)類(lèi)屬性的訪問(wèn)控制,例如只讀屬性、只寫(xiě)屬性、只讀/只寫(xiě)屬性等。通過(guò)在 __get____set__ 方法中添加相應(yīng)的訪問(wèn)控制邏輯,可以限制對(duì)類(lèi)屬性的訪問(wèn)和修改。

class ReadOnly:
    def __init__(self, value):
        self._value = value
    def __get__(self, instance, owner):
        return self._value
    def __set__(self, instance, value):
        raise AttributeError("Read only attribute")
class MyClass:
    read_only_prop = ReadOnly(42)
    writeable_prop = None
my_obj = MyClass()
print(my_obj.read_only_prop)  # 42
my_obj.writeable_prop = "hello"
print(my_obj.writeable_prop)  # hello
my_obj.read_only_prop = 100  # raises AttributeError

在上面的例子中,ReadOnly 描述器只實(shí)現(xiàn)了 __get__ 方法,而 __set__ 方法則拋出了 AttributeError 異常,從而實(shí)現(xiàn)了只讀屬性的訪問(wèn)控制。

實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證和轉(zhuǎn)換

描述器還可以用于實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證和轉(zhuǎn)換邏輯。通過(guò)在 __set__ 方法中添加數(shù)據(jù)驗(yàn)證和轉(zhuǎn)換邏輯,可以確保設(shè)置的值符合某些特定的要求。例如,可以使用描述器來(lái)確保設(shè)置的值是整數(shù)、在某個(gè)范圍內(nèi)、符合某個(gè)正則表達(dá)式等。

class Bounded:
    def __init__(self, low, high):
        self._low = low
        self._high = high
    def __get__(self, instance, owner):
        return self._value
    def __set__(self, instance, value):
        if not self._low <= value <= self._high:
            raise ValueError(f"Value must be between {self._low} and {self._high}")
        self._value = value
class MyClass:
    bounded_prop = Bounded(0, 100)
my_obj = MyClass()
my_obj.bounded_prop = 50
print(my_obj.bounded_prop)  # 50
my_obj.bounded_prop = 200  # raises ValueError

在上面的例子中,Bounded 描述器在 __set__ 方法中進(jìn)行了數(shù)值范圍的檢查,如果值不在指定范圍內(nèi),則拋出了 ValueError 異常。

實(shí)現(xiàn)延遲加載和緩存

描述器還可以用于實(shí)現(xiàn)延遲加載和緩存邏輯。通過(guò)在 __get__ 方法中添加邏輯,可以實(shí)現(xiàn)屬性的延遲加載,即當(dāng)屬性第一次被訪問(wèn)時(shí)才進(jìn)行加載。此外,還可以使用描述器來(lái)實(shí)現(xiàn)緩存邏輯,以避免重復(fù)計(jì)算。

class LazyLoad:
    def __init__(self, func):
        self._func = func
    def __get__(self, instance, owner):
        if instance is None:
            return self
        value = self._func(instance)
        setattr(instance, self._func.__name__, value)
        return value
class MyClass:
    def __init__(self):
        self._expensive_data = None
    @LazyLoad
    def expensive_data(self):
        print("Calculating expensive data...")
        self._expensive_data = [i ** 2 for i in range(10)]
        return self._expensive_data
my_obj = MyClass()
print(my_obj.expensive_data)  # Calculating expensive data... 
print(my_obj.expensive_data)

上面的程序的輸出結(jié)果如下所示:

Calculating expensive data...
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

從上面的結(jié)果可以看到,只有在第一次使用屬性的時(shí)候才調(diào)用函數(shù),后續(xù)再次調(diào)用函數(shù)將不會(huì)再調(diào)用函數(shù)而是直接返回緩存的結(jié)果。

實(shí)現(xiàn) ORM 映射

ORM 的主要作用是把數(shù)據(jù)庫(kù)中的關(guān)系數(shù)據(jù)轉(zhuǎn)化為面向?qū)ο蟮臄?shù)據(jù),讓開(kāi)發(fā)者可以通過(guò)編寫(xiě)面向?qū)ο蟮拇a來(lái)操作數(shù)據(jù)庫(kù)。ORM 技術(shù)可以把面向?qū)ο蟮木幊陶Z(yǔ)言和關(guān)系數(shù)據(jù)庫(kù)之間的映射關(guān)系抽象出來(lái),開(kāi)發(fā)者可以不用寫(xiě) SQL 語(yǔ)句,而是直接使用面向?qū)ο蟮恼Z(yǔ)法進(jìn)行數(shù)據(jù)庫(kù)操作。

我們現(xiàn)在需要實(shí)現(xiàn)一個(gè)功能,user.name 直接從數(shù)據(jù)庫(kù)的 user 表當(dāng)中查詢(xún) name 等于 user.name 的數(shù)據(jù),user.name = "xxx" 根據(jù) user 的主鍵 id 進(jìn)行更新數(shù)據(jù)。這個(gè)功能我們就可以使用描述器實(shí)現(xiàn),因?yàn)橹恍枰私馊绾问褂妹枋銎鞯模虼嗽谙旅娴拇a當(dāng)中并沒(méi)有連接數(shù)據(jù)庫(kù):

conn = dict()
class Field:
    def __set_name__(self, owner, name):
        self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.key}=?;'
        print(f"{self.fetch = }")
        self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.key}=?;'
        print(f"{self.store = }")
    def __get__(self, obj, objtype=None):
        return conn.execute(self.fetch, [obj.key]).fetchone()[0]
    def __set__(self, obj, value):
        conn.execute(self.store, [value, obj.key])
        conn.commit()
class User:
    table = 'User'                    # Table name
    key = 'id'                       # Primary key
    name = Field()
    age = Field()
    def __init__(self, key):
        self.key = key
if __name__ == '__main__':
    u = User("Bob")

上面的程序輸出結(jié)果如下所示:

self.fetch = 'SELECT name FROM User WHERE id=?;'
self.store = 'UPDATE User SET name=? WHERE id=?;'
self.fetch = 'SELECT age FROM User WHERE id=?;'
self.store = 'UPDATE User SET age=? WHERE id=?;

從上面的輸出結(jié)果我們可以看到針對(duì) name 和 age 兩個(gè)字段的查詢(xún)和更新語(yǔ)句確實(shí)生成了,當(dāng)我們調(diào)用 u.name = xxx 或者 u.age = xxx 的時(shí)候就執(zhí)行 __set__ 函數(shù),就會(huì)連接數(shù)據(jù)庫(kù)進(jìn)行相應(yīng)的操作了。

總結(jié)

在本篇文章當(dāng)中主要給大家介紹了什么是描述器以及我們能夠使用描述器來(lái)實(shí)現(xiàn)什么樣的功能,事實(shí)上 python 是一個(gè)比較隨意的語(yǔ)言,因此我們可以利用很多有意思的語(yǔ)法做出黑多黑科技。python 語(yǔ)言本身也利用描述器實(shí)現(xiàn)了很多有意思的功能,比如 property、staticmethod 等等,這些內(nèi)容我們?cè)诤竺娴奈恼庐?dāng)中再進(jìn)行分析。

到此這篇關(guān)于深入理解Python虛擬機(jī)中描述器的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Python虛擬機(jī)描述器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用python實(shí)現(xiàn)rsa算法代碼

    使用python實(shí)現(xiàn)rsa算法代碼

    RSA算法是一種非對(duì)稱(chēng)加密算法,是現(xiàn)在廣泛使用的公鑰加密算法,主要應(yīng)用是加密信息和數(shù)字簽名。本文給大家介紹python實(shí)現(xiàn)rsa算法代碼,感興趣的朋友一起學(xué)習(xí)吧
    2016-02-02
  • python獲取淘寶服務(wù)器時(shí)間的代碼示例

    python獲取淘寶服務(wù)器時(shí)間的代碼示例

    這篇文章主要介紹了python獲取淘寶服務(wù)器時(shí)間的代碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Python os.access()用法實(shí)例

    Python os.access()用法實(shí)例

    在本篇文章里小編給大家分享了關(guān)于Python os.access()用法實(shí)例內(nèi)容以及相關(guān)知識(shí)點(diǎn),需要的朋友們學(xué)習(xí)下。
    2019-02-02
  • pyecharts的Tab和Legend布局詳情

    pyecharts的Tab和Legend布局詳情

    這篇文章主要介紹了pyecharts的Tab和Legend布局,pyecharts是百度開(kāi)源的一款第三方繪圖模塊,結(jié)合的python語(yǔ)言的簡(jiǎn)易性和Echarts的強(qiáng)大繪圖特性,可以用python對(duì)其調(diào)用,輸出交互性好,精美乖巧且符合審美的圖表,下文我們就來(lái)學(xué)習(xí)pyecharts的Tab和Legend煩人布局布局
    2022-03-03
  • Python 遍歷子文件和所有子文件夾的代碼實(shí)例

    Python 遍歷子文件和所有子文件夾的代碼實(shí)例

    本篇文章主要介紹了Python 遍歷子文件和所有子文件夾的代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2016-12-12
  • python3發(fā)送郵件需要經(jīng)過(guò)代理服務(wù)器的示例代碼

    python3發(fā)送郵件需要經(jīng)過(guò)代理服務(wù)器的示例代碼

    今天小編就為大家分享一篇python3發(fā)送郵件需要經(jīng)過(guò)代理服務(wù)器的示例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-07-07
  • 用Python從0開(kāi)始實(shí)現(xiàn)一個(gè)中文拼音輸入法的思路詳解

    用Python從0開(kāi)始實(shí)現(xiàn)一個(gè)中文拼音輸入法的思路詳解

    中文輸入法是一個(gè)歷史悠久的問(wèn)題,但也實(shí)在是個(gè)繁瑣的活,不知道這是不是網(wǎng)上很少有人分享中文拼音輸入法的原因,接下來(lái)通過(guò)本文給大家分享使用Python從0開(kāi)始實(shí)現(xiàn)一個(gè)中文拼音輸入法,需要的朋友可以參考下
    2019-07-07
  • Python進(jìn)度條的制作代碼實(shí)例

    Python進(jìn)度條的制作代碼實(shí)例

    這篇文章主要介紹了Python進(jìn)度條的制作代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • python編程嵌套函數(shù)實(shí)例代碼

    python編程嵌套函數(shù)實(shí)例代碼

    這篇文章主要介紹了python編程嵌套函數(shù)實(shí)例代碼,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • python實(shí)現(xiàn)動(dòng)態(tài)數(shù)組的示例代碼

    python實(shí)現(xiàn)動(dòng)態(tài)數(shù)組的示例代碼

    這篇文章主要介紹了python實(shí)現(xiàn)動(dòng)態(tài)數(shù)組的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07

最新評(píng)論