รายการความเข้าใจกับแผนที่


732

มีเหตุผลที่ต้องการใช้map()มากกว่าความเข้าใจในรายการหรือในทางกลับกัน? เป็นหนึ่งในนั้นโดยทั่วไปมีประสิทธิภาพมากขึ้นหรือพิจารณาโดยทั่วไปยิ่งใหญ่กว่าอีกหรือไม่?


8
โปรดทราบว่า PyLint เตือนถ้าคุณใช้ map แทนของรายการความเข้าใจเห็นข้อความ W0141
lumbric

2
@lumbric ฉันไม่แน่ใจ แต่จะใช้เฉพาะเมื่อ lambda ถูกใช้ในแผนที่
0xc0de

คำตอบ:


660

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

ตัวอย่างของข้อได้เปรียบความเร็วจิ๋วของแผนที่เมื่อใช้ฟังก์ชันเดียวกันทั้งหมด:

$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

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

$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop

39
ใช่จริง ๆ แล้วคู่มือสไตล์ Python ภายในของเราในที่ทำงานแสดงรายการแนะนำรายการกับแผนที่และตัวกรองอย่างชัดเจน (แม้จะไม่พูดถึงแผนที่การปรับปรุงประสิทธิภาพเล็ก ๆ แต่สามารถวัดได้สามารถให้ได้ในบางกรณี ;-)
Alex Martelli

46
อย่าให้ Kibash กับคะแนนสไตล์ที่ไม่มีขีด จำกัด ของ Alex แต่บางครั้งแผนที่ดูเหมือนจะอ่านง่ายกว่าสำหรับฉัน: data = map (str, some_list_of_objects) บางคนอื่น ๆ ... operator.attrgetter, operator.itemgetter ฯลฯ
เกร็กลินด์

57
map(operator.attrgetter('foo'), objs)อ่านง่ายกว่า[o.foo for o in objs]!
Alex Martelli

52
@Alex: ฉันไม่ต้องการแนะนำชื่อที่ไม่จำเป็นเช่นoที่นี่และตัวอย่างของคุณแสดงว่าทำไม
Reid Barton

29
ฉันคิดว่า @GreggLind มีประเด็นด้วยstr()ตัวอย่างของเขา
Eric O Lebigot

474

กรณี

  • กรณีทั่วไป : เกือบทุกครั้งคุณจะต้องใช้ list comprehension ในpythonเพราะจะเห็นได้ชัดเจนยิ่งขึ้นว่าคุณกำลังทำอะไรกับโปรแกรมเมอร์มือใหม่ที่อ่านโค้ดของคุณ (สิ่งนี้ใช้ไม่ได้กับภาษาอื่น ๆ ซึ่งอาจใช้สำนวนอื่น ๆ ) มันจะยิ่งชัดเจนยิ่งขึ้นว่าคุณกำลังทำอะไรกับโปรแกรมเมอร์ไพ ธ อนเนื่องจากรายการความเข้าใจเป็นมาตรฐาน de-facto ใน python สำหรับการทำซ้ำ พวกเขาจะคาดว่า
  • กรณีทั่วไปที่น้อยกว่า : อย่างไรก็ตามหากคุณมีฟังก์ชั่นที่กำหนดไว้แล้วมักจะมีเหตุผลที่จะใช้mapแม้ว่ามันจะถูกพิจารณาว่าเป็น ยกตัวอย่างเช่นmap(sum, myLists)เป็น / [sum(x) for x in myLists]สั้นสง่างามมากกว่า คุณได้รับความหรูหราโดยไม่ต้องสร้างตัวแปรจำลอง (เช่นsum(x) for x...หรือsum(_) for _...หรือsum(readableName) for readableName...) ซึ่งคุณต้องพิมพ์สองครั้งเพื่อย้ำ อาร์กิวเมนต์เดียวกันเก็บไว้filterและreduceและอะไรจากitertoolsโมดูล: ถ้าคุณมีฟังก์ชั่นที่มีประโยชน์คุณสามารถไปข้างหน้าและทำโปรแกรมฟังก์ชั่นบางอย่าง การทำเช่นนี้จะทำให้สามารถอ่านได้ในบางสถานการณ์และสูญเสียไปในบางกรณี (เช่นโปรแกรมเมอร์มือใหม่ข้อโต้แย้งหลายข้อ) ... แต่ความสามารถในการอ่านรหัสของคุณขึ้นอยู่กับความคิดเห็นของคุณ
  • แทบจะไม่ : คุณอาจต้องการใช้mapฟังก์ชั่นเป็นฟังก์ชั่นนามธรรมที่บริสุทธิ์ในขณะที่ทำการเขียนโปรแกรมฟังก์ชั่นที่คุณทำแผนที่mapหรือ currying mapหรือได้รับประโยชน์จากการพูดคุยmapเป็นฟังก์ชั่น ใน Haskell ตัวอย่างเช่นอินเตอร์เฟส functor ที่เรียกว่าfmapการทำแผนที่ทั่วไปกับโครงสร้างข้อมูลใด ๆ นี่เป็นเรื่องแปลกมากในไพ ธ อนเพราะไวยากรณ์ของไพ ธ อนบังคับให้คุณใช้ตัวสร้างสไตล์เพื่อพูดคุยเกี่ยวกับการวนซ้ำ คุณไม่สามารถพูดคุยได้อย่างง่ายดาย (บางครั้งก็ใช้ได้ดีและบางครั้งก็แย่) คุณอาจจะพบกับตัวอย่างของงูหลามซึ่งmap(f, *lists)เป็นสิ่งที่ควรทำ ตัวอย่างที่ใกล้เคียงที่สุดที่ฉันสามารถหาได้sumEach = partial(map,sum)คือซึ่งเป็นหนึ่งในสายการบินที่เทียบเท่ากับ:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • เพียงแค่ใช้for-loop : แน่นอนคุณสามารถใช้ for-loop ได้เช่นกัน แม้ว่าบางครั้งตัวแปรนอกระบบจะทำให้โค้ดมีความชัดเจนมากขึ้นในภาษาการเขียนโปรแกรมที่จำเป็นเช่นไพ ธ อนเพราะคนส่วนใหญ่คุ้นเคยกับการอ่านโค้ดในลักษณะนั้น โดยทั่วไปแล้วสำหรับการวนซ้ำนั้นมีประสิทธิภาพมากที่สุดเมื่อคุณเพียงแค่ทำการดำเนินการที่ซับซ้อนที่ไม่ได้สร้างรายการเช่น list-comprehensions และแผนที่ได้รับการปรับให้เหมาะสมสำหรับ (เช่นการรวมหรือสร้างต้นไม้เป็นต้น) - อย่างน้อย มีประสิทธิภาพในแง่ของหน่วยความจำ (ไม่จำเป็นในแง่ของเวลาที่ฉันคาดหวังที่เลวร้ายที่สุดเป็นปัจจัยคงที่, ยกเว้นบางอย่างที่หายากทางพยาธิสภาพขยะสะสมคอลเลกชัน)

"Pythonism"

ฉันไม่ชอบคำว่า "pythonic" เพราะฉันไม่พบว่า pythonic นั้นงดงามในสายตาของฉันเสมอ อย่างไรก็ตามmapและfilterฟังก์ชั่นที่คล้ายกัน (เช่นitertoolsโมดูลที่มีประโยชน์มาก) อาจถูกพิจารณาว่าไม่ไพเราะในแง่ของสไตล์

ความเกียจคร้าน

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

>>> map(str, range(10**100))
<map object at 0x2201d50>

ลองทำด้วยความเข้าใจในรายการ:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

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

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

คุณโดยทั่วไปสามารถคิดของไวยากรณ์เป็นผ่านในการแสดงออกกำเนิดเพื่อนวกรรมิกรายการเช่น[...]list(x for x in range(5))

ตัวอย่างที่วางแผนไว้โดยย่อ

from operator import neg
print({x:x**2 for x in map(neg,range(5))})

print({x:x**2 for x in [-y for y in range(5)]})

print({x:x**2 for x in (-y for y in range(5))})

รายการความเข้าใจไม่ขี้เกียจดังนั้นอาจต้องใช้หน่วยความจำเพิ่มเติม (เว้นแต่คุณจะใช้ตัวสร้างความเข้าใจ) วงเล็บเหลี่ยม[...]มักทำให้สิ่งต่าง ๆ ชัดเจนโดยเฉพาะอย่างยิ่งเมื่ออยู่ในวงเล็บ [x for x in...ในทางกลับกันบางครั้งคุณจะขึ้นเป็นอย่างละเอียดเช่นการพิมพ์ ตราบใดที่คุณทำให้ตัวแปรตัววนซ้ำของคุณสั้นรายการความเข้าใจมักจะชัดเจนถ้าคุณไม่เยื้องรหัสของคุณ แต่คุณสามารถเยื้องรหัสของคุณได้เสมอ

print(
    {x:x**2 for x in (-y for y in range(5))}
)

หรือทำลายสิ่งต่าง ๆ :

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

การเปรียบเทียบประสิทธิภาพสำหรับ python3

map ตอนนี้ขี้เกียจ:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

ดังนั้นหากคุณจะไม่ใช้ข้อมูลทั้งหมดของคุณหรือไม่ทราบล่วงหน้าว่าต้องการข้อมูลมากแค่ไหนmapใน python3 (และนิพจน์ตัวสร้างใน python2 หรือ python3) จะหลีกเลี่ยงการคำนวณค่าของมันจนกว่าจะถึงเวลาสุดท้ายที่จำเป็น โดยปกตินี้มักจะมีค่าเกินค่าใช้จ่ายใด ๆ mapจากการใช้ ข้อเสียคือสิ่งนี้มีข้อ จำกัด อย่างมากในภาษาไพ ธ อนเมื่อเทียบกับภาษาที่ใช้งานได้ส่วนใหญ่: คุณจะได้รับประโยชน์นี้เฉพาะเมื่อคุณเข้าถึงข้อมูลจากซ้ายไปขวา "ตามลำดับ" เนื่องจากนิพจน์ของตัวสร้างไพx[0], x[1], x[2], ...ธ อน

อย่างไรก็ตามสมมติว่าเรามีฟังก์ชั่นที่ทำไว้ล่วงหน้าที่fเราต้องการmapและเราไม่สนใจความเกียจคร้านmapโดยบังคับให้ประเมินผลlist(...)ทันที เราได้ผลลัพธ์ที่น่าสนใจ:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'                                                                                                                                                
10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                    for list(<map object>)

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'                                                                                                                                      
10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                    for list(<generator>), probably optimized

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'                                                                                                                                    
1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                    for list(<generator>)

ผลลัพธ์อยู่ในรูปแบบ AAA / BBB / CCC โดยที่ A ถูกดำเนินการบนเวิร์กสเตชัน Intel ในปี 2010 ที่มี python 3.?.? และ B และ C ดำเนินการกับเวิร์กสเตชัน AMD ในปี 2013 ที่มี python 3.2.1 กับฮาร์ดแวร์ที่แตกต่างกันมาก ผลลัพธ์ดูเหมือนจะเป็นความเข้าใจแผนที่และรายการนั้นเปรียบได้กับประสิทธิภาพซึ่งได้รับผลกระทบอย่างมากจากปัจจัยสุ่มอื่น ๆ สิ่งเดียวที่เราสามารถบอกได้ว่าเป็นเรื่องแปลกในขณะที่เราคาดหวังว่ารายการความเข้าใจ[...]ในการทำงานได้ดีกว่านิพจน์ตัวสร้าง(...)นั้นmapก็มีประสิทธิภาพมากกว่าที่นิพจน์ตัวสร้าง (อีกครั้งโดยถือว่าค่าทั้งหมดได้รับการประเมิน / ใช้งาน)

สิ่งสำคัญคือต้องตระหนักว่าการทดสอบเหล่านี้ถือว่าเป็นฟังก์ชั่นที่ง่ายมาก (ฟังก์ชั่นตัวตน); อย่างไรก็ตามนี่เป็นสิ่งที่ดีเพราะถ้าฟังก์ชั่นนั้นซับซ้อนค่าใช้จ่ายในการปฏิบัติงานนั้นจะเล็กน้อยเมื่อเทียบกับปัจจัยอื่น ๆ ในโปรแกรม (มันอาจจะน่าสนใจที่จะทดสอบกับสิ่งอื่น ๆ เช่นf=lambda x:x+x)

หากคุณมีทักษะในการอ่านชุดประกอบหลามคุณสามารถใช้disโมดูลเพื่อดูว่าจริง ๆ แล้วเกิดอะไรขึ้นเบื้องหลัง:

>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_NAME                0 (xs) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 RETURN_VALUE         
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
  1           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                18 (to 27) 
              9 STORE_FAST               1 (x) 
             12 LOAD_GLOBAL              0 (f) 
             15 LOAD_FAST                1 (x) 
             18 CALL_FUNCTION            1 
             21 LIST_APPEND              2 
             24 JUMP_ABSOLUTE            6 
        >>   27 RETURN_VALUE

 

>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>) 
              6 MAKE_FUNCTION            0 
              9 LOAD_NAME                1 (xs) 
             12 GET_ITER             
             13 CALL_FUNCTION            1 
             16 CALL_FUNCTION            1 
             19 RETURN_VALUE         
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
  1           0 LOAD_FAST                0 (.0) 
        >>    3 FOR_ITER                17 (to 23) 
              6 STORE_FAST               1 (x) 
              9 LOAD_GLOBAL              0 (f) 
             12 LOAD_FAST                1 (x) 
             15 CALL_FUNCTION            1 
             18 YIELD_VALUE          
             19 POP_TOP              
             20 JUMP_ABSOLUTE            3 
        >>   23 LOAD_CONST               0 (None) 
             26 RETURN_VALUE

 

>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_NAME                1 (map) 
              6 LOAD_NAME                2 (f) 
              9 LOAD_NAME                3 (xs) 
             12 CALL_FUNCTION            2 
             15 CALL_FUNCTION            1 
             18 RETURN_VALUE 

ดูเหมือนว่ามันจะดีกว่าที่จะใช้ไวยากรณ์กว่า[...] list(...)น่าเสียดายที่mapคลาสนั้นค่อนข้างทึบแสงสำหรับการถอดแยกชิ้นส่วน แต่เราสามารถทำการทดสอบความเร็วได้


5
"โมดูล itertools ที่มีประโยชน์มาก [อาจ] อาจถือว่าไม่ไพเราะในแง่ของสไตล์" อืมมม ฉันไม่ชอบคำว่า "Pythonic" เช่นกันดังนั้นในบางแง่มุมฉันไม่สนใจสิ่งที่มันหมายถึง แต่ฉันไม่คิดว่ามันยุติธรรมกับผู้ที่ใช้มันเพื่อพูดว่าตาม Pinsonicness "Pythonicness" builtins mapและfilterพร้อมกับห้องสมุดมาตรฐานitertoolsเป็นสไตล์ที่ไม่ดีโดยเนื้อแท้ เว้นแต่ GvR จริงบอกว่าพวกเขามีทั้งความผิดพลาดที่น่ากลัวหรือเพียงเพื่อประสิทธิภาพการทำงานข้อสรุปเพียงธรรมชาติถ้านั่นคือสิ่งที่ "Pythonicness" กล่าวคือการลืมเกี่ยวกับมันโง่ ;-)
สตีฟเจสซอพ

4
@SteveJessop: ที่จริงแล้วGuido คิดว่าการวางmap/ filterเป็นความคิดที่ดีสำหรับ Python 3และมีเพียงกบฏของ Pythonistas อื่น ๆ เก็บไว้ในเนมสเปซในตัว (ในขณะที่reduceถูกย้ายไปfunctools) โดยส่วนตัวแล้วฉันไม่เห็นด้วย ( mapและfilterใช้ได้กับฟังก์ชั่นที่กำหนดไว้ล่วงหน้าโดยเฉพาะอย่างยิ่งในตัวไม่เคยใช้มันหากlambdaจำเป็นต้องใช้) แต่ GvR ได้เรียกพวกเขาว่าไม่ใช่ Pythonic เป็นเวลาหลายปี
ShadowRanger

@ShadowRanger: จริง แต่ GvR เคยวางแผนที่จะลบitertoolsหรือไม่ ส่วนที่ฉันอ้างจากคำตอบนี้เป็นข้อเรียกร้องหลักที่ทำให้ฉันสับสน ฉันไม่รู้ว่าในโลกอุดมคติของเขาmapและfilterจะย้ายไปที่itertools(หรือfunctools) หรือไปอย่างสิ้นเชิง แต่ไม่ว่าจะเป็นกรณีใดเมื่อมีคนบอกว่าitertoolsไม่มีเสียงไพเราะครบถ้วนแล้วฉันไม่รู้จริงๆว่า "ไพ ธ อน" คืออะไร ควรจะหมายถึง แต่ฉันไม่คิดว่ามันจะเป็นอะไรที่คล้ายกับ "สิ่งที่ GvR แนะนำให้คนอื่นใช้"
Steve Jessop

2
@SteveJessop: ฉันเป็นเพียงคนเดียวที่อยู่map/ ไม่filter itertoolsโปรแกรมการทำงานเป็นอย่างดี Pythonic ( itertools, functoolsและoperatorได้รับการออกแบบทั้งหมดโดยเฉพาะกับโปรแกรมการทำงานในใจและผมใช้สำนวนการทำงานในหลามตลอดเวลา) และitertoolsมีคุณสมบัติที่จะเป็นความเจ็บปวดที่จะใช้ตัวเองมันโดยเฉพาะmapและfilterเป็นที่ซ้ำซ้อนกับการแสดงออกกำเนิด นั่นทำให้กุยโด้เกลียดพวกเขา itertoolsได้ดีเสมอ
ShadowRanger

1
ฉันชื่นชอบคำตอบนี้ถ้ามีวิธี อธิบายได้ดี
NelsonGon

95

Python 2: คุณควรใช้mapและfilterแทนรายการความเข้าใจ

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

ฉันได้รับสิ่งนี้มากกว่าหนึ่งครั้ง:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

แต่ถ้าฉันบอกว่า:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

ถ้าอย่างนั้นทุกอย่างก็คงจะดี

คุณสามารถพูดได้ว่าฉันโง่ในการใช้ชื่อตัวแปรเดียวกันในขอบเขตเดียวกัน

ฉันไม่ได้ แต่เดิมรหัสก็ใช้ได้ดี - ทั้งสองxไม่ได้อยู่ในขอบเขตเดียวกัน
มันเป็นหลังจากที่ฉันย้ายบล็อกด้านในไปยังส่วนอื่นของรหัสที่ปัญหาเกิดขึ้น (อ่าน: ปัญหาระหว่างการบำรุงรักษาไม่ใช่การพัฒนา) และฉันไม่ได้คาดหวัง

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

สรุป:

การใช้งานและmap filterซึ่งจะป้องกันข้อบกพร่องที่เกี่ยวข้องกับขอบเขตอย่างละเอียด

หมายเหตุด้านข้าง:

อย่าลืมพิจารณาใช้imapและifilter(ในitertools) หากเหมาะสมกับสถานการณ์ของคุณ!


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

13
@wim: นี่เป็นเพียงเกี่ยวกับ Python 2 แม้ว่าจะใช้กับ Python 3 หากคุณต้องการใช้งานร่วมกันได้ ฉันรู้เรื่องนี้มาแล้วและฉันก็เคยใช้ Python มาระยะหนึ่งแล้ว (ใช่มากกว่าไม่กี่เดือน) และมันก็เกิดขึ้นกับฉัน ฉันเห็นคนอื่นที่ฉลาดกว่าฉันตกหลุมพรางเดียวกัน หากคุณฉลาดและมีประสบการณ์ว่านี่ไม่ใช่ปัญหาสำหรับคุณฉันมีความสุขกับคุณฉันไม่คิดว่าคนส่วนใหญ่จะเป็นเหมือนคุณ ถ้าเป็นเช่นนั้นคงไม่มีการกระตุ้นให้แก้ไขใน Python 3
user541686

12
ฉันขอโทษ แต่คุณเขียนสิ่งนี้ในปลายปี 2012 ดีหลังจากที่ python 3 อยู่ในที่เกิดเหตุและคำตอบอ่านเหมือนกับที่คุณแนะนำให้ใช้การเข้ารหัส python ในรูปแบบที่ไม่เป็นที่นิยมอย่างอื่นเพียงเพราะคุณถูกกัดด้วยบั๊กขณะตัดและ วางรหัส ฉันไม่เคยอ้างว่าสดใสหรือมีประสบการณ์ฉันไม่เห็นด้วยว่าการอ้างสิทธิ์ที่เป็นตัวหนานั้นเป็นเหตุผลของคุณ
Wim

8
@wim: อืม? Python 2 ยังคงถูกใช้งานในหลาย ๆ ที่ความจริงที่ว่า Python 3 นั้นมีอยู่ไม่เปลี่ยนแปลง และเมื่อคุณพูดว่า "ไม่ใช่ข้อผิดพลาดที่ละเอียดอ่อนสำหรับใครก็ตามที่ใช้ Python มากกว่าสองสามเดือน" ประโยคนั้นหมายถึง "นี่เป็นเพียงข้อกังวลของผู้พัฒนาที่ไม่มีประสบการณ์" (ไม่ใช่คุณ) และสำหรับบันทึกคุณเห็นได้ชัดว่าไม่ได้อ่านคำตอบเพราะฉันพูดอย่างกล้าหาญว่าฉันเคลื่อนไหวไม่คัดลอกรหัส ข้อบกพร่องในการคัดลอกวางค่อนข้างเหมือนกันในทุกภาษา ข้อผิดพลาดแบบนี้มีลักษณะเฉพาะของ Python มากกว่าเพราะมีขอบเขต มันง่ายกว่าที่จะลืมและคิดถึง
user541686

3
ก็ยังคงไม่ได้เป็นเหตุผลตรรกะสำหรับการเปลี่ยนไปและmap / หรือ filterหากมีสิ่งใดการแปลที่ตรงที่สุดและสมเหตุสมผลที่สุดเพื่อหลีกเลี่ยงปัญหาของคุณไม่map(lambda x: x ** 2, numbers)เพียง แต่เป็นการแสดงออกของเครื่องกำเนิดlist(x ** 2 for x in numbers)ซึ่งไม่รั่วไหลดังที่ JeromeJ ได้ชี้ไปแล้ว ดู Mehrdad อย่าลงคะแนนส่วนตัวเป็นการส่วนตัวฉันแค่ไม่เห็นด้วยอย่างยิ่งกับเหตุผลของคุณที่นี่
Wim

46

ที่จริงแล้วmapความเข้าใจในรายการนั้นมีลักษณะแตกต่างกันมากในภาษา Python 3 ดูโปรแกรม Python 3 ต่อไปนี้:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

คุณอาจคาดหวังให้พิมพ์บรรทัด "[1, 4, 9]" สองครั้ง แต่กลับพิมพ์ "[1, 4, 9]" ตามด้วย "[]" ครั้งแรกที่คุณดูsquaresมันดูเหมือนว่าจะทำงานเป็นลำดับขององค์ประกอบสามอย่าง แต่ครั้งที่สองเป็นสิ่งที่ว่างเปล่า

ในภาษา Python 2 mapจะส่งคืนรายการเก่าแบบธรรมดาเช่นเดียวกับ list comprehensions ที่ทำในทั้งสองภาษา crux คือค่าที่ส่งคืนของmapใน Python 3 (และimapใน Python 2) ไม่ใช่รายการ - เป็นตัววนซ้ำ!

องค์ประกอบจะถูกใช้เมื่อคุณวนซ้ำตัววนซ้ำซึ่งแตกต่างจากเมื่อคุณวนซ้ำตามรายการ นี่คือเหตุผลที่squaresดูว่างเปล่าในprint(list(squares))บรรทัดสุดท้าย

เพื่อสรุป:

  • เมื่อต้องรับมือกับผู้วนซ้ำคุณต้องจำไว้ว่าพวกเขามีสถานะเป็นรัฐและพวกเขากลายพันธุ์เมื่อคุณสำรวจพวกมัน
  • รายการสามารถคาดการณ์ได้มากขึ้นเนื่องจากมีการเปลี่ยนแปลงเฉพาะเมื่อคุณกลายพันธุ์อย่างชัดเจนเท่านั้น พวกเขาเป็นภาชนะ
  • และโบนัส: ตัวเลขสายอักขระและสิ่งอันดับจะคาดเดาได้มากขึ้นเนื่องจากไม่สามารถเปลี่ยนแปลงได้เลย พวกเขามีค่า

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

@semiomant ฉันจะบอกว่าขี้เกียจแผนที่ (เช่นใน python3) เป็น 'การทำงาน' มากกว่าแผนที่กระตือรือร้น (เช่นใน python2) ตัวอย่างเช่นแผนที่ใน Haskell ขี้เกียจ (ดีทุกอย่างใน Haskell ขี้เกียจ ... ) อย่างไรก็ตามแผนที่ที่ขี้เกียจจะดีกว่าสำหรับการโยงแผนที่ - ถ้าคุณมีแผนที่ที่ใช้กับแผนที่ที่ใช้กับแผนที่คุณจะมีรายการสำหรับการเรียกใช้แผนที่ระดับกลางแต่ละครั้งใน python2 ในขณะที่ใน python3 คุณมีเพียงหนึ่งรายการเท่านั้น .
MnZrK

ฉันเดาสิ่งที่ฉันต้องการสำหรับmapการสร้างโครงสร้างข้อมูลไม่ใช่ตัววนซ้ำ แต่ตัวทำตัวขี้เกียจอาจจะง่ายกว่าโครงสร้างข้อมูลแบบขี้เกียจ อาหารสมอง. ขอบคุณ @MnZrK
semiomant

คุณต้องการที่จะบอกว่า map คืนค่า iterable ไม่ใช่ตัววนซ้ำ
user541686

16

ฉันพบว่ารายการความเข้าใจโดยทั่วไปแสดงออกถึงสิ่งที่ฉันพยายามทำมากกว่าmap- พวกเขาทำมันเสร็จแล้ว แต่ก่อนหน้านี้ช่วยลดภาระทางจิตของการพยายามทำความเข้าใจว่าอะไรคือการlambdaแสดงออกที่ซับซ้อน

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


9
ใช่ถอนหายใจ แต่กุยความตั้งใจเดิมที่จะเอาแลมบ์ดาทั้งหมดในหลาม 3 มีเขื่อนกั้นน้ำของการวิ่งเต้นกับมันเพื่อให้เขากลับไปอยู่กับมันแม้จะมีการสนับสนุนอ้วนของฉัน - อาดีเดาแลมบ์ดาเป็นเพียงที่มีประโยชน์มากเกินไปในหลายSIMPLEกรณีเท่านั้น ปัญหาคือเมื่อเกินขอบเขตของSIMPLEหรือถูกกำหนดให้เป็นชื่อ (ในกรณีหลังมันซ้ำซ้อนกับ def def! -)
Alex Martelli

1
บทสัมภาษณ์ที่คุณคิดว่าเป็นเรื่องนี้: amk.ca/python/writing/gvr-interviewที่ Guido กล่าวว่า"บางครั้งฉันก็เร็วเกินไปที่จะยอมรับการมีส่วนร่วมและต่อมาก็รู้ว่ามันเป็นความผิดพลาดตัวอย่างหนึ่งก็คือ ฟีเจอร์การตั้งโปรแกรมฟังก์ชั่นบางอย่างเช่นฟังก์ชั่นแลมบ์ดาแลมบ์ดาเป็นคำหลักที่ช่วยให้คุณสร้างฟังก์ชั่นที่ไม่ระบุตัวตนเล็ก ๆ ฟังก์ชั่นในตัวเช่นแผนที่ตัวกรองและลดการเรียกใช้ฟังก์ชัน "
J. Taylor

3
@ อเล็กซ์ฉันไม่ได้มีประสบการณ์หลายปีของคุณ แต่ฉันได้เห็นรายการความเข้าใจที่ซับซ้อนเกินกว่าแลมบ์ดา แน่นอนว่าการใช้ภาษาในทางที่ผิดนั้นเป็นการยากที่จะต่อต้าน มันน่าสนใจที่รายการความเข้าใจ (สังเกตุ) ดูเหมือนว่าจะมีแนวโน้มที่จะถูกทำร้ายมากกว่าลูกแกะ แต่ฉันไม่แน่ใจว่าทำไมจึงเป็นเช่นนั้น ฉันจะชี้ให้เห็นว่า "hobbled" ไม่ใช่สิ่งเลวร้ายเสมอไป การลดขอบเขตของ "สิ่งต่าง ๆ ที่บรรทัดนี้อาจกำลังทำอยู่" บางครั้งสามารถทำให้ผู้อ่านง่ายขึ้น ตัวอย่างเช่นconstคำหลักใน C ++ นั้นประสบความสำเร็จอย่างยิ่งใหญ่ในสายเหล่านี้
Stuart Berg

> guido ซึ่งเป็นหลักฐานอีกชิ้นหนึ่งที่กุยโด้ไม่อยู่ในใจของเขา แน่นอนlambdaว่ามันถูกทำให้ง่อย (ไม่มีข้อความใด ๆ .. ) ว่ามันยากที่จะใช้และ จำกัด อยู่ดี
javadba

16

นี่คือกรณีหนึ่งที่เป็นไปได้:

map(lambda op1,op2: op1*op2, list1, list2)

เมื่อเทียบกับ:

[op1*op2 for op1,op2 in zip(list1,list2)]

ฉันคาดเดารหัสไปรษณีย์ () เป็นค่าใช้จ่ายที่ไม่ดีและไม่จำเป็นที่คุณต้องทำหากคุณยืนยันในการใช้รายการความเข้าใจแทนแผนที่ จะดีมากถ้ามีคนชี้แจงเรื่องนี้ไม่ว่าจะเป็นการยืนยันหรือไม่ก็ตาม


"[op1 * op2 จาก op1, op2 ใน zip (list1, list2)]" | s / form / for / และรายการที่เทียบเท่าโดยไม่ต้อง zip: (อ่านน้อยกว่า) [list1 [i] * list2 [i] สำหรับ i ในช่วง (len (list1))]
อ่อนแอ

2
ควรเป็น "สำหรับ" ไม่ "จาก" ในเครื่องหมายคำพูดโค้ดที่สองของคุณ @andz และในความคิดเห็นของ @ weakish เช่นกัน ฉันคิดว่าฉันค้นพบวิธีการสร้างประโยคใหม่เพื่อแสดงรายการความเข้าใจ ... Darn
physicsmichael

4
เพื่อเพิ่มความคิดเห็นที่ล่าช้ามากคุณสามารถทำให้zipขี้เกียจได้โดยใช้itertools.izip
tacaswell

5
map(operator.mul, list1, list2)ผมคิดว่าผมยังคงชอบ มันอยู่ที่การแสดงออกทางด้านซ้ายอย่างง่าย ๆ ที่ความเข้าใจจะซุ่มซ่าม
Yann Vernier

1
ฉันไม่ได้ตระหนักว่าแผนที่อาจใช้เวลาหลายฟังก์ชั่นเป็นอินพุตสำหรับฟังก์ชั่นของมันและสามารถหลีกเลี่ยงการใช้ซิป
bli

16

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



1
โมดูลมัลติโพรเซสซิงของไพ ธ อนทำเช่นนี้: docs.python.org/2/library/multiprocessing.html
Robert L.

9

ดังนั้นเนื่องจาก Python 3 map()เป็นตัววนซ้ำคุณจำเป็นต้องคำนึงถึงสิ่งที่คุณต้องการ: ตัววนซ้ำหรือlistวัตถุ

ในฐานะที่เป็น @AlexMartelli แล้วกล่าวถึง , map()เร็วกว่าความเข้าใจรายการเฉพาะในกรณีที่คุณไม่ได้ใช้lambdaฟังก์ชั่น

ฉันจะให้คุณเปรียบเทียบเวลา

Python 3.5.2 และ CPython
ฉันใช้โน๊ตบุ๊ค Jupiterและโดยเฉพาะอย่างยิ่ง%timeitคำสั่งเวทในตัว
การวัด : s == 1000 ms == 1,000 * 1,000 µs = 1000 * 1000 * 1000 ns

ติดตั้ง:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

ฟังก์ชั่นในตัว:

%timeit map(sum, x_list)  # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop

%timeit list(map(sum, x_list))  # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop

%timeit [sum(x) for x in x_list]  # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop

lambda ฟังก์ชั่น:

%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop

%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop

%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop

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

%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop

%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop

%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop

%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop

คุณต้องการlistวัตถุ:

ใช้รายการความเข้าใจถ้ามันเป็นฟังก์ชั่นที่กำหนดเองใช้list(map())ถ้ามีฟังก์ชั่นในตัว

คุณไม่จำเป็นต้องมีlistวัตถุคุณเพียงแค่ต้องการมัน:

ใช้เสมอmap()!


1

ฉันรันการทดสอบอย่างรวดเร็วเปรียบเทียบสามวิธีในการเรียกใช้เมธอดของวัตถุ ในกรณีนี้ความแตกต่างของเวลาเล็กน้อยและเป็นเรื่องของฟังก์ชั่นที่เป็นปัญหา (ดูการตอบสนองของ @Alex Martelli ) ที่นี่ฉันดูวิธีการต่อไปนี้:

# map_lambda
list(map(lambda x: x.add(), vals))

# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))

# map_comprehension
[x.add() for x in vals]

ฉันดูรายการ (เก็บไว้ในตัวแปรvals) ของทั้งจำนวนเต็ม (Python int) และจำนวนจุดลอยตัว (Python float) สำหรับการเพิ่มขนาดรายการ คลาส dummy ต่อไปนี้DummyNumถูกพิจารณา:

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

โดยเฉพาะaddวิธีการ __slots__แอตทริบิวต์คือการเพิ่มประสิทธิภาพที่เรียบง่ายในหลามเพื่อกำหนดหน่วยความจำทั้งหมดที่จำเป็นโดยชั้น (คุณลักษณะ) ลดขนาดหน่วยความจำ นี่คือแผนการที่เกิดขึ้น

ประสิทธิภาพของการทำแผนที่วัตถุของงูหลาม

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

เยี่ยมชมpastebin นี้เพื่อดูแหล่งที่ใช้สร้างพล็อตและข้อมูล


1
ตามที่อธิบายไว้แล้วในคำตอบอื่น ๆmapจะเร็วขึ้นเฉพาะในกรณีที่มีการเรียกใช้ฟังก์ชันในลักษณะเดียวกัน (เช่น[*map(f, vals)]vs. [f(x) for x in vals]) ดังนั้นจะเร็วกว่าlist(map(methodcaller("add"), vals)) อาจไม่เร็วขึ้นเมื่อคู่การวนซ้ำใช้วิธีการโทรที่แตกต่างกันซึ่งสามารถหลีกเลี่ยงค่าใช้จ่ายบางอย่าง (เช่นหลีกเลี่ยงค่าใช้จ่ายในการแสดงออกแลมบ์ดา) สำหรับกรณีทดสอบนี้จะเร็วขึ้น (เพราะและมีประสิทธิภาพการทำงานเหมือนกัน) [methodcaller("add")(x) for x in vals]mapx.add()methodcaller[*map(DummyNum.add, vals)]DummyNum.add(x)x.add()
GZ0

1
โดยวิธีการlist()โทรชัดเจนจะช้ากว่าความเข้าใจในรายการเล็กน้อย [*map(...)]สำหรับการเปรียบเทียบยุติธรรมที่คุณจำเป็นต้องเขียน
GZ0

@ GZ0 ขอบคุณสำหรับคำติชมที่ยอดเยี่ยม! ทั้งหมดทำให้รู้สึกและฉันก็ไม่รู้ว่าการlist()โทรเพิ่มขึ้นค่าใช้จ่าย ควรใช้เวลาอ่านคำตอบให้มากขึ้น ฉันจะเรียกใช้การทดสอบเหล่านี้อีกครั้งเพื่อเปรียบเทียบอย่างเป็นธรรม แต่อาจมีความแตกต่างเล็กน้อย
craymichael

0

ผมคิดว่าวิธีที่ Pythonic ที่สุดคือการใช้ความเข้าใจในรายชื่อแทนและmap filterเหตุผลก็คือว่า comprehensions รายการมีความชัดเจนกว่าและmapfilter

In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension

In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter

In [3]: odd_cubes == odd_cubes_alt
Out[3]: True

เมื่อคุณเห็นความเข้าใจไม่จำเป็นต้องมีlambdaการแสดงออกเพิ่มเติมตามmapความต้องการ นอกจากนี้ความเข้าใจยังช่วยกรองได้อย่างง่ายดายในขณะที่mapต้องการfilterอนุญาตให้กรอง


0

ฉันลองใช้รหัสโดย @ alex-martelli แต่พบความแตกต่างบางอย่าง

python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop

แผนที่ใช้เวลาเท่ากันแม้ในช่วงที่มีขนาดใหญ่มากในขณะที่การใช้ความเข้าใจในรายการใช้เวลานานมากเท่าที่เห็นได้จากรหัสของฉัน ดังนั้นนอกเหนือจากการได้รับการพิจารณาว่า "ไม่ไพเราะ" ฉันไม่เคยประสบปัญหาด้านประสิทธิภาพใด ๆ ที่เกี่ยวข้องกับการใช้งานแผนที่


3
นี่เป็นคำถามที่เก่ามากและคำตอบที่คุณอ้างถึงน่าจะเป็นไปได้มากในการอ้างอิงกับ Python 2 ซึ่งmapจะส่งคืนรายการ ใน Python 3 mapมีการประเมินอย่างเกียจคร้านดังนั้นการโทรmapไม่ได้คำนวณองค์ประกอบรายการใหม่ใด ๆ ดังนั้นทำไมคุณถึงได้รับช่วงเวลาสั้น ๆ
kaya3

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