Рассмотрим следующий модуль с именем "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_check с помощью условия 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 с python 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; }
.
.Но, в конце концов, это сводится к вызову функции и ее выполнению во время выполнения. Не могу понять, как вы могли бы избежать этого, но, по крайней мере, вы могли бы попытаться максимально автоматизировать это.