7. Entrada e Saída
******************

Existem várias maneiras de apresentar a saída de um programa; os dados
podem ser exibidos 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 do tipo arquivo; o arquivo saída padrão pode ser referenciado
como "sys.stdout". Veja a Referência da Biblioteca Python para mais
informações sobre isso.)

Muitas vezes se deseja mais controle sobre a formatação da saída do
que simplesmente exibir valores separados por espaço. Existem várias
maneiras de formatar a saída.

* Para usar strings literais formatadas, comece uma string com "f" ou
  "F", antes de abrir as aspas ou aspas triplas. Dentro dessa string,
  pode-se escrever uma expressão Python entre caracteres "{" e "}",
  que podem se referir a variáveis, ou valores literais.

     >>> ano = 2016
     >>> evento = 'Referendo'
     >>> f'Resultados do {evento} {ano}'
     'Resultados do Referendo 2016'

* O método de strings "str.format()" requer mais esforço manual. Ainda
  será necessário usar "{" e "}" para marcar onde a variável será
  substituída e pode-se incluir diretivas de formatação detalhadas,
  mas também precisará incluir a informação a ser formatada. No bloco
  de código a seguir há dois exemplos de como formatar variáveis:

     >>> votos_sim = 42_572_654
     >>> votos_totais = 85_705_149
     >>> porcentagem = votos_sim / votos_totais
     >>> '{:-9} votos SIM  {:2.2%}'.format(votos_sim, porcentagem)
     ' 42572654 votos SIM  49.67%'

  Observe como "yes_votes" são preenchidos com espaços e um sinal
  negativo apenas para números negativos. O exemplo também imprime
  "percentage" multiplicado por 100, com 2 casas decimais e seguido
  por um sinal de porcentagem (veja Minilinguagem de especificação de
  formato para detalhes).

* Finalmente, pode-se fazer todo o tratamento da saída usando as
  operações de fatiamento e concatenação de strings para criar
  qualquer layout que se possa imaginar. O tipo string possui alguns
  métodos que realizam operações úteis para preenchimento de strings
  para uma determinada largura de coluna.

Quando não é necessário sofisticar a saída, mas apenas exibir algumas
variáveis com propósito de depuração, pode-se converter qualquer valor
para uma string com as funções "repr()" ou "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 = 'Olá, mundo.'
   >>> str(s)
   'Olá, mundo.'
   >>> repr(s)
   "'Olá, mundo.'"
   >>> str(1/7)
   '0.14285714285714285'
   >>> x = 10 * 3.25
   >>> y = 200 * 200
   >>> s = 'O valor de x é ' + repr(x) + ' e de y é ' + repr(y) + '...'
   >>> print(s)
   O valor de x é 32.5 e de y é 40000...
   >>> # A repr() da string adiciona aspas e contrabarras:
   >>> olá = 'olá, mundo\n'
   >>> olás = repr(olá)
   >>> print(olás)
   'olá, mundo\n'
   >>> # O argumento de repr() pode ser qualquer objeto Python:
   >>> repr((x, y, ('spam', 'ovos')))
   "(32.5, 40000, ('spam', 'ovos'))"

O módulo "string" contém uma classe "Template" que oferece ainda outra
maneira de substituir valores em strings, usando espaços reservados
como "$x" e substituindo-os por valores de um dicionário, mas oferece
muito menos controle da formatação.


7.1.1. Strings literais formatadas
----------------------------------

Strings literais formatadas (também chamadas f-strings, para abreviar)
permite que se inclua o valor de expressões Python dentro de uma
string, prefixando-a com "f" ou "F" e escrevendo expressões na forma
"{expression}".

Um especificador opcional de formato pode ser incluído após a
expressão. Isso permite maior controle sobre como o valor é formatado.
O exemplo a seguir arredonda pi para três casas decimais:

   >>> import math
   >>> print(f'O valor de pi é aproximadamente {math.pi:.3f}.')
   O valor de pi é aproximadamente 3.142.

Passando um inteiro após o "':'" fará com que o campo tenha um número
mínimo de caracteres de largura. Isso é útil para alinhar colunas.

   >>> tabela = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
   >>> for nome, telefone in tabela.items():
   ...     print(f'{nome:10} ==> {telefone:10d}')
   ...
   Sjoerd     ==>       4127
   Jack       ==>       4098
   Dcab       ==>       7678

Outros modificadores podem ser usados para converter o valor antes de
ser formatado. "'!a'" aplica a função "ascii()", "'!s'" aplica a
função "str()" e "'!r'" aplica a função "repr()"

   >>> animais = 'enguias'
   >>> print(f'Meu hovercraft está cheio de {animais}.')
   Meu hovercraft está cheio de enguias.
   >>> print(f'Meu hovercraft está cheio de {animais!r}.')
   Meu hovercraft está cheio de 'enguias'.

O especificador "=" pode ser usado para expandir uma expressão para o
texto da expressão, um sinal de igual e, então, a representação da
expressão avaliada:

>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'

Veja expressões autodocumentadas para mais informações sobre o
especificador "=". Para obter uma referência sobre essas
especificações de formato, consulte o guia de referência para a
Minilinguagem de especificação de formato.


7.1.2. O método format()
------------------------

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

   >>> print('Nós somos os {} que dizem "{}!"'.format('cavaleiros', 'Ni'))
   Nós somos os cavaleiros que dizem "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} e {1}'.format('spam', 'ovos'))
   spam e eggs
   >>> print('{1} e {0}'.format('spam', 'ovos'))
   eggs e spam

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

   >>> print('Este {comida} está {adjetivo}.'.format(
   ...       comida='spam', adjetivo='absolutamente horrível'))
   Este spam está absolutamente horrível.

Argumentos posicionais e nomeados podem ser combinados à vontade:

   >>> print('A história de {0}, {1} e {outro}.'.format('Bill', 'Manfred',
   ...                                                    outro='Georg'))
   A história de Bill, Manfred e Georg.

Se uma string de formatação é muito longa, e não se deseja quebrá-la,
pode ser bom fazer referência 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.

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

Isto também pode ser feito passando o dicionário "table" como
argumentos nomeados com a notação "**".

   >>> tabela = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
   >>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**tabela))
   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:

   >>> tabela = {k: str(v) for k, v in vars().items()}
   >>> mensagem = " ".join([f'{k}: ' + '{' + k +'};' for k in tabela.keys()])
   >>> print(mensagem.format(**tabela))
   __name__: __main__; __doc__: None; __package__: None; __loader__: ...

Como exemplo, as linhas seguintes produzem um conjunto de colunas
alinhadas, com alguns inteiros e seus quadrados e cubos:

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

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


7.1.3. Formatação manual de string
----------------------------------

Aqui está a mesma tabela de quadrados e cubos, formatados manualmente:

   >>> for x in range(1, 11):
   ...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
   ...     # Observe o uso do 'end' na linha anterior
   ...     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

(Note que o espaço entre cada coluna foi adicionado pela forma que a
função "print()" funciona: sempre adiciona espaços entre seus
argumentos.)

O método "str.rjust()" justifica uma string à direita, num campo de
tamanho definido, acrescentando espaços à esquerda. De forma similar,
os métodos "str.ljust()", justifica à esquerda, e "str.center()", para
centralizar. Esses métodos não escrevem nada, apenas retornam uma nova
string. Se a string de entrada é muito longa, os métodos não truncarão
a saída, e retornarão a mesma string, sem mudança; isso vai atrapalhar
o layout da coluna, mas geralmente é melhor do que a alternativa, que
estaria distorcendo o valor. (Se realmente quiser truncar, sempre se
pode adicionar uma operação de fatiamento, como em "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'


7.1.4. Formatação de strings à moda antiga
------------------------------------------

O operador % (módulo) também pode ser usado para formatação de string.
Dado "formato % valores" (onde *formato* é uma string), as instâncias
de "%" em "formato" serão substituídas por zero ou mais elementos de
"valores". Essa operação é conhecida como interpolação de string. Por
exemplo:

   >>> import math
   >>> print('O valor de pi é aproximadamente %5.3f.' % math.pi)
   O valor de pi é aproximadamente 3.142.

Mais informação pode ser encontrada na seção Formatação de string no
estilo printf.


7.2. Leitura e escrita de arquivos
==================================

"open()" retorna um *objeto arquivo*, e é mais utilizado com dois
argumentos posicionais e um argumento nomeado: "open(filename, mode,
encoding=None)"

   >>> f = open('arquivo_de_trabalho', 'w', encoding="utf-8")

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. *modo* 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 argumento *modo* é opcional, em caso de omissão será
presumido "'r'".

Normalmente, arquivos são abertos no *modo texto*, o que significa que
você lê strings de e para o arquivo, o qual está em um codificação
específica. Se a *codificação* não for especificada, o padrão irá
depender da plataforma (veja "open()"). Como o UTF-8 é o padrão mais
moderno, "encoding="utf-8"" é recomendado a não ser que você precise
utilizar uma *codificação* diferente. Adicionando "'b'" ao modo irá
abrir o o arquivo em *modo binário*. Dados no modo binário são lidos e
escritos como objetos "bytes".  Você não pode especificar a
*codificação* quando estiver abrindo os arquivos em modo binário.

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.

É uma boa prática usar a palavra-chave "with" ao lidar com arquivos. A
vantagem é que o arquivo é fechado corretamente após o término de sua
utilização, mesmo que uma exceção seja levantada em algum momento.
Usar "with" também é muito mais curto que escrever seu bloco
equivalente "try"-"finally":

   >>> with open('arquivo_de_trabalho', encoding="utf-8") as f:
   ...     read_data = f.read()

   >>> # Podemos verificar se o arquivo foi fechado automaticamente.
   >>> f.closed
   True

Se você não está usando a palavra reservada "with", então você deveria
chamar "f.close()" para fechar o arquivo e imediatamente liberar
qualquer recurso do sistema usado por ele.

Aviso:

  Chamar "f.write()" sem usar a palavra reservada "with" ou chamar
  "f.close()" **pode** resultar nos argumentos de "f.write()" não
  serem completamente escritos no disco, mesmo se o programa for
  encerrado com êxito.

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 objetos arquivo
---------------------------------

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

Para ler o conteúdo de um arquivo, chame "f.read(tamanho)", que lê um
punhado de dados devolvendo-os como uma string (em modo texto) ou
bytes (em modo binário). *tamanho* é um argumento numérico opcional.
Quando *tamanho* é omitido ou negativo, todo o conteúdo do arquivo é
lido e devolvido; se o arquivo é duas vezes maior que memória da
máquina, o problema é seu. Caso contrário, no máximo *tamanho*
caracteres (em modo texto) ou *tamanho* bytes (em modo binário) são
lidos e devolvidos. Se o fim do arquivo for atingido, "f.read()"
devolve uma string vazia ("''").

   >>> f.read()
   'Este é o arquivo inteiro.\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 terminar com uma quebra de
linha. Isso elimina a ambiguidade do valor retornado; se
"f.readline()" retorna uma string vazia, o fim do arquivo foi
atingido. Linhas em branco são representadas por um "'\n'" -- uma
string contendo apenas o caractere terminador de linha.

   >>> f.readline()
   'Está é a primeira linha do arquivo.\n'
   >>> f.readline()
   'Segunda linha do arquivo\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='')
   ...
   Esta é a primeira linha do arquivo.
   Segunda linha do arquivo

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('Este é um teste\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 = ('a resposta', 42)
   >>> s = str(value)  # converte a tupla para string
   >>> f.write(s)
   18

"f.tell()" retorna um inteiro dando a posição atual do objeto 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, use "f.seek(offset, de_onde)". A nova posição é
computada pela soma do deslocamento *offset* a um ponto de referência
especificado pelo argumento *de-onde*. Se o valor de *de_onde* é 0,a
referência é o início do arquivo, 1 refere-se à posição atual, e 2
refere-se ao fim do arquivo. Este argumento pode ser omitido e o valor
padrão é 0, usando o início do arquivo como referência.

   >>> f = open('arquivo_de_trabalho', 'rb+')
   >>> f.write(b'0123456789abcdef')
   16
   >>> f.seek(5)      # Vai até o 6º byte no arquivo
   5
   >>> f.read(1)
   b'5'
   >>> f.seek(-3, 2)  # Vai até o 3º byte antes do fim
   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étodos 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 estruturados 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 troca de dados. Pessoas que programam já estão
  familiarizadas 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
   >>> x = [1, 'lista', 'simples']
   >>> json.dumps(x)
   '[1, "lista", "simples"]'

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 objeto *arquivo
binário* ou *arquivo texto* que foi aberto para leitura:

   x = json.load(f)

Nota:

  Arquivos JSON devem ser codificados em UTF-8. Use "encoding="utf-8""
  quando abrir um arquivo JSON como um *arquivo texto* tanto para
  leitura quanto para escrita.

Essa técnica de serialização simples pode manipular listas e
dicionários, mas a serialização de instâncias de classes 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 linguagens. 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.
