2012-07-10 46 views
7

Dưới đây là một ví dụ tôi nhận được từ blog của ai đó về việc đóng cửa python. Tôi chạy nó trong python 2,7 và nhận được một đầu ra khác với mong đợi của tôi.Giới thiệu về đóng cửa python

flist = [] 

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

for f in flist: 
    print f(2) 

sản lượng dự kiến ​​của tôi là: 0, 2, 4
Nhưng đầu ra là: 4, 4, 4
Có ai có thể giúp giải thích nó?
Cảm ơn bạn trước.

+3

thể trùng lặp của [đóng cửa từ vựng bằng Python] (http://stackoverflow.com/questions/233673/lexical-closures-in-python) – BrenBarn

Trả lời

16

Loops không giới thiệu phạm vi trong Python, vì vậy tất cả ba chức năng gần trên cùng một biến i, và sẽ đề cập đến giá trị cuối cùng của mình sau khi kết thúc vòng lặp, mà là 2.

Có vẻ như tất cả mọi người gần tôi nói chuyện với những người sử dụng bao đóng trong Python đã bị cắn bởi điều này. Hệ quả là hàm bên ngoài có thể thay đổi i nhưng hàm bên trong không thể (vì điều đó sẽ làm cho i một địa phương thay vì đóng cửa dựa trên các quy tắc cú pháp của Python).

Có hai cách để giải quyết này:

# avoid closures and use default args which copy on function definition 
for i in xrange(3): 
    def func(x, i=i): 
     return x*i 
    flist.append(func) 

# or introduce an extra scope to close the value you want to keep around: 
for i in xrange(3): 
    def makefunc(i): 
     def func(x): 
      return x*i 
     return func 
    flist.append(makefunc(i)) 

# the second can be simplified to use a single makefunc(): 
def makefunc(i): 
    def func(x): 
     return x*i 
    return func 
for i in xrange(3): 
    flist.append(makefunc(i)) 

# if your inner function is simple enough, lambda works as well for either option: 
for i in xrange(3): 
    flist.append(lambda x, i=i: x*i) 

def makefunc(i): 
    return lambda x: x*i 
for i in xrange(3): 
    flist.append(makefunc(i)) 
+0

+1 Giải thích và giải pháp tuyệt vời. – jamylak

+0

Lưu ý cho các độc giả khác: Python 3 thêm từ khóa 'nonlocal', cho phép mỗi' func' thay đổi giá trị 'i', do đó sẽ ảnh hưởng đến các giá trị khác. Không hữu ích trong trường hợp này, nhưng có khả năng hữu ích nếu bạn có một số chức năng bên trong. –

+0

Bạn có thể đơn giản hóa cái cuối cùng nhiều hơn một chút với lambda, mặc dù hạn chế những gì func() có thể làm. – Dubslow

4

Bạn không tạo bao đóng. Bạn đang tạo ra một danh sách các chức năng mà mỗi truy cập biến toàn cầu i bằng 2 sau vòng lặp đầu tiên. Vì vậy, bạn kết thúc với 2 * 2 cho mỗi cuộc gọi chức năng.

+0

Cảm ơn bạn đã câu trả lời. Nếu tôi muốn tạo ra đóng cửa để có được sản lượng dự kiến ​​của tôi, làm thế nào để thay đổi mã? Bạn có thể vui lòng cho tôi một số gợi ý? –

+0

@ Alex.Zhang: Vâng, bạn đã yêu cầu giải thích lý do tại sao hành vi có kinh nghiệm không giống như bạn mong đợi. Xem http://stackoverflow.com/a/11408601/21945 để biết giải pháp. – mhawke

1

Mỗi chức năng truy cập vào toàn cầu i.

functools.partial đến để giải thoát:

from functools import partial 
flist = [] 

for i in xrange(3): 
    def func(x, multiplier=None): 
     return x * multiplier 
    flist.append(partial(func, multiplier=i)) 
Các vấn đề liên quan