如何为特定类型添加支票

时间:2019-06-19 21:00:47

标签: python python-3.x typing

通常我会发现自己的类型与Python中的基本类型极为相似,但是它们具有某些我希望类型检查器知道的属性。

例如,我可能有ColorValue = NewType('ColorValue', int),其中ColorValue的范围应在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等类型检查器完成此检查,并且我不希望该错误仅在运行时发生。

1 个答案:

答案 0 :(得分:0)

考虑以下名为“ 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

不使用if __name__ == "__main__"子句保护static_check会允许您在导入时进行检查。

旧答案

您可以在解析时检查,例如假设您有一个名为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,例如除python restricted.py外,还使用mypy restricted.py从bash中进行。

显然,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; }

但是最后,归结为调用该函数并在运行时执行它。无法看到如何避免这种情况,但是至少您可以尝试尽可能自动地做到这一点。