2015-02-01 19 views
15

Tôi đang chạy Python 3.4.2 và tôi bị nhầm lẫn với hành vi mã của mình. Tôi đang cố gắng để tạo ra một danh sách các hàm đa thức callable với mức độ ngày càng tăng:Đọc danh sách Python với lambdas

bases = [lambda x: x**i for i in range(3)] 

Nhưng đối với một số lý do nó thực hiện điều này:

print([b(5) for b in bases]) 
# [25, 25, 25] 

Tại sao bases dường như một danh sách các biểu thức lambda ngoái, trong danh sách hiểu, lặp đi lặp lại?

+6

Bạn có thể thấy hữu ích này: [Ghi chú: Python, Phạm vi, và đóng cửa] (http://eev.ee/blog/2011/04/24/gotcha-python-scoping-closures/) – unutbu

+0

unutbu : Bất kỳ ý tưởng về cách làm cho nó hoạt động? – L3viathan

+0

Có thể những gì bạn muốn là 'base = lambda x: [x ** i cho i trong phạm vi (3)]'? – user3467349

Trả lời

16

Sự cố, là classic "gotcha", là rằng i được tham chiếu trong hàm lambda là not looked up until the lambda function is called. Tại thời điểm đó, giá trị của i là giá trị cuối cùng mà nó bị ràng buộc khi kết thúc for-loop, tức là 2.

Nếu bạn ràng buộc i đến một giá trị mặc định trong định nghĩa của lambda chức năng, sau đó mỗi i trở thành một biến địa phương, và giá trị mặc định của nó được đánh giá và ràng buộc với chức năng tại thời điểm lambda được định nghĩa hơn gọi là.

Như vậy, khi lambda được gọi, i hiện đang nhìn lên trong phạm vi địa phương, và giá trị mặc định của nó được sử dụng:

In [177]: bases = [lambda x, i=i: x**i for i in range(3)] 

In [178]: print([b(5) for b in bases]) 
[1, 5, 25] 

Để tham khảo:

+2

Gọn gàng (+1). Nó sẽ là tuyệt vời nếu câu trả lời giải thích lý do tại sao mã ban đầu đã không làm việc, và tại sao điều này không. Cảm ơn. – NPE

+0

Có lý do nào tốt để python diễn giải mã như thế không? Điều này là khá obfuscated ... – user3467349

+0

@ user3467349: Vâng, bây giờ, Python sử dụng [độ phân giải tên động] (http://en.wikipedia.org/wiki/Name_resolution#Static_versus_dynamic). Nhưng [tài liệu nhà nước] (https://docs.python.org/3/tutorial/classes.html), "định nghĩa ngôn ngữ đang phát triển theo hướng phân giải tên tĩnh", vì vậy có thể trong tương lai các quy tắc sẽ thay đổi. – unutbu

2

Là giải pháp thay thế, bạn có thể sử dụng chức năng một phần:

>>> bases = [(lambda i: lambda x: x**i)(i) for i in range(3)] 
>>> print([b(5) for b in bases]) 
[1, 5, 25] 

Ưu điểm duy nhất của xây dựng mà so với giải pháp cổ điển do @unutbu là theo cách đó, bạn không thể giới thiệu các lỗi lén lút bằng cách gọi chức năng của bạn với sai số đối số:

>>> print([b(5, 8) for b in bases]) 
#    ^^^ 
#    oups 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <listcomp> 
TypeError: <lambda>() takes 1 positional argument but 2 were given 

Như đề xuất của Adam Smith trong một bình luận dưới đây, thay vì sử dụng "lambda lồng nhau" bạn có thể sử dụng functools.partial với cùng một lợi ích:

>>> import functools 
>>> bases = [functools.partial(lambda i,x: x**i,i) for i in range(3)] 
>>> print([b(5) for b in bases]) 
[1, 5, 25] 
>>> print([b(5, 8) for b in bases]) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <listcomp> 
TypeError: <lambda>() takes 2 positional arguments but 3 were given 
+2

Nếu bạn định xây dựng một phần chức năng, bạn nên sử dụng 'functools.partial'! –

+0

@Adam Có. Bạn đúng rồi. Tôi đã chỉnh sửa câu trả lời của mình cho phù hợp. Một điều khiến tôi bận tâm khi sử dụng 'partial' là thông báo lỗi báo cáo một số" sai "đối số vị trí cần thiết. Nhưng đây là một dấu hiệu khá nhỏ cho một cú pháp rõ ràng hơn để hiểu. –

4

một nhiều cách tiếp cận 'pythonic':
sử dụng chức năng lồng nhau:

def polyGen(degree): 
    def degPolynom(n): 
     return n**degree 
    return degPolynom 

polynoms = [polyGen(i) for i in range(5)] 
[pol(5) for pol in polynoms] 

đầu ra:

>> [1, 5, 25, 125, 625]

1

Tôi không thi nk "tại sao điều này xảy ra" khía cạnh của câu hỏi đã được trả lời được nêu ra.

Lý do tên các tên không phải cục bộ trong một hàm không được coi là hằng số sao cho các tên không phải địa phương này sẽ khớp với hành vi của tên chung. Đó là, các thay đổi đối với tên chung sau khi hàm được tạo được quan sát khi hàm được gọi.

ví dụ:

# global context 
n = 1 
def f(): 
    return n 
n = 2 
assert f() == 2 

# non-local context 
def f(): 
    n = 1 
    def g(): 
     return n 
    n = 2 
    assert g() == 2 
    return g 
assert f()() == 2 

Bạn có thể thấy rằng trong cả ngữ cảnh chung và phi địa phương nếu giá trị của tên được thay đổi, thì thay đổi đó được phản ánh trong lời gọi tương lai của hàm tham chiếu đến tên. Nếu globals và không phải người dân địa phương được đối xử khác nhau thì điều đó sẽ gây nhầm lẫn. Do đó, hành vi được thực hiện nhất quán. Nếu bạn cần giá trị hiện tại của một tên để tạo ra hằng số cho một hàm mới thì cách thành ngữ là ủy nhiệm việc tạo hàm cho một hàm khác. Hàm được tạo trong phạm vi của hàm tạo (không có gì thay đổi), và do đó giá trị của tên sẽ không thay đổi.

ví dụ:

def create_constant_getter(constant): 
    def constant_getter(): 
     return constant 
    return constant_getter 

getters = [create_constant_getter(n) for n in range(5)] 
constants = [f() for f in getters] 
assert constants == [0, 1, 2, 3, 4] 

Cuối cùng, dưới dạng phụ lục, chức năng có thể sửa đổi tên không phải địa phương (nếu tên được đánh dấu như vậy) giống như họ có thể sửa đổi tên chung. ví dụ.

def f(): 
    n = 0 
    def increment(): 
     nonlocal n 
     n += 1 
     return n 
    return increment 
g = f() 
assert g() + 1 == g() 
+0

Giải thích hay và cảm ơn vì đã nói với tôi về 'không tập trung'! – Jasper

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