Fungsi #
Fungsi adalah blok kode yang diberi nama, dapat dipanggil berulang kali, dan membantu memecah program besar menjadi unit-unit kecil yang mudah dipahami dan diuji. Di Python, fungsi adalah first-class object — artinya fungsi bisa disimpan dalam variabel, dioper sebagai argumen, dikembalikan dari fungsi lain, dan disimpan dalam struktur data seperti list atau dict. Fleksibilitas ini membuat fungsi di Python jauh lebih powerful dibanding di banyak bahasa lain. Artikel ini membahas semua aspek fungsi Python — dari sintaks dasar, jenis-jenis parameter, hingga closure dan lambda.
Mendefinisikan dan Memanggil Fungsi #
Fungsi didefinisikan dengan keyword def, diikuti nama fungsi, tanda kurung berisi parameter, titik dua, dan blok kode yang diindentasi.
# Definisi fungsi dasar
def sapa():
"""Mencetak salam sederhana."""
print("Halo, selamat datang!")
# Memanggil fungsi
sapa() # → Halo, selamat datang!
# Fungsi dengan parameter dan nilai kembalian
def tambah(a, b):
"""Mengembalikan jumlah dari dua bilangan."""
return a + b
hasil = tambah(3, 5)
print(hasil) # → 8
# Fungsi tanpa return mengembalikan None secara implisit
def cetak_nama(nama):
print(f"Nama: {nama}")
hasil = cetak_nama("Budi")
print(hasil) # → None
Jenis-jenis Parameter #
Python memiliki sistem parameter yang sangat fleksibel. Memahami semua jenisnya adalah kunci untuk menulis API fungsi yang bersih dan ekspresif.
Parameter Positional #
Parameter standar yang nilainya ditetapkan berdasarkan urutan saat pemanggilan:
def bagi(dividend, divisor):
return dividend / divisor
print(bagi(10, 2)) # → 5.0 (positional: 10→dividend, 2→divisor)
Keyword Arguments #
Saat memanggil fungsi, kamu bisa menyebut nama parameter secara eksplisit — memungkinkan urutan yang berbeda dan membuat kode lebih mudah dibaca:
def buat_profil(nama, umur, kota):
return f"{nama}, {umur} tahun, dari {kota}"
# Positional — urutan harus tepat
print(buat_profil("Budi", 25, "Jakarta"))
# Keyword — urutan bebas, lebih eksplisit
print(buat_profil(umur=25, kota="Jakarta", nama="Budi"))
# Campuran — positional harus sebelum keyword
print(buat_profil("Budi", kota="Jakarta", umur=25))
Parameter Default #
Nilai default digunakan ketika argumen tidak diberikan saat pemanggilan:
def koneksi_db(host="localhost", port=5432, ssl=False):
print(f"Koneksi ke {host}:{port} (SSL: {ssl})")
koneksi_db() # → localhost:5432 (SSL: False)
koneksi_db("db.prod.com") # → db.prod.com:5432 (SSL: False)
koneksi_db("db.prod.com", ssl=True) # → db.prod.com:5432 (SSL: True)
koneksi_db("db.prod.com", 5433, True) # → db.prod.com:5433 (SSL: True)
Anti-Pattern: Mutable Default Argument #
Ini adalah salah satu jebakan paling terkenal di Python — menggunakan objek mutable (list, dict, set) sebagai nilai default parameter:
# ANTI-PATTERN: list sebagai default — dishare antar semua pemanggilan!
def tambah_item(item, keranjang=[]):
keranjang.append(item)
return keranjang
print(tambah_item("apel")) # → ['apel']
print(tambah_item("jeruk")) # → ['apel', 'jeruk'] ← mengejutkan!
print(tambah_item("mangga")) # → ['apel', 'jeruk', 'mangga'] ← data lama menumpuk!
# Mengapa? Karena default value dievaluasi SEKALI saat fungsi didefinisikan,
# bukan setiap kali fungsi dipanggil. List default tersebut dishare.
# BENAR: gunakan None sebagai sentinel, buat objek baru di dalam fungsi
def tambah_item(item, keranjang=None):
if keranjang is None:
keranjang = []
keranjang.append(item)
return keranjang
print(tambah_item("apel")) # → ['apel']
print(tambah_item("jeruk")) # → ['jeruk'] ← benar, list baru setiap kali
Jangan pernah gunakan[],{}, atauset()sebagai default value parameter. Selalu gunakanNonedan buat objek baru di dalam body fungsi. Ini berlaku untuk semua objek mutable apapun — termasuk instance kelas custom.
*args — Argumen Positional Tak Terbatas
#
*args menangkap semua argumen positional ekstra ke dalam sebuah tuple:
def jumlahkan(*angka):
"""Jumlahkan argumen positional sebanyak apapun."""
print(type(angka)) # → <class 'tuple'>
return sum(angka)
print(jumlahkan(1, 2, 3)) # → 6
print(jumlahkan(1, 2, 3, 4, 5)) # → 15
print(jumlahkan()) # → 0
# *args bisa digabung dengan parameter biasa — tapi harus di belakang
def log(level, *pesan):
print(f"[{level}]", *pesan)
log("INFO", "Server", "started") # → [INFO] Server started
log("ERROR", "Connection", "timed", "out") # → [ERROR] Connection timed out
**kwargs — Keyword Arguments Tak Terbatas
#
**kwargs menangkap semua keyword argument ekstra ke dalam sebuah dict:
def cetak_info(**data):
"""Cetak semua keyword argument yang diberikan."""
print(type(data)) # → <class 'dict'>
for kunci, nilai in data.items():
print(f" {kunci}: {nilai}")
cetak_info(nama="Budi", umur=25, kota="Jakarta")
# → nama: Budi
# → umur: 25
# → kota: Jakarta
# Berguna untuk fungsi yang meneruskan kwargs ke fungsi lain
def buat_koneksi(**config):
# config bisa berisi host, port, user, password, dll.
return Database(**config)
Urutan Parameter yang Benar #
Python memiliki urutan ketat untuk jenis-jenis parameter dalam satu definisi fungsi:
# Urutan yang benar:
# 1. Positional biasa
# 2. *args
# 3. Keyword-only (setelah *args)
# 4. **kwargs
def fungsi_lengkap(pos1, pos2, *args, kw_only1, kw_only2="default", **kwargs):
print(f"pos1={pos1}, pos2={pos2}")
print(f"args={args}")
print(f"kw_only1={kw_only1}, kw_only2={kw_only2}")
print(f"kwargs={kwargs}")
fungsi_lengkap(
"a", "b", # pos1, pos2
"c", "d", # masuk ke args
kw_only1="wajib", # keyword-only (wajib karena tidak ada default)
extra="tambahan" # masuk ke kwargs
)
Keyword-Only Parameters #
Parameter setelah * (atau setelah *args) hanya bisa diisi dengan keyword, tidak bisa positional:
# Gunakan * kosong untuk memaksa semua parameter setelahnya menjadi keyword-only
def kirim_email(tujuan, subjek, *, cc=None, bcc=None, prioritas="normal"):
print(f"Kirim ke {tujuan}: {subjek}")
if cc:
print(f"CC: {cc}")
# BENAR: parameter keyword-only diisi dengan keyword
kirim_email("[email protected]", "Halo", cc="[email protected]")
# ANTI-PATTERN: mencoba isi keyword-only secara positional
kirim_email("[email protected]", "Halo", "[email protected]")
# → TypeError: kirim_email() takes 2 positional arguments but 3 were given
Positional-Only Parameters (Python 3.8+) #
Parameter sebelum / hanya bisa diisi secara positional, tidak bisa dengan keyword:
# Gunakan / untuk memaksa parameter sebelumnya menjadi positional-only
def bagi(dividend, divisor, /):
return dividend / divisor
bagi(10, 2) # ✓ positional
bagi(dividend=10, divisor=2) # ✗ TypeError — tidak boleh keyword
Nilai Kembalian (return)
#
Fungsi bisa mengembalikan satu atau banyak nilai. Tanpa return, fungsi mengembalikan None.
# Kembalikan satu nilai
def kuadrat(x):
return x ** 2
# Kembalikan beberapa nilai (dikemas sebagai tuple)
def statistik(data):
return min(data), max(data), sum(data) / len(data)
minimum, maksimum, rata = statistik([3, 1, 4, 1, 5, 9, 2, 6])
print(minimum, maksimum, rata) # → 1 9 3.875
# Return di tengah fungsi — untuk early exit
def bagi_aman(a, b):
if b == 0:
return None # early return
return a / b
print(bagi_aman(10, 2)) # → 5.0
print(bagi_aman(10, 0)) # → None
Type Hints pada Fungsi #
Type hints membuat signature fungsi lebih jelas dan membantu IDE memberikan autocomplete yang akurat:
from typing import Optional, Union
# Anotasi parameter dan return type
def hitung_luas(panjang: float, lebar: float) -> float:
return panjang * lebar
# Optional — boleh None
def cari_pengguna(user_id: int) -> Optional[str]:
pengguna = db.get(user_id)
return pengguna.nama if pengguna else None
# Union (Python 3.9 ke bawah) atau | (Python 3.10+)
def format_harga(harga: Union[int, float]) -> str:
return f"Rp{harga:,.0f}"
def format_harga(harga: int | float) -> str: # Python 3.10+
return f"Rp{harga:,.0f}"
# Tipe koleksi
from typing import List, Dict, Tuple
def rata_rata(angka: List[float]) -> float:
return sum(angka) / len(angka)
def parse_config(raw: Dict[str, str]) -> Dict[str, int]:
return {k: int(v) for k, v in raw.items()}
# Fungsi yang tidak mengembalikan nilai
def log_error(pesan: str) -> None:
print(f"ERROR: {pesan}")
Lambda — Fungsi Anonim #
Lambda adalah fungsi kecil tanpa nama yang didefinisikan dalam satu ekspresi. Berguna untuk fungsi sederhana yang hanya dipakai sekali, terutama sebagai argumen key dalam sorting.
# Sintaks: lambda parameter: ekspresi
kuadrat = lambda x: x ** 2
print(kuadrat(5)) # → 25
tambah = lambda a, b: a + b
print(tambah(3, 4)) # → 7
# Penggunaan paling umum: sebagai key dalam sorted()
mahasiswa = [
{"nama": "Budi", "nilai": 85},
{"nama": "Ani", "nilai": 92},
{"nama": "Citra", "nilai": 78},
]
# Urutkan berdasarkan nilai
urut = sorted(mahasiswa, key=lambda m: m["nilai"], reverse=True)
for m in urut:
print(f"{m['nama']}: {m['nilai']}")
# → Ani: 92
# → Budi: 85
# → Citra: 78
# Lambda dengan filter() dan map()
angka = [1, -2, 3, -4, 5, -6]
positif = list(filter(lambda n: n > 0, angka)) # → [1, 3, 5]
kuadrat = list(map(lambda n: n**2, angka)) # → [1, 4, 9, 16, 25, 36]
# ANTI-PATTERN: lambda kompleks yang seharusnya jadi fungsi biasa
proses = lambda data, threshold, scale: [x * scale for x in data if x > threshold]
# BENAR: fungsi biasa lebih mudah dibaca, bisa diberi docstring, dan bisa diuji
def filter_dan_skala(data, threshold, scale):
"""Filter elemen > threshold lalu kalikan dengan scale."""
return [x * scale for x in data if x > threshold]
Fungsi sebagai First-Class Object #
Di Python, fungsi adalah objek seperti halnya int atau string — bisa disimpan, dioper, dan dikembalikan:
# Simpan fungsi dalam variabel
def sapa(nama):
return f"Halo, {nama}!"
ucapan = sapa # bukan sapa() — kita menyimpan fungsinya, bukan memanggilnya
print(ucapan("Budi")) # → Halo, Budi!
# Simpan fungsi dalam list
def tambah(a, b): return a + b
def kurang(a, b): return a - b
def kali(a, b): return a * b
operasi = [tambah, kurang, kali]
for op in operasi:
print(op(10, 3)) # → 13, 7, 30
# Oper fungsi sebagai argumen (higher-order function)
def terapkan(fungsi, data):
return [fungsi(x) for x in data]
hasil = terapkan(lambda x: x**2, [1, 2, 3, 4, 5])
print(hasil) # → [1, 4, 9, 16, 25]
# Kembalikan fungsi dari fungsi (function factory)
def buat_pengali(faktor):
def pengali(x):
return x * faktor
return pengali
kali3 = buat_pengali(3)
kali5 = buat_pengali(5)
print(kali3(10)) # → 30
print(kali5(10)) # → 50
Closure #
Closure adalah fungsi yang “mengingat” nilai dari lingkup luar tempat ia didefinisikan, bahkan setelah lingkup luar itu selesai dieksekusi:
def buat_counter(mulai=0):
hitung = mulai # variabel di enclosing scope
def tambah(n=1):
nonlocal hitung # referensi ke hitung di buat_counter
hitung += n
return hitung
def reset():
nonlocal hitung
hitung = mulai
def nilai():
return hitung
return tambah, reset, nilai
# Buat dua counter independen
tambah_a, reset_a, nilai_a = buat_counter()
tambah_b, reset_b, nilai_b = buat_counter(100)
tambah_a()
tambah_a()
tambah_a(5)
print(nilai_a()) # → 7
tambah_b(50)
print(nilai_b()) # → 150
reset_a()
print(nilai_a()) # → 0 (a di-reset)
print(nilai_b()) # → 150 (b tidak terpengaruh)
Closure sering digunakan untuk membuat fungsi dengan konfigurasi awal yang berbeda tanpa perlu membuat kelas:
def buat_validator_panjang(min_len, max_len):
"""Membuat validator panjang string yang dapat dikonfigurasi."""
def validasi(teks):
return min_len <= len(teks) <= max_len
return validasi
validasi_username = buat_validator_panjang(3, 20)
validasi_password = buat_validator_panjang(8, 64)
print(validasi_username("ab")) # → False (terlalu pendek)
print(validasi_username("budi123")) # → True
print(validasi_password("pass")) # → False (terlalu pendek)
print(validasi_password("s3cr3t!pass")) # → True
Unpacking Argumen dengan * dan **
#
Operator * dan ** juga bisa digunakan saat memanggil fungsi untuk mengekspansi koleksi menjadi argumen:
def tambah(a, b, c):
return a + b + c
angka = [1, 2, 3]
print(tambah(*angka)) # setara dengan tambah(1, 2, 3) → 6
# ** untuk mengekspansi dict menjadi keyword arguments
config = {"host": "localhost", "port": 5432, "ssl": True}
koneksi_db(**config) # setara dengan koneksi_db(host="localhost", port=5432, ssl=True)
# Sangat berguna untuk meneruskan argumen dinamis
def buat_pengguna(**data):
return Pengguna(**data)
params = {"nama": "Budi", "email": "[email protected]", "aktif": True}
pengguna = buat_pengguna(**params)
Ringkasan #
- Jangan gunakan mutable sebagai default argument — selalu gunakan
Nonelalu buat objek baru di dalam fungsi. Mutable default dishare antar semua pemanggilan.- Keyword-only parameter (setelah
*) memaksa pemanggil menggunakan nama — ideal untuk parameter opsional yang maknanya tidak jelas dari posisi.*argsmenghasilkan tuple,**kwargsmenghasilkan dict — keduanya memungkinkan fungsi menerima jumlah argumen yang fleksibel.- Type hints tidak mengubah perilaku runtime — tapi sangat membantu IDE, linter, dan pembaca kode memahami kontrak fungsi.
- Lambda untuk fungsi sederhana satu ekspresi — terutama sebagai argumen
keydisorted(),min(),max(). Jangan gunakan lambda untuk logika kompleks.- Fungsi adalah first-class object — bisa disimpan dalam variabel, list, dict, dioper sebagai argumen, dan dikembalikan dari fungsi lain.
- Closure “mengingat” enclosing scope — berguna untuk membuat function factory dan state yang terenkapsulasi tanpa perlu kelas.
*dan**untuk unpacking saat pemanggilan — ekspansi list menjadi positional args dan dict menjadi keyword args.