views:

141

answers:

4

I tried a couple of approaches, I am really only concerned with performance, not correctness. I noticed that the regex based implementation is about 3-4x slower than the one that uses type coercion. Is there another, more efficient way of doing this?

def IsNumber(x):
    try:
        _ = float(x)
    except ValueError:
        return False
    return True

 def IsNumber2(x):
     import re
     if re.match("^\d*.?\d*$", x) == None:
         return False
     return True

Thanks!

+2  A: 

Not really. Coercion is the accepted way to do this.

Ignacio Vazquez-Abrams
Yes, your method looks quite good. I'm not surprised at all that the regex method is slower. (for one thing it is importing re inside of the function).
Justin Peel
@Justin: Importing `re` the second time around is just a matter of copying a reference from `sys.modules`. The slow part is compiling the regex each time.
Ignacio Vazquez-Abrams
import statements do perform some undesirable locking. It is faster to keep them out of functions.
joeforker
thanks! moving the import outside does change things a little bit, but it's still very slow compared to the other function
fsm
+6  A: 

First of all, they're not doing the same thing. Floats can be specified as "1e3", for example, and float() will accept that. It's also not coercion, but conversion.

Secondly, don't import re in IsNumber2, especially if you're trying to use it with timeit. Do the import outside of the function.

Finally, it doesn't surprise me that float() is faster. It's a dedicated routine written in C for a very specific purpose, while regex must be converted into a form that's interpreted.

Is your first version, that uses float(), fast enough? It should be, and I don't know of a better way to do the same thing in Python.

Roger Pate
the first version is fast enough I guess, I was just curious. Thanks!
fsm
A: 

You might try compiling your regular expression first, but I'd imagine it would still be slower.

Also, if you want to know if your string is a number because you're going to do calculations with it, you'll have to coerce it anyway.

Seth
Compiling has only a tiny impact, they are cached. http://stackoverflow.com/questions/452104/is-it-worth-using-pythons-re-compile
Roger Pate
+2  A: 

The answer depends a lot on what you mean by 'numeric string'. If your definition of numeric string is 'anything that float accepts', then it's difficult to improve on the try-except method.

But bear in mind that float may be more liberal than you want it to be: on most machines, it'll accept strings representing infinities and nans. On my machine, it accepts 'nan(dead!$#parrot)', for example. It will also accept leading and trailing whitespace. And depending on your application, you may want to exclude exponential representations of floats. In these cases, using a regex would make sense. To just exclude infinities and nans, it might be quicker to use the try-except method and then use math.isnan and math.isinf to check the result of the conversion.

Writing a correct regex for numeric strings is a surprisingly error-prone task. Your IsNumber2 function accepts the string '.', for example. You can find a battle-tested version of a numeric-string regex in the decimal module source. Here it is (with some minor edits):

_parser = re.compile(r"""        # A numeric string consists of:
    (?P<sign>[-+])?              # an optional sign, followed by either...
    (
        (?=\d|\.\d)              # ...a number (with at least one digit)
        (?P<int>\d*)             # having a (possibly empty) integer part
        (\.(?P<frac>\d*))?       # followed by an optional fractional part
        (E(?P<exp>[-+]?\d+))?    # followed by an optional exponent, or...
    |
        Inf(inity)?              # ...an infinity, or...
    |
        (?P<signal>s)?           # ...an (optionally signaling)
        NaN                      # NaN
        (?P<diag>\d*)            # with (possibly empty) diagnostic info.
    )
    \Z
""", re.VERBOSE | re.IGNORECASE | re.UNICODE).match

This pretty much matches exactly what float accepts, except for the leading and trailing whitespace and some slight differences for nans (the extra 's' for signaling nans, and the diagnostic info). When I need a numeric regex, I usually start with this one and edit out the bits I don't need.

N.B. It's conceivable that float could be slower than a regex, since it not only has to parse the string, but also turn it into a float, which is quite an involved computation; it would still be a surprise if it were, though.

Mark Dickinson