11. Krótka wycieczka po bibliotece standardowej --- część II
************************************************************

Ta druga wycieczka obejmuje bardziej zaawansowane moduł, które
wspierają profesjonalne potrzeby programistyczne.  Te moduł rzadko
występują w małych skryptach.


11.1. Formatowanie wyjścia
==========================

"reprlib" moduł zapewnia wersja z "repr()" dostosowany do skróconego
wyświetlania dużych lub głęboko zagnieżdżonych kontenerów:

   >>> import reprlib
   >>> reprlib.repr(set('supercalifragilisticexpialidocious'))
   "{'a', 'c', 'd', 'e', 'f', 'g', ...}"

Moduł "pprint" oferuje bardziej wyrafinowaną kontrolę nad drukowaniem
zarówno wbudowanej, jak i obiektów zdefiniowanych przez użytkownika w
sposób czytelny dla interpretera. Gdy wynik jest dłuższy niż jedna
linia, "ładna drukarka" dodaje podziały linii i wcięcia, aby wyraźniej
ujawnić strukturę danych:

   >>> import pprint
   >>> t = [[[['czarny', 'cyjan'], 'biały', ['zielony', 'czerwony']], [['magenta',
   ...     'żółty'], 'niebieski']]]
   ...
   >>> pprint.pprint(t, width=30)
   [[[['czarny', 'cyjan'],
      'biały',
      ['zielony', 'czerwony']],
     [['magenta', 'żółty'],
      'niebieski']]]

Moduł "textwrap" formatuje akapity tekstu tak, aby pasowały do danej
szerokości ekranu:

   >>> import textwrap
   >>> doc = """Metoda wrap() jest taka sama jak fill() z tą różnicą, że zwracać
   ... listę napisow zamiast jednego dużego napisu z nowymi liniami do oddzielenia
   ... zawinięte linie."""
   ...
   >>> print(textwrap.fill(doc, width=40))
   Metoda wrap() jest taka sama jak fill()
   z tą różnicą, że zwracać listę napis
   zamiast jednego dużego napisu z nowymi liniami
   aby oddzielić zawinięte linie.

"locale" moduł uzyskuje dostęp do bazy danych formatów danych
specyficznych dla danej kultury. Grupowanie atrybut formatu locale
funkcja zapewnia bezpośredni sposób formatowania liczb za pomocą
separatorów grup:

   >>> import locale
   >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
   'English_United States.1252'
   >>> conv = locale.localeconv() # uzyskać mapowanie konwencji
   >>> x = 1234567.8
   >>> locale.format_string("%d", x, grouping=True)
   '1,234,567'
   >>> locale.format_string("%s%.*f", (conv['currency_symbol'],
   ... conv['frac_digits'], x), grouping=True)
   '$1,234,567.80'


11.2. Szablonowanie
===================

Moduł "string" zawiera wszechstronną klasę "Template" z uproszczoną
składnią odpowiednią do edycji przez użytkowników końcowych.  Pozwala
to użytkownikom na dostosowanie ich aplikacja bez konieczności zmiany
aplikacja.

Format wykorzystuje nazwy placeholder utworzone przez "$" z ważnymi
identyfikatorami Python (alfanumeryczne znak i podkreślenia).
Otoczenie placeholder nawiasami klamrowymi pozwala na umieszczenie po
nim kolejnych liter alfanumerycznych bez spacji.  Napisanie "$$"
tworzy pojedynczy "$":

   >>> from string import Template
   >>> t = Template('${village}anie wysyłają $$10 na $cause.')
   >>> t.substitute(village='Nottingham', cause='fundusz rowu')
   'Nottinghamanie wysyłają $10 na fundusz rowu.'

Metoda "substitute()" rzuci "KeyError", gdy placeholder nie jest
podany w słowniku lub argument nazwany.  W przypadku mail-merge
aplikacjach, dane dostarczone przez użytkownika mogą być niekompletne,
a metoda "safe_substitute()" może być bardziej odpowiednia ---
pozostawi placeholder bez zmian, jeśli brakuje danych:

   >>> t = Template('Zwróć $item do $owner.')
   >>> d = dict(item='nieobciążoną jaskółkę')
   >>> t.substitute(d)
   Traceback (most recent call last):
   ...
   KeyError: 'owner'
   >>> t.safe_substitute(d)
   'Zwróć nieobciążoną jaskółkę do $owner.'

Szablon podklas mogą określać niestandardowy separator.  Na przykład,
narzędzie do wsadowej zmiany nazw dla przeglądarki zdjęć może
zdecydować się na użycie znaków procentowych jako placeholder, takich
jak bieżące dane, numer sekwencyjny obrazu lub format pliku:

   >>> import time, os.path
   >>> pliki_zdjecia = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
   >>> class ZmienNazwePlikow(Template):
   ... delimiter = '%'
   ...
   >>> format_nazwy = input('Podaj styl zmiany nazwy (-data -numer -format): ')
   Podaj styl zmiany nazwy (-data -numer -format): Ashley_

   >>> zmiana_nazwy = ZmienNazwePlikow(format_nazwy)
   >>> data = time.strftime('%b%y')
   >>> for i, nazwa_pliku in enumerate(pliki_zdjecia):
   ... baza, rozszerzenie = os.path.splitext(nazwa_pliku)
   ... nowa_nazwa = zmiana_nazwy.substitute(d=data, n=i, f=rozszerzenie)
   ... print('{0} --> {1}'.format(nazwa_pliku, nowa_nazwa))
   ...
   img_1074.jpg --> Ashley_0.jpg
   img_1076.jpg --> Ashley_1.jpg
   img_1077.jpg --> Ashley_2.jpg

Innym aplikacja dla szablonów jest oddzielenie logiki programu od
szczegółów wielu formatów wyjściowych.  Umożliwia to zastąpienie
niestandardowych szablonów plikami XML, raportami tekstowymi i
raportami internetowymi HTML.


11.3. Praca z układami rekordów danych binarnych
================================================

Moduł "struct" udostępnia "pack()" i "unpack()" funkcje do pracy z
formatami rekordów binarnych o zmiennej długości.  Poniższy przykład
pokazuje, jak zapętlić informacje nagłówka w pliku ZIP bez użycia
moduł "zipfile"  .  Kody pakietów ""H"" i ""I"" reprezentują
odpowiednio dwu- i czterobajtowe liczby bez znaku.  Oznaczenie ""<""
wskazuje, że mają one standardowy rozmiar i kolejność bajtów little-
endian:

   import struct

   with open('myfile.zip', 'rb') as f:
       data = f.read()

   start = 0
   for i in range(3):                      # pokaż pierwsze 3 nagłówki pliku
       start += 14
       fields = struct.unpack('<IIIHH', data[start:start+16])
       crc32, comp_size, uncomp_size, filenamesize, extra_size = fields

       start += 16
       filename = data[start:start+filenamesize]
       start += filenamesize
       extra = data[start:start+extra_size]
       print(filename, hex(crc32), comp_size, uncomp_size)

       start += extra_size + comp_size # przeskocz do następnego nagłówka


11.4. Wielowątkowość
====================

Wątkowanie jest techniką oddzielania zadań, które nie są sekwencyjnie
zależne. Wątek może być używany do poprawy szybkości reakcji
aplikacji, które akceptują dane wejściowe użytkownika, podczas gdy
inne zadania działają w tle.  Powiązanym przypadkiem użycia jest
równoległe wykonywanie operacji wejścia/wyjścia z obliczeniami w innym
wątku.

Poniższy kod pokazuje, w jaki sposób wysokopoziomowy "threading" moduł
może uruchamiać zadania w tle, podczas gdy główny program nadal
działa:

   import threading, zipfile

   class AsyncZip(threading.Thread):
       def __init__(self, infile, outfile):
           super().__init__()
           self.infile = infile
           self.outfile = outfile

       def run(self):
           with zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) as f:
               f.write(self.infile)
           print('Zakończono zipowanie w tle pliku:', self.infile)

   background = AsyncZip('mojedane.txt', 'mojearchiwum.zip')
   background.start()
   print('Główny program nadal działa.')

   background.join() # Poczekaj na zakończenie zadania w tle
   print('Główny program czekał, aż tło zostanie wykonane.')

Głównym wyzwaniem wielowątkowej aplikacji jest koordynacja wątkow,
które współdzielą dane lub inne zasoby.  W tym celu wątkowanie moduł
zapewnia szereg prymitywów synchronizacji, w tym blokady, zdarzenia,
zmienne warunkowe i semafory.

Chociaż narzędzia te są potężne, drobne błędy projektowe mogą
skutkować trudnymi do odtworzenia problemami.  Dlatego preferowanym
podejściem do koordynacji zadań jest skoncentrowanie całego dostępu do
zasobu w jednym wąteku, a następnie wykorzystanie moduł "queue" do
zasilania tego wąteka żądaniami z innych wątekow. Aplikacja
wykorzystuje obiekty "Queue" do komunikacji i koordynacji
międzywątekowych są łatwiejsze do zaprojektowania, bardziej czytelne i
niezawodne.


11.5. Logowanie
===============

Moduł "logging" oferuje w pełni funkcjonalny i elastyczny system
logowania. W najprostszym przypadku komunikaty dziennika są wysyłane
do pliku lub na adres "sys.stderr":

   import logging
   logging.debug('Informacje do debugowania')
   logging.info('Komunikat informacyjny')
   logging.warning('Ostrzeżenie: nie znaleziono pliku konfiguracyjnego %s', 'server.conf')
   logging.error('Wystąpił błąd')
   logging.critical('Błąd krytyczny -- wyłączanie')

Daje to taki wyniki:

   WARNING:root:Ostrzeżenie: nie znaleziono pliku konfiguracyjnego server.conf
   ERROR:root:Wystąpił błąd
   CRITICAL:root:Błąd krytyczny -- wyłączanie

Domyślnie, komunikaty informacyjne i debugowanie są tłumione, a dane
wyjściowe są wysyłane do standardowego błędu.  Inne opcje wyjściowe
obejmują kierowanie wiadomości przez e-mail, datagramy, gniazda lub do
serwera HTTP.  Nowe filtry mogą wybierać różne trasy na podstawie
priorytetu wiadomości: "DEBUG", "INFO", "WARNING", "ERROR" i
"CRITICAL".

System logowania może być skonfigurowany bezpośrednio z poziomu
Pythona lub może być załadowany z edytowalnego przez użytkownika pliku
konfiguracyjnego w celu dostosowania rejestrowania bez zmiany
aplikacji.


11.6. Wsparcie słabych odniesień
================================

Python wykonuje automatyczne zarządzanie pamięcią (liczenie referencji
dla większości obiektów i *garbage collection* w celu wyeliminowania
cykli).  Pamięć jest zwalniana wkrótce po usunięciu ostatniego
odniesienia do niej.

Takie podejście sprawdza się w większości przypadków aplikacji, ale
czasami istnieje potrzeba śledzenia obiektów tylko tak długo, jak są
one używane przez coś innego. Niestety, samo ich śledzenie tworzy
odniesienie, które czyni je trwałymi. "weakref" moduł zapewnia
narzędzia do śledzenia obiektów bez tworzenia odniesienia.  Gdy obiekt
nie jest już potrzebny, jest automatycznie usuwany z tabeli weakref i
uruchamiane jest wywołanie zwrotne dla obiektów weakref.  Typowe
aplikacja obejmują buforowanie obiektów, których utworzenie jest
kosztowne:

   >>> import weakref, gc
   >>> class A:
   ... def __init__(self, value):
   ... self.value = value
   ... def __repr__(self):
   ... return str(self.value)
   ...
   >>> a = A(10) # utwórz odniesienie
   >>> d = weakref.WeakValueDictionary()
   >>> d['primary'] = a # nie tworzy odniesienia
   >>> d['primary'] # pobierz obiekt, jeśli nadal istnieje
   10
   >>> del a # usuń jedyne odniesienie
   >>> gc.collect() # wymuś natychmiastowe uruchomienie garbage collection
   0
   >>> d['primary'] # wpis został automatycznie usunięty
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       d['primary'] # wpis został automatycznie usunięty
     File "C:/python314/lib/weakref.py", line 46, in __getitem__
       o = self.data[key]()
   KeyError: 'primary'


11.7. Narzędzia do pracy z listami
==================================

Wiele potrzeb związanych ze strukturami danych można zaspokoić za
pomocą listy wbudowanej. Czasami jednak istnieje potrzeba
alternatywnych implementacji z różnymi kompromisami w zakresie
wydajności.

Moduł "array" udostępnia obiekt "array", który jest jak lista,
przechowuje tylko jednorodne dane i przechowuje je w bardziej
kompaktowy sposób.  Poniższy przykład pokazuje tablica liczb
przechowywanych jako dwubajtowe liczby binarne bez znaku (kod typowy
""H"") zamiast zwykłych 16 bajtów na wpis dla zwykłych list obiektów
Python int:

   >>> from array import array
   >>> a = array('H', [4000, 10, 700, 22222])
   >>> sum(a)
   26932
   >>> a[1:3]
   array('H', [10, 700])

Moduł "collections" udostępnia obiekt "deque", który jest jak lista z
szybszym dołączaniem i wyskakiwaniem z lewej strony, ale wolniejszym
wyszukiwaniem w środku. Obiekty te dobrze nadają się do implementacji
kolejek i przeszukiwania drzewa w pierwszej kolejności:

   >>> from collections import deque
   >>> d = deque(["zadanie1", "zadanie2", "zadanie3"])
   >>> d.append("zadanie4")
   >>> print("Obsługiwanie", d.popleft())
   Obsługiwanie zadanie1

   unsearched = deque([starting_node])
   def breadth_first_search(unsearched):
       node = unsearched.popleft()
       for m in gen_moves(node):
           if is_goal(m):
               return m
           unsearched.append(m)

Oprócz alternatywnych implementacji list, biblioteka oferuje również
inne narzędzia, takie jak "bisect" moduł z funkcja do manipulowania
posortowanymi listami:

   >>> import bisect
   >>> wyniki = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
   >>> bisect.insort(wyniki, (300, 'ruby'))
   >>> wyniki
   [(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

Moduł "heapq" udostępnia funkcje do implementacji stert opartych na
listach regularnych.  Wpis o najniższej wartości jest zawsze
przechowywany na pozycji zero.  Jest to przydatne dla aplikacji, które
wielokrotnie uzyskują dostęp do najmniejszego elementu, ale nie chcą
uruchamiać pełnego sortowania listy:

   >>> from heapq import heapify, heappop, heappush
   >>> dane = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
   >>> heapify(dane) # uporządkuj listę w kolejności na stercie
   >>> heappush(dane, -5) # dodanie nowego wpisu
   >>> [heappop(dane) for i in range(3)] # pobranie trzech najmniejszych wpisów
   [-5, 0, 1]


11.8. Dziesiętna arytmetyka zmiennoprzecinkowa
==============================================

Klasa moduł "decimal" oferuje typ danych "Decimal" dla dziesiętnej
arytmetyki zmiennoprzecinkowej.  W porównaniu do wbudowanej "float"
implementacji binarnej liczby zmiennoprzecinkowej, klasa ta jest
szczególnie pomocna dla

* finansowe aplikacja i inne zastosowania wymagające dokładnej
  reprezentacji dziesiętnej,

* kontrola nad precyzją,

* kontrolę nad zaokrągleniami w celu spełnienia wymogów prawnych lub
  regulacyjnych,

* śledzenie znaczących miejsc po przecinku, lub

* aplikacja gdzie użytkownik oczekuje, że wyniki będą zgodne z
  obliczeniami wykonanymi ręcznie.

Na przykład, obliczenie 5% podatku od 70 centów opłaty telefonicznej
daje różne wyniki w dziesiętnej zmiennoprzecinkowej i binarnej
zmiennoprzecinkowej. Różnica staje się znacząca, jeśli wyniki zostaną
zaokrąglone do najbliższego centa:

   >>> from decimal import *
   >>> round(Decimal('0.70') * Decimal('1.05'), 2)
   Decimal('0.74')
   >>> round(.70 * 1.05, 2)
   0.73

Wynik "Decimal" zachowuje końcowe zero, automatycznie wywnioskowując
znaczenie czterech miejsc z mnożników o znaczeniu dwóch miejsc.
Decimal odtwarza matematykę wykonaną ręcznie i unika problemów, które
mogą powstać, gdy binarna liczba zmiennoprzecinkowa nie może dokładnie
reprezentować wielkości dziesiętnych.

Dokładna reprezentacja umożliwia klasie "Decimal" wykonywanie obliczeń
modulo i testów równości, które nie są odpowiednie dla binarnych liczb
zmiennoprzecinkowych:

   >>> Decimal('1.00') % Decimal('.10')
   Decimal('0.00')
   >>> 1.00 % 0.10
   0.09999999999999995

   >>> sum([Decimal('0.1')]*10) == Decimal('1.0')
   True
   >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
   False

Moduł "decimal" zapewnia arytmetykę z taką precyzją, jaka jest
potrzebna:

   >>> getcontext().prec = 36
   >>> Decimal(1) / Decimal(7)
   Decimal('0.142857142857142857142857142857142857')
