Python from attribute to property detailed explanation

The difference in literal meaning

Both Attribute and property can be translated into attributes. Although their meanings are almost the same in both Chinese and English, there are still some differences. After several times of Google, I found a more reliable explanation:

According to Webster, a property is a characteristic that belongs to a thing’s essential nature and may be used to describe a type or species.
An attribute is a modifier word that serves to limit, identify, particularize, describe, or supplement the meaning of the word it modifies.

Simply put, property is the essential attribute of a class, which can be used to define and describe a category or species; attribute is used to specify the object it describes, which is the specific attribute of the object.

For example: Everyone has a mouth. Some people have a big mouth, which is one of the properties of a person, and a big mouth can only be said to be an attribute of some people.

In this sense, property is a subset of attribute.

Attributes and properties in Python

Back to Python.

Attribute and property are not distinguished in Java, but they are different in Python. The following is an (informal) definition given by Fluent Python (Chapter 19):

Explain separately next.

attribute

All data attributes and methods are attributes.According to the owner of the attribute, it can be divided into class attribute and instance attribute.All attributes of class or instance are stored in their own dict attributes.

E.g:

# Python3
classFoo():
 name ='Foo class attribute'
 def fn(self):
 pass
print('class attribute:', Foo.__dict__)print()
foo =Foo()
foo.name ='foo instance attribute'print('instance attribute:', foo.__dict__)

Output:

class attribute: {‘fn’: <function Foo.fn at 0x7fd135ec8ea0 , … , ‘name’: ‘Foo class attribute’}
instance attribute: {‘name’: ‘foo instance attribute’}

property

Property is to replace the data attribute with setter/getter methods for security reasons, for example, read-only attributes and attribute value legality verification.

Read-only attribute

E.g:

classFoo():
 def __init__(self, name):
 self.name = name

foo =Foo('I do not want to be changed')print('foo.name = ', foo.name)
foo.name ='Unluckily, I can be changed'print('foo.name = ', foo.name)

Output:

foo.name = I do not want to be changed
foo.name = Unluckily, I can be changed

In the above code, if we only want to expose the name attribute of foo to the outside for reading, but don't want it to be modified, what should we do? Two solutions were listed in the Python definition of read-only attributes. One solution: "through private attributes", in fact, is to use property instead of attribute.

Rewrite the above foo.name into property:

classFoo():
 def __init__(self, name):
 self.__name = name

 @ property
 def name(self):return self.__name

foo =Foo('I do not want to be changed')print('foo.name = ', foo.name)
foo.name ='Luckily, I really can not be changed'

Output:

foo.name = I do not want to be changed

---------------------------------------------------------------------------
AttributeError       Traceback(most recent call last)<ipython-input-69-101c96ba497e  in<module()9 foo =Foo('I do not want to be changed')10print('foo.name = ', foo.name)---11 foo.name ='Luckily, I really can not be changed'

AttributeError: can't set attribute

There are two points to note:

It is true that foo.name cannot be modified by foo.name = …, that is, foo.name is already a read-only attribute.

After changing foo.name from attribute to property, its access method has not changed. In other words, the external interface has not changed. This advantage allows us to write code calmly without having to worry about whether to use property or attribute at the beginning. Because you can use attributes, if necessary, you can modify it at any time without affecting the external code. It is difficult to do this in Java (if it can be done).

Property value legality verification

In the above example, foo.name has only a getter method, which is read-only, but in fact the property is also modifiable. You only need to add a setter method to it. Then the problem is, if the property is also readable and can be modified , Then why bother to rewrite attribute into property?

Imagine a simple shopping-related business scenario. An Item represents a thing purchased by a user, mainly including category, price and quantity attributes:

classItem():
 def __init__(self, category, count, price):
 self.cat = category
 self.count = count
 self.price = price

The normal call is similar to this, the price and quantity are both positive numbers:

item = Item(‘Bread’, 1, 10)

However, if the price or quantity is set to a negative number, no error will be reported:

item.price =-10
item.count =-1
invalid_item1 =Item('Bread',-1,10)
invalid_item2 =Item('Bread',1,-10)

From a grammatical point of view, these statements are legal, but from a business point of view, they are all illegal. So, how can we prevent this illegal assignment? One solution is to implement a Java style in accordance with the Java style In the setter method, set the price attribute through item.set_price(price), and then write the verification code in the set_price method. This is feasible, but not Pythonic enough. The Python style is to read and write through the attribute name:

print(item.price)
item.price = -10

The benefits of this have been mentioned before: the external interface will not be changed when the attribute is rewritten as a property. So, how to check the legality of -10 when item.price = -10? The most straightforward way is in _setattr Set interception in the _ method, but it is very troublesome, especially when there are many attributes that need to be verified. (If you don't believe it, you can refer to the second Python definition of read-only attributes to try).

The best solution provided by Python is through the setter method of the property:

classItem():
 def __init__(self, category, count, price):
 self.__cat = category # attribute
 self.count = count # property
 self.price = price # property

 @ property
 def cat(self):return self.__cat

 @ property
 def count(self):return self.__dict__['count']
 @ count.setter
 def count(self, value):if value <0:
 raise ValueError('count can not be minus: %r'%(value))
 self.__dict__['count']= value

 @ property
 def price(self):return self.__dict__['price']

 @ price.setter
 def price(self, value):if value <0:
 raise ValueError('price can not be minus: %r'%(value))
 self.__dict__['price']= value

Statements that were legal before can still run normally:

item =Item('Bread',1,10)
item.price =20
item.count =2print(item.price)

But when the following statement is executed, an error will be reported:

item =Item('Bread',1,-10)
# or
item.price =-10

Will report the same error:

---------------------------------------------------------------------------
ValueError        Traceback(most recent call last)<ipython-input-93-4fcbd1284b2d  in<module()----1 item.price =-10<ipython-input-91-7546240b5469  inprice(self, value)27  def price(self, value):28if value <0:---29    raise ValueError('price can not be minus: %r'%(value))30   self.__dict__['price']= value

ValueError: price can not be minus:-10

Other ways to define properties

@ Although the property in property can be used as a modifier, it is actually a class (please refer to the documentation for the specific API), so the above code can also be rewritten as:

classItem():
 def __init__(self, category, count, price):
 self.__cat = category # attribute
 self.count = count # property
 self.price = price # property

 def get_cat(self):return self.__cat

 def get_count(self):return self.__dict__['count']

 def set_count(self, value):if value <0:
 raise ValueError('count can not be minus: %r'%(value))
 self.__dict__['count']= value

 def get_price(self):return self.__dict__['price']

 def set_price(self, value):if value <0:
 raise ValueError('price can not be minus: %r'%(value))
 self.__dict__['price']= value
 bill =property(get_bill)
 cat =property(get_cat)
 count =property(get_count, set_count)
 price =property(get_price, set_price)

The function meets the requirements, but the code itself looks very verbose, longer than the getter/setter style in Java. At this time, the code can be simplified through the property factory:

First define the property factory function that can be shared:

def readonly_prop(storage_name):
 def getter(instance):return instance.__dict__[storage_name]returnproperty(getter)
def positive_mutable_prop(storage_name):
 def getter(instance):return instance.__dict__[storage_name]
 def setter(instance, value):if value <0:
 raise ValueError('%s can not be minus: %r'%(storage_name, value))
 instance.__dict__[storage_name]= value
 returnproperty(getter, setter)

Then, the previous sample code can be simplified to:

classItem():
 def __init__(self, category, count, price):
 self.__cat = category # attribute
 self.count = count # property
 self.price = price # property

 cat =readonly_prop('__cat')
 count =positive_mutable_prop('count')
 price =positive_mutable_prop('price')

In this way, access control and legality verification are achieved while ensuring the simplicity of the code.

property will not be overwritten by instance attribute

Previously, the attribute parsing process was shown in the article Attribute Access Process of Python Objects, from which it is known that the class attribute can be overridden by the instance attribute:

classFoo():
 name ='Foo'

foo =Foo()
foo.name ='foo'
codes =['Foo.name','foo.name']for code in codes:print(code,'=',eval(code))

The output is:

Foo.name = Foo
foo.name = foo

But this kind of thing does not happen to properties:

classFoo():
 @ property
 def name(self):return'Foo'

foo =Foo()
foo.__dict__['name']='foo'#Can no longer pass foo.name assigned
codes =['Foo.name','foo.name']for code in codes:print(code,'=',eval(code))

Output:

Foo.name = <property object at 0x7fd135e7ecc8
foo.name = Foo

**At least two things can be seen: **

  1. Accessing Foo.name through class Foo gets the property object, not the property value.

  2. When foo.name is accessed, the property value of Foo.name is returned. The reason is that the priority of property is the highest in the property parsing process.

to sum up

**1. Python's attribute is different from property: **

attribute: data attribute + method

property: replace attribute with access control methods like getter/setter, for security reasons.

**2. Property can be defined in many ways: **

@ property

property(getter, setter)

property factory

3. Property has the highest priority in attribute parsing and will not be overwritten by instance attribute.

The above detailed explanation of Python from attribute to property is all the content shared by the editor. I hope to give you a reference.

Recommended Posts

Python from attribute to property detailed explanation
Detailed explanation of the attribute access process of Python objects
Detailed explanation of python backtracking template
Detailed explanation of python sequence types
Python error handling assert detailed explanation
Python from entry to proficiency (2): Introduction to Python
Detailed explanation of Python IO port multiplexing
Detailed explanation of -u parameter of python command
Detailed explanation of Python guessing algorithm problems
Ubuntu20.04 install Python3 virtual environment tutorial detailed explanation
Detailed explanation of the principle of Python super() method
Detailed explanation of python standard library OS module
Python3 development environment to build a detailed tutorial
01. Introduction to Python
Detailed explanation of the usage of Python decimal module
Detailed explanation of how python supports concurrent methods
Detailed explanation of data types based on Python
Detailed examples of using Python to calculate KS
Introduction to Python
The tutorial for upgrading from Centos7 to Centos8 (detailed graphic)
Detailed explanation of the principle of Python function parameter classification
Detailed explanation of the principle of Python timer thread pool
Detailed explanation of the implementation steps of Python interface development
Detailed explanation of Python web page parser usage examples
Detailed explanation of the remaining problem based on python (%)
Centos 6.4 python 2.6 upgrade to 2.7
Centos 6.4 python 2.6 upgrade to 2.7
Detailed Python loop nesting
Python—requests module detailed explanation