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.