@typing.no_type_check behavior for class variables
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
assignee = None closed_at = <Date 2022-02-19.01:54:58.041> created_at = <Date 2022-01-29.14:12:02.478> labels = ['type-bug', 'library', '3.9', '3.10', '3.11'] title = 'Strange `@typing.no_type_check` behavior for class variables' updated_at = <Date 2022-02-19.01:54:58.040> user = 'https://github.com/sobolevn'
activity = <Date 2022-02-19.01:54:58.040> actor = 'JelleZijlstra' assignee = 'none' closed = True closed_date = <Date 2022-02-19.01:54:58.041> closer = 'JelleZijlstra' components = ['Library (Lib)'] creation = <Date 2022-01-29.14:12:02.478> creator = 'sobolevn' dependencies =  files =  hgrepos =  issue_num = 46571 keywords = ['patch'] message_count = 14.0 messages = ['412073', '412074', '412079', '412083', '412130', '412151', '412153', '412158', '412159', '412160', '412162', '412178', '413523', '413525'] nosy_count = 5.0 nosy_names = ['gvanrossum', 'JelleZijlstra', 'sobolevn', 'kj', 'AlexWaygood'] pr_nums = ['31042'] priority = 'normal' resolution = 'fixed' stage = 'resolved' status = 'closed' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue46571' versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']
The text was updated successfully, but these errors were encountered:
I was working on improving coverage and test quality of
Let's dive into this!
## Everything is correct
We will start with a basic example, that illustrates that everything works fine:
Ok, let's move on.
|"""Decorator to indicate that annotations are not type hints.|
|The argument must be a class or function; if it is a class, it|
|applies recursively to all methods and classes defined in that class|
|(but not to methods defined in its superclasses or subclasses).|
|This mutates the function(s) or class(es) in place.|
|if isinstance(arg, type):|
|arg_attrs = arg.__dict__.copy()|
|for attr, val in arg.__dict__.items():|
|if val in arg.__bases__ + (arg,):|
|for obj in arg_attrs.values():|
|if isinstance(obj, types.FunctionType):|
|obj.__no_type_check__ = True|
|if isinstance(obj, type):|
|arg.__no_type_check__ = True|
|except TypeError: # built-in classes|
As you can see above, we traverse all
__dict__ values of the given
class and for some reason recurse into all nested types.
I think that the original goal was to handle cases like:
@no_type_check class Outer: class Inner: ...
And now it also affects regular assignments.
So, what can we do?
- Nothing, it works correctly (I disagree)
- Do not cover nested classes at all with
@no_type_check, only cover methods
- Only cover types that are defined in this class, like my
- Something else?
I think that
(2) is more inline with the currect implementation, so my vote is for it.
I would like to implement this when we will have the final agreement :)
...Option 3). Deprecate @no_type_check?
Maybe we should gather some stats on how many people are using @no_type_check? My feeling is that it's never achieved widespread adoption, so maybe it's just not that useful a thing to have in the stdlib.
Anyway, the behaviour you've pointed out is nuts, so I agree that something needs to change here!
Let's not jump into deprecating it.
I think (2) makes the most sense. If we can get that working reliably (using __qualname__ I assume), it would be my preference; otherwise we should do (1).
This problem can also affect function objects, right?
@no_type_check (introduced by PEP-484) is intended for static checkers, to signal to them that they shouldn't check the given function or class.
I don't think PEP-484 specifies its runtime effect -- arguably get_type_hints() could just ignore it. Or raise an exception when called on such a class. We could even argue that @no_type_check shouldn't have a runtime effect.
But before we change anything we should be guided by:
## 1. What is documented?
The docs makes this even more weird!
Docs do not mention modifing nested classes at all! So, it looks like the
## 2. What does it do now?
It modifies nested types, even ones used in assignments.
## 3. How is that used by runtime type inspectors?
I've made a little research:
So, it always tries to coerce types. Docs: https://pydantic-docs.helpmanual.io/usage/types/
They also use it inside their own code-base: https://github.com/samuelcolvin/pydantic/search?q=no_type_check Probably for
So, as far as I understand, they only have
So, to conclude, some project might still rely on current behavior that nested types are also implicitly marked as
The noticable thing is that this never came up before in ~6 years while this logic exists: https://github.com/python/cpython/blame/8fb36494501aad5b0c1d34311c9743c60bb9926c/Lib/typing.py#L1969-L1970
It helps to prove my point: probably, no one uses it.
## 3. How is that used by runtime type inspectors?
With all the information I gathered, I've changed my opinion :)
This is what docs say. This will also solve the original problem.
So, do others have any objections / comments / feedback? :)