带有可选参数(即函数)的 Python 装饰器

标签 python python-2.7 decorator python-decorators

注意:我知道带有可选参数的装饰器包含三个嵌套函数。但这里的可选参数是函数本身。在将其标记为重复之前,请仔细阅读完整的帖子。我已经尝试了带有可选参数的装饰器的所有技巧,但我找不到任何以 function 作为参数的装饰器。

我有一个用于包装错误的装饰器:

def wrap_error(func):
    from functools import wraps

    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            import sys

            exc_msg = traceback.format_exception(*sys.exc_info())
            raise MyCustomError(exc_msg)

    return wrapper

如果某个函数引发任何异常,它会包装该错误。这个包装器的用法如下:

@wrap_error
def foo():
    ...

现在我想用额外的回调函数来修改这个包装器,这是可选的。我希望这个包装器用作:

@wrap_error
def foo():
    ...

@wrap_error(callback)
def foo():
    ...

我知道如何编写带有可选参数的装饰器(如果传递的参数不是函数,基于包装器中的 isfunction(func) 检查)。但我不知道如何处理这个案子。

注意:我不能使用@wrap_error()来代替@wrap_error。该包装器在多个包中使用,并且不可能更新所有的更改

这是拦截器: 将包装器视为:

@wrap_error(callback)               --->       foo = wrap_error(callback)(foo)
def foo():
    ...

因此,当执行 wrap_error(foo) 时,我们不知道此后是否会有回调函数执行(如果我们只使用 @wrap_error 而不是 @wrap_error(callback))。

如果没有(callback),wrap_error中的包装函数将返回func(*args.**kwargs),以便我可以引发异常。否则我们必须返回 func 以便在下一步调用它,如果 func() 引发异常,我们调用callback()在 except block 中。

最佳答案

由于很难区分 decorator(func)decorator(callback),因此创建两个装饰器:

from functools import wraps

class MyCustomError(Exception):
    def __init__(self):
        print('in MyCustomError')

# Common implementation
def wrap(func,cb=None):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            if cb is not None:
                cb()
            raise MyCustomError()
    return wrapper

# No parameters version
def wrap_error(func):
    return wrap(func)

# callback parameter version
def wrap_error_cb(cb):
    def deco(func):
        return wrap(func,cb)
    return deco

@wrap_error
def foo(a,b):
    print('in foo',a,b)
    raise Exception('foo exception')

def callback():
    print('in callback')

@wrap_error_cb(callback)
def bar(a):
    print('in bar',a)
    raise Exception('bar exception')

使用 functools.wraps 检查 foo 和 bar 是否正确:

>>> foo
<function foo at 0x0000000003F00400>
>>> bar
<function bar at 0x0000000003F00598>

检查包装的函数是否工作:

>>> foo(1,2)
in foo 1 2
in MyCustomError
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "C:\test.py", line 16, in wrapper
    raise MyCustomError()
MyCustomError
>>> bar(3)
in bar 3
in callback
in MyCustomError
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "C:\test.py", line 16, in wrapper
    raise MyCustomError()
MyCustomError

已更新

这是一种使用您请求的语法来完成此操作的方法,但我认为上面的答案更清楚。

from functools import wraps

class MyCustomError(Exception):
    def __init__(self):
        print('in MyCustomError')

# Common implementation
def wrap(func,cb=None):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            if cb is not None:
                cb()
            raise MyCustomError()
    return wrapper

def wrap_error(func_or_cb):
    # If the function is tagged as a wrap_error_callback
    # return a decorator that returns the wrapped function
    # with a callback.
    if hasattr(func_or_cb,'cb'):
        def deco(func):
            return wrap(func,func_or_cb)
        return deco
    # Otherwise, return a wrapped function without a callback.
    return wrap(func_or_cb)

# decorator to tag callbacks so wrap_error can distinguish them
# from *regular* functions.
def wrap_error_callback(func):
    func.cb = True
    return func

### Examples of use

@wrap_error
def foo(a,b):
    print('in foo',a,b)
    raise Exception('foo exception')

@wrap_error_callback
def callback():
    print('in callback')

@wrap_error(callback)
def bar(a):
    print('in bar',a)
    raise Exception('bar exception')

关于带有可选参数(即函数)的 Python 装饰器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27167321/

相关文章:

python - PyCharm 错误,ModuleNotFoundError : No module named 'PyQt5'

Python:如何将 float 转换为没有 `.0` 的字符串?

python - 使用 Mechanize (Python) 输入到站点

python-2.7 - 在pygame中创建一个窗口而不显示它

python - 带参数的装饰器?

python - 在字符串的 pandas 数据框中查找值计数

python-2.7 - 如何使用 Python 下载特定的 Google Drive 文件夹?

python - 使用 Python 从 PNG 或 JPG 创建一个在 XP 上运行的 ICO

javascript - 什么是 "decorators"以及它们是如何使用的?

python - 装饰器给 Python 函数调用增加了多少开销