12 Kasım 2012 Pazartesi

Python'da Türkçe Karakter Problemi ve Çözümü

Üzgünüm, ideal bir dünyada yaşamıyoruz, Türkçe karakter ya da daha genel bir deyim ile karakter kodlama ve çözme problemleri ile hemen hemen her programlama dilinde karşılaşıyoruz ve Python'da bunun bir istisnası değil. Bu yazıda fazla teknik detaya girmeden Python programlama dilinde karakter kodlamada karşılaşılan problemlere yönelik basit reçeteler vermeyi hedefliyorum. Konuyla ilgili olarak Joel Spolsky'nin "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)" başlıklı yazısını okumanızı şiddetle tavsiye ediyorum. İngilizcesine yeteri kadar güvenmeyenlere iyi haber, yazı "Kesinlikle En Düşük Seviyeli Yazılım Geliştirici Bile Unicode ve Karakter Setlerini Bilmelidir (Özürsüz !)" başlığı ile Türkçe'ye de çevirilmiş durumda.
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position
10: ordinal not in range(128)
Yukarıdaki hata mesajı tanıdık geldiyse doğru yerdesiniz. Genel bir kural olarak karakter kodlama (chracter encoding) problemlerinden kaçınmak için;
  • Mümkün olduğu kadar erken metni Unicode'a dönüştürmek (decode)
  • Bütün işlemlerde Unicode kullanmak
  • Unicode metni mümkün olduğu kadar geç herhangi bir karakter kodlamasına dönüştürmek.
Bahsettiğim makaleleri okumaya vaktiniz yoksa ve kısa yoldan çözüme ulaşmaya çalışıyorsanız en azından Unicode nedir bilmeniz gerekiyor.

Peki nedir Unicode?

Bilgisayarlar temel olarak sadece sayılarla işlem yaparlar. Harfleri ve diğer karakterleri her birisine birer sayı atayarak depolarlar. Unicode geliştirilmeden önce çeşitli dillerdeki rakam ve harflere sayı atamak için onlarca farklı kodlama sistemi kullanılmaktaydı. Bu kodlama sistemlerinden hiç birisi mevcut bütün harf, işaret ve rakamları kapsayacak kadar geniş değildi. Ayrıca bu kodlama sistemleri birbirleri ile çakışmakta idi. Örneğin iki farklı kodlama sistemi aynı sayıyı iki farklı karakter kullanabileceği gibi aynı karakter için iki farklı sayı da kullanabilir. Türkçe karakterlerin doğru şekilde görüntülenmemesinin ana nedeni budur.

Günümüzde bilgisayarların aynı anda birden fazla karakter kodlamasını desteklemesi zorunlu hale gelmiştir. Ortaya çıkan bu zorunluluk her karaktere bir sayı değeri karşılığı atayan Unicode standardının geliştirilmesine neden olmuştur. Her bir karakter için Unicode standardı tarafından atanan sayıya o karakterin code point'i denilir ve hiçbir zaman değişmez.

Madem bütün karakterleri eşsiz olarak tanımlamak için bir code point dediğimiz bir sayı var neden hala Türkçe karakter problemi yaşıyoruz? Maalesef bu konuda bir standart olmasına rağmen karakter kodlarının (code point) hafızada nasıl tutulacağı ve diske nasıl yazılacağı konusunda birden fazla standart vardır. Metnin diske yazılmasında kullanılan UTF-8 kodlama standardı en yaygın olanıdır ve her türlü metin dosyasında standart olarak UTF-8 kodlaması kullanmanızı hararetle tavsiye ediyorum.


Nerede kalmıştık...

Ehem, yukarıda bahsettiğimiz kuralı bu bilgiler ışığında söyle yeniden ifade edebiliriz.

  • Mümkün olduğu kadar erken metindeki karakterlerin sayı karşılıklarını oku ve bunları evrensel olarak kabul edilen Unicode sayı karşılığına (code point) çevir.
  • Bütün işlemlerde Unicode sayı karşılığını (code point) kullan
  • İşlemler bitince Unicode sayı karşılığını (code point) herhangi bir karakter kodlamasına, tercihan UTF-8, dönüştür ve diske kaydet veya ağ üzerinden aktar.

"Ğ" yumuşak G

Türçe karakter seti için tanımlanan ISO-8859-9 karakter kodlama tablosunda "Ğ" yumuşak G için 208 sayısı atanmıştır. Latin harfleri için tanımlanan ve genellikle İngilizce metinler için kullanılan ISO-8859-1 karakter kodlama tablosunda 208 sayısı "?" karakteri için kullanılmaktadır. Yani metnin karakter kodlamasını bilmiyorsanız 208 sayısının "Ğ" mi yoksa "Ð" karakterini mi temsil ettiğini anlamak son derece zordur. İşte buna benzer karışıklıkların önüne geçmek için ilk olarak yapılması gereken işlem dosyadan okunan metindeki her bir karakterin Unicode sayı karşılığına (code point) çevrilmesidir. "Ğ" yumuşak G nin Unicode sayı karşılığı (code point) 286 (U+011E) dır. Tablo-1 de bazı Türkçe karakterler için (ğışĞİÜ) ISO-8859-9 kod tablosunda kullanılan numara ve Unicode numarasının farklı olduğunu görebilirsiniz. Dikkatli gözler bu karakterlerin Unicode numaralarının 255 den de büyük olduğunu ve en az iki byte ile ifade edilebileceğini de görecektir.

Tablo -1

Farklı karakter kod tablolarının aynı numarayı farklı karakterleri ifade etmek için nasıl kullandığını Tablo-2 de görebilirsiniz. Tabloyu incelediğinizde doğru karakter kodlama tablosunun kullanılmaması durumunda Türkçe karakterlerden "ğ ı ş Ğ İ Ü" nin sıklıkla neden hatalı olarak "ð ý þ Ð Ý Þ" karakteri şeklinde görüntülendiğini anlayabilirsiniz.

Tablo-2

Yukarıdaki gibi Türkçe karakterler içeren ve ISO-8859-9 kodlama tablosu kullanan bir dosyayı problemsiz olarak aşağıdaki şekilde okuyup Python Unicode metin formatına dönüştürebiliriz.

Türkçe karakterler içeren ve ISO-8859-9 karakter kodlamasını kullanan örnek bir metin dosyası.

Hepsi bu kadar mı? Hayır. Burada yaptığımız işlem herhangi bir karakter kodlamasını kullanan (örnekte iso-8859-9) bir metin dosyasını satır satır okuyup her bir satırı Unicode formatına dönüştürmekten ibaret. Bu aşamadan sonra unicode_metin değikenini sanki normal bir string değişkeniymiş gibi kullanabiliriz. Mesela len(unicode_metin) fonksiyonu Türkçe karakter içeren metinler için de doğru sonuç verecektir.

Peki Unicode formatına dönüştürdüğümüz metin üzerinde yapmak istediğimiz işlemler tamamlandıktan sonra metni tekrar bir dosyaya kaydetmek istersek ne yapmamız gerekiyor? Maalesef Unicode metni aşağıdaki gibi doğrudan dosyaya kaydetmek hata ile sonuçlanacaktır.

Hata mesajı şuna benzer olacaktır.

UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 9:
ordinal not in range(128)

Hatanın nedeni Python'da varsayılan karakter kodlamasının ASCII olması ve Unicode metinde ASCII karakterler dışında karakterlerin de bulunmasıdır. Bu nedenle Unicode metin diske kaydedilmeden önce arzu edilen karakter kod tablosuna tekrar dönüştürülmeli ve ondan sonra dosyaya yazılmalıdır. Aşağıdaki örnekte ISO-8859-9 karakter kodlamasına sahip bir metin dosyası satır satır okunmakta, her bir satır Unicode formatına dönüştürülmekte, metin üzerinde işlemler tamamlandıktan sonra unicode_metin değişkeni arzu edilen bir karakter kodlamasına (örnekte UTF-8) dönüştürülerek hedef dosyaya kaydedilmektedir.

Türkçe karakterler içeren ve ISO-8859-9 karakter kodlamasını kullanan örnek dosyanın UTF-8 kodlamasına dönüştürülmüş hali.

Burada kullanılan unicode_metin.encode('utf-8') metodunu daha yakından incelememiz gerekiyor. Metod özetle Unicode metni istenilen kod tablosuna çevirmektedir. Karakter kod tabloları birbirlerinden farklı karakterler içerdiklerinden aralarında yapılan dönüşümlerde bazı karakterlerin karşılığı olmadığından kayıplar kaçınılmaz olmaktadır. İşte bu tür bir problem ile başa çıkabilmek için encode methodu bize opsiyonel ikinci bir parametre olarak bu durumda ne yapmak istediğimizi seçmemize olanak tanımaktadır. Parametrenin varsayılan değeri olan 'strict' böyle bir durumda programın hata aşağıdakine benzer bir hata mesajı vermesine sebep olacaktır. Değer olarak 'ignore' kullanıldığında hedef karakter kod tablosunda karşılığı olmayan Unicode karakterler yok sayılılar. Diğer bir alternatif ise 'replace' değerinin kullanılmasıdır. Bu durumda hedef karakter kod tablosunda karşılığı olmayan Unicode karakterler '?' (soru işareti) ile değiştirlirler. Konuyla ilgili daha detaylı bilgiye buradan ulaşabilirsiniz.

Bu metodun farklı kullanımlarını aşağıda görebilirsiniz.

Burada yazılanları okuduktan sonra kafanız daha da karıştıysa lütfen yorum kısımını kullanarak geri bildirimde bulunun. Elimden geldiği kadar yardımcı olmaya gayeret ederim. Umarım artık Türkçe karakter problemleri artık eskisi kadar korkutucu değildir...

5 yorum:

  1. Merhabalar; bu yazının internette türkçe karakterlee ilgili büyük bir boşluğu dolduruyor. Teşekkür ederiz.
    Ben RSS'yi okuyup dosyaya kaydeden bir uygualama yazarken metotlarınızı şöyle uyguladım:
    f=open("news.xml","a")
    satir="""



    %s

    """%((title),(article),link)
    print str(i)+"\n"
    #print satir
    f.write (tr(satir))
    f.close()

    burada tr fonksiyonu:
    def tr(text):
    encoding="utf-8"
    #method: ignore/replace
    method='replace'
    unicode_metin=text.decode(encoding)
    utf8_metin=unicode_metin.encode(encoding,method)
    return utf8_metin

    Ancak yine hata yine hata. Delireceğim. Yardımcı olur musunuz?
    Saygılarımla

    YanıtlaSil
  2. Yazdığınız kodu ve karşılaştığınız hata mesajını tam olarak gönderirseniz size yardımcı olabilirim.

    YanıtlaSil
  3. Güzel yazı olmuş. Teşekkürler!

    YanıtlaSil
  4. emeğinize sağlık

    YanıtlaSil