描述器功能强大,应用普遍,它能够控制咱们访问属性、方法的行为,是@property、super、静态方法、类方法、甚至属性、实例背后的实现机制,是一种比较底层的设计,所以理解起来也会有一些困难。它们在 Python 内部被普遍使用来实现自 2.2 版中引入的新式类html
一个描述器是一个包含 “绑定行为” 的对象,对其属性的访问被描述器协议中定义的方法覆盖。这些方法有:__get__()
,__set__()
和 __delete__()
。若是某个对象中定义了这些方法中的任意一个,那么这个对象就能够被称为一个描述器。python
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.x
是m实例调用了x这个类属性,然而这个类属性不是普通的值,而是一个描述器,因此咱们从访问这个类属性变成了访问这个描述器__get__
方法__set__
方法.x
触发了__get__
方法,获得的是self.value
的值,在前面__init__
中定义的为10m.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访问,如SubPerson
是Person
的子类,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
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
这种使用很普遍,在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
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 的 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())) # {}
参考连接
毫无理想而又优柔寡断是一种可悲的心理。 -----培根