Back-End

24 ago, 2015

Strings em Python

Publicidade

Eu só entendi essa questão de string depois de ver o assunto no curso PyPrático, com o Luciano Ramalho. Segue o resumo do que entendi e fiquem à vontade para me corrigir 😉

Unicode é uma tabela ideal que liga números a caracteres. Pense nela como um grande dicionário onde a chave é o número e o caractere, seu valor. Ela não tem nada a ver com bytes e esse é o ponto primordial para entender strings. Os números são os conceitos ideais, sem respectiva codificação em bytes.

Encodings são forma de se codificar texto em termos de bytes. Exemplos de codificações famosas: ascii e utf-8.

No Python 2 existe uma certa confusão de strings. O padrão é ascii para qualquer string literal criada. E aqui mora o problema quando se quer usar caracteres especiais. Ex:

print 'ã'

Ao tentar executar você vai levar um erro:

SyntaxError: Non-ASCII character ‘\xc3’ in file /Users/renzo/PycharmProjects/sandbox-python2/uni.py on line 1, but no encoding declared;

Vc está levando erro porque sua string está encodada em ascii. Portanto, ele não consegue entender os bytes presentes em ‘ã’ e por isso dá o erro. Ascii utiliza apenas um byte e, portanto, só possui 256 (2**8) caracteres.

Ao pesquisar a solução, você encontrará alguém dizendo para colocar na primeira linha o seguinte código:

# -*- coding: utf-8 -*

print ‘ã’

Isso faz alusão ao enconding de seu arquivo py. Agora sua string literal está encodada em utf-8 e pode ser executada com ã numa boa. Mas isso com uma ressalva: se seu console utilizar encode utf-8. Isso é uma verdade para sistemas Unix. Contudo, quem usa Windows vai dizer que o problema continua ocorrendo.

Ao executar o programa no sistema do Bill Gates, se estiver em português brasileiro, você vai visualizar o resultado ‘ã’. Isso ocorre porque o encode do console não é utf-8. Para confirmar a afirmação, experimente rodar o seguinte programa para conferir o encoding:

# -*- coding: utf-8 -*
<b></b>import sys

print sys.stdout.encoding

Ao executar no Unix, recebi o resultado “UTF-8”. Já no Windows, recebi “cp1252”. Ou seja, para imprimir corretamente no nesse último sistema, devemos usar o enconding de seu console. Assim, produzi o código abaixo:

# -*- coding: utf-8 -*
s='ã'
s=s.decode('utf8') # passando para 
unicodes=s.encode('cp1252') 
print s

Com ele, recebi o resultado esperado no console. Apesar disso, ainda existe um problema bem grande a ser considerado.

Considere o seguinte código:

s='ã'

cp=s.decode('utf8')

cp=cp.encode('cp1252')

print s+cp

O resultado no terminal unix é “ã�”. Isso ocorre porque tentei concatenar bytes encodados em utf8 (variável s) com caracteres encondados em cp1252 (variável cp). Como o terminal está em utf8, ele não consegue imprimir os bytes em cp1252 do segundo caractere e coloca o sinal � em seu lugar.

Outro problema interessante é verificar o seguinte:

>>> print(len('ã')) 2
>>> print(len('ã'.decode('utf8'))) 1

Será que o Python está maluco? Não, ele não está. No primeiro, caso ele está considerando o número de bytes no caractere ‘ã’, encodado em utf8. E para ele, são necessário dois bytes. Já no segundo caso, a contagem de caractere é do unicode e ‘ã’ é apenas um caractere nessa tabela.

Visto isso, qual a fórmula para tratar strings com sanidade? Eis meu algoritmo:

  1. Verificar com a fonte dos dados o respectivo encoding;
  2. Fazer o decoding para unicode;
  3. Fazer operações da regra de negócio (parsing, concatenação etc);
  4. Ao enviar dados, encodar string e informar encoding na documentação.

Assim, se as variáveis s e cp fossem strings recebidas de um sistema externo, esse seria o tratamento:

# -*- coding: utf-8 -*


s='ã'
cp=s.decode('utf8')
cp=cp.encode('cp1252')


# colocar tudo em unicode
s=s.decode('utf8')
cp=cp.decode('cp1252')


# Fazer regra de negócio


concatenado = s+cp


# Encodar antes de enviar para console ou sistema externo


print concatenado.encode('utf8')

Se você quiser criar uma string em código que seja unicode em Python 2, existem duas formas. A primeira é utilizar o prefixo u antes de strings. Assim o código anterior alterado ficaria:

# -*- coding: utf-8 -*


s=u'ã'  # utilizando prefixo u
cp=s.encode('cp1252')


# colocar tudo em unicode
cp=cp.decode('cp1252')


# Fazer regra de negócio


concatenado = s+cp


# Encodar antes de enviar para console ou sistema externo


print concatenado.encode('utf8')

No Python 3 essa questão foi resolvida. Toda string literal no código é unicode. Mais que isso: a classe string é unicode. Strings encodadas são do tipo bytes. Isso condiz mais com a realidade que apresentei no início desse texto. Dessa forma, em Python 3 o código anterior funcionária sem a adição do prefixo u.

É possível ter esse comportamento também no Pyhton 2. Para isso, basta acrescentar  a linha from __future__ import unicode_literals logo após o encoding. Essa é a segunda maneira de se criar string unicode. É a minha preferida:

# -*- coding: utf-8 -*
from __future__ import unicode_literals


s='ã'
cp=s.encode('cp1252')


# colocar tudo em unicode
cp=cp.decode('cp1252')


# Fazer regra de negócio


concatenado = s+cp


# Encodar antes de enviar para console ou sistema externo


print concatenado.encode('utf8')

Assim você sempre irá criar strings unicode em seu código. Como trabalho sempre dessa forma, configurei minha IDE, o Pycharm, para sempre acrescentar as linhas de enconding e unicode_literals quando utilizo Python 2. O código funciona nas duas versões da linguagem.

Com esse artigo, pretendi explicar um pouco de strings, já que perguntas sobre o assunto são recorrentes na principal lista de discussão brasileira.

Será que consegui te ajudar? Deixe seu comentário abaixo!