Online Book Reader

Home Category

Learning Python - Mark Lutz [512]

By Root 1785 0
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.

How Properties and Descriptors Relate

As mentioned earlier, properties and descriptors are strongly related—the property built-in is just a convenient way to create a descriptor. Now that you know how both work, you should also be able to see that it’s possible to simulate the property built-in with a descriptor class like the following:

class Property:

def __init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel # Save unbound methods

self.__doc__ = doc # or other callables

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) # Pass instance to self

# in property accessors

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) # Use like

Return Main Page Previous Page Next Page

®Online Book Reader