python描述符是什么-創(chuàng)新互聯(lián)

小編給大家分享一下python描述符是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

10年積累的成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站策劃后付款的網(wǎng)站建設(shè)流程,更有芙蓉免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

盡管在開發(fā)的過程中,我們沒有直接的使用過描述符,但是它在底層的運(yùn)用卻是十分頻繁的存在。例如下面的這些:

  • function 、bound method 、unbound method

  • 裝是器property 、staticmethod 、classmethod
    這些是不是都很熟悉?
    其實(shí)這些都與描述符有著千絲萬縷的聯(lián)系,這樣吧,我們通過下面的文章來探討一下描述符背后的工作原理吧。

什么是描述符?

在我們了解什么是描述符前,我們可以先找一個(gè)例子來看一下

class A:
    x = 10print(A.x) # 10

這個(gè)例子很簡單,我們先在類A中定義一個(gè)類屬性x,然后得出它的值。
除了這種直接定義類屬性的方法外,我們還可以這樣去定義一個(gè)類屬性:

class Ten:
    def __get__(self, obj, objtype=None):
        return 10class A:
    x = Ten()   # 屬性換成了一個(gè)類print(A.x) # 10

我們可以發(fā)現(xiàn),這回的類屬性x不是一個(gè)具體的值了,而是一個(gè)類Ten,通過這個(gè)Ten定義了一個(gè)__get__方法,返回具體的值。

因此可得出:在python中,我們可以把一個(gè)類的屬性,托管給一個(gè)類,而這樣的屬性就是一個(gè)描述符
簡而言之,描述符是一個(gè)綁定行為屬性

而這又有著什么意思呢?
回想,我們在開發(fā)時(shí),一般情況下,會(huì)將行為叫做什么?行為即一個(gè)方法。

所以我們也可以將描述符理解為:對象的屬性并非一個(gè)具體的值,而是交給了一個(gè)方法去定義。

可以想像一下,如果我們用一個(gè)方法去定義一個(gè)屬性,這么做有什么好處?

有了方法,我們就可以在方法內(nèi)實(shí)現(xiàn)自己的邏輯,最簡單的,我們可以根據(jù)不同的條件,在方法內(nèi)給屬性賦予不同的值,就像下面這樣:

class Age:
    def __get__(self, obj, objtype=None):
        if obj.name == 'zhangsan':
            return 20
        elif obj.name == 'lisi':
            return 25
        else:
            return ValueError("unknow")class Person:

    age = Age()

    def __init__(self, name):
        self.name = name

p1 = Person('zhangsan')print(p1.age)   # 20p2 = Person('lisi')print(p2.age)   # 25p3 = Person('wangwu')print(p3.age)   # unknow

這個(gè)例子中,age 類屬性被另一個(gè)類托管了,在這個(gè)類的__get__ 中,它會(huì)根據(jù)Person 類的屬性name,決定age 是什么值。

通過這樣一個(gè)例子,我們可以看到,通過描述符的使用,我們可以輕易地改變一個(gè)類屬性的定義方式。

描述符協(xié)議

了解了描述符的定義,現(xiàn)在我們把重點(diǎn)放到托管屬性的類上。

其實(shí),一個(gè)類屬性想要托管給一個(gè)類,這個(gè)類內(nèi)部實(shí)現(xiàn)的方法不能是隨便定義的,它必須遵守「描述符協(xié)議」,也就是要實(shí)現(xiàn)以下幾個(gè)方法:

  • __get__(self, obj, type=None) -> value

  • __set__(self, obj, value) -> None

  • __delete__(self, obj) -> None

只要是實(shí)現(xiàn)了以上幾個(gè)方法的其中一個(gè),那么這個(gè)類屬性就可以稱作描述符。

另外,描述符又可以分為「數(shù)據(jù)描述符」和「非數(shù)據(jù)描述符」:

  • 只定義了__get___,叫做非數(shù)據(jù)描述符

  • 除了定義__get__ 之外,還定義了__set____delete__,叫做數(shù)據(jù)描述符

它們兩者有什么區(qū)別,我會(huì)在下面詳述。

現(xiàn)在我們來看一個(gè)包含__get____set__ 方法的描述符例子:

# coding: utf8class Age:

    def __init__(self, value=20):
        self.value = value

    def __get__(self, obj, type=None):
        print('call __get__: obj: %s type: %s' % (obj, type))
        return self.value

    def __set__(self, obj, value):
        if value <= 0:
            raise ValueError("age must be greater than 0")
        print('call __set__: obj: %s value: %s' % (obj, value))
        self.value = valueclass Person:

    age = Age()

    def __init__(self, name):
        self.name = name

p1 = Person('zhangsan')print(p1.age)# call __get__: obj: <__main__.Person object at 0x1055509e8> type: <class '__main__.Person'># 20print(Person.age)# call __get__: obj: None type: <class '__main__.Person'># 20p1.age = 25# call __set__: obj: <__main__.Person object at 0x1055509e8> value: 25print(p1.age)# call __get__: obj: <__main__.Person object at 0x1055509e8> type: <class '__main__.Person'># 25p1.age = -1# ValueError: age must be greater than 0

在這例子中,類屬性age 是一個(gè)描述符,它的值取決于Age 類。

從輸出結(jié)果來看,當(dāng)我們獲取或修改age 屬性時(shí),調(diào)用了Age__get____set__ 方法:

  • 當(dāng)調(diào)用p1.age 時(shí),__get__ 被調(diào)用,參數(shù)objPerson 實(shí)例,typetype(Person)

  • 當(dāng)調(diào)用Person.age 時(shí),__get__ 被調(diào)用,參數(shù)objNone,typetype(Person)

  • 當(dāng)調(diào)用p1.age = 25時(shí),__set__ 被調(diào)用,參數(shù)objPerson 實(shí)例,value 是25

  • 當(dāng)調(diào)用p1.age = -1時(shí),__set__ 沒有通過校驗(yàn),拋出ValueError

其中,調(diào)用__set__ 傳入的參數(shù),我們比較容易理解,但是對于__get__ 方法,通過類或?qū)嵗?/strong>調(diào)用,傳入的參數(shù)是不同的,這是為什么?

這就需要我們了解一下描述符的工作原理。

描述符的工作原理

要解釋描述符的工作原理,首先我們需要先從屬性的訪問說起。

在開發(fā)時(shí),不知道你有沒有想過這樣一個(gè)問題:通常我們寫這樣的代碼a.b,其背后到底發(fā)生了什么?

這里的ab 可能存在以下情況:

  1. a 可能是一個(gè)類,也可能是一個(gè)實(shí)例,我們這里統(tǒng)稱為對象

  2. b 可能是一個(gè)屬性,也可能是一個(gè)方法,方法其實(shí)也可以看做是類的屬性

其實(shí),無論是以上哪種情況,在 Python 中,都有一個(gè)統(tǒng)一的調(diào)用邏輯:

  1. 先調(diào)用__getattribute__ 嘗試獲得結(jié)果

  2. 如果沒有結(jié)果,調(diào)用__getattr__

用代碼表示就是下面這樣:

def getattr_hook(obj, name):
    try:
        return obj.__getattribute__(name)
    except AttributeError:
        if not hasattr(type(obj), '__getattr__'):
            raise    return type(obj).__getattr__(obj, name)

我們這里需要重點(diǎn)關(guān)注一下__getattribute__,因?yàn)樗撬袑傩圆檎业娜肟?,它?nèi)部實(shí)現(xiàn)的屬性查找順序是這樣的:

  1. 要查找的屬性,在類中是否是一個(gè)描述符

  2. 如果是描述符,再檢查它是否是一個(gè)數(shù)據(jù)描述符

  3. 如果是數(shù)據(jù)描述符,則調(diào)用數(shù)據(jù)描述符的__get__

  4. 如果不是數(shù)據(jù)描述符,則從__dict__ 中查找

  5. 如果__dict__ 中查找不到,再看它是否是一個(gè)非數(shù)據(jù)描述符

  6. 如果是非數(shù)據(jù)描述符,則調(diào)用非數(shù)據(jù)描述符的__get__

  7. 如果也不是一個(gè)非數(shù)據(jù)描述符,則從類屬性中查找

  8. 如果類中也沒有這個(gè)屬性,拋出AttributeError 異常

寫成代碼就是下面這樣:

# 獲取一個(gè)對象的屬性
def __getattribute__(obj, name):
    null = object()
    # 對象的類型 也就是實(shí)例的類
    objtype = type(obj)
    # 從這個(gè)類中獲取指定屬性
    cls_var = getattr(objtype, name, null)
    # 如果這個(gè)類實(shí)現(xiàn)了描述符協(xié)議
    descr_get = getattr(type(cls_var), '__get__', null)
    if descr_get is not null:
        if (hasattr(type(cls_var), '__set__')
            or hasattr(type(cls_var), '__delete__')):
            # 優(yōu)先從數(shù)據(jù)描述符中獲取屬性            return descr_get(cls_var, obj, objtype)
    # 從實(shí)例中獲取屬性    if hasattr(obj, '__dict__') and name in vars(obj):
        return vars(obj)[name]
    # 從非數(shù)據(jù)描述符獲取屬性    if descr_get is not null:
        return descr_get(cls_var, obj, objtype)
    # 從類中獲取屬性    if cls_var is not null:
        return cls_var
    # 拋出 AttributeError 會(huì)觸發(fā)調(diào)用 __getattr__
    raise AttributeError(name)

如果不好理解,你好寫一個(gè)程序測試一下,觀察各種情況下的屬性的查找順序。

到這里我們可以看到,在一個(gè)對象中查找一個(gè)屬性,都是先從__getattribute__ 開始的。

__getattribute__ 中,它會(huì)檢查這個(gè)類屬性是否是一個(gè)描述符,如果是一個(gè)描述符,那么就會(huì)調(diào)用它的__get__ 方法。但具體的調(diào)用細(xì)節(jié)和傳入的參數(shù)是下面這樣的:

  • 如果a 是一個(gè)實(shí)例,調(diào)用細(xì)節(jié)為:

type(a).__dict__['b'].__get__(a, type(a))
  • 如果a 是一個(gè),調(diào)用細(xì)節(jié)為:

a.__dict__['b'].__get__(None, a)

所以我們就能看到上面例子輸出的結(jié)果。

數(shù)據(jù)描述符和非數(shù)據(jù)描述符

了解了描述符的工作原理,我們繼續(xù)來看數(shù)據(jù)描述符和非數(shù)據(jù)描述符的區(qū)別。

從定義上來看,它們的區(qū)別是:

  • 只定義了__get___,叫做非數(shù)據(jù)描述符

  • 除了定義__get__ 之外,還定義了__set____delete__,叫做數(shù)據(jù)描述符

此外,我們從上面描述符調(diào)用的順序可以看到,在對象中查找屬性時(shí),數(shù)據(jù)描述符要優(yōu)先于非數(shù)據(jù)描述符調(diào)用。

在之前的例子中,我們定義了__get____set__,所以那些類屬性都是數(shù)據(jù)描述符

我們再來看一個(gè)非數(shù)據(jù)描述符的例子:

class A:

    def __init__(self):
        self.foo = 'abc'

    def foo(self):
        return 'xyz'print(A().foo)  # 輸出什么?

這段代碼,我們定義了一個(gè)相同名字的屬性和方法foo,如果現(xiàn)在執(zhí)行A().foo,你覺得會(huì)輸出什么結(jié)果?

答案是abc。

為什么打印的是實(shí)例屬性foo 的值,而不是方法foo 呢?

這就和非數(shù)據(jù)描述符有關(guān)系了。

我們執(zhí)行dir(A.foo),觀察結(jié)果:

print(dir(A.foo))# [... '__get__', '__getattribute__', ...]

看到了嗎?Afoo 方法其實(shí)實(shí)現(xiàn)了__get__,我們在上面的分析已經(jīng)得知:只定義__get__ 方法的對象,它其實(shí)是一個(gè)非數(shù)據(jù)描述符,也就是說,我們在類中定義的方法,其實(shí)本身就是一個(gè)非數(shù)據(jù)描述符。

所以,在一個(gè)類中,如果存在相同名字的屬性和方法,按照上面所講的__getattribute__ 中查找屬性的順序,這個(gè)屬性就會(huì)優(yōu)先從實(shí)例中獲取,如果實(shí)例中不存在,才會(huì)從非數(shù)據(jù)描述符中獲取,所以在這里優(yōu)先查找的是實(shí)例屬性foo 的值。

到這里我們可以總結(jié)一下關(guān)于描述符的相關(guān)知識(shí)點(diǎn):

  • 描述符必須是一個(gè)類屬性

  • __getattribute__ 是查找一個(gè)屬性(方法)的入口

  • __getattribute__ 定義了一個(gè)屬性(方法)的查找順序:數(shù)據(jù)描述符、實(shí)例屬性、非數(shù)據(jù)描述符、類屬性

  • 如果我們重寫了__getattribute__ 方法,會(huì)阻止描述符的調(diào)用

  • 所有方法其實(shí)都是一個(gè)非數(shù)據(jù)描述符,因?yàn)樗x了__get__

描述符的使用場景

了解了描述符的工作原理,那描述符一般用在哪些業(yè)務(wù)場景中呢?

在這里我用描述符實(shí)現(xiàn)了一個(gè)屬性校驗(yàn)器,你可以參考這個(gè)例子,在類似的場景中去使用它。

首先我們定義一個(gè)校驗(yàn)基類Validator,在__set__ 方法中先調(diào)用validate 方法校驗(yàn)屬性是否符合要求,然后再對屬性進(jìn)行賦值。

class Validator:

    def __init__(self):
        self.data = {}

    def __get__(self, obj, objtype=None):
        return self.data[obj]

    def __set__(self, obj, value):
        # 校驗(yàn)通過后再賦值
        self.validate(value)
        self.data[obj] = value

    def validate(self, value):
        pass

接下來,我們定義兩個(gè)校驗(yàn)類,繼承Validator,然后實(shí)現(xiàn)自己的校驗(yàn)邏輯。

class Number(Validator):

    def __init__(self, minvalue=None, maxvalue=None):
        super(Number, self).__init__()
        self.minvalue = minvalue
        self.maxvalue = maxvalue

    def validate(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError(f'Expected {value!r} to be an int or float')
        if self.minvalue is not None and value < self.minvalue:
            raise ValueError(
                f'Expected {value!r} to be at least {self.minvalue!r}'
            )
        if self.maxvalue is not None and value > self.maxvalue:
            raise ValueError(
                f'Expected {value!r} to be no more than {self.maxvalue!r}'
            )class String(Validator):

    def __init__(self, minsize=None, maxsize=None):
        super(String, self).__init__()
        self.minsize = minsize
        self.maxsize = maxsize

    def validate(self, value):
        if not isinstance(value, str):
            raise TypeError(f'Expected {value!r} to be an str')
        if self.minsize is not None and len(value) < self.minsize:
            raise ValueError(
                f'Expected {value!r} to be no smaller than {self.minsize!r}'
            )
        if self.maxsize is not None and len(value) > self.maxsize:
            raise ValueError(
                f'Expected {value!r} to be no bigger than {self.maxsize!r}'
            )

最后,我們使用這個(gè)校驗(yàn)類:

class Person:

    # 定義屬性的校驗(yàn)規(guī)則 內(nèi)部用描述符實(shí)現(xiàn)
    name = String(minsize=3, maxsize=10)
    age = Number(minvalue=1, maxvalue=120)

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 屬性符合規(guī)則
p1 = Person('zhangsan', 20)print(p1.name, p1.age)# 屬性不符合規(guī)則
p2 = person('a', 20)# ValueError: Expected 'a' to be no smaller than 3p3 = Person('zhangsan', -1)# ValueError: Expected -1 to be at least 1

現(xiàn)在,當(dāng)我們對Person 實(shí)例進(jìn)行初始化時(shí),就可以校驗(yàn)這些屬性是否符合預(yù)定義的規(guī)則了。

function與method

我們再來看一下,在開發(fā)時(shí)經(jīng)常看到的function、unbound method、bound method 它們之間到底有什么區(qū)別?

來看下面這段代碼:

class A:

    def foo(self):
        return 'xyz'print(A.__dict__['foo']) # <function foo at 0x10a790d70>print(A.foo)     # <unbound method A.foo>print(A().foo)   # <bound method A.foo of <__main__.A object at 0x10a793050>>

從結(jié)果我們可以看出它們的區(qū)別:

  • function 準(zhǔn)確來說就是一個(gè)函數(shù),并且它實(shí)現(xiàn)了__get__ 方法,因此每一個(gè)function 都是一個(gè)非數(shù)據(jù)描述符,而在類中會(huì)把function 放到__dict__ 中存儲(chǔ)

  • 當(dāng)function 被實(shí)例調(diào)用時(shí),它是一個(gè)bound method

  • 當(dāng)function 被類調(diào)用時(shí), 它是一個(gè)unbound method

function 是一個(gè)非數(shù)據(jù)描述符,我們之前已經(jīng)講到了。

bound methodunbound method 的區(qū)別就在于調(diào)用方的類型是什么,如果是一個(gè)實(shí)例,那么這個(gè)function 就是一個(gè)bound method,否則它是一個(gè)unbound method

property/staticmethod/classmethod

我們再來看property、staticmethod、classmethod。

這些裝飾器的實(shí)現(xiàn),默認(rèn)是 C 來實(shí)現(xiàn)的。

其實(shí),我們也可以直接利用 Python 描述符的特性來實(shí)現(xiàn)這些裝飾器,

property 的 Python 版實(shí)現(xiàn):

class property:

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self.fget        if self.fget is None:
            raise AttributeError(), "unreadable attribute"
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError, "can't set attribute"
        return self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError, "can't delete attribute"
        return self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

staticmethod 的 Python 版實(shí)現(xiàn):

class staticmethod:

    def __init__(self, func):
        self.func = func

    def __get__(self, obj, objtype=None):
        return self.func

classmethod 的 Python 版實(shí)現(xiàn):

class classmethod:

    def __init__(self, func):
        self.func = func

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.func(klass, *args)
        return newfunc

除此之外,你還可以實(shí)現(xiàn)其他功能強(qiáng)大的裝飾器。

由此可見,通過描述符我們可以實(shí)現(xiàn)強(qiáng)大而靈活的屬性管理功能,對于一些要求屬性控制比較復(fù)雜的場景,我們可以選擇用描述符來實(shí)現(xiàn)。

以上是“python描述符是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

網(wǎng)站題目:python描述符是什么-創(chuàng)新互聯(lián)
本文鏈接:http://bm7419.com/article14/dgdjge.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、網(wǎng)站營銷、網(wǎng)頁設(shè)計(jì)公司、ChatGPT、全網(wǎng)營銷推廣移動(dòng)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)