Python intermediário - III¶
Entrada e saída de dados¶
O fluxo de informações que ocorre entre o disco rígido de nosso computador e o que vemos impresso na tela, ou entre um ponto de comunicação remoto e o nosso computador local é geralmente realizado por meio de objetos-tipo arquivo (file objects). A esses objetos dá-se também o nome de input/output stream, de onde segue o termo I/O stream.
Tipos de arquivo¶
Para entendimento prático, streams são os tradicionais “arquivos” que transitam em nossas máquinas. Os dois tipos principais de arquivo são:
Texto formatado (Text file). Arquivos de texto recebem e produzem objetos str. Isto significa que um processo de codificação/decodificação dos dados ocorre durante o fluxo.
Binário (Binary file). Arquivos binários lidam com a objetos na forma de bytes. Isto significa que nenhum processo de codificação/decodificação dos dados ocorre durante o fluxo e que o texto é não formatado.
Operações com arquivos¶
Podemos realizar diversas operações com arquivos, tais como modo de apenas leitura (read-only), modo de escrita (write) e modo de escrita e leitura (read-write). Neste capítulo, aprenderemos a manipular o fluxo de entrada e saída de diversos tipos de arquivo. A forma mais fácil de criar uma stream é usando a função predefinida open
.
Exemplo: ler arquivo de texto.
f = open('../etc/icd.yml')
f
<_io.TextIOWrapper name='../etc/icd.yml' mode='r' encoding='UTF-8'>
Comentários:
Por padrão,
open
abre um arquivo em modo de leitura.O arquivo é especificado pelo seu caminho no disco.
f
é um objeto abstrato que mostra o esquema de codificação usado para decodificar arquivo. Quando não especificado, oencoding
padrão do sistema é utilizado.Percebe-se que o encoding padrão do sistema é
UTF-8
.
Exemplo: ler arquivo de texto com especificação de modo.
# o arquivo aqui é uma imagem PNG
o = open('../database/eyes/SyntEyeKTC-106_a-net.png',mode='r')
o
<_io.TextIOWrapper name='../database/eyes/SyntEyeKTC-106_a-net.png' mode='r' encoding='UTF-8'>
Comentários:
O segundo argumento de
open
émode
, no qual especificamos o modo com que o arquivo será tratado.Para modo de leitura, usamos
'r'
; para modo de escrita, usamos'w'
; para modo de apensamento, usamos'a'
e, para modo binário, usamos'b'
. A seguir, veremos mais exemplos.É importante notar que tanto
f
quantoo
estão “abertos” e devem ser fechadas imediatamente após o uso para evitar consumo desnecessário de memória e outros problemas.
# as streams 'f' e 'o' estão abertas
f.closed,o.closed
(False, False)
# fechando streams
f.close(), o.close()
# verifica novamente que foram fechadas
f.closed,o.closed
(True, True)
Exemplo: Ler arquivo e armazenar conteúdo.
with open('../etc/icd.yml') as f:
content = f.read()
# imprime conteúdo do arquivo
print(content)
# Arquivo YAML para construir o ambiente 'icd'
name: icd # nome do ambiente
channels: # lista de canais a utilizar
- defaults # canais padrão
- conda-forge
dependencies: # pacotes dependentes
- numpy
- scipy
- sympy
- matplotlib
- pandas
- seaborn
# não é necessário fechar!
f.closed
True
Comentários:
A keyword
with
possibilita que o fluxo de arquivos seja facilitado.Com a keyword
with
o arquivo é automaticamente fechado.É possível gerir a abertura de mais de um arquivo com apenas uma chamada de
with
usando a seguinte instrução
with open() as a, open() as b:
Note
Para saber mais acerca de with
, consulte o PEP 343.
with open('../etc/icd.yml','r') as f, open('../database/bras-cubas.txt') as g:
content_f = f.read()
content_g = g.read()
# opera sobre conteúdos
len(content_f),type(content_g)
(266, str)
Modos de operação de arquivos¶
Antes de prosseguirmos, vamos resumir os principais modos com os quais um arquivo é operado em Python.
Caracter |
Significado |
---|---|
|
abre para leitura (padrão) |
|
abre para escrita, truncando o arquivo |
|
cria um novo arquivo e o abre para escrita |
|
abre para escrita apensando conteúdo no fim do arquivo |
|
abre em modo binário |
|
abre em modo texto (padrão) |
Para uma discussão mais ampla sobre todas as possibilidades, consulte este post e este.
Exemplo: Ler arquivo linha por linha.
with open('../database/bras-cubas.txt','rt',encoding='utf-8') as f:
for linha in f:
print(linha.lower())
não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado. talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas. o que por agora importa saber é que virgília — chamava-se virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito. o estranho levantou-se e saiu. era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. saiu; virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra. quem diria? de dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados. virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de são joão, na tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.
Discussão:
Note que especificar o modo
'rt'
é totalmente decorativo, já que são as opções padrão.O parâmetro
encoding
indica a codificação na qual o texto deve ser lido.UTF-8
é o padrão. Entretanto, veremos adiante outros sistemas de encoding de caracteres.
Escrita de arquivos de texto¶
Escrever conteúdo para arquivos é uma das tarefas mais frequentes do processamento de dados. Para escrever conteúdo em um arquivo, devemos assumir ou que ele é inexistente e precisa ser criado, ou que ele existe e queremos adicionar informações nele.
Exemplo: escrever um simulacro de “jogo da velha” com caracteres em um arquivo .txt
.
def velha(n):
'''Simula um jogo da velha fictício.'''
from random import sample
c = ['o','x']
l = f' - - - \n'
ll = l
for _ in range(3):
l += f'| {sample(c,1)[0]} | {sample(c,1)[0]} | {sample(c,1)[0]} |\n'
l += ll # sobrecarga de +
l = f'Game: #{n}\n' + l + '\n'
return l
# cria log de n partidas
def n_games_log(n):
with open('../etc/velha-log.txt','w') as v:
for i in range(1,n+1):
v.write(velha(i)) # escreve em arquivo
# executa função para 4 partidas
n_games_log(4)
Para visualizarmos o conteúdo do arquivo podemos usar a função cat
via terminal ou abrir uma nova stream de leitura (exercício):
!cat '../etc/velha-log.txt'
Game: #1
- - -
| x | o | o |
| x | x | o |
| o | o | o |
- - -
Game: #2
- - -
| x | x | o |
| x | o | x |
| o | o | x |
- - -
Game: #3
- - -
| o | o | x |
| x | x | x |
| o | o | x |
- - -
Game: #4
- - -
| o | o | x |
| o | x | o |
| x | o | o |
- - -
Exemplo: escrever arquivo e apensar dados.
# escreve
with open('../etc/lista.txt','w') as f:
f.write( str(list(range(5))).strip('[]')) # escreve str e purga '[' e ']'
!cat ../etc/lista.txt
0, 1, 2, 3, 4
# abre para apensar
with open('../etc/lista.txt','a') as f:
f.write(', ') # o que temos sem isto?
f.write(str(list(range(5,11))).strip('[]'))
!cat ../etc/lista.txt
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Comentários:
No modo de apensamento, o arquivo original não é sobrescrito, mas alterado em seu final.
No exemplo anterior, incluímos a complementaridade da sequência de inteiros de 5 a 10 que não estava no arquivo anterior.
Exemplo: apensar em arquivo redirecionando a saída de print
.
with open('../etc/lista.txt','a') as f:
print('!!!',file=f) # mesmo objeto f
!cat ../etc/lista.txt
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10!!!
Comentário:
Neste exemplo, a string
'!!!'
, que seria impressa na tela porprint
, é apensada no arquivolista.txt
via redirecionamento.
Hint
Em ambiente UNIX, o redirecionamento pode ser feito via Terminal com o operador >
e o apensamento, com >>
. Por exemplo, o comando cat > lista.txt
cria um arquivo vazio e redireciona as linhas digitáveis em tela para o arquivo. O comando cat >> lista.txt
, por outro lado, permite que mais linhas digitáveis em tela sejam adicionadas ao arquivo. Veja um exemplo aqui.
Exemplo: escrever para arquivo com redirecionamento, star expression e sem with
.
dado = ('graus C',16,22.5) # tupla com diferentes dados
f = open('../etc/lista-2.txt','w')
print(*dado,sep=',',file=f)
f.close() # sem 'with', é necessário fechar a stream
!cat ../etc/lista-2.txt
graus C,16,22.5
Discussão:
Neste exemplo,
*dado
desempacota a tupla – que tem tipos de dado diferentes –, separa os elementos por,
, imprime na tela e redireciona para o arquivo de textolista-2.txt
.Observe que o arquivo é aberto em modo de escrita.
Escrita de arquivos binários¶
Exemplo: pré-visualizar um arquivo PDF no Jupyter Notebook.
from IPython.display import IFrame
IFrame('../etc/logo-icd.pdf',width=500,height=500)
Comentários:
Arquivos PDF, assim como áudios, imagens e executáveis, possuem conteúdo em formato binário. O que visualizamos acima é compreensível por humanos.
O exemplo a seguir mostra o real conteúdo do arquivo PDF em termos de bytes.
Warning
Tente reproduzir este exemplo com o Jupyter Notebook executando localmente em sua máquina. Em alguns navegadores, o livro online pode não reproduzir esta pré-visualização.
Exemplo: ler arquivo binário e imprimir seu conteúdo.
# modo 'rb'
with open('../etc/logo-icd.pdf','rb') as f:
pdf = f.read()
# 200 primeiros caracteres
pdf[:200]
b'%PDF-1.3\n%\xc4\xe5\xf2\xe5\xeb\xa7\xf3\xa0\xd0\xc4\xc6\n3 0 obj\n<< /Filter /FlateDecode /Length 87 >>\nstream\nx\x01+T\x08T(T0\x00BSKS\x05\x0b\x13#\x85\xa2T\x85p\x85<\x05\xfd\x80\xd4\xa2\xe4\xd4\x82\x92\xd2\xc4\x1c\x85\xa2L\xa0\x1acCc=S\xb0Jc\x03K=sS\x05C\x13\x03\x10edf\xa6ghd\xca\x95\x9c\xab\xa0\xef\x99k\xa8\xe0\x92\x0f42\x10\x00\xc7\xbe\x15)\nendstream\nendobj\n1 0 obj\n<< /Type /Pa'
# verifica tipo da variável
type(pdf)
bytes
Comentários:
A variável
pdf
guarda dados em formato binário.O prefixo
'b'
sugere que o tipo de dado é binário e está “codificado” em linguagem de bytes.Perceba que nós, humanos, enxergamos caracteres, mas o computador enxerga apenas bytes.
Exemplo: escrever arquivo binário.
# modo 'wb'
with open('../etc/logo-icd-part.pdf','wb') as f:
f.write(pdf[:200])
from IPython.display import IFrame
IFrame('../etc/logo-icd-part.pdf',width=500,height=500)
Discussão:
O conteúdo parcial tomado da string de bytes
'pdf'
é escrito em um segundo arquivo.Ao ler o conteúdo e tentar visualizá-lo, um erro será lançado. Isto é naturalmente esperado, visto que a sequência de bytes foi, propositalmente, danificada.
Warning
Tente reproduzir este exemplo com o Jupyter Notebook executando localmente em sua máquina. Em alguns navegadores, o livro online pode não reproduzir esta pré-visualização.
Como evitar sobrescrição acidental¶
Quando temos um arquivo existente no disco e operamos com escrita de arquivos com nomes similares, é altamente provável que sobrescrevamos o conteúdo daquele arquivo acidentalmente. Para evitar este problema, podemos usar o modo 'x'
, que garante a “exclusividade” do arquivo existente.
Exemplo: escrever arquivo em modo de “exclusividade”.
with open('../etc/lista.txt','x') as f:
print('???',file=f) # mesmo objeto f
---------------------------------------------------------------------------
FileExistsError Traceback (most recent call last)
<ipython-input-20-692bb94f1e63> in <module>
----> 1 with open('../etc/lista.txt','x') as f:
2 print('???',file=f) # mesmo objeto f
FileExistsError: [Errno 17] File exists: '../etc/lista.txt'
Discussão:
Tentamos escrever outro conteúdo para o arquivo
lista.txt
, o que geraria uma sobrescrição de seu conteúdo original.Utilizando o modo
'x'
, o interpretador se encarrega de verificar se o arquivo existe e só permite que a operação seja concluída em caso negativo.Como o arquivo existe no disco, um erro de
FileExistsError
é lançado.O próximo exemplo é bem-sucedido, visto que
lista-3.txt
não existe ainda no disco.
with open('../etc/lista-3.txt','x') as f:
print('OK!',file=f) # mesmo objeto f
!cat ../etc/lista-3.txt
OK!
# remove o arquivo para reproduzir teste
!rm ../etc/lista-3.txt
# verifica remoção
!cat ../etc/lista-3.txt
cat: ../etc/lista-3.txt: No such file or directory
Testando a existência de arquivos¶
Uma forma de verificar a existência de arquivos no disco é valer-se do módulo os
– discutiremos um pouco sobre este módulo à frente –. O exemplo a seguir praticamente alcança o mesmo objetivo que o modo 'x'
ao checar a pré-existência de arquivos.
from os.path import exists
if exists('../etc/lista-3.txt'):
print('O arquivo existe... Pé no freio... :( ')
else:
print('O arquivo não existe! Pé na tábua! 8) ')
O arquivo não existe! Pé na tábua! 8)
Note
O módulo os
fornece meios poderosos para navegar pelo sistema operacional e é bastante útil na leitura e escrita de arquivos.
Leitura e escrita de arquivos comprimidos¶
A compressão de dados baseia-se em reduzir a quantidade de bits utilizadas para armazenar os dados, mas assegurar a sua integridade genuína.
Em Python, temos à disposição, por exemplo, os módulos bz2
e gzip
para operar com arquivos comprimidos. Ambos são baseados na biblioteca zlib. A seguir, veremos exemplos de como podemos manipular arquivos comprimidos.
Exemplo: escrever arquivos comprimidos.
# sem compressão
with open('../etc/texto.txt','w') as f:
f.write('[]'*500000)
# compressão com bz2
import bz2
with bz2.open('../etc/texto.bz2','w') as f:
f.write(b'[]'*500000) # deve ser string de bytes
# compressão com gzip
import gzip
with gzip.open('../etc/texto.gz','w') as f:
f.write(b'[]'*500000) # deve ser string de bytes
! ls -l ../etc/texto.*
-rw-r--r--@ 1 gustavo staff 73 Sep 2 12:36 ../etc/texto.bz2
-rw-r--r--@ 1 gustavo staff 1011 Sep 2 12:36 ../etc/texto.gz
-rw-r--r--@ 1 gustavo staff 1000000 Sep 2 12:36 ../etc/texto.txt
Comentários:
Note a diferença no tamanho dos arquivos. A taxa de compressão é gigantesca! Os arquivos comprimidos por
bz2
egzip
possuem 73 e 1011 bytes de tamanho, nesta ordem, ao passo que o não comprimido possui 1.000.000 de bytes (1 MB) de tamanho.Para realizarmos a leitura dos arquivos, basta alterar o modo de
'w'
para'r'
.
O módulo os
¶
Ao trabalharmos com leitura e escrita de arquivos, é importante saber navegar pelo sistema operacional, ou listando diretórios, seja criando arquivos em lote, seja buscando por extensões específicas. Com o módulo os
, podemos realizar uma série de operações para manipular caminhos de arquivos. Abaixo, discutimos algumas funções desse módulo.
Exemplo: manipular caminhos para coletar informações de diretórios.
import os
arq = '../database/bras-cubas.txt'
# última parte do caminho
os.path.basename(arq)
'bras-cubas.txt'
# diretório
os.path.dirname(arq)
'../database'
# cria caminho unindo partes
os.path.join('pasta','subpasta',os.path.basename(arq))
'pasta/subpasta/bras-cubas.txt'
# separa basename e extensão
os.path.splitext(arq)
('../database/bras-cubas', '.txt')
# separa pasta e nome
os.path.split(arq)
('../database', 'bras-cubas.txt')
Exemplo: realizar testes para verificar tipos de arquivo.
# testa se é arquivo
os.path.isfile('../etc/velha-log.txt')
True
# testa se é diretório
os.path.isdir('../etc/icd.yml')
False
Comentários:
Com o submódulo
os.path
, podemos acessar praticamente toda a hierarquia de arquivos e diretórios no sistema operacional.
Coletando metadados de arquivos¶
Metadados dos arquivos, tais como tamanho e data de modificação, podem ser obtidos por meio do módulo os
também.
Exemplo: obtendo tamanho do arquivo.
# no. de bytes
os.path.getsize('../etc/logo-icd.pdf')
23496
import time
time.ctime(os.path.getmtime('../etc/logo-icd.pdf'))
'Fri Aug 27 23:26:13 2021'
Discussão:
A função
getmtime
retorna a informação temporal da última modificação do arquivo especificado.O módulo
time
fornece funções para manipular quantidades em unidade de tempo.A função
ctime
converte uma medida de tempo em segundos para uma string de data/hora de acordo com as configurações locais da máquina.
Exemplo: listar diretórios.
os.listdir('../etc/')
['velha-log.txt',
'texto.bz2',
'texto.txt',
'icd.yml',
'texto.gz',
'logo-icd-part.pdf',
'.ipynb_checkpoints',
'lista-2.txt',
'lista.txt',
'logo-icd.pdf']
Exemplo: buscar por todos os arquivos comuns em um diretório.
pasta = '../etc/'
arqs = [a for a in os.listdir(pasta)
if os.path.isfile(os.path.join(pasta,a))]
arqs
['velha-log.txt',
'texto.bz2',
'texto.txt',
'icd.yml',
'texto.gz',
'logo-icd-part.pdf',
'lista-2.txt',
'lista.txt',
'logo-icd.pdf']
Exemplo: buscar por todos os diretórios.
pasta = '../'
dirs = [a for a in os.listdir(pasta)
if os.path.isdir(os.path.join(pasta,a))]
dirs
['database',
'papers',
'figs',
'etc',
'ipynb',
'todo',
'_build',
'.ipynb_checkpoints',
'.git',
'rise']
Exemplo: buscar por todos os arquivos de uma dada extensão.
# lista apenas .txt
txts = [a for a in os.listdir('../etc/')
if a.endswith('.txt')]
txts
['velha-log.txt', 'texto.txt', 'lista-2.txt', 'lista.txt']
Codificação e decodificação de caracteres¶
Os caracteres que vemos impressos na tela de um dispositivo digital são apenas símbolos renderizados, isto é “marcas”. Como sabemos, um computador entende apenas uma linguagem binária. Isto significa que antes de um caracter ser mostrado exatamente como esperamos, digamos, em uma “tela”, é necessário que ele seja processado, grosso modo, por duas “camadas abstratas”. Uma é a camada de armazenamento, que associa um número binário ao caracter; a outra, é a camada textual, que usa pontos de código.
Existem vários sistemas de codificação de caracteres. Alguns bem conhecidos são ASCII, ISO-8859, CP-1252 e UTF-8. Entretanto, atualmente, o sistema UTF-8 se destaca pelo uso largamente difundido. No passado, devido à diferença de sistemas de codificação, o número que representava um caracter em um sistema não era o mesmo em outro sistema. Por isso, o padrão [Unicode] propôs a definição de um único número para cada caracter, permanente, e que valesse independentemente de plataforma, programa ou linguagem. Assim, hoje em dia, a melhor definição de caracter que existe é a de um caracter Unicode.
A identidade de um caracter Unicode é o seu ponto de código (code point), um número de 0 a 1.114.111 (em base 10) mostrado no padrão Unicode. O padrão Unicode é formado por 4 a 6 dígitos hexadecimais seguidos do prefixo “U+”. A tabela abaixo mostra alguns exemplos de caracteres, seu ponto de código e nome no padrão Unicode.
Caracter |
Ponto de código |
Nome Unicode |
---|---|---|
â |
U+00E2 |
LATIN SMALL LETTER A WITH CIRCUMFLEX |
ݔ |
U+0754 |
ARABIC LETTER BEH WITH TWO DOTS BELOW AND DOT ABOVE |
😛 |
U+1F61B |
FACE WITH STUCK-OUT TONGUE |
ぎ |
U+304E |
HIRAGANA LETTER GI |
Para imprimir em tela caracteres Unicode como os da tabela acima, temos duas maneiras:
usando uma string Unicode hexadecimal de 32-bits, em cujo caso escrevemos uma string iniciada por
\U
acompanhada de 8 caracteres. Os últimos caracteres correspondem ao ponto de código e as posições anteriores são preenchidas com 0. Neste caso, os caracteres acima poderiam ser impressos com:
'\U000000E2','\U00000754','\U0001f61b','\U0000304e'
usando o nome Unicode do caracter, em cujo caso escrevemos uma string iniciada por
\N
acompanhada do nome exato do caracter confinado entre chaves. Neste caso, os caracteres acima poderiam ser impressos com:
'\N{LATIN SMALL LETTER A WITH CIRCUMFLEX}',
'\N{ARABIC LETTER BEH WITH TWO DOTS BELOW AND DOT ABOVE}',
'\N{FACE WITH STUCK-OUT TONGUE}',
'\N{HIRAGANA LETTER GI}'
Todos os caracteres Unicode são encontrados em tabelas separadas por classes (planes), as chamadas [Code Charts].
bytes x texto¶
A conversão de pontos de código em bytes é chamada de codificação, ou encoding, ao passo que a conversão de bytes em pontos de código é chamada de decodificação, ou decoding.
Exemplo: codificação e decodificação.
s = 'balé'
len(s)
4
b = s.encode('utf8')
b
b'bal\xc3\xa9'
# caracter 'é' representado por dois bytes
# \xc3 e \xa9
len(b)
5
b.decode('utf8')
'balé'
Discussão:
encode
leva o texto para bytes.b'
indica uma string literal de bytes.bal
está no intervalo ASCII imprimível, enquanto que\xc3
e\xa9
não estão.decode
leva de bytes para texto.
Comentários:
O próprio caracter ASCII é usado para bytes no intevalo imprimível.
Para bytes correspondendo ao TAB, newline, carriage return e contrabarra, as sequências de escape
\t
,\n
,\r
e\\
são usadas.Para qualquer outro byte, usa-se uma sequência de escape hexadecimal.
Exemplo: decodificação para outros sistemas.
# erro!
# 'é' não é ASCII
s.encode().decode('ascii')
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-45-036775282faf> in <module>
1 # erro!
2 # 'é' não é ASCII
----> 3 s.encode().decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)
s.encode().decode('iso8859')
'balé'
s.encode().decode('cp1252')
'balé'