Добавление кода в __init__.py

85

Я смотрю, как работает модельная система в django, и заметил кое-что, чего не понимаю.

Я знаю, что вы создаете пустой __init__.pyфайл, чтобы указать, что текущий каталог является пакетом. И что вы можете установить некоторую переменную, __init__.pyчтобы import * работал правильно.

Но django добавляет кучу операторов from ... import ... и определяет кучу классов внутри __init__.py. Зачем? Разве это не просто беспорядочно? Есть ли причина, по которой этот код требуется __init__.py?

Эрик
источник
13
Дело не в Django, не так ли? Да, вы впервые увидели это в Django, но это больше похоже на чистый Python - возможно, тег Django действительно не подходит.
S.Lott
Я не вижу операторов импорта в __init__.pydjango 1.8. Это было для более старой версии? если да, то какая версия?
Гоби Дасу

Ответы:

72

Все операции импорта __init__.pyстановятся доступными при импорте пакета (каталога), который его содержит.

Пример:

./dir/__init__.py:

import something

./test.py:

import dir
# can now use dir.something

РЕДАКТИРОВАТЬ: забыл упомянуть, что код __init__.pyзапускается при первом импорте любого модуля из этого каталога. Так что обычно это хорошее место для размещения любого кода инициализации на уровне пакета.

EDIT2: dgrant указал на возможную путаницу в моем примере. Он __init__.py import somethingможет импортировать любой модуль, не обязательно из пакета. Например, мы можем заменить его на import datetime, тогда на нашем верхнем уровне test.pyоба этих фрагмента будут работать:

import dir
print dir.datetime.datetime.now()

а также

import dir.some_module_in_dir
print dir.datetime.datetime.now()

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

Александр Кожевников
источник
Хорошо спасибо. Но я все еще не уверен, почему было бы неплохо добавить классы. __init__.py Я действительно не рассматриваю код инициализации этих классов (но, возможно, я ошибаюсь в этом).
Эрик
Вероятно, это классы, которые будут полезны каждый раз, когда вы работаете с пакетом. Но я не хочу строить догадки, может быть много причин, по которым они существуют, объективные или нет :)
Александр Кожевников
13
Это также может быть по историческим причинам. Когда вы конвертируете модуль в пакет, module.py в module / __ init__.py весь существующий код может использовать его, как и раньше, но теперь модуль может иметь подмодули.
ukasz
1
Модули выполняют parent __init__.pyнеявно. Импортируя модули внутри __init__.py, вы создаете циклический импорт. __init__.pyНе будет выполнена полностью , прежде чем одного такого импорта. Безопаснее оставить __init__.pyпустым.
Иво Данихелка 09
Важно отметить, что это не относится к __init__.pyфайлам. Если бы у вас был файл, в dir/other.pyкотором было что-то подобное, from datetime import datetimeвы также могли бы позвонить dir.other.datetime.now()или даже from dir.other import datetime.
Carles Sala
37

На самом деле это просто личные предпочтения, и они связаны с компоновкой ваших модулей Python.

Допустим, у вас есть модуль с именем erikutils. Есть два способа , которыми это может быть модулем, либо у вас есть файл с именем erikutils.py на своем контекстуальном sys.pathили у вас есть каталог с именем erikutils на вашем sys.pathс пустым __init__.pyфайлом внутри него. Тогда скажем , у вас есть куча модулей , называемых fileutils, procutils, parseutilsи вы желаете, чтобы быть суб-модулей под erikutils. Таким образом , вы сделать некоторые .py файлы , называемые fileutils.py , procutils.py и parseutils.py :

erikutils
  __init__.py
  fileutils.py
  procutils.py
  parseutils.py

Может быть , у вас есть несколько функций , которые просто не принадлежат в fileutils, procutilsили parseutilsмодулях. Допустим, вам не хочется создавать новый модуль с именем miscutils. И вы хотите иметь возможность вызывать функцию следующим образом:

erikutils.foo()
erikutils.bar()

вместо того, чтобы делать

erikutils.miscutils.foo()
erikutils.miscutils.bar()

Так как erikutilsмодуль - это каталог, а не файл, мы должны определить его функции внутри __init__.pyфайла.

В django лучший пример, который я могу придумать, - это django.db.models.fields. ВСЕ классы django * Field определены в __init__.pyфайле в каталоге django / db / models / fields . Я думаю, они сделали это, потому что не хотели втиснуть все в гипотетическую модель django / db / models / fields.py , поэтому они разделили ее на несколько подмодулей ( например , related.py , files.py ) и они вставили сделанные * определения полей в сам модуль полей (следовательно, __init__.py).

оборота dgrant
источник
1
dgrant, я имел в виду, что это somethingможет быть внешний модуль, dir.something будет работать. Спасибо за комментарий, отредактирую свой пост, чтобы было понятнее.
Александр Кожевников,
29

Использование __init__.pyфайла позволяет сделать внутреннюю структуру пакета невидимой снаружи. Если внутренняя структура изменилась (например, из-за того, что вы разбили один толстый модуль на два), вам нужно настроить только __init__.pyфайл, но не код, который зависит от пакета. Вы также можете сделать части вашего пакета невидимыми, например, если они не готовы к общему использованию.

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

from somemodule import some_function1, some_function2, SomeObject

del somemodule

Теперь, если вы решите разделить, somemoduleновое __init__.pyможет быть:

from somemodule1 import some_function1, some_function2
from somemodule2 import SomeObject

del somemodule1
del somemodule2

Снаружи упаковка выглядит так же, как и раньше.

nikow
источник
1
@Arlen: Дело в том, что это не часть общедоступного API. Если вы переименуете модуль, вы можете быть уверены, что ни один зависимый код не сломается. Вдобавок это гарантирует, что элементы API появляются только один раз, например, когда интроспекция используется для автоматического создания документации API.
nikow
5
@Arlen: Удаление модуля предотвращает import <pack>.somemodule1прямой доступ к нему . Вы можете импортировать только <pack>объекты, определенные или импортированные в его __init__.py, и не удаленные подмодули.
MestreLion