Python -- manage attributes (1)
Manage attributes
Here we will introduce the [property interception] technology mentioned above. Includes the following:
[1] _ getattr _ and _ setattr _ methods. The undefined attributes are obtained and all attributes are assigned to a general processor method.
[2] _ getattribute _ method, pointing all attribute acquisition to a generic Processor
[3] property built-in functions, which locate specific attribute access to get and set Processor functions, also known as properties)
[4] descriptor protocol. Access specific attributes to instances of classes with any get and set Processor Methods
The last two methods apply to specific attributes, while the first two methods are general enough.
This article introduces the following two methods.
========================================================== ========================================================
Property
The feature protocol allows us to direct get and set operations on a specific attribute to the functions or methods we provide, so that we can insert code that runs automatically during attribute access and intercept attribute deletion.
You can use the property built-in functions to create features and allocate them to class attributes, just like method functions. A feature manages a single and specific attribute. Although it cannot extensively capture all attribute access, it allows us to control access and assign values, it also allows us to freely change an attribute from simple data to a computing without affecting the existing code.
Features have a great relationship with descriptors. They are basically a restricted form of descriptors.
You can create a feature by assigning the result of a built-in function to a class attribute:
attrbute = property(fget, fset, fdel, doc)
The parameters of this built-in function are not required. If no parameter is passed, the default value None is used for all parameters. Therefore, such operations are not supported.
When using them, we pass a function to fget to intercept attribute access, assign a value to fset, and pass a function to fdel to delete the attribute; the doc parameter receives a document string for this attribute, if needed. Fget returns the calculated property value, and fset and fdel do not return data, and None is returned.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Example 1
The following class uses a feature to record a method for an attribute named name. The actually stored data is named _ name, so that it is not mixed with the feature:
class Person: def __init__(self,name): self._name = name def getName(self): print('fetch...') return self._name def setName(self,value): print('change...') self._name = name def delName(self): print('remove...') del self._name name = property(getName,setName,delName,'name property docs')bob = Person('Bob Smith')print(bob.name)bob.name = 'Robert Smith'print(bob.name)del bob.nameprint('-'*20)sue = Person('Sue Jones')print(sue.name)print(Person.name.__doc__)
This particular feature does not do much-it only intercepts and tracks an attribute. The two instances inherit this feature, just as they are attached to the other two attributes of their classes. However, their attribute access is captured:
fetch...Bob Smithchange...fetch...Robert Smithremove...--------------------fetch...Sue Jonesname property docs
Just like all the class attributes, instances and lower subclasses inherit the features. If we modify the example as follows:
class Super: def __init__(self,name): self._name = name def getName(self): print('fetch...') return self._name def setName(self,value): print('change...') self._name = value def delName(self): print('remove...') del self._name name = property(getName,setName,delName,'name property docs')class Person(Super): passbob = Person('Bob Smith')print(bob.name)bob.name = 'Robert Smith'print(bob.name)del bob.nameprint('-'*20)sue = Person('Sue Jones')print(sue.name)print(Person.name.__doc__)
The output is the same. The Person subclass inherits the name feature from Super and the bob instance obtains it from Person.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Calculated attributes
The preceding example only briefly tracks attribute access. However, more features are usually used-for example, when an attribute is obtained, the attribute value is dynamically calculated. Take the following example:
Class PropSquare: def _ init _ (self, start): self. value = start def getX (self): return self. value ** 2 def setX (self, value): self. value = value X = property (getX, setX) # Only get and set, no del and docP = PropSquare (3) P = PropSquare (32) print (P. x) #3 ** 2 p. X = 4 print (P. x) #4 ** 2 print (Q. x) #32 ** 2
In this example, an X attribute is defined and accessed as static data, but the actual running code calculates its value when obtaining this attribute.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Write features using the decorator
The function annotator syntax is:
@decoratordef func(args):...
Python automatically translates it into pairs and rebinds the function name to the returned result of the callable decorator:
def func(args):...func = decorator(func)
As a result of this ing, it is confirmed that the built-in function property can act as a modifier to define a function and automatically run this function when obtaining an attribute:
class Person:@propertydef name(self):...
During running, the decoration method is automatically passed to the first parameter (fget) of the property built-in function ). This is actually an alternative syntax for creating a feature and manually binding the property name:
class Person:def name(self):...name = property(name)
In fact, the property object also has getter, setter, and deleter methods. These methods specify the corresponding feature accessors and return a copy of the feature itself. Take the following example:
class Person: def __init__(self, name): self._name = name @property def name(self): "name property docs" print('fetch...') return self._name @name.setter def name(self,value): print('change...') self._name = value @name.deleter def name(self): print('remove...') del self._namebob = Person('Bob Smith')print(bob.name)bob.name = 'Robert Smith'print(bob.name)del bob.nameprint('-'*20)sue = Person('Sue Jones')print(sue.name)print(Person.name.__doc__)
This code is equivalent to the first example written above. In this example, decoration is only an alternative to writing features. The output result is the same:
fetch...Bob Smithchange...fetch...Robert Smithremove...--------------------fetch...Sue Jonesname property docs
Compared with the result of manual property assignment, in this example, only three additional lines of code are required to write features using the decorator. Therefore, it is more convenient to use the decorator.
========================================================== ========================================================
Descriptor
Descriptors provide an alternative method to intercept attribute access. They have a lot to do with features. In fact, the feature is a type of Descriptor-Technically speaking, the property built-in function is only a simplified method for creating a specific type of descriptor, this type of Descriptor runs method functions during attribute access.
Like features, descriptors also manage a single and specific attribute.
The descriptor is created as an independent class and provides the accessors method with a specific name for the attribute access operations to be intercepted. When you access the attributes assigned to the descriptor class instance in a corresponding way, the methods for obtaining, setting, and deleting descriptor classes are automatically run:
class Descriptor:"docstring goes here"def __get__(self, instance, owener):... # Return attr valuedef __set__(self, instance, value):... # Return noting(None)def __del__(self,instance):...# Return noting(None)
Classes with any of these methods can be considered as descriptors, and when one of their instances is assigned to another class, these methods are special-they are automatically called when accessing properties. If any one of these methods is blank, it usually means that access of the corresponding type is not supported. However, unlike features, omitting a _ set _ indicates that this name is allowed to be redefined in an instance. Therefore, to make an attribute read-only, we must define _ set _ to capture the assignment and raise an exception.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Descriptor method parameters
The three descriptor Methods _ get _, _ set _, and _ del _ both pass the descriptor instance (self) and the instance of the customer class attached to the descriptor instance, __get _ the access method also receives an extra owner parameter, specifying the class to be appended to the descriptor instance. The insatance parameter is either the instance to which the accessed attribute belongs (for instance. attr), or None (for class. attr) when the accessed attribute directly belongs to the class ). The former usually calculates a value for instance access. If the access to the descriptor object is supported, the latter usually returns self.
For example, in the following example, when getting X. attr, Python automatically runs the _ get _ method of the Descriptor class and assigns the attributes of the Subject. attr class to this method:
>>> class Descriptor(object):def __get__(self,instance,owner):print(self,instance,owner,sep='\n')>>> class Subject:attr = Descriptor()>>> X = Subject()>>> X.attr<__main__.Descriptor object at 0x032C0F70><__main__.Subject object at 0x032C0FF0>
>>> Subject.attr<__main__.Descriptor object at 0x032C0F70>None
Note that the parameters automatically passed to the _ get _ method in the first property acquisition are as follows:
X.attr --> Descriptor.__get__(Subject.attr, X, Subject)
When the instance parameter of the descriptor is None, the descriptor knows that it will be accessed directly.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Read-Only Descriptor
As shown in the following figure, when the set method is ignored by the feature, the attribute cannot be assigned a value. In this way, attributes can be read-only.
>>> class A :def __init__(self,name):self.name = namedef getName(self):return self.namename = property(getName)>>> a = A(1)Traceback (most recent call last): File "
", line 1, in
a = A(1) File "
", line 3, in __init__ self.name = nameAttributeError: can't set attribute
However, unlike the feature, the omit _ set _ method is not enough to make the attribute read-only, because the descriptor name can be assigned to an instance. In the following example, the attribute assignment of X. a stores a in Instance Object X, thus hiding the descriptor stored in Class C:
>>> class D:def __get__(*args):print('get')>>> class C:a = D()>>> X = C()>>> X.aget>>> C.aget>>> X.a = 99>>> X.a99>>> list(X.__dict__.keys())['a']>>> Y = C()>>> Y.aget>>> C.aget
This is how all instance attributes in Python are assigned values, and it allows classes in their instances to selectively overwrite class-level default values.
To make descriptor-based attributes readable, capture the values assigned in the descriptor class and raise an exception to prevent attribute assignment. When the attribute to be assigned is a descriptor, python effectively bypasses the value assignment at the regular instance level and points the operation to the descriptor object:
>>> class D:def __get__(*args):print('get')def __set__(*args):raise AttributeError('cannot set')>>> class C:a = D()>>> X = C()>>> X.aget>>> X.a = 99Traceback (most recent call last): File "
", line 1, in
X.a = 99 File "
", line 5, in __set__ raise AttributeError('cannot set')AttributeError: cannot set
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Example 1
Now let's modify the first example written for the feature. The following code defines a descriptor to intercept access to an attribute named name in its customer class. The method uses their instance parameters to access the status information in the main instance:
class Name: "name descriptor docs" def __get__(self,instance,owner): print('fetch...') return instance._name def __set__(self,instance,value): print('change...') instance._name = value def __delete__(self,instance): print('remove...') del instance._nameclass Person: def __init__(self,name): self._name = name name = Name()bob = Person('Bob Smith')print(bob.name)bob.name = 'Robert Smith'print(bob.name)del bob.nameprint('-'*20)sue = Person('Sue Jones')print(sue.name)print(Name.__doc__)
The running result is as follows:
fetch...Bob Smithchange...fetch...Robert Smithremove...--------------------fetch...Sue Jonesname descriptor docs
When the _ get _ method of the descriptor runs, it passes three objects to define its context:
[1] self is an instance of Name
[2] The instance is a Person-class instance.
[3] The owner is a Person instance.
Similar to the feature example, a descriptor instance is a Class Attribute and therefore inherited by all instances of the customer class and any subclass. If we change the Person class in the example to the following, the Script output is the same:
...class Super:def __init__(self,name):self._name = namename = Name()class Person(Super):pass...
Of course, this example only traces access to attributes.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Calculation attribute
Similarly, it can be used to calculate the values of attributes each time they are obtained, as shown below:
class DescSquare: def __init__(self,start): self.value = start def __get__(self,instance,owner): return self.value**2 def __set__(self,instance,value): self.value = valueclass Client1: X = DescSquare(3)class Client2: X = DescSquare(32)c1 = Client1()c2 = Client2()print(c1.X)c1.X = 4print(c1.X)print(c2.X)
The running result is as follows:
9161024
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
Use status information in Descriptors
In the above two examples, you may find that they obtain information from different places-the first example uses the data stored in the customer instance, the second example uses the data appended to the descriptor object itself. In fact, the descriptor can use the instance status and descriptor status, or any combination of the two:
[1] descriptor state is used to manage data internally used for descriptor operation
[2] The instance status records information related to the customer class and information that may be created by the customer class.
Certificate ------------------------------------------------------------------------------------------------------------------------------------------
How are features and Descriptors related?
There is a strong correlation between features and descriptors-property built-in functions are only a convenient way to create descriptors. Now that we know how the two work, we can use the following descriptor class to simulate the property built-in function:
class Property: def __init__(self,fget=None,fset=None,fdel=None,doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self,instance,instancetype=None): if instance is None: return self if self.fget is None: raise AttributeError("can't get attribute") return self.fget(instance) def __set__(self,instance,value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(instance,value) def __delete__(self,instance): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(instance)class Person: def getName(self):... def setName(self,value):... name = Property(getName,setName)
This Property class captures attribute access with the descriptor protocol, and locates the request to the function or method passed in and saved in the descriptor state when the class is created.