If you agree, I’ll add you as a sponsor in the next update.
I would be happy to!
From my past experience, it is a good idea to limit the scope of this feature to a bare minimum. We can always add something when needed (like & syntax / inheritance / etc)
I like this proposal. Tbh I’m not a huge fan of TypedDict in general, but given that we seem to be stuck with it, this would make declaring simple TypedDicts much more ergonomic.
My 2c on the open issues section:
Subclassing an inlined typed dictionary: I don’t think either of these should be allowed. They don’t strike me as adding a huge amount of value in exchange for the additional complexity.
Using typing.Dict with a single argument: I prefer the existing proposal’s use of TypedDict, which makes it obvious that the new syntax constructs a TypedDict under the hood.
Should inlined typed dictionaries be proper classes?: No strong opinion on this. My default opinion is that whatever is simplest to implement that doesn’t cause obvious problems is the way to go for a first pass
Inlined typed dictionaries and extra items: I think it would be useful to have a line in the PEP stating that if PEP 728 is accepted, inlined typed dictionaries should be closed by default. That shows that interactions with other features have been thought through.
as an expressive way to declare some keyword args are (truly) not required (as opposed to the alternatives today)
def foo(*, name: str, year : int | None = None): ...
# or, maybe if `None` has meaning in the API
def foo(*, name: str, year : int | OMITTED_T = OMITTED): ...
# or the increasingly-verbose
@overload
def foo(*, name: str): ...
@overload
def foo(*, name: str, year : int): ...
def foo(*, name: str, **kwargs): ...
Yeah, sorry, typo (I always forget the Unpack bit, I’ll edit).
This is exactly what I was suggesting be pointed out in the proposal. That it is expected for it to “just work” in this manner. (Otherwise without an example, all I see is return-type examples, so it could be argued its ambiguous)
| <Unpack> '[' name ']'
(where name refers to an in-scope TypedDict;
valid only in **kwargs annotations)
Alternatively, it could be updated to state to an in-scope or inlined TypedDict. Let me know on the PR, I can add this edit (I also added an example of an inlined typed dictionary used as a parameter type).
@Viicos would you be interested in contributing a runtime implementation to typing-extensions? That should help people experiment with the new feature.
(I’ve often done typing-extensions implementations in the past but I am no longer going to have time for that.)
I’m planning on trying out some runtime implementations, either by defining inlined TDs as instances of a new class or as TypedDict classes (with __name__ set to something like <inlined typed dict>). When initially writing out the PEP, I made a draft implementation of the latter here, but I still need to experiment with the first solution. I’ll try to do so this week.
However, it can get tedious to defineTypeDict classes
A typed dictionary requires a name, which might not be relevant.
Personally, I find these two “motiviations” to be features rather than limitations. I want code that I’m reading to use the current approach rather than an in-line spec.
If a function is returning a TypedDict, then the consumer code also needs to know about that structure. Dicts aren’t special in this regard, the same reasoning applies to other return types including dataclasses, instances of regular classes, and named tuples.
If a value is used outside of a function, its spec needs to be global, not local. How is a consumer supposed to make a type declaration for the value it receives? Will it just have to write, result: dict = func(10) and lose the field specs and type name? I don’t think this is progress.
Also, giving the typed dict a name is essential for 1) understanding what the return value represents, and 2) differentiating it from some other type with the same field names.
Overall, I’m -1 on this proposal. AFAICT it doesn’t even save much code volume. All it does it move a global specification to a local specification, taking it farther from where it is consumed. Also, being anonymous makes it even more opaque, forcing the reader to deduce the meaning rather than telling them.
Not really - I’m sure you’ve returned a tuple from a function at least once. If you think of this as an extension of tuples (both TypedDicts and tuples are structural types, after all) it makes perfect sense.
I don’t know about you, but if func returns a tuple[int, str] I don’t write result: tuple[int, str] = func(10), I just write result = func(10). It works fine.
is very context dependent, and not a general truth, otherwise tuples would never be used. 2) TypedDicts, being structural types, don’t support this in the first place.
I think if you give this proposal a chance you might come to realize it’s pretty cool.
I am strongly in favor. I am in the middle of writing serialization and deserialization for a fairly complicated data structure. There are many data classes that have .serialize() methods returning a JSON conforming dict that is incorporated in the overall JSON dict. The type names are annoying boilerplate and I actually tried to google whether there was an inline form…
I strongly favor this proposal. Dictionaries are an essential part of Python, yet we’ve lacked the ability to specify simple structures inline.
This proposal satisfies a very real need for me in cases like:
The dictionary has a very simple structure, usually with fewer than five key-value pairs.
The dictionary is rarely, if ever, used more than once. Typically, I’m just representing intermediate internal data that will be incorporated into something like a dataclass sooner rather than later.
Considering the above two points, creating a TypedDict for such cases, while easy, simply isn’t worth the effort. It’s an area that can be strictly typed but is usually not worth the trouble. This proposal will definitely help replace a lot of dict[str, Any] in typed codebases.
Consider tuples, for example. We can create inline tuples for simpler, rarely used structures, e.g., tuple[int, int, str], and NamedTuples where we do need reusability and want to represent more complicated structures.
Overall sounds great, it will indeed cover lots of cases when previously user wouldn’t bother introducing a one-time-use class just to annotate some return type / nested typed dict.
Here are some thoughts I had reading PEP.
Although it is not possible to specify any class arguments such as total , any type qualifier can be used for individual fields:
Why it wouldn’t be possible to specify total e.g. as TypedDict[{"a": str}, total=False]? If I’m not missing anything, from PEP it seems like it is possible but it’s intentional decision to avoid possible arguments like total (and maybe closed, extra_items in the future).
From my experience closed inline typed dict would indeed cover 99% cases I’ve met but I don’t know if there’s maybe some niche that’s going to use extra_items all the time. Also there could be other arguments added to the future that may be useful in inline typed dict (though don’t have any at hand right now). Maybe it’s worth considering possibility to provide additional arguments?
Since inline typed dict will have some special properties (unsupported inheritance, possibly closed by default), there will be a bit of a problem for newcomers to understand the difference between the two below. So maybe there should be some common way to mark inline TypedDict to make them easily identifiable. E.g. <TypedDict>?
Not sure about this but any ideas if there are any concerns about formatting inline typed dicts? E.g. would there be any roadblocks formatting them in a readable nested way as suggested in “How to Teach This” section or it should be fine and automatable?
And similar question about representing inline typed dicts in the documentation and tooltips in IDEs. Inline typed dicts can easily grow bigger than the rest of the function signature, so I guess there will be a need for special handling - e.g. replacing in documentation giant inline-typed-dict-monster-return-types with some link to the full typed dict structure (would be nice to know any opinions on this if someone has worked on sphinx or something).
# E.g. replacing in docs this
def test_x() -> TypedDict[{"a": str, "b": int, "c": float}]: ...
# With this
def test_x() -> TD: ... (e.g. TD would be a clickable link)
TD:
a: str
b: int
c: float
Regarding the tooltips, currently Pyright displays it as <TypedDict> though understandably it’s still experimental and probably is subject to change.
I had a concern about the lack of option to name inline typed dict, but now I think that good function name (if it’s inline return type) or argument name should cover this.
This is not currently valid syntax - adding support for it is a whole different proposal that has quite a few problems and has been discussed quite a bit with no clear resolutions. If it gets discussed again, typing usages would be good motivations. But it wouldn’t fly to add this as part of this (or any other typing-focused) PEP.
Okay, another thought – it’s probably out of scope for this PEP but wanted to share it too – what if there would be a way to define an inline typed dictionary that should be inferred based on it’s construction? See examples below.
from typing import TypedDict
# test() -> TypedDict[{"movie": str, "year": int}]
def test():
movie_name: str = ...
year: int = ...
a: TypedDict = {
"movie": movie_name,
"year": year,
}
return a
def test_2():
a: TypedDict = {"x": 25, "y": 33}
a["z"] # Typing Error - key "z" is not present on TypedDict[{"x": int, "y": int}]>
I think that would make more sense as a general feature, something like TypeOf[variable]. Possibly useful, but better discussed on its own merits and not as part of this PEP.