Online Book Reader

Home Category

Learning Python - Mark Lutz [518]

By Root 1912 0
I stated that they intercept undefined and all attribute fetches, respectively, which makes them ideal for delegation-based coding patterns. While this is true for normally named attributes, their behavior needs some additional clarification: for method-name attributes implicitly fetched by built-in operations, these methods may not be run at all. This means that operator overloading method calls cannot be delegated to wrapped objects unless wrapper classes somehow redefine these methods themselves.

For example, attribute fetches for the __str__, __add__, and __getitem__ methods run implicitly by printing, + expressions, and indexing, respectively, are not routed to the generic attribute interception methods in 3.0. Specifically:

In Python 3.0, neither __getattr__ nor __getattribute__ is run for such attributes.

In Python 2.6, __getattr__ is run for such attributes if they are undefined in the class.

In Python 2.6, __getattribute__ is available for new-style classes only and works as it does in 3.0.

In other words, in Python 3.0 classes (and 2.6 new-style classes), there is no direct way to generically intercept built-in operations like printing and addition. In Python 2.X, the methods such operations invoke are looked up at runtime in instances, like all other attributes; in Python 3.0 such methods are looked up in classes instead.

This change makes delegation-based coding patterns more complex in 3.0, since they cannot generically intercept operator overloading method calls and route them to an embedded object. This is not a showstopper—wrapper classes can work around this constraint by redefining all relevant operator overloading methods in the wrapper itself, in order to delegate calls. These extra methods can be added either manually, with tools, or by definition in and inheritance from common superclasses. This does, however, make wrappers more work than they used to be when operator overloading methods are a part of a wrapped object’s interface.

Keep in mind that this issue applies only to __getattr__ and __getattribute__. Because properties and descriptors are defined for specific attributes only, they don’t really apply to delegation-based classes at all—a single property or descriptor cannot be used to intercept arbitrary attributes. Moreover, a class that defines both operator overloading methods and attribute interception will work correctly, regardless of the type of attribute interception defined. Our concern here is only with classes that do not have operator overloading methods defined, but try to intercept them generically.

Consider the following example, the file getattr.py, which tests various attribute types and built-in operations on instances of classes containing __getattr__ and __getattribute__ methods:

class GetAttr:

eggs = 88 # eggs stored on class, spam on instance

def __init__(self):

self.spam = 77

def __len__(self): # len here, else __getattr__ called with __len__

print('__len__: 42')

return 42

def __getattr__(self, attr): # Provide __str__ if asked, else dummy func

print('getattr: ' + attr)

if attr == '__str__':

return lambda *args: '[Getattr str]'

else:

return lambda *args: None

class GetAttribute(object): # object required in 2.6, implied in 3.0

eggs = 88 # In 2.6 all are isinstance(object) auto

def __init__(self): # But must derive to get new-style tools,

self.spam = 77 # incl __getattribute__, some __X__ defaults

def __len__(self):

print('__len__: 42')

return 42

def __getattribute__(self, attr):

print('getattribute: ' + attr)

if attr == '__str__':

return lambda *args: '[GetAttribute str]'

else:

return lambda *args: None

for Class in GetAttr, GetAttribute:

print('\n' + Class.__name__.ljust(50, '='))

X = Class()

X.eggs # Class attr

X.spam # Instance attr

X.other # Missing attr

len(X) # __len__ defined explicitly

try: # New-styles must support [], +, call directly: redefine

X[0] # __getitem__?

except:

print('fail []')

try:

X + 99 # __add__?

except:

print('fail +')

try:

X() # __call__? (implicit via built-in)

except:

print('fail

Return Main Page Previous Page Next Page

®Online Book Reader