Вы открываете файл, чтобы посчитать итог, и видите значения «1 234,50», «2 345,00» и «3 000,25». Внутри стоят разные символы пробела. Если без проверки удалить из строки все разрешённые пробелы, можно скрыть ошибочную группировку. Поэтому сначала проверяют исходную строку по выбранной грамматике, сохраняют её и затем удаляют только разрешённый разделитель.
Дальше я приведу шесть вымышленных строк R01–R06 из одного числового поля, выраженных в условных единицах. Никаких реальных измерений или денег тут нет. Для упражнения я заранее выбрал правило: неотрицательное число, запятая перед ровно двумя десятичными цифрами, до девяти цифр целой части. Группы тысяч, если они есть, состоят из трёх цифр, а разделитель внутри одной строки одинаков. Знаки, экспоненты, единицы измерения и пробелы по краям в эту грамматику не попадают.
Какие символы выглядят как пробел
Обозначение | Символ в нашем наборе |
|---|---|
U+0020 | Обычный пробел, в Unicode он называется SPACE; встречается в строке R01. |
U+00A0 | Неразрывный пробел, NO-BREAK SPACE; R02. |
U+202F | Узкий неразрывный пробел, NARROW NO-BREAK SPACE; R03. |

Функция unicodedata.name возвращает назначенное имя символа; если имени нет, без аргумента default она возбуждает ValueError. В нашем наборе все три пробела имеют имена. По внешнему виду текста нельзя надёжно установить его код. В коде ниже буквальная запись \u00a0 задаёт U+00A0, а \u202f — U+202F. При выполнении каждая такая последовательность задаёт один символ строки.
Сначала проверка записи, потом удаление разделителей
Если сразу удалить все пробелы из «1 23,45», получится 123,45. На вид — нормальное число. Но исходная группа содержала две цифры вместо трёх, и операция это скрыла. Поэтому порядок действий другой: сначала проверяем, соответствует ли строка выбранной грамматике, и только потом удаляем разрешённые разделители.
ID и исходная строка | Результат по выбранному правилу |
|---|---|
R01: 1[U+0020]234,50 | 1234.50; ok |
R02: 2[U+00A0]345,00 | 2345.00; ok |
R03: 3[U+202F]000,25 | 3000.25; ok |
R04: 12,3,4 | None; review: две запятые. |
R05: 1[U+0020]23,45 | None; review: неверная длина группы. |
R06: пустая строка | None; missing: значение не задано. |
Обозначения в квадратных скобках помогают увидеть невидимые символы; в сами строки кода они не входят. None означает, что число не разобрано. Статусы review и missing мы храним отдельно: непонятная запись — это не отсутствующее значение, и ни то, ни другое не превращается в ноль.
Полный исполняемый пример Python
В документации re fullmatch проверяет соответствие всей строки. Decimal создаётся из подготовленного текста по документации decimal, без промежуточного float. Это небольшой разбор указанной грамматики, а не универсальный распознаватель всех форматов чисел.
from decimal import Decimal, localcontext
import re
raw = ["1 234,50", "2\u00a0345,00", "3\u202f000,25",
"12,3,4", "1 23,45", ""]
pattern = re.compile(
r"(?:[0-9]{1,9}|[0-9]{1,3}(?P<sep>[ \u00a0\u202f])"
r"[0-9]{3}(?:(?P=sep)[0-9]{3})?),[0-9]{2}"
)
def parse_number(text):
if text == "":
return None, "missing"
if pattern.fullmatch(text) is None:
return None, "review"
clean = text.translate({32: None, 160: None, 8239: None})
return Decimal(clean.replace(",", ".")), "ok"
parsed = [parse_number(text) for text in raw]
for n, (value, status) in enumerate(parsed, 1):
print(f"R{n:02d}: {value}; {status}")
with localcontext() as ctx:
ctx.prec = 28
total = sum((v for v, s in parsed if s == "ok"), Decimal("0"))
print("sum_ok =", total)
assert total == Decimal("6579.75")
assert [s for v, s in parsed] == ["ok", "ok", "ok", "review", "review", "missing"]
Вывод для шести строк:
R01: 1234.50; ok
R02: 2345.00; ok
R03: 3000.25; ok
R04: None; review
R05: None; review
R06: None; missing
sum_ok = 6579.75
Сверьте сумму и количество строк
Три распознанных значения дают 1234,50 + 2345,00 + 3000,25 = 6579,75 условной единицы. Это сумма только трёх корректно разобранных строк, а не всех шести исходных записей. По R04 и R05 нужно уточнение; R06 не содержит числа. Контроль статусов: 3 ok + 2 review + 1 missing = 6 строк.
Исходные строки оставляйте в поле raw, число — в value, а причину решения — в status. Проверка длины группы не доказывает, что само измерение верно: даже корректно записанное число может требовать содержательной проверки. Показанный код рассчитан только на заданные ограничения и строки; поддержку других типов, знаков и форматов нужно проектировать отдельно.
Что проверить в своём файле
- Какой знак считается десятичным и какие разделители групп допускает документация источника.
- Не являются ли значения идентификаторами, где пробел и ведущие нули могут иметь смысл.
- Сохранены ли исходные символы и все шесть записей.
- Не исправлены ли неизвестные разделители, буквы или единицы угадыванием.
- Указано ли, что сумма 6579,75 относится только к трём ok-строкам.
Эта проверка выполняется уже над текстом одного поля. Настройки чтения файла и сохранение ведущих нулей разобраны отдельно в материале об импорте CSV с десятичной запятой. Изменение разделителя полей CSV само по себе не проверяет внутренние группы тысяч в каждой ячейке.
Если для собственного исследовательского набора нужна помощь с правилами подготовки и проверкой расчёта, можно обсудить подготовку и проверку статистического анализа. Заранее согласуйте вопрос, состав исходных файлов и объём помощи. Автоматическое преобразование строк само по себе не подтверждает достоверность данных.