# Пакеты, __init__.py и if __name__ == "__main__"<no value>

## Что мы изучим?

- **Пакет** (Package) — папку, содержащую модули.
- Файл **`__init__.py`** и его роль.
- Импорт изнутри пакета.
- Специальную переменную **`__name__`** и шаблон **`if __name__ == "__main__":`**.

## Зачем это нужно?

Когда модулей 3, они могут лежать в одной папке. Когда их 30, нужны **папки**: всё, что связано с пользователями, — в одной, всё, что связано с платежами, — в другой. Так устроены все настоящие проекты, а также все библиотеки, которые вы будете устанавливать (FastAPI — огромный пакет с десятками подпакетов).

А `if __name__ == "__main__":` решает проблему из предыдущего урока: тестовый код, который выполняется каждый раз, когда кто-то импортирует файл.

## Объяснение с основ

### Пакет = папка модулей

**Пакет** (Package) — папка, содержащая модули (файлы `.py`), а обычно ещё и файл с именем **`__init__.py`**.

```text
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`, но принятое соглашение — включать его, чтобы было ясно, что это пакет, а не просто папка.)

### Импорт из пакета

Точка отделяет **папку** от модуля, точно как `/` в пути:

```python
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"` (имя модуля) |

И поэтому:

```python
if __name__ == "__main__":
    # runs ONLY when this file is run directly, NOT when it is imported
```

{{< box "analogy" "Аналогия: шеф-повар, который ещё и поставщик" >}}
Шеф готовит в своём ресторане (прямой запуск) и одновременно продаёт соусы магазинам (импорт). Когда он в ресторане, он ещё и обслуживает посетителей: это код внутри `if __name__ == "__main__"`. Когда магазин покупает соус, он получает **только соус** (функции), а не целый обед, навязанный ему.
{{< /box >}}

## Синтаксис

```text
project/
├── main.py
└── package_name/
    ├── __init__.py
    └── module_name.py
```

```python
# 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`:

```python
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`:

```python
import greetings

print("app.py: __name__ is", __name__)
print(greetings.hello("Dana"))
```

**Запуск 1:** `python greetings.py`

```text
greetings.py: __name__ is __main__
Running greetings.py directly - quick self test:
Hello, Tester!
```

**Запуск 2:** `python app.py`

```text
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__"` |

**Вывод:** всё, что должно выполняться **только при прямом запуске файла** (тесты, демонстрации, запуск программы), идёт внутрь условия. Определения функций и констант остаются снаружи, чтобы их можно было импортировать.

## Ещё один пример

**Проект с настоящим пакетом.** Структура:

```text
week-2/store/
├── main.py
└── inventory/
    ├── __init__.py
    ├── products.py
    └── stock.py
```

`inventory/__init__.py`:

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

`inventory/products.py`:

```python
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`:

```python
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`:

```python
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`

```text
{'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()` + условие. Если в будущем кто-то его импортирует, ничего не запустится само.

{{< box "tip" "Как запустить файл, который находится внутри пакета?" >}}
Если попробовать `python inventory/products.py`, иногда получаешь `ModuleNotFoundError`, потому что Python ищет модули из папки **запущенного файла** (`inventory/`), а не из папки проекта. Правильный способ запустить модуль внутри пакета — из папки проекта, с флагом `-m` (module) и **точками вместо косых черт**: `python -m inventory.products`.
{{< /box >}}

## Типичная ошибка

**Ошибка 1: написать `"main"` вместо `"__main__"`.**

```python
if __name__ == "main":
    main()
```

Ничего не выполняется, и ошибки нет! Значение — `"__main__"`, с **двумя подчёркиваниями с каждой стороны**. Тихая Logical Error.

**Ошибка 2: циклический импорт (Circular Import).**

`a.py` импортирует `b`, а `b.py` импортирует `a`:

```text
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)
```

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

**Ошибка 3: положить всю логику внутрь блока `if`.**

```python
if __name__ == "__main__":
    def calculate(x):
        return x * 2
    print(calculate(5))
```

Теперь `calculate` нельзя импортировать из другого файла, потому что она определяется только при прямом запуске. Функции идут **снаружи**, а внутрь — только **запуск программы**.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Практика

Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.

### Упражнение 1: Структура пакета

Нарисуйте структуру папок проекта `store` с файлом `main.py` и пакетом с именем `inventory`, в котором два модуля: `products.py` и `stock.py`. Какой файл обязательно должен быть внутри `inventory` и зачем?

{{< solution >}}
```text
store/
├── main.py
└── inventory/
    ├── __init__.py
    ├── products.py
    └── stock.py
```

`__init__.py` помечает папку как **пакет** (Package) — папку модулей, из которой можно импортировать. Он может быть пустым.
{{< /solution >}}

### Упражнение 2: Импорт из пакета

В `inventory/products.py` есть функция `create_product(name, price)`, которая возвращает словарь. Напишите в `main.py` три способа импортировать её и использовать.

{{< solution >}}
```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`.
{{< /solution >}}

### Упражнение 3: Что такое __name__?

В файле `greetings.py`:

```text
def hello(name):
    return "Hello, " + name + "!"

print("__name__ is", __name__)
```

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

1. Вы запускаете `python greetings.py`.
2. Вы запускаете `main.py`, в котором есть `import greetings`.

{{< solution >}}
1. При прямом запуске: `__name__ is __main__`.
2. При импорте: `__name__ is greetings` — имя модуля.

Python задаёт `__name__` в зависимости от того, **как** запущен файл. На этом основана защита main (main guard).
{{< /solution >}}

### Упражнение 4: Защита main

Добавьте в `greetings.py` (из предыдущего упражнения, с функцией `hello`) самопроверку, которая выполняется **только** при прямом запуске файла, а не при импорте. Проверка печатает `quick self test:`, а затем результат `hello("Tester")`.

Удалите строку `print("__name__ is", __name__)` из предыдущего упражнения.

**Ожидаемый вывод (python greetings.py):**

```text
quick self test:
Hello, Tester!
```

{{< solution >}}
```python
def hello(name):
    return "Hello, " + name + "!"


if __name__ == "__main__":
    print("quick self test:")
    print(hello("Tester"))
```

```text
quick self test:
Hello, Tester!
```

Когда другой файл делает `import greetings`, условие равно `False` — проверка не выполняется, и доступна только функция.
{{< /solution >}}

### Упражнение 5: Пакет mathtools

Постройте пакет с именем `mathtools` с двумя модулями: `basic.py` с `add(a, b)` и `stats.py` с `mean(numbers)` и `maximum(numbers)`. В `main.py` используйте все три.

```text
add(2, 3)
mean([10, 20, 30])
maximum([10, 20, 30])
```

**Ожидаемый вывод:**

```text
add: 5
mean: 20.0
maximum: 30
```

{{< solution >}}
```text
math_project/
├── main.py
└── mathtools/
    ├── __init__.py
    ├── basic.py
    └── stats.py
```

**mathtools/basic.py**

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

**mathtools/stats.py**

```py
def mean(numbers):
    return sum(numbers) / len(numbers)


def maximum(numbers):
    return max(numbers)
```

**main.py**

```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]))
```
{{< /solution >}}

<!-- exercises:end -->

## Итоги

- **Пакет** = папка модулей с **`__init__.py`**, который выполняется при первом импорте и может «выставлять» удобные имена.
- Импорт из пакета: `from package.module import name`. Точка = вход в папку.
- **`__name__`** равно `"__main__"`, когда файл запущен напрямую, и имени модуля, когда его импортировали.
- **`if __name__ == "__main__": main()`** — запускать программу только при прямом запуске. Функции определяйте снаружи.
- Модуль внутри пакета запускают из папки проекта командой `python -m package.module`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

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

- [Python Tutorial — Packages](https://docs.python.org/3/tutorial/modules.html#packages)
- [__main__ — Top-level code environment](https://docs.python.org/3/library/__main__.html#name-main)

<!-- docs:end -->
