Аннотации типов и документация кода · МДК.01.01 · разработка программных модулей
Глава 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, полученный запуском. Номера строк в ответе считаются от первой строки файла, поэтому файл нужно копировать целиком, вместе с импортами и пустыми строками.
Порядок работы с каждым примером одинаковый:
- Замените содержимое
proba.pyкодом примера и сохраните файл. - Посмотрите, какие строки подчеркнул Pylance, и наведите указатель на подчёркивание.
- Выполните в терминале
.venv/bin/mypy proba.py.venv\Scripts\mypy proba.pyи сверьте ответ с книгой. - Исправьте ошибки по разбору под примером и запустите проверку снова: ответ должен смениться на
Success.
В начале каждого раздела стоит ссылка на параграф главы 4.2, где объяснена запись типа. Здесь запись заново не объясняется: раздел начинается сразу с файла.
Pylance в режиме basic подчёркивает почти те же строки, что mypy, но формулирует сообщения по-своему. Где поведение двух программ различается, об этом сказано в разборе.
§2Простые типы: передано не то значение
Запись простых типов — глава 4.2, §1. Самая частая ошибка с ними — значение не того типа в вызове.
В примере две новые функции с простыми типами и вызовы функций проекта: два верных и три неверных.
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)
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. Целое число считается частным случаем дробного. Обратное не верно:
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)
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 пытаются использовать.
from app.ui.console import print_report def label(kopeks: int) -> str: if kopeks > 0: return "есть выручка" result = print_report(["Смена"])
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.
Аннотация списка проверяет не только присваивание, но и всё, что со списком делают потом: добавление элементов, чтение элемента в переменную.
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]
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. У словаря проверяются и ключи, и значения.
В примере три словаря и четыре неверные записи в них. В конце файла — перебор пар словаря с запросом типа: так видно, что проверка знает тип и ключей, и значений.
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)
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 разобран на две переменные, затем пять объявлений кортежей: три верных и два неверных — с лишним элементом и с переставленными элементами.
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, ...] = ()
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.
waiters: set[str] = {"Иванов Пётр", "Петрова Анна"} waiters.add("Сидоров Илья") waiters.add(125000)
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.
Когда тип — класс, проверка знает его поля и методы. Поэтому она находит и опечатки в именах атрибутов.
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")
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переключите вариант
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)
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.
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)
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, если ключа нет:
revenue: dict[str, int] = {"Иванов Пётр": 168500} bonus = revenue.get("Сидоров Илья") reveal_type(bonus) print(bonus + 100) print(revenue.get("Сидоров Илья", 0) + 100)
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. Здесь видно, как проверка следит за типом по ходу функции:
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")
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, дальше по телу значение считается заказом:
from app.domain.order import Order def kopeks_or_zero(order: Order | None) -> int: if order is None: return 0 return order.kopeks
Success: no issues found in 1 source file
§10Объединения: работа без проверки варианта
Запись объединений — глава 4.2, §5. В первом примере вариант проверен через isinstance, во втором — нет.
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)
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 в перечне вариантов не значится.
Без проверки варианта проверка отклонит код, даже если для одного из вариантов он верен:
def to_kopeks(value: int | str) -> int: return value * 100
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. Первый пример показывает, что проверка знает тип значений, которые отдаёт генератор.
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)
proba.py:11: note: Revealed type is "builtins.int" Success: no issues found in 1 source file
Переменная цикла получила тип элемента — int.
Во втором примере параметр fix записан как Callable[[str], str] — «функция, которая принимает строку и возвращает строку». В него по очереди передают три функции.
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)
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. Пример показывает, что проверка видит за псевдонимом исходный тип.
type Kopeks = int type Revenue = dict[str, Kopeks] def total(revenue: Revenue) -> Kopeks: return sum(revenue.values()) reveal_type(total({"Иванов Пётр": 100}))
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 у параметров без аннотаций.
from typing import Any def parse(value: Any) -> int: return value.to_kopeks() parse("125")
Success: no issues found in 1 source file
У строки нет метода to_kopeks, и при запуске будет AttributeError, но проверка ошибок не нашла: для Any она ничего не сверяет. Any ставят там, где тип действительно неизвестен заранее, например у данных из внешней библиотеки без аннотаций. Убирать им замечание проверки нельзя: замечание исчезнет вместе с самой проверкой. В работах курса Any не используется.
§14Переменные: пустая коллекция без аннотации
Где переменным нужна аннотация — глава 4.2, §9. Проверка сама сообщает о таком месте:
totals = {}
names = []
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-type | Argument 1 to "…" has incompatible type | в вызов передано значение не того типа | §2, §4, §7, §8, §10, §11 |
assignment | Incompatible types in assignment | справа от = значение не того типа, что у переменной или элемента | §4, §5, §6 |
return | Missing return statement | функция обещает значение, но возвращает его не во всех случаях | §3 |
return-value | Incompatible return value type | после return значение не того типа, что записан после стрелки | §10 |
func-returns-value | "…" does not return a value | используется результат функции с -> None | §3 |
index | Invalid index type | в квадратных скобках ключ не того типа | §5 |
attr-defined | "Order" has no attribute "…" | у класса нет такого поля или метода | §8 |
union-attr | Item "None" of "… | None" has no attribute | обращение к полю значения, которое может быть None | §9 |
operator | Unsupported operand types for + | операция над значением, которое может быть None | §9 |
var-annotated | Need 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
[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 те же сообщения появятся в собственном коде: аннотации расставляются в проекте, где их нет.
- Правило «int вместо float» — спецификация типов Python.
- Коды ошибок mypy — документация mypy, Error codes enabled by default.
- Сужение типов и
reveal_type— документация mypy, Type narrowing. - Все выводы получены запуском примеров на втором архиве проекта, mypy
1.15; сверка книги с запуском —shots/verify_examples.py.
Ошибки типов:
примеры с разбором
В главе 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 и импортами.
Порядок работы с каждым примером одинаковый:
- Замените содержимое
proba/main.goкодом примера и сохраните файл. - Посмотрите, какие строки подчеркнул gopls, и наведите указатель на подчёркивание.
- Выполните в терминале
go run ./probaи сверьте ответ с книгой. - Исправьте ошибки по разбору под примером и запустите снова: программа должна собраться и напечатать результат.
Команда go run сначала собирает программу. Если сборка не удалась, она печатает ошибки компилятора — строки вида proba/main.go:15:33: … под заголовком # example.com/otchet-kafe/proba — и ничего не запускает. Если сборка прошла, программа выполняется, и в ответе её вывод. Так в одном ответе видны оба рода ошибок: те, что находит компилятор, и те, что случаются при работе.
В начале каждого раздела стоит ссылка на параграф главы 4.2, где объяснена запись типа. Здесь запись заново не объясняется: раздел начинается сразу с файла. gopls показывает в редакторе те же сообщения, что и компилятор, с тем же текстом.
§2Простые типы: передано не то значение
Запись простых типов — глава 4.2, §1. Самая частая ошибка с ними — значение не того типа в вызове. В примере функция isBig и вызовы функций проекта: один верный и три неверных.
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, "сто тысяч")) }
# 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 не подойдёт.
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)) }
# 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).
Та же строгость действует в арифметике: значения разных типов нельзя складывать и умножать.
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) }
# 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. С результатом компилятор следит за двумя ошибками: функция обещает значение, но возвращает его не во всех случаях, и результат функции без результата пытаются использовать.
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 }
# 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. Тип среза проверяет не только объявление, но и всё, что со срезом делают потом: добавление элементов, чтение элемента в переменную.
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 }
# 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. У словаря проверяются и ключи, и значения. В примере три словаря и четыре неверные записи в них.
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 }
# 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. Компилятор требует принять их все: столько переменных, сколько значений возвращает функция.
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) }
# 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, и если перепутать их местами, программа соберётся:
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) }
минимум: 5 максимум: 3
Ошибок сборки нет, а в отчёте минимум больше максимума. Компилятор не может знать, что первое значение — минимум: оба int. В Python у кортежа tuple[str, int] перестановку находила проверка, потому что типы мест различались; для tuple[int, int] не находила бы и она. Защита здесь — имена результатов в подписи (low, high int, err error), которые видны в подсказке редактора, и фраза о порядке в комментарии документации.
§7Структуры: опечатка в поле, словарь вместо структуры
Запись структур — глава 4.2, §3. Когда тип значения — структура, компилятор знает её поля и находит опечатки в именах.
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 }
# 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.
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, "Петрова Анна") }
# 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 есть.
С указателем компилятор такую ошибку уже не найдёт, и появляется другая — обращение к полю через пустой указатель. Она собирается и проявляется только при работе:
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) }
ищем Петрову Анну 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. Значение подходит под интерфейс, если у него есть все методы интерфейса. Компилятор это проверяет и называет пропущенный метод.
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) }
# 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 — «функция, которая принимает строку и возвращает строку». В него по очереди передают три функции.
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)) }
# 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. Пример показывает, чем они отличаются для компилятора.
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) }
# 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 компилятор не разрешает ничего, кроме передачи дальше:
package main import "fmt" func parse(value any) int { return value + 1 } func main() { fmt.Println(parse(125)) }
# 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) компилятор устраивает, но переносит проверку на время работы:
package main import "fmt" func parse(value any) int { return value.(int) + 1 } func main() { fmt.Println(parse(125)) fmt.Println(parse("125")) }
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 к лишнему коду: неиспользованные переменные и импорты — ошибки сборки.
package main import ( "fmt" "strings" ) func main() { total := 0 heading := "Смена" total := 125000 fmt.Println(total) }
# 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.
Последний пример — переменная-словарь без значения. Он собирается, и ошибка видна только при работе:
package main import "fmt" func main() { var totals map[string]int fmt.Println(totals["Иванов Пётр"], len(totals)) totals["Иванов Пётр"] = 125000 fmt.Println(totals) }
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 F | IncompatibleAssign | в вызов передано значение не того типа | §2, §4, §7, §9, §10 |
cannot use X (…) as T value in assignment / variable declaration / map index | IncompatibleAssign | значение не того типа при присваивании, в объявлении, в ключе словаря | §4, §5 |
cannot use nil as T value in return statement | IncompatibleAssign | nil вместо структуры | §8 |
… (truncated) | TruncatedFloat | дробная константа там, где ждут целое | §2 |
invalid operation: … (mismatched types A and B) | MismatchedTypes | операция над значениями разных типов | §2, §11, §12 |
missing return | MissingReturn | функция обещает значение, но возвращает его не во всех случаях | §3 |
(no value) used as value | TooManyValues | используется результат функции без результата | §3 |
assignment mismatch: N variables but F returns M values | WrongAssignCount | принято не столько результатов, сколько возвращает функция | §6 |
X.Y undefined (type T has no field or method Y) | MissingFieldOrMethod | у значения нет такого поля или метода | §7 |
unknown field Y in struct literal | MissingLitField | при создании структуры указано несуществующее поле | §7 |
T does not implement I (missing method M) | InvalidIfaceAssign | у значения нет метода интерфейса или получатель не тот | §9 |
"pkg" imported and not used, declared and not used: x | UnusedImport, 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 те же сообщения появятся в целом проекте: двенадцать ошибок в архиве, который не собирается.
- Правила присваиваемости и констант без типа — спецификация Go, Assignability и Constants.
- Наборы методов и получатели-указатели — спецификация Go, Method sets.
- Виды ошибок, которые показывает gopls, — коды ошибок проверки типов.
- Все выводы получены запуском примеров на втором архиве проекта, Go
1.27; сверка книги с запуском —shots/verify_go.py.