Czy w Pythonie można mieć wiele klas w tym samym pliku?

18

Świeżo przybywam do świata Python po latach Java i PHP. Chociaż sam język jest dość prosty, mam trudności z kilkoma „drobnymi” problemami, których nie potrafię rozwiązać - i na które nie mogłem znaleźć odpowiedzi w licznych dokumentach i samouczkach, które przeczytałem do tej pory .

Dla doświadczonego praktyka Pythona to pytanie może wydawać się głupie, ale naprawdę chcę na nie odpowiedzieć, aby móc przejść dalej z językiem:

W Javie i PHP ( choć nie jest to bezwzględnie wymagane ), oczekuje się, że każdy classz nich zapisuje na swoim własnym pliku, a jego nazwa to classnajlepsza praktyka.

Ale w Pythonie, lub przynajmniej w samouczków Sprawdziłem, że jest OK , aby mieć wiele klas w tym samym pliku.

Czy ta reguła obowiązuje w kodzie produkcyjnym, gotowym do wdrożenia, czy jest zrobiona tylko ze względu na zwięzłość kodu wyłącznie edukacyjnego?

Olivier Malki
źródło

Odpowiedzi:

13

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ę.

Aaron Hall
źródło
1
Cóż, choć rozumiem, dzięki kodowi, który przesłałeś, że posiadanie wielu klas w tym samym pliku jest w porządku, nie mogę znaleźć argumentu bardzo przekonującego. Na przykład w PHP bardzo często występował cały plik z kodem podobnym do tego:class SomeException extends \Exception {}
Olivier Malki
3
Różne społeczności mają różne standardy kodowania. Ludzie Java patrzą na python i mówią „dlaczego pozwala na wiele klas na plik !?”. Ludzie Pythona patrzą na Javę i mówią „dlaczego wymaga, aby każda klasa miała swój własny plik !?”. Najlepiej jest podążać za stylem społeczności, w której pracujesz.
Gort the Robot
Też jestem zdezorientowany. Najwyraźniej z moją odpowiedzią źle zrozumiałem niektóre kwestie dotyczące Pythona. Ale czy zastosować „płaski jest lepszy niż zagnieżdżony”, aby dodać jak najwięcej metod do klasy? Ogólnie sądzę, że zasady spójności i SRP nadal mają zastosowanie do modułu faworyzującego moduły, które zapewniają klasy ściśle ze sobą powiązane pod względem funkcjonalności (chociaż być może bardzo dobrze więcej niż jedną klasę, ponieważ moduł modeluje grubszą koncepcję pakietu niż pojedynczą klasę ), zwłaszcza, że ​​wszelkie zmienne o zasięgu modułowym (choć, ogólnie rzecz biorąc, należy ich unikać), zwiększyłyby zasięg.
1
Zen Pythona to lista zasad, które są ze sobą powiązane. Ktoś może odpowiedzieć w zgodzie z twoją tezą: „Rzadki jest lepszy niż gęsty”. - co natychmiast następuje: „Mieszkanie jest lepsze niż zagnieżdżone”. Poszczególne wiersze z Zen Pythona mogą być łatwo nadużywane i doprowadzane do skrajności, ale jako całość mogą pomóc w sztuce kodowania i znalezieniu wspólnej płaszczyzny, w której rozsądni ludzie inaczej by się nie zgadzali. Nie sądzę, by ludzie uważali moje przykłady kodu za gęste, ale to, co opisujesz, brzmi dla mnie bardzo gęście.
Aaron Hall
Dziękuję Jeffrey Albertson / facet z komiksu. :) Większość użytkowników Pythona nie powinna używać specjalnych metod (podwójnego podkreślenia), ale pozwala głównemu projektantowi / architektowi zaangażować się w metaprogramowanie w celu niestandardowego użycia operatorów, komparatorów, notacji w indeksie dolnym, kontekstów i innych funkcje językowe. Dopóki nie naruszają zasady najmniejszego zaskoczenia, uważam, że stosunek szkód do wartości jest nieskończenie mały.
Aaron Hall
4

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
2

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, vector3i vector4prawdopodobnie nie ma powodu, aby zaimplementować je w osobnych plikach.

cmaster - przywróć monikę
źródło
4
w Javie
gnat