Есть ли лучший способ найти, содержит ли строка цифры?

Я работаю со строками, которые содержат как цифры, так и буквенно-цифровые символы, или просто цифры, но не только альфы. Чтобы проверить наличие ложных совпадений, мне нужно проверить, содержит ли строки хотя бы одну цифру, печатая сообщение об ошибке, если это не так. Я использовал следующий код:

s = '0798237 sh 523-123-asdjlh'

def contains_digits(s):
    for char in list(s):
        if char.isdigit():
            return True
            break
    return False

if contains_digits(s) == True:
    print s
else:
    print 'Error'

Есть ли более пифонический или более простой способ сделать это, или этого достаточно? Кроме того, я не могу просто проверить, является ли строка буквенно-цифровой, потому что строка может содержать различные символы ('-', пробелы и т.д.)

Ответы

Ответ 1

Это одно из тех мест, где регулярное выражение - это просто:

_digits = re.compile('\d')
def contains_digits(d):
    return bool(_digits.search(d))

Маленькая демонстрация:

>>> _digits = re.compile('\d')
>>> def contains_digits(d):
...     return bool(_digits.search(d))
... 
>>> contains_digits('0798237 sh 523-123-asdjlh')
True
>>> contains_digits('sh asdjlh')
False

Вы можете использовать метод any с .isdigit(), как описано в @Wallacolloo answer, но это медленнее, чем простое регулярное выражение:

>>> import timeit
>>> timeit.timeit("contains_digits('0798237 sh 523-123-asdjlh')", 'from __main__ import contains_digits')
0.77181887626647949
>>> timeit.timeit("contains_digits_any('0798237 sh 523-123-asdjlh')", 'from __main__ import contains_digits_any')
1.7796030044555664

Метод if находится на одном уровне с регулярным выражением:

>>> timeit.timeit("contains_digits_if('0798237 sh 523-123-asdjlh')", 'from __main__ import contains_digits_if')
0.87261390686035156

Но все хуже, если цифры появляются в конце текста:

>>> timeit.timeit("contains_digits('asdjlhtaheoahueoaea 11 thou')", 'from __main__ import contains_digits')
1.202538013458252
>>> timeit.timeit("contains_digits_any('asdjlhtaheoahueoaea 11 thou')", 'from __main__ import contains_digits_any')
5.0348429679870605
>>> timeit.timeit("contains_digits_if('asdjlhtaheoahueoaea 11 thou')", 'from __main__ import contains_digits_if')
3.707183837890625

Сроки, проверенные на python 2.6 на Mac OS X 10.7.

Ответ 2

Используйте функцию any, проходящую в последовательности.
Если какой-либо элемент последовательности истинен (т.е. Есть цифра, в данном случае), то any возвращает значение True, иначе False. https://docs.python.org/library/functions.html#any

def contains_digits(s):
    return any(char.isdigit() for char in s)

Если вы обеспокоены производительностью, ваш текущий метод работает быстрее.

Ответ 3

Для тех, кто ищет более короткое решение: any(d in s for d in'0123456789')

Ответ 4

Прочитав обсуждение выше, мне было интересно узнать о производительности такой версии:

def contains_digit(s, digits=set('0123456789')):
    return bool(digits.intersection(s))

В моем тестировании это было немного быстрее в среднем, чем версия re на одном компьютере и немного медленнее на другом (?). Просто для удовольствия я сравнивал и другие версии.

import math
import re
import timeit


def contains_digit_set_intersection(s, digits=set('0123456789')):
    return bool(digits.intersection(s))


def contains_digit_iter_set(s, digits=set('0123456789')):
    for c in s:
        if c in digits:
            return True
    return False


def contains_digit_iter_str(s, digits='0123456789'):
    for c in s:
        if c in digits:
            return True
    return False


def contains_digit_re(s, digits=re.compile(r'\d')):
    return bool(digits.search(s))


def print_times(func, times):
    name = func.__name__
    average = sum(times) / len(times)
    formatted_times = ' '.join('{:.3f}'.format(t) for t in times)
    message = '{name:<31} {times} ~{average:.3f}'
    print(message.format(name=name, times=formatted_times, average=average))


funcs = [
    contains_digit_set_intersection,
    contains_digit_iter_set,
    contains_digit_iter_str,
    contains_digit_re,
]


cases = [
    '1bcdefg7',
    'abcdefg7',
    'abcdefgh',
    '0798237 sh 523-123-asdjlh',
    'asdjlhtaheoahueoaea 11 thou',
]


for func in funcs:
    times = []
    for case in cases:
        func_case = '{func.__name__}("{case}")'.format(func=func, case=case)
        time = timeit.timeit(func_case, globals={func.__name__: func})
        times.append(time)
    print_times(func, times)

Пример пробега для двух компьютеров (время для каждого случая и среднее значение):

contains_digit_set_intersection 0.744 0.731 0.724 1.227 1.113 ~0.908
contains_digit_iter_set         0.264 0.541 0.566 0.260 1.068 ~0.540
contains_digit_iter_str         0.272 0.649 0.632 0.274 1.211 ~0.607
contains_digit_re               0.748 0.854 0.679 0.744 1.006 ~0.806

contains_digit_set_intersection 0.860 0.870 0.855 1.456 1.357 ~1.080
contains_digit_iter_set         0.285 0.613 0.617 0.307 1.163 ~0.597
contains_digit_iter_str         0.295 0.748 0.799 0.288 1.595 ~0.745
contains_digit_re               1.157 1.236 0.927 1.086 1.450 ~1.171