web-dev-qa-db-fra.com

Annotation MyPy pour l'instance de retour de méthode de classe

Comment annoter un @classmethod qui renvoie une instance de cls? Voici un mauvais exemple:

class Foo(object):
    def __init__(self, bar: str):
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls, bar: str) -> ???:
        return cls(bar + "stuff")

Cela renvoie un Foo mais retourne plus précisément la sous-classe de Foo qui est appelée, donc annoter avec -> "Foo" ne serait pas assez bon.

20
taway

L'astuce consiste à ajouter explicitement une annotation au paramètre cls, en combinaison avec TypeVar, pour génériques , et Type, à représente une classe plutôt que l'instance elle-même , comme ceci:

from typing import TypeVar, Type

# Create a generic variable that can be 'Parent', or any subclass.
T = TypeVar('T', bound='Parent')

class Parent:
    def __init__(self, bar: str) -> None:
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls: Type[T], bar: str) -> T:
        # We annotate 'cls' with a typevar so that we can
        # type our return type more precisely
        return cls(bar + "stuff")

class Child(Parent):
    # If you're going to redefine __init__, make sure it
    # has a signature that's compatible with the Parent's __init__,
    # since mypy currently doesn't check for that.

    def child_only(self) -> int:
        return 3

# Mypy correctly infers that p is of type 'Parent',
# and c is of type 'Child'.
p = Parent.with_stuff_appended("10")
c = Child.with_stuff_appended("20")

# We can verify this ourself by using the special 'reveal_type'
# function. Be sure to delete these lines before running your
# code -- this function is something only mypy understands
# (it's meant to help with debugging your types).
reveal_type(p)  # Revealed type is 'test.Parent*'
reveal_type(c)  # Revealed type is 'test.Child*'

# So, these all typecheck
print(p.bar)
print(c.bar)
print(c.child_only())

Normalement, vous pouvez laisser cls (et self) sans annotation, mais si vous devez vous référer à la sous-classe spécifique, vous pouvez ajouter un annotation explicite . Notez que cette fonctionnalité est encore expérimentale et peut être boguée dans certains cas. Vous devrez peut-être également utiliser la dernière version de mypy clonée à partir de Github, plutôt que ce qui est disponible sur pypi - je ne me souviens pas si cette version prend en charge cette fonctionnalité pour les méthodes de classe.

29
Michael0x2a

Juste pour être complet, dans Python 3.7 vous pouvez utiliser le postponed evaluation of annotations comme défini dans PEP56 en important from __future__ import annotations au début du fichier.

Ensuite, pour votre code, il ressemblerait

from __future__ import annotations

class Foo(object):
    def __init__(self, bar: str):
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls, bar: str) -> Foo:
        return cls(bar + "stuff")
6