PythonerrorsValueErrorstrings

ValueError: could not convert string to float в Python

Ошибка could not convert string to float почти всегда означает одно: в float() попала строка с десятичной запятой вроде 77,59. Разбираем запятую, пустую строку, невидимый BOM из Excel и неразрывный пробел в разрядах — и собираем парсер цен, который не падает на грязных данных.

9 мин чтенияСправочникPython · errors · ValueError · strings · CSV

Ошибка could not convert string to float значит одно: в float() попала строка, которую Python не считает записью числа. В русских данных причина почти всегда одна и та же — десятичная запятая: float("77.59") возвращает 77.59, а float("77,59") падает, потому что разделителем дробной части Python признаёт только точку.

Если ты пришёл по запросу «could not convert string to float python запятая» — да, минимальный фикс это price.replace(",", "."). Но одной замены хватает не всегда. Дальше по частоте идут пустая строка из хвоста файла, невидимый BOM в первой ячейке выгрузки из Excel и неразрывный пробел в разрядах вроде 1 234,50. Разберём по одной причине за раз — от того, откуда строка пришла, а не от абстрактного правила.

Что означает ошибка could not convert string to float?

Это ValueError: функция float() получила строку и не смогла разобрать её как число. Python принимает только строгий формат — необязательный знак, цифры, точка как разделитель дробной части и, при желании, экспонента вроде 1e3. Всё остальное — запятая, значок валюты, буквы, пустота — приводит к исключению. Самое полезное в сообщении то, что оно печатает саму проблемную строку в кавычках: это готовая улика, по которой причина определяется за секунду.

price = "77,59"
print(float(price))

Последняя строка вывода:

ValueError: could not convert string to float: '77,59'

Читай то, что стоит в кавычках после двоеточия:

  • '77,59' — запятая вместо точки;
  • '' — пришла пустая строка;
  • '\ufeff77,59' — в начале BOM из файла;
  • '1\xa0234,50' — неразрывный пробел между разрядами;
  • '12.5 руб.' — в строку затесался текст.

Если ты видишь в кавычках \ufeff или \xa0 — символ невидимый, глазами его в терминале не найти. Поэтому первое действие при непонятной строке всегда одно: print(repr(raw)).

Чтобы эту строку прочитать, сообщение должно дожить до твоих глаз. При запуске двойным кликом консольное окно гаснет вместе с процессом, и ValueError исчезает вместе с ним — тогда сначала запусти файл из cmd, как разобрано в статье скрипт закрывается сразу после запуска.

Почему float('12,5') выдаёт ошибку, а float('12.5') работает?

Потому что float() разбирает строку по правилам синтаксиса самого Python, а не по правилам твоей локали. В исходном коде дробная часть отделяется точкой — x = 12.5, — и функция использует ровно тот же парсер. Запятая для него не разделитель, а посторонний символ, из-за которого вся строка перестаёт быть числом. Запись 77,59 приходит из Excel, 1С, банковских выгрузок и просто из input(), поэтому именно она даёт эту ошибку чаще всего.

Строка float(...) Почему так
"77.59" 77.59 точка — единственный разделитель дробной части
"77,59" ValueError запятая не разбирается
"1e3" 1000.0 экспоненциальная запись допустима
"1_000.5" 1000.5 подчёркивания разрешены, как в литералах
" 12.5\n" 12.5 пробелы и перенос по краям отбрасываются
"" ValueError пустая строка
"nan" nan это валидное значение float

Короче говоря, float не принимает число с запятой ни в Python 3.12, ни в любой другой версии. Чтобы преобразовать строку с запятой в число, достаточно поменять разделитель:

price = "77,59"
print(float(price.replace(",", ".")))   # 77.59

Какие вообще строки Python считает числами и чем float() отличается от int() — коротко разобрано в справочнике, в разделе про преобразование типов.

Когда одной замены replace(',', '.') уже мало

Замена лечит запятую и больше ничего. Настоящая выгрузка приносит ещё и разряды, причём разделитель разрядов бывает двух видов: обычный пробел и неразрывный пробел \xa0 (U+00A0), который Excel и веб-страницы вставляют, чтобы число не разорвалось при переносе строки. На экране они выглядят абсолютно одинаково.

raw = " 1\u00a0234,50\n"
print(repr(raw))
' 1\xa0234,50\n'

Тонкость: float() сам отбрасывает пробельные символы по краям строки, включая неразрывный, но не трогает те, что стоят внутри числа. Значит, чистить надо весь набор сразу:

def to_float(raw: str) -> float:
    cleaned = raw.replace("\ufeff", "").replace("\u00a0", "").replace(" ", "")
    return float(cleaned.replace(",", "."))

print(to_float("77,59"))
print(to_float(" 1\u00a0234,50\n"))
print(to_float("12.5"))
77.59
1234.5
12.5
Что в строке Только replace(",", ".") Полная чистка
"77,59" 77.59 77.59
"1 234,50" (обычный пробел) ValueError 1234.5
"1\xa0234,50" (неразрывный) ValueError 1234.5
"\ufeff8,0" (BOM) ValueError 8.0

Обрати внимание на третью строку вывода: to_float("12.5") тоже работает. Замена запятой на точку безопасна для строк, где запятой нет, поэтому одну функцию можно применять ко всей колонке, не разделяя данные на «русские» и «английские».

Соблазн почистить строку через strip() или rstrip() тут велик, но rstrip() — отдельная ловушка: он снимает набор символов, а не окончание строки. Как это молча съедает лишние буквы, разобрано в статье про удаление расширения файла.

Пустая строка и перенос: что из них правда ломает float()

Народное объяснение «мешает \n в конце» неверно. float() сам отбрасывает пробельные символы по краям, поэтому float("12.5\n") спокойно вернёт 12.5. Ломает не перенос, а пустая строка, которая из этого переноса рождается: если файл заканчивается символом \n, то при разбиении по строкам последним элементом окажется ''.

text = "77,59\n1 234,50\n\n8,0\n"
for line in text.split("\n"):
    print(repr(line))
'77,59'
'1 234,50'
''
'8,0'
''

Пустых строк две: одна настоящая, из середины файла, вторая — хвост после последнего \n. Обе дадут ValueError с пустыми кавычками в конце сообщения. Лечится это не try/except, а фильтром: не парсить то, что после чистки осталось пустым.

def to_float(raw: str) -> float:
    cleaned = raw.replace("\ufeff", "").replace("\u00a0", "").replace(" ", "")
    return float(cleaned.replace(",", "."))

lines = ["77,59", "", "8,0", "1\u00a0234,50"]
prices = [to_float(x) for x in lines if x.strip()]
print(prices)
print(sum(prices))
[77.59, 8.0, 1234.5]
1320.09

Почему ошибка появляется только на первой строке CSV-файла?

Почти всегда виноват BOM — три служебных байта в начале файла, которые Excel пишет при сохранении в формате «CSV UTF-8». При чтении с encoding="utf-8" эти байты превращаются в невидимый символ \ufeff, и он прилипает к самому первому значению самого первого ряда. Все остальные ряды разбираются нормально — отсюда и ощущение, что «падает только первая строка», хотя данные в ней выглядят точно так же, как во второй.

row = "\ufeff77,59"
print(len(row))    # 6, хотя на экране видно пять символов
print(repr(row))   # '\ufeff77,59'

Симптом «CSV из Excel не парсится в число, причём только первая строка» почти всегда упирается именно в это. Убрать BOM из первой ячейки CSV правильнее не вручную, а кодировкой при открытии файла: кодек utf-8-sig съедает его сам.

from pathlib import Path

Path("prices.csv").write_bytes("товар;цена\nкофе;77,59\n".encode("utf-8-sig"))

with open("prices.csv", encoding="utf-8") as f:
    print(repr(f.readline()))

with open("prices.csv", encoding="utf-8-sig") as f:
    print(repr(f.readline()))
'\ufeffтовар;цена\n'
'товар;цена\n'

Если файл вместо BOM отдаёт кракозябры или падает с UnicodeDecodeError, дело в другой кодировке — это разобрано отдельно в статье про cp1251 и UTF-8. А разбор CSV по-взрослому, через csv.reader, лежит в справочнике по CSV.

Чем эта ошибка отличается от invalid literal for int()?

Обе ошибки — это ValueError, но выдают их разные функции, и различие практическое. float() жалуется текстом could not convert string to float, int() — текстом invalid literal for int() with base 10. Главное отличие: int() строже и не принимает даже корректно записанное дробное число, потому что не знает, что делать с дробной частью — округлить, отбросить или ругаться.

print(int("12.5"))
ValueError: invalid literal for int() with base 10: '12.5'

Путь для строки с дробной частью — сначала float(), потом int():

print(float("12.5"))       # 12.5
print(int(float("12.5")))  # 12
Вызов Результат
float("12.5") 12.5
int("12.5") ValueError: invalid literal for int()
int(float("12.5")) 12
float("12") 12.0
float("") ValueError: could not convert string to float

То есть по тексту ошибки ты сразу знаешь, какая функция упала: «float» в сообщении — упал float(), «int() with base 10» — упал int().

Проверять заранее или ловить исключение

Предварительная проверка через isdigit() не работает: "12.5".isdigit() — это False, потому что точка не цифра, и "77,59".isdigit() тоже False. Такая проверка отбросит все корректные дробные числа. Писать собственную регулярку под «число с запятой, пробелами и BOM» — тоже занятие на полдня. Надёжнее сначала почистить строку, а потом попробовать преобразовать и поймать ValueError — это и есть питоновский стиль EAFP.

def parse_price(raw: str):
    cleaned = raw.replace("\ufeff", "").replace("\u00a0", "").replace(" ", "")
    try:
        return float(cleaned.replace(",", "."))
    except ValueError:
        return None

rows = ["77,59", "1\u00a0234,50", "", "нет данных", "\ufeff8,0"]
for row in rows:
    print(repr(row), "->", parse_price(row))
'77,59' -> 77.59
'1\xa0234,50' -> 1234.5
'' -> None
'нет данных' -> None
'\ufeff8,0' -> 8.0

Такой парсер не падает на грязных данных и честно помечает непарсящиеся ячейки как None, а не подставляет вместо них ноль — иначе битая строка тихо исказит итоговую сумму. Разница между «спросить заранее» и «попробовать и поймать» разобрана в справочнике, EAFP и LBYL.

Модуль locale и Decimal: что из этого стоит трогать

В интернете часто советуют locale.atof() — функцию, которая разбирает число по правилам локали и потому понимает запятую. Совет рабочий ровно до момента, когда код уезжает на сервер: locale.setlocale(locale.LC_NUMERIC, "ru_RU.UTF-8") требует, чтобы эта локаль была установлена в системе, а в типичном Docker-контейнере её нет. К тому же локаль глобальна для всего процесса и меняет поведение постороннего кода.

import locale

try:
    locale.setlocale(locale.LC_NUMERIC, "ru_RU.UTF-8")
    print(locale.atof("77,59"))
except locale.Error:
    print("локаль ru_RU.UTF-8 в системе не установлена")

Практическое правило: для разбора данных бери явную чистку строки, а locale оставь для отображения чисел пользователю. Отдельно про деньги: float — двоичная дробь, и точных десятичных копеек в нём нет. Если считаешь суммы, бери Decimal, которому строку тоже надо отдать с точкой.

from decimal import Decimal

print(0.1 + 0.2)                               # 0.30000000000000004
print(Decimal("77,59".replace(",", ".")) * 3)  # 232.77

Частые ошибки

  1. Замена запятой в английском формате. В строке "1,234.50" запятая — разделитель разрядов, и replace(",", ".") превратит её в "1.234.50", что не парсится вообще. Сначала посмотри на формат глазами через repr(), потом решай, что менять.
  2. Голый except:. Он проглотит и опечатку в имени переменной, и KeyboardInterrupt. Лови конкретно except ValueError.
  3. Забыть про None. float(None) даёт TypeError, а не ValueError, и блок except ValueError его пропустит. Если в данных бывает None, пиши except (TypeError, ValueError).
  4. Проверка через isdigit(). Она отбрасывает корректные дробные числа: "12.5".isdigit() возвращает False.
  5. Чистить BOM в каждой строке. BOM приходит из файла один раз, и лечится он в open() через encoding="utf-8-sig", а не циклом lstrip("\ufeff") по всем ячейкам.
  6. Подставлять ноль вместо непарсящегося значения. Ноль неотличим от настоящей нулевой цены и молча ломает суммы и средние. Возвращай None и считай такие строки отдельно.
  7. Не замечать nan. float("nan") не падает, потому что nan — валидное значение. Строка "NaN" из выгрузки тихо станет числом, которое не равно даже самому себе: nan == nan даёт False, а nan != nan — наоборот, True. Сравнения <, >, <=, >= с nan тоже все False, поэтому фильтры и сортировки по такой колонке молча врут.

Практика

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

Мини-резюме

  • could not convert string to float — это ValueError от float(), и в кавычках сообщения лежит сама виноватая строка.
  • В русских данных причина номер один — десятичная запятая: float("77,59") падает, float("77.59") работает.
  • Минимальный фикс — replace(",", "."); для выгрузок нужна ещё чистка \xa0, обычных пробелов и BOM.
  • Перенос строки \n сам по себе безопасен: float("12.5\n") работает. Ломает пустая строка '', которая остаётся после разбиения текста.
  • Падение только на первом ряду CSV — почти всегда BOM; лечится encoding="utf-8-sig" в open().
  • int("12.5") даёт другую ошибку — invalid literal for int() with base 10; правильный путь int(float("12.5")).
  • Для грязных данных надёжнее чистка плюс try/except ValueError с возвратом None, а не предварительная проверка через isdigit().

Закрепи на практике

Решай задачи в Python-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр