Пакеты, __init__.py и if __name__ == "__main__"
- Объяснить, что такое пакет (Package), и построить его с
__init__.py - Импортировать модули и функции изнутри пакета
- Объяснить, каково значение
__name__, когда файл запускают и когда его импортируют - Использовать
if __name__ == "__main__":, чтобы отделить «библиотеку» от «программы»
Что мы изучим?
- Пакет (Package) — папку, содержащую модули.
- Файл
__init__.pyи его роль. - Импорт изнутри пакета.
- Специальную переменную
__name__и шаблонif __name__ == "__main__":.
Зачем это нужно?
Когда модулей 3, они могут лежать в одной папке. Когда их 30, нужны папки: всё, что связано с пользователями, — в одной, всё, что связано с платежами, — в другой. Так устроены все настоящие проекты, а также все библиотеки, которые вы будете устанавливать (FastAPI — огромный пакет с десятками подпакетов).
А if __name__ == "__main__": решает проблему из предыдущего урока: тестовый код, который выполняется каждый раз, когда кто-то импортирует файл.
Объяснение с основ
Пакет = папка модулей
Пакет (Package) — папка, содержащая модули (файлы .py), а обычно ещё и файл с именем __init__.py.
store/ ← project folder
├── main.py ← the program you run
└── inventory/ ← a PACKAGE
├── __init__.py ← marks this folder as a package
├── products.py ← module
└── stock.py ← module__init__.py
- Помечает папку как пакет — явно и понятно.
- Выполняется автоматически при первом импорте чего-либо из пакета.
- Обычно пустой. В него можно положить удобные импорты, чтобы писать
from inventory import add_productвместоfrom inventory.products import add_product.
(В современном Python можно импортировать и из папки без __init__.py, но принятое соглашение — включать его, чтобы было ясно, что это пакет, а не просто папка.)
Импорт из пакета
Точка отделяет папку от модуля, точно как / в пути:
import inventory.products # inventory/products.py
from inventory import products # same module, shorter use
from inventory.products import add_product # one function__name__ — «кто я?»
У каждого модуля есть встроенная переменная с именем __name__. Python задаёт её значение в зависимости от того, как был запущен файл:
| Как запущен файл | Значение __name__ |
|---|---|
Запущен напрямую: python prices.py | "__main__" |
Импортирован из другого файла: import prices | "prices" (имя модуля) |
И поэтому:
if __name__ == "__main__":
# runs ONLY when this file is run directly, NOT when it is importedШеф готовит в своём ресторане (прямой запуск) и одновременно продаёт соусы магазинам (импорт). Когда он в ресторане, он ещё и обслуживает посетителей: это код внутри if __name__ == "__main__". Когда магазин покупает соус, он получает только соус (функции), а не целый обед, навязанный ему.
Синтаксис
project/
├── main.py
└── package_name/
├── __init__.py
└── module_name.py# main.py
from package_name.module_name import some_function
from package_name import module_name
# any module that can also be run directly:
def main():
... # what happens when the file is run as a program
if __name__ == "__main__":
main()Шаблон «функция main() + условие в конце файла» — стандарт в каждом файле Python, предназначенном для запуска.
Первый пример кода
Посмотрим на __name__ в действии. week-2/name_demo/greetings.py:
def hello(name):
return "Hello, " + name + "!"
print("greetings.py: __name__ is", __name__)
if __name__ == "__main__":
print("Running greetings.py directly - quick self test:")
print(hello("Tester"))week-2/name_demo/app.py:
import greetings
print("app.py: __name__ is", __name__)
print(greetings.hello("Dana"))Запуск 1: python greetings.py
greetings.py: __name__ is __main__
Running greetings.py directly - quick self test:
Hello, Tester!Запуск 2: python app.py
greetings.py: __name__ is greetings
app.py: __name__ is __main__
Hello, Dana!Разбор кода построчно
Запуск 1 (python greetings.py) | Запуск 2 (python app.py) | |
|---|---|---|
__name__ у greetings.py | "__main__" (это запущенный файл) | "greetings" (его импортировали) |
Первый print в greetings | Выполняется | Выполняется (код на верхнем уровне при импорте выполняется всегда) |
Блок if __name__ == "__main__" | Выполняется (самопроверка) | Не выполняется |
__name__ у app.py | — | "__main__" |
Вывод: всё, что должно выполняться только при прямом запуске файла (тесты, демонстрации, запуск программы), идёт внутрь условия. Определения функций и констант остаются снаружи, чтобы их можно было импортировать.
Ещё один пример
Проект с настоящим пакетом. Структура:
week-2/store/
├── main.py
└── inventory/
├── __init__.py
├── products.py
└── stock.pyinventory/__init__.py:
from inventory.products import create_product
from inventory.stock import add_stock, remove_stockinventory/products.py:
def create_product(name, price):
if price < 0:
raise ValueError("price cannot be negative, got " + str(price))
return {"name": name, "price": price, "quantity": 0}
if __name__ == "__main__":
print(create_product("Test item", 10))inventory/stock.py:
def add_stock(product, amount):
product["quantity"] = product["quantity"] + amount
def remove_stock(product, amount):
if amount > product["quantity"]:
raise ValueError("not enough " + product["name"] + " in stock")
product["quantity"] = product["quantity"] - amountmain.py:
from inventory import create_product, add_stock, remove_stock
def main():
laptop = create_product("Laptop", 3500)
add_stock(laptop, 10)
remove_stock(laptop, 3)
print(laptop)
try:
remove_stock(laptop, 50)
except ValueError as error:
print("Error:", error)
if __name__ == "__main__":
main()Запуск (из week-2/store): python main.py
{'name': 'Laptop', 'price': 3500, 'quantity': 7}
Error: not enough Laptop in stockРазбор:
__init__.pyсобирает важные функции пакета. Благодаря емуmain.pyпишетfrom inventory import create_productи не обязан знать, в каком именно внутреннем файле она лежит. Снаружи пакет выглядит как «одно целое».add_stock(laptop, 10)меняет сам dict, потому что dict изменяемый (тема структур данных и функций!). Функции ничего не нужно возвращать.products.pyсодержит самопроверку внутриif __name__ == "__main__". Его можно запустить отдельно для проверки, и он не «устраивает беспорядок» при импорте.main.pyобёрнут вmain()+ условие. Если в будущем кто-то его импортирует, ничего не запустится само.
Если попробовать python inventory/products.py, иногда получаешь ModuleNotFoundError, потому что Python ищет модули из папки запущенного файла (inventory/), а не из папки проекта. Правильный способ запустить модуль внутри пакета — из папки проекта, с флагом -m (module) и точками вместо косых черт: python -m inventory.products.
Типичная ошибка
Ошибка 1: написать "main" вместо "__main__".
if __name__ == "main":
main()Ничего не выполняется, и ошибки нет! Значение — "__main__", с двумя подчёркиваниями с каждой стороны. Тихая Logical Error.
Ошибка 2: циклический импорт (Circular Import).
a.py импортирует b, а b.py импортирует a:
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)Два модуля, зависящие друг от друга, — признак неудачного разделения. Перенесите общую часть в третий модуль, из которого импортируют оба.
Ошибка 3: положить всю логику внутрь блока if.
if __name__ == "__main__":
def calculate(x):
return x * 2
print(calculate(5))Теперь calculate нельзя импортировать из другого файла, потому что она определяется только при прямом запуске. Функции идут снаружи, а внутрь — только запуск программы.
Практика
Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.
Упражнение 1: Структура пакета
Нарисуйте структуру папок проекта store с файлом main.py и пакетом с именем inventory, в котором два модуля: products.py и stock.py. Какой файл обязательно должен быть внутри inventory и зачем?
Показать решение
store/
├── main.py
└── inventory/
├── __init__.py
├── products.py
└── stock.py__init__.py помечает папку как пакет (Package) — папку модулей, из которой можно импортировать. Он может быть пустым.
Упражнение 2: Импорт из пакета
В inventory/products.py есть функция create_product(name, price), которая возвращает словарь. Напишите в main.py три способа импортировать её и использовать.
Показать решение
import inventory.products
laptop = inventory.products.create_product("Laptop", 3500)
from inventory import products
mouse = products.create_product("Mouse", 80)
from inventory.products import create_product
desk = create_product("Desk", 900)Точка в пути (inventory.products) — как / в папках: модуль products внутри пакета inventory.
Упражнение 3: Что такое name?
В файле greetings.py:
def hello(name):
return "Hello, " + name + "!"
print("__name__ is", __name__)Что будет напечатано в каждом случае?
- Вы запускаете
python greetings.py. - Вы запускаете
main.py, в котором естьimport greetings.
Показать решение
- При прямом запуске:
__name__ is __main__. - При импорте:
__name__ is greetings— имя модуля.
Python задаёт __name__ в зависимости от того, как запущен файл. На этом основана защита main (main guard).
Упражнение 4: Защита main
Добавьте в greetings.py (из предыдущего упражнения, с функцией hello) самопроверку, которая выполняется только при прямом запуске файла, а не при импорте. Проверка печатает quick self test:, а затем результат hello("Tester").
Удалите строку print("__name__ is", __name__) из предыдущего упражнения.
Ожидаемый вывод (python greetings.py):
quick self test:
Hello, Tester!Показать решение
def hello(name):
return "Hello, " + name + "!"
if __name__ == "__main__":
print("quick self test:")
print(hello("Tester"))quick self test:
Hello, Tester!Когда другой файл делает import greetings, условие равно False — проверка не выполняется, и доступна только функция.
Упражнение 5: Пакет mathtools
Постройте пакет с именем mathtools с двумя модулями: basic.py с add(a, b) и stats.py с mean(numbers) и maximum(numbers). В main.py используйте все три.
add(2, 3)
mean([10, 20, 30])
maximum([10, 20, 30])Ожидаемый вывод:
add: 5
mean: 20.0
maximum: 30Показать решение
math_project/
├── main.py
└── mathtools/
├── __init__.py
├── basic.py
└── stats.pymathtools/basic.py
def add(a, b):
return a + bmathtools/stats.py
def mean(numbers):
return sum(numbers) / len(numbers)
def maximum(numbers):
return max(numbers)main.py
from mathtools.basic import add
from mathtools import stats
print("add:", add(2, 3))
print("mean:", stats.mean([10, 20, 30]))
print("maximum:", stats.maximum([10, 20, 30]))Итоги
- Пакет = папка модулей с
__init__.py, который выполняется при первом импорте и может «выставлять» удобные имена. - Импорт из пакета:
from package.module import name. Точка = вход в папку. __name__равно"__main__", когда файл запущен напрямую, и имени модуля, когда его импортировали.if __name__ == "__main__": main()— запускать программу только при прямом запуске. Функции определяйте снаружи.- Модуль внутри пакета запускают из папки проекта командой
python -m package.module.