日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python描述器深度解析

發布時間:2024/1/23 python 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python描述器深度解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

寫在篇前

??在之前的博客Python面向對象、魔法方法中曾簡單提到魔法方法__get__、__set__、__delete__,但只給出一個例子,這篇文章將對它做一個更詳細的總結,因為這三個魔法函數表征著一個專業術語描述器。

什么是描述器

??簡而言之,如果一個類中定義了__get__、__set__、__delete__中的任意一個,這個類的實例就可以叫做一個描述器,其功能強大,應用廣泛,它可以控制訪問屬性、方法的行為,是屬性、方法、靜態方法、類方法、super函數背后的實現機制。

??首先看一個例子,說明什么是描述器:

# 例一 class A():"""描述器類該描述器的實現改變了 name屬性 默認的存取行為這種類是當做工具使用的,不單獨使用"""def __init__(self):print('descriptor init')self.name = 'descriptor'def __get__(self, instance, owner):print('descriptor get')return self.namedef __set__(self, instance, value):if isinstance(value, str):self.name = valuereturnraise TypeError('name must be string type')class B(object):name = A()def __init__(self):self.name = 'common instance'>>> b = B() >>> b.name >>> b.name = 'jeffery'# 輸出 descriptor init descriptor get common instance

??上面代碼中,A類實現了__get__和__set__方法,因此是一個描述器類,B類的類屬性name就是一個描述器。這里我們首先需要知道程序是如何訪問到name描述器的?以上述代碼中b對象為例,類屬性的訪問過程是:

  • 查找b.__dict__里面是否存在name
  • 查找type(b).__dict__里面是否存在name
  • 查找super(b)中是否存在屬性name
  • 若存在,返回值;若都不存在,拋出異常
  • ??此時,不知道你有沒有疑問,為什么最后得到的值是類屬性的值?猜想一下的話,最可能的就是描述器覆蓋了實例屬性。但是,是不是所有描述器都會’覆蓋’實例屬性呢?答案是否定的,因為描述器分為以下兩類:

    • 資料描述器

      同時定義了__get__和__set__方法的描述器

    • 非資料描述器

      只定義了__get__的描述器

    另外,還有一種稱為 只讀描述器,其實現__get__和__set__,但是__set__函數拋出AttributeError,但是這同時也是一個資料描述器。

    ??它們存在潛在優先級關系:資料描述器 > 實例屬性 > 非資料描述器;實例屬性 > 類屬性,請看以下實例:

    # 例子二 class A:def __init__(self):self.x = 1def __get__(self, instance, owner):"""以下兩個參數都是「必須參數」,約定使用instance: 描述器所在類的實例owner:調用描述器的類"""return self.xdef __set__(self, instance, value):"""以下兩個參數都是「必須參數」,約定使用instance: 描述器所在類的實例value:用來設置屬性的值"""self.x = valuedef __delete__(self, instance):"""以下參數是「必須參數」,約定使用instance: 描述器所在類的實例"""passclass B:def __init__(self):self.x = 1def __get__(self, instance, owner):return self.xclass C:a = A()b = B()def __init__(self, a, b):self.b = aself.b = b

    ??上面在class C中,分別定義了資料描述器和非資料描述器a,b以及同名的實例屬性,進行以下輸出測試,發現實例c只存在實例屬性b,不存在實例屬性a,說明其確實是被資料描述器,即類屬性a給屏蔽了,從而證實了優先級關系:資料描述器 > 實例屬性 > 非資料描述器。

    >>> c = C(7,8) >>> c.__dict__ {'b': 8}

    ??關于資料描述器、非資料描述器的調用,可用如下方式:

    # 資料描述器 >>> c.a 1 >>> type(c).__dict__['a'].__get__(c,C) 1# 非資料描述器 >>> C.b 1 >>> type(c).__dict__['b'].__get__(c,C) 1

    描述器調用原理

    ??為了探究描述器的底層調用原理,我們借用例一中定義的class A 和 class B,實際上如果我們直接初始化一個class A對象,那么其調用方式很顯然是:

    >>> a = A() >>> a.__get__(None,None)

    ??那class B實例中的描述器是如何調用的呢?實際上,python是通過__getattribute__()函數來實現的,但它的使用C語言實現的,如果用python來寫的話,類似于下面的代碼:

    def __getattribute__(self, key):"Emulate type_getattro() in Objects/typeobject.c"v = object.__getattribute__(self, key)if hasattr(v, '__get__'):return v.__get__(None, self)return v

    ??也就是說,當在字典中找到一個值之后,會判斷他是不是一個描述器,如果是的話,就調用__get__方法,從而實現描述器的訪問功能。如果你重寫__getattribute__()函數,那么就可以改變對描述器訪問的行為,如下面例子所示:

    class A():def __init__(self):self.name = 'descriptor'def __get__(self, instance, owner):return self.namedef __set__(self, instance, value):raise TypeError('name must be string type')class B(object):name = A()def __getattribute__(self, item):if item in type(self).__dict__:if hasattr(type(self).__dict__[item], '__get__'):print('oh, be careful, it is a descriptor')if __name__ == '__main__':b = B()b.name# 輸出 oh, be careful, it is a descriptor

    描述器實例

    ??在我的上一篇博客python裝飾器詳細剖析中,我舉例分析了實例方法、類方法、靜態方法、抽象方法的差異,這里我們進行深度挖掘一下其實現原理:

    首先定義一個類ICU996,用于相關測試:

    import abcclass ICU996():@staticmethoddef static_m():print('static_m')@classmethoddef class_m(cls):print('class_m')def instance_m(self):print('instance_m')@abc.abstractmethoddef abstract_m(self):print('abstract_m')

    ??下面代碼分別使用屬性訪問和字典訪問的方式,來看看各類函數的差異。可以看出,實例對象直接調用時,類方法、抽象方法、實例方法都是bound method,而靜態方法是function。值得注意的是,靜態方法和類方法用字典調用得到的其實分別是staticmethod和classmethod兩個類的對象,這兩個類其實是定義描述器的類,所以用字典訪問的兩個方法得到的都是描述器對象。

    icu = ICU996()print(icu.static_m) print(ICU996.__dict__['static_m'])print(icu.class_m) print(ICU996.__dict__['class_m'])print(icu.instance_m) print(ICU996.__dict__['instance_m'])print(icu.abstract_m) print(ICU996.__dict__['abstract_m'])# 輸出 <function ICU996.static_m at 0x11a9cff28> <staticmethod object at 0x10edcd208><bound method ICU996.class_m of <class '__main__.ICU996'>> <classmethod object at 0x10edcd320><bound method ICU996.instance_m of <__main__.ICU996 object at 0x10edcd2b0>> <function ICU996.instance_m at 0x11a9f60d0><bound method ICU996.abstract_m of <__main__.ICU996 object at 0x10edcd2b0>> <function ICU996.abstract_m at 0x11a9f6158>

    ??既然它們是描述器對象,那么調用就可以使用__get__函數來實現;另外,實際上其他函數也都是通過描述器實現的(通過非資料描述器實現的),只不過其他方法應該傳入一個self參數,不妨試一試:

    ICU996.__dict__['class_m'].__get__(None, ICU996) ICU996.__dict__['static_m'].__get__(None, ICU996)ICU996.__dict__['instance_m'].__get__(icu, ICU996) ICU996.__dict__['abstract_m'].__get__(icu, ICU996)

    property

    ??因為property的實現是用C語言實現的,我們可以看一個等效的python實現,大致是這樣的:

    class Property(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def __init__(self, fget=None, fset=None, fdel=None, doc=None):self.fget = fgetself.fset = fsetself.fdel = fdelself.__doc__ = docdef __get__(self, obj, objtype=None):if obj is None:return selfif 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"self.fset(obj, value)def __delete__(self, obj):if self.fdel is None:raise AttributeError, "can't delete attribute"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__)

    ??舉個使用的示例,通過property類來實現屬性代理,其效果與@property示例等效,但是重點是要去思考,為什么等效?

    class Student(object):def get_score(self):return self._scoredef set_score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = valuedef del_score(self):del self._scorescore = property(get_score, set_score, del_score, 'doc string')

    ??接著上面的問題,為什么等效?是怎么調用的,思考一下描述器原理,不難想出調用的過程就是:

    >>> s = Student() >>> s.score = 10 >>> type(s).__dict__['score'].__get__(s, type(s))

    Functions&methods

    ??Python的面向對象特征是建立在基于函數的環境之上的,非資料描述器把兩者無縫地連接起來。為了支持方法調用,函數包含一個 __get__() 方法以便在屬性訪問時綁定方法。這就是說所有的函數都是非資料描述器,它們返回綁定(bound)還是非綁定(unbound)的方法取決于他們是被實例調用還是被類調用。借用上文定義的class ICU996,調用一個方法的實際過程是:

    >>> icu = ICU996() >>> ICU996.__dict__['instance_m'].__get__(icu, ICU996)()

    ??因此可以猜測到,__get__()函數的實現方式如下面代碼所描述。但是可能依舊會有疑問,就算真的存在這樣一個描述器,我們定義上面的instance_m方法并沒有像使用property描述器一樣顯示調用呀?怎么就起作用了呢?原因很簡單,隱式的調用了!那在哪里調用了呢?我的猜想是,一切皆為對象,函數也是對象,那么函數也就必然屬于一個類,比如他叫Function,Function類實現了如下的代碼,也就是函數就是一個描述器,因此通過以下__get__方法實現方法到對象的綁定。

    class Function(object):. . .def __get__(self, obj, objtype=None):"Simulate func_descr_get() in Objects/funcobject.c"return types.MethodType(self, obj, objtype)

    staticmethod&classmethod

    ??其實類似的,通過實現__get__方法,使其變成一個描述器,下面分別實現以下classmethod和staticmethod:

    class myclassmethod(object):def __init__(self, method):self.method = methoddef __get__(self, instance, cls):return lambda *args, **kw: self.method(cls, *args, **kw) class myclassmethod(object):def __init__(self, method):self.method = methoddef __get__(self, instance, cls):return self.method

    小結

    ??一般方法、靜態方法、類方法的調用轉換可以歸結為下表:

    TransformationCalled from an ObjectCalled from a Class
    functionf(obj, *args)f(*args)
    staticmethodf(*args)f(*args)
    classmethodf(type(obj), *args)f(klass, *args)

    參考來源

    descriptor HowTo

    location of classmethod

    總結

    以上是生活随笔為你收集整理的python描述器深度解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。