¿Cuál es la profundidad de recursión máxima y cómo aumentarla?

9 minutos de lectura

Avatar de usuario de quantumSoup
sopa cuántica

Tengo esta función recursiva de cola aquí:

def recursive_function(n, sum):
    if n < 1:
        return sum
    else:
        return recursive_function(n-1, sum+n)

c = 998
print(recursive_function(c, 0))

funciona hasta n=997entonces simplemente se rompe y escupe un RecursionError: maximum recursion depth exceeded in comparison. ¿Es esto solo un desbordamiento de pila? ¿Hay alguna forma de evitarlo?

  • Ver también stackoverflow.com/questions/5061582/…

    – Tomas Ahle

    28 de abril de 2014 a las 19:09

  • memorización podría acelerar su función y aumentar su profundidad recursiva efectiva al hacer que los valores calculados previamente terminen en lugar de aumentar el tamaño de la pila.

    – Cyoce

    11 de enero de 2016 a las 18:47

  • El límite de recurrencia suele ser 1000.

    – Boris Verjovskiy

    24 de abril de 2019 a las 7:29

  • @tonix el intérprete agrega un marco de pila (el line <n>, in <module> en seguimientos de pila) y este código toma 2 marcos de pila para n=1 (porque el caso base es n < 1así que para n=1 todavía recurre). Y supongo que el límite de recurrencia no es inclusivo, ya que es “error cuando llegas a 1000” no “error si superas 1000 (1001)”. 997 + 2 es menos de 1000 por lo que funciona 998 + 2 no lo hace porque llega al límite.

    – Boris Verjovskiy

    15 dic 2019 a las 19:00

  • @tonix No. recursive_function(997) funciona, se rompe en 998. Cuando usted llama recursive_function(998) utiliza 999 marcos de pila y el intérprete agrega 1 marco (porque su código siempre se ejecuta como si fuera parte del módulo de nivel superior), lo que hace que alcance el límite de 1000.

    – Boris Verjovskiy

    15 de diciembre de 2019 a las 19:49

Avatar de usuario de Thomas Wouters
Tomás Wouters

Es una protección contra un desbordamiento de pila, sí. Python (o más bien, la implementación de CPython) no optimiza la recursividad final, y la recursividad desenfrenada provoca desbordamientos de pila. Puede comprobar el límite de recurrencia con sys.getrecursionlimit:

import sys
print(sys.getrecursionlimit())

y cambiar el límite de recurrencia con sys.setrecursionlimit:

sys.setrecursionlimit(1500)

pero hacerlo es peligroso: el límite estándar es un poco conservador, pero los stackframes de Python pueden ser bastante grandes.

Python no es un lenguaje funcional y la recursión de cola no es una técnica particularmente eficiente. Reescribir el algoritmo iterativamente, si es posible, generalmente es una mejor idea.

Avatar de usuario de David Young
david joven

Parece que solo necesitas establecer una mayor profundidad de recursión:

import sys
sys.setrecursionlimit(1500)

  • En mi caso, olvidé la declaración de devolución en el caso base y superó los 1000. Python comenzó a lanzar esta excepción y me sorprendió, porque estaba seguro del no. de pilas que va a crear para ejecutarlo.

    – vijayraj34

    19 de mayo de 2019 a las 8:07

  • sys.setrecursionlimit(50) o una pequeña cantidad es útil si su programa está entrando en recursividad y desea que el mensaje de error NO sea páginas y páginas del mismo texto. Encontré esto muy útil al depurar (mi) código recursivo incorrecto.

    – peawormsworth

    22 de febrero de 2020 a las 0:38

  • Tenga en cuenta que esto está sujeto a los límites dependientes de la plataforma

    – Sólido

    19 de febrero a las 20:36

Avatar de usuario de Eugene Yarmash
Eugene Yarmash

Si a menudo necesita cambiar el límite de recurrencia (por ejemplo, al resolver problemas de programación), puede definir un simple administrador de contexto como esto:

import sys

class recursionlimit:
    def __init__(self, limit):
        self.limit = limit

    def __enter__(self):
        self.old_limit = sys.getrecursionlimit()
        sys.setrecursionlimit(self.limit)

    def __exit__(self, type, value, tb):
        sys.setrecursionlimit(self.old_limit)

Luego, para llamar a una función con un límite personalizado, puede hacer:

with recursionlimit(1500):
    print(fib(1000, 0))

A la salida del cuerpo del with instrucción, el límite de recurrencia se restaurará al valor predeterminado.

PD: es posible que también desee aumentar el tamaño de la pila del proceso de Python para valores grandes del límite de recurrencia. Eso se puede hacer a través de la ulimit shell incorporado o limits.conf(5) archivo, por ejemplo.

  • También desea aumentar el límite de recurrencia del proceso con resource. Sin él, obtendrá una falla de segmentación y todo el proceso de Python fallará si setrecursionlimit demasiado alto e intente usar el nuevo límite (alrededor de 8 megabytes de marcos de pila, lo que se traduce en ~ 30,000 marcos de pila con la función simple anterior, en mi computadora portátil).

    – Boris Verjovskiy

    28 de diciembre de 2019 a las 19:35


  • @Boris: eso podría agregarse al administrador de contexto, sin embargo, aumentar el límite de tamaño de la pila solo funcionará para la raíz (superusuario).

    – Eugene Yarmash

    9 de noviembre de 2021 a las 21:41

Avatar de usuario de Scharron
Scharron

Es para evitar un desbordamiento de pila. El intérprete de Python limita la profundidad de la recursividad para ayudarlo a evitar recurrencias infinitas, lo que resulta en desbordamientos de pila. Intente aumentar el límite de recurrencia (sys.setrecursionlimit) o reescribiendo su código sin recursividad.

Desde el Documentación de Python:

sys.getrecursionlimit()

Devuelve el valor actual del límite de recurrencia, la profundidad máxima de la pila del intérprete de Python. Este límite evita que la recurrencia infinita provoque un desbordamiento de la pila C y bloquee Python. Se puede establecer por setrecursionlimit().

Ciro Santilli Avatar de usuario de OurBigBook.com
Ciro Santilli OurBigBook.com

resource.setrlimit también debe usarse para aumentar el tamaño de la pila y evitar fallas de segmento

El núcleo de Linux limita la pila de procesos.

Python almacena variables locales en la pila del intérprete, por lo que la recursividad ocupa espacio en la pila del intérprete.

Si el intérprete de Python intenta sobrepasar el límite de la pila, el kernel de Linux lo convierte en un error de segmentación.

El tamaño límite de la pila se controla con el getrlimit y setrlimit llamadas del sistema.

Python ofrece acceso a esas llamadas al sistema a través del resource módulo.

sys.setrecursionlimit mencionado, por ejemplo, en https://stackoverflow.com/a/3323013/895245 solo aumenta el límite que el propio intérprete de Python impone sobre su propio tamaño de pila, pero no toca el límite impuesto por el kernel de Linux en el proceso de Python.

Programa de ejemplo:

principal.py

import resource
import sys

print resource.getrlimit(resource.RLIMIT_STACK)
print sys.getrecursionlimit()
print

# Will segfault without this line.
resource.setrlimit(resource.RLIMIT_STACK, [0x10000000, resource.RLIM_INFINITY])
sys.setrecursionlimit(0x100000)

def f(i):
    print i
    sys.stdout.flush()
    f(i + 1)
f(0)

Por supuesto, si sigue aumentando setrlimitsu RAM eventualmente se agotará, lo que ralentizará su computadora debido a la locura de intercambio, o matará a Python a través del OOM Killer.

Desde bash, puede ver y establecer el límite de pila (en kb) con:

ulimit -s
ulimit -s 10000

El valor predeterminado para mí es 8Mb.

Ver también:

  • Configuración del tamaño de pila en un script de python
  • ¿Cuál es el límite estricto de recursividad para Linux, Mac y Windows?

Probado en Ubuntu 16.10, Python 2.7.12.

  • Intentando establecer rlimit_stack después Choque de pila las soluciones pueden resultar en fallas o problemas relacionados. Véase también Sombrero rojo Problema 1463241

    – jww

    21 de junio de 2017 a las 16:35

  • Utilicé esto (la parte de recursos de Python) para ayudar a mi implementación del algoritmo de Kosaraju en el conjunto de datos medio (enorme) del profesor Tim Roughgarden. Mi implementación funcionó en conjuntos pequeños, ciertamente el problema con un conjunto de datos grande era el límite de recursividad/apilado… ¿O no? Bueno, ¡sí lo fue! ¡Gracias!

    – nilo

    28 de enero de 2019 a las 8:04


  • Gracias Ciro gran respuesta detalles!

    – X0-usuario-0X

    26 de junio de 2022 a las 15:23

Avatar de usuario de Marcelo Cantos
marcelo cantos

Utilice un lenguaje que garantice la optimización de la cola de llamadas. O usa la iteración. Alternativamente, ponte lindo con decoradores.

  • Intentando establecer rlimit_stack después Choque de pila las soluciones pueden resultar en fallas o problemas relacionados. Véase también Sombrero rojo Problema 1463241

    – jww

    21 de junio de 2017 a las 16:35

  • Utilicé esto (la parte de recursos de Python) para ayudar a mi implementación del algoritmo de Kosaraju en el conjunto de datos medio (enorme) del profesor Tim Roughgarden. Mi implementación funcionó en conjuntos pequeños, ciertamente el problema con un conjunto de datos grande era el límite de recursividad/apilado… ¿O no? Bueno, ¡sí lo fue! ¡Gracias!

    – nilo

    28 de enero de 2019 a las 8:04


  • Gracias Ciro gran respuesta detalles!

    – X0-usuario-0X

    26 de junio de 2022 a las 15:23

Avatar de usuario de Daniel
Daniel

Me doy cuenta de que esta es una pregunta antigua, pero para aquellos que leen, recomendaría no usar la recursividad para problemas como este: las listas son mucho más rápidas y evitan la recursividad por completo. Yo implementaría esto como:

def fibonacci(n):
    f = [0,1,1]
    for i in xrange(3,n):
        f.append(f[i-1] + f[i-2])
    return 'The %.0fth fibonacci number is: %.0f' % (n,f[-1])

(Use n+1 en xrange si comienza a contar su secuencia de Fibonacci desde 0 en lugar de 1).

  • ¿Por qué usar el espacio O(n) cuando puedes usar O(1)?

    – Janus Troelsen

    12 de marzo de 2014 a las 9:11

  • En caso de que el comentario del espacio O(n) fuera confuso: no use una lista. La lista mantendrá todos los valores cuando todo lo que necesite sea el valor n. Un algoritmo simple sería mantener los dos últimos números de Fibonacci y sumarlos hasta llegar al que necesita. También hay mejores algoritmos.

    – milimétrico

    14/07/2014 a las 19:12

  • @Mathime: xrange se llama simplemente rangeen Python 3.

    – Eric O. Lebigot

    3 de agosto de 2016 a las 9:50

  • @EOL Soy consciente de esto

    – Mathime

    3 de agosto de 2016 a las 9:51

  • @Mathime Estaba haciendo las cosas explícitas para aquellos que leen estos comentarios.

    – Eric O. Lebigot

    3 de agosto de 2016 a las 9:54

¿Ha sido útil esta solución?