Efektywny Python 59 sposobow na lepszy kod efepyt

background image
background image

Tytuł oryginału: Effective Python: 59 Specific Ways to Write Better Python (Effective Software
Development Series)

Tłumaczenie: Robert Górczyński

ISBN: 978-83-283-1540-2

Authorized translation from the English language edition, entitled: EFFECTIVE PYTHON: 59 SPECIFIC
WAYS TO WRITE BETTER PYTHON; ISBN 0134034287; by Brett Slatkin; published by Pearson
Education, Inc, publishing as Addison Wesley Professional.
Copyright © 2015 by Pearson Education, Inc.

All rights reserved. No part of this book may by reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from Pearson Education, Inc.
Polish language edition published by HELION S.A. Copyright © 2015.

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej
publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną,
fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje
naruszenie praw autorskich niniejszej publikacji.

Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich
właścicieli.

Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były
kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane
z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie
ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji
zawartych w książce.

Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail:

helion@helion.pl

WWW:

http://helion.pl (księgarnia internetowa, katalog książek)

Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/efepyt
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

Printed in Poland.

Kup książkę

Poleć książkę

Oceń książkę

Księgarnia internetowa

Lubię to! » Nasza społeczność

background image

Spis treĤci

Wprowadzenie ................................................................................ 11

Podzičkowania ............................................................................... 15

O autorze ....................................................................................... 17

Rozdziaã 1. Programowanie zgodne z duchem Pythona ................... 19

Sposób 1. Ustalenie uİywanej wersji Pythona ........................................... 19
Sposób 2. Stosuj styl PEP 8 ...................................................................... 21
Sposób 3. Róİnice mičdzy typami bytes, str i unicode ............................... 23
Sposób 4. Decyduj sič na funkcje pomocnicze

zamiast na skomplikowane wyraİenia ....................................... 26

Sposób 5. Umiejčtnie podziel sekwencje .................................................... 29
Sposób 6. Unikaj uİycia indeksów poczĈtek, koniec

i wartoĤci kroku w pojedynczej operacji podziaãu ...................... 31

Sposób 7. Uİywaj list skãadanych zamiast funkcji map() i filter() ................... 33
Sposób 8. Unikaj wičcej niİ dwóch wyraİeę na liĤcie skãadanej .................... 35
Sposób 9. Rozwaİ uİycie generatora wyraİeę dla duİych list skãadanych ....36
Sposób 10. Preferuj uİycie funkcji enumerate() zamiast range() ...................... 38
Sposób 11. Uİycie funkcji zip() do równoczesnego przetwarzania iteratorów ... 39
Sposób 12. Unikaj bloków else po pčtlach for i while ................................... 41
Sposób 13. Wykorzystanie zalet wszystkich bloków

w konstrukcji try-except-else-finally ......................................... 44

Rozdziaã 2. Funkcje ....................................................................... 47

Sposób 14. Preferuj wyjĈtki zamiast zwrotu wartoĤci None .......................... 47
Sposób 15. Zobacz, jak domkničcia wspóãdziaãajĈ z zakresem zmiennej ...... 49
Sposób 16. Rozwaİ uİycie generatorów, zamiast zwracaþ listy ...................... 54

Poleć książkę

Kup książkę

background image

8

Spis treĤci

Sposób 17. Podczas iteracji przez argumenty

zachowuj postawč defensywnĈ .................................................. 56

Sposób 18. Zmniejszenie wizualnego zagmatwania

za pomocĈ zmiennej liczby argumentów pozycyjnych ................ 61

Sposób 19. Zdefiniowanie zachowania opcjonalnego

za pomocĈ argumentów w postaci sãów kluczowych .................. 63

Sposób 20. Uİycie None i docstring w celu

dynamicznego okreĤlenia argumentów domyĤlnych ................... 66

Sposób 21. Wymuszaj czytelnoĤþ kodu,

stosujĈc jedynie argumenty w postaci sãów kluczowych ............ 69

Rozdziaã 3. Klasy i dziedziczenie .................................................... 73

Sposób 22. Preferuj klasy pomocnicze zamiast sãowników i krotek .............. 73
Sposób 23. Dla prostych interfejsów akceptuj funkcje zamiast klas ............ 78
Sposób 24. Uİycie polimorfizmu @classmethod

w celu ogólnego tworzenia obiektów .......................................... 82

Sposób 25. Inicjalizacja klasy nadrzčdnej za pomocĈ wywoãania super() ...... 87
Sposób 26. Wielokrotnego dziedziczenia

uİywaj jedynie w klasach narzčdziowych .................................. 91

Sposób 27. Preferuj atrybuty publiczne zamiast prywatnych ....................... 95
Sposób 28. Dziedziczenie po collections.abc

w kontenerach typów niestandardowych ................................... 99

Rozdziaã 4. Metaklasy i atrybuty ................................................... 105

Sposób 29. Uİywaj zwykãych atrybutów zamiast metod typu getter i setter ...105
Sposób 30. Rozwaİ uİycie @property zamiast refaktoryzacji atrybutów ..... 109
Sposób 31. Stosuj deskryptory, aby wielokrotnie wykorzystywaþ

metody udekorowane przez @property .................................... 113

Sposób 32. Uİywaj metod __getattr__(), __getattribute__() i __setattr__()

dla opóĮnionych atrybutów ..................................................... 117

Sposób 33. Sprawdzaj podklasy za pomocĈ metaklas ................................ 122
Sposób 34. Rejestruj istniejĈce klasy wraz z metaklasami ......................... 124
Sposób 35. Adnotacje atrybutów klas dodawaj za pomocĈ metaklas .......... 128

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ ..................................... 131

Sposób 36. Uİywaj moduãu subprocess

do zarzĈdzania procesami potomnymi ..................................... 132

Sposób 37. Uİycie wĈtków dla operacji blokujĈcych wejĤcie-wyjĤcie,

unikanie równolegãoĤci ........................................................... 136

Sposób 38. Uİywaj klasy Lock, aby unikaþ stanu wyĤcigu w wĈtkach ....... 140
Sposób 39. Uİywaj klasy Queue do koordynacji pracy mičdzy wĈtkami ..... 143

Poleć książkę

Kup książkę

background image

Spis treĤci

9

Sposób 40. Rozwaİ uİycie wspóãprogramów

w celu jednoczesnego wykonywania wielu funkcji ................... 150

Sposób 41. Rozwaİ uİycie concurrent.futures(),

aby otrzymaþ prawdziwĈ równolegãoĤþ .................................... 158

Rozdziaã 6. Wbudowane moduãy ....................................................163

Sposób 42. Dekoratory funkcji definiuj za pomocĈ functools.wraps ........... 163
Sposób 43. Rozwaİ uİycie poleceę contextlib i with

w celu uzyskania wielokrotnego uİycia konstrukcji try-finally .... 166

Sposób 44. Niezawodne uİycie pickle wraz z copyreg ................................ 169
Sposób 45. Podczas obsãugi czasu lokalnego uİywaj moduãu datetime

zamiast time ........................................................................... 174

Sposób 46. Uİywaj wbudowanych algorytmów i struktur danych .............. 178
Sposób 47. Gdy waİna jest precyzja, uİywaj moduãu decimal ................... 183
Sposób 48. Kiedy szukaþ moduãów opracowanych przez spoãecznoĤþ? ....... 185

Rozdziaã 7. Wspóãpraca .................................................................187

Sposób 49. Dla kaİdej funkcji, klasy i moduãu utwórz docstring ............... 187
Sposób 50. Uİywaj pakietów do organizacji moduãów

i dostarczania stabilnych API .................................................. 191

Sposób 51. Zdefiniuj gãówny wyjĈtek Exception

w celu odizolowania komponentu wywoãujĈcego od API ........... 196

Sposób 52. Zobacz, jak przerwaþ krĈg zaleİnoĤci ...................................... 199
Sposób 53. Uİywaj Ĥrodowisk wirtualnych

dla odizolowanych i powtarzalnych zaleİnoĤci ......................... 204

Rozdziaã 8. Produkcja ...................................................................211

Sposób 54. Rozwaİ uİycie kodu o zasičgu moduãu

w celu konfiguracji Ĥrodowiska wdroİenia ............................... 211

Sposób 55. Uİywaj ciĈgów tekstowych repr

do debugowania danych wyjĤciowych ..................................... 214

Sposób 56. Testuj wszystko za pomocĈ unittest ........................................ 217
Sposób 57. Rozwaİ interaktywne usuwanie bãčdów za pomocĈ pdb ........... 220
Sposób 58. Przed optymalizacjĈ przeprowadzaj profilowanie ...................... 222
Sposób 59. Stosuj moduã tracemalloc, aby poznaþ sposób uİycia pamičci

i wykryþ jej wycieki ................................................................. 226

Skorowidz ........................................................................................ 229

Poleć książkę

Kup książkę

background image

10

Spis treĤci

Poleć książkę

Kup książkę

background image

WspóãbieİnoĤþ

i równolegãoĤþ

WspóãbieİnoĤþ wystčpuje wtedy, gdy komputer pozornie wykonuje jednocze-
Ĥnie wiele róİnych zadaę. Na przykãad w komputerze wyposaİonym w pro-
cesor o tylko jednym rdzeniu system operacyjny bčdzie bardzo szybko
zmieniaã aktualnie wykonywany program na inny. Tym samym programy
sĈ wykonywane na przemian, co tworzy iluzjč ich jednoczesnego dziaãania.

Z kolei równolegãoĤþ to faktyczne wykonywanie jednoczeĤnie wielu róİnych
zadaę. Jeİeli komputer jest wyposaİony w wielordzeniowy procesor, to po-
szczególne rdzenie mogĈ jednoczeĤnie wykonywaþ róİne zadania. Poniewaİ
poszczególne rdzenie procesora wykonujĈ polecenia innego programu, wičc
poszczególne aplikacje dziaãajĈ jednoczeĤnie i w tym samym czasie kaİda
z nich odnotowuje postčp w dziaãaniu.

W ramach jednego programu wspóãbieİnoĤþ to narzčdzie uãatwiajĈce pro-
gramistom rozwiĈzywanie pewnego rodzaju problemów. Programy wspóãbieİ-
ne pozwalajĈ na zastosowanie wielu róİnych Ĥcieİek dziaãania, aby uİyt-
kownik miaã wraİenie, İe poszczególne operacje w programie odbywajĈ sič
jednoczeĤnie i niezaleİnie.

Kluczowa róİnica mičdzy wspóãbieİnoĤciĈ i równolegãoĤciĈ to szybkoĤþ. Kiedy
w programie sĈ stosowane dwie oddzielne Ĥcieİki jego wykonywania, to czas
potrzebny na wykonanie caãego zadania programu zmniejsza sič o poãowč.
Wspóãczynnik szybkoĤci wykonywania wynosi wičc dwa. Z kolei wspóãbieİnie
dziaãajĈce programy mogĈ wykonywaþ tysiĈce oddzielnych Ĥcieİek dziaãania,
ale to nie przeãoİy sič w ogóle na zmniejszenie iloĤci czasu, jaki jest potrzebny
na wykonanie caãej pracy.

Python uãatwia tworzenie programów wspóãbieİnych. Ponadto jest uİywany
do równolegãego wykonywania zadaę za pomocĈ wywoãaę systemowych,
podprocesów oraz rozszerzeę utworzonych w jčzyku C. Jednak osiĈgničcie

Poleć książkę

Kup książkę

background image

132

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

stanu, w którym wspóãbieİny kod Pythona bčdzie faktycznie wykonywany
równolegle, moİe byþ bardzo trudne. Dlatego teİ niezwykle waİne jest po-
znanie najlepszych sposobów wykorzystania Pythona w tych nieco odmien-
nych sytuacjach.

Sposób 36. Uİywaj moduãu subprocess

do zarzĈdzania procesami potomnymi

Python oferuje zaprawione w bojach biblioteki przeznaczone do wykonywa-
nia procesów potomnych i zarzĈdzania nimi. Tym samym Python staje sič
doskonaãym jčzykiem do ãĈczenia ze sobĈ innych narzčdzi, na przykãad
dziaãajĈcych w powãoce. Kiedy istniejĈce skrypty powãoki z czasem stajĈ sič
skomplikowane, jak to czčsto sič zdarza, wówczas przepisanie ich w Pytho-
nie jest naturalnym wyborem w celu zachowania czytelnoĤci kodu i moİli-
woĤci jego dalszej obsãugi.

Procesy potomne uruchamiane przez Pythona mogĈ dziaãaþ równolegle,
a tym samym Python moİe wykorzystaþ wszystkie rdzenie komputera i zmak-
symalizowaþ przepustowoĤþ aplikacji. Wprawdzie sam Python moİe byþ ogra-
niczany przez procesor (patrz sposób 37.), ale bardzo ãatwo wykorzystaþ
ten jčzyk do koordynowania zadaę obciĈİajĈcych procesor.

Na przestrzeni lat Python oferowaã wiele sposobów uruchamiania podpro-
cesów, mičdzy innymi za pomocĈ wywoãaę

popen

,

popen2

i

os.exec*

. Obecnie

najlepszym i najprostszym rozwiĈzaniem w zakresie zarzĈdzania procesami
potomnymi jest uİycie wbudowanego moduãu

subprocess

.

Uruchomienie podprocesu za pomocĈ moduãu

subprocess

jest proste. W po-

niİszym fragmencie kodu konstruktor klasy

Popen

uruchamia proces. Z kolei

metoda

communicate()

odczytuje dane wyjĤciowe procesu potomnego i czeka

na jego zakoęczenie.

proc = subprocess.Popen(
['echo', 'Witaj z procesu potomnego!'],
stdout=subprocess.PIPE)
out, err = proc.communicate()
print(out.decode('utf-8'))
>>>
Witaj z procesu potomnego!

Procesy potomne bčdĈ dziaãaãy niezaleİnie od ich procesu nadrzčdnego,
czyli interpretera Pythona. Ich stan moİna okresowo sprawdzaþ, gdy Python
wykonuje inne zadania.

proc = subprocess.Popen(['sleep', '0.3'])
while proc.poll() is None:
print('Pracujú...')

# Miejsce na zadania, których wykonanie wymaga dużo czasu.

Poleć książkę

Kup książkę

background image

Sposób 36. Uİywaj moduãu subprocess do zarzĈdzania procesami potomnymi

133

# ...
print('Kod wyjħcia', proc.poll())
>>>
Pracujú...
Pracujú...
Kod wyjħcia 0

Oddzielenie procesów potomnego i nadrzčdnego oznacza, İe proces nadrzčdny
moİe równoczeĤnie uruchomiþ dowolnĈ liczbč procesów potomnych. Moİna
to zrobiþ, uruchamiajĈc jednoczeĤnie wszystkie procesy potomne.

def run_sleep(period):

proc = subprocess.Popen(['sleep', str(period)])
return proc

start = time()
procs = []
for _ in range(10):
proc = run_sleep(0.1)
procs.append(proc)

Nastčpnie moİna czekaþ na zakoęczenie przez nie operacji wejĤcia-wyjĤcia
i zakoęczyþ ich dziaãanie za pomocĈ metody

communicate()

.

for proc in procs:

proc.communicate()
end = time()
print('Zakoēczono w ciægu %.3f sekund' % (end - start))
>>>
Zakoēczono w ciægu 0.117 sekund

Wskazówka

Jeżeli wymienione procesy działają w sekwencji, to całkowite opóźnienie wynosi sekundę, a nie
tylko mniej więcej 0,1 sekundy, jak to zostało zmierzone w omawianym programie.

Istnieje równieİ moİliwoĤþ potokowania danych z programu Pythona do
podprocesów oraz pobierania ich danych wyjĤciowych. Tym samym moİna
wykorzystaþ inne programy do równoczesnego dziaãania. Na przykãad przyj-
mujemy zaãoİenie, İe narzčdzie powãoki

openssl

jest uİywane do szyfrowa-

nia pewnych danych. Uruchomienie procesu potomnego wraz z argumen-
tami pochodzĈcymi z powãoki oraz potokowanie wejĤcia-wyjĤcia jest ãatwe.

def run_openssl(data):
env = os.environ.copy()
env['password'] = b'\xe24U\n\xd0Ql3S\x11'
proc = subprocess.Popen(
['openssl', 'enc', '-des3', '-pass', 'env:password'],
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
proc.stdin.write(data)

proc.stdin.flush() # Gwarantujemy, że proces potomny otrzyma dane wejściowe.
return proc

Poleć książkę

Kup książkę

background image

134

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

W przedstawionym fragmencie kodu potokujemy losowo wygenerowane bajty
do funkcji szyfrujĈcej. W praktyce bčdĈ to dane wejĤciowe podane przez uİyt-
kownika, uchwyt do pliku, gniazdo sieciowe itd.

procs = []
for _ in range(3):
data = os.urandom(10)
proc = run_openssl(data)
procs.append(proc)

Procesy potomne bčdĈ dziaãaãy równolegle z nadrzčdnym, a takİe bčdĈ ko-
rzystaãy z danych wejĤciowych procesów nadrzčdnych. W poniİszym kodzie
czekamy na zakoęczenie dziaãania procesów potomnych, a nastčpnie po-
bieramy wygenerowane przez nie ostateczne dane wyjĤciowe.

for proc in procs:
out, err = proc.communicate()
print(out[-10:])
>>>
b'o4,G\x91\x95\xfe\xa0\xaa\xb7'
b'\x0b\x01\\\xb1\xb7\xfb\xb2C\xe1b'
b'ds\xc5\xf4;j\x1f\xd0c-'

Moİna teİ tworzyþ ãaęcuchy równoczeĤnie dziaãajĈcych procesów, podobnie
jak potoków w systemie UNIX, uİywajĈc danych wyjĤciowych jednego pro-
cesu potomnego jako danych wejĤciowych innego procesu potomnego itd.
Poniİej przedstawiãem funkcjč uruchamiajĈcĈ proces potomny, który z kolei
spowoduje, İe polecenie powãoki

md5

pobierze strumieę danych wejĤciowych:

def run_md5(input_stdin):

proc = subprocess.Popen(
['md5'],
stdin=input_stdin,
stdout=subprocess.PIPE)
return proc

Wskazówka

Wbudowany moduł Pythona o nazwie

hashlib oferuje funkcję md5(), a więc uruchomienie te-

go rodzaju procesu potomnego nie zawsze jest konieczne. Moim celem jest tutaj pokazanie, jak
podprocesy mogą potokować dane wejściowe i wyjściowe.

Teraz wykorzystujemy zbiór procesów

openssl

do szyfrowania pewnych da-

nych, a kolejny zbiór procesów do utworzenia wartoĤci hash na podstawie
zaszyfrowanych danych.

input_procs = []
hash_procs = []
for _ in range(3):
data = os.urandom(10)
proc = run_openssl(data)
input_procs.append(proc)
hash_proc = run_md5(proc.stdout)
hash_procs.append(hash_proc)

Poleć książkę

Kup książkę

background image

Sposób 36. Uİywaj moduãu subprocess do zarzĈdzania procesami potomnymi

135

Operacje wejĤcia-wyjĤcia mičdzy procesami potomnymi bčdĈ zachodziãy au-
tomatycznie po uruchomieniu procesów. Twoim zadaniem jest jedynie za-
czekaþ na zakoęczenie dziaãania procesów potomnych i wyĤwietliþ ostateczne
wyniki ich dziaãania.

for proc in input_procs:
proc.communicate()
for proc in hash_procs:
out, err = proc.communicate()
print(out.strip())
>>>
b'7a1822875dcf9650a5a71e5e41e77bf3'
b'd41d8cd98f00b204e9800998ecf8427e'
b'1720f581cfdc448b6273048d42621100'

Jeİeli masz obawy, İe procesy potomne nigdy sič nie zakoęczĈ lub coĤ bč-
dzie blokowaão potoki danych wejĤciowych bĈdĮ wyjĤciowych, to upewnij sič,
czy metodzie

communicate()

zostaã przekazany parametr

timeout

. Przekazanie

tego parametru sprawi, İe nastĈpi zgãoszenie wyjĈtku, jeĤli proces potomny
nie udzieli odpowiedzi w podanym czasie. Tym samym zyskasz moİliwoĤþ za-
koęczenia dziaãania nieprawidãowo zachowujĈcego sič procesu potomnego.

proc = run_sleep(10)
try:
proc.communicate(timeout=0.1)
except subprocess.TimeoutExpired:
proc.terminate()
proc.wait()

print('Kod wyjħcia', proc.poll())
>>>
Kod wyjħcia -15

Niestety, parametr

timeout

jest dostčpny jedynie w Pythonie 3.3 oraz no-

wych wydaniach. We wczeĤniejszych wersjach Pythona konieczne jest uİy-
cie wbudowanego moduãu

select

w

proc.stdin

,

proc.stdout

i

proc.stderr

w celu

wymuszenia stosowania limitu czasu w trakcie operacji wejĤcia-wyjĤcia.

Do zapamičtania



Uİywaj moduãu

subprocess

do uruchamiania procesów potomnych oraz

zarzĈdzania ich strumieniami danych wejĤciowych i wyjĤciowych.



Procesy potomne dziaãajĈ równolegle wraz z interpreterem Pythona, co
pozwala na maksymalne wykorzystanie dostčpnego procesora.



Uİywaj parametru

timeout

w metodzie

communicate()

, aby unikaþ zakleszczeę

i zawieszenia procesów potomnych.

Poleć książkę

Kup książkę

background image

136

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

Sposób 37. Uİycie wĈtków dla operacji blokujĈcych

wejĤcie-wyjĤcie, unikanie równolegãoĤci

Sposób 37. Uİycie wĈtków dla operacji blokujĈcych wejĤcie-wyjĤcie

Standardowa implementacja Pythona nosi nazwč CPython. Implementacja
ta uruchamia program Pythona w dwóch krokach. Pierwszy to przetworze-
nie i kompilacja kodu Įródãowego na kod bajtowy. Drugi to uruchomienie
kodu bajtowego za pomocĈ interpretera opartego na stosie. Wspomniany
interpreter kodu bajtowego ma stan, który musi byþ obsãugiwany i spójny
podczas wykonywania programu Pythona. Jčzyk Python wymusza spójnoĤþ
za pomocĈ mechanizmu o nazwie GIL (ang. global interpreter lock).

W gruncie rzeczy mechanizm GIL to rodzaj wzajemnego wykluczania (mutex)
chroniĈcy CPython przed wpãywem wywãaszczenia wielowĈtkowego, gdy je-
den wĈtek przejmuje kontrolč nad programem przez przerwanie dziaãania
innego wĈtku. Tego rodzaju przerwanie moİe doprowadziþ do uszkodzenia
interpretera, jeĤli wystĈpi w nieoczekiwanym czasie. Mechanizm GIL chroni
przed wspomnianymi przerwaniami i gwarantuje, İe kaİda instrukcja kodu
bajtowego dziaãa poprawnie z implementacjĈ CPython oraz jej moduãami
rozszerzeę utworzonych w jčzyku C.

Mechanizm GIL powoduje pewien waİny negatywny efekt uboczny. W przy-
padku programów utworzonych w jčzykach takich jak C++ lub Java wiele
wĈtków wykonywania oznacza, İe program moİe jednoczeĤnie wykorzystaþ
wiele rdzeni procesora. Wprawdzie Python obsãuguje wiele wĈtków wykony-
wania, ale mechanizm GIL powoduje, İe w danej chwili tylko jeden z nich
robi postčp. Dlatego teİ jeĤli sičgasz po wĈtki w celu przeprowadzania rów-
nolegãych obliczeę i przyĤpieszenia programów Pythona, to bčdziesz srodze
zawiedziony.

Przyjmujemy zaãoİenie, İe chcesz w Pythonie wykonaþ zadanie wymagajĈce
duİej iloĤci obliczeę. Uİyjemy algorytmu rozkãadu liczby na czynniki.

def factorize(number):
for i in range(1, number + 1):
if number % i == 0:
yield i

Rozkãad zbioru liczb moİe wymagaþ caãkiem duİej iloĤci czasu.

numbers = [2139079, 1214759, 1516637, 1852285]
start = time()
for number in numbers:
list(factorize(number))
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 1.040 sekund

W innych jčzykach programowania uİycie wielu wĈtków bčdzie miaão sens,
poniewaİ wówczas wykorzystasz wszystkie rdzenie dostčpne w procesorze.

Poleć książkę

Kup książkę

background image

Sposób 37. Uİycie wĈtków dla operacji blokujĈcych wejĤcie-wyjĤcie

137

Spróbujmy to zrobiþ w Pythonie. Poniİej zdefiniowaãem wĈtek Pythona prze-
znaczony do przeprowadzenia tych samych obliczeę co wczeĤniej:

from threading import Thread

class FactorizeThread(Thread):
def __init__(self, number):
super().__init__()
self.number = number

def run(self):
self.factors = list(factorize(self.number))

Teraz uruchamiam wĈtki w celu równolegãego rozkãadu poszczególnych liczb.

start = time()
threads = []
for number in numbers:
thread = FactorizeThread(number)
thread.start()
threads.append(thread)

Pozostaão juİ tylko zaczekaþ na zakoęczenie dziaãania wszystkich wĈtków.

for thread in threads:
thread.join()
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 1.061 sekund

ZaskakujĈce moİe byþ, İe równolegãe wykonywanie metody

factorize()

trwaão

dãuİej niİ w przypadku jej szeregowego wywoãywania. PrzeznaczajĈc po
jednym wĈtku dla kaİdej liczby, w innych jčzykach programowania moİna
oczekiwaþ przyĤpieszenia dziaãania programu nieco mniejszego niİ cztero-
krotne, co wynika z obciĈİenia zwiĈzanego z tworzeniem wĈtków i ich ko-
ordynacjĈ. W przypadku komputera wyposaİonego w procesor dwurdzeniowy
moİna oczekiwaþ jedynie okoão dwukrotnego przyĤpieszenia wykonywania
programu. Jednak nigdy nie bčdziesz sič spodziewaã, İe wydajnoĤþ bčdzie
gorsza, gdy do obliczeę moİna wykorzystaþ wiele rdzeni procesora. To demon-
struje wpãyw mechanizmu GIL na programy wykonywane przez standar-
dowy interpreter CPython.

IstniejĈ róİne sposoby pozwalajĈce CPython na wykorzystanie wielu wĈtków,
ale nie dziaãajĈ one ze standardowĈ klasĈ

Thread

(patrz sposób 41.) i imple-

mentacja tych rozwiĈzaę moİe wymagaþ doĤþ duİego wysiãku. MajĈc Ĥwia-
domoĤþ istnienia wspomnianych ograniczeę, moİesz sič zastanawiaþ, dla-
czego Python w ogóle obsãuguje wĈtki. Mamy ku temu dwa dobre powody.

Pierwszy — wiele wĈtków daje zãudzenie, İe program wykonuje jednocze-
Ĥnie wiele zadaę. Samodzielna implementacja mechanizmu jednoczesnego
wykonywania zadaę jest trudna (przykãad znajdziesz w sposobie 40.). Dzički
wĈtkom pozostawiasz Pythonowi obsãugč równolegãego uruchamiania funkcji.

Poleć książkę

Kup książkę

background image

138

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

To dziaãa, poniewaİ CPython gwarantuje zachowanie równoĤci mičdzy uru-
chomionymi wĈtkami Pythona, nawet jeĤli ze wzglčdu na ograniczenie na-
kãadane przez mechanizm GIL w danej chwili tylko jeden z nich robi postčp.

Drugi powód obsãugi wĈtków w Pythonie to blokujĈce operacje wejĤcia-
-wyjĤcia, które zachodzĈ, gdy Python wykonuje okreĤlonego typu wywoãania
systemowe. Za pomocĈ wspomnianych wywoãaę systemowych programy
Pythona proszĈ system operacyjny komputera o interakcjč ze Ĥrodowiskiem
zewnčtrznym. Przykãady blokujĈcych operacji wejĤcia-wyjĤcia to odczyt i zapis
plików, praca z sieciami, komunikacja z urzĈdzeniami takimi jak monitor
itd. WĈtki pomagajĈ w obsãudze blokujĈcych operacji wejĤcia-wyjĤcia przez
odizolowanie Twojego programu od czasu, jakiego system operacyjny potrze-
buje na udzielenie odpowiedzi na İĈdania.

Zaãóİmy, İe za pomocĈ portu szeregowego chcesz wysãaþ sygnaã do zdalnie
sterowanego Ĥmigãowca. Jako proxy dla tej czynnoĤci wykorzystamy wolne
wywoãanie systemowe (

select

). Funkcja prosi system operacyjny o blokadč

trwajĈcĈ 0,1 sekundy, a nastčpnie zwraca kontrolč z powrotem do programu.
Otrzymujemy wičc sytuacjč podobnĈ, jaka zachodzi podczas uİycia synchro-
nicznego portu szeregowego.

import select

def slow_systemcall():
select.select([], [], [], 0.1)

Szeregowe wykonywanie wywoãaę systemowych powoduje liniowe zwičk-
szanie sič iloĤci czasu niezbčdnego do ich wykonania.

start = time()

for _ in range(5):
slow_systemcall()
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 0.503 sekund

Problem polega na tym, İe w trakcie wykonywania funkcji

slow_systemcall()

program nie moİe zrobiþ İadnego innego postčpu. Gãówny wĈtek programu
zostaã zablokowany przez wywoãanie systemowe

select

. Tego rodzaju sytu-

acja w praktyce jest straszna. Potrzebujesz sposobu pozwalajĈcego na obli-
czanie kolejnego ruchu Ĥmigãowca podczas wysyãania sygnaãu, w przeciwnym
razie Ĥmigãowiec moİe sič rozbiþ. Kiedy wystčpuje potrzeba jednoczesnego
wykonania blokujĈcych operacji wejĤcia-wyjĤcia i pewnych obliczeę, najwyİ-
sza pora rozwaİyþ przeniesienie wywoãaę systemowych do wĈtków.

W poniİszym fragmencie kodu mamy kilka wywoãaę funkcji

slow_systemcall()

w oddzielnych wĈtkach. To pozwoli na jednoczesnĈ komunikacjč z wieloma
portami szeregowymi (i Ĥmigãowcami), natomiast wĈtek gãówny bčdzie po-
zostawiony do wykonywania niezbčdnych obliczeę.

Poleć książkę

Kup książkę

background image

Sposób 37. Uİycie wĈtków dla operacji blokujĈcych wejĤcie-wyjĤcie

139

start = time()
threads = []
for _ in range(5):

thread = Thread(target=slow_systemcall)
thread.start()
threads.append(thread)

Po uruchomieniu wĈtków mamy do wykonania pewnĈ pracč, czyli oblicze-
nie kolejnego ruchu Ĥmigãowca przed oczekiwaniem na zakoęczenie dziaãa-
nia wĈtków obsãugujĈcych wywoãania systemowe.

def compute_helicopter_location(index):

# ...

for i in range(5):
compute_helicopter_location(i)
for thread in threads:
thread.join()
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 0.102 sekund

Caãkowita iloĤþ czasu potrzebnego na równolegãe wykonanie operacji jest
pičciokrotnie mniejsza niİ w przypadku szeregowego wykonywania zadaę.
To pokazuje, İe wywoãania systemowe sĈ wykonywane równoczeĤnie w wielu
wĈtkach Pythona, nawet pomimo ograniczeę nakãadanych przez mechanizm
GIL. Wprawdzie mechanizm GIL uniemoİliwia równolegãe wykonywanie kodu
utworzonego przez programistč, ale nie ma wpãywu ubocznego na wywoãania
systemowe. Przedstawione rozwiĈzanie sič sprawdza, poniewaİ wĈtki Pythona
zwalniajĈ mechanizm GIL przed wykonaniem wywoãaę systemowych i ponow-
nie do niego powracajĈ po zakoęczeniu wywoãania systemowego.

Poza wĈtkami istnieje jeszcze wiele innych sposobów pracy z blokujĈcymi
operacjami wejĤcia-wyjĤcia, na przykãad uİycie moduãu

asyncio

. Wspomniane

rozwiĈzania alternatywne przynoszĈ waİne korzyĤci. Jednak wymagajĈ takİe
dodatkowej pracy w postaci koniecznoĤci refaktoryzacji kodu Įródãowego,
aby go dopasowaþ do innego modelu wykonywania (patrz sposób 40.). Uİycie
wĈtków to najprostszy sposób na równolegãe wykonywanie blokujĈcych ope-
racji wejĤcia-wyjĤcia i jednoczeĤnie wymaga wprowadzania jedynie mini-
malnych zmian w programie.

Do zapamičtania



Z powodu dziaãania globalnej blokady interpretera (mechanizm GIL)
wĈtki Pythona nie pozwalajĈ na równolegãe uruchamianie kodu bajtowe-
go w wielu rdzeniach procesora.



Pomimo istnienia mechanizmu GIL wĈtki Pythona nadal pozostajĈ uİyteczne,
poniewaİ oferujĈ ãatwy sposób jednoczesnego wykonywania wielu zadaę.

Poleć książkę

Kup książkę

background image

140

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ



Uİywaj wĈtków Pythona do równoczesnego wykonywania wielu wywoãaę
systemowych. Tym samym bčdzie moİna jednoczeĤnie wykonywaþ blo-
kujĈce operacje wejĤcia-wyjĤcia oraz pewne obliczenia.

Sposób 38. Uİywaj klasy Lock, aby unikaþ stanu wyĤcigu

w wĈtkach

Po dowiedzeniu sič o istnieniu mechanizmu GIL (patrz sposób 37.) wielu
nowych programistów Pythona przyjmuje zaãoİenie, İe moİna zrezygnowaþ
z uİycia muteksu w kodzie. Skoro mechanizm GIL uniemoİliwia wĈtkom
Pythona ich równoczesne dziaãanie w wielu rdzeniach procesora, wičc moİna
wysnuþ wniosek, İe ta sama blokada musi dotyczyþ takİe struktur danych
programu, prawda? Pewne testy przeprowadzone na typach takich jak listy
i sãowniki mogĈ nawet pokazaþ, İe przyjčte zaãoİenie jest sãuszne.

Musisz mieþ jednak ĤwiadomoĤþ, İe niekoniecznie tak jest. Mechanizm GIL
nie zapewnia ochrony programowi. Wprawdzie w danej chwili jest wykony-
wany tylko jeden wĈtek Pythona, ale operacje wĈtku na strukturach danych
mogĈ byþ zakãócone mičdzy dwoma instrukcjami kodu bajtowego w interpre-
terze Pythona. To jest niebezpieczne, jeĤli jednoczeĤnie z wielu wĈtków pró-
bujesz uzyskaþ dostčp do tych samych obiektów. Struktury danych mogĈ byþ
praktycznie w kaİdej chwili uszkodzone na skutek wspomnianych zakãó-
ceę, co doprowadzi do uszkodzenia programu.

Zaãóİmy, İe tworzysz program przeprowadzajĈcy równoczeĤnie wiele opera-
cji, takich jak sprawdzanie poziomu Ĥwiatãa w pewnej liczbie czujników
sieciowych. Jeİeli chcesz okreĤliþ caãkowitĈ liczbč próbek, jakie miaãy miej-
sce w danym czasie, moİesz je agregowaþ za pomocĈ nowej klasy.

class Counter(object):
def __init__(self):
self.count = 0

def increment(self, offset):

self.count += offset

WyobraĮ sobie, İe kaİdy czujnik ma wãasny wĈtek roboczy, poniewaİ odczyt
czujnika wymaga blokujĈcej operacji wejĤcia-wyjĤcia. Po przeprowadzeniu
pomiaru wĈtek roboczy inkrementuje wartoĤþ licznika, cykl jest powtarzany
aİ do osiĈgničcia maksymalnej liczby oczekiwanych operacji odczytu.

def worker(sensor_index, how_many, counter):
for _ in range(how_many):

# Odczyt danych z czujnika.
# ...
counter.increment(1)

Poleć książkę

Kup książkę

background image

Sposób 38. Uİywaj klasy Lock, aby unikaþ stanu wyĤcigu w wĈtkach

141

Poniİej przedstawiãem definicjč funkcji uruchamiajĈcej wĈtek roboczy dla
poszczególnych czujników oraz oczekujĈcej na zakoęczenie odczytu przez
kaİdy z nich:

def run_threads(func, how_many, counter):
threads = []
for i in range(5):
args = (i, how_many, counter)
thread = Thread(target=func, args=args)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()

Jednoczesne uruchomienie pičciu wĈtków wydaje sič proste, a dane wyj-
Ĥciowe powinny byþ oczywiste.

how_many = 10**5
counter = Counter()
run_threads(worker, how_many, counter)
print('Oczekiwana liczba próbek %d, znaleziona %d' %
(5 * how_many, counter.count))
>>>
Oczekiwana liczba próbek 500000, znaleziona 278328

Jednak wynik znacznie odbiega od oczekiwanego! Co sič staão? Jak coĤ tak
prostego mogão sič nie udaþ, zwãaszcza İe w danej chwili moİe dziaãaþ tylko
jeden wĈtek interpretera Pythona?

Interpreter Pythona wymusza zachowanie sprawiedliwoĤci mičdzy wyko-
nywanymi wĈtkami, aby wszystkie otrzymaãy praktycznie takĈ samĈ iloĤþ
czasu procesora. Dlatego teİ Python bčdzie wstrzymywaþ dziaãanie bieİĈ-
cego wĈtku i wznawiaþ dziaãanie kolejnego. Problem polega na tym, İe do-
kãadnie nie wiesz, kiedy Python wstrzyma dziaãanie Twoich wĈtków. WĈtek
moİe byþ wičc wstrzymany nawet w poãowie operacji, która powinna pozostaþ
niepodzielna. Tak sič wãaĤnie staão w omawianym przykãadzie.

Metoda

increment()

obiektu

Counter

wyglĈda na prostĈ.

counter.count += offset

Jednak operator

+=

uİyty w atrybucie obiektu tak naprawdč nakazuje Pytho-

nowi wykonanie w tle trzech oddzielnych operacji. Powyİsze polecenie jest
odpowiednikiem trzech poniİszych:

value = getattr(counter, 'count')
result = value + offset
setattr(counter, 'count', result)

WĈtki Pythona przeprowadzajĈce inkrementacjč mogĈ zostaþ wstrzymane
mičdzy dwoma dowolnymi operacjami przedstawionymi powyİej. To bčdzie
problematyczne, jeĤli stara wersja

value

zostanie przypisana licznikowi. Oto

przykãad nieprawidãowej interakcji mičdzy dwoma wĈtkami A i B:

Poleć książkę

Kup książkę

background image

142

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

# Wykonywanie wątku A.
value_a = getattr(counter, 'count')
# Przełączenie kontekstu do wątku B.
value_b = getattr(counter, 'count')
result_b = value_b + 1
setattr(counter, 'count', result_b)
# Przełączenie kontekstu z powrotem do wątku A.
result_a = value_a + 1
setattr(counter, 'count', result_a)

Po przeãĈczeniu kontekstu z wĈtku A do B nastĈpião usuničcie caãego po-
stčpu w trakcie operacji inkrementacji licznika. Dokãadnie to zdarzyão sič
w przedstawionym powyİej przykãadzie obsãugi czujników Ĥwiatãa.

Aby zapobiec tego rodzaju sytuacji wyĤcigu do danych oraz innym formom
uszkodzenia struktur danych, Python zawiera solidny zestaw narzčdzi do-
stčpnych we wbudowanym module

threading

. Najprostsze i najuİyteczniej-

sze z nich to klasa

Lock

zapewniajĈca obsãugč muteksu.

Dzički zastosowaniu blokady klasa

Counter

moİe chroniþ jej wartoĤþ bieİĈcĈ

przed jednoczesnym dostčpem z wielu wĈtków. W danej chwili tylko jeden
wĈtek bčdzie miaã moİliwoĤþ naãoİenia blokady. W poniİszym fragmencie
kodu uİyãem polecenia

with

do naãoİenia i zwolnienia blokady. To znacznie

uãatwia ustalenie, który kod jest wykonywany w trakcie trwania blokady
(wičcej informacji szczegóãowych na ten temat znajdziesz w sposobie 43.).

class LockingCounter(object):

def __init__(self):
self.lock = Lock()
self.count = 0

def increment(self, offset):
with self.lock:
self.count += offset

Teraz podobnie jak wczeĤniej uruchamiam wĈtki robocze, ale w tym celu
uİywam wywoãania

LockingCounter()

.

counter = LockingCounter()

run_threads(worker, how_many, counter)
print('Oczekiwana liczba próbek %d, znaleziona %d' %

(5 * how_many, counter.count))
>>>
Oczekiwana liczba próbek 500000, znaleziona 500000

Otrzymany wynik dokãadnie pokrywa sič z oczekiwanym. Klasa

Lock

pozwo-

liãa na rozwiĈzanie problemu.

Do zapamičtania



Choþ Python ma mechanizm GIL, nadal pozostajesz odpowiedzialny za uni-
kanie powstawania sytuacji wyĤcigu do danych mičdzy wĈtkami uİywany-
mi przez Twój program.

Poleć książkę

Kup książkę

background image

Sposób 39. Uİywaj klasy Queue do koordynacji pracy mičdzy wĈtkami

143



Twoje programy mogĈ uszkodziþ stosowane w nich struktury danych, jeĤli
pozwolisz, aby wiele wĈtków jednoczeĤnie modyfikowaão te same obiekty
bez nakãadania na nie blokad.



Klasa

Lock

oferowana przez wbudowany moduã

threading

to standardowa

implementacja mutekstu w Pythonie.

Sposób 39. Uİywaj klasy Queue do koordynacji pracy

mičdzy wĈtkami

Programy Pythona równoczeĤnie wykonujĈce wiele zadaę czčsto muszĈ ko-
ordynowaþ tč pracč. Jednym z najuİyteczniejszych narzčdzi przeznaczo-
nych do koordynacji jednoczeĤnie wykonywanych zadaę jest potokowanie
funkcji.

Potokowanie dziaãa na zasadzie podobnej do linii montaİowej w przedsič-
biorstwie. Potoki majĈ wiele faz w serii wraz z okreĤlonymi funkcjami dla
poszczególnych faz. Nowe zadania do wykonania sĈ nieustannie umieszczane
na poczĈtku potoku. Wszystkie funkcje mogĈ równolegle pracowaþ nad zada-
niami w obsãugiwanych przez nie fazach. Caãa praca przesuwa sič do przodu,
gdy wszystkie funkcje zakoęczĈ swoje zadanie. Cykl trwa aİ do wykonania
wszystkich faz. Tego rodzaju podejĤcie jest szczególnie dobre w przypadku
pracy wymagajĈcej uİycia blokujĈcych operacji wejĤcia-wyjĤcia lub podproce-
sów — czyli w przypadku zadaę, które mogĈ byþ ãatwo wykonywane rów-
nolegle za pomocĈ Pythona (patrz sposób 37.).

Na przykãad chcesz zbudowaþ system, który bčdzie pobieraã staãy strumieę
zdjčþ z aparatu cyfrowego, zmieniaã ich wielkoĤþ, a nastčpnie przekazywaã
zdjčcia do galerii w internecie. Tego rodzaju program moİna podzieliþ na
trzy fazy potoku. W pierwszej fazie bčdĈ pobierane nowe zdjčcia z aparatu.
W drugiej fazie pobrane zdjčcia zostanĈ przetworzone przez funkcjč odpo-
wiedzialnĈ za zmianč ich wielkoĤci. Nastčpnie w trzeciej i ostatniej fazie zmo-
dyfikowane zdjčcia bčdĈ za pomocĈ odpowiedniej funkcji przekazane do
galerii internetowej.

WyobraĮ sobie, İe juİ utworzyãeĤ funkcje Pythona przeznaczone do wyko-
nywania poszczególnych faz:

download()

,

resize()

i

upload()

. W jaki sposób moİ-

na przygotowaþ potok, aby praca mogãa byþ prowadzona równoczeĤnie?

Przede wszystkim potrzebny jest sposób umoİliwiajĈcy przekazywanie pra-
cy mičdzy poszczególnymi fazami potoku. Do tego celu moİna wykorzystaþ
zapewniajĈcĈ bezpieczeęstwo wĈtków kolejkč producent-konsument. (Za-
poznaj sič ze sposobem 38., aby zrozumieþ wagč bezpieczeęstwa wĈtków
w Pythonie. Z kolei w sposobie 46. znajdziesz wičcej informacji o klasie

deque

).

Poleć książkę

Kup książkę

background image

144

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

class MyQueue(object):
def __init__(self):
self.items = deque()

self.lock = Lock()

Producent, czyli w omawianym przykãadzie aparat cyfrowy, umieszcza no-
we zdjčcia na koęcu listy oczekujĈcych elementów.

def put(self, item):
with self.lock:
self.items.append(item)

Konsument, czyli w omawianym przykãadzie pierwsza faza potoku przetwa-
rzania, usuwa zdjčcia z poczĈtku listy oczekujĈcych elementów.

def get(self):
with self.lock:
return self.items.popleft()

Poniİej poszczególne fazy potoku przedstawiãem jako wĈtek Pythona, który
pobiera pracč z kolejki, takiej jak wczeĤniej wspomniana, wykonuje odpo-
wiedniĈ funkcjč, a nastčpnie uzyskany wynik umieszcza w innej kolejce.
Ponadto monitoruje liczbč razy, jakie wĈtek roboczy zostaã sprawdzony pod
kĈtem nowych danych wejĤciowych oraz iloĤþ wykonanej pracy.

class Worker(Thread):
def __init__(self, func, in_queue, out_queue):
super().__init__()
self.func = func
self.in_queue = in_queue
self.out_queue = out_queue
self.polled_count = 0
self.work_done = 0

Najtrudniejsza czčĤþ wiĈİe sič z tym, İe wĈtek roboczy musi prawidãowo ob-
sãuİyþ sytuacjč, w której kolejka danych wejĤciowych bčdzie pusta, ponie-
waİ poprzednia faza jeszcze nie zakoęczyãa swojego zadania. Tym zajmujemy
sič tam, gdzie nastčpuje zgãoszenie wyjĈtku

IndexError

. Moİna to potraktowaþ

jako przestój na linii montaİowej.

def run(self):
while True:
self.polled_count += 1
try:

item = self.in_queue.get()
except IndexError:
sleep(0.01) # Brak zadania do wykonania.
else:
result = self.func(item)
self.out_queue.put(result)
self.work_done += 1

Teraz pozostaão juİ poãĈczenie trzech wymienionych faz ze sobĈ przez
utworzenie kolejek przeznaczonych do koordynacji oraz odpowiednich wĈt-
ków roboczych.

Poleć książkę

Kup książkę

background image

Sposób 39. Uİywaj klasy Queue do koordynacji pracy mičdzy wĈtkami

145

download_queue = MyQueue()
resize_queue = MyQueue()
upload_queue = MyQueue()

done_queue = MyQueue()
threads = [
Worker(download, download_queue, resize_queue),
Worker(resize, resize_queue, upload_queue),
Worker(upload, upload_queue, done_queue),
]

Moİna uruchomiþ wĈtki, a nastčpnie wstrzyknĈþ pewnĈ iloĤþ pracy do pierw-
szej fazy potoku. W poniİszym fragmencie kodu jako proxy dla rzeczywi-
stych danych wymaganych przez funkcjč

download()

wykorzystaãem zwykãy

egzemplarz

object

.

for thread in threads:
thread.start()
for _ in range(1000):
download_queue.put(object())

Pozostaão juİ zaczekaþ do chwili, gdy wszystkie elementy zostanĈ przetwo-
rzone przez potok i znajdĈ sič w kolejce

done_queue

.

while len(done_queue.items) < 1000:

# Zrób coś użytecznego podczas oczekiwania.
# ...

RozwiĈzanie dziaãa prawidãowo, ale wystčpuje interesujĈcy efekt uboczny
spowodowany przez wĈtki sprawdzajĈce ich kolejki danych wejĤciowych
pod kĈtem nowych zadaę do wykonania. Najtrudniejsza czčĤþ podczas prze-
chwytywania wyjĈtków

IndexError

w metodzie

run()

jest wykonywana bardzo

duİĈ liczbč razy.

processed = len(done_queue.items)
polled = sum(t.polled_count for t in threads)
print('Prztworzono', processed, 'elementów po wykonaniu',
polled, 'sprawdzeē')
>>>
Przetworzono 1000 elementów po wykonaniu 3030 sprawdzeē

SzybkoĤþ dziaãania poszczególnych funkcji roboczych moİe byþ róİna, a wičc
wczeĤniejsza faza moİe uniemoİliwiþ dokonanie postčpu w póĮniejszych fa-
zach, tym samym korkujĈc potok. To powoduje, İe póĮniejsze fazy sĈ wstrzy-
mane i nieustannie sprawdzajĈ ich kolejki danych wejĤciowych pod kĈtem
nowych zadaę do wykonania. Skutkiem bčdzie marnowanie przez wĈtki
robocze czasu procesora na wykonywanie nieuİytecznych zadaę (bčdĈ ciĈ-
gle zgãaszaþ i przechwytywaþ wyjĈtki

IndexError

).

To jednak dopiero poczĈtek nieodpowiednich dziaãaę podejmowanych przez tč
implementacjč. WystčpujĈ w niej jeszcze trzy kolejne bãčdy, których równieİ
naleİy unikaþ. Po pierwsze, operacja okreĤlenia, czy wszystkie dane wejĤciowe
zostaãy przetworzone, wymaga oczekiwania w kolejce

done_queue

. Po drugie,

Poleć książkę

Kup książkę

background image

146

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

w klasie

Worker

metoda

run()

bčdzie wykonywana w nieskoęczonoĤþ w pčtli. Nie

ma moİliwoĤci wskazania wĈtkowi roboczemu, İe czas zakoęczyþ dziaãanie.

Po trzecie (to najpowaİniejszy w skutkach z bãčdów), zatkanie potoku moİe
doprowadziþ do awarii programu. Jeİeli w fazie pierwszej nastĈpi duİy po-
stčp, natomiast w fazie drugiej duİe spowolnienie, to kolejka ãĈczĈca obie
fazy bčdzie sič nieustannie zwičkszaþ. Druga faza po prostu nie bčdzie w sta-
nie nadĈİyþ za pierwszĈ z wykonywaniem swojej pracy. Przy wystarczajĈco
duİej iloĤci czasu i danych wejĤciowych skutkiem bčdzie zuİycie przez pro-
gram caãej wolnej pamičci, a nastčpnie awaria aplikacji.

Moİna wičc wyciĈgnĈþ wniosek, İe potoki sĈ zãym rozwiĈzaniem. Trudno
samodzielnie zbudowaþ dobrĈ kolejkč producent-konsument.

Ratunek w postaci klasy Queue

Klasa

Queue

z wbudowanego moduãu

queue

dostarcza caãĈ funkcjonalnoĤþ,

której potrzebujemy do rozwiĈzania przedstawionych wczeĤniej problemów.

Klasa

Queue

eliminuje oczekiwanie w wĈtku roboczym, poniewaİ metoda

get()

jest zablokowana aİ do chwili udostčpnienia nowych danych. Na przykãad po-
niİej przedstawiãem kod uruchamiajĈcy wĈtek, który oczekuje na pojawie-
nie sič w kolejce pewnych danych wejĤciowych.

from queue import Queue
queue = Queue()

def consumer():
print('Konsument oczekuje')

queue.get() # Uruchomienie po metodzie put() przedstawionej poniżej.
print('Konsument zakoēczyđ pracú')
thread = Thread(target=consumer)
thread.start()

Wprawdzie wĈtek jest uruchomiony jako pierwszy, ale nie zakoęczy dziaãa-
nia aİ do chwili umieszczenia elementu w egzemplarzu

Queue

, gdy metoda

get()

bčdzie miaãa jakiekolwiek dane do przekazania.

print('Producent umieszcza dane')

queue.put(object()) # Uruchomienie przed metodą get() przedstawioną powyżej.
thread.join()
print('Producent zakoēczyđ pracú')
>>>
Konsument oczekuje
Producent umieszcza dane
Konsument zakoēczyđ pracú
Producent zakoēczyđ pracú

W celu rozwiĈzania problemu z zatykaniem potoku, klasa

Queue

pozwala na

podanie maksymalnej liczby zadaę, jakie mogĈ mičdzy dwoma fazami oczeki-
waþ na wykonanie. Bufor ten powoduje wywoãanie metody

put()

w celu naão-

Poleć książkę

Kup książkę

background image

Sposób 39. Uİywaj klasy Queue do koordynacji pracy mičdzy wĈtkami

147

İenia blokady, gdy kolejka jest juİ zapeãniona. W poniİszym fragmencie kodu
przedstawiãem definicjč wĈtku oczekujĈcego chwilč przed uİyciem kolejki:

queue = Queue(1) # Bufor o wielkości 1.

def consumer():
time.sleep(0.1) # Oczekiwanie.
queue.get() # Drugie wywołanie.
print('Konsument pobiera dane 1')
queue.get() # Czwarte wywołanie.
print('Konsument pobiera dane 2')

thread = Thread(target=consumer)
thread.start()

Oczekiwanie powinno pozwoliþ wĈtkowi producenta na umieszczenie obu
obiektów w kolejce, zanim wĈtek konsumenta w ogóle wywoãa metodč

get()

.

Jednak wielkoĤþ

Queue

wynosi

1

. To oznacza, İe producent dodajĈcy elementy

do kolejki bčdzie musiaã zaczekaþ, aİ wĈtek konsumenta przynajmniej raz
wywoãa metodč

get()

. Dopiero wtedy drugie wywoãanie

put()

zwolni blokadč

i pozwoli na dodanie drugiego elementu do kolejki.

queue.put(object()) # Pierwsze wywołanie.
print('Producent umieszcza dane 1')
queue.put(object()) # Trzecie wywołanie.
print('Producent umieszcza dane 2')
thread.join()
print('Producent zakoēczyđ pracú')
>>>
Producent umieszcza dane 1
Konsument pobiera dane 1
Producent umieszcza dane 2
Konsument pobiera dane 2
Producent zakoēczyđ pracú

Klasa

Queue

moİe równieİ monitorowaþ postčp pracy, uİywajĈc do tego metody

task_done()

. W ten sposób moİna zaczekaþ, aİ kolejka danych wejĤciowych fazy

zostanie opróİniona, co eliminuje koniecznoĤþ sprawdzania kolejki

done_queue

na koęcu potoku. Na przykãad poniİej zdefiniowaãem wĈtek konsumenta
wywoãujĈcy metodč

task_done()

po zakoęczeniu pracy nad elementem.

in_queue = Queue()

def consumer():
print('Konsument oczekuje')
work = in_queue.get() # Zakończone jako drugie.
print('Konsument pracuje')

# Wykonywanie pracy.
# ...
print('Konsument zakoēczyđ pracú')

in_queue.task_done() # Zakończone jako trzecie.

Thread(target=consumer).start()

Poleć książkę

Kup książkę

background image

148

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

Teraz kod producenta nie musi ãĈczyþ sič z wĈtkiem konsumenta lub spraw-
dzaþ go. Producent moİe po prostu poczekaþ na zakoęczenie pracy przez
kolejkč

in_queue

, wywoãujĈc metodč

join()

w egzemplarzu

Queue

. Nawet jeĤli

kolejka

in_queue

jest pusta, to nie bčdzie moİna sič do niej przyãĈczyþ, do-

póki nie zostanie wywoãana metoda

task_done()

dla kaİdego elementu, który

kiedykolwiek byã kolejkowany.

in_queue.put(object()) # Zakończone jako pierwsze.
print('Producent oczekuje')
in_queue.join() # Zakończone jako czwarte.
print('Producent zakoēczyđ pracú')
>>>
Konsument oczekuje
Producent oczekuje
Konsument pracuje
Konsument zakoēczyđ pracú
Producent zakoēczyđ pracú

Wszystkie wymienione funkcje moİna umieĤciþ razem w podklasie klasy

Queue

, która równieİ poinformuje wĈtek roboczy o koniecznoĤci zakoęczenia

przetwarzania. W poniİszym fragmencie kodu znajduje sič zdefiniowana
metoda

close()

dodajĈca do kolejki element specjalny, który wskazuje, İe po

nim nie powinny znajdowaþ sič juİ İadne elementy danych wejĤciowych:

class ClosableQueue(Queue):

SENTINEL = object()

def close(self):
self.put(self.SENTINEL)

Nastčpnie definiujemy iterator dla kolejki, który wyszukuje wspomniany
element specjalny i zatrzymuje iteracjč po znalezieniu tego elementu. Metoda
iteratora

__iter__()

powoduje równieİ wywoãanie metody

task_done()

w odpo-

wiednim momencie, co pozwala na monitorowanie postčpu pracy w kolejce.

def __iter__(self):
while True:
item = self.get()
try:
if item is self.SENTINEL:

return # Powoduje zakończenie działania wątku.
yield item
finally:
self.task_done()

Teraz moİna przedefiniowaþ wĈtek roboczy, aby opieraã sič na funkcjonal-
noĤci dostarczanej przez klasč

ClosableQueue

. WĈtek zakoęczy dziaãanie po

zakoęczeniu pčtli.

class StoppableWorker(Thread):
def __init__(self, func, in_queue, out_queue):

# ...

Poleć książkę

Kup książkę

background image

Sposób 39. Uİywaj klasy Queue do koordynacji pracy mičdzy wĈtkami

149

def run(self):
for item in self.in_queue:
result = self.func(item)

self.out_queue.put(result)

Poniİej przedstawiãem kod odpowiedzialny za utworzenie zbioru wĈtków
roboczych na podstawie nowej klasy:

download_queue = ClosableQueue()
# ...
threads = [
StoppableWorker(download, download_queue, resize_queue),

# ...
]

Po uruchomieniu wĈtków roboczych sygnaã zatrzymania podobnie jak wcze-
Ĥniej jest wysyãany przez zamkničcie kolejki danych wejĤciowych dla pierw-
szej fazy po umieszczeniu w niej wszystkich elementów.

for thread in threads:
thread.start()
for _ in range(1000):
download_queue.put(object())
download_queue.close()

Pozostaão juİ tylko oczekiwanie na zakoęczenie pracy przez poãĈczenie po-
szczególnych kolejek znajdujĈcych sič mičdzy fazami. Gdy dana faza zo-
stanie zakoęczona, to jest to sygnalizowane kolejnej fazie przez zamkničcie
jej kolejki danych wejĤciowych. Na koęcu kolejka

done_queue

zawiera zgodnie

z oczekiwaniami wszystkie obiekty danych wyjĤciowych.

download_queue.join()
resize_queue.close()
resize_queue.join()
upload_queue.close()
upload_queue.join()
print(done_queue.qsize(), 'elementów zostađo przetworzonych')
>>>
1000 elementów zostađo przetworzonych

Do zapamičtania



Potoki to doskonaãy sposób organizowania sekwencji zadaę jednoczeĤnie
wykonywanych przez wiele wĈtków Pythona.



Musisz byþ Ĥwiadom, İe podczas tworzenia potoków, które jednoczeĤnie
wykonujĈ wiele zadaę, pojawiajĈ sič problemy: oczekiwanie blokujĈce do-
stčp, zatrzymywanie wĈtków roboczych i niebezpieczeęstwo zuİycia caãej
dostčpnej pamičci.



Klasa

Queue

oferuje caãĈ funkcjonalnoĤþ, jakiej potrzebujesz do przygoto-

wania niezawodnych potoków: obsãugč blokad, bufory o wskazanej wiel-
koĤci i doãĈczanie do kolejek.

Poleć książkę

Kup książkę

background image

150

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

Sposób 40. Rozwaİ uİycie wspóãprogramów

w celu jednoczesnego wykonywania wielu funkcji

Sposób 40. Uİycie wspóãprogramów w celu jednoczesnego wykonywania wielu funkcji

WĈtki umoİliwiajĈ programistom Pythona pozornie jednoczesne wykony-
wanie wielu funkcji (patrz sposób 37.). Jednak z wĈtkami wiĈİĈ sič trzy
powaİne problemy.

Q

WymagajĈ zastosowania specjalnych narzčdzi do koordynacji bezpie-
czeęstwa (patrz sposoby 38. i 39). Dlatego teİ kod oparty na wĈtkach jest
trudniejszy do zrozumienia niİ kod proceduralny wykonywany w jednym
wĈtku. Wspomniana trudnoĤþ powoduje, İe kod wykorzystujĈcy wĈtki
staje sič trudniejszy do rozbudowy i obsãugi.

Q

WĈtki wymagajĈ duİej iloĤci pamičci — mniej wičcej 8 MB dla kaİdego
wykonywanego wĈtku. W wielu komputerach iloĤþ dostčpnej pamičci po-
zwala na obsãugč sporej liczby wĈtków. Co sič jednak stanie, gdy program
bčdzie próbowaã wykonywaþ „jednoczeĤnie” dziesiĈtki tysičcy funkcji?
Wspomniane funkcje mogĈ odpowiadaþ İĈdaniom uİytkowników kiero-
wanym do serwera, pikselom na ekranie, czĈsteczkom w symulacji itd.
Próba uruchomienia oddzielnego wĈtku dla kaİdej unikalnej czynnoĤci
sič nie sprawdza.

Q

Uruchamianie wĈtków jest kosztowne. Jeİeli program ma nieustannie two-
rzyþ nowe jednoczeĤnie dziaãajĈce funkcje i koęczyþ ich dziaãanie, to obciĈ-
İenie zwiĈzane z uİyciem wĈtków stanie sič ogromne i spowolni program.

Python pozwala na zniwelowanie wszystkich wymienionych powyİej pro-
blemów za pomocĈ wspóãprogramów. Wspóãprogramy pozwalajĈ na uİycie
w programie Pythona wielu pozornie jednoczeĤnie wykonywanych funkcji.
Wspóãprogramy sĈ implementowane jako rozszerzenie generatorów (patrz
sposób 16.). Kosztem uruchomienia wspóãprogramu generatora jest wywoãa-
nie funkcji. Po uruchomieniu kaİdy z nich uİywa poniİej 1 KB pamičci.

Dziaãanie wspóãprogramu polega na umoİliwieniu kodowi uİywajĈcemu gene-
ratora na wykonanie funkcji

send()

w celu wysãania wartoĤci z powrotem do

funkcji generatora po kaİdym wyraİeniu

yield

. Funkcja generatora otrzymuje

wartoĤþ przekazanĈ funkcji

send()

jako wynik wykonania odpowiedniego wyra-

İenia

yield

.

def my_coroutine():
while True:
received = yield
print('Otrzymano:', received)

it = my_coroutine()

next(it) # Wywołanie generatora.
it.send('Pierwszy')
it.send('Drugi')

Poleć książkę

Kup książkę

background image

Sposób 40. Uİycie wspóãprogramów w celu jednoczesnego wykonywania wielu funkcji

151

>>>
Otrzymano: Pierwszy
Otrzymano: Drugi

PoczĈtkowe wywoãanie

next()

jest wymagane do przygotowania generatora

na otrzymanie pierwszego wywoãania

send()

przez przejĤcie do pierwszego

wyraİenia

yield

. Razem polecenie

yield

i wywoãanie

send()

zapewniajĈ gene-

ratorowi standardowy sposób na zróİnicowanie kolejnej wartoĤci w odpo-
wiedzi na zewnčtrzne dane wejĤciowe.

Na przykãad chcesz zaimplementowaþ wspóãprogram generatora dostar-
czajĈcy wartoĤþ minimalnĈ, która byãa dotĈd uİyta. W poniİszym fragmencie
kodu

yield

przygotowuje wspóãprogram wraz z poczĈtkowĈ wartoĤciĈ mini-

malnĈ pochodzĈcĈ z zewnĈtrz. Nastčpnie generator ciĈgle otrzymuje nowe
minimum w zamian za nowĈ wartoĤþ do rozwaİenia.

def minimize():

current = yield
while True:
value = yield current
current = min(value, current)

Kod wykorzystujĈcy generator moİe wykonywaþ po jednym kroku w danej
chwili i bčdzie wyĤwietlaã wartoĤþ minimalnĈ po otrzymaniu kolejnych da-
nych wejĤciowych.

it = minimize()

next(it) # Wywołanie generatora.
print(it.send(10))
print(it.send(4))
print(it.send(22))
print(it.send(-1))
>>>
10
4
4
-1

Funkcja generatora bčdzie pozornie dziaãaãa w nieskoęczonoĤþ i robiãa postčp
wraz z kaİdym nowym wywoãaniem

send()

. Podobnie jak wĈtki, wspóãpro-

gramy to niezaleİne funkcje pobierajĈce dane wejĤciowe z ich Ĥrodowiska
i generujĈce dane wyjĤciowe. Róİnica polega na pauzie po kaİdym wyraİeniu

yield

w funkcji generatora i wznowieniu dziaãania po kaİdym wywoãaniu

send()

pochodzĈcym z zewnĈtrz. Tak wyglĈda magiczny mechanizm wspóãprogramów.

Przedstawione powyİej zachowanie pozwala, aby kod wykorzystujĈcy gene-
rator podejmowaã dziaãanie po kaİdym wyraİeniu

yield

we wspóãprogramie.

Kod moİe uİyþ wartoĤci danych wyjĤciowych generatora w celu wywoãania
innych funkcji i uaktualnienia struktur danych. Co waİniejsze, moİe po-
sunĈþ do przodu inne funkcje generatora, aİ do ich nastčpnego wyraİenia

yield

. Dzički przesuničciu do przodu wielu oddzielnych generatorów wydaje

Poleć książkę

Kup książkę

background image

152

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

sič, İe wszystkie one dziaãajĈ jednoczeĤnie. To pozwala w Pythonie na na-
Ĥladowanie zachowania wĈtków.

Gra w İycie

MoİliwoĤþ jednoczesnego dziaãania wspóãprogramów zademonstrujč teraz
na przykãadzie. Zaãóİmy, İe chcemy je wykorzystaþ do implementacji gry
w İycie. Reguãy gry sĈ proste: mamy dwuwymiarowĈ planszč o dowolnej wiel-
koĤci. Kaİde pole na planszy moİe byþ İywe lub puste.

ALIVE = '*'
EMPTY = '-'

Postčp w grze jest oparty na jednym tykničciu zegara. W trakcie tykničcia
nastčpuje sprawdzenie kaİdego pola i ustalenie, ile z jego oĤmiu sĈsiednich
pól nadal pozostaje İywych. Na podstawie liczby İywych sĈsiadów podejmo-
wana jest decyzja o stanie sprawdzanego pola: pozostaje İywe, umiera lub
sič regeneruje. Poniİej przedstawiãem przykãad planszy o wymiarach 5×5
po czterech kolejkach. Kaİdy kolejny stan gry jest przedstawiony po prawej
stronie poprzedniego. ObjaĤnienie konkretnych reguã znajdziesz poniİej.

0 | 1 | 2 | 3 | 4
----- | ----- | ----- | ----- | -----
-*--- | --*-- | --**- | --*-- | -----
--**- | --**- | -*--- | -*--- | -**--
---*- | --**- | --**- | --*-- | -----
----- | ----- | ----- | ----- | -----

Grč moİna modelowaþ, przedstawiajĈc poszczególne pola jako wspóãpro-
gram generatora dziaãajĈcy ramič w ramič z innymi.

Aby zaimplementowaþ grč, przede wszystkim potrzebny jest sposób na po-
branie stanu sĈsiednich pól. Do tego celu moİemy wykorzystaþ wspóãprogram
o nazwie

count_neighbors()

, którego dziaãanie polega na dostarczaniu obiektów

Query

. WspomnianĈ klasč

Query

zdefiniujemy samodzielnie. Jej przeznaczeniem

jest dostarczenie wspóãprogramu generatora sprawdzajĈcego stan otaczajĈce-
go go Ĥrodowiska.

Query = namedtuple('Query', ('y', 'x'))

Wspóãprogram dostarcza obiekt

Query

dla kaİdego sĈsiedniego pola. Wyni-

kiem poszczególnych wyraİeę

yield

bčdzie wartoĤþ

ALIVE

lub

EMPTY

. Mičdzy

wspóãprogramem i korzystajĈcym z niego kodem zostaã zdefiniowany interfejs.
Generator

count_neighbors()

sprawdza stan sĈsiednich pól i zwraca liczbč pól

uznawanych za İywe.

def count_neighbors(y, x):

n_ = yield Query(y + 1, x + 0) # Północ.
ne = yield Query(y + 1, x + 1) # Północny wschód.
# Zdefiniowanie kolejnych kierunków e_, se, s_, sw, w_, nw ...

Poleć książkę

Kup książkę

background image

Sposób 40. Uİycie wspóãprogramów w celu jednoczesnego wykonywania wielu funkcji

153

# ...
neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]
count = 0
for state in neighbor_states:
if state == ALIVE:
count += 1
return count

Wspóãprogramowi

count_neighbors()

moİemy teraz dostarczyþ przykãadowe

dane, aby przetestowaþ jego dziaãanie. Poniİej pokazaãem, jak obiekty

Query

bčdĈ dostarczane dla kaİdego sĈsiedniego pola. Wspóãprogram oczekuje na
informacje o stanie kaİdego obiektu

Query

przekazywane metodĈ

send()

wspóã-

programu. Ostateczna wartoĤþ licznika jest zwracana w wyjĈtku

StopIteration

,

który jest zgãaszany, gdy generator jest wyczerpany przez polecenie

return

.

it = count_neighbors(10, 5)
q1 = next(it) # Pobranie pierwszego obiektu.
print('Pierwsze wyraľenie yield: ', q1)

q2 = it.send(ALIVE) # Wysłanie informacji o stanie q1, pobranie q2.
print('Drugie wyraľenie yield:', q2)
q3 = it.send(ALIVE) # Wysłanie informacji o stanie q2, pobranie q3
# ...
try:
count = it.send(EMPTY) # Wysłanie informacji o stanie q8, pobranie ostatecznej wartości licznika.
except StopIteration as e:
print('Liczba: ', e.value) # Wartość pochodząca z polecenia return.
>>>
Pierwsze wyraľenie yield: Query(y=11, x=5)
Drugie wyraľenie yield: Query(y=11, x=6)
...
Liczba: 2

Teraz potrzebujemy moİliwoĤci wskazania, İe pole przejdzie do nowego stanu
w odpowiedzi na liczbč İywych sĈsiadów zwróconĈ przez

count_neighbors()

.

W tym celu definiujemy kolejny wspóãprogram o nazwie

step_cell()

. Ten

generator bčdzie wskazywaã zmianč stanu pola przez dostarczanie obiektów

Transition

. To jest kolejna klasa, która podobnie jak

Query

bčdzie zdefiniowana.

Transition = namedtuple('Transition', ('y', 'x', 'state'))

Wspóãprogram

step_cell()

otrzymuje argumenty w postaci danych wspóãrzčd-

nych pola na planszy. Pobiera obiekt

Query

w celu uzyskania poczĈtkowego

stanu wspomnianych wspóãrzčdnych. Uruchomi wspóãprogram

count_neighbors()

do sprawdzenia sĈsiednich pól. Wykonuje takİe logikč gry w celu ustalenia,
jaki stan dane pole powinno mieþ dla kolejnego tykničcia zegara. Na koniec
pobierany jest obiekt

Transition

, aby wskazaþ Ĥrodowisku nastčpny stan pola.

def game_logic(state, neighbors):

# ...

def step_cell(y, x):
state = yield Query(y, x)

Poleć książkę

Kup książkę

background image

154

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

neighbors = yield from count_neighbors(y, x)
next_state = game_logic(state, neighbors)
yield Transition(y, x, next_state)

Co waİniejsze, wywoãanie

count_neighbors()

uİywa wyraİenia

yield from

. Wy-

raİenie to pozwala Pythonowi na ãĈczenie wspóãprogramów generatora, co
uãatwia wielokrotne uİycie niewielkich fragmentów funkcjonalnoĤci i two-
rzenie skomplikowanych wspóãprogramów na podstawie prostych. Po wy-
czerpaniu

count_neighbors()

ostateczna wartoĤþ zwracana przez wspóãprogram

(za pomocĈ polecenia

return

) bčdzie przekazana do

step_cell()

jak wynik wyra-

İenia

yield from

.

Teraz moİemy wreszcie zdefiniowaþ prostĈ logikč gry w İycie. Tak napraw-
dč mamy jedynie trzy reguãy.

def game_logic(state, neighbors):
if state == ALIVE:
if neighbors < 2:
return EMPTY # Śmierć: zbyt mało.
elif neighbors > 3:

return EMPTY # Śmierć: zbyt wiele.
else:
if neighbors == 3:
return ALIVE # Regeneracja.
return state

Wspóãprogramowi

step_cell()

dostarczamy przykãadowe dane, aby go prze-

testowaþ.

it = step_cell(10, 5)

q0 = next(it) # Obiekt Query położenia początkowego.
print('Ja: ', q0)
q1 = it.send(ALIVE) # Wysłanie mojego stanu, ustawienie pola sąsiada.
print('Q1: ', q1)
# ...
t1 = it.send(EMPTY) # Wysłanie stanu q8, podjęcie decyzji w grze.
print('Wynik: ', t1)
>>>
Ja: Query(y=10, x=5)
Q1: Query(y=11, x=5)
...
Wynik: Transition(y=10, x=5, state='-')

Celem gry jest wykonanie tej logiki dla wszystkich pól znajdujĈcych sič na
planszy. W tym celu moİemy umieĤciþ wspóãprogram

step_cell()

we wspóã-

programie

simulate()

. Wspóãprogram bčdzie analizowaã kolejne pola planszy

przez wielokrotne pobieranie

step_cell()

. Po sprawdzeniu wszystkich wspóã-

rzčdnych nastčpuje dostarczenie obiektu

TICK

, wskazujĈcego, İe bieİĈca gene-

racja pól zostaãa zakoęczona.

TICK = object()

def simulate(height, width):

Poleć książkę

Kup książkę

background image

Sposób 40. Uİycie wspóãprogramów w celu jednoczesnego wykonywania wielu funkcji

155

while True:
for y in range(height):
for x in range(width):

yield from step_cell(y, x)
yield TICK

W przypadku wspóãprogramu

simulate()

imponujĈce jest to, İe pozostaje on

caãkowicie niezwiĈzany z otaczajĈcym go Ĥrodowiskiem. Nadal nie zdefiniowa-
liĤmy sposobu przedstawienia planszy w obiektach Pythona, obsãugi war-
toĤci

Query

,

Transition

i

TICK

na zewnĈtrz, a takİe tego, jak gra pobiera stan

poczĈtkowy. Jednak logika pozostaje czytelna. Kaİde pole przeprowadzi
zmianč stanu za pomocĈ

step_cell()

. Nastčpnie mamy tykničcie zegara gry.

Proces bčdzie kontynuowany w nieskoęczonoĤþ, dopóki trwa postčp we
wspóãprogramie

simulate()

.

Na tym polega pičkno wspóãprogramów. PomagajĈ skoncentrowaþ sič na lo-
gice tego, co próbujesz osiĈgnĈþ. PozwalajĈ na oddzielenie poleceę kodu dla
Ĥrodowiska od jego implementacji, a tym samym wspóãprogramy mogĈ dziaãaþ
równoczeĤnie. Na przestrzeni czasu zyskujesz moİliwoĤþ poprawienia im-
plementacji wspomnianych poleceę kodu bez koniecznoĤci zmiany wspóã-
programów.

Teraz chcemy uruchomiþ

simulate()

w prawdziwym Ĥrodowisku. W tym celu

potrzebujemy sposobu na przestawienie stanu poszczególnych pól planszy.
Poniİej przedstawiãem klasč odpowiedzialnĈ za obsãugč planszy:

class Grid(object):
def __init__(self, height, width):
self.height = height
self.width = width
self.rows = []
for _ in range(self.height):
self.rows.append([EMPTY] * self.width)

def __str__(self):

# ...

Plansza pozwala na pobieranie i ustawianie wartoĤci dowolnej wspóãrzčd-
nej. Wspóãrzčdne wykraczajĈce poza granice bčdĈ zawijane, co powoduje,
İe plansza dziaãa na zasadzie nieskoęczonego miejsca.

def query(self, y, x):
return self.rows[y % self.height][x % self.width]

def assign(self, y, x, state):
self.rows[y % self.height][x % self.width] = state

Musimy jeszcze zdefiniowaþ funkcjč interpretujĈcĈ wartoĤci otrzymane ze
wspóãprogramu

simulate()

oraz jego wszystkich wewnčtrznych wspóãpro-

gramów. Funkcja ta zamienia instrukcje ze wspóãprogramów na interakcje
z otaczajĈcym Ĥrodowiskiem. Dla caãej planszy wykonuje jeden krok do przo-
du, a nastčpnie zwraca nowĈ planszč zawierajĈcĈ kolejny stan.

Poleć książkę

Kup książkę

background image

156

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

def live_a_generation(grid, sim):
progeny = Grid(grid.height, grid.width)
item = next(sim)

while item is not TICK:
if isinstance(item, Query):
state = grid.query(item.y, item.x)
item = sim.send(state)
else: # Konieczne jest przekształcenie.
progeny.assign(item.y, item.x, item.state)
item = next(sim)
return progeny

Aby zobaczyþ tč funkcjč w dziaãaniu, konieczne jest utworzenie planszy
i ustawienie jej stanu poczĈtkowego. Poniİej przedstawiãem przykãad utwo-
rzenia klasycznego ksztaãtu.

grid = Grid(5, 9)
grid.assign(0, 3, ALIVE)
# ...
print(grid)
>>>
---*-----
----*----
--***----
---------

---------

Teraz moİemy wykonaþ jeden krok naprzód. Moİesz zobaczyþ, İe w oparciu
o proste reguãy zdefiniowane w funkcji

game_logic()

ksztaãt ten zostaje prze-

suničty na dóã i w prawĈ stronč.

class ColumnPrinter(object):

# ...

columns = ColumnPrinter()
sim = simulate(grid.height, grid.width)
for i in range(5):
columns.append(str(grid))
grid = live_a_generation(grid, sim)
print(columns)
>>>
0 | 1 | 2 | 3 | 4
---*----- | --------- | --------- | --------- | ---------
----*---- | --*-*---- | ----*---- | ---*----- | ----*----
--***---- | ---**---- | --*-*---- | ----**--- | -----*---
--------- | ---*----- | ---**---- | ---**---- | ---***---
--------- | --------- | --------- | --------- | ---------

Najlepsze w omawianym podejĤciu jest to, İe moİna zmieniþ funkcjč

game_

´

logic()

bez koniecznoĤci wprowadzania jakichkolwiek modyfikacji w ota-

czajĈcym jĈ kodzie. Istnieje wičc moİliwoĤþ zmiany reguã lub dodania wičk-
szych sfer wpãywu za pomocĈ istniejĈcej mechaniki obiektów

Query

,

Transition

i

TICK

. To pokazuje, jak wspóãprogramy pozwalajĈ na zachowanie podziaãu

zadaę, co jest niezwykle waİnĈ zasadĈ projektowĈ.

Poleć książkę

Kup książkę

background image

Sposób 40. Uİycie wspóãprogramów w celu jednoczesnego wykonywania wielu funkcji

157

Wspóãprogramy w Pythonie 2

Niestety, Python 2 nie oferuje pewnych syntaktycznych cech, dzički którym
wspóãprogramy sĈ tak eleganckim rozwiĈzaniem w Pythonie 3. W Pythonie 2
istniejĈ dwa powaİne ograniczenia.

Pierwsze to brak wyraİenia

yield from

. Jeİeli wičc chcesz ãĈczyþ wspóãprogra-

my generatora w Pythonie 2, musisz zastosowaþ dodatkowĈ pčtlč w punk-
cie delegacji.

# Python 2
def delegated():
yield 1
yield 2

def composed():
yield 'A'
for value in delegated(): # Odpowiednik wyrażenia yield from w Pythonie 3.
yield value
yield 'B'

print list(composed())
>>>

['A', 1, 2, 'B']

Drugie ograniczenie polega na braku obsãugi polecenia

return

w generato-

rach Pythona 2. W celu uzyskania tego samego zachowania, zapewniajĈcego
prawidãowe dziaãanie z blokami

try-except-finally

, konieczne jest zdefiniowanie

wãasnego typu wyjĈtku i jego zgãaszanie, gdy ma byþ zwrócona wartoĤþ.

# Python 2
class MyReturn(Exception):
def __init__(self, value):
self.value = value

def delegated():
yield 1

raise MyReturn(2) # Odpowiednik polecenia return 2 w Pythonie 3.
yield 'Nie osiægniúto'

def composed():
try:
for value in delegated():
yield value
except MyReturn as e:
output = e.value
yield output * 4

print list(composed())
>>>

[1, 8]

Poleć książkę

Kup książkę

background image

158

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

Do zapamičtania



Wspóãprogramy oferujĈ efektywny sposób wykonywania dziesiĈtek tysič-
cy funkcji pozornie w tym samym czasie.



W przypadku generatora wartoĤciĈ wyraİenia

yield

bčdzie wartoĤþ prze-

kazana metodzie

send()

generatora z poziomu zewnčtrznego kodu.



Wspóãprogramy sĈ waİnym narzčdziem pozwalajĈcym na oddzielenie pod-
stawowej logiki programu od jego interakcji z otaczajĈcym go Ĥrodowiskiem.



Python 2 nie obsãuguje wyraİenia

yield from

, a takİe zwrotu wartoĤci

z generatorów.

Sposób 41. Rozwaİ uİycie concurrent.futures(),

aby otrzymaþ prawdziwĈ równolegãoĤþ

Na pewnym etapie tworzenia programów w Pythonie moİesz dotrzeþ do
Ĥciany, jeĤli chodzi o kwestie wydajnoĤci. Nawet po przeprowadzeniu opty-
malizacji kodu (patrz sposób 58.) wykonywanie programu wciĈİ moİe okazaþ
sič za wolne w stosunku do potrzeb. W nowoczesnych komputerach, w któ-
rych nieustannie zwičksza sič liczba dostčpnych rdzeni procesora, moİna
przyjĈþ zaãoİenie, İe jedynym rozsĈdnym rozwiĈzaniem jest równolegãoĤþ.
Co sič stanie, jeİeli kod odpowiedzialny za obliczenia podzielisz na nieza-
leİne fragmenty jednoczeĤnie dziaãajĈce w wielu rdzeniach procesora?

Niestety, mechanizm GIL w Pythonie uniemoİliwia osiĈgničcie prawdziwej
równolegãoĤci w wĈtkach (patrz sposób 37.), a wičc tč opcjč moİna wyklu-
czyþ. InnĈ czčsto pojawiajĈcĈ sič propozycjĈ jest ponowne utworzenie kodu
o znaczeniu krytycznym dla wydajnoĤci. Nowy kod powinien mieþ postaþ
moduãu rozszerzenia i byþ utworzony w jčzyku C. Dzički jčzykowi C zbli-
İasz sič bardziej do samego sprzčtu, a utworzony w nim kod dziaãa szybciej
niİ w Pythonie, co eliminuje koniecznoĤþ zastosowania równolegãoĤci. Rozsze-
rzenia utworzone w jčzyku C mogĈ równieİ uruchamiaþ rodzime wĈtki dzia-
ãajĈce równoczeĤnie i wykorzystujĈce wiele rdzeni procesora. API Pythona
przeznaczone dla rozszerzeę tworzonych w jčzyku C jest doskonale udo-
kumentowane i stanowi doskonaãe wyjĤcie awaryjne.

Jednak ponowne utworzenie kodu w jčzyku C wiĈİe sič z wysokim kosztem.
Kod, który w Pythonie jest krótki i zrozumiaãy, w jčzyku C moİe staþ sič
rozwlekãy i skomplikowany. Tego rodzaju kod wymaga starannego przete-
stowania i upewnienia sič, İe funkcjonalnoĤþ odpowiada pierwotnej, utwo-
rzonej w Pythonie. Ponadto trzeba sprawdziþ, czy nie zostaãy wprowadzone
nowe bãčdy. Czasami wãoİony wysiãek sič opãaca, co wyjaĤnia istnienie w spo-
ãecznoĤci Pythona ogromnego ekosystemu moduãów rozszerzeę utworzonych
w jčzyku C. Dzički wspomnianym rozszerzeniom moİna przyĤpieszyþ operacje

Poleć książkę

Kup książkę

background image

Sposób 41. Rozwaİ uİycie concurrent.futures(), aby otrzymaþ prawdziwĈ równolegãoĤþ

159

takie jak przetwarzanie tekstu, tworzenie obrazów i operacje na macierzach.
IstniejĈ nawet narzčdzia typu open source, na przykãad Cython (http://cython.
org/
) i Numba (http://numba.pydata.org/) uãatwiajĈce przejĤcie do jčzyka C.

Problem polega na tym, İe utworzenie jednego fragmentu programu w jč-
zyku C w wičkszoĤci przypadków okaİe sič niewystarczajĈce. Zoptymalizo-
wane programy Pythona zwykle nie majĈ tylko jednego Įródãa powolnego
dziaãania, ale raczej wiele powaİnych Įródeã. Aby wičc wykorzystaþ szybkoĤþ
oferowanĈ przez jčzyk C i wĈtki, konieczne bčdzie przepisanie duİych frag-
mentów programu, co drastycznie wydãuİa czas potrzebny na jego przetesto-
wanie i zwičksza ryzyko. Musi istnieþ lepszy sposób pozwalajĈcy na rozwiĈ-
zywanie trudnych problemów obliczeniowych w Pythonie.

Wbudowany moduã

multiprocessing

, ãatwo dostčpny za pomocĈ innego wbu-

dowanego moduãu,

concurrent.futures

, moİe byþ dokãadnie tym, czego po-

trzebujesz. Pozwala Pythonowi na jednoczesne wykorzystanie wielu rdzeni
procesora dzički uruchomieniu dodatkowych interpreterów jako procesów
potomnych. Wspomniane procesy potomne sĈ niezaleİne od gãównego in-
terpretera, a wičc ich blokady globalne równieİ pozostajĈ oddzielne. Kaİdy
proces potomny moİe w peãni wykorzystaþ jeden rdzeę procesora. Ponadto
kaİdy z nich ma odwoãanie do procesu gãównego, z którego otrzymuje pole-
cenia przeprowadzenia obliczeę i do którego zwraca wynik.

Na przykãad przyjmujemy zaãoİenie, İe w Pythonie ma zostaþ przeprowa-
dzona operacja wykonujĈca intensywne obliczenia i wykorzystujĈca wiele
rdzeni procesora. W poniİszym przykãadzie uİyãem implementacji algoryt-
mu wyszukujĈcego najwičkszy wspólny mianownik dwóch liczb jako proxy
dla dwóch znacznie bardziej wymagajĈcych obliczeę algorytmów, takich jak
symulacja dynamiki cieczy i równania Naviera-Stokesa.

def gcd(pair):
a, b = pair
low = min(a, b)
for i in range(low, 0, -1):
if a % i == 0 and b % i == 0:
return i

Szeregowe wykonywanie tej funkcji oznacza liniowy wzrost czasu potrzebnego
na przeprowadzenie obliczeę, poniewaİ nie zostaãa uİyta równolegãoĤþ.

numbers = [(1963309, 2265973), (2030677, 3814172),
(1551645, 2229620), (2039045, 2020802)]
start = time()
results = list(map(gcd, numbers))
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 1.170 sekund

Poleć książkę

Kup książkę

background image

160

Rozdziaã 5. WspóãbieİnoĤþ i równolegãoĤþ

Jeİeli ten kod zostanie wykonany w wielu wĈtkach Pythona, nie spowoduje to
İadnej poprawy wydajnoĤci, poniewaİ mechanizm GIL uniemoİliwia Pythono-
wi jednoczesne uİycie wielu rdzeni procesora. Poniİej prezentujč, jak wyglĈda
przeprowadzenie tych samych obliczeę za pomocĈ moduãu

concurrent.futures

,

jego klasč

ThreadPoolExecutor

i dwa wĈtki robocze (w celu dopasowania ich do

liczby rdzeni w moim komputerze).

start = time()
pool = ThreadPoolExecutor(max_workers=2)
results = list(pool.map(gcd, numbers))
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 1.199 sekund

Jak widzisz, czas wykonania zadania jeszcze sič wydãuİyã, co ma zwiĈzek
z obciĈİeniem dotyczĈcym uruchomienia puli wĈtków i komunikacji z niĈ.

Pora na coĤ zaskakujĈcego: zmiana tylko jednego wiersza kodu wystarczy,
aby staão sič coĤ magicznego. Jeİeli klasč

ThreadPoolExecutor

zastĈpimy klasĈ

ProcessPoolExecutor

z moduãu

concurrent.futures

, to wszystko ulegnie przy-

Ĥpieszeniu.

start = time()

pool = ProcessPoolExecutor(max_workers=2) # Jedyna zmiana w kodzie.
results = list(pool.map(gcd, numbers))
end = time()
print('Operacja zabrađa %.3f sekund' % (end - start))
>>>
Operacja zabrađa 0.663 sekund

Po uruchomieniu kodu na moim dwurdzeniowym komputerze widaþ zna-
czĈcĈ poprawč wydajnoĤci. Jak to moİliwe? Poniİej przedstawiam faktycz-
ny sposób dziaãania klasy

ProcessPoolExecutor

z uİyciem niskiego poziomu

konstrukcji dostarczanych przez moduã

multiprocessing

:

1.

Kaİdy element danych wejĤciowych

numbers

zostaje przekazany do

map

.

2.

Dane sĈ serializowane na postaþ danych binarnych za pomocĈ moduãu

pickle

(patrz sposób 44.).

3.

Serializowane dane sĈ z procesu interpretera gãównego kopiowane
do procesu interpretera potomnego za pomocĈ gniazda lokalnego.

4.

Kolejnym krokiem jest deserializacja danych na postaþ obiektów
Pythona z wykorzystaniem

pickle

. Odbywa sič to w procesie potomnym.

5.

Import moduãu Pythona zawierajĈcego funkcjč

gcd

.

6.

Uruchomienie funkcji wraz z otrzymanymi danymi wejĤciowymi.
Inne procesy potomne wykonujĈ tč samĈ funkcjč, ale z innymi danymi.

7.

Serializacja wyniku na postaþ bajtów.

Poleć książkę

Kup książkę

background image

Sposób 41. Rozwaİ uİycie concurrent.futures(), aby otrzymaþ prawdziwĈ równolegãoĤþ

161

8.

Skopiowanie bajtów przez gniazdo lokalne do procesu nadrzčdnego.

9.

Deserializacja bajtów z powrotem na postaþ obiektów Pythona w procesie
nadrzčdnym.

10.

PoãĈczenie wyników z wielu procesów potomnych w pojedynczĈ listč
bčdĈcĈ ostatecznym wynikiem.

Wprawdzie przedstawiony powyİej proces wydaje sič prosty dla programisty,
ale moduã

multiprocessing

i klasa

ProcessPoolExecutor

muszĈ wykonaþ ogromnĈ

pracč, aby równolegãe wykonywanie zadaę byão moİliwe. W wičkszoĤci innych
jčzyków programowania jedynym miejscem wymagajĈcym koordynacji dwóch
wĈtków jest pojedyncza blokada lub niepodzielna operacja. ObciĈİenie zwiĈ-
zane z uİyciem moduãu

multiprocessing

jest duİe z powodu koniecznoĤci

przeprowadzania serializacji i deserializacji mičdzy procesami nadrzčdnym
i potomnymi.

Schemat ten wydaje sič doskonale dopasowany do pewnego typu odizolo-
wanych zadaę, w duİej mierze opartych na dĮwigni. Tutaj „odizolowanych”
oznacza, İe funkcja nie musi z innymi czčĤciami programu wspóãdzieliþ
informacji o stanie. Z kolei wyraİenie „w duİej mierze opartych na dĮwigni”
oznacza tutaj sytuacjč, gdy mičdzy procesami nadrzčdnym i potomnym musi
byþ przekazywana jedynie niewielka iloĤþ danych niezbčdnych do przeprowa-
dzenia duİych obliczeę. Algorytm najwičkszego wspólnego mianownika jest
przykãadem takiej sytuacji, choþ wiele innych algorytmów matematycznych
dziaãa podobnie.

Jeİeli charakterystyka obliczeę, które chcesz przeprowadziþ, jest inna od
przedstawionej powyİej, to obciĈİenie zwiĈzane z uİyciem moduãu

multipro-

cessing

moİe uniemoİliwiþ zwičkszenie wydajnoĤci dziaãania programu po

zastosowaniu równolegãoĤci. W takich przypadkach moduã

multiprocessing

oferuje funkcje zaawansowane zwiĈzane z pamičciĈ wspóãdzielonĈ, bloka-
dami mičdzy procesami, kolejkami i proxy. Jednak wszystkie wymienione
funkcje sĈ niezwykle skomplikowane. Naprawdč trudno znaleĮþ uzasad-
nienie dla umieszczania tego rodzaju narzčdzi w pamičci jednego procesu
wspóãdzielonego mičdzy wĈtkami Pythona. Przeniesienie tego poziomu skom-
plikowania do innych procesów i angaİowanie gniazd jeszcze bardziej utrud-
nia zrozumienie kodu.

Sugerujč unikanie moduãu

multiprocessing

i uİycie wymienionych funkcji za

pomocĈ prostszego moduãu

concurrent.futures

. Moİesz rozpoczĈþ od zasto-

sowania klasy

ThreadPoolExecutor

w celu wykonywania odizolowanych i sta-

nowiĈcych duİe obciĈİenie funkcji w wĈtkach. Nastčpnie moİesz przejĤþ
do klasy

ProcessPoolExecutor

, aby zwičkszyþ szybkoĤþ dziaãania aplikacji. Po

wyczerpaniu wszystkich opcji moİesz rozwaİyþ bezpoĤrednie uİycie moduãu

multiprocessing

.

Poleć książkę

Kup książkę

background image

Skorowidz

A

adnotacje atrybutów klas, 128
algorytmy wbudowane, 178
API, 78, 196, 205
argumenty

funkcji, 66
pozycyjne, 61
z gwiazdkĈ, 61

ASCII, 32
atrybut foo, 118
atrybuty, 105

prywatne, 95
publiczne, 95

B

blok

else, 41, 42, 45
except, 197
finally, 46
try, 44

bãĈd

w implementacji, 198
zakresu, 52

bufory, 149

C

ciĈg tekstowy, 126, 214
collections.abc, 99
czas koordynowany UTC, 174

D

dane JSON, 45
debuger, 220
debugowanie danych

wyjĤciowych, 214

dekorator @property, 112–115
dekoratory

klasy, 127
funkcji, 163

deserializacja, 171, 173

ciĈgu tekstowego, 125
danych, 160
danych JSON, 169

deskryptor, 113, 114

Field, 129
Grade, 115, 117

diamentowa hierarchia klas, 89
docstring, 66, 187, 191
dokumentacja, 187, 188
dokumentowanie

funkcji, 190
klas, 189
moduãów, 188

doãĈczanie do kolejek, 149
domieszka, 91
domkničcia, 49
dostčp do

atrybutów, 115
docstring, 188
elementu sekwencji, 100
nazwy klasy, 123
wãaĤciwoĤci prywatnych, 97

dwukierunkowa kolejka, 178
dynamiczne okreĤlenie

argumentów, 66

dynamiczny import, 203
dziedziczenie, 73, 99
dziedziczenie wielokrotne, 91

E

EDT, Eastern Daylight Time,

176

F

FIFO, first-in, first-out, 178
filtrowanie elementów, 182
format JSON, 94, 124
functools.wraps, 163
funkcja, 47

__init__(), 89
configure(), 202

create_workers(), 86
datetime.now(), 67
download(), 145
enumerate(), 39
eval(), 215
fibonacci(), 164
filter(), 33
generate_inputs(), 84, 85
help(), 164, 165
helper(), 52
index_words(), 54, 55
inspect(), 192
int(), 28
iter(), 59
localtime(), 175
log(), 61
log_missing(), 79
map(), 33
MapReduce, 83
mapreduce(), 84, 86
my_utility(), 225
next(), 37, 55
normalize(), 57, 59
print(), 214
range(), 38, 39
register_class(), 127
repr(), 214, 216
safe_division(), 70
safe_division_b(), 70
send(), 150
setattr(), 120
slow_systemcall(), 138
strptime(), 176
super(), 89
test(), 223
wrapper(), 164
wraps(), 165
zip(), 39, 40
zip_longest(), 41

funkcje

domkničcia, 54
generujĈce, 54
metaklasy, 128
moduãu itertools, 182
pierwszorzčdne, 79

Poleć książkę

Kup książkę

background image

230

Skorowidz

G

generator, 54
generator wyraİeę, 36
GIL, global interpreter lock, 136
gra w İycie, 152
gwiazdka, 61

H

hierarchia klas, 89

I

ignorowanie przepeãnienia, 69
implementacja moduãu API,

198

import, 202
import dynamiczny, 203
inicjalizacja klasy nadrzčdnej,

87

interaktywny debuger, 220
interfejs, 78

CountMissing, 80
publiczny mypackage, 194

iteracja, 56
iterator, 57

J

jčzyk C, 162

K

klasa, 73

BetterSerializable, 127
ClosableQueue, 148
Counter, 142
Customer, 129
Decimal, 184, 185
defaultdict, 79
deque, 143, 178
Exception, 198, 199
GameState, 170
GenericWorker, 85
Grade, 116
InputData, 82
JsonMixin, 94
Lock, 140
OrderedDict, 179
ProcessPoolExecutor, 160,

161

Queue, 143, 146
RegisteredSerializable, 127
TestCase, 219
Thread, 137
ThreadPoolExecutor, 160

ToDictMixin, 93
ValidatingDB, 119

klasy

nadrzčdne, 87
pomocnicze, 73
potomne, 97

kodowanie

ASCII, 32
UTF-8, 23

kolejka

FIFO, 178
sterty, 180

kolejnoĤþ poleceę import, 201
komunikaty o bãčdach, 57
konfiguracja, 202
konfiguracja Ĥrodowiska

programistycznego, 211

konstrukcja

if-else, 28
try-except-else, 42
try-except-else-finally, 44,

45

try-finally, 42, 166

konstruktor, 88
kontekst, 167
konwencje nazw, 21
koordynacja pracy

mičdzy wĈtkami, 143

krĈg zaleİnoĤci, 200
krotka, 48, 75

L

listy skãadane, 33

â

ãĈczenie elementów, 182

M

mapowanie obiektowo-

relacyjne, 128

mechanizm GIL, 136, 139
menedİer kontekstu, 167
metaklasy, 105, 122, 128, 130
metoda

__call__(), 81, 82
__getattr__(), 117–119
__getattribute__(), 117,

119, 121

__getitem__(), 29, 101
__init__(), 87, 88
__setattr__(), 120, 121
__setitem__(), 29
_traverse(), 92
average_grade(), 74, 75
communicate(), 132, 133

deduct(), 111
factorize(), 137
fill(), 111
foo.__iter__(), 59
get(), 26
increment(), 141
index(), 181
put(), 146
report_grade(), 74
run(), 145
runcall(), 223, 226
sort(), 181
super(), 90
task_done(), 147

metody

@property, 108
typu getter, 105
typu setter, 105, 107

moduã

app, 200
collections, 76, 179
configparser, 213
copyreg, 171–174
cProfile, 223
datetime, 174, 176
decimal, 183
dialog, 201
functools, 165
gc, 228
hashlib, 134
itertools, 41, 182
main, 202
models, 194
multiprocessing, 159–162
pickle, 169, 170, 174
pytz, 177, 185
queue, 146
subprocess, 132, 135
sys, 213
threading, 142
time, 175, 176
tracemalloc, 226–228
unittest, 217, 219
unittest.mock, 218
weakref, 116

moduãy wbudowane, 163
MRO, method resolution

order, 88

N

nadpisanie klasy, 97
narzčdzia

iteratora, 182
profilowania, 222, 223

narzčdzie

Cython, 159
openssl, 133
pip, 186, 204

Poleć książkę

Kup książkę

background image

Skorowidz

231

Pylint, 23
pyvenv, 208, 211
virtualenv, 209

O

obiekty pierwszorzčdne, 50
obsãuga

blokad, 149
czasu lokalnego, 174
zdarzeę, 199

odtworzenie zaleİnoĤci, 208
okno dialogowe, 199
operator

*, 61, 71
**, 71

organizacja moduãów, 191
ORM, object- -relationalship

mappings, 128

P

pakiet mypackage, 194
pakiety, 191
parametr timeout, 135
PDT, Pacific Daylight Time,

176

pčtla

for, 41
while, 41

plik

__init__.py, 192, 194
models.py, 194
requirements.txt, 208,

209

pliki __main__, 212
pobieranie danych, 52
podziaã, 181
polecenia, 22

debugera, 221
import, 202
powãoki, 221

polecenie

class, 89, 124
contextlib, 166
def, 191
if, 27
import, 193, 204
import *, 196
nonlocal, 52, 54
python, 20
pyvenv, 206
try-except, 198
with, 142, 166–168
yield, 151

polimorfizm @classmethod,

82, 85

potokowanie, 143
procesy potomne, 132, 135
produkcja, 211
protokóã iteratora, 58
przekazywanie argumentów

poprzez ich poãoİenie, 63
za pomocĈ sãowa

kluczowego, 63

przeãĈczanie kontekstu, 142
przestrzeę nazw, 192

R

refaktoryzacja, 47

atrybutów, 109
klasy, 113
kodu, 76, 139

repozytorium PyPI, 186
rozszerzenie klasy, 97
równoczesne przetwarzanie

iteratorów, 39

równolegãe wykonywanie

metody, 137

równolegãoĤþ, 131, 158

S

sekwencja, 100
serializacja obiektów, 164
serializowane dane, 160, 169
skanowanie liniowane

danych, 222

sãownik, 74

__dict__, 118
_values, 116
dict, 179
domyĤlny, 180
JSON, 124
OrderedDict, 179
uporzĈdkowany, 179

specyfikacja PEP 8, 21
sprawdzanie typu, 217
stabilne

API, 191, 193, 195
Ĥcieİki importu, 174

staãa TESTING, 212
stan wyĤcigu, 140
sterta, 180
strefa czasowa

EDT, 176
PDT, 176

struktury danych, 178
styl

PEP 8, 21
Pythonic, 19

szyfrowanie, 134

ģ

Ĥcieİki importu, 173
Ĥrodowisko

produkcyjne, 211
programistyczne, 211
uruchomieniowe, 226
wirtualne, 204, 206, 209

T

tabela hash, 179
test, 217

integracji, 219
jednostkowy, 219

tworzenie

docstrine, 187
testów, 217
testów jednostkowych,

220

wĈtków roboczych, 149

typ

bytes, 23
namedtuple, 76–78
str, 23
unicode, 23, 26

typy niestandardowe, 99

U

unikanie równolegãoĤci, 136
UTC, Universal Coordinated

Time, 174, 177

UTF-8, 23, 32
uİycie

@property, 109
atrybutów prywatnych,

98, 99

concurrent.futures(), 158
konstrukcji try-finally,

166

metaklas, 124, 130
pamičci, 226
polecenia with, 167
uİycie wĈtków, 136

W

wartoĤþ

domyĤlna atrybutu, 171
False, 48
None, 47, 48, 67

wĈtek, 136

gãówny, 138
roboczy, 140

wersja Pythona, 20
wersjonowanie klas, 122, 172

Poleć książkę

Kup książkę

background image

232

Skorowidz

wãaĤciwoĤci chronione, 97, 99
wspóãbieİnoĤþ, 131
wspóãprogram, 150, 153–158
wstrzykiwanie zaleİnoĤci, 203
wyciek pamičci, 226
wyjĈtek, 49

AttributeError, 119, 201
Exception, 196
IndexError, 144
OverflowError, 69

StopIteration, 57
SyntaxError, 217
TypeError, 60, 72
ValueError, 45, 196
ZeroDivisionError, 69

wykonanie

kodu, 202
wielu funkcji, 150

wyraİenia, 22
wyraİenia generatorowe, 37
wyraİenie yield, 56, 150, 167

Z

zaczepy, 78
zakres, 53

globalny, 51
zmiennej, 49

zarzĈdzanie procesami

potomnymi, 132

znak

@, 164
zachčty, 220

znaki odstčpu, 21

Poleć książkę

Kup książkę

background image

Poleć książkę

Kup książkę

background image
background image

Wyszukiwarka

Podobne podstrony:
Efektywny Python 59 sposobow na lepszy kod
Efektywny Python 59 sposobow na lepszy kod 2
C++ 50 efektywnych sposobów na udoskonalenie Twoich programów
C++ 50 efektywnych sposobów na udoskonalenie Twoich programów
6 sposobów na to abyś miała jeszcze lepszy seks
Zen To Done Proste sposoby na zwiekszenie efektywnosci z
Zen To Done Proste sposoby na zwiekszenie efektywnosci zentod 3
Zen To Done Proste sposoby na zwiekszenie efektywnosci zentod
Zen To Done Proste sposoby na zwiekszenie efektywnosci zentod
LEPSZY SPOSÓB NA ZARABIANIE PIENIĘDZY
michalpasterski pl 10 sposobw na nieograniczon motywacj
3 dietetyczne sposoby na nadmierne pocenie się!
SUPER SPOSÓB NA ZARABIANIE, pliki zamawiane, edukacja

więcej podobnych podstron