1

I want to decorate a simple method, to run 5 times:

def do_5(f):
    @wraps(f)
    def wr(*a,**kw):
        i = 0
        while i < 5:
            f(a,kw)
            i += 1
    return wr

class a(object):
    @do_5
    def f(self, x):
        print x

however, this only makes the func print {} where x is actually 1

Using ipdb I saw that self is the first of *a so I tried changing the wrapper to

In [37]:     def do_5(f):
    ...:         @wraps(f)
    ...:         def wr(*a,**kw):
    ...:             self, other_args = a[0], a[1:]
    ...:             i = 0
    ...:             while i < 5:
    ...:                 f(self,other_args,kw)
    ...:                  i += 1
    ...:         return wr

and

In [37]:     def do_5(f):
    ...:         @wraps(f)
    ...:         def wr(*a,**kw):
    ...:             self, other_args = a[0], a[1:]
    ...:             i = 0
    ...:             while i < 5:
    ...:                 self.f(other_args,kw)
    ...:                  i += 1
    ...:         return wr

but got:

RuntimeError: maximum recursion depth exceeded

and

TypeError: f() takes exactly 2 arguments (3 given)

respectively

How can I solve this?

BTW, can I make this decorator dynamic, as in @do_n(f, n) and decorate it like so (@do_n(100)) and just use the n instead of 5 in the wrapper?

CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • `while i < 5:` - you never do `i += 1` ? – Chris_Rands Apr 10 '19 at 12:24
  • stupid mistake. But now it only fixes the infinite recursion, it's not the main issue of the question – CIsForCookies Apr 10 '19 at 12:25
  • 1
    You still need to unpack the arguments passed to the wrapper: `f(*a, **kw)`. Otherwise, you are just passing two arguments to `f`: a tuple and a dict. – chepner Apr 10 '19 at 12:26
  • @CIsForCookies Re *making this decorator dynamic*; i.e. making a decorator with parameters: have a look [here](https://stackoverflow.com/questions/5929107/decorators-with-parameters) – shmee Apr 10 '19 at 13:40

2 Answers2

4

When you define the wrapper with parameters *a and **kw, a is a tuple and kw is a dict. The * and ** in a function definition are a signal to collect arguments into the two parameters when the function is called. You are passing those two objects to f, rather than the arguments contained in them. You need to unpack them in the call to the wrapped function. In a call, * and ** unpack the attached names.

def do_5(f):
    @wraps(f)
    def wr(*a,**kw):
        i = 0
        while i < 5:
            f(*a, **kw)
            i += 1
    return wr

Now, a().f(3) will output

3
3
3
3
3

as expected.

chepner
  • 497,756
  • 71
  • 530
  • 681
0

You need to pass the data forwards as it is, f(*a,**kw), a is a tuple and kw is dict.

from functools import wraps


def do_5(f):
    @wraps(f)
    def wr(*a,**kw):
        i = 0
        while i < 5:
            f(*a,**kw)
            i += 1
    return wr

class a(object):
    @do_5
    def f(self, x):
        print x

b = a()
b.f(1) 
Arghya Saha
  • 5,599
  • 4
  • 26
  • 48