Reputation: 328
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
Upvotes: 11
Views: 11584
Reputation: 7877
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.
Upvotes: 14
Reputation: 179
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.
Upvotes: 7