python-描述符(descriptor)

2018-04-07  本文已影响0人  大碗喝茶丶

原文地址:Descriptor HowTo Guide

描述符定义

一般来说,描述符是一个具有“binding behavior”的对象属性,它的属性访问被描述符协议里的方法重写。这些方法是__get__(),__set__(),__delete__(),如果这三个方法中任意一个被某个对象实现,那么该对象就被成为描述符。

属性访问的默认行为是从对象的词典中get、set或者delete属性。例如,a.x则依次从a.__dict__['x']type(a).__dict__['x']type(a)的除元类外的基类的__dict__中查找。然后最终的查找结果是描述符的话,那么Python可能会重写默认行为并调用描述符方法,这取决于定义了哪些描述符方法。Note that descriptors are only invoked for new style objects or classes (a class is new style if it inherits from object or type).

描述符是一个强大的通用协议,它们是属性,方法,静态方法,类方法和super()的机制。它们被用于整个Python本身,以实现版本2.2中引入的新风格类。描述符简化了底层C代码,为日常Python程序提供了一套灵活的新工具。

描述符协议

执行后输出:

  {'d': 'dict_d'}
  invoking __get__
  12

如果把类Desc__set__()注释掉,那么它就从“data descriptor”变成了“no-data descriptor”。这样再次执行一次,输入:

  {'d': 'dict_d'}
   dict_d

同时定义__get__()__set__(),然后使__set__()方法抛出AttributeError异常,这就成了只读的描述符

调用描述符

描述符可以直接通过方法名调用,比如d.__get__(obj).
另外,更常见的是通过属性访问自动调用,比如:obj.b则从obj的字典中查找b,如果b定义了__get__()方法,__get__(obj)将会通过下面列出的优先级规则被调用。
调用的细节取决于obj是对象还是类。无论是那种,但必须是新式对象或新式类(object的子类是新式类)。
如果是对象,该机制由object.__getattribute__()实现,当b.x时实际执行的是type(b).__dict__['x'].__get__(b, type(b))。优先级是这样的:"data descriptions" > 实例变量 > "no-data descriptions" > getattr_().
如果是类。该机制由type.__getattribute__()实现,它将B.b转变为B.__dict__['x'].__get__(None, B)来执行。如果用纯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

(不懂,__getattribute__(),__getattr__,object,type

重点是:

Properties

调用property()是一种简洁的构建数据描述符的方法,可以在访问属性时触发函数调用:property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

class C(object):
    def getx(self): return self.__x
    def setx(self, value): self.__x = value
    def delx(self): del self.__x
    x = property(getx, setx, delx, "I'm the 'x' property.")

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
          if doc is None and fget is not None:
              doc = fget.__doc__
          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__)

内建函数property()适用于向用户开放了读权限,但修改值需要用方法来处理的时候。

class Cell(object):
    . . .
    def getvalue(self):
        "Recalculate the cell before returning value"
        self.recalc()
        return self._value
    value = property(getvalue)

然后就可以通过Cell("B2").value来获取值了。
还有这种装饰器用法:

  class Student(object):
  
      @property
      def score(self):
          return self._score
  
      @score.setter
      def 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 = value
上一篇下一篇

猜你喜欢

热点阅读