Unicode, UTF-8 ve Python (Turkish)
Sep 10, 2013
4 minutes read

Genelde kodlama(encoding) sorunuyla karşılaşıldığında akla gelen bir konu bu. Halbuki detayları öğrenildiğinde bitmeyen sinir bozucu hatalardan kurtulmak mümkün.

Neden ihtiyaç duyuyoruz?

Bilgisayarlar için herşey bayttan meydana gelir. Dosyadaki bilgiler, ağdan geçen veriler hep bayt cinsinden saklanır. Baytlar onları geri çözümleyene kadar anlamsızdırlar. İlk bilgisayarlar ortaya çıktığında ASCII karakter seti belirlendi. Her karakter için 1 byte kullanmak yeterliydi. Bu şekilde en fazla 256 karakter elde etmek mümkündü. Her karakterin 1 sayı karşılığı olduğunu düşünürsek 2^8 = 256.

Bu kadar az karakter çeşidi elbette tüm diller için yetersizdi. Hatta latin dilleri için bile yetersizdi. Zamanla farklı karakter setleri ortaya çıktı ama durumu daha karışık hale getirdi. Bu yüzden Unicode adında genel geçer ve tüm dilleri kapsayan bir karakteri seti ortaya çıktı. Doğal olarak 1 karakter 1 bayt ile ifade edilemiyor, 4 bayta kadar ifade edilen karakterler var.

Unicode Nedir?

Unicode şu an 110 bin karakteri içeren bir karakter setidir. Orhun Kitabeleri’nin yazıldığı eski Türkçe alfabesi bile mevcuttur. U+00CA ya da \u00CA şeklinde u’dan sonra 16′lık sayı birimiyle gösterilirler.

Türkçe karakterlerin Unicode karşılıkları şunlardır:

ğ – \u011f
Ğ – \u011e
ı – \u0131
İ – \u0130
ö – \u00f6
Ö – \u00d6
ü – \u00fc
Ü – \u00dc
ş – \u015f
Ş – \u015e
ç – \u00e7
Ç – \u00c7

Görüldüğü gibi ö, ü, ç 1 bayt ile ifade edilebiliyor. Bunun sebebi Unicode setinin ilk 128 karakteri ASCII ile, ilk 256 karakteri ISO-8859-9 (Latin-1) ile aynı tutulmuştur. Bu karakterler ISO-8859-9′da olduğu için sıraları daha öndedir. Bu yüzdendir ki bazı yazılarda ö, ü, ç harfleri düzgün çıkar ama diğer Türkçe karakterler bozuk çıkar.

UTF-8 Nedir?

UTF-8 bir kodlama yöntemidir. UTF Unicode Transformation Format’ın kısaltılmış halidir. Veriler dosyaya yazılacağı ya da ağ üzerinden geçeceği zaman baytlara çevrilmek zorundadır. Bu çevrilme işlemine kodlama denir. Unicode karakterlerin kodlanmasında en yaygın kullanılan yöntem UTF-8′dir. UTF-8 en Unicode karakter setini en verimli yani en az bayt kaplayacak kadar kodlamaya çalışır.

Örneğin U+015E Unicode karakteri olan Ş’nin UTF-8 karşılığı 0xc59e olarak gösterilir.

Python 2.X’te Unicode Python’da metinler 2 şekilde tutulur. Unicode ya da bayt serisi şeklinde. Unicode olduğunu belirtmek için metinin başına “u” koyulur. Bayt serisi olan verinin başına “b” konulabilir fakat opsiyoneldir.

Örneğin

veri = 'Merhaba'
print(type(veri))
<type 'str'>

veri = u'Merhaba'
print(type(veri))
<type 'unicode'>

Görüldüğü gibi Unicode ve Bayt serileri farklı tipte nesnelerdir. Python’da eğer farklı bir değer verilmediyse, bayt serileri varsayılan olarak ASCII’ye çevrilmeye çalışır. Eğer ASCII’ye çevrilemiyorsa hata alınır.

Örneğin

veri = 'Merhaba Dünya'
print(veri)
File "/home/user/PycharmProjects/practice/practice2.py", line 3
SyntaxError: Non-ASCII character '\xc3' in file /home/user/PycharmProjects/practice/practice2.py on line 3, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Unicode'a çevirelim:

veri = u'Merhaba Dünya'
print(veri)
File "/home/user/PycharmProjects/practice/practice2.py", line 3
SyntaxError: Non-ASCII character '\xc3' in file /home/user/PycharmProjects/practice/practice2.py on line 3, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Yine hata aldık! Bunun sebebi ” arasına yazdığımız ifadenin ASCII’den Unicode’a çevrilmeye çalışılmasıdır. Bu yüzden ASCII karşılığı olmayan bir karakter çevrilemez.

Doğrusu:

veri = 'Merhaba D\u00fcnya'
print(veri)
Merhaba D\u00fcnya

veri = u'Merhaba D\u00fcnya'
print(veri)
Merhaba Dünya

Aynı kodu dosyaya yazmak için kullanalım:

veri = u'Merhaba D\u00fcnya'
f = open('/home/user/practice2.txt', 'w')
f.write(veri)
f.close()

Traceback (most recent call last):
  File "/home/user/PycharmProjects/practice/practice2.py", line 8, in
    f.write(veri)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 9: ordinal not in range(128)

Yine ne oldu? Hani Unicode yapmıştık? Eğer Python kullanılan terminalin kodlamasını tanıyabilirse sys.stdout.encoding parametresini bu kodlama değeri yapar. Büyük ihtimal terminalizin kodlaması UTF-8′di ve yukarıdaki kodlar bu yüzden çalıştı. Dosyaya yazarken herhangi bir kodlama belirtmediğimiz için ve sys.getdefaultencoding() ASCII olduğu için hata alındı.

Girdilerde çözümleme(decoding), çıktılarda kodlama(encoding) yaparak sürekli Unicode değerlerle çalışmamız en iyisi olacaktır.

Aşağıdaki kod hatasız çalışacaktır.

veri = u'Merhaba D\u00fcnya'
veri = veri.encode('utf-8')
f = open('/home/user/practice2.txt', 'w')
f.write(veri)
f.close()

Kodlama değerlerinin okunması ve ayarlanması:

print(sys.getdefaultencoding())
print(sys.stdout.encoding)
ascii
UTF-8

kodlama bilgileri bu şekilde okunabilir.

import os, sys
if sys.stdout.encoding == None:
    os.putenv("PYTHONIOENCODING",'UTF-8')
    os.execv(sys.executable,['python']+sys.argv)

gerekirse PYTHONIOENCODING parametresi UTF-8 yapılabilir. Unicode ve ASCII metin işlemleri:

veri = 'Merhaba ' + u'D\u00fcnya'
print(type(veri))
<type 'unicode'>

Python 2.X Unicode ile ASCII birleştirildiğinde bunu Unicode’a çevirir. (Python 3.X bu durumda hata verir.)

Python 3.X’te Unicode

Python 3.X’te str tipi zaten veriyi Unicode olarak saklar. Unicode’a çevirmek için “u” eklenmesi gerekmez. Aksine bayt olan verinin başına b”” eklemek gerekir. Ayrıca sys.getdefaultencoding() değeri ASCII yerine UTF-8‘dir. Python 3.X “Mer” + b”haba” gibi Unicode ve bayt değerin işleme sokulmasına izin vermez.

Öneriler

Bir girdi söz konusu ise mutlaka çözümleme(decode) yapılarak Unicode’a çevrilmelidir. Bir çıktı söz konusu ise mutlaka kodlama(encode) yapılarak UTF-8′e (ya da istediğinize) çevrilmelidir. Kodda sadece Unicode değerler işleme sokulmalıdır. Eldeki değerin tipine bakılarak gerekirse Unicode’a dönüştürülmelidir. Uygulama farklı karakter setleriyle test edilmelidir.