Harita neden Python 3'te bir liste yerine bir harita nesnesi döndürüyor?


83

Python 3.x'in yeni dil tasarımını anlamakla ilgileniyorum .

Python 2.7'de şu işlevden hoşlanıyorum map:

Python 2.7.12
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: [2, 3, 4]

Ancak, Python 3.x'te işler değişti:

Python 3.5.1
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: <map at 0x4218390>

Nasıl olduğunu anlıyorum ama neden olduğuna dair bir referans bulamadım. Dil tasarımcıları neden bu seçimi yaptılar ki bu bence büyük bir acı getiriyor. Bu, anlaşmaları listelemek için geliştiricileri kol güreşi yapmak için miydi?

IMO, liste doğal olarak Functors olarak düşünülebilir ; ve bir şekilde şu şekilde düşünmem düşünülmüştü:

fmap :: (a -> b) -> f a -> f b

2
Gerekçe, liste anlamaları yerine neden oluşturucular kullandığımızla aynı olmalıdır. Tembel değerlendirmeyi kullanarak, büyük şeyleri hafızamızda tutmamız gerekmez. Kabul edilen yanıtı buradan kontrol edin: stackoverflow.com/questions/1303347/…
Moberg

8
Bunun size neden "çok fazla acı" getirdiğini açıklayabilir misiniz?
RemcoGerlich

3
Sanırım yıllarca kullanımın en yaygın kullanımlarının mapsonuç üzerinde basitçe yinelendiğini göstermesi nedeniyle. İhtiyacınız olmadığında bir liste oluşturmak verimsizdir, bu nedenle geliştiriciler maptembel olmaya karar verir . Burada performans için kazanılacak çok şey var ve kaybedilecek çok şey yok (Bir listeye ihtiyacınız varsa, sadece bir tane isteyin ... list(map(...))).
mgilson

3
Tamam, Functor modelini korumak ve List'in tembel bir versiyonunu sunmak yerine, bir şekilde haritalandığında bir listenin tembel bir değerlendirmesini zorlama kararı vermelerini ilginç buluyorum. Kendi seçimimi yapma hakkına sahip olmayı tercih ederdim, yani Jeneratör -> harita -> Jeneratör veya Liste -> harita -> Liste (bana bağlı)
NoIdeaHowToFixThis

4
@NoIdeaHowToFixThis, aslında size kalmış, eğer tüm listeye ihtiyacınız varsa, sadece bir listeye dönüştürün, cehennem kadar kolay
Netwave

Yanıtlar:


37

Ben haritası hala var nedeni düşünmek hiç zaman jeneratör ifadeler de var, hepsi fonksiyonu içine üzerine halka ve geçirilen birden yineleyici argümanlar alabilir olmasıdır:

>>> list(map(min, [1,2,3,4], [0,10,0,10]))
[0,2,0,4]

Bu, zip kullanmaktan biraz daha kolaydır:

>>> list(min(x, y) for x, y in zip([1,2,3,4], [0,10,0,10]))

Aksi takdirde, jeneratör ifadelerine hiçbir şey eklemez.


1
Bence liste anlayışlarının daha pitonik olduğunu vurgulama arzusu eklersek ve dil tasarımcıları bunu vurgulamak isterlerse, bence bu en yerinde cevaptır. @vishes_shell bir şekilde dil tasarımına yeterince odaklanmıyor.
NoIdeaHowToFixThis

2
İki liste eşit uzunlukta değilse Python 2 ve 3'te farklı sonuçlar üretir . c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))Python 2 ve python 3'te deneyin .
cdarke

1
Haritayı python3'ten tamamen kaldırmaya yönelik orijinal plan için bir referans: artima.com/weblogs/viewpost.jsp?thread=98196
Bernhard

Hmm, haritayı listeye sardığımda ne kadar garip, 1 element listesinin bir listesini alıyorum.
awiebe

24

Yineleyici döndürdüğü için, tam boyutlu listeyi bellekte saklamayı çıkarır. Böylece, gelecekte hafızanıza herhangi bir acı vermeyecek şekilde kolayca yineleyebilirsiniz. Muhtemelen tam bir listeye bile ihtiyacınız yok, ancak durumunuza ulaşıncaya kadar bir kısmına ihtiyacınız var.

Bu dokümanları yararlı bulabilirsiniz , yineleyiciler harika.

Bir veri akışını temsil eden bir nesne. Yineleyicinin __next__()yöntemine yapılan tekrarlanan çağrılar (veya bunu yerleşik işleve geçirerek next()), akıştaki ardışık öğeleri döndürür. Daha fazla veri olmadığında StopIterationbunun yerine bir istisna oluşur. Bu noktada, yineleyici nesnesi tükenmiştir ve __next__()yöntemine yapılan diğer çağrılar StopIterationtekrar yükselir. Yineleyiciler __iter__(), yineleyici nesnesini kendisi döndüren bir yönteme sahip olmalıdır, böylece her yineleyici yinelenebilir ve diğer yinelemelerin kabul edildiği çoğu yerde kullanılabilir. Dikkate değer bir istisna, birden çok yineleme geçişini deneyen koddur. Bir kap nesnesi (a gibi list), onu her aktarımınızda yeni ve yeni bir yineleyici üretir.iter()işlevi veya bir for döngüsü içinde kullanın. Bunu bir yineleyici ile denemek, önceki yineleme geçişinde kullanılan aynı bitmiş yineleyici nesnesini döndürerek boş bir kap gibi görünmesini sağlar.


14

Guido bu soruyu burada yanıtlıyor : " çünkü bir liste oluşturmak israf olur ".

Ayrıca doğru dönüşümün düzenli bir fordöngü kullanmak olduğunu söylüyor .

Dönüştürme map()2 ila 3 sadece yapışmasını basit durumda olmayabilir list( )etrafında. Guido ayrıca şunları söylüyor:

"Giriş dizileri eşit uzunlukta değilse, map() en kısa olanı sona erdiğinde duracaktır. map()Python 2.x ile tam uyumluluk için dizileri de sarınitertools.zip_longest() , örn.

map(func, *sequences)

olur

list(map(func, itertools.zip_longest(*sequences)))

"


3
Guido yorumu, map()işlevin bir işlevci olarak kullanımına değil , işlevin yan etkileri için çağrılır .
abukaj

4
İle dönüşüm zip_longestyanlıştır. Kullanmak zorunda itertools.starmapo eşdeğer olabilmesi için: list(starmap(func, zip_longest(*sequences))). Bunun nedeni zip_longest, demetler üretmesidir, bu nedenle arama sırasında olduğu gibi farklı argümanlar yerine functek bir nargüman alır . nmap(func, *sequences)
Bakuriu

12

Python 3'te birçok işlev (yalnızca mapdeğil zip,range ve diğerleri) tam listesi yerine bir yineleyici döndürür. Bir yineleyici isteyebilirsiniz (örn. Tüm listeyi bellekte tutmaktan kaçınmak için) veya bir liste isteyebilirsiniz (örn. İndeksleyebilmek için).

Bununla birlikte, Python 3'teki değişikliğin temel nedeninin, bir yineleyiciyi list(some_iterator)ters eşdeğerini kullanarak bir listeye dönüştürmenin önemsiz olduğunu düşünüyorum.iter(some_list) istenen sonucu elde etmemesi, çünkü tam liste zaten oluşturulmuş ve bellekte tutulmuş olduğunu düşünüyorum.

Örneğin, Python 3'te nesneyi oluşturmanın ve sonra onu bir listeye dönüştürmenin list(range(n))çok az maliyeti olduğu için gayet iyi çalışıyor range. Ancak, Python 2'de iter(range(n))herhangi bir bellek kaydetmez çünkü tam liste range()yineleyici oluşturulmadan önce oluşturulur.

Bu nedenle, Python 2'de, bir liste yerine yineleyici oluşturmak imapiçin for map( tam olarak eşdeğer olmasalar da ), xrangefor range, izipfor gibi ayrı işlevler gereklidir zip. Buna karşılık, Python 3 list(), gerekirse tam listeyi bir çağrı oluşturduğu için tek bir işlev gerektirir.


Python 2.7'de AFAIK, itertoolsdönüş yineleyicilerinden de işlev görür . Ayrıca, yineleyicileri tembel listeler olarak görmezdim, çünkü listeler birden çok kez yinelenebilir ve rastgele erişilebilir.
abukaj

@abukaj tamam teşekkürler, daha net olmak için cevabımı düzenledim
Chris_Rands

@IgorRivin ne demek istiyorsun? Python 3 mapnesnelerinin bir next()yöntemi vardır. Python 3 rangearalığı nesneleri bildiğim kadarıyla yineleyiciler değil
Chris_Rands

Anaconda dağıtım python 3.6.2'de @Chris_Rands, yapmak foo = map(lambda x: x, [1, 2, 3])bir harita nesnesi döndürür foo. yapmak foo.next()bir hatayla geri geliyor:'map' object has no attribute 'next'
Igor Rivin

1
@IgorRivin: ile başlayan ve biten yöntemler __Python'a ayrılmıştır; bu çekince olmadan next, sadece bir yöntem olan (aslında yineleyici değiller) ve yineleyici olan şeyleri ayırt etmekte sorun yaşarsınız. Pratikte, yöntemleri atlamalı ve sadece 2.6'dan itibaren her Python sürümünde düzgün çalışan next()işlevi (örn. next(foo)) Kullanmalısınız. Bu, gayet iyi len(foo)çalışsa bile foo.__len__(), kullandığınız yolla aynıdır ; Dunder yöntemler genellikle amaçlanan değil doğrudan çağrılacak, ama üstü kapalı başka operasyon kapsamında.
ShadowRanger
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.