-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Create TypedDict from positional dict #1046
Comments
The typing implementation of TypedDict: object I think you're referring to a fallback definition that mypy uses in some cases, but Pyright never uses it. # Internal mypy fallback type for all typed dicts (does not exist at runtime)
class _TypedDict(Mapping[str, object], metaclass=ABCMeta):
... So I think the PEP is correct, and the If I attempt to initialize a from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
a = Movie({name: "hi", year: 1923})
# NameError: name 'name' is not defined Pyright correctly emits an error in this case. |
I believe it only name-errors because a = Movie({"name": "hi", "year": 1923}) Or even: Movie([("name", "hi"), ("year", 1923)]) |
Of course quotes are needed here. (Sorry, I clearly haven't had enough caffeine yet this morning. :) ) Yes, it appears that these are supported at runtime, but PEP 589 indicates that they are not allowed by a type checker. Pyright follows the PEP as it's written. Supporting those other forms adds no value and would represent a lot of extra work in a type checker, so I'm not surprised that the PEP is written as it is. I don't see any benefit of providing support for these alternative forms when you can already do this: a: Movie = {"name": "hi", "year": 1923} and a = Movie(name = "hi", year = 1923) |
Well, the more general issue is that you cannot convert an existing dictionary to a # Expression of type "List[Dict[str, int]]" cannot be assigned to declared type "list[Movie]"
# TypeVar "_T" is invariant
# "Dict[str, int]" is incompatible with "Movie"
b: 'list[Movie]' = [{"name": 0, "year": 1923} for _ in range(1)] |
There's no type-safe way to convert an existing dictionary without manually unpacking it. There's no type information about the individual fields in an existing dictionary. If you could pass any Your example will work if you provide the correct type for the name. b: 'list[Movie]' = [{"name": "title", "year": 1923} for _ in range(1)] |
Well, isn't that the point of casting it to a a: Movie = {"name": "hi", "year": 1923} and a = Movie(**{"name": "hi", "year": 1923}) that the latter should not be type checkable? I would actually expect this to fail as well but it doesn't: class MovieClass:
def __init__(self, name: str, year: int) -> None:
...
c = MovieClass(**{"name": 1, "year": 1923}) This does, however: # Argument of type "Literal[1]" cannot be assigned to parameter "name" of type "str" in function "__init__"
# "Literal[1]" is incompatible with "str"
c = MovieClass(*(1, 1923)) Although I am straying from |
I've investigated this further, and I think Pyright is doing the right thing. Here's my thinking. First, PEP 589 very clearly states that "It can be used as a callable object with keyword arguments corresponding to the TypedDict items. Non-keyword arguments are not allowed". Mypy breaks with the spec in allowing a positional argument to the constructor. Pyright is following the spec. Second, where named parameters are allowed, the type checker should not emit errors if a dictionary unpack operator ("**") is used. For example: def callee(/, name: str, year: int): ...
def caller1(**kwargs: Any):
# This should not emit an error
func(**kwargs)
def caller2(my_dict: Dict[str, Union[int, str]):
# This should not emit an error
func(**my_dict) You asked why this doesn't work as you expect: a = Movie(**{"name": "hi", "year": 1923}) Here is that the type of the expression |
Should the |
Ah yes, I meant: def callee(*, name: str, year: int): ...
def caller1(**kwargs: Any):
# This should not emit an error
callee(**kwargs)
def caller2(my_dict: Dict[str, Union[int, str]]):
# This should not emit an error
callee(**my_dict) |
Good point about the syntax error. I created this issue: #1064 |
Found using `pyright`. Discussion here: microsoft/pyright#1046 Essentially, `TypedDict` doesn't allow a single-object dictionary for construction.
PEP 589 says: "The created TypedDict type object is not a real class object. Here are the only uses of the type a type checker is expected to allow: ... It can be used as a callable object with keyword arguments corresponding to the TypedDict items. Non-keyword arguments are not allowed." However, the
typing
implementation permitsdict
s and dict literals in addition to keyword arguments and it appears to have been that way since it was first added tomypy_extensions
four years ago; I assume they simply omitted to mention it in the PEP and that by 'non-keyword arguments' they mean that it cannot be initialised like aNamedTuple
ordataclass
where the positional arguments correspond to keys or fields on the class.The text was updated successfully, but these errors were encountered: