New addition: typing.CustomType

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.

Anyone interested in this idea?

I assume you want this in the context of subclassing otherwise there’s no reason to use a type var or annotate self. For that see Mailman 3 Thoughts on a typing.Self? - Typing-sig - python.org and the PEP draft at Self type draft PEP - Google Docs.

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.

@layday, yes, I want this in the context of subclassing. Do you think that this CustomType annotation might be regarded as a one honking great idea?

Looks you want “Self-Type”. Mailman 3 Self type PEP - Typing-sig - python.org

@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 think you should not do it in that way. Typing is more about the meta of your class, not actual class that you are currently working on.

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.)

You would not need to annotate self when using Self, please read the PEP. You would be able to replace your original example with:

from __future__ import annotations
from typing import Self

class MyClass:
    ...

    def create_copy(self) -> Self:
        ...

And in fact you already can in Pyright.

1 Like

I’m not a big user of typing, but I’m confused by this proposal. Isn’t
the idea of from __future__ import annotations to allow us to write:

class MyClass:
    def create_copy(self:MyClass) -> MyClass:
        ...

without needing a type alias? And if you don’t want the future import,
just quote the annotations to make them usable as a forward reference?

def create_copy(self:"MyClass") -> "MyClass":

What have I missed here?

@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, isn’t your second example the same as below?

from __future__ import annotations

class Foo:
    def foo(self: Foo) -> Foo:
        ...

class Bar(Foo):
    ...

Bar().foo()  # To a type checker this is also Foo

Yes or no?

No, here Bar().foo() is Foo. In my second example, Bar().foo() 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 )?

I’m not so advanced in Python annotations. So, some concepts are not yet clear to me.

Please take a look at this code:

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.

I have edited the draft of the PEP discussed above about adding Self to typing. The edit involves adding Instance to typing.

Here’s the link to it: Self type draft PEP - Google Docs