Курс по Python: https://stepik.org/course/100707
На этом занятии речь пойдет о пакетах, узнаем, как их создавать и импортировать. Ранее мы с вами
уже говорили о модулях – отдельных файлах с текстами программ, которые можно
импортировать в другие программы. А пакет (package) – это
специальным образом организованный подкаталог с набором модулей, как правило,
решающих сходные задачи.
Давайте в
качестве примера создадим пакет из модулей по обучающим курсам:
Это можно
сделать, следующим образом. В PyCharm во вкладке «Project» щелкнуть
правой кнопкой мыши и из выпадающего меню выбрать New -> Python Package. Здесь нам нужно
придумать название пакета, пусть оно будет:
courses
Нажимаем Enter и пакет в
рабочем каталоге создан. Посмотрим, что он из себя представляет. Переходим в
рабочий каталог и видим в нем подкаталог с указанным именем courses. Внутри
этого каталога находится только один пустой файл __init__.py. Чуть позже мы
узнаем для чего он нужен.
Итак, пакет в Python – это обычный
каталог, в котором обязательно должен располагаться специальный файл __init__.py.
Но пока наш
пакет пустой, в нем нет ни одного модуля. Добавим их, то есть, добавим обычные python-файлы в этот
каталог. Пусть они будут такими:
html.py:
def get_html():
print("курс по HTML")
java.py
def get_java():
print("Курс по Java")
php.py
def get_php():
pass
def get_mysql():
pass
python.py
def
get_python():
print("курс по Python")
И, кроме того, в
файл __init__.py добавим строчку:
И обратите
внимание. Для корректной обработки модулей в пакете, все файлы следует
создавать с кодировкой UTF-8.
Все, мы
сформировали пакет и теперь можем его импортировать в нашу программу. Для этого
записывается ключевое слово import и указывается имя пакета
(название подкаталога):
Давайте
посмотрим, что в итоге было импортировано:
Мы видим имя
нашей переменной NAME и вспомогательные переменные, которые были созданы
автоматически средой Python:
['NAME',
'__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
'__package__', '__path__', '__spec__']
Выведем значение
переменной NAME:
И в консоли
отображается строчка, прописанная в файле __init__.py. О чем это
говорит? О том, что при импорте пакета файл __init__.py был выполнен. В
действительности – это инициализатор пакета. В нем мы прописываем то, что
следует импортировать при импорте пакета. Например, в нашем каталоге courses присутствуют
четыре наших файла, но ни один из них не был использован в момент импорта. Это
произошло, как раз, по той причине, что в файле __init__.py мы их никак не
обрабатывали.
Чтобы получить
доступ к функциям из модулей внутри пакета, их, в свою очередь, нужно
импортировать в инициализаторе __init__.py, например, так:
Я, думаю, вы
понимаете, почему вначале указан подкаталог courses? Так как модули
находятся по нестандартному пути, то этот путь следует явно прописать. В данном
случае, достаточно указать имя подкаталога, а затем, через точку имя файла.
Теперь, после
запуска программы, мы видим, что в пространстве имен пакета courses появилось имя python. Поэтому, мы
можем обратиться к этому модулю и вызвать его функцию:
courses.python.get_python()
Но, обычно, в
инициализаторе пакетов импорт выполняют с помощью конструкции:
from courses.python import get_python
чтобы, затем, не
указывать имя модуля в основной программе:
Но, все же, у
такого способа импортирования модулей в инициализаторе пакета есть один
существенный недостаток – мы, фактически, указываем полный путь к модулю (это
называется абсолютным импортом). Представьте, например, что в будущем,
по каким-либо причинам, придется поменять название пакета. Тогда и все
абсолютные импорты также придется переписывать. Поэтому здесь лучше
воспользоваться относительным способом импортирования. Для этого, вместо
имени основного подкаталога courses, ставится точка:
from .python import get_python
Эта точка означает
«использовать текущий каталог». Так гораздо практичнее и мы теперь совершенно
не привязаны к названию пакета.
Или, можно
записать так:
from . import html, java, php, python
тогда
импортируем все модули в текущем пакете. Но, мы вернем прежний импорт и запишем
его со звездочкой для получения всех данных из файла:
Ранее, я вам
говорил, что в программах так лучше не делать. Однако, внутри пакетов делают –
это исключение из общего правила. Почему такой импорт целесообразен? Дело в том,
что модули внутри пакетов постоянно совершенствуются. Появляются новые функции,
переменные, классы. И, чтобы каждый раз не менять их список в импорте,
прописывают звездочку, так удобнее. При этом конфликта имен можно избежать,
контролируя импортируемые данные. Для этого внутри модуля прописывается
специальная переменная __all__ со списком импортируемых данных, если
используется оператор *. Например, если в модуле php.py, прописать:
__all__ = ['get_php', 'get_mysql']
то в пакет будут
импортированы обе функции и мы их можем вызвать:
courses.get_php()
courses.get_mysql()
ошибок никаких
не будет. Но, если оставить только какую-нибудь одну:
то прежняя
программа выполнится с ошибками, так как вторая функция не была импортирована.
Эту же коллекцию
__all__ можно
записывать и в инициализаторе, например, так:
from .python import *
from .php import *
NAME = "package courses"
__all__ = ['python', 'php']
Тогда модули python и php импортируются,
а переменная NAME нет.
И последнее, о
чем я, думаю, следует сказать – это возможность создавать вложенные пакеты.
Делается это очевидным образом, создаем еще один вложенный каталог, например, с
именем:
doc
через PyCharm (также). И,
далее, разместим в этом вложенном пакете два файла:
java_doc.py:
doc = """Документация по языку Java"""
python_doc.py:
doc = """Документация по языку Python"""
Затем, в файле
инициализатора этого пакета запишем импорт этих модулей:
from . import python_doc, java_doc
А в
инициализаторе основного пакета – импорт вложенного пакета:
и скорректируем
коллекцию:
__all__ = ['python', 'php', "doc"]
Все, теперь в
основной программе можно обращаться к переменным вложенного пакета, следующим
образом:
print(courses.python_doc.doc)
Мало того, в
самих модулях вложенного пакета можно обращаться к модулям внешнего пакета. Для
этого следует при импорте указать две точки (переход во внешний каталог) и
далее имя модуля, например (в файле python_doc.py):
from ..python import get_python
Затем, можно
вызвать эту функцию:
doc = """Документация по языку Python: """ + get_python()
соответственно,
ее переписав (в файле python.py):
def get_python():
return "курс по Python"
Вот так
относительно легко и просто можно создавать свои собственные пакеты, а также
вкладывать один в другой. Принцип описания фрагментов программ на уровне пакета
– довольно распространенная практика и вы теперь знаете, как все это работает.
Курс по Python: https://stepik.org/course/100707