Pythonowe sztuczki – Generatory
Python ma to do siebie, że udostępnia ułatwienia dla programistów, które występują w niewielu innych językach. Sceptyk mógłby powiedzieć, że nie wnoszą one niczego nowego. To prawda, wszystkie rzeczy, które można za ich pomocą osiągnąć, można również uzyskać innymi sposobami w dowolnym języku. Gdyby jednak chodziło tylko o osiągany efekt, moglibyśmy pisać wszystko w asemblerze. Tak naprawdę w cenie jest również zwięzłość zapisu i elegancja. W tym wpisie przedstawiam jedną z moich ulubionych wyjątkowych cech Pythona – generatory. Generatory Podstawowe dwa cele, które osiąga się za pomocą generatorów to:
- czytelniejszy kod
- oszczędność pamięci
Istnieją również inne ich zastosowania, jednak tutaj skupię się na tym podstawowym. Rozważmy przykład:
def foo(bar): result = [] for x in bar: # praca praca praca result.append(y) return result baz = list(foo([1,2,3,4]))
i funkcja, która robi dokładnie to samo z użyciem yield
:
def foo(bar): for x in bar: # praca praca praca yield y baz = list(foo([1,2,3,4]))
Efekt działania obu przykładów jest taki sam. Realizują go one jednak w inny sposób. Pierwsza funkcja zwraca listę, druga zaś tworzy generator, który za pomocą funkcji list jest konwertowany na listę. Zyskaliśmy zatem większą czytelność zapisu. W czym jednak kryje się wyjątkowość generatora? Aby to zobaczyć, należy prześledzić poniższy kod:
def foo1(bar): result = [] for x in bar: y = x * 3 print "foo1: %d" % y result.append(y) return result def foo2(bar): for x in bar: y = x * 3 print "foo2: %d" % y yield y def mysum(l): result = 0 for x in l: print "mysum: %d" % x result += x return result mysum(foo1([1,2,3,4,5])) mysum(foo2([1,2,3,4,5]))
i wynik jego działania:
foo1: 3 foo1: 6 foo1: 9 foo1: 12 foo1: 15 mysum: 3 mysum: 6 mysum: 9 mysum: 12 mysum: 15 foo2: 3 mysum: 3 foo2: 6 mysum: 6 foo2: 9 mysum: 9 foo2: 12 mysum: 12 foo2: 15 mysum: 15
Jak widać, generator jest wyliczany ,,leniwie”. Oznacza to ni mniej, ni więcej, że dana wartość jest liczona dopiero wtedy, gdy zażąda jej for, by wykonać kolejny obrót pętli. Z punktu widzenia procedury wygląda to tak, że jej praca jest wstrzymywana, gdy wywoływany jest yield i wznawiana, gdy ktoś z zewnątrz zażąda następnej wartości. Największą zaletą wykorzystania generatora jest to, że w procedurze nie jest tworzona tymczasowa tablica, dzięki czemu oszczędza się dużo pamięci. Wadą rozwiązania jest to, że jakiekolwiek zmiany w obiektach z których korzysta generator, w trakcie gdy generator jest wstrzymany, mogą doprowadzić do rezultatów, których się nie spodziewaliśmy, pisząc tę procedurę. Podsumowując, generatory pozwalają na prowadzenie obliczeń w sposób kaskadowy, bez tworzenia tymczasowych tablic, które w niektórych przypadkach mogą kosztować bardzo dużo pamięci.
W pierwszych dwóch przykładach wierzę, że chcesz appendować x, nie y.
Co do generatorów to moim zdaniem są świetne! Świetnie sprawdzają się jako iteratory dla nietypowych obiektów jakie możemy sobie wymarzyć. Poza tym generatory świetnie nadają się przy rozwiązywaniu niektórych równań matematycznych, np. wzór na PI (pisałem kod do konwersji na system hexadecymalny).
Dzięki za uważne czytanie. Faktycznie tamten fragment wygląda trochę jak błąd. Mimo wszystko napisałem dokładnie to co chciałem naprawdę. W tamtym przykładzie po prostu wyobrażam sobie, że ,,y” zostanie wyprodukowany gdzieś po drodze, w trakcie pracy.
Tak czy siak – dzięki za zwrócenie uwagi.