4. Como lidar com strings

Esta seção explica como as cadeias de caracteres são representadas no Python 2.x, no Python 3.x e no GTK+ e discute erros comuns que surgem ao trabalhar com strings.

4.1. Definições

Conceitualmente, uma string é uma lista de caracteres como “A”, “B”, “C” ou “É”. Caracteres são representações abstratas e seu significado depende do idioma e do contexto em que são usados. O padrão Unicode descreve como os caracteres são representados por pontos de código. Por exemplo, os caracteres acima são representados com os pontos de código U+0041, U+0042, U+0043 e U+00C9, respectivamente. Basicamente, os pontos de código são números no intervalo de 0 a 0x10FFFF.

Como mencionado anteriormente, a representação de uma string como uma lista de pontos de código é abstrata. Para converter essa representação abstrata em uma sequência de bytes, a string Unicode deve ser codificada. A forma mais simples de codificação é ASCII e é executada da seguinte maneira:

  1. Se o ponto de código for < 128, cada byte é o mesmo que o valor do ponto de código.

  2. Se o ponto de código for 128 ou maior, a string Unicode não poderá ser representada nessa codificação. (Python dispara uma exceção UnicodeEncodeError neste caso.)

Embora a codificação ASCII seja simples de aplicar, ela só pode codificar 128 caracteres diferentes, o que não é suficiente. Uma das codificações mais usadas para resolver esse problema é o UTF-8 (ele pode manipular qualquer ponto de código Unicode). UTF significa “Formato de Transformação Unicode”, do inglês “Unicode Transformation Format”, e “8” significa que números de 8 bits são usados na codificação.

4.2. Python 2

4.2.1. Suporte a Unicode do Python 2.x

O Python 2 vem com dois tipos diferentes de objetos que podem ser usados para representar strings str e unicode. Instâncias do último são usadas para expressar strings Unicode, enquanto instâncias do tipo str são representações de byte (a string codificada). Sob o capô, Python representa strings Unicode como números inteiros de 16 ou 32 bits, dependendo de como o interpretador Python foi compilado. Strings Unicode podem ser convertidas em strings de 8 bits com unicode.encode():

>>> unicode_string = u"Fu\u00dfb\u00e4lle"
>>> print unicode_string
Fußbälle
>>> type(unicode_string)
<type 'unicode'>
>>> unicode_string.encode("utf-8")
'Fu\xc3\x9fb\xc3\xa4lle'

As strings de 8 bits do Python têm um método str.decode() que interpreta a string usando a codificação fornecida:

>>> utf8_string = unicode_string.encode("utf-8")
>>> type(utf8_string)
<type 'str'>
>>> u2 = utf8_string.decode("utf-8")
>>> unicode_string == u2
True

Infelizmente, o Python 2.x permite que você misture unicode e str se a string de 8 bits contivesse apenas bytes de 7 bits (ASCII), mas obteria UnicodeDecodeError se contivesse valores não-ASCII:

>>> utf8_string = " sind rund"
>>> unicode_string + utf8_string
u'Fu\xdfb\xe4lle sind rund'
>>> utf8_string = " k\xc3\xb6nnten rund sein"
>>> print utf8_string
 könnten rund sein
>>> unicode_string + utf8_string
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 2:
ordinal not in range(128)

4.2.2. Unicode no GTK+

O GTK+ usa strings codificadas em UTF-8 para todo o texto. Isto significa que se você chamar um método que retorna uma string, você sempre obterá uma instância do tipo str. O mesmo se aplica aos métodos que esperam um ou mais strings como parâmetro, eles devem ser codificados em UTF-8. No entanto, por conveniência, o PyGObject converterá automaticamente qualquer instância unicode para str se fornecido como argumento:

>>> from gi.repository import Gtk
>>> label = Gtk.Label()
>>> unicode_string = u"Fu\u00dfb\u00e4lle"
>>> label.set_text(unicode_string)
>>> txt = label.get_text()
>>> type(txt), txt
(<type 'str'>, 'Fu\xc3\x9fb\xc3\xa4lle')
>>> txt == unicode_string
__main__:1: UnicodeWarning: Unicode equal comparison failed to convert
both arguments to Unicode - interpreting them as being unequal
False

Observe o aviso no final. Apesar de chamarmos Gtk.Label.set_text() com uma instância de unicode como argumento, Gtk.Label.get_text() sempre retornará uma instância str. Assim, txt e unicode_string não são iguais.

Isto é especialmente importante se você quiser internacionalizar seu programa usando gettext. Você precisa ter certeza de que gettext retornará strings de 8 bits codificadas em UTF-8 para todos os idiomas. Em geral, recomenda-se não usar objetos unicode em aplicativos GTK+ e usar somente objetos codificados em UTF-8 str, já que o GTK+ não se integra totalmente a objetos unicode. Caso contrário, você teria que decodificar os valores de retorno para cadeias de caracteres Unicode cada vez que você chamar um método GTK+:

>>> txt = label.get_text().decode("utf-8")
>>> txt == unicode_string
True

4.3. Python 3

4.3.1. Suporte a Unicode do Python 3.x

Desde o Python 3.0, todas as strings são armazenadas como Unicode em uma instância do tipo str. Strings codificadas, por outro lado, são representadas como dados binários na forma de instâncias do tipo bytes. Conceitualmente, str refere-se a texto, enquanto bytes refere-se a dados. Use str.encode() para ir de str para bytes e bytes.decode() para ir de bytes para str.

Além disso, não é mais possível misturar strings Unicode com strings codificadas, porque resultará em um TypeError:

>>> text = "Fu\u00dfb\u00e4lle"
>>> data = b" sind rund"
>>> text + data
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> text + data.decode("utf-8")
'Fußbälle sind rund'
>>> text.encode("utf-8") + data
b'Fu\xc3\x9fb\xc3\xa4lle sind rund'

4.3.2. Unicode no GTK+

Como consequência, as coisas são muito mais limpas e consistentes com o Python 3.x porque o PyGObject irá automaticamente codificar/decodificar para/de UTF-8 se você passar uma string para um método ou um método retornar uma string. Strings, ou text, sempre serão representados como instâncias de str apenas:

>>> from gi.repository import Gtk
>>> label = Gtk.Label()
>>> text = "Fu\u00dfb\u00e4lle"
>>> label.set_text(text)
>>> txt = label.get_text()
>>> type(txt), txt
(<class 'str'>, 'Fußbälle')
>>> txt == text
True

4.4. Referências

O que há de novo no Python 3.0 descreve os novos conceitos que distinguir claramente entre texto e dados.

O Unicode HOWTO aborda o suporte do Python 2.x a Unicode e explica vários problemas que as pessoas comumente encontram ao tentar trabalhar com o Unicode.

O Unicode HOWTO for Python 3.x discute o suporte a Unicode no Python 3.x.

A tabela de codificação UTF-8 e os caracteres Unicode contém uma lista de pontos de código Unicode e sua respectiva codificação UTF-8.