Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you annotate return type when value is instance of cls?

Given a class with a helper method for initialization:

class TrivialClass:
    def __init__(self, str_arg: str):
        self.string_attribute = str_arg

    @classmethod
    def from_int(cls, int_arg: int) -> ?:
        str_arg = str(int_arg)
        return cls(str_arg)

Is it possible to annotate the return type of the from_int method?

I'v tried both cls and TrivialClass but PyCharm flags them as unresolved references which sounds reasonable at that point in time.

like image 756
Jonatan Avatar asked Aug 29 '16 11:08

Jonatan


People also ask

What is CLS function in Python?

cls accepts the class Person as a parameter rather than Person's object/instance. Now, we pass the method Person. printAge as an argument to the function classmethod . This converts the method to a class method so that it accepts the first parameter as a class (i.e. Person).

What is TypeVar?

In short, a TypeVar is a variable you can use in type signatures so you can refer to the same unspecified type more than once, while a NewType is used to tell the type checker that some values should be treated as their own type.


2 Answers

Use a generic type to indicate that you'll be returning an instance of cls:

from typing import Type, TypeVar

T = TypeVar('T', bound='TrivialClass')

class TrivialClass:
    # ...

    @classmethod
    def from_int(cls: Type[T], int_arg: int) -> T:
        # ...
        return cls(...)

Any subclass overriding the class method but then returning an instance of a parent class (TrivialClass or a subclass that is still an ancestor) would be detected as an error, because the factory method is defined as returning an instance of the type of cls.

The bound argument specifies that T has to be a (subclass of) TrivialClass; because the class doesn't yet exist when you define the generic, you need to use a forward reference (a string with the name).

See the Annotating instance and class methods section of PEP 484.


Note: The first revision of this answer advocated using a forward reference naming the class itself as the return value, but issue 1212 made it possible to use generics instead, a better solution.

As of Python 3.7, you can avoid having to use forward references in annotations when you start your module with from __future__ import annotations, but creating a TypeVar() object at module level is not an annotation. This is still true even in Python 3.10, which defers all type hint resolution in annotations.

like image 53
Martijn Pieters Avatar answered Oct 19 '22 19:10

Martijn Pieters


From Python 3.7 you can use __future__.annotations:

from __future__ import annotations


class TrivialClass:
    # ...

    @classmethod
    def from_int(cls, int_arg: int) -> TrivialClass:
        # ...
        return cls(...)

Edit: you can't subclass TrivialClass without overriding the classmethod, but if you don't require this then I think it's neater than a forward reference.

like image 10
rusheb Avatar answered Oct 19 '22 17:10

rusheb