isinstance("my string", "str | int")
isinstance("my string", "list")
有没有办法根据存储为字符串的有效类型来检查变量的类型(在本例中为
"my string"
)?
我不需要导入类型,我知道它是内置的(int / str / float / etc)。
天真的方法是调用
eval()
,但这当然是不安全的。除了使用正则表达式/字符串拆分来提取单个类型并将它们与巨大的 if/else 块中的评估类型匹配之外,还有其他安全的方法吗?
对于内置类型,您可以在
builtins
模块中进行查找:
>>> import builtins
>>> getattr(builtins, 'str')
<class 'str'>
对于您在自己的代码中定义的类,有
globals()
字典:
>>> class Foo:
... pass
...
>>> globals()['Foo']
<class '__main__.Foo'>
请注意,这不处理联合或通用类型。
这里有一个相当复杂的方法来避免使用
eval
(直接;见下文)并使用现有的机制来评估类型提示。
def foo(x):
pass
foo.__annotations__['x'] = "str|int"
assert typing.get_type_hints(foo)['x'] == (str|int)
最终,
typing.ForwardRef
本身使用eval
将"str|int"
评估为str|int
,但要么
eval
或 typing
模块的任何未来更改。fast_eval
对于 Python 3.6+
我建议使用下面定义的
fast_eval
声明,这是可以满足上述问题的最快实现方式。
def fast_eval(val: Any, annotation: str) -> bool:
cls_name = val.__class__.__name__
if '|' in annotation:
for tp in annotation.split('|'):
if tp.strip() == cls_name:
return True
return False
return annotation.strip() == cls_name
例子:
>>> fast_eval("hey", "int | str")
True
>>> fast_eval("hey", "int | bool")
False
研究表明,它比最直接的实施快10x:
注意:以下仅适用于Python 3.10+!
isinstance("hey", eval("int | str"))
基准代码:
# Requires: Python 3.10+
import builtins
from datetime import datetime
from timeit import timeit
from typing import Any
def fast_eval(val: Any, annotation: str) -> bool:
cls_name = val.__class__.__name__
if '|' in annotation:
for tp in annotation.split('|'):
if tp.strip() == cls_name:
return True
return False
return annotation.strip() == cls_name
def eval_with_type(val: Any, annotation: str, __globals=None, t=None) -> bool:
if __globals is None:
__globals = {}
if t is None:
t = type(val)
if '|' in annotation:
for tp in annotation.split('|'):
if eval_with_type(val, tp, __globals, t):
return True
return False
annotation = annotation.strip()
try:
return t is getattr(builtins, annotation.strip())
except AttributeError:
return t is __globals.get(annotation, None)
# asserts
assert fast_eval("hey", "int | datetime | str") is eval_with_type("hey", "int | datetime | str", globals()) is True
assert fast_eval("hey", "int | datetime | bool") is eval_with_type("hey", "int | datetime | bool", globals()) is False
# timings
print('fast_eval: ', timeit('fast_eval("hey", "int | datetime | str")', globals=globals()))
print('eval_with_type: ', timeit('eval_with_type("hey", "int | datetime | str", globals())', globals=globals()))
print('eval: ', timeit('isinstance("hey", eval("int | datetime | str", globals()))', globals=globals()))
我的 Mac OS 上的结果:
fast_eval: 0.29372966598020867
eval_with_type: 1.2097240000148304
eval: 3.5093690829817206