PEP 484 currently says:
A
# type: ignorecomment on a line by itself disables all type checking for the rest of the file.
I think that's excessive. It should probably be limited to the current indented block, e.g.
if blargh():
# type: ignore
1 + 'a' # No error here
2 + 'b' # Should be an error
I agree, while placing #type: ignore at top indentation level would prohibit type checking for the rest of the file.
Agreed.
Refining the extent to the enclosing scope (module/function/class) makes sense, but any narrower might be tricky.
Is it meaningful to infer types with a # type: ignore block?
What about
if cond:
# type: ignore
1 + 'a' # No error here
else:
1 + 'a' #Is this an error?
I think that in Mark's example the second 1 + 'a' (in the else block) should not be an error. That seems easy.
The trickier bit is whether assignments still get inferred:
if cond:
# type: ignore
x = 42
else:
x = 'z'
What's the type of x here? Union[int, str]? If this would work the same as just adding an inline # type: ignore to each line of the block, that's what it would mean, and that seems a fine rule.
if-else may be relatively easy but what about try-except-finally?
What is the motivation for this?
I can see why I might want the checker to ignore a whole function. I struggle to think of a case where I might want it to ignore just a part of a function.
I would definitely like to ignore a conditional block full of platform-specific imports. (E.g. if I have no stubs for them.)
I wouldn't want to have a syntax where a # type: ignore placed anywhere in a function had the effect of ignoring the entire function body.
The motivation is to define a syntax that is easy to understand and syntactically orthogonal. Given that we have at least two use cases (ignore a while function, ignore a branch of a conditional), the best approach seems to be to define the scope of # type: ignore as the remainder of the current "block" as determined by indentation. A block is a well-understood syntactic construct in Python, and used in many places. So whenever you see a block containing # type: ignore you will be able to understand the scope of that directive.
I'm not sure if it's abusing it a bit, but what about making no_type_check also a context manager? In that way you can write:
with no_type_check():
'a' + 1
Here the block scope is very obvious to a reader (because context managers are conceptually tied to a block). I think this eliminates all ambiguity about else branches or try/except being covered or not. OTOH it adds an indentation level, and it's mixing a bit with a run-time concept like context managers with something that is very not run-time.
# type: ignore is nice in that it has no runtime overhead. It can be important in a tight loop or in a function that gets called a lot.
I'd stay away from a context manager -- people expect runtime behavior from
those.