python - 为什么我的 Python 装饰器有时是“str”类型的?
问题描述
背景:
我正在编写一个装饰器函数,val_limiter
它限制子函数传递的参数的值。为了彻底(以及将来可能使用),我想让我的函数能够接受数字字符串,同时能够适当地处理非数字字符串。因此,例如,看下面的代码:
@val_limiter('5')
def test(val)
return val
test(3)
运行的返回值为3
。该函数成功转换'5'
为5
. 当我尝试执行以下操作时会出现问题:
@val_limiter('foo')
def test(val)
return val
这引发TypeError: 'str' object is not callable
了@val_limiter('foo')
. 我想处理类似的异常test(3)
,它会返回一条错误消息(即:'The argument must be a number (you tried 'foo')
问题:
当我跑步时type(val_limiter('5'))
,我得到function
,但当我跑步时type(val_limiter('foo'))
,我得到str
。为什么会这样?处理此异常的最佳方法是什么?
源代码:
import operator
from functools import wraps
def val_limiter(val=0, limit=max, equal=True, force=False):
val_types = [int, float, str]
if type(val) not in val_types:
val = int(val)
if type(val) == str:
try:
val = int(val)
except ValueError:
return f'First argument must be a number (tried {val})'
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
ops = {
"<": operator.lt,
">": operator.gt
}
for i in args:
i = int(i)
modifier = 0
op = '<'
try:
eq_string = 'is'
if not equal:
eq_string = 'cannot exceed'
modifier = 1 if limit == min else -1
if limit == min: op = '>'
if ops[op](val + modifier, i):
if force:
return val + modifier
raise ValueError
except ValueError:
return f'The {limit.__name__}imum value accepted {eq_string} {val} (tried {i})'
return fn(int(*args), **kwargs)
return wrapper
return decorator
*编辑:这是错误的完整追溯:
Traceback (most recent call last):
File "/home/ec2-user/environment/playground/test.py", line 79, in test_bb_arg_1_is_str_fail
@val_limiter('foo')
TypeError: 'str' object is not callable
*编辑:我目前正在通过一个测试文件运行它。这是失败测试的运行摘录:
import unittest
class TestValLimiter(unittest.TestCase):
def test_bb_arg_1_is_str_fail(self):
@val_limiter('foo')
def test(val):
return val
self.assertEqual(test(3), 'First argument must be a number (tried foo)')
if __name__ == '__main__':
unittest.main()
解决方案
问题在这里:
if type(val) == str:
try:
val = int(val)
except ValueError:
return f'First argument must be a number (tried {val})'
当您通过'foo'
时,它(正确)被检测为 a str
,并尝试将其转换为int
. 但'foo'
不能解析为int
,所以它返回错误信息。
装饰器只是语法糖:
@val_limiter('foo')
def test(val)
return val
大致相当于:
def test(val)
return val
test = val_limiter('foo')(test)
因此,对于这种'foo'
情况,最后的等效行变为:
test = 'First argument must be a number (tried foo)'(test)
它试图“调用” a str
,就好像它是一个函数一样。
主要的收获是当你不能对它们做任何有用的事情时,你不应该捕获异常。许多入门的编程课程都教如何捕获异常,但不教什么时候这样做(实际上,教你如何做的练习经常做错了,将异常转换为print
s 或return
s 没有意义,除非调用者显式检查返回的类型和值,这是应该避免的异常)。
在这种情况下,您可能需要更有用的错误消息,因此如果您使用更好的错误消息引发新异常(您正在发出更友好的消息,但仍有一个错误您没有上下文来处理),而不是默默地返回垃圾(返回除了函数之外的任何东西都是返回垃圾)。所以在这种情况下,你可能会改变:
return f'First argument must be a number (tried {val})'
至:
raise ValueError(f'First argument must be a number (tried {val!r})')
这会引发一个不能意外用作函数的异常(使用非错误返回的方式)。
推荐阅读
- r - 在 R 中可视化几列的正确方法是什么?
- react-native - React Native Navigation 导航未从 useEffect 触发
- python - 根据前两个字母删除分类变量
- mysql - 使用多语言站点(polylang)搜索和替换查询
- linux - Flutter linux build error A required package was not found
- java - Quartz Scheduler + Spring:创建触发器时如何从JobStore获取初始cron表达式?
- javascript - 不能让测试失败?选择器可能是错误的
- c++ - Opencv.exe 中 0x00007FFFB9423B29 处未处理的异常:Microsoft C++ 异常:内存位置的 cv::Exception
- android - Android上的Mapbox:地图移动时取消onMapLongClick或函数
- java - selenium 和 java 无法识别电子弹出窗口