Helpex - Trao đổi & giúp đỡ Đăng nhập
154

Trong khi tôi đang điều tra một vấn đề tôi gặp phải với các đóng từ vựng trong mã Javascript, tôi đã giải quyết vấn đề này bằng Python:

flist = []

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

for f in flist:
    print f(2)

Lưu ý rằng ví dụ này nên tránh lambda. Nó in "4 4 4", thật đáng ngạc nhiên. Tôi mong đợi "0 2 4".

Mã Perl tương đương này làm đúng:

my @flist = ();

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

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

"0 2 4" được in.

Bạn có thể vui lòng giải thích sự khác biệt?


Cập nhật:

Vấn đề không phải làiđược toàn cầu. Điều này hiển thị cùng một hành vi:

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)

Như dòng bình luận cho thấy, ikhông rõ tại thời điểm đó. Tuy nhiên, nó vẫn in "4 4 4".

154 hữu ích 2 bình luận 36k xem chia sẻ
10 trả lời 10
158

Python thực sự đang hoạt động như được định nghĩa. Ba hàm riêng biệt được tạo ra, nhưng mỗi hàm đều có sự đóng lại của môi trường mà chúng được định nghĩa - trong trường hợp này là môi trường toàn cục (hoặc môi trường của hàm bên ngoài nếu vòng lặp được đặt bên trong một hàm khác). Tuy nhiên, đây chính xác là vấn đề - trong môi trường này, i bị đột biến và các lệnh đóng đều quy về cùng một i .

Đây là giải pháp tốt nhất mà tôi có thể đưa ra - tạo một trình tạo hàm và gọi thay thế. Điều này sẽ buộc các môi trường khác nhau cho mỗi chức năng được tạo, với một i khác nhau trong mỗi chức năng .

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)

Đây là những gì sẽ xảy ra khi bạn kết hợp các tác dụng phụ và lập trình chức năng.

158 hữu ích 5 bình luận chia sẻ
157

Các hàm được định nghĩa trong vòng lặp tiếp tục truy cập vào cùng một biến itrong khi giá trị của nó thay đổi. Ở cuối vòng lặp, tất cả các hàm trỏ đến cùng một biến, biến này đang giữ giá trị cuối cùng trong vòng lặp: hiệu ứng là những gì được báo cáo trong ví dụ.

Để đánh giá ivà sử dụng giá trị của nó, một mẫu phổ biến là đặt nó làm tham số mặc định: các giá trị mặc định của tham số được đánh giá khi defcâu lệnh được thực thi và do đó giá trị của biến vòng lặp bị đóng băng.

Những điều sau đây hoạt động như mong đợi:

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)
157 hữu ích 3 bình luận chia sẻ
36

Đây là cách bạn làm điều đó bằng cách sử dụng functoolsthư viện (mà tôi không chắc là có sẵn tại thời điểm câu hỏi được đặt ra).

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)

Kết quả 0 2 4, như mong đợi.

36 hữu ích 3 bình luận chia sẻ
14

nhìn vào cái này:

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

Nó có nghĩa là tất cả chúng đều trỏ đến cùng một cá thể biến thứ i, sẽ có giá trị là 2 khi vòng lặp kết thúc.

Một giải pháp có thể đọc được:

for i in xrange(3):
        def ffunc(i):
            def func(x): return x * i
            return func
        flist.append(ffunc(i))
14 hữu ích 2 bình luận chia sẻ
8

Điều đang xảy ra là biến i bị bắt, và các hàm đang trả về giá trị mà nó bị ràng buộc tại thời điểm nó được gọi. Trong các ngôn ngữ chức năng, loại tình huống này không bao giờ xảy ra, vì tôi sẽ không bị phục hồi. Tuy nhiên với python, và như bạn đã thấy với ngọng, điều này không còn đúng nữa.

Sự khác biệt với ví dụ lược đồ của bạn là làm với ngữ nghĩa của vòng lặp do. Lược đồ đang tạo một cách hiệu quả biến i mới mỗi lần thông qua vòng lặp, thay vì sử dụng lại ràng buộc i hiện có như với các ngôn ngữ khác. Nếu bạn sử dụng một biến khác được tạo bên ngoài vòng lặp và thay đổi nó, bạn sẽ thấy hành vi tương tự trong lược đồ. Hãy thử thay thế vòng lặp của bạn bằng:

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

Hãy xem ở đây để thảo luận thêm về điều này.

[Chỉnh sửa] Có thể một cách tốt hơn để mô tả nó là nghĩ về vòng lặp do như một macro thực hiện các bước sau:

  1. Xác định lambda lấy một tham số duy nhất (i), với phần thân được xác định bởi phần thân của vòng lặp,
  2. Một cuộc gọi ngay lập tức của lambda đó với các giá trị thích hợp của i làm tham số của nó.

I E. tương đương với python bên dưới:

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 không còn là biến trong phạm vi cha mà là một biến hoàn toàn mới trong phạm vi của chính nó (tức là tham số cho lambda) và do đó bạn có được hành vi mà bạn quan sát được. Python không có phạm vi mới tiềm ẩn này, vì vậy phần thân của vòng lặp for chỉ chia sẻ biến i.

8 hữu ích 1 bình luận chia sẻ
4

Vấn đề là tất cả các hàm cục bộ liên kết với cùng một môi trường và do đó với cùng một ibiến. Giải pháp (cách giải quyết) là tạo các môi trường riêng biệt (khung ngăn xếp) cho từng chức năng (hoặc lambda):

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

>>> t[1](2)
2
>>> t[2](2)
4
4 hữu ích 0 bình luận chia sẻ
4

Tôi vẫn không hoàn toàn bị thuyết phục tại sao trong một số ngôn ngữ, điều này hoạt động theo một cách và theo một cách khác. Trong Common Lisp, nó giống như Python:

(defvar *flist* '())

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

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

In "6 6 6" (lưu ý rằng ở đây danh sách là từ 1 đến 3 và được xây dựng ngược lại "). Khi ở trong Scheme, nó hoạt động giống như trong 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)

In "6 4 2"

Và như tôi đã đề cập, Javascript nằm trong trại Python / CL. Có vẻ như có một quyết định thực hiện ở đây, mà các ngôn ngữ khác nhau tiếp cận theo những cách khác nhau. Tôi rất muốn hiểu chính xác quyết định là gì.

4 hữu ích 1 bình luận chia sẻ
2

Biến ilà một toàn cục, có giá trị là 2 tại mỗi thời điểm hàm fđược gọi.

Tôi sẽ có xu hướng thực hiện hành vi mà bạn đang theo đuổi như sau:

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

Đáp lại cập nhật của bạn : Đây không phải là globalness của i mỗi gia nhập mà gây ra hành vi này, đó là một thực tế rằng đó là một biến từ một phạm vi kèm theo trong đó có một giá trị cố định so với thời điểm khi f được gọi. Trong ví dụ thứ hai của bạn, giá trị của iđược lấy từ phạm vi của kkkhàm và không có gì thay đổi khi bạn gọi các hàm trên flist.

2 hữu ích 0 bình luận chia sẻ
0

Lý do đằng sau hành vi đã được giải thích và nhiều giải pháp đã được đăng, nhưng tôi nghĩ đây là điều khó hiểu nhất (hãy nhớ rằng mọi thứ trong Python đều là một đối tượng!):

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)

Câu trả lời của Claudiu khá hay, bằng cách sử dụng một trình tạo hàm, nhưng thành thật mà nói, câu trả lời của piro là một cuộc tấn công, vì nó khiến tôi trở thành một đối số "ẩn" với một giá trị mặc định (nó sẽ hoạt động tốt, nhưng nó không phải là "pythonic") .

0 hữu ích 2 bình luận chia sẻ
0

Tôi không thích cách các giải pháp ở trên tạo ra wrapperstrong vòng lặp. Lưu ý: python 3.xx

flist = []

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

for i in range(3):
    flist.append(func(i))

for f in flist:
    print f(2)
0 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ python closures lazy-evaluation late-binding python-closures , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading