Подтвердить что ты не робот

Импорта модулей и __init__.py в Python

Я пытаюсь понять, что лучше всего относится к механике импорта Python (v2.7). У меня есть проект, который начал немного расти и позволяет сказать, что мой код организован следующим образом:

foo/
    __init__.py
    Foo.py
    module1.py
    module2.py
    module3.py

Имя пакета foo, а под ним есть модуль Foo.py, который содержит код для класса foo. Поэтому я использую одно и то же имя для пакета, модуля и класса, которые могут быть не очень умными для начала.

__init__.py пусто и класс foo должен импортировать module1, module2 and module3, поэтому часть моего файла Foo.py выглядит так:

# foo/Foo.py

import module1
import module2
import module3

class Foo(object):
    def __init__(self):
....
....
if __name__ == '__main__':
    foo_obj = Foo()

Однако позже я повторил это, и я подумал, что было бы лучше иметь весь импорт в файле __init__.py. Следовательно, мой __init__.py теперь выглядит следующим образом:

# foo/__init__.py

import Foo
import module1
import module2
import module3
....
....

а мне Foo.py нужно импортировать foo:

# foo/Foo.py

import foo

Хотя это выглядит удобно, поскольку это один лайнер, я немного обеспокоен тем, что он может создавать круговые импорт. Я имею в виду, что при запуске script Foo.py он будет импортировать все, что может, и тогда будет вызываться __init__.py, который снова импортирует Foo.py (это правильно?). Кроме того, использование одного и того же имени для пакета, модуля и класса делает вещи более запутанными.

Имеет ли смысл то, как я это сделал? Или я прошу о неприятностях?

4b9b3361

Ответ 1

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

Если вы будете искать эту тему, вы неизбежно столкнетесь с людьми, рекомендующими рекомендации PEP8. Это де-факто канонические стандарты для организации кода на Python.

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

Исходя из этих рекомендаций, ваши модули проекта должны быть названы так:

foo/
    __init__.py
    foo.py
    module1.py
    module2.py
    module3.py

Я считаю, что лучше всего избегать ненужного импорта модулей в __init__.py если вы не делаете это по причинам пространства имен. Например, если вы хотите, чтобы пространство имен для вашего пакета выглядело так

from foo import Foo

вместо

from foo.foo import Foo

Тогда имеет смысл поставить

from .foo import Foo

в вашем __init__.py. Поскольку ваш пакет становится больше, некоторые пользователи могут не захотеть использовать все подпакеты и модули, поэтому нет смысла заставлять пользователя ждать загрузки всех этих модулей, неявно импортируя их в ваш __init__.py, Кроме того, вы должны рассмотреть, хотите ли вы, чтобы module1, module2 и module3 частью вашего внешнего API. Используются ли они только Foo и не предназначены для конечных пользователей? Если они используются только для внутреннего использования, не включайте их в __init__.py

Я также рекомендовал бы использовать абсолютный или явный относительный импорт для импорта подмодулей. Например, в foo.py

абсолют

from foo import module1
from foo import module2
from foo import module3

Явный Относительный

from . import module1
from . import module2
from . import module3

Это предотвратит любые возможные проблемы с именами других пакетов и модулей. Это также облегчит задачу, если вы решите поддерживать Python3, поскольку используемый в настоящее время неявный относительный синтаксис импорта не поддерживается в Python3.

Кроме того, файлы внутри вашего пакета обычно не должны содержать

if __name__ == '__main__'

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

Лучший способ предоставить исполняемые сценарии пользователям - использовать функцию scripts или console_scripts в setuptools. Способ организации ваших сценариев может быть разным в зависимости от того, какой метод вы используете, но я обычно организовываю свои так:

foo/
    __init__.py
    foo.py
    ...
scripts/
     foo_script.py
setup.py

Ответ 2

Согласно PEP 0008, "Публичные и внутренние интерфейсы" :

Импортированные имена всегда должны рассматриваться как детализация реализации. Другие модули не должны полагаться на косвенный доступ к таким импортированным именам, если они не являются явно документированной частью содержащего API модуля, например os.path или модуля пакета __init__, который предоставляет функции из подмодулей.

Таким образом, это может означать, что в модуле __init__ можно помещать импорт, если __init__ используется для отображения функций из подмодулей. Здесь - короткое сообщение в блоге, которое я нашел с несколькими примерами использования Pythonic __init__, используя импорт, чтобы сделать подпакеты доступными на уровне пакета.

Пример переноса операторов импорта на __init__, чтобы иметь только один импорт в Foo, кажется, что не соответствует этому правилу. Моя интерпретация заключается в том, что импорт в вашем __init__ должен использоваться для внешних интерфейсов, иначе просто поместите свои операторы импорта в необходимый им файл. Это сэкономит вам проблемы при изменении имен подмодулей и избавит вас от ненужного или труднодоступного импорта, когда вы добавляете больше файлов, в которых используется другое подмножество подмодулей.

Что касается круговых ссылок, это определенно возможно в Python (например). Я писал об этом, прежде чем я попробовал свой пример с игрушкой, но чтобы сделать пример работы, мне пришлось переместить Foo.py на уровень, например:

Foo.py
foo/
    __init__.py
    module1.py
    module2.py
    module3.py

С помощью этой настройки и некоторых операторов печати запуск python Foo.py дает результат:

module 1
module 2
module 3
hello Foo constructor

и нормально выходит. Обратите внимание, что это связано с добавлением if __name__ == "__main__" - если вы добавляете инструкцию печати за пределы этого, вы можете видеть, что Python по-прежнему загружает модуль дважды. Лучшим решением было бы удалить импорт из __init__.py. Как я сказал ранее, это может быть или не иметь смысла, в зависимости от того, что эти подмодули.

Ответ 3

Вы можете обратиться к "Руководству по стилю для кода Python" для лучших практик, импорт хранится в классе в этом руководстве.

https://www.python.org/dev/peps/pep-0008/#imports

Ответ 4

Я не могу утверждать окончательно, если это правильный путь, но я всегда делал это прежним способом. То есть я всегда __init__.py пустым и просто импортирую вещи в Foo.py мере необходимости.

Из того, как вы это описываете, кажется, что в последней форме происходит некая круговая логика.

Ответ 5

Попробуй это:

package1

package1.py

__init__.py

package2

test.py

package1.py:-

class abc:
    a = 'hello'
    def print_a(self):
    print(a)

init.py: -

from .package1 import abc

package2.py:-

From package1.package1 import abc

Я использую эти __init__.py для импорта из пакета.