Breve Passeio à Biblioteca Standard – Parte II

Prévio Próximo

Python Logo11.  Breve Passeio na Biblioteca Standard – Parte II

Esta segunda turnê abrange módulos mais avançados que suportam as necessidades da programação profissional. Estes módulos raramente ocorrem em pequenos scripts.

11.1. Formatação de Saída

O módulo repr fornece uma versão de repr() personalizado para abreviadamente exibir recipientes grande ou profundamente aninhados:
>>> import repr
>>> repr.repr(set('supercalifragilisticexpialidocious'))
"set(['a', 'c', 'd', 'e', 'f', 'g', ...])"
O módulo pprint oferece um controle mais sofisticado sobre a impressão de ambos os objetos embutidos e definidos pelo usuário de uma forma que pode ser lido pelo interpretador. Quando o resultado é mais do que uma linha, a “pretty printer”, acrescenta quebras de linha e identação para revelar mais claramente a estrutura de dados:
>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
...     'yellow'], 'blue']]]
...
>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
   'white',
   ['green', 'red']],
  [['magenta', 'yellow'],
   'blue']]]
Os formatos de módulo TextWrap  faz parágrafos de texto para atender a uma determinada largura da tela:
>>> 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.
O módulo locale acessa um banco de dados de formatos de dados de cultura específica. O atributo de agrupamento da função de formato de localidade fornece uma maneira direta de formatação de números com separadores de grupo:
>>> 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("%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

O módulo string inclui uma classe Template versátil, com uma sintaxe simplificada adequada para edição por usuários finais. Isso permite que os usuários personalizem suas aplicações sem ter que alterar o aplicativo.

O formato utiliza nomes placeholders formados por $ com identificadores válidos Python (caracteres alfanuméricos e sublinhados). Cercando os placeholders  com colchetes permite que eles sejam seguidos por letras mais alfanuméricos sem espaços. Escrevendo $$ cria um único $ escapado:

>>> 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.'
O método substitute() levanta uma KeyError quando um placeholder não é fornecido em um dicionário ou um argumento keyword. Para aplicações de estilo de mala direta, dados fornecidos pelo usuário podem ser incompletos e o método safe_substitute() pode ser mais apropriado – ele vai deixar espaços reservados inalterado se os dados estiverem faltando:
>>> 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.'
Subclasses de modelo podem especificar um delimitador de costume. Por exemplo, um utilitário de renomeação de lote para um navegador de foto pode optar pelo uso de sinais de porcentagem para placeholders, como a data atual, o número de seqüência de imagens, ou formato de arquivo:
>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
...     delimiter = '%'
>>> fmt = raw_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
Outra aplicação para modelagem é separar a lógica do programa a partir dos detalhes de vários formatos de saída. Isso torna possível para substituir templates personalizados para arquivos XML, relatórios de texto simples e relatórios web HTML

11.3. Trabalhando Layout de Registros de Dados Binários

O módulo struct fornece funções pack() e unpack()  para trabalhar com formatos de comprimento variável de registros binários. O exemplo a seguir mostra como fazer um loop através de informações de cabeçalho em um arquivo ZIP sem usar o módulo zipfile. Códigos Pack “H” e “I” representam números não assinados dois e quatro bytes, respectivamente. O “<” indica que eles são de tamanho padrão e na ordem de bytes little-endian:
import struct

data = open('myfile.zip', 'rb').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

Threading é uma técnica para separar as tarefas que não são sequencialmente dependente. Threads podem ser usados para melhorar a capacidade de resposta dos aplicativos que aceitam a entrada do usuário, enquanto outras tarefas sπo executadas em segundo plano. Um caso de uso relacionado está em execução I / O em paralelo com os cálculos em outro segmento.

O código a seguir mostra como o módulo de segmentação de alto nível pode executar tarefas em segundo plano enquanto o programa principal continua a ser executado:

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.'

O principal desafio de aplicações multi-threaded está em coordenar threads que compartilham dados ou outros recursos. Para esse fim, o módulo de threading fornece um número de primitivas de sincronização incluindo bloqueios, eventos, variáveis de condição e semáforos.

Embora essas ferramentas sejam poderosas, erros de concepção menores pode resultar em problemas que são difíceis de reproduzir. Assim, a abordagem preferida para a tarefa de coordenação é concentrar todo o acesso a um recurso em um único thread e, em seguida, usar o módulo de queue para alimentar esse thread com pedidos de outros tópicos. Aplicações usando objetos Queue.Queue para a comunicação inter-thread e coordenação são mais fáceis de desenhar, mais legível, e mais confiável.

11.5. Logging

O módulo logging oferece um sistema de registro cheio de featured e flexível. Na sua forma mais simples, mensagens de log são enviadas para um arquivo ou para 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')
Isso produz o seguinte resultado:
WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down

Por padrão, mensagens informativas e de depuração são suprimidas e a saída é enviada para o erro padrão. Outras opções de saída incluem mensagens de roteamento através de e-mail, datagrama, sockets, ou para um servidor HTTP. Novos filtros pode selecionar encaminhamentos diferentes com base na prioridade da mensagem: DEBUGINFOWARNINGERROR CRITICAL.

O sistema de registro pode ser configurado diretamente do Python ou pode ser carregado a partir de um arquivo de configuração de usuário editável para registro personalizado, sem alterar o aplicativo.

11.6. Referências Fracas

Python faz gerenciamento automático de memória (contagem de referência para a maioria dos objetos e coleta de lixo para eliminar ciclos). A memória é libertada pouco depois da última referência a que tenha sido eliminado.

Esta abordagem funciona bem para a maioria das aplicações, mas de vez em quando há uma necessidade de rastrear objetos apenas enquanto eles estão sendo usados por outra coisa. Infelizmente, apenas segui-los cria uma referência de que os torna permanente. O módulo weakref fornece ferramentas para objetos de rastreamento sem criar uma referência. Quando o objeto não é mais necessário, ele será automaticamente removido de uma tabela weakref e um callback é acionado para objetos weakref. Aplicações típicas incluem objetos de cache que são caros para criar:

>> 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 alive10
>>> 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:/python26/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

11.7. Ferramentas para Trabalhar com Listas

Muitas necessidades das estruturas de dados podem ser atendidas com o tipo de lista interna. No entanto, por vezes, há uma necessidade de implementações alternativas com diferentes trade-offs de desempenho.
O módulo array fornece um objeto array(), que é como uma lista que armazena apenas os dados homogêneos e os armazena de forma mais compacta. O exemplo a seguir mostra uma array de números armazenados como números binários de dois bytes não assinados (typecode “H“) em vez dos habituais 16 bytes por entrada para as listas regulares de objetos int Python:
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)26932
>>> a[1:3]
array('H', [10, 700])
O módulo collections fornece um objeto deque() que é como uma lista com appends e pops mais rápidos e pops do lado esquerdo, mas pesquisas mais lentas no meio. Esses objetos são bem adequados para a implementação de queues e  pesquisas breadth-first de árvores:
>>> 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)
Em adição às implementações alternativas da lista, a biblioteca também oferece outras ferramentas, tais como o módulo bisect com funções para a manipulação de listas ordenadas:
>>> 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')]
O módulo heapq fornece funções para a implementação de heaps (pilhas) com base em listas regulares. A entrada de menor valor é sempre mantido na posição zero. Isso é útil para aplicativos que acessam repetidamente o menor elemento, mas não queiram executar uma lista de classificação completa:
>>> 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. Aritmética de Decimal de Ponto Flutuante

O módulo decimal oferece um tipo de dados Decimal para aritmética decimal de ponto flutuante. Em comparação com a implementação interna float de ponto flutuante binário, a classe é especialmente útil para
  • aplicações financeiras e outros usos que requerem representação decimal exata,
  • controle sobre a precisão,
  • controle sobre o arredondamento para atender às exigências legais ou regulamentares,
  • rastreamento de casas decimais significativas, ou
  • aplicações onde o usuário espera os resultados para coincidir com cálculos feitos à mão.
Por exemplo, o cálculo de um imposto de 5% sobre a taxa de telefone de 70 centavos dá resultados diferentes em ponto flutuante decimal e ponto flutuante binário. A diferença torna-se significativo se os resultados são arredondados para o centavo mais próximo:
>>> from decimal import *
>>> x = Decimal('0.70') * Decimal('1.05')
>>> xDecimal('0.7350')
>>> x.quantize(Decimal('0.01'))  # round to nearest cent
Decimal('0.74')
>>> round(.70 * 1.05, 2)         # same calculation with floats
0.73
O resultado Decimal mantém um zero à direita, inferindo automaticamente significância de quatro lugares de multiplicandos com dois lugares significados. Decimal reproduz matemática como é feito à mão e evita problemas que podem surgir quando ponto flutuante binário não pode representar exatamente quantidades decimais.
Representação exata permite a classe Decimal realizar cálculos de modulo e testes de igualdade que são inadequados para ponto flutuante binário:
>>> Decimal('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995

>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> sum([0.1]*10) == 1.0
False
O módulo decimal fornece aritmética com o máximo de precisão que seja necessário:
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')
Prévio Próximo