Skip to content

Enable subscripting generic types in annotations. #461

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

udifuchs
Copy link
Contributor

Some classes like lxml.etree.ElementTree are generic types that can contain different kinds of elements. The type of these elements has to be specified in type annotations. For example:

element_tree: lxml.etree.ElementTree[lxml.etree.Element]

This requires adding a class_getitem class method to these class, as specified in PEP 560. This commit adds these class methods.

The list of generic types in lxml was taken from:

https://github.com/abelcheung/types-lxml/wiki/Using-specialised-class-directly

This PR is a reincarnation of PR #401. The difference is that now there is no reference to private classes anymore.

Some classes like lxml.etree.ElementTree are generic types that can contain
different kinds of elements. The type of these elements has to be specified
in type annotations. For example:

element_tree: lxml.etree.ElementTree[lxml.etree.Element]

This requires adding a __class_getitem__ class method to these class, as
specified in PEP 560. This commit adds these class methods.

The list of generic types in lxml was taken from:

https://github.com/abelcheung/types-lxml/wiki/Using-specialised-class-directly
@scoder
Copy link
Member

scoder commented Apr 26, 2025

Hmm, is it still a good idea to return a string from __class_getitem__, now that we have types.GenericAlias? Admittedly, that's only available from Py3.9 onwards, but it's probably ok to return strings (or anything, really) in Py3.8 and use GenericAlias for everything since.

@udifuchs
Copy link
Contributor Author

I will look into the GenericAlias and update this PR.

Make ElementTree a subclass of typing.Generic instead of adding
a __class_getattr__ method.

For the other scriptable classes, subclassing Generic conflicts with cython,
so we stick to __class_getattr__ returning GenericAlias.

For testing, check the return values of typing.get_origin and typing.get_args.
These functions seem to be the cannonical consumers of this data.
@scoder
Copy link
Member

scoder commented May 2, 2025

Rather than doing a version check on each use, in all __class_getitem__ methods, I'd use something like

cdef object _GenericAlias  # only in .pyx files, to store it outside of the module dict
try:
    from types import GenericAlias as _GenericAlias
except ImportError:
    # Python 3.8 - we only need this as return value from "__class_getitem__"
    def _GenericAlias(cls, item):
        return f"{cls.__name__}[{item.__name__}]"

EDIT: In general, I prefer code that prefers the future proof solution and makes the fallback easy to remove. At some point, the fallback will be removed and that works best if it touches little to nothing of the remaining implementation.

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.

2 participants