Both methods look like 'is this a number?' checks, but they follow Unicode rules, not int() rules. A real run on Python 3.13.5 shows where they differ.
4 min read
Python isdigit vs isnumeric: isdigit() returns True for digit characters, including superscripts like ², while isnumeric() also accepts fractions like ⅕, Roman numerals like Ⅻ and CJK numerals like 三. Neither one accepts '-5', '3.14' or ' 7'. If you want strings that int() can parse, isdecimal() is the closer match.
The Python docs define both methods through Unicode properties. str.isdigit() is True when every character has Numeric_Type Digit or Decimal and there is at least one character. str.isnumeric() is True when every character has Numeric_Type Digit, Decimal or Numeric. That extra Numeric group is where fractions, Roman numerals and many CJK numerals live. str.isdecimal() is the narrowest: it only accepts characters in the Unicode Nd category, which are the characters that can form base-10 numbers.
So every string that passes isdecimal() also passes isdigit(), and every string that passes isdigit() also passes isnumeric().
We ran this on Python 3.13.5:
| String | isdecimal() | isdigit() | isnumeric() |
|---|---|---|---|
| '123' | True | True | True |
| '٠١٢' (Arabic-Indic digits) | True | True | True |
| '²' (superscript two) | False | True | True |
| '⅕' (vulgar fraction) | False | False | True |
| 'Ⅻ' (Roman numeral twelve) | False | False | True |
| '三' (CJK three) | False | False | True |
| '-5' | False | False | False |
| '3.14' | False | False | False |
| ' 7' | False | False | False |
| '' (empty) | False | False | False |
| '12' (full-width digits) | True | True | True |
The minus sign, the decimal point and the space are not digits, so all three methods reject those strings. An empty string is always False.
int() parses base-10 digits from the Unicode Nd category, plus an optional sign and surrounding whitespace. A superscript two passes isdigit() but is not an Nd character, so int() rejects it. In our run, int('²') raised ValueError: invalid literal for int() with base 10: '²'. Meanwhile int('٠١٢') and int('12') both returned 12, because those are decimal digits in other scripts.
That is the main trap with isdigit(): it can say yes to a string that int() then refuses.
None of the three methods handles a sign, a decimal point or spaces. For real input, try the conversion and catch the error:
In our run, to_int('-5') returned -5, to_int(' 7') returned 7, and to_int('3.14') and to_int('²') returned None. Use float() the same way when decimals are allowed.
Combine two checks: s.isascii() and s.isdigit(). In our run that returned True for '12' and False for '١٢', which is useful when a form field or ID should only contain plain ASCII digits.
The unicodedata module exposes the values behind these methods. unicodedata.numeric('⅕') returned 0.2 and unicodedata.digit('²') returned 2 in our run. That is a good way to see why a character passes one method and fails another.
Which should I use to validate a number typed by a user?Usually neither. Call int() or float() inside try/except. If you only need to know whether int() will accept a string of plain digits, isdecimal() is the better match than isdigit().
Does bytes have isnumeric()?No. bytes has isdigit(), which checks ASCII 0 to 9, but it has no isnumeric() or isdecimal(). In our run b'123'.isdigit() returned True.
Is isnumeric() True for '½'?Yes. Vulgar fractions have Numeric_Type=Numeric, the same as '⅕' in the table.
Lab evidence
Facts checked on 26 Oct 2026 against the Python docs listed in Sources. The output table is from a real run of the snippet below on Python 3.13.5 on our Linux box, including the int() calls and the ValueError for the superscript two. We did not benchmark speed and make no performance claims here.
Explore the product
We may earn a commission
We ran three typescript-eslint presets on a real open-source TypeScript library and counted every finding and second. The jump to strict was smaller than the jump to type-aware linting, and the findings themselves tell you whether your team is ready.
Summary4 Nov 20267 min read
Both cut a string at a separator, but they fail in different ways. We ran them on Python 3.13.5 to show where partition() is safer and where split() is the right tool.
Summary2 Nov 20263 min read
What changes when you swap sort() for toSorted(), with a real 1-million-item Node.js benchmark and the support table.
Summary1 Nov 20263 min read