as the questions describes, I wanna type hint a self return , something like:
class A:
def foo(self) -> [what goes here?]:
# do something
return self
Things I already tried:
A ( adding from __future__ import annotations at the top ): this means the method returns an instantiated A() object, not necessarily self.Type[A] ( adding from typing import Type ): this means the method is returning is returning an un-instantiated A, which isnt remotely close to self.Self ( adding from typing_extensions import Self ): mypy gives an error:
Variable "typing_extensions.Self" is not valid as a type [valid-type]mypy(error)Things that might be of help: hovering over the method foo with no annotations of a return value, VScode hints shows - Self@A, i dont understand it but, this definitely differentiates between returning another instantiated class A() and returning self ... Thanks
I can't find any question that covers this closely, so will try to explain.
Well, probably this is the long form of "you can't, and you shouldn't".
Type checking aims to confirm that all functions are called with proper argument types and return expected types. I suggest to read PEP483 first to understand concept of type better. Suppose you have the following:
s1 = ''.join(['a', 'b', 'c'])
s2 = ''.join(['a', 'b', 'c'])
assert s1 is not s2
assert s1 == s2
(join to avoid optimization, but it's another story). Are they the same object? No, is not clearly states this (they have different memory addresses). But will s2 be acceptable whenever you want s1? Definitely yes. You will not create a function that operates only on s1 and checks this fact with is, right?
Now what is the difference between self as a reference to an exact object and self as any A instance? When we talk about type checking, all A instances are completely equivalent and indistinguishable. They have the same set of methods and attributes (including types). We can ask: "which type errors can be introduced or removed, if we explicitly declare object to be self instance and not just self type?" I really cannot think of any. If you want this for semantics, use docstring - types should not be abused for everything. self object is absolutely the same as any other A() instance for type checker.
Your first code sample is almost fine. Annotate the return type as A to tell that it returns an instance of class A, it will work for a final class:
class A:
def foo(self) -> A:
return self
However, this approach has a drawback (it is well explained in PEP673 about Self type):
class AChild(A):
pass
# mypy
reveal_type(AChild().foo()) # N: revealed type is "__main__.A"
If you create a new A in foo and return it, then this approach is perfect. If you return self - it is valid, but not precise. That's why we need Self type.
Self typeSelf type was introduced in PEP673, and was not supported by mypy at the time of writing this. (update: supported since mypy 1.0 released on Feb. 6, 2023) Your usage in 3rd example was perfectly valid and will work after implementation in type checkers (see 5-th code block in "Motivation" section of PEP).
Here's how you can use Self (assuming python>=3.11 not to bother with typing_extensions):
from typing import Self
class A:
def foo(self) -> Self:
return self
class AChild(A):
pass
# mypy
reveal_type(AChild().foo()) # N: revealed type is "__main__.AChild"
reveal_type(A().foo()) # N: revealed type is "__main__.A"
Self type without using itHowever, you can mimic Self accurately with a few lines of code (and python >= 3.7, AFAIR).
from typing import TypeVar
_Self = TypeVar('_Self', bound='A')
class A:
def foo(self: _Self) -> _Self:
return self
class AChild(A):
pass
# mypy
reveal_type(AChild().foo()) # N: revealed type is "__main__.AChild"
reveal_type(A().foo()) # N: revealed type is "__main__.A"
Now this works. All subclasses will return their class instance.
A simple way is to put the class name in the quotation: e.g:
class A:
def foo(self) -> 'A':
# do something
return self
In IDE(e.g. Pycharm) you can navigate to the class by ctl(or cmd in macos) and click 'A'. Also, when refactoring the code, e.g to rename the class A in Pycharm, the 'A' would be renamed as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With