Чи нормально мати кілька класів в одному файлі в Python?


18

Я щойно приїжджаю у світ Python через роки Java та PHP. Незважаючи на те, що мова сама по собі є досить простою, я бореться з деякими "незначними" питаннями, про які я не можу обернути голову - і на які я не могла знайти відповіді у численних документах та навчальних посібниках, які я прочитала до цього часу .

Для досвідченого практикуючого Python це питання може здатися нерозумним, але я дуже хочу відповісти на нього, щоб я міг рухатись далі мовою:

У Java та PHP ( хоча це не обов'язково ), ви повинні писати кожен classу своєму власному файлі, а ім'я файлу - classце найкраща практика.

Але в Python або, принаймні, у підручниках, які я перевірив, нормально мати кілька класів в одному файлі.

Чи це правило дотримується у виробництві, готовому до розгортання коді чи це робиться лише заради стислості в навчальному коді?

Відповіді:


13

Чи нормально мати кілька класів в одному файлі в Python?

Так. Як з філософської точки зору, так і з практичної.

У Python модулі - це простір імен, який існує один раз у пам'яті.

Скажімо, у нас була така гіпотетична структура каталогів з одним класом, визначеним на файл:

                    Defines
 abc/
 |-- callable.py    Callable
 |-- container.py   Container
 |-- hashable.py    Hashable
 |-- iterable.py    Iterable
 |-- iterator.py    Iterator
 |-- sized.py       Sized
 ... 19 more

Усі ці класи доступні в collectionsмодулі і (насправді їх 25), визначених у стандартному модулі бібліотеки в_collections_abc.py

Тут є кілька питань, які, на мою думку, _collections_abc.pyперевершують альтернативну гіпотетичну структуру каталогів.

  • Ці файли сортуються за алфавітом. Ви можете їх сортувати іншими способами, але я не знаю особливості, яка сортує файли за семантичною залежністю. Джерело модуля _collections_abc організовується залежно.
  • У непатологічних випадках і модулі, і визначення класів є одинаковими, що виникають один раз у пам'яті. Було б бієктивне відображення модулів на класи - що робить модулі надмірними.
  • Збільшення кількості файлів робить менш зручним випадкове читання класів (якщо у вас немає IDE, що робить його простим) - робить його менш доступним для людей без інструментів.

Чи заважаєте вам розбивати групи класів на різні модулі, коли вам здається бажаним з точки зору простору імен та організаційної точки зору?

Ні.

З Дзен Пітона , який відображає філософію та принципи, на яких він рос і розвивався:

Простори імен - це чудова ідея - давайте зробимо більше таких!

Але майте на увазі, що він також говорить:

Квартира краще, ніж вкладена.

Python неймовірно чистий і легкий для читання. Це спонукає вас прочитати. Поміщення кожного окремого класу в окремий файл перешкоджає читанню. Це суперечить основній філософії Пітона. Подивіться на структуру бібліотеки «Стандарт» , переважна більшість модулів - це однофайлові модулі, а не пакети. Я хотів би сказати вам, що ідіоматичний код Python написаний у тому ж стилі, що і стандартна вкладка CPython.

Ось фактичний код з абстрактного модуля базового класу . Мені подобається використовувати це як орієнтир для позначення різних абстрактних типів у мові.

Ви б сказали, що для кожного з цих класів потрібен окремий файл?

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

Тож чи повинні вони мати свій файл?

Я сподіваюся, що не.

Ці файли не просто код - вони є документацією на семантику Python.

Вони в середньому можуть бути від 10 до 20 рядків. Чому я повинен перейти до повністю окремого файлу, щоб побачити ще 10 рядків коду? Це було б непрактично. Крім того, був би майже однаковий імпорт котлових плит на кожен файл, додаючи більше зайвих рядків коду.

Мені дуже корисно знати, що є єдиний модуль, де я можу знайти всі ці абстрактні базові класи, замість того, щоб переглядати список модулів. Перегляд їх у контексті один з одним дозволяє мені краще їх зрозуміти. Коли я бачу, що Ітератор є Ітерабельним, я можу швидко переглянути, з чого складається Ітерабель, поглянувши вгору.

Я інколи закінчую пару дуже коротких занять. Вони залишаються у файлі, навіть якщо з часом вони потребують збільшення. Іноді зрілі модулі мають понад 1000 рядків коду. Але ctrl-f простий, і деякі IDE полегшують перегляд контурів файлу - тому незалежно від того, наскільки великий файл, ви можете швидко перейти до будь-якого об'єкта чи методу, який ви шукаєте.

Висновок

Мій напрям, в контексті Python, - віддавати перевагу збереженню пов'язаних та семантично подібних визначень класу в одному файлі. Якщо файл зростає настільки великим, що стає громіздким, тоді слід розглянути питання про реорганізацію.


1
Хоча я розумію, що завдяки коду, який ви подали, що в одному файлі нормально мати декілька класів, я не можу вважати аргумент дуже переконливим. Наприклад, у PHP було дуже часто мати цілий файл із лише кодом, подібним до цього:class SomeException extends \Exception {}
Олів'є Малкі

3
У різних спільнотах є різні стандарти кодування. Люди Java дивляться на python і кажуть "чому це дозволяє кілька класів на файл!". Люди Python дивляться на Java і кажуть "чому він вимагає, щоб у кожного класу був свій файл !?". Найкраще дотримуватися стилю спільноти, в якій ви працюєте.
Gort the Robot

Я тут також плутаюся. Мабуть, я неправильно зрозумів деякі речі про Python своєю відповіддю. Але чи можна застосувати "краще, ніж вкладено", щоб додати якомога більше методів до класу? Взагалі, я думаю, що принципи згуртованості та SRP все ще застосовуються до модуля, який надає перевагу модулям, які забезпечують класи, які тісно співвідносяться один з одним за функціональністю (хоча, можливо, це дуже добре, ніж один клас, оскільки модуль моделює більш грубу концепцію пакету, ніж один клас ), тим більше, що будь-які змінні області модуля (хоча сподіваємось взагалі цього уникати) збільшуватимуть сферу застосування.

1
Дзен Пітона - це перелік принципів, які перебувають у напрузі один з одним. Можна відповісти, погоджуючись із вашою думкою: "Рідке краще, ніж щільне". - з чого відразу випливає: "Квартира краще, ніж вкладена". Окремі рядки з "Дзен Пітона" можуть бути легко використані та використані в крайнощах, але в цілому вони можуть допомогти в мистецтві кодування та пошуку спільної точки, де розумні люди можуть інакше не погодитися. Я не думаю, що люди вважають мої приклади коду щільними, але те, що ви описуєте, звучить для мене дуже щільно.
Аарон Хол

Дякую Джефрі Альбертсон / Хлопець коміксів. :) Більшість користувачів Python не повинні використовувати (подвійне підкреслення) спеціальних методів, але вони дозволяють основним дизайнерам / архітекторам займатися метапрограмуванням для користувацького використання операторів, компараторів, нотаток підписки, контекстів та інших мовні особливості. Поки вони не порушують принцип найменшого здивування, я вважаю, що співвідношення шкоди та вартості нескінченно мало.
Аарон Хол

4

Структуруючи свою програму в Python, вам потрібно подумати над пакетами та модулями.

Модулі - це про файли, про які ви говорите. Добре мати купу занять в одному модулі. Мета полягає в тому, щоб усі класи в межах одного модуля мали слугувати одній цілі / логіці. Якщо модуль йде занадто довго, то подумайте про його підрозділ, переробивши свою логіку.

Не забудьте час від часу читати про пропозиції щодо вдосконалення програми Python .


2

Реальна відповідь на це загальна, а не залежна від мови, яка використовується: Те, що має бути у файлі, не залежить насамперед від того, скільки класів він визначає. Це залежить і від логічної зв’язності, і від складності. Період.

Таким чином, якщо у вас є кілька дуже малих класів, які сильно пов'язані між собою, їх слід об’єднати в один і той же файл. Вам слід розділити клас, якщо він або не тісно пов'язаний з іншим класом, або якщо його занадто складний, щоб включити його до іншого класу.

Однак, правило одного класу на файл зазвичай є евристичним. Однак є важливі винятки: невеликий допоміжний клас, який насправді є лише детальною інформацією про реалізацію свого єдиного класу користувача, як правило, має бути вбудований у файл цього класу користувача. Так само, якщо у вас є три класи vector2, vector3і vector4, ймовірно, мало причин реалізувати їх в окремих файлах.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.