Embora seja possível com regex, acho desnecessário. O loop que sugeri acima é bem mais simples, além de ser mais rápido. Fiz um teste básico pra verificar:
import string, re
from timeit import timeit
r = re.compile('[0-9]')
string_chain = '123@#$%456&*()789-09'
# executa 1 milhão de vezes cada teste
params = { 'number' : 1000000, 'globals': globals() }
# imprime os tempos em segundos
# todos retornam uma lista com os dígitos já convertidos para int
# loop simples
print(' loop:', timeit('list(int(c) for c in string_chain if c in string.digits)', **params))
# loop simples, usando um set com os dígitos (ligeiramente mais rápido)
digitos = set(string.digits)
print(' loop com set:', timeit('list(int(c) for c in string_chain if c in digitos)', **params))
# regex finditer (o mais lento)
print('regex finditer:', timeit('list(int(c[0]) for c in r.finditer(string_chain))', **params))
# regex findall (o segundo mais lento)
print(' regex findall:', timeit('list(int(c) for c in r.findall(string_chain))', **params))
Os tempos exatos variam de acordo com o hardware, mas de forma geral, regex foi o mais lento. Na minha máquina o resultado foi (tempos em segundos, quanto menor, mais rápido):
loop: 2.2100358860002416
loop com set: 1.788499561000208
regex finditer: 3.175833621000038
regex findall: 2.61522052600003
Claro que para poucas strings pequenas, a diferença será imperceptível. Mas se tiver milhões de strings para validar, começa a fazer diferença.