In general, a descriptor is an object attribute that has "binding behavior" and whose access control is overridden by the descriptor protocol methods. These methods are __get__(), __set__(), and __delete__(). An object with these methods is called a descriptor.
The default access control for attributes is to get, set, and delete them from the object's dictionary (__dict__). For example, the lookup order is, a.__dict__['x'] , then type(a). __dict__['x'] , then type(a)'s parent (excluding metaclasses). If the value found by the lookup is a descriptor, Python calls the descriptor's methods to override the default control behavior. Where in the lookup chain this override occurs depends on which descriptor method is defined. Note that descriptors only work in newer classes. (A newer class is one that inherits from type or object).
Descriptors are powerful and widely used. Descriptors are the implementation mechanism behind properties, instance methods, static methods, class methods and super. Descriptors are widely used in Python itself to implement the new style of classes introduced in Python 2.2. Descriptors simplify the underlying C code and provide a flexible new set of tools for everyday programming in Python.
descriptor protocol
descr.__get__(self, obj, type=None) --> value descr.__get__(self, obj, value) --> None descr.__delete__(self, obj) --> None
An object that is a descriptor that is treated as an object property (important) overrides the default lookup behavior.
If an object defines both __get__ and __set__, it is called a data descriptor. a descriptor that defines only __get__ is called a non-data descriptor.
The difference between a data descriptor and a non-data descriptor is the following: Priority over the instance's dictionary; if the instance dictionary has an attribute with the same name as the descriptor, the data descriptor is used first if the descriptor is a data descriptor; if it is a non-data descriptor, the attribute in the dictionary is used first if the descriptor has the same name as the descriptor; if it is a data descriptor, the attribute in the dictionary is used first. , the attributes in the dictionary are used in preference.
class B(object): def __init__(self): = 'mink' def __get__(self, obj, objtype=None): return class A(object): name = B() a = A() print a.__dict__ # print {} print # print mink = 'kk' print a.__dict__ # print {'name': 'kk'} print # print kk
Here B is a non-data descriptor so when = 'kk' there will be a name attribute in a.__dict__, next set it to __set__.
def __set__(self, obj, value): = value ... do something a = A() print a.__dict__ # print {} print # print mink = 'kk' print a.__dict__ # print {} print # print kk
Because the data descriptor accesses attributes with higher priority than the instance's dictionary, a.__dict__ is empty.
Descriptor Calls
The descriptor can be called directly like this: d.__get__(obj)
However, it is more common for descriptors to be called automatically when properties are accessed. For example, d is looked up in obj's dictionary, and if d defines a __get__ method, then d.__get__(obj) is called according to the following precedence rules.
The details of the call depend on whether obj is a class or an instance. In addition, the descriptor only works for newer objects and newer classes. Classes that inherit from object are called new-style classes.
For objects, the method object.__getattribute__() turns into type(b). __dict__['x']. __get__(b, type(b)). The implementation is based on an order of precedence where profile descriptors take precedence over instance variables, instance variables take precedence over non-profile descriptors, and the __getattr__() method (if included in the object) has the lowest precedence. The full C implementation can be viewed at PyObject_GenericGetAttr() in Objects/.
For classes, method type.__getattribute__() turns into B.__dict__['x']. __get__(None, B). In Python, this would be.
def __getattribute__(self, key): "Emulate type_getattro() in Objects/" v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(None, self) return v
Some of the important points:
- The descriptor is called because __getattribute__()
- Overriding the __getattribute__() method blocks normal descriptor calls
- __getattribute__() is only available for instances of newer classes.
- object.__getattribute__() and type.__getattribute__() do not make the same call to __get__().
- The profile descriptor always takes precedence over the instance dictionary.
- Non-informational descriptors may be overridden by instance dictionaries. (Non-informational descriptors are less preferred than instance dictionaries)
- The object returned by super() also has a customized __getattribute__() method for calling the descriptor. A call to super(B, obj).m() first looks up the base class A immediately adjacent to B in obj.__class__. __mro__ to find the base class A immediately adjacent to B, and then returns A.__dict__['m']. __get__(obj, A). If it is not a descriptor, return m as is. If m is not found in the instance dictionary, it backtracks to continue calling object.__getattribute__() to find it. (Translation: i.e., look in the next base class in __mro__)
Caution.In Python 2.2, if m is a descriptor, super(B, obj).m() only calls the method __get__(). In Python 2.3, non-informational descriptors (unless it's an old-style class) are also called. The implementation details of super_getattro() are at: Objects/ , [del] An equivalent Python implementation is at Guido's Tutorial [/del] (Translator's note: this sentence has been removed from the original, and is kept for your reference).
The above shows that the descriptor mechanism is implemented in the __getattribute__() methods of object, type, and super. Classes derived from object automatically inherit this mechanism, or they have a metaclass with a similar mechanism. Similarly, the __getattribute__() method of a class can be overridden to turn off the descriptor behavior of that class.
Descriptor Example
The following code defines a profile descriptor that prints a message for each get and set. Overriding __getattribute__() is another way to make all attributes have this behavior. However, descriptors are useful when monitoring specific attributes.
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'): = initval = name def __get__(self, obj, objtype): print 'Retrieving', return def __set__(self, obj, val): print 'Updating' , = val >>> class MyClass(object): x = RevealAccess(10, 'var "x"') y = 5 >>> m = MyClass() >>> Retrieving var "x" 10 >>> = 20 Updating var "x" >>> Retrieving var "x" 20 >>> 5
The protocol is very simple and offers exciting possibilities. Some uses are so common that they are packaged into separate functions. Things like properties, methods (bound and unbound method), static methods and class methods are all based on the descriptor protocol.