У темі 14 ми навчилися пакувати шматок роботи у функцію й давати їй ім'я, а в темі 15 розібрали, як значення потрапляють усередину функції та за якими правилами Python шукає імена. Усе це відбувалося в одному файлі. Сьогодні ми виводимо код за межі файлу — і саме тут починається справжня організація програми.
Причина проста, і ти вже могла її відчути. Файл на п'ятдесят рядків читається легко. Файл на п'ятсот — уже ні: щоб знайти потрібну функцію, доводиться гортати; щоб узяти цю функцію в інший проєкт, доводиться копіювати. А копія — це майже завжди майбутній баг, бо виправлення в одному місці не доїжджає до другого.
01 / МотивОдин файл перестає вміщувати
Уяви крамницю, для якої ми пишемо облік. У програмі є три різні за змістом купки коду: розрахунок цін і знижок, друк чеків, збереження даних на диск. Поки їх десять рядків, вони мирно живуть разом. Коли їх стане триста, почнеться неприємне:
- Пошук. Щоб виправити округлення знижки, треба знайти те місце серед трьохсот рядків, де воно ховається.
- Повторне використання. Розрахунок знижок потрібен ще й у звітах — а він приварений до друку чеків.
- Зіткнення імен. Функція
formatдля чека й функціяformatдля звіту не можуть співіснувати: друга просто затре першу. - Робота вдвох. Двоє людей, що правлять один файл, гарантовано отримають конфлікт при злитті змін.
Розв'язок називається модуль (module). Це найдешевший спосіб розділити програму: просто розкласти код по різних файлах і навчитися брати з них потрібне.
02 / ОзначенняМодуль — це просто файл
Модуль у Python — це файл із розширенням .py. Оце й усе
означення, іншого немає. Будь-який файл, який ти написала, вже є модулем — ти просто ще жодного
разу його не імпортувала.
Ім'я модуля — це ім'я файлу без .py. Файл znyzhky.py — модуль
znyzhky. Створимо його поруч із головним файлом:
STANDARTNA = 0.10
def cina_zi_znyzhkoyu(cina, vidsotok):
# повертаємо ціну, зменшену на заданий відсоток
return round(cina * (1 - vidsotok), 2)
А тепер головний файл поруч із ним:
import znyzhky
print(znyzhky.cina_zi_znyzhkoyu(250, znyzhky.STANDARTNA))
225.0
Зверни увагу на форму звертання: znyzhky.cina_zi_znyzhkoyu. Крапка тут означає те
саме, що й завжди в Python, — «візьми з цього об'єкта оте, що всередині». Після імпорту
znyzhky стає звичайним об'єктом, у якого всередині лежать усі імена з файлу.
module: функція globals() повертає словник імен свого файлу, а не
всієї програми. Модуль дає кожному файлу власний простір імен, і STANDARTNA в
одному файлі ніяк не заважає STANDARTNA в іншому.03 / ПошукКуди дивиться import
Коли інтерпретатор бачить import znyzhky, він не обшукує весь диск. Він іде
чітко визначеним маршрутом і бере перше, що знайшов. Маршрут такий:
sys.modules— словник уже завантажених модулів. Якщо модуль там є, робота закінчена, далі не дивимось.- Вбудовані модулі — ті, що вшиті в сам інтерпретатор
(
sys,builtins,time). Їх не лежить окремим файлом ніде на диску. - Список тек із
sys.path— по черзі, згори вниз. Перший рядок цього списку — тека, з якої запустили скрипт; далі теки стандартної бібліотеки; наприкінціsite-packages, куди складає файлиpip.
Якщо маршрут закінчився нічим, ти бачиш ModuleNotFoundError: No module named
'znyzhky'. Це не «Python зламався», а рівно одне: «я пройшов увесь список і не знайшов».
Обери модуль в інтерактиві й простеж маршрут очима:
Інтерактив 1 · Маршрут пошуку модуля
Місця перевіряються згори вниз. Виграє перше, у якому щось знайшлося.
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 | одне ім'я math | math.sqrt(16) |
| from math import sqrt | одне ім'я sqrt | sqrt(16) |
| import math as m | одне ім'я m | m.sqrt(16) |
| from math import * | усі публічні імена модуля | sqrt(16) |
Важлива й неочевидна деталь: у всіх чотирьох випадках модуль завантажується цілком.
from math import sqrt не «бере лише квадратний корінь» — Python виконує весь
math, кладе його в sys.modules, і тільки потім витягує звідти одне
ім'я. Різниця лише в тому, що потрапляє в твій простір імен.
Подивись, як після кожної форми змінюється простір імен файлу. Уяви, що до імпорту у файлі
вже жили дві речі: наша змінна e (енергія в джоулях) і вбудована функція
pow:
Інтерактив 2 · Що саме з'являється у просторі імен
Ліворуч код, праворуч — імена, доступні у файлі після нього.
файл main.py
простір імен після імпорту
05 / ПасткаЧому зірочка — погана звичка
Формально from math import * працює. Практично це найшвидший спосіб зіпсувати
собі вечір, і причин три.
Перша: затінення імен. Ми щойно бачили це в інтеракторі. Зірочка привела
шістдесят імен, серед яких e і pow. Твоя змінна e
перестала існувати — не з помилкою, а мовчки. Програма далі рахує з числом 2.718 замість твоєї
енергії й дає правдоподібну дурницю. Це найгірший тип багу: без traceback.
Друга: незрозуміло, звідки ім'я. Побачивши sqrt(16) посеред
чужого коду з трьома зірочними імпортами вгорі, ти не можеш сказати, з якого саме модуля воно
прийшло. Доводиться відкривати всі три. З math.sqrt(16) питання не виникає взагалі.
Третя: порядок раптом починає значити. Якщо два модулі мають функцію з однаковим ім'ям, виграє той, чий зірочний імпорт стоїть нижче. Переставили два рядки місцями — програма змінила поведінку. Це те, чого в коді бути не повинно.
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
.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__
(два підкреслення з кожного боку). Значення в неї кладеться за одним простим правилом:
Ти набрала python3 zvit.py. Цей файл — точка входу програми, головний.
Python кладе в його __name__ рядок "__main__".
Хтось інший написав import zvit. Тепер файл — допоміжний, він обслуговує
когось. Python кладе в __name__ справжнє ім'я модуля: "zvit".
Один і той самий файл може дізнатися, у якій він ролі. А отже — виконати щось лише в
одній із них. Умова if __name__ == "__main__": читається дослівно як
«якщо мене запустили головним, а не імпортували».
Навіщо це потрібно? Згадай: імпорт виконує весь файл. Якщо внизу zvit.py стоїть
print(porahuvaty(1000)) — воно надрукується щоразу, коли хтось імпортує модуль
заради однієї функції. Захисна умова прибирає цей побічний ефект. Перемкни режим і подивись,
що виконується:
Інтерактив 4 · Одне ім'я — два значення
Той самий файл zvit.py у трьох сценаріях запуску.
zvit.py — не змінюється
що станеться
sys.modules, уже містить цю інформацію: головний файл програми
завжди лежить там під ключем "__main__". Тобто перевірка нічого не вигадує — вона
просто читає те, що інтерпретатор і так записав.09 / ПакетТека з модулями
Коли модулів стає багато, наступний крок очевидний: скласти їх у теку. Тека з модулями називається пакетом (package), і звертаються до її вмісту через крапку — так само, як до вмісту модуля.
├── 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.
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 · Шість модулів, які варто знати вже зараз
Обери модуль — побачиш, навіщо він і як виглядає найпростіший виклик.
А коли потрібного модуля в стандартній бібліотеці немає — по нього йдуть у зовнішні пакети
через pip, і ставлять їх не в систему, а у віртуальне середовище проєкту. Це
рівно те, чим займається наступна тема.
12 / ГлибшеДля тих, хто вже писав код
Далі — те, з чим новачок зіткнеться не сьогодні, але що ламає реальні проєкти регулярно.
Циклічні імпорти
Ситуація: модуль a імпортує b, а b імпортує
a. Виглядає безглуздо, а виникає природно — щойно два шматки коду починають
потребувати один одного. І ламається не завжди, а залежно від того, що саме береться і
коли.
Розберемо покроково. Ключ до розуміння — той самий sys.modules: запис у ньому
з'являється до того, як тіло модуля виконалось. Тобто в кеші деякий час лежить
напівготовий модуль:
Інтерактив 7 · Де саме ламається цикл
Два модулі, що потребують один одного. Крок за кроком — до збою й після виправлення.
a.py
b.py
Два робочі виходи ти щойно бачила. Перший і правильний — переставити код: знайти те спільне, заради чого модулі тягнуться один до одного, і винести його в третій модуль, від якого залежать обидва. Цикл зникає разом із причиною.
Другий — перенести import усередину функції. Тоді він виконається
не під час завантаження модуля, а під час виклику — коли обидва модулі вже добудовані.
Коли імпорт усередині функції виправданий
Загальне правило PEP 8 — імпорти вгорі файлу, і воно не просто так: там їх видно, і збій станеться одразу при старті, а не на п'ятій хвилині роботи. Але є три випадки, коли імпорт всередині функції — усвідомлений вибір.
- Розрив циклу, коли переставити код прямо зараз неможливо. Тимчасовий захід, але робочий.
- Дорогий модуль, потрібний зрідка. Якщо важка бібліотека потрібна лише в одній функції з тридцяти, її імпорт угорі сповільнює кожен запуск програми. Утиліти командного рядка так економлять помітну частку часу старту.
- Необов'язкова залежність. Модуль може бути не встановлений, і код має працювати без нього — тоді імпорт робиться на місці й обгортається обробкою помилки (докладно — у темі 18).
import —
це пошук у словнику sys.modules і присвоєння локального імені. Мільйон викликів
у циклі помітиш, звичайну функцію — ні. Тож аргумент «це повільно» тут не працює; справжній
аргумент проти — «збій знайдеться пізно».Дрібниці, які тішать
модуль.__file__ друкує шлях, з якого модуль реально завантажився, — найшвидший
спосіб зловити тінь від власного файлу. dir(модуль) показує всі імена всередині.
python3 -c "import sys; print(sys.path)" покаже маршрут пошуку на твоїй машині.
А python3 -m json.tool dani.json нагадує, що модуль зі стандартної бібліотеки часто
можна запустити як програму — і if __name__ == "__main__" усередині нього саме для
цього й написаний.
13 / ПідсумокЩо ми тепер уміємо
Ми перестали жити в одному файлі. Тепер програма — це набір частин, які знаходять одна одну за зрозумілими правилами:
- модуль — файл
.py, який імпорт виконує, а не «підключає»; - маршрут пошуку —
sys.modules, вбудовані, теки зsys.pathзгори вниз; своя тека перша, звідси й тіні від власних файлів; - три форми імпорту — різняться лише тим, які імена з'являються у файлі; зірочка тихо затирає своє;
- один раз — тіло модуля виконується при першому імпорті, далі працює кеш;
__name__—"__main__"при прямому запуску й справжнє ім'я при імпорті, звідси захисна умова внизу файлу;- пакет — тека з
__init__.py, звертання через крапку, імпорти абсолютні.
І одразу питання, яке з цього виростає. Стандартної бібліотеки вистачає надовго, але не назавжди: рано чи пізно знадобиться чужий пакет. Ставити його «в систему» не можна — ми бачили чому ще у темі 02. Отже, потрібне окреме місце для бібліотек кожного проєкту, щоб два проєкти з різними версіями однієї бібліотеки не воювали. Це віртуальні середовища і pip — наступна тема.
practice.ipynb ти напишеш власний модуль
і пакет прямо із зошита, спробуєш усі форми імпорту, побачиш __name__ в обох ролях
і на власні очі переконаєшся, що тіло модуля виконується один раз.Далі в темі
Теорію прочитано. Тепер закріпи її на практиці.