Является ли 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, а затем... Я буду смущен, потому что я не знаю, где это!

Глупый пример, да? Но это дает точку.