Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

Python Class Subscription and __class_getitem__

Learn how Python classes customize square-bracket subscription with __class_getitem__, including generic-style parameterization and the boundary with instance subscription.

Levels: Advanced ~24 min read

Advanced

Python normally uses square brackets to subscribe to an object, but square brackets can also appear directly after a class name. This lesson explains how a class can customize that class-level subscription with __class_getitem__(). This is the protocol behind parameterized forms such as list[int] and related generic class syntax.

Give a class a subscription protocol

When Python evaluates Box[int], the object being subscribed is the class Box. If the class can use __class_getitem__(), Python can call that method to decide what the class subscription produces.

python
1
2
3
4
5
class Box:
    def __class_getitem__(cls, item):
        return (cls.__name__, item)

print(Box[int])

Box[int] calls Box.__class_getitem__(int), so the class decides what that subscription means.

The hook is already a class method

You do not need to write @classmethod above __class_getitem__(). Python treats this special method as a class method, so cls is the class being subscribed.

python
1
2
3
4
5
class Registry:
    def __class_getitem__(cls, key):
        return f"{cls.__name__}[{key.__name__}]"

print(Registry[str])

The first parameter is the class itself even though no @classmethod decorator is present.

Handle one or several type arguments

The expression inside the brackets can contain one value or a tuple of values. A generic class may therefore accept Pair[int, str] and normalize the received key before building its result.

python Multiple bracketed values arrive as one tuple, while a single value can be normalized into a one-item tuple.
1
2
3
4
5
6
7
8
9
class Pair:
    def __class_getitem__(cls, key):
        if not isinstance(key, tuple):
            key = (key,)
        names = tuple(item.__name__ for item in key)
        return f"{cls.__name__}[{', '.join(names)}]"

print(Pair[int, str])
print(Pair[bytes])

.join(names)}]" print(Pair[int, str]) print(Pair[bytes])

Return a GenericAlias for a generic-style class

For a custom class that follows Python’s generic-type model, __class_getitem__() can return a types.GenericAlias. This produces the same kind of parameterized runtime object used by built-in generic classes.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from types import GenericAlias

class Box:
    def __class_getitem__(cls, item):
        return GenericAlias(cls, item)

specialized = Box[int]
print(specialized)
print(specialized.__origin__ is Box)
print(specialized.__args__)

Returning GenericAlias gives Box[int] a parameterized runtime representation rather than an arbitrary application-specific value.

Keep class and instance subscription separate

Do not confuse __class_getitem__() with __getitem__(). The latter handles subscription of an instance. The former can handle subscription when the object being subscribed is itself a class.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Catalog:
    def __class_getitem__(cls, key):
        return f"class subscription: {cls.__name__}[{key.__name__}]"

    def __getitem__(self, key):
        return f"instance item: {key}"

print(Catalog[int])
catalog = Catalog()
print(catalog[0])

Catalog[int] uses the class-level hook, while catalog[0] uses the instance-level hook.

Inherited class subscription works through the subclass

Because the hook belongs to the class, a subclass can inherit it. The cls argument then identifies the subclass that was actually subscribed.

python
1
2
3
4
5
6
7
8
class Base:
    def __class_getitem__(cls, item):
        return f"{cls.__name__}[{item.__name__}]"

class Child(Base):
    pass

print(Child[float])

An inherited __class_getitem__ receives Child as cls when Child[float] is evaluated.

A metaclass can take a different path

Class subscription has one important boundary: if the class’s metaclass defines __getitem__(), that metaclass hook can handle the class subscription instead. In that situation __class_getitem__() is not necessarily called.

python
1
2
3
4
5
6
7
8
9
class Meta(type):
    def __getitem__(cls, key):
        return f"metaclass subscription: {cls.__name__}[{key.__name__}]"

class Product(metaclass=Meta):
    def __class_getitem__(cls, key):
        return "class hook"

print(Product[int])

The metaclass supplies __getitem__(), so that hook handles Product[int] instead of Product.__class_getitem__().

Connect the hook to generic type syntax

Python’s built-in generic classes use class subscription to represent parameterized types. User-defined generic classes can use the modern generic syntax taught in the Generic Type Parameters lesson; this lesson focuses on the lower-level class-subscription protocol and how __class_getitem__() participates in it.

python
1
2
3
alias = list[int]
print(alias)
print(type(alias).__name__)

Built-in list uses class subscription to produce a parameterized generic alias.

Choose the protocol deliberately

Use __class_getitem__() when a class needs meaningful square-bracket parameterization. For custom generic types, follow Python’s generic-type facilities rather than inventing an unrelated meaning for class subscription.

Key takeaways

  • __class_getitem__() handles subscription of a class when the subscription machinery selects the class-level hook.
  • Python supplies the subscribed class as cls automatically; no @classmethod decorator is required.
  • Multiple bracketed arguments arrive together, commonly as a tuple, so implementations can normalize one-versus-many arguments.
  • __getitem__() handles instance subscription, while __class_getitem__() handles a distinct class-subscription path.
  • A metaclass __getitem__() can take precedence for class subscription, which is an important boundary of the protocol.
Was this page helpful?

Mark this learning resource complete to track your learning progress.