Тема 04 · Аннотации типов и документация кода

Аннотации типов и документация кода · МДК.01.01 · разработка программных модулей

Глава 4.1

4.1

Аннотация типа:
что это и что она делает

В этой главе разобрано, что такое аннотация типа и как она записывается у параметра, у результата и у переменной. На функциях учебного проекта показано, как читать подпись с аннотациями, что Python делает с аннотацией при запуске программы и что меняется в редакторе, когда аннотации в коде есть.

Категория
Понятия
Уровень
Начальный
Опирается на
Модули и пакеты, тема 02
Зачем это знать
Понимать, какие значения принимает функция, не открывая её тело

§1Тип значения

Тип — вид значения, от которого зависит, что с ним можно делать. У числа 5 тип int: его можно складывать и делить. У строки "5" тип str: её можно разрезать и склеить с другой строкой, а сложить с числом нельзя. Узнать тип любого значения можно функцией type: type(5) вернёт <class 'int'>, type("5") — <class 'str'>.

Python определяет тип значения во время работы программы, в момент, когда значение уже получено. Такой подход называют динамической типизацией. Объявлять заранее, какие значения принимает функция, не требуется: в def calculate_average(grades) можно передать список чисел, список строк или одно число. Если значение не подходит, ошибка возникнет только при выполнении той строки, которая с ним не справится.

Пока функция и её вызов лежат в одном файле, это не мешает: тело функции видно рядом. Когда функций десятки и они разнесены по пакетам, как в проекте из темы 02, при каждом вызове приходится открывать чужой модуль и выяснять одно и то же: что передавать и что вернётся. Аннотации типов записывают ответ на этот вопрос прямо в первой строке функции.

§2Проект темы

Трудность из §1 видна только в проекте, где функций много и они лежат в разных модулях. Поэтому все примеры темы взяты из одного такого проекта — «Отчёт кафе». Он читает файл смены с заказами, считает выручку по каждому официанту и печатает отчёт в консоль. Десять модулей разложены по пакетам: domain — заказ и смена, services — чтение файла и расчёты, utils — имена, суммы и дата, ui — печать. Суммы в проекте хранятся в копейках, целыми числами: 125000 означает 1 250,00 ₽.

Проект выдаётся в трёх архивах с одним и тем же кодом на разных этапах темы. Первый — код без аннотаций: на нём видно, каких сведений не хватает при вызове функций. Второй — тот же код с аннотациями. В этой главе оба архива открыты рядом, и каждое свойство аннотаций показано сравнением: что видно в первом архиве и что — во втором. Третий архив, с описаниями функций, нужен во второй части темы.

Скачайте первые два архива и подготовьте каждый к работе одинаково:

  1. Распакуйте архив. В Проводнике щёлкните по архиву правой кнопкой и выберите Извлечь всё.В Finder дважды щёлкните по архиву: рядом появится папка.В файловом менеджере щёлкните по архиву правой кнопкой и выберите Извлечь или выполните в терминале unzip имя-архива.zip.
  2. Откройте получившуюся папку в VS Code: File → Open Folder. Открывать нужно папку, в которой лежат main.py и requirements.txt.
  3. Откройте терминал редактора (Ctrl + `) и создайте окружение: python3 -m venv .venvpy -m venv .venv.
  4. Установите зависимости: .venv/bin/pip install -r requirements.txt.venv\Scripts\pip install -r requirements.txt. Вместе с pytest поставятся Ruff и mypy.
  5. Запустите проект: .venv/bin/python main.py.venv\Scripts\python main.py. В терминале появится отчёт по смене.
Терминалчто печатает проект
$ .venv/bin/python main.py> .venv\Scripts\python main.py
=========== Смена 21.09.2026 ===========
Петрова Анна                  2 480,50 ₽
Иванов Пётр                   1 685,00 ₽
Сидоров Илья                    772,00 ₽
=========== Итого 4 937,50 ₽ ===========

Команды в книге показаны для одной системы. Систему выбирают переключателем Windows · macOS · Linux вверху страницы; выбор запоминается для всех глав темы.

На полях: команды с путём и команды после активации

В книге программы окружения вызываются по пути: .venv/bin/python.venv\Scripts\python, .venv/bin/mypy.venv\Scripts\mypy. Такая команда работает в любом терминале, открытом в папке проекта.

Второй способ — один раз активировать окружение: source .venv/bin/activate.venv\Scripts\Activate.ps1 в PowerShell или .venv\Scripts\activate.bat в командной строке. В начале строки терминала появится (.venv), и до закрытия терминала команды пишутся коротко и одинаково во всех системах: python main.py, mypy app, python -m pytest -q. Активация разобрана в главе 2.1.

Оба архива печатают одинаковый отчёт. В первом аннотаций нет, и программа всё равно работает: для запуска аннотации не нужны. Дата в шапке — текущая: проект берёт её из часов компьютера. Раз поведение одинаковое, разница между архивами — только в тексте кода. С неё и начинается разбор.

§3Как записывается аннотация

Откройте в обоих архивах файл app/services/calculator.py и сравните первую строку функции calculate_average:

Без аннотаций

def calculate_average(grades):

Из подписи видно только имя параметра. Список это, кортеж или одно число, можно узнать, только прочитав тело.

→
С аннотациями

def calculate_average(grades: list[int]) -> float:

Параметр — список целых чисел, результат — дробное число. Тело для этого читать не нужно.

Добавленные во втором архиве : list[int] и -> float — это аннотации. Аннотация типа — запись в коде, которая указывает, значения какого типа ожидаются в параметре, в переменной или в результате функции.

У calculate_average аннотации стоят в двух местах — у параметра и у результата. Всего таких мест три: третье — переменная. Все три показаны на схеме. Для неё взята функция format_title из app/utils/formatter.py, потому что у неё есть и обычный параметр, и параметр со значением по умолчанию, и результат; пример переменной — строка из функции revenue_by_waiter.

def format_title( text: str , width: int = 40 ) -> str : параметр: тип параметр: тип = умолчание -> тип результата totals : dict[str, int] = {} переменная: тип = значение
Три места, где пишется аннотация. У параметра — после имени через двоеточие; если есть значение по умолчанию, оно идёт после типа через знак равенства. У результата — после закрывающей скобки через ->. У переменной — между именем и знаком равенства.
ГдеЗаписьЧитается как
Параметрtext: strпараметр text принимает строку
Параметр с умолчаниемwidth: int = 40целое число; если его не передать, будет 40
Результат-> strфункция возвращает строку
Функция без результата-> Noneфункция ничего не возвращает
Переменнаяtotals: dict[str, int] = {}словарь «строка — целое число», сейчас пустой
Атрибут объектаself.orders: list[Order] = []список заказов, сейчас пустой

В таблице те же три места, что на схеме, и два частных случая: -> None у функции, которая ничего не возвращает, и аннотация атрибута объекта self.orders — она пишется так же, как у переменной.

Кроме места, у аннотаций есть правила оформления; они соблюдаются во всём проекте. После двоеточия ставят один пробел, перед ним — ни одного: text: str. Вокруг стрелки — по пробелу: ) -> str:. У параметра со значением по умолчанию вокруг знака равенства ставят пробелы, если есть аннотация (width: int = 40), и не ставят, если её нет (width=40).

В примерах уже встречались составные записи: list[int] — «список, в котором лежат целые числа», dict[str, int] — «словарь со строковыми ключами и целыми значениями». В квадратных скобках после имени коллекции указывают, что в ней хранится. Для чтения подписей в этой главе этого достаточно; все такие записи разобраны в главе 4.2.

§4Как читать подпись функции

Запись аннотаций разобрана. Теперь можно вернуться к трудности из §1 — что передать функции и что она вернёт — и посмотреть, как подписи с аннотациями отвечают на этот вопрос для всего проекта.

Подпись функции — первая строка её объявления: имя, параметры и результат. С аннотациями подпись отвечает на три вопроса: какие значения передать, в каком порядке и что вернётся. Таблица ниже собрана из второго архива: из каждого модуля выписаны первые строки функций, которые вызываются из других модулей, а справа записано, как эти строки читаются. Подпись читают слева направо: сначала имя, затем каждый параметр с его типом, в конце тип после стрелки.

ПодписьЧто передаютЧто получают
def today_label() -> strничегостроку с датой, например "21.09.2026"
def normalize_name(name: str) -> strстроку с именемстроку: имя в виде «Фамилия Имя»
def format_kopeks(kopeks: int) -> strцелое число копеекстроку "1 250,00 ₽"
def format_title(text: str, width: int = 40) -> strстроку и, по желанию, ширинустроку ровно заданной ширины
def calculate_average(grades: list[int]) -> floatсписок целых чиселдробное число
def grade_bounds(grades: list[int]) -> tuple[int, int]список целых чиселпару целых: минимум и максимум
def load_orders(path: Path) -> list[Order]путь к файлу — объект Pathсписок заказов
def revenue_by_waiter(orders: list[Order], top: int = 0) -> dict[str, int]список заказов и, по желанию, числословарь «имя — сумма»
def sort_orders_in_place(orders: list[Order]) -> Noneсписок заказовничего: функция меняет переданный список
def save_report(path: Path, lines: list[str]) -> intпуть и список строкцелое число — сколько строк записано
def print_report(lines: list[str]) -> Noneсписок строкничего: функция только печатает

Все сведения в правых колонках взяты только из подписей: тела функций для этого не открывались. Значит, по таблице можно составить вызов, не открывая ни одного модуля. Например, load_orders принимает Path и возвращает list[Order], а revenue_by_waiter принимает list[Order]. Поэтому результат первой функции подходит второй без преобразований — так они и вызываются в main.py:

main.pyрезультат одной функции идёт в другую
def build_report(path: Path) -> list[str]:
    orders = load_orders(path)               # list[Order]
    revenue = revenue_by_waiter(orders)      # dict[str, int]
    ...

В таблицу не попали методы класса Shift из app/domain/shift.py: у подписей методов есть особенность. Первый параметр метода, self, аннотации не получает: это сам объект, и его тип известен из класса. Метод __init__ всегда возвращает None, поэтому его подпись в проекте — def __init__(self, day: date) -> None. Класс целиком, без описаний:

app/domain/shift.pyметоды класса Shift
class Shift:
    def __init__(self, day: date) -> None:
        self.day = day
        self.orders: list[Order] = []

    def add(self, order: Order) -> None:
        self.orders.append(order)

    def total_kopeks(self) -> int:
        return sum(order.kopeks for order in self.orders)

Здесь видны все три места аннотаций сразу. day: date — параметр: в смену передают дату, объект date из модуля datetime. self.orders: list[Order] = [] — атрибут объекта: пустой список, в который потом попадут заказы; без аннотации по пустым скобкам нельзя понять, что в нём будет. -> int у total_kopeks — результат: сумма копеек целым числом. -> None у add означает, что метод меняет объект и ничего не возвращает.

§5Что Python делает с аннотацией

Подписи из §4 сообщают, что функция ожидает. Следующий вопрос — следит ли Python за этими ожиданиями, когда программа работает. Ответ дают опыты в интерактивном Python. Откройте терминал в папке второго архива и запустите .venv/bin/python.venv\Scripts\python. Приглашение >>> означает, что Python ждёт команду; каждая строка выполняется сразу после Enter.

Сначала — где Python держит аннотации. Импортируйте функцию и посмотрите её атрибут __annotations__:

Терминалвторой архив, интерактивный Python
>>> from app.services.calculator import calculate_average
>>> calculate_average.__annotations__
{'grades': list[int], 'return': <class 'float'>}

Это обычный словарь: ключи — имена параметров, отдельный ключ 'return' — тип результата. Python сохранил аннотации, когда создавал функцию. Проверяет ли он по ним аргументы, показывают вызовы ниже. В первых трёх вкладках в той же сессии >>> вызвана calculate_average(grades: list[int]) -> float — сначала с подходящим аргументом, затем с двумя неподходящими. Для четвёртой вкладки нужна функция, которой в проекте нет, — сложение двух сумм. Она записана в отдельный файл proba.py рядом с main.py и запущена командой .venv/bin/python proba.py.venv\Scripts\python proba.py. Все выводы получены запуском.

Аннотация при запускепереключите вызов

Терминалтип совпадает с аннотацией
>>> calculate_average([5, 4, 5, 3, 5])
4.4

РаботаетПередан список целых чисел, как и записано в аннотации. Функция вернула среднее.

Терминалтип не совпадает с аннотацией
>>> calculate_average([5.5, 4.0])
4.75

РаботаетАннотация требует целые числа, переданы дробные. Python вызов не остановил: сложение и деление для дробных чисел определены, и функция вернула результат.

Терминалтип не совпадает с аннотацией
>>> calculate_average(['5', '4'])
  File "app/services/calculator.py", line 4, in calculate_average
    return round(sum(grades) / len(grades), 2)
                 ^^^^^^^^^^^
TypeError: unsupported operand type(s) for +: 'int' and 'str'

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

proba.pyсложение двух сумм
def add_kopeks(a: int, b: int) -> int:
    return a + b


print(add_kopeks(150, 200))
print(add_kopeks("150", "200"))
Терминалpython proba.py
350
150200

Неверный результат без ошибкиДля строк оператор + означает склейку, поэтому вместо суммы 350 получилась строка "150200". Программа продолжит работу с неверным значением. Такой случай реален: числа из CSV-файла читаются строками, и в load_orders сумму переводят в число вызовом int(row["kopeks"]).

Из четырёх вызовов следует, что аннотация описывает ожидание, но при запуске его не обеспечивает. Неверный тип либо проходит незамеченным, либо приводит к ошибке внутри функции, далеко от места вызова. Сверку вызовов с аннотациями до запуска выполняют отдельные программы — Pylance в редакторе и mypy в терминале. Работе с ними посвящена глава 4.3.

Ассоциация: разметка на парковке

В жизни

Линии на асфальте показывают, где место для легковой машины, а где для грузовика. Сами по себе они не мешают грузовику встать на легковое место. Нарушение замечает человек, который обходит парковку и сверяет машины с разметкой.

В коде

Аннотация — разметка: она показывает, какое значение ожидается. При запуске Python её не проверяет. Сверку выполняет программа проверки типов, по тексту кода.

Где не совпадаетНа парковке нарушение видно, когда машина уже стоит. Программа проверки находит неверный вызов в тексте, до того как программа запущена.

§6Подсказка редактора

Python аннотации не проверяет, и при запуске они ничего не меняют. Используют их другие программы. Первая из них уже открыта — редактор VS Code: он читает аннотации и показывает их в подсказке у места вызова, без всяких настроек.

Подсказку вызывают наведением указателя на имя функции или клавишами: курсор на имя, затем Ctrl + K и Ctrl + I (на macOS — Cmd вместо Ctrl). Повторите опыт в обоих архивах: откройте main.py, найдите строку 11 — revenue = revenue_by_waiter(orders) — и наведите указатель на revenue_by_waiter. Файл main.py в двух архивах одинаковый, отличается только модуль app/services/orders.py, где записана сама функция. На кадрах сравните строку с параметром orders и тип после ->.

Подсказка VS Code на вызове revenue_by_waiter в проекте без аннотаций: orders: Any, top: int = 0, результат dict
Первый архив, аннотаций нет. Редактор выводит типы сам, по коду функции. Тип top выведен из значения по умолчанию: top: int = 0. Для orders выводить не из чего, и в подсказке стоит Any — «любой тип». Результат — dict без указания содержимого: что лежит в словаре, неизвестно.
Подсказка VS Code на том же вызове в проекте с аннотациями: orders: list[Order], top: int = 0, результат dict[str, int]
Второй архив. В подсказке стоят записанные типы: orders: list[Order], результат dict[str, int]. Из места вызова видно, что передать и что вернётся.

Строка вызова в двух кадрах одна и та же. Разные сведения в подсказке взялись из подписи функции в app/services/orders.py: в первом архиве там def revenue_by_waiter(orders, top=0):, во втором — подпись с аннотациями из таблицы §4.

§7Автодополнение

Подсказка показывает типы, когда код уже написан. Те же типы редактор использует, пока код набирается, — в автодополнении.

Автодополнение — список, который редактор показывает при наборе: имена переменных, функций, атрибутов и методов, подходящих в этом месте. После точки в нём перечислены атрибуты значения слева от точки. Составить такой список редактор может, только если знает тип значения. Опыт снова в двух архивах: откройте app/services/orders.py, поставьте курсор в конец тела функции sort_orders_in_place — последней в файле, — нажмите Enter и наберите orders[0].: первый элемент списка заказов и точку. Если список не появился сам, вызовите его клавишами Ctrl + Пробел. На macOS это сочетание часто занято переключением раскладки; тогда список вызывают командой Trigger Suggest из палитры команд (Cmd + Shift + P). После опыта отмените набранное: Ctrl + Z.

VS Code: после orders[0]. в проекте без аннотаций редактор пишет No suggestions
Первый архив, подпись def sort_orders_in_place(orders):. Тип orders неизвестен, значит, неизвестен и тип его элемента. Редактор отвечает No suggestions — «предложений нет». Имя атрибута придётся набирать по памяти, и опечатка вроде order.kopek обнаружится только при запуске.
VS Code: после orders[0]. в проекте с аннотациями редактор предлагает kopeks и waiter, рядом подсказка kopeks: int
Второй архив, подпись def sort_orders_in_place(orders: list[Order]) -> None:. Элемент списка — Order, и первыми в списке стоят его поля kopeks и waiter. Справа — тип выбранного поля: kopeks: int. Ниже идут служебные атрибуты, которые есть у любого объекта, — их имена начинаются и заканчиваются двумя подчёркиваниями.

Что аннотации дают при работе с кодом: итог §4–§7

  • Вызов без чтения тела функции. Подсказка показывает, какие значения передавать и что вернётся.
  • Имена атрибутов из списка. Автодополнение предлагает поля объекта, и order.kopeks выбирается из списка; набирать имя по памяти не нужно.
  • Проверка до запуска. Программы проверки типов сверяют каждый вызов с аннотациями и показывают строку, где передано не то.

Первые два пункта работают сразу, их показали кадры §6 и §7. Третий требует отдельных программ: они разобраны в главе 4.3. Перед этим, в главе 4.2, показано, как записать любой тип, — от int до «значение или None». В главе 4.4 собраны ошибки, которые проверка находит в каждом из этих типов.

§8Частые вопросы

Обычно думаютС аннотацией Python не даст передать значение другого типа.
На самом делеПри запуске аннотации не проверяются. Вызов add_kopeks("150", "200") выполняется и возвращает "150200", вызов со строками в calculate_average завершается ошибкой внутри функции. Несовпадения находит программа проверки типов.
Обычно думаютАннотация превращает значение в указанный тип.
На самом делеАннотация ничего не превращает. Для перевода строки в число вызывают int(...), как в функции load_orders при чтении файла: kopeks=int(row["kopeks"]).
Обычно думаютЕсли тип понятен из имени, аннотация не нужна.
На самом делеПо имени orders не видно, список это или словарь и что в нём лежит. Редактор и программа проверки тип по имени не определяют: в первом архиве у orders тип Any, и автодополнение ничего не предлагает.

§9Проверьте себя

Где пишется аннотация у параметра, у результата и у переменной?

У параметра — после имени через двоеточие: text: str. У результата — после закрывающей скобки через стрелку: -> str. У переменной — между именем и знаком равенства: totals: dict[str, int] = {}.

Прочитайте подпись: def is_big(kopeks: int, limit: int = 100_000) -> bool.

Функция принимает целое число копеек и, по желанию, порог — тоже целое число, по умолчанию 100_000. Возвращает True или False. Подчёркивание внутри числа 100_000 Python пропускает, оно только разделяет разряды.

Прочитайте подпись: def save_report(path: Path, lines: list[str]) -> int.

Функция принимает путь — объект Path — и список строк. Возвращает целое число. Что это за число — количество записанных строк, — из подписи не видно: такие сведения пишут в описании функции.

Что вернёт calculate_average([5.5, 4.0]), если в аннотации записано list[int]?

4.75. Python при запуске аннотации не проверяет, а для дробных чисел сложение и деление определены.

Почему у self в методах класса нет аннотации?

self — сам объект, и его тип известен из класса, в котором записан метод. Редактор и программа проверки выводят его сами.

Почему в первом архиве после orders[0]. редактор ничего не предлагает?

У параметра orders нет аннотации, и тип его элементов неизвестен. Перечень атрибутов редактор берёт из типа значения, а типа нет.

§10Шпаргалка по главе

Параметр text: str width: int = 40
Результат -> str -> None — ничего не возвращает
Переменная и атрибут totals: dict[str, int] = {} self.orders: list[Order] = []
Метод def add(self, order: Order) -> None self — без аннотации
При запуске хранятся в __annotations__ не проверяются
В редакторе подсказка: Ctrl+K, Ctrl+I автодополнение: Ctrl+Пробел

Итог главы

Аннотация типа записывает в подпись функции, какие значения она принимает и что возвращает. По подписям с аннотациями можно составить вызов, не открывая модуль. При запуске Python аннотации не проверяет: неверный тип проходит незамеченным или приводит к ошибке внутри функции. Редактор использует аннотации в подсказке и в автодополнении, а сверку вызовов до запуска выполняют программы проверки типов из главы 4.3.

Откуда взяты факты
  1. Синтаксис аннотаций функций — PEP 3107, переменных — PEP 526, оформление — PEP 8, Function Annotations.
  2. Назначение аннотаций и то, что интерпретатор их не проверяет, — PEP 484, Type Hints.
  3. Атрибут __annotations__ — документация Python, Annotations Best Practices.
  4. Выводы в терминале получены запуском архивов проекта темы на Python 3.12; кадры сняты в VS Code 1.138 с расширением Pylance 2026.4.
4.1

Тип в подписи функции:
что Go проверяет до запуска

В этой главе разобрано, где в Go записывается тип — у параметра, у результата и у переменной — и как читать подпись функции целиком. На функциях проекта темы показано, что делает компилятор, когда тип аргумента не совпадает с подписью, и как редактор использует типы в подсказке и в автодополнении.

Категория
Понятия
Уровень
Начальный
Опирается на
Модули и пакеты Go, тема 02
Зачем это знать
Понимать, какие значения принимает функция, не открывая её тело

§1Тип значения

Тип — вид значения, от которого зависит, что с ним можно делать. У числа 5 тип int: его можно складывать и делить. У строки "5" тип string: её можно склеить с другой строкой, а сложить с числом нельзя. Узнать тип значения можно функцией fmt.Printf с указателем %T: для 125000 она напечатает int, для "Смена" — string, для 0.15 — float64.

Go определяет тип каждой переменной, параметра и результата до запуска программы, при сборке. Такой подход называют статической типизацией. У любой переменной тип один и не меняется: если в переменную price положено целое число, строку в неё положить уже нельзя. Если тип значения где-то не совпадает с ожидаемым, компилятор сообщает об ошибке и исполняемый файл не создаёт.

В этом отличие от Python, где тип у параметра можно не писать. В Go подпись функции без типов не существует: func CalculateAverage(grades) — синтаксическая ошибка. Тип параметра и тип результата пишутся всегда, и поэтому первая строка любой функции уже отвечает на вопрос, что ей передать и что она вернёт.

Как соотносится с веткой Python

В Python запись типа называется аннотацией и необязательна: интерпретатор её не проверяет, сверку выполняют отдельные программы. В Go тип в подписи — часть языка: без него код не компилируется, а сверку выполняет сам компилятор. Слово «аннотация» к Go не применяют, говорят просто «тип параметра», «тип результата».

§2Проект темы

Трудность «что передать и что вернётся» видна только в проекте, где функций много и они лежат в разных пакетах. Поэтому все примеры темы взяты из одного такого проекта — «Отчёт кафе». Он читает файл смены с заказами, считает выручку по каждому официанту и печатает отчёт в консоль. Код разложен по пакетам внутри папки internal, как в теме 02: domain — заказ и смена, services — чтение файла и расчёты, utils — имена, суммы и дата, ui — печать. Суммы хранятся в копейках, целыми числами: 125000 означает 1 250,00 ₽.

Проект выдаётся в трёх архивах с одним и тем же кодом на разных этапах темы. Первый архив не собирается: в нём двенадцать ошибок типов, их исправляют в практике главы 4.5. Второй — рабочий код без комментариев документации; на нём построены главы 4.1–4.4 и вся вторая часть темы. Третий — тот же код с документацией, эталон для сверки в конце.

Для этой главы нужен второй архив. Подготовьте его к работе:

  1. Распакуйте архив. В Проводнике щёлкните по архиву правой кнопкой и выберите Извлечь всё.В Finder дважды щёлкните по архиву: рядом появится папка.В файловом менеджере щёлкните по архиву правой кнопкой и выберите Извлечь или выполните в терминале unzip имя-архива.zip.
  2. Откройте получившуюся папку в VS Code: File → Open Folder. Открывать нужно папку, в которой лежат go.mod и main.go: по файлу go.mod Go и редактор находят корень модуля.
  3. Откройте терминал редактора (Ctrl + `) и проверьте версию Go: go version. Нужна версия 1.23 или новее. Установка Go разобрана в главе 2.1, ветка Go.
  4. Соберите все пакеты: go build ./.... Запись ./... означает «текущая папка и все вложенные». Если команда ничего не напечатала, сборка прошла без ошибок.
  5. Запустите проект: go run .. В терминале появится отчёт по смене.
Терминалчто печатает проект
$ go run .
=========== Смена 21.09.2026 ===========
Петрова Анна                  2 480,50 ₽
Иванов Пётр                   1 685,00 ₽
Сидоров Илья                    772,00 ₽
=========== Итого 4 937,50 ₽ ===========

Дата в шапке — текущая: проект берёт её из часов компьютера. Команды Go одинаковы в Windows, macOS и Linux. Окружение, как у Python, создавать не нужно: зависимости модуля записаны в go.mod, а у этого проекта внешних зависимостей нет.

§3Где записывается тип

Откройте файл internal/services/calculator.go и найдите первую строку функции CalculateAverage. Рядом для сравнения — та же функция из ветки Python:

Python, с аннотациями

def calculate_average(grades: list[int]) -> float:

Тип пишется после имени через двоеточие, результат — после стрелки. Аннотации можно не писать.

→
Go

func CalculateAverage(grades []int) (float64, error)

Тип пишется после имени через пробел, без двоеточия. Результатов два: дробное число и ошибка. Без типов строка не скомпилируется.

В Go тип записывается в трёх местах: у параметра, у результата и у переменной. Все три показаны на схеме. Для неё взята функция FormatTitle из internal/utils/formatter.go: у неё два параметра разных типов и один результат. Пример переменной — строка из функции RevenueByWaiter.

func FormatTitle( text string , width int ) string { имя тип имя тип тип результата var names []string var имя тип — значение будет «нулевым»: пустой срез
Три места, где пишется тип. У параметра — после имени через пробел. У результата — после закрывающей скобки параметров, перед телом функции. У переменной, объявленной словом var, — после имени.
ГдеЗаписьЧитается как
Параметрtext stringпараметр text принимает строку
Несколько параметров одного типаa, b intоба параметра — целые числа; тип пишут один раз в конце
Один результат) stringфункция возвращает строку
Несколько результатов) (float64, error)функция возвращает дробное число и ошибку; список в скобках
Результаты с именами) (low, high int, err error)три результата, у каждого есть имя
Нет результатаfunc PrintReport(lines []string)после скобок ничего: функция ничего не возвращает
Переменная с типомvar names []stringсрез строк, сейчас пустой
Переменная без записи типаtotal := 0тип int выведен из значения 0
Поле структурыKopeks intполе Kopeks хранит целое число

В таблице кроме трёх мест есть частные случаи. Если у функции несколько результатов, их типы перечисляют в скобках через запятую. Самое частое сочетание в Go — значение и ошибка: (float64, error). Тип error означает «ошибка или её отсутствие»; когда всё прошло хорошо, на месте ошибки возвращается nil. Функция без результата ничего не пишет после скобок: аналога -> None в Go нет.

У переменной тип можно не писать, если он понятен из значения. Запись total := 0 — короткое объявление: компилятор сам выводит тип int из числа 0. Тип у такой переменной всё равно есть и не меняется, его просто не набирают руками. Запись var names []string нужна, когда значения пока нет: переменная получает нулевое значение своего типа — 0 у чисел, "" у строки, пустой срез у среза.

Оформление кода в Go задаёт программа gofmt, и VS Code с расширением Go запускает её при каждом сохранении файла. Пробелы, отступы и выравнивание полей структуры приводятся к одному виду сами.

В примерах уже встречались составные типы: []int — «срез целых чисел», то есть список; map[string]int — «словарь со строковыми ключами и целыми значениями». Для чтения подписей в этой главе этого достаточно; все такие записи разобраны в главе 4.2.

§4Как читать подпись функции

Подпись функции — первая строка её объявления: имя, параметры с типами и типы результатов. Подпись отвечает на три вопроса: какие значения передать, в каком порядке и что вернётся. Таблица ниже собрана из второго архива: из каждого пакета выписаны функции, которые вызываются из других пакетов, а справа записано, как эти строки читаются. Подпись читают слева направо: имя, затем каждый параметр с типом, в конце типы результатов.

ПодписьЧто передаютЧто получают
func TodayLabel() stringничегостроку с датой, например "21.09.2026"
func NormalizeName(name string) stringстроку с именемстроку: имя в виде «Фамилия Имя»
func FormatKopeks(kopeks int) stringцелое число копеекстроку "1 250,00 ₽"
func FormatTitle(text string, width int) stringстроку и ширину — оба аргумента обязательныстроку заданной ширины
func CalculateAverage(grades []int) (float64, error)срез целых чиселдробное число и ошибку
func GradeBounds(grades []int) (low, high int, err error)срез целых чиселминимум, максимум и ошибку
func NewOrder(waiter string, kopeks int) (Order, error)имя и суммузаказ и ошибку
func LoadOrders(path string) ([]domain.Order, error)путь к файлу строкойсрез заказов и ошибку
func RevenueByWaiter(orders []domain.Order, top int) ([]WaiterRevenue, error)срез заказов и числосрез пар «официант — сумма» и ошибку
func SortOrdersInPlace(orders []domain.Order)срез заказовничего: функция меняет порядок в переданном срезе
func SaveReport(path string, lines []string) (int, error)путь и срез строкцелое число и ошибку
func PrintReport(lines []string)срез строкничего: функция только печатает

Все сведения в правых колонках взяты только из подписей. Значит, по таблице можно составить вызов, не открывая ни одного пакета. LoadOrders возвращает []domain.Order и ошибку, а RevenueByWaiter принимает []domain.Order. Поэтому первый результат одной функции передаётся в другую без преобразований — так они и вызываются в main.go:

main.goрезультат одной функции идёт в другую
func buildReport(path string) ([]string, error) {
	orders, err := services.LoadOrders(path)          // []domain.Order, error
	if err != nil {
		return nil, err
	}
	revenue, err := services.RevenueByWaiter(orders, 0) // []services.WaiterRevenue, error
	...

Из этого фрагмента видны три правила вызова, которые следуют из подписей. Первое: если функция возвращает два значения, их принимают в две переменные — orders, err := …. Второе: ошибку проверяют сразу после вызова, сравнивая с nil. Третье: у параметров Go нет значений по умолчанию, поэтому RevenueByWaiter вызывается с явным 0 на месте top. В Python этот аргумент можно было пропустить.

Имя пакета перед функцией тоже часть чтения. services.LoadOrders — функция LoadOrders из пакета services; в подписи тип domain.Order — тип Order из пакета domain. Внутри своего пакета имя пишут без приставки: в internal/domain/order.go это просто Order. Имена с заглавной буквы видны из других пакетов, со строчной — только внутри своего; это правило разобрано в главе 2.4, ветка Go.

В таблицу не попали методы типа Shift из internal/domain/shift.go: у подписи метода есть особенность. Перед именем метода в скобках стоит получатель — объект, у которого метод вызывают, с его типом. В Python это self без аннотации; в Go у получателя тип пишется всегда:

internal/domain/shift.goтип Shift и его методы
type Shift struct {
	Day    time.Time
	Orders []Order
}

func NewShift(day time.Time) *Shift {
	return &Shift{Day: day}
}

func (s *Shift) Add(order Order) {
	s.Orders = append(s.Orders, order)
}

func (s *Shift) TotalKopeks() int {
	total := 0
	for _, order := range s.Orders {
		total += order.Kopeks
	}
	return total
}

Здесь видны все три места записи типа. Поля структуры: Day time.Time — дата из пакета time, Orders []Order — срез заказов. Получатель (s *Shift) читается так: «метод вызывают у указателя на смену, внутри метода смена называется s». Звёздочка нужна, чтобы Add менял ту самую смену, у которой его вызвали, а не её копию; указатели разобраны в главе 4.2. Результат int у TotalKopeks — сумма копеек. У Add после скобок ничего нет: метод меняет объект и ничего не возвращает. Функция NewShift — конструктор: обычная функция, которая создаёт и возвращает новый объект. Отдельного __init__ в Go нет.

§5Что Go делает с типом

Подписи из §4 сообщают, что функция ожидает. Следующий вопрос — что будет, если передать не то. Ответ дают опыты. Создайте в корне проекта папку proba, а в ней файл main.go: это отдельная маленькая программа внутри того же модуля, она может импортировать пакеты проекта. Запускается она командой go run ./proba. Во всех вкладках ниже меняется только содержимое proba/main.go; выводы получены запуском.

Первые три вкладки вызывают CalculateAverage(grades []int) (float64, error) — сначала с подходящим аргументом, затем с двумя неподходящими. Для четвёртой нужна функция, которой в проекте нет, — сложение двух сумм; она записана прямо в proba/main.go.

Тип при сборкепереключите вызов

proba/main.goтип совпадает с подписью
package main

import (
	"fmt"

	"example.com/otchet-kafe/internal/services"
)

func main() {
	average, err := services.CalculateAverage([]int{5, 4, 5, 3, 5})
	fmt.Println(average, err)
}
Терминалgo run ./proba
4.4 <nil>

Собралось и работаетПередан срез целых чисел, как записано в подписи. Программа собрана и запущена: среднее 4.4, ошибки нет — на её месте <nil>.

proba/main.goв срезе целых — дробное число
package main

import (
	"fmt"

	"example.com/otchet-kafe/internal/services"
)

func main() {
	average, err := services.CalculateAverage([]int{5.5, 4.0})
	fmt.Println(average, err)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:10:50: cannot use 5.5 (untyped float constant) as int value in array or slice literal (truncated)

Не собралосьКомпилятор отказался собирать программу: 5.5 нельзя положить в срез целых чисел без потери дробной части — «(truncated)», «было бы обрезано». Число 4.0 ошибки не вызвало: оно равно целому 4, и компилятор переводит его без потерь. В Python тот же вызов выполнился и вернул 4.75.

proba/main.goсрез строк вместо среза чисел
package main

import (
	"fmt"

	"example.com/otchet-kafe/internal/services"
)

func main() {
	average, err := services.CalculateAverage([]string{"5", "4"})
	fmt.Println(average, err)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:10:44: cannot use []string{…} (value of type []string) as []int value in argument to services.CalculateAverage

Не собралосьСообщение указывает на строку вызова — proba/main.go, строка 10 — и называет оба типа: передан []string, ожидался []int. В Python ошибка возникла внутри функции, в строке с sum, и место неверного вызова из неё видно не было.

proba/main.goсложение двух сумм
package main

import "fmt"

func addKopeks(a int, b int) int {
	return a + b
}

func main() {
	fmt.Println(addKopeks(150, 200))
	fmt.Println(addKopeks("150", "200"))
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:11:24: cannot use "150" (untyped string constant) as int value in argument to addKopeks
proba/main.go:11:31: cannot use "200" (untyped string constant) as int value in argument to addKopeks

Не собралось, ни одна строка не выполненаОшибочна только строка 11, но не выполнилась и строка 10 с верным вызовом: программа с ошибкой типа не собирается целиком. В Python этот вызов тихо вернул строку "150200" вместо суммы 350. В Go такая подмена невозможна: строку из CSV-файла нужно явно перевести в число вызовом strconv.Atoi, как в LoadOrders.

Из четырёх опытов видно, как Go обращается с типом в подписи. Компилятор сверяет каждый вызов с подписью до запуска и не собирает программу, пока есть хоть одно несовпадение. Ошибка указывает на строку вызова и называет оба типа — переданный и ожидаемый.

Компилятор проверяет и количество результатов. Если забыть, что CalculateAverage возвращает два значения, и принять одно, программа тоже не соберётся:

proba/main.goпринят один результат из двух
package main

import (
	"fmt"

	"example.com/otchet-kafe/internal/services"
)

func main() {
	average := services.CalculateAverage([]int{5, 4, 5, 3, 5})
	fmt.Println(average)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:10:13: assignment mismatch: 1 variable but services.CalculateAverage returns 2 values

Сообщение читается так: «несовпадение при присваивании: одна переменная, а функция возвращает два значения». Ошибку из результата нельзя случайно потерять: её нужно принять в переменную. Если ошибка точно не нужна, её место явно занимают знаком подчёркивания: average, _ := ….

Ассоциация: турникет в метро

В жизни

Турникет сверяет билет на входе. С неподходящим билетом человек на станцию не проходит и в поезд не попадает.

В коде

Компилятор сверяет каждый вызов с подписью при сборке. Программа с неверным типом аргумента не собирается и до запуска не доходит.

Где не совпадаетТурникет пропускает людей по одному, а компилятор проверяет всю программу целиком: одна неверная строка останавливает сборку всего пакета, даже верные строки не выполняются.

§6Подсказка редактора

Компилятор проверяет типы при сборке. Редактор использует те же типы раньше — пока код читают и пишут. В VS Code этим занимается расширение Go: оно запускает программу gopls (произносится «гоу-плиз»; ls в имени — от language server) — языковой сервер Go. Языковой сервер — программа, которая постоянно разбирает код проекта и отвечает редактору: какой тип у имени под курсором, где объявлена функция, какие ошибки в файле. Установка расширения и gopls показана по шагам в главе 4.3; если расширение уже стоит, опыт можно повторить сразу.

Подсказку вызывают наведением указателя на имя или клавишами: курсор на имя, затем CtrlCmd + K и CtrlCmd + I. Откройте main.go, найдите строку 17 — revenue, err := services.RevenueByWaiter(orders, 0) — и наведите указатель на RevenueByWaiter.

Подсказка VS Code на вызове services.RevenueByWaiter: func services.RevenueByWaiter(orders []domain.Order, top int) ([]services.WaiterRevenue, error)
В подсказке — подпись функции из пакета services: параметры orders []domain.Order и top int, результаты []services.WaiterRevenue и error. Из места вызова видно, что передать и что вернётся. Нижняя строка — ссылка на общий сайт документации pkg.go.dev; проект курса там не опубликован, поэтому по ней ничего не откроется. Во втором архиве комментариев документации нет, поэтому между подписью и ссылкой текста нет.

Наведите указатель на переменную revenue слева от :=. Тип у неё не записан, но он есть: компилятор вывел его из результата функции, и gopls показывает его так же, как записанный.

Подсказка VS Code на переменной revenue: var revenue []services.WaiterRevenue
Подсказка var revenue []services.WaiterRevenue: переменная — срез значений типа WaiterRevenue из пакета services. Так проверяют тип любой переменной, объявленной через :=.

§7Автодополнение

Подсказка показывает типы в готовом коде. Те же типы редактор использует, пока код набирается, — в автодополнении.

Автодополнение — список, который редактор показывает при наборе: имена переменных, функций, полей и методов, подходящих в этом месте. После точки в нём перечислены поля и методы значения слева от точки. Составить такой список редактор может, только если знает тип значения, а в Go тип известен всегда. Откройте internal/services/orders.go, поставьте курсор в конец строки func SortOrdersInPlace(orders []domain.Order) {, нажмите Enter и наберите orders[0]. — первый элемент среза и точку. Если список не появился сам, вызовите его клавишами Ctrl + Пробел. На macOS это сочетание часто занято переключением раскладки; тогда список вызывают командой Trigger Suggest из палитры команд (CtrlCmd + Shift + P). После опыта отмените набранное: CtrlCmd + Z.

VS Code: после orders[0]. редактор предлагает поля Kopeks и Waiter, рядом их тип int и string
Элемент среза — domain.Order, и в списке его поля Kopeks и Waiter с типами int и string. Слева — панель подробностей выбранного пункта: тип поля int. Служебных имён, как у объектов Python, в списке нет: у структуры Go есть только объявленные поля и методы.

Что дают типы в подписи при работе с кодом: итог §4–§7

  • Вызов без чтения тела функции. Подсказка показывает, какие значения передавать и что вернётся.
  • Имена полей из списка. Автодополнение предлагает поля структуры, и order.Kopeks выбирается из списка; набирать имя по памяти не нужно.
  • Проверка до запуска. Компилятор сверяет каждый вызов с подписью и не собирает программу с неверным типом; gopls показывает ту же ошибку в редакторе ещё до сборки.

Как увидеть ошибки в редакторе и в терминале, по шагам показано в главе 4.3. Перед этим, в главе 4.2, разобрано, как записать любой тип Go — от int до «значение или ничего». В главе 4.4 собраны ошибки, которые компилятор находит в каждом из этих типов.

§8Частые вопросы

Обычно думаютРаз у переменной, объявленной через :=, тип не записан, у неё нет типа, как в Python.
На самом делеТип есть всегда: компилятор выводит его из значения справа и запоминает. total := 0 — переменная типа int, положить в неё строку нельзя.
Обычно думаютЕсли тип записан, Go сам переведёт значение в этот тип.
На самом делеСам Go переводит только числа, записанные прямо в коде, и только без потерь: 4.0 в int можно, 5.5 — нельзя. Строку в число переводят явно: strconv.Atoi(row["kopeks"]), целое в дробное — float64(sum).
Обычно думаютЕсли программа собралась, ошибок типов в ней не будет и при работе.
На самом делеКомпилятор проверяет то, что видно в тексте. Деление на ноль, выход за границу среза, обращение через пустой указатель случаются при работе. Эти случаи разобраны в главе 4.3.

§9Проверьте себя

Где пишется тип у параметра, у результата и у переменной?

У параметра — после имени через пробел: text string. У результата — после закрывающей скобки параметров: ) string, а если результатов несколько, — списком в скобках: ) (float64, error). У переменной — после имени в объявлении var: var names []string; в записи := тип выводится из значения.

Прочитайте подпись: func IsBig(kopeks, limit int) bool.

Функция принимает два целых числа — сумму и порог; тип int записан один раз и относится к обоим параметрам. Возвращает true или false. Значения по умолчанию у порога нет: передавать нужно оба аргумента.

Прочитайте подпись: func SaveReport(path string, lines []string) (int, error).

Функция принимает путь строкой и срез строк. Возвращает целое число и ошибку. Что это за число — количество записанных строк, — из подписи не видно: такие сведения пишут в комментарии документации.

Что будет при сборке вызова CalculateAverage([]int{5.5, 4.0})?

Программа не соберётся: 5.5 нельзя положить в срез []int без потери дробной части. Компилятор сообщит cannot use 5.5 (untyped float constant) as int value.

Что означает (s *Shift) перед именем метода?

Это получатель: метод вызывают у указателя на Shift, внутри метода объект называется s. Через указатель метод меняет ту самую смену, у которой его вызвали.

Почему RevenueByWaiter(orders) без второго аргумента не собирается, хотя в Python так было можно?

У параметров Go нет значений по умолчанию. Подпись требует два аргумента — orders и top, — и вызов с одним компилятор отклоняет. Значение «все официанты» передают явно: RevenueByWaiter(orders, 0).

§10Шпаргалка по главе

Параметр text string a, b int — общий тип
Результат ) string ) (float64, error)
Переменная и поле var names []string total := 0 — тип int выведен
Метод func (s *Shift) Add(order Order) получатель — с типом
При сборке несовпадение — не собирается %T — напечатать тип
В редакторе подсказка: Ctrl+K, Ctrl+I автодополнение: Ctrl+Пробел

Итог главы

В Go тип записывается у каждого параметра и результата, а у переменной выводится из значения, если не записан явно. По подписям можно составить вызов, не открывая пакет. Компилятор сверяет вызовы с подписями до запуска и не собирает программу, пока есть несовпадение; сообщение называет строку вызова и оба типа. Редактор через gopls показывает те же типы в подсказке и в автодополнении.

Откуда взяты факты
  1. Объявления функций, параметров и результатов — The Go Programming Language Specification, Function declarations; короткое объявление переменной и нулевые значения — разделы Short variable declarations и The zero value.
  2. Методы и получатели — спецификация, Method declarations; несколько результатов и ошибки — Effective Go, Multiple return values.
  3. Языковой сервер — документация gopls.
  4. Выводы в терминале получены запуском второго архива на Go 1.27; кадры сняты в VS Code 1.138 с расширением Go 0.56 и gopls 0.24.
Глава 4.1 · Аннотация типа · МДК.01.01 Автор: Макаров Максим Николаевич