Чи нормально мати кілька класів в одному файлі в 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, - віддавати перевагу збереженню пов'язаних та семантично подібних визначень класу в одному файлі. Якщо файл зростає настільки великим, що стає громіздким, тоді слід розглянути питання про реорганізацію.
class SomeException extends \Exception {}