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?
python
list-comprehension
map-function
TimothyAWiseman
sumber
sumber
Jawaban:
map
dalam 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:
Contoh bagaimana perbandingan kinerja terbalik sepenuhnya ketika peta membutuhkan lambda:
sumber
map(operator.attrgetter('foo'), objs)
lebih mudah dibaca daripada[o.foo for o in objs]
?!o
sini, dan contoh Anda menunjukkan alasannya.str()
contohnya.Kasing
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 (missum(x) for x...
atausum(_) for _...
atausum(readableName) for readableName...
) yang harus Anda ketik dua kali, hanya untuk beralih. Argumen yang sama berlaku untukfilter
danreduce
dan apa pun dariitertools
modul: 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.map
fungsi sebagai fungsi abstrak murni saat melakukan pemrograman fungsional, di mana Anda memetakanmap
, atau menjelajahmap
, atau mendapat manfaat dari berbicara tentangmap
fungsi. Dalam Haskell misalnya, antarmuka functor disebutfmap
menggeneralisasi 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 manamap(f, *lists)
hal yang masuk akal untuk dilakukan. Contoh terdekat yang bisa saya temukan adalahsumEach = partial(map,sum)
, yang merupakan satu-liner yang kira-kira setara dengan: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,
map
danfilter
dan fungsi-fungsi serupa (sepertiitertools
modul 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:
Coba lakukan itu dengan pemahaman daftar:
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:
Anda pada dasarnya dapat menganggap
[...]
sintaks sebagai meneruskan ekspresi generator ke konstruktor daftar, sepertilist(x for x in range(5))
.Contoh singkat dibikin
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.atau memecah hal-hal:
Perbandingan efisiensi untuk python3
map
sekarang malas:Karena itu jika Anda tidak akan menggunakan semua data Anda, atau tidak tahu sebelumnya berapa banyak data yang Anda butuhkan,
map
di 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 digunakanmap
. 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 urutannyax[0], x[1], x[2], ...
.Namun katakanlah kita memiliki fungsi pra-dibuat yang
f
kita inginkanmap
, dan kita mengabaikan kemalasanmap
dengan segera memaksa evaluasi dengannyalist(...)
. Kami mendapatkan beberapa hasil yang sangat menarik: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(...)
,map
adalah 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
dis
modul untuk melihat apakah itu yang sebenarnya terjadi di balik layar:Tampaknya lebih baik menggunakan
[...]
sintaks daripadalist(...)
. Sedihnya,map
kelasnya agak buram untuk dibongkar, tetapi kita dapat melakukannya karena dengan tes kecepatan kami.sumber
map
danfilter
bersama dengan perpustakaan standaritertools
adalah 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 ;-)map
/filter
adalah ide yang bagus untuk Python 3 , dan hanya pemberontakan oleh Pythonista lainnya yang menyimpannya di namespace bawaan (saatreduce
dipindahkan kefunctools
). Saya pribadi tidak setuju (map
danfilter
baik-baik saja dengan fungsi yang telah ditentukan, terutama built-in, tidak pernah menggunakannya jikalambda
diperlukan), tetapi GvR pada dasarnya menyebut mereka bukan Pythonic selama bertahun-tahun.itertools
? Bagian yang saya kutip dari jawaban ini adalah klaim utama yang membingungkan saya. Saya tidak tahu apakah di dunia idealnya,map
danfilter
akan pindah keitertools
(ataufunctools
) atau pergi sepenuhnya, tetapi yang mana yang terjadi, sekali orang mengatakan bahwa ituitertools
tidak 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".map
/filter
, tidakitertools
. Pemrograman fungsional adalah Pythonic yang sempurna (itertools
,functools
danoperator
semuanya dirancang khusus dengan pemrograman fungsional dalam pikiran, dan saya menggunakan idiom fungsional dalam Python sepanjang waktu), danitertools
menyediakan fitur yang akan menyusahkan untuk mengimplementasikan diri Anda, Ini secara khususmap
danfilter
menjadi berlebihan dengan ekspresi generator itu membuat Guido membenci mereka.itertools
selalu baik-baik saja.Python 2: Anda harus menggunakan
map
danfilter
bukannya 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:
tetapi jika saya mengatakan:
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
x
tidak 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
map
danfilter
. Mereka mencegah bug terkait lingkup yang sulit didiagnosis secara halus.Catatan:
Jangan lupa untuk mempertimbangkan menggunakan
imap
danifilter
(itertools
jika) sesuai untuk situasi Anda!sumber
map
dan / ataufilter
. Jika ada, terjemahan yang paling langsung dan logis untuk menghindari masalah Anda adalah bukan untukmap(lambda x: x ** 2, numbers)
ekspresi generatorlist(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.Sebenarnya,
map
dan daftar pemahaman berperilaku sangat berbeda dalam bahasa Python 3. Lihatlah program Python 3 berikut:Anda mungkin mengharapkannya untuk mencetak baris "[1, 4, 9]" dua kali, tetapi ia mencetak "[1, 4, 9]" diikuti oleh "[]". Pertama kali Anda melihatnya
squares
tampaknya berperilaku sebagai urutan tiga elemen, tetapi yang kedua sebagai yang kosong.Dalam bahasa Python 2
map
mengembalikan daftar lama polos, seperti halnya pemahaman daftar lakukan di kedua bahasa. Intinya adalah bahwa nilai pengembalianmap
di Python 3 (danimap
Python 2) bukan daftar - itu adalah iterator!Elemen-elemen tersebut dikonsumsi ketika Anda beralih pada iterator tidak seperti ketika Anda mengulangi daftar. Inilah sebabnya mengapa
squares
terlihat kosong diprint(list(squares))
baris terakhir .Untuk meringkas:
sumber
map
menghasilkan struktur data, bukan iterator. Tapi mungkin iterator malas lebih mudah daripada struktur data malas. Bahan untuk dipikirkan. Terima kasih @MnZrKSaya 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 menjadilambda
ekspresi yang kompleks .Ada juga wawancara di luar sana (saya tidak dapat menemukannya secara langsung) di mana Guido mencantumkan
lambda
fungsi fungsional sebagai hal yang paling disesalnya tentang penerimaan ke Python, sehingga Anda dapat membuat argumen bahwa mereka tidak Pythonic berdasarkan dari itu.sumber
const
kata kunci dalam C ++ adalah kemenangan besar di sepanjang baris ini.lambda
dibuat sangat timpang (tidak ada pernyataan ..) sehingga sulit digunakan dan dibatasi.Ini ada satu kemungkinan kasus:
melawan:
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.
sumber
zip
malas dengan menggunakanitertools.izip
map(operator.mul, list1, list2)
. Ekspresi sisi kiri yang sangat sederhana inilah yang membuat pemahaman menjadi kikuk.Jika Anda berencana untuk menulis kode asinkron, paralel, atau terdistribusi, Anda mungkin akan lebih suka
map
daripada pemahaman daftar - karena sebagian besar paket asinkron, paralel, atau terdistribusi menyediakanmap
fungsi untuk membebani python yang berlebihanmap
. Kemudian dengan meneruskanmap
fungsi yang sesuai ke kode Anda yang lain, Anda mungkin tidak perlu memodifikasi kode serial asli Anda agar dapat berjalan secara paralel (dll).sumber
Jadi karena Python 3,
map()
adalah iterator, Anda perlu mengingat apa yang Anda butuhkan: iterator ataulist
objek.Seperti @AlexMartelli telah sebutkan ,
map()
lebih cepat dari daftar pemahaman hanya jika Anda tidak menggunakanlambda
fungsi.Saya akan menyajikan beberapa perbandingan waktu.
Python 3.5.2 dan CPython
Saya telah menggunakan notebook Jupiter dan terutama
%timeit
built-in magic commandMeasurements : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns
Mendirikan:
Fungsi bawaan:
lambda
fungsi:Ada juga yang namanya ekspresi generator, lihat PEP-0289 . Jadi saya pikir akan bermanfaat untuk menambahkannya sebagai perbandingan
Anda membutuhkan
list
objek:Gunakan pemahaman daftar jika itu fungsi khusus, gunakan
list(map())
jika ada fungsi builtinAnda tidak perlu
list
objek, Anda hanya perlu objek iterable:Selalu gunakan
map()
!sumber
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:
Saya melihat daftar (disimpan dalam variabel
vals
) dari kedua bilangan bulat (Pythonint
) dan angka floating point (Pythonfloat
) untuk meningkatkan ukuran daftar. Kelas dummy berikutDummyNum
dipertimbangkan:Secara khusus,
add
metode. 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.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_comprehension
teknik) 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.
sumber
map
lebih cepat hanya jika fungsinya dipanggil dengan cara yang sama persis (yaitu[*map(f, vals)]
vs.[f(x) for x in vals]
). Jadilist(map(methodcaller("add"), vals))
lebih cepat daripada[methodcaller("add")(x) for x in vals]
.map
mungkin tidak lebih cepat ketika rekan perulangan menggunakan metode panggilan yang berbeda yang dapat menghindari beberapa overhead (mis.x.add()
menghindarimethodcaller
atau ekspresi overhead lambda). Untuk kasus uji khusus ini,[*map(DummyNum.add, vals)]
akan lebih cepat (karenaDummyNum.add(x)
danx.add()
pada dasarnya memiliki kinerja yang sama).list()
panggilan eksplisit sedikit lebih lambat dari daftar. Untuk perbandingan yang adil, Anda perlu menulis[*map(...)]
.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.Saya menganggap bahwa cara paling Pythonic adalah dengan menggunakan daftar pemahaman daripada
map
danfilter
. Alasannya adalah bahwa pemahaman daftar lebih jelas daripadamap
danfilter
.Seperti yang Anda lihat, pemahaman tidak memerlukan
lambda
ekspresi tambahan sebagaimap
kebutuhan. Selain itu, pemahaman juga memungkinkan penyaringan dengan mudah, sementaramap
mengharuskanfilter
penyaringan.sumber
Saya mencoba kode dengan @ alex-martelli tetapi menemukan beberapa perbedaan
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.
sumber
map
mengembalikan daftar. Dalam Python 3,map
dievaluasi malas, jadi hanya memanggilmap
tidak menghitung elemen daftar baru, maka mengapa Anda mendapatkan waktu sesingkat itu.