-
-
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
TypedDict cannot be used where a normal dict is expected #4976
Comments
This is probably related to invariance. See http://mypy.readthedocs.io/en/latest/common_issues.html#invariance-vs-covariance. |
Yeah, this is a bit unfortunate, and it's related to invariance. You can replace |
Thank you very much. Using I'm ready to make a pull request that adds a mention of this to the docs for TypedDict if that helps. Otherwise I'm ok with closing this. |
Can someone show a working code sample? |
I think it's ridiculous to have to go through this, but this is a working method of handling a library function that only accepts Dict but you want to use TypedDict: import csv
import typing
from mypy_extensions import TypedDict
User = TypedDict('User', { # pylint: disable=invalid-name
'uid': str,
'email': str,
})
def write_to_csv(users: typing.List[User], outfile: str) -> None:
""" Writes users list to CSV file. """
fieldnames = list(users[0].keys())
with open(outfile, 'w', newline='') as csvfile:
writer = csv.DictWriter(outfile, fieldnames)
writer.writeheader()
for usr in users:
# Cast to Dict until TypedDict is Dict compatable
# https://github.com/python/mypy/issues/4976
usr_dict = typing.cast(typing.Dict[str, typing.Any], usr)
writer.writerow(usr_dict) Without the usr_dict typing.cast() you get the error:
|
@JukkaL can we do something here, or should we just close this issue? |
Making TypedDicts compatible with Another idea would be to change typeshed signatures that only accept Any thoughts? |
I think it is probably OK to special-case |
I guess I just don't understand mypy's plans for Dict and TypedDict. Because I mostly deal with JSON APIs, Dict is not something I ever intend to use (excepting casting). I guess I just assumed that eventually Dict and TypedDict would be merged. I see in the documentation explaining why TypedDict doesn't extend Dict:
But I don't know why a subtype of Dict cannot disallow arbitrary keys. I also don't see why TypedDict cannot accept arbitrary keys. TypeScript interfaces support all the complicated objects I've ever thrown at it. After all TypedDict isn't a Python-ism, it's a mypy solution to a lack of Dict's ability to accept complicated patterns. I was really hoping that eventually I would be able to do something like If we cannot do that, then can we just have TypedDict figure out the equivalent Dict syntax? User = TypedDict('User', { # pylint: disable=invalid-name
'id': int,
'email': str,
'birthdate': datetime.date,
'meta': UserMeta # A TypedDict
}) # == typing.Dict[str, typing.Union[int, str, datetime.date, UserMeta]] Or add a third parameter that allows me to manually specify the Dict type of the TypedDict: User = TypedDict('User', { # pylint: disable=invalid-name
'id': int,
'email': str,
'birthdate': datetime.date,
'meta': UserMeta # A TypedDict
}, typing.Dict[str, typing.Any]) I just think it's very confusing to have to create a new variable to cast a variable for the type checker to be happy. |
In your example, if I'm not quite sure what you mean by equivalent Dict syntax, as the two types don't seem to be equivalent. Can you provide a more complete code example that shows what isn't working right now as you'd expect, and explain how your proposal would resolve the issue? And maybe you can also explain how you'd deal with the issue in TypeScript. |
In typescript I can do a mixture of known and unknown properties on my object like this: interface MyInterface {
fixedKey1: string,
fixedKey2: number,
[x: string]: string | number,
} And in my head I would translate that to something like: MyInterface = TypedDict('MyInterface',
{'fixedKey1': str, 'fixedKey2': int},
arbitrary=[str, Union[str, int]]
) Then I could use the type like:
The important part being that I cannot make |
In my case, if I replace from typing import Mapping
from typing_extensions import TypedDict
def myfunc(d: Mapping):
d.pop("key")
TD = TypedDict("TD", {"b": int})
b: TD = {"b": 2}
myfunc(b)
If I replace
|
@gricey43 One of the reasons why TypedDict types are compatible with |
After more practical experience with TypedDicts, not having TypedDict compatible with |
from typing import MutableMapping, Mapping
from typing import Any
from typing_extensions import TypedDict, NotRequired
class X(TypedDict):
i: int
s: str
f: NotRequired[float]
def mongodb_fn(x: MutableMapping[str, Any]) -> None:
pass
x: X = {"i" : 1, "s" : "A"}
# no warning here - it's mutable
x["i"] = 23
# Argument 1 to "mongodb_fn" has incompatible type "X"; expected "MutableMapping[str, Any]"
mongodb_fn(x)
Also, PEP 589 explicitly says
https://peps.python.org/pep-0589/#type-consistency I'm hoping closing this issue can be reconsidered at some point. |
I hope maintainers will revisit opening this issue, since the primary usecase of TypedDict is a drop-in replacement for a plain dict in code. If you use TypedDict in that way, then you (a) encounter this somewhat bizarre issue; and (b) have to go through your entire codebase changing all type annotations from dict to Mapping. This to me seems contrary to the spirit of the reason why TypedDict was introduced in the first place. Consider some large codebase with many functions of the form
Now you want to add some restrictions on |
Isn't it the whole point of static type checking, so it validates all the places in the code interacting according to the same contract?
Not sure how. TypeScript compiler would error out while compiling mismatching types and in Python all static type checkers are 3rd-party tools and the interpreter just skips types. TypeScript would definitely report as a warning a plain |
@boompig, the typing rules for It's unlikely that the behavior of @gh-andre, here's an example in TypeScript that demonstrates what @boompig is referring to. This code doesn't generate any errors in TypeScript. function foo(obj: { [key: string]: any }) {
obj.a = 'hi';
}
const x: { a: number } = { a: 1 };
foo(x);
console.log(x.a); // Prints 'hi' TypeScript accommodates unsafe behaviors in the case where |
PEP589 says that |
Just bumped into this where we modify a log record typed with from typing import TypedDict
class LogRecord(TypedDict):
message: str
log_record: LogRecord = {
"message": "something happened"
}
# simplifying a much longer code use case
log_record["event"] = log_record.pop("message") Mypy error error:11 TypedDict "LogRecord" has no key "event" [typeddict-unknown-key]
error:11 Key "message" of TypedDict "LogRecord" cannot be deleted [misc] |
@heitorlessa from typing_extensions import TypedDict, NotRequired
class LogRecord(TypedDict):
message: str
event: NotRequired[str]
log_record: LogRecord = {
"message": "something happened"
}
log_record["event"] = log_record.pop("message") |
Thanks Andre :) I should’ve provided more context — the example is for when
customers want to rename that field, e.g., customer wants to bring their
own logging standards.
We use NotRequired for other fields (it’s great!)
…On Fri, 9 Jun 2023 at 20:03, Andre ***@***.***> wrote:
@heitorlessa <https://github.com/heitorlessa>
Just as a quick note, for the top error, you need NotRequired for event,
like this:
from typing_extensions import TypedDict, NotRequired
class LogRecord(TypedDict):
message: str
event: NotRequired[str]
log_record: LogRecord = {
"message": "something happened"
}
log_record["event"] = log_record.pop("message")
—
Reply to this email directly, view it on GitHub
<#4976 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAZPQBGABZ674ZCEWP7STR3XKNQPXANCNFSM4E4WJW3Q>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
@heitorlessa Now that I'm thinking, you also need |
thanks again @gh-andre, that wouldn't be accurate in our case tho, since we're gonna add two specific ignores in this case now. Appreciate the care either way |
@heitorlessa That's one of the challenges of I opted for some code duplication in having multiple , but I can see use cases in which such duplication would be a non-starter because of increased maintenance. |
Using Mapping allows us to give MessageDict object and such directly, without casting them as Dict. Related Mypy issue: python/mypy#4976
An issue related to python TypedDict where they cannot be bound to any instance of dict when using TypeVar python/mypy#4976 (comment)
This is probably intended behavior, and if so it be good to have the motivation documented somewhere alongside TypedDict's current docs. Apologies if I'm missing something in the docs.
I would expect to be able to pass a TypedDict to any function that expects a plain old dict:
However the above snippet fails:
TypedDict is a plain dictionary at runtime, which is what the signature of
use_any_dict
says it accepts. So why doesn't MyPy allow it?Going the other direction- passing a Dict[Any,Any] to something expecting a certain TypedDict- also fails, which makes sense to me. But it leaves me wondering how code using normal dicts is supposed to interop with newer code using TypedDict aside from using casts or
# type: ignore
.I've tested this with mypy 0.590 from PyPi as well as commit
d6a22cf13a5a44d7db181ae5e1a3faf2c55c02b4
from trunk. I'm invoking MyPy as follows:The text was updated successfully, but these errors were encountered: