Skip to content

Implement support for PEP 764 (inline typed dictionaries) #579

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

Closed
wants to merge 2 commits into from

Conversation

Viicos
Copy link
Contributor

@Viicos Viicos commented Apr 6, 2025

Fixes #572.

As discussed in the PEP thread, I've tried implementing inlined typed dictionaries as instances of a new InlinedTypedDict class, that would look like:

class InlinedTypedDict:
    def __init__(self, fields):
        required_keys = set()
        optional_keys = set()
        readonly_keys = set()
        mutable_keys = set()

        for ann_key, ann_type in fields.items():
            qualifiers = set(_get_typeddict_qualifiers(ann_type))

            if NotRequired not in qualifiers:
                required_keys.add(ann_key)
            else:
                optional_keys.add(ann_key)

            if ReadOnly in qualifiers:
                readonly_keys.add(ann_key)
            else:
                mutable_keys.add(ann_key)

        assert required_keys.isdisjoint(optional_keys), (
            f"Required keys overlap with optional keys:"
            f" {required_keys=}, {optional_keys=}"
        )

        self.__required_keys__ = frozenset(required_keys)
        self.__optional_keys__ = frozenset(optional_keys)
        self.__readonly_keys__ = frozenset(readonly_keys)
        self.__mutable_keys__ = frozenset(mutable_keys)
        self.__total__ = True

    def __call__(self, *args, **kwargs):
        return dict(*args, **kwargs)

But I don't think it is worth the effort:

  • We still need to set __mro_entries__ on InlinedTypedDict to allow subclassing inlined TDs. To be compatible with the existing logic in _TypedDictMeta.__new__, we would most likely need to create an inner "real" TypedDict class matching the InlinedTypedDict instance.
  • Although the logic InlinedTypedDict.__init__() is simpler as it is guaranteed to not have base classes, logic is still duplicated in two places.

Having "<inlined TypedDict>" as a __name__ might be considered as a slightly ugly implementation detail, but considering the above it is worth the trade-off.

# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module
ns = {'__annotations__': dict(fields)}
module = _caller(depth=3)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be great to have _caller() match the upstream, in particular having python/cpython#99520. Let me know and I'll send a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please do! Seems like it would have to be version-guarded.

@Viicos Viicos force-pushed the pep-764 branch 2 times, most recently from c26d37d to 09bfecc Compare April 6, 2025 18:40
@JelleZijlstra JelleZijlstra self-requested a review April 6, 2025 20:04
@brianschubert
Copy link
Contributor

brianschubert commented Apr 6, 2025

I only just picked up on the fact that the PEP uses the term inlined TypeDict; somehow I've been reading that as inline TypedDict this whole time (possible because that's the term the mypy extension used?).

The term makes sense to me when I think about it, but admittedly when I was toying with this locally and saw <class '__main__.<inlined TypedDict>'> in the output, my brain jumped to interpreting it as something like "there was a TypedDict here, but it got inlined away / converted into something else", sort of like an like an "inlined function" compiler optimization. Not that that interpretation would make sense here, but it makes me wonder if a term like <anonymous TypedDict> might be clearer.

@JelleZijlstra
Copy link
Member

"Inlined" does read wrong to me, I'd also expect that to have something to do with function inlining. "Inline" or "anonymous" seems better.

(Sorry no review of the code yet.)

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a couple of high-level comments!

extra_items=NoExtraItems,
**kwargs
):
class TypedDict(metaclass=_TypedDictMeta):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it's necessary to make TypedDict a class so that you can subscript it? What about making it an instance of a _SpecialForm subclass, similar to how Annotated is implemented in CPython?

I doubt there's much difference between the two in terms of the user-facing behaviour, but conceptually it sort-of feels "wrong" for TypedDict to be a class. You use TypedDict to create types; it is not in itself a type; calling TypedDict does not create an "instance of TypedDict"; x: TypedDict is an invalid type annotation; etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give it a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened #580 to compare both.

@Viicos
Copy link
Contributor Author

Viicos commented Apr 7, 2025

"Inlined" does read wrong to me, I'd also expect that to have something to do with function inlining. "Inline" or "anonymous" seems better.

(Sorry no review of the code yet.)

I think this is a good point, I'll switch to inline in the PEP document.

@Viicos Viicos changed the title Implement support for PEP 764 (inlined typed dictionaries) Implement support for PEP 764 (inline typed dictionaries) Apr 7, 2025
@Viicos
Copy link
Contributor Author

Viicos commented Apr 23, 2025

Closing as #579 seems to be the approach we'll stick to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

support inline TypedDicts
4 participants