xticks barplot python design dependencies class-design python-import

python - barplot - pandas plot



Python circular importa una vez más(también conocido como lo que está mal con este diseño) (4)

Consideremos las secuencias de comandos de python (3.x):

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

Ahora, por supuesto, tengo importación circular y espléndido ImportError.

Entonces, al no ser pythonista, tengo tres preguntas. Ante todo:

yo. ¿Cómo puedo hacer que esto funcione?

Y, sabiendo que alguien inevitablemente dirá "Las importaciones circulares siempre indican un problema de diseño", la segunda pregunta viene:

ii. ¿Por qué este diseño es malo?

Y finalmente, el tercero:

iii. ¿Cuál sería la mejor alternativa?

Para ser precisos, la verificación de tipos como la anterior es solo un ejemplo, también hay una capa de índice basada en la clase, que permite, por ejemplo. Encontrar a todos los usuarios como miembros de un equipo (la clase de usuario tiene muchas subclases, por lo que el índice se duplica, para los usuarios en general y para cada subclase específica) o para todos los equipos que hayan dado usuario como miembro.

Editar:

Espero que un ejemplo más detallado aclare lo que intento lograr. Archivos omitidos para ser legibles (pero tener un archivo fuente de 300kb me asusta de alguna manera, así que supongamos que cada clase está en un archivo diferente)

# 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}

y ahora algo de uso:

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]

Por lo tanto, funciona muy bien (detalles de implementación omitidos, pero no hay nada complicado) además de esta cosa impía de importación circular.


Aquí hay algo que aún no he visto. ¿Es una mala idea / diseño usando sys.modules directamente? Después de leer la solución de @bobince, pensé que había entendido todo el negocio de importación, pero luego me encontré con un problema similar al de una question relacionada con este.

Aquí hay otra toma de la solución:

# 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

y el archivo de test/__init__.py está vacío. La razón por la que esto funciona es porque test.team se está importando primero. En el momento en que python está importando / leyendo un archivo, agrega el módulo a sys.modules . Cuando importamos test/user.py el módulo test.team ya estará definido, ya que lo estamos importando en main.py

Me está comenzando a gustar esta idea para módulos que crecen bastante pero hay funciones y clases que dependen unas de otras. Supongamos que hay un archivo llamado util.py y este archivo contiene muchas clases que dependen el uno del otro. Quizás podríamos dividir el código entre diferentes archivos que dependen el uno del otro. ¿Cómo salimos de la importación circular?

Bueno, en el archivo util.py simplemente importamos todos los objetos de los otros archivos "privados", digo privado ya que esos archivos no están destinados a ser accedidos directamente, sino que accedemos a ellos a través del archivo original:

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

Luego en cada uno de los otros archivos:

# 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

La llamada a sys.modules funcionará siempre que se intente primero importar mymodule.util .

Por último, solo señalaré que esto se está haciendo para ayudar a los usuarios con la legibilidad (archivos más cortos) y, por lo tanto, no diría que las importaciones circulares son "inherentemente" malas. Todo podría haberse hecho en el mismo archivo, pero estamos usando esto para poder separar el código y no confundirnos al desplazarnos por el enorme archivo.


Las importaciones circulares no son inherentemente una mala cosa. Es natural que el código del team dependa del user mientras el user hace algo con el team .

La peor práctica aquí es from module import member . El módulo de team está tratando de obtener la clase de user en el momento de la importación, y el módulo de user está tratando de obtener la clase de team . Pero la clase de team aún no existe porque todavía estás en la primera línea de team.py cuando se ejecuta user.py

En cambio, importa solo módulos. Esto da como resultado un espacio de nombres más claro, posibilita el parche posterior de mono y resuelve el problema de importación. Debido a que solo está importando el módulo en el momento de la importación, no le importa que la clase dentro de él aún no esté definida. Para cuando consigas usar la clase, será.

Entonces, prueba / 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 y luego from test import teams teams.Team también está bien, si desea escribir menos test . Eso sigue importando un módulo, no un miembro de módulo.

Además, si el Team y el User son relativamente simples, colóquelos en el mismo módulo. No es necesario seguir la expresión idiomática de una clase por archivo de Java. Las pruebas de isinstance y los métodos de set también me gritan unpythonic-Java-wart; Dependiendo de lo que esté haciendo, es posible que sea mejor utilizar una @property simple sin verificación de @property .


Mala práctica / apestoso son las siguientes cosas:

  • Comprobación de tipos innecesariamente probalia ( ver también aquí ). Solo usa los objetos que obtienes como si fueran un usuario / equipo y genera una excepción (o en la mayoría de los casos, uno se genera sin necesidad de código adicional) cuando se rompe. Deje esto, y sus importaciones circulares desaparecerán (al menos por ahora). Mientras los objetos que obtienes se comporten como un usuario / equipo, podrían ser cualquier cosa. ( Pato escribiendo )
  • clases en minúsculas (esto es más o menos una cuestión de gusto, pero el estándar aceptado generalmente ( PEP 8 ) lo hace de manera diferente
  • setter donde no es necesario: solo puedes decir: my_team.leader=user_b y user_b.team=my_team
  • problemas con la coherencia de los datos: ¿y si (my_team.leader.team!=my_team) ?

yo. Para que funcione, puede usar una importación diferida. Una forma sería dejar user.py solo y cambiar team.py a:

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

iii. Para una alternativa, ¿por qué no poner las clases de equipo y usuario en el mismo archivo?