पहले उपयोग के बाद पुन: असाइन किए जाने पर स्थानीय चर पर UnboundLocalError


208

निम्नलिखित कोड पाइथन 2.5 और 3.0 दोनों में अपेक्षित रूप से काम करता है:

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

हालांकि, जब मैं लाइन (बी) को अनकम्फर्ट करता हूं, तो मुझे एक UnboundLocalError: 'c' not assignedलाइन (ए) मिलती है । के मूल्यों aऔर bसही ढंग से मुद्रित कर रहे हैं। यह मुझे दो कारणों से पूरी तरह से चकित कर गया है:

  1. लाइन (बी) पर एक बाद के बयान के कारण लाइन (ए) पर रनटाइम त्रुटि क्यों होती है ?

  2. चरों aऔर bअपेक्षा के अनुसार मुद्रित क्यों किया जाता है, जबकि cएक त्रुटि होती है?

मैं केवल एक ही स्पष्टीकरण के साथ आ सकता हूं कि असाइनमेंट द्वारा एक स्थानीय चर cबनाया जाता है c+=1, जो cस्थानीय चर बनाने से पहले ही "वैश्विक" चर पर मिसाल कायम करता है। बेशक, यह एक चर के लिए समझ में नहीं आता है इससे पहले कि यह मौजूद है "चोरी" गुंजाइश।

क्या कोई इस व्यवहार की व्याख्या कर सकता है?

जवाबों:


216

पायथन भिन्न रूप से फ़ंक्शन में चर मानता है, भले ही आप फ़ंक्शन के अंदर या बाहर से उन्हें मान प्रदान करते हैं। यदि कोई चर किसी फ़ंक्शन के भीतर असाइन किया गया है, तो इसे स्थानीय चर के रूप में डिफ़ॉल्ट रूप से व्यवहार किया जाता है। इसलिए, जब आप उस लाइन को अनकंफर्ट करते हैं, जिसे आप स्थानीय वैरिएबल को रेफर करने की कोशिश कर रहे हैं, तो cइससे पहले कि कोई वैल्यू उसे सौंपी जाए।

यदि आप चाहते हैं कि वैरिएबल फ़ंक्शन से पहले असाइन किए गए cवैश्विक को देखेंc = 3

global c

फ़ंक्शन की पहली पंक्ति के रूप में।

अजगर 3 के लिए, अब है

nonlocal c

कि आप एक cचर है पास पास संलग्न समारोह गुंजाइश को संदर्भित करने के लिए उपयोग कर सकते हैं ।


3
धन्यवाद। त्वरित प्रश्न। क्या इसका मतलब यह है कि पायथन कार्यक्रम चलाने से पहले प्रत्येक चर का दायरा तय करता है? फंक्शन चलाने से पहले?
tba

7
चर गुंजाइश निर्णय कंपाइलर द्वारा किया जाता है, जो आम तौर पर एक बार चलता है जब आप पहली बार प्रोग्राम शुरू करते हैं। हालाँकि यह ध्यान में रखने योग्य है कि कंपाइलर बाद में भी चल सकता है यदि आपके प्रोग्राम में "eval" या "exec" स्टेटमेंट हैं।
ग्रेग हेविल

2
ठीक है धन्यवाद। मुझे लगता है कि "व्याख्या की गई भाषा" का मतलब उतना नहीं है जितना मैंने सोचा था।
tba

1
आह कि 'नॉनक्लॉक' कीवर्ड वास्तव में वही था जो मैं खोज रहा था, ऐसा लगता था कि पायथन इसे याद कर रहा था। संभवत: इस 'cascades' के माध्यम से प्रत्येक एन्कोडिंग स्कोप है जो इस कीवर्ड का उपयोग करके वेरिएबल को आयात करता है?
ब्रेंडन 20

6
@brainfsck: यह समझना सबसे आसान है कि क्या आप "लुक अप" और "असाइन" करने के बीच अंतर बनाते हैं। यदि लुकअप को वर्तमान स्कोप में नहीं पाया जाता है, तो लुकअप उच्च दायरे में वापस आ जाता है। असाइनमेंट हमेशा लोकल स्कोप में किया जाता है (जब तक आप ग्लोबल या नॉनक्लॉक असाइनमेंट का इस्तेमाल नहीं करते globalया nonlocalजबरदस्ती नहीं करते हैं )
स्टीवन

71

पाइथन थोड़ा अजीब है कि यह विभिन्न scopes के लिए एक शब्दकोश में सब कुछ रखता है। मूल a, b, c ऊपरवाले के दायरे में हैं और इसलिए उस ऊपरवाले शब्दकोश में। फ़ंक्शन का अपना शब्दकोश है। जब आप पहुंचते हैं print(a)और print(b)बयान देते हैं, तो शब्दकोश में उस नाम से कुछ भी नहीं होता है, इसलिए पायथन सूची को देखता है और उन्हें वैश्विक शब्दकोश में पाता है।

अब, हम c+=1, जो, के बराबर है c=c+1। जब पायथन उस रेखा को स्कैन करता है, तो वह कहता है, "अ, सी नाम का एक चर है, मैं इसे अपने स्थानीय दायरे के शब्दकोश में डालूंगा।" फिर जब यह असाइनमेंट के दाहिने हाथ की तरफ सी के लिए सी के लिए एक मूल्य की तलाश में जाता है, तो यह सी नाम के अपने स्थानीय चर को ढूंढता है , जिसका अभी तक कोई मूल्य नहीं है, और इसलिए त्रुटि फेंकता है।

global cऊपर उल्लिखित कथन बस पार्सर को बताता है कि यह cवैश्विक दायरे से उपयोग करता है और इसलिए इसे नए की आवश्यकता नहीं है।

इसका कारण यह है कि लाइन पर एक मुद्दा है जो ऐसा करता है क्योंकि यह प्रभावी रूप से कोड उत्पन्न करने से पहले नामों की तलाश कर रहा है, और इसलिए कुछ अर्थों में ऐसा नहीं लगता कि यह वास्तव में अभी तक उस लाइन को कर रहा है। मेरा तर्क है कि एक प्रयोज्य बग है, लेकिन यह आम तौर पर एक संकलक के संदेशों को बहुत गंभीरता से नहीं लेने के लिए सीखना एक अच्छा अभ्यास है ।

यदि यह कोई आराम है, तो मैंने शायद एक दिन खुदाई और इसी मुद्दे के साथ प्रयोग करने से पहले मैंने पाया कि गुइडो ने कुछ शब्दकोशों के बारे में लिखा था, जो सब कुछ समझाया।

अपडेट करें, टिप्पणियां देखें:

यह कोड को दो बार स्कैन नहीं करता है, लेकिन यह कोड को दो चरणों में स्कैन करता है, लेक्सिंग और पार्सिंग।

विचार करें कि कोड की इस पंक्ति का पार्स कैसे काम करता है। लेक्सर स्रोत पाठ को पढ़ता है और इसे व्याकरण के "सबसे छोटे घटकों" लेक्समेस में तोड़ता है। तो जब यह लाइन मारता है

c+=1

यह इसे कुछ इस तरह से तोड़ता है

SYMBOL(c) OPERATOR(+=) DIGIT(1)

पार्सर अंततः इसे एक पार्स ट्री में बनाना चाहता है और इसे निष्पादित करता है, लेकिन चूंकि यह एक असाइनमेंट है, ऐसा करने से पहले, यह स्थानीय शब्दकोश में सी नाम की तलाश करता है, इसे नहीं देखता है और इसे शब्दकोश में सम्मिलित करता है, अंकन करता है यह असंवैधानिक है। पूरी तरह से संकलित भाषा में, यह सिर्फ प्रतीक तालिका में जाएगा और पार्स की प्रतीक्षा करेगा, लेकिन चूंकि यह WON'T के पास एक दूसरे पास की लक्जरी नहीं है, इसलिए बाद में जीवन को आसान बनाने के लिए लेक्सर थोड़ा अतिरिक्त काम करता है। केवल, फिर यह OPERATOR देखता है, देखता है कि नियम कहते हैं "यदि आपके पास एक ऑपरेटर है = बाएं हाथ की तरफ प्रारंभिक किया गया है" और कहता है "वाह!"

यहाँ मुद्दा यह है कि यह वास्तव में अभी तक लाइन के पार्स को शुरू नहीं किया है । यह सभी वास्तविक पार्स के लिए तैयारी की तरह हो रहा है, इसलिए लाइन काउंटर अगली पंक्ति के लिए उन्नत नहीं है। इस प्रकार जब यह त्रुटि को इंगित करता है, तो यह अभी भी पिछली पंक्ति पर सोचता है।

जैसा कि मैं कहता हूं, आप तर्क कर सकते हैं कि यह एक बेकार बग है, लेकिन यह वास्तव में एक काफी सामान्य बात है। कुछ कंपाइलर इसके बारे में अधिक ईमानदार होते हैं और कहते हैं "एरर ऑन या अराउंड XXX", लेकिन यह नहीं है।


1
ठीक है आपकी प्रतिक्रिया के लिए धन्यवाद; यह अजगर में scopes के बारे में कुछ बातें मेरे लिए मंजूरी दे दी। हालांकि, मुझे अभी भी समझ में नहीं आया कि लाइन (बी) के बजाय लाइन (ए) में त्रुटि क्यों उठाई गई है। क्या पायथन प्रोग्राम को चलाने के लिए अपना वैरिएबल स्कोप शब्दकोश बनाता है?
tba

1
नहीं, यह अभिव्यक्ति के स्तर पर है। मैं जवाब में जोड़ दूंगा, मुझे नहीं लगता कि मैं इसे एक टिप्पणी में फिट कर सकता हूं।
चार्ली मार्टिन

2
कार्यान्वयन विवरण पर ध्यान दें: CPython में, स्थानीय स्कोप आमतौर पर एक के रूप में नहीं संभाला जाता है dict, यह आंतरिक रूप से सिर्फ एक सरणी है ( वापसी के लिए locals()पॉप्युलेट करेगा dict, लेकिन इसमें परिवर्तन नया नहीं बनाते हैं locals)। पार्स चरण एक स्थानीय को प्रत्येक असाइनमेंट ढूंढ रहा है और नाम से स्थिति में उस सरणी में परिवर्तित कर रहा है, और जब भी नाम संदर्भित हो, उस स्थिति का उपयोग कर रहा है। फ़ंक्शन में प्रवेश करने पर, गैर-तर्क स्थानीय लोगों को एक प्लेसहोल्डर से आरंभ किया जाता है, और UnboundLocalErrorऐसा तब होता है जब एक चर पढ़ा जाता है और इसके संबंधित सूचकांक में अभी भी प्लेसहोल्डर मूल्य होता है।
शैडो रेंजर

44

असावधानी पर एक नज़र डालने से स्पष्ट हो सकता है कि क्या हो रहा है:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

जैसा कि आप देख सकते हैं, एक है LOAD_FAST, और बी के लिए उपयोग करने के लिए bytecode LOAD_GLOBAL। ऐसा इसलिए है क्योंकि संकलक ने पहचान लिया है कि ए को फ़ंक्शन के भीतर सौंपा गया है, और इसे स्थानीय चर के रूप में वर्गीकृत किया गया है। स्थानीय लोगों के लिए अभिगम तंत्र ग्लोबल्स के लिए मौलिक रूप से अलग है - उन्हें सांख्यिकीय रूप से फ्रेम के चर तालिका में एक ऑफसेट सौंपा गया है, जिसका अर्थ है कि ग्लोबल्स के लिए अधिक महंगी तानाशाही के बजाय लुकअप एक त्वरित सूचकांक है। इस वजह से, पायथन print aलाइन को "स्थानीय चर का मान प्राप्त करें 'a' स्लॉट 0 में आयोजित किया गया है, और इसे प्रिंट करें" के रूप में पढ़ रहा है , और जब यह पता लगाता है कि यह चर अभी भी एकतरफा है, एक अपवाद उठाता है।


10

जब आप पारंपरिक वैश्विक चर शब्दार्थ को आज़माते हैं, तो पायथन का दिलचस्प व्यवहार होता है। मुझे विवरण याद नहीं है, लेकिन आप 'वैश्विक' क्षेत्र में घोषित एक चर के मूल्य को ठीक पढ़ सकते हैं, लेकिन यदि आप इसे संशोधित करना चाहते हैं, तो आपको globalकीवर्ड का उपयोग करना होगा । test()इसे बदलने का प्रयास करें:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

इसके अलावा, जिस कारण से आपको यह त्रुटि हो रही है, क्योंकि आप उस फ़ंक्शन के अंदर एक नया वैरिएबल भी घोषित कर सकते हैं, जिसका नाम 'ग्लोबल' है, और यह पूरी तरह से अलग होगा। दुभाषिया सोचता है कि आप इस दायरे में एक नया चर बनाने की कोशिश कर रहे हैं, जिसे बुलाया जाता है cऔर इसे एक ऑपरेशन में संशोधित किया जाता है , जिसे पायथन में अनुमति नहीं है क्योंकि यह नया cआरंभिक नहीं था।


आपकी प्रतिक्रिया के लिए धन्यवाद, लेकिन मुझे नहीं लगता कि यह बताता है कि त्रुटि को लाइन (ए) पर क्यों फेंक दिया जाता है, जहां मैं केवल एक चर प्रिंट करने की कोशिश कर रहा हूं। कार्यक्रम कभी भी लाइन (बी) में नहीं जाता है जहां यह संयुक्त राष्ट्र के आरंभिक चर को संशोधित करने की कोशिश कर रहा है।
tba

1
पाइथन कार्यक्रम को चलाने से पहले पूरे फ़ंक्शन को आंतरिक बाइटकोड में पढ़ेगा, पार्स करेगा और बदल देगा, इसलिए यह तथ्य कि "टर्न सी टू लोकल वैरिएबल" वैसा ही होता है जैसा कि वैल्यू की प्रिंटिंग के बाद होता है, जैसा कि यह था।
वेटिन

6

यह स्पष्ट करने वाला सबसे अच्छा उदाहरण है:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

कॉल करते समय foo(), यह भी उठता है, UnboundLocalError हालांकि हम कभी भी लाइन तक नहीं पहुंचेंगे bar=0, इसलिए तार्किक रूप से स्थानीय चर कभी नहीं बनाया जाना चाहिए।

यह रहस्य " पायथन एक व्याख्यात्मक भाषा है " में निहित है और फ़ंक्शन की घोषणा fooको एक एकल कथन (यानी एक यौगिक कथन) के रूप में व्याख्या की जाती है, यह सिर्फ इसे गूढ़ रूप से व्याख्या करता है और स्थानीय और वैश्विक स्कोप बनाता है। इसलिए barनिष्पादन से पहले स्थानीय दायरे में मान्यता प्राप्त है।

इस तरह के और अधिक उदाहरणों के लिए इस पोस्ट को पढ़ें: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

यह पोस्ट चर का पूर्ण विवरण और विश्लेषण प्रदान करता है:


5

यहाँ दो लिंक हैं जो मदद कर सकते हैं

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

लिंक एक त्रुटि UnboundLocalError का वर्णन करता है। लिंक दो आपके परीक्षण फ़ंक्शन को फिर से लिखने में मदद कर सकते हैं। लिंक दो के आधार पर, मूल समस्या को फिर से लिखा जा सकता है:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

4

यह आपके प्रश्न का सीधा जवाब नहीं है, लेकिन यह निकटता से संबंधित है, क्योंकि यह संवर्धित असाइनमेंट और फ़ंक्शन स्कोप के बीच संबंध के कारण एक और गेटा है।

ज्यादातर मामलों में, आप संवर्धित असाइनमेंट ( a += b) को साधारण असाइनमेंट ( a = a + b) के बराबर समझते हैं । एक कोने के मामले में, हालांकि इसके साथ कुछ परेशानी में आना संभव है। मुझे समझाने दो:

जिस तरह से पायथन का सरल असाइनमेंट काम करता है, aउसका मतलब है कि यदि किसी फंक्शन में पास किया जाता है (जैसे func(a); ध्यान दें कि पायथन हमेशा पास-बाय-रेफरेंस होता है), तो a = a + bवह संशोधित नहीं होगा aजो इसमें पास किया गया है। इसके बजाय, यह केवल स्थानीय पॉइंटर को संशोधित करेगा a

लेकिन अगर आप उपयोग करते हैं a += b, तो इसे कभी-कभी लागू किया जाता है:

a = a + b

या कभी-कभी (यदि विधि मौजूद है):

a.__iadd__(b)

पहले मामले में (जब तक aवैश्विक घोषित नहीं किया जाता है), स्थानीय दायरे के बाहर कोई दुष्प्रभाव नहीं हैं, क्योंकि असाइनमेंट aसिर्फ एक पॉइंटर अपडेट है।

दूसरे मामले में, aवास्तव में खुद को संशोधित करेगा, इसलिए सभी संदर्भ aसंशोधित संस्करण को इंगित करेंगे। यह निम्नलिखित कोड द्वारा प्रदर्शित किया जाता है:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

इसलिए ट्रिक फ़ंक्शन तर्कों पर संवर्धित असाइनमेंट से बचने के लिए है (मैं केवल स्थानीय / लूप वेरिएबल्स के लिए इसका उपयोग करने की कोशिश करता हूं)। सरल असाइनमेंट का उपयोग करें, और आप अस्पष्ट व्यवहार से सुरक्षित रहेंगे।


2

पायथन दुभाषिया पूर्ण इकाई के रूप में एक फ़ंक्शन पढ़ेगा। मुझे लगता है कि इसे दो पास में पढ़ने के रूप में, एक बार इसके बंद होने (स्थानीय चर) को इकट्ठा करने के लिए, फिर इसे बाइट-कोड में बदलने के लिए।

जैसा कि मुझे यकीन है कि आप पहले से ही अवगत थे, '=' के बाईं ओर इस्तेमाल किया गया कोई भी नाम एक स्थानीय चर है। एक से अधिक बार मुझे एक चर पहुंच को बदलकर + पर ले जाया गया है और यह अचानक एक भिन्न चर है।

मैं यह भी बताना चाहता था कि यह विशेष रूप से वैश्विक दायरे के साथ करने के लिए वास्तव में कुछ भी नहीं है। आपको नेस्टेड फ़ंक्शन के साथ समान व्यवहार मिलता है।


2

c+=1असाइन cकिए गए, अजगर मान लिया गया चर स्थानीय हैं, लेकिन इस मामले में इसे स्थानीय रूप से घोषित नहीं किया गया है।

या तो का उपयोग globalया nonlocalकीवर्ड।

nonlocal केवल अजगर 3 में काम करता है, इसलिए यदि आप अजगर 2 का उपयोग कर रहे हैं और अपने चर को वैश्विक नहीं बनाना चाहते हैं, तो आप एक परिवर्तनशील वस्तु का उपयोग कर सकते हैं:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()

1

कक्षा चर तक पहुंचने का सबसे अच्छा तरीका सीधे वर्ग के नाम से है

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1

0

अजगर में हमारे पास सभी प्रकार के चर स्थानीय, वर्ग चर और वैश्विक चर के लिए समान घोषणा है। जब आप विधि से वैश्विक चर का संदर्भ देते हैं, तो अजगर को लगता है कि आप वास्तव में उस विधि से चर का जिक्र कर रहे हैं जिसे अभी तक परिभाषित नहीं किया गया है और इसलिए त्रुटि फेंक देते हैं। वैश्विक चर को संदर्भित करने के लिए हमें ग्लोबल्स () ['वैरिएमनेम'] का उपयोग करना होगा।

आपके मामले में, क्रमशः, बी और सी के बजाय ग्लोबल्स () ['ए], ग्लोबल्स () [' बी '] और ग्लोबल्स () [(सी ’] का उपयोग करें।


0

वही समस्या मुझे परेशान करती है। का उपयोग करके nonlocalऔर globalसमस्या को हल कर सकते हैं।
हालांकि, के उपयोग के लिए आवश्यक ध्यान nonlocal, यह नेस्टेड कार्यों के लिए काम करता है। हालांकि, एक मॉड्यूल स्तर में, यह काम नहीं करता है। यहाँ उदाहरण देखें ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.