如何为特定类型添加检查

2024-04-24 09:58:46 发布

您现在位置:Python中文网/ 问答频道 /正文

我经常发现自己的类型与Python中的基类型极其相似,但它们有一些特定的属性,我希望typechecker知道这些属性。你知道吗

例如,我可能有ColorValue = NewType('ColorValue', int),其中ColorValue的范围应该在0到255之间。你知道吗

然后,我想让typechecker告诉我是否与类型规范不匹配。例如,类似于:

red: ColorValue = 300 # value of 300 is not compatible with type ColorValue

理想的情况下,我希望能够设置这样的东西了

ColorValue = NewType('ColorValue', int, check=lambda value: 0 <= value <= 255)

有没有办法让类型检查器检查特定的属性?你知道吗

编辑:

为了清楚起见,我希望这个检查由mypy或pytype这样的类型检查器完成,并且我不希望错误只在运行时发生。


Tags: of规范类型属性isvaluetypewith
1条回答
网友
1楼 · 发布于 2024-04-24 09:58:46

考虑以下模块,称为“限制.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)

另一个模块叫做主.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

不使用if __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,例如从bash中,除了mypy restricted.py之外,还打印python restricted.py。你知道吗

显然,mypy和pytype都没有单独打印消息,因此看起来它们实际上并不导入模块,而是直接解析文件。可以将bash中的类型检查和导入与tpcheck() { mypy $1 && python $1; }融合,然后可以调用tpcheck restricted.py来完成这两项工作。你知道吗

只需注意:NewType实际上并不创建新类。正如文档所说:“在运行时,NewType(name,tp)返回一个伪函数,这个伪函数只返回它的参数”。你知道吗

另一种选择是自动生成单元测试,例如使用auger-python。例如,当我们将以下代码添加到前面的代码段时:

def output_foo():
    return Restricted(999)

if __name__ == '__main__':
    import auger

    with auger.magic([Restricted]):
        output_foo()

tpcheck还向我显示了输出foo中的错误,即Value 999 is too high in line 22。请注意,我展示了一个关于auger的bug,我必须手动修复它(参见https://github.com/laffra/auger/issues/23)。另外,mypy抱怨缺少auger的导入,所以我不得不重新定义tpcheck() { mypy $1 ignore-missing-imports && python3 $1; }。你知道吗

但最终,这归结为调用函数,并在运行时执行它。我看不出你怎么能逃避,但至少你可以尝试尽可能地自动化。你知道吗

相关问题 更多 >