3 puan yazan GN⁺ 2024-04-28 | 1 yorum | WhatsApp'ta paylaş
  • Python 3.15, UTF-8 modunu varsayılan olarak açarak dosyalar, standart giriş/çıkış ve pipe'lar için varsayılan kodlamayı UTF-8'e getiriyor
  • UTF-8; kaynak dosyaları, JSON·TOML·YAML, başlıca editörler, web verileri ve Node.js·Go·Rust·Java gibi ortamlarda fiilî standart kodlama olarak kullanıldığı için birlikte çalışabilirlik artıyor
  • Mevcut varsayılan kodlama platforma göre değiştiğinden, Unix geliştiricileri encoding="utf-8" ifadesini atladığında Windows gibi ortamlarda uyuşmazlık kaynaklı hatalar oluşabiliyor
  • Gerekirse PYTHONUTF8=0 veya -X utf8=0 ile kapatılabiliyor; uyumluluk denetimi için EncodingWarning, encoding="utf-8", encoding="locale", locale.getencoding() kullanılabiliyor
  • Varsayılan kodlamaya bağımlı programlar özellikle Windows'ta UnicodeError, mojibake ve sessiz veri bozulması yaşayabileceği için önceden kontrol edilmesi gerekiyor

Python 3.15'te değişen varsayılan kodlama

  • PEP 686, PEP 540'daki UTF-8 modunu varsayılan olarak etkinleştiren bir değişiklik
  • Varsayılan kodlama gerektiren dosyalar, stdio ve pipe'larda Python artık tutarlı şekilde UTF-8 kullanıyor
  • Bu özellik Python 3.15 ile varsayılan olacak; kullanıcılar ise şu yollarla devre dışı bırakabilecek
    • PYTHONUTF8=0
    • -X utf8=0

Neden varsayılan olarak UTF-8?

  • UTF-8 birçok ortamda standart metin kodlaması haline geldi
    • Python kaynak dosyalarının varsayılan kodlaması UTF-8
    • JSON, TOML ve YAML UTF-8 kullanıyor
    • Visual Studio Code ve Windows Notepad dahil çoğu metin editörü varsayılan olarak UTF-8 kullanıyor
    • İnternetteki web sitelerinin ve metin verilerinin büyük kısmı UTF-8 kullanıyor
    • Node.js, Go, Rust ve Java dahil birçok popüler programlama dili varsayılan olarak UTF-8 kullanıyor
  • Python'un varsayılan kodlamasının UTF-8'e geçmesi, diğer araçlar·diller·veri biçimleriyle birlikte çalışabilirliği artırıyor
  • Birçok Unix ortamı Python geliştiricisi, varsayılan kodlamanın platforma bağımlı olduğunu unutup JSON·TOML·Markdown·Python kaynak dosyaları gibi UTF-8 metinleri okurken encoding="utf-8" yazmıyor
  • Platformlar arasındaki varsayılan kodlama farkı, bu tür kodların başka ortamlarda bozulmasına yol açan bir hata kaynağı oluyor

locale API'si ve encoding="locale" düzeltmesi

  • UTF-8 modu locale.getpreferredencoding(False) sonucunu etkilediği için, UTF-8 modundan bağımsız olarak locale kodlamasını döndüren bir API'ye ihtiyaç vardı
  • locale.getencoding() bu amaçla eklendi; locale kodlamasını döndürürken UTF-8 modunu yok sayıyor
    • Bu API Python 3.11'de eklendi
  • warn_default_encoding seçeneği verildiğinde locale.getpreferredencoding(), open() gibi PEP 597'deki EncodingWarning uyarısını üretiyor
  • PEP 597, TextIOWrapper için encoding="locale" seçeneğini ekleyerek locale kodlamasının açıkça belirtilmesini sağladı
  • Önceden UTF-8 modunda encoding="locale" belirtilse bile TextIOWrapper "UTF-8" kullanıyordu
    • Bu durum PEP 597'nin gerekçesiyle uyumlu değildi
    • Çünkü Python'un varsayılan metin kodlaması değiştiğinde UTF-8 modunun varsayılan hale geleceği senaryo öngörülmemişti
  • Bu tutarsızlık Python 3.11'de düzeltildi; artık UTF-8 modunda da encoding="locale" geçirilirse locale kodlaması kullanılıyor

Geriye dönük uyumluluk ve geçiş süreci

  • Çoğu Unix sistemi UTF-8 locale kullanıyor ve Python locale C veya POSIX olduğunda zaten UTF-8 modunu etkinleştirdiği için değişikliğin etkisi ağırlıklı olarak Windows kullanıcılarında görülecek
  • Varsayılan kodlamaya bağımlı Python programları şu sorunları yaşayabilir
    • UnicodeError
    • mojibake
    • sessiz veri bozulması
  • Geriye dönük uyumluluk sorunlarını düzeltmek için önerilen süreç şöyle
    1. UTF-8 modunu devre dışı bırakın
    2. PEP 597'deki EncodingWarning ile UTF-8 modundan etkilenen yerleri bulun
      • encoding seçeneği atlandıysa encoding="utf-8" veya encoding="locale" kullanmayı değerlendirin
      • locale.getpreferredencoding() kullanıldıysa "utf-8" veya locale.getencoding() kullanmayı değerlendirin
    3. Uygulamayı UTF-8 modunda test edin

Ruby·Java örnekleri ve reddedilen alternatifler

  • Ruby, Ruby 3.0 ile 2020'de Windows'taki varsayılan external_encoding değerini UTF-8 olarak değiştirdi
  • Java, JDK 18 ile 2022'de varsayılan metin kodlamasını UTF-8 olarak değiştirdi
  • Ruby ve Java geriye dönük uyumluluk için seçenekler sunuyor, ancak Python'daki EncodingWarning gibi bir varsayılan kodlama kullanım uyarısı sunmuyor
  • Varsayılan kodlama kullanımını tamamen kaldırma fikri reddedildi
    • Çünkü birçok durumda varsayılan kodlama yalnızca ASCII metin okumak ve yazmak için kullanılıyor
    • Yalnızca Unix'te çalışan, çapraz platform olmayan uygulamalar için böyle bir uyarı faydalı olmayabilir
    • Her yerde encoding zorunlu kılınırsa kullanıcı yükü artar ve çok sayıda DeprecationWarning, kullanıcıların uyarıları görmezden gelmesine yol açabilir
    • PEP 387, geriye dönük uyumluluğu bozan değişikliklerde uyarı eklenmesini ister; ancak bunun mutlaka DeprecationWarning olması gerekmez
  • subprocess modülündeki pipe'lar için varsayılan kodlama olarak PYTHONIOENCODING kullanma önerisi de reddedildi
    • Bu yaklaşım, UTF-8 modunda bile subprocess.Popen(text=True) için eski kodlamanın kullanılmasına izin verirdi
    • Ancak bu, “varsayılan kodlama” kavramını daha karmaşık hale getiriyor ve kendi başına da geriye dönük uyumluluğu bozan bir değişiklik sayılıyor
    • Kullanıcılar text=True kullanımını encoding="utf-8" veya encoding="locale" ile değiştirene kadar UTF-8 modunu kapatabilir

Kullanıcı eğitimi açısından

  • Yeni kullanıcıların ilk yıl içinde metin kodlamasını öğrenme ihtiyacı azalacak
  • Kodlama bilgisini yalnızca UTF-8 olmayan metin dosyalarıyla çalışmaları gerektiğinde edinmeleri yeterli olacak
  • Mevcut kullanıcıların ise geriye dönük uyumluluk sürecine göre etkilenen noktaları kontrol etmesi gerekiyor

1 yorum

 
GN⁺ 2024-04-28
Hacker News yorumları
  • Varsayılan metin dosyası kodlamasının platforma göre değişmesi her zaman can sıkıcıydı; bu değişiklik sevindirici
    Dosya sistemi kodlamasına dokunmaya çalışmamaları da iyi. O ayrı bir mesele ve kendi başına baş ağrısı

    • Windows’un sistem varsayılan kod sayfası yalnızca platforma değil, sistem yereline de bağlı
      Windows’un TextOutA gibi ANSI işlevlerinin UTF-8 kod sayfasını kullanmasını basitçe seçmenin bir yolunu uzun süre sunmamış olması büyük bir hataydı. Bunun manifest dosyasıyla mümkün hale gelmesi Windows 10 geliştirmelerinin ortalarına doğru oldu; böyle bir özellik NT4 ya da Windows 98 zamanında gelmeliydi
    • Tarihsel olarak bakınca mantıklıydı. Çünkü çoğu yazılım yerel kullanıma yönelikti ve metin dosyalarının da yerel kodlamada olması bekleniyordu
      Yalnızca platforma değil, kullanıcının tercih ettiği yerele de bağlıydı; C standart kütüphanesi de aynı şekilde davranır. Örneğin Unix/Linux’ta Batı Avrupa dilleri için iso-8859-1 yaygındı; euro’nun gelmesinden sonra ise simgesini içeren iso-8859-15e geçmek sıklaştı. UTF-8’in sorunsuz çalışmaya başlaması 2000’lerin sonlarına denk gelir; Debian, Etch sürümünde varsayılanı UTF-8 yaptı
    • Birkaç gün önce de satır sonlarının örtük olarak değiştirilmesi yüzünden sorun yaşadım
      Şirket dizüstümde yerel testlerin hepsi iyi geçti, ama Linux ana makineye dağıtınca alt uygulama CRLF istediği için veriyi tüketemedi. Bazen hatırlamak zorunda kaldığınız küçük ve aptal sorunlardan biri. Yine de yeni yazılmış bir yazılımın neden belirli bir satır sonlandırıcı istediği de geçerli bir soru
    • Windows’ta biri kod yazmaya başladığında bu sorunla birkaç kez karşılaştık
  • Oynak sistem varsayılanlarına yaslanmamak iyi bir şey
    Bu değerler bir noktada varsaydığımdan farklı dönme eğiliminde. Birkaç yıl önce Ubuntu ve init.d betikleriyle uğraşırken, Java’yı başlatan betik root olarak çalışıyordu; Docker öncesi olduğu için bu daha da belirgindi ve normal kullanıcılar için düzgün UTF-8 varsayılanlarını ayarlamayan bir shell içinde yürütülüyordu. Sonuçta Java’nın işletim sistemi varsayılanını kullanan kötü API kullanımını ortaya çıkardı
    Günümüzde çoğu yerde kodlamayı açıkça belirtebileceğiniz alternatif API’ler var ve statik kod denetleyicileri yanlış olanı kullanınca uyarıyor. Ama tek bir yer bile atlanırsa içerik bozulmaya başlıyor. Artık UTF-8 dışı bir kodlama kullanımı büyük olasılıkla çoğu zaman kasıtsızdır; kasıtlıysa da işletim sisteminin tuhaf dolaylı ayarlarına yaslanmak yerine açıkça belirtilmelidir. Bu yüzden iyi bir değişiklik ve bundan bozulan koda basit bir düzeltme eklenmesi daha iyi

    • PowerShell’de takma ad olarak oluşturduğum touch işlevinin ürettiği .gitignore dosyasını kullanıyordum; ne yaparsam yapayım Git bunu dikkate almıyordu
      Kontrol edince oluşturulan metin dosyasının UTF-16 olduğunu ve fiilen yok sayıldığını gördüm. Dersimi alıp sistem varsayılanını UTF-8’e çevirdim, ama şimdi sadece metin düzenleyiciye güveniyorum
    • Global yerel ayar yalnızca kodlama açısından değil, genel olarak bir hataydı
      printf("%f", 4.2) ortama göre sihirli biçimde farklı bir dize yazdırıyorsa, çözdüğünden fazla sorun çıkarır. Yerel ayara bağlı davranış istendiğinde, yerel bilgiyi ya da ilgili parçaları işleve açıkça geçirmek gerekir
  • Son birkaç on yılda giderek daha doğru çıkan bir sezgisel kural var: Bir yerde charset ayarı varsa ve UTF-8 değilse yanlıştır
    Python 2 karakter kümesinden bağımsız olduğu için hep çalışırdı, ama Python 3’teki iyileştirme yalnızca basit bir iyileştirme değildi. Python 3 betiği ile Python 2 betiğini ayırmanın yolu şu: içinde utf-8 dizgesi varsa Python 3’tür; yalnızca C.UTF-8 yerelinde çalışıyorsa Python 3’tür. Bu değişiklik Python 3’ü “onarıyor” gibi anlaşıldığı için memnuniyetle karşılıyorum

  • Bunun Python 3’ten beri varsayılan olduğunu sanıyordum

    • Muhtemelen Python 3’te u"" önekinin gereksiz hale geldiği dizeleri düşünmüşsünüz
      Az önce Python 2.7’de "éķů" yazdım; ilgili karakterlerin UTF-8 baytlarını yazdırdı, bu yüzden u önekinin tam olarak ne yaptığından emin değilim. Ama Python 2’den 3’e geçişteki büyük değişikliklerden biri, dizelerin bir kodlamaya sahip olması ve bayt dizelerinin kodlamasız bayt dizileri haline gelmesiydi. Bu değişiklik esas olarak Windows gibi varsayılan kodlaması UTF-8 olmayan ortamlarda open('filename', mode='r') kullanırken open('filename', mode='r', encoding='UTF-8') yazmayı açıkça belirtme gerekliliğiyle ilgili görünüyor
    • Python 3’te Python kaynak kodu varsayılan olarak UTF-8’dir. Ama dosyaya kaydederken kullanılan karakter kodlaması hakkında hiçbir şey söylemez; varsayılan yerel ayara bağlıdır
      Path("filenames use their own encoding").write_text("file content encoding uses yet another encoding") örneğinde olduğu gibi, dize literali, dosya adı ve dosya içeriği kodlamaları birbirinden farklıdır. Karşılık gelen kodlamalar tokenize.open için UTF-8, os.fsencode için sys.getfilesystemencoding(), open için locale.getpreferredencoding()dir
  • “Node.js, Go, Rust ve Java dahil diğer popüler programlama dilleri de varsayılan olarak UTF-8 kullanır” denmiş; Java’nın UTF-16’dan UTF-8’e geçtiğini kaçırmışım

    • Java’da baytları dizelere dönüştürürken kullanılan varsayılan kodlama eskiden platforma bağlıydı, şimdi UTF-8
      String sınıfının içinde UTF-16 ve latin-1 kodlamaları hâlâ kullanılıyor; JVM de eskisi gibi değiştirilmiş UTF-8 kodlamasını kullanıyor. String sınıfı başlangıçta yalnızca UTF-16 kullanıyordu, ancak Java 9’dan beri mümkün olduğunda karakter başına 1 baytlık latin-1 kodlamasını da kullanıyor
    • İç dize temsili ile okuma/yazma kodlamasını karıştırıyor gibisiniz
      Java, okuma/yazma kodlamasının varsayılanı olarak hiç UTF-16 kullanmadı
    • Görünüşe göre bu iki yıl önce Java 18’de değişmiş
  • CPython’in iç kodlaması artık UTF-8 mi?
    Python dizgeleri indisle erişilebilir, ama rastgele erişim yeterince nadir olduğundan gerektiğinde tembel indeksleme yapmak sorun olmayacak gibi. Yalnızca bir karakter ileri ya da geri gitmek gerekiyorsa indekse ihtiyaç yok; dolayısıyla iç gösterimin UTF-8 olması da gayet mümkün

    • str’yi temsil eden şey PyUnicode nesnesidir
      UTF-8 baytları istendiğinde, gerekirse bir bytes nesnesi oluşturulur, PyUnicode’un parçası olarak önbelleğe alınır ve PyUnicode serbest bırakıldığında onunla birlikte serbest bırakılır. Bunun dışında, dizgeyi oluşturan kod noktaları rastgele erişim mümkün olsun diye basit bir dizide saklanır. Her kod noktasının boyutu 1, 2 veya 4 bayt olabilir; PyUnicode oluşturulurken en büyük kod noktası değeri belirtildiğinde bu değer 127, 255, 65535 veya 1.114.111 değerlerinden birine yuvarlanır ve 1/2/4 bayt kullanılıp kullanılmayacağı belirlenir
      En büyük kod noktası değeri 127 ise bu dizi gösterimi doğrudan UTF-8 olarak kullanılabilir. Yani sorunun yanıtı şu: Tüm kod noktaları 127 veya altında olan pek çok dizge UTF-8 olarak saklanır. Ancak bir dizge üzerinde gezinirken bunu kod noktası biriminde yapmamak gerekir. Kullanıcının algıladığı karakter, yani grafem kümesi, bir veya daha fazla kod noktasından oluşur. Örneğin aksanlı bir e, e kod noktasının ardından gelen bir birleştirici aksan kod noktasından oluşabilir; anka kuşu emojisi ise kuş emojisi, sıfır genişlikli birleştirici ve ateş emojisinden oluşur. Yüz milyonlarca kişinin kullandığı bazı yazı sistemleri de ünsüzlere ünlüyü gösteren birleştirici işaretlerin eklenmesine benzer bir şekilde çalışır. Bu - - 5 kod noktasıdır ve çeşitli dillerin bunun “uzunluğunu” nasıl raporladığını ele alan iyi bir yazı var: https://hsivonen.fi/string-length/. Bu kısmı ele alan Unicode TR29’u Python C uzantısı olarak yeni uygulamış biri olarak söylüyorum
  • Neden utf-8-sig olmadığını merak ediyorum. İsteğe bağlı BOM’u işliyor; daha geçen hafta bu yüzden bir betiği düzeltmem gerekti

    • Artık hiçbir şey UTF-8’e BOM koymamalı
      Önerilmiyor da; günümüzde BOM yüzünden hata vermek de makul bir davranış bence
    • Python’ı tüm girdi/çıktıların başına sessizce görünmez bir BOM ekleyecek şekilde değiştirmek iyi bir fikir değil
  • UTF-8’den söz açılmışken, Linux framebuffer’ın çoktan doğru düzgün UTF-8 desteğine sahip olması gerekirdi
    256/512 gliflik olanlardan değil, gerçek destekten bahsediyorum. GNU Hurd’ün bile yaklaşık 2007’den beri UTF-8 destekleyen daha iyi bir terminal konsolu vardı; şimdi 2024’teyiz

  • Güzel. Şimdi geriye yalnızca JS’nin UTF-8’e geçmesi kaldı
    Tabii JS iyileştirilemez. Çünkü başka hiçbir programlama dilinden farklı olarak 1995’te yazılmış kodlarla uyumlu olmak zorunda

    • Bu, Python’dan bir dosyayı “metin olarak” açmasını istediğinizde varsayılan olarak hangi kodlamayı kullanacağıyla ilgili
      Dizgelerin iç temsili ayrı bir konu; JavaScript gibi Python da içeride “sadece UTF-8” kullanmıyor
  • “Unix kullanan pek çok Python geliştiricisi, varsayılan kodlamanın platforma bağlı olduğunu unutuyor ve UTF-8 ile kodlanmış metin dosyalarını okurken encoding="utf-8" belirtmeyi atlıyor” kısmında, bu unutulmaktan ziyade yeterince bilinmiyor olabilir
    Açıkçası Python’ın açıkça farklı bir şey istenmediği sürece her yerde yalnızca UTF-8 kullandığını sanıyordum

    • Gerçekte duruma göre değişiyor
      bytes.decode ve str.encode, en azından Python 3’ten beri varsayılan olarak UTF-8 kullanıyor. Buna karşılık dosya adlarını çözerken varsayılan kodlama sys.getfilesystemencoding() kullanır; Windows ve macOS’te bu da UTF-8’dir, ama Linux’ta yerel ayara, daha somut olarak CODESET’e bağlıdır. Son olarak open doğrudan locale.getencoding() kullanır