It would be very fruitful for Python to have a typing.CustomType to annotate custom types. Like if I create a class named MyClass, I could annotate it like this:
from __future__ import annotations
from typing import CustomType
class MyClass:
...
def create_copy(self: CustomType[MyClass]) -> CustomType[MyClass]:
...
Because I hate this:
from __future__ import annotations
from typing import TypeVar
MyClassT = TypeVar("MyClassT", bound="MyClass")
class MyClass:
...
def create_copy(self: MyClassT) -> MyClassT:
...
The reason for CustomType to exist is to avoid that ugly and very confusing MyClassT = TypeVar("MyClassT", bound="MyClass") thing.
My idea of having a CustomType annotation is to replace that ugly and confusing MyClassT = TypeVar("MyClassT", bound="MyClass"). So it’s sort of like a syntactic sugar proposition.
I like to follow this principle: I hate code, I want as little of it as possible. And having a CustomType annotation would certainly reduce one line of code.
@guido, actually, the name is simply Self, not SelfType. That seems correct, since self (as a name of a self-reference by Python convention) is not actually a type, but just a reference to the current instance of a class.
Although, self: Self as in the PEP, seems superflous to me. I don’t know why do we need to annotate self in the first place. We all know that self is a reference to the current instance of a class. All noobs learn this when learning about Python classes. self is just self, we don’t need to annotate it. This is just creating bloat in the language we all love. But as a return value annotation, I really like the idea of having -> Self.
EDIT: But then again, if you think about it, the name Self might be improved. I’d rather see this thing be named Instance. That way, self: Instance doesn’t look so dull anymore (as does self: Self), and also having -> Instance as a return value annotation expresses more clearly that a method of a class is returning an instance of that class.
I understand, but this metadata should not transform software into bloatware. The metadata should make it explicit what thing does it represent. Having self: Self is like having cheese: Cheese, i.e., not adding any meaningful metadata to the cheese parameter.
Good day. What is this thing?
-cheese, sir.
I see. What type of cheese?
-Ah, now that would be Cheese, sir.
Yes. But what type of cheese is that?
-With all due respect, Cheese, sir.
(I can already see John Cleese in a Monty Python skit.)
@steven.daprano, you’re absolutely right. As I said, this is bloatware. You guys, just please wait for Python 3.11, and all of this will go away once from __future__ import annotations gets built in (hopefully!). Then we could annotate these things as @steven.daprano mentioned.
What is it that you are actually trying to accomplish?
If you want to annotate a method so that its return type is a given class you don’t need anything other than:
class Foo:
def foo(self) -> "Foo":
...
class Bar(Foo):
pass
Bar().foo() # To a type checker this is `Foo`
If you want to annotate a method so that it’s statically bound to its class and its return type is an instance of that class you need to use a type variable or Self:
TFoo = TypeVar("TFoo", bound="Foo")
class Foo:
def foo(self: TFoo) -> TFoo:
...
class Bar(Foo):
pass
Bar().foo() # To a type checker this is `Bar`
@layday, I see. But I’d like to clear something. I’d like to know what do you mean by statically bound to its class . Are you refering to a class method (i.e., a method decorated with @classmethod )?
from __future__ import annotations
class MyClass:
def create_copy(self: MyClass) -> MyClass:
...
And now take a look at this code:
from __future__ import annotations
class MyClass:
def create_copy(self) -> MyClass:
...
Both versions of the code (where the first has self: MyClass and the second has only self) succeded when analyzed by mypy. So why would some people create MyClassT = TypeVar("MyClassT", bound="MyClass"). Is it because those people don’t know about the from __future__ import annotations import?
Forget about the future import. It’s completely orthogonal to the type var usage. All from __future__ import annotations does is stringify annotations so you don’t have to quote them where they might be undefined at runtime, e.g. in the body of a class.
mypy infers the type of self as an instance of the class that the method is defined in. You would only annotate self if you want to tie it to another parameter or the return type of the method.
Do you understand the fundamentals of type variables? When you have a type var T in a function:
def foo(value: T) -> T:
...
Then what does that mean? It means that whatever type gets passed into the function is returned by the function. Whereas if you have, say:
def bar(value: int) -> int:
...
The output is independent from the input. Let’s see this in practice. Remember that bool is a subclass of int:
foo(1) # Return type is int
foo(True) # Return type is bool
bar(1) # Return type is int
bar(True) # Return type is int still!
Now apply the same concept to the methods of a class.
@layday, very well explained, thank you. Now I understand this. But I still oppose to the annotation name discussed previously being Self, because I find self: Self very idiotic and dull. I’d rather see it be named Instance, so then self: Instance doesn’t seem so idiotic and dull anymore.