Собираем пакет и запускаем его
Группируем связанные модули, добавляем __init__.py и разбираем команду python -m.
Разбираем, что такое пакет и internal, и собираем проект командами go build и go run.
Когда становится тесно в корне
- Где
- Explorer в той же папке student-tools.
- Действие
- Проверьте раскладку из главы 2.4: main.go в корне, internal/services/calculator.go, internal/utils/formatter.go.
- Результат
- В корне остались только main.go, go.mod и go.sum; прикладной код — в internal.
- Проверка
- Раскройте папки и сопоставьте пути с деревом ниже. Затем выполните go build ./... — сборка должна пройти молча.
Три файла легко найти. Когда модулей становится тридцать, связанные части объединяют в папки. Пакет — это папка с модулями, которую можно импортировать: путь app.services.calculator сообщает, где лежит файл и за что он отвечает.
По какому принципу файл попадает в свою папку
Раскладку определяет ответственность файла — то, что он делает. В проекте student-tools ответственностей четыре, и каждой соответствует своё место:
| Что делает файл | Куда попадает | Наш пример |
|---|---|---|
| запускает программу: берёт данные, вызывает расчёт, печатает результат | точка запуска в корне пакета | app/main.py |
| решает задачу предметной области: считает средний балл | services — правила этого проекта | app/services/calculator.py |
| выполняет мелкую вспомогательную работу: превращает число в строку | utils — функции, переносимые в другой проект без изменений | app/utils/formatter.py |
| проверяет код | tests — отдельная папка в корне проекта | tests/test_calculator.py (глава 2.6) |
Проверить раскладку можно одним вопросом: «что придётся открыть, чтобы изменить формулу расчёта?» В правильной структуре ответ — один файл, calculator.py. Если для этого нужно править ещё и точку запуска, ответственности перемешаны.
Окружение .venv в эту схему не входит: это не код проекта, а локальный Python с установленными библиотеками. Оно остаётся в корне рядом с app.
Что меняется в папке
Переместите main.py в app, calculator.py в app/services, formatter.py в app/utils. Старые копии в корне удалите после переноса. Слева — как было после главы 2.4, справа — как станет:
student-tools/ ├ .venv/ ├ main.py ├ calculator.py └ formatter.py
student-tools/
├ .venv/
└ app/
├ __init__.py
├ main.py
├ services/
│ ├ __init__.py
│ └ calculator.py
└ utils/
├ __init__.py
└ formatter.py
Сверху состояние после главы 2.4: три файла лежат рядом в корне. Снизу состояние после переноса: все файлы кода находятся внутри app, в каждой папке появился __init__.py. Папка .venv из вывода исключена — в структуру проекта она не входит.
Нажмите на изображение, чтобы увеличить.- Где
- Explorer в той же папке student-tools.
- Действие
- Создайте app, внутри services и utils. Перетащите main.py в app, calculator.py в services, formatter.py в utils.
- Результат
- В корне не осталось прежних .py-файлов; .venv остаётся в корне.
- Проверка
- Сопоставьте свои папки с деревом выше. Пока не запускайте: импорты обновим через два шага.
Три файла легко найти. Когда пакетов становится тридцать, связанные части объединяют в каталоги. Путь internal/services/calculator.go говорит больше, чем случайное file3.go, а имя internal вдобавок сообщает, что эти пакеты не предназначены для внешнего использования.
В Go пакеты собраны ещё в главе 2.4: каждая папка стала отдельным пакетом в момент создания. В этой главе мы разбираем, что именно из этого следует, и учимся собирать проект целиком, а не только запускать его.
Корень проекта. Отсюда выполняются команды запуска.
В главе 2.6 в корень добавятся папка tests, файлы .gitignore, requirements.txt и README.md.
В главе 2.6 рядом с кодом появится файл теста, а в корне — .gitignore и README.md.
__init__.py: обычный пакет
- Где
- Explorer: папки app, app/services и app/utils.
- Действие
- В каждой папке: New File → __init__.py → сохранить пустым.
- Результат
- Появились три отдельных пустых файла с одинаковым именем.
- Проверка
- Проверьте два подчёркивания до и после init и расширение .py.
Создайте пустой __init__.py в app, services и utils. В имени по два подчёркивания с обеих сторон init. Ниже — что это за файл и почему он пустой.
Модуль, пакет и точки в импорте
Два слова, которые дальше встречаются постоянно. Модуль — это один файл .py, например calculator.py. Пакет — это папка с модулями, которую тоже можно импортировать, например app. Точки в записи app.services.calculator обозначают вложенность: папка app → папка services → файл calculator.py. Читая импорт, вы читаете путь по дереву проекта.
Что делает сам файл __init__.py
Он играет две роли сразу.
- Объявляет папку обычным пакетом. По этому файлу Python понимает, что папку нужно считать пакетом.
- Является телом пакета. Код внутри
__init__.pyвыполняется в момент первого импорта пакета — так же, как код верхнего уровня обычного модуля из главы 2.4. Пустой файл просто ничего не делает.
Причём выполняется он не только при import app. Импортируя что-то в глубине, Python проходит весь путь по дереву и выполняет __init__.py каждого пакета по дороге — сверху вниз.

Для наглядности в каждый __init__.py временно добавлен print. Команда импортирует одну функцию, но Python сначала выполнил app/__init__.py, затем app/services/__init__.py и только потом сам calculator.py. В вашем проекте эти файлы пустые, поэтому вы ничего не замечаете — но проходит Python по ним точно так же.
Скачать текст вывода ↓ · Нажмите на изображение, чтобы увеличить.Почему в учебном проекте он пустой
В __init__.py обычно складывают то, что относится к пакету целиком: короткие псевдонимы для импортов, версию, общие настройки. Этот код выполнится при любом импорте из пакета, даже когда нужна одна функция: импорт app.services.calculator сначала выполнит app/__init__.py. Поэтому в __init__.py не кладут расчёты, чтение файлов и обращения к сети, а в учебном проекте оставляют его пустым.
Почему иногда пакет работает и без него
Вы можете удалить __init__.py, и импорт всё равно сработает. Начиная с Python 3.3 папка без этого файла тоже импортируется — как namespace package. Разница видна, если посмотреть на сам объект пакета.

Сверху обычный пакет: Python показывает, из какого файла он собран, и __file__ указывает на app/__init__.py. Снизу та же папка без файла: в скобках стоит слово namespace, а __file__ равно None — тела у такого пакета просто нет. Механизм namespace нужен для редкого случая, когда один пакет собирают из нескольких папок в разных местах диска.
Скачать текст вывода ↓ · Нажмите на изображение, чтобы увеличить.В своих проектах создавайте __init__.py явно. Пакет получает тело, поведение импорта становится предсказуемым, а инструменты и старые версии библиотек надёжнее видят вашу папку как пакет. При этом отсутствие файла в чужом проекте само по себе не является ошибкой.
Папка — это и есть пакет
- Где
- Терминал в student-tools.
- Действие
- Выполните go list ./... — команда перечислит все пакеты модуля.
- Результат
- Три строки: сам модуль, internal/services и internal/utils.
- Проверка
- Число строк совпадает с числом папок, в которых лежат .go-файлы.
go list ./...Никакого файла-маркера вроде __init__.py в Go нет. Папка становится пакетом сама, как только в ней появляется .go-файл с объявлением package. Создавать что-то дополнительно не нужно — и забыть тоже нечего.
Файл, пакет и путь импорта
Три слова, которые дальше встречаются постоянно. Файл — одна единица текста, calculator.go. Пакет — папка целиком: все файлы в ней с одинаковой строкой package services образуют один пакет и видят имена друг друга без импортов. Путь импорта — адрес папки от имени модуля: example.com/student-tools/internal/services.
Отсюда следствие, непривычное после Python: разбивать пакет на несколько файлов можно свободно. Можно держать calculator.go и average.go в одной папке, и это по-прежнему один пакет services с общим набором имён. Новая граница возникает только вместе с новой папкой.
Все файлы одной папки — один пакет
Из этого следует жёсткое правило: в одной папке не может быть двух разных значений package. Попытка положить рядом файл с package services и файл с package calculator закончится ошибкой сборки found packages services and calculator in .... Компилятор не выбирает между ними: сборка останавливается.
Имя пакета при этом не обязано совпадать с именем папки: язык этого не требует. Но в путях импорта видно именно имя папки, а в вызовах — имя пакета, поэтому расхождение сбивает читателя. Договорённость такая: называйте пакет так же, как папку; расхождение допускают в отдельных случаях и объясняют его в README.
Зачем нужна папка internal
Имя internal Go понимает особым образом. Пакеты внутри неё доступны только коду того же модуля — точнее, коду, лежащему рядом с папкой internal или ниже. Чужой модуль, подключивший ваш как библиотеку, получит на импорте ошибку use of internal package not allowed.
Это единственный способ в Go ограничить видимость на уровне пакетов, и работает он на компиляции, а не по договорённости. Заглавная буква решает, что видно за пределами пакета; internal решает, что видно за пределами модуля.
Импорт отражает новую структуру
- Где
- Редактор: student-tools/main.go.
- Действие
- Сверьте два внутренних импорта с кодом ниже: они начинаются с имени модуля.
- Результат
- Пути импортов начинаются с example.com/student-tools/internal.
- Проверка
- calculator.go и formatter.go остались прежними. Следующим шагом соберём проект целиком.
- Где
- Редактор: student-tools/app/main.py.
- Действие
- Обновите два локальных импорта как в коде ниже и сохраните файл.
- Результат
- Пути импортов начинаются с app.services и app.utils.
- Проверка
- calculator.py и formatter.py остались прежними. Следующим шагом выполните запуск через -m.
package main
import (
"github.com/fatih/color"
"example.com/student-tools/internal/services"
"example.com/student-tools/internal/utils"
)
func main() {
values := []int{5, 4, 5, 3, 5}
average, err := services.CalculateAverage(values)
if err != nil {
color.Red("Ошибка: %v", err)
return
}
color.Green(utils.FormatAverage(average))
}from rich.console import Console
from app.services.calculator import calculate_average
from app.utils.formatter import format_average
def main():
values = [5, 4, 5, 3, 5]
average = calculate_average(values)
console = Console()
console.print(f"[bold green]{format_average(average)}[/bold green]")
if __name__ == "__main__":
main()Содержимое calculator.py и formatter.py остаётся прежним. Меняются два импорта в main.py: добавляются имена пакетов.
Содержимое calculator.go и formatter.go остаётся прежним. Внутренние импорты — это всегда полный путь от имени модуля из go.mod; сокращённой и относительной записи в Go нет, поэтому по любому импорту сразу видно, из какого модуля пришёл код.
Нажмите на часть пути: от пакета приложения до конкретной функции.
Форма импорта и команда запуска связаны
- Где
- Терминал: student-tools/, рядом с app.
- Действие
- Выполните python -m app.main, затем команду проверки импорта в конце раздела.
- Результат
- Запуск печатает 4.40; импорт app.main не печатает ничего.
- Проверка
- Если Python сообщает No module named app, проверьте текущую папку: она должна содержать app, а не находиться внутри неё.
python -m app.mainФлаг -m просит Python найти модуль по имени и выполнить его как главный. Имя app.main читается как путь: папка app, файл main.py.
Откуда Python ищет пакеты
Перед первым импортом Python составляет список папок, в которых будет искать модули, — sys.path. Первая запись в этом списке зависит от способа запуска:
python -m app.main— в начало списка попадает текущая папка, то есть корень проекта. Из него видна папкаapp;python app/main.py— в начало списка попадает папка запускаемого файла, то естьapp. Из неё видныservicesиutils, а сам пакетapp— нет.
Отсюда правило: команда запуска должна соответствовать тому, с чего начинаются импорты в файлах проекта.
| Импорты в main.py | Команда, которая работает | Что будет при другой команде |
|---|---|---|
from app.services.calculator import …путь от корня проекта | python -m app.mainиз корня проекта | python app/main.py → ModuleNotFoundError: No module named 'app' |
from services.calculator import …путь внутри пакета | python app/main.py | python -m app.main → ModuleNotFoundError: No module named 'services' |

Сверху импорты записаны от корня: запуск через -m печатает результат, запуск файлом не находит пакет app. Снизу те же файлы с импортами внутри пакета: работает запуск файлом, а -m не находит services. Ошибка в обоих случаях одна и та же по смыслу — начало пути импорта не совпало с первой папкой поиска.
Нажмите на изображение, чтобы увеличить.Какой вариант берём в проекте
Дальше в курсе используется первая строка таблицы: импорты от корня и запуск python -m app.main. Причина в тестах из главы 2.6. Файлы тестов лежат в папке tests рядом с app и запускаются из корня проекта, поэтому импортировать расчёт они могут только как app.services.calculator. Если в main.py написать импорт внутри пакета, в проекте окажутся две разные записи одного и того же пути.
Вариант с запуском файла остаётся рабочим — его используют для маленьких скриптов из одного-двух файлов, где пакета ещё нет.
Python начинает поиск с текущей папки — корня проекта. Внутри него виден пакет app, поэтому импорт from app.services.calculator работает.
Проверка: импорт не запускает программу
Убедитесь, что в выводе запуска осталось Средний результат: 4.40. Затем импортируйте точку запуска: команда должна завершиться без отчёта об оценках — за это отвечает условие __name__ из главы 2.4.
python -c "import app.main"
После python -m app.main появляется отчёт. После import app.main вывода нет: условие __name__ не пустило программу в запуск.
Скачать текст вывода ↓ · Нажмите на изображение, чтобы увеличить.Собирайте и запускайте из корня
- Где
- Терминал: student-tools/, рядом с go.mod.
- Действие
- Выполните три команды ниже по очереди.
- Результат
- go vet и go build молчат, go run . печатает 4.40.
- Проверка
- Если Go сообщает про go.mod, проверьте текущую папку: в ней должен лежать go.mod.
go build ./...
go vet ./...
go run .Разберём по частям. go build компилирует код и сообщает об ошибках; ./... означает «текущая папка и все вложенные», то есть все пакеты модуля сразу. Если команда ничего не напечатала, собрались все пакеты. go vet ищет подозрительные места, которые формально компилируются: например неверное число аргументов в Printf. go run . собирает и тут же запускает пакет из текущей папки.
Проверяются все пакеты, включая те, которые никто не импортирует. Ошибка в internal/utils обнаружится сразу, даже если main.go до него не добирается. В Python аналогичная опечатка дождалась бы первого импорта во время работы программы.
Почему нет ошибки «пакет не найден»
В Python стартовая папка решала, будет ли виден пакет app, — отсюда разница между python -m app.main и python app/main.py. В Go этого класса проблем нет: пути импорта абсолютны и отсчитываются от имени модуля в go.mod, а не от текущего каталога. Поэтому команду можно выполнять из любой папки проекта:
go build ./...
go test .Go поднимется вверх до go.mod, определит модуль и соберёт указанные пакеты. Единственное требование — находиться внутри модуля. Снаружи вы получите go.mod file not found in current directory or any parent directory, другой ошибки такого рода здесь не возникает.
Собранная программа — это один файл
Ещё одно отличие, которое пригодится в главе 2.6. go build без аргументов кладёт рядом исполняемый файл — student-tools на macOS и Linux, student-tools.exe на Windows. Он не требует ни установленного Go, ни исходников, ни зависимостей: всё нужное вкомпилировано внутрь. Запускается он напрямую:
go build -o student-tools .
./student-toolsНа Windows последняя команда записывается как .\student-tools.exe. Этот файл — результат сборки, а не исходный код, поэтому в Git он не попадает: в следующей главе мы исключим его через .gitignore.
Структура и архитектура
Структура отвечает на вопрос «где лежит файл?». Архитектура — «какая у части ответственность и от кого она зависит?». Папка services сама не делает программу правильно устроенной: важно, чтобы вычисления не зависели от интерфейса вывода.
Имена app, src, core зависят от команды и проекта. Здесь services хранит прикладной расчёт, utils — небольшую функцию оформления. Создавать пустые папки «на будущее» не требуется.
Имена internal, cmd, pkg — распространённые соглашения сообщества, а не требования языка: обязательное значение есть только у internal. Здесь services хранит прикладной расчёт, utils — небольшую функцию оформления. Создавать пустые папки «на будущее» не требуется: пакет без файлов для Go не существует.
Направление зависимостей
Правило этой структуры — направление зависимостей. Точка запуска знает про расчёт и оформление, а они про неё — нет. Расчёт вообще ни от чего не зависит: ему всё равно, кто его вызвал и куда пойдёт результат.
Стрелка одна и вниз: точка запуска импортирует расчёт и оформление. Калькулятор можно вызвать из тестов, из другой программы, из будущего веб-интерфейса — он ничего не знает о том, кто его использует.
Если стрелка появляется в обратную сторону — расчёт импортирует точку запуска, — получается цикл: два модуля ждут друг друга, и Python сообщает об этом ошибкой partially initialized module. Данные передают вниз аргументами, а результат возвращают вверх через return; обратный импорт для этого не нужен.
Если стрелка появляется в обратную сторону — расчёт импортирует точку запуска, — получается цикл. Go в этом месте строже Python: такой проект вообще не собирается, компилятор отвечает import cycle not allowed и перечисляет участников цикла. Данные передают вниз аргументами, а результат возвращают вверх через return; обратный импорт для этого не нужен.
Проверьте себя · Что изменилось после переноса функций в пакеты?
Расположение файлов, пути двух импортов и команда запуска. Формула расчёта и ожидаемый результат остались прежними.
Проверьте себя · Чем go build ./... отличается от go run .?
Первая команда проверяет и собирает все пакеты модуля, включая те, которые сейчас никто не импортирует, и ничего не запускает. Вторая собирает только пакет из текущей папки и сразу его выполняет. Перед сдачей работы выполняют обе: сборку — чтобы не осталось непроверенного кода, запуск — чтобы увидеть результат.
Документация к теме
Go: организация кода · Go: файл go.mod · Go: команда go · Go: пакеты internal
Python: venv · Python: модули и пакеты · VS Code: Python environments · pip freeze