python - तर्क अनपॅकिंग अपशिष्ट ढेर फ्रेम्स





recursion cpython python-internals (2)


अपवाद संदेश वास्तव में आपको एक संकेत प्रदान करता है। गैर-अनपॅकिंग विकल्प की तुलना करें:

>>> import sys
>>> sys.setrecursionlimit(4)  # to get there faster
>>> def f(): f()
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
  File "<stdin>", line 1, in f
  File "<stdin>", line 1, in f
RuntimeError: maximum recursion depth exceeded

साथ में:

>>> def f(): f(*())
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
  File "<stdin>", line 1, in f
RuntimeError: maximum recursion depth exceeded while calling a Python object

while calling a Python object के अतिरिक्त ध्यान दें। यह अपवाद PyObject_CallObject() फ़ंक्शन के लिए विशिष्ट है। जब आप एक अजीब रिकर्सन सीमा निर्धारित करते हैं तो आपको यह अपवाद नहीं दिखाई देगा:

>>> sys.setrecursionlimit(5)
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
  File "<stdin>", line 1, in f
RuntimeError: maximum recursion depth exceeded

क्योंकि यह PyEval_EvalFrameEx() के अंदर ceval.c फ्रेम मूल्यांकन कोड में उठाया गया विशिष्ट अपवाद है:

/* push frame */
if (Py_EnterRecursiveCall(""))
    return NULL;

वहां खाली संदेश ध्यान दें। यह एक महत्वपूर्ण अंतर है।

आपके 'नियमित' फ़ंक्शन (कोई परिवर्तनीय तर्क नहीं) के लिए, क्या होता है कि एक अनुकूलित पथ चुना जाता है; एक पाइथन फ़ंक्शन जिसे fast_function() या कीवर्ड तर्क fast_function() समर्थन की आवश्यकता नहीं है, मूल्यांकन लूप के fast_function() फ़ंक्शन में सीधे संभाला जाता है। फ़ंक्शन के लिए पायथन बाइटकोड ऑब्जेक्ट के साथ एक नया फ्रेमोबेट बनाया गया है, और चलाया गया है। यह एक रिकर्सन चेक है।

लेकिन परिवर्तनीय तर्क ( fast_function() या डिक्शनरी या दोनों) के साथ फ़ंक्शन कॉल के लिए, fast_function() कॉल का उपयोग नहीं किया जा सकता है। इसके बजाय, ext_do_call() (विस्तारित कॉल) का उपयोग किया जाता है, जो तर्क PyObject_Call() संभालता है, फिर फ़ंक्शन को आमंत्रित करने के लिए PyObject_Call() का उपयोग करता है। PyObject_Call() एक रिकर्सन सीमा जांच करता है, और फ़ंक्शन ऑब्जेक्ट को 'कॉल' करता है। फ़ंक्शन ऑब्जेक्ट को function_call() फ़ंक्शन के माध्यम से बुलाया जाता है, जो PyEval_EvalCodeEx() कॉल करता है , जो PyEval_EvalCodeEx() को कॉल करता है, जो दूसरी रिकर्सन सीमा जांच करता है।

टीएल; डीआर संस्करण

पाइथन फ़ंक्शन को कॉल करने वाले पायथन फ़ंक्शंस को अनुकूलित किया जाता है और PyObject_Call() सी-एपीआई फ़ंक्शन को बाईपास किया जाता है, जब तक कि तर्क PyObject_Call() न हो । पाइथन फ्रेम निष्पादन और PyObject_Call() दोनों रिकर्सन सीमा परीक्षण करते हैं, इसलिए PyObject_Call() को छोड़कर प्रति कॉल रिकर्सन सीमा जांच में वृद्धि से बचा जाता है।

'अतिरिक्त' रिकर्सन गहराई जांच के साथ और अधिक जगहें

आप Py_EnterRecursiveCall लिए पाइथन स्रोत कोड को अन्य स्थानों के लिए grep कर सकते हैं जहां रिकर्सन गहराई जांच की जाती है; json और pickle जैसे विभिन्न पुस्तकालयों का उपयोग पार्सिंग संरचनाओं से बचने के लिए किया जाता है जो कि बहुत गहराई से घिरे या पुनरावर्ती हैं, उदाहरण के लिए। अन्य चेक list में रखा गया है और __gt__ कार्यान्वयन, समृद्ध तुलना ( __gt__ , __lt__ , __eq__ , इत्यादि) __lt__ , __eq__ कॉल करने योग्य ऑब्जेक्ट हुक को संभालने और __call__ कॉल को संभालने के लिए।

इस प्रकार, आप रिकर्सन सीमा को अभी भी बहुत तेजी से हिट कर सकते हैं:

>>> class C:
...     def __str__(self):
...         global depth
...         depth += 1
...         return self()
...     def __call__(self):
...         global depth
...         depth += 1
...         return str(self)
... 
>>> depth = 0
>>> sys.setrecursionlimit(10)
>>> C()()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __call__
  File "<stdin>", line 5, in __str__
RuntimeError: maximum recursion depth exceeded while calling a Python object
>>> depth
2

जब तर्क को अनपॅक करके फ़ंक्शन कहा जाता है, तो यह दो बार रिकर्सन गहराई को बढ़ाता है। मैं जानना चाहता हूं कि ऐसा क्यों होता है।

सामान्य रूप से:

depth = 0

def f():
    global depth
    depth += 1
    f()

try:
    f()
except RuntimeError:
    print(depth)

#>>> 999

एक अनपॅकिंग कॉल के साथ:

depth = 0

def f():
    global depth
    depth += 1
    f(*())

try:
    f()
except RuntimeError:
    print(depth)

#>>> 500

सिद्धांत में दोनों को लगभग 1000 तक पहुंचना चाहिए:

import sys
sys.getrecursionlimit()
#>>> 1000

यह सीपीथन 2.7 और सीपीथन 3.3 पर होता है।

पीपीपी 2.7 और पीपीपी 3.3 पर एक अंतर है, लेकिन यह बहुत छोटा है (1480 बनाम 13 9 5 और 1526 बनाम 13 9 5)।

जैसा कि आप अलग-अलग हिस्सों से देख सकते हैं, कॉल के प्रकार के अलावा दोनों के बीच थोड़ा अंतर होता है ( CALL_FUNCTION बनाम CALL_FUNCTION_VAR ):

import dis
def f():
    f()

dis.dis(f)
#>>>  34           0 LOAD_GLOBAL              0 (f)
#>>>               3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
#>>>               6 POP_TOP
#>>>               7 LOAD_CONST               0 (None)
#>>>              10 RETURN_VALUE
def f():
    f(*())

dis.dis(f)
#>>>  47           0 LOAD_GLOBAL              0 (f)
#>>>               3 BUILD_TUPLE              0
#>>>               6 CALL_FUNCTION_VAR        0 (0 positional, 0 keyword pair)
#>>>               9 POP_TOP
#>>>              10 LOAD_CONST               0 (None)
#>>>              13 RETURN_VALUE



अपने कुछ अन्य रजिस्टरों को यह देखने के लिए देखें कि उनमें से एक में स्टैक पॉइंटर कैश किया गया है या नहीं। वहां से, आप एक ढेर को पुनः प्राप्त करने में सक्षम हो सकते हैं। इसके अलावा, अगर यह एम्बेडेड है, तो अक्सर एक विशेष पते पर ढेर को परिभाषित किया जाता है। इसका उपयोग करके, आप कभी-कभी सभ्य ढेर भी प्राप्त कर सकते हैं। यह सब मानते हैं कि जब आप हाइपरस्पेस पर कूद गए, तो आपके कार्यक्रम ने सभी मेमोरी को रास्ते में नहीं लगाया ...





python recursion cpython python-internals