Έλεγχος και αλλαγή του ορίου αναδρομής της Python (π.χ. sys.setrecursionlimit)

Επιχείρηση

Στην Python, υπάρχει ένα ανώτατο όριο στον αριθμό των αναδρομών (ο μέγιστος αριθμός αναδρομών). Για να εκτελέσετε μια αναδρομική συνάρτηση με μεγάλο αριθμό κλήσεων, είναι απαραίτητο να αλλάξετε το όριο. Χρησιμοποιήστε τις συναρτήσεις της ενότητας sys της τυπικής βιβλιοθήκης.

Ο αριθμός των αναδρομών περιορίζεται επίσης από το μέγεθος της στοίβας. Σε ορισμένα περιβάλλοντα, η ενότητα resource της τυπικής βιβλιοθήκης μπορεί να χρησιμοποιηθεί για να αλλάξει το μέγιστο μέγεθος στοίβας (λειτούργησε στο Ubuntu, αλλά όχι στα Windows ή στο mac).

Οι ακόλουθες πληροφορίες παρέχονται εδώ.

  • Λήψη του ανώτερου ορίου του τρέχοντος αριθμού αναδρομών:sys.getrecursionlimit()
  • Αλλαγή του ανώτερου ορίου του αριθμού των αναδρομών:sys.setrecursionlimit()
  • Αλλαγή του μέγιστου μεγέθους της στοίβας:resource.setrlimit()

Ο κώδικας του δείγματος τρέχει σε Ubuntu.

Λήψη του τρέχοντος ορίου αναδρομής: sys.getrecursionlimit()

Το τρέχον όριο αναδρομής μπορεί να ληφθεί με την sys.getrecursionlimit().

import sys
import resource

print(sys.getrecursionlimit())
# 1000

Στο παράδειγμα, ο μέγιστος αριθμός αναδρομών είναι 1000, ο οποίος μπορεί να διαφέρει ανάλογα με το περιβάλλον σας. Σημειώστε ότι ο πόρος που εισάγουμε εδώ θα χρησιμοποιηθεί αργότερα, αλλά όχι στα Windows.

Ως παράδειγμα, θα χρησιμοποιήσουμε την ακόλουθη απλή αναδρομική συνάρτηση. Αν ως όρισμα ορίζεται ένας θετικός ακέραιος n, ο αριθμός των κλήσεων θα είναι n φορές.

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

Ένα σφάλμα (RecursionError) θα προκύψει αν προσπαθήσετε να εκτελέσετε αναδρομή πάνω από το ανώτερο όριο.

recu_test(950)
# Finish

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

Σημειώστε ότι η τιμή που λαμβάνεται από την sys.getrecursionlimit() δεν είναι αυστηρά ο μέγιστος αριθμός αναδρομών, αλλά το μέγιστο βάθος στοίβας του διερμηνευτή της Python, οπότε ακόμη και αν ο αριθμός των αναδρομών είναι ελαφρώς μικρότερος από αυτή την τιμή, θα αναφερθεί σφάλμα (RecursionError).

Το όριο αναδρομής δεν είναι το όριο της αναδρομής, αλλά το μέγιστο βάθος της στοίβας του διερμηνέα της 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

Αλλαγή του ορίου αναδρομής: sys.setrecursionlimit()

Το ανώτερο όριο του αριθμού των αναδρομών μπορεί να αλλάξει με την sys.setrecursionlimit(). Το ανώτερο όριο καθορίζεται ως όρισμα.

Επιτρέπει την εκτέλεση βαθύτερης αναδρομής.

sys.setrecursionlimit(2000)

print(sys.getrecursionlimit())
# 2000

recu_test(1500)
# Finish

Εάν το καθορισμένο ανώτατο όριο είναι πολύ μικρό ή πολύ μεγάλο, θα εμφανιστεί σφάλμα. Αυτός ο περιορισμός (άνω και κάτω όρια του ίδιου του ορίου) ποικίλλει ανάλογα με το περιβάλλον.

Η μέγιστη τιμή του ορίου εξαρτάται από την πλατφόρμα. Αν χρειάζεστε βαθιά αναδρομή, μπορείτε να καθορίσετε μια μεγαλύτερη τιμή εντός του εύρους που υποστηρίζεται από την πλατφόρμα, αλλά να γνωρίζετε ότι αυτή η τιμή θα προκαλέσει κατάρρευση αν είναι πολύ μεγάλη.
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

Ο μέγιστος αριθμός αναδρομών περιορίζεται επίσης από το μέγεθος της στοίβας, όπως εξηγείται στη συνέχεια.

Αλλαγή του μέγιστου μεγέθους της στοίβας: resource.setrlimit()

Ακόμη και αν έχει οριστεί μια μεγάλη τιμή στη sys.setrecursionlimit(), ενδέχεται να μην εκτελεστεί αν ο αριθμός των αναδρομών είναι μεγάλος. Ένα σφάλμα τμηματοποίησης εμφανίζεται ως εξής.

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

# recu_test(10 ** 5)
# Segmentation fault

Στην Python, η ενότητα resource της τυπικής βιβλιοθήκης μπορεί να χρησιμοποιηθεί για την αλλαγή του μέγιστου μεγέθους στοίβας. Ωστόσο, η ενότητα resource είναι μια ειδική ενότητα για το Unix και δεν μπορεί να χρησιμοποιηθεί στα Windows.

Με την resource.getrlimit(), μπορείτε να λάβετε το όριο του πόρου που καθορίζεται στο όρισμα ως πλειάδα (soft limit, hard limit). Εδώ, καθορίζουμε ως πόρο τον resource.RLIMIT_STACK, ο οποίος αντιπροσωπεύει το μέγιστο μέγεθος της στοίβας κλήσεων της τρέχουσας διεργασίας.

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

Στο παράδειγμα, το μαλακό όριο είναι 8388608 (8388608 B = 8192 KB = 8 MB) και το σκληρό όριο είναι -1 (απεριόριστο).

Μπορείτε να αλλάξετε το όριο του πόρου με την resource.setrlimit(). Εδώ, το μαλακό όριο ορίζεται επίσης σε -1 (κανένα όριο). Μπορείτε επίσης να χρησιμοποιήσετε τη σταθερά resource.RLIM_INFINIT για να αναπαραστήσετε το απεριόριστο όριο.

Η βαθιά αναδρομή, η οποία δεν μπορούσε να εκτελεστεί λόγω σφάλματος τμηματοποίησης πριν από την αλλαγή του μεγέθους της στοίβας, μπορεί τώρα να εκτελεστεί.

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

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

recu_test(10 ** 5)
# Finish

Εδώ, το μαλακό όριο έχει οριστεί σε -1 (κανένα όριο) για ένα απλό πείραμα, αλλά στην πραγματικότητα, θα ήταν ασφαλέστερο να το περιορίσετε σε μια κατάλληλη τιμή.

Επιπλέον, όταν προσπάθησα να ορίσω ένα απεριόριστο μαλακό όριο και στο mac μου, εμφανίστηκε το ακόλουθο σφάλμα.ValueError: not allowed to raise maximum limit
Η εκτέλεση του σεναρίου με sudo δεν βοήθησε. Μπορεί να είναι περιορισμένο από το σύστημα.

Μια διεργασία με το πραγματικό UID ενός υπερ-χρήστη μπορεί να ζητήσει οποιοδήποτε λογικό όριο, συμπεριλαμβανομένου του μηδενικού ορίου.
Ωστόσο, μια αίτηση που υπερβαίνει το όριο που επιβάλλει το σύστημα θα εξακολουθήσει να οδηγεί σε ValueError.
resource.setrlimit() — Resource usage information — Python 3.10.0 Documentation

Τα Windows δεν διαθέτουν ενότητα πόρων και το mac δεν μπορούσε να αλλάξει το μέγιστο μέγεθος στοίβας λόγω περιορισμών του συστήματος. Αν μπορέσουμε να αυξήσουμε το μέγεθος της στοίβας με κάποιο τρόπο, θα πρέπει να μπορέσουμε να λύσουμε το σφάλμα τμηματοποίησης, αλλά δεν μπορέσαμε να το επιβεβαιώσουμε αυτό.