JSON prettifier

Once in a while I need to prettify a JSON document (read: add indentations and new lines) to make it (more) readable. Usually what I will do is finding an online prettifier and copy-paste the JSON onto there. What I usually don’t realize is that I already have that tool in my toolbox! I figured this out from a post somewhere on the net, sorry I don’t remember exactly where.

If you have Python installed, that means you already have a tool that can make a JSON document pretty. Just use it as the following.

$ python -m json.tool document.json

Or if you are an stdin fan, it can also read from there.

$ python -m json.tool < document.json

There you have it!

Mengelompokkan barisan nilai

*Sepertinya judul tulisan ini aga2 tidak menjelaskan xD*

Intinya begini. Saya punya sebuah barisan nilai, katakanlah seperti di bawah ini

nilai = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

Nah saya ingin membuat kelompok untuk setiap 2 buah nilai berurutan. Barisan di atas ingin saya ubah menjadi seperti berikut.

pasangan = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10), (11, 12)]

Bagaimana cara cepatnya? Salah satu trik yang saya temukan (di Internet :P) adalah dengan menggunakan fungsi zip dan iter.

Fungsi zip sudah dikenal dapat dipakai untuk menggabungkan dua atau lebih barisan untuk membuat barisan kelompok dari setiap elemen yang ada (err..?). Contohnya

>>> angka = [1, 2, 3]
>>> tulisan = ["satu", "dua", "tiga"]
>>> zip(angka, tulisan)
[(1, 'satu'), (2, 'dua'), (3, 'tiga')]

Nah yang saya mau adalah saya ingin membuat kelompok dari setiap dua elemen berurutan. Untuk ini, fungsi iter dapat kita salahgunakan 😀 Mari kembali ke contoh awal.

>>> nilai = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
>>> iter_nilai = iter(nilai)
>>> zip(iter_nilai, iter_nilai)
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10), (11, 12)]

Hore jadi 😀

Apa yang terjadi di belakang layar? Setelah baca2 source code fungsi zip (Python 2.7.5), setiap barisan yang dimasukkan ke dalam fungsi zip akan dijadikan iterator dan nilainya akan diambil satu persatu sampai salah satu barisan tidak dapat mengeluarkan elemen berikutnya. Andaikan parameter yang dimasukkan sudah berupa iterator, saya asumsikan Python tidak akan membuat iterator baru. Nah berhubung parameter yang dimasukkan berasal dari iterator yang sama, maka nilai akan disebar selang-seling ke kelompok/tuple yang dihasilkan.

Bagaimana cara membuat kelompok dari setiap 3 bilangan berurutan? Tambahkan lagi saja parameter si zip!

>>> nilai = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
>>> iter_nilai = iter(nilai)
>>> zip(iter_nilai, iter_nilai, iter_nilai)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12)]

Himpunan unik di Python

Sebelum ini, saya sempat menulis tentang cara membuat fungsi pembanding sendiri untuk keperluan pembuatan himpunan unik. Singkatnya, di bahasa Java, Scala, dan mungkin bahasa lain yang jalan di atas JVM, kita perlu mengimplementasikan ulang method hashCode dan equals. Setelah menulis itu, saya sempat berpikir bagaimana cara melakukannya di Python?

Yang ingin saya lakukan adalah jika saya memiliki beberapa objek penampung nilai, saya ingin membuat mereka unik (katakanlah dengan memasukkannya ke dalam set). Jadi jika ada lebih dari satu objek yang bernilai sama, hanya satu di antara mereka saja yang diambil. Sisanya bisa dibuang karena saya tidak memerlukannya.

Katakanlah saya memiliki kelas seperti berikut.

class M(object):
    def __init__(self, posisi, asam_amino):
        self.posisi = posisi
        self.asam_amino = asam_amino

    def __repr__(self):
        return "%s%s" % (self.posisi, self.asam_amino)

Lalu saya membuat beberapa objek dari kelas tersebut.

mutasi = [ M(1, "a"), M(2, "b"), M(1, "c"), M(3, "b"), M(1, "a") ]

Mari kita coba panggil fungsi set

>>> print mutasi
[1a, 2b, 1c, 3b, 1a]
>>> print set(mutasi)
set([3b, 1c, 1a, 1a, 2b])

Seperti yang sudah diduga, fungsi set belum berhasil menghapus nilai ganda.

Setelah ngubek2 sana sini, ternyata di Python kita juga harus mengimplementasikan dua buah method yang sama (versi Python tentunya)! Kedua method tersebut adalah __eq__ dan __hash__. Kedua method ini dapat diimplementasikan ulang seperti berikut.

    def __eq__(self, o):
        if not isinstance(o, M):
            return False
        return self.posisi == o.posisi \
               and self.asam_amino == o.asam_amino

    def __hash__(self):
        return hash((self.posisi, self.asam_amino))

Mari kita coba lagi sekarang.

>>> mutasi = [ M(1, "a"), M(2, "b"), M(1, "c"), M(3, "b"), M(1, "a") ]
>>> print mutasi
[1a, 2b, 1c, 3b, 1a]
>>> print set(mutasi)
set([1c, 1a, 2b, 3b])

Klopt!

Kode lengkap dapat dilihat di https://gist.github.com/fajran/5744836

Blogger to WordPress

When I merged all of my scattered blogs few days ago, I tried to import my blog posts from Blogger to this WordPress installation. My first shot was to use WordPress’ own Blogger Importer but apparently the result was not so good (I think). Some texts were garbled and gone. For example, the iframe tag from SoundCloud in my post about OS X’ Text-To-Speech “Improvisation” was eaten by either the converter script or WordPress. Either way, it didn’t work flawlessly and I had to find out another way to export the posts.

Then I realized that Blogger provides a tool to download all of our blog posts, including comments (and I assume the pages as well, but I didn’t test it since I don’t have pages). I tried this out and got a machine readable XML file. Wohoo!

WordPress has it’s own way to do data migration between WordPress blogs. The good thing is it’s also an XML file! If somehow I can convert the XML file from Blogger to the one that WordPress uses, everything should be set 😀

A couple hours later, I got this very simple converter script. It may not be efficient and it also has several hardcoded values (check the categories part) but it suits what I wanted 😀

https://gist.github.com/fajran/5659455

Have fun!

Ubuntu Repository: Total Packages and Sizes

Long time ago, I made a chart that shows Ubuntu Repositories’ size over several releases. At that time, there were only 6 releases and 1 release that was still being worked on. Fast forward almost 6 years later, now we have 18 Ubuntu releases, yay! How does that chart look like now?

Well, here we go! I just made some new charts!

Ubuntu Releases - Packages and SIzes

The full charts can be seen at http://fajran.github.io/vis-ubuntu-pkg/

As you can see, the repositories are constantly growing. Ubuntu starts with about 8 GB of packages and now it has more than 50 GB. The number of packages is also growing from just over 12.5 thousands and now it almost three times larger.

How did I count the numbers? I took the package index files (a.k.a. Packages file) and counted the number of packages inside. I also summed the size of each packages to get the size of the repository. The files are from the at-release repository which means I excluded the -updates and -security repositories. The at-release repository is presumably frozen and therefore the size won’t change anymore, where the latest two repositories are for the updates, which by definition are always updated. Well.. except when the release is not supported anymore.

I put all of the codes I wrote and use in my GitHub repository https://github.com/fajran/vis-ubuntu-pkg. Feel free to check, modify, and update it. I release the whole thing as public domain!

Di balik Python Trivia #1 dan #2

Daripada blog ini kembali kosong, mari kita bahas Python Trivia babak pertama dan babak kedua yang saya keluarkan lebih dari satu tahun lalu! Mari kita mulai dari babak pertama.

Berikut ini cuplikan kode yang saya tulis:

class Kantong(object):
    def __init__(self, data=[]):
        self.data = data
    def add_data(self, angka):
        self.data.append(angka)
    def cetak(self):
        print 'Daftar angka:', self.data

Lalu mari kita pakai kelas di atas seperti berikut.

satu = Kantong()
satu.add_data(1)
satu.add_data(2)
satu.cetak()

Apa keluarannya?

Daftar angka: [1, 2]

Data yang kita miliki adalah [1, 2] yang berasal dari 2 pemanggilan add_data() dengan parameter 1 dan 2. Gak ada yg protes kan? 😀

Setelah itu, kita buah sebuah objek lain dari kelas yg sama. Lalu 3 buah data baru dimasukkan dan lalu dicetak.

dua = Kantong()
dua.add_data(3)
dua.add_data(4)
dua.add_data(5)
dua.cetak()

Hasilnya adalah.. jreng jreng jreng..

Daftar angka: [1, 2, 3, 4, 5]

Lho lho lho.. kok ada [1, 2] dan bukan cuma [3, 4, 5] padahal kita cuma memasukkan 3 angka di objek baru ini?

Mengapa begini mengapa begitu?

Kalau kita baca kelas Kandong sekali lagi, fungsi add_data akan memasukkan angka ke variabel self.data. Variabel ini diinisialisasi pada bagian constructor __init__() dan nilai awal yg dimasukkan adalah nilai yang ada dalam variabel data dari parameter si constructor.

Jika parameter pada constructor ini tidak diisi, maka secara default nilai data adalah sebuah list kosong []

...
def __init__(self, data=[]):
...

Objek satu dan dua yang dibuat dari kelas Kantong ini tidak menyertakan parameter saat objek dibuat. Alhasil, nilai [] tadi akan dijadikan nilai awal untuk self.data.

Pertanyaannya adalah kapan nilai [] dibuat? Ternyata nilai ini dibuat hanya satu kali pada saat kelas didefinisikan! Artinya, jika nilai tadi tidak diganti, semua objek yang berasal dari kelas Kantong akan memiliki variabel self.data yang sama! Walau tersebar di banyak objek, tapi variabel tersebut menunjuk ke sebuah list yang sama sehingga jika ada penambahan/pengurangan isi list tersebut, semua objek yang memegang list tersebut akan dapat melihatnya.

Lalu bagaimana?

Jika yang kita maksud dengan tidak memberikan parameter adalah sebuah list baru (dan kosong) akan dibuat, maka kelas di atas bisa diubah menjadi berikut.

class Kantong(object):
    def __init__(self, data=None):
        if data is None:
            self.data = []
        else:
            self.data = data
    ...

Dengan demikian, jika parameter tidak dimasukkan (yang artinya data akan bernilai None), variabel self.data akan diisi dengan sebuah list baru yang berbeda dengan list dari objek lain dari kelas yang sama.

Kesimpulannya, pada cara pertama, nilai self.data secara default akan diisi sebuah list yang dibuat pada saat kelas didefinisikan. Pemanggilan constructor (juga termasuk fungsi) akan menggunakan list yang sama dan efeknya adalah adanya penggunaan data bersama. Sedangkan pada contoh kelas ke dua, nilai self.data dibuat pada saat constructor/fungsi dijalankan sehingga pada setiap eksekusi, list baru dan berbeda akan dibuat dan efeknya tidak akan ada penggunaan data bersama.

Bagaimana dengan trivia babak kedua?

Kasus sama juga terjadi di sini.

a = [1] * 5

Kode di atas dapat dibaca seperti berikut. Buat sebuah list baru yang isinya adalah isi list sekarang yang diulang sebanyak 5 kali. Nilai dari a adalah seperti berikut.

[1, 1, 1, 1, 1]

Jika kita ubah salah satu elemennya..

a[3] = 200

Maka nilai si a akan menjadi..

[1, 1, 1, 200, 1]

Masih oke kan? 😀 Apa yang terjadi jika kita menggunakan dict dan bukan angka 1?

b = [{}] * 5

Nilai si b tentunya akan menjadi seperti berikut.

[{}, {}, {}, {}, {}]

Bagimana jika salah dict diubah isinya?

b[0]['a'] = 100

Baris kode di atas artinya ambil elemen nomor 0 dari b (yaitu sebuah dict), lalu kita set kunci 'a' supaya bernilai 100. Bagaimana jika kita cetak nilai si b?

[{'a': 100}, {'a': 100}, {'a': 100}, {'a': 100}, {'a': 100}]

Sesuatu yang (mungkin) tidak diharapkan terjadi lagi 😀 Lagi-lagi hal ini terjadi karena sebenarnya 5 buah dict yang ada di dalam b sebenarnya adalah dict yang sama namun dimasukkan sebanyak 5 kali di dalam list b.

Mudah2an jelas 😀

Komunikasi .NET dan Python melalui COM

Saat ini saya sedang membutuhkan akses ke sebuah pustaka yang hanya tersedia dalam platform Microsoft .NET. Berhubung apa yang saya buat semuanya tertulis dalam bahasa Python, tepatnya CPython, saya perlu mencari cara bagaimana agar saya bisa mengakses pustaka dalam .NET tersebut dari Python. Setelah ngubek sana sini, akhirnya ketemu juga (salah satu) caranya, yaitu melalui Component Object Model alias COM. Singkatnya, COM ini memungkinkan berbagai aplikasi dengan berbagai bahasa untuk berkomunikasi.

Membuat komponen COM dengan C#

Dalam kasus yang saya hadapi, saya perlu mengakses pustaka .NET dari Python. Oleh karena itu saya membutuhkan sebuah komponen COM yang tertulis dalam, misalnya, C# yang dapat mengakses pustaka tersebut. Komponen dalam C# ini lalu saya atur agar bisa diakses melalui COM sehingga pada akhirnya aplikasi Python saya bisa mengakesenya.

Kali ini saya akan mencontohkan cara membuat sebuah kelas dalam C# yang dapat diakses melalui COM. Kelas ini adalah sebuah kelas kalkulator yang berisi fungsi penjumlahan angka. Contoh ini saya adaptasi dari beberapa referensi bagus yang ada di [1,2,3].

Buat sebuah proyek C# baru bernama Matematika dan kemudian buat sebuah sebuah kelas dengan isi seperti berikut.

namespace Matematika {
    public interface IKalkulator {
        int tambah(int a, int b);
    }

    public class Kalkulator : IKalkulator {
        public int tambah(int a, int b) {
            return a + b;
        }
    }
}

Interface adalah bagian penting dalam pembuatan komponen COM karena dalam COM pengaksesan komponen akan mengacu kepada interface yang dibuat.

Untuk keperluan pendaftaran komponen, kita memerlukan sebuah kunci. Buka Visual Studio Command Prompt, pindah ke direktori proyek (direktori dimana kode sumber berada, bukan direktori Solution-nya visual studio) dan jalankan perintah berikut.

sn.exe -k Matematika.snk

Setelah itu, buka berkas AssemblyInfo.cs (ada di bawah Solution -> Matematika -> Properties) dan tambahkan 3 baris berikut.

[assembly: ComVisible(true)]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("Matematika.snk")]

Lakukan kompilasi proyek untuk membuat berkas pustaka Matematika.dll. Berkas pustaka ini akan berada di bawah direktori binRelease yang ada di bawah direktori proyek.

Agar pustaka atau komponen COM ini dapat diakses dari aplikasi lain, kita harus mendaftarkan pustaka ini terlebih dahulu. Untuk ini, kita perlu menggunakan akses sebagai Administrator. Buka Visual Studio Command Prompt lagi namun kali ini sebagai Administrator (Klik kanan di shortcut dan pilih Run as Administrator). Masuk ke direktori tempat pustaka Matematika.dll tadi berada (direktori binRelease di bawah direktori kerja) lalu jalankan perintah pendaftaran berikut.

regasm.exe Matematika.dll /tlb:Matematika.tlb /codebase

Setelah perintah di atas dijalankan, Anda akan mendapatkan pesan kalau pustaka sudah didaftarkan dan juga sebuah berkas baru bernama Matematika.tlb yang berisi deskripsi mengenai seluruh interface dan struktur data yang ada.

Sebelum lanjut ke cara mengakses komponen tadi, buka lagi berkas AssemblyInfo.cs dan dapatkan tanda pengenal aplikasi yang barusan dibuat. Tanda pengenal ini adalah sebuah GUID yang didefinisikan seperti berikut.

[assembly: Guid("9808bb58-40fe-4373-acc3-f577b55a2a99")]

Deretan angka dan huruf di atas, yaitu 9808bb58-40fe-4373-acc3-f577b55a2a9, akan menjadi (salah satu) tanda pengenal yang dibutuhkan saat mengakses komponen COM tadi.

Mengakses COM dari Python

Python tidak menyediakan dukungan terhadap COM secara langsung sehingga kita perlu memasang modul tambahan yang dapat membantu kita untuk mengakses COM. Saya menemukan ada dua modul yang bisa digunakan, yaitu PyWin32 dan comtypes. Dalam petualangan beberapa hari terakhir ini, saya belum berhasil mengakses COM yang saya buat di .NET dengan bantuan PyWin32 namun comtypes dapat melakukannya dengan cukup mudah. Oleh karena itu, saya akan mencontohkan penggunaan comtypes untuk mengakses komponen COM yang tadi dibuat. Berhubung comtypes itu bukan bawaan asli dari Python, unduh dan pasang dulu modul ini dari http://pypi.python.org/pypi/comtypes.

Kalau sudah, buat sebuah berkas matematika.py yang berisi kode berikut.

import comtypes
import comtypes.client as cc

guid = comtypes.GUID("{9808bb58-40fe-4373-acc3-f577b55a2a99}")
cc.GetModule((guid, 1, 0))

from comtypes.gen import Matematika

obj = cc.CreateObject("Matematika.Kalkulator", None, None,
                      Matematika.IKalkulator)

a = 10
b = 200
hasil = obj.tambah(a, b)

print a, '+', b, '=', hasil

Setelah itu, jalankan dengan python.exe

C:tmp>c:Python26python.exe matematika.py
# Generating comtypes.gen._9808BB58_40FE_4373_ACC3_F577B55A2A99_0_1_0
# Generating comtypes.gen._00020430_0000_0000_C000_000000000046_0_2_0
# Generating comtypes.gen.stdole
# Generating comtypes.gen._BED7F4EA_1A96_11D2_8F08_00A0C9A6186D_0_2_0
# Generating comtypes.gen.mscorlib
# Generating comtypes.gen.Matematika
10 + 200 = 210

Dalam eksekusinya, comtypes akan membuat beberapa modul tambahan (yang berfungsi kode penghubung dengan komponen COM) secara otomatis seperti yang diperlihatkan dalam hasil di atas. Modul dan pesan ini hanya akan dibuat satu kali sehingga pada eksekusi selanjutnya pesan di atas tidak akan tampil lagi.

Jika ada perubahan dalam kode C# yang kita buat, selama kita tidak mengubah interface, kita cukup melakukan kompilasi ulang dan pustaka baru akan langsung dipakai. Jika interface berubah, pustaka perlu didaftarkan ulang (dan berkas .tlb-nya juga perlu dibuat ulang) agar perubahan dapat diketahui. Berdasarkan berkas .tlb yang berubah, comtypes akan melakukan pembuatan ulang modul penghubung secara otomatis juga.

Referensi

  1. http://msdn.microsoft.com/en-us/library/aa645738(v=vs.71).aspx
  2. http://support.microsoft.com/kb/828736
  3. http://cppkid.wordpress.com/2009/01/02/how-to-call-a-managed-dll-from-unmanaged-code/

Metaclass di Python

Ternyata metaclass di Python itu sangat menarik karena kita bisa melakukan ilmu hitam 😀 Salah satu ilmu hitam yang sedang/akan saya pakai adalah membuat deklarasi kelas yang baru saja dibuat menjadi lenyap secara tiba2 dan alih-alih kita mendapatkan sebuah objek/instance dari kelas tersebut.

Mengapa saya memerlukan sesuatu yang “aneh” seperti ini? Singkatnya saya sedang membuat semacam domain specific language yang berbasiskan Python. Dengan menggunakan trik sulap di atas, saya bisa membuat bahasa yang saya buat tadi menjadi lebih mudah dipakai. Maaf kalo saya blom bisa menjelaskan dg lebih detail.

Di Python, metaclass bisa digambarkan sebagai sebuah kode yang dijalankan saat sebuah kelas dideklarasikan. Dengan demikian kita bisa mengutak atik wujud sebuah kelas sebelum kelas teresebut siap dipakai. Contoh penggunaan dasarnya adalah sebagai berikut.

class MetaProvinsi(type):
    def __new__(cls, name, bases, attrs):
        return type.__new__(cls, name, bases, attrs)

class Provinsi(object):
    __metaclass__ = MetaProvinsi

Pada contoh di atas, ada sebuah kelas bernama Provinsi yang memiliki metaclass MetaProvinsi. Kode pada fungsi __new__ di atas adalah bagian kode yang dapat diisi untuk mengubah wujud kelas Provinsi. Fungsi ini menerima 4 buah parameter yaitu:

  1. cls yang menunjuk ke diri sendiri (yaitu MetaProvinsi),
  2. name yang berisi nama kelas yang dibuat (dalam contoh di atas, name berisi Provinsi),
  3. bases yang berisi daftar kelas yang diturunkan oleh si Provinsi (yaitu object), dan
  4. attrs yang berupa sebuah dictionary berisi daftar atribut (instance variables dan/atau methods) dari Provinsi.

Metaclass pada contoh di atas masih tidak melakukan hal apa-apa sehingga kelas Provinsi tsb sebenarnya tidak mengalami perubahan apa-apa. Sekarang mari kita ubah sedikit contoh di atas.

class MetaKabupaten(type):
    def __new__(cls, name, bases, attrs):
        attrs['nama_kelas'] = name
        return type.__new__(cls, name, bases, attrs)

class Kabupaten(object):
    __metaclass__ = MetaKabupaten

Kali ini kita mengubah dict attrs dengan menambahkan sebuah isian dengan kunci nama_kelas yang berisi nilai dari variabel name, yaitu nama kelas yang baru saja dibuat yaitu Kabupaten.

Apa bedanya contoh pertama dengan kedua? mari kita buat objek dari kelas Provinsi dan Kabupaten yang sudah dideklarasikan di atas.

p = Provinsi()
print p.nama_kelas

Andai kode di atas dijalankan, maka Python akan mengatakan bahwa atribut nama_kelas tidak ditemukan pada objek dari kelas Provinsi

AttributeError: 'Provinsi' object has no attribute 'nama_kelas'

Alasannya sudah jelas karena pada saat mendeklarasikan kelas Provinsi (maupun saat menginisialisasi kelas tersebut), kita tidak pernah membuat sebuah instance variable bernama nama_kelas. Sekarang kita coba dengan kelas Kabupaten

k = Kabupaten()
print k.nama_kelas

Bukan sulap bukan sihir, Python dapat dijalankan sampai selesai dan kita mendapat sebuah keluaran di layar seperti berikut.

Kabupaten

Keluaran tersebut dihasilkan dari k.nama_kelas yaitu nilai sebuah instance variable bernama nama_kelas dari objek k yang dibangun dari kelas Kabupaten. Kapan instance variable nama_kelas tersebut hadir? Jawabannya adalah pada saat kelas Kabupaten tersebut dibuat, lebih tepatnya dilakukan oleh metaclass MetaKabupaten.

Sekian dulu pengantar penggunaan Metaclass dari saya. Lain kali saya contohkan ilmu hitam yg sedang saya pakai maupun contoh pe(nyalah)gunaan lainnya. Contoh kode bisa dilihat di https://gist.github.com/814525

Python Trivia #2

Teka teki Python babak 2, masih ada hubungannya dg babak pertama.

Salah satu cara cepat untuk membuat list dengan n elemen adalah dg menggunakan operator kali seperti contoh di bawah

>>> [1] * 5
[1, 1, 1, 1, 1]
>>> [None] * 5
[None, None, None, None, None]

Pada list yang dihasilkan, tentu saja kita bisa ngubah nilai elemennya

>>> a = [1] * 5
>>> a
[1, 1, 1, 1, 1]
>>> a[3] = 200
>>> a
[1, 1, 1, 200, 1]

Nah.. coba perhatikan kode berikut dan tebak apa nilai akhir dari variabel b

>>> b = [{}] * 5
>>> b
[{}, {}, {}, {}, {}]
>>> b[0]['a'] = 100

Python Trivia

Coba compile dan jalanin kode berikut di otak. Jangan nyontek!

class Kantong(object):
    def __init__(self, data=[]):
        self.data = data
    def add_data(self, angka):
        self.data.append(angka)
    def cetak(self):
        print 'Daftar angka:', self.data

satu = Kantong()
satu.add_data(1)
satu.add_data(2)
satu.cetak()

dua = Kantong()
dua.add_data(3)
dua.add_data(4)
dua.add_data(5)
dua.cetak()

Apa keluarannya?

Sekarang coba pake interpreter python yang Anda punya. Jalankan dan lihat apa keluarannya.

Sama kah dengan yang Anda pikirkan sebelumnya? Kalau sama, selamat!. Kalau beda, coba cari tau apa masalahnya 😀