1

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!

4
  • The first way is right and useful. The second is indirect and has multiple subtle bugs. Commented Feb 11, 2010 at 22:29
  • @Mike Graham: Your comment sounds like an answer. Please delete the comment, post the answer, and we can upvote it properly.
    – S.Lott
    Commented Feb 11, 2010 at 22:42
  • Another bug in your regex: you need to escape that . before the ? unless you want 123z45 to be treated as "a number". Commented Feb 11, 2010 at 22:48
  • Also, using single backslashes to do regex escaping in a non-raw string is bad form (though it happens to work in this case). Either say \\d or use a raw string. Commented Feb 11, 2010 at 22:50

4 Answers 4

6

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.

1
  • the first version is fast enough I guess, I was just curious. Thanks!
    – fsm
    Commented Feb 11, 2010 at 22:32
2

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

4
  • 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). Commented Feb 11, 2010 at 22:25
  • @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. Commented Feb 11, 2010 at 22:26
  • import statements do perform some undesirable locking. It is faster to keep them out of functions.
    – joeforker
    Commented Feb 11, 2010 at 22:31
  • thanks! moving the import outside does change things a little bit, but it's still very slow compared to the other function
    – fsm
    Commented Feb 11, 2010 at 22:32
2

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.

0

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.

1

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.