9

Các thử nghiệm sau thất bại:Tại sao kết quả của bản đồ() và danh sách hiểu khác nhau?

#!/usr/bin/env python 
def f(*args): 
    """ 
    >>> t = 1, -1 
    >>> f(*map(lambda i: lambda: i, t)) 
    [1, -1] 
    >>> f(*(lambda: i for i in t)) # -> [-1, -1] 
    [1, -1] 
    >>> f(*[lambda: i for i in t]) # -> [-1, -1] 
    [1, -1] 
    """ 
    alist = [a() for a in args] 
    print(alist) 

if __name__ == '__main__': 
    import doctest; doctest.testmod() 

Nói cách khác:

>>> t = 1, -1 
>>> args = [] 
>>> for i in t: 
... args.append(lambda: i) 
... 
>>> map(lambda a: a(), args) 
[-1, -1] 
>>> args = [] 
>>> for i in t: 
... args.append((lambda i: lambda: i)(i)) 
... 
>>> map(lambda a: a(), args) 
[1, -1] 
>>> args = [] 
>>> for i in t: 
... args.append(lambda i=i: i) 
... 
>>> map(lambda a: a(), args) 
[1, -1] 
+3

Đối với những người như tôi đọc câu hỏi nhưng không nhận thấy bất kỳ vấn đề nào lúc đầu: lưu ý '[- 1, -1] '! Về cơ bản 'lambda i: ...' trong một vòng lặp không nắm bắt được giá trị hiện tại của i. –

+0

liên quan từ Python FAQ: [Tại sao lambdas được định nghĩa trong một vòng lặp với các giá trị khác nhau tất cả trả về cùng một kết quả?] (Https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined -in-a-loop-với-khác nhau-giá trị-all-return-the-cùng-kết quả) – jfs

Trả lời

9

Họ là khác nhau, vì giá trị của i trong cả hai biểu thức máy phát điện và danh sách comp được đánh giá một cách lười biếng, tức là khi các hàm ẩn danh được gọi trong f.
Vào thời điểm đó, i bị ràng buộc với giá trị cuối cùng nếu t, là -1.

Vì vậy, về cơ bản, đây là những gì trong danh sách hiểu làm (tương tự như vậy cho genexp):

x = [] 
i = 1 # 1. from t 
x.append(lambda: i) 
i = -1 # 2. from t 
x.append(lambda: i) 

Bây giờ lambdas mang theo một đóng cửa tham chiếu i, nhưng i là ràng buộc để -1 trong cả hai trường hợp, bởi vì đó là giá trị cuối cùng nó được gán cho.

Nếu bạn muốn chắc chắn rằng lambda nhận được giá trị hiện tại của i, làm

f(*[lambda u=i: u for i in t]) 

Bằng cách này, bạn buộc các đánh giá của i tại thời điểm đóng cửa được tạo ra.

Chỉnh sửa: Có một sự khác biệt giữa biểu thức trình tạo và danh sách hiểu: sau này làm rò biến biến vòng lặp vào phạm vi xung quanh.

+0

Lambdas là ác vì nó không phải là rõ ràng những gì bối cảnh thời gian chạy thực sự là gì. –

+4

@ S.Lott: Các hàm bình thường trong Python không khác nhau. 'def f(): return i' Bạn không biết những gì' i' thực sự là bất kể chức năng hoặc lambda được xem xét. – jfs

+1

Trong Python3, ["các biến điều khiển vòng lặp không còn bị rò rỉ vào phạm vi xung quanh."] (Https://docs.python.org/3.0/whatsnew/3.0.html#changed-syntax) – unutbu

5

Lambda chụp biến, không phải giá trị, do đó các mã

lambda : i 

sẽ luôn luôn trả về giá trị i là hiện tại bị ràng buộc trong lúc đóng. Vào thời điểm được gọi, giá trị này đã được đặt thành -1.

Để có được những gì bạn muốn, bạn sẽ cần phải nắm bắt các ràng buộc thực tế tại thời điểm lambda được tạo ra, bởi:

>>> f(*(lambda i=i: i for i in t)) # -> [-1, -1] 
[1, -1] 
>>> f(*[lambda i=i: i for i in t]) # -> [-1, -1] 
[1, -1] 
3

Biểu f = lambda: i tương đương với:

def f(): 
    return i 

Biểu g = lambda i=i: i tương đương với:

def g(i=i): 
    return i 

i là một free variable trong trường hợp đầu tiên và nó được liên kết với các tham số chức năng trong trường hợp thứ hai tức là, nó là một biến cục bộ trong trường hợp đó. Giá trị cho các tham số mặc định được đánh giá tại thời điểm định nghĩa hàm.

biểu Generator là phạm vi kèm theo gần nhất (nơi i được định nghĩa) cho i tên trong biểu thức lambda, do đó i được giải quyết trong khối rằng:

f(*(lambda: i for i in (1, -1)) # -> [-1, -1] 

i là một biến cục bộ của khối lambda i: ..., do đó đối tượng mà nó đề cập đến được xác định trong khối đó:

f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1] 
Các vấn đề liên quan