Пакеты, __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.py

inventory/__init__.py:

from inventory.products import create_product
from inventory.stock import add_stock, remove_stock

inventory/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"] - amount

main.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__)

Что будет напечатано в каждом случае?

  1. Вы запускаете python greetings.py.
  2. Вы запускаете main.py, в котором есть import greetings.
Показать решение
  1. При прямом запуске: __name__ is __main__.
  2. При импорте: __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.py

mathtools/basic.py

def add(a, b):
    return a + b

mathtools/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.

Официальная документация