ทำไมไพ ธ อนไม่อนุญาตให้แกะหลายสาย?


50

บางคนสามารถอธิบายเหตุผลที่เป็นรูปธรรมได้ว่าทำไม BDFL จึงเลือกที่จะสร้าง Python lambdas บรรทัดเดียว?

ดีจัง:

lambda x: x**x

ซึ่งส่งผลให้เกิดข้อผิดพลาด:

lambda x:
    x**x

ฉันเข้าใจว่าการสร้างแลมบ์ดาหลายบรรทัดจะ "รบกวน" กฎการเยื้องตามปกติและจะต้องเพิ่มข้อยกเว้นเพิ่มเติม แต่นั่นไม่คุ้มกับผลประโยชน์หรือไม่

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


3
เมื่อพิจารณาว่าคุณทราบถึงเหตุผลที่ชัดเจนว่าทำไมกุยโดไม่อนุญาตให้แกะแล้งหลายแสดงออกแล้วละทิ้งฉันจะถือว่าคุณกำลังมองหาการตรวจสอบมากกว่าคำตอบที่แท้จริง
Jason Baker

3
นอกเหนือจากการบันทึกตัวละครเจ็ดตัวแล้วอะไรจะดีไปกว่านี้def? ตอนนี้มันมีโครงสร้างภาพเหมือนกันทุกประการ
Detly

คำตอบ:


42

Guido van van Rossum ตอบด้วยตัวเอง:

แต่โซลูชั่นดังกล่าวมักจะขาด "Pythonicity" - นั่นเป็นลักษณะที่เข้าใจยากของคุณลักษณะ Python ที่ดี เป็นไปไม่ได้ที่จะแสดง Pythonicity เป็นข้อ จำกัด ที่ยาก แม้แต่ Zen of Python ก็ไม่ได้แปลเป็นการทดสอบแบบเรียบง่ายของ Pythonicity ...

ในตัวอย่างข้างต้นมันง่ายที่จะหาส้น Achilles ของวิธีแก้ปัญหาที่เสนอ: เครื่องหมายทวิภาคสองตัวในขณะที่ syntactically ไม่ชัดเจน (หนึ่งใน "ข้อ จำกัด ปริศนา") เป็นกฎเกณฑ์อย่างสมบูรณ์และไม่เหมือนกับสิ่งอื่นใน Python ...

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

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

โดยพื้นฐานแล้วเขาบอกว่าถึงแม้จะมีทางออกก็ตาม แต่ก็ไม่สอดคล้องกับวิธีการของ Python


2
+1 ขอบคุณสำหรับลิงค์กระทู้ - แต่ฉันยังคงขอบคุณ lambdas หลายบรรทัด - มีค่า - ดู JavaScript, PHP รวมไว้ด้วย
treecoder

2
@greengit คุณสามารถใช้ def ที่ซ้อนกันได้ มันไม่เหมือนกับฟังก์ชั่นนิรนาม แต่มันใกล้พอ
jsternberg

2
defs ที่ซ้อนกันไม่ได้ช่วยเมื่อผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์ - นั่นคือเหตุผลอันดับหนึ่งว่าทำไมฉันถึงชอบแกะหลายเส้น
treecoder

1
@greengit - ฉันคิดว่าคุณควรสละเรื่องนี้กับ GvR มากกว่าการโพสต์ความคิดเห็นของคุณที่นี่
Jason Baker

9
@greengit: คุณรู้หรือไม่ว่าคุณสามารถส่งผ่านฟังก์ชันเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอื่นได้หรือไม่ คุณไม่สามารถเขียนแบบอินไลน์ได้ แต่ไม่มีเทคนิคการเขียนโปรแกรมที่คุณไม่สามารถใช้ได้
btilly

24

เป็นการดีที่จะทำแลมบ์ดาแบบหลายบรรทัดในหลาม: ดู

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

ข้อ จำกัด ของแลมบ์ดาที่แท้จริงคือความจริงที่ว่าแลมบ์ดาจะต้องเป็นคนเดียวแสดงออก ; ไม่สามารถมีคำหลัก (เช่น python2 printหรือreturn)

GvR เลือกที่จะทำเช่นนั้นเพื่อ จำกัด ขนาดของแลมบ์ดาตามปกติจะใช้เป็นพารามิเตอร์ หากคุณต้องการฟังก์ชั่นจริงให้ใช้def


1
multi lineเป็นเรื่องเกี่ยวกับการแทรกตัวอักษร '\ n': D python ไม่มีคำสั่ง lambda หลายคำ คุณต้องการใช้defจริงๆ คิดเกี่ยวกับมัน: คุณต้องการ callable เป็นพารามิเตอร์ของฟังก์ชันของคุณหรือไม่ และผู้ใช้ของฟังก์ชั่นนั้นไม่ได้รับอนุญาตให้ผ่านการโทรเริ่มต้นของคุณ ? พวกเขาจะผ่านมันไปได้อย่างไรถ้าคุณไม่ให้มัน?
Vito De Tullio

btw คุณสามารถให้ตัวอย่างของความต้องการฟังก์ชัน anonimous ได้หรือไม่?
Vito De Tullio

1
ใช่ฉันพบข้อ จำกัด ของการแสดงออกเพียงครั้งเดียวที่น่าผิดหวังจริงๆ จริงอยู่ว่าถ้าพวกเขาอนุญาตให้ลูกแกะที่แสดงออกได้หลายคนจะเริ่มละเมิดมันอย่างแน่นอน แต่อีกวิธีหนึ่งคือโอโฮ จำกัด
rbaleksandar

10

ฉันรู้ว่านี่เก่ามาก แต่การอ้างอิงไว้ที่นี่

ทางเลือกอื่นในการใช้แลมบ์ดาอาจใช้วิธีdefที่ไม่ธรรมดา เป้าหมายคือการส่งผ่านdefไปยังฟังก์ชั่นซึ่งสามารถทำได้ในสถานการณ์เดียว - มัณฑนากร ขอให้สังเกตว่ามีการดำเนินการนี้def resultจะไม่สร้างฟังก์ชั่นมันจะสร้างผลมาจากการที่สิ้นสุดขึ้นเป็นreduce()dict

ปลั๊กไร้ยางอาย : ฉันทำสิ่งนี้มากมายที่นี่

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

โปรดทราบว่า lambdas แบบหลายประโยคสามารถทำได้ แต่ด้วยรหัสที่น่าเกลียดจริงๆเท่านั้น อย่างไรก็ตามสิ่งที่น่าสนใจคือการกำหนดขอบเขตทำงานกับการนำไปใช้งานนี้อย่างไร (สังเกตการใช้งานหลายครั้งของnameตัวแปรและการแรเงาของmessageตัวแปร

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!

+1 สำหรับแนวทางแบบ monadic
jozefg

Monads นั้นเรียกอีกอย่างว่าแล้วหรือในอนาคต / สัญญาหรือแม้กระทั่งการเรียกกลับใน JavaScript BTW
aoeu256

3

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

ฉันครอบคลุมกี่วิธีที่จะทำเช่นนี้ในบล็อกของฉัน

ยกตัวอย่างเช่นการค้ำประกันหลามในการประเมินองค์ประกอบของ tuple ที่ในการสั่งซื้อเพื่อให้เราสามารถใช้มากเช่นความจำเป็น, ;เราสามารถแทนที่คำสั่งมากมายprintเช่นsys.stdout.writeไลค์ด้วยนิพจน์เช่น

ดังนั้นต่อไปนี้จะเทียบเท่า:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

โปรดทราบว่าฉันได้เพิ่มNoneท้ายแล้วแยกโดยใช้[-1]; เป็นการตั้งค่าส่งคืนอย่างชัดเจน เราไม่ต้องทำเช่นนี้ แต่หากไม่มีเราก็จะได้รับ(None, None, None)ผลตอบแทนที่ขี้ขลาดซึ่งเราอาจจะสนใจหรือไม่สนใจก็ได้

ดังนั้นเราสามารถจัดลำดับการกระทำของ IO แล้วตัวแปรท้องถิ่นล่ะ

Python =สร้างคำแถลงดังนั้นเราจำเป็นต้องค้นหาการแสดงออกที่เทียบเท่ากัน วิธีหนึ่งคือการกลายพันธุ์เนื้อหาของโครงสร้างข้อมูลส่งผ่านเป็นอาร์กิวเมนต์ ตัวอย่างเช่น:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

มีเทคนิคเล็กน้อยที่ใช้ในstateful_lambda:

  • *_อาร์กิวเมนต์ช่วยให้แลมบ์ดาเราที่จะใช้ใด ๆจำนวนของการขัดแย้ง ตั้งแต่นี้ช่วยให้ศูนย์stateful_defการขัดแย้งเรากู้คืนเรียกประชุมของ
    • การเรียกอาร์กิวเมนต์_เป็นเพียงแบบแผนที่บอกว่า "ฉันจะไม่ใช้ตัวแปรนี้"
  • เรามีหนึ่งฟังก์ชัน ("wrapper") ที่ส่งคืนฟังก์ชันอื่น ("main"): lambda state: lambda *_: ...
    • ขอบคุณขอบเขตคำศัพท์อาร์กิวเมนต์ของฟังก์ชันแรกจะอยู่ในขอบเขตสำหรับฟังก์ชันที่สอง
    • ยอมรับข้อโต้แย้งบางในขณะนี้และกลับมาอีกฟังก์ชั่นที่จะยอมรับส่วนที่เหลือต่อมาเป็นที่รู้จักกันดีความชอบ
  • เราเรียกฟังก์ชัน "wrapper" ทันทีผ่านพจนานุกรมที่ไม่มีข้อมูล: (lambda state: ...)({})
    • สิ่งนี้ทำให้เราสามารถกำหนดตัวแปรstateให้กับค่าได้{}โดยไม่ต้องใช้คำสั่งกำหนดค่า (เช่นstate = {})
  • เราปฏิบัติต่อคีย์และค่าในstateฐานะชื่อตัวแปรและค่าที่ถูกผูกไว้
    • สิ่งนี้ยุ่งยากน้อยกว่าการใช้ lambdas ที่เรียกว่าทันที
    • สิ่งนี้ทำให้เราสามารถเปลี่ยนค่าของตัวแปรได้
    • เราใช้state.setdefault(a, b)แทนa = bและstate.get(a)แทนa
  • เราใช้สิ่งอันดับสองเพื่อเชื่อมโยงผลข้างเคียงของเราเข้าด้วยกันเหมือนเมื่อก่อน
  • เราใช้[-1]เพื่อแยกค่าสุดท้ายซึ่งทำหน้าที่เหมือนreturnคำสั่ง

แน่นอนว่ามันค่อนข้างยุ่งยาก แต่เราสามารถสร้าง nicer API ด้วยฟังก์ชั่นตัวช่วยได้:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])

คุณล้อเล่นนี่เป็นฝันร้ายมั้ย
Alexander Mills

1
@AlexanderMills Heh นี้ไม่ได้มีเจตนาเป็นตัวอย่างที่โลกแห่งความจริงมากขึ้นของการพิสูจน์ของ pyrospade ของ lambdas ใน lambdas ใน lambdas วิธีการที่จะแสดงให้เห็นว่าสิ่งที่ไม่ว่าไม่ดี ในความเป็นจริงสิ่งนี้สามารถลดความซับซ้อนได้มากขึ้นในขณะนี้ว่าเรามีpython.org/dev/peps/pep-0572
Warbo

1

ฉันแม้ว่าฉันสามารถมีส่วนร่วมใช้ตัวแบ่งบรรทัด:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

อย่าลืมสิ่งที่ดีมาก ๆ ที่ไพ ธ อนสามารถเขียนผู้เลียนแบบได้ดังตัวอย่าง:

a=b=0; c=b+a; d = a+b**2 #etc etc

และแลมบ์ดาจะมีประสิทธิภาพมาก แต่ก็ไม่ได้หมายความว่าเปลี่ยนตัว 1 ฟังก์ชั่นทั้งหมดที่ฉันหมายถึงคุณสามารถตัดมันเหมือน (ยืมตัวอย่างจากเพื่อนร่วมงานดังกล่าวข้างต้น):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

แต่คุณต้องการทำเช่นนี้จริง ๆ ? ส่วนใหญ่จะอ่านไม่ได้หลังจากผ่านไประยะหนึ่งมันก็เหมือนกับการเริ่มต้นของเชือกที่เริ่มต้นด้วยปลายที่ไม่มีการไข เชือกที่ไม่ได้ยึดติด

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

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

คุณสามารถใช้มันเป็นฟังก์ชั่นการแสดงออกของฟังก์ชั่นโดย deifning ฟังก์ชั่นที่ซับซ้อน (หรือมากกว่าและหลาย lambdas และวางไว้ในแลมบ์ดาอื่น:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

แต่ Python มีการสนับสนุนการแสดงออกของฟังก์ชันในอีกทางหนึ่ง: - บอกว่าคุณมีฟังก์ชั่นที่เรียกว่าsuperAwesomeFunctionและฟังก์ชั่นนั้นสามารถทำสิ่งที่ยอดเยี่ยมมากคุณสามารถกำหนดให้กับตัวแปรโดยไม่เรียกมันเช่นนี้

SAF = superAwesomeFunction # there is no () at the end, 

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

คุณสามารถค้นหาตำแหน่งของโมดูลที่นำเข้าได้โดยป้อนในคอนโซลimportedModule.__file__(ตัวอย่างจริงimport os;os.__file__) และเพียงแค่ติดตามไดเรกทอรีนั้นไปยังไฟล์ที่เรียกว่าimportModule.pyและเปิดในตัวแก้ไขและค้นหาวิธีที่คุณสามารถเพิ่ม "ความรู้" ของคุณเองได้

ฉันหวังว่าสิ่งนี้จะช่วยคุณและเพื่อนร่วมงานคนอื่นอาจมีปัญหา

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