Является ли pythonic использовать __init__.py модули пакета для общих, абстрактных классов?
Я разрабатываю довольно сложное приложение для своей компании, следуя объектно-ориентированной модели в python3. Приложение содержит несколько пакетов и подпакетов, каждый из которых, конечно, содержит модуль __init__.py.
В основном я использовал эти модули __init__.py для объявления общих классов для пакета внутри них, которые предназначены для использования в качестве абстрактных шаблонов только для их соответствующего пакета.
Теперь мой вопрос: Является ли это "хорошим" / "правильным" / "питоническим" способом использования модулей __init__.py? Или я предпочитаю объявлять свои общие классы где-то еще?
Чтобы привести пример, допустим пакет mypkg
:
mypkg.__init__.py
:
class Foo(object):
__some_attr = None
def __init__(self, some_attr):
self.__some_attr = some_attr
@property
def some_attr(self):
return self.__some_attr
@some_attr.setter
def some_attr(self, val):
self.__some_attr = val
mypkg.myfoo.py
:
from . import Foo
class MyFoo(Foo):
def __init__(self):
super().__init__("this is my implemented value")
def printme(self):
print(self.some_attr)
Ответы
Ответ 1
Это зависит от того, какой API вы хотите предоставить. Например, модуль collections
в стандартной библиотеке определяет все классы в __init__.py
1.
И стандартная библиотека должна быть довольно "питонической", что бы это ни значило.
Однако он обеспечивает в основном "модульный" интерфейс. Очень редко можно увидеть:
import collections.abc
Если у вас уже есть подпакеты, вы, вероятно, лучше вводите новый подпакет.
Если в настоящее время использование пакета на самом деле не зависит от подпакетов, вы можете рассмотреть возможность размещения кода в __init__.py
. Или поместите код в какой-то частный модуль и просто импортируйте имена внутри __init__.py
(это то, что я бы предпочел)
Если вы обеспокоены тем, где лучше разместить абстрактные базовые классы, как показано выше (collections.abc
содержит абстрактные базовые классы пакета collections
), и, как вы можете видеть из стандартной библиотеки abc
, он обычно определяет подмодуль abc.py
, который их содержит.
Вы можете рассматривать их непосредственно из __init__.py
:
from .abc import *
from . import abc
# ...
__all__ = list_of_names_to_export + abc.__all__
внутри __init__.py
.
1 Фактическая реализация используется в C: _collectionsmodule.c
Ответ 2
Вы всегда можете поместить все в один исходный файл. Причиной разделения более сложного кода на отдельные модули или пакеты является разделение вещей, которые взаимно связаны с вещами, которые не связаны друг с другом. Разделенные вещи должны быть как можно более независимыми на остальных. И это применимо к любому уровню: структуры данных, функции, классы, модули, пакеты, приложения.
Внутри модуля или внутри пакета должны применяться те же правила. Я согласен с Bakuriu, что __init__.py
должен быть тесно связан с инфраструктурой пакета, но не обязательно для функциональности модуля. Мое личное мнение состоит в том, что __init__.py
должно быть максимально простым. Причина во-первых заключается в том, что все должно быть как можно проще, но не проще. Во-вторых, люди, читающие код вашего пакета, как правило, думают так. Они могут не ожидать неожиданной функциональности в __init__.py
. Возможно, было бы лучше создать generic.py
внутри пакета для этой цели. Легче документировать цель модуля (скажем, через его верхнюю докструю).
Чем лучше разделение с самого начала, тем лучше могут быть объединены независимые функции в будущем. Вы получаете большую гибкость - как для использования модуля внутри пакета, так и для будущих модификаций.
Ответ 3
Действительно, можно использовать __init__.py
для конкретной инициализации модуля, но я никогда не видел, чтобы кто-то использовал его для определения новых функций. Единственное "правильное" использование, которое я знаю, это то, что обсуждается в этой теме....
В любом случае, поскольку у вас может быть сложное приложение, вы можете использовать временный файл, в котором вы определяете все необходимые функции, а не определяете их непосредственно в модуле __init__.py
. Это позволит вам более легко читаться, а потом легче изменить.
Ответ 4
Не < использовать __init__.py
для чего-либо, кроме определения __all__
. Вы сэкономите столько жизней, если сможете избежать этого.
Причина. Обычно разработчики рассматривают пакеты и модули. Но есть проблема, с которой вы иногда сталкиваетесь. Если у вас есть пакет, вы предполагаете, что внутри него есть модули и код. Но вы редко будете считать __init__.py
как один, потому что, позвольте ему взглянуть на него, в большинстве случаев это просто требование сделать модули из каталога импортируемыми.
package
__init__.py
mod1.py
humans.py
cats.py
dogs.py
cars.py
commons.py
Где должен находиться класс Family
? Это общий класс, и это зависит от других, потому что мы можем создать семью людей, собак и кошек, даже автомобили!
По моей логике и логике моих друзей это должны быть места в отдельном файле, но я постараюсь найти его в commons
, затем в humans
, а затем... Я буду смущен, потому что я не знаю, где это!
Глупый пример, да? Но это дает точку.