48 votes

python circulaire importations de nouveau (aka quel est le problème avec cette conception)

Considérons python (3.x) les scripts:

main.py:

from test.team import team
from test.user import user

if __name__ == '__main__':
    u = user()
    t = team()
    u.setTeam(t)
    t.setLeader(u)

test/user.py:

from test.team import team

class user:
    def setTeam(self, t):
        if issubclass(t, team.__class__):
            self.team = t

test/team.py:

from test.user import user

class team:
    def setLeader(self, u):
        if issubclass(u, user.__class__):
            self.leader = u

Maintenant, bien sûr, j'ai importation circulaire et splendide ImportError.

Alors, pas pythonista, j'ai trois questions. Tout d'abord:

j'. Comment puis-je faire de cette chose que le travail ?

Et, sachant que quelqu'un va inévitablement dire "Circulaire importations de toujours indiquer un problème de conception", la deuxième question qui me vient:

ii. Pourquoi est-ce la conception de mauvais?

Et enfin, la troisième:

iii. Ce serait une meilleure alternative?

Pour être précis, la vérification du type que ci-dessus n'est qu'un exemple, il y a aussi un indice de la couche en fonction de la classe, qui permet à ie. trouver tous les utilisateurs qui sont membres d'une équipe (l'utilisateur de la classe a de nombreuses sous-classes, de sorte que l'indice est doublé, pour les utilisateurs en général et pour chaque sous-classe spécifique) ou toutes les équipes ayant donné utilisateur en tant que membre

Edit:

J'espère que de plus amples exemple permettra de clarifier ce que je cherche à atteindre. Fichiers omis pour la lisibilité (mais en avoir une de 300 ko fichier source qui me fait peur en quelque sorte, veuillez donc supposer que chaque classe est différente de fichier)

# ENTITY

class Entity:
    _id    = None
    _defs  = {}
    _data  = None

    def __init__(self, **kwargs):
        self._id   = uuid.uuid4() # for example. or randint(). or x+1.
        self._data = {}.update(kwargs)

    def __settattr__(self, name, value):
        if name in self._defs:
            if issubclass(value.__class__, self._defs[name]):
                self._data[name] = value

                # more stuff goes here, specially indexing dependencies, so we can 
                # do Index(some_class, name_of_property, some.object) to find all   
                # objects of some_class or its children where
                # given property == some.object

            else:
                raise Exception('Some misleading message')
        else:
            self.__dict__[name] = value    

    def __gettattr__(self, name):
        return self._data[name]

# USERS 

class User(Entity):
    _defs  = {'team':Team}

class DPLUser(User):
    _defs  = {'team':DPLTeam}

class PythonUser(DPLUser)
    pass

class PerlUser(DPLUser)
    pass

class FunctionalUser(User):
    _defs  = {'team':FunctionalTeam}

class HaskellUser(FunctionalUser)
    pass

class ErlangUser(FunctionalUser)
    pass

# TEAMS

class Team(Entity):
    _defs  = {'leader':User}

class DPLTeam(Team):
    _defs  = {'leader':DPLUser}

class FunctionalTeam(Team):
    _defs  = {'leader':FunctionalUser}

et maintenant, certains d'utilisation:

t1 = FunctionalTeam()
t2 = DLPTeam()
t3 = Team()

u1 = HaskellUser()
u2 = PythonUser()

t1.leader = u1 # ok
t2.leader = u2 # ok
t1.leader = u2 # not ok, exception
t3.leader = u2 # ok

# now , index

print(Index(FunctionalTeam, 'leader', u2)) # -> [t2]
print(Index(Team, 'leader', u2)) # -> [t2,t3]

Donc, il fonctionne très bien (détails de mise en œuvre oublié, mais il n'y a rien de compliqué) en plus de ce qui est profane importation circulaire chose.

82voto

bobince Points 270740

Circulaire importations ne sont pas en soi une mauvaise chose. Il est naturel pour l' team code de compter sur user , tandis que l' user fait quelque chose avec l' team.

Le pire pratique ici est - from module import member. L' team module est d'essayer d'obtenir l' user classe à l'importation, et l' user module est d'essayer d'obtenir l' team classe. Mais l' team classe n'existe pas encore parce que vous êtes encore à la première ligne de l' team.py lorsque user.py est exécuté.

Au lieu de cela, importer uniquement les modules. Il en résulte plus claire namespacing, rend plus tard, le singe de correction possible, et résout le problème d'importation. Parce que vous êtes seulement de l'importation du module à l'importation-temps, vous ne vous souciez pas de la classe à l'intérieur, il n'est pas encore définie. Par le temps de vous promener à l'aide de la classe, il sera.

Donc, test/users.py:

import test.teams

class User:
    def setTeam(self, t):
        if isinstance(t, test.teams.Team):
            self.team = t

test/teams.py:

import test.users

class Team:
    def setLeader(self, u):
        if isinstance(u, test.users.User):
            self.leader = u

from test import teams puis teams.Team est également OK, si vous voulez écrire test de moins. C'est encore l'importation d'un module un module de membre.

Aussi, si Team et User sont relativement simples, les mettre dans le même module. Vous n'avez pas besoin de suivre le code Java de la classe-par-fichier de l'idiome. L' isinstance tests et set ailleurs, les méthodes de crier unpythonic-Java-verrue à moi, selon ce que vous êtes en train de faire peut très bien être mieux à l'aide d'une plaine, non-type-vérifié @property.

3voto

snapshoe Points 3455

j'. Pour le faire fonctionner, vous pouvez utiliser un différé d'importation. Un autre moyen serait de laisser user.py seul et changement team.py pour:

class team:
    def setLeader(self, u):
        from test.user import user
        if issubclass(u, user.__class__):
            self.leader = u

iii. Pour une alternative, pourquoi ne pas mettre l'équipe et de l'utilisateur de classes dans le même fichier?

2voto

knitti Points 3258

Les mauvaises pratiques/malodorantes sont les choses suivantes:

  • Probaly inutile la vérification du type (voir aussi ici). Juste utiliser les objets que vous obtenez comme un utilisateur/une équipe et lever une exception (ou dans la plupart des cas, on est soulevée sans avoir besoin de code supplémentaire) quand il se casse. Laissez cette distance, et vous circulaire importations en aller (au moins pour l'instant). Aussi longtemps que les objets que vous obtenez de se comporter comme un utilisateur / une équipe, ils pourraient être n'importe quoi. (Duck-Typing)
  • minuscules classes (c'est plus ou moins une question de goût, mais la norme acceptée (PEP 8) le fait différemment
  • setter où pas nécessaire: vous venez de dire: my_team.leader=user_b et user_b.team=my_team
  • des problèmes avec la cohérence des données: si (my_team.leader.team!=my_team)?

0voto

jmlopez Points 2040

Voici quelque chose que je n'ai pas encore vu. Est-ce une mauvaise idée/de la conception à l'aide de sys.modules directement? Après la lecture de @bobince solution, j'ai pensé que j'avais compris opérations d'importation, mais ensuite, j'ai rencontré un problème semblable à une question qui renvoie à celui-ci.

Voici une autre de prendre sur la solution:

# main.py
from test import team
from test import user

if __name__ == '__main__':
    u = user.User()
    t = team.Team()
    u.setTeam(t)
    t.setLeader(u)

# test/team.py
from test import user

class Team:
    def setLeader(self, u):
        if isinstance(u, user.User):
            self.leader = u

# test/user.py
import sys
team = sys.modules['test.team']

class User:
    def setTeam(self, t):
        if isinstance(t, team.Team):
            self.team = t

et le fichier test/__init__.py fichier vide. La raison pour laquelle cela fonctionne est parce qu' test.team est importé en premier. Le moment python est de l'import ou de la lecture d'un fichier, il ajoute le module sys.modules. Quand nous importons test/user.py le module test.team sera déjà défini puisque nous sommes à l'importation en main.py.

Je commence à aimer cette idée pour les modules qui poussent assez grande mais il y a des fonctions et des classes qui dépendent les uns des autres. Laisse supposer qu'il y a un fichier appelé util.py et ce fichier contient de nombreuses classes qui dépendent les uns des autres. Peut-être on pourrait diviser le code entre les différents fichiers qui dépendent l'un de l'autre. Comment pouvons-nous obtenir autour de la circulaire d'importation?

Eh bien, en util.py le fichier de nous il suffit d'importer tous les objets de l'autre "privé" des fichiers, je dis privé puisque ces fichiers ne sont pas destinés à être accessible directement, au lieu de nous d'y accéder via le fichier d'origine:

# mymodule/util.py
from mymodule.private_util1 import Class1
from mymodule.private_util2 import Class2
from mymodule.private_util3 import Class3

Puis sur chacun des autres fichiers:

# mymodule/private_util1.py
import sys
util = sys.modules['mymodule.util']
class Class1(object):
    # code using other classes: util.Class2, util.Class3, etc

# mymodule/private_util2.py
import sys
util = sys.modules['mymodule.util']
class Class2(object):
    # code using other classes: util.Class1, util.Class3, etc

L' sys.modules appel travailler aussi longtemps que l' mymodule.util est tenté d'être importés en premier.

Enfin, je me contenterai de souligner que ce qui est fait pour aider les utilisateurs à la lisibilité (fichiers plus courts) et donc je ne dirais pas que la circulaire importations sont "intrinsèquement" mauvais. Tout aurait pu être fait dans le même fichier, mais nous sommes à l'aide de sorte que nous pouvons séparer le code et de ne pas confondre-mêmes tout en faisant défiler l'énorme fichier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X