¿Está bien tener varias clases en el mismo archivo en Python?

18

Recién estoy llegando al mundo de Python después de años de Java y PHP. Si bien el lenguaje en sí es bastante sencillo, estoy luchando con algunos problemas 'menores' que no puedo entender y a los que no pude encontrar respuestas en los numerosos documentos y tutoriales que he leído hasta ahora .

Para el experimentado practicante de Python, esta pregunta puede parecer tonta, pero realmente quiero una respuesta para poder ir más allá con el lenguaje:

En Java y PHP ( aunque no es estrictamente obligatorio ), se espera que escriba cada uno classen su propio archivo, con el nombre del archivo classcomo la mejor práctica.

Pero en Python, o al menos en los tutoriales que he revisado, está bien tener varias clases en el mismo archivo.

¿Esta regla se cumple en la producción, el código listo para la implementación o se hace solo por razones de brevedad en el código educativo solamente?

Olivier Malki
fuente

Respuestas:

13

¿Está bien tener varias clases en el mismo archivo en Python?

Si. Tanto desde una perspectiva filosófica como 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 directorio 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 collectionsmódulo y (de hecho, hay 25 en total) definidas en el módulo de biblioteca estándar en_collections_abc.py

Hay un par de problemas aquí que creo que hacen _collections_abc.pyque la estructura de directorio hipotética sea superior a la alternativa.

  • Estos archivos están ordenados alfabéticamente. Puede ordenarlos de otras maneras, pero no conozco una característica que clasifique 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, que ocurren una vez en la memoria. Habría un mapeo biyectivo de módulos en clases, haciendo que los módulos sean redundantes.
  • El creciente número de archivos hace que sea menos conveniente leer las clases de manera informal (a menos que tenga un IDE que lo simplifique), lo que lo hace menos accesible para las personas sin herramientas.

¿Se le impide dividir grupos de clases en diferentes módulos cuando lo considera deseable desde una perspectiva organizacional y de espacios 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 de eso!

Pero tengamos en cuenta que también dice:

Plano es mejor que anidado.

Python es increíblemente limpio y fácil de leer. Te alienta 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 diría que el código idiomático de Python está escrito en el mismo estilo que la biblioteca estándar de CPython.

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 cada uno tener su propio archivo?

Espero que no.

Estos archivos no son solo código, son documentación sobre la semántica de Python.

Son quizás 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 muy poco práctico. Además, habría importaciones casi idénticas en cada archivo, añadiendo líneas de código más redundantes.

Me resulta bastante útil saber que hay un solo módulo donde puedo encontrar todas estas clases base abstractas, en lugar de tener que revisar una lista de módulos. Verlos en contexto entre ellos me permite comprenderlos mejor. Cuando veo que un Iterador es Iterable, puedo revisar rápidamente en qué consiste un Iterable al mirar 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 contornos del archivo, por lo que no importa qué tan grande sea el archivo, puede ir rápidamente al objeto o método que esté buscando.

Conclusión

Mi dirección, en el contexto de Python, es preferir mantener 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.

Aaron Hall
fuente
1
Bueno, aunque entiendo, gracias al código que envió que está bien tener varias clases en el mismo archivo, no puedo encontrar el argumento muy convincente. Por ejemplo, era muy común en PHP tener un archivo completo con un código similar a este:class SomeException extends \Exception {}
Olivier Malki el
3
Las diferentes comunidades tienen diferentes estándares de codificación. La gente de Java mira python y dice "¿por qué permite múltiples clases por archivo?". La gente de Python mira Java y dice "¿por qué requiere que cada clase tenga su propio archivo?". Es mejor seguir el estilo de la comunidad en la que está trabajando.
Gort the Robot
Estoy confundido aquí también. Aparentemente entendí mal algunas cosas sobre Python con mi respuesta. ¿Pero se aplicaría "plano es mejor que anidado" para agregar tantos métodos como sea posible a una clase? En general, creo que los principios de cohesión y SRP aún se aplican a un módulo que favorece módulos que proporcionan clases que se relacionan estrechamente entre sí en funcionalidad (aunque quizás muy bien más de una clase ya que un módulo modela un concepto de paquete más grueso que una sola clase ), especialmente porque cualquier variable de alcance de módulo (aunque con suerte se evitará en general) aumentaría en alcance.
1
El Zen de Python es una lista de principios que están en tensión entre sí. Uno podría responder de acuerdo con su punto con: "Escaso es mejor que denso". - que sigue inmediatamente, "Flat es mejor que anidado". Las líneas individuales de The Zen of Python pueden ser fácilmente mal utilizadas y llevadas a extremos, pero en su conjunto, pueden ayudar en el arte de la codificación y encontrar un terreno común donde personas razonables pueden estar en desacuerdo. No creo que la gente considere que mis ejemplos de código son densos, pero lo que usted describe me parece muy denso.
Aaron Hall
Gracias Jeffrey Albertson / Comic Book Guy. :) La mayoría de los usuarios de Python no deberían usar los métodos especiales (doble guión bajo), pero sí permiten que un diseñador / arquitecto principal participe en la metaprogramación para hacer un uso personalizado de operadores, comparadores, notación de subíndices, contextos y otros características del lenguaje Mientras no violen el principio de menor sorpresa, creo que la relación daño / valor es infinitesimal.
Aaron Hall el
4

Al estructurar su aplicación en Python, debe pensar en términos de paquetes y módulos.

Los módulos son sobre los archivos de los que está hablando. Está bien tener un montón de clases dentro del mismo módulo. El objetivo es que todas las clases dentro del mismo módulo sirvan para el mismo propósito / lógica. Si el módulo dura demasiado, piense en subdividirlo rediseñando su lógica.

No olvide leer de vez en cuando sobre el índice de propuestas de mejora de Python .


fuente
2

La respuesta real a esto es general y no depende del lenguaje que se use: lo que debe estar en un archivo no depende principalmente de cuántas clases define. Depende de la conexión lógica y de la complejidad. Período.

Por lo tanto, si tiene algunas clases muy pequeñas que están altamente interconectadas, deben agruparse en el mismo archivo. Debería dividir una clase si no está estrechamente conectada a otra clase o si es demasiado compleja para ser incluida en otra clase.

Dicho esto, la regla de una clase por archivo suele ser una buena heurística. Sin embargo, hay excepciones importantes: una pequeña clase auxiliar que es realmente solo el detalle de implementación de su única clase de usuario generalmente debe agruparse en el archivo de esa clase de usuario. Del mismo modo, si tiene tres clases vector2, vector3y vector4, es probable que haya pocas razones para implementarlas en archivos separados.

cmaster - restablecer monica
fuente