Python - неоднозначность с декораторами, получающими один аргумент - PullRequest
1 голос
/ 21 августа 2009

Я пытаюсь написать декоратор, который получает один аргумент, т. Е.

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

Итак, наивно я пытался:

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

Это нормально, но он также работает как декоратор, не получая аргументов, т.е.

@Printer
def a():
   print 3

Как я могу предотвратить это?

Ответы [ 4 ]

4 голосов
/ 21 августа 2009

Ну, это уже эффективно предотвращено, в том смысле, что вызов a() не работает.

Но чтобы остановить ее, как определена функция, я полагаю, вам нужно изменить __init__, чтобы проверить тип num:

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

Я не знаю, стоит ли это беспокоить. Это уже не работает как есть; Вы действительно просите ввести типы аргументов в языке типа "утка".

1 голос
/ 21 августа 2009

Я не могу придумать идеального ответа, но если вы принудительно создадите экземпляр класса Printer с помощью аргумента с ключевым словом, он никогда не сможет попытаться создать экземпляр с помощью самого декоратора, поскольку он имеет дело только с аргументами, не являющимися ключевыми словами:

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

...

@Printer(num=1)
def a():
    print 3
1 голос
/ 21 августа 2009

Декоратор - это любое выражение после @. В первом случае это экземпляр Printer, поэтому то, что происходит (в значительной степени) эквивалентно

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"

Во втором случае это класс Printer, поэтому у вас есть

decorator = Printer # the class

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

Итак, хотя это работает (потому что конструктор Printer принимает один дополнительный аргумент, как и __call__), это совершенно другая вещь.

Способ предотвращения этого на python обычно таков: не делайте этого. Уточните (например, в строке документации), как работает декоратор, и затем поверьте, что люди поступают правильно.

Если вы действительно хотите чек, Ответ Eevee предоставляет способ уловить эту ошибку (конечно, во время выполнения - это Python).

1 голос
/ 21 августа 2009

Вы уверены, что это работает без аргументов? Если я их опускаю, я получаю это сообщение об ошибке:

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

Однако вы можете попробовать это альтернативное определение, если у вас не работает классовое определение.

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

    return wrapper
...