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) --> NoneIf 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 witha.__dict__['x']
, thentype(a).__dict__['x']
, and continuing through the base classes of type(a) excluding metaclasses.For objects, the machinery is in
object.__getattribute__()
which transformsb.x
intotype(b).__dict__['x'].__get__(b, type(b))
For classes, the machinery is in
type.__getattribute__()
which transformsB.x
intoB.__dict__['x'].__get__(None, B)
.The object returned by
super()
also has a custom__getattribute__()
method for invoking descriptors. The callsuper(B, obj).m()
searchesobj.__class__.__mro__
for the base class A immediately following B and then returnsA.__dict__['m'].__get__(obj, B)
. If not a descriptor, m is returned unchanged. If not in the dictionary, m reverts to a search usingobject.__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__ = 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__)
- 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 anddel c.x
the deleter.
class C:
def __init__(self):
self._x = None
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.")
- 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_self
是NULL
,就是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 = f
def __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 = f
def __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 tox.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 tox.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 todel x.foobar
.
__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()区别