Chức năng Lambda trong việc hiểu danh sách


149

Tại sao đầu ra của hai cách hiểu danh sách sau đây khác nhau, mặc dù flambdachức năng là như nhau?

f = lambda x: x*x
[f(x) for x in range(10)]

[lambda x: x*x for x in range(10)]

Tâm trí bạn, cả hai type(f)type(lambda x: x*x)trả lại cùng một loại.


[lambda x: x*x for x in range(10)]nhanh hơn cái đầu tiên, vì nó không gọi hàm vòng lặp bên ngoài, f lặp đi lặp lại.
riza

@Selinap: ... không, thay vào đó, bạn đang tạo ra một thương hiệu đánh vào chức năng mới mỗi lần qua vòng lặp. ... Và chi phí tạo ra chức năng mới này, sau đó gọi chậm hơn một chút (dù sao trên hệ thống của tôi).
Gerrat

@Gerrat: Ngay cả khi có phí, nó vẫn nhanh hơn. Nhưng, tất nhiên [x*x for x in range(10)]là tốt hơn.
riza

34
Tôi vừa nhập vào đây để có quyền truy cập google foobar :)
Gal Margalit

Câu trả lời:


267

Cái đầu tiên tạo ra một hàm lambda duy nhất và gọi nó là mười lần.

Cái thứ hai không gọi hàm. Nó tạo ra 10 chức năng lambda khác nhau. Nó đặt tất cả những người trong một danh sách. Để làm cho nó tương đương với đầu tiên bạn cần:

[(lambda x: x*x)(x) for x in range(10)]

Hoặc tốt hơn:

[x*x for x in range(10)]

13
Hoặc map(lambda x: x*x, range(10)), đó có lẽ là ý nghĩa của OP ở nơi đầu tiên.
Daniel Roseman

vâng, lambda x: x * x .. (x) dường như nguyên lý.
tĩnh

[lambda x: x * x cho x trong phạm vi (10)] về cơ bản là một dấu chấm câu trong haskell
beeri

@DanielRoseman, hoặc chính xác list(map(lambda x: x*x, range(10)))sẽ cung cấp cho bạn[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane

108

Câu hỏi này chạm vào một phần rất hôi thối của cú pháp Python "nổi tiếng" và "rõ ràng" - điều được ưu tiên, lambda hoặc để hiểu danh sách.

Tôi không nghĩ mục đích của OP là tạo ra một danh sách các ô vuông từ 0 đến 9. Nếu đó là trường hợp, chúng tôi có thể đưa ra nhiều giải pháp hơn nữa:

squares = []
for x in range(10): squares.append(x*x)
  • đây là cách tốt của cú pháp mệnh lệnh.

Nhưng nó không phải là vấn đề. Vấn đề là W (hy) TF có phải là biểu hiện mơ hồ như vậy phản trực giác? Và tôi có một trường hợp ngu ngốc cho bạn vào cuối, vì vậy đừng bỏ qua câu trả lời của tôi quá sớm (tôi đã có nó trong một cuộc phỏng vấn xin việc).

Vì vậy, sự hiểu biết của OP đã trả về một danh sách lambdas:

[(lambda x: x*x) for x in range(10)]

Tất nhiên đây chỉ là 10 bản sao khác nhau của hàm bình phương, xem:

>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]

Lưu ý các địa chỉ bộ nhớ của lambdas - tất cả chúng đều khác nhau!

Tất nhiên bạn có thể có phiên bản "tối ưu" (haha) hơn của biểu thức này:

>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]

Xem? 3 lần cùng lambda.

Xin lưu ý rằng tôi đã sử dụng _như là forbiến. Nó không có gì để làm với xtrong lambda(nó bị lu mờ lexically!). Hiểu rồi?

Tôi đang rời khỏi cuộc thảo luận, tại sao ưu tiên cú pháp không phải như vậy, tất cả đều có nghĩa là:

[lambda x: (x*x for x in range(10))]

có thể là : [[0, 1, 4, ..., 81]], hoặc [(0, 1, 4, ..., 81)], hoặc tôi thấy hợp lý nhất , đây sẽ là một listtrong 1 phần tử - generatortrả về các giá trị. Nó không phải là trường hợp, ngôn ngữ không hoạt động theo cách này.

NHƯNG GÌ, Nếu ...

Điều gì sẽ xảy ra nếu bạn không làm lu mờ forbiến và VÀ sử dụng nó trong lambdas ???

Vâng, sau đó tào lao xảy ra. Nhìn vào cái này

[lambda x: x * i for i in range(4)]

điều này có nghĩa là tất nhiên:

[(lambda x: x * i) for i in range(4)]

NHƯNG nó không có nghĩa là:

[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]

Điều này thật điên rồ!

Các lambdas trong việc hiểu danh sách là một đóng cửa trong phạm vi của sự hiểu biết này. Một đóng cửa từ vựng , vì vậy họ tham chiếu iqua tham chiếu, và không phải giá trị của nó khi chúng được đánh giá!

Vì vậy, biểu thức này:

[(lambda x: x * i) for i in range(4)]

IS đại khái là:

[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]

Tôi chắc rằng chúng ta có thể thấy nhiều hơn ở đây bằng cách sử dụng trình dịch ngược python (ý tôi là ví dụ như dismô-đun), nhưng đối với thảo luận về thuyết bất khả tri của Python-VM thì điều này là đủ. Quá nhiều cho câu hỏi phỏng vấn xin việc.

Bây giờ, làm thế nào để tạo ra một listsố nhân lambdas, mà thực sự nhân với số nguyên liên tiếp? Vâng, tương tự như câu trả lời được chấp nhận, chúng ta cần phá vỡ mối quan hệ trực tiếp ibằng cách gói nó vào một câu trả lời khác lambda, được gọi trong biểu thức hiểu danh sách:

Trước:

>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2

Sau:

>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1

(Tôi cũng có biến lambda bên ngoài = i, nhưng tôi quyết định đây là giải pháp rõ ràng hơn - tôi đã giới thiệu yđể tất cả chúng ta có thể thấy phù thủy nào là phù hợp).

Chỉnh sửa 2019-08-30:

Theo gợi ý của @josoler, cũng có mặt trong câu trả lời của @sheridp - giá trị của "vòng lặp" hiểu danh sách có thể được "nhúng" vào bên trong một đối tượng - khóa được truy cập vào đúng thời điểm. Phần "Sau" ở trên thực hiện bằng cách gói nó vào phần khác lambdavà gọi nó ngay lập tức với giá trị hiện tại là i. Một cách khác (dễ đọc hơn một chút - nó không tạo ra hiệu ứng 'WAT') là lưu trữ giá trị ibên trong một partialđối tượng và để "bên trong" (bản gốc) lambdalấy nó làm đối số (được cung cấp bởi partialđối tượng tại thời gian của cuộc gọi), tức là:

Sau 2:

>>> from functools import partial
>>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Tuyệt vời, nhưng vẫn có một chút thay đổi cho bạn! Giả sử chúng ta sẽ không làm cho trình đọc mã dễ dàng hơn và chuyển yếu tố theo tên (làm đối số từ khóa cho partial). Chúng ta hãy đổi tên:

Sau 2,5:

>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)]
>>> a[0](1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() got multiple values for argument 'coef'

BÌNH THƯỜNG?

>>> a[0]()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

Đợi đã ... Chúng ta sẽ thay đổi số lượng đối số bằng 1 và chuyển từ "quá nhiều" sang "quá ít"?

Chà, nó không phải là một LỚN thực sự, khi chúng ta chuyển coefsang partialtheo cách này, nó trở thành một đối số từ khóa, vì vậy nó phải xuất hiện sau xđối số vị trí , như vậy:

Sau 3:

>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Tôi thích phiên bản cuối cùng hơn lambda lồng nhau, nhưng với mỗi ...


22
đó là một câu hỏi phỏng vấn công việc độc ác và bất thường.
szeitlin

1
Nếu đồng nghiệp của tôi không hỏi, có lẽ tôi sẽ không bao giờ tìm kiếm câu trả lời này
piggybox 10/11/2016

8
Ồ Tôi vừa bị cắn xấu bởi hành vi vô lý này. Cảm ơn vì bài đăng của bạn!
Kiến

1
Câu trả lời tuyệt vời. Tôi chỉ gặp vấn đề này là tốt. Một mặt, nó chỉ ra một giới hạn của Python, nhưng mặt khác nó cũng có thể là một chỉ báo mùi mã. Tôi đang sử dụng giải pháp này cho một dự án đồ chơi, nhưng nó có thể là một tín hiệu để tái cấu trúc trong môi trường sản xuất.
ahota

2
Để rõ ràng và đầy đủ, bạn có thể viết phần hiểu danh sách cuối cùng như:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler

19

Sự khác biệt lớn là ví dụ đầu tiên thực sự gọi lambda f(x), trong khi ví dụ thứ hai thì không.

Ví dụ đầu tiên của bạn tương đương với [(lambda x: x*x)(x) for x in range(10)]trong khi ví dụ thứ hai của bạn tương đương với [f for x in range(10)].


11

Cái đầu tiên

f = lambda x: x*x
[f(x) for x in range(10)]

chạy f()cho từng giá trị trong phạm vi, vì vậy nó làm f(x)cho từng giá trị

cái thứ hai

[lambda x: x*x for x in range(10)]

chạy lambda cho mỗi giá trị trong danh sách, vì vậy nó tạo ra tất cả các hàm đó.


11

Người đã gửi câu trả lời tốt nhưng quên đề cập đến phần quan trọng nhất trong quan điểm của tôi: Trong ví dụ thứ hai Xcủa danh sách hiểu là không giống như Xcác lambdachức năng, họ là hoàn toàn không liên quan. Vì vậy, ví dụ thứ hai thực sự giống như:

[Lambda X: X*X for I in range(10)]

Các lần lặp bên trong range(10)chỉ chịu trách nhiệm tạo 10 hàm lambda tương tự trong một danh sách (10 hàm riêng biệt nhưng hoàn toàn tương tự - trả lại công suất 2 của mỗi đầu vào).

Mặt khác, ví dụ đầu tiên hoạt động hoàn toàn khác nhau, bởi vì X của các lần lặp DO tương tác với các kết quả, với mỗi lần lặp, giá trị là X*Xdo đó kết quả sẽ là[0,1,4,9,16,25, 36, 49, 64 ,81]


Đây là một điểm quan trọng. Tôi ủng hộ bạn và giải thích nó trong câu trả lời của tôi.
Tomasz Gandor

6

Các câu trả lời khác là đúng, nhưng nếu bạn đang cố gắng tạo một danh sách các hàm, mỗi hàm có một tham số khác nhau, có thể được thực thi sau đó , đoạn mã sau sẽ thực hiện điều đó:

import functools
a = [functools.partial(lambda x: x*x, x) for x in range(10)]

b = []
for i in a:
    b.append(i())

In [26]: b
Out[26]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Trong khi ví dụ được đặt ra, tôi thấy nó hữu ích khi tôi muốn một danh sách các hàm mà mỗi hàm in một cái gì đó khác nhau, nghĩa là

import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]

for i in a:
    i()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.