Python модули и пакеты

Kate

Administrator
Команда форума
При написании объёмного кода, часто прибегают к разбиению такового на логически независимые блоки и к последующему выносу в другие файлы. Это повышает читаемость как самого кода, так и проекта целиком. Что влечет за собой менее ресурсозатратную поддержку(дальнейшую модификацию кодовой базы для разных нужд).

После разделения кода по файлам, следует выстроить их взаимодействие. В языке программирования Python данный механизм реализуется с использованием import. Импортировать можно любые компоненты(если Вы кодом не ограничивали таковые) модулей или пакетов.

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

Модули​

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

Дальше стоит понимать, что любая программа имеет некую точку входа. Это своего рода место с которого стартует наш скрипт. В языках предшественниках данной точкой служила функция main и могла быть лишь только одной. В нашем случае допускается отсутствие таковой, но это снижает качество кода, делая его сложным и малопредсказуемым(при импорте код содержащийся на верхнем уровне исполняется). Для того чтобы указать точку входа(может быть указана только в модулях) используется специальная переменная __name__, в которой содержится наименование текущего модуля или пакета. Если текущий модуль находится на верхнем уровне исполнения(мы явно его передали на исполнение Python), то он называется __main__ независимо от названия файла.

# Указание входной точки
## Если __name__ равно "__main__" исполни
if __name__ == "__main__":
print('Я главный!')
# Вызов других функций, например main()
Для примера реализуем простой модуль, который будет возвращать нам информацию:

# http_get.modules.http_get
# Расположен в дирректории http_get, modules и назван http_get
def get_dict():
return {'satus': 200, 'data': 'success'}


# Поскольку зависимых импортов нет, мы можем исполнить этот код для проверки
# Т.е. в качестве входной точки использовать нашу функцию
# Данный код исполнится только, когда этот файл будет исполняемым(не импортируемым)
if __name__ == '__main__':
print(get_dict())
Далее в корне создадим main.py файл, в который импортируем наш модуль двумя разными способами(об импортах описано в статье):

# main.py
from ModulesAndPackages.module_examples.http_get.modules.http_get import get_dict as absolute
from http_get.modules.http_get import get_dict as relative


def main():
# Работает
print(absolute())
print(relative())


if __name__ == '__main__':
main()
Все без проблем исполняется.

Трудности​

При переносе файлов куда-либо из директории возникнут проблемы из-за первого импорта(main.py). Поскольку часто приходится писать один и тот же код, использование уже хорошо написанного пакета или модуля может экономить много времени, но исправление большого количества импортов требует Ваших ресурсов. Хорошо написанный пакет, модуль или импорт может экономить ваши рабочие часы, а иногда и нервы.

Смена директории
Не изменяя наши модули(импорты), при изменении положения файлов возникает ошибка импорта:
# Не работает в другом проекте
from ModulesAndPackages.module_examples.http_get.modules.http_get import get_dict as absolute
# Всегда работает
from http_get.modules.http_get import get_dict as relative


def main():

print(absolute())
print(relative())


if __name__ == '__main__':
main()

Пакеты​

В языке программирования Python пакетами являются все директории(вне зависимости от наличия в них модулей), содержащие файл __init__.py, который исполняется при импорте пакета и несет его название (__name__).
Для примера реализуем простой пакет(package), на базе вышеописанного модуля(http_get.py):
package/modules/http_get.py
# package/modules/http_get.py
def get_dict():
return {'satus': 200, 'data': 'success'}


if __name__ == '__main__':
print(get_dict())
# package/__init__.py
from .modules.http_get import get_dict
...


def get_data():
return get_dict()


...

# Не работает
# __init__ не может иметь точки входа
#
# if __name__ == '__main__':
# get_data()
А также реализуем простой пакет с такой же логикой, но с использованием абсолютного импорта:
package_2
# package_2/modules/http_get.py

def get_dict():
return {'satus': 200, 'data': 'success'}


if __name__ == '__main__':
print(get_dict())

# package_2/__init__.py
from ModulesAndPackages.package_examples.package_2.modules.http_get import get_dict
...


def get_data():
return get_dict()


...

# Не работает
# __init__ не может иметь точки входа
#
# if __name__ == '__main__':
# get_data()
В корне директории(на уровень выше пакета) создадим файл, в котором воспользуемся нашими пакетами(main.py):
# main.py
from package import get_data
from package_2 import get_data as get_data_2


def main():
# Работает
print(get_data())
print(get_data_2())


if __name__ == '__main__':
main()
Все работает без ошибок.

Трудности​

Но при переносе нашего package_2 в другой проект, он теряет свою работоспособность из-за ошибки импортирования в __init__.py файле, в отличии от package.
Смена директории
# package_transferring/package/modules/http_get.py

def get_dict():
return {'satus': 200, 'data': 'success'}


if __name__ == '__main__':
print(get_dict())
# package_transferring/package/__init__.py
from .modules.http_get import get_dict
...


def get_data():
return get_dict()


...

# Не работает
# __init__ не может иметь точки входа
#
# if __name__ == '__main__':
# get_data()
# package_transferring/package_2/modules/http_get.py

def get_dict():
return {'satus': 200, 'data': 'success'}


if __name__ == '__main__':
print(get_dict())
# package_transferring/package_2/__init__.py
# Ошибка импорта т.к. изменилась директория
from ModulesAndPackages.package_examples.package_2.modules.http_get import get_dict
...


def get_data():
return get_dict()


...

# Does not work!
# Because init file in package could not have entry point
#
# if __name__ == '__main__':
# get_data()
# package_transferring/main.py
from package import get_data

# Ошибка импорта
from package_2 import get_data as get_data_2


def main():
print(get_data())
print(get_data_2())


if __name__ == '__main__':
main()

P.S.​

Данная статья написана для новичков, которые изучают язык программирования Python. Задача которой продемонстрировать на простых примерах способы написания пакетов и модулей(не является туториалом), а так же показать какие трудности могут возникнуть и пути их решения.
Github с проектом к данной статье: ModulesAndPackages

 
Сверху