使用装饰器将所有函数包装成 "if func returned false, return false"

Use decorators to wrap all functions with "if func returned false, return false"

我正在编写一个非常基本的 Python 脚本,该脚本基于一个 main 函数,该函数顺序调用其他函数。

我想做的是将所有从 main 调用的函数包装成如下内容:

result = func(*some_args):
   if (result != True):
       return result

例如,对于此代码:

def func1(arg1 , arg2):
     if (some_cond):
        return False #Or some err_val
     return True

def func2(arg1):
     if (some_cond):
        return False
     return True

def main():
    func1(val1 , val2)
    func2(val3)
    return True

if __name__ == "__main__":
     import sys
     result = main()
     if (result == err_val1):
          # Do something. Maybe print. Maybe call some function.
          sys.exit(1)

我希望如果其中一个函数失败 main 会中断并且 return 它会出错。我可以使用装饰器来做到这一点吗?

我想,您真正想要的是一个通用的异常捕获器,它可以捕获并 return 任何包装函数的异常。你可以很容易地这样做。

def return_exception(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            return e
    return wrapper

例子

In [3]: @return_exception
   ...: def div(a, b):
   ...:     return a / b
   ...: 

In [4]: div(1, 0)
Out[4]: ZeroDivisionError('division by zero')

这样您就可以按照您想要的方式处理 return 异常对象,尽管很难说出您为什么需要它。

Update 正如其他人指出的那样,通常只捕获特定的异常是好的。您可以稍微修改装饰器。

def return_exception(*exception_types):
    def build_wrapper(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except exception_types as e:
                return e
        return wrapper
    return build_wrapper

示例:

In [6]: @return_exception(ZeroDivisionError)
   ...: def div(a, b):
   ...:     return a / b
   ...: 

In [7]: div(0, 1)
Out[7]: 0.0

In [8]: div(1, 0)
Out[8]: ZeroDivisionError('division by zero')

In [9]: div(1, "a")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
...
TypeError: unsupported operand type(s) for /: 'int' and 'str'

In [10]: @return_exception(ZeroDivisionError, TypeError)
   ....: def div(a, b):
   ....:     return a / b
   ....: 

In [11]: div(1, 0)
Out[11]: ZeroDivisionError('division by zero')

In [12]: div(1, "a")
Out[12]: TypeError("unsupported operand type(s) for /: 'int' and 'str'")

如您所见,您只捕获指定的异常(不过您仍然可以指定通用 Exception class)。

我认为最好的解决方案是使用异常。如果这绝对不是你想要的,你可以做一些短路:

return func1() and func2()

要将其扩展到更多功能而无需大量 ands:

from functools import partial

def main():
    funcs = (partial(func1, arg1, arg2),
             partial(func2, arg1))

    if any(!f() for f in funcs):
        return False

虽然这不是 return "its error"(函数失败的错误),但它只是 returns False。如果您想更多地区分不同类型的错误...好吧,您又回到了异常。

这正是 Python.

中构建异常的原因
# imports belong at the *top* of the file
import sys


class SomeDescriptiveError(Exception): pass
class SomeOtherSpecialError(Exception): pass


def func1(arg1 , arg2):
     if (some_cond):
        raise SomeDescriptiveError('Cannot frobnosticate the fizzbuzz')
     return arg1 + arg2
     # or skip the return statement altogether

def func2(arg1):
     if (some_cond):
        raise SomeOtherSpecialError('The frontobulator is no longer cromulent')
     return ''.join(reversed(arg1))

def main():
    print(func1(val1 , val2))
    print(func2(val3))

if __name__ == "__main__":
     try:
         result = main()
     except SomeDescriptiveError as e:
         print('Oh dear')
         sys.exit(e.args[0])
     except SomeOtherSpecialError as e:
         print('Oh no')
         sys.exit(e.args[0])
     else:
         print('All systems are fully operational')
     finally:
         print('I really should clean up all these bits.')

由于您确实实际上希望程序在发生这些错误之一时终止,您不妨提出SystemExit。这是使用装饰器实现的方法。

flag = 2

def die_on_not_True(func):
    def wrapper(*args):
        rc = func(*args)
        if rc is not True:
            fmt = 'Function {} failed with return value {!r}'
            print(fmt.format(func.__name__, rc))
            raise SystemExit(1)
        return True
    return wrapper

@die_on_not_True
def func1(arg1 , arg2):
     if arg1 == flag:
        return 'error 1' 
     return True

@die_on_not_True
def func2(arg1):
     if arg1 == flag:
        return 'error 2'
     return True

def main():
    val1, val2, val3 = 1, 2, 3
    print(func1(val1, val2))
    print('one')
    print(func2(val3))
    print('two')


if __name__ == '__main__':
    main()

输出

True
one
True
two

如果我们设置flag = 1,输出变为

Function func1 failed with return value 'error 1'

如果我们设置flag = 3,输出变为

True
one
Function func2 failed with return value 'error 2'

flag等于2时,返回0的退出状态为shell,当flag等于1或3时,返回1的退出状态。


如果您想在打印错误消息后进行进一步处理,则引发自定义异常而不是 SystemExit 并通过将 main 调用包装在 try...except 中来捕获它。