首页 > 解决方案 > Python通过装饰器将参数传递给函数

问题描述

我正在构建一个类,该类具有一个方法,该方法接受要在该方法中调用的用户定义函数。该函数可以接受来自该函数所需的一组预定义参数的参数。声明函数时的“正常”做法是将所有参数设置为关键字参数,默认值为None,并且只使用所需的参数。但是,我正在寻找一个更优雅的解决方案,它不需要明确列出所有参数。

所以我在类中编写了一些装饰器函数,用户可以在声明函数时使用它们来通知类需要传递哪些参数(请注意,下面的代码纯粹是展示概念的示例):

class MyClass:
    def __init__(self):
        self.a = None
        self.b = None
    
    def use_a(self, func):
        def wrapper(*args, **kwargs):
            kwargs.update({"a": self.a})
            return func(*args, **kwargs)
        return wrapper

    def use_b(self, func):
        def wrapper(*args, **kwargs):
            kwargs.update({"b": self.b})
            return func(*args, **kwargs)
        return wrapper

    def run(self, func):
        for x in range(10):
            self.a = x
            self.b = x * 10
            func()


g = MyClass()

@g.use_a
@g.use_b
def my_custom_function(a, b):
    print(a, b)

myfunc = my_custom_function
g.run(myfunc)

我认为唯一的问题是用户定义的函数必须在类初始化之后声明,如果我想在另一个模块中定义函数,这就会成为一个问题。因此,为了缓解这种情况,我决定创建一个工厂函数,它接受一个实例作为其参数,并返回修饰函数:

# defined in another module
def external_function(instance):
    @instance.use_a
    @instance.use_b
    def custom_func_b(a, b):
        print(a, b)
    return custom_func_b

from mymodule import external_function
g = MyClass()
myfunc = external_function(g)
g.run(myfunc)
  1. 请注意,外部定义的函数会传递其返回值,同时在类初始化范围内声明的函数也会传递函数本身。这有可能令人困惑。
  2. 如您所见,使用装饰器传递参数似乎不是最佳设计,因为不建议在类作用域中使用装饰器。我想知道是否值得继续使用这种设计,而不是接受所有默认值设置为的参数None,或者存在更适合我的情况的其他方法。

标签: pythonarchitecture

解决方案


据我了解,您有一些函数可以接受一组参数,并且您希望有一些东西可以代表您内联它们,这样您就不需要每次都明确地传递它们。

如果是这种情况,您就是在重新发明部分函数应用程序,这(过于简单化)归结为创建一个函数 B,它将调用函数 A,其中 A 的一些参数已经传入。这可以通过创建您自己的参数来实现实现partial或使用 functools 模块。在代码中:

# The base function
def base_function(a, b, c=None):
    print(a, b, c)

# The function that implements partial application
def my_partial(func, *args, **kwargs):
    positional_args = args
    keyword_args = kwargs
    def wrapped(*args, **kwargs):
        all_pos_args = positional_args + args
        all_kw_args = {**keyword_args, **kwargs}
        return func(*all_pos_args, **all_kw_args)
    return wrapped

# Usage
new_func = my_partial(base_function, 1, c=3)
new_func(2) # Outputs: 1 2 3

# Now using functools partial
from functools import partial
new_func2 = partial(base_function, 1, c=3)
new_func2(2) # Again: 1 2 3

出于两个原因,我通常会采用部分解决方案而不是更复杂的解决方案。首先是你的类装饰器的状态隐藏了调用可能导致错误的装饰函数的结果,并且在使用装饰器之前必须初始化类。第二个是显式传递关键字参数使代码意图更清晰。

作为旁注,接收参数的装饰器也有它们的位置,例如,您可以检查 Flask 服务器库如何使用@app.route("/my/route")装饰器实现 url 路由。


推荐阅读