在 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】:
一个问题是你的装饰者事先不知道x
和y
是什么。这可以使用'x'
和'y'
来排序,而不是x
和y
。但是,如果您也修改函数签名,将参数强制为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 异常处理的主要内容,如果未能解决你的问题,请参考以下文章