Online Book Reader

Home Category

Learning Python - Mark Lutz [524]

By Root 1865 0
we must code a __setattr__ in order to intercept and validate attribute assignments.

As for the property and descriptor versions of this example, it’s critical to notice that the attribute assignments inside the __init__ constructor method trigger the class’s __setattr__ method too. When this method assigns to self.name, for example, it automatically invokes the __setattr__ method, which transforms the value and assigns it to an instance attribute called name. By storing name on the instance, it ensures that future accesses will not trigger __getattr__. In contrast, acct is stored as _acct, so that later accesses to acct do invoke __getattr__.

In the end, this class, like the prior two, 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 is computed on demand.

For comparison purposes, this alternative comes in at 32 lines of code—7 fewer than the property-based version, and 13 fewer than the version using descriptors. Clarity matters more than code size, of course, but extra code can sometimes imply extra development and maintenance work. Probably more important here are roles: generic tools like __getattr__ may be better suited to generic delegation, while properties and descriptors are more directly designed to manage specific attributes.

Also note that the code here incurs extra calls when setting unmanaged attributes (e.g., addr), although no extra calls are incurred for fetching unmanaged attributes, since they are defined. Though this will likely result in negligible overhead for most programs, properties and descriptors incur an extra call only when managed attributes are accessed.

Here’s the __getattr__ version of our 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 __setattr__ too

self.age = age # _acct not mangled: name tested

self.addr = addr # addr is not managed

# remain has no data

def __getattr__(self, name):

if name == 'acct': # On undefined attr fetches

return self._acct[:-3] + '***' # name, age, addr are defined

elif name == 'remain':

return self.retireage - self.age # Doesn't trigger __getattr__

else:

raise AttributeError(name)

def __setattr__(self, name, value):

if name == 'name': # On all attr assignments

value = value.lower().replace(' ', '_') # addr stored directly

elif name == 'age': # acct mangled to _acct

if value < 0 or value > 150:

raise ValueError('invalid age')

elif name == 'acct':

name = '_acct'

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

if len(value) != self.acctlen:

raise TypeError('invald acct number')

elif name == 'remain':

raise TypeError('cannot set remain')

self.__dict__[name] = value # Avoid looping

Using __getattribute__ to Validate

Our final variant uses the __getattribute__ catchall to intercept attribute fetches and manage them as needed. Every attribute fetch is caught here, so we test the attribute names to detect managed attributes and route all others to the superclass for normal fetch processing. This version uses the same __setattr__ to catch assignments as the prior version.

The code works very much like the __getattr__ version, so I won’t repeat the full description here. Note, though, that because every attribute fetch is routed to __getattribute__, we don’t need to mangle names to intercept them here (acct is stored as acct). On the other hand, this code must take care to route nonmanaged attribute fetches to a superclass to avoid looping.

Also notice that this version incurs extra calls for both setting and fetching unmanaged attributes (e.g., addr); if speed is paramount, this alternative may be the slowest of the bunch. For comparison purposes, this version amounts to 32 lines of code, just like the prior version:

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

Return Main Page Previous Page Next Page

®Online Book Reader