arquivos especiais - zip e pickle


Arquivos especiais - zip e pickle

Na prática é comum encontrar situações onde arquivos crescem até ficar enormes. Como o hardware, memoria, hard disks, pendrives e tal são limitados temos um problema de falta de espaço para armazenar arquivos. Outro problema é transmitir tais arquivos grandes, por exemplo via e-mail.
Outra situação comum é ter arquivos de tamanho razoável mas em números muito grandes. Tambem neste caso espaço faz falta. Por exemplo, na Câmara dos Deputados temos mais de 70,000 arquivos com dados de projetos de lei de diversos tipos. Na média são arquivos relativamente pequenos, cerca de 70 KB em tamanho, mas a quantidade é muito grande.
Espaço de memória não é o único problema. Um problema tambem importante é o tempo gasto na transferência, seja via downloads ou wi-fi e outros meios.
Por estas razões houve muito esforço para desenvolver algoritmos que permitam diminuir tamanho de arquivos de dados. Hoje em dia existem vários padrões para tal compressão de arquivos.
Nesta lição veremos dois tipos de pacotes para comprimir dados de arquivos.

Compressão de dados

Quando guardamos dados tais como textos ou imagens em arquivos tipicamente estamos inserindo dados redundantes no conteúdo. Quando digo redundantes quero dizer que a mesma informação poderia ser guardada, sem nenhuma perda, reduzindo a quantidade de simbolos ou bits armazenados. Ou seja, mesma informação em arquivo de menor tamanho.
Para ver como isto ocorre considere uma imagem digital. Por exemplo, uma tela típica com 1380 pontos por 720 pontos. Esta tela contem 1380 X 720 = 993,600 pontos, ou pixels. Cada pixel tem associado um valor de côr.
Entretanto olhando uma imagem típica vemos que é composta de áreas ou manchas de mesma côr. Se formos representar côres por números digamos, de 0 a 255, examinando os pixels de cada uma das 720 linhas da imagem tipicamente veríamos algo como:
0,0,0,17,17,17,17,18,0,0,0,0,0,0,0,0,0,0,0,0,0,233,233,233,233,0,0,0,.......
Em que sentido esta sequência é "redundante"?
Quando repetimos o número de uma côr em sucessivos pixels (na prática a repetição é de milhares de pontos!) estamos introduzindo redundância porque, acima de certo tamanho da sub-sequência de côr repetida, sería mais sucinto indicar o número de repetições em vez de dar a côr de cada pixel. Se você tem uma sequência ininterrupta de 300 zeros é mais sucinto representá-la por "300 vêzes zero" do que por "0, 0, 0, ..." Esta idéia aliás é a base do algoritmo RLE (run-length encoding) um dos usados em arquivos de imagens.
Por exemplo vejamos a sequência acima. Vamos representar a mesma como uma sequencia de números separados por vírgulas. Cada número é a côr de um pixel.
Depois vamos representar a mesma sequência mas subsequencias repetidas serão substituidas pelo seguinte formato:
número de repetições seguido por letra 'x' seguido por numero da côr
Por exemplo, a subsequencia 17,17,17,17,17 é substituida por 5x17 ("cinco vêzes 17")
In [1]:
import os, pprint

# cria uma lista com cores de pixels
c = [0,0,0,17,17,17,17,18,0,0,0,0,0,0,0,0,0,0,0,0,0,233,233,233,233,0,0,0]

# converte a lista num string de cores separadas por virgulas
s = ''
for e in c:
    if s == '':
        s = str(e)
    else:
        s = s + ',' + str(e)
print(s)
print(len(s))

# agora a mesma lista mas com a informação comprimida
s1 = '3x0,4x17,18,13x0,4x233,3x0'
print(s1)
print(len(s1))

# calcula taxa de compressão
print('Taxa de compressão = ', len(s1)/len(s))
0,0,0,17,17,17,17,18,0,0,0,0,0,0,0,0,0,0,0,0,0,233,233,233,233,0,0,0
68
3x0,4x17,18,13x0,4x233,3x0
26
Taxa de compressão =  0.38235294117647056
Ou seja reduzimos para 38% do tamanho original. Obviamente esta redução depende da imagem. Na prática existem sistemas melhores de compressão de imagens, tais como o formato JPEG e outros. O exemplo aqui é só ilustrativo.

De forma similar é possivel diminuir tamanho de textos. As possibilidades de codificação são muitas. Por exemplo, palavras mais compridas podem ser substituidas por códigos mais curtos. Letras podem ter representação binária mais curta para letras que ocorrem mais no texto (códigos de Huffman) e assim por diante.
Uma compressão de dados muito popular é o ZIP.

ZIP

O módulo de compressão de dados que usaremos chama-se zipfile.
Como muitos módulos zipfile esconde complexidade atrás de uma interface símples e fácil de usar. Sua documentação pode ser vista aqui
Um arquivo zip é um container de outros arquivos como se fôra uma pasta. Os arquivos contidos estão em forma comprimida, obtida usando os algorítmos de compressão do padrão zip. Além de arquivos quaisquer um arquivo zip também pode conter pastas com seus respectivos conteúdos, tudo sempre devidamente comprimido.
O módulo zipfile permite construir objetos zipfile.ZipFile, os quais permitem lêr e escrever em arquivos zip. Algo parecido com o objeto File que usamos para manipular arquivos texto.
Vejamos como "lêr" arquivos zip e extrair seus conteúdos.

Extraindo conteúdo de arquivos zip

Para extrair os arquivos contidos num arquivo zip na sua forma original descomprimida devemos primeiro criar um objeto zipfile.ZipFile(file, mode='r') onde file é o nome do arquivo zip (por exemplo, 'Abril1991.zip').
Para este exemplo vamos usar o arquivo 'Abril1991.zip' com dados de votações da Câmara dos Deputados, que pode ser baixado aqui.
In [2]:
import zipfile, os

zip = zipfile.ZipFile('Abril1991.zip')  # o default é 'r'
In [3]:
# vejamos quais arquivos estão comprimidos dentro de Abril1991.zip
zip.namelist()
Out[3]:
['Abril1991/HECD01O026O000000.TXT',
 'Abril1991/HECD01O027O000000.TXT',
 'Abril1991/HECD01O028O000000.TXT',
 'Abril1991/HECD01O028O100000567.TXT',
 'Abril1991/HECD01O031O000000.TXT',
 'Abril1991/HECD01O036O000000.TXT',
 'Abril1991/HECD01O037O000000.TXT',
 'Abril1991/HECD01O038O000000.TXT',
 'Abril1991/HECD01O041O000000.TXT',
 'Abril1991/HECD01O042O000000.TXT',
 'Abril1991/HECD01O042O100000568.TXT',
 'Abril1991/HECD01O042O100000569.TXT',
 'Abril1991/LPCD01O026O000000.TXT',
 'Abril1991/LPCD01O027O000000.TXT',
 'Abril1991/LPCD01O028O000000.TXT',
 'Abril1991/LPCD01O031O000000.TXT',
 'Abril1991/LPCD01O036O000000.TXT',
 'Abril1991/LPCD01O037O000000.TXT',
 'Abril1991/LPCD01O038O000000.TXT',
 'Abril1991/LPCD01O041O000000.TXT',
 'Abril1991/LPCD01O042O000000.TXT',
 'Abril1991/LVCD01O028O100000567.TXT',
 'Abril1991/LVCD01O042O100000568.TXT',
 'Abril1991/LVCD01O042O100000569.TXT',
 'Abril1991/']
Temos 24 arquivos contidos em Abril1991.zip. Podemos extrair qualquer um ou então todos de uma vez, com os métodos extract(nome_arquivo) e extractall(path_diretorio) respectivamente.
In [4]:
# vamos extrait um arquivo texto pelo nome
nome = 'Abril1991/HECD01O026O000000.TXT'
zip.extract(nome)

# agora podemos abrir normalmente o arquivo extraido e ler seu conteudo
with open(nome, 'r') as arq:
    txt = arq.read()

texto = txt.split('\n')
texto
Out[4]:
['CD01O026O',
 '000000',
 '02/04/1991',
 '00:00:00',
 ' ',
 '000',
 '000',
 '000',
 '000',
 '000',
 '000',
 '000',
 '359',
 'Lista de Presença no Plenário',
 '']
In [5]:
# vamos extrair todos de uma vez e colocar no diretorio 'exemplo'
s = os.getcwd()
path = s + '\\' + 'exemplo'
zip.extractall(path)

# examinando o novo diretorio
os.listdir(path)
Out[5]:
['Abril1991']
In [6]:
# fechar sempre arquivos abertos após seu uso
zip.close()
In [7]:
# o que foi extraido?
path = path + '\\Abril1991'
os.listdir(path)
Out[7]:
['HECD01O026O000000.TXT',
 'HECD01O027O000000.TXT',
 'HECD01O028O000000.TXT',
 'HECD01O028O100000567.TXT',
 'HECD01O031O000000.TXT',
 'HECD01O036O000000.TXT',
 'HECD01O037O000000.TXT',
 'HECD01O038O000000.TXT',
 'HECD01O041O000000.TXT',
 'HECD01O042O000000.TXT',
 'HECD01O042O100000568.TXT',
 'HECD01O042O100000569.TXT',
 'LPCD01O026O000000.TXT',
 'LPCD01O027O000000.TXT',
 'LPCD01O028O000000.TXT',
 'LPCD01O031O000000.TXT',
 'LPCD01O036O000000.TXT',
 'LPCD01O037O000000.TXT',
 'LPCD01O038O000000.TXT',
 'LPCD01O041O000000.TXT',
 'LPCD01O042O000000.TXT',
 'LVCD01O028O100000567.TXT',
 'LVCD01O042O100000568.TXT',
 'LVCD01O042O100000569.TXT']
Objetos ZipFile possuem diversos outros métodos úteis. Para detalhes consulte sua documentação no link dado acima.
Vejamos agora a tarefa reversa, criar um arquivo zip e guardar nele diversos arquivos texto comprimidos.

Criando arquivos zip e guardando arquivos neles

Vamos criar um arquivo zip e salvar nêle alguns dos arquivos que acabamos de extrair no diretório exemplo\Abril1991.
Para criar arquivos zip também usamos o objeto zipfile.ZipFile mas agora com a opção 'w' (write) explicitada.
In [8]:
# criar um objeto ZipFile para escrita num arquivo zip
zap = zipfile.ZipFile('arqnovo.zip', 'w')

# vamos criar uma lista com nomes de arquivos para comprimir
lst = os.listdir(path)

# guardaremos só os primeiros 5 arquivos da lista
for i in range(6):
    caminho = path + '\\' + lst[i]
    zap.write(caminho)
    
# e finalmente fechamos o arquivo zip recem criado
zap.close()

# podemos verificar o conteudo de arqnovo.zip
vzip = zipfile.ZipFile('arqnovo.zip')           # não precisa o 'r' porque é o default
vzip.namelist()
Out[8]:
['Users/ps/Desktop/Blog/exemplo/Abril1991/HECD01O026O000000.TXT',
 'Users/ps/Desktop/Blog/exemplo/Abril1991/HECD01O027O000000.TXT',
 'Users/ps/Desktop/Blog/exemplo/Abril1991/HECD01O028O000000.TXT',
 'Users/ps/Desktop/Blog/exemplo/Abril1991/HECD01O028O100000567.TXT',
 'Users/ps/Desktop/Blog/exemplo/Abril1991/HECD01O031O000000.TXT',
 'Users/ps/Desktop/Blog/exemplo/Abril1991/HECD01O036O000000.TXT']
In [9]:
# sempre fechar arquivos abertos
zap.close()
vzip.close()

pickle

Muitas vezes queremos salvar uma estrutura de dados criada em nosso programa, tal como uma lista ou um diretório, de maneira que possamos lêr a mesma para a memória mais tarde e recuperá-la exatamente como a salvamos.
Obviamente estas estruturas em memória estão em formato binário, de modo que seu salvamento é operação relativamente mais complicada. Felizmente a complexidade fica oculta por mais um módulo util do Python, o módulo pickle (de pickles, no sentido de criar conservas, objetos em conserva...).
O módulo pickle é muito útil para programação orientada a objetos e tópicos avançados de programação. Para nós aqui basta saber usá-lo para salvar e para recuperar nossas estruturas de dados familiares.
Vejamos como usar este módulo muito util, cuja documentação oficial detalhada pode ser vista aqui.
In [11]:
import pickle, pprint

# vamos criar um dicionário com valores de diversos tipos
d = {
    1: ['a','b','c'],                 # uma lista
    2: ('banana', 123, 456),          # uma tupla 
    3: {111, 222, 333, 444}           # um set
}

# agora vamos salvar este dicionário num arquivo tipo pickle
# repare que abrimos um arquivo data.pickle para escrita binária 'wb'
with open('data.pickle', 'wb') as f:
    pickle.dump(d, f)                     # o metodo dump() descarrega d em f
    
Examinando seu diretório corrente ali deve estar um arquivo 'data.pickle'. Este arquivo está em formato binário então não será legivel em leitor de arquivo texto, tal como o Notepad.
Para examinar o seu conteúdo vamos abrir o arquivo usando o módulo pickle e depois ver na memória qual objeto foi recuperado.
In [13]:
# abre o arquivo data.pickle e lê seu conteudo binário
with open('data.pickle', 'rb') as f:
    data = pickle.load(f)

print(type(data))           # qual tipo de coisa veio do arquivo

pprint.pprint(data)
<class 'dict'>
{1: ['a', 'b', 'c'], 2: ('banana', 123, 456), 3: {444, 333, 222, 111}}
Isto encerra a lição.

No comments:

Post a Comment