11. Kort rundtur i standardbiblioteket — Del II¶
Denna andra genomgång omfattar mer avancerade moduler som stöder professionella programmeringsbehov. Dessa moduler förekommer sällan i små skript.
11.1. Formatering av utdata¶
Modulen reprlib
tillhandahåller en version av repr()
som är anpassad för förkortad visning av stora eller djupt nästlade behållare:
>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"
Modulen pprint
erbjuder mer sofistikerad kontroll över utskriften av både inbyggda och användardefinierade objekt på ett sätt som är läsbart för tolken. När resultatet är längre än en rad lägger ”pretty printer” till radbrytningar och indrag för att tydligare avslöja datastrukturen:
>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
... 'yellow'], 'blue']]]
...
>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
'white',
['green', 'red']],
[['magenta', 'yellow'],
'blue']]]
Modulen textwrap
formaterar textstycken så att de passar en given skärmbredd:
>>> 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.
Modulen locale
ger tillgång till en databas med kulturspecifika dataformat. Grupperingsattributet i locales formatfunktion ger ett direkt sätt att formatera tal med gruppseparatorer:
>>> 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. Templating¶
Modulen string
innehåller en mångsidig klass Template
med en förenklad syntax som är lämplig för redigering av slutanvändare. Detta gör det möjligt för användare att anpassa sina applikationer utan att behöva ändra applikationen.
Formatet använder platshållarnamn som bildas av $
med giltiga Python-identifierare (alfanumeriska tecken och understreck). Genom att omge platshållaren med hakparenteser kan den följas av fler alfanumeriska bokstäver utan mellanliggande mellanslag. Att skriva $$
skapar en enda escapad $
:
>>> 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.'
Metoden substitute()
ger upphov till ett KeyError
när en platshållare inte anges i en ordbok eller ett nyckelordsargument. För applikationer av typen mail-merge kan användarens data vara ofullständiga och metoden safe_substitute()
kan vara mer lämplig — den lämnar platshållare oförändrade om data saknas:
>>> 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.'
Mallunderklasser kan ange en anpassad avgränsare. Ett verktyg för namnändring i batch för en fotobrowser kan till exempel välja att använda procenttecken för platshållare som aktuellt datum, bildsekvensnummer eller filformat:
>>> 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
Ett annat användningsområde för mallar är att skilja programlogiken från detaljerna i olika utdataformat. Detta gör det möjligt att ersätta anpassade mallar för XML-filer, rapporter i klartext och HTML-webbrapporter.
11.3. Arbeta med layouter för binära dataposter¶
Modulen struct
innehåller funktionerna pack()
och unpack()
för att arbeta med binära postformat med variabel längd. Följande exempel visar hur man loopar genom headerinformation i en ZIP-fil utan att använda modulen zipfile
. Paketkoderna "H"
och "I"
representerar osignerade tal på två respektive fyra byte. Tecknet "<"
anger att de är av standardstorlek och i little-endian byteordning:
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. Multi-threading¶
Trådning är en teknik för att frikoppla uppgifter som inte är sekventiellt beroende av varandra. Trådar kan användas för att förbättra responsen i applikationer som accepterar användarinmatning medan andra uppgifter körs i bakgrunden. Ett relaterat användningsfall är att köra I/O parallellt med beräkningar i en annan tråd.
Följande kod visar hur högnivåmodulen threading
kan köra uppgifter i bakgrunden medan huvudprogrammet fortsätter att köras:
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('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.')
Den största utmaningen med flertrådade program är att koordinera trådar som delar data eller andra resurser. För detta ändamål tillhandahåller trådningsmodulen ett antal synkroniseringsprimitiver, inklusive lås, händelser, villkorsvariabler och semaforer.
Även om dessa verktyg är kraftfulla kan mindre konstruktionsfel leda till problem som är svåra att reproducera. Det bästa sättet att samordna uppgifter är därför att koncentrera all åtkomst till en resurs till en enda tråd och sedan använda modulen queue
för att mata den tråden med förfrågningar från andra trådar. Program som använder Queue
-objekt för kommunikation och samordning mellan trådar är enklare att utforma, mer läsbara och mer tillförlitliga.
11.5. Loggning¶
Modulen logging
erbjuder ett fullt utrustat och flexibelt loggningssystem. I sin enklaste form skickas loggmeddelanden till en fil eller till sys.stderr
:
import loggning
logging.debug('Felsökningsinformation')
logging.info('Informativt meddelande')
logging.warning('Varning:config-filen %s hittades inte', 'server.conf')
logging.error('Fel inträffade')
logging.critical('Kritiskt fel - stängs av')
Detta ger följande resultat:
WARNING:root:Warning:config-filen server.conf hittades inte
ERROR:root:Fel inträffade
CRITICAL:root:Kritiskt fel -- stänger ner
Som standard undertrycks informations- och felsökningsmeddelanden och utdata skickas till standardfel. Andra utdataalternativ inkluderar dirigering av meddelanden via e-post, datagram, socklar eller till en HTTP-server. Nya filter kan välja olika routning baserat på meddelandets prioritet: DEBUG
, INFO
, WARNING
, ERROR
och CRITICAL
.
Loggningssystemet kan konfigureras direkt från Python eller laddas från en konfigurationsfil som kan redigeras av användaren för anpassad loggning utan att applikationen behöver ändras.
11.6. Svaga referenser¶
Python gör automatisk minneshantering (referensräkning för de flesta objekt och garbage collection för att eliminera cykler). Minnet frigörs kort efter att den sista referensen till det har eliminerats.
Detta tillvägagångssätt fungerar bra för de flesta tillämpningar, men ibland finns det ett behov av att spåra objekt endast så länge de används av något annat. Tyvärr skapar bara spårningen av dem en referens som gör dem permanenta. Modulen weakref
ger verktyg för att spåra objekt utan att skapa en referens. När objektet inte längre behövs tas det automatiskt bort från en weakref-tabell och en återuppringning utlöses för weakref-objekt. Typiska tillämpningar inkluderar cachelagring av objekt som är dyra att skapa:
>>> 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. Verktyg för att arbeta med listor¶
Många behov av datastrukturer kan tillgodoses med den inbyggda listtypen. Ibland finns det dock behov av alternativa implementeringar med olika prestandakompromisser.
Modulen array
tillhandahåller ett array
-objekt som är som en lista som bara lagrar homogena data och lagrar dem mer kompakt. Följande exempel visar en matris med tal som lagras som två byte osignerade binära tal (typkod "H"
) i stället för de vanliga 16 byte per post för vanliga listor med Python int-objekt:
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])
Modulen collections
tillhandahåller ett deque
-objekt som är som en lista med snabbare appends och pops från vänster sida men långsammare lookups i mitten. Dessa objekt är väl lämpade för att implementera köer och breda första trädsökningar:
>>> from collections import deque
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print("Handling", d.popleft())
Handling task1
unsearched = deque([start_node])
def breadth_first_search(unsearched):
nod = unsearched.popleft()
för m i gen_moves(nod):
if is_goal(m):
returnera m
unsearched.append(m)
Förutom alternativa implementeringar av listor erbjuder biblioteket även andra verktyg, t.ex. modulen bisect
med funktioner för att manipulera sorterade listor:
>>> 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')]
Modulen heapq
tillhandahåller funktioner för att implementera högar baserade på vanliga listor. Den lägst värderade posten hålls alltid på position noll. Detta är användbart för applikationer som upprepade gånger vill komma åt det minsta elementet men inte vill köra en fullständig list sortering:
>>> 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. Decimal Aritmetik med flyttal¶
Modulen decimal
erbjuder en Decimal
-datatyp för decimal aritmetik med flyttal. Jämfört med den inbyggda float
-implementeringen av binär flyttalsaritmetik är klassen särskilt användbar för
finansiella applikationer och andra användningsområden som kräver exakt decimal representation,
kontroll över precision,
kontroll över avrundningar för att uppfylla lagstadgade eller regulatoriska krav,
spårning av signifikanta decimaler, eller
applikationer där användaren förväntar sig att resultaten ska matcha beräkningar som gjorts för hand.
Om man t.ex. beräknar 5% tax för en telefonavgift på 70 cent får man olika resultat med decimalt flyttal och binärt flyttal. Skillnaden blir betydande om resultaten avrundas till närmaste cent:
>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73
Resultatet av Decimal
behåller en efterföljande nolla och härleder automatiskt fyrställig signifikans från multiplikander med tvåställig signifikans. Decimal återger matematiken som den görs för hand och undviker problem som kan uppstå när binär flyttal inte exakt kan representera decimala kvantiteter.
Exakt representation gör det möjligt för klassen Decimal
att utföra modulo-beräkningar och likhetstester som är olämpliga för binär flyttal:
>>> 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
Modulen decimal
ger aritmetik med så stor precision som behövs:
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')