Python’s str.isdigit vs. str.isnumeric

February 17, 2019 . By Reuven

Let’s say that I want to write some Python code that invites the user to enter a number, and then prints that number, tripled. We could say:

>>> n = input("Enter a number: ")
>>> print(f"{n} * 3 = {n*3}")

The good news is that this code works just fine. The bad news is that it probably doesn’t do what you might expect. If I run this program, I’ll see:

Enter a number: 5
5 * 3 = 555

The reason for this output is that the “input” function always returns a string. So sure, we asked the user for a number, but we got the string ‘5’, rather than the integer 5. The ‘555’ output is thanks to the fact that you can multiply strings in Python by integers, getting a longer string back. So ‘a’ * 5 will give us ‘aaaaa’.

Of course, we can always create an integer from a string by applying the “int” class to the user’s input:

>>> n = input("Enter a number: ")
>>> n = int(n)
>>> print(f"{n} * 3 = {n*3}")

Sure enough, we get the following output:

Enter a number: 5
5 * 3 = 15

Great, right? But what happens if the user gives us something that’s no longer numeric? The program will blow up:

Enter a number: abcd

ValueError: invalid literal for int() with base 10: 'abcd'

Clearly, we want to avoid this problem. You could make a good argument that in this case, it’s probably best to run the conversion inside of a “try” block, and trap any exception that we might get.

But there’s another way to test this, one which I use with my into Python classes, before we’ve covered exceptions: Strings have a great method called “isdigit” that we can run, to tell us whether a string contains only digits (0-9), or if it contains something else. For example:

>>> '1234'.isdigit()
True

>>> '1234 '.isdigit() # space at the end
False

>>> '1234a'.isdigit() # letter at the end
False

>>> 'a1234'.isdigit() # letter at the start
False

>>> '12.34'.isdigit() # decimal point
False

>>> ''.isdigit() # empty string
False

If you know regular expressions, then you can see that str.isdigit returns True for ‘^\d+$’. Which can be very useful, as we can see here:

>>> n = input("Enter a number: ")
>>> if n.isdigit():
n = int(n)
print(f"{n} * 3 = {n*3}")

But wait: Python also includes another method, str.isnumeric. And it’s not at all obvious, at least at first, what the difference is between them, because they would seem to give the same results:

>>> n = input("Enter a number: ")
>>> if n.isnumeric():
n = int(n)
print(f"{n} * 3 = {n*3}")

So, what’s the difference? It’s actually pretty straightforward, but took some time for me to find out: Bascially, str.isdigit only returns True for what I said before, strings containing solely the digits 0-9.

By contrast, str.isnumeric returns True if it contains any numeric characters. When I first read this, I figured that it would mean decimal points and minus signs — but no! It’s just the digits 0-9, plus any character from another language that’s used in place of digits.

For example, we’re used to writing numbers with Arabic numerals. But there are other languages that traditionally use other characters. For example, in Chinese, we count 1, 2, 3, 4, 5 as 一,二,三,四, 五. It turns out that the Chinese characters for numbers will return False for str.isdigit, but True for str.isnumeric, behaving differently from their 0-9 counterparts:

>>> '12345'.isdigit()
True

>>> '12345'.isnumeric()
True

>>> '一二三四五'.isdigit()
False

>>> '一二三四五'.isnumeric()
True

So, which should you use? For most people, “isdigit” is probably a better choice, simply because it’s more clearly what you likely want. Of course, if you want to accept other types of numerals and numeric characters, then “isnumeric” is better. But if you’re interested in turning strings into integers, then you’re probably safer using “isdigit”, just in case someone tries to enter something else:

>>> int('二')
ValueError: invalid literal for int() with base 10: '二'

Just when I thought I was done with this, David Beazley reminded me that there’s a third method I should be dealing with: str.isdecimal. This asks a slightly different question, namely whether we have something which is a number but not a decimal number. What does this mean?

Well, in Python, we can describe “2 to the 2nd power” as “2 ** 2”. But if you want to print it, you’ll probably use something a bit fancier, such as 2². This is a two-character string, in which the first character is ‘2’ and the second character is ‘²’. The second one contains digits, but it’s not described as a decimal number. Thanks to Unicode, you can create such strings either via the keyboard or using Unicode-entry syntax. Thus:

>>> s = '2²'    # or if you prefer, s = '2' + '\u00B2'

>>> s.isdigit()
True

>>> s.isnumeric()
True

>>> s.isdecimal()
False

Most of us, most of the time, can thus use these three methods interchangeably, with little chance of being mixed up. Once you start using all sorts of interesting Unicode-based numbers, things can get a bit weird and interesting.

I tried to determine whether there was any difference in speed between these methods, just in case, but after numerous tests with “%timeit” in Jupyter, I found that I was getting roughly the same speeds from all methods.

If you’re like me, and ever wondered how a language that claims to have “one obvious way to do it” can have multiple seemingly identical methods… well, now you know!

Related Posts

Prepare yourself for a better career, with my new Python learning memberships

Prepare yourself for a better career, with my new Python learning memberships

I’m banned for life from advertising on Meta. Because I teach Python.

I’m banned for life from advertising on Meta. Because I teach Python.

Sharpen your Pandas skills with “Bamboo Weekly”

Sharpen your Pandas skills with “Bamboo Weekly”
  • Dave Rove says:

    As you’ve already pointed out, the unicode power-of-2 , ² , is recognized as a digit by str.isdigit, so str.isdigit is an unreliable test for a string that can be converted to an integer without crashing out. I suggest that you rewrite that test script to use str.isdecimal instead.

  • Thanks for the explanation. For me it wasn’t clear what isnumeric was about. The python help says:

    > isnumeric() method of builtins.str instance
    > Return True if the string is a numeric string, False otherwise.
    >
    > A string is numeric if all characters in the string are numeric and
    > there is at least one character in the string.

    As you said on the text, I was also thinking about things like ‘-1’, ‘+34’, ‘0xE’, but it gives everything false. So, your explanation says everything I needed to know.

    Best regards
    Josef

  • Thanks for the great explaining.

    Oshin

  • Anonymous says:

    I think there’s a small typo in your first code block describing isnumeric(): if n.numeric(): should be if n.isnumeric():

  • isdigit() is probably just reimplementing what the C library had to offer. That would make porting software from C easier.

    isnumeric() should be the way to go when writing from scratch, and I I agree that int() (and float()!) should accept those characters for conversion. What about opening a bug in https://bugs.python.org/?

  • Bob Beechey says:

    The defects of isdigit() – not dealing with leading/trailing spaces, negative numbers, decimal numbers etc can be avoided by this function I created for exactly tose reasons:

    def isnumber(strnum):
    ss = str(strnum).strip()
    if len(ss)==0:
    return false
    if ss[0]==’-‘ or ss[0] == ‘+’:
    ss = ss[1:]
    if ss.find(‘.’)==ss.rfind(‘.’):
    ss=ss.replace(‘.’,”)
    return ss.isdigit()

  • Nice tip, this type of code deserves try/catch

    • Right, I mentioned in the post that the more Pythonic way to do things is to trap an exception — but my students often don’t know about exceptions at the start of my courses, and I want a simpler way to test for input. That’s what led me to look into these methods.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >