Ruang Belajar Karinoya

Kualifikasi · Lab Lulus Cloud / AI / Python

Fungsi, Modul, dan Input/Output

Soal dan pembahasan dapat dibaca dalam Bahasa Indonesia. Materi kuliah (artikel penjelasan) hanya tersedia dalam bahasa Jepang.

Lihat versi bahasa Jepang (dengan materi kuliah) →

Soal 1 | Penerimaan argumen

Fungsi berikut yang memiliki parameter formal berbintang dipanggil hanya dengan argumen posisional. Isi apa yang ditampilkan?

def f(a, b=2, *args, **kwargs):    return (a, b, args, kwargs)print(f(1, 3, 4, 5))
  1. (1, 3, (4, 5), None)
  2. (1, 3, (4, 5), {})
  3. (1, 2, (3, 4, 5), {})
  4. (1, 3, [4, 5], {})
JawabanB. (1, 3, (4, 5), {})

Argumen posisional dipasangkan secara berurutan dari kiri, sehingga a menjadi 1, b menjadi 3. b memiliki nilai baku 2, tetapi karena nilai sudah diberikan, nilai itulah yang diutamakan. 4 dan 5 yang tidak tertampung dirangkum sebagai tuple ke dalam args berbintang satu, menjadi (4, 5). Karena tidak ada keyword argument yang diberikan, kwargs menjadi dictionary kosong {}, bukan None. Jika dianggap b tetap dengan nilai baku 2, akan menjadi (1, 2, (3, 4, 5), {}), tetapi argumen posisional lebih diutamakan daripada nilai baku. Yang masuk ke args adalah tuple bukan list, sehingga tidak akan ditampilkan dengan tanda kurung siku.

Soal 2 | Penentuan lewat keyword

Manakah isi yang ditampilkan dari pemanggilan berikut yang memberikan nama yang tidak ada di definisi lewat keyword?

def f(a, b=2, *args, **kwargs):    return (a, b, args, kwargs)print(f(1, x=9))
  1. (1, 9, (), {})
  2. (1, 2, (), {'x': 9})
  3. (1, 2, (9,), {})
  4. (1, 2, {'x': 9}, {})
JawabanB. (1, 2, (), {'x': 9})

Karena argumen posisional hanya 1, a menjadi 1, dan karena b tidak diberikan, b tetap dengan nilai baku 2. Karena parameter formal bernama x tidak ada di definisi, keyword argument x=9 masuk ke kwargs berbintang dua sebagai {'x': 9}. Karena tidak ada sisa argumen posisional, args menjadi tuple kosong (). Jika dianggap x=9 masuk ke b, akan menjadi (1, 9, (), {}), tetapi keyword argument hanya masuk ke parameter formal yang namanya cocok. Jika dianggap 9 masuk ke args, akan menjadi (1, 2, (9,), {}), tetapi nilai yang diberikan dengan nama tidak akan dialihkan ke sisa argumen posisional. Jika bintang satu dan bintang dua tertukar antara dictionary dan tuple, akan tampak seolah {'x': 9} masuk ke args dan kwargs menjadi kosong.

Soal 3 | List kosong sebagai baku

Fungsi berikut yang menulis list kosong sebagai nilai baku dipanggil 3 kali. Manakah 3 baris yang ditampilkan?

def add(x, lst=[]):    lst.append(x)    return lstprint(add(1))print(add(2))print(add(3))
  1. Dari atas ke bawah ditampilkan None / None / None
  2. Dari atas ke bawah ditampilkan [1] / [1, 2] / [1, 2, 3]
  3. Dari atas ke bawah ditampilkan [1] / [2] / [3]
  4. Dari atas ke bawah ditampilkan [1] / [1, 2] / [2, 3]
JawabanB. Dari atas ke bawah ditampilkan [1] / [1, 2] / [1, 2, 3]

Nilai baku [] dibuat hanya sekali ketika baris def dijalankan, dan list yang sama itu disimpan pada objek fungsi. Karena tidak dibuat list kosong baru setiap kali dipanggil, hasil append dari panggilan sebelumnya tetap tersisa untuk panggilan berikutnya, sehingga bertambah menjadi [1], [1, 2], [1, 2, 3]. Jika dianggap list baru dibuat setiap kali dipanggil, akan menjadi [1] / [2] / [3], tetapi itu adalah perilaku jika nilai baku diset lst=None lalu dibuat ulang di badan fungsi. Jika badan fungsi ditulis keliru sebagai return lst.append(x), akan tampil None sebanyak 3 kali, yaitu nilai kembalian dari append. Perlu dicatat, jika ditulis dalam 1 baris seperti print(add(1), add(2), add(3)) bukan 3 baris terpisah, karena ketiganya menunjuk list yang sama, akan tampil [1, 2, 3] sebanyak 3 kali setelah semuanya selesai dievaluasi.

Soal 4 | Berbagi nilai baku

Menampilkan atribut objek fungsi setelah dipanggil 3 kali. Isi apa yang ditampilkan?

def add(x, lst=[]):    lst.append(x)    return lstadd(1)add(2)add(3)print(add.__defaults__)
  1. Output menjadi seperti ((1, 2, 3),)
  2. Output menjadi seperti ([],)
  3. Output menjadi seperti [[1, 2, 3]]
  4. Output menjadi seperti ([1, 2, 3],)
JawabanD. Output menjadi seperti ([1, 2, 3],)

Objek fungsi menyimpan nilai baku dalam atribut bernama __defaults__ sebagai tuple. Di antara parameter formal, hanya lst yang memiliki nilai baku, sehingga menjadi tuple berisi 1 elemen, ditampilkan dengan koma di akhir. Isinya adalah list yang sama yang hanya dibuat sekali pada baris def, dan karena hasil append dari 3 kali pemanggilan tetap tersisa apa adanya, menjadi ([1, 2, 3],). Jika belum pernah dipanggil sama sekali, akan menjadi ([],), tetapi munculnya pengaruh pemanggilan di sini adalah bahaya dari argumen baku yang mutable. Karena __defaults__ itu sendiri adalah tuple, tidak akan ditampilkan dengan tanda kurung siku, dan list di dalamnya pun tidak berubah menjadi tuple.

Soal 5 | Evaluasi nilai baku

Menulis variabel sebagai nilai baku, lalu mengubah variabel tersebut belakangan. Manakah 2 baris yang ditampilkan?

i = 5def h(x=i):    return xi = 10print(h())print(h(i))
  1. 5 lalu 5
  2. 5 lalu 10
  3. 10 lalu 10
  4. 10 lalu 5
JawabanB. 5 lalu 10

Ekspresi nilai baku tidak dievaluasi setiap kali fungsi dipanggil, melainkan dievaluasi hanya sekali pada saat baris def dijalankan. Karena i saat itu adalah 5, nilai 5 tersimpan pada objek fungsi. Meski i diubah menjadi 10 setelahnya, nilai baku yang sudah tersimpan tidak berubah, sehingga h() mengembalikan 5. Sebaliknya h(i) membaca i pada saat pemanggilan lalu meneruskannya, sehingga mengembalikan nilai saat itu yaitu 10. Jika dianggap nilai baku dievaluasi setiap kali dipanggil, kedua baris akan menjadi 10, dan jika dianggap nilai yang diberikan lewat argumen pun ikut tergantikan nilai baku, kedua baris akan menjadi 5. Jebakan argumen baku yang mutable pun muncul dari aturan yang sama ini.

Soal 6 | Urutan argumen

Kode berikut disimpan ke file dan dijalankan dengan python3. Apa yang terjadi?

def g(a=1, b):    return a + bprint(g(2, 3))
  1. Terjadi SyntaxError pada saat definisi, tidak ada satu baris pun yang dijalankan
  2. Ditampilkan 4, lalu eksekusi berakhir normal sampai selesai
  3. Terjadi TypeError pada saat pemanggilan, dan eksekusi berhenti di situ
  4. Ditampilkan 5, lalu eksekusi berakhir normal sampai selesai
JawabanA. Terjadi SyntaxError pada saat definisi, tidak ada satu baris pun yang dijalankan

Parameter formal tanpa nilai baku tidak boleh diletakkan setelah parameter formal yang memiliki nilai baku. Aturan ini diperiksa pada tahap sintaks, sehingga saat file dijalankan langsung terjadi SyntaxError dengan pesan non-default argument follows default argument. Karena analisis sintaks dilakukan untuk seluruh file sebelum eksekusi, tidak ada satu baris pun yang dijalankan, termasuk baris print. Ini sangat berbeda tahap deteksinya dari TypeError yang terjadi akibat kesalahan cara pemanggilan. g(2, 3) itu sendiri sudah sesuai jumlah parameter formal, sehingga jika definisinya benar seharusnya menampilkan 5, tetapi tidak sampai ke situ. Penulisan yang benar adalah meletakkan yang memiliki nilai baku di belakang, seperti def g(b, a=1).

Soal 7 | Nilai kembalian fungsi

Dua fungsi dengan ada-tidaknya return yang berbeda dipanggil seperti berikut. Isi apa yang ditampilkan?

def one():    passdef two():    return 1, 2print(one(), type(two()).__name__)
  1. None tuple
  2. None list
  3. () list
  4. () tuple
JawabanA. None tuple

one berakhir tanpa pernah melewati return sama sekali, sehingga nilai kembaliannya None. Bukan tuple kosong atau string kosong yang dikembalikan. two menjajarkan 1 dan 2 dengan koma setelah return, tetapi ini bukan mengembalikan 2 nilai, melainkan mengembalikan 1 tuple yaitu (1, 2). Oleh karena itu type(two()).__name__ menjadi tuple. Jika dianggap beberapa nilai kembalian dirangkum jadi list, akan menjadi list, tetapi ekspresi yang dijajarkan dengan koma membentuk tuple. Kesulitan yang sering muncul saat mencoba menugaskan dan memakai nilai kembalian fungsi yang menampilkan sendiri, biasanya disebabkan oleh None ini.

Soal 8 | Pemanggilan unpack

Isi apa yang ditampilkan dari kode berikut yang memanggil dengan tanda bintang di depan list?

def f(a, b=2, *args, **kwargs):    return (a, b, args, kwargs)vals = [1, 2, 3]print(f(*vals))
  1. ([1, 2, 3], 2, (), {})
  2. (1, 2, (3,), None)
  3. (1, 2, [3], {})
  4. (1, 2, (3,), {})
JawabanD. (1, 2, (3,), {})

Jika di sisi pemanggil diberi tanda bintang satu di depan list, isinya dibagikan satu per satu sebagai argumen posisional. Oleh karena itu a menjadi 1, b menjadi 2, dan 3 yang tersisa dirangkum sebagai tuple ke dalam args berbintang satu menjadi (3,). Karena berisi 1 elemen, ada koma di akhir. Karena tidak ada keyword argument yang diberikan, kwargs adalah dictionary kosong {}, bukan None. Jika lupa memberi tanda bintang dan menulis f(vals), list itu sendiri masuk ke parameter formal pertama sehingga menjadi ([1, 2, 3], 2, (), {}), dan hanya hasilnya yang berubah tanpa terjadi error. Yang masuk ke args adalah tuple, sehingga tidak akan ditampilkan dengan tanda kurung siku.

Soal 9 | Perlakuan anotasi tipe

Fungsi berikut dipanggil dengan nilai yang berbeda dari tipe yang ditulis pada anotasi. Apa yang terjadi?

def ann(a: int, b: str = "x") -> bool:    return aprint(ann("文字列", 999))print(ann.__annotations__['a'])
  1. Baris 1 menampilkan 文字列, baris 2 menampilkan <class 'int'>
  2. Baris 1 menjadi TypeError, memberitahu bahwa tipe yang berbeda dari anotasi tidak boleh diberikan
  3. Baris 1 menampilkan 文字列, baris 2 menampilkan int
  4. Baris 1 menjadi ValueError, memberitahu bahwa tidak bisa dikonversi ke int
JawabanA. Baris 1 menampilkan 文字列, baris 2 menampilkan <class 'int'>

Anotasi hanya dicatat dalam dictionary bernama __annotations__ milik objek fungsi, dan tipe tidak diperiksa saat eksekusi. Meski ditulis int untuk a, string bisa diberikan, dan meski ditulis str untuk b, bilangan bulat bisa diberikan. Meski ditulis bool untuk nilai kembalian, yang sebenarnya dikembalikan adalah string yang diterima itu sendiri. Oleh karena itu baris 1 menampilkan 文字列 apa adanya. Yang tersimpan di __annotations__['a'] pada baris 2 adalah objek tipe int, sehingga jika di-print akan tampil dalam bentuk <class 'int'>. Bukan hanya 3 huruf int saja yang muncul. Jika ingin benar-benar memeriksa tipe, perlu menggunakan alat pemeriksa lain atau memeriksanya sendiri di dalam badan fungsi.

Soal 10 | Cara memperbaiki nilai baku

Kode berikut ditulis ulang dengan nilai baku None. Manakah 3 baris yang ditampilkan?

def add2(x, lst=None):    if lst is None:        lst = []    lst.append(x)    return lstprint(add2(1))print(add2(2))print(add2(3, [9]))
  1. Dari atas ke bawah ditampilkan [1] / [1, 2] / [9, 3]
  2. Dari atas ke bawah ditampilkan [1] / [2] / [9, 3]
  3. Dari atas ke bawah ditampilkan [1] / [2] / [3, 9]
  4. Dari atas ke bawah ditampilkan [1] / [2] / [3]
JawabanB. Dari atas ke bawah ditampilkan [1] / [2] / [9, 3]

Pola baku adalah menjadikan nilai baku None, lalu memeriksa apakah None di awal badan fungsi dan membuat list kosong baru jika ya. Karena None adalah nilai yang tidak bisa diubah, meski hanya dievaluasi sekali, masalah berbagi tidak akan terjadi. Panggilan pertama dan kedua yang mengosongkan lst masing-masing membuat list kosong yang berbeda, sehingga menjadi [1] dan [2], dan elemen dari panggilan sebelumnya tidak tersisa. Panggilan ketiga karena sisi pemanggil memberikan [9], lst is None menjadi salah, sehingga 3 ditambahkan ke akhir list yang diberikan, menjadi [9, 3]. Jika tetap memakai lst=[] sebelum diperbaiki, pada panggilan kedua yang mengosongkan argumen, elemen panggilan sebelumnya akan tersisa menjadi [1, 2]. Jika list yang diberikan sisi pemanggil diabaikan dan tetap membuat list baru, akan menjadi [1] / [2] / [3], sehingga hilang keuntungan pola ini yaitu 'memakai yang diberikan ketika memang diberikan'. Jika bukan append ke akhir melainkan insert(0, x) untuk memasukkan di depan, panggilan ketiga akan menjadi [3, 9].

Soal 11 | Urutan pencarian nama

Manakah urutan LEGB yang benar untuk Python mencari nama variabel?

  1. Dicari dengan urutan fungsi yang sedang berjalan, level atas modul, fungsi luar, built-in
  2. Dicari dengan urutan fungsi yang sedang berjalan, fungsi luar, level atas modul, built-in
  3. Dicari dengan urutan level atas modul, built-in, fungsi yang sedang berjalan, fungsi luar
  4. Dicari dengan urutan built-in, level atas modul, fungsi luar, fungsi yang sedang berjalan
JawabanB. Dicari dengan urutan fungsi yang sedang berjalan, fungsi luar, level atas modul, built-in

LEGB adalah singkatan dari Local, Enclosing, Global, Built-in, menyatakan pencarian dari yang paling dalam ke luar. Pertama dilihat di dalam fungsi yang sedang berjalan, lalu di dalam fungsi luar yang membungkusnya, lalu level atas modul, dan terakhir built-in seperti len atau print. Pencarian berhenti begitu ditemukan, sehingga jika nama yang sama dipakai di dalam, nama di luar tersembunyi. Jika urutannya dibalik dari luar ke dalam, variabel lokal yang dibuat di dalam fungsi akan kalah dengan variabel bernama sama di level atas modul, yang tidak sesuai dengan perilaku sebenarnya. Kesalahan menukar urutan Global dan Enclosing juga sering terjadi, tetapi fungsi luar berada lebih dalam dibanding level atas modul.

Soal 12 | Penugasan lokal

Kode berikut menugaskan nilai ke nama yang sama dengan di luar, di dalam fungsi. Manakah 2 baris yang ditampilkan?

x = 'global'def f():    x = 'local'    print(x)f()print(x)
  1. local lalu local
  2. local lalu global
  3. global lalu global
  4. global lalu local
JawabanB. local lalu global

Jika di dalam fungsi menugaskan nilai ke suatu nama, nama tersebut dibuat sebagai variabel lokal baru, dan variabel bernama sama di level atas modul sama sekali tidak terpengaruh. print(x) di dalam fungsi membaca x lokal sehingga menampilkan local, tetapi print(x) setelah keluar fungsi membaca x level atas modul sehingga menampilkan global. Jika dianggap penugasan di dalam fungsi sampai ke luar, kedua baris akan menjadi local, tetapi untuk itu perlu mendeklarasikan global x di awal fungsi. Sebaliknya jika dianggap penugasan di dalam fungsi diabaikan dan nilai di luar yang dibaca, kedua baris akan menjadi global, tetapi karena sudah ditugaskan, variabel lokal memang dibuat. Ketidaksimetrisan 'membaca sampai ke luar, tetapi menulis tidak sampai' inilah dasar scope Python.

Soal 13 | nonlocal

Kode berikut mencoba mengubah variabel luar dari fungsi dalam. Isi apa yang ditampilkan?

def outer():    n = 1    def inner():        nonlocal n        n = 2    inner()    return ndef outer2():    n = 1    def inner():        n = 2    inner()    return nprint(outer(), outer2())
  1. 1 2
  2. 2 2
  3. 2 1
  4. 1 1
JawabanC. 2 1

Karena inner pada outer menulis nonlocal n, penugasan ke n sampai ke n milik outer yang satu tingkat di luarnya. n yang dikembalikan outer setelah memanggil inner adalah 2. Karena inner pada outer2 tidak memiliki deklarasi tersebut, n = 2 hanya membuat variabel lokal baru milik inner, dan n milik outer2 tetap tidak berubah yaitu 1. Oleh karena itu ditampilkan 2 1. Jika dianggap keduanya sampai ke luar, akan menjadi 2 2, dan jika dianggap keduanya tidak sampai, akan menjadi 1 1, tetapi yang menciptakan perbedaan hanyalah 1 baris nonlocal. Perlu dicatat, nonlocal tidak bisa dipakai jika tidak ada variabel bernama sama di fungsi luarnya, dan tidak bisa menunjuk variabel level atas modul. Untuk mengubah variabel level atas, gunakan global.

Soal 14 | Error belum terikat

Membaca nama yang memiliki penugasan di baris belakangnya, pada baris sebelum itu. Apa yang terjadi ketika kode berikut dijalankan?

z = 100def bad():    print(z)    z = 1bad()
  1. Terjadi UnboundLocalError, tidak ada yang ditampilkan lalu berhenti
  2. Ditampilkan 100, lalu eksekusi berakhir normal sampai selesai
  3. Ditampilkan None, lalu eksekusi berakhir normal sampai selesai
  4. Ditampilkan 1, lalu eksekusi berakhir normal sampai selesai
JawabanA. Terjadi UnboundLocalError, tidak ada yang ditampilkan lalu berhenti

Python, pada saat mengompilasi badan fungsi, menentukan bahwa semua nama yang memiliki penugasan di dalam fungsi itu adalah variabel lokal. Penentuan ini dilakukan untuk keseluruhan fungsi, tidak peduli urutan barisnya. Pada bad ini, karena ada penugasan z = 1 di baris belakang, z diperlakukan sebagai variabel lokal fungsi ini. Akibatnya pada saat print(z), nilainya belum terisi sehingga terjadi UnboundLocalError. Bunyi pesan exception ini bisa berbeda tergantung versi Python, tetapi nama tipenya tetap UnboundLocalError. z = 100 di level atas modul pun tidak akan dirujuk. Jika dianggap karena berada sebelum baris penugasan maka yang dibaca adalah sisi luar, akan menjadi 100, tetapi Python tidak bekerja seperti itu. Variabel lokal yang belum terisi nilai pun tidak akan menjadi None.

Soal 15 | Deklarasi global

Kode berikut menjajarkan fungsi yang memiliki deklarasi global dan yang tidak. Isi apa yang ditampilkan?

n = 10def f():    global n    n = 20    return ndef g():    n = 30    return nprint(f(), g(), n)
  1. 20 30 30
  2. 20 30 20
  3. 20 30 10
  4. 10 30 10
JawabanB. 20 30 20

Karena f menulis global n, n = 20 mengubah n di level atas modul. f sendiri mengembalikan 20, dan n di level atas modul pun menjadi 20. Karena g tidak memiliki deklarasi, n = 30 hanya membuat variabel lokal milik g, dan tidak memengaruhi n di level atas modul. g mengembalikan 30 miliknya sendiri. n yang dibaca print terakhir adalah milik level atas modul, dan karena sudah diubah oleh f, nilainya 20. Jika dianggap penugasan g pun sampai ke luar, akan menjadi 20 30 30, dan jika dianggap global tidak berpengaruh, akan menjadi 20 30 10. Perlu diperhatikan juga bahwa deklarasi global adalah instruksi 'sampaikan penugasan pada nama ini ke level atas', dan jika hanya membaca nilai, deklarasi tidak diperlukan.

Soal 16 | Efek __all__

mymod.py berikut disiapkan, lalu di file lain ditulis from mymod import *. Manakah nama yang dibawa masuk ke sisi pemanggil?

# mymod.py__all__ = ['pub']pub = 'P'other = 'O'def hello():    return 'H'
  1. pub, other, dan hello, ketiganya dibawa masuk semua
  2. pub dan hello dibawa masuk, other tidak dibawa masuk
  3. other dan hello dibawa masuk, pub tidak dibawa masuk
  4. Hanya pub yang dibawa masuk, other dan hello tidak dibawa masuk
JawabanD. Hanya pub yang dibawa masuk, other dan hello tidak dibawa masuk

Pengambilan dengan tanda bintang biasanya membawa masuk semua nama yang tidak diawali garis bawah, tetapi jika sisi modul memiliki list string bernama __all__, akan dipersempit hanya menjadi nama yang tercantum di sana. Pada contoh ini, __all__ hanya berisi pub, sehingga other maupun hello tidak dibawa masuk. other tidak diawali garis bawah, tetapi selama __all__ ada, daftar itulah yang diutamakan. Karena bukan hal yang diperlakukan istimewa karena berupa fungsi, hello pun sama-sama tidak dibawa masuk. __all__ hanya berlaku pada pengambilan dengan tanda bintang, dan tidak menghalangi pengambilan dengan menyebut nama seperti from mymod import hello, atau cara import mymod lalu menulis mymod.hello().

Soal 17 | Nilai __name__

Untuk mymod2.py berikut, manakah kombinasi nilai __name__ ketika dijalankan langsung dengan python3 mymod2.py, dan ketika di-import mymod2 dari file lain?

# mymod2.pyprint('mymod2:', __name__)if __name__ == '__main__':    print('直接実行')
  1. Saat dijalankan langsung menjadi mymod2, saat di-import menjadi __main__
  2. Baik dijalankan langsung maupun di-import, keduanya menjadi __main__
  3. Saat dijalankan langsung menjadi __main__, saat di-import menjadi mymod2
  4. Baik dijalankan langsung maupun di-import, keduanya menjadi mymod2
JawabanC. Saat dijalankan langsung menjadi __main__, saat di-import menjadi mymod2

Ketika file tersebut dijalankan langsung dengan python3, __name__ berisi '__main__'. Ketika di-import dari file lain, berisi 'mymod2', yaitu nama file tanpa ekstensi. Oleh karena itu, saat dijalankan langsung muncul 2 baris mymod2: __main__ dan 直接実行 (eksekusi langsung), dan saat di-import hanya muncul 1 baris mymod2: mymod2. if __name__ == '__main__': adalah penulisan baku yang memanfaatkan perbedaan ini untuk memisahkan 'proses yang hanya ingin dijalankan saat dieksekusi langsung'. Jika kombinasinya dibalik, proses untuk verifikasi operasi akan ikut berjalan saat di-import, yang tidak sesuai dengan tujuan penulisan ini. Jika kedua kasus menghasilkan nilai yang sama, pemisahan ini sejak awal tidak mungkin dilakukan.

Soal 18 | sys.argv

args.py berikut dijalankan dengan perintah python3 args.py foo bar. Manakah 2 baris yang ditampilkan?

# args.pyimport sysprint(len(sys.argv))print(sys.argv[1:])
  1. 2 lalu ['foo', 'bar']
  2. 3 lalu ['args.py', 'foo']
  3. 3 lalu ['foo', 'bar']
  4. 2 lalu ['args.py', 'foo']
JawabanC. 3 lalu ['foo', 'bar']

sys.argv adalah list yang menjajarkan nilai yang diberikan dari command line, dan di posisi paling depan berisi nama skrip itu sendiri. Pada contoh ini menjadi list 3 elemen seperti ['args.py', 'foo', 'bar'], sehingga len-nya 3. sys.argv[1:] yang mengambil mulai indeks 1 menjadi ['foo', 'bar'] tanpa nama skrip. Jika dianggap jumlah argumen yang diberikan langsung menjadi panjangnya, akan menjadi 2, tetapi ini terlewat menghitung nama skrip di depan. Menganggap sys.argv[1:] menjadi ['args.py', 'foo'] terjadi jika posisi awal slicing tertukar. Jika dijalankan tanpa memberikan argumen sama sekali, panjangnya 1, dan sys.argv[1:] menjadi list kosong.

Soal 19 | Pemuatan ulang modul

Manakah perilaku yang benar ketika modul yang sama di-import dua kali di dalam satu program?

  1. Kedua kalinya badan modul dibaca ulang dari awal dan dijalankan lagi
  2. Kedua kalinya dibuat objek yang berbeda, status tidak lagi dibagikan
  3. Kedua kalinya cache dipakai, badan modul tidak dijalankan ulang
  4. Kedua kalinya menjadi ImportError, nama yang sama hanya bisa dipakai sekali
JawabanC. Kedua kalinya cache dipakai, badan modul tidak dijalankan ulang

Modul yang sudah dimuat sekali disimpan dalam cache di dictionary bernama sys.modules. Import kedua hanya menghubungkan kembali nama ke objek modul yang sama yang sudah terdaftar di sana, sehingga tidak terjadi pembacaan ulang file maupun eksekusi ulang badan modul. Jika print ditulis di level atas modul, bisa dipastikan bahwa meski di-import dua kali, hanya ditampilkan sekali. Meski diberi nama alias saat import, yang ditunjuk tetaplah objek yang sama. Oleh karena itu, jika variabel suatu modul diubah, dari tempat lain mana pun yang meng-import modul itu akan terlihat nilai yang sudah berubah. Meng-import nama yang sama dua kali tidak akan menimbulkan error, dan jika ingin memuat ulang badan modul selama pengembangan, perlu menggunakan fitur importlib secara eksplisit.

Soal 20 | Deteksi virtual environment

Manakah cara yang tepat untuk menentukan apakah Python yang sedang berjalan berada di dalam virtual environment yang dibuat dengan venv?

  1. Jika sys.prefix tidak terdefinisi, bisa ditentukan sedang berada di dalam virtual environment
  2. Jika sys.prefix dan sys.base_prefix sama, bisa ditentukan sedang berada di dalam virtual environment
  3. Jika sys.base_prefix terdefinisi, bisa ditentukan sedang berada di dalam virtual environment
  4. Jika sys.prefix dan sys.base_prefix berbeda, bisa ditentukan sedang berada di dalam virtual environment
JawabanD. Jika sys.prefix dan sys.base_prefix berbeda, bisa ditentukan sedang berada di dalam virtual environment

sys.prefix menunjuk lokasi tempat Python yang sedang berjalan terinstal, dan sys.base_prefix menunjuk lokasi Python asal yang menjadi sumbernya. Di luar virtual environment, keduanya bernilai sama, tetapi di dalam environment yang dibuat dengan python3 -m venv, sys.prefix menunjuk direktori environment yang dibuat, sedangkan sys.base_prefix tetap menunjuk Python asal, sehingga keduanya berbeda. Ada tidaknya perbedaan inilah yang menjadi petunjuk penentuan. Menyatakan sama berarti di dalam virtual environment adalah kondisi yang terbalik. sys.base_prefix selalu terdefinisi bahkan di luar virtual environment, sehingga ada-tidaknya definisi tidak bisa dipakai untuk menentukan. sys.prefix juga selalu terdefinisi. Karena string path konkret dan versi pip berbeda-beda tiap environment, cukup ingat hubungan ini saja.

Soal 21 | Nilai kembalian write

Kode berikut menerima nilai kembalian metode penulisan. Isi apa yang ditampilkan?

with open('/tmp/sample.txt', 'w') as f:    n = f.write("one\ntwo\nthree\n")print(n)
  1. Ditampilkan None
  2. Ditampilkan 3
  3. Ditampilkan 14
  4. Ditampilkan 11
JawabanC. Ditampilkan 14

write mengembalikan jumlah karakter yang ditulis sebagai nilai kembalian. "one\ntwo\nthree\n" terdiri dari one 3 karakter, two 3 karakter, three 5 karakter, totalnya 11 karakter, ditambah 3 karakter baris baru sehingga totalnya 14 karakter. Jika lupa menghitung baris baru, akan menjadi 11. Jika dianggap jumlah baris yang ditulis yang dikembalikan, akan menjadi 3, tetapi yang dihitung write adalah jumlah karakter. Menganggap tidak mengembalikan apa pun dan memilih None juga kesalahan yang sering terjadi, mudah tertukar dengan metode seperti append atau sort yang mengembalikan None, tetapi write mengembalikan angka. Perlu dicatat, write tidak menambahkan baris baru secara otomatis, sehingga jika ingin memisahkan baris perlu menuliskannya sendiri di dalam string.

Soal 22 | readlines

File berisi 3 baris dibaca dengan readlines. Isi apa yang ditampilkan?

with open('/tmp/sample.txt', 'w') as f:    f.write("one\ntwo\nthree\n")with open('/tmp/sample.txt') as f:    print(f.readlines())
  1. Ditampilkan ['one\ntwo\nthree\n']
  2. Ditampilkan 'one\ntwo\nthree\n'
  3. Ditampilkan ['one\n', 'two\n', 'three\n']
  4. Ditampilkan ['one', 'two', 'three']
JawabanC. Ditampilkan ['one\n', 'two\n', 'three\n']

readlines mengembalikan seluruh baris sebagai list, tetapi karakter baris baru di akhir tiap elemen tetap dipertahankan. Oleh karena itu menjadi ['one\n', 'two\n', 'three\n']. Jika dianggap baris baru dihilangkan, akan menjadi ['one', 'two', 'three'], tetapi untuk mendapatkan itu perlu menerapkan rstrip pada tiap elemen. Yang mengembalikan sebagai satu string tanpa dipisah adalah read, sedangkan readlines mengembalikan list. Jika dianggap file keseluruhan hanya dibungkus sebagai 1 elemen tanpa memperlakukan baris baru sebagai pemisah, akan menjadi ['one\ntwo\nthree\n'], tetapi readlines memisahkan elemen setiap kali menemukan baris baru. Ketika mengambil baris satu per satu dengan for line in f pun, baris yang diterima sama-sama membawa karakter baris baru.

Soal 23 | read kedua kali

Membaca dua kali berturut-turut dari objek file yang sama. Isi apa yang ditampilkan?

with open('/tmp/sample.txt') as f:    a = f.read()    b = f.read()print(len(a), b == '')
  1. Ditampilkan 14 False
  2. Ditampilkan 14 True
  3. Ditampilkan 0 False
  4. Ditampilkan 0 True
JawabanB. Ditampilkan 14 True

Objek file mengingat posisi saat ini. read pertama membaca habis dari awal sampai akhir, sehingga a berisi 14 karakter termasuk baris baru, dan posisi berpindah ke akhir. read kedua mencoba membaca dari akhir, tetapi karena tidak ada sisa, dikembalikan string kosong. Oleh karena itu len(a) adalah 14, dan b == '' adalah True. Jika dianggap kedua kalinya mengembalikan isi yang sama, b == '' akan menjadi False, tetapi untuk membaca ulang isi yang sama perlu mengembalikan posisi ke awal dengan f.seek(0). len(a) menjadi 0 terjadi jika file kosong atau posisi sudah berada di akhir sejak awal. Situasi ini mudah disalahpahami sebagai kegagalan pembacaan karena tidak ada yang ditampilkan, padahal penyebabnya adalah posisi berada di akhir.

Soal 24 | with dan close

Memastikan closed di dalam dan di luar blok with. Manakah 2 baris yang ditampilkan?

with open('/tmp/sample.txt') as f:    print(f.closed)print(f.closed)
  1. False lalu False
  2. False lalu True
  3. True lalu False
  4. True lalu True
JawabanB. False lalu True

Di dalam blok with, file masih terbuka, sehingga closed bernilai False. Karena ditutup secara otomatis saat keluar dari blok, closed setelahnya menjadi True. Oleh karena itu ditampilkan False lalu True. Tujuan pemakaian with adalah agar proses penutupan ini tetap dijalankan meski lupa dituliskan, bahkan jika terjadi exception di dalam blok yang menyebabkan keluar di tengah jalan, file tetap ditutup. Jika dipisah dengan koma, beberapa file bisa dibuka sekaligus dalam satu pernyataan with, dan dalam kasus itu pun semuanya ditutup dengan cara yang sama. Mencoba membaca dari objek file yang sudah ditutup akan menimbulkan ValueError, sehingga tidak bisa membaca ulang di luar with.

Soal 25 | Mode penulisan

File yang sama dibuka 3 kali untuk ditulis lalu dibaca. Isi apa yang ditampilkan?

p = '/tmp/sample.txt'with open(p, 'w') as f:    f.write('AAA')with open(p, 'a') as f:    f.write('BBB')with open(p, 'w') as f:    f.write('C')with open(p) as f:    print(f.read())
  1. Isi file menjadi AAABBB
  2. Isi file menjadi AAABBBC
  3. Isi file menjadi C
  4. Isi file menjadi CAABBB
JawabanC. Isi file menjadi C

Jika dibuka dengan 'w', file dipotong menjadi panjang 0 pada saat itu juga, tanpa memandang ada tidaknya isi yang sudah ada. Jika ditelusuri urut, pembukaan pertama dengan 'w' memasukkan AAA, lalu pembukaan berikutnya dengan 'a' bersifat menambahkan sehingga menjadi AAABBB. Namun begitu dibuka ketiga kalinya dengan 'w', AAABBB terhapus, lalu C ditulis ke sana, sehingga pada akhirnya hanya tersisa C. Jika dianggap ketiga kalinya dibuka dengan 'a', akan menjadi AAABBBC, dan jika dianggap penulisan terakhir diabaikan, akan menjadi AAABBB. Menimpa dari awal tanpa memotong adalah perilaku 'r+', dan dalam kasus itu hanya 1 karakter di awal yang tergantikan seperti CAABBB. Pemotongan terjadi pada saat membuka, bukan pada saat menulis, sehingga jika hanya dibuka dengan 'w' tanpa menulis apa pun, file akan menjadi kosong. Jika bermaksud menambahkan gunakan 'a', jika bermaksud membuat ulang gunakan 'w'; nyatakan maksud lewat mode.

Soal 26 | Operasi setelah ditutup

Apa yang terjadi jika mencoba membaca objek file setelah close?

f = open('/tmp/sample.txt')f.close()print(f.read())
  1. Terjadi ValueError, memberitahu bahwa itu operasi pada file yang sudah tertutup
  2. String kosong ditampilkan, lalu eksekusi berakhir normal
  3. Dibuka ulang secara otomatis, lalu seluruh isi file ditampilkan
  4. Terjadi OSError, memberitahu bahwa file tidak bisa dibuka ulang
JawabanA. Terjadi ValueError, memberitahu bahwa itu operasi pada file yang sudah tertutup

Memanggil read pada objek file setelah close akan menimbulkan ValueError dengan pesan I/O operation on closed file. Fakta bahwa file sudah ditutup diingat oleh objek itu sendiri, sehingga tidak akan dibuka ulang secara otomatis meski dibutuhkan. Bukan berarti tidak ada yang bisa dibaca sehingga string kosong dikembalikan; itu adalah perilaku read setelah dibaca habis sampai akhir. FileNotFoundError ketika file tidak ditemukan adalah bagian dari OSError, tetapi exception ketika mengoperasikan file yang sudah ditutup berbeda, yaitu ValueError. Dengan menggunakan pernyataan with, upaya membaca setelah keluar blok itu sendiri bisa dihindari secara struktural.

Soal 27 | Hanya baca

Apa yang terjadi jika mencoba menulis ke file yang dibuka dengan mode dikosongkan?

with open('/tmp/sample.txt', 'w') as f:    f.write('data')with open('/tmp/sample.txt') as f:    f.write('more')
  1. Terjadi io.UnsupportedOperation, memberitahu bahwa tidak bisa ditulis
  2. Terjadi ValueError, memberitahu bahwa penentuan mode tidak benar
  3. more ditambahkan di akhir isi, lalu eksekusi berakhir normal
  4. Isi digantikan menjadi more, lalu eksekusi berakhir normal
JawabanA. Terjadi io.UnsupportedOperation, memberitahu bahwa tidak bisa ditulis

Jika argumen kedua open dikosongkan, menjadi hanya-baca 'r'. Memanggil write pada objek file yang dibuka hanya untuk dibaca akan menjadi io.UnsupportedOperation dengan pesan not writable. Karena secara tata bahasa penulisan ini tetap lolos, kesalahan penentuan mode baru disadari setelah dijalankan. Jika ingin menimpa gunakan 'w', jika ingin menambahkan di akhir gunakan 'a', jika ingin membaca dan menulis sekaligus buka ulang dengan 'r+'. Sebaliknya jika memanggil read pada file yang dibuka dengan 'w' atau 'a', akan diberitahu io.UnsupportedOperation yang sama dengan pesan not readable. Mode bisa dikatakan sebagai tempat mendeklarasikan apa yang diizinkan pada objek file tersebut.

Soal 28 | seek dan tell

Kode berikut hanya membaca 5 karakter dari awal lalu memastikan posisinya. Isi apa yang ditampilkan?

with open('/tmp/sample.txt') as f:    head = f.read(5)    pos = f.tell()    f.seek(0)    print(repr(head), pos, f.tell())
  1. 'one\ntwo\nthree\n' 14 0
  2. 'one\nt' 5 0
  3. 'one\nt' 5 5
  4. 'one\nt' 0 0
JawabanB. 'one\nt' 5 0

Jika angka diberikan ke read, hanya akan membaca sebanyak jumlah karakter itu. 5 karakter dari awal adalah 3 karakter one, 1 karakter baris baru, dan t dari two, sehingga menjadi 'one\nt'. Karena posisi maju sebanyak yang dibaca, tell berikutnya mengembalikan 5. seek(0) berikutnya mengembalikan posisi ke awal, sehingga tell terakhir menjadi 0. Jika dianggap read tidak menggerakkan posisi, kedua tell akan menjadi 0, dan jika dianggap seek tidak berpengaruh, kedua tell akan menjadi 5. Jika argumen read tertukar sebagai jumlah baris bukan jumlah karakter, dengan maksud membaca sampai baris ke-5, akan terpilih keseluruhan file 'one\ntwo\nthree\n' dan 14. Dengan menggunakan seek(0), bisa membaca ulang dari awal dengan objek file yang sama.

Soal 29 | JSON dan tuple

Kode berikut mengubah dictionary yang berisi tuple menjadi JSON lalu membacanya kembali. Manakah 2 baris yang ditampilkan?

import jsons = json.dumps({'t': (1, 2)})back = json.loads(s)print(s)print(back, type(back['t']).__name__)
  1. {"t": "(1, 2)"} lalu {'t': '(1, 2)'} str
  2. {"t": [1, 2]} lalu {'t': (1, 2)} tuple
  3. {"t": (1, 2)} lalu {'t': (1, 2)} tuple
  4. {"t": [1, 2]} lalu {'t': [1, 2]} list
JawabanD. {"t": [1, 2]} lalu {'t': [1, 2]} list

JSON hanya memiliki array, tidak ada tipe bernama tuple. Oleh karena itu, pada saat dumps, tuple dikonversi menjadi array, dan hasilnya {"t": [1, 2]}. Saat dibaca kembali pun, array dipulihkan sebagai list, sehingga tipe back['t'] adalah list, bukan kembali menjadi tuple. Karena informasi bahwa aslinya adalah tuple tidak tersimpan di mana pun, tipenya berubah ketika bolak-balik. Jika ingin tetap diperlakukan sebagai tuple, perlu menerapkan tuple() sendiri setelah dibaca kembali. Tanda kurung biasa tidak akan muncul pada output JSON, dan tuple juga tidak dikonversi menjadi string. Dengan alasan yang sama, jika key dictionary memakai bilangan bulat, akan dikonversi menjadi string oleh dumps, dan tidak akan kembali menjadi bilangan bulat meski dibaca ulang.

Soal 30 | Key pada JSON

Apa yang terjadi jika mencoba mengubah dictionary dengan key bilangan bulat dan sebuah set menjadi JSON?

import jsonprint(json.dumps({1: 'a'}))print(json.dumps({1, 2}))
  1. Baris 1 muncul {1: "a"}, baris 2 muncul [1, 2]
  2. Baris 1 menjadi TypeError, baris 2 tidak dijalankan
  3. Baris 1 muncul {"1": "a"}, baris 2 menjadi TypeError
  4. Baris 1 muncul {"1": "a"}, baris 2 muncul [1, 2]
JawabanC. Baris 1 muncul {"1": "a"}, baris 2 menjadi TypeError

Key pada objek JSON harus berupa string, sehingga jika dictionary dengan key bilangan bulat di-dumps, key dikonversi menjadi string dan ditampilkan {"1": "a"}. Ini adalah proses yang berhasil secara normal, tidak menimbulkan exception di sini. Namun perlu diperhatikan bahwa key saat dibaca kembali tetap berupa string, tidak kembali menjadi bilangan bulat. Set pada baris 2 tidak memiliki tipe yang setara di JSON, sehingga jika dicoba di-dumps akan menjadi TypeError dengan pesan Object of type set is not JSON serializable. Jika dianggap set ditulis sebagai array, akan menjadi [1, 2], tetapi jika ingin dikonversi seperti itu, perlu menerapkan list() sendiri sebelum diberikan. Untuk nilai yang mengandung bahasa Jepang, secara baku akan di-escape saat ditampilkan, dan jika ensure_ascii diberi nilai salah, akan ditampilkan dengan karakter apa adanya.

Latihan: kerjakan soal di halaman ini

Ini adalah alat latihan dengan soal acak (berfungsi jika JavaScript aktif). Semua soal dan penjelasan di atas tetap dapat dibaca tanpa alat ini.

* Pembahasan ini merupakan informasi untuk keperluan belajar. Cakupan dan sistem ujian dapat berubah setiap tahun, jadi selalu periksa pengumuman resmi dari lembaga penyelenggara ujian.

Halaman ini adalah terjemahan dari teks asli berbahasa Jepang. Jika terdapat perbedaan antara terjemahan dan teks asli, versi bahasa Jepang yang berlaku. Lihat teks asli bahasa Jepang