python - व्याख्यात्मक बंद कैसे काम करते हैं?




closures lazy-evaluation (6)

इसे देखो:

for f in flist:
    print f.func_closure


(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)

इसका मतलब है कि वे सभी एक ही वैरिएबल इंस्टेंस को इंगित करते हैं, जिसमें लूप खत्म होने के बाद 2 का मूल्य होगा।

एक पठनीय समाधान:

for i in xrange(3):
        def ffunc(i):
            def func(x): return x * i
            return func
        flist.append(ffunc(i))

जबकि मैं एक समस्या की जांच कर रहा था, मुझे जावास्क्रिप्ट कोड में लेक्सिकल क्लोजर के साथ था, मैं पाइथन में इस समस्या के साथ आया था:

flist = []

for i in xrange(3):
    def func(x): return x * i
    flist.append(func)

for f in flist:
    print f(2)

ध्यान दें कि यह उदाहरण दिमाग से lambda से बचाता है। यह "4 4 4" प्रिंट करता है, जो आश्चर्यजनक है। मैं उम्मीद करता हूं "0 2 4"।

यह बराबर पर्ल कोड सही है:

my @flist = ();

foreach my $i (0 .. 2)
{
    push(@flist, sub {$i * $_[0]});
}

foreach my $f (@flist)
{
    print $f->(2), "\n";
}

"0 2 4" मुद्रित है।

क्या आप अंतर को समझा सकते हैं?

अद्यतन करें:

समस्या वैश्विक नहीं है। यह वही व्यवहार प्रदर्शित करता है:

flist = []

def outer():
    for i in xrange(3):
        def inner(x): return x * i
        flist.append(inner)

outer()
#~ print i   # commented because it causes an error

for f in flist:
    print f(2)

जैसा कि टिप्पणी की गई पंक्ति दिखाती है, i उस बिंदु पर अज्ञात i । फिर भी, यह "4 4 4" प्रिंट करता है।


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

अपनी योजना उदाहरण के साथ अंतर डू लूप के अर्थशास्त्र के साथ करना है। स्कीम प्रभावी रूप से अन्य भाषाओं के साथ बाध्यकारी होने का उपयोग करने के बजाय, लूप के माध्यम से हर बार एक नया i चर बना रहा है। यदि आप लूप के बाहरी बाहरी बनाते हैं और इसे म्यूटेट करते हैं, तो आप योजना में वही व्यवहार देखेंगे। अपने लूप को इसके साथ बदलने का प्रयास करें:

(let ((ii 1)) (
  (do ((i 1 (+ 1 i)))
      ((>= i 4))
    (set! flist 
      (cons (lambda (x) (* ii x)) flist))
    (set! ii i))
))

इस बारे में कुछ और चर्चा के लिए here एक नज़र डालें।

[संपादित करें] संभवतः इसका वर्णन करने का एक बेहतर तरीका यह है कि डू लूप को मैक्रो के रूप में सोचें जो निम्न चरणों का पालन करता है:

  1. एक लैम्ब्डा को एक पैरामीटर (i) लेते हुए परिभाषित करें, लूप के शरीर द्वारा परिभाषित शरीर के साथ,
  2. उस पैराम्बा के रूप में उचित मूल्यों के साथ उस लैम्ब्डा का तत्काल कॉल।

अर्थात। नीचे पायथन के बराबर:

flist = []

def loop_body(i):      # extract body of the for loop to function
    def func(x): return x*i
    flist.append(func)

map(loop_body, xrange(3))  # for i in xrange(3): body

मैं अब माता-पिता के दायरे में से एक नहीं बल्कि अपने स्वयं के दायरे में एक नया नया चर (यानी लैम्ब्डा के पैरामीटर) में है और इसलिए आप जो व्यवहार देखते हैं उसे प्राप्त करते हैं। पाइथन में यह अंतर्निहित नया गुंजाइश नहीं है, इसलिए लूप का शरीर केवल i चर साझा करता है।


मैं अभी भी पूरी तरह से आश्वस्त नहीं हूं कि कुछ भाषाओं में यह एक तरह से काम करता है, और किसी अन्य तरीके से। सामान्य लिस्प में यह पाइथन की तरह है:

(defvar *flist* '())

(dotimes (i 3 t)
  (setf *flist* 
    (cons (lambda (x) (* x i)) *flist*)))

(dolist (f *flist*)  
  (format t "~a~%" (funcall f 2)))

प्रिंट "6 6 6" (ध्यान दें कि सूची 1 से 3 तक है, और रिवर्स में निर्मित है ")। योजना में रहते हुए यह पर्ल में काम करता है:

(define flist '())

(do ((i 1 (+ 1 i)))
    ((>= i 4))
  (set! flist 
    (cons (lambda (x) (* i x)) flist)))

(map 
  (lambda (f)
    (printf "~a~%" (f 2)))
  flist)

प्रिंट "6 4 2"

और जैसा कि मैंने पहले ही उल्लेख किया है, जावास्क्रिप्ट पाइथन / सीएल शिविर में है। ऐसा लगता है कि यहां एक कार्यान्वयन निर्णय है, जो अलग-अलग भाषाओं में अलग-अलग तरीकों से संपर्क करता है। मुझे समझना अच्छा लगेगा कि निर्णय क्या है।


यहां बताया गया है कि आप functools लाइब्रेरी का उपयोग करके इसे कैसे करते हैं (जो मुझे यकीन नहीं है कि सवाल functools उत्पन्न हुआ था)।

from functools import partial

flist = []

def func(i, x): return x * i

for i in xrange(3):
    flist.append(partial(func, i))

for f in flist:
    print f(2)

अपेक्षित के रूप में आउटपुट 0 2 4।


वेरिएबल i एक वैश्विक है, जिसका मूल्य प्रत्येक बार फंक्शन f कहा जाता है।

मैं आपके द्वारा अनुसरण किए जाने वाले व्यवहार को लागू करने के इच्छुक हूं:

>>> class f:
...  def __init__(self, multiplier): self.multiplier = multiplier
...  def __call__(self, multiplicand): return self.multiplier*multiplicand
... 
>>> flist = [f(i) for i in range(3)]
>>> [g(2) for g in flist]
[0, 2, 4]

आपके अपडेट का जवाब : यह प्रतिभा की वैश्विकता नहीं है जो इस व्यवहार को जन्म दे रहा है, यह तथ्य यह है कि यह एक संलग्न गुंजाइश से एक चर है जिसका समय एफ के नाम पर निश्चित मूल्य है। आपके दूसरे उदाहरण में, i का मूल्य kkk फ़ंक्शन के दायरे से लिया जाता है, और जब आप flist पर फ़ंक्शंस को कॉल करते हैं तो कुछ भी नहीं बदल रहा है।


व्यवहार के पीछे तर्क पहले से ही समझाया गया है, और कई समाधान पोस्ट किए गए हैं, लेकिन मुझे लगता है कि यह सबसे पायथनिक है (याद रखें, पाइथन में सबकुछ एक वस्तु है!):

flist = []

for i in xrange(3):
    def func(x): return x * func.i
    func.i=i
    flist.append(func)

for f in flist:
    print f(2)

क्लाउडियो का जवाब एक फ़ंक्शन जनरेटर का उपयोग करके बहुत अच्छा है, लेकिन पिरो का जवाब एक हैक है, ईमानदार होने के लिए, क्योंकि यह मुझे एक डिफ़ॉल्ट मूल्य के साथ "छुपा" तर्क में बना रहा है (यह ठीक काम करेगा, लेकिन यह "पायथनिक" नहीं है) ।





late-binding