Один файл становится тремя
Разделяем расчёт, форматирование и запуск. Читаем импорт как связь между частями.
Разделяем расчёт, форматирование и запуск по пакетам. Читаем импорт как связь между частями.
Сначала проблема, потом новый файл
На двадцати строках всё видно сразу. Когда добавится работа с пользователями, базой данных и отчётами, файл станет труднее читать и изменять. Поэтому код разделяют по ответственности: формула расчёта лежит отдельно от кода вывода, и при смене оформления её не нужно искать среди строк печати.
Модуль в нашем примере — файл .py. Он может содержать функции, классы и код верхнего уровня. Учебный модуль книги и Python-модуль — разные значения слова: в одной теме у нас несколько небольших файлов.
соединяет→calculator.py
считает+formatter.py
оформляет
Перенесите функции, сохранив поведение
- Где
- Explorer: корень student-tools, рядом с main.py.
- Действие
- Создайте calculator.py и formatter.py. Перенесите функции. Замените main.py кодом ниже.
- Результат
- В корне три .py-файла; в main.py нет копий двух вынесенных функций.
- Проверка
- Сохраните все файлы и выполните python main.py: ожидается 4.40.
В корне student-tools создайте два файла. Перенесите в них соответствующие функции из main.py. Не оставляйте вторые копии: иначе при исправлении одной другая может остаться старой.
def calculate_average(values: list[int]) -> float:
if not values:
raise ValueError("Список оценок не должен быть пустым")
return sum(values) / len(values)def format_average(value: float) -> str:
return f"Средний результат: {value:.2f}"from rich.console import Console
from calculator import calculate_average
from 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()python main.pyРезультат снова Средний результат: 4.40. Изменилось расположение кода, а не задача программы. Если результат другой, сначала проверьте перенос функций и импорты.

Команда ls показывает три файла в корне проекта: расчёт, оформление и точка запуска. Команда запуска не изменилась, результат тот же, что был до разделения.
Нажмите на изображение, чтобы увеличить.Как читать import
import calculator
result = calculator.calculate_average([5, 4, 5])
from calculator import calculate_average
result = calculate_average([5, 4, 5])Обе строки загружают один и тот же файл. Разница только в том, какое имя после этого появляется в вашем файле и как вы обращаетесь к функции.
Видно, откуда пришла функция: имя модуля остаётся в строке вызова. Удобно, когда из модуля нужно много всего или когда имена в разных модулях совпадают.
Расширение .py в импорте не пишется: там указывают имя модуля, а не файла. Не используйте from calculator import * — такая запись втягивает все имена сразу, и по коду становится не видно, откуда взялась функция.
Что делает строка import
Импорт выполняет файл целиком, сверху вниз. Результат зависит от того, что в этом файле написано:
def calculate_average(...):— создаёт функцию, но не выполняет её тело. Тело сработает, когда функцию вызовут.print(...),main(), чтение файла, запрос в сеть на верхнем уровне (без отступа, не внутри функции) — выполнятся сразу, при первом же импорте.
Отсюда следует то, что разобрано в следующем разделе: вызов main(), оставленный в конце файла без условия, запускает программу при каждом импорте этого файла.
Повторный импорт файл не выполняет
Первый import calculator выполняет файл. Все последующие импорты того же модуля — в этом же запуске программы, в любом файле — берут уже загруженный модуль из словаря sys.modules.

В файле три обращения к calculator подряд, а сообщение о загрузке напечаталось один раз. Повторный импорт не замедляет программу, но и код верхнего уровня во второй раз не выполняет.
Скачать текст вывода ↓ · Нажмите на изображение, чтобы увеличить.Зачем нужна строка if __name__ == "__main__"
Эта строка решает одну задачу: не дать программе запуститься, когда файл импортируют. Ниже разобрано, откуда берётся такая ситуация, что за переменная __name__ и как условие читается целиком.
Шаг 1. Что происходит без условия
Пусть в конце main.py стоит просто вызов main(). При запуске файла программа посчитает средний балл и напечатает результат — этого мы и ждём. Но из предыдущего раздела известно: импорт выполняет файл сверху вниз. Значит, при import main Python дойдёт до строки main() и выполнит её тоже.

В main_noguard.py вызов main() стоит на верхнем уровне. Файл только импортировали — программа всё равно посчитала и напечатала результат.
Скачать текст вывода ↓ · Нажмите на изображение, чтобы увеличить.В главе 2.6 тесты будут импортировать ваши модули. Без условия каждый запуск тестов начинался бы с печати отчёта об оценках.
Шаг 2. Переменная __name__
Загружая файл, Python сам создаёт в нём несколько служебных переменных. Одна из них — __name__, обычная строка с именем модуля. Вы её не объявляете, значение подставляет Python:
- файл, который вы запустили сами (
python main.pyилиpython -m app.main), получает__name__ == "__main__"; - файл, попавший в программу через
import, получает своё имя модуля:"calculator","app.services.calculator".
Значение "__main__" сообщает одно: этот файл сейчас запущен напрямую. К тому, какой файл в проекте считается главным, оно отношения не имеет.
Проверить значение можно печатью — временно добавьте строку в начало файла:
print("Модуль main загружается. __name__ =", __name__)
Сверху main.py запущен: __name__ равно __main__, поэтому вызвался main() и появился результат. Снизу тот же файл импортирован: __name__ стало main, расчёт не выполнялся. У calculator в обоих случаях имя calculator — он попал в программу через import.
Скачать текст вывода ↓ · Нажмите на изображение, чтобы увеличить.Шаг 3. Как читается условие
if __name__ == "__main__":
main()Дословно: «если этот файл сейчас запущен напрямую — вызови main()». При импорте условие ложно, вызова нет, а все функции из файла остаются доступными.
Правило: под условием оставляют только запуск. Строки def и import внутрь if не переносят — при импорте они перестанут выполняться, и файл потеряет свои функции.
Файл запущен напрямую: определения созданы и программа стартовала. Это обычный запуск вашей программы.
Названия помогают импортам
Используйте короткие имена маленькими латинскими буквами, слова разделяйте подчёркиванием: user_service.py, calculator.py. Пробелы и дефисы мешают обычному синтаксису импорта. Латиница в именах файлов принята в командной работе: такие имена одинаково читаются в любой системе и в любом терминале.
Сначала проблема, потом новый пакет
На тридцати строках всё видно сразу. Но если добавить работу с пользователями, базой данных и отчётами, файл станет труднее читать и изменять. Мы разделяем код по ответственности: при смене оформления не должны искать формулу среди кода вывода.
Пакет в Go — это папка с .go-файлами, у которых в первой строке одно и то же package имя. Отличие от Python: единицей разделения служит папка, а не файл. Два файла в одной папке — один пакет, они видят друг друга без всяких импортов. Чтобы получилась отдельная единица, нужна отдельная папка.
соединяет→services/
считает+utils/
оформляет
Папки мы положим внутрь internal/. Это особое имя: пакеты из internal может импортировать только код того же модуля. Если кто-то подключит ваш модуль как библиотеку, до этих пакетов он не доберётся — Go запретит на компиляции. В учебном проекте это сразу показывает, что внутренняя логика не предназначена для внешнего использования.
Перенесите функции, сохранив поведение
- Где
- Explorer: корень student-tools, рядом с main.go.
- Действие
- Создайте папки internal/services и internal/utils, в них — calculator.go и formatter.go. Перенесите функции. Замените main.go кодом ниже.
- Результат
- Три .go-файла в трёх папках; в main.go нет копий двух вынесенных функций.
- Проверка
- Сохраните все файлы и выполните go run . — ожидается 4.40.
Создайте папки и файлы, перенесите в них соответствующие функции из main.go. Не оставляйте вторых копий: иначе при исправлении одной другая может остаться старой.
package services
import "errors"
// CalculateAverage возвращает среднюю оценку или ошибку для пустого среза.
func CalculateAverage(values []int) (float64, error) {
if len(values) == 0 {
return 0, errors.New("список оценок не должен быть пустым")
}
sum := 0
for _, value := range values {
sum += value
}
return float64(sum) / float64(len(values)), nil
}package utils
import "fmt"
// FormatAverage превращает число в строку с двумя знаками после точки.
func FormatAverage(value float64) string {
return fmt.Sprintf("Средний результат: %.2f", value)
}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))
}go run .Результат снова Средний результат: 4.40. Изменилось расположение кода, а не задача программы. Если результат другой, сначала проверьте перенос функций и импорты.
Как читать import
import "example.com/student-tools/internal/services"
result, err := services.CalculateAverage([]int{5, 4, 5})В импорте указывается путь к папке, а не к файлу: расширение .go и имена файлов там не появляются вообще. После импорта в вашем файле появляется одно имя — имя пакета (последняя строка package services, а не последний сегмент пути, хотя обычно они совпадают). Обращаться к функциям можно только через это имя: сокращённой формы вроде from … import … в Go нет, поэтому по строке вызова всегда видно, откуда пришла функция.
Видимость задаёт первая буква имени
В Python видимость — договорённость: подчёркивание в начале имени просит «не трогать снаружи», но не запрещает. В Go это правило языка. Имя с заглавной буквы видно из других пакетов, имя со строчной — нет. Никаких public и private не существует: решает первая буква.
Имя начинается с заглавной буквы — функция экспортирована. Это часть публичного интерфейса пакета: её могут вызывать другие пакеты и тесты.
Поэтому при переносе функций мы переименовали их: calculate_average стало CalculateAverage. Заодно поменялся стиль записи — в Go слова в именах не разделяют подчёркиванием, а пишут слитно с заглавных букв.
Импорт не «выполняет» пакет
Строка import в Go — не исполнение файла, а указание компилятору на зависимость. Соединяет код компоновщик при сборке, а не интерпретатор по ходу работы. Отсюда два следствия. Во-первых, порядок импортов ни на что не влияет, и повторный импорт того же пакета невозможен по построению. Во-вторых, неиспользованный импорт — это ошибка компиляции, а не замечание: "fmt" imported and not used. Редактор с расширением Go убирает лишние импорты при сохранении файла.
Где здесь точка запуска
В Python один и тот же файл можно и запустить, и импортировать, поэтому в конце файла пишут условие if __name__ == "__main__". В Go такой двойственности нет, и условие не нужно: роль файла определяется ещё до запуска, его первой строкой.
package main
func main() {
// Точка входа программы. Вызывается средой исполнения.
}Правило состоит из двух частей, и обе обязательны. Пакет должен называться main — именно это делает его исполняемой программой, а не библиотекой. И в нём должна быть функция main() без аргументов и без возвращаемого значения — её вызывает среда исполнения при старте.
Любой другой пакет — services, utils — запустить нельзя: у него нет точки входа, и go run на него ответит ошибкой. Импортировать пакет main тоже нельзя. Роли разведены на уровне языка: импорт пакета не запускает расчёт.
Побочных эффектов при импорте в Go почти не бывает. Есть функция init(), которая выполняется при подключении пакета, но пользуются ей редко и осознанно. Тесты из главы 2.6 импортируют ваши пакеты, и расчёт от этого не запускается.
Названия помогают импортам
Имя папки становится частью пути импорта, а имя пакета — частью каждого вызова. Поэтому в Go имена пакетов делают короткими, в одно слово, маленькими латинскими буквами, без подчёркиваний и дефисов: services, utils, storage. Плохой пример — user_service_helpers: в коде это будет выглядеть как user_service_helpers.Get(...).
Имя пакета не повторяют в имени функции. В Go пишут services.CalculateAverage, а не services.ServicesCalculateAverage: имя пакета уже стоит перед точкой. По той же причине пакеты не называют util, common или base — такое имя ничего не сообщает о содержимом, и по нему нельзя понять, что лежит внутри.
Проверьте себя · Функция есть в файле, но пакет main её «не видит». Почему?
Скорее всего имя начинается со строчной буквы: такие имена не экспортируются и снаружи пакета не существуют. Переименуйте calculateAverage в CalculateAverage. Вторая возможная причина — файл лежит в другой папке, чем вы думаете: в Go пакетом является папка, и импорт ведёт именно к ней.
Проверьте себя · Почему калькулятор не должен импортировать main?
main уже использует калькулятор. Обратная связь создаёт цикл и связывает вычисления с запуском. Данные передаются функции аргументами, результат возвращается через return.
Документация к теме
Go: организация кода · Go: имена пакетов · Go: экспортируемые имена · Effective Go
Python: venv · Python: модули и пакеты · VS Code: Python environments · pip freeze