The idea would be to define a new concept on top of inlined TDs, that would allow creating dynamic and flexible TypedDict types (partial, omit, etc). The discussion raises issues about this concept, and in particular this comment suggesting to define a proper new syntax. Feel free to discuss this subject here as well, if you think inlined TDs could be defined using another syntax that would allow for this broader concept.
Thanks in advance for your feedback! (there are also open issues to discuss).
Would the suggested TypedDict[{'name': str, 'year': int}] prevent using the same square brackets for Generic typed dictionaries, e.g. TypedDict[T] defined in the usual way, referring to T), or is there a means under the hood to detect an unassigned type variable, or by parsing the curly braces?
The proposed inlined mechanism doesn’t support generic TypedDict class definitions. However, it does allow the use of type variables that are associated with an outer scope.
from typing import TypedDict
# T is scoped to func and can be used in the inlined TypedDict definition
def func[T](x: T) -> TypedDict[{"x": T}]:
return {"x": x}
If you want to define a generic TypedDict where the type variable is scoped to the TypedDict class itself, you’d need to use the existing class-based declaration mechanism.
IIRC this is the proposed operator for intersection types, which fits quite naturally for extending a typed dictionary. Based on Python’s operator precedence, & is of higher precedence than the union type operator |, so I don’t forsee any issues at runtime for expressions like int | InlinedBase & TypedDict[{'b': int}].
As inlined typed dictionaries are are meant to be anonymous , their __name__ attribute will be set to an empty string.
I don’t think this is a good idea. If code is going to access __name__ for these kinds of objects at all, then probably in a pretty generic manner, printing it to the user. And seeing __main__. as the name of an object is pretty confusing.
Instead, the precedent of anonymous functions should be followed, i.e. something like <lambda>.
The open issues sections suggests <TypedDict>. I would be ok with this and don’t have a better idea.
Hi, thanks for this PEP! I wrote a very similar one, but I think that I did too much there: I also included inheritance to the PEP. So, let’s add “rejected ideas” section about that maybe? Just for the historical purposes.
Btw, if you need formal “core-dev” sponsor, feel free to count me in
In particular because of the order that things would have to be defined - the innermost structures would have to be defined far away from where they make most sense.
Subclassing and intersection are two different concepts, so I don’t think it is a good idea to use the & operator for this (even though in the case of TDs, subclassing with the class-based syntax does not have the same meaning – it’s just a convenient way to reuse keys definitions from another TD. Still, TypedDict[{'a': int}] & TypedDict[{'b': int}] could in fact mean TypedDict[{}]).
Indeed, either '<TypedDict>', or '<anonymous TypedDict>' as suggested here.
Making it an instance of a new typing._InlinedTypedDict is also an interesting alternative.
Thanks @sobolevn, actually some of the alternatives mentioned in the PEP were already explored by you in this thread. If you agree, I’ll add you as a sponsor in the next update.
I can remove the recommendation, and leave the stylistic/readability concerns up to the user (note that TypeScript truly shines in that aspect, as you can use plain curly brackets).
Does TypedDict[{'a': int}] & TypedDict[{'b': int}] being interpreted as TypedDict[{}] come from its corresponding operation in builtins.set()? If so, That’ll make the binary OR operator | be the one which combines the keys, which goes against the idea of a type union.
No, it should not ever mean that. TypedDict[{'a': int}] is the set of dictionaries with a key 'a' of type int; TypedDict[{'b': int}] is the set of dictionaries with a key 'b' of type int. The intersection of those two types is the set of dictionaries that have both a key 'a' of type int and a key 'b' of type int.
The & operator makes a lot of sense here; this is a type intersection operation. My main concern would be that if we use it for this, that we avoid locking in any behaviors that would cause trouble for a future intersections PEP.
Yes sorry, I had the sequence-not-str example in mind (Sequence[str] & ~str) and somehow got confused.
This is an approach I explored here (at least it has similarities), but you’ll see in this thread that there are some concerns with this approach, in particular runtime support.
Languages like Kotlin and C# use a similar syntax for inheritance.
And even though I have barely any experience with such languages, this unconventional slice operator usage looks very natural to me .
This would require a syntax change in the language, and if we go this way I think it’s best to try including the broader concepts introduced here (and this comment is especially relevant).
The inlined TypedDict mechanism is not intended to support all of the functionality of the class and functional syntax. Examples include totality, generics, and extra items / closed-ness. The inlined version is meant for quick-and-dirty definitions that don’t require these more advanced features. I think it’s reasonable to direct users to the existing TypedDict mechanisms if they need these features. With this context in mind, is extension (i.e. structural inheritance) needed for inlined TypedDict definitions? My inclination is to keep the inlined version simple.
As others have pointed out, if / when intersections are added to the type system, that will provide a natural way to spell a type that effectively extends an existing TypedDict. My recommendation is to wait for intersections rather than adding a different (what will be redundant) mechanism to the inlined TypedDict mechanism.
I also agree that its best to not include extension with this concept, considering intersections or the work related to partial et al. may be able to support this separately.
I’ll try to play more with inlined TDs as instances instead of classes (e.g. how to provide introspection attributes, etc).
That makes sense. Especially when you picture a function signature with an inline typeddict return type that has several base-typeddicts and type_params=(T1, T2, ...). Or even worse; the spaghetti that would be reported in case of a type-checker error .