2012-04-09 39 views
7

Tôi muốn có một danh sách các lambdas hoạt động như sắp xếp của một bộ nhớ cache để một số tính toán nặng và nhận thấy điều này:Lambdas danh sách bên comprehensions

>>> [j() for j in [lambda:i for i in range(10)]] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

Mặc dù

>>> list([lambda:i for i in range(10)]) 
[<function <lambda> at 0xb6f9d1ec>, <function <lambda> at 0xb6f9d22c>, <function <lambda> at 0xb6f9d26c>, <function <lambda> at 0xb6f9d2ac>, <function <lambda> at 0xb6f9d2ec>, <function <lambda> at 0xb6f9d32c>, <function <lambda> at 0xb6f9d36c>, <function <lambda> at 0xb6f9d3ac>, <function <lambda> at 0xb6f9d3ec>, <function <lambda> at 0xb6f9d42c>] 

Ý nghĩa rằng lambdas là các hàm duy nhất nhưng tất cả chúng đều chia sẻ cùng một giá trị chỉ mục.

Đây có phải là một lỗi hoặc một tính năng? Làm cách nào để tránh sự cố này? Nó không giới hạn comprehensions danh sách ...

>>> funcs = [] 
... for i in range(10): 
...  funcs.append(lambda:i) 
... [j() for j in funcs] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

Trả lời

13

lambda trả về giá trị i tại thời điểm bạn gọi. Vì bạn gọi số lambda sau khi vòng lặp kết thúc chạy, giá trị của i sẽ luôn là 9.

Bạn có thể tạo một i biến địa phương trong lambda để giữ giá trị tại thời điểm lambda được định nghĩa:

>>> [j() for j in [lambda i=i:i for i in range(10)]] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Một giải pháp khác là tạo ra một hàm trả về lambda:

def create_lambda(i): 
    return lambda:i 
>>> [j() for j in [create_lambda(i) for i in range(10)]] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Tính năng này hoạt động vì có một kết thúc khác (giữ giá trị khác nhau là i) được tạo cho mỗi lần gọi create_lambda.

+0

Cách tiếp cận đầu tiên không hoạt động tốt khi bạn muốn 'lambda' hỗ trợ' * args'. Cách tiếp cận thứ hai sẽ hoạt động ở đó, nhưng ... làm việc nhiều hơn :) –

+2

Quá tệ Tôi không thể đưa dấu màu xanh lục cho cả hai câu trả lời. Tôi đã chọn điều này vì nó thực sự đã đưa ra mã chính xác để cắt & dán như tôi năm và tôi thích cách tiếp cận đó để trả lời trên SO. – ubershmekel

+0

Đối với những người trong chúng ta ít giỏi về Python, bất kỳ cơ hội nào bạn có thể sử dụng một biến khác với 'i' cho một trong các cá thể trong phương pháp đầu tiên? – Chowlett

0

Tôi không chắc chắn nếu đó là một lỗi hoặc một tính năng, nhưng những gì xảy ra là lambda:i không đánh giá tôi trước khi hình thành các chức năng lambda. Vì vậy, đó là nghĩa đen chỉ là một chức năng mà đánh giá bất cứ giá trị hiện tại của tôi là gì. Đây là một ví dụ khác về cách điều này xảy ra.

>>> i=5 
>>> x=lambda:i 
>>> x() 
5 
>>> i=6 
>>> x() 
6 

Vì vậy, rõ ràng, những gì đang xảy ra là điều tương tự, ngoại trừ việc tôi sẽ đến 9 trong ví dụ của bạn vì nó được chỉ định trong khoảng từ 0 đến 9 theo thứ tự đó.

Tôi không nghĩ rằng thực sự có cách nào tốt để tránh điều đó. Hàm lambda trong Python khá hạn chế. Nó không thực sự là một ngôn ngữ chức năng trong trái tim.

7

Những gì bạn thấy ở đây là hiệu ứng của closures. Lambda đang ghi lại trạng thái từ chương trình được sử dụng sau này. Vì vậy, trong khi mỗi lambda là một đối tượng duy nhất, nhà nước không nhất thiết phải là duy nhất.

Thực tế 'gotchya' ở đây, là biến số i bị bắt, không phải giá trị i đại diện tại thời điểm đó. Chúng tôi có thể minh họa điều này với ví dụ dễ dàng hơn nhiều:

>>> y = 3 
>>> f = lambda: y 
>>> f() 
3 
>>> y = 4 
>>> f() 
4 

Lambda giữ tham chiếu đến biến và đánh giá biến đó khi bạn thực thi lambda.

Để làm việc xung quanh này, bạn có thể gán cho một biến địa phương trong lambda:

>>> f = lambda y=y:y 
>>> f() 
4 
>>> y = 6 
>>> f() 
4 

Cuối cùng, trong trường hợp của một vòng lặp, biến vòng lặp chỉ là 'tuyên bố' một lần. Do đó, bất kỳ tham chiếu nào đến biến vòng lặp trong vòng lặp sẽ vẫn tồn tại trong quá trình lặp tiếp theo. Điều này bao gồm biến trong danh sách hiểu.

+0

Vâng, mọi chức năng lambda đều bị đóng. Tôi không nghĩ rằng các poster đã nhầm lẫn về những gì một đóng cửa được. Phần không mong muốn là nó sử dụng một tham chiếu biến trong trường hợp này chứ không phải là giá trị. Hầu hết các ngôn ngữ sử dụng bao đóng sẽ thay thế giá trị trong trường hợp này trừ khi loại tham chiếu được sử dụng. –

+4

@KeithIrwin, hầu hết các ngôn ngữ cho phép đóng cửa bị 'gotchya' chính xác này. Javascript và C# là hai ví dụ đáng chú ý. Ngoài ra, OP không bao giờ đề cập đến việc đóng cửa từ, vì vậy tôi figured tôi muốn liên kết với tài liệu giải thích nó nếu họ không biết. –

+0

Nó chỉ hiển thị trong các ngôn ngữ không phân biệt rõ ràng giữa các giá trị và tham chiếu. Ví dụ, nó xuất hiện trong gia đình Lisp của các ngôn ngữ chức năng, nhưng không phải trong các ngôn ngữ được đánh máy mạnh hơn như họ ML hoặc Haskell hay Scala. Nếu anh ta làm việc bằng một ngôn ngữ như F #, Ocaml, Haskell, hay Scala, anh ta sẽ mong đợi một cách hợp lý hành vi ngược lại chính xác. Tôi cho rằng anh ta có ít nhất một số kinh nghiệm với các ngôn ngữ chức năng bởi vì anh ta đã gắn thẻ bài viết đó là về lập trình hàm. –

1

Vấn đề là bạn không nắm bắt được giá trị của i trên mỗi lần lặp lại của việc hiểu danh sách, bạn đang nắm bắt biến mỗi lần.

Vấn đề là việc đóng cửa sẽ nắm bắt các biến theo tham chiếu. Trong trường hợp này, bạn đang ghi lại một biến có giá trị thay đổi theo thời gian (như với tất cả các biến vòng lặp), vì vậy nó có giá trị khác khi bạn chạy nó hơn khi bạn tạo nó.

0

Tôi không chắc chắn những gì bạn đang cố gắng làm với chức năng lambda của bạn. Nó không lấy một đối số ...

Tôi nghĩ rằng Python đang tạo một trình tạo tương tự như (i for i in range(10)) và sau đó lặp lại điều đó. Sau khi đếm 10 lần, giá trị cuối cùng của i là 9, và sau đó đó là hàm lambda trả về.

Bạn có muốn tạo một số loại đối tượng có thể tạo ra các con số từ [0,9] không? Bởi vì nếu bạn muốn làm điều đó, chỉ cần sử dụng xrange(10), trả về một trình vòng lặp cho kết quả là số [0,9].

def f(x): 
    return x 

lst = [f(x) for x in xrange(10)] 
print(lst == range(10)) # prints True 

Lưu ý: Tôi nghĩ rằng bạn nên đặt tên hàm là j(). Python sử dụng j để biểu thị phần ảo của một số phức và tôi cho rằng hàm có tên j có thể gây nhầm lẫn cho trình phân tích cú pháp. Tôi nhận được một số thông báo lỗi lạ cố gắng chạy mã của bạn.

Các vấn đề liên quan