Verificar e alterar o limite de recorrência Python (por exemplo, sys.setrecursionlimit)

O negócio

Em Python, há um limite superior para o número de recidivas (o número máximo de recidivas). Para executar uma função recursiva com um grande número de chamadas, é necessário alterar o limite. Utilizar as funções do módulo sys da biblioteca padrão.

O número de recidivas é também limitado pelo tamanho da pilha. Em alguns ambientes, o módulo de recursos da biblioteca padrão pode ser utilizado para alterar o tamanho máximo da pilha (funcionou no Ubuntu, mas não no Windows ou mac).

A seguinte informação é fornecida aqui.

  • Obtenha o limite superior do número actual de recidivas:sys.getrecursionlimit()
  • Alterar o limite superior do número de recidivas:sys.setrecursionlimit()
  • Alterar o tamanho máximo da pilha:resource.setrlimit()

O código da amostra está a correr no Ubuntu.

Obtenha o limite de recorrência actual: sys.getrecursionlimit()

O limite de recorrência actual pode ser obtido com sys.getrecursionlimit().

import sys
import resource

print(sys.getrecursionlimit())
# 1000

No exemplo, o número máximo de recorrências é de 1000, o que pode variar dependendo do seu ambiente. Note que o recurso que estamos a importar aqui será utilizado mais tarde, mas não no Windows.

Como exemplo, utilizaremos a seguinte função simples recursiva. Se um número inteiro positivo for especificado como um argumento, o número de chamadas será n vezes.

def recu_test(n):
    if n == 1:
        print('Finish')
        return
    recu_test(n - 1)

Um erro (RecursionError) será elevado se tentar realizar a repetição mais do que o limite superior.

recu_test(950)
# Finish

# recu_test(1500)
# RecursionError: maximum recursion depth exceeded in comparison

Note-se que o valor obtido por sys.getrecursionlimit() não é estritamente o número máximo de recorrências, mas a profundidade máxima da pilha do intérprete Python, portanto, mesmo que o número de recorrências seja ligeiramente inferior a este valor, um erro (RecursionError) será aumentado.

O limite de recorrência não é o limite de recorrência, mas a profundidade máxima da pilha do intérprete python.
python – Max recursion is not exactly what sys.getrecursionlimit() claims. How come? – Stack Overflow

# recu_test(995)
# RecursionError: maximum recursion depth exceeded while calling a Python object

Alterar limite de recursividade: sys.setrecursionlimit()

O limite superior do número de repetições pode ser alterado pelo sys.setrecursionlimit(). O limite superior é especificado como um argumento.

Permite uma recorrência mais profunda a ser realizada.

sys.setrecursionlimit(2000)

print(sys.getrecursionlimit())
# 2000

recu_test(1500)
# Finish

Se o limite superior especificado for demasiado pequeno ou demasiado grande, ocorrerá um erro. Esta restrição (limites superior e inferior do próprio limite) varia em função do ambiente.

O valor máximo do limite depende da plataforma. Se precisar de uma repetição profunda, pode especificar um valor maior dentro do intervalo suportado pela plataforma, mas tenha em conta que este valor causará uma queda se for demasiado grande.
If the new limit is too low at the current recursion depth, a RecursionError exception is raised.
sys.setrecursionlimit() — System-specific parameters and functions — Python 3.10.0 Documentation

sys.setrecursionlimit(4)
print(sys.getrecursionlimit())
# 4

# sys.setrecursionlimit(3)
# RecursionError: cannot set the recursion limit to 3 at the recursion depth 1: the limit is too low

sys.setrecursionlimit(10 ** 9)
print(sys.getrecursionlimit())
# 1000000000

# sys.setrecursionlimit(10 ** 10)
# OverflowError: signed integer is greater than maximum

O número máximo de repetições é também limitado pelo tamanho da pilha, como se explica a seguir.

Alterar o tamanho máximo da pilha: resource.setrlimit()

Mesmo que um grande valor seja definido em sys.setrecursionlimit(), pode não ser executado se o número de recidivas for grande. Uma falha de segmentação ocorre da seguinte forma.

sys.setrecursionlimit(10 ** 9)
print(sys.getrecursionlimit())
# 1000000000
recu_test(10 ** 4)
# Finish

# recu_test(10 ** 5)
# Segmentation fault

Em Python, o módulo de recursos na biblioteca padrão pode ser utilizado para alterar o tamanho máximo da pilha. No entanto, o módulo de recursos é um módulo específico do Unix e não pode ser utilizado em Windows.

Com resource.getrlimit(), pode obter o limite do recurso especificado no argumento como um tuple de (limite suave, limite duro). Aqui, especificamos resource.RLIMIT_STACK como o recurso, que representa o tamanho máximo da pilha de chamadas do processo actual.

print(resource.getrlimit(resource.RLIMIT_STACK))
# (8388608, -1)

No exemplo, o limite suave é 8388608 (8388608 B = 8192 KB = 8 MB) e o limite duro é -1 (ilimitado).

Pode alterar o limite do recurso com resource.setrlimit(). Aqui, o limite suave é também definido para -1 (sem limite). Também pode utilizar o recurso constante resource.RLIM_INFINIT para representar o limite ilimitado.

A recursividade profunda, que não podia ser realizada devido a falha de segmentação antes da mudança do tamanho da pilha, pode agora ser realizada.

resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))

print(resource.getrlimit(resource.RLIMIT_STACK))
# (-1, -1)

recu_test(10 ** 5)
# Finish

Aqui, o limite suave é fixado em -1 (sem limite) para uma simples experiência, mas na realidade, seria mais seguro limitá-lo a um valor apropriado.

Além disso, quando tentei estabelecer um limite suave ilimitado também no meu mac, ocorreu o seguinte erro.ValueError: not allowed to raise maximum limit
Executar o guião com o sudo não ajudou. Pode ser restringido pelo sistema.

Um processo com a UID eficaz de um superutilizador pode solicitar qualquer limite razoável, incluindo nenhum limite.
No entanto, um pedido que exceda o limite imposto pelo sistema ainda resultará num ValueError.
resource.setrlimit() — Resource usage information — Python 3.10.0 Documentation

O Windows não tem um módulo de recursos, e o Mac não conseguiu alterar o tamanho máximo da pilha devido a limitações do sistema. Se conseguirmos aumentar o tamanho da pilha por algum meio, devemos ser capazes de resolver a falha de segmentação, mas não fomos capazes de confirmar isto.