Pereda Sakit Kepala: Urgensi Memahami String (di Python)


Tipe variabel string gampang² susah untuk dipahami, sebenarnya tidak hanya di Python, tapi juga di hampir semua bahasa pemrograman. Kadang ia baik, kadang pemarah ... eh salah ... maksudnya selain mempengaruhi akurasi model data science/machine learning, program yang berjalan dengan baik bisa tiba² crash karena penanganan string yang tidak tepat. Di level produksi minimal ini adalah disaster level Dragon (OPM-😁).
[Python 3 Unicode]
Tulisan ini diambil terinspirasi dari video presentasi Ned Batchelder
 di PyCon 2012 [Link], saya akan banyak menggunakan referensi ini ketika membahas tentang string di Python (juga "pinjem" beberapa gambarnya 😄 - semoga beliau ikhlas).  Tulisan ini hanya akan membahas string di Python 3, bagi mereka yang retro dan ga mau move on dari Mantan Python 2, silahkan konsultasi dengan Pak Ned Batchelder 😄. Ok mari kita mulai ...
Sebenarnya string di Python mudah kan ya? Misal:
  T1 = "Hi, ini adalah contoh pertama string di Python 3"
    T2 = "dan ini adalah contoh kedua"
Lalu string tersebut sebenarnya adalah class/object yang bisa di akses dan operasikan seperti tuple/list, misal:
   print(T1[:30] + T2[0:3] + T2[-6:] + " jika digabungkan")
outputnya adalah:
"Hi, ini adalah contoh pertama dan kedua jika digabungkan"
atau
  print("Panjang string T1={0} dan panjang string T2={1}".format(len(T1),len(T2)))
outputnya:
"Panjang string T1=48 dan panjang string T2=27"
Lalu karena string adalah class/object, maka ia punya beberapa method, misal:
T1.split() # output => ['Hi,', 'ini', 'adalah', 'contoh', 'pertama', 'string', 'di', 
'Python', '3']
    print(T1[:3].upper() + T2[-6:].lower()) # output => "HI kedua"
Berikut ini kurang-lebih daftar Methods-nya:
Masalah selesai kan? Ga susah kan? .... Sayangnya ... ini masih level beginner. Masalah akan mulai muncul ketika Text berasal dari sumber lain, misal dari aplikasi, media sosial, web, imported files, dsb.  Untuk membahas string ke level berikutnya kita harus mulai dari teori ala kuliah dulu waktu di TK Penerbangan jurusan Sastra Komputer. Kita akan pakai konsep Ned Batchelder dalam membahas ini : "5 Fakta + 3 tips profesional".

Fakta #1 : Komputer Mengolah Text dalam Bytes

Komputer menyimpan, mengoperasikan, menerima (received) dan mengirimkan (send) string dalam bentuk bytes. Wajar saja karena, bytes (e.g. 0110010101) is the language of the machines. Namun demikian, komputer perlu berinteraksi dengan manusia, sehingga bytes tersebut perlu dipresentasikan ke dalam bentuk yang bisa dipahami manusia dengan mudah [great articles about this: Link1, Link2].  Misal, kita membuat pemetaan seperti ini:
Dengan begitu kita ga perlu lagi hidup seperti di Film the Matrix:
[Kira-The Matrix 😎]
Nah konversi dari byte ke bentuk yang manusiawi ini yang disebut sebagai "Encoding". Proses sebaliknya disebut "Decoding". Tapi tentu saja kita perlu kesepakatan umum untuk memetakan dari byte ke characters. Dengan menggunakan 1 byte, early encodings yang paling mahsyur adalah encodings berikut:
[Encoding Byte to character using only 1 byte, source @nedbat bit.ly/unipain]
Nah disini mulai timbul masalah. 1 byte = 8 bits = 8 binari digit 0 atau 1. Sehingga total kombinasi adalah 2^8 = 256. Padahal Kanji huruf jepang saja >10.000 jumlahnya. Belum lagi Kanji Cina, huruf Arab, korea, perancis, emoji, huruf Alay para ABG, dsb. Sehingga sampailah kita pada Fakta Ned Batchelder kedua:

Fakta #2: Dunia butuh lebih dari 1 byte characters

Ok, sudah paham kalau butuh >1 byte untuk encoding, karena ternyata penduduk dunia ndak cuma berbicara dalam bahasa Indonesia & Inggris 😁. Tapi apakah kita akan pakai 2 byte? 3 byte? ... atau???.... Nope, ternyata pendekatan ini kurang baik untuk di generalisir ke masa depan (Silahkan tonton video Pak Ned untuk keterangan lebih jelasnya).  Artinya kelak jika kita ingin mendaftarkan karakter² baru ciptaan para ABG Alayers, maka akan sulit membuat pemetaan yang konsisten.
Sehingga terciptalah sistem "UNICODE". Unicode menggunakan "Code Points" (CP) dalam bentuk integers dengan jumlah kombinasi sekitar 1,1 juta. Dalam versi terkini (ver 10 di juni 2017) sudah terpakai  136.755 CP to characters mapping untuk mengakomodir sekitar 139 macam scripts (~bahasa). Artinya baru terpakai sekitar 10% saja dari slot CP yang tersedia untuk mengakomodir berbagai bahasa utama di dunia ini. Pemetaan Unicode lengkapnya bisa dilihat disini. Dengan Unicode ini bahkan Aksara Jawa juga bisa di presentasikan oleh komputer [Link].
Kita bisa melihat mapping characters (Code Points) ini di Python dengan fungsi ord, contoh:
print([ord(t) for t in T1]) # output => [72, 105, 44, 32, ... , 110, 32, 51]
di Python kita juga bisa melakukan hal ini:
print("\N{GREEK CAPITAL LETTER DELTA}") # output (system dependent) => 'Δ'
di Python 3 default string adalah str dan str ini adalah Unicode:
print(type(T1)) # output => str
Kalau kita mau menyimpan teks dalam bytes, maka di Python 3 perintahnya seperti ini:   
T3 = b"Hi, ini adalah contoh pertama string di Python 3"
Perhatikan ada "b" kecil sebelum tanda " pertama. Kalau kita lakukan perintah
print(type(T3))
Maka outputnya adalah "bytes".
Python string dalam bytes tetap bisa dioperasikan sebagaimana unicode string, misal fungsi split, strip, lower, dsb.  Apakah dengan ini maka sebaiknya kita cukup memahami atau memakai satu tipe saja (bytes/unicode)? .... Sayangnya tidak. Mengapa? Nanti kita bahas di Fakta #3. Sebelum kesana saya mau mengingatkan hal berikut:
  print(b"hi"=="hi") # output => False
Artinya hati-hati dalam menyimpan string di Python, yakinkan semua seragam dalam bentuk tertentu yang seragam Unicode atau bytes (Unicode is recommended-Nanti ada di Pro tips #1).

Fakta #3: Kita Harus Memahami Keduanya: Bytes dan Unicode

Kalau di dunia ini semua orang hanya menggunakan Alfanumerik di komputer ("a-z" & 0-9) maka kita ndak perlu memahami bytes dan unicodes sekaligus. Tapi sayangnya dunia tak seindah surga, tidak demikian. Bayangkan anda membuat software, lalu di install di perusahaan client, lalu client menggunakan Kanji Jepang. Atau clientnya orang Indonesia, tapi karyawannya alay dan menginput karakter para ABG di input programnya. Atau bayangkan agan melakukan Social Media Analytics dari para Jomblo yang juga Alay. ... Nah !!!... Mulai paham kan disaster level Dragon di awal artikel ini?.... 😄.
Ok berikut contoh sederhananya. Misal client mengirim suatu text ("你好" : "Hello There"):
T4 = b'\xe4\xbd\xa0\xe5\xa5\xbd' # pesan sebenarnya = "你好"
Lalu file tersebut di simpan dalam sebuah file atau dikirim lewat internet ke aplikasi kita. Ingat pesan disimpan dan dikirim oleh komputer dalam bentuk bytes (Fakta #1). Sehingga ketika pesan itu sampai ke server/komputer, kita perlu men-decodenya. Apabila kita berfikir pesannya dalam bentuk ASCII dan melakukan hal ini:
T4.decode(encoding='ascii') # output => Error!!!!.... Disaster happen!...
Mengapa? Karena karakter di T4 tidak bisa di petakan di tabel pemetaan ASCII. Atau kode pemetaannya diluar domainnya si ASCII. Tapi kalau kita lakukan ini:
print(T4.decode(encoding='utf-8')) 
Maka outputnya benar = "你好".  Sehingga sampailah kita pada Fakta #4.

Fakta #4: Encoding tidak bisa di infer (ketahui secara otomatis)

Dengan kata lain: "Kita harus Wajib bin Kudu wal Tau apa encoding bytes string yang dikirimkan, simpan, atau olah".
Loh bukannya kita bisa Trial and error aja dalam melakukan encoding? Misal:
List_Encodings = [e1,e2,...,eN] # Misal e1, e2, ... eN adalah berbagai macam encodings.
lalu lakukan:
  for encoding in List_Encodings :
        try:
            T4.decode(encoding=encoding)
            break  #Stop nyoba kalau ndak error
        except:
            pass # Nyobain encoding berikutnya kalau terjadi error
Iya kan? iya kan? .... bisa kan? .... #NgarepSambilPasangMataCute #AlaKucing #TapiNdakImut
Sayangnya tidak. Suatu bytes text memungkinkan untuk di encode dengan beberapa encoding, namun nanti akan menghasilkan output yang berbeda-beda (tanpa pesan error). Pak Ned sudah kasih contohnya:
[Ilustrasi (contoh) mengapa coba² encoding is a bad idea]
"Lalu apa yang harus kita lakukaaaannnn???..." #ModeDramaIndia:ON #KeduaTanganPegangKepala #KameraMuterMuterSambilMajuMundur ....
Tenang Gan ... Best practice-nya adalah menggunakan informasi yang embedded di suatu file. Informasi ini bisa kita dapatkan dari "meta"-information filenya. Misal di Code Python suka ada tulisan seperti ini:
atau di XML seperti ini: <?xml version="1.0" encoding="ISO-8859-1"?>
atau JSON style: Content-Type: application/json; charset=utf-8', dsb.
Kalau di Python 3, best practice-nya adalah membuka file dengan secara explicit menyatakan akan menggunakan encoding apa dan mode apa, misal jika ingin read dengan mode binary dan encoding 'utf-8':
  with open('FileTeks.txt', encoding='utf-8', mode='rt') as file:
        for baris in file:
            print(baris)
Atau jika ingin jika ingin membacanya dalam encoding Arabic (cp720) dalam bentuk bytes, berarti perintahnya jadi seperti ini:   
with open('FileTeks.txt', encoding='cp720', mode='rb') as file:
        for baris in file:
            print(baris)
Daftar tipe "mode"-nya bisa diakses disini dan daftar encoder Python (>100) bisa di dapatkan disini. Bisa juga menggunakan "codecs" dan (misal) kita ingin write ke file maka jadi ndak perlu hawatir lagi dengan encoding-decoding:
import codecs
    f = codecs.open('file.txt','w','utf-8')
    f.write(Unicode_string)  # Stored on disk as UTF-8
Apakah masalah kita sudah selesai sampai disini? ... Maaf gan... belum ... 😅 ... Sampailah kita pada Fakta terakhir.

Fakta #5: People Lies, Data is Dirty

Sayangnya meta information yang diberikan di dalam file yang kita terima/buka tidak selamanya benar/konsisten. Begitu juga jika Agan memberikan software/app/sistem ke client dan memberikan requirement ke mereka bahwa system hanya bisa menerima (misal) utf-8 input dan output, tapi mungkin saja bisa terjadi dimana inputnya diluar batasan tersebut. Hal ini juga terjadi ketika mengolah data media sosial. Terkadang walau di meta (profile) user datanya terindikasi "en" atau "id", bukan berarti post-nya selalu mengikuti bahasa tersebut. Belum ditambah para Alayers yang sudah kita bahas sebelumnya. Tapiii... ga usah hawatir kok, ada beberapa trik yang bisa dilakukan/consider untuk menangani hal ini. Tapi dibahwas di tulisan lain saja ya. Yang ini sudah kepanjangan.
Lalu apa yang bisa kita lakukaaaannn???... #DramaLagi...
Inilah saatnya membahas 3 Pro Tips dari Pak Ned Batchelder.

Pro Tips#1: Sandwhich Method

Intinya "byte-Unicode-byte", artinya data diterima/buka dalam bentuk bytes, tapi kemudian segera rubah dalam bentuk unicode ketika masuk sistem (misal NLP analytics). Kemudian di akhir sekali dari prosesnya kembali ke bytes (ilustrasinya Pak Ned).  Tapi kalau bagi orang Data Science atau orang Social Media Analytics, "menurut saya" ndak begitu perlu juga melakukan sandwhich ini. Karena output biasanya berupa hasil analisa baik berupa model, visualisasi, atau interpretasinya. Tentu saja lain cerita lagi bagi rekan-rekan di (big) data engineer. Gagal menangani string ini akan berakibat kehilangan data atau minimal rusak/hilangnya data dalam jumlah yang bisa jadi sangat signifikan.

Pro tips #2: Mengetahui Tipe Data saat ini (bytes/unicode)

Ini sebenarnya kalau kata orang Sunda:" it goes without saying" atau bahasa Jerman-nya "ya iyalaaahhh" ... Seandainya tips #1 dijalankan dengan baik, tips #2 ini juga jadi tidak terlalu penting. Tapi jika ragu ga ada salahnya gunakan perintah "type" di Python untuk memeriksa tipe string yang sekarang sedang digunakan.

Pro tips #3: Testing

Ini Standard Operating Procedure (SOP), tentu saja. Melakukan Beta testing lalu mencoba sistem kita menerima data dalam dunia nyata.

Saya juga punya beberapa tips personal sederhana:

Kalau Agan punya tips dan ingin di share (sebagai amalan Agan :) ), silahkan comment di bawah. [1]. Mengetahui suatu string ASCII atau bukan: Agan bisa lakukan ini untuk memeriksa/filtering non Ascii Texts: isascii = lambda s: len(s) == len(s.encode())        Lalu menggunakan fungsi tersebut dengan cara seperti ini:
           for word in a LongListofText:                if isascii(word):                    print(word)
[2]. Encoding with Exceptions        Zen Python bilang: "Errors should never pass silently. Unless explicitly silenced." Untuk menghindari error encoding, kita bisa menambahkan parameter exception "ignore", misal:             T4.decode(encoding="utf-8", errors="ignore")        Pilihan error handling lainnya bisa 'strict' (default), 'replace', 'xmlcharrefreplace',       'backslashreplace' atau sembarang codecs register error (baca disni). Walau dengan ignore kita bisa menghindari Disaster level dragon, tapi ... sebaiknya "menurut saya" kita tetap berhati-hati dengan encoding error ini. Misal kasusnya jika sekelompok data dari client memang menggunakan string dengan encoding diluar kebiasaan. Atau ketika kita mau mendeteksi percakapan khusus di suatu sistem (misal para koruptor atau teroris). Maka ada baiknya melakukan Try and catch, misal.
try:
        T4.decode(encoding="utf-8", errors="strict")
    except:
         TheExceptionsList.append(T4)
Ketika TheexceptionList membesar, kita mulai curiga ada udang dibalik bakwan. Lalu lakukan sesuatu akannya. Di Data Science/Sosial Media Analytics/NLP exceptions ini sebenarnya jika tidak signifikan jumlahnya sebenarnya tidak terlalu bermasalah (kita bahas nanti di post lain).
[3]. utf-8 encoding paling sakti.
Ascii bisa direpresentasikan dengan utf-8, tapi tidak sebaliknya. (Ini dibahas di videonya Pak Ned). Intinya our safest bet adalah utf-8. [4]. Read dalam bytes terkadang lebih cepat [Link] [5]. Code Snippet ini:
import six
text = b'Any byte text from any source'
if isinstance(text, six.binary_type):
    text = text.decode('utf-8')
Gan, ente buat sesuatu yang simpel jadi rumit ... ente orang orde baru ya? ....
Maaf Gan, bukan maksud ane mempersulit keadaan bangsa kita ini ... tapi hal ini tetap perlu untuk disampaikan agar analytic engine dan model data science kita lebih robust #SambilPasangPoseBiksu&PegangPegangJenggot.
Ah udah ah ... artikelnya udah kepanjangan (saatnya balik ke kerjaan kantor 😂) ...  Silahkan share jika sekiranya bermanfaat, like kalau emang suka, dan cicing wae kalau ngga ... 😁
Cheers,
</TES>®

1 komentar:

  1. This URL https://unicode-table.com/ has changed to https://symbl.cc/

    BalasHapus

Relevant & Respectful Comments Only.