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
yuser_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?