2012-11-13 15 views
24

मैं एक समारोह के हस्ताक्षर से एक चर को खत्म करने के बंद होने का उपयोग करने के कोशिश कर रहा हूँ (आवेदन सभी कार्यों शब्दकोश में मानकों का एक बड़ा सा संख्या नियंत्रित करने देता इंटरफेस के लिए क्यूटी संकेतों को जोड़ने के लिए आवश्यक लेखन कराने जा रहा है मूल्यों को स्टोर करता है)।अजगर लैम्ब्डा बंद scoping

मुझे समझ नहीं आता क्यों lambda एक और समारोह में लिपटे नहीं का उपयोग करने का मामला सभी मामलों के लिए अंतिम नाम देता है।

names = ['a', 'b', 'c'] 

def test_fun(name, x): 
    print(name, x) 

def gen_clousure(name): 
    return lambda x: test_fun(name, x) 

funcs1 = [gen_clousure(n) for n in names] 
funcs2 = [lambda x: test_fun(n, x) for n in names] 

# this is what I want 
In [88]: for f in funcs1: 
    ....:  f(1) 
a 1 
b 1 
c 1 

# I do not understand why I get this 
In [89]: for f in funcs2: 
    ....:  f(1) 
c 1 
c 1 
c 1 

उत्तर

44

कारण यह है कि बंद होने पर (लैम्बडा या अन्यथा) नामों पर बंद होते हैं, मान नहीं। जब आप lambda x: test_fun(n, x) को परिभाषित करते हैं, तो एन का मूल्यांकन नहीं किया जाता है, क्योंकि यह फ़ंक्शन के अंदर होता है। समारोह बुलाया है जब यह मूल्यांकन किया जाता है जो उस समय मान पाश से पिछले मूल्य नहीं होता है।

आप शुरुआत है कि आप "उपयोग बंद एक समारोह हस्ताक्षर से एक चर को खत्म करने की" करना चाहते हैं पर कहते हैं, लेकिन यह वास्तव में उस तरह से काम नहीं करता। (हालांकि, नीचे दिए गए देखें, जिस तरह से आप "खत्म" कर सकते हैं, उसके आधार पर आपको संतुष्ट कर सकते हैं।) फ़ंक्शन बॉडी के अंदर चर का मूल्यांकन नहीं किया जाएगा जब फ़ंक्शन परिभाषित किया जाता है। आदेश समारोह में एक चर के रूप में यह समारोह परिभाषा समय में मौजूद है "स्नैपशॉट" लेने के लिए पाने के लिए, आप चर एक तर्क के रूप गुजरना होगा। ऐसा करने का सामान्य तरीका यह है कि फ़ंक्शन को एक तर्क दें जिसका डिफ़ॉल्ट मान बाहरी दायरे से चर है। इन दो उदाहरणों के बीच अंतर को देखो: दूसरे उदाहरण में

>>> stuff = [lambda x: n+x for n in [1, 2, 3]] 
>>> for f in stuff: 
...  print f(1) 
4 
4 
4 
>>> stuff = [lambda x, n=n: n+x for n in [1, 2, 3]] 
>>> for f in stuff: 
...  print f(1) 
2 
3 
4 

, n कि कार्य करने के लिए एन के वर्तमान मूल्य "में ताले" समारोह के लिए एक तर्क के रूप गुजर। यदि आप इस तरह से मूल्य को लॉक करना चाहते हैं तो आपको ऐसा कुछ करना होगा। (यदि यह इस तरह से काम नहीं करता है, तो ग्लोबल वैरिएबल जैसी चीजें बिल्कुल काम नहीं करतीं; यह आवश्यक है कि उपयोग के समय फ्री वैरिएबल को देखा जाए।)

ध्यान दें कि इस व्यवहार के बारे में कुछ भी नहीं है lambdas के लिए विशिष्ट है । यदि आप 0xका उपयोग उस फ़ंक्शन को परिभाषित करने के लिए करते हैं जो संलग्न दायरे से संदर्भ चर का उपयोग करता है तो वही स्कॉइंग नियम प्रभावी होते हैं।

तुम सच में करने के लिए, आप अपने लौटे कार्य करने के लिए अतिरिक्त तर्क जोड़ने से बच सकते हैं, लेकिन इतना की तरह, ऐसा करने के लिए तो आप अभी तक एक समारोह में कहा कि समारोह लपेट चाहिए चाहते हैं:

>>> def makeFunc(n): 
...  return lambda x: x+n 
>>> stuff = [makeFunc(n) for n in [1, 2, 3]] 
>>> for f in stuff: 
...  print f(1) 
2 
3 
4 

यहाँ, आंतरिक लैम्ब्डा अभी भी n के मूल्य को देखता है जब इसे कहा जाता है। लेकिन n यह अब वैश्विक वैरिएबल नहीं है बल्कि संलग्न चर makeFunc के अंदर एक स्थानीय चर है। इस स्थानीय चर का एक नया मूल्य हर बार makeFunc कहा जाता है बन जाता है, और लौट आए लैम्ब्डा एक बंद बनाता है कि "बचाता है" स्थानीय चर मूल्य कि makeFunc की कि मंगलाचरण के लिए प्रभाव में था। इस प्रकार लूप में बनाए गए प्रत्येक फ़ंक्शन का अपना "निजी" चर होता है जिसे x कहा जाता है। (इस सरल मामले के लिए, यह भी बाहरी समारोह --- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]] के लिए एक लैम्ब्डा का उपयोग कर किया जा सकता है --- लेकिन यह कम पढ़ी जा सकती है।)

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

अपशॉट यह है कि एक ट्रेडऑफ है: आप फ़ंक्शन-निर्माण प्रक्रिया को सरल बना सकते हैं (यानी, दो नेस्टेड फ़ंक्शंस की आवश्यकता नहीं है), लेकिन फिर आपको परिणामस्वरूप फ़ंक्शन को थोड़ा और जटिल बनाना चाहिए (यानी, इसमें यह अतिरिक्त n=n तर्क)। या आप फ़ंक्शन को सरल बना सकते हैं (यानी, इसमें n= एन तर्क नहीं है), लेकिन फिर आपको फ़ंक्शन-निर्माण प्रक्रिया को और अधिक जटिल बनाना होगा (यानी, आपको तंत्र को लागू करने के लिए दो नेस्टेड फ़ंक्शंस की आवश्यकता है)।

+0

यह किसी भी प्रश्न के समान इन उत्तरों के मुकाबले इस पायथन व्यवहार का कहीं बेहतर स्पष्टीकरण है [https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures -capture/23557126) –

संबंधित मुद्दे