Online Book Reader

Home Category

Learning Python - Mark Lutz [394]

By Root 1680 0
and __setattr__

The __getattr__ method intercepts attribute qualifications. More specifically, it’s called with the attribute name as a string whenever you try to qualify an instance with an undefined (nonexistent) attribute name. It is not called if Python can find the attribute using its inheritance tree search procedure. Because of its behavior, __getattr__ is useful as a hook for responding to attribute requests in a generic fashion. For example:

>>> class empty:

... def __getattr__(self, attrname):

... if attrname == "age":

... return 40

... else:

... raise AttributeError, attrname

...

>>> X = empty()

>>> X.age

40

>>> X.name

...error text omitted...

AttributeError: name

Here, the empty class and its instance X have no real attributes of their own, so the access to X.age gets routed to the __getattr__ method; self is assigned the instance (X), and attrname is assigned the undefined attribute name string ("age"). The class makes age look like a real attribute by returning a real value as the result of the X.age qualification expression (40). In effect, age becomes a dynamically computed attribute.

For attributes that the class doesn’t know how to handle, __getattr__ raises the built-in AttributeError exception to tell Python that these are bona fide undefined names; asking for X.name triggers the error. You’ll see __getattr__ again when we see delegation and properties at work in the next two chapters, and I’ll say more about exceptions in Part VII.

A related overloading method, __setattr__, intercepts all attribute assignments. If this method is defined, self.attr = value becomes self.__setattr__('attr', value). This is a bit trickier to use because assigning to any self attributes within __setattr__ calls __setattr__ again, causing an infinite recursion loop (and eventually, a stack overflow exception!). If you want to use this method, be sure that it assigns any instance attributes by indexing the attribute dictionary, discussed in the next section. That is, use self.__dict__['name'] = x, not self.name = x:

>>> class accesscontrol:

... def __setattr__(self, attr, value):

... if attr == 'age':

... self.__dict__[attr] = value

... else:

... raise AttributeError, attr + ' not allowed'

...

>>> X = accesscontrol()

>>> X.age = 40 # Calls __setattr__

>>> X.age

40

>>> X.name = 'mel'

...text omitted...

AttributeError: name not allowed

These two attribute-access overloading methods allow you to control or specialize access to attributes in your objects. They tend to play highly specialized roles, some of which we’ll explore later in this book.

Other Attribute Management Tools

For future reference, also note that there are other ways to manage attribute access in Python:

The __getattribute__ method intercepts all attribute fetches, not just those that are undefined, but when using it you must be more cautious than with __getattr__ to avoid loops.

The property built-in function allows us to associate methods with fetch and set operations on a specific class attribute.

Descriptors provide a protocol for associating __get__ and __set__ methods of a class with accesses to a specific class attribute.

Because these are somewhat advanced tools not of interest to every Python programmer, we’ll defer a look at properties until Chapter 31 and detailed coverage of all the attribute management techniques until Chapter 37.

Emulating Privacy for Instance Attributes: Part 1

The following code generalizes the previous example, to allow each subclass to have its own list of private names that cannot be assigned to its instances:

class PrivateExc(Exception): pass # More on exceptions later

class Privacy:

def __setattr__(self, attrname, value): # On self.attrname = value

if attrname in self.privates:

raise PrivateExc(attrname, self)

else:

self.__dict__[attrname] = value # self.attrname = value loops!

class Test1(Privacy):

privates = ['age']

class Test2(Privacy):

privates = ['name', 'pay']

def __init__(self):

self.__dict__['name'] = 'Tom'

x = Test1()

y = Test2()

Return Main Page Previous Page Next Page

®Online Book Reader