segunda-feira, 17 de agosto de 2015

Strings em Python

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 caracter 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 vc 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 utilizar apenas um byte e, portanto, só possui 256 (2**8) caracteres.


Ao pesquisar a solução, vc 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, vc 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 -*


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 caracter e coloca o sinal � em seu lugar.


Outro problema interessante é vericar 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 caracter 'ã' encodado em utf8. E para ele, são necessário dois bytes. Já no segundo caso a contagem de caracter é do unicode e 'ã' é apenas um caracter 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 vc 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 vc 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 =D