The current typing spec includes a short description of the @no_type_check
decorator here. The wording was copied directly from PEP 484. It’s short, so I’ll copy it verbatim here:
To mark portions of the program that should not be covered by type hinting, you can use the @typing.no_type_check decorator on a class or function. Functions with this decorator should be treated as having no annotations.
There are multiple issues with the current definition.
Issue 1: Applying @no_type_check
to classes
The spec indicates that @no_type_check
can be applied to a class, but it doesn’t specify what behavior is implied in this case. Should all type errors within the class definition be suppressed? Should the entire class be treated as Any
? Should any class derived from this class be treated as Any
? Should the decorator automatically apply to nested classes, methods and functions within the class?
No type checker (including mypy, which was the reference implementation for PEP 484) has ever implemented support for @no_type_check
applied to a class. It is simply ignored by all four major type checkers in this context.
Issue 2: Applying @no_type_check
to functions
Mypy (and only mypy, among the four major type checkers) does implement support for @no_type_check
when applied to a function.
The spec is ambiguous about what behavior this should entail, but it does hint at the intended behavior.
The problem is that the phrase “treated as having no annotations” has different meanings to different type checkers (and even different modes within type checkers). For example, mypy defaults to suppressing all errors in unannotated functions, but this can be overridden with the check_untyped_defs
option. The other three major type checkers report errors by default in unannotated functions.
My best guess is that the authors intended for the spec to mean:
- All type-related errors within the function should be suppressed, including any errors related to its declaration, body, or any other decorators applied to it.
- If the function’s signature includes type annotations for any of its parameter or its return type, these should be ignored and assumed to be
Any
when evaluating the type of the function symbol. This would presumably also apply to the signature of the post-decorated function if decorators were applied on top of@no_type_check
.
If that interpretation is correct, I would expect to see the following behavior:
@no_type_check
def func(a: int, b: int) -> int:
return a + b + "" # No error
func("", "") # No error
func() # Error
func(other=1) # Error
Mypy does not match the behavior. Instead, it appears to replace the entire signature with Callable[..., Any]
and allow any arguments to be passed. This seems counter to the text description in the spec, and it’s not clear to me why this behavior would be desired.
Questions for the Community
- Do any of you use
@no_type_check
in your code base? If so, why? - Are there any concerns about deprecating
@no_type_check
?
Commentary
I’d like to explore the idea of deprecating @no_type_check
.
I think the main motivation for originally adding support for @no_type_check
was to support alternative uses for the annotation syntax. The type system has evolved considerably since these early days, and Annotated
(which was added in PEP 593) is now the recommended way to handle non-type-related annotations.
Because of this, I don’t see any remaining utility for @no_type_check
. If we do identify some need for it, we should then discuss how to better define it because the current spec is too ambiguous.
Based on my experience as maintainer of pyright, here are my recommendations in order or preference:
- Deprecate
@no_type_check
. Conformant type checkers are allowed to ignore it (and/or flag it as deprecated) regardless of whether it’s applied to functions or classes. Thetyping.no_type_check
symbol is slated for removal in a future release of Python. - Deprecate
@no_type_check
for classes; type checkers are allowed to ignore it in this case. When applied to a function,@no_type_check
indicates that all type-related errors within that function are suppressed. From the perspective of a caller, all of the function’s parameters and return type are assumed to be implicitlyAny
even if they are otherwise annotated or the type checker would normally infer the return type. Non-type errors (like syntax errors or conditions that will always result in a runtime exception) can still be reported.