100776900

Page 1


SZCZEGÓ Ł OWY SPIS TRE Ś

CI

PODZIĘKOWANIA

WPROWADZENIE

Kto i dlaczego powinien przeczytać tę książkę

O tej książce

ROZPOCZYNANIE PROJEKTU

Wersje Pythona

Układ projektu

Czego nie robić

Numerowanie wersji

Style programowania i automatyczne sprawdzanie

Narzędzia

Niestandardowe importery

Obiekty wyszukujące z listy

Przydatne standardowe biblioteki

Zewnętrzne biblioteki

Lista wymagań, jakie powinna spełniać bezpieczna biblioteka zewnętrzna

Chronienie kodu przy użyciu opakowania API

Instalacja pakietu: dodatkowe możliwości narzędzia pip

Stosowanie i wybieranie platform

Doug Hellmann, Python Core Developer, o bibliotekach Pythona

1

Rozpoczęcie pracy ze Sphinksem i reST

Pisanie rozszerzeń Sphinksa

ądzanie zmianami interfejsów API

Numerowanie wersji API

Dokumentowanie zmian

Oznaczanie przestarzałych funkcji przy użyciu modułu warnings

Podsumowanie

Christophe de Vienne o rozwijaniu interfejsów API

4

OBSŁUGA ZNACZNIKÓW CZASU I STREF CZASOWYCH

Problem brakujących stref czasowych

Budowanie domyślnych obiektów datetime

Rozpoznawanie stref czasowych przy użyciu dateutil

Serializowanie obiektów datetime rozpoznających strefę czasową

Rozwiązywanie problemu niejednoznacznych godzin

Podsumowanie

5

Krótka historia plików setup.py

Zarządzanie pakietami przy użyciu setup.cfg

Standardowy format dystrybucji wheel

Dzielenie się swoją pracą ze światem

Punkty wejścia

punktów wejścia

skryptów konsoli

Wykorzystywanie wtyczek i sterowników

Nick Coghlan o zarządzaniu pakietami

Podstawy testowania

Pomijanie testów

Uruchamianie wybranych testów

Równoległe uruchamianie testów

83

85

Tworzenie obiektów wykorzystywanych w testach przy użyciu fikstur . . 86

Uruchamianie scenariuszy testowych

Kontrolowane testy bazujące na atrapach obiektów

Odkrywanie nieprzetestowanego kodu przy użyciu coverage .

Środowiska wirtualne . . . . .

Konfigurowanie środowiska wirtualnego

Stosowanie virtualenv wraz z narzędziem tox

Odtwarzanie środowiska

87

88

93

95

96

98

Stosowanie różnych wersji Pythona

Integrowanie innych testów

Zasady dotyczące testowania

Robert Collins o testowaniu

7

METODY I DEKORATORY

Dekoratory i kiedy ich używać

Tworzenie dekoratorów

Pisanie dekoratorów

Stosowanie wielu dekoratorów

Pisanie dekoratorów klas

W jaki sposób działają metody w Pythonie

Metody statyczne

Metody klasy

Metody abstrakcyjne

Mieszanie metod statycznych, klasy i abstrakcyjnych

Umieszczanie implementacji w metodach abstrakcyjnych

Prawda o metodzie super

Podsumowanie

8

PROGRAMOWANIE FUNKCYJNE

Tworzenie czystych funkcji

Tworzenie generatora

Zwracanie i przekazywanie wartości przy użyciu instrukcji yield

Inspekcje generatorów

Wyrażenia listowe

Funkcjonowanie funkcji funkcyjnych

Stosowanie funkcji na elementach przy użyciu funkcji map()

Filtrowanie list przy użyciu funkcji filter()

Pobieranie indeksów przy użyciu funkcji enumerate()

Sortowanie listy przy użyciu funkcji sorted()

Wyszukiwanie przy użyciu any() i all() elementów spe

niających warunki

Łączenie list przy użyciu funkcji zip()

Rozwiązania typowych problemów

Przydatne funkcje itertools

Pisanie

Przechodzenie

Rozszerzanie narzędzia flake8 o analizy AST

Pisanie klasy

Pomijanie nieistotnego kodu

Sprawdzanie, czy istnieje odpowiedni dekorator

Poszukiwanie argumentu self

Szybkie wprowadzenie do Hy

Podsumowanie

Paul Tagliamonte o AST i Hy

10

WYDAJNO

ŚĆ I OPTYMALIZACJE

Struktury danych

Zrozumienie działania programu przez profilowanie

Dezasemblowanie przy użyciu modułu dis

Efektywne definiowanie funkcji

Listy uporządkowane i bisect

namedtuple i __slots__

Memoizacja .

Szybszy Python dzięki PyPy

Eliminacja kopiowania przy użyciu protokołu bufora

Podsumowanie

Victor Stinner o optymalizacji

SKALOWALNOŚĆ I ARCHITEKTURA

Wielowątkowość w Pythonie i jej ograniczenia

Przetwarzanie wieloprocesowe kontra wielowątkowe

Architektura sterowana zdarzeniami

Inne opcje i asyncio

Architektura zorientowana na usługi

Komunikacja między procesami przy użyciu ZeroMQ

Podsumowanie

ZARZĄDZANIE

Rozwiązania RDBMS, ORM i kiedy ich używa

Wewnętrzne bazy danych

Strumieniowe przesyłanie danych przy użyciu Flask i PostgreSQL

Pisanie aplikacji do strumieniowego przesyłania danych

Budowanie aplikacji

Dimitri Fontaine on Databases

Użycie modułu six do wspierania wersji Python 2 i 3

Ciągi a unicode

Rozwiązywanie problemu przeniesienia modułów Pythona

Moduł modernize

Wykorzystywanie Pythona jak języka Lisp do tworzenia jednego dyspozytora

Tworzenie metod generycznych w języku Lisp

Metody generyczne w Pythonie

Menedżery kontekstu

Redukowanie szablonowego kodu przy użyciu attr

Podsumowanie

INDEKS

Szczegó

pe nego potencja u programowania funkcyjnego, gdy ma si jedynie do wiadczenie w imperatywnym programowaniu obiektowym. Lisp nie jest czysto funkcyjny, ale k adzie wi kszy nacisk na programowanie funkcyjne ni j zyk Python.

Tworzenie czystych funkcji

Gdy piszemy kod w stylu funkcyjnym, nasze funkcje s zaprojektowane tak, aby nie mia y adnych efektów ubocznych. Powinny jedynie pobiera dane wej ciowe i zwraca wynik bez zachowywania stanu b d mody kowania czegokolwiek, co nie jest odzwierciedlone w warto ci zwrotnej. Funkcje zgodne z t koncepcj nazywane s czysto funkcyjnymi. Zacznijmy od przyk adu zwyk ej funkcji, która nie jest czysto funkcyjna i usuwa ostatni element z listy:

def remove_last_item(mylist): """Usuwa ostatni element z listy.""" mylist.pop(-1) # Modyfikuje mylist

A oto czysta wersja tej samej funkcji:

def butlast(mylist):

return mylist[:-1] # Zwraca kopi mylist

De niujemy funkcj butlast() w taki sposób, aby dzia a a ona jak butlast w Lispie, zwracaj c list bez ostatniego elementu, nie mody kuj c oryginalnej listy. Zamiast tego zwraca kopi listy, w której wprowadzone zosta y zmiany, umo liwiaj c zachowanie orygina u.

Praktyczne zalety programowania funkcyjnego to m.in.:

Modu owo Funkcyjny styl pisania kodu zmusza do wprowadzenia pewnego rozdzia u podczas rozwi zywania poszczególnych problemów i u atwia ponowne wykorzystywanie fragmentów kodu w innym kontek cie. Poniewa funkcja nie zale y od adnej zewn trznej zmiennej ani stanu, wywo anie jej z innego miejsca w kodzie jest bardzo proste. Zwi z o Programowanie funkcyjne jest zwykle mniej rozwlek e ni inne paradygmaty programowania.

Wspó bie no Czysto funkcyjne funkcje s bezpieczne w tkowo i mog by uruchamiane równolegle. Niektóre j zyki funkcyjne robi to w sposób automatyczny, co mo e by bardzo pomocne w przysz o ci, gdy pojawia si potrzeba skalowania aplikacji. Python nie oferuje jeszcze takiej mo liwo ci.

Testowalno Testowanie programu funkcyjnego jest niesamowicie proste: wystarczy zestaw danych wej ciowych i zestaw oczekiwanych

wyników. Funkcje s idempotentne, co oznacza, e ka de z wielu wywo a tej samej funkcji z tymi samymi argumentami zwróci zawsze ten sam wynik.

Generatory

Generator jest obiektem, który zachowuje si jak iterator pod tym wzgldem, e generuje i zwraca warto w ka dym wywo aniu metody next(), do momentu zg oszenia wyj tku StopIteration. Generatory, wprowadzone po raz pierwszy w PEP 255, oferuj prosty sposób tworzenia obiektów, które implementuj protokó iteratora. Cho zasadniczo generatorów nie trzeba implementowa w stylu funkcyjnym, u atwia to ich pisanie oraz debugowanie i jest powszechn praktyk .

Aby utworzy generator, wystarczy napisa zwyk funkcj Pythona, która zawiera instrukcj yield . Python wykryje u ycie instrukcji yield i oznaczy funkcj jako generator. Gdy nadchodzi czas wykonania instrukcji yield , funkcja zwraca warto jak w przypadku u ycia instrukcji return , z jedn istotn ró nic : interpreter zapisze odwo anie do stosu, które zostanie wykorzystane do wznowienia wykonania funkcji, gdy funkcja next() zostanie ponownie wywo ana.

Gdy funkcje s wykonywane, a cuch ich wykona tworzy stos – mówi si , e wywo ania funkcji s umieszczane na stosie. Gdy funkcja zwraca warto , zostaje usuni ta ze stosu i zwracana przez ni warto zostaje przekazana do funkcji wywo uj cej. W przypadku generatora funkcja tak naprawd nie zwraca warto ci, a jedynie j przekazuje (yield). Dlatego Python zapisuje stan funkcji w postaci odwo ania do stosu, wznawiaj c wykonanie generatora od zapisanego miejsca, gdy potrzebna jest nast pna iteracja generatora.

Tworzenie generatora

Jak wspomnieli my, generator tworzymy, pisz c normaln funkcj i umieszczaj c w niej instrukcj yield. Listing 8.1 s u y do utworzenia generatora o nazwie mygenerator(), który zawiera trzy instrukcje yield, co oznacza, e dokona iteracji podczas trzech kolejnych wywo a funkcji next()

>> def mygenerator():

... yield 1

... yield 2

yield 'a' ...

>>> mygenerator()

<generator object mygenerator at 0x10d77fa50>

>>> g = mygenerator()

>>> next(g)

1

wolniej lub szybciej. Zamiast spekulować, pomożemy zrozumieć, jak profilować aplikację, aby wiedzieć, która część programu spowalnia jego działanie i gdzie znajdują się wąskie gardła.

Struktury danych

Większość problemów programistycznych można rozwiązać w elegancki i prosty sposób przy użyciu odpowiednich struktur danych. Python oferuje wiele różnych struktur danych i umiejętność właściwego ich wykorzystywania pozwala na uzyskiwanie bardziej czytelnego i stabilnego rozwiązania niż programowanie własnych struktur danych. Na przykład wszyscy używają dict, ale ile razy widziałeś kod, który próbuje uzyskać dostęp do słownika, przechwytując wyjątek KeyError, jak poniżej:

def get_fruits(basket, fruit): try: return basket[fruit] except KeyError: return None

lub sprawdzając najpierw, czy klucz istnieje:

def get_fruits(basket, fruit): if fruit in basket: return basket[fruit]

Je ś li u ż yjemy metody get() dost ę pnej w klasie dict , to unikamy konieczności przechwytywania wyjątku bądź wstępnego sprawdzania, czy klucz istnieje:

def get_fruits(basket, fruit): return basket.get(fruit)

Metoda dict.get() może również zwracać domyślną wartość zamiast None – wystarczy dodać do jej wywołania drugi argument:

def get_fruits(basket, fruit): # Zwraca fruit lub Banana, jeśli nie znaleziono obiektu fruit. return basket.get(fruit, Banana())

Wielu programistów ma na sumieniu stosowanie podstawowych struktur danych Pythona bez poznania wszystkich oferowanych przez nie metod. Odnosi się to również do zbiorów: dostępne w nich metody mogą rozwiązywać wiele problemów, które w przeciwnym razie wymagałyby napisania zagnieżdżonych bloków for / if . Na przykład programiści często używają

pętli for/if do sprawdzania, czy istnieją elementy nienależące do listy, jak w tym przykładzie:

def has_invalid_fields(fields): for field in fields: if field not in ['foo', 'bar']: return True return False

Pętla przechodzi po wszystkich elementach listy i sprawdza, czy wszystkie z nich są równe foo albo bar. Jednak można osiągnąć ten cel w bardziej efektywny sposób, bez stosowania pętli:

def has_invalid_fields(fields): return bool(set(fields) - set(['foo', 'bar']))

Ten zmieniony kod przekształca fields na zbiór i wyznacza resztę zbioru, odejmując od niego set(['foo', 'bar']). Następnie przekształca zbiór na wartość logiczną, aby sprawdzić, czy pozostały jakieś elementy, które nie są równe ani foo, ani bar. Zastosowanie zbiorów eliminuje konieczność iteracji po liście i sprawdzania pojedynczych elementów. Prosta operacja na dwóch zbiorach, przeprowadzona wewnętrznie przez Pythona, jest szybsza. Python oferuje również bardziej zaawansowane struktury danych, które mog ą znacznie u ł atwia ć utrzymywanie kodu. Przeanalizujmy przyk ł ad przedstawiony na listingu 10.1.

def add_animal_in_family(species, animal, family): if family not in species: species[family] = set() species[family].add(animal)

species = {}

add_animal_in_family(species, 'cat', 'felidea')

Listing 10.1. Dodawanie wpisu do słownika zbiorów

Ten kod jest całkiem poprawny, ale ile razy będziemy potrzebować w naszym programie odmiany kodu z listingu 10.1? Dziesiątki? Setki? Python oferuje strukturę collections.defaultdict, która w elegancki sposób rozwiązuje ten problem:

import collections

def add_animal_in_family(species, animal, family): species[family].add(animal)

species = collections.defaultdict(set) add_animal_in_family(species, 'cat', 'felidea')

Memoizacja

Memoizacja to technika optymalizacji służąca do przyspieszania wywołań funkcji przez przechowywanie ich wyników w pamięci podręcznej. Wynik funkcji mo ż e zosta ć przechowany tylko wtedy, gdy funkcja jest czysta , czyli nie ma żadnych skutków ubocznych i nie zależy od żadnego globalnego stanu (dodatkowe informacje o funkcjach czystych znaleźć można w rozdziale 8).

Prosty przykład funkcji, którą można poddać memoizacji, stanowi funkcja sin(), przedstawiona na listingu 10.17.

>>> import math

>>> _SIN_MEMOIZED_VALUES = {}

>>> def memoized_sin(x):

... if x not in _SIN_MEMOIZED_VALUES:

... _SIN_MEMOIZED_VALUES[x] = math.sin(x)

return _SIN_MEMOIZED_VALUES[x]

>>> memoized_sin(1)

0.8414709848078965

>>> _SIN_MEMOIZED_VALUES

{1: 0.8414709848078965}

>>> memoized_sin(2)

0.9092974268256817

>>> memoized_sin(2)

0.9092974268256817

>>> _SIN_MEMOIZED_VALUES

{1: 0.8414709848078965, 2: 0.9092974268256817}

>>> memoized_sin(1)

0.8414709848078965

>>> _SIN_MEMOIZED_VALUES

{1: 0.8414709848078965, 2: 0.9092974268256817}

Listing 10.17. Funkcja sin() poddana memoizacji

Na listingu 10.17, gdy funkcja memoized_sin() jest wywoływana po raz pierwszy z argumentem, który nie znajduje się w słowniku _SIN_MEMOIZED_ VALUES, jej wartość jest wyliczana i przechowywana w tym słowniku. Jeśli ponownie wywołamy funkcję z tą samą wartością, to wynik zostanie pobrany ze słownika, zamiast wyliczony. Wartość funkcji sin() jest wyliczana bardzo szybko, jednak niektóre zaawansowane funkcje wymagające przeprowadzenia skomplikowanych obliczeń są bardziej czasochłonne i to w takiej sytuacji memoizacja przynosi najlepsze efekty.

Jeśli czytałeś już o dekoratorach (w przeciwnym razie zajrzyj do podrozdziału „Dekoratory i kiedy ich używać” na stronie 108), to możesz zauważyć, że jest to doskonała okazja do ich zastosowania. PyPI zawiera kilka implementacji memoizacji bazujących na dekoratorach, od bardzo prostych przypadków po bardzo złożone i kompleksowe.

Począwszy od wersji Python 3.3, moduł functools zawiera dekorator pamięci podręcznej LRU (least recently used, najdawniej użyty). Oferuje on tak ą sam ą funkcjonalno ść jak memoizacja, a dodatkowo ogranicza liczbę pozycji w pamięci podręcznej, usuwając najdawniej użyty element, gdy pamięć podręczna osiągnęła maksymalny rozmiar. Ten moduł oferuje również dane statystyczne m.in. liczby trafień i chybień w pamięci podręcznej (czy wartość była w niej dostępna, czy też nie). Moim zdaniem te dane statystyczne są niezbędne do implementowania tego typu pamięci podręcznej. Siła memoizacji, jak i każdej innej techniki przechowywania w pamięci podręcznej, leży w możliwości mierzenia jej użycia i użyteczności.

Listing 10.18 demonstruje, jak można wykorzystać metodę functools. lru_cache() do zaimplementowania memoizacji funkcji. Po udekorowaniu funkcja uzyskuje metodę cache_info(), którą można wywołać w celu sprawdzenia statystyk użycia pamięci podręcznej.

>>> import functools

>>> import math

>>> @functools.lru_cache(maxsize=2)

... def memoized_sin(x):

... return math.sin(x)

...

>>> memoized_sin(2)

0.9092974268256817

>>> memoized_sin.cache_info()

CacheInfo(hits=0, misses=1, maxsize=2, currsize=1)

>>> memoized_sin(2)

0.9092974268256817

>>> memoized_sin.cache_info()

CacheInfo(hits=1, misses=1, maxsize=2, currsize=1)

>>> memoized_sin(3)

0.1411200080598672

>>> memoized_sin.cache_info()

CacheInfo(hits=1, misses=2, maxsize=2, currsize=2)

>>> memoized_sin(4)

-0.7568024953079282

>>> memoized_sin.cache_info()

CacheInfo(hits=1, misses=3, maxsize=2, currsize=2)

>>> memoized_sin(3)

0.1411200080598672

>>> memoized_sin.cache_info()

CacheInfo(hits=2, misses=3, maxsize=2, currsize=2)

>>> memoized_sin.cache_clear()

>>> memoized_sin.cache_info()

CacheInfo(hits=0, misses=0, maxsize=2, currsize=0)

Listing 10.18. Sprawdzanie statystyk użycia pamięci podręcznej

Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.