سؤال

في حين كان التحقيق مشكلة لدي مع إغلاق المعجمية في شفرة جافا سكريبت, لقد جاءت هذه المشكلة في بايثون:

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" مطبوع.

هل يمكنك أن تشرح الفرق ؟


تحديث:

المشكلة لا مع i يجري العالمية.يعرض هذا نفس السلوك:

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 هو معروف في تلك المرحلة.مازال يطبع "4 4 4".

هل كانت مفيدة؟

المحلول

بيثون هو في الواقع يتصرف كما عرفت. ثلاث وظائف يتم إنشاؤها ، ولكن كل لديهم إغلاق البيئة التي حددت في - في هذه الحالة ، البيئة العالمية (أو الخارجي الدالة البيئة حلقة يتم وضعها داخل وظيفة أخرى).هذا هو بالضبط المشكلة, على الرغم من ذلك - في هذه البيئة ، أنا هو تحور, و إغلاق جميع تشير إلى نفس أنا.

هنا هو أفضل حل يمكن أن يأتي مع إنشاء وظيفة creater و الاحتجاج أن بدلا من ذلك.هذا سوف يجبر بيئات مختلفة لكل من الوظائف التي تم إنشاؤها ، مختلفة أنا في كل واحد.

flist = []

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

for f in flist:
    print f(2)

هذا ما يحدث عند خلط الآثار الجانبية و البرمجة الوظيفية.

نصائح أخرى

المهام المحددة في حلقة الحفاظ على الوصول إلى نفس المتغير i في حين أن تغيير القيمة الخاصة به.في نهاية الحلقة, جميع وظائف تشير إلى نفس المتغير الذي يحمل القيمة الأخيرة في الحلقة:وأثر هو ما ذكرت في المثال.

من أجل تقييم i واستخدام قيمة النمط الشائع هو أن المعلمة الافتراضية:المعلمة الافتراضية يتم تقييمها عند def بيان يتم تنفيذها ، ومن ثم قيمة المتغير حلقة المجمدة.

الأعمال التالية كما هو متوقع:

flist = []

for i in xrange(3):
    def func(x, i=i): # the *value* of i is copied in func() environment
        return x * i
    flist.append(func)

for f in flist:
    print f(2)

هنا كيف يمكنك أن تفعل ذلك باستخدام 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 ، كما هو متوقع.

النظر في هذا:

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))

ما يحدث هو أن المتغير هو القبض ، وظائف يعودون قيمة لا بد أن في ذلك الوقت يطلق عليه.في اللغات الوظيفية مثل هذا الوضع لم ينشأ ، كما لن يكون انتعاش.ولكن مع بيثون ، و أيضا كما رأيت مع اللثغة ، هذا لم يعد صحيحا.

الفرق مع المخطط الخاص بك على سبيل المثال هو أن تفعل مع دلالات فعل الحلقة.المخطط هو على نحو فعال خلق جديد وأنا متغير في كل مرة خلال الحلقة ، بدلا من إعادة استخدام القائمة أنا ملزمة كما هو الحال مع اللغات الأخرى.إذا كنت تستخدم متغير مختلفة تم إنشاؤها الخارجية إلى حلقة وتتحور ، سترى نفس السلوك في المخطط.حاول استبدال حلقة الخاص بك مع:

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

نلقي نظرة هنا بعض مزيد من المناقشة حول هذا.

[تحرير] ربما أفضل طريقة لوصف ذلك هو أن نفكر في القيام حلقة كما ماكرو التي بتنفيذ الخطوات التالية:

  1. تعريف امدا أخذ معلمة واحدة (أنا) ، مع مجموعة محددة من الجسم من حلقة ،
  2. دعوة فورية من أن امدا مع القيم المناسبة من أنا لها المعلمة.

ie.ما يعادل أقل من الثعبان:

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

أنا لم يعد أحد من الوالدين نطاق ولكن العلامة التجارية الجديدة متغير في نطاق (ie.المعلمة لامدا) و حتى تحصل على سلوك كنت مراقبة.الثعبان لا يملك هذا الضمني النطاق الجديد ، لذلك الجسم من حلقة فقط سهم أنا متغير.

ما زلت غير مقتنع تماما لماذا في بعض اللغات يعمل هذا طريقة واحدة و في بعض طريقة أخرى.في Common Lisp انها مثل بايثون:

(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 ، و بنيت في الاتجاه المعاكس").بينما في نظام يعمل مثل Perl:

(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"

و كما ذكرت مسبقا, جافا سكريبت في بيثون/CL المخيم.يبدو هناك تنفيذ القرار هنا ، والتي لغات مختلفة النهج في طرق مختلفة.أحب أن أفهم ما هو قرار بالضبط.

المشكلة هي أن جميع الوظائف المحلية ربط نفس البيئة وبالتالي على نفسه i متغير.الحل (الحل) هو خلق بيئات منفصلة (إطارات المكدس) لكل وظيفة (أو امدا):

t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]

>>> t[1](2)
2
>>> t[2](2)
4

المتغير i هي شركة عالمية ذات القيمة 2 في كل وظيفة 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]

استجابة التحديث:ليس من globalness i في حد ذاته هو الذي يسبب هذا السلوك ، هو حقيقة أنه متغير من أرفق النطاق الذي له قيمة ثابتة على مر الأوقات و يسمى.في المثال الثاني, قيمة i يؤخذ من نطاق kkk وظيفة و لا شيء يتغير أنه عند استدعاء وظائف على flist.

المنطق وراء السلوك وقد سبق شرح و حلول متعددة تم نشرها ، ولكن أعتقد أن هذا هو الأكثر pythonic (تذكر أن كل شيء في بيثون هو كائن!):

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)

كلاوديو الجواب هو جيد جدا ، باستخدام وظيفة مولد, ولكن بيرو الجواب هو الإختراق, أن نكون صادقين, كما أنه صنع أنا في "مخفية" حجة مع قيمة افتراضية (انها سوف تعمل بشكل جيد, لكنه ليس "pythonic").

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top