¿Está bien tener varias clases en el mismo archivo en Python?
Sí. Tanto desde una perspectiva filosófica como desde la práctica.
En Python, los módulos son un espacio de nombres que existe una vez en la memoria.
Supongamos que tenemos la siguiente estructura de directorios hipotética, con una clase definida por archivo:
Defines
abc/
|-- callable.py Callable
|-- container.py Container
|-- hashable.py Hashable
|-- iterable.py Iterable
|-- iterator.py Iterator
|-- sized.py Sized
... 19 more
Todas estas clases están disponibles en el módulo collections
y (de hecho, hay un total de 25) definidas en el módulo de biblioteca estándar en _collections_abc.py
Hay un par de problemas aquí que creo que hacen que _collections_abc.py
sea superior a la estructura de directorio hipotética alternativa.
- Estos archivos están ordenados alfabéticamente. Puede ordenarlos de otras maneras, pero no conozco una característica que ordene los archivos por dependencias semánticas. La fuente del módulo _collections_abc está organizada por dependencia.
- En casos no patológicos, tanto los módulos como las definiciones de clase son singletons, y ocurren una vez en memoria. Habría una asignación biyectiva de módulos a clases, haciendo que los módulos sean redundantes.
- El número creciente de archivos hace que sea menos conveniente leer de manera informal las clases (a menos que tenga un IDE que lo haga simple), lo que hace que sea menos accesible para las personas sin herramientas.
¿Se le impide dividir grupos de clases en módulos diferentes cuando lo encuentra conveniente desde una perspectiva organizativa y de espacio de nombres?
No.
Del Zen de Python , que refleja la filosofía y los principios bajo los cuales creció y evolucionó :
Los espacios de nombres son una gran idea, ¡hagamos más!
Pero tengamos en cuenta que también dice:
Plano es mejor que anidado.
Python es increíblemente limpio y fácil de leer. Te anima a leerlo. Poner cada clase separada en un archivo separado desalienta la lectura. Esto va en contra de la filosofía central de Python. Mire la estructura de la Biblioteca estándar , la gran mayoría de los módulos son módulos de un solo archivo, no paquetes . Le enviaría a usted que el código idiomático de Python está escrito en el mismo estilo que el CPython estándar lib.
Aquí está el código real del módulo de clase base abstracta . Me gusta usarlo como referencia para la denotación de varios tipos abstractos en el lenguaje.
¿Diría que cada una de estas clases debería requerir un archivo separado?
class Hashable:
__metaclass__ = ABCMeta
@abstractmethod
def __hash__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Hashable:
try:
for B in C.__mro__:
if "__hash__" in B.__dict__:
if B.__dict__["__hash__"]:
return True
break
except AttributeError:
# Old-style class
if getattr(C, "__hash__", None):
return True
return NotImplemented
class Iterable:
__metaclass__ = ABCMeta
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
if _hasattr(C, "__iter__"):
return True
return NotImplemented
Iterable.register(str)
class Iterator(Iterable):
@abstractmethod
def next(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
if _hasattr(C, "next") and _hasattr(C, "__iter__"):
return True
return NotImplemented
class Sized:
__metaclass__ = ABCMeta
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
if _hasattr(C, "__len__"):
return True
return NotImplemented
class Container:
__metaclass__ = ABCMeta
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if _hasattr(C, "__contains__"):
return True
return NotImplemented
class Callable:
__metaclass__ = ABCMeta
@abstractmethod
def __call__(self, *args, **kwds):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Callable:
if _hasattr(C, "__call__"):
return True
return NotImplemented
Entonces, ¿deberían tener cada uno su propio archivo?
Espero que no.
Estos archivos no son solo código, son documentación sobre la semántica de Python.
Son tal vez de 10 a 20 líneas en promedio. ¿Por qué debería tener que ir a un archivo completamente separado para ver otras 10 líneas de código? Eso sería altamente impráctico. Además, habría importaciones casi idénticas en cada archivo, agregando más líneas de código redundantes.
Me resulta muy útil saber que hay un solo módulo donde puedo encontrar todas estas Clases básicas abstractas, en lugar de tener que revisar una lista de módulos. Verlos en contexto unos con otros me permite entenderlos mejor. Cuando veo que un Iterador es un Iterable, puedo revisar rápidamente en qué consiste un Iterable mirando hacia arriba.
A veces termino teniendo un par de clases muy cortas. Permanecen en el archivo, incluso si necesitan crecer con el tiempo. A veces los módulos maduros tienen más de 1000 líneas de código. Pero ctrl-f es fácil, y algunos IDE facilitan la visualización de los esquemas del archivo, por lo que no importa el tamaño del archivo, puede ir rápidamente a cualquier objeto o método que esté buscando.
Conclusión
Mi dirección, en el contexto de Python, es mantener las definiciones de clase relacionadas y semánticamente similares en el mismo archivo. Si el archivo crece tanto que se vuelve difícil de manejar, considere una reorganización.