Python|Python descriptor and attribute access

Descriptor In general, a descriptor is an object attribute with binding behavior, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
Descriptor protocol

  • descr.__get__(self, obj, type=None) --> value
    descr.__set__(self, obj, value) --> None
    descr.__delete__(self, obj) --> None
  • If an object defines both __get__() and __set__(), it is considered a data descriptor. Descriptors that only define __get__() are called non-data descriptors.
Invoking descriptor GET attribute
  • 1 调用 object.__getattribute__(self, name)
  • 2 Data descriptors, like property
  • 3 Instance variables from the object's __dict__
  • 4 Non-Data descriptors (like methods) and other class variables
  • 5 __getattr__
SET attribute
  • 1 调用 setattr
  • 2 Data descriptors, like property
  • 3 nstance variables from the object's __dict__
具体过程:
  • The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.
  • For objects, the machinery is in object.__getattribute__() which transforms b.x into type(b).__dict__['x'].__get__(b, type(b))
  • For classes, the machinery is in type.__getattribute__() which transforms B.x into B.__dict__['x'].__get__(None, B).
  • The object returned by super() also has a custom __getattribute__() method for invoking descriptors. The call super(B, obj).m() searches obj.__class__.__mro__ for the base class A immediately following B and then returns A.__dict__['m'].__get__(obj, B). If not a descriptor, m is returned unchanged. If not in the dictionary, m reverts to a search using object.__getattribute__().
!Note: descriptors are invoked by the __getattribute__() method
@property Calling property() is a succinct way of building a data descriptor that triggers function calls upon access to an attribute.
  • 用纯 Python 实现 Property:
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__ = docdef __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__)

  • A typical use is to define a managed attribute x:
    If c is an instance of C, c.x will invoke the getter, c.x = value will invoke the setter and del c.x the deleter.
class C: def __init__(self): self._x = Nonedef getx(self): return self._xdef setx(self, value): self._x = valuedef delx(self): del self._xx = property(getx, setx, delx, "I'm the 'x' property.")

  • Using property as a decorator:
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

Functions and Methods
  • Python’s object oriented features are built upon a function based environment. Using non-data descriptors.
  • Class dictionaries store methods as functions. Methods only differ from regular functions in that the first argument is reserved for the object instance. By Python convention, the instance reference is called self but may be called this or any other variable name.
  • bound 和 unbound method 虽然表现为两种不同的类型,但是在C源代码里,是同一种实现。如果第一个参数im_selfNULL,就是unbound method; 如果im_self有值,那么就是bound method。
  • To support method calls, functions include the __get__() method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound methods when they are invoked from an object. In pure python, it works like this:
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)

Static Methods and Class Methods Using the non-data descriptor protocol, a pure Python version of staticmethod() would look like this:
class StaticMethod(object): "Emulate PyStaticMethod_Type() in Objects/funcobject.c"def __init__(self, f): self.f = fdef __get__(self, obj, objtype=None): return self.f

  • Using the non-data descriptor protocol, a pure Python version of classmethod() would look like this:
class ClassMethod(object): "Emulate PyClassMethod_Type() in Objects/funcobject.c"def __init__(self, f): self.f = fdef __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc

Other Magic Methods __getitem__, __setitem__, __delitem__
  • object.__getitem__(self, key)
    Called to implement evaluation of self[key].
  • object.__setitem__(self, key, value)
    Called to implement assignment to self[key].
  • object.__delitem__(self, key)
    Called to implement deletion of self[key].
  • examples
class MyList(object): def __init__(self, *args): self.numbers = list(args) def __getitem__(self, item): return self.numbers[item] def __setitem__(self, item, value): self.numbers[item] = value def __delitem__(self, item): del self.numbers[item]my_list = MyList(1, 2, 3, 4, 6, 5, 3) print(my_list[2])# 3 my_list[2] = 10 print(my_list[2])# 10 del my_list[2] print(my_list[2])# 4

introspection function
  • hasattr(object, name)
    The arguments are an object and a string. The result is True if the string is the name of one of the object’s attributes, False if not.
  • getattr(object, name[, default])
    Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.
  • setattr(object, name, value)
    The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.
  • delattr(object, name)
    The arguments are an object and a string. The string must be the name of one of the object’s attributes. The function deletes the named attribute, provided the object allows it. For example, delattr(x, 'foobar') is equivalent to del x.foobar.
  • Python的hasattr() getattr() setattr() 函数使用方法详解
__setattr__, __getattr__, __delattr__
  • object.__setattr__(self, name, value)
    Called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.
    If __setattr__() wants to assign to an instance attribute, it should call the base class method with the same name, for example, object.__setattr__(self, name, value).
!NOTE: 若在 __setattr__ 中直接为: self.name = value, 会造成递归死循环
  • object.__getattr__(self, name)
    Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.
    Note that if the attribute is found through the normal mechanism, __getattr__() is not called.
  • object.__getattribute__(self, name)
    Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an AttributeError. This method should return the (computed) attribute value or raise an AttributeError exception. In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).
  • object.__delattr__(self, name)
    Like __setattr__() but for attribute deletion instead of assignment. This should only be implemented if del obj.name is meaningful for the object.
dir() v.s. __dict__
  • dir([object])
    Without arguments, return the list of names in the current local scope. With an argument, attempt to return a list of valid attributes for that object.
  • object.__dict__
    A dictionary or other mapping object used to store an object’s (writable) attributes.
具体用例见 Python dict与dir()区别
参考
  • Descriptor HowTo Guide
  • 理解Python对象的属性和描述器
  • Python ducumentation: data model
  • Python documentation: Built-in Functions
read more( 进阶 )
  • python描述器与属性查找
  • How Does Attribute Access Work?
  • 【Python|Python descriptor and attribute access】描述符

    推荐阅读