TLDR: Leitura e escrita de Unicode em arquivos com Python 2 e 3

Published on August 23, 2018

Conversando no Telegram com o Mário Sérgio sobre problemas que surgem ao migrar código entre versões do Python me veio a ideia de escrever este tldr para ajudar quem precisa fazer leitura ou escrita de arquivos que contenham texto com caracteres Unicode que não estão presentes na tabela ASCII como caracteres acentuados, alfabetos diferentes do romano e emoji, através de um código que funcione em ambas versões.

No Python 2 não existe uma distinção entre byte e string, o que faz com que códigos que lidam com entrada e saída sem o devido cuidado com codificação e decodificação funcionem sem nenhum erro aparente. Mas quando ele acontece deixa muita gente perdida com mensagens deste tipo:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)

Mudando para Python 3, onde byte e string são tipos distintos, o código normalmente para de funcionar ou funciona de forma estranha, misturando o texto com a representação textual da codificação:

'ol\xc3\xa1'

A solução para escrever um código que rode em ambas versões sem comportamentos inesperados é seguir o zen do python onde diz que explícito é melhor que implícito e sempre definir de forma clara o que é byte do que é string desta forma:

encode decode

O código abaixo abre um arquivo temporário para escrita em byte e escreve nele uma string que foi codificada em byte usando o padrão utf-8. Em seguida é feito o processo inverso, abrindo o arquivo para leitura em byte e decodificando seu conteúdo em string para imprimir na tela:

# -*- coding: utf-8 -*-

from __future__ import print_function, unicode_literals

import tempfile

unicode_file = tempfile.mktemp()

# escrita
with open(unicode_file, 'wb') as f:
    f.write('こんにちは'.encode('utf-8'))

# leitura
with open(unicode_file, 'rb') as f:
    print(f.read().decode('utf-8'))

O resultado é um código que roda tanto em Python 2 quanto em Python 3:

rodando o código

Esta dica também funciona para outros padrões de codificação como latin-1 por exemplo