web-dev-qa-db-fra.com

Comment spécifier que le type de retour d'une méthode est identique à la classe elle-même?

J'ai le code suivant dans python 3:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

Mais mon éditeur (PyCharm) dit que la position de référence ne peut pas être résolue (dans la méthode __add__). Comment dois-je spécifier que le type de retour attendu est de type Position?

Edit: Je pense que ceci est en fait un problème de PyCharm. Il utilise en fait les informations dans ses avertissements et la complétion de code

Mais corrigez-moi si je me trompe et que je dois utiliser une autre syntaxe.

228

TL; DR : si vous utilisez Python 4.0, cela fonctionne. À compter d'aujourd'hui (2019) dans la version 3.7+, vous devez activer cette fonctionnalité à l'aide d'une instruction future (from __future__ import annotations) - pour Python 3.6 ou inférieur, utilisez une chaîne.

Je suppose que vous avez cette exception:

NameError: name 'Position' is not defined

En effet, Position doit être défini avant de pouvoir l'utiliser dans une annotation, à moins que vous n'utilisiez Python 4.

Python 3.7+: from __future__ import annotations

Python 3.7 introduit PEP 563: évaluation différée des annotations . Un module qui utilise l'instruction future from __future__ import annotations stockera automatiquement les annotations sous forme de chaînes:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Ceci est programmé pour devenir la valeur par défaut dans Python 4.0. Étant donné que Python est toujours un langage typé de manière dynamique, aucune vérification de type n'est effectuée au moment de l'exécution, les annotations de frappe ne doivent pas avoir d'impact sur les performances, n'est-ce pas? Faux! Avant python 3.7, le module de typage était l'un des modules python les plus lents du noya so si vous import typing vous verrez performances multipliées par 7 lors de la mise à niveau vers la version 3.7.

Python <3.7: utiliser une chaîne

Selon PEP 484 , vous devriez utiliser une chaîne au lieu de la classe elle-même:

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

Si vous utilisez le framework Django, il se peut que les modèles Django utilisent également des chaînes de caractères pour les références en aval (définitions de clé étrangère où le modèle étranger est self ou n'est pas encore déclaré ). Cela devrait fonctionner avec Pycharm et d'autres outils.

Sources

Les parties pertinentes de PEP 484 et PEP 56 , pour vous épargner le voyage:

Références en avant

Lorsqu'un indice de type contient des noms qui n'ont pas encore été définis, cette définition peut être exprimée sous la forme d'un littéral de chaîne, à résoudre ultérieurement.

Une situation dans laquelle cela se produit généralement est la définition d'une classe de conteneur, où la classe en cours de définition se trouve dans la signature de certaines des méthodes. Par exemple, le code suivant (le début d'une implémentation d'arborescence binaire simple) ne fonctionne pas:

class Tree:
    def __init__(self, left: Tree, right: Tree):
    self.left = left
    self.right = right

Pour résoudre ce problème, nous écrivons:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

Le littéral de chaîne doit contenir une expression Python valide (c'est-à-dire que compile (allumé, '', 'eval') doit être un objet code valide) et doit être évalué sans erreur une fois le module entièrement chargé. Les espaces de noms local et global dans lesquels il est évalué doivent être les mêmes espaces de noms dans lesquels les arguments par défaut de la même fonction seront évalués.

et PEP 563:

Dans Python 4.0, les annotations de fonction et de variable ne seront plus évaluées au moment de la définition. Au lieu de cela, une forme de chaîne sera conservée dans le dictionnaire __annotations__ correspondant. Les vérificateurs de type statiques ne verront aucune différence de comportement, tandis que les outils utilisant des annotations au moment de l'exécution devront effectuer une évaluation différée.

...

La fonctionnalité décrite ci-dessus peut être activée à partir de Python 3.7 à l'aide de l'importation spéciale suivante:

from __future__ import annotations

Des choses que vous pourriez être tenté de faire à la place

A. Définir un mannequin Position

Avant la définition de classe, placez une définition factice:

class Position(object):
    pass


class Position(object):
    ...

Cela éliminera la NameError et vous aurez peut-être l'air OK:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

Mais est-ce?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

B. Monkey-patch pour ajouter les annotations:

Vous voudrez peut-être essayer la magie de la méta programmation Python et écrire à un décorateur pour qu'il corrige la définition de la classe afin d'ajouter des annotations:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

Le décorateur devrait être responsable de l'équivalent de ceci:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

Au moins cela semble juste:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

Probablement trop de problèmes.

Conclusion

Si vous utilisez la version 3.6 ou une version antérieure, utilisez un littéral de chaîne contenant le nom de la classe. Sous 3.7, utilisez from __future__ import annotations et tout fonctionnera.

339
Paulo Scardine

Le nom 'Position' n'est pas disponible au moment où le corps de la classe est analysé. Je ne sais pas comment vous utilisez les déclarations de type, mais le PEP 484 de Python - ce que la plupart des modes devraient utiliser si vous utilisez ces astuces de frappe, indiquez que vous pouvez simplement mettre le nom sous forme de chaîne à ce stade:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Check https://www.python.org/dev/peps/pep-0484/#forward-references - les outils conformes à celui-ci sauront décompresser le nom de la classe à partir de là et l'utiliser. (Il est toujours important de garder à l'esprit que le langage Python ne fait rien en soi. Ces annotations sont généralement destinées à l'analyse de code statique, ou on pourrait avoir une bibliothèque/un cadre pour la vérification de type en mode d'exécution. temps - mais vous devez définir explicitement cela)

11
jsbueno

Spécifier le type en tant que chaîne convient, mais vous me permettez toujours de comprendre que nous sommes en train de contourner l'analyseur. Donc, vous feriez mieux de ne pas mal orthographier l'une de ces chaînes littérales:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Une légère variation consiste à utiliser un typevar lié, au moins vous devez écrire la chaîne une seule fois lors de la déclaration du typevar:

from typing import TypeVar

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

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)
8
vbraun