8.7.2 property decorator
Perhaps, readers also believe that Python cannot realize object encapsulation in the real sense. As we have seen from the previous section, the naming starting with a single underline is "gentleman's agreement", and the naming starting with a double underline is "a false shot". If it's true, can Python do it?
Python does not define classes with keywords such as public and private as in some languages such as Java. It can be said that all classes are pbulic. The encapsulation in the form of named "privatization" described in section 8.7.1 is not private in the Java language. However, there is a way in Python to make objects in a program closer to "encapsulation".
In the IDE, write a file named mypassword Py, the code is as follows:
#coding:utf-8 ''' filename: mypassword.py ''' class User: def __init__(self): self.password = 'default' def get_pwd(self): return self.password def set_pwd(self, value): self.password = value print("you have set your password.")
Then enter the Python interactive mode from the directory where the file is located (see the method shown in section 8.5.2. This debugging method will be often used in the future. Please be sure to master it).
>>> from mypassword import * >>> laoqi = User() >>> laoqi.password 'default' >>> laoqi.get_pwd() 'default' >>> laoqi.set_pwd('123') you have set your password. >>> laoqi.password '123' >>> laoqi.password = '456' >>> laoqi.get_pwd() '456'
As can be seen from the above operations, the password of the instance laoqi can be obtained through the attribute password or the method get_pwd() can also be read through the attribute password or method set_pwd() reset. Obviously, such password management is very insecure - to properly "encapsulate", the basic requirement is that the password can only be read through attributes, not reset through attributes, that is, it is read-only.
Set mypassword The files in py are modified as follows.
#coding:utf-8 ''' filename: mypassword.py ''' class User: def __init__(self): self.__password = 'default' @property # (1) def password(self): # (2) return self.__password def set_pwd(self, value): self.__password = value print("you have set your password.")
In order to realize the requirement of read-only password, the decorator @ property shown in note (1) is used -- this decorator is based on the built-in function property(), and the original method get_pwd() is renamed password() (as shown in note (2)). In addition, rename the original instance property password to__ password . Reload the mypassword module (see section 8.5.2. The simplest method is to execute the exit() function in the interactive mode. After exiting, enter the interactive mode), and perform the following operations:
>>> from mypassword import * >>> laoqi = User() >>> laoqi.password 'default' >>> laoqi.password = '123' # (3) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute >>> laoqi.__password = '123' # (4) >>> laoqi.password 'default'
Call the password attribute through the instance laoqi -- note, not laoqi password() method. Although note (2) defines the password () method, after this method is decorated with @ property, it can be called in the form of a property with the same name, and the default password value is obtained.
Note (3) an attempt to change the password through an assignment statement failed. However, note (4) seems to have succeeded. In fact, it has not been modified laoqi The value of password only adds a name for the instance laoqi__ Instance property of password. In this way, the "read-only" function of password is realized.
Of course, because there are Laoqi set_ PWD () method, calling it can still reset the password.
>>> laoqi.set_pwd('456') you have set your password. >>> laoqi.password '456'
However, it's a little "ugly" to reset in this way. We still use Laoqi The method of password = '456' is more elegant -- the execution result of note (3) has explained that it cannot be reset with assignment statement. Also, is it too unsafe to save in plain code? After resetting the password, it is best to encrypt and save it.
Therefore, there is a second requirement: the password can be reset with an assignment statement (similar to note (3)) (it is very necessary to reset the password from the user's point of view), and the password should be encrypted - otherwise it is not called a "secret" code.
According to these needs, modify mypassword again Py file.
#coding:utf-8 ''' filename: mypassword.py ''' class User: def __init__(self): self.__password = 'default' @property def password(self): return self.__password @password.setter # (5) def password(self, value): # (6) value = int(value) + 728 # Lowest level encryption method self.__password = str(value) print("you have set your password.")
Observe the changes. Annotation (5) adds a decorator (annotation writing method), which is used to make the method defined in annotation (6) in the form of attribute assignment. In the method of note (6), the most clumsy encryption method is used.
After reloading the module mypassword (see section 8.5.2), continue the operation in interactive mode:
>>> from mypassword import * >>> laoqi = User() >>> laoqi.password 'default' >>> laoqi.password = '456' # (7) you have set your password. >>> laoqi.password # (8) '1184'
Note (7) realizes the need to reset the password with an assignment statement, and from the return value of note (8), it can be seen that the password submitted by note (7) has been "encrypted".
From the above, we have preliminarily understood one function of the @ property decorator: converting methods into property access. With this function, it can make the program "elegant" a lot. For example:
#coding:utf-8 ''' filename: computearea.py ''' class Rectangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height if __name__ == "__main__": rect = Rectangle(7, 8) # (9) rect_area = rect.area() # (10) print(f"width = {rect.width}, height = {rect.height}") print(f"call rect.area() method to rectangle area = {rect_area}")
In this program, annotation (9) creates an instance of a rectangle, and annotation (10) obtains the area of the rectangle. Although this writing method can realize functions, in the eyes of Python developers pursuing "elegance", annotation (10) is "ugly". The width and length of the instance are represented by the attribute rect Width and rect Height, then the area should also be the attribute of the instance, not the method. So use rect Area() calculates the area, which itself is not very "OOP". If rect The area of the instance is obtained in the form of an attribute such as area, which is in line with the OOP idea, reflects the elegance of Python and contains the wisdom of developers.
Modify the program with @ property:
#coding:utf-8 ''' filename: computearea.py ''' class Rectangle: def __init__(self, width, height): self.width = width self.height = height @property # Add decorator def area(self): return self.width * self.height if __name__ == "__main__": rect = Rectangle(7, 8) #rect_area = rect.area() # No more method calls print(f"width = {rect.width}, height = {rect.height}") print(f"call rect.area attribute to rectangle area = {rect.area}")
Execution results:
% python computearea.py width = 7, height = 8 call rect.area attribute to rectangle area = 56
The decorator @ property is based on the built-in function property(), which is similar to the class methods and static method decorators used in sections 8.4.2 and 8.4.3. It can not only read and write "attributes", but also delete them. In the following example, the reader further understands the role of @ property.
#coding:utf-8 ''' temperature.py ''' class Celsius: def __init__(self, temperature=0): self.__temperature = temperature @property def temperature(self): return self.__temperature @temperature.setter def temperature(self, value): if value < -273.15: # (11) raise ValueError("Temperature below -273 is not possible") self.__temperature = value @temperature.deleter def temperature(self): raise AttributeError("Can't delete attribute")
Enter the interactive mode (see section 8.5.2) and perform the following operations:
>>> from temperature import * >>> person = Celsius() >>> person.temperature 0 >>> person.temperature = 36 >>> person.temperature = -300 # (12) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/qiwsir/Documents/my_books/Python Complete self-study manual/codes/temperature.py", line 16, in temperature raise ValueError("Temperature below -273 is not possible") ValueError: Temperature below -273 is not possible >>> del person.temperature # (13) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/qiwsir/Documents/my_books/Python Complete self-study manual/codes/temperature.py", line 21, in temperature raise AttributeError("Can't delete attribute") AttributeError: Can't delete attribute
Focus on the operation result of comment (12). The exception thrown is because comment (11) judges the size of the value in the program. If the conditions are met, execute the raise statement (see section 10.3 in Chapter 10).
Looking at the execution result of comment (13), the AttributeError exception is thrown because the decorator @ temperature. Is used In the method decorated by the delete, the raise statement is executed, that is, the deletion of person is prohibited Temperature, so the object is "protected".
★ self study suggestions Learning this chapter is the biggest test for readers. Ordinary learners will stop at Chapter 7 and flinch from Chapter 8 and beyond. Why? Because from this chapter, we should not only comprehensively use the learned knowledge, but also challenge the daily way of thinking based on "intuitive feeling". In section 8.3 [self study suggestions], the importance of "abstract ability" to writing classes has been mentioned, and readers are advised to "practice more". It is further suggested that the original practice works should be optimized with the follow-up learning. If readers now "look back" on the various exercises they have done since Chapter 1, they may have new thoughts on some problems, and even think that the code in the book is not good - this shows that they have a high ability to appreciate and evaluate. In addition to criticism, you should do it yourself and optimize the sample code I wrote first - don't forget to tell me so that I and other readers can make progress. Also, be "bold", dare to design (or "Imagine") some projects by yourself, and try to "do it". Even if you don't succeed in the end, you also have experience. "