Можно ли иметь несколько классов в одном файле в 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. Посмотрите на структуру стандартной библиотеки , подавляющее большинство модулей представляют собой однофайловые модули, а не пакеты. Я хотел бы представить вам, что идиоматический код 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 строк кода? Это было бы крайне непрактично. Кроме того, в каждом файле будет практически идентичный импорт шаблонов, добавляя более избыточные строки кода.

Я нахожу весьма полезным знать, что существует один модуль, в котором я могу найти все эти абстрактные базовые классы вместо того, чтобы просматривать список модулей. Просмотр их в контексте друг с другом позволяет мне лучше понять их. Когда я вижу, что Iterator является Iterable, я могу быстро просмотреть, из чего состоит Iterable, посмотрев вверх.

Иногда я провожу пару очень коротких занятий. Они остаются в файле, даже если они должны расти со временем. Иногда зрелые модули содержат более 1000 строк кода. Но ctrl-f прост, а некоторые IDE облегчают просмотр контуров файла - поэтому независимо от размера файла вы можете быстро перейти к любому объекту или методу, который вы ищете.

Вывод

В контексте Python мое направление состоит в том, чтобы предпочесть хранить связанные и семантически похожие определения классов в одном файле. Если файл становится настолько большим, что становится громоздким, подумайте о реорганизации.

Аарон Холл
источник
1
Ну, хотя я понимаю, что благодаря предоставленному вами коду, все в одном файле нормально, я не могу найти аргумент очень убедительным. Например, в PHP было очень распространено иметь весь файл с кодом, похожим на этот:class SomeException extends \Exception {}
Оливье Малки
3
Разные сообщества имеют разные стандарты кодирования. Java-люди смотрят на python и говорят: «Почему он допускает несколько классов на файл !?». Люди Python смотрят на Java и говорят: «Почему он требует, чтобы у каждого класса был свой файл !?». Лучше всего следовать стилю сообщества, в котором вы работаете.
Gort the Robot
Я тоже запутался здесь. Видимо, я неправильно понял некоторые вещи о Python с моим ответом. Но можно ли применить «квартира лучше вложенной», чтобы добавить как можно больше методов в класс? В целом, я думаю, что принципы когезии и SRP по-прежнему применимы к модулю, предпочитающему модули, которые предоставляют классы, которые по функциональности тесно связаны друг с другом (хотя, возможно, очень хорошо, чем один класс, поскольку модуль моделирует более грубую концепцию пакета, чем отдельный класс). ), тем более что любые переменные области видимости модуля (хотя, как мы надеемся, их вообще избегают) увеличатся в объеме.
1
Zen of Python - это список принципов, которые находятся в напряжении друг с другом. Можно согласиться с вашей точкой зрения: «Разреженный лучше, чем плотный». - что сразу следует: «Квартира лучше, чем вложенная». Отдельные строки из Zen of Python легко могут быть неправильно использованы и доведены до крайности, но в целом это может помочь в искусстве кодирования и нахождении точки соприкосновения, в которой разумные люди могут не согласиться. Я не думаю, что люди посчитают мои примеры кода плотными, но то, что вы описываете, звучит очень плотно для меня.
Аарон Холл
Спасибо, Джеффри Альбертсон, парень комиксов. :) Большинству пользователей Python не следует использовать специальные методы (двойное подчеркивание), но они позволяют основному дизайнеру / архитектору участвовать в метапрограммировании, чтобы использовать пользовательские операторы, компараторы, индексную запись, контексты и другие. языковые особенности. Пока они не нарушают принцип наименьшего удивления, я думаю, что соотношение вреда и стоимости бесконечно мало.
Аарон Холл
4

При структурировании вашего приложения на Python вам нужно думать с точки зрения пакетов и модулей.

Модули - это файлы, о которых вы говорите. Хорошо иметь несколько классов в одном модуле. Цель состоит в том, чтобы все классы в одном и том же модуле служили одной и той же цели / логике. Если модуль идет слишком долго, подумайте о его подразделении, изменив логику.

Не забывайте время от времени читать об Индексах предложений по улучшению Python .


источник
2

Реальный ответ на этот вопрос является общим и не зависит от используемого языка: что должно быть в файле, в первую очередь не зависит от того, сколько классов он определяет. Это зависит от логической связности и сложности. Период.

Таким образом, если у вас есть несколько очень маленьких классов, которые сильно взаимосвязаны, они должны быть объединены в один файл. Вы должны разделить класс, если он либо не тесно связан с другим классом, либо если он слишком сложен для включения в другой класс.

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

cmaster - восстановить монику
источник