预计阅读本页时间:-
Using State Information in Descriptors
If you study the two descriptor examples we’ve written so far, you might notice that they get their information from different places—the first (the name attribute example) uses data stored on the client instance, and the second (the attribute squaring example) uses data attached to the descriptor object itself. In fact, descriptors can use both instance state and descriptor state, or any combination thereof:
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
- Descriptor state is used to manage data internal to the workings of the descriptor.
- Instance state records information related to and possibly created by the client class.
Descriptor methods may use either, but descriptor state often makes it unnecessary to use special naming conventions to avoid name collisions for descriptor data stored on an instance. For example, the following descriptor attaches information to its own instance, so it doesn’t clash with that on the client class’s instance:
class DescState: # Use descriptor state
def __init__(self, value):
self.value = value
def __get__(self, instance, owner): # On attr fetch
print('DescState get')
return self.value * 10
def __set__(self, instance, value): # On attr assign
print('DescState set')
self.value = value
# Client class
class CalcAttrs:
X = DescState(2) # Descriptor class attr
Y = 3 # Class attr
def __init__(self):
self.Z = 4 # Instance attr
obj = CalcAttrs()
print(obj.X, obj.Y, obj.Z) # X is computed, others are not
obj.X = 5 # X assignment is intercepted
obj.Y = 6
obj.Z = 7
print(obj.X, obj.Y, obj.Z)
This code’s value information lives only in the descriptor, so there won’t be a collision if the same name is used in the client’s instance. Notice that only the descriptor attribute is managed here—get and set accesses to X are intercepted, but accesses to Y and Z are not (Y is attached to the client class and Z to the instance). When this code is run, X is computed when fetched:
DescState get
20 3 4
DescState set
DescState get
50 6 7
It’s also feasible for a descriptor to store or use an attribute attached to the client class’s instance, instead of itself. The descriptor in the following example assumes the instance has an attribute _Y attached by the client class, and uses it to compute the value of the attribute it represents:
class InstState: # Using instance state
def __get__(self, instance, owner):
print('InstState get') # Assume set by client class
return instance._Y * 100
def __set__(self, instance, value):
print('InstState set')
instance._Y = value
# Client class
class CalcAttrs:
X = DescState(2) # Descriptor class attr
Y = InstState() # Descriptor class attr
def __init__(self):
self._Y = 3 # Instance attr
self.Z = 4 # Instance attr
obj = CalcAttrs()
print(obj.X, obj.Y, obj.Z) # X and Y are computed, Z is not
obj.X = 5 # X and Y assignments intercepted
obj.Y = 6
obj.Z = 7
print(obj.X, obj.Y, obj.Z)
This time, X and Y are both assigned to descriptors and computed when fetched (X is assigned the descriptor of the prior example). The new descriptor here has no information itself, but it uses an attribute assumed to exist in the instance—that attribute is named _Y, to avoid collisions with the name of the descriptor itself. When this version is run the results are similar, but a second attribute is managed, using state that lives in the instance instead of the descriptor:
DescState get
InstState get
20 300 4
DescState set
InstState set
DescState get
InstState get
50 600 7
Both descriptor and instance state have roles. In fact, this is a general advantage that descriptors have over properties—because they have state of their own, they can easily retain data internally, without adding it to the namespace of the client instance object.