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.