Question

I want to create a custom decorator, called captain_hook. It should store hooks - functions to be called before the decorated function - and call them:

def captain_hook(func):
    def closure(hooks=[], *args, **kwargs):
        for hook in hooks: 
            hook(*args, **kwargs)
        return func(*args, **kwargs)
    return closure

Now I could say:

@captain_hook
def sum(a, b):
    return a+b
sum.__defaults__[0].append(lambda arg: sys.stdout.write(str(arg))) #this lambda function prints argument
sum(1, 2) #should write (1, 2) and return 3

The problem is that I can't pass the arguments to closure, cause first element of args mistakingly unpacks to hooks argument: sum(1, 2) sets hook=1, a=2, b - undefined. How do I do that?

Was it helpful?

Solution

Modifying __defaults__ is a very dubious way to handle this. __defaults__ is for argument default values. If your hooks aren't going to be passed as arguments, you shouldn't store them as an argument default value.

If you really want to be able to modify the hooks later on, you should make your decorator a class that stores the hooks on itself. Here's an example:

class CaptainHook(object):
    def __init__(self, func):
        self.hooks = []
        self.func = func

    def __call__(self, *args, **kwargs):
        for hook in self.hooks:
            hook(*args, **kwargs)
        return self.func(*args, **kwargs)

Then:

>>> @CaptainHook
... def sum(a, b):
...     return a+b
>>> sum(1, 2)
3
>>> def aHook(*args, **kwargs):
...     print "I am a hook!"
>>> sum.hooks.append(aHook)
>>> sum(1, 2)
I am a hook!
3
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top