What do the square brackets mean in this typing example?

ScreenResultType = TypeVar(“ScreenResultType”)

class Screen(Generic[ScreenResultType], Widget):

class ModalScreen(Screen[ScreenResultType]):

class MessageDialog(ModalScreen[bool | None]):

I am completely lost. Can anyone help to explain each square blanket pair (list?) means, especially the last one?

In short the last line just means a MessageDialogthat returns a value that can be a bool or None.

The dialog class probably has some method that returns the value selected. Without generics (what the square brackets signify) we would either need to define multiple versions of the dialog class, one for each type we want to return, or have a method that returns an untyped value such as Any.

Generics allows us to create and specify “variants” of the MessageBox that return specific types without declaring separate classes.

A simple and common example of these square brackets in typing would look like this: my_list: list[int] = [1, 2, 3]. In this case, you can read it as “list of ints”.

When I see ModalScreen[bool | None], my first instinct is to read it as “a ModalScreen of bool | None”. It’s not entirely obvious what that means yet, but it’s a good starting point! Maybe to someone more familiar with the code, it’s more obvious what it means to be a ModalScreen of bool | None.

After the [bool | None] was deleted, this module still can run without error. So, is it just a kind of type-hint syntax?

Yes, it’s syntax for type hints.

Thank you for confirming it. But I have another question. If these brackets are type-hint, why it shows “TypeError: <class ‘textual.screen.ModalScreen’> is not a generic class.” when the [ScreenResultType] was removed from the code below?

In python I believe type hints usually appear during variable declaration or parameter declaration and they appear after the full colon (:).

a:int = 0
names:list[str] = ["Jach", "Fong", "Tobias"]

def func(a:int,b:int) -> int:
    pass

 

In the above example, those are type hints and they shall be ignored by python.

On the other hand. What you declared above doesn’t seem to be a type hint declaration but you’re subclassing off of the parent class. It could be a way of creating a type that could be used as a type hint but in that case I believe its not being used for that. You’re actually subclass from an actual genetic type. That is not a type hint so it will raise errors in python if it evaluates to a wrong type. Soution: keep the generic part there.

If the [bool | None] can be removed without problem, why the [ScreenResultType] can’t? Is it because of Screen has multiple inheritance? If it’s then why the error message is not “TypeError: <class ‘textual.screen.ModalScreen’> is not a Widget class.”?

You should be able to remove [bool | None] and [ScreenResultType] from ModalScreen as long as you remove both. You can omit to supply a type argument when using a generic class but you can’t supply to a class that’s not generic which is what happens when you get the error.

Your example having three layers makes it a bit more complicated so I’ve removed Widget and only use Screen and ModalScreen below

from typing import Generic, TypeVar

ScreenResultType = TypeVar("ScreenResultType")

# Screen is generic and you *may* supply a type argument when using or not.
class Screen(Generic[ScreenResultType]):
    pass

# This should be fine (a linter/typechecker may warn but python should not)
class ModalScreen(Screen):
    pass

# This is also fine as Screen is generic
class ModalScreen2(Screen[bool | None]):
    pass

# Non-generic screen
class Screen2:
    pass

# This is fine, Screen2 isn't generic
class ModalScreen3(Screen2):
    pass

# This will error as your example since Screen2 isn't Generic and therefore can't be indexed with [] brackets.
class ModalScreen3(Screen2[bool | None]):
    pass
    

For your original code it’s not as clear as ModalScreen isn’t explicitly inheriting from Generic but instead get that from the Screen base class.

If this is code you’re writing yourself you can leave out Generic, the TypeVar and the brackets and it should work fine. If you need help understanding a specific framework using Generics somewhat like above providing more context would help explaining that further.

Thank you for explanation.

This error happens when both [bool | None] and [ScreenResultType] are removed.

These codes come from the packages of textual and textual_cogs. The syntax of an inherited class name with subscription in a class definition is new to me, and I am trying to understand its meaning and usage. Reading the related documents seems doesn’t help much. So far to now, it is still messy.

You can make a class generic using two methods in modern Python:


# New style generic (no explicit inheritance)

class MyClass[T]:
    def __init__(self, val: T):
        self.val = val

#Old style generic (inherit Generic)

T = TypeVar("T")
class MyClass2(Generic[T]):
    def __init__(self, val: T):
        self.val = val

Both of these create a placeholder type that show the class will take on the type passed to the val parameter. Basically anywhere in your class definition where you see T now will be replaced with int if val is an integer, or str if val is a string, etc.

Because the Class[Type] syntax requires a __class_getitem__ to be defined, you will get errors if you try and use that syntax on a class that hasn’t been made generic.

You can monkey patch it by just defining is as a noop, but it’s not a bad idea to use generics in more complicated structures, since it’s useful to know what the expected type of say, a container are.

I use the generics a ton for driver code that can attach to any implementation of a certain type. Which is what it looks like is happening here in this code.

Quoted from the Python library document:

“”"
26.1 typing — Support for type hints
Note: The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
“”"

The above mentions function and variable, no class. But IMHO that those square brackets as class description are still for type checking purpose and can be omitted safely.
such as:

from typing import Generic, TypeVar
T = TypeVar('T')
class Hoo(Generic[T], Foo, Goo):
    def __init__(self, value):
        Foo.__init__(self, value)
        Goo.__init__(self, value)
        self._hoo = value
    def hoo(self):
        print("I'm hoo")

class Koo(Hoo[str]):
    pass

works the same as:

class Hoo(Foo, Goo):
    def __init__(self, value):
        Foo.__init__(self, value)
        Goo.__init__(self, value)
        self._hoo = value
    def hoo(self):
        print("I'm hoo")

class Koo(Hoo):
    pass

I am not sure but it seems like a option list. i’m new to python so don’t follow this answer as an solution.