python描述器介绍

简介

描述器功能强大,应用普遍,它能够控制咱们访问属性、方法的行为,是@property、super、静态方法、类方法、甚至属性、实例背后的实现机制,是一种比较底层的设计,所以理解起来也会有一些困难。它们在 Python 内部被普遍使用来实现自 2.2 版中引入的新式类html

定义

一个描述器是一个包含 “绑定行为” 的对象,对其属性的访问被描述器协议中定义的方法覆盖。这些方法有:__get__()__set__() 和 __delete__()。若是某个对象中定义了这些方法中的任意一个,那么这个对象就能够被称为一个描述器。python

类型

  1. 描述符有__get__和__set__2个方法
  2. 非数据描述符只有一个__get__方法,一般用于方法。非数据描述符的优先级低于实例属性。

简单实例

class RevealAccess(object):
    """A data descriptor that sets and returns values
       normally and prints a message logging their access.
    """

    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print('Retrieving', self.name)
        return self.val

    def __set__(self, obj, val):
        print('Updating', self.name)
        self.val = val

class MyClass(object):
    x = RevealAccess(10, 'var "x"')
    y = 5

m = MyClass()
print("="*20)
print(m.x)
print("="*20)
m.x = 20
print(m.x)
print("="*20)
print(m.y)

输出:express

====================
Retrieving var "x"
10
====================
Updating var "x"
Retrieving var "x"
20
====================
5

上面这个例子中:segmentfault

  • 建立m实例和普通类没什么区别,咱们从m.x开始看
  • m.x是m实例调用了x这个类属性,然而这个类属性不是普通的值,而是一个描述器,因此咱们从访问这个类属性变成了访问这个描述器
  • 若是调用时获得的是一个描述器,python内部就会自动触发一套使用机制
  • 访问的话自动触发描述器的__get__方法
  • 修改设置的话就自动触发描述器的__set__方法
  • 这里就是m.x触发了__get__方法,获得的是self.value的值,在前面__init__中定义的为10
  • m.x = 20则触发了__set__方法,赋的值20传到value参数之中,改变了self.value的值,因此下一次m.x调用的值也改变了

描述器的访问

整个描述器的核心是__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)this

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

访问的优先级

实例

上面提到实例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'。可见类属性的优先级高于描述器。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')

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

类属性 > 数据描述器 > 实例属性 > 非数据描述符 > __getter__() 方法
若是有__getattribute__方法,当__getattribute__出现异常时可能会调用__getter__()

函数都是非数据描述器

类字典将方法存储为函数。在类定义中,方法是用 def 或 lambda 这两个建立函数的经常使用工具编写的。方法与常规函数的不一样之处仅在于第一个参数是为对象实例保留的。按照 Python 约定,实例引用称为 self ,但也能够称为 this 或任何其余变量名称。

为了支持方法调用,函数包含 __get__() 方法用于在访问属性时将其绑定成方法。这意味着全部函数都是非数据描述器,当从对象调用它们时,它们返回绑定方法。在纯 Python 中,它的工做方式以下:

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

 描述器的使用

描述器就是属性访问的代理,经过描述器来访问属性,须要把描述器(实例)做为一个类的属性(做为实例的属性没啥用),经过内部的__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__)

实现StaticMethod

非数据描述器 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

非数据描述器 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()))
# {}

 

参考连接

python 描述符解析

描述器使用指南

Python 描述符简介

 

 

毫无理想而又优柔寡断是一种可悲的心理。   -----培根