7. Os arquivos de texto

7.1. Script [fic_01]: leitura/gravação de um arquivo de texto
O script a seguir ilustra um exemplo de manipulação de arquivos de texto:
# importações
import sys
# criação e, em seguida, processamento sequencial de um arquivo de texto
# este é um conjunto de linhas no formato login:pwd:uid:gid:infos:dir:shell
# cada linha é inserida em um dicionário no formato login => uid:gid:infos:dir:shell
# --------------------------------------------------------------------------
def affiche_infos(dico: dict, clé: str):
# exibe o valor associado à chave no dicionário “dico”, caso ela exista
if clé in dico.keys():
# exibe o valor associado à chave
print(f"{clé} : {dico[clé]}")
else:
# a chave não é uma chave do dicionário “dico”
print(f"la clé [{clé}] n'existe pas")
# main -----------------------------------------------
# define-se o nome do arquivo
FILE_NAME = "./data/infos.txt"
# criação e preenchimento do arquivo de texto
fic = None
try:
# abertura do arquivo para gravação (w=write)
fic = open(FILE_NAME, "w")
# gera-se um conteúdo arbitrário
for i in range(1, 101):
# uma linha
ligne = f"login{i}:pwd{i}:uid{i}:gid{i}:infos{i}:dir{i}:shell{i}"
# é gravada no arquivo de texto
fic.write(f"{ligne}\n")
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# fecha-se o arquivo, caso tenha sido aberto
if fic:
fic.close()
# abre-se o arquivo para leitura
fic = None
try:
# abertura do arquivo para leitura
fic = open(FILE_NAME, "r")
# dicionário vazio inicialmente
dico = {}
# cada linha é inserida no dicionário [dico] no formato login => uid:gid:infos:dir:shell
# leitura da primeira linha, removendo os espaços no início e no final da linha
ligne = fic.readline().strip()
# desde que a linha não esteja vazia
while ligne != '':
# colocamos a linha em uma tabela
infos = ligne.split(":")
# recuperamos o login
login = infos[0]
# ignora-se a senha
infos[0:2] = []
# cria-se uma entrada no dicionário
dico[login] = infos
# leitura da próxima linha
ligne = fic.readline().strip()
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# fecha-se o arquivo, caso ele tenha sido aberto
if fic:
fic.close()
# utilização do dicionário
affiche_infos(dico, "login10")
affiche_infos(dico, "X")
Observações:
- linha 28: abertura do arquivo para gravação (w=write). Se o arquivo já existir, ele será sobrescrito;
- linhas 30-34: são geradas 100 linhas no arquivo de texto;
- linha 34: para gravar uma linha no arquivo de texto. O método [write] não adiciona o caractere de fim de linha. Portanto, é necessário incluí-lo no texto gravado;
- linhas 35-37: tratamento de uma eventual exceção;
- linha 37: interrupção da execução do script (contudo, após a execução da cláusula finally);
- linhas 38-41: em todos os casos, haja ou não erro, o arquivo é fechado se estiver aberto;
- linha 47: abertura do arquivo para leitura (r=read);
- linha 49: definição de um dicionário vazio;
- linha 52: o método [readline] lê uma linha de texto, incluindo o caractere de fim de linha. O método [strip] remove os “espaços” no início e no final da string. Por “espaço”, entenda-se caractere em branco, marca de fim de linha, quebra de página, tabulação e alguns outros. Portanto, neste caso, o [ligne] não terá os caracteres de fim de linha do [\r\n] (Windows) ou do [\n] (Unix);
- linha 54: o arquivo é processado até que seja encontrada uma linha vazia;
- linhas 54-64: o arquivo de texto é transferido para o dicionário [dico]. A chave é o campo [login], e o valor são os campos [uid:gid:infos:dir:shell];
- linhas 65-67: tratamento de uma eventual exceção;
- linhas 68-71: fechamento do arquivo em todos os casos, haja ou não erro;
- linhas 74-75: utilização do dicionário [dico];
O arquivo [data/infos.txt]:
Resultados na tela:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fichiers/fic_01.py
login10 : ['uid10', 'gid10', 'infos10', 'dir10', 'shell10']
la clé [X] n'existe pas
Process finished with exit code 0
7.2. Script [fic_02]: gerenciar arquivos de texto codificados em UTF-8
No restante deste documento, vamos lidar exclusivamente com arquivos de texto codificados em UTF-8. Primeiro, vamos configurar o PyCharm:

- em [5-6]: selecionar a codificação UTF-8 para os arquivos do projeto;
Para criar um arquivo codificado em UTF-8, pode-se proceder da seguinte forma (fic-02):
# importações
import codecs
# gravação em UTF-8 em um arquivo de texto
# não há tratamento de exceções
file=codecs.open("./data/utf8.txt","w","utf8")
file.write("Hélène est partie à Bâle pendant l'été chez sa grand-mère")
file.close()
Notas
- linha 2: para gerenciar a codificação dos arquivos, importa-se o módulo [codecs];
- linha 6: o método [codecs.open] é utilizado da mesma forma que a função clássica [open]. No entanto, é possível especificar a codificação desejada (criação) ou existente (leitura). Após a abertura, o objeto [file] obtido na linha 6 é utilizado como um arquivo convencional;
- linha 7: foram utilizados caracteres acentuados que, na maioria das vezes, apresentam representações diferentes dependendo do código de caracteres utilizado;
Resultados
Ao abrir o arquivo [data/utf8.txt] obtido (ver linha 6), obtém-se o seguinte resultado:

7.3. Script [fic_03]: gerenciar arquivos de texto codificados em ISO-8859-1
O script [fic_03] faz o mesmo que o script [fic_02], mas codifica o arquivo de texto em ISO-8859-1. Queremos mostrar a diferença entre os arquivos obtidos:
# importações
import codecs
# gravação em ISO-8859-1 em um arquivo de texto
# não há tratamento de exceções
file=codecs.open("./data/iso-8859-1.txt","w","iso-8859-1")
file.write("Hélène est partie à Bâle pendant l'été chez sa grand-mère")
file.close()
Ao abrir o arquivo [data/iso-8859-1] criado na linha 6, obtém-se o seguinte resultado:

Como configuramos o projeto para funcionar com arquivos UTF-8, o PyCharm tentou abrir o arquivo [iso-8859-1.txt] como UTF-8. Ele consegue identificar que o arquivo [1] não é o UTF-8. Em seguida, sugere que o arquivo [2] seja recarregado em outra codificação:

- em [3-5]: o arquivo é recarregado usando a codificação ISO-8859-1;

- em [6], o mesmo arquivo, mas exibido com uma codificação diferente;
Se voltarmos às configurações do projeto:

- vemos que, no [6-7], o PyCharm registrou que o arquivo [iso-8859-1.txt] deveria ser aberto com a codificação ISO-8859-1. Trata-se, portanto, de uma exceção à regra [5];
7.4. Script [json_01]: gerenciamento de um arquivo jSON
JSON significa JavaScript Object Notation. Como o próprio nome indica, trata-se de um modo de representação textual dos objetos da linguagem JavaScript. Vamos utilizá-lo aqui com objetos Python.
O arquivo jSON gerenciado por [data/in.json] será o seguinte:

- No [2], vemos que o conteúdo de texto do arquivo [in.json] poderia representar um dicionário Python. O PyCharm formatou (Ctrl-Alt-L) esse texto, mas, mesmo que ele estivesse em uma única linha, isso não mudaria nada. A formatação do texto não tem importância, desde que ele represente sintaticamente um objeto Python;
O script [json-01] mostra como utilizar esse arquivo:
# importações
import codecs
import json
import sys
# leitura/gravação de um arquivo jSON
inFile=None
outFile=None
try:
# abertura do arquivo jSON para leitura
inFile = codecs.open("./data/in.json", "r", "utf8")
# transferência do conteúdo para um dicionário
data = json.load(inFile)
# exibição dos dados lidos
print(f"data={data}, type(data)={type(data)}")
limites = data['limites']
print(f"limites={limites}, type(limites)={type(limites)}")
print(f"limites[1]={limites[1]}, type(limites[1])={type(limites[1])}")
# transferência do dicionário [data] para um arquivo JSON
outFile = codecs.open("./data/out.json", "w", "utf8")
json.dump(data, outFile)
except BaseException as erreur:
# exibe o erro e encerra
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# fechamento dos arquivos, caso estejam abertos
if inFile:
inFile.close()
if outFile:
outFile.close()
Notas
- linha 3: para trabalhar com o JSON, importamos o módulo [json];
- linha 11: vamos trabalhar com arquivos jSON codificados em UTF-8. Aqui, abrimos o arquivo [data/in.json] com o módulo [codecs];
- linha 13: o método [json.load] lê o conteúdo do arquivo jSON e o armazena na variável [data]. O tipo dessa variável será, neste caso, um dicionário;
- linhas 15-18: para mostrar que realmente obtivemos um dicionário Python, exibimos alguns de seus elementos;
- linhas 20-21: realizamos a operação inversa: o dicionário [data] é gravado em um arquivo com o nome UTF-8 por meio do método [json.dump];
- linhas 22-25: tratamento de uma eventual exceção;
- linhas 26-31: em todos os casos, haja ou não erro, fechamos os arquivos que possam ter sido abertos;
Resultados
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fichiers/json_01.py
data={'limites': [9964, 27519, 73779, 156244, 0], 'coeffR': [0, 0.14, 0.3, 0.41, 0.45], 'coeffN': [0, 1394.96, 5798, 13913.69, 20163.45], 'PLAFOND_QF_DEMI_PART': 1551, 'PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION': 21037, 'PLAFOND_REVENUS_COUPLE_POUR_REDUCTION': 42074, 'VALEUR_REDUC_DEMI_PART': 3797, 'PLAFOND_DECOTE_CELIBATAIRE': 1196, 'PLAFOND_DECOTE_COUPLE': 1970, 'PLAFOND_IMPOT_COUPLE_POUR_DECOTE': 2627, 'PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE': 1595, 'ABATTEMENT_DIXPOURCENT_MAX': 12502, 'ABATTEMENT_DIXPOURCENT_MIN': 437}, type(data)=<class 'dict'>
limites=[9964, 27519, 73779, 156244, 0], type(limites)=<class 'list'>
limites[1]=27519, type(limites[1])=<class 'int'>
Process finished with exit code 0
- as linhas 2-4 mostram que o dicionário presente no arquivo jSON foi recuperado corretamente;
Agora, vamos examinar o conteúdo do arquivo [data/out.json]:

O texto do arquivo está em uma única linha. No entanto, o PyCharm reconhece os arquivos jSON e é possível formatá-los, assim como os arquivos Python e outros, usando Ctrl-Alt-L. Obtemos então o seguinte:

7.5. Script [json_02]: gerenciamento de arquivos jSON codificados em UTF-8
Um arquivo jSON codificado em UTF-8 pode assumir duas formas:
# importações
import codecs
import json
import sys
# dicionário
data = {'marié': 'oui', 'impôt': 1340}
# gravação de um arquivo jSON
out_file1 = None
out_file2 = None
try:
# transferência do dicionário [data] para um arquivo JSON
out_file1 = codecs.open("./data/out1.json", "w", "utf8")
json.dump(data, out_file1, ensure_ascii=True)
# transferência do dicionário [data] para um arquivo JSON
out_file2 = codecs.open("./data/out2.json", "w", "utf8")
json.dump(data, out_file2, ensure_ascii=False)
except BaseException as erreur:
# exibe o erro e encerra o programa
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# fechamento dos arquivos, caso estejam abertos
if out_file1:
out_file1.close()
if out_file2:
out_file2.close()
…
- neste script, o dicionário [data] (linha 7) é gravado em dois arquivos jSON (linhas 14 e 17);
- linhas 14 e 17: nos dois casos, cria-se um arquivo de texto UTF-8;
- linhas 15: ao gravar o dicionário, utiliza-se o parâmetro denominado [ensure_ascii=True];
- linhas 18: ao gravar o dicionário, utiliza-se o parâmetro denominado [ensure_ascii=False];
Aqui estão os dois arquivos obtidos:

- no arquivo [out1.json], os caracteres acentuados foram substituídos por uma sequência de caracteres que representam seu código UTF-8. Às vezes, diz-se que eles foram “escapados”. Tecnicamente, no arquivo binário [out1.json], encontram-se, para o caractere “é” de [marié], sucessivamente os códigos binários UTF-8 dos 6 caracteres [\u00e9];
- no arquivo [out2.json], os caracteres acentuados foram mantidos como estavam. Isso significa que, no arquivo binário de [out2.json], esses caracteres são representados por seu código binário UTF-8 (apenas 1 código, UTF-8, em vez de 6 para out1). Para o caractere é de [marié], encontraremos, portanto, o código binário [00e9] em 4 bytes;
- é o valor do parâmetro [ensure_ascii] do método [json.dump] que determina o formato utilizado;
Algumas aplicações utilizam o UTF-8 “escapado” para seus arquivos jSON. Nesse caso, deve-se utilizar o valor [ensure_ascii=True]. Esse valor é, na verdade, o valor padrão. Portanto, se o parâmetro [ensure_ascii] não for utilizado, trabalhar-se-á com arquivos jSON e UTF-8 com caracteres de escape.
O script continua da seguinte maneira:
# importações
import codecs
import json
import sys
# dicionário
data = {'marié': 'oui', 'impôt': 1340}
…
# revisão dos arquivos jSON
in_file1 = None
in_file2 = None
try:
# transferência do arquivo jSON 1 para um dicionário
in_file1 = codecs.open("./data/out1.json", "r", "utf8")
dico1 = json.load(in_file1)
# exibição
print(f"dico1={dico1}")
# transferência do arquivo jSON 2 para um dicionário
in_file2 = codecs.open("./data/out2.json", "r", "utf8")
dico2 = json.load(in_file2)
# exibição
print(f"dico2={dico2}")
except BaseException as erreur:
# exibe o erro e encerra
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# fechamento dos arquivos, caso estejam abertos
if in_file1:
in_file1.close()
if in_file2:
in_file2.close()
Notas
- linhas 11-34: leitura dos dois arquivos [out1.json, out2.json] e exibição do dicionário lido em cada um dos casos;
Resultados
Surpreendentemente, verifica-se que não foi necessário especificar à função [json.load] (linhas 17, 22) o tipo de codificação (com ou sem escape) da sequência jSON a ser lida. Em ambos os casos, obtém-se o dicionário correto.