7. Entrada e Saída

Existem várias maneiras de apresentar a saída de um programa; os dados podem ser exibidos ou impressos em forma legível para seres humanos, ou escritos em arquivos para uso posterior. Este capítulo apresentará algumas das possibilidades.

7.1. Refinando a formatação de saída

Até agora vimos duas maneiras de exibir valores: expressões e a função print(). (Uma outra maneira é utilizar o método write() de objetos arquivo; o arquivo saída padrão pode ser referenciada como sys.stdout. Veja a Referência da Biblioteca Python para mais informações sobre isto.)

Frequentemente é desejável mais controle sobre a formatação de saída do que simplesmente exibir valores separados por espaços. Existem duas formas de formatar a saída. A primeira é manipular strings através de fatiamento (slicing) e concatenação. Os tipos string têm métodos úteis para criar strings com tamanhos determinados, usando caracteres de preenchimento; eles serão apresentados a seguir. A segunda forma é usar o método str.format().

O módulo string tem uma classe string.Template que oferece uma outra maneira de inserir valores em strings.

Permanece a questão: como converter valores para strings? Felizmente, Python possui duas maneiras de converter qualquer valor para uma string: as funções repr() e str().

A função str() serve para retornar representações de valores que sejam legíveis para as pessoas, enquanto repr() é para gerar representações que o interpretador Python consegue ler (ou levantará uma exceção SyntaxError, se não houver sintaxe equivalente). Para objetos que não têm uma representação adequada para consumo humano, str() devolve o mesmo valor que repr(). Muitos valores, tal como números ou estruturas, como listas e dicionários, têm a mesma representação usando quaisquer das funções. Strings, em particular, têm duas representações distintas.

Alguns exemplos:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

A seguir, duas maneiras de se escrever uma tabela de quadrados e cubos::

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

>>> for x in range(1, 11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(Note that in the first example, one space between each column was added by the way print() works: by default it adds spaces between its arguments.)

Esse exemplo demonstra o método str.rjust() de objetos string, que alinha uma string à direita juntando espaços adicionais à esquerda. Existem métodos análogas str.ljust() e str.center(). Esses métodos não exibem nada na tela, apenas devolvem uma nova string formatada. Se a entrada extrapolar o comprimento especificado, a string original é devolvida sem modificação; isso pode estragar o alinhamento das colunas, mas é melhor do que a alternativa, que seria apresentar um valor mentiroso. (Se for realmente desejável truncar o valor, pode-se usar fatiamento, por exemplo: x.ljust(n)[:n].)

Existe ainda o método str.zfill() que preenche uma string numérica com zeros à esquerda, e sabe lidar com sinais positivos e negativos:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

Um uso básico do método str.format() tem esta forma:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

As chaves e seus conteúdos (chamados campos de formatação) são substituídos pelos objetos passados para o método str.format(). Um número nas chaves pode ser usado para referenciar a posição do objeto passado no método str.format().

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

Se argumentos nomeados são passados para o método str.format(), seus valores serão referenciados usando o nome do argumento:

>>> print('This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.

Argumentos posicionais e nomeados podem ser combinados à vontade:

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                       other='Georg'))
The story of Bill, Manfred, and Georg.

'!a' (apply ascii()), '!s' (apply str()) and '!r' (apply repr()) can be used to convert the value before it is formatted:

>>> contents = 'eels'
>>> print('My hovercraft is full of {}.'.format(contents))
My hovercraft is full of eels.
>>> print('My hovercraft is full of {!r}.'.format(contents))
My hovercraft is full of 'eels'.

Após o identificador do campo, uma especificação de formato opcional pode ser colocada depois de : (dois pontos). O exemplo abaixo arredonda Pi até a terceira casa após o ponto decimal.

>>> import math
>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
The value of PI is approximately 3.142.

Colocar um inteiro n logo após o : fará o campo ocupar uma largura mínima de n caracteres. Isto é útil para organizar tabelas.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print('{0:10} ==> {1:10d}'.format(name, phone))
...
Jack       ==>       4098
Dcab       ==>       7678
Sjoerd     ==>       4127

Se uma string de formatação é muito longa, e não se deseja quebrá-la, pode ser bom referir-se aos valores a serem formatados por nome, em vez de posição. Isto pode ser feito passando um dicionário usando colchetes [] para acessar as chaves:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Isto também pode ser feito passando o dicionário como argumento do método, usando a notação **:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Isto é particularmente útil em conjunto com a função embutida vars(), que devolve um dicionário contendo todas as variáveis locais.

Para uma visão completa da formatação de strings com str.format(), veja a seção Sintaxe das strings de formato.

7.1.1. Formatação de strings à moda antiga

O operador % também pode ser usado para formatação de strings. O operando da esquerda é interpretado de forma semelhante ao estilo de formatação da função sprintf() da linguagem C, aplicando a formatação ao operando da direita, e devolvendo a string resultante. Por exemplo:

>>> import math
>>> print('The value of PI is approximately %5.3f.' % math.pi)
The value of PI is approximately 3.142.

Mais informação pode ser encontrada na seção Formatação de String no Formato printf-style.

7.2. Leitura e escrita de arquivos

A função open() devolve um file object, e é frequentemente usada com dois argumentos: open(filename, mode).

>>> f = open('workfile', 'w')

O primeiro argumento é uma string contendo o nome do arquivo. O segundo argumento é outra string, contendo alguns caracteres que descrevem o modo como o arquivo será usado. O parâmetro mode pode ser 'r' quando o arquivo será apenas lido, 'w' para escrever (se o arquivo já existir seu conteúdo prévio será apagado), e 'a' para abrir o arquivo para adição; qualquer escrita será adicionada ao final do arquivo. A opção 'r+' abre o arquivo tanto para leitura como para escrita. O parâmetro mode é opcional, em caso de omissão será assumido 'r'.

Normalmente, os arquivos são abertos em modo texto, ou seja, você lê e grava strings de e para o arquivo, numa codificação específica. Se a codificação não for especificada, o padrão é dependente da plataforma (consulte open()). 'b' anexado ao modo abre o arquivo em modo binário: agora os dados são lidos e escritos na forma de bytes. Este modo deve ser usado para todos os arquivos que não contenham texto.

Em modo texto, o padrão durante a leitura é converter terminadores de linha específicos da plataforma (\n no Unix, \r\n no Windows) para apenas \n. Ao escrever no modo de texto, o padrão é converter as ocorrências de \n de volta para os finais de linha específicos da plataforma. Essa modificação de bastidores nos dados do arquivo é adequada para arquivos de texto, mas corromperá dados binários, como arquivos JPEG ou EXE. Tenha muito cuidado para só usar o modo binário, ao ler e gravar esses arquivos.

It is good practice to use the with keyword when dealing with file objects. The advantage is that the file is properly closed after its suite finishes, even if an exception is raised at some point. Using with is also much shorter than writing equivalent try-finally blocks:

>>> with open('workfile') as f:
...     read_data = f.read()
>>> f.closed
True

Se você não estiver usando a palavra-chave with, então você deve chamar f.close() para fechar o arquivo e liberar imediatamente quaisquer recursos do sistema usados por ele. Se você não fechar explicitamente um arquivo, o coletor de lixo do Python irá eventualmente destruir o objeto e fechar o arquivo aberto para você, mas o arquivo pode ficar aberto por um tempo. Outro risco é que diferentes implementações de Python farão essa limpeza em momentos diferentes.

Depois que um arquivo é fechado, seja por uma instrução with ou chamando f.close(), as tentativas de usar o arquivo falharão automaticamente.

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

7.2.1. Métodos de arquivos

Para simplificar, o resto dos exemplos nesta seção assumem que um objeto arquivo chamado f já foi criado.

Para ler o conteúdo de um arquivo, chame f.read(size), que lê uma quantidade de dados e retorna-os como uma string (em modo texto) ou bytes (em modo binário). O argumento numérico size é opcional. Quando size é omitido ou negativo, todo o conteúdo do arquivo é lido e retornado; se o arquivo é duas vezes maior que a memória da máquina, o problema é seu. Caso contrário, no máximo size bytes serão lidos e retornados. Se o fim do arquivo for atingido, f.read() retornará uma string vazia (“”).

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

O método f.readline() lê uma única linha do arquivo; o caractere de quebra de linha (\n) é mantido ao final da string, e só é omitido na última linha do arquivo, se o arquivo não termina com uma quebra de linha. Isso elimina a ambiguidade do valor retornado; se f.readline() devolver uma string vazia, o fim do arquivo foi atingido. Linhas em branco são representadas por um '\n' – uma string contendo apenas o terminador de linha.

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

Uma maneira alternativa de ler linhas do arquivo é iterar diretamente pelo objeto arquivo. É eficiente, rápido e resulta em código mais simples:

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

Se desejar ler todas as linhas de um arquivo em uma lista pode-se usar list(f) ou f.readlines().

f.write(string) escreve o conteúdo de string para o arquivo, retornando o número de caracteres escritos.

>>> f.write('This is a test\n')
15

Outros tipos de objetos precisam ser convertidos – seja para uma string (em modo texto) ou para bytes (em modo binário) – antes de escrevê-los:

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

f.tell() retorna um inteiro dando a posição atual do objeto de arquivo, no arquivo representado, como número de bytes desde o início do arquivo, no modo binário, e um número ininteligível, quando no modo de texto.

Para mudar a posição do objeto arquivo, use f.seek(offset, from_what). A posição é calculada somando-se offset ao ponto de referência; o ponto de referência é selecionado a partir do argumento from_what. Um valor 0 para from_what indica o início do arquivo, 1 usa a posição atual no arquivo, e 2 usa o fim do arquivo como ponto de referência. from_what pode ser omitido e assumirá o valor 0, usando o início do arquivo como ponto de referência.

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'

Em arquivos texto (abertos sem um b, em modo string), somente seeks relativos ao início do arquivo serão permitidos (exceto se for indicado o final do arquivo, com seek(0, 2)) e o único valor válido para offset são aqueles retornados por chamada à f.tell(), ou zero. Qualquer outro valor para offset produz um comportamento indefinido.

Objetos arquivo tem alguns método adicionais, como isatty() e truncate() que não são usados com frequência; consulte a Biblioteca de Referência para um guia completo de objetos arquivo.

7.2.2. Gravando dados estruturados com json

Strings podem ser facilmente gravadas e lidas em um arquivo. Números dão um pouco mais de trabalho, já que o método read() só retorna strings, que terão que ser passadas para uma função como int(), que pega uma string como '123' e retorna seu valor numérico 123. Quando você deseja salvar tipos de dados mais complexos, como listas e dicionários aninhados, a análise e serialização manual tornam-se complicadas.

Ao invés de ter usuários constantemente escrevendo e depurando código para gravar tipos complicados de dados em arquivos, o Python permite que se use o popular formato de troca de dados chamado JSON (JavaScript Object Notation). O módulo padrão chamado json pode pegar hierarquias de dados em Python e convertê-las em representações de strings; esse processo é chamado serialização. Reconstruir os dados da representação string é chamado desserialização. Entre serializar e desserializar, a string que representa o objeto pode ser armazenada em um arquivo ou estrutura de dados, ou enviada por uma conexão de rede para alguma outra máquina.

Nota

O formato JSON é comumente usado por aplicativos modernos para permitir a troca de dados. Pessoas que programam já estão familiarizados com esse formato, o que o torna uma boa opção para interoperabilidade.

Um objeto x, pode ser visualizado na sua representação JSON com uma simples linha de código:

>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

Outra variação da função dumps(), chamada dump(), serializa o objeto para um arquivo texto. Se f é um arquivo texto aberto para escrita, podemos fazer isto:

json.dump(x, f)

Para decodificar o objeto novamente, se f é um arquivo texto que foi aberto para leitura:

x = json.load(f)

Essa técnica de serialização simples pode manipular listas e dicionários, mas a serialização de instâncias de classe arbitrárias no JSON requer um pouco mais de esforço. A referência para o módulo json contém uma explicação disso.

Ver também

O módulo pickle

Ao contrário do JSON, pickle é um protocolo que permite a serialização de objetos Python arbitrariamente complexos. Por isso, é específico do Python e não pode ser usado para se comunicar com aplicativos escritos em outros idiomas. Também é inseguro por padrão: desserializar dados de pickle, provenientes de uma fonte não confiável, pode executar código arbitrário, se os dados foram criados por um invasor habilidoso.