Python black magic Descriptor instance parsing, pythondescriptor
In Python, the priority order for accessing an attribute is as follows:
1: class attributes
2: Data Descriptor
3: instance attributes
4: Non-data Descriptor
5 :__ the complete definition of the getattr _ () method is as follows:
Def _ getattr (self, attr): # attr is an attribute name pass of self;
The following describes what a data descriptor is.
A Data descriptor is a class attribute that implements the _ get __,__ set __,__ del _ method (because in Python, everything is an object, so you may consider all attributes as objects)
PS: I personally think it is best to define the data descriptor as an interface that defines three methods: _ get __,__ set __,__ del.
_ Get __,__ set __,__ del __
The following three methods are described:
The standard definition of _ get _ is _ get _ (self, obj, type = None), which is very similar to the get of JavaBean.
The first function is to call its instance. obj refers to the method for accessing the attribute. The last type is an optional parameter, usually None (this is to be further studied)
For example, if Class X and instance x are given, calling x. foo is equivalent to calling:
type(x).__dict__['foo'].__get__(x,type(x))
Calling X. foo is equivalent to calling:
type(x).__dict__['foo'].__get__(None,type(x))
The standard definition of the second function _ set _ is _ set _ (self, obj, val), which is very close to the set Method of JavaBean, the last parameter is the value to be assigned.
The standard definition of the third function _ del _ is _ del _ (self, obj), which is very close to the Finailize () method of the Object in Java, it refers to the Destructor called by Python when it recycles this spam object, but this function will never throw an exception. Because this object has not been referenced to point to it, throwing an exception does not make any sense.
Priority
Next, we will compare these priorities one by one.
First, let's look at the class attributes.
class A(object): foo=1.3; print str(A.__dict__);
Output:
{'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 'foo': 1.3, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
It can be seen that the foo attribute is in the _ dict _ attribute of the class, so A. foo can be directly found here. Here, we first go over the data Descriptor and look at the instance attributes directly.
class A(object): foo=1.3; a=A(); print a.foo; a.foo=15; print a.foo;
Here, a. foo outputs 1.3 first and then 15. Doesn't it mean that the class attribute has a higher priority than the instance attribute? A. foo should remain unchanged? In fact, this is just an illusion. The real reason is that the reference object a. foo can be understood as a pointer to any data type and to the int object 15.
Do not believe it. You can continue to read it:
class A(object): foo=1.3; a=A(); print a.foo; a.foo=15; print a.foo; del a.foo; print a.foo;
This time, 1.3 is output, and 1.3 is output again and again after 15, because a. foo directly finds the class attribute A. foo in the order of priority.
Descriptor and Object Attributes
In OOP theory, class member variables include attributes and methods. So what is a property in Python? Modify the above PythonSite class as follows:
class PythonSite(object): webframework = WebFramework() version = 0.01 def __init__(self, site): self.site = site
Here, a version Class Attribute and an instance attribute site are added. View the attributes of the class and instance object respectively:
In [1]: pysite = PythonSite('ghost')In [2]: vars(PythonSite).items()Out[2]:[('__module__', '__main__'), ('version', 0.01), ('__dict__', <attribute '__dict__' of 'PythonSite' objects>), ('webframework', <__main__.WebFramework at 0x10d55be90>), ('__weakref__', <attribute '__weakref__' of 'PythonSite' objects>), ('__doc__', None), ('__init__', <function __main__.__init__>)]In [3]: vars(pysite)Out[3]: {'site': 'ghost'}In [4]: PythonSite.__dict__Out[4]:<dictproxy {'__dict__': <attribute '__dict__' of 'PythonSite' objects>, '__doc__': None, '__init__': <function __main__.__init__>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'PythonSite' objects>, 'version': 0.01, 'webframework': <__main__.WebFramework at 0x10d55be90>}>
The vars method is used to view object attributes. It is equivalent to the _ dict _ content of an object. From the preceding results, we can see that the attributes of the PythonSite class and the instance pysite differ in that the former has two attributes: webframework and version, and the _ init _ method. The latter has only one site attribute.
Class and instance attributes
Class attributes can be accessed using objects and classes. Multiple instance objects share one class variable. However, only the class can be modified.
In [6]: pysite1 = PythonSite('ghost')In [7]: pysite2 = PythonSite('admin')In [8]: PythonSite.versionOut[8]: 0.01In [9]: pysite1.versionOut[9]: 0.01In [10]: pysite2.versionOut[10]: 0.01In [11]: pysite1.version is pysite2.versionOut[11]: TrueIn [12]: pysite1.version = 'pysite1'In [13]: vars(pysite1)Out[13]: {'site': 'ghost', 'version': 'pysite1'}In [14]: vars(pysite2)Out[14]: {'site': 'admin'}In [15]: PythonSite.version = 0.02In [16]: pysite1.versionOut[16]: 'pysite1'In [17]: pysite2.versionOut[17]: 0.02
As shown in the code above, both instance objects can access the version class attribute, which is the same class attribute. When pysite1 modifies the version, it actually adds a version attribute to itself. The class property is not changed. When PythonSite changes the version attribute, this attribute of pysite2 is also changed.
Principles and descriptors of attribute access
The result of attribute access is displayed. This result is implemented based on the Python descriptor. Generally, a class or instance accesses a property through the. Operator. For example, access to pysite1.site and pysite1.version. First access the _ dict __of the object, if there is no partition class (or parent class, except the metadata class) _ dict __. If the last _ dict _ object is a descriptor, the _ get _ method of the descriptor is called.
In [21]: pysite1.siteOut[21]: 'ghost'In [22]: pysite1.__dict__['site']Out[22]: 'ghost'In [23]: pysite2.versionOut[23]: 0.02In [24]: pysite2.__dict__['version']---------------------------------------------------------------------------KeyError Traceback (most recent call last)<ipython-input-24-73ef6aeba259> in <module>()----> 1 pysite2.__dict__['version']KeyError: 'version'In [25]: type(pysite2).__dict__['version']Out[25]: 0.02In [32]: type(pysite1).__dict__['webframework']Out[32]: <__main__.WebFramework at 0x103426e90>In [38]: type(pysite1).__dict__['webframework'].__get__(None, PythonSite)Out[38]: 'Flask'
Instance method, class method, static method and Descriptor
When the descriptor is called, object. _ getattribute _ () is actually called __(). It depends on whether the object or class that describes its device is called. If it is an object obj. x, type (obj) is called ). _ dict _ ['X']. _ get _ (obj, type (obj )). If it is a class, class. x, type (class). _ dict _ ['X']. _ get _ (None, type (class) is called ).
In this case, the Python method, static method, and class method are analyzed below. Refactor PythonSite:
class PythonSite(object): webframework = WebFramework() version = 0.01 def __init__(self, site): self.site = site def get_site(self): return self.site @classmethod def get_version(cls): return cls.version @staticmethod def find_version(): return PythonSite.version
Class method, @ classmethod Modifier
First, let's look at the class method. The class method is defined by @ classmethod. The decorator is a descriptor. Class and instance can call the class method:
In [1]: ps = PythonSite('ghost')In [2]: ps.get_versionOut[2]: <bound method type.get_version of <class '__main__.PythonSite'>>In [3]: ps.get_version()Out[3]: 0.01In [4]: PythonSite.get_versionOut[4]: <bound method type.get_version of <class '__main__.PythonSite'>>In [5]: PythonSite.get_version()Out[5]: 0.01
Get_version is a bound method. Next, let's look at the ps. get_version call. We will first look for its _ dict _ whether it has the get_version attribute. If not, we will look for its class.
In [6]: vars(ps)Out[6]: {'site': 'ghost'}In [7]: type(ps).__dict__['get_version']Out[7]: <classmethod at 0x108952e18>In [8]: type(ps).__dict__['get_version'].__get__(ps, type(ps))Out[8]: <bound method type.get_version of <class '__main__.PythonSite'>>In [9]: type(ps).__dict__['get_version'].__get__(ps, type(ps)) == ps.get_versionOut[9]: True
In vars (ps), __dict _ does not have the get_version attribute. According to the descriptor protocol, type (ps) is called ). _ dict _ ['get _ version'] The _ get _ method of the descriptor. Because ps is an instance, the object. _ getattribute _ () calls _ get _ (obj, type (obj )).
Now let's look at the call of the class method:
In [10]: PythonSite.__dict__['get_version']Out[10]: <classmethod at 0x108952e18>In [11]: PythonSite.__dict__['get_version'].__get__(None, PythonSite)Out[11]: <bound method type.get_version of <class '__main__.PythonSite'>>In [12]: PythonSite.__dict__['get_version'].__get__(None, PythonSite) == PythonSite.get_versionOut[12]: True
Because this call to get_version is a Class object rather than an instance object, object. _ getattribute _ () will call _ get _ (None, Class) like this ).
Static Method, @ staticmethod
You can also call static methods for instances and classes:
In [13]: ps.find_versionOut[13]: <function __main__.find_version>In [14]: ps.find_version()Out[14]: 0.01In [15]: vars(ps)Out[15]: {'site': 'ghost'}In [16]: type(ps).__dict__['find_version']Out[16]: <staticmethod at 0x108952d70>In [17]: type(ps).__dict__['find_version'].__get__(ps, type(ps))Out[17]: <function __main__.find_version>In [18]: type(ps).__dict__['find_version'].__get__(ps, type(ps)) == ps.find_versionOut[18]: TrueIn [19]: PythonSite.find_version()Out[19]: 0.01In [20]: PythonSite.find_versionOut[20]: <function __main__.find_version>In [21]: type(ps).__dict__['find_version'].__get__(None, type(ps))Out[21]: <function __main__.find_version>In [22]: type(ps).__dict__['find_version'].__get__(None, type(ps)) == PythonSite.find_versionOut[22]: True
There is little difference with class methods. Their main difference is that within Class methods, class methods can have cls class references, but not static access. If static methods want to use class variables, only hard-coded class names are allowed.
Instance method
The instance method is the most complex and exclusive to the instance. When using a class call, it will be an unbound method.
In [2]: ps.get_siteOut[2]: <bound method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>In [3]: ps.get_site()Out[3]: 'ghost'In [4]: type(ps).__dict__['get_site']Out[4]: <function __main__.get_site>In [5]: type(ps).__dict__['get_site'].__get__(ps, type(ps))Out[5]: <bound method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>In [6]: type(ps).__dict__['get_site'].__get__(ps, type(ps)) == ps.get_siteOut[6]: True
The instance method is also an attribute of the class, but for the class, the descriptor changes it to the unbound method:
In [7]: PythonSite.get_siteOut[7]: <unbound method PythonSite.get_site>In [8]: PythonSite.get_site()---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-8-99c7d7607137> in <module>()----> 1 PythonSite.get_site()TypeError: unbound method get_site() must be called with PythonSite instance as first argument (got nothing instead)In [9]: PythonSite.get_site(ps)Out[9]: 'ghost'In [10]: PythonSite.__dict__['get_site']Out[10]: <function __main__.get_site>In [11]: PythonSite.__dict__['get_site'].__get__(None, PythonSite)Out[11]: <unbound method PythonSite.get_site>In [12]: PythonSite.__dict__['get_site'].__get__(None, PythonSite) == PythonSite.get_siteOut[12]: TrueIn [14]: PythonSite.__dict__['get_site'].__get__(ps, PythonSite)Out[14]: <bound method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>In [15]: PythonSite.__dict__['get_site'].__get__(ps, PythonSite)()Out[15]: 'ghost'
It can be seen that the class cannot directly call the instance method unless a class instance is manually bound to the descriptor. When using a class object to call the descriptor, the first parameter __get _ is None. To call the descriptor successfully, replace this parameter with instance ps, this process is the bound process of the method.
Instance
According to the previous definition, a class implementing _ get __,__ set __,__ del _ is called a data descriptor. Let's take a look at the next simple example.
class simpleDescriptor(object): def __get__(self,obj,type=None) : pass; def __set__(self,obj,val): pass; def __del__(self,obj): pass class A(object): foo=simpleDescriptor(); print str(A.__dict__); print A.foo; a=A(); print a.foo; a.foo=13; print a.foo;
Here, the get, set, and del methods are omitted. Although simple, they are also a data descriptor. Let's take a look at its output:
{'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 'foo': <__main__.simpleDescriptor object at 0x00C46930>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} None None None
As you can see, although we assigned a value to a. foo, it is still None, because the _ get _ method does not return anything.
To further deepen our understanding of the data descriptor, we will make a simple transformation.
class simpleDescriptor(object): def __init__(self): self.result=None; def __get__(self,obj,type=None) : return self.result-10; def __set__(self,obj,val): self.result=val+3; print self.result; def __del__(self,obj): pass class A(object): foo=simpleDescriptor(); a=A(); a.foo=13; print a.foo;
The output result is:
16 6
The first 16 is for. when foo is assigned a value, the value of 13 and 3 are artificially added as the value of foo. The second value of 6 is that we are returning. previously, foo manually subtracted 10 from it.
Therefore, we can guess that when defining the get and set methods in a conventional Python class, if there is no special requirement, the corresponding attribute is directly assigned or the attribute value is directly returned. If you define classes and inherit object classes, these methods are not required.
Next let's take a look at the instance attributes and non-data descriptors.
class B(object): foo=1.3; b=B(); print b.__dict__ #print b.bar; b.bar=13; print b.__dict__ print b.bar;
Output result:
{}{'bar': 13}13
We can see that the bar attribute is found in instance B. _ dict _, so this time we can get 13
So what is a non-data descriptor? To put it simply, it means that no class is implemented for the get, set, and del methods.
Let's take a look at the description of a function at will:
def hello(): pass print dir(hello)
Output:
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
We can see from the above that all functions have the get method, but there is no set or del method, so all class member functions are non-data descriptors.
Let's look at a simple example:
class simpleDescriptor(object): def __get__(self,obj,type=None) : return 'get',self,obj,type; class D(object): foo=simpleDescriptor(); d=D(); print d.foo; d.foo=15; print d.foo;
Output:
('get', <__main__.simpleDescriptor object at 0x00C46870>, <__main__.D object at 0x00C46890>, <class '__main__.D'>) 15
We can see that the instance attribute masks non-data descriptors.
Finally, let's take a look at the _ getatrr _ method. Its standard definition is __getattr _ (self, attr), where attr is the attribute name
Let's look at a simple example:
class D(object): def __getattr__(self,attr): return attr; #return self.attr; d=D(); print d.foo,type(d.foo); d.foo=15; print d.foo;
Output:
foo <type 'str'> 15
As you can see, when Python cannot find a method, it will turn to the _ getattr _ method.
Note that we should avoid unintentional recursion and make a slight change:
class D(object): def __getattr__(self,attr): #return attr; return self.attr; d=D(); print d.foo,type(d.foo); d.foo=15; print d.foo;
This time, a stack overflow exception will be thrown directly, as shown below:
RuntimeError: maximum recursion depth exceeded