ไม่มี Multiline Lambda ใน Python: ทำไมล่ะ


335

ฉันเคยได้ยินว่ามีการกล่าวว่าไม่สามารถเพิ่มแลมบ์ดาหลายเส้นใน Python ได้เพราะพวกเขาจะขัดแย้งกับไวยากรณ์อื่นที่สร้างขึ้นใน Python ฉันกำลังคิดเกี่ยวกับสิ่งนี้บนรถบัสในวันนี้และตระหนักว่าฉันไม่สามารถนึกถึงงูหลามตัวเดียวที่สร้างลูกแกะหลายตัว เนื่องจากฉันรู้ว่าภาษาค่อนข้างดีสิ่งนี้ทำให้ฉันประหลาดใจ

ตอนนี้ฉันแน่ใจว่ากุยโดมีเหตุผลที่จะไม่รวม lambdas หลายภาษาในภาษา แต่ด้วยความอยากรู้: สถานการณ์ที่การรวมแลมบ์ดาหลายสายจะคลุมเครือ? เป็นสิ่งที่ฉันเคยได้ยินจริงหรือมีเหตุผลอื่นที่งูใหญ่ไม่อนุญาตให้แกะหลายตัว?


12
tl; dr version:เนื่องจาก Python เป็นภาษาสันหลังยาวที่ไม่มี {} บล็อกดังนั้นจึงไม่อนุญาตให้ทำเช่นนี้เพื่อคงรูปแบบการสร้างประโยคให้สอดคล้องกัน
แอนดรู

11
นอกจากนี้: ฉันประหลาดใจอย่างทั่วถึงไม่มีใครพูดถึงเรื่องนี้ในคำตอบ ... คุณสามารถจบบรรทัดด้วยตัวละคร \ ใน Python และดำเนินการต่อไปยังบรรทัดถัดไป ... ข้อมูลนี้จะแทนที่คำถามทั้งหมดนี้ดังนั้น ...
Andrew


"การออกแบบการสร้างประโยค"
นิโคลัส

สิ่งนี้จะต้องมีการอนุญาตให้ใช้คำสั่งในการแสดงออก หากคุณกำลังจะทำเช่นนั้นคุณไม่จำเป็นต้องมีlambdaการแสดงออกในสถานที่แรก; คุณสามารถใช้defคำสั่งในการแสดงออก
chepner

คำตอบ:


153

ดูที่ต่อไปนี้:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

นี่เป็นแลมบ์ดาที่ส่งคืน(y, [1,2,3])(ดังนั้นแผนที่จะรับเพียงพารามิเตอร์เดียวทำให้เกิดข้อผิดพลาด) หรือมันจะกลับมาy? หรือเป็นข้อผิดพลาดทางไวยากรณ์เนื่องจากเครื่องหมายจุลภาคในบรรทัดใหม่ถูกวางผิดที่หรือไม่ Python จะรู้สิ่งที่คุณต้องการได้อย่างไร

ภายในการย่อหน้าการเยื้องนั้นไม่สำคัญกับไพ ธ อนดังนั้นคุณจึงไม่สามารถทำงานกับหลายบรรทัดได้อย่างไม่น่าสงสัย

นี่เป็นเพียงวิธีง่ายๆอาจมีตัวอย่างมากกว่านี้


107
พวกเขาสามารถบังคับให้ใช้วงเล็บได้หากคุณต้องการคืนค่า tuple จากแลมบ์ดา IMO นี้ควรมีการบังคับใช้เสมอเพื่อป้องกันความคลุมเครือดังกล่าว แต่ก็ดี
mpen

26
นี่เป็นความคลุมเครืออย่างง่ายที่ต้องแก้ไขโดยการเพิ่มชุดพิเศษของ parens บางสิ่งที่มีอยู่ในหลายสถานที่แล้วเช่นการแสดงออกของเครื่องกำเนิดไฟฟ้าที่ล้อมรอบด้วยข้อโต้แย้งอื่น ๆ เรียกวิธีการในจำนวนเต็มตามตัวอักษร ชื่อฟังก์ชั่นไม่สามารถเริ่มต้นด้วยหลัก) และแน่นอน lambdas บรรทัดเดียวเช่นกัน (ซึ่งอาจเป็นนิพจน์ที่ยาวเขียนในหลายบรรทัด) แกะหลายสายจะไม่แตกต่างจากกรณีเหล่านี้เป็นพิเศษโดยรับประกันว่าจะไม่รวมอยู่ในเกณฑ์นั้น นี่คือคำตอบที่แท้จริง
nmclean

3
ผมชอบวิธีการมีภาษา gazillion ว่าการจัดการกับมันทำให้ไม่สบายใจ แต่อย่างใดมีบางเหตุผลลึกเหตุผลที่มันเป็นที่คาดคะเนยากมากหากไม่ได้เป็นไปไม่ได้
นิโคลัส

1
@ นิโคลัสที่หลามอย่างย่อ
javadba

เหตุผลที่ฉันไม่ใช้แลมบ์ดาดังนั้นพัฒนาน้อยใน Python
NoName

635

กุยรถตู้ซัม (นักประดิษฐ์ของงูใหญ่) ตอบคำถามตรงนี้ตัวเองอยู่ในบล็อกโพสต์เก่า
โดยพื้นฐานแล้วเขายอมรับว่ามันเป็นไปได้ในทางทฤษฎี แต่วิธีการแก้ปัญหาใด ๆ ที่เสนอจะเป็นแบบ Pythonic

"แต่ความซับซ้อนของการแก้ปัญหาที่เสนอใด ๆ สำหรับปริศนานี้นั้นใหญ่หลวงสำหรับฉัน: มันต้องใช้ parser (หรือแม่นยำกว่า lexer) เพื่อให้สามารถสลับไปมาระหว่างโหมดเยื้องที่มีความอ่อนไหวและเยื้อง ของโหมดก่อนหน้าและระดับการเยื้องเทคนิคที่สามารถแก้ไขได้ทั้งหมด (มีการแบ่งระดับการเยื้องที่สามารถทำให้เป็นมาตรฐานได้) แต่ไม่มีสิ่งใดที่ทำให้ลำไส้ของฉันรู้สึกว่ามันคือการคุมกำเนิดที่ซับซ้อนของRube Goldberg "


108
ทำไมนี่ไม่ใช่คำตอบยอดนิยม มันไม่ได้เกี่ยวกับเหตุผลทางเทคนิค แต่เป็นตัวเลือกการออกแบบตามที่นักประดิษฐ์ระบุไว้อย่างชัดเจน
Dan Abramov

13
@DanAbramov เพราะ OP ไม่ได้เข้าสู่ระบบนานหลายปี
ศ. Falken ผิดสัญญา

7
สำหรับผู้ที่ไม่เข้าใจการอ้างอิง Rube Goldberg โปรดดู: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj

56
คำตอบของกุยโด้เป็นอีกเหตุผลหนึ่งที่ฉันหวังว่า Python ไม่ได้ขึ้นอยู่กับการเยื้องเพื่อกำหนดบล็อก
LS

25
ฉันไม่แน่ใจว่าฉันเรียกว่า ;)
Elliot Cameron

54

โดยทั่วไปแล้วเป็นเรื่องที่น่าเกลียดมาก (แต่บางครั้งทางเลือกก็น่าเกลียดกว่า) ดังนั้นวิธีแก้ปัญหาคือการทำเครื่องหมายปีกกา:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

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

ตัวอย่าง:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

2
เจ้านายของฉันแค่ขออะไรแบบนี้ในแอปพลิเคชั่น PyQt ของเรา ! น่ากลัว
TheGerm

1
ขอบคุณสำหรับสิ่งนี้ฉันยังมองหาวิธีที่ดีในการใช้ lambdas สั้น ๆ (แต่ยังคงเป็นหลายสาย) เป็น callback สำหรับ PySide UI ของเรา
Michael Leonard

และตอนนี้ฉันเห็นสิ่งนี้ทันทีที่แนะนำให้ใช้lambda argและsetattr(arg, 'attr','value')ล้มล้าง "ไม่มีการมอบหมาย ... " และจากนั้นก็มีการประเมินผลการลัดวงจรandและor... มันเป็นจาวาสคริปต์ที่ทำ จมรากสู่คุณเช่นไม้เลื้อยเข้าไปในผนัง ฉันเกือบหวังว่าฉันจะลืมสิ่งนี้ในช่วงคริสต์มาส
nigel222

ค่อนข้างฉลาด - และค่อนข้างอ่านได้ ตอนนี้ - เกี่ยวกับการมอบหมาย (หายไป .. ) เหล่านั้น..
javadba

@ nigel222 ทำไมรู้สึกละอายใจ? ภาษางูหลามเป็นง่อยพื้นฐาน - แต่ก็เป็นหนึ่งที่ใช้anywaysมากสำหรับข้อมูลวิทยาศาสตร์ ดังนั้นเราจึงทำการปรับเปลี่ยน การค้นหาวิธีที่จะทำผลข้างเคียง (มักจะเพียงพอเพียงแค่การพิมพ์ / การบันทึก!) และการมอบหมาย (มักจะพอที่จะเป็นเพียงแค่กลาง vars!) ควรได้รับการจัดการโดยภาษา แต่พวกเขาก็ไม่ได้รับการสนับสนุน (อย่างน้อยถ้าคุณทำตามPEPแนวทาง)
javadba

17

ลิงค์ที่เกี่ยวข้องสองสามข้อ:

ในขณะที่ฉันกำลังติดตามการพัฒนาของ Reia ซึ่งในขั้นต้นจะมีการอิงตามการเว้นย่อหน้าของ Python ที่มีบล็อก Ruby เช่นกันทั้งหมดอยู่ด้านบนของ Erlang แต่นักออกแบบก็ยอมแพ้ต่อความอ่อนไหวเยื้องและโพสต์นี้เขาเขียนเกี่ยวกับการตัดสินใจนั้นรวมถึงการอภิปรายเกี่ยวกับปัญหาที่เขาพบเจอกับการเยื้อง + บล็อกหลายบรรทัด

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

นอกจากนี้ต่อไปนี้เป็นข้อเสนอที่น่าสนใจสำหรับบล็อกรูปแบบทับทิมใน Python ฉันวิ่งข้ามไปที่ที่ Guido โพสต์คำตอบโดยไม่มีการยิงจริง (ไม่แน่ใจว่ามีการยิงที่ตามมา):

http://tav.espians.com/ruby-style-blocks-in-python.html


12

[แก้ไข] อ่านคำตอบนี้ มันอธิบายว่าทำไมแลมบ์ดาหลายชั้นไม่ใช่เรื่อง

พูดง่ายๆคือมันไม่มีเสียงประสาน จากโพสต์บล็อกของ Guido van Rossum:

ฉันพบวิธีแก้ไขปัญหาใด ๆ ที่ยอมรับไม่ได้ซึ่งฝังบล็อกที่ใช้การเยื้องในระหว่างการแสดงออก เนื่องจากฉันพบไวยากรณ์ทางเลือกสำหรับการจัดกลุ่มคำสั่ง (เช่นเครื่องหมายปีกกาหรือคำหลักเริ่มต้น / สิ้นสุด) ยอมรับไม่ได้เท่า ๆ กันสิ่งนี้ทำให้แลมบ์ดาแบบหลายบรรทัดเป็นปริศนาที่แก้ไม่ได้


10

ผมขอเสนอให้คุณแฮ็คที่มีชื่อเสียง แต่น่ากลัว:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

ตอนนี้คุณสามารถใช้LETแบบฟอร์มนี้เช่น:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

ซึ่งจะช่วยให้: [0, 3, 8]


1
โพสต์ครั้งแรกที่gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210

นี่มันเจ๋งมาก! ฉันคิดว่าฉันจะใช้มันในครั้งต่อไปที่ฉันเขียน Python ฉันเป็นโปรแกรมเมอร์ Lisp และ JS เป็นหลักและการขาดแลมบาดาหลายสายก็ทำให้เจ็บ นี่คือวิธีที่จะได้รับ
Christopher Dumas

7

ฉันมีความผิดในการฝึกแฮ็คสกปรกนี้ในบางโครงการของฉันซึ่งง่ายกว่าเล็กน้อย:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

ฉันหวังว่าคุณสามารถหาวิธีที่จะอยู่ pythonic แต่ถ้าคุณต้องทำมันเจ็บปวดน้อยกว่าการใช้ exec และจัดการ globals


6

ให้ฉันพยายามแก้ไขปัญหาการแยกวิเคราะห์ @balpha ฉันจะใช้วงเล็บล้อมรอบหลายแลมด้า หากไม่มีวงเล็บคำจำกัดความของแลมบ์ดานั้นเป็นความโลภ แลมบ์ดาค่ะ

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

ส่งคืนฟังก์ชันที่ส่งคืน (y*z, [1,2,3])

แต่

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

วิธี

map(func, [1,2,3])

โดยที่ func คือแลมบ์ดาหลายชั้นที่คืนค่า y * z ใช้งานได้หรือไม่


1
ฉันคิดว่าคนอันดับหนึ่งควรกลับมาmap(func, [1,2,3])และอันที่ด้านล่างควรเป็นข้อผิดพลาดเนื่องจากฟังก์ชันแผนที่ไม่มีข้อโต้แย้งเพียงพอ นอกจากนี้ยังมีวงเล็บพิเศษบางอย่างในรหัส
Samy Bencherif

การปล่อยสิ่งนั้นลงใน pycharm ที่รัน python2.7.13 จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์
simbo1905

วงเล็บพิเศษ
Samy Bencherif

4

(สำหรับทุกคนที่สนใจในหัวข้อนี้)

พิจารณาสิ่งนี้ (รวมถึงการใช้งานค่าส่งคืนคำสั่งในข้อความเพิ่มเติมในแลมบ์ดา "multiline" แม้ว่ามันจะน่าเกลียดจนถึงจุดที่อาเจียน ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12

สิ่งนี้จะไม่ทำงานเมื่อถูกเรียกเป็นครั้งที่สองที่มีพารามิเตอร์ต่างกันและทำให้หน่วยความจำรั่วเว้นแต่ว่าบรรทัดแรกนั้นเป็นstate.clear()เพราะอาร์กิวเมนต์เริ่มต้นจะถูกสร้างขึ้นเพียงครั้งเดียวเมื่อฟังก์ชันถูกสร้างขึ้น
Matthew D. Scholefield

1

คุณสามารถใช้เครื่องหมายทับ ( \) หากคุณมีหลายบรรทัดสำหรับฟังก์ชั่นแลมบ์ดาของคุณ

ตัวอย่าง:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30

คำถามเกี่ยวข้องกับตัวเองโดยใช้มากกว่า 1 นิพจน์มากกว่ามากกว่า 1 ตัวอักษร
Tomas Zubiri

1

ฉันเริ่มต้นด้วย python แต่มาจาก Javascript วิธีที่ชัดเจนที่สุดคือแยกนิพจน์เป็นฟังก์ชัน ....

ตัวอย่างที่คาดไว้, การแสดงออกของการคูณ(x*2)จะถูกแยกออกเป็นฟังก์ชั่นและดังนั้นฉันสามารถใช้หลายบรรทัด:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

บางทีมันอาจไม่ตอบคำถามอย่างแน่นอนหากเป็นวิธีการทำ multiline ในการแสดงออกแลมบ์ดาเองแต่ในกรณีที่มีใครบางคนได้รับเธรดนี้ดูวิธีแก้จุดบกพร่องการแสดงออก (เช่นฉัน) ฉันคิดว่ามันจะช่วย


2
ทำไมฉันถึงทำอย่างนั้นและไม่เพียงแค่เขียนmap(multiply, [1, 2, 3])?
thothal

0

ในเรื่องของการแฮ็กที่น่าเกลียดคุณสามารถใช้การรวมกันของexecและฟังก์ชั่นปกติเพื่อกำหนดฟังก์ชั่นหลายสายเช่นนี้:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

คุณสามารถห่อสิ่งนี้เป็นฟังก์ชั่นเช่น:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')

0

ฉันแค่เล่นไปสักหน่อยเพื่อพยายามลดความเข้าใจผิดของ dict และเกิดขึ้นกับแฮ็กซับอันนี้:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

ฉันแค่พยายามทำสิ่งเดียวกับที่ทำใน Javascript dict comprehension: https://stackoverflow.com/a/11068265


0

นี่คือการใช้ lambdas แบบหลายบรรทัดที่น่าสนใจยิ่งขึ้น เป็นไปไม่ได้ที่จะสำเร็จเพราะไพ ธ อนใช้การเยื้องเป็นวิธีการสร้างรหัส

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

ตามที่บางคนชี้ให้เห็นคุณสามารถเขียนรหัสของคุณเช่น:

lambda args: (expr1, expr2,... exprN)

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

วิธีหนึ่งในการบรรลุสิ่งที่ verbose มากขึ้นคือการมี

lambda args: [lambda1, lambda2, ..., lambdaN]

ที่แต่ละแลมบ์ดาได้รับการขัดแย้งจากก่อนหน้านี้

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

วิธีนี้ช่วยให้คุณเขียนสิ่งที่เป็นเสียงกระเพื่อม / แบบแผนเช่น

ดังนั้นคุณสามารถเขียนสิ่งนี้:

let(lambda x, y: x+y)((1, 2))

วิธีการที่ซับซ้อนมากขึ้นสามารถใช้ในการคำนวณด้านตรงข้ามมุมฉาก

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

นี่จะส่งคืนรายการหมายเลขสเกลาร์เพื่อให้สามารถใช้เพื่อลดค่าหลายค่าให้เป็นหนึ่ง

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


-3

เพราะแลมบ์ดาฟังก์ชั่นควรจะเป็นหนึ่ง - เรียงเป็นรูปแบบที่ง่ายที่สุดของการทำงาน an entrance, then return


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