Проверяем, что строка состоит из цифр на python
Ни так давно мне потребовалось проверить в одном месте, что строка состоит из цифр, к которым мы все так давно привыкли. На всякий случай напомню, как выглядят для меня всем привычные цифры. Вот так: 0123456789
.
В памяти возникли какие-то воспоминания про str.isdigit()
. «Ха, плевое дело!» – подумал я. Этим и воспользовался. Написал. Тесты проходят. Кажется, что все работает так, как нужно. Но как оказалось, что не все так просто…
В python, как в высокоуровневом языке, есть аж целых три метода для того, чтобы понять, что строка состоит из цифр: str.isdigit(), str.isnumeric() и str.isdecimal(). Подробнее, как это работает, разберем на примерах:
>>> '1'.isdigit()
True
>>> '𐩁'.isdigit()
True
>>> '٠'.isdigit()
True
>>> '⅕'.isdigit()
False
>>> '1a'.isdigit()
False
>>> '1'.isnumeric()
True
>>> '𐩁'.isnumeric()
True
>>> '٠'.isnumeric()
True
>>> '⅕'.isnumeric()
True
>>> '1a'.isnumeric()
False
>>> '1'.isdecimal()
True
>>> '𐩁'.isdecimal()
False
>>> '٠'.isdecimal()
True
>>> '⅕'.isdecimal()
False
>>> '1a'.isdecimal()
False
Да, казалось бы, что str.isdecimal()
– это почти то, что нам нужно, но положительный результат с символом U+2155 – явно не то, что в действительности хотелось бы нам увидеть. Видимо, придется двигаться в пользу небольшого самописного варианта.
Начнем с самого простого, что приходит в голову: пытаемся сразу преобразовать строку к числу, а при невозможности операции предполагаем, что это не число. Вот как это должно примерно выглядеть:
>>> def is_digit(value: str) -> bool:
... try:
... int(value)
... except ValueError:
... return False
... return True
...
>>> is_digit('1')
True
>>> is_digit('𐩁')
False
>>> is_digit('٠')
True
>>> is_digit('⅕')
False
>>> is_digit('1a')
False
Получилось то же самое, что и str.isdecimal()
, только кода в разы больше. Ну что, теперь настало время регулярных выражений:
>>> import re
>>> def is_digit(value: str) -> bool:
... return bool(re.match('^[0-9]+$', value))
...
>>> is_digit('1')
True
>>> is_digit('𐩁')
False
>>> is_digit('٠')
False
>>> is_digit('⅕')
False
>>> is_digit('1a')
False
Этот вариант – то, что нам нужно. В принципе, можно на нем остановиться. Единственное, что если вы испытываете к регулярным выражениям не самые теплые чувства, то можно переписать это без их использования:
>>> import string
>>> def is_digit(value: str) -> bool:
... return bool(value and all(s in string.digits for s in value))
...
>>> is_digit('1')
True
>>> is_digit('𐩁')
False
>>> is_digit('٠')
False
>>> is_digit('⅕')
False
>>> is_digit('1a')
False
Стало ли лучше? Возможно. Конечно, можно заморочиться, сравнить оба метода по скорости и после этого выбрать то, что быстрее. Но это точно не цель этой заметки. Оставим это на откуп пытливым умам и юным оптимизаторам.
От себя скажу лишь то, что по возможности лучше испльзовать встроенные методы или, если они не подходят, то стартаться писать понятный код и уж только потом, когда не хватает скорости, пробовать это переписать на другой язык. На этом перестаю капитанить и до скорых встреч в следующих заметках. 😉