用方法装饰一个函数(带任意参数)

0 投票
1 回答
1259 浏览
提问于 2025-04-17 15:26

参考资料

在写这个问题之前,我参考了一些有趣的问题,觉得这个情况没有被解释或覆盖:

我有一个 function 如下:

def simple_function():
    print 'this is a simple_function'

还有一个 class,里面有一些 methods

class Test:

    def Test_method_1(self, func, args=None, kwargs=None):
        print 'this is Test::Test_method_1'
        <execute some instructions here>

    def Test_method_2(self):
        print 'this is Test::Test_method_2'

我使用 Test_method_1simple_function 如下:

t = Test()
t.Test_method_1(simple_function)

假设 simple_function 接受任意参数,那么 Test_method_1 的调用方式如下:

def simple_function(a, b, c, d=f, g=h):
    print 'this is a simple_function'

t = Test()
t.Test_method_1(simple_function, args=[a, b, c], kwargs={d:f, g:h})

现在,我想把 Test_method_2 用作 Test_method_1decorator 版本。所以 simple_function 的定义可以写成这样:

t = Test()

@t.Test_method_2
def simple_function():
    print 'this is a simple_function'

注意:Test_method_2 会用适当的参数调用 Test_method_1

现在的问题:

  • 1 - 这可能吗?
  • 2 - 如果可能,装饰的函数名称(这里是 simple_function)将如何作为参数传递给 Test_method_2(包括 self)?
  • 3 - [我认为这是关键问题] 我想将任意参数传递给 Test_method_2(装饰器)和 simple_function(被装饰的函数) - *args 和 **kwargs - Test_method_2 应该如何定义以接收这些参数(包括 self)?

Test_method_2 作为 simple_function 的装饰器,并且两个都可以接受任意参数的用法如下:

t = Test()
@t.Test_method_2(a, b, c, d, e=f, g=h)
def simple_function(x, y, z, n=m):
    print 'this is a simple_function'

1 个回答

1

当然是可以的。@decorator 这个语法的作用就是定义后面的函数,然后调用这个装饰器,把后面的函数传进去,并用装饰器返回的东西替换掉原来的函数引用。

所以,下面这段:

@foo
def bar():
    pass

实际上是变成了:

def bar():
    pass
bar = foo(bar)

这意味着你的 t.Test_method_2() 方法需要接受一个参数,就是要被装饰的函数,并返回一个可以调用的东西:

import functools

def Test_method_2(self, func):
    @functools.wraps(func)
    def wrapper(self, *args, **kw):
        print 'Wrapped function name:', func.__name__
        return func(*args, **kw)
    return wrapper

这段代码就是一个最简单的装饰器,它返回一个包装函数,并在被调用时打印出被包装函数的名字。参数的名字可以随便取;我这里用的是 func,但你可以用任何合法的 Python 标识符。

这里的 self 是方法签名的标准部分。因为你是在实例 t 上调用 Test_method_2,所以 Python 会自动处理 self 参数,就像处理所有方法一样。

@ 后面的内容只是一个表达式。所以如果你使用这样的语法:

@t.Test_method_2(a, b, c, d, e=f, g=h)

那么 Test_method_2() 应该返回一个装饰器函数。只需要多一层作用域嵌套就可以实现:

def Test_method_2(self, *args, **kw):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*wrapperargs, **wrapperkw):
            fargs = args + wrapperargs
            fkw = dict(kw)
            fkw.update(wrapperkw)
            return func(*fargs, **fkw)
        return wrapper
    return decorator

拆解一下这个过程:

@t.Test_method_2(5, 6, 7, 8, baz='spam', ham='eggs')
def simple_function(x, y, z, n=m):
    print 'this is a simple_function'

@ 后面的部分,t.Test_method_2(5, 6, 7, 8, baz='spam', ham='eggs') 返回的是嵌套的函数 decorator

@decorator
def simple_function(x, y, z, n=m):
    print 'this is a simple_function'

然后 Python 会把它变成:

simple_function = decorator(simple_function)

decorator(func) 会返回 wrapper(*wrapperargs, **wrapperkw)

调用 simple_function(1, 2, foo='bar') 就会导致调用 wrapper(1, 2, foo='bar'),这会用 fargs = [5, 6, 7, 8, 1, 2]fkw = {'baz': 'spam', 'ham': 'eggs', 'foo': 'bar'} 作为位置参数和关键字参数,去调用原始的 simple_function()

你在相关问题中看到的类装饰器模式也是类似的;@ 后面的表达式返回的是一个可以被调用的东西;这里不是嵌套函数,而是创建了一个类实例。这只是两种不同的方式来存储装饰器需要使用的状态。

嵌套函数写起来稍微简洁一些,而使用类则给你提供了比嵌套作用域更多的反射选项。

撰写回答