Python 描述器


1. 什么是描述器

描述器实际上是任何新式类(新式类是继承自 type 或者 object 的类),这种类至少实现了3个特殊的方法__get__, __set__, __delete__中的一个。而这3个特殊的方法充当描述器协议的作用。

同时实现了__get__()__get__()的类被称为数据描述器(data descriptor)。只实现了 __get__() 方法的类是非数据描述器(常用于方法,当然其他用途也是可以的)。

__get__()__get__()__delete__的原型如下:

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

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

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

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

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描述原型如下:

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__还是空的。

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__中。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值