Czy w Pythonie można mieć wiele klas w tym samym pliku?
Tak. Zarówno z perspektywy filozoficznej, jak i praktycznej.
W Pythonie moduły to przestrzeń nazw, która istnieje raz w pamięci.
Powiedzmy, że mieliśmy następującą hipotetyczną strukturę katalogów, z jedną klasą zdefiniowaną dla pliku:
Defines
abc/
|-- callable.py Callable
|-- container.py Container
|-- hashable.py Hashable
|-- iterable.py Iterable
|-- iterator.py Iterator
|-- sized.py Sized
... 19 more
Wszystkie te klasy są dostępne w collectionsmodule i (w sumie jest ich 25) zdefiniowane w standardowym module bibliotecznym w_collections_abc.py
Jest tutaj kilka kwestii, które moim zdaniem są _collections_abc.pylepsze od alternatywnej hipotetycznej struktury katalogów.
- Te pliki są sortowane alfabetycznie. Możesz sortować je na inne sposoby, ale nie znam funkcji sortującej pliki według zależności semantycznych. Źródło modułu _collections_abc jest zorganizowane według zależności.
- W przypadkach niepatologicznych zarówno moduły, jak i definicje klas są singletonami, występującymi raz w pamięci. Byłoby bijectywne mapowanie modułów na klasy - dzięki czemu moduły byłyby zbędne.
- Rosnąca liczba plików sprawia, że mniej wygodne jest swobodne czytanie klas (chyba że masz IDE, które to upraszcza) - co czyni go mniej dostępnym dla osób bez narzędzi.
Czy uniemożliwia ci się dzielenie grup klas na różne moduły, jeśli uważasz, że jest to pożądane z perspektywy przestrzeni nazw i organizacji?
Nie.
Z Zen Pythona , który odzwierciedla filozofię i zasady, na podstawie których wyrósł i ewoluował:
Przestrzenie nazw to jeden świetny pomysł - zróbmy ich więcej!
Pamiętajmy jednak, że mówi również:
Mieszkanie jest lepsze niż zagnieżdżone.
Python jest niezwykle czysty i łatwy do odczytania. Zachęca cię do przeczytania. Umieszczenie każdej osobnej klasy w osobnym pliku zniechęca do czytania. Jest to sprzeczne z podstawową filozofią Pythona. Spójrz na strukturę biblioteki standardowej , zdecydowana większość modułów to moduły jednoplikowe, a nie pakiety. Podałbym wam, że idiomatyczny kod Pythona jest napisany w tym samym stylu co standardowa biblioteka CPython.
Oto aktualny kod z abstrakcyjnego modułu klasy bazowej . Lubię używać go jako odniesienia do denotacji różnych typów abstrakcyjnych w języku.
Czy powiedziałbyś, że każda z tych klas powinna wymagać osobnego pliku?
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
Czy więc każdy powinien mieć własny plik?
Mam nadzieję, że nie.
Te pliki to nie tylko kod - to dokumentacja dotycząca semantyki Pythona.
Średnio są to od 10 do 20 linii. Dlaczego powinienem przejść do zupełnie osobnego pliku, aby zobaczyć kolejne 10 wierszy kodu? To byłoby bardzo niepraktyczne. Co więcej, każdy plik miałby prawie identyczny import na płycie zbiorczej, dodając więcej zbędnych wierszy kodu.
Uważam za użyteczne wiedzieć, że istnieje jeden moduł, w którym mogę znaleźć wszystkie te abstrakcyjne klasy podstawowe, zamiast patrzeć na listę modułów. Oglądanie ich w kontekście pozwala mi je lepiej zrozumieć. Kiedy widzę, że iterator jest iterowalny, mogę szybko sprawdzić, z czego składa się iterator, podnosząc wzrok.
Czasami mam kilka bardzo krótkich zajęć. Pozostają w aktach, nawet jeśli z czasem muszą się powiększać. Czasami dojrzałe moduły zawierają ponad 1000 linii kodu. Ale ctrl-f jest łatwe, a niektóre IDE ułatwiają przeglądanie konturów pliku - więc bez względu na to, jak duży jest plik, możesz szybko przejść do dowolnego obiektu lub metody, której szukasz.
Wniosek
W kontekście Pythona kieruję się wolą przechowywania pokrewnych i semantycznie podobnych definicji klas w tym samym pliku. Jeśli plik staje się tak duży, że staje się nieporęczny, rozważ reorganizację.
class SomeException extends \Exception {}