4. Hur du hanterar strängar

Detta avsnitt förklarar hur strängar representeras i Python 2.x, Python 3.x och GTK+ samt diskuterar vanliga fel som uppstår vid arbete med strängar.

4.1. Definitioner

Som ett koncept är en sträng en lista tecken så som ”A”, ”B”, ”C” or ”É”. Tecken är abstrakta representationer och deras betydelse beror på språket och sammanhanget de används i. Unicode-standarden beskriver hur tecken representeras med kodpunkter. Exempelvis representeras tecknen ovan med kodpunkterna U+0041, U+0042, U+0043 respektive U+00C9. Kodpunkter är helt enkelt tal i intervallet 0 till 0x10FFFF.

Som nämndes tidigare är representationen av en sträng som en lista med kodpunkter abstrakt. För att konvertera denna abstrakta representation till en sekvens byte så behöver Unicode-strängen kodas. Den enklaste formen av kodning är ASCII och utförs som följer:

  1. Om kodpunkten är < 128 så är varje byte detsamma som värdet på kodpunkten.

  2. Om kodpunkten är 128 eller större, så kan Unicode-strängen inte representeras i denna kodning. (Python utfärdar ett UnicodeEncodeError-undantag i detta fall.)

Även om ASCII-kodning är lätt att tillämpa så kan den bara koda 128 olika tecken vilket knappast är tillräckligt. En av de vanligast använda kodningarna som tar itu med detta problem är UTF-8 (den kan hantera alla Unicode-kodpunkter). UTF står för ”Unicode Transformation Format”, och ”8” står för att 8-bitars tal används i kodningen.

4.2. Python 2

4.2.1. Unicode-stöd i Python 2.x

Python 2 kommer med två olika sorters objekt som kan användas för att representera strängar, str och unicode. Instanser av den senare används för att uttrycka Unicode-strängar, medan instanser av typen str är byterepresentationer (den kodade strängen). Under huven representerar Python Unicode-strängar antingen som 16- eller 32-bitars heltal, beroende på hur Python-tolken kompilerades. Unicode-strängar kan konverteras till 8-bitars strängar med 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'

Pythons 8-bitars strängar har en str.decode()-metod som tolkar strängen med given kodning:

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

Tyvärr låter Python 2.x dig mixa unicode och str om 8-bitarssträngen råkade innehålla endast 7-bitars byte (ASCII), men skulle få UnicodeDecodeError om den innehöll värden som inte var 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 i GTK+

GTK+ använder UTF-8-kodade strängar för all text. Detta betyder att om du anropar en metod som returnerar en sträng kommer du alltid erhålla en instans av typen str. Detsamma gäller metoder som förväntar sig en eller flera strängar som parameter, de måste vara UTF-8-kodade. För bekvämlighet kommer dock PyGObject automatiskt konvertera alla unicode-instanser till str om de tillhandahålls som argument:

>>> 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

Observera varningen i slutet. Även om vi anropade Gtk.Label.set_text() med en unicode-instans som argument, så kommer Gtk.Label.get_text() alltid returnera en str-instans. Därmed är txt och unicode_string inte lika.

Detta är särskilt viktigt om du vill internationalisera ditt program med gettext. Du måste säkerställa att gettext kommer returnera UTF-8-kodade 8-bitars strängar för alla språk. I allmänhet rekommenderas det att inte använda unicode-objekt i GTK+-program överhuvudtaget och endast använda UTF-8-kodade str-objekt då GTK+ inte är helt integrerat med unicode-objekt. I annat fall kommer du behöva avkoda returvärdena till Unicode-strängar varje gång du anropar en GTK+-metod:

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

4.3. Python 3

4.3.1. Unicode-stöd i Python 3.x

Sedan Python 3.0 lagras alla strängar som Unicode i en instans av typen str. Kodade strängar representeras å andra sidan som binärdata i form av instanser av typen bytes. Konceptuellt refererar str till text, medan bytes refererar till data. Använd str.encode() för att gå från str till bytes, och bytes.decode() för att gå från bytes till str.

Vidare är det inte längre möjligt att mixa Unicode-strängar med kodade strängar, eftersom det kommer resultera i ett 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 i GTK+

Som en följd av detta är saker mycket prydligare och mer konsekventa med Python 3.x, för PyGObject kommer automatiskt koda/avkoda till/från UTF-8 om du skickar med en sträng till en metod eller om en metod returnerar en sträng. Strängar, eller text, kommer alltid endast att representeras som instanser av str:

>>> 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. Referenser

Vad är nytt i Python 3.0 beskriver de nya koncept som tydligt skiljer mellan text och data.

Unicode HOWTO diskuterar Unicode-stödet i Python 2.x, och förklarar olika problem som folk vanligen stöter på då de försöker arbeta med Unicode.

Unicode HOWTO för Python 3.x diskuterar Unicode-stöd i Python 3.x.

UTF-8 encoding table and Unicode characters innehåller en lista över Unicode-kodpunkter och deras respektive UTF-8-kodning.