在 Python 中使用装饰器进行 AssertionError 异常处理

Posted

技术标签:

【中文标题】在 Python 中使用装饰器进行 AssertionError 异常处理【英文标题】:Using decorators for AssertionError exception handling in Python 【发布时间】:2022-01-05 05:23:11 【问题描述】:

这是我在这里的第一个问题,请告诉我问得是否正确。 :x

我正在尝试更好地理解 Python AssertionError 异常处理和装饰器,我想知道是否可以用装饰器替换函数内的一系列嵌套 try/except/else 块……

示例:我通常会做什么:

# CHECK functions:
def is_int(x):
    assert isinstance(x,int), f"'x' is not an integer"
    return True

def is_str(y):
    assert isinstance(y,str), f"'y' is not a string"
    return True


# FUNCTION I want to decorate 
def pretty_function(x:int, y:str):
    try:
        is_int(x) 
    except AssertionError as exc:
        print(exc)
    else:
        try:
            is_str(y) 
        except AssertionError as exc:
            print(exc)
        else:
            print(f'There is/are x y')

输入:

pretty_function(2,'cat')
pretty_function(2,3)
pretty_function('2','cat')

输出:

There is/are 2 cat
'3' is not a string
'2' is not an integer

所以这很好用,但我想改用装饰器...尤其是当我有超过 2 个嵌套的 try/except/else 块时。

我想做的事:

# DECORATOR ---> Don't know how to make it work as I want
def test_assertion(test_function):
    def wrapper(*args, **kwargs):
        try:
            test_function(*args, **kwargs)
        except AssertionError as exc:
            print(exc)
        else:
            return test_function(*args, **kwargs)
    return wrapper


# CHECK functions --> No idea how to write these correctly
@test_assertion
def is_int(func):
    def wrapper(x):
        assert isinstance(x,int), f"'x' is not an integer"
        return True
    return wrapper

@test_assertion
def is_str(func):
    def wrapper(y):
        assert isinstance(y,str), f"'y' is not a string"
        return True
    return wrapper


# FUNCTION I want to decorate 
@is_int(x)
@is_str(y)
def pretty_function(x:int, y:str):
    print(f'There is/are x y')

输入:

pretty_function(2,'cat')
pretty_function(2,3)
pretty_function('2','cat')

输出:

# What I get so far:
    ---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
    /var/folders/r9/nj5lw_gj0fvfp4bsmcy587ch0000gn/T/ipykernel_2388/1180576500.py in <module>
     28 
     29 # FUNCTION I want to decorate
---> 30 @is_int(x)
     31 @is_str(y)
     32 def pretty_function(x:int, y:str):
    
NameError: name 'x' is not defined

所以我的实验代码不起作用...... :'( 然而,第一个装饰器似乎工作: 如果我只是输入:

# DECORATOR 
def test_assertion(test_function):
    def wrapper(*args, **kwargs):
        try:
            test_function(*args, **kwargs)
        except AssertionError as exc:
            print(exc)
        else:
            return test_function(*args, **kwargs)
    return wrapper


# CHECK functions 
@test_assertion
def is_int(x):
    assert isinstance(x,int), f"'x' is not an integer"
    return True

@test_assertion
def is_str(y):
    assert isinstance(y,str), f"'y' is not a string"
    return True

输入:

print(is_int(2))
print(is_int('2'))
print(is_str(2))
print(is_str('2'))

我得到这个输出:

True
'2' is not an integer
None
'2' is not a string
None
True

问题是,到目前为止,我只发现了 else 语句不存在的情况......以及对于初学者来说太复杂而无法理解的情况。 :/

有什么想法吗?

【问题讨论】:

不可能是简单的 1/@is_int(x) 如果 x 未定义 2/ 即使它是 1,则不是有效语句,或者您接受 @is_int('x') 之类的内容,您必须提取名称函数定义中的函数参数 【参考方案1】:

一个问题是你的装饰者事先不知道xy 是什么。这可以使用'x''y' 来排序,而不是xy。但是,如果您也修改函数签名,将参数强制为keyword only,将会简单得多。 一种方法是:

def assert_type(kwd, _type):
    def decorator(func):
        def wrapper(**kwargs):
            assert isinstance(kwargs[kwd], _type), f"kwargs[kwd] not a _type.__name__"
            return func(**kwargs)
        return wrapper
    return decorator

@assert_type('x', int)
@assert_type('y', str)
def pretty_function(*, x:int, y:str):
    print(f'There is/are x y')

更一般的情况是:

def assert_types(**mapping):
    def decorator(func):
        def wrapper(**kwargs):
            for kwd, _type in mapping.items():
                assert isinstance(kwargs[kwd], _type), f"kwargs[kwd] not a _type.__name__"
            return func(**kwargs)
        return wrapper
    return decorator

@assert_types(x=int, y=str)
def pretty_function(*, x:int, y:str):
    print(f'There is/are x y')

然后你调用:

pretty_function(x=2,y='cat')
pretty_function(x=2,y=3)
pretty_function(x='2',y='cat')

要获得所需的程序行为(您仍然必须使用'x''y'),您可以使用函数的__annotations__ 属性来获取args 的名称。

from functools import wraps
def is_int(arg):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            params = (*args, *kwargs.values())
            mapping = dict(zip(func.__annotations__, params))
            assert isinstance(mapping[arg], int), f"mapping[arg] not an integer"
            return func(*args, **kwargs)
        return wrapper
    return decorator

def is_str(arg):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            params = (*args, *kwargs.values())
            mapping = dict(zip(func.__annotations__, params))
            assert isinstance(mapping[arg], str), f"mapping[arg] not a string"
            return func(*args, **kwargs)
        return wrapper
    return decorator

@is_int('x')
@is_str('y')
def pretty_function(x:int, y:str):
    print(f'There is/are x y')

如果你准备使用inspect.signature,那么你也可以使用以下:

from functools import wraps
from inspect import signature

def assert_types(**type_mapping):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            params = signature(func).bind(*args, **kwargs).arguments
            for argname, val in params.items():
                _type = type_mapping[argname]
                assert isinstance(val, _type), f"val!r is not _type.__name__"
            return func(**kwargs)
        return wrapper
    return decorator

@assert_types(x=int, y=str)
def pretty_function(x:int, y:str):
    print(f'There is/are x y')

【讨论】:

以上是关于在 Python 中使用装饰器进行 AssertionError 异常处理的主要内容,如果未能解决你的问题,请参考以下文章

python 通过在Python中使用装饰器进行尾递归优化

python 通过在Python中使用装饰器进行尾递归优化

python 通过在Python中使用装饰器进行尾递归优化

python 通过在Python中使用装饰器进行尾递归优化

python函数装饰器

如何在 Python 中对装饰器进行分组