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):
threading.Thread.__init__(self)
self.infile = infile
self.outfile = outfile
def run(self):
f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
f.write(self.infile)
f.close()
print('Zakończono zipowanie w tle pliku:', self.infile)
background = AsyncZip('mydata.txt', 'myarchive.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 o debugowaniuwarning')
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łączenie')
Daje to taki wyniki:
WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down
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) # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a # does not create a reference
>>> d['primary'] # fetch the object if it is still alive
10
>>> del a # remove the one reference
>>> gc.collect() # run garbage collection right away
0
>>> d['primary'] # entry was automatically removed
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
d['primary'] # entry was automatically removed
File "C:/python312/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')