-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Set TypedDicts to always be their declared type as opposed to the type inferred when instantiated #2621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@davidfstr What do you think of this? It looks reasonable to me -- if you agree I'll merge it. |
Making the typed dict field types covariant isn't safe, since the fields are mutable. Dictionaries are invariant as well. The dictionary example works because mypy uses "type context" to infer the key/value types for a dict literal. The modified example below won't work, since now there's no type context when we define
I think that the correct fix would be for the call to a typed dict type to result in the exact declared typed dict type. Example:
|
+1. Yes this is unfortunate. I originally had the fields support covariance until I noticed they were mutable.
Yes this would make sense to me. I'm actually a bit surprised this isn't the current behavior. |
@JukkaL @davidfstr makes sense. So it sounds like a proper fix would be able to pass with all of the test cases I added and the existing test case that enforces invariance. Assuming we fix #2487, how would this interact with dictionary literals? |
I imagine the following two code fragments would create values of equivalent types, i.e. Point:
I believe there is still a problem where an unknown type (i.e. Any) is not treated as "equivalent" to a required item type. So I suspect the following would still fail:
|
#2487 would need to consider the type context when inferring the type of a literal. For example, if the context has a type like |
TypedDicts appear to have explicitly decided not to accept subtypes on fields, but this behavior is counter intuitive. This made it so TypedDicts didn't respect `Any` and caused problems with what should have been ducktype compatible. This also brings TypedDicts more in line with other container types and with how fields on classes behave. ```python from typing import Dict def foo() -> Dict[float, object]: return { 1: 32 } ``` This fixes python#2610
… in exact declared type See python#2621 (comment)
abb3de9
to
0ca0ae6
Compare
@davidfstr I took @JukkaL 's advice and made the type of the TypedDict always the declared type. This matches what I would expected anyway. This did break a few existing test cases, I'd love your input on the changes. |
@rowillia these changes look good to me, including the test changes. Not related to your commit, but I'm surprised the revealed types of the named types (like |
@davidfstr yeah I'd love to see that fixed. |
… in exact declared type See python#2621 (comment)
… in exact declared type See python#2621 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good now! Just a few test case related requests.
I'd like to see a type context related test case, where a value type is, say, List[int]
, and it's given an empty list []
as an argument, e.g. MyTypedDict(items=[])
. Use reveal_type
on the resulting typed dict.
Another thing below.
from typing import Any, Mapping | ||
Point = TypedDict('Point', {'x': float, 'y': float}) | ||
def create_point() -> Point: | ||
return Point(x=1, y=2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also do something like reveal_type(Point(x=1, y=2))
in a test case?
@rowillia Are you interested in finishing this up? If you are currently busy, I can do the remaining bit of work and get this merged. |
…e inferred when instantiated (2) (#3099) [This was implemented originally by @rowillia in #2621. This PR includes a few test updates.] * Allow fields on a TypedDict to be subtypes of their declared types. TypedDicts appear to have explicitly decided not to accept subtypes on fields, but this behavior is counter intuitive. This made it so TypedDicts didn't respect `Any` and caused problems with what should have been ducktype compatible. This also brings TypedDicts more in line with other container types and with how fields on classes behave. ```python from typing import Dict def foo() -> Dict[float, object]: return { 1: 32 } ``` This fixes #2610 * Take suggestion from Jukka to have calls to typed dicts always result in exact declared type See #2621 (comment)
TypedDicts appear to have explicitly decided not to accept subtypes on fields,
but this behavior is counter intuitive. This made it so TypedDicts didn't
respect
Any
and caused problems with what should have been ducktypecompatible. This also brings TypedDicts more in line with other container
types and with how fields on classes behave. The following code is already OK
with mypy:
This fixes #2610