繁体   English   中英

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

[英]Using decorators for AssertionError exception handling in Python

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

我试图更好地理解 Python AssertionError 异常处理和装饰器,我想知道是否可以通过装饰器替换 function 中的一系列嵌套 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')

Output:

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')

Output:

# 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'))

我得到这个 output:

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

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

任何想法?

一个问题是您的装饰者事先不知道xy是什么。 这可以使用'x''y'来排序,而不是xy 但是,如果您也修改 function 签名,以强制 arguments 为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__的 __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}')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM