На этом шаге мы рассмотрим особенности выполнения этой операции.
У вас есть модуль, который вы хотели бы разделить на несколько файлов. Однако вы хотели бы сделать это, не ломая существующий код - сохранив отдельные файлы объединенными в один логический модуль.
Программный модуль можно разделить на отдельные файлы путем превращения в пакет. Рассмотрим следующий простой модуль:
# mymodule.py class A: def spam(self): print('A.spam') class B(A): def bar(self): print('B.bar')
Предположим, что вы хотите разделить mymodule.py на два файла, по одному на каждое определение класса. Чтобы это сделать, начните с замены файла mymodule.py на каталог с именем mymodule. В этом каталоге создайте следующие файлы:
mymodule/
__init__.py
a.py
b.py
В файл a.py поместите этот код:
# a.py class A: def spam(self): print('A.spam')
В файл b.py поместите этот код:
# b.py from .a import A class B(A): def bar(self): print('B.bar')
И наконец, склейте в файле __init__.py оба этих файла:
# __init__.py from .a import A from .b import B
Если вы выполните эти шаги, получившийся пакет mymodule будет работать как единый логический модуль:
>>> import mymodule >>> a = mymodule.A() >>> a.spam() A.spam >>> b = mymodule.B() >>> b.bar() B.bar >>>
Основная проблема в этом рецепте - понять, хотите ли вы, чтобы пользователи работали с большим количеством маленьких модулей или одним модулем. Например, если у вас обширная кодовая база, вы можете просто разбить все на отдельные файлы и заставить пользователей писать множество инструкций import:
from mymodule.a import A from mymodule.b import B . . .
Это работает, но на пользователей возлагается бремя - они должны знать, где расположены различные части. Часто проще все объединить и разрешить единый импорт:
from mymodule import A, B
В последнем случае о mymodule обычно думают как о большом файле с исходным кодом. Однако этот рецепт показывает, как сшить в единое логическое пространство имен множество файлов. Ключ к этому - создать каталог пакета и использовать для склеивания частей __init__.py.
Когда модуль разделяется, вы должны внимательно отнестись к перекрестным ссылкам имен файлов. В этом рецепте, например, класс B нуждается в доступе к классу A как к базовому классу. Чтобы добиться этого, используется относительное импортирование на уровне пакета from .a import A.
Относительное импортирование на уровне пакета использовано в этом рецепте для устранения прямого прописывания имени модуля высшего уровня в исходном коде. Это делает проще переименование и перемещение модуля (см. 185 шаг).
Для этого рецепта есть интересное дополнение, которое вводит концепцию "ленивого" импортирования. Как показано выше, __init__.py импортирует все требуемые подкомпоненты за один раз. Однако для очень большого модуля может быть целесообразно импортировать компоненты по мере необходимости. Чтобы сделать это, немного изменим __init__.py:
# __init__.py def A(): from .a import A return A() def B(): from .b import B return B()
В этой версии классы A и B заменены функциями, которые загружают нужные классы при первом доступе к ним. С точки зрения пользователя это не слишком большое отличие. Например:
>>> import mymodule >>> a = mymodule.A() >>> a.spam() A.spam >>>
if isinstance(x, mymodule.A): # Ошибка if isinstance(x, mymodule.a.A): # Ok
Если вам нужен пример ленивой загрузки из реального мира, посмотрите на исходный код multiprocessing/__init__.py в стандартной библиотеке.
На следующем шаге мы рассмотрим создание отдельных каталогов с кодом для импорта под общим пространством имен.