Pemahaman daftar vs peta

733

Apakah ada alasan untuk memilih menggunakan map()pemahaman daftar berlebihan atau sebaliknya? Apakah salah satu dari mereka umumnya lebih efisien atau secara umum dianggap lebih pythonic daripada yang lain?

TimothyAWiseman
sumber
8
Perhatikan bahwa PyLint memperingatkan jika Anda menggunakan peta alih-alih pemahaman daftar, lihat pesan W0141 .
lumbric
2
@umbric, saya tidak yakin tetapi hanya jika lambda digunakan di peta.
0xc0de

Jawaban:

662

mapdalam beberapa kasus mungkin lebih cepat secara mikroskopis (ketika Anda TIDAK membuat lambda untuk tujuan itu, tetapi menggunakan fungsi yang sama di peta dan listcomp). Pemahaman daftar mungkin lebih cepat dalam kasus lain dan kebanyakan (tidak semua) pythonista menganggapnya lebih langsung dan lebih jelas.

Contoh keunggulan kecepatan kecil peta saat menggunakan fungsi yang persis sama:

$ 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

Contoh bagaimana perbandingan kinerja terbalik sepenuhnya ketika peta membutuhkan lambda:

$ 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
Alex Martelli
sumber
39
Yap, memang panduan gaya Python internal kami di tempat kerja secara eksplisit merekomendasikan listcomps terhadap peta dan filter (bahkan tidak menyebutkan peta peningkatan kinerja kecil tapi terukur dapat memberikan dalam beberapa kasus ;-).
Alex Martelli
46
Bukan untuk kibash pada poin gaya Alex yang tak terbatas, tetapi terkadang peta tampaknya lebih mudah dibaca oleh saya: data = peta (str, some_list_of_objects). Beberapa yang lain ... operator.attrgetter, operator.itemgetter, dll.
Gregg Lind
57
map(operator.attrgetter('foo'), objs)lebih mudah dibaca daripada [o.foo for o in objs]?!
Alex Martelli
52
@Alex: Saya lebih suka untuk tidak memperkenalkan nama yang tidak perlu, seperti di osini, dan contoh Anda menunjukkan alasannya.
Reid Barton
29
Saya berpikir bahwa @GreggLind benar, dengan str()contohnya.
Eric O Lebigot
474

Kasing

  • Kasus umum : Hampir selalu, Anda akan ingin menggunakan pemahaman daftar dalam python karena akan lebih jelas apa yang Anda lakukan untuk pemrogram pemula yang membaca kode Anda. (Ini tidak berlaku untuk bahasa lain, di mana idiom lain mungkin berlaku.) Bahkan akan lebih jelas apa yang Anda lakukan untuk programmer python, karena pemahaman daftar adalah standar de-facto dalam python untuk iterasi; mereka diharapkan .
  • Kasus yang kurang umum : Namun jika Anda sudah memiliki fungsi yang didefinisikan , sering kali wajar untuk digunakan map, meskipun itu dianggap 'unpythonic'. Misalnya, map(sum, myLists)lebih elegan / singkat daripada [sum(x) for x in myLists]. Anda mendapatkan keanggunan karena tidak harus membuat variabel dummy (mis sum(x) for x...atau sum(_) for _...atau sum(readableName) for readableName...) yang harus Anda ketik dua kali, hanya untuk beralih. Argumen yang sama berlaku untuk filterdan reducedan apa pun dari itertoolsmodul: jika Anda sudah memiliki fungsi berguna, Anda bisa melanjutkan dan melakukan pemrograman fungsional. Ini mendapatkan keterbacaan dalam beberapa situasi, dan kehilangan pembacaannya di situasi lain (mis. Programmer pemula, banyak argumen) ... tetapi keterbacaan kode Anda sangat tergantung pada komentar Anda.
  • Hampir tidak pernah : Anda mungkin ingin menggunakan mapfungsi sebagai fungsi abstrak murni saat melakukan pemrograman fungsional, di mana Anda memetakan map, atau menjelajah map, atau mendapat manfaat dari berbicara tentang mapfungsi. Dalam Haskell misalnya, antarmuka functor disebut fmapmenggeneralisasi pemetaan atas struktur data apa pun. Ini sangat tidak biasa dalam python karena tata bahasa python memaksa Anda untuk menggunakan generator-style untuk berbicara tentang iterasi; Anda tidak dapat menggeneralisasikannya dengan mudah. (Ini terkadang baik dan kadang-kadang buruk.) Anda mungkin dapat menemukan contoh python langka di mana map(f, *lists)hal yang masuk akal untuk dilakukan. Contoh terdekat yang bisa saya temukan adalah sumEach = partial(map,sum), yang merupakan satu-liner yang kira-kira setara dengan:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Hanya menggunakan for-loop : Tentu saja Anda juga bisa menggunakan for-loop. Meskipun tidak elegan dari sudut pandang fungsional-pemrograman, kadang-kadang variabel non-lokal membuat kode lebih jelas dalam bahasa pemrograman imperatif seperti python, karena orang sangat terbiasa membaca kode seperti itu. For-loop juga, umumnya, paling efisien ketika Anda hanya melakukan operasi kompleks apa pun yang tidak membangun daftar seperti daftar-pemahaman dan peta dioptimalkan untuk (misalnya menjumlahkan, atau membuat pohon, dll.) - setidaknya efisien dalam hal memori (tidak harus dalam hal waktu, di mana saya harapkan paling tidak merupakan faktor konstan, kecuali beberapa cegukan pengumpulan sampah patologis yang jarang terjadi).

"Pythonisme"

Saya tidak suka kata "pythonic" karena saya tidak menemukan bahwa pythonic selalu anggun di mata saya. Namun demikian, mapdan filterdan fungsi-fungsi serupa (seperti itertoolsmodul yang sangat berguna ) mungkin dianggap unpythonic dalam hal gaya.

Kemalasan

Dalam hal efisiensi, seperti kebanyakan konstruksi pemrograman fungsional, PETA DAPAT MENJADI LAZY , dan sebenarnya malas dengan python. Itu berarti Anda dapat melakukan ini (dalam python3 ) dan komputer Anda tidak akan kehabisan memori dan kehilangan semua data yang belum disimpan:

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

Coba lakukan itu dengan pemahaman daftar:

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

Perhatikan bahwa pemahaman daftar juga inheren malas, tetapi python telah memilih untuk mengimplementasikannya sebagai tidak malas . Namun demikian, python tidak mendukung pemahaman daftar malas dalam bentuk ekspresi generator, sebagai berikut:

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

Anda pada dasarnya dapat menganggap [...]sintaks sebagai meneruskan ekspresi generator ke konstruktor daftar, seperti list(x for x in range(5)).

Contoh singkat dibikin

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))})

Pemahaman daftar tidak malas, jadi mungkin memerlukan lebih banyak memori (kecuali jika Anda menggunakan pemahaman generator). Kurung kotak [...]sering membuat hal-hal yang jelas, terutama ketika dalam kurung. Di sisi lain, terkadang Anda berakhir seperti mengetik [x for x in.... Selama Anda membuat variabel iterator Anda singkat, daftar pemahaman biasanya lebih jelas jika Anda tidak membuat indentasi kode Anda. Tetapi Anda selalu bisa membuat indentasi kode Anda.

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

atau memecah hal-hal:

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

Perbandingan efisiensi untuk python3

map sekarang malas:

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

Karena itu jika Anda tidak akan menggunakan semua data Anda, atau tidak tahu sebelumnya berapa banyak data yang Anda butuhkan, mapdi python3 (dan ekspresi generator di python2 atau python3) akan menghindari penghitungan nilainya sampai saat-saat terakhir yang diperlukan. Biasanya ini biasanya akan melebihi biaya overhead yang digunakan map. Kelemahannya adalah ini sangat terbatas dalam python dibandingkan dengan sebagian besar bahasa fungsional: Anda hanya mendapatkan manfaat ini jika Anda mengakses data Anda dari kiri ke kanan "dalam urutan", karena ekspresi generator python hanya dapat dievaluasi urutannya x[0], x[1], x[2], ....

Namun katakanlah kita memiliki fungsi pra-dibuat yang fkita inginkan map, dan kita mengabaikan kemalasan mapdengan segera memaksa evaluasi dengannya list(...). Kami mendapatkan beberapa hasil yang sangat menarik:

% 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>)

Hasilnya adalah dalam bentuk AAA / BBB / CCC di mana A dilakukan dengan pada sekitar-2010 Intel workstation dengan python 3..?., Dan B dan C dilakukan dengan sekitar-2013 workstation AMD dengan python 3.2.1, dengan perangkat keras yang sangat berbeda. Hasilnya tampaknya bahwa pemahaman peta dan daftar sebanding dalam kinerja, yang paling kuat dipengaruhi oleh faktor acak lainnya. Satu-satunya hal yang dapat kami katakan adalah bahwa, anehnya, sementara kami mengharapkan daftar pemahaman [...]untuk berkinerja lebih baik daripada ekspresi generator (...), mapadalah JUGA lebih efisien daripada ekspresi generator (sekali lagi dengan asumsi bahwa semua nilai dievaluasi / digunakan).

Penting untuk disadari bahwa tes-tes ini mengasumsikan fungsi yang sangat sederhana (fungsi identitas); namun ini baik-baik saja karena jika fungsinya rumit, maka overhead kinerja akan diabaikan dibandingkan dengan faktor-faktor lain dalam program. (Mungkin masih menarik untuk menguji dengan hal-hal sederhana lainnya seperti f=lambda x:x+x)

Jika Anda ahli membaca perakitan python, Anda dapat menggunakan dismodul untuk melihat apakah itu yang sebenarnya terjadi di balik layar:

>>> 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 

Tampaknya lebih baik menggunakan [...]sintaks daripada list(...). Sedihnya, mapkelasnya agak buram untuk dibongkar, tetapi kita dapat melakukannya karena dengan tes kecepatan kami.

ninjagecko
sumber
5
"modul itertools yang sangat berguna mungkin dianggap unpythonic dalam hal gaya". Hmm. Saya tidak suka istilah "Pythonic", jadi dalam beberapa hal saya tidak peduli apa artinya, tapi saya tidak berpikir itu adil bagi mereka yang menggunakannya, untuk mengatakan bahwa menurut built-in "Pythonicness" bawaan mapdan filterbersama dengan perpustakaan standar itertoolsadalah gaya yang pada dasarnya buruk. Kecuali jika GvR benar-benar mengatakan bahwa mereka adalah kesalahan yang mengerikan atau semata-mata untuk kinerja, satu-satunya kesimpulan alami jika itu yang dikatakan "Pythonicness" adalah melupakannya sebagai hal yang bodoh ;-)
Steve Jessop
4
@SteveJessop: Sebenarnya, Guido berpikir bahwa menjatuhkan map/ filteradalah ide yang bagus untuk Python 3 , dan hanya pemberontakan oleh Pythonista lainnya yang menyimpannya di namespace bawaan (saat reducedipindahkan ke functools). Saya pribadi tidak setuju ( mapdan filterbaik-baik saja dengan fungsi yang telah ditentukan, terutama built-in, tidak pernah menggunakannya jika lambdadiperlukan), tetapi GvR pada dasarnya menyebut mereka bukan Pythonic selama bertahun-tahun.
ShadowRanger
@ShadowRanger: benar, tetapi apakah GvR pernah berencana untuk menghapus itertools? Bagian yang saya kutip dari jawaban ini adalah klaim utama yang membingungkan saya. Saya tidak tahu apakah di dunia idealnya, mapdan filterakan pindah ke itertools(atau functools) atau pergi sepenuhnya, tetapi yang mana yang terjadi, sekali orang mengatakan bahwa itu itertoolstidak sepenuhnya Phythonic, maka saya tidak benar-benar tahu apa "Pythonic" itu seharusnya berarti tetapi saya tidak berpikir itu bisa menjadi sesuatu yang mirip dengan "apa yang direkomendasikan GvR digunakan orang".
Steve Jessop
2
@SteveJessop: Saya hanya menangani map/ filter, tidak itertools. Pemrograman fungsional adalah Pythonic yang sempurna ( itertools, functoolsdan operatorsemuanya dirancang khusus dengan pemrograman fungsional dalam pikiran, dan saya menggunakan idiom fungsional dalam Python sepanjang waktu), dan itertoolsmenyediakan fitur yang akan menyusahkan untuk mengimplementasikan diri Anda, Ini secara khusus mapdan filtermenjadi berlebihan dengan ekspresi generator itu membuat Guido membenci mereka. itertoolsselalu baik-baik saja.
ShadowRanger
1
Saya bisa menyukai jawaban ini jika ada cara. Diterangkan dengan baik.
NelsonGon
95

Python 2: Anda harus menggunakan mapdan filterbukannya daftar pemahaman.

Sebuah Tujuan alasan mengapa Anda harus memilih mereka meskipun mereka tidak "Pythonic" adalah ini:
Mereka membutuhkan fungsi / lambdas sebagai argumen, yang memperkenalkan lingkup baru .

Saya telah digigit oleh ini lebih dari sekali:

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

tetapi jika saya mengatakan:

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

maka semuanya akan baik-baik saja.

Bisa dibilang saya bodoh karena menggunakan nama variabel yang sama dalam cakupan yang sama.

Bukan saya. Kode awalnya baik-baik saja - keduanya xtidak dalam cakupan yang sama.
Hanya setelah saya memindahkan blok dalam ke bagian kode yang berbeda masalah muncul (baca: masalah selama pemeliharaan, bukan pengembangan), dan saya tidak mengharapkannya.

Ya, jika Anda tidak pernah membuat kesalahan ini, maka daftar pemahaman lebih elegan.
Tetapi dari pengalaman pribadi (dan dari melihat orang lain membuat kesalahan yang sama) Saya telah melihatnya terjadi cukup sering sehingga saya pikir itu tidak sebanding dengan rasa sakit yang harus Anda alami ketika bug ini menyusup ke dalam kode Anda.

Kesimpulan:

Gunakan mapdan filter. Mereka mencegah bug terkait lingkup yang sulit didiagnosis secara halus.

Catatan:

Jangan lupa untuk mempertimbangkan menggunakan imapdan ifilter( itertoolsjika) sesuai untuk situasi Anda!

pengguna541686
sumber
7
Terima kasih telah menunjukkan ini. Tidak secara eksplisit terpikir oleh saya bahwa pemahaman daftar berada dalam lingkup yang sama dan bisa menjadi masalah. Dengan mengatakan itu, saya pikir beberapa jawaban lain memperjelas bahwa pemahaman daftar harus menjadi pendekatan default sebagian besar waktu tetapi bahwa ini adalah sesuatu yang perlu diingat. Ini juga merupakan pengingat umum yang baik untuk menjaga fungsi (dan dengan demikian ruang lingkup) kecil dan memiliki tes unit menyeluruh dan menggunakan pernyataan tegas.
TimothyAWiseman
13
@ wim: Ini hanya tentang Python 2, meskipun itu berlaku untuk Python 3 jika Anda ingin tetap kompatibel. Saya tahu tentang hal itu dan saya telah menggunakan Python untuk sementara waktu sekarang (ya, lebih dari hanya beberapa bulan), namun itu terjadi pada saya. Saya telah melihat orang lain yang lebih pintar dari saya jatuh ke dalam perangkap yang sama. Jika Anda sangat cerdas dan / atau berpengalaman bahwa ini bukan masalah bagi Anda maka saya senang untuk Anda, saya tidak berpikir kebanyakan orang seperti Anda. Jika ya, tidak akan ada keinginan untuk memperbaikinya dengan Python 3.
user541686
12
Maaf, tetapi Anda menulis ini pada akhir 2012, setelah python 3 muncul, dan jawabannya berbunyi seperti Anda merekomendasikan gaya pengkodean python yang tidak populer hanya karena Anda digigit bug saat memotong-dan- kode paste. Saya tidak pernah mengaku pintar atau berpengalaman, saya hanya tidak setuju bahwa klaim berani dibenarkan oleh alasan Anda.
wim
8
@wim: Hah? Python 2 masih digunakan di banyak tempat, fakta bahwa Python 3 ada tidak mengubah itu. Dan ketika Anda mengatakan "itu bukan bug yang halus untuk siapa pun yang telah menggunakan Python lebih dari beberapa bulan" kalimat itu secara harfiah berarti "ini hanya menyangkut pengembang yang tidak berpengalaman" (jelas bukan Anda). Dan sebagai catatan, Anda jelas tidak membaca jawabannya karena saya katakan dengan berani bahwa saya bergerak , bukan menyalin, kode. Bug salin-rekat sangat seragam di berbagai bahasa. Jenis bug ini lebih unik untuk Python karena pelingkupannya; lebih halus & lebih mudah untuk dilupakan dan dilewatkan.
user541686
3
Ini masih bukan alasan logis untuk beralih ke mapdan / atau filter. Jika ada, terjemahan yang paling langsung dan logis untuk menghindari masalah Anda adalah bukan untuk map(lambda x: x ** 2, numbers)ekspresi generator list(x ** 2 for x in numbers)yang tidak bocor, seperti yang ditunjukkan JeromeJ. Lihatlah Mehrdad, jangan mengambil downvote secara pribadi, saya hanya sangat tidak setuju dengan alasan Anda di sini.
wim
46

Sebenarnya, mapdan daftar pemahaman berperilaku sangat berbeda dalam bahasa Python 3. Lihatlah program Python 3 berikut:

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

Anda mungkin mengharapkannya untuk mencetak baris "[1, 4, 9]" dua kali, tetapi ia mencetak "[1, 4, 9]" diikuti oleh "[]". Pertama kali Anda melihatnya squarestampaknya berperilaku sebagai urutan tiga elemen, tetapi yang kedua sebagai yang kosong.

Dalam bahasa Python 2 mapmengembalikan daftar lama polos, seperti halnya pemahaman daftar lakukan di kedua bahasa. Intinya adalah bahwa nilai pengembalian mapdi Python 3 (dan imapPython 2) bukan daftar - itu adalah iterator!

Elemen-elemen tersebut dikonsumsi ketika Anda beralih pada iterator tidak seperti ketika Anda mengulangi daftar. Inilah sebabnya mengapa squaresterlihat kosong di print(list(squares))baris terakhir .

Untuk meringkas:

  • Ketika berhadapan dengan iterator, Anda harus ingat bahwa mereka stateful dan bahwa mereka bermutasi saat Anda melintasi mereka.
  • Daftar lebih mudah diprediksi karena mereka hanya berubah ketika Anda secara eksplisit mengubahnya; mereka adalah wadah .
  • Dan bonus: angka, string, dan tupel bahkan lebih dapat diprediksi karena tidak bisa berubah sama sekali; mereka adalah nilai .
raek
sumber
ini mungkin argumen terbaik untuk memahami daftar. python peta bukan peta fungsional tetapi anak tiri berkepala merah implementasi fungsional. Sangat sedih, karena saya benar-benar tidak menyukai pemahaman.
semiomant
@semiomant Saya akan mengatakan peta malas (seperti di python3) lebih 'fungsional' dari peta bersemangat (seperti di python2). Misalnya, peta di Haskell malas (yah, semua yang ada di Haskell malas ...). Bagaimanapun, lazy map lebih baik untuk chaining map - jika Anda memiliki peta yang diterapkan untuk peta yang diterapkan ke peta, Anda memiliki daftar untuk masing-masing panggilan peta perantara di python2, sedangkan di python3 Anda hanya memiliki satu daftar yang dihasilkan, sehingga lebih hemat memori. .
MnZrK
Saya kira yang saya inginkan adalah mapmenghasilkan struktur data, bukan iterator. Tapi mungkin iterator malas lebih mudah daripada struktur data malas. Bahan untuk dipikirkan. Terima kasih @MnZrK
semiomant
Anda ingin mengatakan peta mengembalikan iterable, bukan iterator.
user541686
16

Saya menemukan daftar pemahaman umumnya lebih ekspresif dari apa yang saya coba lakukan daripada map- mereka berdua menyelesaikannya, tetapi yang pertama menghemat beban mental mencoba memahami apa yang bisa menjadi lambdaekspresi yang kompleks .

Ada juga wawancara di luar sana (saya tidak dapat menemukannya secara langsung) di mana Guido mencantumkan lambdafungsi fungsional sebagai hal yang paling disesalnya tentang penerimaan ke Python, sehingga Anda dapat membuat argumen bahwa mereka tidak Pythonic berdasarkan dari itu.

Dan
sumber
9
Ya, desah, tetapi niat asli Guido untuk menghapus lambda sama sekali di Python 3 mendapat rentetan melobi menentangnya, jadi dia kembali pada itu meskipun saya mendukung - ah, tebak lambda terlalu berguna dalam banyak kasus SEDERHANA , satu-satunya Masalahnya adalah ketika ia melampaui batas SIMPLE atau ditugaskan ke sebuah nama (dalam hal yang terakhir itu adalah duplikat dari def!) yang konyol.
Alex Martelli
1
Wawancara yang Anda pikirkan adalah wawancara ini: amk.ca/python/writing/gvr-interview , di mana Guido mengatakan "Kadang-kadang saya terlalu cepat dalam menerima kontribusi, dan kemudian menyadari bahwa itu adalah kesalahan. Salah satu contohnya adalah beberapa fitur pemrograman fungsional, seperti fungsi lambda. lambda adalah kata kunci yang memungkinkan Anda membuat fungsi anonim kecil, fungsi bawaan seperti memetakan, memfilter, dan mengurangi menjalankan fungsi pada jenis urutan, seperti daftar. "
J. Taylor
3
@Alex, saya tidak memiliki pengalaman bertahun-tahun, tetapi saya telah melihat daftar pemahaman yang jauh lebih rumit daripada lambda. Tentu saja, menyalahgunakan fitur bahasa selalu merupakan godaan yang sulit untuk ditolak. Sangat menarik bahwa pemahaman daftar (secara empiris) tampaknya lebih rentan terhadap pelecehan daripada lambdas, meskipun saya tidak yakin mengapa itu harus terjadi. Saya juga akan menunjukkan bahwa "pincang" tidak selalu merupakan hal yang buruk. Mengurangi ruang lingkup "hal-hal yang mungkin dilakukan oleh garis ini" kadang-kadang dapat memudahkan pembaca. Misalnya, constkata kunci dalam C ++ adalah kemenangan besar di sepanjang baris ini.
Stuart Berg
> guido. Yang merupakan bukti lain bahwa Guido keluar dari pikirannya. Tentu saja lambdadibuat sangat timpang (tidak ada pernyataan ..) sehingga sulit digunakan dan dibatasi.
javadba
16

Ini ada satu kemungkinan kasus:

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

melawan:

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

Saya menduga zip () adalah overhead yang tidak menguntungkan dan tidak perlu yang harus Anda nikmati jika Anda bersikeras menggunakan pemahaman daftar alih-alih peta. Akan lebih bagus jika seseorang mengklarifikasi hal ini apakah secara afirmatif atau negatif.

Andz
sumber
"[op1 * op2 dari op1, op2 di zip (list1, list2)]" | s / form / for / Dan daftar setara dengan zip keluar: (kurang dapat dibaca) [list1 [i] * list2 [i] untuk saya dalam kisaran (len (list1))]
lemah
2
Seharusnya "untuk" bukan "dari" dalam kutipan kode kedua Anda, @ andz, dan dalam komentar @ lemah juga. Saya pikir saya telah menemukan pendekatan sintaksis baru untuk daftar pemahaman ... Sial.
physicsmichael
4
untuk menambahkan komentar yang sangat terlambat, Anda dapat membuat zipmalas dengan menggunakanitertools.izip
tacaswell
5
Saya rasa saya masih lebih suka map(operator.mul, list1, list2). Ekspresi sisi kiri yang sangat sederhana inilah yang membuat pemahaman menjadi kikuk.
Yann Vernier
1
Saya tidak menyadari bahwa peta dapat mengambil beberapa iterables sebagai input untuk fungsinya dan dengan demikian dapat menghindari zip.
bli
16

Jika Anda berencana untuk menulis kode asinkron, paralel, atau terdistribusi, Anda mungkin akan lebih suka mapdaripada pemahaman daftar - karena sebagian besar paket asinkron, paralel, atau terdistribusi menyediakan mapfungsi untuk membebani python yang berlebihan map. Kemudian dengan meneruskan mapfungsi yang sesuai ke kode Anda yang lain, Anda mungkin tidak perlu memodifikasi kode serial asli Anda agar dapat berjalan secara paralel (dll).

Mike McKerns
sumber
9

Jadi karena Python 3, map()adalah iterator, Anda perlu mengingat apa yang Anda butuhkan: iterator atau listobjek.

Seperti @AlexMartelli telah sebutkan , map()lebih cepat dari daftar pemahaman hanya jika Anda tidak menggunakan lambdafungsi.

Saya akan menyajikan beberapa perbandingan waktu.

Python 3.5.2 dan CPython
Saya telah menggunakan notebook Jupiter dan terutama %timeitbuilt-in magic command
Measurements : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

Mendirikan:

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

Fungsi bawaan:

%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 fungsi:

%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

Ada juga yang namanya ekspresi generator, lihat PEP-0289 . Jadi saya pikir akan bermanfaat untuk menambahkannya sebagai perbandingan

%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

Anda membutuhkan listobjek:

Gunakan pemahaman daftar jika itu fungsi khusus, gunakan list(map())jika ada fungsi builtin

Anda tidak perlu listobjek, Anda hanya perlu objek iterable:

Selalu gunakan map()!

vishes_shell
sumber
1

Saya menjalankan tes cepat membandingkan tiga metode untuk memohon metode objek. Perbedaan waktu, dalam hal ini, dapat diabaikan dan merupakan masalah fungsi yang dimaksud (lihat tanggapan @Alex Martelli ). Di sini, saya melihat metode berikut:

# 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]

Saya melihat daftar (disimpan dalam variabel vals) dari kedua bilangan bulat (Python int) dan angka floating point (Python float) untuk meningkatkan ukuran daftar. Kelas dummy berikut DummyNumdipertimbangkan:

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

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

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

Secara khusus, addmetode. The __slots__atribut adalah optimasi sederhana dengan Python untuk menentukan total memori yang dibutuhkan oleh kelas (atribut), mengurangi ukuran memori. Berikut adalah plot yang dihasilkan.

Kinerja pemetaan metode objek Python

Seperti yang dinyatakan sebelumnya, teknik yang digunakan membuat perbedaan minimal dan Anda harus kode dengan cara yang paling mudah dibaca oleh Anda, atau dalam keadaan tertentu. Dalam hal ini, pemahaman daftar ( map_comprehensionteknik) adalah yang tercepat untuk kedua jenis penambahan dalam suatu objek, terutama dengan daftar yang lebih pendek.

Kunjungi pastebin ini untuk sumber yang digunakan untuk menghasilkan plot dan data.

Craymichael
sumber
1
Seperti yang sudah dijelaskan dalam jawaban lain, maplebih cepat hanya jika fungsinya dipanggil dengan cara yang sama persis (yaitu [*map(f, vals)]vs. [f(x) for x in vals]). Jadi list(map(methodcaller("add"), vals))lebih cepat daripada [methodcaller("add")(x) for x in vals]. mapmungkin tidak lebih cepat ketika rekan perulangan menggunakan metode panggilan yang berbeda yang dapat menghindari beberapa overhead (mis. x.add()menghindari methodcalleratau ekspresi overhead lambda). Untuk kasus uji khusus ini, [*map(DummyNum.add, vals)]akan lebih cepat (karena DummyNum.add(x)dan x.add()pada dasarnya memiliki kinerja yang sama).
GZ0
1
Omong-omong, list()panggilan eksplisit sedikit lebih lambat dari daftar. Untuk perbandingan yang adil, Anda perlu menulis [*map(...)].
GZ0
@ GZ0 terima kasih atas umpan balik yang bagus! Semua masuk akal, dan saya tidak menyadari bahwa list()panggilan meningkat di atas kepala. Seharusnya menghabiskan lebih banyak waktu membaca jawaban. Saya akan menjalankan kembali tes ini untuk perbandingan yang adil, betapapun kecilnya perbedaan yang mungkin terjadi.
craymichael
0

Saya menganggap bahwa cara paling Pythonic adalah dengan menggunakan daftar pemahaman daripada mapdan filter. Alasannya adalah bahwa pemahaman daftar lebih jelas daripada mapdan filter.

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

Seperti yang Anda lihat, pemahaman tidak memerlukan lambdaekspresi tambahan sebagai mapkebutuhan. Selain itu, pemahaman juga memungkinkan penyaringan dengan mudah, sementara mapmengharuskan filterpenyaringan.

lmiguelvargasf
sumber
0

Saya mencoba kode dengan @ alex-martelli tetapi menemukan beberapa perbedaan

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

peta membutuhkan jumlah waktu yang sama bahkan untuk rentang yang sangat besar saat menggunakan pemahaman daftar membutuhkan banyak waktu seperti terbukti dari kode saya. Jadi selain dianggap "unpythonic", saya belum menghadapi masalah kinerja yang berkaitan dengan penggunaan peta.

Mohit Raj
sumber
3
Ini adalah pertanyaan yang sangat lama, dan jawaban yang Anda maksud sangat mungkin ditulis dengan mengacu pada Python 2, di mana mapmengembalikan daftar. Dalam Python 3, mapdievaluasi malas, jadi hanya memanggil maptidak menghitung elemen daftar baru, maka mengapa Anda mendapatkan waktu sesingkat itu.
kaya3
Saya pikir Anda menggunakan Python 3.x Ketika saya mengajukan pertanyaan ini, Python 3 baru saja dirilis dan Python 2.x sangat standar.
TimothyAWiseman