2015-07-04 12 views
20

से अनपेक्षित उत्पादन मैं एक सूची है और एक lambda समारोहसूची (जनरेटर)

In [1]: i = lambda x: a[x] 
In [2]: alist = [(1, 2), (3, 4)] 

के रूप में परिभाषित किया गया है तो मैं एक सरल योग

सबसे पहले विधि गणना करने के दो अलग अलग तरीकों की कोशिश करो।

In [3]: [i(0) + i(1) for a in alist] 
Out[3]: [3, 7] 

दूसरी विधि।

In [4]: list(i(0) + i(1) for a in alist) 
Out[4]: [7, 7] 

दोनों परिणाम अप्रत्याशित रूप से अलग हैं। ऐसा क्यों हो रहा है?

+0

यह मेरे लिए त्रुटि दिखाता है। –

+0

@AvinashRaj दूसरी विधि को चलाने से पहले 'नाम त्रुटि: वैश्विक नाम' ए 'परिभाषित नहीं किया गया है' –

+1

आपकी समस्या है, पहली बार (3, 4) के रूप में परिभाषित किया जाता है, फिर सूची() फ़ंक्शन हमेशा लेता है 'a' – TheGeorgeous

उत्तर

15

यह व्यवहार अजगर 3. में तय किया गया है आप एक सूची समझ [i(0) + i(1) for a in alist] का उपयोग करते हैं तो आप अपने आसपास के दायरे जो i के लिए सुलभ है में a परिभाषित करेगा। एक नए सत्र में list(i(0) + i(1) for a in alist) त्रुटि फेंक देगा।

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> list(i(0) + i(1) for a in alist) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <genexpr> 
    File "<stdin>", line 1, in <lambda> 
NameError: global name 'a' is not defined 

एक सूची समझ एक जनरेटर नहीं है: Generator expressions and list comprehensions

Generator expressions are surrounded by parentheses (“()”) and list comprehensions are surrounded by square brackets (“[]”).

अपने उदाहरण list() के रूप में एक वर्ग चर का अपना ही गुंजाइश है और यह अधिक से अधिक वैश्विक चर तक पहुँच गया है में। जब आप इसका उपयोग करते हैं, i उस दायरे के अंदर a देखेंगे। नए सत्र में इस प्रयास करें:

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> [i(0) + i(1) for a in alist] 
[3, 7] 
>>> a 
(3, 4) 

यह करने के लिए यह तुलना एक और सत्र में:

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> l = (i(0) + i(1) for a in alist) 
<generator object <genexpr> at 0x10e60db90> 
>>> a 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
NameError: name 'a' is not defined 
>>> [x for x in l] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <genexpr> 
    File "<stdin>", line 1, in <lambda> 
NameError: global name 'a' is not defined 

जब आप list(i(0) + i(1) for a in alist) चलाने आप list वर्ग है जो यह यह करने के लिए कनवर्ट करने की कोशिश करेंगे के लिए एक जनरेटर (i(0) + i(1) for a in alist) पारित करेंगे सूची वापस करने से पहले अपने दायरे में एक सूची। इस जनरेटर के लिए जिसमें लैम्ब्डा फ़ंक्शन के अंदर कोई पहुंच नहीं है, परिवर्तनीय a का कोई अर्थ नहीं है।

जनरेटर ऑब्जेक्ट <generator object <genexpr> at 0x10e60db90> ने परिवर्तनीय नाम a खो दिया है। फिर जब list जनरेटर को कॉल करने का प्रयास करता है, तो लैम्ब्डा फ़ंक्शन अपरिभाषित a के लिए त्रुटि फेंक देगा।

जनरेटर के साथ विपरीत में सूची comprehensions का व्यवहार भी here उल्लेख किया:

List comprehensions also "leak" their loop variable into the surrounding scope. This will also change in Python 3.0, so that the semantic definition of a list comprehension in Python 3.0 will be equivalent to list(). Python 2.4 and beyond should issue a deprecation warning if a list comprehension's loop variable has the same name as a variable used in the immediately surrounding scope.

python3 में:

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> [i(0) + i(1) for a in alist] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <listcomp> 
    File "<stdin>", line 1, in <lambda> 
NameError: name 'a' is not defined 
+0

यह दोनों के लिए आउटपुट कैसे उत्पन्न करता है? –

+0

@AvinashRaj: पहले सूची समझ को चलाकर, 'ए' अभी भी '(3, 4)' tuple से जुड़ा हुआ है। –

1

a वैश्विक क्षेत्र में है। तो यह त्रुटि

समाधान देना चाहिए:

i = lambda a, x: a[x]

5

आप अपने लैम्ब्डा समारोह के लिए a एक पैरामीटर बनाना चाहिए।यह काम करता है के रूप में उम्मीद:

In [10]: alist = [(1, 2), (3, 4)] 

In [11]: i = lambda a, x: a[x] 

In [12]: [i(a, 0) + i(a, 1) for a in alist] 
Out[12]: [3, 7] 

In [13]: list(i(a, 0) + i(a, 1) for a in alist) 
Out[13]: [3, 7] 

एक वैकल्पिक तरीका प्राप्त करने के लिए एक ही परिणाम होगा:

In [14]: [sum(a) for a in alist] 
Out[14]: [3, 7] 

संपादित इस जवाब सिर्फ एक सरल समाधान नहीं है और सवाल का कोई वास्तविक जवाब नहीं है। मनाया प्रभाव थोड़ा अधिक जटिल है, मेरे other answer देखें।

1

[i(0) + i(1) for a in alist] के बाद निष्पादित किया गया है, a(3,4) बन जाता है।

फिर जब नीचे लाइन निष्पादित होने

list(i(0) + i(1) for a in alist) 

(3,4) मूल्य a के मूल्य के रूप में लैम्ब्डा समारोह i द्वारा दोनों समय प्रयोग किया जाता है, तो यह [7,7].

इसके बजाय आप अपने लैम्ब्डा परिभाषित करना चाहिए प्रिंट दो पैरामीटर a और x वाले फ़ंक्शन। यहाँ समझने के लिए

i = lambda a,x : a[x] 
5

महत्वपूर्ण बातें

  1. जनरेटर भाव समारोह बनाने वाले हैं आंतरिक वस्तुओं लेकिन सूची समझ नहीं होगा रहे हैं।

  2. वे दोनों लूप वैरिएबल को मानों से जोड़ देंगे और लूप वैरिएबल वर्तमान दायरे में होंगे यदि वे पहले से नहीं बनाए गए हैं।

जनरेटर अभिव्यक्ति

>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec')) 
    1   0 LOAD_CONST    0 (<code object <genexpr> at ...>) 
       3 MAKE_FUNCTION   0 
       6 LOAD_NAME    0 (alist) 
       9 GET_ITER    
      10 CALL_FUNCTION   1 
      13 POP_TOP    
      14 LOAD_CONST    1 (None) 
      17 RETURN_VALUE   

यह कोड वस्तु को लोड करता है और फिर इसे यह एक समारोह में आता है की बाइट कोड देखते हैं। आइए वास्तविक कोड ऑब्जेक्ट देखें।

>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec').co_consts[0]) 
    1   0 LOAD_FAST    0 (.0) 
     >> 3 FOR_ITER    27 (to 33) 
       6 STORE_FAST    1 (a) 
       9 LOAD_GLOBAL    0 (i) 
      12 LOAD_CONST    0 (0) 
      15 CALL_FUNCTION   1 
      18 LOAD_GLOBAL    0 (i) 
      21 LOAD_CONST    1 (1) 
      24 CALL_FUNCTION   1 
      27 BINARY_ADD   
      28 YIELD_VALUE   
      29 POP_TOP    
      30 JUMP_ABSOLUTE   3 
     >> 33 LOAD_CONST    2 (None) 
      36 RETURN_VALUE   

आप यहाँ देख के रूप में, पुनरावर्तक से वर्तमान मूल्य चर a में संग्रहित है। लेकिन चूंकि हम इसे एक फ़ंक्शन ऑब्जेक्ट बनाते हैं, इसलिए a बनाया गया केवल जनरेटर अभिव्यक्ति के भीतर दिखाई देगा।

लेकिन सूची समझ के मामले में,

>>> dis(compile('[i(0) + i(1) for a in alist]', 'string', 'exec')) 
    1   0 BUILD_LIST    0 
       3 LOAD_NAME    0 (alist) 
       6 GET_ITER    
     >> 7 FOR_ITER    28 (to 38) 
      10 STORE_NAME    1 (a) 
      13 LOAD_NAME    2 (i) 
      16 LOAD_CONST    0 (0) 
      19 CALL_FUNCTION   1 
      22 LOAD_NAME    2 (i) 
      25 LOAD_CONST    1 (1) 
      28 CALL_FUNCTION   1 
      31 BINARY_ADD   
      32 LIST_APPEND    2 
      35 JUMP_ABSOLUTE   7 
     >> 38 POP_TOP    
      39 LOAD_CONST    2 (None) 
      42 RETURN_VALUE   

कोई स्पष्ट समारोह रचना है और चर a वर्तमान क्षेत्र में बनाया जाता है। तो, a वर्तमान दायरे में लीक हो गया है।


इस समझ के साथ, आपकी समस्या से संपर्क करने दें।

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
अब

, जब आप समझ के साथ एक सूची बनाते हैं,

>>> [i(0) + i(1) for a in alist] 
[3, 7] 
>>> a 
(3, 4) 

आप देख सकते हैं कि a वर्तमान क्षेत्र में लीक हो जाता है और यह अभी भी यात्रा से अंतिम मान के लिए बाध्य है।

तो, जब आप सूची समझ के बाद जनरेटर अभिव्यक्ति को पुन: सक्रिय करते हैं, तो lambda फ़ंक्शन लीक a का उपयोग करता है। यही कारण है कि आप [7, 7] प्राप्त कर रहे हैं, क्योंकि a अभी भी (3, 4) से जुड़ा हुआ है।

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

नोट: उसी व्यवहार को पायथन 3.x में नहीं देखा जा सकता है, क्योंकि लीकिंग सूची समझ के लिए कार्यों को बनाकर भी रोका जाता है। आप इसके बारे में पाइथन ब्लॉग के इतिहास, From List Comprehensions to Generator Expressions के इतिहास में इसके बारे में अधिक पढ़ना चाहेंगे, जो कि ग्विडो द्वारा लिखे गए हैं।

2

एक कामकाज के लिए मेरा दूसरा जवाब देखें। लेकिन थोड़ा और सोचने के बारे में सोचते हुए, समस्या थोड़ा और जटिल लगती है। मुझे लगता है कि कई मुद्दों यहाँ पर जा रहे हैं:

  • जब आप i = lambda x: a[x] करते हैं, चर a एक पैरामीटर कार्य करने के लिए नहीं है, यह एक closure कहा जाता है। यह लैम्ब्डा अभिव्यक्तियों और सामान्य कार्य परिभाषाओं दोनों के लिए समान है।

  • पायथन स्पष्ट रूप से 'देर से बाध्यकारी' करता है, जिसका अर्थ है कि आपके द्वारा बंद किए गए चर के मूल्य को केवल उस क्षण देखा जाता है जब आप फ़ंक्शन को कॉल करते हैं। इससे various अप्रत्याशित results हो सकता है।

  • पायथन 2 में, सूची समझ के बीच एक अंतर है, जो उनके लूप वैरिएबल को रिसाव करता है, और जेनरेटर एक्सप्रेशन, जिसमें लूप वैरिएबल रिसाव नहीं करता है (विवरण के लिए this PEP देखें)। यह अंतर पायथन 3 में हटा दिया गया है, जहां एक सूची समझ list(generater_expression) के लिए एक शॉर्टकट है। मुझे यकीन नहीं है, लेकिन इसका शायद मतलब है कि पाइथन 2 सूची समझ उनके बाहरी दायरे में निष्पादित होती है, जबकि जनरेटर अभिव्यक्तियों और पायथन 3 सूची की समझें अपना आंतरिक क्षेत्र बनाते हैं।

प्रदर्शन (को Python2 में):

In [1]: def f(): # closes over a from global scope 
    ...:  return 2 * a 
    ...: 

In [2]: list(f() for a in range(5)) # does not find a in global scope 
[...] 
NameError: global name 'a' is not defined 

In [3]: [f() for a in range(5)] 
# executes in global scope, so f finds a. Also leaks a=8 
Out[3]: [0, 2, 4, 6, 8] 

In [4]: list(f() for a in range(5)) # finds a=8 in global scope 
Out[4]: [8, 8, 8, 8, 8] 

python3 में:

In [1]: def f(): 
    ...:  return 2 * a 
    ...: 

In [2]: list(f() for a in range(5)) 
# does not find a in global scope, does not leak a 
[...]  
NameError: name 'a' is not defined 

In [3]: [f() for a in range(5)] 
# does not find a in global scope, does not leak a 
[...] 
NameError: name 'a' is not defined 

In [4]: list(f() for a in range(5)) # a still undefined 
[...] 
NameError: name 'a' is not defined 
संबंधित मुद्दे