-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Problem with setting one TypedDict variable to another TypedDict variable #6577
Comments
Your example is not type safe, though it may not be immediately obvious why. Consider this example, which is similar to your example but with a twist: from mypy_extensions import TypedDict
T1 = TypedDict('T1', {'x': int, 'y': int}, total=False)
T2 = TypedDict('T2', {'x': int}, total=False)
T3 = TypedDict('T3', {'x': int, 'y': str}, total=False)
t3: T3 = {'x': 0, 'y': 'foo'}
t2: T2 = t3
t1: T1 = t2 # This is currently an error
t1['y'] = 1
if 'y' in t3:
print(t3['y'] + 'bar') # Runtime error If we'd allow the assignment, mypy wouldn't be able to prevent the runtime error. I can see how allowing the |
@JukkaL Thank you for the response. I think I'm fundamentally misunderstanding something - in your example, why are you allowed to set It's a type error if you do this:
It says "Extra key 'y' for TypedDict "T2"". That makes perfect sense to me. So why then are you allowed to do The behavior I would expect is that Can you let me know what's missing in my mental model here? |
Mypy treats creation of a TypedDict as a separate operation from assignment. This is creation (since there's a dictionary display/literal): t: T = {'x': 0} This is assignment (we don't construct a new instance): t2: T2 = t In creation we reject extra items, since this is typically a code quality issue. For example, if we remove a TypedDict item, we'd generally like mypy to help us remove the item from constructed TypedDict objects. Also, if you misspell the key of a non-total TypedDict, mypy will catch this. However, when we assign a TypedDict to a variable with a different TypedDict type, we use structural compatibility, similar to how protocols and ABCs work. A good mental model might be to treat construction equivalent to a constructor call such as |
Thanks again for the response! I understand fully now how it works currently. I guess my next question is, is that what we actually want? This still seems like basically the opposite of what I and I have to imagine most users would expect. For two As an example, I have an api where the endpoint accepts some data of type I can't make Maybe |
I would also be willing to put in the work to add this feature - ability to type check in this way is important to me. |
I'm actually currently writing a PEP to standardize how TypedDict works, and you can bring this change up in the discussion that will follow. I'm planning to post the PEP draft to the typing-sig mailing list in the not too distant future (python/typing#590 discusses how to subscribe). An alternative way to make this use case a bit better would to provide a custom cast function that will "widen" a TypedDict type. For example, assume from mypy_extensions import widen
def f(b: B) -> None: ...
a: A = { ... }
f(widen(a)) # OK |
I'll definitely join that mailing list and take part in discussion, thanks for the link! In the meantime, if I were to write a plugin that would create a wrapper around the current check that happens when a TypedDict variable gets set to another TypedDict value, which hook should I add? The docs don't make it explicitly clear to me. Also, can you tell me where in the source code this check happens so I can use it for reference? If I can't figure out how to do that, I'll likely write a minimal proprietary "safely_cast_to_some_other_typed_dict" type function like you suggest. |
I think this only appeared once before in #6522 Since mypy is correct (it flags unsafe code), I think we can live with status quo if we clearly document this behavior (plus maybe give a better error message). |
* Typeshed cherry-pick: Relax signature of logging.config.loadConfig (#6577) * Typeshed cherry-pick: Use AbstractSet instead of set in random and inspect (#6574) * Partial typeshed cherry-pick: Add `SupportsRichComparison` type to `_typeshed` (#6583) The original PR doesn't apply cleanly so I only included the changes to builtins to avoid having to manually deal with all the conflicts.
* Typeshed cherry-pick: Relax signature of logging.config.loadConfig (python#6577) * Typeshed cherry-pick: Use AbstractSet instead of set in random and inspect (python#6574) * Partial typeshed cherry-pick: Add `SupportsRichComparison` type to `_typeshed` (python#6583) The original PR doesn't apply cleanly so I only included the changes to builtins to avoid having to manually deal with all the conflicts.
When fixing this we should make sure we follow the spec: https://typing.readthedocs.io/en/latest/spec/typeddict.html#assignability The issue boils down to mypy has the assignability backwards - I fear fixing this would have a bad primer report :/ |
Bug
or a mock-up repro if the source is private. We would appreciate
if you try to simplify your case to a minimal repro.
Basically, the code above, including its inline notes, but I'll summarize:
If you assign a value who's type is a TypedDict with some keys and total=False to be the value of a variable who's type is another TypedDict with total=False and a subset of the first TypedDict's keys, it throws an error, but it should not.
If you assign a value who's type is a TypedDict with some keys and total=False to be the value of a variable who's type is another TypedDict with total=False and a superset of the first TypedDict's keys, it does not throw an error, but it should.
Python version 3.6.4
Mypy version 0.670
After installing mypy-0.680+dev.4e0a1583aeb00b248e187054980771f1897a1d31 from github, I get the same exact errors.
Here is my mypy.ini:
If mypy crashed with a traceback, please paste
It did not.
The text was updated successfully, but these errors were encountered: