2Python untuk AI

Functions

6 jam14 min baca
Tujuan

Function = abstraksi paling penting di programming. Setelah ini kamu bisa bikin function yang clean, reusable, dan Pythonic.

03 — Functions

Estimasi: 6 jam Tujuan: Function = abstraksi paling penting di programming. Setelah ini kamu bisa bikin function yang clean, reusable, dan Pythonic.


Kenapa Materi Ini Penting?

Setiap library AI yang akan kamu pakai pada akhirnya adalah kumpulan function: np.mean(), pd.read_csv(), model.fit(), tokenizer.encode(). Memahami function bukan cuma soal sintaks def, tapi soal berpikir modular — memecah masalah kompleks jadi unit-unit kecil yang bisa di-test dan di-reuse.

Function juga adalah pintu masuk ke konsep besar: pure function (foundation functional programming), higher-order function (yang dipakai di map/filter/reduce dan banyak ML utilities), dan eventually decorator (@staticmethod, @torch.no_grad, @app.route). Skill ini compound — semakin lihai pecah masalah jadi function, semakin produktif kamu di fase ML/LLM nanti.

Analogi besar: function = mesin di pabrik. Kamu masukkan bahan mentah lewat input (parameter), mesin proses sesuai resep (body), output produk jadi (return). Mesin yang sama bisa dipakai berulang-ulang dengan bahan berbeda.


Peta Konsep

flowchart TD
    A[⚙️ Functions] --> B[📥 Parameters]
    A --> C[📤 Return]
    A --> D[🔍 Scope]
    A --> E[🎁 Advanced]

    B --> B1[Positional]
    B --> B2[Keyword]
    B --> B3[Default]
    B --> B4[*args / **kwargs]

    C --> C1[Single value]
    C --> C2[Multiple tuple]
    C --> C3[None implicit]

    D --> D1[Local]
    D --> D2[Enclosing]
    D --> D3[Global]
    D --> D4[Built-in]

    E --> E1[Lambda]
    E --> E2[Higher-order]
    E --> E3[Type hints]

Bagian 1 — Kenapa Function?

Visualisasi: Function = Mesin

Cara Membaca Diagram

Kiri adalah input (argument), tengah adalah function body (logika), kanan adalah output (return value). Edge dari kiri ke kanan menunjukkan flow data. Function adalah black box: kasih input, terima output, tidak peduli detail dalam.

Walkthrough Step-by-Step

  1. Input — kamu kirim berat=70 dan tinggi=1.7 sebagai argument.
  2. Function body — Python alokasi local scope, parameter berat dan tinggi di-bind ke nilai input.
  3. Compute — eksekusi logika: berat / (tinggi ** 2).
  4. Return — kirim hasil 24.22 keluar dari function. Local scope dibuang.
  5. Output — caller (kode pemanggil) terima nilai untuk dipakai (assign ke variabel, print, dll).

Function ini bisa dipanggil ulang dengan input berbeda — itulah inti reusability.

Analogi Sehari-hari

Function = mesin jus. Kamu masukkan buah (input), tekan tombol, keluar jus (output). Mesin yang sama bisa dipakai untuk apel, jeruk, mangga — resep dalam mesin (function body) tetap, cuma bahan beda. Kalau lupa masukin bahan (lupa argument) → error. Kalau mesin rusak di tengah proses → exception.

Diagram statis Mermaid sebagai fallback:

flowchart LR
    I1[📥 berat: 70] --> M[⚙️ hitung_bmi]
    I2[📥 tinggi: 1.7] --> M
    M --> O[📤 24.22]

Mesin ini bisa dipakai untuk siapa saja — Budi, Ani, Cici. Resep di dalam mesin tetap sama, hanya inputnya beda.

Code Tanpa Function

# Hitung BMI 3 orang
berat = 70
tinggi = 1.7
bmi = berat / (tinggi ** 2)
print(f"BMI: {bmi:.2f}")

berat = 80
tinggi = 1.75
bmi = berat / (tinggi ** 2)
print(f"BMI: {bmi:.2f}")

berat = 65
tinggi = 1.65
bmi = berat / (tinggi ** 2)
print(f"BMI: {bmi:.2f}")

Masalah:

  • Repetitif
  • Kalau rumus berubah, harus edit 3 tempat
  • Sulit di-test

Code Dengan Function

def hitung_bmi(berat, tinggi):
    return berat / (tinggi ** 2)

print(f"BMI: {hitung_bmi(70, 1.70):.2f}")
print(f"BMI: {hitung_bmi(80, 1.75):.2f}")
print(f"BMI: {hitung_bmi(65, 1.65):.2f}")

Keuntungan:

  • DRY (Don't Repeat Yourself)
  • Logic terpusat
  • Mudah di-test
  • Mudah di-baca

Aturan emas: kalau kamu copy-paste 3 kali, ubah jadi function.


Bagian 2 — Anatomi Function

def nama_function(parameter1, parameter2):
    """Docstring — penjelasan singkat function ini."""
    # Body
    hasil = parameter1 + parameter2
    return hasil
  • def = keyword untuk define function
  • nama_function = nama (snake_case)
  • parameter1, parameter2 = input
  • """...""" = docstring (opsional tapi disarankan)
  • return = output

Calling Function

hasil = nama_function(5, 3)   # 8
print(hasil)

Function Tanpa Return

def greet(nama):
    print(f"Halo, {nama}!")

greet("Yazid")    # print "Halo, Yazid!"
hasil = greet("Ani")   # hasil = None

Function tanpa return otomatis return None.


Bagian 3 — Parameter & Argument

Parameter = variabel di definisi function. Argument = nilai yang dikirim saat memanggil.

def tambah(a, b):    # a, b = parameter
    return a + b

tambah(5, 3)         # 5, 3 = argument

Positional Argument

Berdasarkan urutan:

def info(nama, umur, kota):
    print(f"{nama}, {umur} tahun, dari {kota}")

info("Budi", 25, "Bandung")   # urut: nama, umur, kota

Keyword Argument

Sebutkan nama parameter:

info(nama="Budi", umur=25, kota="Bandung")

# Bisa diacak urutannya
info(kota="Bandung", umur=25, nama="Budi")

# Bisa campur, tapi positional dulu
info("Budi", kota="Bandung", umur=25)

Default Value

def greet(nama, salam="Halo"):
    print(f"{salam}, {nama}!")

greet("Budi")              # Halo, Budi!
greet("Ani", "Hi")         # Hi, Ani!
greet("Cici", salam="Hey") # Hey, Cici!

Hati-hati: default value untuk mutable object (list, dict) bisa jadi bug. Akan dibahas di Common Pitfalls.


Bagian 4 — *args dan **kwargs

Untuk function yang menerima jumlah argument bervariasi.

*args — Banyak Positional Argument

def jumlah(*angka):
    return sum(angka)

jumlah(1, 2, 3)              # 6
jumlah(1, 2, 3, 4, 5)        # 15
jumlah()                     # 0

*angka mengumpulkan semua argument jadi tuple.

**kwargs — Banyak Keyword Argument

def info(**data):
    for key, value in data.items():
        print(f"{key}: {value}")

info(nama="Budi", umur=25, kota="Bandung")
# nama: Budi
# umur: 25
# kota: Bandung

**data mengumpulkan semua keyword argument jadi dict.

Kombinasi

def fleksibel(a, b, *args, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"kwargs={kwargs}")

fleksibel(1, 2, 3, 4, 5, x=10, y=20)
# a=1, b=2
# args=(3, 4, 5)
# kwargs={'x': 10, 'y': 20}

Urutan parameter:

def f(positional, *args, default=value, **kwargs):

Contoh Praktis: Function Wrapper

def log_call(func_name, *args, **kwargs):
    print(f"Calling {func_name}")
    print(f"  args: {args}")
    print(f"  kwargs: {kwargs}")

log_call("hitung_bmi", 70, 1.7, unit="metric")

Bagian 5 — Return Multiple Values

Python bisa return banyak nilai sekaligus (sebenarnya jadi tuple).

def stats(angka):
    return min(angka), max(angka), sum(angka) / len(angka)

minimum, maksimum, rata = stats([1, 2, 3, 4, 5])
print(minimum, maksimum, rata)   # 1 5 3.0

# Atau ambil sebagai tuple
hasil = stats([1, 2, 3, 4, 5])
print(hasil)                      # (1, 5, 3.0)

Skip Return Value yang Tidak Dipakai

_, _, rata = stats([1, 2, 3])  # cuma butuh rata

_ adalah konvensi untuk "tidak dipakai".


Bagian 6 — Scope (Cakupan Variabel)

Diagram LEGB

Cara Membaca Diagram

Python cari variabel berurutan dari Local → Enclosing → Global → Built-in. Edge solid menuju "Ketemu" (pakai value). Edge dashed menuju level berikutnya kalau tidak ditemukan. Kalau semua level kosong, NameError.

Walkthrough Step-by-Step

  1. Python ketemu nama variabel di kode (misal x).
  2. L (Local) — cek di scope function saat ini. Ada? Pakai. Tidak? Lanjut.
  3. E (Enclosing) — cek di function pembungkus (kalau nested). Ada? Pakai.
  4. G (Global) — cek di module level (top of file). Ada? Pakai.
  5. B (Built-in) — cek di built-in Python (print, len, dll). Ada? Pakai.
  6. Kalau tetap tidak ketemu → NameError: name 'x' is not defined.

Analogi Sehari-hari

Cari kunci motor di rumah. Pertama cek kantong celana (local). Tidak ada? Cek tas yang lagi dibawa (enclosing). Tidak ada? Cek rak kunci di rumah (global). Tidak ada? Cek tempat umum / pos satpam (built-in). Begitu ketemu, berhenti cari. Kalau semua sudah dicek dan tidak ada → kunci hilang (NameError).

Diagram statis Mermaid sebagai fallback:

flowchart TD
    A[🔍 Cari variabel x] --> L{Local scope?}
    L -->|Ada| L1[✅ Pakai Local]
    L -->|Tidak| E{Enclosing?}
    E -->|Ada| E1[✅ Pakai Enclosing]
    E -->|Tidak| G{Global?}
    G -->|Ada| G1[✅ Pakai Global]
    G -->|Tidak| B{Built-in?}
    B -->|Ada| B1[✅ Pakai Built-in]
    B -->|Tidak| X[❌ NameError]

Analogi: Python cari variabel kayak kamu cari kunci motor — kantong celana (local) → tas (enclosing) → rak rumah (global) → tempat umum (built-in). Begitu ketemu, berhenti cari.

Local Scope

def tambah(a, b):
    hasil = a + b      # variabel local
    return hasil

print(tambah(2, 3))    # 5
print(hasil)           # NameError! hasil hanya ada di dalam function

Global Scope

PI = 3.14159           # global

def luas_lingkaran(r):
    return PI * r ** 2  # bisa baca global

print(luas_lingkaran(5))

Modifikasi Global (Hindari!)

counter = 0

def increment():
    global counter      # explicit declare
    counter += 1

increment()
increment()
print(counter)         # 2

Best practice: HINDARI modifikasi global. Pakai parameter dan return value. Global state = source of bugs.

LEGB Rule

Python cari variabel dengan urutan:

  1. Local — di dalam function saat ini
  2. Enclosing — function pembungkus
  3. Global — module level
  4. Built-in — built-in Python (print, len, dll)
x = "global"

def luar():
    x = "enclosing"
    
    def dalam():
        x = "local"
        print(x)        # local
    
    dalam()
    print(x)            # enclosing

luar()
print(x)                # global

Bagian 7 — Lambda (Anonymous Function)

Function singkat tanpa nama.

# Function biasa
def kuadrat(x):
    return x ** 2

# Lambda equivalent
kuadrat = lambda x: x ** 2

print(kuadrat(5))    # 25

Kapan Pakai Lambda?

Saat butuh function kecil sekali pakai, biasanya sebagai argument:

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

# Sort berdasarkan elemen ke-2
sorted_data = sorted(data, key=lambda item: item[1])
print(sorted_data)   # [(3, 'a'), (1, 'b'), (2, 'c')]
nilai = [85, 60, 75, 90]

# Filter nilai >= 70
lulus = filter(lambda n: n >= 70, nilai)
print(list(lulus))   # [85, 75, 90]

# Mapping (kuadratkan)
kuadrat = map(lambda n: n ** 2, nilai)
print(list(kuadrat)) # [7225, 3600, 5625, 8100]

Aturan: kalau lambda sudah lebih dari 1 expression sederhana, pakai def biasa. Lambda untuk one-liner.


Bagian 8 — Type Hints (Penting di AI/Industry)

Python dinamis (tidak strict types), tapi kamu bisa kasih petunjuk tipe:

def hitung_bmi(berat: float, tinggi: float) -> float:
    return berat / (tinggi ** 2)

def greet(nama: str, salam: str = "Halo") -> None:
    print(f"{salam}, {nama}!")

def get_users() -> list[dict]:
    return [{"id": 1, "name": "Budi"}]

Manfaat:

  • IDE autocomplete lebih baik
  • Bug lebih cepat ketahuan
  • Code lebih self-documenting
  • Standar di proyek profesional

Wajib pakai type hints. PyTorch, FastAPI, Pydantic, semua framework modern pakai. Kebiasaan ini akan membuatmu menonjol di bootcamp.


Bagian 9 — Common Pitfalls

Pitfall 1: Mutable Default Argument

# ❌ BAHAYA
def add_item(item, list_=[]):    # default list di-share antar call!
    list_.append(item)
    return list_

print(add_item("a"))   # ["a"]
print(add_item("b"))   # ["a", "b"]  ← bug!
# ✅ BENAR
def add_item(item, list_=None):
    if list_ is None:
        list_ = []
    list_.append(item)
    return list_

Pitfall 2: Lupa Return

def kuadrat(x):
    x ** 2     # forget to return!

hasil = kuadrat(5)
print(hasil)   # None — bug!

Pitfall 3: Modifikasi Argument

def tambah_item(lst, item):
    lst.append(item)    # modifikasi list yang dikirim!
    return lst

original = [1, 2, 3]
tambah_item(original, 4)
print(original)   # [1, 2, 3, 4]  ← terjadi side effect

Best practice: function tidak modifikasi argument. Buat copy dulu kalau perlu.

Pitfall 4: Function Terlalu Panjang

# ❌ Function 100 baris yang ngapain banyak hal
def process_user(...):
    # validate
    # parse
    # save to db
    # send email
    # log
    # ...
# ✅ Pecah jadi function-function kecil
def validate_user(...): ...
def parse_user(...): ...
def save_user(...): ...
def notify_user(...): ...

def process_user(data):
    user = parse_user(data)
    if not validate_user(user):
        return None
    save_user(user)
    notify_user(user)
    return user

Single Responsibility Principle: satu function, satu tugas. Aturan dari Robert Martin's "Clean Code".


Bagian 10 — Higher-Order Functions

Diagram: Function Sebagai Bahan

flowchart LR
    F1[⚙️ kuadrat] -->|jadi argument| H[⚙️ apply]
    V[📥 value: 7] --> H
    H --> O[📤 49]

Analogi: higher-order function = mesin yang menerima alat sebagai bagian inputnya. Bayangin mesin bor universal yang bisa pasang berbagai mata bor — mata bor itulah function yang di-pass.

Function yang menerima atau mengembalikan function. Penting di functional programming, banyak dipakai di ML.

Function Sebagai Argument

def apply(func, value):
    return func(value)

apply(len, "hello")             # 5
apply(lambda x: x ** 2, 7)      # 49

Function Sebagai Return Value

def buat_pengali(n):
    def pengali(x):
        return x * n
    return pengali

kali_2 = buat_pengali(2)
kali_5 = buat_pengali(5)

print(kali_2(10))   # 20
print(kali_5(10))   # 50

Ini disebut closure. Akan dibahas detail di file 09 (advanced).


Common Mistakes & FAQ

❌ Mistake 1: Lupa parantheses saat call

def hello():
    print("hi")

hello       # ❌ ini reference ke function, tidak panggil
hello()     # ✅ panggil function

❌ Mistake 2: Variable shadowing

sum = 10              # ❌ override built-in `sum`
print(sum([1, 2, 3])) # TypeError: 'int' object is not callable

❌ Mistake 3: Bingung pass-by-reference vs value

def reset(x):
    x = []           # ini cuma rebind local

original = [1, 2, 3]
reset(original)
print(original)      # [1, 2, 3] — tidak berubah!

# Tapi modifikasi in-place beda:
def clear_inplace(x):
    x.clear()        # ini modify object yang sama

clear_inplace(original)
print(original)      # []

Python pass reference. Kalau rebind dalam function, original tidak berubah. Kalau modify in-place, berubah.

❌ Mistake 4: Return di tengah loop tanpa exit

def find(data, target):
    for x in data:
        if x == target:
            return True
        return False    # ❌ return setelah iter pertama!
    
# Fix: pindah return False keluar loop
def find(data, target):
    for x in data:
        if x == target:
            return True
    return False

FAQ

Q: Function harus ada return? A: Tidak wajib. Tanpa return, otomatis return None. Tapi kalau function bertujuan komputasi (bukan side effect), wajib return.

Q: Berapa parameter ideal? A: Aturan umum: ≤ 3-4 parameter. Lebih dari itu, kemungkinan function terlalu banyak tugas → split, atau pakai dict/dataclass untuk grouping.

Q: Lambda bisa multi-line? A: Tidak. Lambda hanya untuk satu expression. Multi-line → pakai def.

Q: Apa beda lambda dan def? A: Lambda = anonymous, satu expression, return implicit. def = bisa multi-statement, ada nama, lebih mudah debug. Pakai def 95% kasus.

**Q: Kapan pakai *args vs kwargs? A:

  • *args = positional argument bervariasi (min(1, 2, 3))
  • **kwargs = keyword argument bervariasi (dict(a=1, b=2))
  • Bisa kombinasi: def f(*args, **kwargs) untuk fleksibilitas maksimum (sering di decorator).

Cek Pemahaman

  • Bisa bedakan parameter dan argument?
  • Tahu kapan pakai *args vs **kwargs?
  • Bisa return multiple values?
  • Paham LEGB scope rule?
  • Tahu kapan pakai lambda vs def?
  • Bisa pakai type hints di function?
  • Tahu mutable default argument pitfall?

Challenge 2.3

Challenge 1 — Function Refactoring

Refactor kode ini jadi function:

# Hitung diskon untuk 3 produk
harga = 100000
diskon_persen = 20
diskon = harga * diskon_persen / 100
final = harga - diskon
print(f"Final: {final}")

harga = 250000
diskon_persen = 15
diskon = harga * diskon_persen / 100
final = harga - diskon
print(f"Final: {final}")

# (3 produk lagi serupa)

Bikin hitung_diskon(harga, diskon_persen) yang return final price. Pakai type hints.

Challenge 2 — Calculator Function-Based

Refactor calculator dari challenge sebelumnya jadi pakai function:

  • tambah(a, b), kurang(a, b), kali(a, b), bagi(a, b)
  • 1 function calculate(op, a, b) yang panggil function di atas
  • Validasi: kalau op tidak valid, return None

Challenge 3 — *args Practice

Bikin function statistik(*nilai) yang return tuple (min, max, avg, count).

print(statistik(85, 60, 75, 90, 55))
# (55, 90, 73.0, 5)

Challenge 4 — **kwargs Practice

Bikin function buat_user(**data) yang validasi:

  • Wajib ada key: nama, email
  • Optional: umur, kota
  • Kalau tidak ada wajib, return error message
  • Kalau valid, return dict user

Challenge 5 — Lambda + sorted

Diberikan list of dict:

users = [
    {"nama": "Budi", "umur": 25, "skor": 85},
    {"nama": "Ani", "umur": 30, "skor": 92},
    {"nama": "Cici", "umur": 22, "skor": 78},
]

Pakai lambda + sorted untuk:

  1. Sort berdasarkan umur
  2. Sort berdasarkan skor descending
  3. Sort berdasarkan panjang nama

Challenge 6 — Higher-Order Function

Bikin function apply_to_all(func, items) yang aplikasikan func ke setiap item dan return list hasilnya.

print(apply_to_all(lambda x: x ** 2, [1, 2, 3, 4]))
# [1, 4, 9, 16]

Challenge 7 — Function Sebagai Pipeline

Bikin pipeline yang:

  1. bersihkan(text) — lowercase, strip whitespace
  2. hitung_kata(text) — return jumlah kata
  3. kata_unik(text) — return list kata unik (set lalu list)

Lalu bikin process(text, *funcs) yang aplikasikan semua function berturut-turut.

text = "  Hello World HELLO Python WORLD  "
result = process(text, bersihkan, kata_unik, len)
print(result)   # 3 (hello, world, python)

Hint: pakai for loop untuk apply functions.

Challenge 8 — Type Hints

Tambahkan type hints lengkap ke function-function di challenge sebelumnya. Run mypy (install dengan pip install mypy) untuk cek.


Selanjutnya: 04-data-structures.md — list, dict, set, tuple plus comprehension. Inti manipulasi data di Python.