11. Stručná prohlídka standardní knihovny — část II¶
Tato druhá prohlídka se věnuje pokročilejším modulům, které podporují potřeby profesionálního programování. V malých skriptech se tyto moduly objevují zřídka.
11.1. Formátování výstupu¶
Modul reprlib poskytuje variantu funkce repr() přizpůsobenou pro
zkrácené zobrazení velkých nebo hluboce vnořených kontejnerů:
>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"
Modul pprint nabízí propracovanější kontrolu nad výpisem vestavěných i
uživatelsky definovaných objektů ve formě čitelné interpretem. Pokud je výsledek
delší než jeden řádek, „úhledný výpis“ přidá zalomení řádků a odsazení, aby byla
struktura dat zřetelnější:
>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
... 'yellow'], 'blue']]]
...
>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
'white',
['green', 'red']],
[['magenta', 'yellow'],
'blue']]]
Modul textwrap formátuje odstavce textu tak, aby se vešly do zadané šířky
obrazovky:
>>> import textwrap
>>> doc = """The wrap() method is just like fill() except that it returns
... a list of strings instead of one big string with newlines to separate
... the wrapped lines."""
...
>>> print(textwrap.fill(doc, width=40))
The wrap() method is just like fill()
except that it returns a list of strings
instead of one big string with newlines
to separate the wrapped lines.
Modul locale zpřístupňuje databázi formátů dat specifických pro jednotlivé
kultury. Atribut grouping formátovací funkce tohoto modulu umožňuje přímo
formátovat čísla s oddělovači skupin číslic:
>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'
>>> conv = locale.localeconv() # get a mapping of conventions
>>> 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. Používání šablon¶
Modul string obsahuje všestrannou třídu Template se
zjednodušenou syntaxí vhodnou pro úpravy koncovými uživateli. Uživatelé tak
mohou aplikaci přizpůsobit, aniž by museli měnit její kód.
Formát používá názvy zástupných symbolů tvořené znakem $ a platným
identifikátorem Pythonu (alfanumerickými znaky a podtržítky). Uzavření zástupného
symbolu do složených závorek umožňuje, aby za ním bez mezery následovaly další
alfanumerické znaky. Zápis $$ vytvoří jeden escapovaný znak $:
>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'
Metoda substitute() vyvolá KeyError, pokud
zástupný symbol nebyl dodán ve slovníku ani jako argument klíčového slova. V
aplikacích podobných hromadné korespondenci mohou být uživatelská data neúplná,
a proto může být vhodnější metoda safe_substitute() —
při chybějících datech ponechá zástupné symboly beze změny:
>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
...
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'
Podtřídy třídy Template mohou určit vlastní oddělovač. Nástroj pro dávkové přejmenování v prohlížeči fotografií může například používat znak procenta pro zástupné symboly aktuálního data, pořadového čísla obrázku nebo formátu souboru:
>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
... delimiter = '%'
...
>>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format): ')
Enter rename style (%d-date %n-seqnum %f-format): Ashley_%n%f
>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
... base, ext = os.path.splitext(filename)
... newname = t.substitute(d=date, n=i, f=ext)
... print('{0} --> {1}'.format(filename, newname))
img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg
Dalším využitím šablon je oddělení logiky programu od podrobností různých výstupních formátů. Díky tomu lze dosazovat vlastní šablony pro soubory XML, prosté textové sestavy a webové sestavy HTML.
11.3. Práce s rozvržením záznamů binárních dat¶
Modul struct poskytuje funkce pack() a
unpack() pro práci s formáty binárních záznamů proměnné délky.
Následující příklad ukazuje, jak procházet informace v hlavičkách souboru ZIP
bez použití modulu zipfile. Kódy balení "H" a "I" představují
dvoubajtová, respektive čtyřbajtová celá čísla bez znaménka. Znak "<"
označuje standardní velikost a pořadí bajtů od nejméně významného
(little-endian):
import struct
with open('myfile.zip', 'rb') as f:
data = f.read()
start = 0
for i in range(3): # show the first 3 file headers
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 # skip to the next header
11.4. Vícevláknové zpracování¶
Vlákna jsou technikou pro oddělení úloh, které na sobě sekvenčně nezávisí. Lze jimi zlepšit odezvu aplikací, které přijímají vstup uživatele, zatímco jiné úlohy běží na pozadí. Souvisejícím použitím je souběžné provádění vstupně- výstupních operací a výpočtů v jiném vlákně.
Následující kód ukazuje, jak může vysokoúrovňový modul threading spouštět
úlohy na pozadí, zatímco hlavní program pokračuje v běhu:
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('Finished background zip of:', self.infile)
background = AsyncZip('mydata.txt', 'myarchive.zip')
background.start()
print('The main program continues to run in foreground.')
background.join() # Wait for the background task to finish
print('Main program waited until background was done.')
Hlavní výzvou vícevláknových aplikací je koordinace vláken, která sdílejí data nebo jiné prostředky. Modul threading k tomu poskytuje řadu synchronizačních primitiv včetně zámků, událostí, podmínkových proměnných a semaforů.
Přestože jsou tyto nástroje výkonné, i drobné chyby návrhu mohou způsobit obtížně
reprodukovatelné problémy. Upřednostňovaným přístupem ke koordinaci úloh je proto
soustředit veškerý přístup k prostředku do jediného vlákna a pomocí modulu
queue mu předávat požadavky z ostatních vláken. Aplikace používající
objekty Queue pro komunikaci a koordinaci mezi vlákny se snáze
navrhují, jsou čitelnější a spolehlivější.
11.5. Protokolování¶
Modul logging nabízí plnohodnotný a flexibilní systém protokolování. V
nejjednodušší podobě se zprávy protokolu odesílají do souboru nebo na
sys.stderr:
import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')
Tím vznikne následující výstup:
WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down
Ve výchozím nastavení jsou informační a ladicí zprávy potlačeny a výstup se
odesílá na standardní chybový výstup. Mezi další možnosti patří směrování zpráv
prostřednictvím e-mailu, datagramů, soketů nebo na server HTTP. Filtry mohou
vybírat různé směrování podle priority zprávy: DEBUG,
INFO, WARNING, ERROR a
CRITICAL.
Systém protokolování lze konfigurovat přímo z Pythonu nebo načíst z uživatelsky upravitelného konfiguračního souboru, a přizpůsobit jej tak bez změny aplikace.
11.6. Slabé reference¶
Python provádí automatickou správu paměti (pro většinu objektů počítání referencí a automatický úklid paměti k odstranění cyklů). Paměť se uvolní krátce po odstranění poslední reference na objekt.
Tento přístup dobře funguje ve většině aplikací, někdy je však třeba sledovat
objekty pouze po dobu, kdy je používá něco jiného. Samotné sledování bohužel
vytváří referenci, která by objekty zachovala trvale. Modul weakref
poskytuje nástroje pro sledování objektů bez vytvoření silné reference. Jakmile
objekt není zapotřebí, automaticky se odstraní z tabulky weakref a pro objekty
slabých referencí se zavolá zpětné volání. Typickým použitím je ukládání nákladně
vytvářených objektů do mezipaměti:
>>> 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:/python314/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
11.7. Nástroje pro práci se seznamy¶
Mnoho požadavků na datové struktury lze splnit vestavěným typem seznamu. Někdy jsou však zapotřebí alternativní implementace s jinými výkonnostními kompromisy.
Modul array poskytuje objekt array, který se podobá
seznamu, ale ukládá pouze homogenní data a činí tak úsporněji. Následující
příklad ukazuje pole čísel uložených jako dvoubajtová binární čísla bez znaménka
(kód typu "H") namísto obvyklých 16 bajtů na položku v běžném seznamu
objektů int Pythonu:
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])
Modul collections poskytuje objekt deque, který se
podobá seznamu, ale nabízí rychlejší přidávání a odebírání z levé strany za cenu
pomalejšího vyhledávání uprostřed. Tyto objekty jsou vhodné k implementaci front
a prohledávání stromů do šířky:
>>> from collections import deque
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print("Handling", d.popleft())
Handling task1
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)
Kromě alternativních implementací seznamů nabízí knihovna i další nástroje,
například modul bisect s funkcemi pro práci se seřazenými seznamy:
>>> import bisect
>>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
>>> bisect.insort(scores, (300, 'ruby'))
>>> scores
[(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]
Modul heapq poskytuje funkce pro implementaci hald založených na běžných
seznamech. Položka s nejnižší hodnotou se vždy uchovává na pozici nula. To je
užitečné pro aplikace, které opakovaně přistupují k nejmenšímu prvku, ale
nechtějí pokaždé provádět úplné řazení seznamu:
>>> from heapq import heapify, heappop, heappush
>>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
>>> heapify(data) # rearrange the list into heap order
>>> heappush(data, -5) # add a new entry
>>> [heappop(data) for i in range(3)] # fetch the three smallest entries
[-5, 0, 1]
11.8. Desetinná aritmetika s plovoucí řádovou čárkou¶
Modul decimal nabízí datový typ Decimal pro desetinnou
aritmetiku s plovoucí řádovou čárkou. Ve srovnání s vestavěnou binární
implementací float je tato třída zvláště užitečná pro
finanční aplikace a další použití vyžadující přesnou desetinnou reprezentaci,
řízení přesnosti,
řízení zaokrouhlování podle právních či regulatorních požadavků,
sledování platných desetinných míst nebo
aplikace, v nichž uživatel očekává výsledky odpovídající ručnímu výpočtu.
Výpočet pětiprocentní daně z telefonního poplatku 70 centů například poskytne v desetinné a binární aritmetice s plovoucí řádovou čárkou odlišné výsledky. Rozdíl se projeví při zaokrouhlení na nejbližší cent:
>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73
Výsledek Decimal zachová koncovou nulu a z činitelů se dvěma
platnými místy automaticky odvodí čtyři platná místa. Decimal napodobuje ruční
výpočty a vyhýbá se problémům vznikajícím tehdy, když binární čísla s plovoucí
řádovou čárkou nedokážou přesně reprezentovat desetinné hodnoty.
Přesná reprezentace umožňuje třídě Decimal provádět výpočty
modulo a testy rovnosti, pro které nejsou binární čísla s plovoucí řádovou
čárkou vhodná:
>>> 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
Modul decimal poskytuje aritmetiku s libovolnou potřebnou přesností:
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')