预计阅读本页时间:-
Open Issues
As is, this example works as planned under Python 2.6 and 3.0 (provided operator overloading methods to be delegated are redefined in the wrapper). As with most software, though, there is always room for improvement.
Caveat: operator overloading methods fail to delegate under 3.0
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
Like all delegation-based classes that use __getattr__, this decorator works cross-version for normally named attributes only; operator overloading methods like __str__ and __add__ work differently for new-style classes and so fail to reach the embedded object if defined there when this runs under 3.0.
As we learned in the prior chapter, classic classes look up operator overloading names in instances at runtime normally, but new-style classes do not—they skip the instance entirely and look up such methods in classes. Hence, the __X__ operator overloading methods implicitly run for built-in operations do not trigger either __getattr__ or __getattribute__ in new-style classes in 2.6 and all classes in 3.0; such attribute fetches skip our onInstance.__getattr__ altogether, so they cannot be validated or delegated.
Our decorator’s class is not coded as new-style (by deriving from object), so it will catch operator overloading methods if run under 2.6. Since all classes are new-style automatically in 3.0, though, such methods will fail if they are coded on the embedded object. The simplest workaround in 3.0 is to redefine redundantly in onInstance all the operator overloading methods that can possibly be used in wrapped objects. Such extra methods can be added by hand, by tools that partly automate the task (e.g., with class decorators or the metaclasses discussed in the next chapter), or by definition in superclasses.
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)
<access.onInstance object at 0x025E0790>
>>> X + 10
TypeError: unsupported operand type(s) for +: 'onInstance' and 'int'
>>> print(X)
<access.onInstance object at 0x025E0790>
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 class is new-style in 3.0, delegation-based code is more difficult (though not impossible) in this release.
On the other hand, delegation wrappers could simply inherit from a common superclass that redefines operator overloading methods once, with standard delegation code. Moreover, tools such as additional class decorators or metaclasses might automate some of the work of adding such methods to delegation classes (see the class augmentation examples in Chapter 39 for details). Though still not as simple as the 2.6 solution, such techniques might help make 3.0 delegation classes more general.
Implementation alternatives: __getattribute__ inserts, call stack inspection
Although redundantly defining operator overloading methods in wrappers is probably the most straightforward workaround to Python 3.0 dilemma outlined in the prior section, it’s not necessarily the only one. We don’t have space to explore this issue much further here, so investigating other potential solutions is relegated to a suggested exercise. Because one dead-end alternative underscores class concepts well, though, it merits a brief mention.
One downside of this example is that instance objects are not truly instances of the original class—they are instances of the wrapper instead. In some programs that rely on type testing, this might matter. To support such cases, we might try to achieve similar effects by inserting a __getattribute__ method into the original class, to catch every attribute reference made on its instances. This inserted method would pass valid requests up to its superclass to avoid loops, using the techniques we studied in the prior chapter. Here is the potential change to our class decorator’s code:
# trace support as before
def accessControl(failIf):
def onDecorator(aClass):
def getattributes(self, attr):
trace('get:', attr)
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return object.__getattribute__(self, attr)
aClass.__getattribute__ = getattributes
return aClass
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
This alternative addresses the type-testing issue but suffers from others. For example, it handles only attribute fetches—as is, this version allows private names to be assigned freely. Intercepting assignments would still have to use __setattr__, and either an instance wrapper object or another class method insertion. Adding an instance wrapper to catch assignments would change the type again, and inserting methods fails if the original class is using a __setattr__ of its own (or a __getattribute__, for that matter!). An inserted __setattr__ would also have to allow for a __slots__ in the client class.
In addition, this scheme does not address the built-in operation attributes issue described in the prior section, since __getattribute__ is not run in these contexts, either. In our case, if Person had a __str__ it would be run by print operations, but only because it was actually present in that class. As before, the __str__ attribute would not be routed to the inserted __getattribute__ method generically—printing would bypass this method altogether and call the class’s __str__ directly.
Although this is probably better than not supporting operator overloading methods in a wrapped object at all (barring redefinition, at least), this scheme still cannot intercept and validate __X__ methods, making it impossible for any of them to be Private. Although most operator overloading methods are meant to be public, some might not be.
Much worse, because this nonwrapper approach works by adding a __getattribute__ to the decorated class, it also intercepts attribute accesses made by the class itself and validates them the same as accesses made from outside—this means the class’s method won’t be able to use Private names, either!
In fact, inserting methods this way is functionally equivalent to inheriting them, and implies the same constraints as our original Chapter 29 privacy code. To know whether an attribute access originated inside or outside the class, our method might need to inspect frame objects on the Python call stack. This might ultimately yield a solution (replace private attributes with properties or descriptors that check the stack, for example), but it would slow access further and is far too dark a magic for us to explore here.
While interesting, and possibly relevant for some other use cases, this method insertion technique doesn’t meet our goals. We won’t explore this option’s coding pattern further here because we will study class augmentation techniques in the next chapter, in conjunction with metaclasses. As we’ll see there, metaclasses are not strictly required for changing classes this way, because class decorators can often serve the same role.