BAB 21: Object, ID, dan Referensi

Pahami bagaimana Python mengelola data di memori — setiap object punya identitas unik, dan variabel hanyalah label yang menunjuk ke object tersebut.

Di BAB 20, kita mendalami tipe numerik Python sampai ke detail presisi float dan bilangan kompleks. Tapi ada satu pertanyaan yang mungkin belum pernah terlintas: ketika kamu menulis skor = 85, apa sebenarnya yang terjadi di dalam Python? Apakah skor adalah angka 85, atau skor menunjuk ke angka 85?

Perbedaan ini terdengar filosofis, tapi berdampak sangat konkret pada bagaimana kode kamu berperilaku — terutama saat bekerja dengan list, dictionary, dan tipe data kolektif lain yang sudah kita pelajari. Memahami konsep object, ID, dan referensi akan membuat kamu jarang terkejut dengan perilaku Python yang awalnya terasa aneh.

Setiap Data Adalah Object

Di Python, semua data adalah object — bukan hanya instance class yang kamu buat sendiri. Angka 85, string "Rina", list [1, 2, 3], bahkan True dan None semuanya adalah object. Setiap object punya tiga hal yang melekat padanya:

  • Tipe — menentukan operasi apa yang bisa dilakukan (sudah kita bahas di BAB 8)
  • Nilai — data aktual yang disimpan
  • Identitas — alamat unik di memori

Fungsi id() bawaan Python mengembalikan identitas object tersebut, berupa angka integer yang merepresentasikan alamat memori:

# kuis.py — identitas object
skor = 85
nama = "Rina"
hasil = [85, 72, 91]

print(id(skor))
print(id(nama))
print(id(hasil))
140234567890112
140234567891456
140234567892800

Angka yang muncul akan berbeda di setiap komputer dan setiap kali program dijalankan — itu normal. Yang penting adalah setiap object punya angka yang unik di satu sesi program.

Identitas adalah milik data (object), bukan milik variabel. Variabel hanyalah nama yang kamu tempel pada sebuah object. Satu object bisa punya banyak nama; satu nama bisa dipindahkan ke object yang berbeda.

Variabel Sebagai Label

Cara paling tepat membayangkan variabel di Python: bukan kotak yang menyimpan data, melainkan label tempel yang diikatkan ke sebuah object. Ketika kamu menulis a = b, Python tidak menyalin data — Python hanya mengikatkan label a ke object yang sama dengan b.

# kuis.py — variabel sebagai label
nilai_rina = 85
nilai_backup = nilai_rina   # backup menunjuk ke object yang SAMA

print(f"ID nilai_rina:   {id(nilai_rina)}")
print(f"ID nilai_backup: {id(nilai_backup)}")
print(f"Sama? {id(nilai_rina) == id(nilai_backup)}")
ID nilai_rina:   140234567890112
ID nilai_backup: 140234567890112
Sama? True

Kedua variabel punya ID yang identik — keduanya menunjuk ke object 85 yang sama di memori.

Sekarang perhatikan apa yang terjadi saat nilai_rina diubah:

# kuis.py — integer bersifat immutable
nilai_rina = 85
nilai_backup = nilai_rina

nilai_rina = 90   # nilai_rina sekarang menunjuk ke object baru

print(f"nilai_rina:   {nilai_rina}")
print(f"nilai_backup: {nilai_backup}")
print(f"ID nilai_rina:   {id(nilai_rina)}")
print(f"ID nilai_backup: {id(nilai_backup)}")
nilai_rina:   90
nilai_backup: 85
ID nilai_rina:   140234567890368
ID nilai_backup: 140234567890112

nilai_backup tetap 85. Ketika kamu menulis nilai_rina = 90, Python tidak mengubah object 85 — ia membuat object baru 90 dan memindahkan label nilai_rina ke sana. Label nilai_backup masih menempel di object 85 yang lama.

Operator is vs ==

Sejauh ini kita memakai == untuk membandingkan data. Operator == membandingkan nilai dua object. Tapi ada operator lain yang membandingkan identitas: is.

# kuis.py — is vs ==
rekap_a = [85, 72, 91]
rekap_b = rekap_a          # reference ke object yang sama
rekap_c = [85, 72, 91]     # object baru dengan nilai yang sama

print(f"rekap_a == rekap_b: {rekap_a == rekap_b}")   # nilai sama
print(f"rekap_a is rekap_b: {rekap_a is rekap_b}")   # identitas sama

print(f"rekap_a == rekap_c: {rekap_a == rekap_c}")   # nilai sama
print(f"rekap_a is rekap_c: {rekap_a is rekap_c}")   # identitas berbeda
rekap_a == rekap_b: True
rekap_a is rekap_b: True
rekap_a == rekap_c: True
rekap_a is rekap_c: False

rekap_b dan rekap_a adalah dua nama untuk object yang sama, sehingga is menghasilkan True. rekap_c dibuat secara terpisah — nilainya identik tapi identitasnya berbeda, jadi is menghasilkan False.

Gunakan is hanya untuk membandingkan identitas, bukan nilai. Pola umum yang benar adalah if nilai is None: atau if status is True:. Jangan pakai is untuk membandingkan angka atau string — hasilnya bisa tidak konsisten karena Python melakukan optimasi internal yang tidak bisa kamu andalkan.

Referensi dan Mutability

Inilah bagian yang sering menyebabkan bug tersembunyi. Tipe data mutable (bisa diubah) seperti list dan dictionary berperilaku berbeda dari tipe immutable seperti integer dan string ketika ada dua variabel yang menunjuk ke object yang sama.

# kuis.py — referensi pada mutable object
daftar_nilai = [85, 72, 91, 68]
salinan_ref = daftar_nilai   # BUKAN salinan — keduanya menunjuk ke object yang sama

salinan_ref.append(77)       # memodifikasi object yang sama

print(f"daftar_nilai: {daftar_nilai}")
print(f"salinan_ref:  {salinan_ref}")
print(f"Sama object? {daftar_nilai is salinan_ref}")
daftar_nilai: [85, 72, 91, 68, 77]
salinan_ref:  [85, 72, 91, 68, 77]
Sama object? True

append() mengubah object list yang ada di memori. Karena daftar_nilai dan salinan_ref keduanya menunjuk ke object yang sama, perubahan melalui satu variabel langsung terlihat dari variabel yang lain.

Bandingkan dengan integer di contoh sebelumnya: ketika nilai_rina = 90 dilakukan, Python tidak mengubah object 85 — ia membuat object baru. Ini karena integer bersifat immutable (tidak bisa diubah setelah dibuat). List bersifat mutable — isinya bisa dimodifikasi di tempat, dan semua referensi ke list tersebut akan melihat perubahan itu.

Membuat Salinan Nyata

Jika kamu ingin dua list yang independen satu sama lain, buat salinan eksplisit:

# kuis.py — membuat salinan list yang independen
daftar_nilai = [85, 72, 91, 68]

# cara 1: slicing penuh
salinan_slice = daftar_nilai[:]

# cara 2: method copy()
salinan_copy = daftar_nilai.copy()

# cara 3: konstruktor list()
salinan_list = list(daftar_nilai)

salinan_slice.append(77)

print(f"daftar_nilai:  {daftar_nilai}")   # tidak berubah
print(f"salinan_slice: {salinan_slice}")  # punya 77
print(f"Sama object? {daftar_nilai is salinan_slice}")
daftar_nilai:  [85, 72, 91, 68]
salinan_slice: [85, 72, 91, 68, 77]
Sama object? False

Ketiga cara di atas menghasilkan object list baru yang berisi salinan elemen-elemen dari list asal. Perubahan pada salinan tidak akan mempengaruhi list asalnya.

Slicing [:] adalah cara paling ringkas dan idiomatis untuk menyalin list di Python. Untuk list yang berisi object mutable lain (list di dalam list), kamu perlu copy.deepcopy() dari modul copy — tapi untuk list elemen sederhana, [:] sudah cukup.

Caching Integer Kecil

Ada satu perilaku Python yang menarik sekaligus sedikit membingungkan. Python melakukan optimasi memori dengan men-cache integer dalam rentang -5 hingga 256. Integer dalam rentang ini selalu menggunakan object yang sama — Python tidak membuat object baru setiap kali kamu menulis angka tersebut.

# kuis.py — integer caching
a = 100
b = 100
print(f"a is b (100): {a is b}")   # True — di-cache

x = 300
y = 300
print(f"x is y (300): {x is y}")   # False — tidak di-cache, object baru
a is b (100): True
x is y (300): False

Ini menjelaskan kenapa kamu sebaiknya tidak mengandalkan is untuk membandingkan nilai integer. Hasilnya tergantung pada apakah Python men-cache angka tersebut atau tidak — dan batasan caching ini adalah detail implementasi yang bisa berbeda antar versi Python.

Hal serupa terjadi pada string pendek yang hanya mengandung huruf, angka, dan underscore — Python sering meng-intern (men-cache) string semacam itu. Tapi string yang lebih panjang atau mengandung karakter khusus umumnya tidak di-cache.

Latihan

Dua tantangan yang menguji pemahaman tentang referensi dan identitas:

  1. Buat list peserta = ["Rina", "Budi", "Sari"]. Buat dua variabel lain: referensi = peserta dan salinan = peserta[:]. Tambahkan nama "Dimas" ke referensi. Cetak ketiga variabel dan ID masing-masing. Jelaskan lewat komentar di kode mengapa hasilnya berbeda antara referensi dan salinan.

  2. Buat fungsi sederhana (boleh pakai satu baris lambda atau definisi biasa) yang menerima sebuah list dan mengembalikan list yang sama setelah menambahkan nilai 0 di awal. Uji fungsi itu dengan satu list, lalu cetak ID list sebelum dan sesudah fungsi dipanggil. Apakah ID-nya sama atau berbeda? Mengapa?

Sekarang kamu sudah mengerti bahwa variabel di Python bukan kotak — mereka label yang menunjuk ke object. Pemahaman ini akan berulang kali berguna ketika kita mulai memecah kode menjadi fungsi di bab berikutnya. Fungsi menerima argumen berupa referensi, dan mengetahui apakah argumen itu mutable atau immutable menentukan apakah perubahan di dalam fungsi akan terlihat di luar.

Referensi

  1. 1Python Docs — Data Model: Objects, values and types
  2. 2Python Docs — Built-in Functions: id()
  3. 3Python Docs — copy: Shallow and deep copy operations