将默认参数传递给python中的装饰器

Posted

技术标签:

【中文标题】将默认参数传递给python中的装饰器【英文标题】:Passing default arguments to a decorator in python 【发布时间】:2015-10-22 01:56:12 【问题描述】:

我正在尝试找到一种将函数的​​默认参数传递给装饰器的方法。我不得不说我对装饰行业还很陌生,所以也许我没有正确理解它,但我还没有找到任何答案。

这是我修改后的 Python functools.wraps manual page 示例。

from functools import wraps
def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
            print('Calling decorated function')
            print('args:', args)
            print('kwargs:', kwds)
            return f(*args, **kwds)
    return wrapper

@my_decorator
def example(i, j=0):
    """Docstring"""
    print('Called example function')

example(i=1)

我也希望通过j=0。所以输出应该是:

Calling decorated function
args: ()
kwargs: 'i': 1, 'j': 0
Called example function

但是我得到了

Calling decorated function
args: ()
kwargs: 'i': 1
Called example function

【问题讨论】:

j=0 通过,但不在wrapper。如果您在example 中使用print i, j,您会看到它就在那里。你可以使用例如inspect.getargspec(f) 来查看被装饰的函数设置了哪些默认值,但是为什么需要访问wrapper 中的默认值? 我知道它已传递给example,但我需要将j 传递给wrapper,因为我使用许多函数进行计算时需要它。但通常使用inspect.getargspec(f) 会起作用,谢谢。 【参考方案1】:

获取 args 和 kwargs 的确切列表有点棘手,因为您可以将位置 args 作为 kwarg 传递,反之亦然。较新版本的 python 还添加了仅位置或仅关键字参数。

但是,inspect.signature 有一种可以应用默认值的机制:调用 .bind(*args, **kwargs) 后跟 .apply_defaults()。这可以有效地为您提供一个字典,其中包含函数的所有参数。在 OP 中的示例中,这变为:

from functools import wraps
import inspect
def my_decorator(f):
    sig = inspect.signature(f)
    @wraps(f)
    def wrapper(*args, **kwds):
        bound = sig.bind(*args, **kwds)
        bound.apply_defaults()
        print('Calling decorated function')
        print('called with:', bound.arguments)
        return f(*args, **kwds)
    return wrapper

@my_decorator
def example(i, j=0):
    """Docstring"""
    print('Called example function')

example(i=1)

这会在 Python 3.9 上输出以下内容:

Calling decorated function
called with: OrderedDict([('i', 1), ('j', 0)])
Called example function

【讨论】:

【参考方案2】:

默认参数是函数签名的一部分。它们在装饰器调用中不存在。

要在包装器中访问它们,您需要将它们从函数中取出,如this question 所示。

import inspect
from functools import wraps

def get_default_args(func):
    signature = inspect.signature(func)
    return 
        k: v.default
        for k, v in signature.parameters.items()
        if v.default is not inspect.Parameter.empty
    

def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
            print('Calling decorated function')
            print('args:', args)
            kwargs = get_default_args(f)
            kwargs.update(kwds)
            print('kwargs:', kwargs)
            return f(*args, **kwds)
    return wrapper

@my_decorator
def example(i, j=0):
    """Docstring"""
    print('Called example function')

example(i=1)

输出:

Calling decorated function
args: ()
kwargs: 'i': 1, 'j': 0
Called example function

【讨论】:

感谢您的回答,好主意!对于发现这一点的其他人,值得注意的是 dict.update() 将使用默认值覆盖任何关键字参数。您需要滚动自己的 dict 更新而不进行覆盖以避免这种行为。 调用example(1) 得到kwargs: 'j': 0 缺少i 键,这可能是个问题。【参考方案3】:

您可以使用__defaults__ 特殊属性获取默认参数值。

def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwds):
    print('def args values', f.__defaults__)
    return f(*args, **kwds)
return wrapper

参考:在https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy中寻找__defaults__

一个元组,包含那些具有默认值的参数的默认参数值,如果没有参数具有默认值,则为 None

【讨论】:

返回参数默认值的元组,但不返回参数名称。

以上是关于将默认参数传递给python中的装饰器的主要内容,如果未能解决你的问题,请参考以下文章

将参数传递给要装饰的类方法的装饰器

如何将非硬编码参数传递给 Python 装饰器?

Django - 将参数传递给 CBV 装饰器的正确方法?

在 Python 中,如何获取带参数传递给装饰器的函数名称?

将参数传递给 decontext 装饰器

将附加参数传递给角度组件内的方法装饰器