How is an instance attribute of the same type as the instance type-hinted?

How is an instance attribute of the same type as the instance type-hinted?

I am trying to assign a variable to an instance of a class such that the variable is of the same type. I want to use the instance itself in the construction of the variable. In order to be compatible with inheritance, I want to type hint it as Self rather than the class.

The following works:

class Foo:

    var: "Foo"

    def bar(self) -> None:
        self.var = self

However, var is marked as being of type Foo rather than Self. What I want is:

from typing import Self

class Foo:

    var: Self

    def bar(self) -> None:
        self.var = self

In this case, mypy gives me the following message:

error: Incompatible types in assignment (expression has type "Foo", variable has type "Self")  [assignment]

Clearly, self is not regarded as being of type Self but only of type Foo.

Answer

PEP 673 (ref Read more) introduced typing.Self in Python 3.11 to let you write:

from typing import Self

class Foo:
    def clone(self) -> Self:
        …

so that in subclasses clone() is recognized as returning the subclass type. But at the moment mypy only special‐cases Self in method signatures, not in attribute annotations at the class level. So when you write:

class Foo:
    var: Self

    def bar(self) -> None:
        self.var = self

mypy still treats Self in var: Self as “the current class” (i.e. Foo) and complains when you assign self (which it types as plain Foo) to something it thinks is Self (the unknown late‐bound type). It’s essentially a bug/limitation in mypy’s handling of late‐bound attribute annotations.

The pep has examples using TypeVar. I suggest combining that with generic to create a workaround.
E.g.,

from typing import Generic, TypeVar

T = TypeVar("T", bound="Foo")

class Foo(Generic[T]):
    var: T        # var is *exactly* whatever subclass type T is

    def bar(self: T) -> None:
        # now mypy knows `self` is of type T, and var is typed T,
        # so this assignment is safe
        self.var = self

Alternately, if you want a "quick fix", just annotate var with the class-name as you already did in your OP.

Enjoyed this article?

Check out more content on our blog or follow us on social media.

Browse more articles