python3 ที่ดีเทียบเท่ากับการแกะกล่องอัตโนมัติในแลมบ์ดาคืออะไร?


97

พิจารณารหัส python2 ต่อไปนี้

In [5]: points = [ (1,2), (2,3)]

In [6]: min(points, key=lambda (x, y): (x*x + y*y))
Out[6]: (1, 2)

สิ่งนี้ไม่รองรับใน python3 และฉันต้องทำสิ่งต่อไปนี้:

>>> min(points, key=lambda p: p[0]*p[0] + p[1]*p[1])
(1, 2)

นี่มันน่าเกลียดมาก ถ้าแลมด้าเป็นฟังก์ชันฉันทำได้

def some_name_to_think_of(p):
  x, y = p
  return x*x + y*y

การลบคุณสมบัตินี้ใน python3 บังคับให้โค้ดทำในแบบที่น่าเกลียด (ด้วยดัชนีวิเศษ) หรือสร้างฟังก์ชันที่ไม่จำเป็น (ส่วนที่น่ารำคาญที่สุดคือการคิดชื่อที่ดีสำหรับฟังก์ชันที่ไม่จำเป็นเหล่านี้)

ฉันคิดว่าควรเพิ่มคุณลักษณะนี้กลับไปที่ lambdas เพียงอย่างเดียว มีทางเลือกอื่นที่ดีหรือไม่?


อัปเดต:ฉันกำลังใช้ตัวช่วยต่อไปนี้เพื่อขยายความคิดในคำตอบ

def star(f):
  return lambda args: f(*args)

min(points, key=star(lambda x,y: (x*x + y*y))

Update2:เวอร์ชันที่สะอาดกว่าสำหรับstar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner

4
อาจเป็นไปได้มากกว่าที่lambdaจะถูกลบออกจากภาษาทั้งหมดจากนั้นเพื่อย้อนกลับการเปลี่ยนแปลงที่ทำให้ใช้งานยากขึ้น แต่คุณสามารถลองโพสต์เกี่ยวกับ python-ideas หากคุณต้องการแสดงความปรารถนาที่จะเห็นคุณลักษณะที่เพิ่มกลับมา
Wooble

3
ฉันไม่ได้รับอย่างใดอย่างหนึ่ง แต่มันดูเหมือนว่า BDFL opposes lambdaในจิตวิญญาณเดียวกับที่เขา opposes map, และreduce filter
Cuadue

3
lambdaถูกกำหนดให้นำออกใน py3k เนื่องจากเป็นภาษาที่ไม่เหมาะสม แต่ไม่มีใครสามารถเห็นด้วยกับทางเลือกที่เหมาะสมในการกำหนดฟังก์ชันที่ไม่ระบุตัวตนดังนั้นในที่สุดกุยโดก็ยกแขนขึ้นด้วยความพ่ายแพ้และนั่นก็คือสิ่งนั้น
roippi

5
ฟังก์ชั่นที่ไม่ระบุชื่อเป็นสิ่งที่ต้องมีในภาษาที่เหมาะสมและฉันค่อนข้างชอบแลมบ์ดา ฉันจะต้องอ่านเหตุผลของการอภิปรายดังกล่าว (แม้ว่าmapและfilterจะถูกแทนที่ด้วยความเข้าใจได้ดีที่สุด แต่ฉันก็ชอบreduce)
njzk2

13
สิ่งหนึ่งที่ฉันไม่ชอบเกี่ยวกับ Python 3 ...
timgeb

คำตอบ:


34

ไม่มีวิธีอื่น คุณครอบคลุมมันทั้งหมด วิธีที่จะไปคือการยกปัญหานี้ในรายการส่งเมลไอเดีย Pythonแต่เตรียมพร้อมที่จะโต้แย้งมากมายเพื่อให้ได้รับแรงฉุด

ที่จริงแล้วอย่าพูดว่า "ไม่มีทางออก" วิธีที่สามอาจใช้การเรียกแลมด้าเพิ่มขึ้นอีกระดับหนึ่งเพียงเพื่อขยายพารามิเตอร์ - แต่นั่นจะไม่มีประสิทธิภาพและอ่านยากกว่าคำแนะนำสองข้อในคราวเดียว:

min(points, key=lambda p: (lambda x,y: (x*x + y*y))(*p))

อัปเดต Python 3.8

ณ ตอนนี้ Python 3.8 alpha1 พร้อมใช้งานและมีการนำนิพจน์การกำหนด PEP 572 มาใช้

ดังนั้นหากมีใครใช้กลอุบายเพื่อดำเนินการหลายนิพจน์ภายในแลมบ์ดา - ฉันมักจะทำเช่นนั้นโดยสร้างทูเพิลและส่งคืนส่วนประกอบสุดท้ายของมันก็เป็นไปได้ที่จะทำ:

>>> a = lambda p:(x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
>>> a((3,4))
25

โปรดทราบว่าโค้ดประเภทนี้แทบจะไม่สามารถอ่านได้หรือใช้งานได้จริงมากกว่าการมีฟังก์ชันเต็มรูปแบบ ยังคงมีการใช้งานที่เป็นไปได้ - หากมี one-liners หลายตัวที่จะดำเนินการกับสิ่งนี้pointการมี namedtuple และใช้นิพจน์การกำหนดเพื่อ "ส่ง" ลำดับที่เข้ามาให้กับ namestuple อย่างมีประสิทธิภาพ:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]

3
และแน่นอนฉันลืมที่จะพูดถึงว่าวิธี "หนึ่งและเห็นได้ชัด" การทำเช่นนี้คือการใช้จ่ายจริงสามบรรทัดฟังก์ชันการใช้defที่ถูกกล่าวถึงในคำถามที่อยู่ภายใต้ some_name_to_think-ofNME
jsbueno

2
คำตอบนี้ยังคงเห็นผู้เยี่ยมชมบางส่วน - ด้วยการใช้งาน PEP 572 ควรมีวิธีสร้างตัวแปรภายในนิพจน์แลมบ์ดาเช่นใน:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno

1
สำนวนมอบหมาย !! ฉัน (น่าพอใจ) ทำให้พวกเขาประหลาดใจ
StephenBoesch

24

อ้างอิงจากhttp://www.python.org/dev/peps/pep-3113/การแกะกล่อง tuple หายไปและ2to3จะแปลดังนี้:

เนื่องจาก lambdas ใช้พารามิเตอร์ tuple เนื่องจากข้อ จำกัด นิพจน์เดียวจึงต้องได้รับการสนับสนุนด้วย สิ่งนี้ทำได้โดยมีอาร์กิวเมนต์ลำดับที่คาดหวังผูกไว้กับพารามิเตอร์เดียวจากนั้นสร้างดัชนีบนพารามิเตอร์นั้น:

lambda (x, y): x + y

จะถูกแปลเป็น:

lambda x_y: x_y[0] + x_y[1]

ซึ่งค่อนข้างคล้ายกับการใช้งานของคุณ


3
ดีที่ไม่ได้ถูกลบออกเพื่อความเข้าใจผิด
balki

12

ฉันไม่ทราบทางเลือกทั่วไปที่ดีสำหรับพฤติกรรมการคลายแพ็กของ Python 2 คำแนะนำสองสามข้อที่อาจเป็นประโยชน์ในบางกรณี:

  • ถ้าคุณนึกชื่อไม่ออก ใช้ชื่อของพารามิเตอร์คำหลัก:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
    
  • คุณสามารถดูว่า a namedtupleทำให้โค้ดของคุณอ่านง่ายขึ้นหรือไม่หากมีการใช้รายการในหลาย ๆ ที่:

    from collections import namedtuple
    from itertools import starmap
    
    points = [ (1,2), (2,3)]
    Point = namedtuple('Point', 'x y')
    points = list(starmap(Point, points))
    
    result = min(points, key=lambda p: p.x**2 + p.y**3)
    

ในบรรดาคำตอบทั้งหมดสำหรับคำถามนี้จนถึงตอนนี้การใช้ nametuple เป็นแนวทางปฏิบัติที่ดีที่สุดที่นี่ ไม่เพียง แต่คุณสามารถเก็บแลมด้าของคุณไว้ได้ แต่ฉันยังพบว่าเวอร์ชัน namestuple นั้นอ่านได้ง่ายกว่าเนื่องจากความแตกต่างระหว่างlambda (x, y):และlambda x, y:ไม่ชัดเจนตั้งแต่แรกเห็น
Burak Arslan

5

แม้ว่าอาร์กิวเมนต์ที่ทำลายโครงสร้างจะถูกลบออกใน Python3 แต่ก็ไม่ได้ถูกลบออกจากความเข้าใจ เป็นไปได้ที่จะใช้ในทางที่ผิดเพื่อให้ได้ลักษณะการทำงานที่คล้ายกันใน Python 3 โดยพื้นฐานแล้วเราใช้ประโยชน์จากข้อเท็จจริงที่ว่ากิจวัตรร่วมช่วยให้เราสามารถเปลี่ยนฟังก์ชันจากภายในสู่ภายนอกได้และผลตอบแทนไม่ใช่คำสั่งและด้วยเหตุนี้จึงได้รับอนุญาตภายใน lambdas

ตัวอย่างเช่น:

points = [(1,2), (2,3)]
print(min(points, key=lambda y: next(x*x + y*y for x,y in (lambda a: (yield a))(y))))

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

values = [(('A',1),'a'), (('B',0),'b')]
print(min(values, key=lambda y: next(b for (a,b),c in (lambda x: (yield x))(y))))

เมื่อเปรียบเทียบกับ

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda p: (lambda a,b: (lambda x,y: (y))(*a))(*p)))

อีกทางหนึ่งก็สามารถทำได้เช่นกัน

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda y: next(b for (a,b),c in [y])))

หรือดีกว่าเล็กน้อย

print(min(values, key=lambda y: next(b for ((a,b),c) in (y,))))

นี่เป็นเพียงการแนะนำว่าสามารถทำได้และไม่ควรนำมาเป็นคำแนะนำ


1

ผมคิดว่าไวยากรณ์ที่ดีคือx * x + y * y let x, y = point, letคำหลักที่ควรจะเลือกอย่างระมัดระวังมากขึ้น

แลมด้าคู่เป็นรุ่นที่ใกล้เคียงที่สุด lambda point: (lambda x, y: x * x + y * y)(*point)

ตัวช่วยฟังก์ชันลำดับสูงจะมีประโยชน์ในกรณีที่เราตั้งชื่อให้ถูกต้อง

def destruct_tuple(f):
  return lambda args: f(*args)

destruct_tuple(lambda x, y: x * x + y * y)

0

ตามคำแนะนำของ Cuadue และความคิดเห็นของคุณเกี่ยวกับการเปิดกล่องยังคงอยู่ในความเข้าใจคุณสามารถใช้โดยใช้numpy.argmin:

result = points[numpy.argmin(x*x + y*y for x, y in points)]

0

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

min((x * x + y * y, (x, y)) for x, y in points)[1]

0

พิจารณาว่าคุณจำเป็นต้องแกะทูเปิลตั้งแต่แรกหรือไม่:

min(points, key=lambda p: sum(x**2 for x in p))

หรือคุณจำเป็นต้องระบุชื่อที่ชัดเจนเมื่อแกะกล่อง:

min(points, key=lambda p: abs(complex(*p))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.