Python isdigit vs isdecimal vs isnumeric
Learn the differences between Python's `isdigit()`, `isdecimal()`, and `isnumeric()` methods for Unicode string validation.
Python's string methods isdigit(), isdecimal(), and isnumeric() are easy to confuse because they all test whether a string is made up of numeric characters. They apply different Unicode classification rules, so choosing the wrong method can cause subtle validation bugs. This article explains what each method accepts, shows concrete edge cases, and describes when to use each one.
What Each Method Checks
The three methods are instance methods on str that return True if every character in the string satisfies a specific Unicode property. The key difference lies in which Unicode character categories they accept.
str.isdecimal()returnsTrueonly for characters that are decimal digits, i.e., characters in the Unicode categoryNd(Number, Decimal Digit). This includes the ASCII digits0-9and the decimal digits of many other scripts, such as Arabic-Indic digits (e.g.,\u0660).str.isdigit()returnsTruefor characters that are decimal digits and also for characters that have the Unicode propertyNumeric_Type=DigitorNumeric_Type=Decimal. This includes superscript digits like²(\u00B2) and circled digits like①(\u2460).str.isnumeric()returnsTruefor any character that has the Unicode propertyNumeric_Type=Digit,Numeric_Type=Decimal, orNumeric_Type=Numeric. This includes fractions like½(\u00BD), Roman numerals, and other numeric symbols.
In practice, isdecimal() is the strictest, isnumeric() is the most permissive, and isdigit() sits in between.
Character Classification Rules
To understand the differences precisely, you need to know how Python maps these methods to Unicode character categories.
| Method | Unicode Categories Accepted | Example Characters |
|---|---|---|
isdecimal() | Nd (Decimal Digit) | 0-9, Arabic-Indic digits |
isdigit() | Nd plus characters with Numeric_Type=Digit | 0-9, superscripts, circled digits |
isnumeric() | Nd, Numeric_Type=Digit, Numeric_Type=Numeric | 0-9, fractions, Roman numerals |
These methods check individual characters only; they do not parse the string as a numeric value. They also return False for an empty string. For example, '123'.isdigit() is True, but '12.3'.isdigit() is False because the decimal point is not a digit.
Practical Examples With Edge Cases
Let's look at concrete examples to see how the methods behave with different strings.
samples = [ '123', # ASCII digits '١٢٣', # Arabic-Indic digits (U+0660-0669) '²', # Superscript two (U+00B2) '½', # Vulgar fraction one half (U+00BD) '①', # Circled digit one (U+2460) 'Ⅳ', # Roman numeral four (U+2163) 'abc', # Letters '12.3', # Decimal point ] for s in samples: print(f'{s!r}: isdecimal={s.isdecimal()}, isdigit={s.isdigit()}, isnumeric={s.isnumeric()}')
Output:
'123': isdecimal=True, isdigit=True, isnumeric=True
'١٢٣': isdecimal=True, isdigit=True, isnumeric=True
'²': isdecimal=False, isdigit=True, isnumeric=True
'½': isdecimal=False, isdigit=False, isnumeric=True
'①': isdecimal=False, isdigit=True, isnumeric=True
'Ⅳ': isdecimal=False, isdigit=False, isnumeric=True
'abc': isdecimal=False, isdigit=False, isnumeric=False
'12.3': isdecimal=False, isdigit=False, isnumeric=False
The superscript ² is considered a digit by isdigit() but not by isdecimal(). The fraction ½ is numeric but not a digit. Roman numerals are numeric but neither decimal nor digit.
Choosing the Right Method for Validation
Selecting the correct method depends on what kind of input you are validating.
- Use
isdecimal()when you need to accept only base-10 decimal digits. This is appropriate for plain integer form fields. It is stricter thanisdigit()and rejects superscripts, fractions, and Roman numerals.int()also accepts an optional sign and surrounding whitespace, so handle those separately if your input allows them. For example,int('²')raisesValueError, so usingisdecimal()prevents a later conversion failure. - Use
isdigit()when you want to accept digits that are not decimal, such as superscripts or circled digits. However, be aware thatint()will not accept these, so you may need to normalize them first. - Use
isnumeric()when you want to accept any Unicode numeric representation, including fractions and Roman numerals. This is rarely useful for direct numeric conversion but may be relevant for text analysis or classification tasks.
For anything else—such as signed numbers, decimals, or scientific notation—none of these methods are appropriate. Use a dedicated parsing function or a regular expression that matches the exact format you expect.
In most web form validation or API input handling, isdecimal() is the strictest of the three and is usually the right choice for plain integer fields. If your application must accept only ASCII 0-9, add an ASCII-only check or use a regular expression.
Performance and Runtime Considerations
All three methods are implemented in C and scan the string once, so the worst-case work is O(n), where n is the string length. For typical validation strings, the choice should be driven by correctness, not performance. Prefer these predicate methods over exception-based conversion when you only need to validate the character set, because they make the allowed input explicit and avoid try/except control flow.
Common Pitfalls and Misconceptions
A frequent mistake is assuming isdigit() is equivalent to checking for ASCII digits only. In Python 3, isdigit() returns True for many non-ASCII characters, as shown above. If you need to restrict input to ASCII 0-9, use a regular expression like ^[0-9]+$, or check the characters explicitly. For example, s and all(c in '0123456789' for c in s) is an ASCII-only check.
Another misconception is that these methods check whether the string represents a numeric value. They do not. For example, '-123'.isdigit() is False because of the minus sign, and '12.3'.isnumeric() is False because the decimal point is not numeric. If you need to validate a number that may have a sign or a decimal point, use a regular expression or try to convert with float() and catch ValueError.
Maintainability and Code Clarity
Using the right method makes your intent explicit. If you write s.isdecimal() in a validation function, the reader immediately knows you expect a decimal integer. If you use s.isnumeric(), the reader may wonder whether you intentionally accept fractions and Roman numerals. This clarity reduces the chance of future maintainers changing the behavior accidentally.
Consider wrapping the validation in a named function or a small helper to make the purpose even clearer:
def is_positive_integer(value: str) -> bool: return value.isdecimal() and int(value) > 0
This not only documents the requirement but also centralizes the logic if the rule changes later.
By understanding the precise Unicode semantics of isdecimal(), isdigit(), and isnumeric(), you can avoid subtle bugs and write validation code that behaves predictably across different locales and scripts.