नेस्टेड कार्यों में स्थानीय चर


105

ठीक है, इस पर मेरे साथ रहो, मुझे पता है कि यह बहुत बुरी तरह से समझा जा रहा है, लेकिन कृपया मुझे समझने में मदद करें कि क्या हो रहा है।

from functools import partial

class Cage(object):
    def __init__(self, animal):
        self.animal = animal

def gotimes(do_the_petting):
    do_the_petting()

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        cage = Cage(animal)

        def pet_function():
            print "Mary pets the " + cage.animal + "."

        yield (animal, partial(gotimes, pet_function))

funs = list(get_petters())

for name, f in funs:
    print name + ":", 
    f()

देता है:

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

तो मूल रूप से, मुझे तीन अलग-अलग जानवर क्यों नहीं मिल रहे हैं? cageनेस्टेड फ़ंक्शन के स्थानीय दायरे में 'पैक' नहीं है ? यदि नहीं, तो नेस्टेड फ़ंक्शन के लिए कॉल स्थानीय चर को कैसे दिखता है?

मुझे पता है कि इस तरह की समस्याओं में चलने का मतलब आमतौर पर एक 'गलत करना' होता है, लेकिन मैं समझना चाहता हूं कि क्या होता है।


1
प्रयास करें for animal in ['cat', 'dog', 'cow']... मुझे यकीन है कि किसी के साथ आ जाएगा हूँ और यद्यपि यह समझाने - यह उन अजगर पकड़ लिया है :) में से एक है
जॉन क्लेमेंट्स

जवाबों:


114

नेस्टेड फ़ंक्शन पेरेंट स्कोप से वैरिएबल दिखता है जब निष्पादित किया जाता है, न कि जब परिभाषित किया जाता है।

फ़ंक्शन बॉडी को संकलित किया गया है, और 'फ्री' वेरिएबल्स (असाइनमेंट द्वारा फ़ंक्शन में खुद को परिभाषित नहीं किया गया है) सत्यापित हैं, फिर प्रत्येक सेल को संदर्भित करने के लिए एक इंडेक्स का उपयोग करके कोड के साथ फ़ंक्शन को क्लोजर सेल के रूप में बाध्य किया गया है। pet_functionइस प्रकार एक मुक्त चर ( cage) है जो तब एक क्लोजर सेल, इंडेक्स 0. के माध्यम से संदर्भित होता है। क्लोजर ही फ़ंक्शन cageमें स्थानीय चर को इंगित get_pettersकरता है।

जब आप वास्तव में फ़ंक्शन को कॉल करते हैं, तो उस क्लोजर का उपयोग फ़ंक्शन को कॉल करने के समयcage आसपास के दायरे में मूल्य को देखने के लिए किया जाता है । यहाँ समस्या है। जब तक आप अपने कार्यों को कॉल करते हैं, तब तक फ़ंक्शन पहले से ही गणना कर लेता है, यह परिणाम है। कि निष्पादन के दौरान कुछ बिंदु पर स्थानीय चर में से प्रत्येक सौंपा गया था , और तार, लेकिन समारोह के अंत में, कि पिछले मान । इस प्रकार, जब आप गतिशील रूप से लौटाए गए प्रत्येक फ़ंक्शन को कॉल करते हैं, तो आपको मूल्य मुद्रित होता है।get_petterscage'cow''dog''cat'cage'cat''cat'

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

  • आंशिक फ़ंक्शन उदाहरण, का उपयोग कर functools.partial():

    from functools import partial
    
    def pet_function(cage=None):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage)))
  • एक नया स्कोप उदाहरण बनाना:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
  • कीवर्ड पैरामीटर के लिए चर को एक डिफ़ॉल्ट मान के रूप में बांधना:

    def pet_function(cage=cage):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, pet_function))

scoped_cageलूप में फ़ंक्शन को परिभाषित करने की आवश्यकता नहीं है , संकलन केवल एक बार होता है, लूप के प्रत्येक पुनरावृत्ति पर नहीं।


1
मैंने काम के लिए स्क्रिप्ट पर आज 3 घंटे के लिए इस दीवार पर अपना सिर पीटा। आपका अंतिम बिंदु बहुत महत्वपूर्ण है, और मुख्य कारण है कि मुझे इस समस्या का सामना करना पड़ा। मेरे पास अपने कोड में प्रचुर मात्रा में क्लोजर के साथ कॉलबैक है, लेकिन एक ही तकनीक को एक लूप में आजमाने से मुझे मिला है।
DrEsperanto

12

मेरी समझ यह है कि पैतृक फलन नाम स्थान में पिंजरे की तलाश तब की जाती है जब पैदावार पेट_फंक्शन वास्तव में कहा जाता है, पहले नहीं।

तो जब आप करते हैं

funs = list(get_petters())

आप 3 फ़ंक्शन उत्पन्न करते हैं जो अंतिम रूप से बनाए गए पिंजरे को ढूंढेंगे।

यदि आप अपने अंतिम लूप की जगह लेते हैं:

for name, f in get_petters():
    print name + ":", 
    f()

आपको वास्तव में मिलेगा:

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

6

यह निम्नलिखित से उपजा है

for i in range(2): 
    pass

print(i)  # prints 1

मान के पुनरावृत्ति के बाद iआलसी को उसके अंतिम मूल्य के रूप में संग्रहीत किया जाता है।

एक जनरेटर के रूप में समारोह काम करेगा (यानी बदले में प्रत्येक मान मुद्रण), लेकिन जब एक सूची में बदलने यह जनरेटर पर चलता है , इसलिए सभी कॉल cage( cage.animal) वापसी बिल्लियों।


0

चलिए सवाल को आसान करते हैं। निर्धारित करें:

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        def pet_function():
            return "Mary pets the " + animal + "."

        yield (animal, pet_function)

फिर, जैसे प्रश्न में, हम प्राप्त करते हैं:

>>> for name, f in list(get_petters()):
...     print(name + ":", f())

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

लेकिन अगर हम list()पहले बनाने से बचते हैं :

>>> for name, f in get_petters():
...     print(name + ":", f())

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

क्या चल रहा है? यह सूक्ष्म अंतर हमारे परिणामों को पूरी तरह से क्यों बदलता है?


यदि हम देखते हैं list(get_petters()), तो यह बदलते स्मृति पतों से स्पष्ट है कि हम वास्तव में तीन अलग-अलग कार्य करते हैं:

>>> list(get_petters())

[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
 ('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
 ('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]

हालाँकि, cellइन कार्यों के लिए बाध्य हैं:

>>> for _, f in list(get_petters()):
...     print(f(), f.__closure__)

Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)

>>> for _, f in get_petters():
...     print(f(), f.__closure__)

Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)

दोनों छोरों के लिए, cellऑब्जेक्ट पूरे पुनरावृत्तियों में समान रहता है। हालाँकि, उम्मीद के मुताबिक, यह विशिष्ट strसंदर्भ दूसरे लूप में भिन्न होता है। cellवस्तु को संदर्भित करता है animal, जो जब बनाई गई है get_petters()कहा जाता है। हालांकि, यह animalबदलता है कि जनरेटर फ़ंक्शन के रूप मेंstr यह किस ऑब्जेक्ट को संदर्भित करता है ।

पहले लूप में, प्रत्येक पुनरावृत्ति के दौरान, हम सभी को बनाते हैं f, लेकिन जनरेटर get_petters()के पूरी तरह से समाप्त होने के बाद ही हम उन्हें कॉल करते हैं और listपहले से ही एक फ़ंक्शन बनाया जाता है।

दूसरे लूप में, प्रत्येक पुनरावृत्ति के दौरान, हम get_petters()जनरेटर को रोक रहे हैं और fप्रत्येक ठहराव के बाद बुला रहे हैं । इस प्रकार, हम animalउस समय का मान पुनः प्राप्त करते हैं, जब जनरेटर फ़ंक्शन रोका जाता है।

@Claudiu एक ऐसे ही सवाल के जवाब में कहते हैं :

तीन अलग-अलग फ़ंक्शंस बनाए जाते हैं, लेकिन उनमें से प्रत्येक को उस वातावरण में बंद किया जाता है जिसे वे परिभाषित करते हैं - इस मामले में, वैश्विक वातावरण (या बाहरी फ़ंक्शन का वातावरण यदि लूप को किसी अन्य फ़ंक्शन के अंदर रखा जाता है)। यह वास्तव में समस्या है, हालांकि - इस वातावरण में, animalउत्परिवर्तित है, और क्लोजर सभी समान हैं animal

[संपादक नोट: iबदल दिया गया है animal।]

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