Python 描述器


1. 什麼是描述器

描述器其實是任何新式類(新式類是繼承自 type 或者 object 的類),這種類至少實現了3個特殊的方法__get__, __set__, __delete__中的一個。而這3個特殊的方法充當描述器協議的做用。html

同時實現了__get__()__get__()的類被稱爲數據描述器(data descriptor)。只實現了 __get__() 方法的類是非數據描述器(經常使用於方法,固然其餘用途也是能夠的)。python

__get__()__get__()__delete__的原型以下:segmentfault

Descriptor.__get__(self, instance, owner)  --> value

Descriptor.__set__(self, instance, value) --> None

Descriptor.__delete__(self, instance) --> None

python的object.__get__(self, instance, owner):ide

Called to get the attribute of the owner class (class attribute access) or of an instance of that class函數

(instance attribute access). owner is always the owner class, while instance is the instance that the
attribute was accessed through, or None when the attribute is accessed through the owner. This method
should return the (computed) attribute value or raise an AttributeError exception.

2.描述器的訪問

整個描述器的核心是__getattribute__(),由於對像任何屬性的訪問都會調用到這個特殊的方法。這個方法被用來查找屬性,同時也是你的一個代理,調用它能夠進行屬性的訪問操做。
通常咱們的類的__getattribute__()方法都是繼承自object,本身改寫__getattribute__()是很危險的,也會阻止正常的描述器調用。__getattribute__()的Python描述原型以下:ui

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

若是經過實例ins訪問描述器,由__getattribute__()轉化爲:
type(ins).__dict__['attr'].__get__(ins, type(ins)
若是經過類Class訪問描述器,由__getattribute__()轉化爲:
Class.__dict__['attr'].__get__(None, Class)翻譯

class Descriptor(object):
    def __init__(self):
        self.aaaa = 'anonymous'

    def __get__(self, instance, owner):
        print('instance: %s' % instance)
        print('owner: %s' % owner)
        print("Invoke __get__: %s" % self.aaaa)
        return self.aaaa
 
    def __set__(self, instance, name):
        print("invoke __set__: %s" % name)
        self.aaaa = name.title()
 
    def __delete__(self, instance):
        print("Invoke __delete__: %s" % self.aaaa)
        del self.aaaa


class Person(object):
    name = Descriptor()

# 經過類Person訪問
print(Person.name)
# instance: None
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous

print(Person.__dict__['name'].__get__(None, Person))
# instance: None
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous

user = Person()

# 經過實例user訪問, `owner`訪問描述器實例的對象。`instance`則是訪問描述器實例的實例
print(user.name)
# instance: <__main__.Person object at 0x7f88c5472dd0>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous

print(type(user).__dict__['name'].__get__(user, type(user)))
# instance: <__main__.Person object at 0x7f0873fb5d90>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous

user.name = 'jack'
# invoke __set__: jack

del user.name
# Invoke __delete__: Jack

另外經過super訪問,如SubPersonPerson的子類,super(SubPerson, subins).name)訪問經過subins.__class__.__mro__查找到Person類,而後調用:
Person.__dict__['name'].__get__(subins, Person)代理

class SubPerson(Person):
    pass

subins = SubPerson()

print(subins.__class__.__mro__)
# (<class '__main__.SubPerson'>, <class '__main__.Person'>, <class 'object'>)

# 經過super訪問
print(super(SubPerson, subins).name)
# instance: <__main__.SubPerson object at 0x7f30b1537f28>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous

print(Person.__dict__['name'].__get__(subins, Person))
# instance: <__main__.SubPerson object at 0x7f30b1537f28>
# owner: <class '__main__.Person'>
# Invoke __get__: anonymous
# anonymous
class ClassA(object):

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

    def __getattr__(self, attr):
        return('invoke __getattr__', attr)

    def __getattribute__(self, attr):
        return('invoke __getattribute__', attr)


insA = ClassA('ClassA')
print(insA.__dict__)
# ('invoke __getattribute__', '__dict__')

print(insA.classname)
# ('invoke __getattribute__', 'classname')

print(insA.grade)
# ('invoke __getattribute__', 'grade')

實例訪問的優先級

上面提到實例ins訪問描述器,實際是由__getattribute__()訪問: type(ins).__dict__['attr'].__get__(ins, type(ins)
具體實現是依據這樣的優先順序是:數據描述器 > 實例屬性 > 非數據描述符 -> __getter__() 方法
以下,咱們user.name = 'andy'咱們經過實例對屬性name賦值,但因爲數據描述器優先級高於實例屬性。賦值操做被數據描器中的__set__方法截獲,咱們在__set__忽略了從新賦值(固然也能夠在其中更新賦值,但實質不是經過實例屬性綁定的方式)。易見實例user的屬性字典__dict__仍是空的。code

class Descriptor(object):
    def __init__(self, name):
        self.aaaa = name

    def __get__(self, instance, owner):
        print("Invoke __get__: %s" % self.aaaa)
        return self.aaaa
 
    def __set__(self, instance, name):
        print("invoke __set__, ignore assignment.")
 
    def __delete__(self, instance):
        print("Invoke __delete__: %s" % self.aaaa)
        del self.aaaa

class Person(object):
    name = Descriptor('jack')
user = Person()

print(user.name)
# Invoke __get__: jack
# jack
print(user.__dict__)
# {}

user.name = 'andy' # 實例屬性賦值
# invoke __set__, ignore assignment.

print(user.name)
# Invoke __get__: jack
# jack
print(user.__dict__)
# {}

再看非數據描述器和實例屬性比較。user.name = 'andy'成功的把屬性name綁定到user.__dict__中。htm

class Descriptor(object):
    def __init__(self, name):
        self.aaaa = name

    def __get__(self, instance, owner):
        print("Invoke __get__: %s" % self.aaaa)
        return self.aaaa

class Person(object):
    name = Descriptor('jack')

user = Person()

print(user.name)
# Invoke __get__: jack
# jack
print(user.__dict__)
# {}

user.name = 'andy'

print(user.name)
# andy
print(user.__dict__)
# {'name': 'andy'}

類對象訪問的優先級

若是經過類Class訪問描述器,由__getattribute__()訪問:Class.__dict__['attr'].__get__(None, Class)
優先級是:類屬性 > 描述器。
經過類對象Person.name = 'andy'更新屬性name,並無進入到描述器的__set__方法中,並且Person.__dict__中的屬性name也由描述器<__main__.Descriptor object at 0x7f1a72df9710>更新爲字符串'andy'。可見類屬性的優先級高於描述器。

class Descriptor(object):
    def __init__(self, name):
        self.aaaa = name

    def __get__(self, instance, owner):
        print("Invoke __get__: %s" % self.aaaa)
        return self.aaaa
 
    def __set__(self, instance, name):
        print("invoke __set__, ignore assignment.")
 
    def __delete__(self, instance):
        print("Invoke __delete__: %s" % self.aaaa)
        del self.aaaa

class Person(object):
    name = Descriptor('jack')

print(Person.__dict__)
# {'__module__': '__main__', 'name': <__main__.Descriptor object at 0x7f1a72df9710>, 
# '__dict__': <attribute '__dict__' of 'Person' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Person' objects>}
# Invoke __get__: jack

print(Person.name)
# jack
Person.name = 'andy'

print(Person.__dict__)
# {'__module__': '__main__', 'name': 'andy', '__dict__': <attribute '__dict__' of 'Person' objects>, 
# '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Person' objects>}
print(Person.name)
# andy

綜上,__getattribute__方法查找屬性的優先級是:
類屬性 > 數據描述器 > 實例屬性 > 非數據描述符 > __getter__() 方法
若是有__getattribute__方法,當__getattribute__出現異常時可能會調用__getter__()

3. 函數都是非數據描述器

函數包含一個 __get__()方法以便在屬性訪問時綁定方法。這就是說全部的函數都是非資料描述器,它們返回綁定(bound)仍是非綁定(unbound)的方法取決於他們是被實例調用仍是被類調用。用Python代碼來描述就是:

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

ins = MyClass()

print(MyClass.__dict__['foo'])
# <function MyClass.foo at 0x7fc7cf543a60>

print(MyClass.foo)
# <function MyClass.foo at 0x7fc7cf543a60>

print(ins.foo) # # 從實例來訪問,返回bound method
# <bound method MyClass.foo of <__main__.MyClass object at 0x7fc7cf552710>>

4. 描述器的使用

描述器就是屬性訪問的代理,經過描述器來訪問屬性,須要把描述器(實例)做爲一個類的屬性(做爲實例的屬性沒啥用),經過內部的__get__,__set__,__delete__方法處理對一個屬性的操做。

常規類方法建立描述器

class Descriptor(object):
    def __init__(self, name):
        self.aaaa = name

    def __get__(self, instance, owner):
        print("Invoke __get__: %s" % self.aaaa)
        return self.aaaa
 
    def __set__(self, instance, name):
        print("invoke __set__, ignore assignment.")
 
    def __delete__(self, instance):
        print("Invoke __delete__: %s" % self.aaaa)
        del self.aaaa

class Person(object):
    name = Descriptor('jack')

user = Person()

user.name = 'andy'
# invoke __set__, ignore assignment.
print(user.name)
# Invoke __get__: jack
# jack
del user.name
# Invoke __delete__: jack

使用property類建立描述器

class property(fget=None, fset=None, fdel=None, doc=None)fget是獲取屬性的函數,fset是設置屬性的函數,fdel是刪除屬性的函數,doc是這個屬性的文檔字符串。

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        print('invoke getx')
        return self._x

    def setx(self, value):
        print('invoke setx')
        self._x = value

    def delx(self):
        print('invoke delx')
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")

ins = C()

ins.x = 'property'
# invoke setx

print(ins.x)
# invoke getx
# property

print(C.x.__doc__)
# I'm the 'x' property.

del ins.x
# invoke delx

使用 @property 裝飾器建立描述器

這種使用很普遍,在python源碼中常常碰見。

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

ins = C()

ins.x = 'property'

print(ins.x)
# property

print(C.x.__doc__)
# I'm the 'x' property.

del ins.x

Property純Python的等價實現

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    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
        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"
        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__)

5. StaticMethod 和 ClassMethod

非數據描述器 StaticMethod 的 Python版本:

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

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

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

class E(object):

    @staticmethod
    def f(x):
          print(x)
    # f = staticmethod(f)

E.f(3)
# 3
E().f(3)
# 3

非數據描述器 ClassMethod 的 Python版本:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

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

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

class E(object):
    def f(klass, x):
        return klass.__name__, x
    f = classmethod(f)

print(E.f(3))
# ('E', 3)
print(E().f(3))
# ('E', 3)

print(vars(E))
# {'__module__': '__main__', 'f': <classmethod object at 0x028DAAF0>, 
# '__dict__': <attribute '__dict__' of 'E' objects>, '__weakref__': 
# <attribute '__weakref__' of 'E' objects>, '__doc__': None}

print(vars(E()))
# {}

參考:

  1. Python幫助文檔: Python描述器引導(翻譯)

  2. python 描述符解析

  3. Introduction to Python descriptors

相關文章
相關標籤/搜索