The definition of descriptor descriptors in Python: Typically, we can assume that "a property of an object is bound (
__get__
,
__set__
,
__delete__
Any one of these three methods ", then we call this property" descriptor "
class Foo(object): def init(self, name, age): self.name = name self.age = agefoo = Foo("pizza", 18)
We cannot call Foo.name, foo.age These two attributes are descriptors, because none of them are bound to the above three methods. By default, the object's property access is achieved by using the three methods of Get, set, delete, which access the property's Dictionary __dict__. For example, a.x will first look for a.__dict__[' X '], and if not found, look for type (a). __dict__[' X '], and then keep looking up until metaclass (excluding Metaclass). As shown in the following code:
class Foo(object): country = "China" def __init__(self, name, age): self.name = name self.age = age foo = Foo("pizza", 18)print(foo.__dict__) # {‘name‘: ‘pizza‘, ‘age‘: 18}print(type(foo).__dict__) # {‘__module__‘: ‘__main__‘, ‘country‘: ‘China‘, ‘__init__‘: <function Foo.__init__ at 0x103802488>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘Foo‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Foo‘ objects>, ‘__doc__‘: None}
In the above code, if print (Foo.name) or print (foo.age) or find
foo.__dict__
If print (foo.country) looks for the type (foo)
Foo.__dict__
。 If a descriptor is encountered during a lookup, the Python interpreter replaces the lookup order with the method in the descriptor, in which case the __dict__ or descriptor of the object is first found, depending on the descriptor type, which we will demonstrate in the following subsections. Descriptor protocol
descr.__get__(self, obj, type=None) --> valuedescr.__set__(self, obj, value) --> Nonedescr.__delete__(self, obj) --> None
If any of the above three methods are defined, then we can assume that the object is a descriptor object that overrides the order in which the object's properties are looked up. As shown in the following code:
class Bar(object): def __get__(self, instance, owner): print("__get__") def __set__(self, instance, value): print("__set__") def __delete__(self, instance, value): print("__delete__") class Foo(object): bar = Bar() foo = Foo()
In the above code, the bar attribute of Foo is considered to be a descriptor. Descriptor types are mentioned above, and descriptors are divided into Data descriptor and Non-data descriptor. If an object defines the
__get__
() and
__set__
() These two methods, then we think the object is a data descriptor. If you define only the
__get__
() method, that is Non-data descriptor, as shown in the following code: Data descriptor
class Bar(object): def __get__(self, instance, owner): print("get") def __set__(self, instance, value): print("__set__") def __delete__(self, instance, value): print("__delete__")class Foo(object): bar = Bar() foo = Foo()
In the above code, the Bar property of Foo is considered to be a descriptor, and it is the data descriptor. Non-data Descriptor
class Bar(object): def __get__(self, instance, owner): print("__get__")class Foo(object): bar = Bar()foo = Foo()
In the above code, the Bar property of Foo is considered to be a descriptor, and it is non-data descriptor. The difference between Data and non-data descriptors is in the way that object properties are accessed. If the object's dictionary
__dict__
Has a property with the same name as data descriptor, data descriptor overwrites
__dict__
Lookup, as shown in the following code:
class Bar(object): def __get__(self, instance, owner): print("__get__") def __set__(self, obj, value): print("__set__")class Foo(object): bar = Bar() def __init__(self, name, age): self.name = name self.age = age self.bar = "bar"foo = Foo("pizza", 18)foo.bar # __get__
In the above code, the Bar property lookup of the Foo object executes the object's
__get__
Method. Because, Data descriptor will overwrite
__dict__
The lookup. If the object's dictionary
__dict__
Has a property with the same name as Non-data descriptor, then the object's
__dict__
The lookup overwrites Non-data descriptor, as shown in the following code:
class Bar(object): def __get__(self, instance, owner): print("__get__") class Foo(object): bar = Bar() def __init__(self, name, age): self.name = name self.age = age self.bar = "bar"foo = Foo("pizza", 18)foo.bar # "bar"
In the above code, the Bar property lookup for Foo object will print "bar" because the object's
__dict__
The lookup overwrites the Non-data descriptor. The default property of Python in Python object-oriented design, there is a very important point of knowledge, called the property, it is implemented in a variety of ways, we demonstrate one of the following code:
class Foo(object): def __init__(self, name): self._name = name @property def name(self): return self._name foo = Foo("Pizza")print(foo._name) # "Pizza"print(foo.name) # "Pizza"
In the preceding code example, we use Foo._name to find the value of the property "Pizza", and we can also find the value of the property "Pizza" by Foo.name (because the property returns
self._name
)。 We do not make common function calls by defining a name method in the Foo class and then implementing the Access method through the default adorner property. In this process, there is a doubt, since you want to access the method of the object through the way of the property, and the return value is a property, then why not directly in the
__init__
A property is defined inside. Instead of simply returning the value of an existing property, for example, the property is dynamically getting a value and preserving the way the property is accessed, such as:
class Foo(object): def __init__(self, name): self._name = name @property def stock(self): return 100 + 100 foo = Foo("Pizza")print(foo._name) # "Pizza"print(foo.stock) # 200
As we mentioned in the previous chapters, the implementation of Property,static Method,class method in Python relies on the mechanism of descriptor. So, next, we come from defining a property. Using descriptor custom property from the previous section, we can see that the method in the class is modified to a property, that is, the adorner @property is used. We know the adorner syntax sugar @decorator, equivalent to Func = Decorator (func), as shown in the following code:
class Foo(object): def stock(self): return 100 + 100 print(stock) # <function Foo.stock at 0x101a57048>class Foo(object): @property def stock(self): return 100 + 100 print(stock) # <property object at 0x103811c78>
In the preceding code example, print (stock) results in a
<property object at 0x103811c78>
And
<function Foo.stock at 0x101a57048>
, even after adding @property to the stock method, the stock method becomes the object of the property, with the first print (stock)
<function Foo.stock at 0x101a57048>
Different. Next, our goal is to implement the custom property through descriptor. Before implementing a custom property, let's assume that there is a class, as shown in the following code:
class Foo(object): def stock(self): return 100 + 100foo = Foo()foo.stock
We know the following points:
- Adorner syntax sugar @property equivalent to stock = property (stock);
- A descriptor is an instantiated object of a class, such as Bar = Bar (), which is then defined in the bar class
__get__, __set__, __delete__
;
Our goal is to get a return value of 200 by means of a similar property access (Foo.stock) instead of a method call (Foo.stock ()). First, we use descriptors to implement simple property access, as shown in the following code:
class Stock(object): def __get__(self, instance, owner): print("__get__") return 100 + 100 class Foo(object): stock = Stock() foo = Foo()foo.stock
At this point, the access to Foo.stock will first print
__get__
, and then show 200. So, what if we turn the stock into a method in the class? As shown in the following code:
class Stock(object): def __get__(self, instance, owner): print("__get__") return 100 + 100class Foo(object): def stock(self): print("stock")
If the stock method can be changed into a descriptor, then we can access the descriptor by Foo.stock
__get__
method, and then gets its return value, both, 200. We know that turning a property into a descriptor is done directly by binding the property to the
__get__
method, such as: stock = stock (), but how to use the adorner syntax sugar? We know that the adorner syntax sugar @stock is equivalent to the stock = stock (stock), so we need to define one in the stock class
__init__
method, and define a formal parameter to receive the stock function that is passed in when the stock class is instantiated, as shown in the following code:
class Stock(object): def __init__(self, stock): self.stock = stock def __get__(self, instance, owner): print("__get__") return 100 + 100 class Foo(object): @Stock # stock = Stock(stock) def stock(self): print("stock")
Through the above, the code, we will be in the Foo class of the stock method, successfully turned into a descriptor, next we can use the Foo class instantiation object to access the stock method, and using the Normal property invocation method, This is because the stock method in the Foo class is already a descriptor.
foo = Foo()foo.stock
The above code will print first
__get__
, and then show 200. In fact, the careful classmate will find that if the implementation of this way, we have achieved a custom property, but, with the official genuine property there is still a gap, the gap is that When accessing Foo.stock, the stock in the Foo class is not executed, and the properties in the original property are executed, that is, the value we need to get at the end is obtained directly from the property, as shown in the following code:
class Foo(object): @property def stock(self): return 100 + 100 foo = Foo()foo.stock
In the above code example, we obtained the result 200 through foo.stock, which is calculated by the method of the stock in the Foo class. What if I want to use the same approach in my custom property? We know that in defining
__get__
method, it accepts three parameters, the first self represents descriptor, and below we print the second and third parameters, respectively, to see what they represent:
class Stock(object): def __init__(self, stock): self.stock = stock def __get__(self, instance, owner): print("__get__") print("instance: ", instance) print("owner:", owner) return 100 + 100 class Foo(object): @Stock def stock(self): print("stock") foo = Foo()foo.stock
The above code executes the following results:
__get__instance: <__main__.Foo object at 0x106c2b9b0>owner: <class ‘__main__.Foo‘>200
From the execution results of the above code can be seen, instance and owner of the two parameters, respectively, is passed to the Foo and Foo two objects, one is the Foo class instantiation object, and the Foo class itself, then whether we can use Foo or foo in
__get__
method, call the stock? The answer is no, because at this time the stock is already a descriptor, if the
__get__
method is called, then it goes into the dead loop and repeats the execution
__get__
Method. The original method of the stock in the Foo class was passed to the stock class in the @stock.
__init__
method is initialized, so at this point we can only invoke it in the way that is used in the following code example:
class Stock(object): def __init__(self, stock): self.stock = stock def __get__(self, instance, owner): return self.stock(instance) class Foo(object): @Stock def stock(self): return 100 + 100 foo = Foo()foo.stock
The above code executes the following results:
200
We can see from the results that the results are consistent with the previous implementation. Simply modify the code example to be more understandable, as follows:
class myproperty(object): def __init__(self, stock): self.stock = stock def __get__(self, instance, owner): return self.stock(instance) class Foo(object): @myproperty def stock(self): return 100 + 100 foo = Foo()foo.stock
At this point, we have implemented a custom property.
The descriptor in Python