Можно ли иметь несколько классов в одном файле в 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 мое направление состоит в том, чтобы предпочесть хранить связанные и семантически похожие определения классов в одном файле. Если файл становится настолько большим, что становится громоздким, подумайте о реорганизации.
class SomeException extends \Exception {}