liste anlama ve lambda + filtre


857

Kendimi temel bir filtreleme ihtiyacına sahip buldum: Bir listem var ve öğelerin bir özelliğine göre filtrelemek zorundayım.

Kodum şöyle görünüyordu:

my_list = [x for x in my_list if x.attribute == value]

Ama sonra düşündüm, böyle yazmak daha iyi olmaz mıydı?

my_list = filter(lambda x: x.attribute == value, my_list)

Daha okunabilir ve performans için gerekirse bir şey kazanmak için lambda çıkarılabilir.

Soru: İkinci yolu kullanırken herhangi bir uyarı var mı? Performans farkı var mı? Pythonic Way ™ 'i tamamen mi kaçırıyorum ve başka bir şekilde yapmalıyım (lambda yerine itemgetter kullanmak gibi)?


19
Daha iyi bir örnek, yükleminiz olarak kullanmak için zaten güzel adlandırılmış bir işleve sahip olduğunuz bir durum olabilir. Bu durumda, çok daha fazla insanın filterdaha okunabilir olduğunu kabul edeceğini düşünüyorum . Listcomp'de olduğu gibi kullanılabilecek, ancak geçmek için bir lambda (veya benzer şekilde inşa edilmiş partialveya operatorfonksiyonlar, vb.) filterİçine sarılması gereken basit bir ifadeniz varsa, o zaman listcomps kazanır.
abarnert

3
En azından Python3'te, dönüşünün filterbir liste değil, bir filtre üreteci nesnesi olduğu söylenmelidir .
Matteo Ferla

Yanıtlar:


588

Güzelliğin farklı insanlar için ne kadar değiştiği garip. Liste kavrayışını filter+ ' dan çok daha net buluyorum lambda, ancak hangisini daha kolay bulursanız kullanın.

Kullanımınızı yavaşlatabilecek iki şey vardır filter.

Birincisi, işlev çağrısı ek yüküdür: Bir Python işlevi ( defveya tarafından oluşturulmuş olsun lambda) kullandığınız anda, filtrenin liste kavrayışından daha yavaş olması muhtemeldir. Neredeyse kesinlikle önemli değildir ve kodunuzu zamanlayana ve bir darboğaz olarak bulana kadar performans hakkında fazla düşünmemelisiniz, ancak fark orada olacaktır.

Uygulanabilecek diğer ek yük, lambda'nın kapsamlı bir değişkene ( value) erişmeye zorlanmasıdır . Bu, yerel bir değişkene erişmekten daha yavaştır ve Python 2.x'te liste kavrama yalnızca yerel değişkenlere erişir. Python 3.x kullanıyorsanız, liste kavraması ayrı bir işlevde çalışır, böylece valuebir kapatma yoluyla da erişir ve bu fark geçerli olmaz.

Dikkate alınması gereken diğer seçenek, liste kavraması yerine bir jeneratör kullanmaktır:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

Daha sonra ana kodunuzda (okunabilirliğin gerçekten önemli olduğu yer) hem liste kavrayışını hem de filtreyi umarım anlamlı bir işlev adıyla değiştirdiniz.


68
Jeneratör için +1. Evde jeneratörlerin ne kadar şaşırtıcı olabileceğini gösteren bir sunuma bağlantım var. Ayrıca, sadece değiştirerek jeneratör ifade ile liste anlama yerini alabilir []için (). Ayrıca, liste comp daha güzel olduğunu kabul ediyorum.
Wayne Werner

1
Aslında, hiçbir filtre daha hızlı değildir. Stackoverflow.com/questions/5998245/…
skqr

2
@skqr sadece benchmark için timeit kullanmak daha iyidir, ancak filterbir Python geri arama işlevi kullanarak daha hızlı bulduğunuz bir örnek verin .
Duncan

8
@ tnq177 David Beasley'nin jeneratörler üzerine sunumu - dabeaz.com/generators
Wayne Werner

2
@ VictorSchröder evet, belki de belirsizdim. Söylemeye çalıştığım ana kodda daha büyük resmi görebilmeniz gerektiğiydi. Küçük yardımcı fonksiyonunda sadece bir fonksiyona dikkat etmeniz gerekir, dışarıda neler olup bittiğini görmezden gelebilirsiniz.
Duncan

237

Bu Python'da biraz dini bir konudur. Olsa Guido çıkarmadan kabul map, filterve reducePython 3'ten , sonunda sadece bir tepkisinden yeterince yoktu reduceinşa buralardan taşındı functools.reduce .

Şahsen liste kavrayışlarının okunmasını daha kolay buluyorum. [i for i in list if i.attribute == value]Tüm davranışlar filtre işlevinin içinde olmayan yüzeyde olduğundan , ifadeden neler olduğu daha açıktır .

Marjinal olduğu için iki yaklaşım arasındaki performans farkı konusunda fazla endişelenmem. Bunu sadece uygulamanızdaki darboğazın olası olmadığı kanıtlanırsa optimize ederim.

Ayrıca BDFL dilden gitmek istediğinden filter, o zaman kesinlikle liste kavrayışlarını otomatik olarak daha Pythonic ;-) yapar


1
Guido'nun girdisine bağlantılar için teşekkürler, benim için başka bir şey yoksa, onları artık kullanmamaya çalışacağım, böylece alışkanlığı alamayacağım ve o dini destekleyemeyeceğim :)
dashesy

1
ancak azaltmak basit araçlarla en karmaşık olanıdır! harita ve filtrelerin yerini kavrayışla değiştirmek önemsizdir!
njzk2

8
Python3 indirgeme düştü bilmiyordum. içgörü için teşekkürler! reduce () hala PySpark gibi dağıtılmış bilgi işlem konusunda oldukça yardımcıdır. Sanırım bu bir hataydı ..
Tagar

1
@Tagar hala kullanabilirsiniz functools ithal etmek zorunda azaltmak
icc97

69

Herhangi bir hız farkının minik olması gerektiğinden, filtre mi yoksa liste kavrayışı mı kullanmak bir zevk meselesi haline gelir. Genel olarak kavrayışları kullanmaya eğilimliyim (buradaki diğer cevapların çoğuna katılıyor gibi görünüyor), ancak tercih ettiğim bir durum var filter.

Çok sık kullanılan bir kullanım durumu, yüklem P (x) 'e tabi olarak tekrarlanabilir bazı X değerlerini çıkarır:

[x for x in X if P(x)]

ancak bazen önce değerlere bazı işlevler uygulamak istersiniz:

[f(x) for x in X if P(f(x))]


Belirli bir örnek olarak,

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

Bence bu kullanmaktan biraz daha iyi görünüyor filter. Ama şimdi düşünün

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

Bu durumda filterhesaplanan değere karşı çıkmak istiyoruz . Küpü iki kez hesaplama sorununun yanı sıra (daha pahalı bir hesaplama hayal edin), ifadeyi iki kez yazma, DRY estetiğini ihlal etme sorunu vardır . Bu durumda kullanmaya eğilimli olurum

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

7
Asalı başka bir liste kavrayışı ile kullanmayı düşünmez misiniz? Gibi[prime(i) for i in [x**3 for x in range(1000)]]
viki.omega9

20
x*x*xasal bir sayı olamaz, olduğu gibi x^2ve xbir faktör olarak, örnek gerçekten matematiksel olarak anlamlı değildir, ama belki de yine de helpul'dur. (Belki de daha iyi bir şey bulabiliriz?)
Zelphir Kaltstahl

3
Hafızada yemek istemiyorsak son örnek için bir jeneratör ifadesi kullanabileceğimizi unutmayın:prime_cubes = filter(prime, (x*x*x for x in range(1000)))
Mateen Ulhaq

4
@MateenUlhaq Bu, prime_cubes = [1]hem bellek hem de işlemci döngülerini kaydetmek için optimize edilebilir ;-)
Dennis Krupenik

7
@DennisKrupenik Ya da,[]
Mateen Ulhaq

29

filter"Daha hızlı yol" olsa da, "Pythonic yolu" performans kesinlikle kritik olmadıkça (bu durumda Python kullanmazsınız!) Bu tür şeyleri umursamayacaktır.


9
Sık görülen bir argümana geç yorum: Bazen bir analiz 10 yerine 5 saatte bir çalışma yapmak fark yaratır ve eğer bu bir saat python kodunu optimize ederek elde edilebilirse, buna değebilir (özellikle de python ile rahat ve daha hızlı dillerle değil).
bli

Ama daha da önemlisi, kaynak kodun onu okumaya ve anlamaya çalışırken bizi ne kadar yavaşlattığı!
thoni56

20

Ben sadece python 3, filtre () aslında bir yineleyici nesne eklemek olacağını düşündüm, bu yüzden filtre yöntemi listesini oluşturmak için listeye () filtre yöntemi çağrısı geçmek zorunda kalacak. Python 2'de:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

b ve c listeleri aynı değerlere sahiptir ve filter () ile yaklaşık aynı sürede tamamlanmıştır [z, eğer x in y için x]. Bununla birlikte, 3'te aynı kod, filtrelenmiş bir liste değil, bir filtre nesnesi içeren c listesini bırakacaktır. 3'te aynı değerleri üretmek için:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

Sorun şu ki, list () argümanı olarak yinelenebilir ve bu argümandan yeni bir liste oluşturur. Sonuç olarak, bu şekilde python 3'te filtre kullanılması, orijinal listenin yanı sıra filter () 'den gelen çıktıyı yinelemeniz gerektiğinden [x için x in y in x] yönteminin iki katına kadar sürebilir.


13

Önemli bir fark Liste kavraması dönecektir olmasıdır listfiltre getiri bir süre filterşöyle bir şey manipüle edemez list(: çağrı yani lendönüşü ile çalışmıyor üzerinde, filter).

Kendi kendi öğrenimim beni benzer bir konuya getirdi.

Bu varlık Elde var bir yolu varsa, söz konusu listbir mesafede filter, bunu yaptığında .NET yapacağını gibi biraz lst.Where(i => i.something()).ToList(), bunu bilmek merak ediyorum.

DÜZENLEME: Bu Python 3 için geçerlidir, 2 değil (yorumlardaki tartışmaya bakın).


4
filtre bir liste döndürür ve üzerinde len kullanabiliriz. En azından Python'um 2.7.6.
thiruvenkadam

7
Python 3'te durum böyle değil. a = [1, 2, 3, 4, 5, 6, 7, 8] f = filter(lambda x: x % 2 == 0, a) lc = [i for i in a if i % 2 == 0] >>> type(f) <class 'filter'> >>> type(lc) <class 'list'>
Adeynack

3
msgstr "sonuç listesine sahip olmanın bir yolu varsa ... bunu merak ediyorum". Sadece çağrı list()sonucu: list(filter(my_func, my_iterable)). Ve tabii ki yerini alabilir listile set, ya tuple, ya bir iterable sürer başka bir şey. Ancak, işlevsel programcılar dışında herhangi biri için durum, filterartı açık dönüşüm yerine bir liste kavrama kullanmak için daha da güçlüdür list.
Steve Jessop

10

İkinci yolu daha okunaklı buluyorum. Niyetin tam olarak ne olduğunu söyler: listeye filtre uygulayın.
Not: 'list' değişken adı olarak kullanılmaz


7

filteryerleşik bir işlev kullanılıyorsa genellikle biraz daha hızlıdır.

Dava listenizde anlaşmanın biraz daha hızlı olmasını beklerdim


python -m timeit 'filtresi ([1,2,3,4,5]' de lambda x: x, aralık (10000000)) '10 döngü, döngü başına 3: 1,44 sn'nin en iyisi python -m timeit' [x için x aralıkta (10000000) [1,2,3,4,5]] x 10 döngüde ise, döngü başına 3: 860 msn'lik en iyisi Gerçekten değil mi ?!
giaosudau

@sepdau, lambda işlevleri yerleşik değildir. Liste kavrayışları son 4 yılda iyileşti - şimdi yerleşik fonksiyonlarda bile fark göz ardı edilebilir
John La Rooy

7

Filtre sadece budur. Bir listenin öğelerini filtreler. Tanımda aynı ifadeleri görebilirsiniz (daha önce bahsettiğim resmi dokümanlar bağlantısında). Oysa liste kavrama, önceki listedeki bir şey üzerinde işlem yaptıktan sonra yeni bir liste üreten bir şeydir . (Hem filtre hem de liste kavrama, yeni liste oluşturur ve eski liste yerine işlem gerçekleştirmez. Buradaki yeni liste, , örneğin, tamamen yeni bir veri türü. Tam sayıları dizeye dönüştürmek gibi.)

Örneğinizde, tanıma göre filtre kavramasını liste kavramasından daha iyidir. Ancak, örneğin, liste öğelerinden other_attribute diyelim, örneğinizde yeni bir liste olarak alınacaksa, liste kavrama özelliğini kullanabilirsiniz.

return [item.other_attribute for item in my_list if item.attribute==value]

Aslında filtre ve liste kavrayışı hatırlıyorum. Bir listedeki birkaç şeyi kaldırın ve diğer öğeleri olduğu gibi koruyun, filtre kullanın. Öğelerde kendi başınıza bir mantık kullanın ve bir amaca uygun sulandırılmış bir liste oluşturun, liste kavrayışı kullanın.


2
Aşağı oylamanın nedenini bilmekten mutluluk duyacağım, böylece ileride hiçbir yerde tekrar etmeyeceğim.
thiruvenkadam

Filtre ve liste kavrayışının tanımı, anlamı tartışılmadığından gerekli değildi. Bir liste kavramasının yalnızca “yeni” listeler için kullanılması gerektiği, ancak tartışılmadığı anlamına gelir.
Agos

Tanımın, filtrenin size bir vaka için geçerli olan aynı öğelerle listeyi verdiğini söylemek için kullandım, ancak liste kavrama ile int'i str'ye dönüştürmek gibi öğeleri kendimiz değiştirebiliriz. Ama nokta alındı ​​:-)
thiruvenkadam

4

İşte liste kavrayışından sonra bir şeye filtre uygulamak gerektiğinde kullandığım kısa bir parça . Sadece filtre, lambda ve listelerin bir kombinasyonu (bir kedinin sadakati ve bir köpeğin temizliği olarak bilinir).

Bu durumda bir dosyayı okuyorum, boş satırları çıkarıyorum, satırları yorumladım ve bir satıra yorum yaptıktan sonra herhangi bir şey:

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]

Bu gerçekten çok az kodda çok şey başarıyor. Bence kolayca anlamak için bir satırda biraz fazla mantık olabilir ve önemli olan okunabilirliktir.
Zelphir Kaltstahl

Bunu şu şekilde yazabilirsinizfile_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
Steve Jessop

4

Kabul edilen cevaba ek olarak, liste kavramak yerine filtre kullanmanız gereken bir köşe vakası vardır. Liste paylaşılamıyorsa, doğrudan liste kavrayışı ile işleyemezsiniz. Gerçek bir dünya örneği, pyodbcbir veritabanındaki sonuçları okumak için kullanmanızdır . fetchAll()Elde edilen sonuçlar cursorbir unhashable listesidir. Bu durumda, döndürülen sonuçlara doğrudan müdahale etmek için filtre kullanılmalıdır:

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

Burada liste kavrama kullanırsanız hatayı alırsınız:

TypeError: shahableble type: 'list'


1
tüm listeler paylaşılmaz >>> hash(list()) # TypeError: unhashable type: 'list'ikinci olarak bu iyi çalışıyor:processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
Thomas Grainger

"Liste paylaşılamazsa, doğrudan liste kavrayışı ile işleyemezsiniz." Bu doğru değil ve tüm listeler zaten paylaşılamaz.
juanpa.arrivillaga

3

Bana alışmak için biraz zaman aldı higher order functions filterve map. Bu yüzden onlara alıştım ve gerçekten filterdoğru olanı koruyarak filtrelediği açıktı ve bazı functional programmingterimleri bildiğimi hissettim .

Sonra bu pasajı (Akıcı Python Kitabı) okudum:

Harita ve filtre işlevleri hala Python 3'te yerleşiktir, ancak liste kavrayışları ve jeneratör açıklamaları sunulduğundan bu kadar önemli değildir. Listcomp veya genexp, harita ve filtre işlerini bir araya getirir, ancak daha okunabilirdir.

Ve şimdi düşünüyorum, neden filter/ kavram mapanlama gibi zaten yayılmış deyimlerle başarabilirseniz / kavramı ile uğraşabilirsiniz. Ayrıca mapsve filterstür fonksiyonları vardır. Bu durumda Anonymous functionslambda kullanmayı tercih ederim .

Son olarak, sadece test etmek uğruna, her iki yöntemi de ( mapve listComp) zamanladım ve bununla ilgili tartışmalar yapmayı haklı çıkaracak herhangi bir ilgili hız farkı görmedim.

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602

0

Merakla Python 3'te, filtrenin liste kavrayışlarından daha hızlı performans gösterdiğini görüyorum.

Her zaman liste kavrayışının daha verimli olacağını düşündüm. Şuna benzer bir şey: [ad None değilse brand_names_db dosyasındaki ad] Oluşturulan bayt kodu biraz daha iyi.

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

Ama aslında daha yavaşlar:

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214

8
Geçersiz karşılaştırma . İlk olarak, lambda işlevini filtre sürümüne geçirmezsiniz, bu da kimlik işlevini varsayılan yapar. Tanımlarken if not Noneliste anlamada size olan bir lambda fonksiyonu tanımlayan (fark MAKE_FUNCTIONdeyimi). İkinci olarak, liste kavrama sürümü yalnızca Nonedeğeri kaldıracağı için sonuçlar farklıdır, oysa filtre sürümü tüm "falsy" değerlerini kaldıracaktır. Bunu söyledikten sonra, mikro karşılaştırmanın tüm amacı işe yaramaz. Bunlar bir milyon iterasyon, çarpı 1k öğeler! Fark ihmal edilebilir .
Victor Schröder

-7

Benim almam

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]

3
iasla bir olduğu söylenmedi dictve buna gerek yok limit. Bunun dışında OP'nin önerdiğinden farkı nedir ve soruyu nasıl cevaplıyor?
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.