【问题标题】:Passing arguments to decorator from decorated function从装饰函数将参数传递给装饰器
【发布时间】:2021-11-21 06:09:20
【问题描述】:

我已经准备了一些函数装饰器,从装饰器的角度传递了默认参数:

def decorator_wraper(max_repeating=20)
    def decorator(function):
        def wrap_function(*args, **kwargs):
            print(f"max_repeating = {max_repeating}")
            repeat = 0
            while repeat < max_repeating:
                try:
                    return function(*args, **kwargs)
                    break
                except Exception:
                    pass
                repeat += 1
        return wrap_function
    return decorator

它工作正常,所以如果某个装饰函数失败 - 装饰器允许重复该函数直到成功或重复 >= max_repeating。

但是如果我需要从修饰函数的角度更改默认的 max_repeating 怎么办?

这里我有两个装饰函数:

@decorator_wraper(5)
def decorate_me_with_argument(max_repeating=10):
    print('decorate_me_with_argument')

@decorator_wraper()
def decorate_me_with_default():
    print('decorate_me_with_default')

calling:
decorate_me_with_argument() # max_repeating should be 5 cause while decorating the function I have passed 5.
decorate_me_with_default() # max repeating should be 20 cause default for the decorator is 20.

以及我想要达到的目标:

decorate_me_with_argument(3) # max_repeating should be 3
decorate_me_with_default(8) # max repeating should be 8

也许有什么简单的方法可以解决这样的问题?

【问题讨论】:

  • 您传递给decorate_me_with_argument 的任何参数现在都将转到wrap_function,如果包装后的function 也接受参数怎么办?
  • 我已将所有参数强制为 key-args,它对我有用。 (我的意思是big_bad_bison提出的解决方案)也许你有更好的解决方案?

标签: python python-decorators


【解决方案1】:

wrap_function 的第一个参数设为重复次数,并使用装饰器中指定的默认值max_repeats

def decorator_wraper(max_repeats=20):
    def decorator(function):
        def wrap_function(max_repeating=max_repeats, *args, **kwargs):
            print(f"max_repeating = {max_repeating}")
            repeat = 0
            while repeat < max_repeating:
                try:
                    return function(*args, **kwargs)
                except Exception:
                    pass
        return wrap_function
    return decorator


@decorator_wraper(5)
def decorate_me_with_argument():
    print('decorate_me_with_argument')


@decorator_wraper()
def decorate_me_with_default():
    print('decorate_me_with_default')


decorate_me_with_argument()  # max_repeating = 5
decorate_me_with_default()  # max_repeating = 20
decorate_me_with_argument(3)  # max_repeating = 3
decorate_me_with_default(8)  # max_repeating = 8

【讨论】:

  • 真的很喜欢这个解决方案!
【解决方案2】:

另一种方法是在wrap_function 上公开重复次数:

def decorator_wrapper(max_repeating=20):
    def decorator(function):
        def wrap_function(*args, **kwargs):
            print(f"max_repeating = {wrap_function.max_repeating}")
            for _ in range(wrap_function.max_repeating):
                try:
                    return function(*args, **kwargs)
                except Exception:
                    pass
        wrap_function.max_repeating = max_repeating
        return wrap_function
    return decorator

这样你不会改变被包裹的function的界面,但你仍然可以在初始修饰后改变重复限制:

>>> @decorator_wrapper()
... def bad_func():
...     print("in bad func")
...     raise Exception("oh no!")
...
>>> bad_func.max_repeating
20
>>> bad_func.max_repeating = 3
>>> bad_func()
max_repeating = 3
in bad func
in bad func
in bad func

将任意属性添加到wrap_function 还允许您提供内联 API 来调用具有不同重试次数的包装函数,例如类似:

bad_func.retry_times(3)()

同样bad_func.retry_times(3)bad_func 的直接替代品,因为它返回一个接受完全相同参数的函数,而不是添加到包装函数的现有参数(并有与之冲突的风险)。


请注意,如果最后一次重试失败,您可能应该提出错误,否则它将完全丢失:

def retry(times, func, *args, **kwargs):
    for _ in range(times):
        try:
            return func(*args, **kwargs)
        except Exception as exc:
            exception = exc
    raise exception

需要exception = exc,因为except-clause deletes local variable

【讨论】:

  • 哦,是的,当然 repeat += 1 应该在装饰器中。
  • @tomm 我从while 循环切换到for,所以你根本不需要repeat 变量。
  • 你在上面添加了什么真的很有趣。我只是对上面的一些问题。添加示例“bad_func.retry_times(3)()”时您是什么意思?怎么用?
  • @tomm 1. 在您当前的实现中,如果函数失败每次都会重试,包装器只是默默地返回并且无法处理错误; 2. 我的意思是,与其添加一个整数的max_repeating 属性,不如添加一个函数的retry_times 属性。这如何使用它,你调用属性来获得一个具有正确重试次数的函数,然后用任何需要的参数调用该函数。
【解决方案3】:

以下与我在项目中的类似,kwargs 参数获取从装饰函数传递的所有内容:

def decorator_wraper(func):
    def wrap_function(max_repeating=20, *args, **kwargs):
        if kwargs.get('max_repeating'):
            max_repeating = kwargs['max_repeating']
        print(max_repeating)
        # TODO
        return func(*args, **kwargs)
    return wrap_function

@decorator_wraper
def decorate_me():
    pass

decorate_me()  # should print 20
decorate_me(max_repeating=10)  # should print 10

【讨论】:

  • max_repeatingwrap_function 的第一个位置 参数,所以如果你用任何位置参数装饰一个函数,你将永远无法调用它没有设置max_repeating
猜你喜欢
  • 1970-01-01
  • 2014-11-17
  • 2021-11-15
  • 2016-02-14
  • 2018-08-24
  • 2014-10-01
  • 1970-01-01
  • 2021-08-06
  • 1970-01-01
相关资源
最近更新 更多