It's mostly about unicode classifications. Here's some examples to show discrepancies:
>>> def spam(s):
... for attr in 'isnumeric', 'isdecimal', 'isdigit':
... print(attr, getattr(s, attr)())
...
>>> spam('?')
isnumeric True
isdecimal False
isdigit False
>>> spam('3')
isnumeric True
isdecimal False
isdigit True
Specific behaviour is in the official docs here.
Script to find all of them:
import sys
import unicodedata
from collections import defaultdict
d = defaultdict(list)
for i in range(sys.maxunicode + 1):
s = chr(i)
t = s.isnumeric(), s.isdecimal(), s.isdigit()
if len(set(t)) == 2:
try:
name = unicodedata.name(s)
except ValueError:
name = f'codepoint{i}'
print(s, name)
d[t].append(s)
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…