एक लूप में कार्य करना


102

मैं एक लूप के अंदर कार्य बनाने की कोशिश कर रहा हूं:

functions = []

for i in range(3):
    def f():
        return i

    # alternatively: f = lambda: i

    functions.append(f)

समस्या यह है कि सभी कार्य एक ही होने का अंत करते हैं। 0, 1, और 2 वापस करने के बजाय, सभी तीन कार्य 2 वापस आते हैं:

print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output:   [2, 2, 2]

ऐसा क्यों हो रहा है, और मुझे 3 अलग-अलग कार्यों को करने के लिए क्या करना चाहिए जो क्रमशः 0, 1, और 2 आउटपुट करते हैं?


4
अपने आप को एक अनुस्मारक के रूप में: docs.python-guide.org/en/latest/writing/gotchas/…
Chuntao Lu

जवाबों:


167

आप देर से बाध्यकारी के साथ एक समस्या में चल रहे हैं - प्रत्येक फ़ंक्शन iयथासंभव देर से दिखता है (इस प्रकार, जब लूप के अंत के बाद कहा जाता है, तो इसे iसेट किया जाएगा 2)।

परिवर्तन: आसानी से जल्दी बाध्यकारी मजबूर कर तय def f():करने के लिए def f(i=i):इस तरह:

def f(i=i):
    return i

डिफ़ॉल्ट मान (दाहिने हाथ iमें i=iतर्क नाम के लिए एक डिफ़ॉल्ट मान है i, जो बाएं हाथ iमें है i=i) defसमय पर देखा जाता है, समय पर नहीं call, इसलिए अनिवार्य रूप से वे विशेष रूप से शुरुआती बंधन की तलाश करने का एक तरीका हैं।

यदि आप fएक अतिरिक्त तर्क प्राप्त करने के बारे में चिंतित हैं (और इस प्रकार संभवतः इसे गलत तरीके से कहा जा रहा है), तो एक और अधिक परिष्कृत तरीका है जो "फ़ंक्शन फ़ैक्टरी" के रूप में एक बंद का उपयोग करना शामिल है:

def make_f(i):
    def f():
        return i
    return f

और बयान के f = make_f(i)बजाय अपने पाश उपयोग मेंdef


7
आप इन चीजों को कैसे ठीक करना जानते हैं?
alwbtc

3
@alwbtc यह ज्यादातर सिर्फ अनुभव है, ज्यादातर लोगों ने अपने दम पर इन चीजों का सामना किया है।
रूहोला

क्या आप बता सकते हैं कि यह काम क्यों कर रहा है? (आप लूप में उत्पन्न कॉलबैक पर मुझे बचाते हैं, तर्क लूप के अंतिम भाग को धन्यवाद देते हैं, इसलिए धन्यवाद!)
विन्सेन्ट बेनेट

20

स्पष्टीकरण

यहां मुद्दा यह है कि iफ़ंक्शन fके निर्माण के समय का मान सहेजा नहीं जाता है। बल्कि, जब यह कहा जाता है fके मूल्य को देखता iहै ।

यदि आप इसके बारे में सोचते हैं, तो यह व्यवहार पूर्ण समझ में आता है। वास्तव में, यह एकमात्र उचित तरीका कार्य कर सकता है। कल्पना कीजिए कि आपके पास एक ऐसा फ़ंक्शन है जो वैश्विक चर को एक्सेस करता है, जैसे:

global_var = 'foo'

def my_function():
    print(global_var)

global_var = 'bar'
my_function()

जब आप इस कोड को पढ़ते हैं, तो आप निश्चित रूप से - "बार" प्रिंट करने की अपेक्षा करेंगे, न कि "फू", क्योंकि global_varफ़ंक्शन घोषित होने के बाद मूल्य बदल गया है। आपके अपने कोड में भी यही बात हो रही है: जब तक आप कॉल करते हैं f, तब तक मान iबदल गया है और सेट हो गया है2

समाधान

वास्तव में इस समस्या को हल करने के कई तरीके हैं। यहाँ कुछ विकल्प दिए गए हैं:

  • iडिफ़ॉल्ट तर्क के रूप में इसका उपयोग करके बाध्यकारी को जल्दी से बाध्य करें

    क्लोजर वैरिएबल्स (जैसे i) के विपरीत , फंक्शन को परिभाषित करने पर डिफ़ॉल्ट तर्कों का तुरंत मूल्यांकन किया जाता है:

    for i in range(3):
        def f(i=i):  # <- right here is the important bit
            return i
    
        functions.append(f)

    यह कैसे / क्यों काम करता है, इस बारे में थोड़ी जानकारी देने के लिए: फ़ंक्शन के डिफ़ॉल्ट तर्कों को फ़ंक्शन की विशेषता के रूप में संग्रहीत किया जाता है; इस प्रकार वर्तमान मूल्य का iस्नैपशॉट लिया और सहेजा गया है।

    >>> i = 0
    >>> def f(i=i):
    ...     pass
    >>> f.__defaults__  # this is where the current value of i is stored
    (0,)
    >>> # assigning a new value to i has no effect on the function's default arguments
    >>> i = 5
    >>> f.__defaults__
    (0,)
  • iएक क्लोजर में वर्तमान मूल्य पर कब्जा करने के लिए एक फ़ंक्शन फ़ैक्टरी का उपयोग करें

    आपकी समस्या की जड़ iएक परिवर्तनशील चर है। हम इस समस्या के इर्द-गिर्द एक और वैरिएबल बनाकर काम कर सकते हैं जो कभी नहीं बदलने की गारंटी है - और ऐसा करने का सबसे आसान तरीका एक बंद है :

    def f_factory(i):
        def f():
            return i  # i is now a *local* variable of f_factory and can't ever change
        return f
    
    for i in range(3):           
        f = f_factory(i)
        functions.append(f)
  • का प्रयोग करें functools.partialके वर्तमान मूल्य बाध्य करने iके लिएf

    functools.partialआपको किसी मौजूदा फ़ंक्शन में तर्क संलग्न करने देता है। एक तरह से, यह भी एक प्रकार का फ़ंक्शन कारखाना है।

    import functools
    
    def f(i):
        return i
    
    for i in range(3):    
        f_with_i = functools.partial(f, i)  # important: use a different variable than "f"
        functions.append(f_with_i)

चेतावनी: ये समाधान केवल काम आप यदि आवंटित चर करने के लिए एक नया मान। यदि आप चर में संग्रहित वस्तु को संशोधित करते हैं, तो आप फिर से उसी समस्या का अनुभव करेंगे:

>>> i = []  # instead of an int, i is now a *mutable* object
>>> def f(i=i):
...     print('i =', i)
...
>>> i.append(5)  # instead of *assigning* a new value to i, we're *mutating* it
>>> f()
i = [5]

ध्यान दें कि iहम इसे एक डिफ़ॉल्ट तर्क में बदल देने के बावजूद कैसे बदल गए हैं! यदि आपका कोड उत्परिवर्तित होता है i , तो आपको अपने फ़ंक्शन की एक प्रति बांधनी चाहिए i, जैसे:

  • def f(i=i.copy()):
  • f = f_factory(i.copy())
  • f_with_i = functools.partial(f, i.copy())
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.