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 collections
module 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.py
lepsze 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 {}
Podczas konstruowania aplikacji w Pythonie musisz myśleć o pakietach i modułach.
Moduły dotyczą plików, o których mówisz. Dobrze jest mieć kilka klas w tym samym module. Celem jest, aby wszystkie klasy w tym samym module miały służyć temu samemu celowi / logice. Jeśli moduł trwa zbyt długo, pomyśl o podzieleniu go przez przeprojektowanie logiki.
Nie zapomnij od czasu do czasu czytać o Indeksie Propozycji Ulepszeń Pythona .
źródło
Prawdziwa odpowiedź na to pytanie jest ogólna i nie zależy od używanego języka: To, co powinno znajdować się w pliku, nie zależy przede wszystkim od liczby zdefiniowanych przez niego klas. To zależy od logicznej łączności i złożoności. Kropka.
Tak więc, jeśli masz kilka bardzo małych klas, które są ze sobą ściśle powiązane, powinny one zostać umieszczone w tym samym pliku. Powinieneś podzielić klasę, jeśli nie jest ściśle związana z inną klasą lub jeśli jest zbyt złożona, aby można ją było zaliczyć do innej klasy.
To powiedziawszy, reguła „jedna klasa na plik” jest zazwyczaj dobrą heurystyką. Istnieją jednak ważne wyjątki: mała klasa pomocnicza, która jest tak naprawdę tylko szczegółem implementacji jej jedynej klasy użytkownika, powinna być zasadniczo dołączona do pliku tej klasy użytkownika. Podobnie, jeśli masz trzy klasy
vector2
,vector3
ivector4
prawdopodobnie nie ma powodu, aby zaimplementować je w osobnych plikach.źródło