Skip to content

Anonymous and inline declaration of TypedDict types #9884

Closed

Description

@jetzhou

Feature
Currently, TypedDict must be declared and used as so:

MyDict = TypedDict('MyDict', {'foo': int, 'bar': bool})
def fn() -> MyDict:
    pass

In many situations, the type doesn't actually get re-used outside the context of this one local function, so a much more concise declaration would be:

def fn() -> TypedDict({'foo': int, 'bar': bool}):
    pass

or alternatively:

def fn() -> TypedDict(foo=int, bar=bool):
    pass

This syntax makes the TypedDict definition both anonymous and inline, which are two separate features I guess, even though they are pretty much hand in hand. Both are desirable in situations where the return type isn't re-used in multiple places so there is no need for the separate MyDict definition.

Pitch

I've read through #985 and python/typing#28. Both issues had a little bit of discussion on anonymous TypedDict and dismissed the feature as not being useful enough.

In my case, we run a GraphQL server and many of our mutation types return dictionaries/JSON objects with the shape {'ok': bool, 'error': str, 'item': <ItemType>}. These objects have no relevance outside of where their mutation function is defined, so it is extremely verbose and pointless to type:

ReturnType = TypedDict('ReturnedType', {'ok': bool, 'error': str, 'item': <ItemType>})
def mutation() -> ReturnType:
    return {'ok', True, 'error', '', 'item': <item>}

When everything can be succinctly expressed as:

def mutation() -> TypedDict({'ok': bool, 'error': str, 'item': <ItemType>}):
    return {'ok', True, 'error', '', 'item': <item>}

The simplified syntax has better readability and loses none of the type safety that's achieved with the type hints.

Outside of my use cases, I can also imagine that inline anonymous TypedDict can be pretty useful when they are used in a nested fashion, something like:

MyType = TypedDict({
    'foo': int,
    'bar': TypedDict({
        'baz': int,
    })
})

Happy to provide more context on the use case that I have or to come up with more use cases. If this seems like a worthy direction to pursue, I would definitely love to dig into implementation and figure out what needs to be done to make this happen.

Activity

  1. bennettdams commented on Mar 16, 2021

    @bennettdams

    Just to give a perspective from another language: One of TypeScript's main features is exactly this "object typing". It's even a main bullet point in their documentation and one of the first examples.
    This way of "inline typing" is useful for many use cases, but especially for return values, as @jetzhou described.

    https://www.typescriptlang.org/docs/handbook/2/objects.html

  2. zuckerruebe commented on Jan 6, 2022

    @zuckerruebe

    I'd prefer the following syntax:

    def fn() -> {"foo": int, "bar": bool}:
        pass

    That would be so much better than having to do

    def fn() -> Tuple[it, bool]:
        pass

    (as is the case now) and then having to remember whether [0] or [1] was foo.

  3. amh4r commented on Apr 24, 2022

    @amh4r

    I'd love this feature! I'm writing a library that codegens JSON Schema into TypedDicts, but JSON schema has nested object schemas.

    For example, I'd like to convert this JSON schema:

    {
        "id": "#root",
        "properties": {
            "Foo": {
                "properties": {
                    "bar": {
                        "type": "object",
                        "properties": {
                            "baz": {"type": "integer"}
                        }
                    }
                }
            }
        }
    }

    Into something like this:

    Foo = TypedDict(
        {
            "bar": {
                {"baz": int},
            },
        },
    )

    I could write logic that creates something like this:

    class FooBar(TypedDict):
        baz: int
    
    class Foo(TypedDict):
        bar: FooBar

    But that adds complexity to my recursive functions:

    • They need to have extra context to generate the path-based name (i.e. Foo.bar -> FooBar).
    • They need to handle name conflicts. Like if the schema explicitly declares FooBar somewhere, then the generated name would need to be something like FooBar2.

    TypeScript supports nested interfaces and it's made JSON Schema codegen much easier. Hopefully TypedDict can adopt some of that flexibility

  4. mahmoudajawad commented on May 19, 2022

    @mahmoudajawad

    Does this require a PEP or it can be implemented on mypy as feature?

  5. JelleZijlstra commented on May 19, 2022

    @JelleZijlstra
    Member

    Mypy can implement its own extensions if it wants, but standardizing this feature would require a new PEP.

  6. JukkaL commented on May 19, 2022

    @JukkaL
    Collaborator

    TypedDict({'foo': int, 'bar': bool}) generates a TypeError at runtime. To properly support this we'd need changes to typing.TypedDict.

  7. erictraut commented on May 19, 2022

    @erictraut

    If you want to pursue this, I recommend discussing it in the typing-sig email list or the python/typing discussion forum. It deserves broader discussion and input before it is implemented in type checkers.

  8. jetzhou commented on May 20, 2022

    @jetzhou
    Author

    @erictraut, @JelleZijlstra thanks for the pointers! I'm glad that this thread/feature is gaining some traction here. I have some time on my hands so I will look into starting the discussion on the mailing list and the PEP process. Will update here if it gets somewhere. Thanks all!

  9. andersk commented on Jul 21, 2022

    @andersk
    Contributor

    We could represent an anonymous TypedDict with an empty string for the name: TypedDict("", {'foo': int, 'bar': bool}). This already works at runtime.

  10. emmatyping commented on Sep 15, 2022

    @emmatyping
    Member

    I suggest we close this in favor of discussing this on typing-sig and having it standardized in a PEP, I'd love to see something like {'foo': int} be shorter syntax for an anonymous TypedDict, similar to how we now allow | for union, dict from builtins etc.

  11. michaeloliverx commented on May 19, 2023

    @michaeloliverx

    It would be great if type checkers were able to infer the return types of functions as typed dicts if returning literal dicts e.g.

    def foo():
        return {
            "foo: "bar"
        }

    TypeScript shines in this regard.

  12. DetachHead commented on Apr 6, 2024

    @DetachHead
    Contributor

    pyright supports this using the following syntax:

    foo: dict[{"a": str}] = {"a": "asdf"}
  13. MrLoh commented on Apr 18, 2024

    @MrLoh

    @DetachHead is this pyright feature documented somewhere?

  14. DetachHead commented on Apr 18, 2024

    @DetachHead
    Contributor

    it's an experimental feature that i don't think is documented anywhere except this thread python/typing#1391

  15. erictraut commented on Apr 18, 2024

    @erictraut

    Yes, this feature is experimental in pyright. The idea was discussed in a typing forum thread, but it was never fully fleshed out in a specification. I will likely remove from pyright since no one has stepped up to write a PEP.

    If you would like to see such a feature added to the type system, please consider starting a new thread in the typing forum, gather feedback and ideas, and write a draft PEP.

  16. ilevkivskyi commented on Jul 7, 2024

    @ilevkivskyi
    Member

    An experimental support for this has been merged to master, you can play with it using --enable-incomplete-feature=InlineTypedDict.

  17. layday commented on Jul 7, 2024

    @layday

    Support for inline dicts was removed from Pyright in June, by the way, which seems rather unfortunate.

  18. erictraut commented on Jul 7, 2024

    @erictraut

    Yes, I removed support for this experimental feature because no one stepped up to formally specify it, and there was little or no feedback (positive or negative) from pyright users.

    If someone is interested in spearheading the formal specification for such a feature, the Python typing forum is a good place to start the discussion. This feature would require a PEP.

  19. JelleZijlstra commented on Jul 7, 2024

    @JelleZijlstra
    Member

    We had a typing meetup presentation from @sobolevn about this some time ago, and as I remember, there was significant disagreement over how inheritance should work, with the result that Nikita stopped pursuing the proposal.

  20. DetachHead commented on Jul 7, 2024

    @DetachHead
    Contributor

    this feature is still supported in basedpyright

  21. Viicos commented on Jan 29, 2025

    @Viicos
    Contributor

    For anyone interested, a PEP is under discussion here: https://discuss.python.org/t/78779.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions