How do I check if a string represents a number (float or int)?

Viewed 1618577

How do I check if a string represents a numeric value in Python?

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

The above works, but it seems clunky.


If what you are testing comes from user input, it is still a string even if it represents an int or a float. See How can I read inputs as numbers? for converting the input, and Asking the user for input until they give a valid response for ensuring that the input represents an int or float (or other requirements) before proceeding.

38 Answers

For non-negative (unsigned) integers only, use isdigit():

>>> a = "03523"
>>> a.isdigit()
True
>>> b = "963spam"
>>> b.isdigit()
False

Documentation for isdigit(): Python2, Python3

For Python 2 Unicode strings: isnumeric().

Which, not only is ugly and slow

I'd dispute both.

A regex or other string parsing method would be uglier and slower.

I'm not sure that anything much could be faster than the above. It calls the function and returns. Try/Catch doesn't introduce much overhead because the most common exception is caught without an extensive search of stack frames.

The issue is that any numeric conversion function has two kinds of results

  • A number, if the number is valid
  • A status code (e.g., via errno) or exception to show that no valid number could be parsed.

C (as an example) hacks around this a number of ways. Python lays it out clearly and explicitly.

I think your code for doing this is perfect.

Which, not only is ugly and slow, seems clunky.

It may take some getting used to, but this is the pythonic way of doing it. As has been already pointed out, the alternatives are worse. But there is one other advantage of doing things this way: polymorphism.

The central idea behind duck typing is that "if it walks and talks like a duck, then it's a duck." What if you decide that you need to subclass string so that you can change how you determine if something can be converted into a float? Or what if you decide to test some other object entirely? You can do these things without having to change the above code.

Other languages solve these problems by using interfaces. I'll save the analysis of which solution is better for another thread. The point, though, is that python is decidedly on the duck typing side of the equation, and you're probably going to have to get used to syntax like this if you plan on doing much programming in Python (but that doesn't mean you have to like it of course).

One other thing you might want to take into consideration: Python is pretty fast in throwing and catching exceptions compared to a lot of other languages (30x faster than .Net for instance). Heck, the language itself even throws exceptions to communicate non-exceptional, normal program conditions (every time you use a for loop). Thus, I wouldn't worry too much about the performance aspects of this code until you notice a significant problem.

Casting to float and catching ValueError is probably the fastest way, since float() is specifically meant for just that. Anything else that requires string parsing (regex, etc) will likely be slower due to the fact that it's not tuned for this operation. My $0.02.

So to put it all together, checking for Nan, infinity and complex numbers (it would seem they are specified with j, not i, i.e. 1+2j) it results in:

def is_number(s):
    try:
        n=str(float(s))
        if n == "nan" or n=="inf" or n=="-inf" : return False
    except ValueError:
        try:
            complex(s) # for complex
        except ValueError:
            return False
    return True

The input may be as follows:

a="50" b=50 c=50.1 d="50.1"


1-General input:

The input of this function can be everything!

Finds whether the given variable is numeric. Numeric strings consist of optional sign, any number of digits, optional decimal part and optional exponential part. Thus +0123.45e6 is a valid numeric value. Hexadecimal (e.g. 0xf4c3b00c) and binary (e.g. 0b10100111001) notation is not allowed.

is_numeric function

import ast
import numbers              
def is_numeric(obj):
    if isinstance(obj, numbers.Number):
        return True
    elif isinstance(obj, str):
        nodes = list(ast.walk(ast.parse(obj)))[1:]
        if not isinstance(nodes[0], ast.Expr):
            return False
        if not isinstance(nodes[-1], ast.Num):
            return False
        nodes = nodes[1:-1]
        for i in range(len(nodes)):
            #if used + or - in digit :
            if i % 2 == 0:
                if not isinstance(nodes[i], ast.UnaryOp):
                    return False
            else:
                if not isinstance(nodes[i], (ast.USub, ast.UAdd)):
                    return False
        return True
    else:
        return False

test:

>>> is_numeric("54")
True
>>> is_numeric("54.545")
True
>>> is_numeric("0x45")
True

is_float function

Finds whether the given variable is float. float strings consist of optional sign, any number of digits, ...

import ast

def is_float(obj):
    if isinstance(obj, float):
        return True
    if isinstance(obj, int):
        return False
    elif isinstance(obj, str):
        nodes = list(ast.walk(ast.parse(obj)))[1:]
        if not isinstance(nodes[0], ast.Expr):
            return False
        if not isinstance(nodes[-1], ast.Num):
            return False
        if not isinstance(nodes[-1].n, float):
            return False
        nodes = nodes[1:-1]
        for i in range(len(nodes)):
            if i % 2 == 0:
                if not isinstance(nodes[i], ast.UnaryOp):
                    return False
            else:
                if not isinstance(nodes[i], (ast.USub, ast.UAdd)):
                    return False
        return True
    else:
        return False

test:

>>> is_float("5.4")
True
>>> is_float("5")
False
>>> is_float(5)
False
>>> is_float("5")
False
>>> is_float("+5.4")
True

what is ast?


2- If you are confident that the variable content is String:

use str.isdigit() method

>>> a=454
>>> a.isdigit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'isdigit'
>>> a="454"
>>> a.isdigit()
True

3-Numerical input:

detect int value:

>>> isinstance("54", int)
False
>>> isinstance(54, int)
True
>>> 

detect float:

>>> isinstance("45.1", float)
False
>>> isinstance(45.1, float)
True

str.isnumeric()

Return True if all characters in the string are numeric characters, and there is at least one character, False otherwise. Numeric characters include digit characters, and all characters that have the Unicode numeric value property, e.g. U+2155, VULGAR FRACTION ONE FIFTH. Formally, numeric characters are those with the property value Numeric_Type=Digit, Numeric_Type=Decimal or Numeric_Type=Numeric.

str.isdecimal()

Return True if all characters in the string are decimal characters and there is at least one character, False otherwise. Decimal characters are those that can be used to form numbers in base 10, e.g. U+0660, ARABIC-INDIC DIGIT ZERO. Formally a decimal character is a character in the Unicode General Category “Nd”.

Both available for string types from Python 3.0.

In a most general case for a float, one would like to take care of integers and decimals. Let's take the string "1.1" as an example.

I would try one of the following:

1.> isnumeric()

word = "1.1"

"".join(word.split(".")).isnumeric()
>>> True

2.> isdigit()

word = "1.1"

"".join(word.split(".")).isdigit()
>>> True

3.> isdecimal()

word = "1.1"

"".join(word.split(".")).isdecimal()
>>> True

Speed:

► All the aforementioned methods have similar speeds.

%timeit "".join(word.split(".")).isnumeric()
>>> 257 ns ± 12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit "".join(word.split(".")).isdigit()
>>> 252 ns ± 11 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit "".join(word.split(".")).isdecimal()
>>> 244 ns ± 7.17 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

I think your solution is fine, but there is a correct regexp implementation.

There does seem to be a lot of regexp hate towards these answers which I think is unjustified, regexps can be reasonably clean and correct and fast. It really depends on what you're trying to do. The original question was how can you "check if a string can be represented as a number (float)" (as per your title). Presumably you would want to use the numeric/float value once you've checked that it's valid, in which case your try/except makes a lot of sense. But if, for some reason, you just want to validate that a string is a number then a regex also works fine, but it's hard to get correct. I think most of the regex answers so far, for example, do not properly parse strings without an integer part (such as ".7") which is a float as far as python is concerned. And that's slightly tricky to check for in a single regex where the fractional portion is not required. I've included two regex to show this.

It does raise the interesting question as to what a "number" is. Do you include "inf" which is valid as a float in python? Or do you include numbers that are "numbers" but maybe can't be represented in python (such as numbers that are larger than the float max).

There's also ambiguities in how you parse numbers. For example, what about "--20"? Is this a "number"? Is this a legal way to represent "20"? Python will let you do "var = --20" and set it to 20 (though really this is because it treats it as an expression), but float("--20") does not work.

Anyways, without more info, here's a regex that I believe covers all the ints and floats as python parses them.

# Doesn't properly handle floats missing the integer part, such as ".7"
SIMPLE_FLOAT_REGEXP = re.compile(r'^[-+]?[0-9]+\.?[0-9]+([eE][-+]?[0-9]+)?$')
# Example "-12.34E+56"      # sign (-)
                            #     integer (12)
                            #           mantissa (34)
                            #                    exponent (E+56)

# Should handle all floats
FLOAT_REGEXP = re.compile(r'^[-+]?([0-9]+|[0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?$')
# Example "-12.34E+56"      # sign (-)
                            #     integer (12)
                            #           OR
                            #             int/mantissa (12.34)
                            #                            exponent (E+56)

def is_float(str):
  return True if FLOAT_REGEXP.match(str) else False

Some example test values:

True  <- +42
True  <- +42.42
False <- +42.42.22
True  <- +42.42e22
True  <- +42.42E-22
False <- +42.42e-22.8
True  <- .42
False <- 42nope

Running the benchmarking code in @ron-reiter's answer shows that this regex is actually faster than the normal regex and is much faster at handling bad values than the exception, which makes some sense. Results:

check_regexp with good floats: 18.001921
check_regexp with bad floats: 17.861423
check_regexp with strings: 17.558862
check_correct_regexp with good floats: 11.04428
check_correct_regexp with bad floats: 8.71211
check_correct_regexp with strings: 8.144161
check_replace with good floats: 6.020597
check_replace with bad floats: 5.343049
check_replace with strings: 5.091642
check_exception with good floats: 5.201605
check_exception with bad floats: 23.921864
check_exception with strings: 23.755481

This code handles the exponents, floats, and integers, wihtout using regex.

return True if str1.lstrip('-').replace('.','',1).isdigit() or float(str1) else False
import re
def is_number(num):
    pattern = re.compile(r'^[-+]?[-0-9]\d*\.\d*|[-+]?\.?[0-9]\d*$')
    result = pattern.match(num)
    if result:
        return True
    else:
        return False


​>>>: is_number('1')
True

>>>: is_number('111')
True

>>>: is_number('11.1')
True

>>>: is_number('-11.1')
True

>>>: is_number('inf')
False

>>>: is_number('-inf')
False

Here's my simple way of doing it. Let's say that I'm looping through some strings and I want to add them to an array if they turn out to be numbers.

try:
    myvar.append( float(string_to_check) )
except:
    continue

Replace the myvar.apppend with whatever operation you want to do with the string if it turns out to be a number. The idea is to try to use a float() operation and use the returned error to determine whether or not the string is a number.

I also used the function you mentioned, but soon I notice that strings as "Nan", "Inf" and it's variation are considered as number. So I propose you improved version of your function, that will return false on those type of input and will not fail "1e3" variants:

def is_float(text):
    try:
        float(text)
        # check for nan/infinity etc.
        if text.isalpha():
            return False
        return True
    except ValueError:
        return False

User helper function:

def if_ok(fn, string):
  try:
    return fn(string)
  except Exception as e:
    return None

then

if_ok(int, my_str) or if_ok(float, my_str) or if_ok(complex, my_str)
is_number = lambda s: any([if_ok(fn, s) for fn in (int, float, complex)])
def is_float(s):
    if s is None:
        return False

    if len(s) == 0:
        return False

    digits_count = 0
    dots_count = 0
    signs_count = 0

    for c in s:
        if '0' <= c <= '9':
            digits_count += 1
        elif c == '.':
            dots_count += 1
        elif c == '-' or c == '+':
            signs_count += 1
        else:
            return False

    if digits_count == 0:
        return False

    if dots_count > 1:
        return False

    if signs_count > 1:
        return False

    return True

I know I'm late to the party, but figured out a solution which wasn't here: This solution follows the EAFP principle in Python

def get_number_from_string(value):
    try:
        int_value = int(value)
        return int_value

    except ValueError:
        return float(value)

Explanation:

If the value in the string is a float and I first try to parse it as an int, it will throw a ValueError. So, I catch that error and parse the value as float and return.

Sorry for the Zombie thread post - just wanted to round out the code for completeness...

# is_number() function - Uses re = regex library
# Should handle all normal and complex numbers
# Does not accept trailing spaces. 
# Note: accepts both engineering "j" and math "i" but only the imaginary part "+bi" of a complex number a+bi
# Also accepts inf or NaN
# Thanks to the earlier responders for most the regex fu

import re

ISNUM_REGEXP = re.compile(r'^[-+]?([0-9]+|[0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?[ij]?$')

def is_number(str):
#change order if you have a lot of NaN or inf to parse
    if ISNUM_REGEXP.match(str) or str == "NaN" or str == "inf": 
        return True 
    else:
        return False
# A couple test numbers
# +42.42e-42j
# -42.42E+42i

print('Is it a number?', is_number(input('Gimme any number: ')))

Gimme any number: +42.42e-42j

Is it a number? True

For my very simple and very common use-case: is this human written string with keyboard a number?

I read through most answers, and ended up with:

def isNumeric(string):
    result = True
    try:
        x = float(string)
       result = (x == x) and (x - 1 != x)
    except ValueError:
        result = False
    return result

It will return False for (+-)NaN and (+-)inf.

You can check it out here: https://trinket.io/python/ce32c0e54e

One fast and simple option is to check the data type:

def is_number(value):
    return type(value) in [int, float]

Or if you want to test if the values os a string are numeric:

def isNumber (value):
    return True if type(value) in [int, float] else str(value).replace('.','',1).isdigit()

tests:

>>> isNumber(1)
True

>>> isNumber(1/3)
True

>>> isNumber(1.3)
True

>>> isNumber('1.3')
True

>>> isNumber('s1.3')
False
Related