Issue about Self type mismatch

code snippet as follow:

from typing import List, Self

class A:
    def __init__(self) -> None:
        self.a_instances: List[Self] = []

    def test(self):

then the type checker (Pyright) will raise an type mismatch between Self@A and A

AFAIK, type of A() is equal to Self and it should be. Is there something wrong?

That’s not true if you think of inheritance. If you have a class B that inherits A, B.test still appends an A which is not Self.


@gmdzy2010 Mypy is fine with your code. Perhaps it’s a Pyright issue.

Success: no issues found in 1 source file

This would make Self no more useful than hardcoding the name of the class (A) into the annotation (albeit more maintainable). Self is a lot more useful than that, and subclasses are specifically catered for.

Perhaps you meant that a=A(); a.test(B()) would fail type-checking?

from typing import Self, reveal_type

class Foo:
    def return_self(self) -> Self:
        return self

class SubclassOfFoo(Foo): pass

reveal_type(Foo().return_self())  # Revealed type is "Foo"
reveal_type(SubclassOfFoo().return_self())  # Revealed type is "SubclassOfFoo"
1 Like

I put pyright self into a search engine and got a Stack Overflow Q&A off the top that seems to be exactly the question of this thread:

1 Like

It really helps me thanks a lot!

@JamesParrott I suppose that’s mypy’s fault, because I don’t think the code is valid. Consider

class B(A): 

b = B()


instance = b.a_instances[0]

What is this type? The a_instances is declared as list[Self], so the result should be a Self, i.e, B. But test() appends an A.

Your example doesn’t demonstrate what I’m talking about. This method return self, so the annotation is -> Self as it should be. What I’m talking about is more akin to this:

class Foo:
    def return_self(self):
        return Foo()

You can’t annotate this method -> Self, because it returns Foo which is not Self for child classes. Similarly for OP’s question, they don’t return but append to a list, but it’s the same idea.

I don’t know anything about Self, or what you mean A to be, but unless you expect A to be a tree, I would expect a_instances to be a class attribute.

class A:
    a_instances: List[Self] = []

    def __init__(self) -> None:

If you do want an instance attribute to make a tree or more general graph, children would be a more usual name.

1 Like

I don’t find “correctness” in the form of Python type hinting to be half as helpful, as it is in the form of using ABCs or protocols, from the point of view of expressing intent. And even then, I don’t think correctness is particularly helpful at all. Python’s a dynamic language… But my opinion for what it’s worth:

The programmer can both choose to hardcode .test to append A(), and type hint a_instances as Self with no harm done, only if they do not support, and never intend to subclass A.

If they want to subclass A (or if A is part of a library API, whose downstream users may quite reasonably wish to subclass A), and append subclasses of A, then yes indeedy, they should either change the annotation to List[A], or .test should append self.__class__() instead (if the Self type hint is to be kept).

Alternatively, perhaps the whole point of .test is that the test fails for subclasses? If that’s what the programmer wants to test, then Python allows that.

1 Like