Python не превращает строку в число автоматически. Строка "2" и число 2 не сложатся, а input() всегда возвращает строку, даже когда пользователь ввёл цифры. Преобразование делают явно тремя функциями, и почти все ошибки в теме растут из того, что происходит внутри них. Код выполнен на CPython 3.14.5.
input даёт строку, даже если введены цифры
input() не угадывает тип: даже цифры приходят строкой. int() превращает её в число до арифметики.Три функции: int, float и str
Имя функции совпадает с именем типа:
print(int("42"), float("3.14"), str(42))
print(type(int("42")).__name__, type(float("3.14")).__name__, type(str(42)).__name__)
42 3.14 42
int float str
Во второй строке видно, что результат действительно сменил тип, хотя на печати 42 и "42" выглядят одинаково. Обратное направление, число в текст для вывода, чаще делают f-строкой: она вставляет значение прямо в предложение и заодно умеет форматировать.
int разбирает только целое. Строка с точкой его роняет:
try:
int("42.5")
except ValueError as exc:
print(f"ValueError: {exc}")
print(int(float("42.5")))
ValueError: invalid literal for int() with base 10: '42.5'
42
Через float проходит, но дробная часть отбрасывается, а большие числа могут потерять точность ещё до вызова int. Если важно точно разобрать десятичную запись, используй Decimal из модуля decimal, затем int. Пробелы по краям десятичной записи int прощает, а буквы нет:
print(int(" 42 "))
try:
int("42px")
except ValueError as exc:
print(f"ValueError: {exc}")
42
ValueError: invalid literal for int() with base 10: '42px'
Здесь прилетел ValueError, а не TypeError, и это не случайность: строка — подходящий для int() тип, не годится только её содержимое. Правило, по которому эти два исключения различаются, разобрано отдельно.
У int есть второй аргумент, система счисления:
print(int("1010", 2), int("ff", 16), int("0b1010", 0))
10 255 10
Ноль означает «определи по префиксу»: 0b двоичное, 0x шестнадцатеричное.
Почему int обрезает, а не округляет
Из дробного числа int берёт целую часть, отбрасывая хвост. Причём отбрасывает в сторону нуля, а не вниз:
print(int(3.9), int(-3.9), int(0.99))
3 -3 0
Минус три, а не минус четыре. Ноль вместо почти единицы. Если нужно именно округление, берут round, но и там сюрприз.
Почему round(2.5) даёт 2
Python округляет половину к ближайшему чётному, а не всегда вверх:
print(round(0.5), round(1.5), round(2.5), round(3.5))
0 2 2 4
Половина от 0.5 ушла вниз, от 1.5 вверх, от 2.5 снова вниз. Это не ошибка, а банковское округление: на большом наборе чисел оно не накапливает перекос вверх, как школьное правило «половина всегда вверх».
Второй сюрприз касается округления до знаков после запятой:
print(round(2.675, 2))
2.67
Ожидалось 2.68. Причина не в round, а в том, что 2.675 невозможно точно представить в двоичной дробной арифметике: на самом деле там чуть меньше, и округление честно идёт вниз.
Почему 0.1 + 0.2 не равно 0.3
Тот же корень, и это не особенность Python, а свойство двоичного представления дробей:
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
0.30000000000000004
False
Отсюда правило: дробные числа не сравнивают на равенство. Сравнивают с допуском либо через math.isclose, либо округляя оба значения до нужной точности.
Для денег float не годится вовсе, там берут Decimal, который считает в десятичной системе:
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))
print(Decimal(0.1))
True
0.1000000000000000055511151231257827021181583404541015625
Первая строка показывает, зачем Decimal нужен, вторая — как им пользоваться нельзя: число создают из строки, потому что при передаче готового float внутрь попадёт уже испорченное значение со всем двоичным хвостом.
Строку с числом не сложить
Оператор + не преобразует строку в число или число в строку:
try:
print("2" + 2)
except TypeError as exc:
print(f"TypeError: {exc}")
TypeError: can only concatenate str (not "int") to str
Сообщение читается прямо: к строке можно приклеить только строку. А вот умножение строки на число работает, и это сбивает с толку:
print("2" * 3, [0] * 3)
222 [0, 0, 0]
Умножение последовательности на целое повторяет её, поэтому "2" * 3 даёт "222", а не 6. Между числами приведение всё-таки есть, но только внутри числовых типов:
print(1 + 2.5, type(1 + 2.5).__name__)
print(10 / 5, type(10 / 5).__name__, 10 // 5, type(10 // 5).__name__)
3.5 float
2.0 float 2 int
Целое поднялось до дробного, чтобы сложение имело смысл. Для встроенных int и float обычное / возвращает float, даже когда делится нацело. // округляет частное вниз и возвращает int, только если оба операнда целые: 5 // 2 — это 2, а 5.0 // 2 — 2.0.
Какие значения Python считает ложными
Функция bool превращает что угодно в истину или ложь, и правило простое: ложны ноль, пустота и None.
for v in [0, 1, "", "0", "False", [], [0], {}, None, 0.0]:
print(f"{v!r:8} -> {bool(v)}")
0 -> False
1 -> True
'' -> False
'0' -> True
'False' -> True
[] -> False
[0] -> True
{} -> False
None -> False
0.0 -> False
Две строки здесь ловят почти всех. "0" и "False" истинны: это непустые строки, а содержимое bool не читает. Проверка вида if input("да?"): пропустит любой ответ, включая «нет». Список [0] тоже истинен: он не пустой, хотя единственный элемент ложный.
Сам bool при этом наследуется от int, поэтому истина равна единице и складывается:
print(int(True), int(False), True + True, isinstance(True, int))
1 0 2 True
Свойство используют для подсчёта: sum(x > 10 for x in nums) считает, сколько элементов больше десяти.
Как безопасно превратить ввод в число
Проверять строку методами is до конверсии соблазнительно, но покрытие получается дырявым:
for v in ["42", "-7", "3.14", "", "42px"]:
print(f"{v!r:8} isdecimal={v.isdecimal()}")
'42' isdecimal=True
'-7' isdecimal=False
'3.14' isdecimal=False
'' isdecimal=False
'42px' isdecimal=False
Отрицательное число не проходит из-за минуса, дробное из-за точки. Дописывать проверки под каждый случай бессмысленно. Надёжнее один раз попробовать преобразовать и перехватить отказ:
def to_int(text, default=None):
try:
return int(text)
except (ValueError, TypeError):
return default
print(to_int("42"), to_int("42px"), to_int(None), to_int("-7"))
42 None None -7
TypeError в кортеже нужен затем, чтобы функция пережила None вместо строки: у него своя ошибка, не ValueError. Про выбор типов в except подробно рассказано в статье про исключения, а про сами методы проверки строк — в статье про методы строк.
Ошибки, на которых спотыкаются
int() вместо round(). int(3.9) даёт 3, а не 4. Отбрасывание и округление — разные операции.
Сравнение дробных на равенство. 0.1 + 0.2 == 0.3 ложно. Сравнивай с допуском.
Decimal(0.1) вместо Decimal("0.1"). Из числа внутрь попадает уже испорченное значение, и смысл Decimal теряется.
Проверка if строка: вместо сравнения. Строка "0" истинна, поэтому такая проверка пропустит ответ «нет» и любую другую непустую строку.
None в конверсии. int(None) даёт TypeError, а не ValueError, и узкий перехват только ValueError его пропустит.
Забытая конверсия после input(). Ввод всегда строка, поэтому input() + 1 падает, а input() * 2 тихо удваивает текст.
Частые вопросы
Как превратить строку в число
int("42") для целого, float("3.14") для дробного. int(float("42.5")) отбросит дробную часть, но промежуточный float может потерять точность. Для точной десятичной записи используй int(Decimal("42.5")), предварительно импортировав Decimal из decimal.
Как превратить число в строку
str(42) либо f-строка, если число вставляется в текст. Результат в обоих случаях одинаковый.
Как округлить число правильно
round(x) округляет половину к чётному. Если нужно всегда вверх, берут math.ceil, всегда вниз — math.floor, а для денег переходят на Decimal с явным правилом округления.
Почему сравнение 1 == True истинно
Потому что bool наследуется от int, и True равен единице. При этом "1" == 1 ложно: строка и число не равны никогда.
Как проверить, что строку можно превратить в число
Попыткой в try, а не набором проверок is: они не покрывают ни минус, ни точку, ни пробелы. Готовая функция с default показана выше.
Где потренироваться
В пути «Python с нуля» на Koddo конверсия идёт вместе с проверкой ввода: разобрать введённое, отсечь мусор, вернуть понятную ошибку. Проверить себя можно на форматировании длительности, где на входе целое число секунд, а на выходе строка заданного вида.