`@no_type_check` decorator

Sorry, let me try to be clearer. What I said was meant to be a response to you saying that the intent was for @no_type_check to make the type checker ignore all type annotations on the function. Even if type annotations are ignored, there are still errors (like wrong number of arguments) that the type checker can detect and report, and which I thought you and Eric were advocating should be reported, so I was explaining why I thought this would be confusing behavior.

I can (kind of) understand why @no_type_check was added back in the day, but the type system (and static type checkers) have come a long way since then. I’m not convinced that it has a place in today’s type system, at least for static type checking.

Let’s ground the discussion in actual use cases. What use cases do we think @no_type_check supports that cannot be handled through other mechanisms in the type system today?

The use cases that I’ve heard so far are:

  1. Allow the annotation syntax to be used for metadata other than type information.
  2. Provide a convenient shortcut to suppress all static type checking errors within a function (or class).
  3. Provide a way to tell runtime type checkers that they should ignore a particular function or class.

Am I missing any?

As I’ve mentioned, usage 1 is no longer needed because we have Annotated.

Usage 2 is arguably not needed because we have # type: ignore and other mechanisms to suppress type errors.

Usage 3 is not addressed by any other mechanism, so it makes sense to leave @no_type_check in place for this use case.

There’s a reasonable backward compatibility argument for usage 1 and 2 specifically for mypy users who have used @no_type_check to decorate functions in their code. I buy this argument, but it does not apply to the other three major type checkers or to the use of @no_type_check on classes.

Coming from a TypeScript background, I find it strange that a type system would allow a user to write code and then say “I don’t want this entire function or class to be type checked”. I’d never want to make that an option for my code base or for my team. That’s why I’ve historically refused to implement any support for @no_type_check in pyright. Providing more ways to disable a static type checker for large blocks of code feels to me like the wrong direction. @rchen152, for that reason, I’m -1 on your proposal to extend the semantics of @no_type_check even further than it was originally intended or is currently implemented in mypy.

My preference is for the typing spec to state that static type checkers do not need to honor @no_type_check. This allows mypy to leave in place its current behavior for backward compatibility. It also preserves the use case for runtime type checking.

No, Annotated does not solve the problem. It attaches extra information to a type, not a variable name. This is a fundamental issue that has come up a few times already. If you want to use annotations only for stuff that isn’t type checking, you would have to write Annotation[Any, ...] everywhere, which is just useless noise. My Proposal would be a massive improvement and would make me agree with you that this Usage is no longer needed. But it would potentially complicate annotation parsers quite a bit.

Which, if you have a really, really weird function (I can’t think of a practical example tbh) would mean adding # type: ignore to every line, which isn’t exactly ideal. But I agree that this is the weakest usecase.

This is incompatible with the basic vision that in python, typing will always be optional. The annotation syntax is quite useful, and stricly reserving it for type checkers is not in the spirit of the original suggestion.

The problem isn’t code bases where you have full control over everything, then you can just not call a static type checker. The worry is about libraries that for example, want to use annotations internally to do something like help with generating C bindings. If those are interfacing with type checked code, the type checker is going to complain about those functions despite nothing being wrong with them.

Sounds like a great linter rule. Completely disallow any and all usage of no_type_check within your code base. I wouldn’t even be opposed to it being a config option for type checkers to raise an error if they see no_type_check. And if you want to not allow pyright to ever allow no_type_check, it just means that I and potentially others who want to use annotations for other stuff are not going to try to support pyright at all.

Either way, the point is that the spec should cover the largest possible usecases. Some of those usecases want to completly disable interpreting annotations as type hints. @no_type_check is the way to currently tell the static type checkers that this is the case.

Note that I am also -1 on completely disabling type checking within such annotated functions. My point is more about the actual annotations. (which would mean a backwards incompatible change in mypy behavior).

2 Likes

IMO, static type checkers recognizing patterns and refusing to support them by raising an error is not a problem. It doesn’t really cause fragmentation where different type checkers interpret the same code differently, it just means that type checkers support different subsections of the spec, and users are clearly told about that. E.g. I don’t consider it a real problem that mypy hasn’t added support for PEP 695. If people at some point care enough, they can go add it themselves. And if type checker implementers have strong opinions that a specific part of the spec shouldn’t exists, they can be clear about that. But the behavior shouldn’t be ignore, it should be error.

1 Like

[Annotated] attaches extra information to a type, not a variable name .

Annotated doesn’t attach extra information to a type. It attaches (often non-type) metadata to a symbol (a variable, parameter, type alias, etc.). That metadata may augment the type of the symbol, but it doesn’t need to. It can supply other information about the symbol that is completely unrelated to its type.

This is incompatible with the basic vision that in python, typing will always be optional.

I don’t think it’s incompatible. Static type annotations are optional in Python, but that doesn’t mean that a static type checker cannot or should not be run on unannotated code. The TypeScript compiler likewise runs on Javascript code, which has no type annotations.

…I and others who want to use annotations for other stuff…

There was an active debate ten years ago about how the annotation syntax would be leveraged in Python. I’m assuming that debate is long over at this point. PEP 484 made it clear that the annotation syntax will be used for type annotations. If you want to use annotations for non-type information, PEP 593 added support for Annotated to allow for alternative use cases.

if type checker implementers have strong opinions that a specific part of the spec shouldn’t exists…

If there’s a reasonable argument for something not to exist in the spec, I’d prefer to have the debate here in this forum and try to eliminate it or at least make it optional rather than mandated.

And yet it travels with the type through type annotations, and is always attached to a type. No matter what you say, it visually and runtime-semantically clearly attaches to a type. (unless there is a proposal for the runtime version of TypeAlias to unpack the outermost Annotated? But I haven’t seen that).

And I agree with you there: @no_type_check should tell the static type checker to behave as if the code has no annotations. Whether or not that means the type checker ignores that function is a different discussion.

And it added an opt-out for code, specifically @no_type_check.

See above and the different thread, but this is honestly a different discussion: Some way to add non-typing annotation data per symbol should exists, you say it’s fullffied by Annotated, I disagree, but either way this can be fixed or not fixed independent of @no_type_check.

Ok, but IMO “it isn’t being used” and “type checkers don’t implement it” aren’t by themselves “reasonable arguments”.

Beside currently being ambiguous (which I agree should be fixed), how does the existence of this part of the spec hurt anything?

1 Like

Before reading this thread, I was not aware of the existence of no_type_check. And I don’t see myself wanting it much in the future.

But trying to think of use cases leads me to prefer Rebecca Chen’s proposal of treating the annotated object as Any (rather than Callable[..., Any]).

If I start attaching lots of attributes to a function, I might find it annoying to have to put # type: ignore on all of them.

def foo(x):
    return x


foo.a = 1  # type: ignore
foo.b = 2  # type: ignore
foo.c = 3  # type: ignore
foo.d = 4  # type: ignore
foo.e = 5  # type: ignore
foo.f = 6  # type: ignore
foo.g = 7  # type: ignore
foo.h = 8  # type: ignore

It seems mypy already supports this use case:

Taking a step back, I believe this is the range of proposals we have so far:

(1) Fully deprecate @no_type_check, and eventually remove it from typing.
(2) Without touching the runtime object, effectively deprecate @no_type_check for static type checking by changing the spec to state that type checkers are allowed to not implement it.
(3) Deprecate @no_type_check for classes, but do not deprecate it for functions. Update the spec to clarify how it should behave on functions.
(4) Do not deprecate @no_type_check. Update the spec to clarify how it should behave on functions and classes.

(1) appears to be out of the question, since runtime type checkers have a valid use for @no_type_check. I dislike (3) - it feels messy and poorly motivated to me.

I’d be okay with either (2) or (4). I honestly don’t have a strong opinion on whether @no_type_check belongs in the type system; my interest here is in simplicity and consistency.

2 Likes

Thanks for the summary, @rchen152. For completeness, let me expand your list of options to include everything we’ve discussed so far.

I agree that it’s not practical to remove @no_type_check from typing because it has potential uses for runtime type checking, so I’m going to eliminate that option.

For static type checking, I’d like to break out our options for classes and functions.

For classes, our options are:

  • C1: Indicate that static type checkers should ignore @no_type_check when applied to a class. (This is what all major static type checkers do today.)
  • C2: Indicate that static type checkers may support @no_type_check when applied to a class:
    ** C2a: If supported, a static type checker should suppress all type-related diagnostics generated within the class definition (including any nested classes or methods within its body).
    ** C2b: In addition to C2a, static type checkers that support @no_type_check on a class should treat the class as type Any.
  • C3: Indicate that static type must honor @no_type_check when applied to a class:
    ** C3a: Same as C2a but required.
    ** C3b: Same as C2b but required.

For functions, our options are:

  • F1: Indicate that static type checkers should ignore @no_type_check when applied to a function. (This is what all major static type checkers except for mypy do today.)
  • F2: Indicate that static type checkers may support @no_type_check when applied to a function:
    ** F2a: If supported, all parameter and return type annotations should be ignored and treated as though they are Any. Also, all type-related diagnostics generated within the function definition (including any nested classes or functions within its body) should be suppressed. (This is consistent with the wording in PEP 484 and the current typing spec.)
    ** F2b: In addition to F2a, the signature of the function should be treated as Callable[..., Any]. (This is consistent with mypy’s current definition.)
  • F3: Indicate that static type checkers must honor @no_type_check when applied to a function:
    ** F3a: Same as F2a but required.
    ** F3b: Same as F3b but required.

No static type checker has ever supported @no_type_check for classes, nor was the intended behavior ever detailed in PEP 484. I think this is good evidence that this functionality is not needed. For that reason, I’d strongly prefer that we adopt option C1. If you feel strongly that we should define the intended behavior, then I’d recommend C2a.

For functions, @no_type_check has only been supported by mypy. The use cases discussed in the thread above argue in favor of F2a or F3a. I haven’t heard any good justifications for F2b or F3b.

In summary, my preferences are (C1 then C2a then C3a) and (F2a then F3a).

@rchen152, let me ask you this… If we adopt F3a and require that static type checkers implement this functionality, do you feel strongly enough about this functionality that you plan to implement it in pytype in the near future?

1 Like

Sounds like a good summary. I haven’t made up my mind yet, but I note that the behavior of changing a function’s perceived signature to Callable[..., Any] can be done with a trivial user-defined decorator:

def untyped(func: Callable[..., Any]) -> Callable[..., Any]:
    return func

So I don’t feel that mypy’s changing the signature to that (instead of what it would be if the function was left unannotated and undecorated) adds much value, and I’d be okay with specifying that that is not conforming behavior. (Assuming we can come up with a rule for which decorator should come first – @untyped or @no_type_check.) This makes me currently lean towards F3a.

For classes I would lean towards C2a – don’t require it but allow it (if no type checkers chose to implment it, there’s little downside to this option).

What I would like is for classes and functions to be treated the same - e.g., C1 and F1, or C2 and F2, but not C1 and F2 - since I’m not convinced that there’s a principled reason for there to be a difference.

May I suggest that C2a and F2a seems like a reasonable option then?

@rchen152, let me ask you this… If we adopt F3a and require that static type checkers implement this functionality, do you feel strongly enough about this functionality that you plan to implement it in pytype in the near future?

The short answer is no, it would not be high on the list of spec-compliance-related changes for pytype. I suspect that this won’t surprise you, since I did say above that I am relatively indifferent to the question of whether @no_type_check belongs in the type system =)

Great, it sounds like we’re converging on an answer.

I’ll compose a draft modification to the typing spec that specifies C2a and F2a, which sounds like the sweet spot given everyone’s feedback.

Here’s a draft spec modification that attempts to capture options C2a and F2a.

For minor suggestions (typos, clarifications, etc.), please add PR comments. For more significant concerns or modifications, please post to this thread for added visibility.

I’d like to suggest a modification for the proposed spec for classes: your draft currently says that if a class is associated with @no_type_check, type checkers that support this feature should suppress any errors within the class. I think they should also ignore all annotations within the class, both instance annotations and parameter/return annotations on methods. That is more consistent with the behavior being specified for functions.

1 Like

I’m finding it difficult to weigh the merits of the various proposals because no one has articulated a compelling use case for @no_type_check on classes.

@Jelle, let me ask you a question similar to the one I asked @rchen152 above. I’m trying to get a sense for how convicted people are here. As a contributor to mypy, do you think mypy will implement this functionality for classes in the foreseeable future if its intended behavior is documented in the spec? If the answer is no, then it probably doesn’t matter how we define the behavior because none of the major type checkers will be implementing it.

Here’s another option for us to consider. Let’s call it C2c.

** C2c: Static type checkers may implement support for the @no_type_check decorator on classes, but the behavior is not defined by the typing spec at this time.

This is effectively what the spec currently says. If someone comes up with a compelling use case for it in the future, we could revisit it at that time.

No, I don’t think mypy is likely to add support for @no_type_check on classes any time soon. If it did, I would advocate for the semantics in my previous message, though.

I would be OK with your C2c proposal to leave the semantics of @no_type_check on classes unspecified.

1 Like

I vote for C2c. I don’t see a strong reason why we should disallow type checkers from implementing this feature if their users ask for it, but I think it’s a sufficiently “fringe” part of the typing module that it’s fine to leave it unspecified (for now, at least) as to exactly how it should be implemented, for those type checkers that choose to do so

I’ve updated the draft spec update to conform with the C2c proposal.

1 Like

I’m unhappy with C2c. There are two possible futures. Either no type checker ever implements it for classes, or at least one implements it. In the latter case, IMO it should receive more guidance from the spec, so that if two type checkers independently implement it at least they will agree.

I would think that the formulation used for functions can be easily adapted for classes:

If a type checker supports the no_type_check decorator for classes, it should suppress all type errors for the class statement and its body including any nested functions or classes. Nested functions and classes should be treated as if each of them was decorated with @no_type_check.

(Honestly the latter sentence might also be usefully added to the paragraph about functions.)


EDIT: It looks like there’s little appetite to reconsider this, so I’m withdrawing it.

1 Like

I know you said you’re withdrawing it, but I think there’s something there about consistency between type checkers worth considering. C2c leaves this in limbo for library authors where it’s effectively dead if they intend to allow their users to support multiple type checkers. Outright saying it’s disallowed on classes is effectively the same, and doesn’t change anything for any current type checker, but prevents typing users being surprised by this.

Your formulation goes the other way of specifying it, but either way seems better than “well something in typing may not work across type checkers” to me.