预计阅读本页时间:-
The Basics
__getattr__ and __setattr__ were introduced in Chapters 29 and 31, and __getattribute__ was mentioned briefly in Chapter 31. In short, if a class defines or inherits the following methods, they will be run automatically when an instance is used in the context described by the comments to the right:
def __getattr__(self, name): # On undefined attribute fetch [obj.name]
def __getattribute__(self, name): # On all attribute fetch [obj.name]
def __setattr__(self, name, value): # On all attribute assignment [obj.name=value]
def __delattr__(self, name): # On all attribute deletion [del obj.name]
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
In all of these, self is the subject instance object as usual, name is the string name of the attribute being accessed, and value is the object being assigned to the attribute. The two get methods normally return an attribute’s value, and the other two return nothing (None). For example, to catch every attribute fetch, we can use either of the first two methods above, and to catch every attribute assignment we can use the third:
class Catcher:
def __getattr__(self, name):
print('Get:', name)
def __setattr__(self, name, value):
print('Set:', name, value)
X = Catcher()
X.job # Prints "Get: job"
X.pay # Prints "Get: pay"
X.pay = 99 # Prints "Set: pay 99"
Such a coding structure can be used to implement the delegation design pattern we met earlier, in Chapter 30. Because all attribute are routed to our interception methods generically, we can validate and pass them along to embedded, managed objects. The following class (borrowed from Chapter 30), for example, traces every attribute fetch made to another object passed to the wrapper class:
class Wrapper:
def __init__(self, object):
self.wrapped = object # Save object
def __getattr__(self, attrname):
print('Trace:', attrname) # Trace fetch
return getattr(self.wrapped, attrname) # Delegate fetch
There is no such analog for properties and descriptors, short of coding accessors for every possible attribute in every possibly wrapped object.
Avoiding loops in attribute interception methods
These methods are generally straightforward to use; their only complex part is the potential for looping (a.k.a. recursing). Because __getattr__ is called for undefined attributes only, it can freely fetch other attributes within its own code. However, because __getattribute__ and __setattr__ are run for all attributes, their code needs to be careful when accessing other attributes to avoid calling themselves again and triggering a recursive loop.
For example, another attribute fetch run inside a __getattribute__ method’s code will trigger __getattribute__ again, and the code will loop until memory is exhausted:
def __getattribute__(self, name):
x = self.other # LOOPS!
To work around this, route the fetch through a higher superclass instead to skip this level’s version—the object class is always a superclass, and it serves well in this role:
def __getattribute__(self, name):
x = object.__getattribute__(self, 'other') # Force higher to avoid me
For __setattr__, the situation is similar; assigning any attribute inside this method triggers __setattr__ again and creates a similar loop:
def __setattr__(self, name, value):
self.other = value # LOOPS!
To work around this problem, assign the attribute as a key in the instance’s __dict__ namespace dictionary instead. This avoids direct attribute assignment:
def __setattr__(self, name, value):
self.__dict__['other'] = value # Use atttr dict to avoid me
Although it’s a less common approach, __setattr__ can also pass its own attribute assignments to a higher superclass to avoid looping, just like __getattribute__:
def __setattr__(self, name, value):
object.__setattr__(self, 'other', value) # Force higher to avoid me
By contrast, though, we cannot use the __dict__ trick to avoid loops in __getattribute__:
def __getattribute__(self, name):
x = self.__dict__['other'] # LOOPS!
Fetching the __dict__ attribute itself triggers __getattribute__ again, causing a recursive loop. Strange but true!
The __delattr__ method is rarely used in practice, but when it is, it is called for every attribute deletion (just as __setattr__ is called for every attribute assignment). Therefore, you must take care to avoid loops when deleting attributes, by using the same techniques: namespace dictionaries or superclass method calls.