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

13

Sono appena arrivato nel mondo Python dopo anni di Java e PHP. Mentre il linguaggio stesso è piuttosto semplice, sto lottando con alcuni problemi "minori" che non riesco a capovolgere - e ai quali non sono riuscito a trovare risposte nei numerosi documenti e tutorial che ho letto fino a qui .

Per il praticante esperto di Python, questa domanda potrebbe sembrare sciocca, ma voglio davvero una risposta per poter andare oltre con la lingua:

In Java e PHP ( anche se non strettamente richiesto ), ci si aspetta che scrivi ogni class sul proprio file, con il nome del file che è class come best practice.

Ma in Python, o almeno nelle esercitazioni 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 educativo?

    
posta Olivier Malki 04.01.2016 - 16:06
fonte

3 risposte

11

Is it ok to have multiple classes in the same file in Python?

Sì. Sia da un punto di vista filosofico che pratico.

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

Supponiamo di avere 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 modulo collections e (ce ne sono, in effetti, 25 in totale) definite nel modulo della libreria standard in _collections_abc.py

Qui ci sono un paio di problemi che ritengo rendono il _collections_abc.py superiore alla struttura di directory ipotetica alternativa.

  • Questi file sono ordinati alfabeticamente. È possibile 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.
  • Nei casi non patologici, sia i moduli che le definizioni di classe sono singleton, che si verificano una volta ciascuno in memoria. Ci sarebbe una mappatura biiettiva dei moduli sulle classi - rendendo i moduli ridondanti.
  • Il numero crescente di file rende meno conveniente leggere casualmente le classi (a meno che non si disponga di un IDE che lo rende semplice) - rendendolo meno accessibile alle persone senza strumenti.

Ti viene impedito di rompere gruppi di classi in moduli diversi quando lo ritieni desiderabile da una prospettiva di namespace e organizzazione?

No.

Dal Zen di Python , che riflette la filosofia e i principi sotto i quali è cresciuto e si è evoluto :

Namespaces are one honking great idea -- let's do more of those!

Ma ricordiamo che dice anche:

Flat is better than nested.

Python è incredibilmente pulito e facile da leggere. Ti incoraggia a leggerlo. Mettere ogni classe separata in un file separato scoraggia la lettura. Questo va contro la filosofia di base di Python. Osserva la struttura della Libreria standard , la maggior parte dei moduli sono moduli a file singolo, non pacchetti . Ti suggerirei che il codice Python idiomatico è scritto nello stesso stile della lib di CPython standard.

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 ciascuno un proprio file?

Spero di no.

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

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

Trovo abbastanza utile sapere che esiste un singolo modulo in cui posso trovare tutte queste astratte classi base, invece di dover consultare un elenco di moduli. Guardarli nel contesto l'uno con l'altro mi consente di comprenderli meglio. Quando vedo che un Iterator è un Iterable, posso rivedere rapidamente ciò che un Iterable consiste nel dare un'occhiata.

A volte finisco per avere un paio di classi molto brevi. Rimangono nel file, anche se hanno bisogno di ingrandirsi nel tempo. A volte i moduli maturi hanno oltre 1000 righe di codice. Ma ctrl-f è semplice e alcuni IDE facilitano la visualizzazione dei contorni del file, quindi non importa quanto grande sia il file, puoi andare rapidamente a qualsiasi oggetto o metodo che stai cercando.

Conclusione

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

    
risposta data 04.01.2016 - 17:10
fonte
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 avere lo stesso scopo / logica. Se il modulo va troppo a lungo, pensa a suddividerlo ridisegnando la tua logica.

Non dimenticare di leggere di tanto in tanto l'Indice delle Proposte di miglioramento Python .

    
risposta data 04.01.2016 - 17:03
fonte
2

La vera risposta a questo è generale, e non dipende dal linguaggio utilizzato: ciò che dovrebbe essere in un file non dipende principalmente dal numero di classi che definisce. Dipende dalla connessione logica e dalla complessità. Periodo.

Quindi, se hai poche classi molto piccole che sono altamente interconnesse, dovrebbero essere raggruppate nello stesso file. Devi dividere una classe se non è strettamente connessa 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 i dettagli di implementazione della sua unica classe utente dovrebbe generalmente essere raggruppata nel file di quella classe utente. Allo stesso modo, se hai tre classi vector2 , vector3 e vector4 , ci sono probabilmente poche ragioni per implementarle in file separati.

    
risposta data 04.01.2016 - 19:25
fonte

Leggi altre domande sui tag