Value and assignment
class Actress(): def __init__(self): self.name = 'TianXin' self.age = 5
There are two member variables name and age in class Actress. External operations on class member variables mainly include value acquisition and assignment. The simple value operation is x=object.var, and the simple assignment operation is object.var=value.
>>> actress = Actress() >>> actress.name #取值操作 'TianXin' >>> actress.age #取值操作 20 >>> actress.name = 'NoName' #赋值操作 >>> actress.name 'NoName'
Use Getter and Setter
The above simple value acquisition and assignment operations cannot meet the requirements in some cases. For example, if you want to limit the age range of Actress, then just using the above simple assignment operation will not meet the requirements. Getters and setters implement such requirements.
class Actress(): def __init__(self): self._name = 'TianXin' self._age = 20 def getAge(self): return self._age def setAge(self, age): if age > 30: raise ValueError self._age = age
Call the setAge function to limit the value range of the variable _age to less than 30.
>>> actress = Actress() >>> actress.setAge(28) >>> actress.getAge() 28 >>> actress.setAge(35) ValueError
Use property
The definition of property is:
Among them, fget is the value function, fset is the assignment function, and fdel is the deletion function. Using property also implements the above-mentioned value restrictions on member variables.
class Actress(): def __init__(self): self._name = 'TianXin' self._age = 20 def getAge(self): return self._age def setAge(self, age): if age > 30: raise ValueError self._age = age age=property(getAge, setAge, None, 'age property')
After the above definition, age can be operated like simple value acquisition and assignment operations. For example,
>>> actress = Actress() >>> actress.age 20 >>> actress.age = 18 >>> actress.age = 55 ValueError
Use @property
Using @property can also achieve the definition of the above class.
class Actress(): def __init__(self): self._name = 'TianXin' self._age = 20 @property def age(self): return self._age @age.setter def age(self, age): if age > 30: raise ValueError self._age = age
Example of usage:
>>> actress = Actress() >>> actress.age 20 >>> actress.age = 18 >>> actress.age = 45 ValueError
The difference between using properties in Python2 and Python3
The above property examples are valid in the Python3 environment. In Python2, when using property, the class definition needs to inherit object. Otherwise, the property assignment operation cannot be used.
The correct way to use property under Python2:
class Actress(object): #差别在这里 def __init__(self): self._name = 'TianXin' self._age = 20 @property def age(self): return self._age @age.setter def age(self, age): if age > 30: raise ValueError self._age = age def setName(self, name): self._name = name def getName(self): return self._name def delName(self): print('Goodbye...') del self._name name = property(getName, setName, delName, 'name property' )
# 以美元为基础货币的Money类的首个版本 class Money: def __init__(self, dollars, cents): self.dollars = dollars self.cents = cents # 还有其他一些方法,我们暂时不必理会
This class was later packaged into a Python library and slowly used by many different applications. For example, Bob, a Python programmer on another team, uses the Money class like this:
money = Money(27, 12) message = "I have {:d} dollars and {:d} cents." print(message.format(money.dollars, money.cents)) # "I have 27 dollars and 12 cents." money.dollars += 2 money.cents += 20 print(message.format(money.dollars, money.cents)) # "I have 29 dollars and 32 cents."
There is nothing wrong with using it this way, but it does cause problems with code maintainability. Did you find out?
A few months or years later. Alice wants to refactor the internal implementation of the Money class so that it no longer records dollars and cents, but only cents, because doing so will make certain operations much simpler. Here are the changes she is likely to make:
# Money类的第二个版本 class Money: def __init__(self, dollars, cents): self.total_cents = dollars * 100 + cents
This modification has one consequence: every line of code that references the Money class must be adjusted. Sometimes you are lucky and you are the maintainer of all this code and you just need to refactor it directly yourself. But Alice's situation was not so good; many teams reused her code. Therefore, she would need to reconcile their code base with her changes, perhaps even going through a particularly painful and lengthy formal deprecation process.
Fortunately, Alice knows a better solution to avoid this headache: use Python's built-in property decorator. @property is generally used on Python methods, which can effectively turn attribute access into method call. For example, let’s put aside the Money class for a moment and imagine a Person class that represents humans:
class Person: def __init__(self, first, last): self.first = first self.last = last @property def full_name(self): return '{} {}'.format(self.first, self.last)
The code style is different because there was a problem with the tool I used before. —EarlGrey
Please note the full_name method. There is nothing different about the declaration of the method except that it is decorated with @property above the def statement. However, this changes the way the Person object works:
>>> buddy = Person('Jonathan', 'Doe') >>> buddy.full_name 'Jonathan Doe'
We found that although full_name is defined as a method, it can be accessed through variable attributes. There is no () operator in the last line of code; I am not calling the full_name method. What we've done is created some sort of dynamic property, so to speak.
Back to the Money class in this article, Alice made the following modifications to it:
# Money类的最终版本 class Money: def __init__(self, dollars, cents): self.total_cents = dollars * 100 + cents # Getter and setter for dollars... @property def dollars(self): return self.total_cents // 100; @dollars.setter def dollars(self, new_dollars): self.total_cents = 100 * new_dollars + self.cents # And the getter and setter for cents. @property def cents(self): return self.total_cents % 100; @cents.setter def cents(self, new_cents): self.total_cents = 100 * self.dollars + new_cents
In addition to using the @property decorator to define the getter of the dollars property, Alice also creates a setter using @dollars.setter. Alice also performed similar processing on the cents` attribute.
So now, what corresponding modifications should be made to Bob’s code? No need to change at all!
# 他的代码完全没有变动,但是却可以正常调用Money类。 money = Money(27, 12) message = "I have {:d} dollars and {:d} cents." print(message.format(money.dollars, money.cents)) # "I have 27 dollars and 12 cents." money.dollars += 2 money.cents += 20 print(message.format(money.dollars, money.cents)) # "I have 29 dollars and 32 cents."# 代码逻辑也没有问题。 money.cents += 112 print(message.format(money.dollars, money.cents)) # "I have 30 dollars and 44 cents."
In fact, all code using the Money class does not need to be modified. Bob doesn't know or care that Alice removed the dollars and cents attributes from the class: his code still executes normally as before. The only code that has been modified is the Money class itself.
Precisely because of the way decorators are handled in Python, you can freely use simple attributes in your classes. If you write a class that changes the way it manages state, you can confidently make changes to that class (and only that class) through the @property decorator. This is a win-win approach! In contrast, in languages such as Java, programmers must actively define methods to access properties (such as getDollars or setCents).
Finally, a reminder: this approach is most important for code that is reused by other programmers and teams. Assuming you just create a class like Money in an application you maintain, then if you change the interface of Money, you only need to refactor your own code. In this case, you don't need to use the @property decorator as mentioned above.