You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[DOC] typing.get_type_hints() raises for type aliases with forward references #89089
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
GitHub fields:
assignee=Noneclosed_at=<Date2021-10-18.07:55:48.195>created_at=<Date2021-08-16.17:15:16.657>labels= ['3.11', 'type-bug', 'library', '3.10', 'docs']
title='[DOC] typing.get_type_hints() raises for type aliases with forward references'updated_at=<Date2021-10-18.07:55:48.191>user='https://github.com/mhils'
Someone reported this rather interesting issue where typing.get_type_hints crashes on type aliases with forward references. The original report is at mitmproxy/pdoc#290. Here's an extended minimal example:
import typing
import foo
def func1(x: foo.FooList1):
pass
def func2(x: foo.FooList2):
pass
print(typing.get_type_hints(func1)) # {'x': list['Foo']}
print(typing.get_type_hints(func2)) # NameError: name 'Foo' is not defined.
Observations:
func1 doesn't crash, but also doesn't resolve the forward reference. I am not sure if this expected behavior.
If it isn't, this should eventually run in the same problem as func2.
func2 crashes because "Foo" is evaluated in the context of bar.py (where class Foo is unknown) and not in the context of foo.py. ForwardRef._evaluate would
somehow need to know in which context it was defined. bpo-41249 (TypedDict inheritance doesn't work with get_type_hints) introduced
ForwardRef.__forward_module__, which would be a logical place for that information. I'm not sure if it is a good idea
to use __forward_module__ more widely.
This may end up as quite a bit of complexity for an edge case, I'm fine if it is considered wontfix.
The reason I'm bringing it up is that PEP-613 (Explicit Type Aliases) decidedly allows forward references in type aliases.
For the record, PEP-563 (postponed evaluations) does not change the outcome here. However, postponed evaluations often make it possible to avoid the forward references by declaring the aliases last.
func1 doesn't crash, but also doesn't resolve the forward reference. I am not sure if this expected behavior.
Good observation! That's indeed the current behavior. The reason is a little subtle - list[...] is using a special builtin type (https://docs.python.org/3/library/stdtypes.html#types-genericalias), while typing.List is the one from typing. The typing version wraps all strings in ForwardRef(), while the builtin version shouldn't import from typing, so it doesn't have that luxury. get_type_hints could probably support the builtin version, though I'd imagine we'd need some rework.
Re: 2. You're right there too. Storing the defined module in __forward_module__ is an interesting proposal.
For your specific use case (where the user is using Python 3.6), you could pass in globalns and localns to get_type_hints as a temporary workaround. Off the top of my head:
get_type_hints(func2, globalns=foo.__dict__) might work. Would that work for your library?
mhils commented on Aug 16, 2021
mhilsmannequin
MannequinAuthor
More actions
For your specific use case (where the user is using Python 3.6), you could pass in globalns and localns to get_type_hints as a temporary workaround. Off the top of my head:
get_type_hints(func2, globalns=foo.__dict__) might work. Would that work for your library?
I guess the hard part is knowing that the type annotation comes from foo. In the example here we can of course hardcode it, but that doesn't work in the general case or for pdoc, the documentation generator I'm working on (https://pdoc.dev). I have experimented quite a bit with walking the AST to figure out where type aliases are imported from to then re-executing ForwardRefs with that globalns. Long story short, trying to reverse-engineer forward_module quickly becomes a tangled hot mess where you need to adjust for import aliases, reimports, and so on.
changed the title [-]typing.get_type_hints() raises for type aliases with forward references[/-][+][DOC] typing.get_type_hints() raises for type aliases with forward references[/+]on Oct 18, 2021
changed the title [-]typing.get_type_hints() raises for type aliases with forward references[/-][+][DOC] typing.get_type_hints() raises for type aliases with forward references[/+]on Oct 18, 2021
I thought of a radical idea for how to fix this: have string literals track the context in which they are defined. So, to use @mhils's example, get_type_hints() would know that func1()'s 'x' argument is a foo.Data and not a bar.Data because the string literal which is stored in foo.FooData knows that it was defined in the foo module at the module level.
Now, the obvious issues are that this would require adding low-level support for a very specific use-case to a very fundamental part of the language, and the potential for performance penalties. For the latter, I note that this extra source information would only need to be tracked for string literals, not all strings, and the number of string literals in any code-base is inherently finite and usually relatively small.
For the former, well, it just comes down to how much being able to reliably evaluate types at runtime is. I, for one, frequently deal with cases where it would be helpful, such as converting structured data (particularly dataclasses) to and from JSON.
Activity
mhils commentedon Aug 16, 2021
Someone reported this rather interesting issue where typing.get_type_hints crashes on type aliases with forward references. The original report is at mitmproxy/pdoc#290. Here's an extended minimal example:
foo.py:
bar.py:
Observations:
If it isn't, this should eventually run in the same problem as func2.
somehow need to know in which context it was defined. bpo-41249 (TypedDict inheritance doesn't work with get_type_hints) introduced
ForwardRef.__forward_module__, which would be a logical place for that information. I'm not sure if it is a good idea
to use __forward_module__ more widely.
The reason I'm bringing it up is that PEP-613 (Explicit Type Aliases) decidedly allows forward references in type aliases.
For the record, PEP-563 (postponed evaluations) does not change the outcome here. However, postponed evaluations often make it possible to avoid the forward references by declaring the aliases last.
Fidget-Spinner commentedon Aug 16, 2021
Good observation! That's indeed the current behavior. The reason is a little subtle - list[...] is using a special builtin type (https://docs.python.org/3/library/stdtypes.html#types-genericalias), while typing.List is the one from typing. The typing version wraps all strings in ForwardRef(), while the builtin version shouldn't import from typing, so it doesn't have that luxury. get_type_hints could probably support the builtin version, though I'd imagine we'd need some rework.
Re: 2. You're right there too. Storing the defined module in __forward_module__ is an interesting proposal.
For your specific use case (where the user is using Python 3.6), you could pass in globalns and localns to get_type_hints as a temporary workaround. Off the top of my head:
get_type_hints(func2, globalns=foo.__dict__) might work. Would that work for your library?
mhils commentedon Aug 16, 2021
I guess the hard part is knowing that the type annotation comes from
foo. In the example here we can of course hardcode it, but that doesn't work in the general case or for pdoc, the documentation generator I'm working on (https://pdoc.dev). I have experimented quite a bit with walking the AST to figure out where type aliases are imported from to then re-executing ForwardRefs with that globalns. Long story short, trying to reverse-engineer forward_module quickly becomes a tangled hot mess where you need to adjust for import aliases, reimports, and so on.17 remaining items
[-]typing.get_type_hints() raises for type aliases with forward references[/-][+][DOC] typing.get_type_hints() raises for type aliases with forward references[/+][-]typing.get_type_hints() raises for type aliases with forward references[/-][+][DOC] typing.get_type_hints() raises for type aliases with forward references[/+]macdjord commentedon Jul 5, 2024
I just got bit by this, and on a case where eliminating the forward references isn't possible, even with delayed evaluation:
I thought of a radical idea for how to fix this: have string literals track the context in which they are defined. So, to use @mhils's example,
get_type_hints()would know thatfunc1()'s 'x' argument is afoo.Dataand not abar.Databecause the string literal which is stored infoo.FooDataknows that it was defined in thefoomodule at the module level.Now, the obvious issues are that this would require adding low-level support for a very specific use-case to a very fundamental part of the language, and the potential for performance penalties. For the latter, I note that this extra source information would only need to be tracked for string literals, not all strings, and the number of string literals in any code-base is inherently finite and usually relatively small.
For the former, well, it just comes down to how much being able to reliably evaluate types at runtime is. I, for one, frequently deal with cases where it would be helpful, such as converting structured data (particularly dataclasses) to and from JSON.