Implementation Details I

This code is a bit complex, and you’re probably best off tracing through it on your own to see how it works. To help you study, though, here are a few highlights worth mentioning.

Inheritance versus delegation

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

The first-cut privacy example shown in Chapter 29 used inheritance to mix in a __setattr__ to catch accesses. Inheritance makes this difficult, however, because differentiating between accesses from inside or outside the class is not straightforward (inside access should be allowed to run normally, and outside access should be restricted). To work around this, the Chapter 29 example requires inheriting classes to use __dict__ assignments to set attributes—an incomplete solution at best.

The version here uses delegation (embedding one object inside another) instead of inheritance; this pattern is better suited to our task, as it makes it much easier to distinguish between accesses inside and outside of the subject class. Attribute accesses from outside the subject class are intercepted by the wrapper layer’s overloading methods and delegated to the class if valid; accesses inside the class itself (i.e., through self inside its methods’ code) are not intercepted and are allowed to run normally without checks, because privacy is not inherited here.

Decorator arguments

The class decorator used here accepts any number of arguments, to name private attributes. What really happens, though, is that the arguments are passed to the Private function, and Private returns the decorator function to be applied to the subject class. So, the arguments are used before decoration ever occurs; Private returns the decorator, which in turn “remembers” the privates list as an enclosing scope reference.

State retention and enclosing scopes

Speaking of enclosing scopes, there are actually three levels of state retention at work in this code:

 

 
  • The arguments to Private are used before decoration occurs and are retained as an enclosing scope reference for use in both onDecorator and onInstance.
  • The class argument to onDecorator is used at decoration time and is retained as an enclosing scope reference for use at instance construction time.
  • The wrapped instance object is retained as an instance attribute in onInstance, for use when attributes are later accessed from outside the class.

This all works fairly naturally, given Python’s scope and namespace rules.

Using __dict__ and __slots__

The __setattr__ in this code relies on an instance object’s __dict__ attribute namespace dictionary in order to set onInstance’s own wrapped attribute. As we learned in the prior chapter, it cannot assign an attribute directly without looping. However, it uses the setattr built-in instead of __dict__ to set attributes in the wrapped object itself. Moreover, getattr is used to fetch attributes in the wrapped object, since they may be stored in the object itself or inherited by it.

Because of that, this code will work for most classes. You may recall from Chapter 31 that new-style classes with __slots__ may not store attributes in a __dict__. However, because we only rely on a __dict__ at the onInstance level here, not in the wrapped instance, and because setattr and getattr apply to attributes based on both __dict__ and __slots__, our decorator applies to classes using either storage scheme.