Online Book Reader

Home Category

Learning Python - Mark Lutz [523]

By Root 1734 0
are invoked; accounts are displayed with some digits hidden, names are converted to a standard format, and time remaining until retirement is computed when fetched using a class attribute cutoff:

12345*** / bob_smith / 40 / 19.5 / 123 main st

23456*** / bob_q._smith / 50 / 9.5 / 123 main st

56781*** / sue_jones / 35 / 24.5 / 124 main st

Bad age for Sue

Can't set sue.remain

Bad acct for Sue

Using Descriptors to Validate

Now, let’s recode our example using descriptors instead of properties. As we’ve seen, descriptors are very similar to properties in terms of functionality and roles; in fact, properties are basically a restricted form of descriptor. Like properties, descriptors are designed to handle specific attributes, not generic attribute access. Unlike properties, descriptors have their own state, and they’re a more general scheme.

To understand this code, it’s again important to notice that the attribute assignments inside the __init__ constructor method trigger descriptor __set__ methods. When the constructor method assigns to self.name, for example, it automatically invokes the Name.__set__() method, which transforms the value and assigns it to a descriptor attribute called name.

Unlike in the prior property-based variant, though, in this case the actual name value is attached to the descriptor object, not the client class instance. Although we could store this value in either instance or descriptor state, the latter avoids the need to mangle names with underscores to avoid collisions. In the CardHolder client class, the attribute called name is always a descriptor object, not data.

In the end, this class implements the same attributes as the prior version: it manages attributes called name, age, and acct; allows the attribute addr to be accessed directly; and provides a read-only attribute called remain that is entirely virtual and computed on demand. Notice how we must catch assignments to the remain name in its descriptor and raise an exception; as we learned earlier, if we did not do this, assigning to this attribute of an instance would silently create an instance attribute that hides the class attribute descriptor. For comparison purposes, this descriptor-based coding takes 45 lines of code:

class CardHolder:

acctlen = 8 # Class data

retireage = 59.5

def __init__(self, acct, name, age, addr):

self.acct = acct # Instance data

self.name = name # These trigger __set__ calls too

self.age = age # __X not needed: in descriptor

self.addr = addr # addr is not managed

# remain has no data

class Name:

def __get__(self, instance, owner): # Class names: CardHolder locals

return self.name

def __set__(self, instance, value):

value = value.lower().replace(' ', '_')

self.name = value

name = Name()

class Age:

def __get__(self, instance, owner):

return self.age # Use descriptor data

def __set__(self, instance, value):

if value < 0 or value > 150:

raise ValueError('invalid age')

else:

self.age = value

age = Age()

class Acct:

def __get__(self, instance, owner):

return self.acct[:-3] + '***'

def __set__(self, instance, value):

value = value.replace('-', '')

if len(value) != instance.acctlen: # Use instance class data

raise TypeError('invald acct number')

else:

self.acct = value

acct = Acct()

class Remain:

def __get__(self, instance, owner):

return instance.retireage - instance.age # Triggers Age.__get__

def __set__(self, instance, value):

raise TypeError('cannot set remain') # Else set allowed here

remain = Remain()

Using __getattr__ to Validate

As we’ve seen, the __getattr__ method intercepts all undefined attributes, so it can be more generic than using properties or descriptors. For our example, we simply test the attribute name to know when a managed attribute is being fetched; others are stored physically on the instance and so never reach __getattr__. Although this approach is more general than using properties or descriptors, extra work may be required to imitate the specific-attribute focus of other tools. We need to check names at runtime, and

Return Main Page Previous Page Next Page

®Online Book Reader