Learning Python - Mark Lutz [551]
To see the difference yourself, try applying the decorator to a class that uses operator overloading methods under 2.6; validations work as before, and both the __str__ method used by printing and the __add__ method run for + invoke the decorator’s __getattr__ and hence wind up being validated and delegated to the subject Person object correctly:
C:\misc> c:\python26\python
>>> from access import Private
>>> @Private('age')
... class Person:
... def __init__(self):
... self.age = 42
... def __str__(self):
... return 'Person: ' + str(self.age)
... def __add__(self, yrs):
... self.age += yrs
...
>>> X = Person()
>>> X.age # Name validations fail correctly
TypeError: private attribute fetch: age
>>> print(X) # __getattr__ => runs Person.__str__
Person: 42
>>> X + 10 # __getattr__ => runs Person.__add__
>>> print(X) # __getattr__ => runs Person.__str__
Person: 52
When the same code is run under Python 3.0, though, the implicitly invoked __str__ and __add__ skip the decorator’s __getattr__ and look for definitions in or above the decorator class itself; print winds up finding the default display inherited from the class type (technically, from the implied object superclass in 3.0), and + generates an error because no default is inherited:
C:\misc> c:\python30\python
>>> from access import Private
>>> @Private('age')
... class Person:
... def __init__(self):
... self.age = 42
... def __str__(self):
... return 'Person: ' + str(self.age)
... def __add__(self, yrs):
... self.age += yrs
...
>>> X = Person() # Name validations still work
>>> X.age # But 3.0 fails to delegate built-ins!
TypeError: private attribute fetch: age
>>> print(X)
>>> X + 10 TypeError: unsupported operand type(s) for +: 'onInstance' and 'int' >>> print(X) Using the alternative __getattribute__ method won’t help here—although it is defined to catch every attribute reference (not just undefined names), it is also not run by built-in operations. Python’s property feature, which we met in Chapter 37, won’t help here either; recall that properties are automatically run code associated with specific attributes defined when a class is written, and are not designed to handle arbitrary attributes in wrapped objects. As mentioned earlier, the most straightforward solution under 3.0 is to redundantly redefine operator overloading names that may appear in embedded objects in delegation-based classes like our decorator. This isn’t ideal because it creates some code redundancy, especially compared to 2.6 solutions. However, it isn’t too major a coding effort, can be automated to some extent with tools or superclasses, suffices to make our decorator work in 3.0, and allows operator overloading names to be declared Private or Public too (assuming each overloading method runs the failIf test internally): def accessControl(failIf): def onDecorator(aClass): class onInstance: def __init__(self, *args, **kargs): self.__wrapped = aClass(*args, **kargs) # Intercept and delegate operator overloading methods def __str__(self): return str(self.__wrapped) def __add__(self, other): return self.__wrapped + other def __getitem__(self, index): return self.__wrapped[index] # If needed def __call__(self, *args, **kargs): return self.__wrapped(*arg, *kargs) # If needed ...plus any others needed... # Intercept and delegate named attributes def __getattr__(self, attr): ... def __setattr__(self, attr, value): ... return onInstance return onDecorator With such operator overloading methods added, the prior example with __str__ and __add__ works the same under 2.6 and 3.0, although a substantial amount of extra code may be required to accommodate 3.0—in principle, every operator overloading method that is not run automatically will need to be defined redundantly for 3.0 in a general tool class like this (which is why this extension is omitted in our code). Since every