web-dev-qa-db-fra.com

Allusion de type Python sans importations cycliques

J'essaye de diviser mon immense classe en deux; Eh bien, fondamentalement, dans la classe "principale" et un mixin avec des fonctions supplémentaires, comme suit:

# main.py
import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...


# mymixin.py
class MyMixin(object):
    def func2(self: Main, xxx):  # <--- note the type hint
        ...

Maintenant, alors que cela fonctionne très bien, l'indicateur de type dans MyMixin.func2 ne peut bien sûr pas fonctionner. Je ne peux pas importer main.py, car j'obtiendrais une importation cyclique et sans indice, mon éditeur (PyCharm) ne peut pas dire ce que self est.

Utilisation de Python 3.4, souhaitant passer à la version 3.5 si une solution est disponible.

Est-il possible de diviser ma classe en deux fichiers et de conserver toutes les "connexions" de sorte que mon IDE m'offre toujours l'auto-complétion et tous les autres avantages qui en découlent en connaissant les types?

27
velis

J'ai bien peur qu'il n'y ait pas de manière extrêmement élégante de gérer les cycles d'importation. Vous avez le choix entre redéfinir votre code pour supprimer la dépendance cyclique ou, si cela n’est pas réalisable, procédez comme suit:

# some_file.py

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    def func2(self, some_param: 'Main'):
        ...

La constante TYPE_CHECKING étant toujours False au moment de l'exécution, l'importation ne sera pas évaluée, mais mypy (et d'autres outils de vérification du type) évaluera le contenu de ce bloc.

Nous avons également besoin de transformer l'annotation de type Main en une chaîne, de manière efficace à la déclarer en aval puisque le symbole Main n'est pas disponible au moment de l'exécution.

Si vous utilisez Python 3.7+, nous pouvons au moins éviter de devoir fournir une annotation de chaîne explicite en tirant parti de PEP 563 :

# some_file.py

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    # Hooray, cleaner annotations!
    def func2(self, some_param: Main):
        ...

L'importation from __future__ import annotations transformera les indicateurs de type all en chaînes et ignorera leur évaluation. Cela peut contribuer à rendre notre code légèrement plus ergonomique.

Cela dit, l'utilisation de mixins avec mypy nécessitera probablement un peu plus de structure que ce que vous avez actuellement. Mypy recommande une approche c'est en gros ce que décrit deceze - pour créer un ABC dont vos classes Main et MyMixin héritent. Je ne serais pas surpris si vous deviez faire quelque chose de similaire pour rendre le vérificateur de Pycharm heureux.

49
Michael0x2a

Le plus gros problème est que vos types ne sont pas sains d’esprit pour commencer. MyMixin fait l'hypothèse codée en dur qu'il sera mélangé à Main, alors qu'il pourrait être mélangé à un nombre quelconque d'autres classes, auquel cas il serait probablement cassé. Si votre mixin est codé en dur pour être mélangé dans une classe spécifique, vous pouvez également écrire les méthodes directement dans cette classe au lieu de les séparer.

Pour faire cela correctement avec une frappe correcte, MyMixin devrait être codé contre une classe interface , ou une classe abstraite en langage Python:

import abc


class MixinDependencyInterface(abc.ABC):
    @abc.abstractmethod
    def foo(self):
        pass


class MyMixin:
    def func2(self: MixinDependencyInterface, xxx):
        self.foo()  # ← mixin only depends on the interface


class Main(MixinDependencyInterface, MyMixin):
    def foo(self):
        print('bar')
9
deceze

Il s'avère que ma tentative initiale était également proche de la solution. C'est ce que j'utilise actuellement:

# main.py
import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...


# mymixin.py
if False:
    from main import Main

class MyMixin(object):
    def func2(self: 'Main', xxx):  # <--- note the type hint
        ...

Notez l'import dans l'instruction if False qui n'est jamais importée (mais IDE le sait de toute façon) et en utilisant la classe Main en tant que chaîne car elle n'est pas connue à l'exécution.

1
velis

Je pense que le moyen idéal devrait être d'importer toutes les classes et dépendances dans un fichier (comme __init__.py) puis from __init__ import * dans tous les autres fichiers. 

Dans ce cas, vous êtes

  1. en évitant les références multiples à ces fichiers et classes et 
  2. de plus, il suffit d'ajouter une ligne dans chacun des autres fichiers et 
  3. le troisième serait le pycharm connaissant toutes les classes que vous pourriez utiliser.
0
TechJS