Пакеты в Python

Содержание
Введение
__path__
sys.path
Создать пакет
Пример с os.path.splitext
__init__.py
Пример: demoreader
Абсолютный импорт
Относительный импорт
Пример с разными импортами
__all__
Полный код
Похожие статьи

Введение

Перед изучением этой статьи убедитесь, что вам знакома тема sys.path в Python

По умолчанию основным инструментом для организации программ является модуль.

Обычно модуль это файл с кодом на Python и расширением .py

Один модуль можно подгрузить в другой модуль или в REPL с помощью import.

У модуля как и у всего остального есть представление в виде объекта.

Пакет это такой тип модуля, который может содержать другие модули а также другие пакеты.

Рассмотрим на примере urllib

>>> import urllib
>>> import urllib.request
>>> type(urllib)

<class 'module'>

>>> type(urllib.request)

<class 'module'>

И urllib и urllib.request имеют тип module

При таком импорте вызвать request без указания родительского модуля нельзя.

Рассмотрим другой способ импорта

>>> from urllib import request
>>> request

<module 'urllib.request' from 'C:\\Users\\Andrei\\AppData\\Local\\Programs\\Python\\Python38-32\\lib\\urllib\\request.py'>

Видно, что request это дочерний модуль urllib

__path__

Разницу между urllib и urllib.request можно заметить по отсутствию у request атрибута __path__

>>> urllib.__path__

['C:\\Users\\Andrei\\AppData\\Local\\Programs\\Python\\Python38-32\\lib\\urllib']

Начиная с Python 3.3+ __path__ это список до этого он был просто строкой

>>> urllib.request.__path__

Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'urllib.request' has no attribute '__path__'

urllib это пакет. Пакеты обычно являются директориями, модули это обычно простые файлы.

sys.path

Перед тем как создавать свои пакеты изучите статью sys.path

Из неё вы узнаете о том, как сделать модуль видимым для Python с помощью sys.path.append или с помощью PYTHONPATH

Создать пакет

Первым делом нужно добавить рабочую директорию в PYTHONPATH

export PYTHONPATH=/home/andrei/packages/

Обратите внимание, что в PYTHONPATH добавлена директория в которой содержится пакет, а не сама директория с пакетом.

Внутри этой директории создадим следующую структуру проекта

packages └── pac ├── app │ └── double_sum.py └── lib └── regular_sum.py

Скрипт reqular_sum.py складывает два числа

# regular_sum.py def my_sum(a: float, b: float) -> float: return a + b if __name__ == "__main__": my_sum(4, 7)

Скрипт double_sum.py складывает два числа с помощью импортированной из regular_sum.py функции my_sum() и удваивает этот результат

# double_sum from regular_sum import my_sum def double_sum(a: float, b: float) -> float: s = my_sum(a, b) return s * 2 if __name__ == "__main__": print(double_sum(7, 11)) # expected result: 36

Если сходу запустить скрипт double_sum.py будет получено сообщение об ошибке

python double_sum.py

Traceback (most recent call last): File "/home/andrei/packages/pac/app/double_sum.py", line 1, in <module> from regular_sum import my_sum ModuleNotFoundError: No module named 'regular_sum'

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

from pac.lib.regular_sum import my_sum

Полный код:

# double_sum from pac.lib.regular_sum import my_sum def double_sum(a: float, b: float) -> float: s = my_sum(a, b) return s * 2 if __name__ == "__main__": print(double_sum(7, 11)) # expected result: 36

python double_sum.py

36

Пример с os.path.splitext

Подробнее про метод os.path вы можете прочитать в статье «Модуль os в Python»

import os my_file = "demo.custom" filename = os.path.splitext(my_file)[0] extension = os.path.splitext(my_file)[1] print(filename) print(extension)

demo
.custom

__init__.py

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

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

Почему это полезно:

С __init__.py файлами структура предыдущего проекта будет выглядеть так

pac ├── app │ ├── double_sum.py │ └── __init__.py ├── __init__.py └── lib ├── __init__.py └── regular_sum.py

Пример: demoreader

Рассмотрим пример применения __init__.py

mkdir demo_reader touch demo_reader/__init__.py python >>> import demo_reader >>> type(demo_reader)

<class 'module'>

>>> demo_reader.__file__

'/home/andrei/demo_reader/__init__.py'

Сделаем так чтобы __init__.py каждый раз информировал нас о том, что пакет импортирован

echo 'print("demo reader is being imported")' >> demo_reader/__init__.py

python >>> import demo_reader

demo reader is being imported

Добавим в пакет demo_reader файл multireader.py

demo_reader ├── __init__.py └── multireader.py

# demo_reader/__init__.py print("demo reader is being imported")

# demo_reader/multireader.py class MultiReader: def __init__(self, filename): self.filename = filename self.f = open(filename, 'rt') def close(self): self.f.close() def read(self): return self.f.read()

python >>> import demo_reader.multireader

demo reader is being imported

>>> r = demo_reader.multireader.MultiReader('demo_reader/__init__.py') >>> r.read()

'# demo_reader/__init__.py\n\nprint("demo reader is being imported")\n'

>>> r.close()

Добавим возможность читать файлы, сжатые, с помощью bz2 и gzip

demo_reader ├── compressed │ ├── bzipped.py │ ├── gzipped.py │ └── __init__.py ├── __init__.py └── multireader.py

# demo_reader/compressed/gzipped.py import gzip import sys opener = gzip.open # Alias for gzip.open # Decompresses during read if __name__ == '__main__': # Use gzip to create compressed file f = gzip.open(sys.argv[1], mode='wt') # Join to space-separaded string f.write(' '.join( sys.argv[2:]) # The data to compress ) f.close()

python -m demo_reader.compressed.gzipped test.gz data compressed with gz

Опция -m говорит о том, что нужно запустить модуль

demo_reader.compressed.gzipped - это имя модуля. Оно должно быть в формате FQMN - Fully-qualified module name, то есть содержать все нужные директории, разделённые точками.

test.gz - это argv[1] то есть имя файла, в который идёт запись

Всё что идёт после это argv[2:], в данном случае argv[2], argv[3], argv[4] и argv[5] - то есть данные, которые будут записаны в файл

Аналогичный скрипт для bz2

# demo_reader/compressed/bzipped.py import bz2 import sys opener = bz2.open if __name__ == '__main__': f = bz2.open(sys.argv[1], mode='wt') f.write(' '.join(sys.argv[2:])) f.close()

python -m demo_reader.compressed.bzipped test.bz2 data compressed with bz2

В результате у нас появилось два файла test.gz и test.bz2

Все модули и пакеты можно импортировать

python >>> import demo_reader demo reader is being imported >>> import demo_reader.multireader >>> import demo_reader.compressed >>> import demo_reader.compressed.gzipped >>> import demo_reader.compressed.bzipped

Изменим скрипт multireader.py чтобы использовать в нём новые модули.

Про метод get() читайте в статье словари в Python

# demo_reader/multireader.py import os from demo_reader.compressed import bzipped, gzipped extension_map = { '.bz2': bzipped.opener, '.gz': gzipped.opener } class MultiReader: def __init__(self, filename): extension = os.path.splitext(filename)[1] opener = extension_map.get(extension, open) self.f = opener(filename, 'rt') def close(self): self.f.close() def read(self): return self.f.read()

python >>> from demo_reader.multireader import MultiReader

demo reader is being imported

>>> r = MultiReader('test.bz2') >>> r.read()

'data compressed with bz2'

>>> r.close() >>> r = MultiReader('test.gz') >>> r.read()

'data compressed with gzip'

>>> r.close() >>> r = MultiReader('demo_reader/__init__.py')

>>> r.read()

'# demo_reader/__init__.py\n\nprint("demo reader is being imported")\n'

>>> r.close()

Абсолютный импорт

Оба примера ниже используют абсолютный импорт. Указывается полное называние пакета и название модуля.

import demo_reader.compressed.bzipped from demo_reader.compressed import bzipped

Относительный импорт

Оба примера ниже используют относительный импорт. Его можно испльзовать только внутри пакета.

Можно указывать название пакета (подпакета) либо просто указать путь.

from ..module_name import name from ..import name

Преимущества относительного импорта - сокращение кода, возможность менять имя пакета и не менять при этом код модулей.

Тем не менее стандартной практикой является избегание относительного импорта везде где это возможно. С помощью современный IDE , таких как PyCharm можно легко рефакторить код в полуавтоматическом режими. Это делает преимущества относительного импорта несущественным.

Пример с относительным импортом

Изменим наш код, чтобы уменьшить повторы и заодно показать оба типа импорта.

Добавим директорию util а в ней файлы __init__.py и writer.py

demo_reader ├── compressed │ ├── bzipped.py │ ├── gzipped.py │ └── __init__.py ├── __init__.py ├── multireader.py └── util ├── __init__.py └── writer.py

# demo_reader/util/writer.py import sys def main(opener): f = opener(sys.argv[1], mode='wt') f.write(' '.join(sys.argv[2:])) f.close()

Теперь bzipped.py и gzipped.py могут пользоваться writer.py

Для этого bzipped.py импортирует writer.py через абсолютный путь а gzipped.py через отностиельный

# demo_reader/compressed/bzipped.py import bz2 from demo_reader.util import writer opener = bz2.open if __name__ == '__main__': writer.main(opener)

Обратите внимание на то, как вызывается функция main - через точку после названия модуля

# demo_reader/compressed/gzipped.py import gzip from ..util import writer opener = gzip.open if __name__ == '__main__': writer.main(opener)

На примере

demo_reader/compressed/bzipped.py

Рассмотрим как выглядят абсолютные и относительные пути для импорта

Относительный Абсолютный
from . import name from demo_reader.compressed import name
from .. import name from demo_reader import name
from ..util import name from demo_reader.util import name
from .. import util from demo_reader import util

__all__

__all__ это атрибут модуля. Он отвечает за то, что будет импортировано если кто-то захочеть сделать

from module import *

Если __all__ не задан, то после import * будут импортированы все публичные имена

__all__ должен быть списком строк каждым элементом которого является имя для импорта

Отредактируем файл __init__.py в директории compressed

from demo_reader.compressed.bzipped import opener as bz2_opener from demo_reader.compressed.gzipped import opener as gzip_opener

Без __all__

python >>> from pprint import pprint >>> pprint(locals())

{'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__name__': '__main__', '__package__': None, '__spec__': None, 'pprint': <function pprint at 0x7fe88894f3a0>}

>>> from demo_reader.compressed import *

demo reader is being imported

>>> pprint(locals())

{'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__name__': '__main__', '__package__': None, '__spec__': None, 'bz2_opener': <function open at 0x7fe888921ee0>, 'bzipped': <module 'demo_reader.compressed.bzipped' from '/home/avorotyn/python/lessons/pluralsight/organizing_larger_programs/chapter3/demo_reader/compressed/bzipped.py'>, 'gzip_opener': <function open at 0x7fe8888d34c0>, 'gzipped': <module 'demo_reader.compressed.gzipped' from '/home/avorotyn/python/lessons/pluralsight/organizing_larger_programs/chapter3/demo_reader/compressed/gzipped.py'>, 'pprint': <function pprint at 0x7fe88894f3a0>}

С помощью __all__ можно импортировать только bz2_opener и gzip_opener

# demo_reader/compressed/__init__.py from demo_reader.compressed.bzipped import opener as bz2_opener from demo_reader.compressed.gzipped import opener as gzip_opener __all__ = ['bz2_opener', 'gzip_opener']

python >>> from pprint import pprint >>> pprint(locals())

{'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__name__': '__main__', '__package__': None, '__spec__': None, 'pprint': <function pprint at 0x7fe88894f3a0>}

>>> from demo_reader.compressed import *

demo reader is being imported

>>> pprint(locals())

{'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__name__': '__main__', '__package__': None, '__spec__': None, 'bz2_opener': <function open at 0x7fe888921ee0>, 'gzip_opener': <function open at 0x7fe8888d34c0>, 'pprint': <function pprint at 0x7fe88894f3a0>}

>>> bz2_opener

<function open at 0x7f3db4c0fee0>

>>> gzip_opener

<function open at 0x7f3db4bc34c0>

Полный код

Создание архивов с помощью

python -m demo_reader.compressed.bzipped test.bz2 data compressed with bz2
python -m demo_reader.compressed.gzipped test.gz data compressed with gz

В этом варианте выдаст предупреждение

/home/andrei/.pyenv/versions/3.9.5/lib/python3.9/runpy.py:127: RuntimeWarning: 'demo_reader.compressed.bzipped' found in sys.modules after import of package 'demo_reader.compressed', but prior to execution of 'demo_reader.compressed.bzipped'; this may result in unpredictable behaviour warn(RuntimeWarning(msg))

Архив, тем не менее, будет создан, я пока что обдумываю решение.

Структура

demo_reader ├── compressed │ ├── bzipped.py │ ├── gzipped.py │ └── __init__.py ├── __init__.py ├── multireader.py └── util ├── __init__.py └── writer.py

# demo_reader/compressed/bzipped.py import bz2 from demo_reader.util import writer opener = bz2.open if __name__ == '__main__': writer.main(opener)

# demo_reader/compressed/gzipped.py import gzip from ..util import writer opener = gzip.open if __name__ == '__main__': writer.main(opener)

# demo_reader/compressed/__init__.py from demo_reader.compressed.bzipped import opener as bz2_opener from demo_reader.compressed.gzipped import opener as gzip_opener __all__ = ['bz2_opener', 'gzip_opener']

# demo_reader/__init__.py print("demo reader is being imported")

# demo_reader/multireader.py import os from demo_reader.compressed import bzipped, gzipped extension_map = { '.bz2': bzipped.opener, '.gz': gzipped.opener } class MultiReader: def __init__(self, filename): extension = os.path.splitext(filename)[1] opener = extension_map.get(extension, open) self.f = opener(filename, 'rt') def close(self): self.f.close() def read(self): return self.f.read()

# demo_reader/util/__init__.py

# demo_reader/util/writer.py import sys def main(opener): f = opener(sys.argv[1], mode='wt') f.write(' '.join(sys.argv[2:])) f.close()

python -m demo_reader.compressed.bzipped test.bz2 data compressed with bz2 by Andrei

python >>> from demo_reader.multireader import MultiReader

demo reader is being imported

>>> r = MultiReader('test.bz2') >>> r.read()

'data compressed with bz2 by Andrei'

Похожие статьи
Пакеты в Python
Namespace пакеты в Python
Правильная структура пакета
setuptools
Плагины
Python

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: