Python з нуля · Блок 4 · Тема 16

Модулі й пакети

Досі вся програма жила в одному файлі. Тепер вона розкладається на частини — і треба зрозуміти, як ці частини знаходять одна одну.

У темі 14 ми навчилися пакувати шматок роботи у функцію й давати їй ім'я, а в темі 15 розібрали, як значення потрапляють усередину функції та за якими правилами Python шукає імена. Усе це відбувалося в одному файлі. Сьогодні ми виводимо код за межі файлу — і саме тут починається справжня організація програми.

Причина проста, і ти вже могла її відчути. Файл на п'ятдесят рядків читається легко. Файл на п'ятсот — уже ні: щоб знайти потрібну функцію, доводиться гортати; щоб узяти цю функцію в інший проєкт, доводиться копіювати. А копія — це майже завжди майбутній баг, бо виправлення в одному місці не доїжджає до другого.

01 / МотивОдин файл перестає вміщувати

Уяви крамницю, для якої ми пишемо облік. У програмі є три різні за змістом купки коду: розрахунок цін і знижок, друк чеків, збереження даних на диск. Поки їх десять рядків, вони мирно живуть разом. Коли їх стане триста, почнеться неприємне:

Розв'язок називається модуль (module). Це найдешевший спосіб розділити програму: просто розкласти код по різних файлах і навчитися брати з них потрібне.

02 / ОзначенняМодуль — це просто файл

Модуль у Python — це файл із розширенням .py. Оце й усе означення, іншого немає. Будь-який файл, який ти написала, вже є модулем — ти просто ще жодного разу його не імпортувала.

Ім'я модуля — це ім'я файлу без .py. Файл znyzhky.py — модуль znyzhky. Створимо його поруч із головним файлом:

# znyzhky.py
STANDARTNA = 0.10

def cina_zi_znyzhkoyu(cina, vidsotok):
    # повертаємо ціну, зменшену на заданий відсоток
    return round(cina * (1 - vidsotok), 2)

А тепер головний файл поруч із ним:

# main.py
import znyzhky

print(znyzhky.cina_zi_znyzhkoyu(250, znyzhky.STANDARTNA))
225.0

Зверни увагу на форму звертання: znyzhky.cina_zi_znyzhkoyu. Крапка тут означає те саме, що й завжди в Python, — «візьми з цього об'єкта оте, що всередині». Після імпорту znyzhky стає звичайним об'єктом, у якого всередині лежать усі імена з файлу.

Місток до теми 15. Пам'ятаєш правило LEGB — локальна, огортаюча, глобальна, вбудована області? Так от, «глобальна» насправді ніколи не була глобальною для всієї програми. Це область одного модуля, і саме тому в правильній назві стоїть слово module: функція globals() повертає словник імен свого файлу, а не всієї програми. Модуль дає кожному файлу власний простір імен, і STANDARTNA в одному файлі ніяк не заважає STANDARTNA в іншому.

03 / ПошукКуди дивиться import

Коли інтерпретатор бачить import znyzhky, він не обшукує весь диск. Він іде чітко визначеним маршрутом і бере перше, що знайшов. Маршрут такий:

  1. sys.modules — словник уже завантажених модулів. Якщо модуль там є, робота закінчена, далі не дивимось.
  2. Вбудовані модулі — ті, що вшиті в сам інтерпретатор (sys, builtins, time). Їх не лежить окремим файлом ніде на диску.
  3. Список тек із sys.path — по черзі, згори вниз. Перший рядок цього списку — тека, з якої запустили скрипт; далі теки стандартної бібліотеки; наприкінці site-packages, куди складає файли pip.

Якщо маршрут закінчився нічим, ти бачиш ModuleNotFoundError: No module named 'znyzhky'. Це не «Python зламався», а рівно одне: «я пройшов увесь список і не знайшов». Обери модуль в інтерактиві й простеж маршрут очима:

Інтерактив 1 · Маршрут пошуку модуля

Місця перевіряються згори вниз. Виграє перше, у якому щось знайшлося.

де знайшовся
Головне, що видно: тека, з якої запустили скрипт, стоїть першою — раніше за стандартну бібліотеку. Саме звідси береться класична пастка: файл random.py у своїй теці робить справжній модуль random недосяжним для всієї програми. Ознака — AttributeError на функції, яка точно існує.
Не називай свої файли іменами зі стандартної бібліотеки. random.py, json.py, math.py, string.py, types.py — усі вони стануть на місце справжніх. Помилка виглядає загадково: import random проходить, а random.randint(1, 6) падає з AttributeError: module 'random' has no attribute 'randint'. Бо імпортувався твій файл, а в ньому такої функції немає. Перевірка одним рядком: надрукуй random.__file__ і подивись, який шлях там написано.

04 / ФормиТри форми імпорту

Одну й ту саму дію можна записати трьома способами, і різняться вони рівно одним: які імена з'являються у твоєму файлі. Не швидкістю, не «глибиною» завантаження — саме іменами.

формащо з'явиться у файліяк звертатись
import mathодне ім'я mathmath.sqrt(16)
from math import sqrtодне ім'я sqrtsqrt(16)
import math as mодне ім'я mm.sqrt(16)
from math import *усі публічні імена модуляsqrt(16)

Важлива й неочевидна деталь: у всіх чотирьох випадках модуль завантажується цілком. from math import sqrt не «бере лише квадратний корінь» — Python виконує весь math, кладе його в sys.modules, і тільки потім витягує звідти одне ім'я. Різниця лише в тому, що потрапляє в твій простір імен.

Подивись, як після кожної форми змінюється простір імен файлу. Уяви, що до імпорту у файлі вже жили дві речі: наша змінна e (енергія в джоулях) і вбудована функція pow:

Інтерактив 2 · Що саме з'являється у просторі імен

Ліворуч код, праворуч — імена, доступні у файлі після нього.

файл main.py
простір імен після імпорту
додано імен
затерто
Порівняй перші три з четвертою. Перші три додають рівно одне ім'я — ти точно знаєш, звідки воно взялося. Зірочка додає близько шістдесяти, і два з них тихо затирають те, що вже було: наша e стає числом 2.718…, а pow — версією з math, яка завжди повертає дробове число й не вміє третього аргументу.

05 / ПасткаЧому зірочка — погана звичка

Формально from math import * працює. Практично це найшвидший спосіб зіпсувати собі вечір, і причин три.

Перша: затінення імен. Ми щойно бачили це в інтеракторі. Зірочка привела шістдесят імен, серед яких e і pow. Твоя змінна e перестала існувати — не з помилкою, а мовчки. Програма далі рахує з числом 2.718 замість твоєї енергії й дає правдоподібну дурницю. Це найгірший тип багу: без traceback.

Друга: незрозуміло, звідки ім'я. Побачивши sqrt(16) посеред чужого коду з трьома зірочними імпортами вгорі, ти не можеш сказати, з якого саме модуля воно прийшло. Доводиться відкривати всі три. З math.sqrt(16) питання не виникає взагалі.

Третя: порядок раптом починає значити. Якщо два модулі мають функцію з однаковим ім'ям, виграє той, чий зірочний імпорт стоїть нижче. Переставили два рядки місцями — програма змінила поведінку. Це те, чого в коді бути не повинно.

Виняток, який підтверджує правило. У REPL, коли ти щось швидко перевіряєш і за хвилину закриєш вікно, from math import * цілком доречний — тут економія натискань важливіша за чистоту. У файлі, який хтось читатиме, — ні. Ще один живий виняток: деякі бібліотеки навмисно проєктовані під зірочку й обмежують її списком __all__ усередині модуля. Але за замовчуванням правило одне: явне краще за неявне.

Що обирати щодня

Проста рекомендація, яка працює в 95% випадків: import модуль — типовий вибір, бо на місці виклику видно походження. from модуль import ім'я — коли ім'я коротке, однозначне й вживається багато разів (from pathlib import Path). Псевдонім as — коли ім'я довге або є усталена скорочена форма (import numpy as np). Зірочка — ніколи у файлі.

06 / КешМодуль виконується один раз

Ось річ, яка дивує всіх. Імпорт — це виконання файлу. Не «підключення», не «оголошення»: Python бере файл модуля й виконує його згори вниз, як звичайну програму. Рядки def створюють функції, рядки з присвоєнням створюють змінні, а рядок print("привіт") просто друкує.

А тепер продовження: цей файл виконується рівно один раз за весь запуск програми. Другий, третій і сотий import того самого модуля не виконують нічого — вони беруть готовий об'єкт зі словника sys.modules. Натисни «наступний крок» і подивись:

Інтерактив 3 · Чотири імпорти, одне виконання

Модуль nalashtuvannya.py друкує рядок при завантаженні. Скільки разів ми його побачимо?

nalashtuvannya.py
print("завантажую налаштування…")
MOVA = "uk"
VALYUTA = "грн"
main.py
тіло виконано0
взято з кешу0
Практичний наслідок: модуль — природне місце для того, що має існувати в одному екземплярі: прочитані налаштування, підготовлений словник, відкрите з'єднання. Але й навпаки: якщо ти правиш модуль під час роботи програми, зміни не підхопляться — треба перезапустити процес (або в зошиті — перезапустити ядро).
Зворотний бік у зошиті. Саме через цей кеш студенти годинами воюють із Jupyter: правиш свій .py, перезапускаєш клітинку з import — а нічого не змінюється. Модуль уже в sys.modules, і повторний імпорт його звідти й бере. Лікується перезапуском ядра (Kernel → Restart) або, для тих, хто знає, що робить, importlib.reload(модуль).

07 / СлужбовеТека __pycache__

Після першого імпорту поруч із твоїми файлами з'явиться тека __pycache__, а в ній щось на кшталт znyzhky.cpython-312.pyc. Це не сміття й не помилка.

Перш ніж виконати код, Python перекладає його з тексту в байт-код — компактні інструкції для віртуальної машини. Переклад коштує часу, а результат для незмінного файлу завжди однаковий. Тому інтерпретатор його зберігає: наступного разу він порівняє час зміни й розмір .py з тим, що записано в .pyc, і якщо збігається — пропустить компіляцію.

Три речі, які варто знати. По-перше, ім'я містить версію (cpython-312), бо байт-код між версіями несумісний — кеші різних Python мирно лежать поруч. По-друге, кеш робиться лише для імпортованих модулів: файл, який ти запускаєш напряму, компілюється щоразу заново, і .pyc для нього не з'явиться. По-третє, __pycache__ завжди додають у .gitignore: це похідний файл, його не зберігають у репозиторії. Видалити теку безпечно — вона з'явиться знову.

08 / Роздвоєнняif __name__ == "__main__"

Ти бачила цей рядок у чужому коді й, найімовірніше, вважала його магічним заклинанням. Він не магічний, і зараз ми його повністю розберемо.

Почнімо з факту. У кожному модулі Python автоматично створює змінну __name__ (два підкреслення з кожного боку). Значення в неї кладеться за одним простим правилом:

випадок 1 · файл запустили напряму

Ти набрала python3 zvit.py. Цей файл — точка входу програми, головний. Python кладе в його __name__ рядок "__main__".

випадок 2 · файл імпортували

Хтось інший написав import zvit. Тепер файл — допоміжний, він обслуговує когось. Python кладе в __name__ справжнє ім'я модуля: "zvit".

висновок

Один і той самий файл може дізнатися, у якій він ролі. А отже — виконати щось лише в одній із них. Умова if __name__ == "__main__": читається дослівно як «якщо мене запустили головним, а не імпортували».

Навіщо це потрібно? Згадай: імпорт виконує весь файл. Якщо внизу zvit.py стоїть print(porahuvaty(1000)) — воно надрукується щоразу, коли хтось імпортує модуль заради однієї функції. Захисна умова прибирає цей побічний ефект. Перемкни режим і подивись, що виконується:

Інтерактив 4 · Одне ім'я — два значення

Той самий файл zvit.py у трьох сценаріях запуску.

zvit.py — не змінюється
що станеться
__name__ дорівнює
Правило на все життя: у файлі, який хтось може імпортувати, на верхньому рівні мають бути тільки означення — def, class, константи. Усе, що робить щось (друкує, читає файли, ходить у мережу), ховається під захисну умову. Інакше імпорт заради однієї функції потягне за собою всю роботу модуля.
Чому саме така форма, а не прапорець. Модуль не може «спитати», хто його викликав, — ніякого механізму зворотного зв'язку тут немає. Але ім'я, під яким модуль зареєстрований у sys.modules, уже містить цю інформацію: головний файл програми завжди лежить там під ключем "__main__". Тобто перевірка нічого не вигадує — вона просто читає те, що інтерпретатор і так записав.

09 / ПакетТека з модулями

Коли модулів стає багато, наступний крок очевидний: скласти їх у теку. Тека з модулями називається пакетом (package), і звертаються до її вмісту через крапку — так само, як до вмісту модуля.

projekt/
├── main.py
└── krama/           ← пакет
    ├── __init__.py
    ├── tovary.py
    └── znyzhky.py

Тепер from krama import znyzhky — і далі все як раніше. Крапка в krama.znyzhky.cina_zi_znyzhkoyu читається зліва направо: тека, файл у ній, функція у файлі.

Що робить __init__.py

Це звичайний модуль з особливою роллю: він виконується першим, коли імпортують пакет. Ім'я в нього не своє — це і є сам пакет: після import krama ім'я krama вказує на об'єкт, зібраний із __init__.py.

Найчастіше файл лишають порожнім — і цього достатньо. Друге за поширеністю застосування — зробити зручний «фасад»: перетягнути в нього кілька імен, щоб користувач писав from krama import cina_zi_znyzhkoyu, не знаючи внутрішньої будови пакета.

Для тих, хто чув, що __init__.py більше не потрібен. З Python 3.3 тека без цього файлу теж імпортується — механізм зветься namespace package і придуманий для того, щоб один пакет можна було зібрати з кількох різних тек. Штука корисна, але вузька. Побічний ефект неприємний: будь-яка тека раптом стає імпортованою, і одрук у назві дає не помилку, а мовчазний порожній пакет. Тому в звичайному проєкті __init__.py пиши — навіть порожній. Це дешевий спосіб сказати «так, це справді пакет».

Поклацай по файлах у дереві — унизу видно, як саме до кожного звертатись:

Інтерактив 5 · Дерево пакета й шлях імпорту

Обери файл. Усі імпорти написані так, як їх бачить main.py.

повне ім'я модуля
Ключ до всього: шлях імпорту рахується не від файлу, у якому ти пишеш, а від кореня проєкту — теки, що потрапила в sys.path. Тому з будь-якого місця проєкту той самий модуль зветься однаково: krama.zvity.pdf. Це і є абсолютний імпорт.

10 / СтильАбсолютні й відносні імпорти

Усередині пакета модулі часто звертаються один до одного. Це можна записати двома способами.

Абсолютний імпорт називає повний шлях від кореня проєкту: from krama.znyzhky import cina_zi_znyzhkoyu. Він виглядає однаково в усіх файлах пакета й читається без контексту.

Відносний імпорт рахує від поточного модуля: крапка означає «мій пакет», дві крапки — «пакет на рівень вище». Усередині krama/tovary.py це буде from .znyzhky import cina_zi_znyzhkoyu, а з krama/zvity/pdf.py до знижок — from ..znyzhky import cina_zi_znyzhkoyu.

Рекомендація без відтінків: бери абсолютні. Причин дві. Перша — читабельність: from ..utils.text import ochystyty вимагає знати, де ти зараз, а абсолютний варіант — ні. Друга серйозніша: файл із відносними імпортами не можна запустити напряму. Команда python3 krama/tovary.py впаде з ImportError: attempted relative import with no known parent package, бо запущений файл стає __main__ і втрачає інформацію про свій пакет. Запускати доводиться через python3 -m krama.tovary з кореня проєкту — і про це щоразу забувають.

11 / БатарейкиЕкскурсія стандартною бібліотекою

Про Python кажуть «батарейки в комплекті» (batteries included), і це не рекламна фраза. Разом з інтерпретатором ти вже отримала кількасот модулів, які нічого не треба ставити — досить написати import.

Ось шість, які знадобляться найшвидше. Кожному з них далі в курсі присвячена окрема тема, тому тут — по одному реченню й одному прикладу:

Інтерактив 6 · Шість модулів, які варто знати вже зараз

Обери модуль — побачиш, навіщо він і як виглядає найпростіший виклик.

Звичка, яка економить години: перш ніж писати щось самому, перевір, чи цього вже немає в стандартній бібліотеці. Розбір рядка з датою, підрахунок повторів, обхід тек, читання JSON — усе це давно написано, протестовано мільйонами людей і працює швидше за саморобку.

А коли потрібного модуля в стандартній бібліотеці немає — по нього йдуть у зовнішні пакети через pip, і ставлять їх не в систему, а у віртуальне середовище проєкту. Це рівно те, чим займається наступна тема.

12 / ГлибшеДля тих, хто вже писав код

Далі — те, з чим новачок зіткнеться не сьогодні, але що ламає реальні проєкти регулярно.

Циклічні імпорти

Ситуація: модуль a імпортує b, а b імпортує a. Виглядає безглуздо, а виникає природно — щойно два шматки коду починають потребувати один одного. І ламається не завжди, а залежно від того, що саме береться і коли.

Розберемо покроково. Ключ до розуміння — той самий sys.modules: запис у ньому з'являється до того, як тіло модуля виконалось. Тобто в кеші деякий час лежить напівготовий модуль:

Інтерактив 7 · Де саме ламається цикл

Два модулі, що потребують один одного. Крок за кроком — до збою й після виправлення.

a.py
b.py
Діагноз за повідомленням: «partially initialized module … most likely due to a circular import» — це дослівно «ти взяв із модуля те, чого він ще не встиг створити». Питання не «як обійти», а «чому два модулі знають один про одного» — майже завжди в них є спільна частина, яку варто винести окремо.

Два робочі виходи ти щойно бачила. Перший і правильний — переставити код: знайти те спільне, заради чого модулі тягнуться один до одного, і винести його в третій модуль, від якого залежать обидва. Цикл зникає разом із причиною.

Другий — перенести import усередину функції. Тоді він виконається не під час завантаження модуля, а під час виклику — коли обидва модулі вже добудовані.

Коли імпорт усередині функції виправданий

Загальне правило PEP 8 — імпорти вгорі файлу, і воно не просто так: там їх видно, і збій станеться одразу при старті, а не на п'ятій хвилині роботи. Але є три випадки, коли імпорт всередині функції — усвідомлений вибір.

Ціна такого імпорту майже нульова. Повторний import — це пошук у словнику sys.modules і присвоєння локального імені. Мільйон викликів у циклі помітиш, звичайну функцію — ні. Тож аргумент «це повільно» тут не працює; справжній аргумент проти — «збій знайдеться пізно».

Дрібниці, які тішать

модуль.__file__ друкує шлях, з якого модуль реально завантажився, — найшвидший спосіб зловити тінь від власного файлу. dir(модуль) показує всі імена всередині. python3 -c "import sys; print(sys.path)" покаже маршрут пошуку на твоїй машині. А python3 -m json.tool dani.json нагадує, що модуль зі стандартної бібліотеки часто можна запустити як програму — і if __name__ == "__main__" усередині нього саме для цього й написаний.

13 / ПідсумокЩо ми тепер уміємо

Ми перестали жити в одному файлі. Тепер програма — це набір частин, які знаходять одна одну за зрозумілими правилами:

І одразу питання, яке з цього виростає. Стандартної бібліотеки вистачає надовго, але не назавжди: рано чи пізно знадобиться чужий пакет. Ставити його «в систему» не можна — ми бачили чому ще у темі 02. Отже, потрібне окреме місце для бібліотек кожного проєкту, щоб два проєкти з різними версіями однієї бібліотеки не воювали. Це віртуальні середовища і pip — наступна тема.

Далі в практиці. У practice.ipynb ти напишеш власний модуль і пакет прямо із зошита, спробуєш усі форми імпорту, побачиш __name__ в обох ролях і на власні очі переконаєшся, що тіло модуля виконується один раз.

Далі в темі

Теорію прочитано. Тепер закріпи її на практиці.