简体   繁体   中英

Python - ambiguity with decorators receiving a single arg

I am trying to write a decorator that gets a single arg, ie

@Printer(1)
def f():
    print 3

So, naively, I tried:

class Printer:
    def __init__(self,num):
         self.__num=num
    def __call__(self,func):
         def wrapped(*args,**kargs):
              print self.__num
              return func(*args,**kargs**)
         return wrapped

This is ok, but it also works as a decorator receiving no args, ie

@Printer
def a():
   print 3

How can I prevent that?

Well, it's already effectively prevented, in the sense that calling a() doesn't work.

But to stop it as the function is defined, I suppose you'd have to change __init__ to check the type of num :

def __init__(self,num):
    if callable(num):
        raise TypeError('Printer decorator takes an argument')
    self.__num=num

I don't know if this is really worth the bother, though. It already doesn't work as-is; you're really asking to enforce the types of arguments in a duck-typed language.

Are you sure it works without arguments? If I leave them out I get this error message:

Traceback (most recent call last):
  File "/tmp/blah.py", line 28, in ?
    a()
TypeError: __call__() takes exactly 2 arguments (1 given)

You could try this alternative definition, though, if the class-based one doesn't work for you.

def Printer(num):
    def wrapper(func):
        def wrapped(*args, **kwargs):
            print num
            return func(*args, **kwargs)
        return wrapped

    return wrapper

The decorator is whatever the expression after @ evaluates to. In the first case, that's an instance of Printer , so what happens is (pretty much) equivalent to

decorator = Printer(1) # an instance of Printer, the "1" is given to __init__

def f():
    print 3
f = decorator(f) # == dec.__call__(f) , so in the end f is "wrapped"

In the second case, that's the class Printer, so you have

decorator = Printer # the class

def a():
   print 3
a = decorator(a) # == Printer(a), so a is an instance of Printer

So, even though it works (because the constructor of Printer takes one extra argument, just like __call__ ), it's a totally different thing.

The python way of preventing this usually is: Don't do it. Make it clear (eg in the docstring) how the decorator works, and then trust that people do the right thing.

If you really want the check, Eevee's answer provides a way to catch this mistake (at runtime, of course---it's Python).

I can't think of an ideal answer, but if you force the Printer class to be instantiated with a keyword argument, it can never try to instantiate via the decorator itself, since that only deals with non-keyword arguments:

def __init__(self,**kwargs):
     self.__num=kwargs["num"]

...

@Printer(num=1)
def a():
    print 3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM