python - 如何为特定类型添加检查
问题描述
很多时候,我发现自己的类型与 Python 中的基本类型极为相似,但它们有一些我希望类型检查器知道的特定属性。
例如,我可能有ColorValue = NewType('ColorValue', int)
where aColorValue
的范围应该在 0 到 255 之间。
然后,我希望类型检查器让我知道我是否实际上不匹配类型规范。例如,类似:
red: ColorValue = 300 # value of 300 is not compatible with type ColorValue
理想情况下,我希望能够设置这样的东西
ColorValue = NewType('ColorValue', int, check=lambda value: 0 <= value <= 255)
有没有办法让类型检查器检查特定属性?
编辑:
需要明确的是,我希望这种检查由 mypy 或 pytype 之类的类型检查器完成,并且我不希望错误仅在运行时发生。
解决方案
考虑以下名为“restrict.py”的模块
def restrict(cls, cache=[]):
cache.append(cls)
return cls
def static_check(file, restrict):
import re
cache = restrict.__defaults__[0]
with open(file) as f:
lines = f.readlines()
for cls in cache:
c = cls.__name__
for lix, l in enumerate(lines):
m = re.findall(f"{c}[^=)]*\)", l)
for m in m:
try:
print("Attempting", m)
strargs = m.split(c)[1]
cmd = f"cls{strargs}"
eval(cmd)
print(m, "in line", lix, "evaluated")
except ValueError as e:
print(m, "in line", lix,"threw",e)
和另一个名为 main.py 的模块,您要对其进行测试
from restrict import restrict, static_check
@restrict
class Restricted():
def __new__(cls, x:int=None) -> int:
if x is None:
raise ValueError("Unspecified initialization")
elif x < 0:
raise(ValueError("<0"))
elif x > 255:
raise(ValueError(">255"))
return int(x)
def output_foo(x):
Restricted(-1)
return Restricted(999)
Restricted(1)
if __name__ == "__main__":
static_check(__file__, restrict)
python main.py
从终端运行会打印你
Attempting Restricted()
Restricted() in line 5 threw Unspecified initialization
Attempting Restricted(-1)
Restricted(-1) in line 16 threw <0
Attempting Restricted(999)
Restricted(999) in line 17 threw >255
Attempting Restricted(1)
Restricted(1) in line 19 evaluated
不使用该子句保护 static_checkif __name__ == "__main__"
将允许您在导入时进行检查。
旧答案
您可以在解析时检查,例如假设您有一个restricted.py
使用以下代码命名的文件:
class Restricted():
def __new__(cls, x):
import sys
lineno = sys._getframe().f_back.f_lineno
if x < 0:
print(f"Value {x} is too low in line {lineno}")
if x > 255:
print(f"Value {x} is too high in line {lineno}")
return int(x)
def invalid_foo(x:Restricted=Restricted(300)):
return x
def valid_foo(x:Restricted=Restricted(222)):
return x
Value 300 is too high in line 13
它会在您导入模块/解析代码时打印,例如从 bashpython restricted.py
中除mypy restricted.py
.
显然,mypy 和 pytype 都没有单独打印消息,所以看起来他们实际上并没有导入模块,而是直接解析文件。可以将 bash 中的类型检查和导入融合在一起tpcheck() { mypy $1 && python $1; }
,然后您可以调用tpcheck restricted.py
两者来完成。
请注意:NewType 实际上并没有创建一个新类。正如 _doc__ 所说:“在运行时,NewType(name, tp) 返回一个简单地返回其参数的虚拟函数”。
另一种选择可能是自动单元测试生成,例如使用auger-python
. 例如,当我们将以下代码添加到前面的代码段时:
def output_foo():
return Restricted(999)
if __name__ == '__main__':
import auger
with auger.magic([Restricted]):
output_foo()
tpcheck 还向我显示了 output_foo 中的错误,即Value 999 is too high in line 22
. 请注意,我展示了螺旋钻的一个错误,我必须手动修复(参见https://github.com/laffra/auger/issues/23)。此外,mypy 抱怨缺少对螺旋钻的导入,所以我不得不重新定义tpcheck() { mypy $1 --ignore-missing-imports && python3 $1; }
.
但归根结底,这归结为调用函数,并在运行时执行它。看不出你怎么能逃避它,但至少你可以尝试尽可能地自动化它。
推荐阅读
- c# - 为什么我的 azure 函数有时无法读取静态配置变量?
- android - 意外行为改造 + 协程
- angular - 传单 GeoJSON 类型不与 GeoJSON 对象关联
- r - 按数据框中的因子列着色行 - R
- sql-server - 对于源和目标中为 FLOAT 的字段,我应该在 Informatica 中保留什么数据类型?
- tableau-api - 如何在 Tableau 中添加缺少的记录数?
- r - 以宽格式转换具有多个变量的数据,并为每个组提供标题
- html - 为什么 Bootstrap 模态接收焦点在选项卡上?
- azure-api-management - 关于带有 Apim 和 MSI 的示例 Azure 存储 Blob CRUD
- python - 将大型管道转换为蛇形