17
value = 'ad.41.bd'

if len(value) == len(value.strip({0,1,2,3,4,5,6,7,8,9})):
    # no numbers
else:
    # numbers present

There a cleaner way of detecting numbers in a string in Python?

7 Answers 7

19

What about this?

import re
if not re.search('\d+', value):
    # no numbers
else:
    # numbers present
Sign up to request clarification or add additional context in comments.

Comments

9
>>> value="ab3asdf"
>>> any(c.isdigit() for c in value)
True
>>> value="asf"
>>> any(c.isdigit() for c in value)
False




>>> value = 'ad.41.bd'
>>> any(map(lambda c:c.isdigit(),value))
True

EDIT:

>>> value="1"+"a"*10**6
>>> any(map(lambda c:c.isdigit(),value))
True
>>> from itertools import imap
>>> any(imap(lambda c:c.isdigit(),value))
True

map took 1 second (on old python) imap was instant because imap returns a generator. note often in the real world there is a higher probability of the number being at the end of the file name.

8 Comments

It would be interesting to see how good (or bad) this performs compared to a regex.
I like any, but map generates the entire list first. If value is huge and the first character is a digit, this code still processes the whole thing.
I believe any stops when it finds the first True. However map doesn't return a generator (apart from I think in python 3) so a whole list is created in memory (even if the first character is a digit). Otherwise I'm guessing it would be fairly similar.
@robert king, w/o the parens you create a list of methods. This will always evaluate to True! Try it with value = 'abc'.
any(c.isdigit() for c in value) uses a generator expression and dispenses with the lambda and the map.
|
4
from string import digits
def containsnumbers(value):
    return any(char in digits for char in value)

EDIT:

And just for thoroughness:

any(c.isdigit()):

>>> timeit.timeit('any(c.isdigit() for c in value)', setup='value = "abcd1"')
1.4080650806427002

any(c in digits):

>>> timeit.timeit('any(c in digits for c in value)', setup='from string import digits; value = "abcd1"')
1.392179012298584

re.search (1 or more digits):

>>> timeit.timeit("re.search('\d+', value)", setup='import re; value = "abcd1"')
1.8129329681396484

re.search (stop after one digit):

>>> timeit.timeit("re.search('\d', value)", setup='import re; value = "abcd1"')
1.599431037902832

re.match (non-greedy):

>>> timeit.timeit("re.match(r'^.*?\d', value)", setup='import re; value = "abcd1"')
1.6654980182647705

re.match(greedy):

>>> timeit.timeit("re.match(r'^.*\d', value)", setup='import re; value = "abcd1"')
1.5637178421020508

any(map()):

>>> timeit.timeit("any(map(lambda c:c.isdigit(),value))", setup='value = "abcd1"')
1.9165890216827393

any(imap()):

>>> timeit.timeit("any(imap(lambda c:c.isdigit(),value))", setup='from itertools import imap;value = "abcd1"')
1.370448112487793

Generally, the less complex regexps ran more quickly. c.isdigit() and c in digits are almost equivalent. re.match is slightly faster than re.search. map() is the slowest solution, but imap() was the fastest (but within rounding error of any(c.isdigit) and any(c in digits).

2 Comments

I played with your timings and found that as the value increases in length the regexes perform better and better.
@Steven: That seems reasonable as it shifts a lot of the work to C. I changed value to "abcd" * 1000 + "9" and decreased number to 10000. any(imap) took 7.588s, re.search (stop after first match) took 0.377s, and any(c in digits) took 4.283s. Rewriting the last as any(d in value for d in digits) took only .310s (and .073s when the last digit was "1" instead of "9"), again I suppose because it pushed most of the workload to off to core.
3

You can use a regular expression:

import re
# or if re.search(r'\d', value):
if re.match(r'^.*?\d', value):
    # numbers present
else:
    # no numbers

4 Comments

You don't need to anchor the expression with ^ and would probably be better off with re.search. Also, .* matches zero or more characters so ? is redundant.
@Kirk: I thought search might look for all locations that match this pattern (by I need only one), whereas match will only match at the beginning (so yes, I could omit ^ here). I want the shortest possible match, hence the .*? (not greedy).
Good points. After I've slept a little and had some coffee, I'll re-evaluate. :-)
search give only the first match.
1
if not any(c.isdigit() for c in value)
    # no numbers
else:
    # numbers present

Comments

1

To detect signs in the numbers, use the ? operator.

import re
if not re.search('-?\d+', value):
    # no numbers
else:
    # numbers present

Comments

0

If you want to know how big is the difference, you can use re.sub()

import re
digits_num = len(value) - len(re.sub(r'\d','',value))
if not digits_num:
    #without numbers
else:
    #with numbers - or elif digist_num == 3

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.