On Tue, Apr 19, 2022 at 5:47 AM David Foster <davidfstr@gmail.com> wrote:
>     As a completely separate idea, it may be useful to make it easy to
>     extract the extra field values from a runtime value of a TypedDict, and
>     to explicitly copy those field values to a new TypedDict instance.
>     Consider the following:
>
>     class Point2D(TypedDict):
>           x: int
>           y: int
>
>     class NamedPoint2D(Point2D):
>           name: str
>
>     n_point = NamedPoint2D(x=1, y=2, name='Center')
>
>     P = TypeVar('P', Point2D)
>
>     # Does swap the "x" and "y" components of a Point2D,
>     # while preserving its __extra__ fields.
>     def transpose(p: P) -> P:
>           return Point2D(  # maybe write P( here instead?
>               x=p['y'], y=p['x'],
>               **Point2D.__extra__(p))
>
>     transposed_n_point = transpose(n_point)
>     print(transposed_n_point)  # prints: {"x": 2, "y": 1, "name": "Center"}
>     reveal_type(transposed_n_point)  # NamedPoint2D
>
>     The above code (1) uses a new @classmethod called __extra__() on the
>     Point2D TypedDict that extracts any fields from the provided value that
>     are not in Point2D's definition, and (2) allows a Point2D TypedDict to
>     be constructed with a ** splat with statically-unknown extra fields.
>
>
> I’d like to see some use cases for such syntax before we go there.

The "transpose" function above is a scenario. The related general use
case is "I want to generate a derived version of a TypedDict instance,
completely redefining all known field values, but also preserve any
extra fields.". This "copy-on-write" pattern seems to be a relatively
common in coding styles that avoid mutating data structures directly,
such as in Clojure.

Hm, that sounds like a rather theoretical "use case". I don't think we should try to add features to TypedDict to encourage its use for new coding styles, even though those styles are popular in other languages. TypedDict was accepted into the type system because we observed a pattern using dicts instead of classes in legacy code and felt it was important to be able to type-check such code.

To be fair the TypedDicts I use in my own code typically only exist for
a short time before they are JSON-dumped or after they are JSON-parsed
and then discarded (in a request-response cycle of a web app), so my own
code doesn't typically have TypedDict instances that live long enough to
make it useful to support fancy mutations on them.

Okay, so the use case isn't real in your code.
 
> And why would __extra__ need to be a Point2D method?

Well the implementation needs to be some kind of function that takes (1)
the TypedDict instance and (2) the TypedDict type (like Point2D),
because the type is erased from the instance at runtime.

In particular you could *not* write:
        p.__extra__()  # an instance method call
because "p" is a dict at runtime rather than a Point2D.

You *could* write a freestanding function like:
        typing.get_typeddict_extras(p, Point2D)
but it seems more succinct to just make it a class method on Point2D:
        Point2D.__extras__(p)  # use dunder to avoid clash with field names
or equivalently:
        Point2D._get_extras(p)  # use underscore to avoid clash with field names

But then every TypedDict-derived type would have that class method. A generic function makes more sense to me for this API design. But I am far from convinced that we need it.

--
--Guido van Rossum (python.org/~guido)