3 poin oleh GN⁺ 2024-04-28 | 1 komentar | Bagikan ke WhatsApp
  • Python 3.15 menyalakan mode UTF-8 secara default sehingga encoding bawaan untuk file, input/output standar, dan pipe menjadi UTF-8
  • UTF-8 sudah digunakan sebagai encoding standar de facto di source file, JSON·TOML·YAML, editor utama, data web, serta Node.js·Go·Rust·Java, sehingga interoperabilitas meningkat
  • Encoding default sebelumnya berbeda menurut platform, sehingga bila pengembang Unix menghilangkan encoding="utf-8", dapat muncul bug ketidakcocokan di Windows dan lingkungan lain
  • Jika perlu, mode ini bisa dimatikan dengan PYTHONUTF8=0 atau -X utf8=0; untuk pemeriksaan kompatibilitas digunakan EncodingWarning, encoding="utf-8", encoding="locale", dan locale.getencoding()
  • Program yang bergantung pada encoding default dapat mengalami UnicodeError, mojibake, atau kerusakan data diam-diam, terutama di Windows, sehingga perlu diperiksa lebih dulu

Encoding default yang berubah di Python 3.15

  • PEP 686 adalah perubahan yang mengaktifkan mode UTF-8 dari PEP 540 sebagai nilai default
  • Untuk file, stdio, dan pipe yang memerlukan encoding default, Python akan secara konsisten menggunakan UTF-8
  • Mulai Python 3.15, ini akan aktif secara default, dan pengguna dapat menonaktifkannya dengan cara berikut
    • PYTHONUTF8=0
    • -X utf8=0

Mengapa UTF-8 dijadikan default

  • UTF-8 telah menjadi semacam encoding teks standar di berbagai lingkungan
    • Encoding default untuk source file Python adalah UTF-8
    • JSON, TOML, YAML menggunakan UTF-8
    • Sebagian besar editor teks, termasuk Visual Studio Code dan Windows Notepad, menggunakan UTF-8 sebagai default
    • Sebagian besar situs web dan data teks di internet menggunakan UTF-8
    • Banyak bahasa pemrograman populer, termasuk Node.js, Go, Rust, dan Java, menggunakan UTF-8 sebagai default
  • Jika encoding default Python berubah menjadi UTF-8, interoperabilitas dengan alat, bahasa, dan format data lain akan membaik
  • Banyak pengembang Python di lingkungan Unix lupa bahwa encoding default bergantung pada platform, lalu menghilangkan encoding="utf-8" saat membaca teks UTF-8 seperti file JSON·TOML·Markdown·source file Python
  • Perbedaan encoding default antarplatform menjadi penyebab bug yang membuat kode seperti itu rusak di lingkungan lain

Perubahan pada API locale dan encoding="locale"

  • Karena mode UTF-8 memengaruhi locale.getpreferredencoding(False), dibutuhkan API untuk mendapatkan encoding locale tanpa terpengaruh mode UTF-8
  • locale.getencoding() ditambahkan untuk tujuan ini, dan mengembalikan encoding locale sambil mengabaikan mode UTF-8
    • API ini ditambahkan di Python 3.11
  • Jika opsi warn_default_encoding ditentukan, locale.getpreferredencoding() akan memunculkan EncodingWarning dari PEP 597, seperti open()
  • PEP 597 menambahkan opsi encoding="locale" ke TextIOWrapper agar encoding locale bisa ditentukan secara eksplisit
  • Sebelumnya, bahkan jika encoding="locale" ditentukan dalam mode UTF-8, TextIOWrapper tetap menggunakan "UTF-8"
    • Ini tidak sejalan dengan motivasi PEP 597
    • Alasannya karena saat encoding teks default Python berubah, situasi di mana mode UTF-8 menjadi default belum diperkirakan
  • Ketidaksesuaian ini diperbaiki di Python 3.11, dan kini jika encoding="locale" diberikan, encoding locale akan digunakan bahkan dalam mode UTF-8

Kompatibilitas mundur dan prosedur migrasi

  • Sebagian besar sistem Unix menggunakan locale UTF-8, dan Python mengaktifkan mode UTF-8 saat locale adalah C atau POSIX, sehingga dampak perubahan ini terutama terkonsentrasi pada pengguna Windows
  • Program Python yang bergantung pada encoding default dapat mengalami masalah berikut
    • UnicodeError
    • mojibake
    • kerusakan data diam-diam
  • Prosedur yang direkomendasikan untuk memperbaiki masalah kompatibilitas mundur adalah sebagai berikut
    1. Nonaktifkan mode UTF-8
    2. Gunakan EncodingWarning dari PEP 597 untuk menemukan lokasi yang terdampak oleh mode UTF-8
      • Jika opsi encoding dihilangkan, pertimbangkan penggunaan encoding="utf-8" atau encoding="locale"
      • Jika locale.getpreferredencoding() digunakan, pertimbangkan penggunaan "utf-8" atau locale.getencoding()
    3. Uji aplikasi dalam mode UTF-8

Preseden Ruby·Java dan alternatif yang ditolak

  • Ruby mengubah external_encoding default di Windows menjadi UTF-8 pada Ruby 3.0 tahun 2020 melalui perubahan ini
  • Java mengubah encoding teks default menjadi UTF-8 pada JDK 18 tahun 2022 melalui perubahan ini
  • Ruby dan Java sama-sama menyediakan opsi untuk kompatibilitas mundur, tetapi tidak menyediakan peringatan penggunaan encoding default seperti EncodingWarning di Python
  • Opsi untuk menghapus penggunaan encoding default sepenuhnya ditolak
    • Encoding default sering dipakai untuk membaca dan menulis teks ASCII saja
    • Untuk aplikasi non-cross-platform yang hanya berjalan di Unix, peringatan seperti itu tidak terlalu berguna
    • Jika encoding dipaksa di semua tempat, beban pengguna akan besar, dan terlalu banyak DeprecationWarning dapat membuat pengguna mengabaikan peringatan
    • PEP 387 memang meminta penambahan peringatan untuk perubahan yang merusak kompatibilitas mundur, tetapi tidak selalu mewajibkan DeprecationWarning
  • Opsi untuk memakai PYTHONIOENCODING sebagai encoding default pipe di modul subprocess juga ditolak
    • Pendekatan ini memungkinkan subprocess.Popen(text=True) memakai encoding lama bahkan dalam mode UTF-8
    • Namun itu membuat “encoding default” menjadi lebih rumit, dan pendekatan itu sendiri juga merupakan perubahan yang merusak kompatibilitas mundur
    • Pengguna dapat mematikan mode UTF-8 sampai mereka mengganti text=True menjadi encoding="utf-8" atau encoding="locale"

Dari sudut pandang edukasi pengguna

  • Pengguna baru jadi lebih jarang perlu mempelajari encoding teks selama tahun pertama
  • Mereka cukup mempelajari encoding saat perlu menangani file teks non-UTF-8
  • Pengguna lama perlu memeriksa titik-titik yang terdampak sesuai prosedur kompatibilitas mundur

1 komentar

 
GN⁺ 2024-04-28
Opini Hacker News
  • Selalu menyebalkan bahwa encoding file teks default berbeda tergantung platform, jadi perubahan ini menyenangkan
    Bagus juga bahwa mereka tidak mencoba mengutak-atik encoding filesystem. Itu masalah terpisah, dan sama-sama merepotkan

    • Code page default sistem di Windows tidak hanya bergantung pada platform, tetapi juga pada locale sistem
      Kesalahan besar bahwa Windows begitu lama tidak menyediakan cara sederhana untuk memilih agar fungsi ANSI seperti TextOutA memakai code page UTF-8. Ini baru bisa dilakukan lewat file manifest sekitar pertengahan pengembangan Windows 10, padahal fitur seperti ini seharusnya sudah ada sejak era NT4 atau Windows 98
    • Secara historis, itu masuk akal. Sebagian besar software bersifat lokal saja, dan file teks juga diharapkan memakai encoding lokal
      Bukan hanya platform, tetapi juga bergantung pada locale pilihan pengguna, dan pustaka standar C juga bekerja dengan cara yang sama. Misalnya di Unix/Linux, untuk bahasa Eropa Barat iso-8859-1 umum dipakai, dan setelah euro diperkenalkan, orang sering beralih ke iso-8859-15 yang memiliki simbol . UTF-8 mulai bekerja tanpa masalah sekitar akhir 2000-an, dan Debian mengubah default-nya ke UTF-8 pada rilis Etch
    • Beberapa hari lalu saya terkena masalah perubahan newline secara implisit
      Di laptop kantor, semua pengujian lokal berjalan baik, tetapi ketika dideploy ke host Linux, aplikasi turunan tidak bisa mengonsumsinya karena membutuhkan CRLF. Ini salah satu masalah kecil dan bodoh yang sesekali harus diingat. Meski begitu, pertanyaan yang valid juga mengapa software yang baru ditulis membutuhkan terminator baris tertentu
    • Ketika seseorang mulai menulis kode di Windows, saya sudah berkali-kali terkena masalah ini
  • Tidak bergantung pada default sistem yang tidak stabil adalah hal yang baik
    Nilai-nilai seperti ini cenderung suatu saat kembali berbeda dari asumsi saya. Beberapa tahun lalu saat menangani Ubuntu dan skrip init.d, skrip yang menjalankan Java dijalankan sebagai root—dan karena itu masih era sebelum Docker, kondisinya lebih terasa—di shell yang tidak menetapkan default UTF-8 normal untuk pengguna biasa. Akibatnya, penggunaan API Java yang buruk, yang memakai default OS, jadi terlihat
    Sekarang sebagian besar API punya varian yang memungkinkan encoding dinyatakan secara eksplisit, dan static code checker juga memberi peringatan jika memakai yang salah. Namun jika satu tempat saja terlewat, konten mulai rusak. Kini penggunaan encoding selain UTF-8 kemungkinan besar tidak disengaja; kalau memang disengaja, harus dinyatakan eksplisit alih-alih bergantung pada konfigurasi tidak langsung OS yang aneh. Jadi ini perubahan yang baik, dan kode yang rusak karenanya lebih baik diberi perbaikan sederhana

    • Saya pernah memakai .gitignore yang dibuat oleh fungsi touch alias di PowerShell, tetapi Git sama sekali tidak menghormatinya
      Setelah dicek, file teks yang dibuat ternyata UTF-16, sehingga pada dasarnya diabaikan. Saya mendapat pelajaran dan mengubah default sistem ke UTF-8, tetapi sekarang saya hanya mengandalkan editor teks
    • Locale global adalah kesalahan secara umum, bukan hanya soal encoding
      Jika printf("%f", 4.2) secara ajaib mencetak string berbeda tergantung lingkungan, masalah yang ditimbulkan lebih banyak daripada yang diselesaikan. Jika menginginkan perilaku yang bergantung pada locale, informasi locale atau bagian terkait harus diteruskan secara eksplisit ke fungsi
  • Ada heuristik yang makin lama makin tepat selama beberapa dekade terakhir: jika di suatu tempat ada pengaturan charset dan bukan UTF-8, maka itu salah
    Python 2 tidak bergantung pada charset sehingga selalu berjalan, tetapi perbaikan di Python 3 bukan sekadar perbaikan sederhana. Cara membedakan skrip Python 3 dan skrip Python 2 adalah begini: jika berisi string utf-8, itu Python 3; jika hanya berjalan pada locale C.UTF-8, itu Python 3. Perubahan ini saya sambut baik karena terasa seperti “memperbaiki” Python 3

  • Saya kira itu sudah menjadi default sejak Python 3

    • Mungkin yang dimaksud adalah string yang sejak Python 3 tidak lagi membutuhkan prefiks u""
      Saya baru saja mengetik "éķů" di Python 2.7 dan ia mencetak byte UTF-8 untuk karakter tersebut, jadi saya tidak sepenuhnya yakin apa tepatnya fungsi prefiks u. Namun salah satu perubahan besar saat beralih dari Python 2 ke 3 adalah string memiliki encoding, sedangkan byte string menjadi urutan byte tanpa encoding. Perubahan ini tampaknya terutama terkait masalah saat memakai open('filename', mode='r') di lingkungan seperti Windows yang encoding default-nya bukan UTF-8, sehingga sebelumnya perlu menuliskan open('filename', mode='r', encoding='UTF-8') secara eksplisit
    • Di Python 3, kode sumber Python secara default adalah UTF-8. Namun itu tidak mengatakan apa pun tentang encoding karakter yang dipakai saat menyimpan ke file, dan default-nya bergantung pada locale
      Seperti Path("filenames use their own encoding").write_text("file content encoding uses yet another encoding"), encoding untuk literal string, nama file, dan isi file masing-masing berbeda. Encoding yang bersesuaian adalah UTF-8 pada tokenize.open, sys.getfilesystemencoding() pada os.fsencode, dan locale.getpreferredencoding() pada open
  • Katanya “bahasa pemrograman populer lain termasuk Node.js, Go, Rust, dan Java juga memakai UTF-8 sebagai default”; saya rupanya melewatkan bahwa Java berpindah dari UTF-16 ke UTF-8

    • Encoding default saat mengubah byte menjadi string di Java dulunya bergantung pada platform, dan sekarang adalah UTF-8
      Di dalam kelas String, encoding UTF-16 dan latin-1 masih digunakan, dan JVM tetap memakai encoding modified UTF-8 seperti sebelumnya. Kelas String awalnya hanya memakai UTF-16, tetapi sejak Java 9 juga memakai encoding latin-1 1 byte per karakter jika memungkinkan
    • Sepertinya ini mencampuradukkan representasi string internal dengan encoding baca/tulis
      Java tidak pernah memakai UTF-16 sebagai default untuk encoding baca/tulis
    • Tampaknya ini berubah dua tahun lalu di Java 18
  • Apakah encoding internal CPython sekarang UTF-8?
    String Python bisa diindeks dengan subskrip, tetapi akses acak cukup jarang sehingga sepertinya tidak masalah jika pengindeksan dilakukan secara lazy saat diperlukan. Kalau hanya maju satu posisi atau mundur satu posisi, indeks tidak diperlukan, jadi representasi internal dengan UTF-8 juga sepenuhnya memungkinkan

    • Yang merepresentasikan str adalah objek PyUnicode
      Ketika byte UTF-8 diminta, objek bytes dibuat jika diperlukan dan di-cache sebagai bagian dari PyUnicode, lalu dibebaskan bersama saat PyUnicode dibebaskan. Secara terpisah, code point yang menyusun string disimpan dalam array sederhana agar akses acak dimungkinkan. Ukuran tiap code point bisa 1, 2, atau 4 byte; saat membuat PyUnicode, jika nilai code point maksimum ditentukan, nilainya dibulatkan naik ke salah satu dari 127, 255, 65535, atau 1.114.111, yang menentukan apakah akan memakai 1/2/4 byte
      Jika nilai code point maksimum adalah 127, representasi array itu bisa langsung dipakai sebagai UTF-8. Jadi jawaban atas pertanyaannya adalah: banyak string yang semua code point-nya 127 atau kurang memang disimpan sebagai UTF-8. Namun, saat mengiterasi string, jangan melakukannya per code point. Karakter yang dikenali pengguna, yaitu klaster grafem, terdiri dari satu atau lebih code point. Misalnya e beraksen bisa berupa code point e diikuti code point aksen penggabung, dan emoji phoenix terdiri dari emoji burung, zero-width joiner, dan emoji api. Beberapa sistem tulisan yang dipakai ratusan juta orang juga mirip, dengan tanda penggabung untuk vokal yang dilekatkan pada konsonan. - - ini terdiri dari 5 code point, dan ada tulisan bagus yang membahas bagaimana berbagai bahasa melaporkan “panjang”-nya: https://hsivonen.fi/string-length/. Ini berdasarkan pengalaman saya baru saja mengimplementasikan Unicode TR29 yang menangani bagian ini sebagai ekstensi C Python
  • Saya penasaran kenapa bukan utf-8-sig. Itu menangani BOM opsional, dan baru minggu lalu saya harus memperbaiki skrip karena hal itu

    • Sekarang seharusnya tidak ada apa pun yang menaruh BOM di UTF-8
      Itu juga tidak direkomendasikan, dan menurut saya perilaku yang gagal karena BOM saat ini pun masuk akal
    • Mengubah Python agar diam-diam menambahkan BOM tak terlihat di depan semua input/output bukanlah ide bagus
  • Kalau bicara UTF-8, framebuffer Linux seharusnya sudah lama punya dukungan UTF-8 yang benar
    Maksudnya dukungan sungguhan, bukan yang hanya 256/512 glyph. Bahkan GNU Hurd saja sudah punya konsol terminal yang lebih baik dengan dukungan UTF-8 sejak sekitar 2007, sementara sekarang sudah 2024

  • Bagus. Sekarang yang tersisa hanya JS beralih ke UTF-8
    Tentu saja JS tidak bisa diperbaiki. Karena tidak seperti bahasa pemrograman lain mana pun, ia harus kompatibel dengan kode yang ditulis pada 1995

    • Ini soal encoding apa yang dipakai secara default saat kita meminta Python membuka file “sebagai teks”
      Representasi internal string adalah persoalan terpisah, dan seperti JavaScript, Python juga tidak memakai “UTF-8 begitu saja” secara internal
  • Dalam “Banyak developer Python yang memakai Unix lupa bahwa encoding default bergantung pada platform, dan menghilangkan encoding="utf-8" saat membaca file teks yang dienkode UTF-8”, mungkin ini bukan karena lupa, melainkan karena hal itu tidak cukup dikenal
    Sejujurnya saya mengira Python hanya memakai UTF-8 di mana-mana kecuali diminta lain secara eksplisit

    • Kenyataannya tergantung kasus
      bytes.decode dan str.encode sudah memakai UTF-8 sebagai default setidaknya sejak Python 3. Sebaliknya, encoding default saat mendekode nama file memakai sys.getfilesystemencoding(), dan di Windows serta macOS ini juga UTF-8, tetapi di Linux bergantung pada locale, tepatnya CODESET. Terakhir, open memakai locale.getencoding() secara langsung