装饰函数未传递给装饰器

时间:2020-04-12 11:00:13

标签: python decorator

以下是一个无用的示例,它表明我无法用add装饰我的my_decorator函数,并提供了一些额外的参数:

def my_decorator(fn=None, message=None, **options):
    assert fn is not None, 'fn is not found'
    print('function is found')
    print(message)
    return fn

# This syntax works (but does not make sense...)
@my_decorator
def multiply(a, b):
   return a * b

# This syntax does not work
@my_decorator(message='surprise')
def add(a, b):
   return a + b

# This no sugar-syntax works
add = my_decorator(add, message='surprise')  # Works

在第二个示例中,fn被视为None,并提高了AssertionError,但使用的是“无糖”语法!

我的代码有什么问题? (Python 2.7。是的,我们将对其进行迁移...)

2 个答案:

答案 0 :(得分:1)

my_decorator(message='surprise')返回None,因为它隐式使用了fn=None。 然后,您尝试将None应用于add,如您所知,这是行不通的。

您需要一个参数化的函数来返回装饰器。非正式地,这称为参数化装饰器。

这是一个例子:

def make_decorator(message):
    def decorator(func):
        def new_func(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)
        return new_func
    return decorator

@make_decorator(message='surprise')
def add(a, b):
    return a + b

x = add(1, 2) # prints 'surprise' and sets x to 3

请注意

@make_decorator(message='surprise')
def add(a, b):
    return a + b

等同于

def add(a, b):
    return a + b

decorator = make_decorator(message='surprise')
add = decorator(add)

答案 1 :(得分:1)

首先,创建装饰器的最低要求是实现一个在参数中带有函数的函数(装饰器)。

例如,您可以编写:

def my_decorator(f):
    def wrapper(*args, **kwargs):
        print("f() is called")
        return f(*args, **kwargs)

    return wrapper


# You can use your decorator w/o parameter:
@my_decorator
def multiply(a, b):
    return a * b

您得到:

multiply(5, 8)
f() is called
40

如您所见,它可以工作,但装饰器不带任何参数。 因此,您需要创建一个装饰工厂:

def my_decorator_factory(message):
    def my_decorator(f):
        def wrapper(*args, **kwargs):
            print("f() is called, saying: " + message)
            return f(*args, **kwargs)

        return wrapper

    return my_decorator


# You can use your decorator factory with a parameter:
@my_decorator_factory(message="hi")
def multiply(a, b):
    return a * b

您得到:

multiply(5, 8)
f() is called, saying: hi
40

但是,您希望能够像装饰器(不带参数)那样调用装饰器工厂。

因此,您需要更改装饰器工厂的签名以允许以下调用:

my_decorator_factory(message="something")  # or
my_decorator_factory(function)

在这里,您需要检查第一个参数是否是其他函数:

import inspect


def my_decorator_factory(message=None):
    if inspect.isfunction(message):
        return my_decorator_factory(message=None)(message)
    else:
        def my_decorator(f):
            def wrapper(*args, **kwargs):
                print("f() is called, saying: " + (message or "(empty)"))
                return f(*args, **kwargs)

            return wrapper

        return my_decorator


@my_decorator_factory
def multiply(a, b):
    return a * b


@my_decorator_factory(message='surprise')
def add(a, b):
    return a + b

您得到:

multiply(5, 8)
f() is called, saying: (empty)
40
add(3, 2)
f() is called, saying: surprise
5

您可以考虑使用Wrapt来实现“好的”装饰器/装饰器工厂。