ฟังก์ชั่นแลมบ์ดาในรายการความเข้าใจ


149

เหตุใดผลลัพธ์ของสองรายการต่อไปนี้จึงแตกต่างกันถึงแม้ว่าfและlambdaฟังก์ชั่นจะเหมือนกันหรือไม่

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

และ

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

ใจคุณทั้งสองtype(f)และtype(lambda x: x*x)กลับประเภทเดียวกัน


[lambda x: x*x for x in range(10)]เร็วกว่าอันแรกเนื่องจากไม่เรียกใช้ฟังก์ชันลูปนอก f ซ้ำ ๆ
riza

@Selinap: ... ไม่เลยคุณกำลังสร้างฟังก์ชั่นใหม่ ๆ ให้กับแบรนด์ทุกครั้งที่ผ่านลูป ... และค่าใช้จ่ายในการสร้างฟังก์ชั่นใหม่นี้แล้วการโทรจะช้าลงเล็กน้อย (ในระบบของฉัน)
Gerrat

@Gerrat: แม้จะมีค่าใช้จ่ายก็ยังเร็วกว่า แต่แน่นอนว่า[x*x for x in range(10)]ดีกว่า
riza

34
ฉันเพิ่งเข้ามาที่นี่เพื่อรับการเข้าถึง Google foobar :)
Gal Margalit

คำตอบ:


267

คนแรกสร้างฟังก์ชั่นแลมบ์ดาเดียวและเรียกมันว่าสิบครั้ง

ส่วนที่สองไม่เรียกใช้ฟังก์ชัน มันสร้างฟังก์ชั่นแลมบ์ดาได้ 10 แบบ มันทำให้ทุกคนในรายการ เพื่อให้เทียบเท่ากับครั้งแรกที่คุณต้องการ:

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

หรือดีกว่า:

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

13
หรือmap(lambda x: x*x, range(10))ซึ่งอาจเป็นสิ่งที่ OP หมายถึงในตอนแรก
Daniel Roseman

ใช่แลมบ์ดา x: x * x .. (x) ดูเหมือนว่าเป็นทฤษฎี
staticor

[lambda x: x * x สำหรับ x ในช่วง (10)] นั้นเป็นฟังก์ชั่นใน
ฮาสเคล

@DanielRoseman หรือจะแม่นยำlist(map(lambda x: x*x, range(10)))จะทำให้คุณ[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane

108

คำถามนี้สัมผัสส่วนที่มีกลิ่นเหม็นมากของ "Python" ที่มีชื่อเสียง "และ" ชัดเจน "- สิ่งที่จะมาก่อน, แลมบ์ดาหรือสำหรับความเข้าใจในรายการ

ฉันไม่คิดว่าวัตถุประสงค์ของ OP คือการสร้างรายการของช่องสี่เหลี่ยมจาก 0 ถึง 9 หากเป็นกรณีนี้เราสามารถให้คำตอบได้มากกว่าเดิม:

squares = []
for x in range(10): squares.append(x*x)
  • นี่เป็นวิธีการไวยากรณ์ที่ดีของเฒ่า

แต่มันไม่ใช่ประเด็น ประเด็นคือ W (hy) TF คือการแสดงออกที่คลุมเครือดังนั้นตอบโต้ได้ง่าย? และฉันมีคดีงี่เง่าสำหรับคุณในตอนท้ายดังนั้นอย่าเพิกเฉยคำตอบของฉันเร็วเกินไป (ฉันมีมันในการสัมภาษณ์งาน)

ดังนั้นความเข้าใจของ OP จึงส่งกลับรายการ lambdas:

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

นี่เป็นเพียง 10 สำเนาที่แตกต่างกันของฟังก์ชันกำลังสองดู:

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

หมายเหตุที่อยู่หน่วยความจำของ lambdas - พวกมันต่างกัน!

แน่นอนว่าคุณสามารถมีการแสดงออกนี้ "ดีที่สุด" (ฮ่าฮ่า) รุ่นอื่น ๆ :

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

ดู? 3 ครั้งแลมบ์ดาเดียวกัน

โปรดทราบว่าฉันใช้_เป็นforตัวแปร มันไม่มีส่วนเกี่ยวข้องกับสิ่งที่xอยู่ในlambda(มันถูกบดบังด้วยคำศัพท์!) รับมัน

ฉันออกจากการสนทนาทำไมความสำคัญของไวยากรณ์ไม่เป็นเช่นนั้นมันหมายถึงทั้งหมด:

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

ซึ่งอาจเป็น: [[0, 1, 4, ..., 81]]หรือหรือ[(0, 1, 4, ..., 81)]หรือฉันพบว่ามีเหตุผลมากที่สุดนี่จะเป็นหนึ่งlistในองค์ประกอบ - การgeneratorคืนค่า มันไม่ใช่กรณีภาษานั้นใช้งานไม่ได้

แต่ถ้าเป็น ...

ถ้าคุณไม่บดบังforตัวแปรและใช้มันในlambdas?

อึนั้นก็เกิดขึ้น ดูนี่สิ:

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

แน่นอนว่าวิธีนี้:

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

แต่มันไม่ได้หมายความว่า:

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

นี่มันบ้าไปแล้ว!

lambdas ในรายการความเข้าใจเป็นการปิดขอบเขตของความเข้าใจนี้ การปิดคำศัพท์ดังนั้นพวกเขาจึงอ้างถึงการiอ้างอิงผ่านไม่ใช่ค่าของมันเมื่อพวกเขาถูกประเมิน!

ดังนั้นการแสดงออกนี้:

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

ใกล้เคียงกับ:

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

ฉันแน่ใจว่าเราสามารถดูเพิ่มเติมได้ที่นี่โดยใช้disไพ ธ อลเดอร์ไพ ธ อน(ซึ่งฉันหมายถึงเช่นโมดูล) แต่สำหรับการสนทนา Python-VM-agnostic ไม่เพียงพอนี่ก็เพียงพอแล้ว มากสำหรับคำถามสัมภาษณ์งาน

ทีนี้วิธีทำlistlambdas ทวีคูณซึ่งคูณด้วยจำนวนเต็มต่อเนื่องจริง ๆ ? เช่นเดียวกับคำตอบที่ยอมรับเราต้องแบ่งการผูกโดยตรงiโดยการพันคำอีกคำหนึ่งlambdaซึ่งถูกเรียกในนิพจน์ความเข้าใจรายการ:

ก่อน:

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

หลังจาก:

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

(ฉันมีตัวแปรแลมบ์ดาด้านนอกด้วย = iแต่ฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่ชัดเจนกว่า - ฉันแนะนำyเพื่อให้เราทุกคนเห็นว่าแม่มดคนไหนเป็นคนไหน)

แก้ไข 2019-08-30:

ทำตามคำแนะนำโดย @ josoler ซึ่งมีอยู่ในคำตอบโดย @sheridp ด้วย - ค่าของความเข้าใจในรายการ "ตัวแปรวนรอบ" สามารถเป็น "ฝังตัว" ภายในวัตถุได้ - กุญแจสำหรับให้เข้าถึงในเวลาที่เหมาะสม ส่วน "หลังจาก" ดังกล่าวข้างต้นไม่ได้โดยการตัดมันอีกและเรียกมันว่าทันทีที่มีมูลค่าปัจจุบันของlambda iอีกวิธีหนึ่ง (อ่านง่ายขึ้นนิดหน่อย - มันไม่มีผล 'WAT') คือการเก็บค่าของวัตถุiไว้ภายในpartialและให้ "inner" (ดั้งเดิม) lambdaถือเป็นอาร์กิวเมนต์ (ส่งผ่านโดยpartialวัตถุที่ เวลาที่โทร) คือ:

หลังจาก 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)

เยี่ยมมาก แต่ก็ยังมีเรื่องเล็กน้อยสำหรับคุณ! สมมติว่าเราไม่ต้องการทำให้เครื่องอ่านโค้ดง่ายขึ้นและส่งปัจจัยตามชื่อ (เป็นอาร์กิวเมนต์ของคำหลักpartial) มาทำการเปลี่ยนชื่อกันบ้าง:

หลังจาก 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'

วัด?

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

เดี๋ยวก่อน ... เรากำลังเปลี่ยนจำนวนอาร์กิวเมนต์เป็น 1 และเปลี่ยนจาก "มากเกินไป" เป็น "น้อยเกินไป" หรือไม่

ดีก็ไม่ได้เป็นวัดจริงเมื่อเราผ่านcoefไปpartialในลักษณะนี้มันจะกลายเป็นข้อโต้แย้งคำหลักดังนั้นจึงต้องมาหลังจากที่ตำแหน่งxโต้แย้งเช่นดังนั้น:

หลังจาก 3:

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

ฉันต้องการเวอร์ชั่นล่าสุดมากกว่าแลมบ์ดาที่ซ้อนกัน แต่สำหรับแต่ละคน ...


22
นั่นเป็นคำถามสัมภาษณ์งานที่โหดร้ายและผิดปกติ
szeitlin

1
หากเพื่อนร่วมงานของฉันไม่ถามฉันอาจจะไม่ค้นหาคำตอบนี้
piggybox

8
ว้าว. ฉันเพิ่งถูกกัดอย่างรุนแรงจากพฤติกรรมที่ไร้สาระนี้ ขอบคุณสำหรับการโพสต์ของคุณ!
Ant

1
คำตอบที่ยอดเยี่ยม ฉันเพิ่งพบปัญหานี้เช่นกัน ในอีกด้านหนึ่งมันชี้ไปที่ข้อ จำกัด Python แต่ในทางกลับกันมันอาจเป็นตัวบ่งชี้กลิ่นรหัส ฉันใช้โซลูชันนี้สำหรับโครงการของเล่น แต่อาจเป็นสัญญาณให้ปรับโครงสร้างในสภาพแวดล้อมการผลิต
ahota

2
เพื่อความชัดเจนและครบถ้วนคุณสามารถเขียนรายการความเข้าใจสุดท้ายเช่น:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler

19

ความแตกต่างใหญ่คือตัวอย่างแรกเรียกใช้แลมบ์ดาf(x)ในขณะที่ตัวอย่างที่สองไม่ทำ

ตัวอย่างแรกของคุณคือเทียบเท่ากับในขณะที่ตัวอย่างที่สองของคุณจะเทียบเท่ากับ[(lambda x: x*x)(x) for x in range(10)][f for x in range(10)]


11

อันแรก

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

วิ่งf()สำหรับแต่ละค่าในช่วงดังนั้นมันจึงf(x)สำหรับแต่ละค่า

อันที่สอง

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

รันแลมบ์ดาสำหรับแต่ละค่าในรายการดังนั้นมันจึงสร้างฟังก์ชั่นเหล่านั้นทั้งหมด


11

คนให้คำตอบที่ดี แต่ลืมพูดถึงส่วนที่สำคัญที่สุดในความคิดของฉัน: ในตัวอย่างที่สองXของความเข้าใจในรายการไม่ได้เช่นเดียวกับXของlambdaฟังก์ชั่นที่พวกเขาจะไม่เกี่ยวข้องกันโดยสิ้นเชิง ดังนั้นตัวอย่างที่สองจึงเหมือนกับ:

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

การวนซ้ำภายในจะทำrange(10)หน้าที่สร้างฟังก์ชันแลมบ์ดาที่คล้ายกัน 10 รายการเท่านั้น (10 ฟังก์ชั่นแยกจากกัน แต่คล้ายกันทั้งหมด - คืนกำลัง 2 ของแต่ละอินพุต)

ในอีกทางหนึ่งตัวอย่างแรกทำงานแตกต่างกันโดยสิ้นเชิงเนื่องจาก X ของการวนซ้ำ DO โต้ตอบกับผลลัพธ์สำหรับแต่ละการวนซ้ำค่าจะเป็นX*Xดังนั้นผลลัพธ์จะเป็น[0,1,4,9,16,25, 36, 49, 64 ,81]


นี่คือจุดสำคัญ ฉันยกระดับคุณและอธิบายในคำตอบของฉัน
Tomasz Gandor

6

คำตอบอื่น ๆ นั้นถูกต้อง แต่ถ้าคุณพยายามทำรายการฟังก์ชั่นแต่ละอันมีพารามิเตอร์ที่แตกต่างกันซึ่งสามารถดำเนินการได้ในภายหลังรหัสต่อไปนี้จะทำเช่นนั้น:

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]

ในขณะที่ตัวอย่างถูกออกแบบมาฉันพบว่ามีประโยชน์เมื่อฉันต้องการรายการฟังก์ชั่นที่แต่ละงานพิมพ์มีความแตกต่างกันเช่น

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

for i in a:
    i()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.