Динамическая типизация runtime: какие ошибки C-компилятор ловил, а Python не ловит
core перечислить, какие классы ошибок в вашем C-коде компилятор ловил бесплатно, и понять, что их нужно закрывать иначе в Python
В C граница между «код не собрался» и «код упал в проде» — это граница между компилятором и вами. Компилятор ловит несовпадение типов аргументов, вызов функции с неверным числом параметров, присваивание указателя несовместимого типа, обращение к полю структуры, которого не существует. Вы получаете эти проверки бесплатно, на каждой строке, независимо от того, выполнялась ли она хоть раз.
В Python этой стадии нет. Есть только один режим проверки — исполнение конкретной строки с конкретными данными. Если строка x + y никогда не выполнилась с y, которое не поддерживает сложение, интерпретатор никогда не узнает, что там ошибка. Это меняет саму природу «правильности» кода: в C корректность частично гарантирует компилятор до запуска, в Python корректность типов — функция от покрытия кода тестами и реальными входными данными.
Причина — в объектной модели. В Python тип принадлежит объекту, а имя — это лишь привязка к объекту; одно и то же имя может ссылаться сначала на int, потом на str. Интерпретатор не может знать заранее, какой объект придёт в функцию, поэтому проверяет операции «по факту».
| Категория | C | Python |
|---|---|---|
| Несовпадение типа аргумента | ошибка компиляции | TypeError в момент вызова |
| Опечатка в имени переменной/поля | ошибка компиляции | NameError/AttributeError в момент обращения |
| Неверное число аргументов функции | ошибка компиляции | TypeError в момент вызова |
Несовместимая операция (x + структура) |
ошибка компиляции | TypeError при выполнении операции |
graph TD
A["Код .c"] --> B{"Компилятор gcc/clang"}
B -->|"проверка типов пройдена"| C["Исполняемый файл"]
B -->|"ошибка типа"| D["Остановка сборки"]
C --> E["Запуск"]
F["Код .py"] --> G["Интерпретатор python"]
G --> H["Выполнение строки за строкой"]
H -->|"встречена операция с несовместимым типом"| I["Runtime-исключение TypeError"]
H -->|"все выполненные операции корректны"| J["Успешное завершение"]Ключевое отличие не в том, что Python «менее безопасен», а в том, что проверка сдвинута с этапа «до запуска» на этап «во время исполнения конкретной ветки». Если у вас есть код, который срабатывает раз в год при определённом входе, компилятор C проверил бы его типы всё равно. Python проверит их только тогда, когда этот код реально выполнится. Отсюда два следствия: юнит-тесты становятся не роскошью, а заменой части работы компилятора, а часть контроля переносится на внешние инструменты статического анализа (урок 3).
Разобранный пример
Практический перенос C-функции в Python «построчно»:
def clamp(value, low, high):
if value < low:
return low
if value > high:
return high
return value
В C сигнатура int clamp(int value, int low, int high) гарантировала бы: сюда придут только int. Передача указателя несовместимого типа (например, char* там, где ожидается int*) — нарушение ограничений языка: компилятор выдаст диагностику, а с -Werror сборка просто остановится.
В Python эта функция определяется без единой ошибки и спокойно отработает на clamp("abc", "a", "z") — сравнение строк поддерживается, функция вернёт строку. А вот clamp([1, 2], 0, 10) упадёт с TypeError: '<' not supported between instances of 'list' and 'int' — но только в момент вызова, и только если эта ветка реально исполнится. Если в тестах вы гоняли только числа, ошибка приедет в продакшен.
Вывод: «функция определена без ошибок» не означает вообще ничего о корректности типов внутри неё. Единственный сигнал — фактическое исполнение с реальными аргументами.
Попробуй сейчас
Сохраните в файл test_types.py:
def calculate_total_price(base_price, tax_rate, discount):
price_with_tax = base_price * (1 + tax_rate)
return price_with_tax - discount
run_buggy_code = False
if run_buggy_code:
print(calculate_total_price(100, "0.2", 10))
else:
print("Script finished without running the buggy code path.")
- Запустите
python test_types.py. Ошибки нет? - Замените
run_buggy_codeнаTrueи запустите снова.
Ожидаемый результат: при первом запуске скрипт печатает сообщение и завершается успешно — ошибка есть в коде, но ветка не исполнялась. После True он падает на строке price_with_tax = base_price * (1 + tax_rate) с TypeError: unsupported operand type(s) for +: 'int' and 'str', потому что нельзя сложить int (1) и str ("0.2"). Проверка типа произошла ровно в момент выполнения операции.
Получилось, если…
Вы поняли материал, если для незнакомой функции можете сразу сказать: «эту ошибку в C поймал бы компилятор, а здесь её поймает только тест/вызов с такими-то данными» — и предложить конкретный входной пример, который её вскрывает.
Вывод
Компилятор C проверяет типы для всего кода сразу, до запуска. Python проверяет типы только для той строки и той ветки, что реально исполнилась с реальными данными. Отсутствие ошибки при запуске не значит отсутствие ошибки — значит, эта ветка ещё не тронута. Ответственность переходит к вам, тестам и статическим анализаторам.
flowchart LR
subgraph C
A1[Весь исходный код] --> A2[Компилятор проверяет типы везде] --> A3[Бинарник: типы проверены]
end
subgraph Python
B1[Весь исходный код] --> B2[Интерпретатор: типы не проверяются заранее]
B2 --> B3[Строка выполнилась?]
B3 -->|да| B4[Проверка типа именно сейчас]
B3 -->|нет, ветка не тронута| B5[Ошибка типа не найдена]
endAI-generated · source-grounded review
🛡 Fact-checked: 2 risky claims verified · 3 removed · confidence: high · figures: 2
A second model re-checked this lesson's claims and corroborated 3 of 5 , and could not confirm 2 either way. This is incomplete corroboration, not a disagreement.
On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course