2012-04-27 8 views
7

Potrzebuję filtrować duże listy kilka razy, ale jestem zainteresowany zarówno prostotą kodu, jak i wydajnością wykonania. Aby dać przykład:Optymalizowanie list filtrowania w Pythonie 2.7

all_things # huge collection of all things 

# inefficient but clean code 
def get_clothes(): 
    return filter(lambda t: t.garment, allThings) 

def get_hats(): 
    return filter(lambda t: t.headgear, get_clothes()) 

Martwię się, że mam iteracji na listę ubrań, gdy w rzeczywistości został on już potwierdziła się. Chcę również zachować dwie operacje filtrowania oddzielnie, ponieważ należą one do dwóch różnych klas i nie chcę powielać pierwszej funkcji lambda w klasie kapeluszy.

# efficient but duplication of code 
def get_clothes(): 
    return filter(lambda t: t.garment, allThings) 

def get_hats(): 
    return filter(lambda t: t.headgear and t.garment, allThings) 

Badałem funkcje generatora, ponieważ wydawało się, że to jest droga, ale nie wiem jeszcze, jak to zrobić.

+0

Jeśli martwisz się o wydajność, czy ** sprawdziłeś ** wydajność? –

+0

Miałbym, gdybym myślał, że to nie było oczywiste. – cammil

+2

"oczywiste" to niebezpieczne słowo, jeśli chodzi o wydajność. –

Odpowiedz

23

Przede wszystkim użycie kombinacji filter/lambda ma być przestarzałe. Bieżący styl programowania funkcjonalnego jest opisany w Python Functional Programming HOWTO.

Po drugie, jeśli chodzi o wydajność, a nie konstruowanie list, powinieneś zwrócić generators. W tym przypadku są one na tyle proste, aby używać generator expressions.

def get_clothes(): 
    return (t for t in allThings if t.garment) 

def get_hats(): 
    return (t for t in get_clothes() if t.headgear) 

Lub jeśli wolisz, prawdziwy Generatory (rzekomo więcej pythonic):

def get_clothes(): 
    for t in allThings: 
     if t.garment: 
      yield t 

def get_hats(): 
    for t in get_clothes(): 
     if t.headgear: 
      yield t 

Jeśli z jakiegoś powodu, czasem trzeba list zamiast iterator można skonstruować listę według prostego odlewania:

hats_list = list(get_hats()) 

Zauważ, że wyżej będzie nie lista konstrukt ubrań, więc wydajność jest blisko do duplicat Wersja kodu.

+0

Damnation, vartec. Weź moje przegrane. –

+0

@ Li-aungYip: sorry dude ;-) – vartec

+6

1) Kombinacja '' filter/lambda'' nie jest przestarzała. 2) PEP 8 odradza zwracanie wyrażeń generujących - te powinny być konsumowane w tym samym zakresie, niezależnie od tego, czy zostały utworzone - zamiast tego używał zwykłego generatora. 3). Jeśli lista jest potrzebna, to OP powinien używać ze zrozumieniem listy zamiast * listy * owiniętej wokół genexp. –

4

Aby zrobić to w jednym przejściu tylko (pseudokod):

clothes = list() 
hats = list() 
for thing in things: 
    if thing is a garment: 
     clothes.append(thing) 
     if thing is a hat: 
      hats.append(thing) 

to zrobić w jeden wielki mijają a mniejszym przebieg (listowych):

clothes = [ x for x in things if x is garment ] 
hats = [ x for x in clothes if x is hat ] 

Jeśli chcesz utworzyć cała lista nie ma sensu używać wyrażenia generatora do leniwej oceny, ponieważ nie będziesz leniwy.

Jeśli chcesz poradzić sobie tylko z kilkoma rzeczami na raz lub jeśli masz ograniczoną pamięć, użyj rozwiązania generatora @ vartec.

+1

możesz naprawić "rzeczy w rzeczach" użycie – okm

+0

@okm: Nie widząc tego, przepraszam - czy możesz rozwinąć? –

+0

Mam na myśli "[rzecz w ubraniu, jeśli coś jest w kapeluszu]" nie jest poprawna składniowo, prawda? – okm

3

Szukałem podobnego filtrowania list, ale chciałem mieć nieco inny format niż to, co zostało tutaj przedstawione.

Powyższe połączenie jest dobre, ale ograniczone pod względem ponownego użycia. Szukałem czegoś bardziej podobnego do get_hats(get_clothes(all_things)), gdzie można podać źródło (all_things), a następnie jak mało lub tyle poziomów filtrów, ile chcesz.

znalazłem sposób, aby to zrobić z generatorów:

def get_clothes(in_list): 
    for item in in_list: 
     if item.garment: 
      yield item 

def get_hats(in_list): 
    for item in in_list: 
     if item.headgear: 
      yield item 

ten może być następnie zwanych przez:

get_hats(get_clothes(all_things)) 

Testowałem oryginalne rozwiązania, rozwiązanie vartec i to dodatkowe rozwiązanie, aby zobaczyć wydajności i był nieco zaskoczony wynikami. Kod następująco:

Setup:

class Thing: 
    def __init__(self): 
     self.garment = False 
     self.headgear = False 

all_things = [Thing() for i in range(1000000)] 

for i, thing in enumerate(all_things): 
    if i % 2 == 0: 
     thing.garment = True 
    if i % 4 == 0: 
     thing.headgear = True 

oryginalne rozwiązania:

def get_clothes(): 
    return filter(lambda t: t.garment, all_things) 

def get_hats(): 
    return filter(lambda t: t.headgear, get_clothes()) 

def get_clothes2(): 
    return filter(lambda t: t.garment, all_things) 

def get_hats2(): 
    return filter(lambda t: t.headgear and t.garment, all_things) 

Moje rozwiązanie: rozwiązanie

def get_clothes3(in_list): 
    for item in in_list: 
     if item.garment: 
      yield item 

def get_hats3(in_list): 
    for item in in_list: 
     if item.headgear: 
      yield item 

vartec za:

def get_clothes4(): 
    for t in all_things: 
     if t.garment: 
      yield t 

def get_hats4(): 
    for t in get_clothes4(): 
     if t.headgear: 
      yield t 
kod

Timing:

import timeit 

print 'get_hats()' 
print timeit.timeit('get_hats()', 'from __main__ import get_hats', number=1000) 

print 'get_hats2()' 
print timeit.timeit('get_hats2()', 'from __main__ import get_hats2', number=1000) 

print '[x for x in get_hats3(get_clothes3(all_things))]' 
print timeit.timeit('[x for x in get_hats3(get_clothes3(all_things))]', 
        'from __main__ import get_hats3, get_clothes3, all_things', 
        number=1000) 

print '[x for x in get_hats4()]' 
print timeit.timeit('[x for x in get_hats4()]', 
        'from __main__ import get_hats4', number=1000) 

Wyniki:

get_hats() 
379.334653854 
get_hats2() 
232.768362999 
[x for x in get_hats3(get_clothes3(all_things))] 
214.376812935 
[x for x in get_hats4()] 
218.250688076 

Generator wyrażeń pojawiają się nieco szybciej, różnica w czasie pomiędzy rozwiązaniami moje i vartec są prawdopodobnie tylko szum. Ale wolę elastyczność w możliwości stosowania dowolnych filtrów w dowolnej kolejności.