1

Is there any way to call a function multiple times with a decorator?

Here is my code:

def call_func(**case):
 def decorator(func):
 def wrapped_function(*args, **kwargs):
 return func(*args, **case)
 return wrapped_function
 return decorator
@call_func(p=1, o=2)
@call_func(p=3, o=4)
@call_func(p=5, o=6)
def some_func(p, o):
 print(p, o)
some_func()

And the output is:

(5, 6)

But I want:

(1, 2)
(3, 4)
(5, 6)

Is this possible? And, is this Pythonic?

asked Feb 27, 2015 at 14:49
1
  • 2
    Yes, though I'm not sure what you're trying to achieve actually. This sounds like a good use case for functools.partial As it is, each time you use that decorator it's just nesting the original function in a deeper and deeper layer of wrapper functions. It's not going to call the original function more than once. Commented Feb 27, 2015 at 14:53

2 Answers 2

3
import functools
def call_func(cache={}, **case):
 def decorator(func):
 funcname = func.__name__
 if funcname not in cache:
 # save the original function
 cache[funcname] = func
 @functools.wraps(func)
 def wrapped_function(**kwargs):
 if cache[funcname] != func:
 cache[funcname](**case)
 func(**case)
 return wrapped_function
 return decorator
@call_func(p=1, o=2)
@call_func(p=3, o=4)
@call_func(p=5, o=6)
def some_func(p, o):
 print(p, o)
some_func()

yields

(1, 2)
(3, 4)
(5, 6)

Your call_func decorator is close to creating the desired chain of function calls. Consider that:

def call_func(**case):
 def decorator(func):
 def wrapped_function(**kwargs):
 print(case)
 func(**case)
 return wrapped_function
 return decorator
@call_func(p=1, o=2)
@call_func(p=3, o=4)
@call_func(p=5, o=6)
def some_func(p, o):
 print(p, o)
some_func()

yields

{'p': 1, 'o': 2}
{'p': 3, 'o': 4}
{'p': 5, 'o': 6}
(5, 6)

So the wrapped_functions are clearly being called in the right order, and with the desired values for case. The only problem is that we want to call the original function some_func at each stage.

Somehow we need to give each wrapped_function access to the original some_func.

We can do that by giving call_func a cache which records the first time it sees a function of a certain name. This is the purpose of

def call_func(cache={}, **case):
 def decorator(func):
 funcname = func.__name__
 if funcname not in cache:
 # save the original function
 cache[funcname] = func
answered Feb 27, 2015 at 15:55
0

Here are two ways to do it with single decorators. The tricky part is that you can't have multiples of the same key in a dict. The first way uses a list of dicts. The second way uses a dict of tuples, so we can take advantage of Python's machinery for passing named arguments to build the dict from the supplied args.

#!/usr/bin/env python
''' Decorator demo
 Decorate a function so it can be called multiple times 
 with different args specified in the decorator.
 From http://stackoverflow.com/q/28767826/4014959
 Written by PM 2Ring 2015年02月28日
'''
from __future__ import print_function
def multicall0(argdicts):
 def decorator(func):
 def wrapped_function(*args, **kwargs):
 for d in argdicts:
 func(**d)
 return wrapped_function
 return decorator
dlist = [
 {'p':1, 'o':2}, 
 {'p':3, 'o':4}, 
 {'p':5, 'o':6},
]
@multicall0(dlist)
def some_func(p, o):
 print(p, o)
some_func()
# ------------------------------------
def multicall1(**kwargs):
 def decorator(func):
 dlist = [[(k, v) for v in kwargs[k]] for k in kwargs.keys()]
 argdicts = [dict(item) for item in zip(*dlist)]
 def wrapped_function(*args, **kwargs):
 for d in argdicts:
 func(**d)
 return wrapped_function
 return decorator
@multicall1(p=(10, 30, 50, 70), o=(20, 40, 60, 80))
def another_func(p, o):
 print(p, o)
another_func()

output

1 2
3 4
5 6
10 20
30 40
50 60
70 80

Is it Pythonic? Maybe not. :)

answered Feb 27, 2015 at 15:46

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.