Decorators Versus Manager Functions

Regardless of such subtleties, the Tracer class decorator example ultimately still relies on __getattr__ to intercept fetches on a wrapped and embedded instance object. As we saw earlier, all we’ve really accomplished is moving the instance creation call inside a class, instead of passing the instance into a manager function. With the original nondecorator tracing example, we would simply code instance creation differently:

class Spam:                                   # Non-decorator version
    ...                                       # Any class will do
food = Wrapper(Spam())                        # Special creation syntax

@Tracer
class Spam:                                   # Decorator version
    ...                                       # Requires @ syntax at class
food = Spam()                                 # Normal creation syntax

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

Essentially, class decorators shift special syntax requirements from the instance creation call to the class statement itself. This is also true for the singleton example earlier in this section—rather than decorating a class and using normal instance creation calls, we could simply pass the class and its construction arguments into a manager function:

instances = {}
def getInstance(aClass, *args):
    if aClass not in instances:
        instances[aClass] = aClass(*args)
    return instances[aClass]

bob = getInstance(Person, 'Bob', 40, 10)    # Versus: bob = Person('Bob', 40, 10)

Alternatively, we could use Python’s introspection facilities to fetch the class from an already-created instance (assuming creating an initial instance is acceptable):

instances = {}
def getInstance(object):
    aClass = object.__class__
    if aClass not in instances:
        instances[aClass] = object
    return instances[aClass]

bob = getInstance(Person('Bob', 40, 10))    # Versus: bob = Person('Bob', 40, 10)

The same holds true for function decorators like the tracer we wrote earlier: rather than decorating a function with logic that intercepts later calls, we could simply pass the function and its arguments into a manager that dispatches the call:

def func(x, y):                   # Nondecorator version
    ...                           # def tracer(func, args): ... func(*args)
result = tracer(func, (1, 2))     # Special call syntax

@tracer
def func(x, y):                   # Decorator version
    ...                           # Rebinds name: func = tracer(func)
result = func(1, 2)               # Normal call syntax

Manager function approaches like this place the burden of using special syntax on calls, instead of expecting decoration syntax at function and class definitions.