Preverite in spremenite omejitev rekurzije v programu Python (npr. sys.setrecursionlimit)

Posel

V Pythonu obstaja zgornja meja števila rekurzij (največje število rekurzij). Če želite izvesti rekurzivno funkcijo z velikim številom klicev, je treba omejitev spremeniti. Uporabite funkcije v modulu sys standardne knjižnice.

Število rekurzij je prav tako omejeno z velikostjo sklada. V nekaterih okoljih lahko z modulom resource standardne knjižnice spremenite največjo velikost sklada (delovalo je v Ubuntuju, ne pa v Windows ali Macu).

Tu so na voljo naslednje informacije.

  • Pridobi zgornjo mejo trenutnega števila ponovitev:sys.getrecursionlimit()
  • Spremeni zgornjo mejo števila rekurzij:sys.setrecursionlimit()
  • Spremeni največjo velikost sklada:resource.setrlimit()

Vzorčna koda teče v Ubuntuju.

Pridobite trenutno omejitev rekurzije: sys.getrecursionlimit()

Trenutno omejitev rekurzije lahko dobite z ukazom sys.getrecursionlimit().

import sys
import resource

print(sys.getrecursionlimit())
# 1000

V tem primeru je največje število ponovitev 1000, kar se lahko razlikuje glede na vaše okolje. Upoštevajte, da bo vir, ki ga uvažamo tukaj, uporabljen pozneje, vendar ne v sistemu Windows.

Kot primer bomo uporabili naslednjo preprosto rekurzivno funkcijo. Če je kot argument navedeno pozitivno celo število n, bo število klicev n-kratno.

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

Če poskušate izvesti rekurzijo nad zgornjo mejo, se sproži napaka (RecursionError).

recu_test(950)
# Finish

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

Upoštevajte, da vrednost, pridobljena s sys.getrecursionlimit(), ni največje število rekurzij, temveč največja globina sklada prevajalnika Python, zato se bo pojavila napaka (RecursionError), tudi če je število rekurzij nekoliko manjše od te vrednosti.

Meja rekurzije ni meja rekurzije, temveč največja globina sklada v prevajalniku 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

Sprememba omejitve rekurzije: sys.setrecursionlimit()

Zgornjo mejo števila rekurzij lahko spremenite z ukazom sys.setrecursionlimit(). Zgornja meja je navedena kot argument.

Omogoča izvajanje globlje rekurzije.

sys.setrecursionlimit(2000)

print(sys.getrecursionlimit())
# 2000

recu_test(1500)
# Finish

Če je določena zgornja meja premajhna ali prevelika, se pojavi napaka. Ta omejitev (zgornja in spodnja meja same meje) se spreminja glede na okolje.

Največja vrednost omejitve je odvisna od platforme. Če potrebujete globoko rekurzijo, lahko določite večjo vrednost v območju, ki ga podpira platforma, vendar se zavedajte, da bo ta vrednost povzročila sesutje, če bo prevelika.
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

Največje število rekurzij je prav tako omejeno z velikostjo sklada, kot je razloženo v nadaljevanju.

Sprememba največje velikosti sklada: resource.setrlimit()

Tudi če je v funkciji sys.setrecursionlimit() nastavljena velika vrednost, se morda ne bo izvedla, če je število ponovitev veliko. Napaka segmentacije se pojavi na naslednji način.

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

# recu_test(10 ** 5)
# Segmentation fault

V Pythonu lahko z modulom resource v standardni knjižnici spremenite največjo velikost sklada. Vendar je modul resource specifičen za Unix in ga ni mogoče uporabljati v operacijskem sistemu Windows.

S funkcijo resource.getrlimit() lahko dobite omejitev vira, ki je navedena v argumentu, kot tuple (mehka omejitev, trda omejitev). V tem primeru kot vir navedemo resource.RLIMIT_STACK, ki predstavlja največjo velikost sklada klicev trenutnega procesa.

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

V primeru je mehka meja 8388608 (8388608 B = 8192 KB = 8 MB), trda meja pa je -1 (neomejeno).

Omejitev vira lahko spremenite z resource.setrlimit(). Tukaj je tudi mehka omejitev nastavljena na -1 (brez omejitve). Uporabite lahko tudi konstanto resource.RLIM_INFINIT, ki predstavlja neomejeno omejitev.

Globoka rekurzija, ki je pred spremembo velikosti sklada ni bilo mogoče izvesti zaradi napake segmentacije, se zdaj lahko izvede.

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

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

recu_test(10 ** 5)
# Finish

V tem primeru je mehka meja za preprost poskus nastavljena na -1 (brez omejitve), vendar bi bilo v resnici varneje, če bi jo omejili na ustrezno vrednost.

Poleg tega se je ob poskusu nastavitve neomejene mehke omejitve tudi na mojem računalniku mac pojavila naslednja napaka.ValueError: not allowed to raise maximum limit
Zagon skripte s sudo ni pomagal. Morda ga omejuje sistem.

Proces z dejanskim UID superuporabnika lahko zahteva katero koli razumno omejitev, tudi brez omejitve.
Zahteva, ki presega omejitev, ki jo je določil sistem, pa bo še vedno povzročila napako ValueError.
resource.setrlimit() — Resource usage information — Python 3.10.0 Documentation

Sistem Windows nima modula virov, Mac pa zaradi sistemskih omejitev ni mogel spremeniti največje velikosti sklada. Če lahko na kakršen koli način povečamo velikost sklada, bi morali rešiti napako segmentacije, vendar tega nismo mogli potrditi.