关于python:使用装饰器用“if func return false,return false”包装所有函数

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

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

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

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

例如,对于此代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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将中断并返回其错误。我可以用装饰工来做这个吗?


这正是在Python中构建异常的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 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.')


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

1
return func1() and func2()

要将此扩展到更多功能,而不需要大量的EDOCX1[1]s:

1
2
3
4
5
6
7
8
from functools import partial

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

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

虽然这不会返回"its error"(失败函数的错误),但它只返回False。如果您想在不同类型的错误之间进行更多的区分…嗯,你又回到了例外。


既然您确实希望程序在其中一个错误发生时死掉,那么您也可以提高SystemExit。这里有一个方法来做装饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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()

输出

1
2
3
4
True
one
True
two

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

1
Function func1 failed with return value 'error 1'

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

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

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

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


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

1
2
3
4
5
6
7
def return_exception(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            return e
    return wrapper

例子

1
2
3
4
5
6
7
In [3]: @return_exception
   ...: def div(a, b):
   ...:     return a / b
   ...:

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

因此,您可以按照您想要的方式处理返回异常对象,尽管很难说为什么需要它。

正如其他人所指出的,更新通常只捕获特定的异常。您可以稍微修改装饰器。

1
2
3
4
5
6
7
8
9
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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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类)。