Va bene avere più classi nello stesso file in Python?


18

Sto arrivando di recente nel mondo Python dopo anni di Java e PHP. Mentre la lingua in sé è piuttosto semplice, sto lottando con alcuni problemi "minori" che non riesco a scuotere la testa - e ai quali non sono riuscito a trovare le risposte nei numerosi documenti e tutorial che ho letto finora .

Per l'esperto in Python esperto, questa domanda potrebbe sembrare sciocca, ma voglio davvero una risposta in modo da poter andare oltre con il linguaggio:

In Java e PHP ( anche se non strettamente richiesto ), ci si aspetta che scriva ognuno classsul proprio file, con il nome del file è quello della classbest practice.

Ma in Python, o almeno nei tutorial che ho controllato, è ok avere più classi nello stesso file.

Questa regola è valida per la produzione, il codice pronto per la distribuzione o è fatto solo per brevità nel codice solo educativo?

Risposte:


13

Va bene avere più classi nello stesso file in Python?

Sì. Sia da una prospettiva filosofica che pratica.

In Python, i moduli sono uno spazio dei nomi che esiste una volta in memoria.

Supponiamo che avessimo la seguente ipotetica struttura di directory, con una classe definita per file:

                    Defines
 abc/
 |-- callable.py    Callable
 |-- container.py   Container
 |-- hashable.py    Hashable
 |-- iterable.py    Iterable
 |-- iterator.py    Iterator
 |-- sized.py       Sized
 ... 19 more

Tutte queste classi sono disponibili nel collectionsmodulo e (ce ne sono, in effetti, 25 in totale) definite nel modulo libreria standard in_collections_abc.py

Ci sono un paio di problemi qui che credo siano _collections_abc.pysuperiori all'ipotetica struttura di directory alternativa.

  • Questi file sono ordinati alfabeticamente. Potresti ordinarli in altri modi, ma non sono a conoscenza di una funzione che ordina i file in base alle dipendenze semantiche. L'origine del modulo _collections_abc è organizzata per dipendenza.
  • In casi non patologici, sia i moduli che le definizioni di classe sono singoli, che si verificano una volta ciascuno in memoria. Ci sarebbe una mappatura biiettiva di moduli su classi - rendendo i moduli ridondanti.
  • Il numero crescente di file rende meno conveniente la lettura casuale delle classi (a meno che tu non abbia un IDE che lo renda semplice), rendendolo meno accessibile alle persone senza strumenti.

Ti viene impedito di suddividere gruppi di classi in moduli diversi quando lo ritieni opportuno dal punto di vista spaziale e organizzativo?

No.

Dallo Zen di Python , che riflette la filosofia e i principi in base ai quali è cresciuto e si è evoluto:

Gli spazi dei nomi sono una grande idea che suona il clacson: facciamo di più!

Ma ricordiamo che dice anche:

Flat è meglio di nidificato.

Python è incredibilmente pulito e di facile lettura. Ti incoraggia a leggerlo. Mettere ogni classe separata in un file separato scoraggia la lettura. Questo va contro la filosofia fondamentale di Python. Guarda la struttura della libreria standard , la stragrande maggioranza dei moduli sono moduli a file singolo, non pacchetti. Sottolineerei che il codice Python idiomatico è scritto nello stesso stile della libreria standard CPython.

Ecco il codice effettivo dal modulo di classe base astratto . Mi piace usarlo come riferimento per la denotazione di vari tipi astratti nella lingua.

Diresti che ognuna di queste classi dovrebbe richiedere un file separato?

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

Quindi dovrebbero avere ognuno il proprio file?

Spero di no.

Questi file non sono solo codice: sono documentazione sulla semantica di Python.

Sono forse in media da 10 a 20 righe. Perché dovrei andare in un file completamente separato per vedere altre 10 righe di codice? Sarebbe altamente poco pratico. Inoltre, ci sarebbero importazioni quasi identiche di plateplate su ogni file, aggiungendo righe di codice più ridondanti.

Trovo abbastanza utile sapere che esiste un singolo modulo in cui posso trovare tutte queste classi di base astratte, invece di dover consultare un elenco di moduli. Visualizzarli nel contesto reciproco mi permette di capirli meglio. Quando vedo che un Iterator è un Iterable, posso rapidamente rivedere in cosa consiste un Iterable alzando lo sguardo.

A volte finisco per avere un paio di lezioni molto brevi. Rimangono nel file, anche se devono crescere nel tempo. A volte i moduli maturi hanno oltre 1000 righe di codice. Ma ctrl-f è facile e alcuni IDE rendono semplice la visualizzazione dei contorni del file, quindi, indipendentemente dalla dimensione del file, puoi andare rapidamente a qualsiasi oggetto o metodo che stai cercando.

Conclusione

La mia direzione, nel contesto di Python, è quella di preferire mantenere definizioni di classe correlate e semanticamente simili nello stesso file. Se il file diventa così grande da diventare ingombrante, prendere in considerazione una riorganizzazione.


1
Bene, mentre capisco, grazie al codice che hai inviato che è OK avere più classi nello stesso file, non riesco a trovare l'argomento molto convincente. Ad esempio, era molto comune in PHP avere un intero file con solo codice simile a questo:class SomeException extends \Exception {}
Olivier Malki

3
Comunità diverse hanno standard di codifica diversi. Le persone Java guardano Python e dicono "perché consente più classi per file !?". Le persone di Python guardano Java e dicono "perché richiede che ogni classe abbia il proprio file !?". È meglio seguire lo stile della comunità in cui lavori.
Gort the Robot

Sono confuso anche qui. Apparentemente ho frainteso alcune cose su Python con la mia risposta. Ma si potrebbe applicare "flat is better of nided" per aggiungere il maggior numero possibile di metodi a una classe? In generale, penso che i principi di coesione e SRP si applicano ancora a un modulo che favorisce i moduli che forniscono classi che si relazionano strettamente tra loro in termini di funzionalità (anche se forse molto bene più di una classe poiché un modulo modella un concetto di pacchetto più grossolano rispetto a una singola classe ), soprattutto dal momento che qualsiasi variabile di ambito del modulo (sebbene speriamo evitata in generale) aumenterebbe di portata.

1
Lo Zen di Python è un elenco di principi che sono in tensione l'uno con l'altro. Si potrebbe rispondere in accordo con il tuo punto con "Sparse è meglio che denso". - che segue immediatamente "Flat è meglio di nidificato". Le singole linee di The Zen of Python possono essere facilmente usate e portate all'estremo, ma nel loro insieme, possono aiutare nell'arte di codificare e trovare un terreno comune dove persone ragionevoli potrebbero altrimenti non essere d'accordo. Non penso che la gente considererebbe denso il mio esempio di codice, ma ciò che descrivi mi sembra molto denso.
Aaron Hall,

Grazie Jeffrey Albertson / ragazzo del fumetto. :) La maggior parte degli utenti di Python non dovrebbero usare i metodi speciali (doppio trattino basso), ma consentono a un designer / architetto di base di impegnarsi in meta-programmazione per fare un uso personalizzato di operatori, comparatori, notazione di sottoscrizione, contesti e altro caratteristiche del linguaggio. Finché non violano il principio della minima sorpresa, penso che il rapporto danno / valore sia infinitesimale.
Aaron Hall,

4

Quando strutturi la tua applicazione in Python, devi pensare in termini di pacchetti e moduli.

I moduli riguardano i file di cui stai parlando. Va bene avere un sacco di classi all'interno dello stesso modulo. L'obiettivo è che tutte le classi all'interno dello stesso modulo debbano servire lo stesso scopo / logica. Se il modulo va troppo a lungo, allora pensa a suddividerlo ridisegnando la tua logica.

Non dimenticare di leggere di volta in volta su Index of Python Enhancement Proposte .


2

La vera risposta a questo è generale e non dipende dalla lingua utilizzata: ciò che dovrebbe essere in un file non dipende principalmente da quante classi definisce. Dipende dalla connessione logica e dalla complessità. Periodo.

Pertanto, se si dispone di alcune classi molto piccole che sono altamente interconnesse, dovrebbero essere raggruppate nello stesso file. Dovresti dividere una classe se non è strettamente collegata a un'altra classe o se è troppo complessa per essere inclusa in un'altra classe.

Detto questo, la regola di una classe per file è di solito una buona euristica. Tuttavia, ci sono importanti eccezioni: una piccola classe helper che è in realtà solo il dettaglio di implementazione della sua unica classe utente dovrebbe generalmente essere raggruppata nel file di quella classe utente. Allo stesso modo, se avete tre classi vector2, vector3e vector4, c'è poco motivo probabile per la loro applicazione in file separati.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.