Home  >  Article  >  Backend Development  >  Analysis of the usage skills of Python black magic @property decorator

Analysis of the usage skills of Python black magic @property decorator

高洛峰
高洛峰Original
2017-01-23 14:50:321189browse

@property What is the use? On the surface, it is to access a method as an attribute.
The code is the clearest

class Circle(object):
  def __init__(self, radius):
    self.radius = radius
  
  @property
  def area(self):
    return 3.14 * self.radius ** 2
  
c = Circle(4)
print c.radius
print c.area

You can see that although area is defined as a method, after adding @property, you can directly c.area and access it as a property.
Now the question comes, (not which excavator technology company Strong), every time c.area is called, it will be calculated once, which is a waste of CPU. How can it be calculated only once? This is lazy property.

class lazy(object):
  def __init__(self, func):
    self.func = func
  
  def __get__(self, instance, cls):
    val = self.func(instance)
    setattr(instance, self.func.__name__, val)
    return val
  
class Circle(object):
  def __init__(self, radius):
    self.radius = radius
  
  @lazy
  def area(self):
    print 'evalute'
    return 3.14 * self.radius ** 2
  
c = Circle(4)
print c.radius
print c.area
print c.area
print c.area

As you can see, 'evalute' is only output once. If you read my previous blog posts, you should have a good understanding of @lazy's mechanism.
Here, the lazy class has a __get__ method, indicating that it is a descriptor. When c.area was executed for the first time, due to order issues, it was first searched in c.__dict__. If it was not found, it was searched in the class space. In the class Circle, there is an area() method, so it was intercepted by __get__. .
In __get__, call the area() method of the instance to calculate the result, and dynamically add an attribute with the same name to the instance and assign the result to it, that is, add it to c.__dict__.
Execute c again .area, go to c.__dict__ first, because it already exists at this time, so you will not go through the area() method and __get__.

Attention points
Please pay attention to the following code scenarios :

Code snippet 1:
Python2.6 code

class Parrot(object):
  def __init__(self):
    self._voltage = 100000
  
  @property
  def voltage(self):
    """Get the current voltage."""
    return self._voltage
  
if __name__ == "__main__":
  # instance
  p = Parrot()
  # similarly invoke "getter" via @property
  print p.voltage
  # update, similarly invoke "setter"
  p.voltage = 12

Code snippet 2:
Python2.6 code

class Parrot:
  def __init__(self):
    self._voltage = 100000
  
  @property
  def voltage(self):
    """Get the current voltage."""
    return self._voltage
  
if __name__ == "__main__":
  # instance
  p = Parrot()
  # similarly invoke "getter" via @property
  print p.voltage
  # update, similarly invoke "setter"
  p.voltage = 12

The difference between codes 1 and 2 is that

class Parrot(object):

under python2.6, run the test respectively
snippet 1: An expected error message AttributeError: can't set attribute
Fragment 2: Correct operation

Refer to the python2.6 document, @property will provide a ready-only property, the above code does not Provide the corresponding @voltage.setter. It stands to reason that the code in fragment 2 will prompt a running error. In the python2.6 document, we can find the following information:

BIF:
property([fget[, fset[ , fdel[, doc]]]])
Return a property attribute for new-style classes (classes that derive from object).
It turns out that under python2.6, the built-in type object is not the default base class. If there is no clear explanation when defining the class (code snippet 2), the Parrot we define (code snippet 2) will not inherit object

and the object class just provides the @property function we need. We can find the following information in the document:

new-style class
Any class which inherits from object. This includes all built-in types like list and dict. Only new-style classes can use Python's newer , versatile features like __slots__, descriptors, properties, and __getattribute__().

At the same time, we can also verify it through the following methods
Python 2.6 code

class A:
  pass
 
>>type(A)
<type &#39;classobj&#39;>

Python 2.6 code

class A(object):
  pass
 
>>type(A)
<type &#39;type&#39;>

It can be seen from the returned 635a4e04c7149d4983c9db32b6c8ae2d, f5ae6bbdf402bfea04a71faa8c02de77 f5ae6bbdf402bfea04a71faa8c02de77 is the object type we need (Python 3.0 uses the object class as the default base class, so all will return f5ae6bbdf402bfea04a71faa8c02de77)

In order to consider the python version of the code Regarding compatibility issues during the transition period, I think that when defining class files, object should be explicitly defined as a good habit.

The final code will be as follows:

class Parrot(object):
  def __init__(self):
    self._voltage = 100000
 
  @property
  def voltage(self):
    """Get the current voltage."""
    return self._voltage
 
  @voltage.setter
  def voltage(self, new_value):
    self._voltage = new_value
  
if __name__ == "__main__":
  # instance
  p = Parrot()
  # similarly invoke "getter" via @property
  print p.voltage
  # update, similarly invoke "setter"
  p.voltage = 12

In addition, @property was added in 2.6 and 3.0, and 2.5 does not have this function.

For more Python black magic @property decorator usage skills analysis related articles, please pay attention to the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn