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

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

Глава 4.4

4.4

Ошибки типов:
примеры с разбором

В главе 4.2 показано, как записать тип, в главе 4.3 — как запустить Pylance и mypy. В этой главе записи и проверка сведены вместе: двадцать файлов proba.py, в каждом есть верные и неверные строки. Под каждым файлом — настоящий ответ mypy и разбор: что означает сообщение, почему строка отклонена и как её исправить. Примеры идут в том же порядке, что типы в главе 4.2.

Категория
Разбор ошибок
Уровень
Начальный
Опирается на
Главы 4.2 и 4.3
Зачем это знать
Прочитать сообщение проверки и исправить строку

§1Как работать с примерами

Каждый пример главы — содержимое файла proba.py в корне второго архива проекта, того же, что в главе 4.3. Под файлом — ответ команды .venv/bin/mypy proba.py.venv\Scripts\mypy proba.py, полученный запуском. Номера строк в ответе считаются от первой строки файла, поэтому файл нужно копировать целиком, вместе с импортами и пустыми строками.

Порядок работы с каждым примером одинаковый:

  1. Замените содержимое proba.py кодом примера и сохраните файл.
  2. Посмотрите, какие строки подчеркнул Pylance, и наведите указатель на подчёркивание.
  3. Выполните в терминале .venv/bin/mypy proba.py.venv\Scripts\mypy proba.py и сверьте ответ с книгой.
  4. Исправьте ошибки по разбору под примером и запустите проверку снова: ответ должен смениться на Success.

В начале каждого раздела стоит ссылка на параграф главы 4.2, где объяснена запись типа. Здесь запись заново не объясняется: раздел начинается сразу с файла.

Pylance в режиме basic подчёркивает почти те же строки, что mypy, но формулирует сообщения по-своему. Где поведение двух программ различается, об этом сказано в разборе.

§2Простые типы: передано не то значение

Запись простых типов — глава 4.2, §1. Самая частая ошибка с ними — значение не того типа в вызове.

В примере две новые функции с простыми типами и вызовы функций проекта: два верных и три неверных.

proba.pyпростые типы
from app.utils.formatter import format_kopeks
from app.utils.text import normalize_name


def is_big(kopeks: int, limit: int = 100_000) -> bool:
    return kopeks >= limit


def share(part: int, total: int) -> float:
    return part / total


format_kopeks(125000)
format_kopeks("1250")
normalize_name(42)
is_big(150000, limit="сто тысяч")
share(3, 4)
Терминалmypy proba.py
proba.py:14: error: Argument 1 to "format_kopeks" has incompatible type "str"; expected "int"  [arg-type]
proba.py:15: error: Argument 1 to "normalize_name" has incompatible type "int"; expected "str"  [arg-type]
proba.py:16: error: Argument "limit" to "is_big" has incompatible type "str"; expected "int"  [arg-type]
Found 3 errors in 1 file (checked 1 source file)

Разбор по строкам:

  • Строка 13, format_kopeks(125000) — ошибки нет: передано целое число, как в аннотации kopeks: int.
  • Строка 14 — сумма передана строкой "1250". Так бывает, когда значение прочитано из файла и не переведено в число. Исправление — передать число: format_kopeks(1250), а если значение пришло строкой — format_kopeks(int(value)).
  • Строка 15 — в normalize_name передано число вместо имени.
  • Строка 16 — аргумент передан по имени, и mypy называет его по имени: Argument "limit". Для позиционных аргументов в сообщении стоит номер: Argument 1.
  • Строка 17, share(3, 4) — ошибки нет. Внутри 3 / 4 даёт 0.75, и аннотация результата -> float с этим совпадает: деление / в Python всегда возвращает дробное число.

У int и float есть особое правило: где ожидается float, можно передать int. Целое число считается частным случаем дробного. Обратное не верно:

proba.pyint вместо float и наоборот
def average_check(total: float, count: int) -> float:
    return total / count


average_check(4937, 5)
average_check(4937.5, 5)
average_check(4937.5, 5.0)
Терминалmypy proba.py
proba.py:7: error: Argument 2 to "average_check" has incompatible type "float"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

В строке 5 целое 4937 принято на место float. В строке 7 дробное 5.0 передано туда, где ждут целое количество чеков, и mypy это отклонил, хотя по значению 5.0 равно 5: проверка сравнивает типы, значения она не вычисляет.

§3None: пропущенный return и лишнее присваивание

Запись -> None — глава 4.2, §1.

С аннотацией результата проверка следит за двумя ошибками: функция обещает значение, но не во всех случаях его возвращает, и результат функции -> None пытаются использовать.

proba.pyпропущенный return и результат -> None
from app.ui.console import print_report


def label(kopeks: int) -> str:
    if kopeks > 0:
        return "есть выручка"


result = print_report(["Смена"])
Терминалmypy proba.py
proba.py:4: error: Missing return statement  [return]
proba.py:9: error: "print_report" does not return a value (it only ever returns None)  [func-returns-value]
Found 2 errors in 1 file (checked 1 source file)
  • Строка 4 — Missing return statement, «нет инструкции return». Функция обещает строку, но при kopeks, равном нулю, условие не выполнится, и функция вернёт None. Исправление — вернуть строку и во втором случае: добавить в конце return "выручки нет".
  • Строка 9 — результат print_report сохраняется в переменную, но функция всегда возвращает None: «не возвращает значение». Исправление — вызвать функцию без присваивания: print_report(["Смена"]).

Вторую ошибку Pylance в режиме basic не показывает; первую подчёркивает с правилом reportReturnType.

§4Списки: не тот элемент

Запись списков — глава 4.2, §2.

Аннотация списка проверяет не только присваивание, но и всё, что со списком делают потом: добавление элементов, чтение элемента в переменную.

proba.pyсписки
from app.domain.order import Order

grades: list[int] = [5, 4, 5]
names: list[str] = ["Иванов Пётр", "Петрова Анна"]
orders: list[Order] = [Order("Иванов Пётр", 125000)]
groups: list[list[int]] = [[5, 4], [3, 5, 5]]

grades.append("5")
names.append(None)
orders.append({"waiter": "Сидоров Илья", "kopeks": 77200})
first: str = grades[0]
Терминалmypy proba.py
proba.py:8: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"  [arg-type]
proba.py:9: error: Argument 1 to "append" of "list" has incompatible type "None"; expected "str"  [arg-type]
proba.py:10: error: Argument 1 to "append" of "list" has incompatible type "dict[str, object]"; expected "Order"  [arg-type]
proba.py:11: error: Incompatible types in assignment (expression has type "int", variable has type "str")  [assignment]
Found 4 errors in 1 file (checked 1 source file)
  • Строки 3–6 — четыре верных объявления, ошибок нет.
  • Строка 8 — в список оценок добавлена строка "5". Сообщение Argument 1 to "append" of "list" — «первый аргумент метода append у списка»: проверка подставила тип элементов int в метод append и сверила с ним аргумент.
  • Строка 9 — в список имён добавлено None. Список, где могут быть пропуски, записывают как list[str | None] — об этом в §9.
  • Строка 10 — вместо объекта Order добавлен словарь с теми же полями. Для проверки это разные типы: у словаря нет полей .waiter и .kopeks, и следующая строка order.kopeks упала бы при запуске. Исправление — orders.append(Order("Сидоров Илья", 77200)).
  • Строка 11 — элемент списка list[int] — целое число, а переменная объявлена строкой. Сообщение Incompatible types in assignment читается как «несовместимые типы при присваивании»: выражение справа имеет тип int, переменная — str.

§5Словари: не тот ключ, не то значение

Запись словарей — глава 4.2, §2. У словаря проверяются и ключи, и значения.

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

proba.pyсловари
revenue: dict[str, int] = {"Иванов Пётр": 168500}
row: dict[str, str] = {"waiter": "Сидоров Илья", "kopeks": "77200"}
journal: dict[str, list[int]] = {"Иванова": [5, 4], "Петров": [3]}

revenue["Петрова Анна"] = "2480,50"
revenue[1] = 100
journal["Сидоров"] = 5
kopeks: int = row["kopeks"]
for waiter, total in revenue.items():
    reveal_type(total)
Терминалmypy proba.py
proba.py:5: error: Incompatible types in assignment (expression has type "str", target has type "int")  [assignment]
proba.py:6: error: Invalid index type "int" for "dict[str, int]"; expected type "str"  [index]
proba.py:7: error: Incompatible types in assignment (expression has type "int", target has type "list[int]")  [assignment]
proba.py:8: error: Incompatible types in assignment (expression has type "str", variable has type "int")  [assignment]
proba.py:10: note: Revealed type is "builtins.int"
Found 4 errors in 1 file (checked 1 source file)
  • Строка 5 — значение выручки записано строкой с запятой, как его печатают в отчёте. В словаре хранятся копейки целым числом: revenue["Петрова Анна"] = 248050.
  • Строка 6 — ключ не того типа. Код index и слова Invalid index type означают «неверный тип индекса»: всё, что стоит в квадратных скобках после словаря, сверяется с типом ключей.
  • Строка 7 — в журнал оценок вместо списка записано одно число. Нужно journal["Сидоров"] = [5].
  • Строка 8 — в строке CSV все значения — строки, и row["kopeks"] — тоже строка. Чтобы получить число, его преобразуют: kopeks: int = int(row["kopeks"]). Так сделано в load_orders.
  • Строка 10 — note в ответ на reveal_type. При переборе revenue.items() проверка знает тип и ключа, и значения: total — int.

§6Кортежи: не та длина, не тот порядок

Запись кортежей — глава 4.2, §2. У кортежа tuple[int, int] проверяются длина и тип каждого места.

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

proba.pyкортежи
from app.services.calculator import grade_bounds

low, high = grade_bounds([5, 3, 4])
reveal_type(low)

pair: tuple[int, int] = (3, 5, 4)
best: tuple[str, int] = ("Петрова Анна", 248050)
wrong: tuple[str, int] = (248050, "Петрова Анна")
week: tuple[int, ...] = (5, 4, 5, 3)
empty: tuple[int, ...] = ()
Терминалmypy proba.py
proba.py:4: note: Revealed type is "builtins.int"
proba.py:6: error: Incompatible types in assignment (expression has type "tuple[int, int, int]", variable has type "tuple[int, int]")  [assignment]
proba.py:8: error: Incompatible types in assignment (expression has type "tuple[int, str]", variable has type "tuple[str, int]")  [assignment]
Found 2 errors in 1 file (checked 1 source file)
  • Строка 3–4 — результат grade_bounds разобран на две переменные, и каждая получила тип своего места: low — int.
  • Строка 6 — три элемента там, где обещано два. Проверка сравнивает длину: tuple[int, int, int] не совпадает с tuple[int, int].
  • Строка 8 — элементы переставлены. Длина совпадает, но на первом месте ждут строку, а стоит число.
  • Строки 9–10 — с многоточием подходит кортеж любой длины, в том числе пустой.

Порядок элементов в паре проверка видит только по типам. В tuple[int, int] у grade_bounds что идёт первым — минимум или максимум, — аннотация не сообщает: это пишут в описании функции.

§7Множества: не тот элемент

Запись множеств — глава 4.2, §2.

proba.pyмножество
waiters: set[str] = {"Иванов Пётр", "Петрова Анна"}
waiters.add("Сидоров Илья")
waiters.add(125000)
Терминалmypy proba.py
proba.py:3: error: Argument 1 to "add" of "set" has incompatible type "int"; expected "str"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Сообщение такое же, как у append в §4: метод множества add принимает только элементы объявленного типа.

Старая запись коллекций — List[int], Dict[str, int] с импортом из typing — проверяется так же и даёт те же сообщения.

§8Классы: опечатка в поле, словарь вместо объекта

Запись классов — глава 4.2, §3.

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

proba.pyклассы в аннотациях
from datetime import date
from pathlib import Path

from app.domain.order import Order
from app.domain.shift import Shift
from app.services.orders import load_orders


def describe(order: Order) -> str:
    return f"{order.waiter}: {order.kopek}"


shift = Shift(date(2026, 9, 21))
shift.add(Order("Иванов Пётр", 125000))
shift.add({"waiter": "Петрова Анна", "kopeks": 98050})
Shift("21.09.2026")
load_orders(Path("data/smena.csv"))
load_orders("data/smena.csv")
Терминалmypy proba.py
proba.py:10: error: "Order" has no attribute "kopek"; maybe "kopeks"?  [attr-defined]
proba.py:15: error: Argument 1 to "add" of "Shift" has incompatible type "dict[str, object]"; expected "Order"  [arg-type]
proba.py:16: error: Argument 1 to "Shift" has incompatible type "str"; expected "date"  [arg-type]
proba.py:18: error: Argument 1 to "load_orders" has incompatible type "str"; expected "Path"  [arg-type]
Found 4 errors in 1 file (checked 1 source file)
  • Строка 10 — опечатка в имени поля: kopek вместо kopeks. mypy не только сообщает, что такого атрибута нет, но и предлагает похожее имя: maybe "kopeks"?. Без аннотации order: Order эта опечатка обнаружилась бы только при запуске.
  • Строка 15 — словарь вместо заказа, как в §4 со списком.
  • Строка 16 — дата передана строкой. Argument 1 to "Shift" — первый аргумент при создании объекта, то есть параметр day метода __init__. Исправление — Shift(date(2026, 9, 21)).
  • Строка 18 — путь передан строкой. При запуске этот вызов сработает: функция open внутри read_rows принимает и строку, и Path. Проверка же сверяет с аннотацией, а в ней записан Path. Есть два исправления: передавать Path("data/smena.csv"), как в main.py, или, если функция должна принимать оба вида, расширить аннотацию до path: str | Path.

§9Значение или None: поле без проверки

Запись Order | None — глава 4.2, §4. С такой аннотацией проверка не даёт обратиться к полям результата, пока не проверено, что он не None. Разница между вызовом без такой проверки и с ней — в блоке ниже.

Order | Noneпереключите вариант

proba.pyсразу берём поле
from pathlib import Path

from app.domain.order import Order
from app.services.orders import load_orders


def find_order(orders: list[Order], waiter: str) -> Order | None:
    for order in orders:
        if order.waiter == waiter:
            return order
    return None


orders = load_orders(Path("data/smena.csv"))
found = find_order(orders, "Петрова Анна")
print(found.kopeks)
Терминалmypy proba.py
proba.py:16: error: Item "None" of "Order | None" has no attribute "kopeks"  [union-attr]
Found 1 error in 1 file (checked 1 source file)

Ошибка найдена до запускаСообщение читается так: у варианта None из типа Order | None нет атрибута kopeks. Сейчас Петрова Анна в файле есть, и при запуске всё сработает. Ошибка AttributeError: 'NoneType' object has no attribute 'kopeks' возникнет в тот день, когда имени в файле не окажется. Pylance подчёркивает эту строку с правилом reportOptionalMemberAccess.

proba.pyсначала проверка
from pathlib import Path

from app.domain.order import Order
from app.services.orders import load_orders


def find_order(orders: list[Order], waiter: str) -> Order | None:
    for order in orders:
        if order.waiter == waiter:
            return order
    return None


orders = load_orders(Path("data/smena.csv"))
found = find_order(orders, "Петрова Анна")
if found is not None:
    reveal_type(found)
    print(found.kopeks)
Терминалmypy proba.py
proba.py:17: note: Revealed type is "app.domain.order.Order"
Success: no issues found in 1 source file

Ошибок нетВнутри if found is not None тип сужен до Order — это видно по строке Revealed type. Уточнение типа по условию называют сужением: проверка понимает, что в этой ветке None быть не может.

Значение «или None» возвращают не только собственные функции. Метод словаря get без второго аргумента возвращает None, если ключа нет:

proba.pydict.get
revenue: dict[str, int] = {"Иванов Пётр": 168500}

bonus = revenue.get("Сидоров Илья")
reveal_type(bonus)
print(bonus + 100)
print(revenue.get("Сидоров Илья", 0) + 100)
Терминалmypy proba.py
proba.py:4: note: Revealed type is "Union[builtins.int, None]"
proba.py:5: error: Unsupported operand types for + ("None" and "int")  [operator]
proba.py:5: note: Left operand is of type "int | None"
Found 1 error in 1 file (checked 1 source file)

В строке 4 тип bonus — Union[builtins.int, None]: так mypy печатает int | None. В строке 5 сложение отклонено: если ключа нет, получится None + 100, и при запуске будет TypeError. Вторая строка с ошибкой, note, поясняет, какой тип у левой части. Строка 6 проходит: со вторым аргументом get возвращает 0 вместо None, и результат всегда int.

Третий случай — необязательный параметр day: str | None = None. Здесь видно, как проверка следит за типом по ходу функции:

proba.pyнеобязательный параметр
from app.utils.clock import today_label
from app.utils.formatter import format_title


def shift_title(day: str | None = None) -> str:
    if day is None:
        day = today_label()
    reveal_type(day)
    return format_title(f" Смена {day} ")


shift_title()
shift_title("21.09.2026")
Терминалmypy proba.py
proba.py:8: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file

После условия в строках 6–7 day — всегда строка: либо переданная, либо дата из today_label. Проверка это видит, и в строке 8 тип уже str, без None.

Сужение работает и через ранний выход. Если в начале функции вернуть результат для случая None, дальше по телу значение считается заказом:

proba.pyранний выход
from app.domain.order import Order


def kopeks_or_zero(order: Order | None) -> int:
    if order is None:
        return 0
    return order.kopeks
Терминалmypy proba.py
Success: no issues found in 1 source file

§10Объединения: работа без проверки варианта

Запись объединений — глава 4.2, §5. В первом примере вариант проверен через isinstance, во втором — нет.

proba.pyсумма пришла числом или строкой
def to_kopeks(value: int | str) -> int:
    if isinstance(value, str):
        reveal_type(value)
        return int(value)
    reveal_type(value)
    return value


to_kopeks(125)
to_kopeks("125")
to_kopeks(1.5)
Терминалmypy proba.py
proba.py:3: note: Revealed type is "builtins.str"
proba.py:5: note: Revealed type is "builtins.int"
proba.py:11: error: Argument 1 to "to_kopeks" has incompatible type "float"; expected "int | str"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Внутри условия isinstance(value, str) тип сужен до str (строка 3), после условия — до int (строка 5): строки туда уже не доходят, остаётся второй вариант. Вызовы с числом и со строкой прошли, с дробным числом — нет: float в перечне вариантов не значится.

Без проверки варианта проверка отклонит код, даже если для одного из вариантов он верен:

proba.pyобъединение без проверки варианта
def to_kopeks(value: int | str) -> int:
    return value * 100
Терминалmypy proba.py
proba.py:2: error: Incompatible return value type (got "int | str", expected "int")  [return-value]
Found 1 error in 1 file (checked 1 source file)

Для строки выражение "125" * 100 даёт строку из ста повторов "125". Сообщение Incompatible return value type (got "int | str", expected "int") — «тип возвращаемого значения несовместим: получено int | str, ожидалось int». Объединения используют там, где данные действительно приходят в разном виде. Когда вариант один, объединение только добавляет проверок в вызывающий код.

§11Генераторы и функции-аргументы: не та функция

Запись Iterator и Callable — глава 4.2, §6. Первый пример показывает, что проверка знает тип значений, которые отдаёт генератор.

proba.pyгенератор чисел
from collections.abc import Iterator


def countdown(start: int) -> Iterator[int]:
    while start > 0:
        yield start
        start -= 1


for number in countdown(3):
    reveal_type(number)
Терминалmypy proba.py
proba.py:11: note: Revealed type is "builtins.int"
Success: no issues found in 1 source file

Переменная цикла получила тип элемента — int.

Во втором примере параметр fix записан как Callable[[str], str] — «функция, которая принимает строку и возвращает строку». В него по очереди передают три функции.

proba.pyфункция как аргумент
from collections.abc import Callable

from app.utils.text import normalize_name


def fix_names(names: list[str], fix: Callable[[str], str]) -> list[str]:
    return [fix(name) for name in names]


fix_names(["иванов пётр"], normalize_name)
fix_names(["иванов пётр"], str.upper)
fix_names(["иванов пётр"], len)
Терминалmypy proba.py
proba.py:12: error: Argument 2 to "fix_names" has incompatible type "Callable[[Sized], int]"; expected "Callable[[str], str]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Строки 10 и 11 проходят: normalize_name и метод str.upper принимают строку и возвращают строку. В строке 12 передана len — она возвращает число и принимает любой объект с длиной, в сообщении он назван Sized. Проверка сравнила описание переданной функции с ожидаемым и нашла несовпадение в результате.

§12Псевдонимы: какой тип видит проверка

Запись псевдонимов — глава 4.2, §7. Пример показывает, что проверка видит за псевдонимом исходный тип.

proba.pyпсевдонимы для сумм и выручки
type Kopeks = int
type Revenue = dict[str, Kopeks]


def total(revenue: Revenue) -> Kopeks:
    return sum(revenue.values())


reveal_type(total({"Иванов Пётр": 100}))
Терминалmypy proba.py
proba.py:9: note: Revealed type is "builtins.int"
Success: no issues found in 1 source file

Для проверки Kopeks и int — один и тот же тип: результат показан как builtins.int. Поэтому отличить копейки от рублей проверка не сможет: передать в total словарь с рублями — не ошибка типа.

§13Any: ошибка, которую проверка пропускает

Запись Any — глава 4.2, §8. Пример показывает, что для значения типа Any проверка ничего не сверяет. Тот же Any Pylance показывал в главе 4.1 у параметров без аннотаций.

proba.pyAny выключает проверку
from typing import Any


def parse(value: Any) -> int:
    return value.to_kopeks()


parse("125")
Терминалmypy proba.py
Success: no issues found in 1 source file

У строки нет метода to_kopeks, и при запуске будет AttributeError, но проверка ошибок не нашла: для Any она ничего не сверяет. Any ставят там, где тип действительно неизвестен заранее, например у данных из внешней библиотеки без аннотаций. Убирать им замечание проверки нельзя: замечание исчезнет вместе с самой проверкой. В работах курса Any не используется.

§14Переменные: пустая коллекция без аннотации

Где переменным нужна аннотация — глава 4.2, §9. Проверка сама сообщает о таком месте:

proba.pyпустые коллекции без аннотации
totals = {}
names = []
Терминалmypy proba.py
proba.py:1: error: Need type annotation for "totals" (hint: "totals: dict[<type>, <type>] = ...")  [var-annotated]
proba.py:2: error: Need type annotation for "names" (hint: "names: list[<type>] = ...")  [var-annotated]
Found 2 errors in 1 file (checked 1 source file)

Need type annotation — «нужна аннотация типа». В скобках mypy подсказывает форму записи: вместо <type> подставляют типы, например totals: dict[str, int] = {}, как в revenue_by_waiter. Если в той же функции ниже в коллекцию что-то добавляется, mypy иногда выводит тип по этому добавлению; аннотация у пустой коллекции всё равно делает код понятнее. Pylance в режиме basic об этом случае не сообщает.

§15Все сообщения главы

В двадцати примерах встретилось десять видов сообщений mypy. В таблице они собраны вместе: по коду в квадратных скобках сообщение легко найти в выводе.

КодНачало сообщенияЧто произошлоГде разобрано
arg-typeArgument 1 to "…" has incompatible typeв вызов передано значение не того типа§2, §4, §7, §8, §10, §11
assignmentIncompatible types in assignmentсправа от = значение не того типа, что у переменной или элемента§4, §5, §6
returnMissing return statementфункция обещает значение, но возвращает его не во всех случаях§3
return-valueIncompatible return value typeпосле return значение не того типа, что записан после стрелки§10
func-returns-value"…" does not return a valueиспользуется результат функции с -> None§3
indexInvalid index typeв квадратных скобках ключ не того типа§5
attr-defined"Order" has no attribute "…"у класса нет такого поля или метода§8
union-attrItem "None" of "… | None" has no attributeобращение к полю значения, которое может быть None§9
operatorUnsupported operand types for +операция над значением, которое может быть None§9
var-annotatedNeed type annotation for "…"тип пустой коллекции нельзя вывести из значения§14

Строки note: Revealed type is … ошибками не являются: это ответы на reveal_type из главы 4.3, §9.

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

mypy ответил Argument 1 to "append" of "list" has incompatible type "str"; expected "int". Что произошло?

В список, объявленный как list[int], методом append добавляют строку. Нужно добавить целое число.

Чем сообщение с кодом index отличается от сообщения с кодом assignment у словаря?

index — неверный тип ключа, того, что стоит в квадратных скобках. assignment — неверный тип значения справа от знака =.

Какие две ошибки проверка находит у кортежа tuple[str, int]?

Не ту длину — например, три элемента вместо двух — и не тот порядок: число на первом месте, строка на втором.

Почему average_check(4937, 5) проходит проверку при total: float, а average_check(4937.5, 5.0) — нет?

Где ожидается float, разрешено передать int. Второй параметр — count: int, и дробное 5.0 туда не подходит, хотя по значению равно пяти.

Функция возвращает Order | None. Почему проверка не даёт сразу обратиться к result.kopeks?

В результате может оказаться None, у которого нет поля kopeks. Сначала нужна проверка if result is not None: внутри неё тип сужается до Order.

Что вернёт revenue.get("Сидоров Илья") для revenue: dict[str, int] и как получить всегда число?

Тип результата — int | None: если ключа нет, вернётся None. Со значением по умолчанию — revenue.get("Сидоров Илья" — результат всегда int.

Что означает сообщение Missing return statement?

Функция обещает вернуть значение, но в одной из веток return со значением нет, и там вернётся None.

Почему Any нельзя ставить, чтобы убрать замечание проверки?

С Any проверка перестаёт сверять значение: вызов несуществующего метода проходит проверку и завершается ошибкой только при запуске.

Почему у totals = {} нужна аннотация, а у count = 0 — нет?

По нулю проверка выводит тип int. По пустому словарю нельзя узнать тип ключей и значений, и mypy сообщает Need type annotation.

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

Не тот тип в вызове [arg-type] Argument 1 to "f" has incompatible type "str"; expected "int"

Пришло str, ждали int.

Не тот тип при присваивании [assignment] [index] — не тот ключ словаря

Кортеж: сверяются длина и порядок.

Результат функции [return] — нет return в ветке [return-value] — вернули не тот тип [func-returns-value] — результат у -> None
Значение или None [union-attr] — поле без проверки [operator] — None + 100 if x is not None: — исправление
Классы и переменные [attr-defined] — опечатка в поле [var-annotated] — totals = {}
Не ошибки note: Revealed type is … int на месте float Any — проверка молчит

Итог главы

Почти все ошибки в примерах — «передан не тот тип»: mypy называет в сообщении, что пришло и что ожидалось, а код в квадратных скобках указывает место — вызов, присваивание или return. У коллекций проверяется содержимое: элементы списка и множества, ключи и значения словаря, длина и порядок кортежа. Когда тип — класс, проверка находит опечатки в именах полей. С X | None она требует сначала убедиться, что значение не None, и после условия сужает тип. За псевдонимом проверка видит исходный тип, а для Any не сверяет ничего. В главе 4.5 те же сообщения появятся в собственном коде: аннотации расставляются в проекте, где их нет.

Откуда взяты факты
  1. Правило «int вместо float» — спецификация типов Python.
  2. Коды ошибок mypy — документация mypy, Error codes enabled by default.
  3. Сужение типов и reveal_type — документация mypy, Type narrowing.
  4. Все выводы получены запуском примеров на втором архиве проекта, mypy 1.15; сверка книги с запуском — shots/verify_examples.py.
4.4

Ошибки типов:
примеры с разбором

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

Категория
Разбор ошибок
Уровень
Начальный
Опирается на
Главы 4.2 и 4.3
Зачем это знать
Прочитать сообщение компилятора и исправить строку

§1Как работать с примерами

Каждый пример главы — содержимое файла proba/main.go во втором архиве проекта, том же, что в главе 4.3. Под файлом — ответ команды go run ./proba, полученный запуском. Номера строк в ответе считаются от первой строки файла, поэтому файл нужно копировать целиком, вместе с package и импортами.

Порядок работы с каждым примером одинаковый:

  1. Замените содержимое proba/main.go кодом примера и сохраните файл.
  2. Посмотрите, какие строки подчеркнул gopls, и наведите указатель на подчёркивание.
  3. Выполните в терминале go run ./proba и сверьте ответ с книгой.
  4. Исправьте ошибки по разбору под примером и запустите снова: программа должна собраться и напечатать результат.

Команда go run сначала собирает программу. Если сборка не удалась, она печатает ошибки компилятора — строки вида proba/main.go:15:33: … под заголовком # example.com/otchet-kafe/proba — и ничего не запускает. Если сборка прошла, программа выполняется, и в ответе её вывод. Так в одном ответе видны оба рода ошибок: те, что находит компилятор, и те, что случаются при работе.

В начале каждого раздела стоит ссылка на параграф главы 4.2, где объяснена запись типа. Здесь запись заново не объясняется: раздел начинается сразу с файла. gopls показывает в редакторе те же сообщения, что и компилятор, с тем же текстом.

§2Простые типы: передано не то значение

Запись простых типов — глава 4.2, §1. Самая частая ошибка с ними — значение не того типа в вызове. В примере функция isBig и вызовы функций проекта: один верный и три неверных.

proba/main.goпростые типы
package main

import (
	"fmt"

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

func isBig(kopeks, limit int) bool {
	return kopeks >= limit
}

func main() {
	fmt.Println(utils.FormatKopeks(125000))
	fmt.Println(utils.FormatKopeks("1250"))
	fmt.Println(utils.NormalizeName(42))
	fmt.Println(isBig(150000, "сто тысяч"))
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:15:33: cannot use "1250" (untyped string constant) as int value in argument to utils.FormatKopeks
proba/main.go:16:34: cannot use 42 (untyped int constant) as string value in argument to utils.NormalizeName
proba/main.go:17:28: cannot use "сто тысяч" (untyped string constant) as int value in argument to isBig

Разбор по строкам:

  • Строка 14, FormatKopeks(125000) — ошибки нет: передано целое число, как в подписи kopeks int.
  • Строка 15 — сумма передана строкой "1250". Так бывает, когда значение прочитано из файла и не переведено в число. Исправление — передать число: FormatKopeks(1250), а если значение пришло строкой — сначала перевести его: kopeks, err := strconv.Atoi(value).
  • Строка 16 — в NormalizeName передано число вместо имени.
  • Строка 17 — порог передан словами. Сообщение называет функцию isBig, но не номер аргумента: место показывают строка и столбец — 17:28 указывает ровно на "сто тысяч".

Слова untyped string constant в сообщениях означают «строка, записанная прямо в коде». У такой константы ещё нет окончательного типа, и компилятор пробует перевести её в тип параметра. Число 42 в строку не переводится, строка в число — тоже, отсюда ошибка.

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

proba/main.goint и float64
package main

import "fmt"

func averageCheck(total float64, count int) float64 {
	return total / float64(count)
}

func main() {
	fmt.Println(averageCheck(4937, 5))
	fmt.Println(averageCheck(4937.5, 5.0))
	sum := 4937
	checks := 5
	fmt.Println(averageCheck(sum, checks))
	fmt.Println(averageCheck(4937.5, 5.5))
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:14:27: cannot use sum (variable of type int) as float64 value in argument to averageCheck
proba/main.go:15:35: cannot use 5.5 (untyped float constant) as int value in argument to averageCheck (truncated)

Строки 10 и 11 собираются: константа 4937 переводится в float64 без потерь, а 5.0 — в int, потому что дробной части у неё нет. В строке 14 переданы переменные. У sum уже есть тип int, и компилятор не переводит его в float64 сам: variable of type int. Исправление — явный перевод: averageCheck(float64(sum), checks). В строке 15 5.5 нельзя сделать целым без потери — (truncated).

Та же строгость действует в арифметике: значения разных типов нельзя складывать и умножать.

proba/main.goчисла разных типов в выражении
package main

import "fmt"

func main() {
	sum := 4937
	checks := 5
	share := 0.15
	fmt.Println(sum / checks)
	fmt.Println(sum * share)
	fmt.Println(float64(sum) * share)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:10:14: invalid operation: sum * share (mismatched types int and float64)

Строка 9 собирается: int делится на int. Если бы программа запустилась, она напечатала бы 987 — целое деление отбрасывает дробную часть. Строка 10 — mismatched types int and float64, «несовпадающие типы»: целое умножается на дробное. Строка 11 — исправленный вариант: целое явно переведено в float64. Сообщения с mismatched types начинаются словами invalid operation — «недопустимая операция», и называют саму операцию: sum * share.

§3Результат: пропущенный return и функция без результата

Запись результата — глава 4.1, §3. С результатом компилятор следит за двумя ошибками: функция обещает значение, но возвращает его не во всех случаях, и результат функции без результата пытаются использовать.

proba/main.goпропущенный return и результат PrintReport
package main

import "example.com/otchet-kafe/internal/ui"

func label(kopeks int) string {
	if kopeks > 0 {
		return "есть выручка"
	}
}

func main() {
	result := ui.PrintReport([]string{"Смена"})
	_ = result
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:9:1: missing return
proba/main.go:12:12: ui.PrintReport([]string{…}) (no value) used as value
  • Строка 9 — missing return, «нет инструкции return». Функция обещает строку, но если kopeks не больше нуля, условие не выполнится, и вернуть будет нечего. Место ошибки — закрывающая скобка функции: здесь выполнение дошло бы до конца без return. Исправление — вернуть строку и во втором случае: добавить перед скобкой return "выручки нет". В Python функция в этом случае тихо вернула бы None; в Go такое невозможно.
  • Строка 12 — результат PrintReport сохраняется в переменную, но у функции нет результата: (no value) used as value, «отсутствующее значение использовано как значение». Исправление — вызвать функцию без присваивания: ui.PrintReport([]string{"Смена"}).

§4Срезы: не тот элемент

Запись срезов — глава 4.2, §2. Тип среза проверяет не только объявление, но и всё, что со срезом делают потом: добавление элементов, чтение элемента в переменную.

proba/main.goсрезы
package main

import "example.com/otchet-kafe/internal/domain"

func main() {
	grades := []int{5, 4, 5}
	names := []string{"Иванов Пётр", "Петрова Анна"}
	var orders []domain.Order
	groups := [][]int{{5, 4}, {3, 5, 5}}
	_ = groups

	grades = append(grades, "5")
	names = append(names, nil)
	orders = append(orders, map[string]any{"waiter": "Сидоров Илья", "kopeks": 77200})
	var first string = grades[0]
	_ = first
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:12:26: cannot use "5" (untyped string constant) as int value in argument to append
proba/main.go:13:24: cannot use nil as string value in argument to append
proba/main.go:14:26: cannot use map[string]any{…} (value of type map[string]any) as domain.Order value in argument to append
proba/main.go:15:21: cannot use grades[0] (variable of type int) as string value in variable declaration
  • Строки 6–9 — четыре верных объявления, ошибок нет. Строка 10 нужна, чтобы groups считалась использованной.
  • Строка 12 — в срез оценок добавлена строка "5". Сообщение in argument to append — «в аргументе функции append»: компилятор подставил тип элементов int в append и сверил с ним аргумент.
  • Строка 13 — в срез имён добавлен nil. У строки нулевое значение — пустая строка "", а nil строкой быть не может. Если в данных бывают пропуски, их хранят пустой строкой или заводят срез указателей []*string.
  • Строка 14 — вместо структуры Order добавлен словарь с теми же ключами. Для компилятора это разные типы: у словаря нет полей .Waiter и .Kopeks. Исправление — создать заказ: order, err := domain.NewOrder("Сидоров Илья", 77200) и добавить его.
  • Строка 15 — элемент среза []int — целое число, а переменная объявлена строкой: in variable declaration, «в объявлении переменной».

§5Словари: не тот ключ, не то значение

Запись словарей — глава 4.2, §2. У словаря проверяются и ключи, и значения. В примере три словаря и четыре неверные записи в них.

proba/main.goсловари
package main

func main() {
	revenue := map[string]int{"Иванов Пётр": 168500}
	row := map[string]string{"waiter": "Сидоров Илья", "kopeks": "77200"}
	journal := map[string][]int{"Иванова": {5, 4}, "Петров": {3}}

	revenue["Петрова Анна"] = "2480,50"
	revenue[1] = 100
	journal["Сидоров"] = 5
	var kopeks int = row["kopeks"]
	_ = kopeks
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:8:39: cannot use "2480,50" (untyped string constant) as int value in assignment
proba/main.go:9:10: cannot use 1 (untyped int constant) as string value in map index
proba/main.go:10:30: cannot use 5 (untyped int constant) as []int value in assignment
proba/main.go:11:19: cannot use row["kopeks"] (map index expression of type string) as int value in variable declaration
  • Строка 8 — значение выручки записано строкой с запятой, как его печатают в отчёте. В словаре хранятся копейки целым числом: revenue["Петрова Анна"] = 248050.
  • Строка 9 — ключ не того типа: in map index, «в индексе словаря». Всё, что стоит в квадратных скобках после словаря, сверяется с типом ключей.
  • Строка 10 — в журнал оценок вместо среза записано одно число. Нужно journal["Сидоров"] = []int{5}.
  • Строка 11 — в строке CSV все значения — строки, и row["kopeks"] — тоже строка: map index expression of type string. Чтобы получить число, его преобразуют: kopeks, err := strconv.Atoi(row["kopeks"]). Так сделано в LoadOrders.

Множество в Go — словарь map[string]bool, поэтому ошибки с ним те же: ключ не того типа отклоняется сообщением in map index.

§6Несколько результатов: не то число, не тот порядок

Несколько результатов — глава 4.1, §3. Компилятор требует принять их все: столько переменных, сколько значений возвращает функция.

proba/main.goчисло переменных и результатов
package main

import (
	"fmt"

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

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

Сообщение assignment mismatch: 2 variables but services.GradeBounds returns 3 values читается как «несовпадение при присваивании: две переменные, а функция возвращает три значения». Третье значение — ошибка, и её нельзя молча потерять. Исправление — принять все результаты: low, high, err := …, а если какой-то не нужен, поставить на его место _. Во второй строке забыт результат-ошибка у CalculateAverage.

Число результатов компилятор проверяет, а их порядок — только по типам. У GradeBounds минимум и максимум одного типа int, и если перепутать их местами, программа соберётся:

proba/main.goминимум и максимум перепутаны
package main

import (
	"fmt"

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

func main() {
	high, low, err := services.GradeBounds([]int{5, 3, 4})
	if err != nil {
		return
	}
	fmt.Println("минимум:", low, "максимум:", high)
}
Терминалgo run ./proba
минимум: 5 максимум: 3

Ошибок сборки нет, а в отчёте минимум больше максимума. Компилятор не может знать, что первое значение — минимум: оба int. В Python у кортежа tuple[str, int] перестановку находила проверка, потому что типы мест различались; для tuple[int, int] не находила бы и она. Защита здесь — имена результатов в подписи (low, high int, err error), которые видны в подсказке редактора, и фраза о порядке в комментарии документации.

§7Структуры: опечатка в поле, словарь вместо структуры

Запись структур — глава 4.2, §3. Когда тип значения — структура, компилятор знает её поля и находит опечатки в именах.

proba/main.goструктура Order
package main

import (
	"fmt"

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

func main() {
	order, err := domain.NewOrder("Иванов Пётр", 125000)
	if err != nil {
		return
	}
	fmt.Println(order.Kopek)
	fmt.Println(order.Waiter.Kopeks)
	broken := domain.Order{Name: "Сидоров Илья", Kopeks: 77200}
	services.SortOrdersInPlace([]map[string]int{{"kopeks": 100}})
	_ = broken
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:15:20: order.Kopek undefined (type domain.Order has no field or method Kopek)
proba/main.go:16:27: order.Waiter.Kopeks undefined (type string has no field or method Kopeks)
proba/main.go:17:25: unknown field Name in struct literal of type domain.Order
proba/main.go:18:29: cannot use []map[string]int{…} (value of type []map[string]int) as []domain.Order value in argument to services.SortOrdersInPlace
  • Строка 15 — order.Kopek undefined (type domain.Order has no field or method Kopek): у заказа нет поля Kopek, есть Kopeks. В Python без аннотаций такая опечатка обнаружилась бы только при запуске. Сообщение называет тип, в котором искалось поле, — по нему видно, что компилятор правильно понял, что за значение слева от точки.
  • Строка 16 — поле Waiter есть, но это строка, и поля Kopeks у строки нет: type string has no field or method Kopeks.
  • Строка 17 — unknown field Name in struct literal: при создании структуры указано поле, которого в ней нет. Поле имени называется Waiter.
  • Строка 18 — вместо среза заказов передан срез словарей с теми же ключами. Типы разные, и функция его не примет.

§8Значение или ничего: nil там, где его быть не может

Три способа записать «значение или ничего» — глава 4.2, §4. Первая ошибка — попытка вернуть nil вместо структуры, по привычке к return None из Python.

proba/main.gonil вместо структуры
package main

import "example.com/otchet-kafe/internal/domain"

func findOrder(orders []domain.Order, waiter string) domain.Order {
	for _, order := range orders {
		if order.Waiter == waiter {
			return order
		}
	}
	return nil
}

func main() {
	_ = findOrder(nil, "Петрова Анна")
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:11:9: cannot use nil as domain.Order value in return statement

cannot use nil as domain.Order value in return statement: у структуры нет значения nil. Исправление зависит от выбранного способа: вернуть пустую структуру и признак — return domain.Order{}, false с подписью (domain.Order, bool) — или сделать результат указателем *domain.Order, у которого nil есть.

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

proba/main.goполе через пустой указатель
package main

import (
	"fmt"

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

func findOrderPtr(orders []domain.Order, waiter string) *domain.Order {
	for i := range orders {
		if orders[i].Waiter == waiter {
			return &orders[i]
		}
	}
	return nil
}

func main() {
	first, _ := domain.NewOrder("Иванов Пётр", 100)
	orders := []domain.Order{first}
	found := findOrderPtr(orders, "Петрова Анна")
	fmt.Println("ищем Петрову Анну")
	fmt.Println(found.Kopeks)
}
Терминалgo run ./proba
ищем Петрову Анну
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x10 pc=0x104ab5764]

goroutine 1 [running]:
main.main()
	/private/tmp/student-go/otchet-kafe-go-start/proba/main.go:23 +0x134
exit status 2

Первая строка вывода показывает, что программа собралась и запустилась. На строке 23 она остановилась: panic — аварийная остановка, invalid memory address or nil pointer dereference — «обращение по пустому указателю». Ниже — трассировка: где именно случилась паника, main.go:23. Путь к файлу в трассировке у вас будет свой, а числа вида 0x… меняются от запуска к запуску. exit status 2 — код завершения программы.

Исправление — проверить указатель перед обращением: if found != nil { fmt.Println(found.Kopeks) }. В Python проверка mypy требовала такое условие для Order | None; компилятор Go для указателя его не требует, поэтому проверку пишут по привычке, а находят пропущенную — тесты.

§9Интерфейсы: нет метода

Интерфейсы — глава 4.2, §5. Значение подходит под интерфейс, если у него есть все методы интерфейса. Компилятор это проверяет и называет пропущенный метод.

proba/main.goсмена и интерфейс Priced
package main

import (
	"fmt"
	"time"

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

type Priced interface {
	TotalKopeks() int
}

func printTotal(item Priced) {
	fmt.Println(item.TotalKopeks())
}

func main() {
	shift := domain.NewShift(time.Now())
	printTotal(shift)
	printTotal(*shift)
	printTotal(125)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:21:13: cannot use *shift (variable of struct type domain.Shift) as Priced value in argument to printTotal: domain.Shift does not implement Priced (method TotalKopeks has pointer receiver)
proba/main.go:22:13: cannot use 125 (constant of type int) as Priced value in argument to printTotal: int does not implement Priced (missing method TotalKopeks)
  • Строка 20 — ошибки нет: shift — указатель *domain.Shift, у него метод TotalKopeks есть.
  • Строка 21 — передана сама смена, без указателя: *shift разыменовывает указатель. Сообщение длинное, главное в конце: method TotalKopeks has pointer receiver, «у метода получатель-указатель». Метод объявлен как func (s *Shift) TotalKopeks(), и есть он только у *Shift, а у Shift — нет. Исправление — передавать указатель, как в строке 20.
  • Строка 22 — у числа методов нет: missing method TotalKopeks.

Сообщения об интерфейсах состоят из двух частей через двоеточие: сначала общее «нельзя использовать как Priced», затем причина — какого метода не хватает или почему он не подходит.

§10Функции-аргументы: не та функция

Тип функции — глава 4.2, §6. Параметр fix записан как func(string) string — «функция, которая принимает строку и возвращает строку». В него по очереди передают три функции.

proba/main.goфункция как аргумент
package main

import (
	"fmt"
	"strings"
	"unicode/utf8"

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

func fixNames(names []string, fix func(string) string) []string {
	fixed := make([]string, 0, len(names))
	for _, name := range names {
		fixed = append(fixed, fix(name))
	}
	return fixed
}

func main() {
	names := []string{"иванов пётр"}
	fmt.Println(fixNames(names, utils.NormalizeName))
	fmt.Println(fixNames(names, strings.ToUpper))
	fmt.Println(fixNames(names, utf8.RuneCountInString))
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:23:30: cannot use utf8.RuneCountInString (value of type func(s string) (n int)) as func(string) string value in argument to fixNames

Строки 21 и 22 собираются: NormalizeName и strings.ToUpper принимают строку и возвращают строку. В строке 23 передана utf8.RuneCountInString — она принимает строку, но возвращает число. Сообщение показывает её полный тип func(s string) (n int) рядом с ожидаемым func(string) string: имена параметров для совпадения типов не важны, важны типы и их порядок.

§11Новый тип: копейки и рубли

Новый тип и псевдоним — глава 4.2, §7. Пример показывает, чем они отличаются для компилятора.

proba/main.goKopeks, Rubles и Revenue
package main

import "fmt"

type Kopeks int
type Rubles int
type Revenue = map[string]int

func total(revenue Revenue) Kopeks {
	sum := Kopeks(0)
	for _, kopeks := range revenue {
		sum += Kopeks(kopeks)
	}
	return sum
}

func main() {
	fmt.Println(total(map[string]int{"Иванов Пётр": 100}))
	var price Kopeks = 125000
	var tip Rubles = 50
	count := 3
	fmt.Println(price + tip)
	fmt.Println(price * count)
	fmt.Println(price * 3)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:22:14: invalid operation: price + tip (mismatched types Kopeks and Rubles)
proba/main.go:23:14: invalid operation: price * count (mismatched types Kopeks and int)
  • Строка 18 собирается: Revenue — псевдоним, и map[string]int под него подходит без преобразования.
  • Строка 22 — mismatched types Kopeks and Rubles: оба типа хранят целое число, но это разные типы, и сложить копейки с рублями нельзя. Ради этого новые типы и заводят. В Python псевдоним Kopeks = int такую ошибку не находил.
  • Строка 23 — то же с обычным int: переменная count имеет тип int, и умножить на неё Kopeks без перевода нельзя. Исправление — price * Kopeks(count).
  • Строка 24 собирается: 3 — константа без типа, и компилятор переводит её в Kopeks сам.

§12any: ошибка, которую компилятор пропускает

Тип any — глава 4.2, §8. В отличие от Any в Python, со значением any компилятор не разрешает ничего, кроме передачи дальше:

proba/main.goарифметика с any
package main

import "fmt"

func parse(value any) int {
	return value + 1
}

func main() {
	fmt.Println(parse(125))
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:6:9: invalid operation: value + 1 (mismatched types any and untyped int)

mismatched types any and untyped int: к значению неизвестного типа нельзя прибавить число. Чтобы работать со значением, сначала нужно утверждение типа. Короткая форма value.(int) компилятор устраивает, но переносит проверку на время работы:

proba/main.goутверждение типа без проверки
package main

import "fmt"

func parse(value any) int {
	return value.(int) + 1
}

func main() {
	fmt.Println(parse(125))
	fmt.Println(parse("125"))
}
Терминалgo run ./proba
126
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.parse(...)
	/private/tmp/student-go/otchet-kafe-go-start/proba/main.go:6
main.main()
	/private/tmp/student-go/otchet-kafe-go-start/proba/main.go:11 +0x70
exit status 2

Первый вызов напечатал 126: внутри any было число. Второй остановил программу: interface conversion: interface {} is string, not int — «внутри строка, а не число». Строка main.parse(...) в трассировке без адреса: компилятор встроил маленькую функцию в место вызова. Компилятор эту ошибку пропустил: какой тип окажется внутри any, становится известно только при работе. Исправление — форма с признаком n, ok := value.(int) или переключатель switch v := value.(type), как в главе 4.2, а лучше — настоящий тип параметра вместо any.

§13Переменные: лишние и нулевые

Объявления переменных — глава 4.2, §9. Компилятор Go строже Python к лишнему коду: неиспользованные переменные и импорты — ошибки сборки.

proba/main.goлишний импорт, лишняя переменная, повторное :=
package main

import (
	"fmt"
	"strings"
)

func main() {
	total := 0
	heading := "Смена"
	total := 125000
	fmt.Println(total)
}
Терминалgo run ./proba
# example.com/otchet-kafe/proba
proba/main.go:5:2: "strings" imported and not used
proba/main.go:10:2: declared and not used: heading
proba/main.go:11:8: no new variables on left side of :=
  • Строка 5 — "strings" imported and not used: пакет импортирован, но ни одно его имя не используется. В VS Code с расширением Go лишние импорты удаляются сами при сохранении файла.
  • Строка 10 — declared and not used: heading: переменная объявлена и ни разу не прочитана. Обычно это след незаконченной правки: значение вычислили и забыли использовать.
  • Строка 11 — no new variables on left side of :=. Запись := объявляет новую переменную, а total уже объявлена в строке 9. Чтобы изменить значение, пишут обычное присваивание: total = 125000.

Последний пример — переменная-словарь без значения. Он собирается, и ошибка видна только при работе:

proba/main.goзапись в нулевой словарь
package main

import "fmt"

func main() {
	var totals map[string]int
	fmt.Println(totals["Иванов Пётр"], len(totals))
	totals["Иванов Пётр"] = 125000
	fmt.Println(totals)
}
Терминалgo run ./proba
0 0
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
	/private/tmp/student-go/otchet-kafe-go-start/proba/main.go:8 +0xa4
exit status 2

Чтение из нулевого словаря работает: строка 7 напечатала 0 0 — значения нет, длина ноль. Запись в строке 8 остановила программу: assignment to entry in nil map. Объявление var totals map[string]int создаёт переменную, но не словарь. Исправление — создать словарь сразу: totals := map[string]int{}, как в RevenueByWaiter.

§14Все сообщения главы

В восемнадцати примерах встретились сообщения компилятора одиннадцати видов и три вида остановки при работе. В таблице они собраны вместе; вид ошибки в скобках — так его подписывает gopls в панели Problems.

Начало сообщенияВид в goplsЧто произошлоГде разобрано
cannot use X (…) as T value in argument to FIncompatibleAssignв вызов передано значение не того типа§2, §4, §7, §9, §10
cannot use X (…) as T value in assignment / variable declaration / map indexIncompatibleAssignзначение не того типа при присваивании, в объявлении, в ключе словаря§4, §5
cannot use nil as T value in return statementIncompatibleAssignnil вместо структуры§8
… (truncated)TruncatedFloatдробная константа там, где ждут целое§2
invalid operation: … (mismatched types A and B)MismatchedTypesоперация над значениями разных типов§2, §11, §12
missing returnMissingReturnфункция обещает значение, но возвращает его не во всех случаях§3
(no value) used as valueTooManyValuesиспользуется результат функции без результата§3
assignment mismatch: N variables but F returns M valuesWrongAssignCountпринято не столько результатов, сколько возвращает функция§6
X.Y undefined (type T has no field or method Y)MissingFieldOrMethodу значения нет такого поля или метода§7
unknown field Y in struct literalMissingLitFieldпри создании структуры указано несуществующее поле§7
T does not implement I (missing method M)InvalidIfaceAssignу значения нет метода интерфейса или получатель не тот§9
"pkg" imported and not used, declared and not used: xUnusedImport, UnusedVarлишний импорт или переменная§13
no new variables on left side of :=NoNewVar:= для уже объявленной переменной§13
panic: … nil pointer dereference— (при работе)поле через пустой указатель§8
panic: interface conversion— (при работе)утверждение типа не совпало с содержимым any§12
panic: assignment to entry in nil map— (при работе)запись в нулевой словарь§13

Строки с panic — не ошибки компилятора: программа собралась и остановилась при работе. Их находят тесты, а не сборка.

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

Компилятор ответил cannot use "5" (untyped string constant) as int value in argument to append. Что произошло?

В срез []int через append добавляют строку. Нужно добавить целое число.

Почему averageCheck(4937, 5) собирается при total float64, а averageCheck(sum, checks) с переменными int — нет?

4937 — константа без типа, компилятор переводит её в float64 сам. У переменной sum тип уже есть — int, и его переводят явно: float64(sum).

Что означает missing return и почему место ошибки — закрывающая скобка функции?

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

Перепутанные high, low, err := GradeBounds(…) собираются. Почему компилятор не нашёл ошибку?

Оба значения типа int, а компилятор сверяет только типы. Что первое значение — минимум, он не знает. Защита — имена результатов в подписи и комментарий документации.

Почему *shift не подходит под интерфейс с методом TotalKopeks, а shift подходит?

Метод объявлен с получателем-указателем (s *Shift) и есть только у *Shift. *shift — значение типа Shift, у него этого метода нет.

Почему price + tip при Kopeks и Rubles — ошибка, хотя оба хранят целое число?

type Kopeks int и type Rubles int создают два разных новых типа. Операции между разными типами запрещены без явного перевода, и копейки с рублями случайно не сложить.

Какие из ошибок главы компилятор не находит?

Обращение через пустой указатель, неверное утверждение типа у any, запись в нулевой словарь и перепутанный порядок значений одного типа. Первые три останавливают программу при работе с panic, четвёртая даёт неверный результат без остановки.

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

Не тот тип cannot use X as T value in argument to F … in assignment / map index
Разные типы в выражении mismatched types int and float64 float64(n) — исправление
Результаты missing return (no value) used as value assignment mismatch
Структуры и интерфейсы has no field or method unknown field … in struct literal does not implement (missing method)
Лишнее imported and not used declared and not used no new variables on left side of :=
Только при работе nil pointer dereference interface conversion assignment to entry in nil map

Итог главы

Почти все ошибки в примерах — «передан не тот тип»: компилятор называет значение, его тип и тип, который ожидался, а слова после in указывают место — аргумент, присваивание, ключ словаря, return. Числа разных типов не смешиваются ни в вызове, ни в выражении; константы без типа компилятор переводит сам, переменные — нет. Проверяются число результатов, поля структур, методы интерфейсов и типы функций-аргументов. Новый тип не смешивается даже с типом, на котором основан. Неиспользованные переменные и импорты — ошибки сборки. Пропускает компилятор то, что зависит от данных при работе: пустой указатель, содержимое any, нулевой словарь, порядок значений одного типа. В главе 4.5 те же сообщения появятся в целом проекте: двенадцать ошибок в архиве, который не собирается.

Откуда взяты факты
  1. Правила присваиваемости и констант без типа — спецификация Go, Assignability и Constants.
  2. Наборы методов и получатели-указатели — спецификация Go, Method sets.
  3. Виды ошибок, которые показывает gopls, — коды ошибок проверки типов.
  4. Все выводы получены запуском примеров на втором архиве проекта, Go 1.27; сверка книги с запуском — shots/verify_go.py.
Глава 4.4 · Ошибки типов · МДК.01.01 Автор: Макаров Максим Николаевич