Lampiran B — Lembar Contekan

Bayangkan catatan kecil yang ditempel di dinding bengkel: bukan untuk belajar dari nol, melainkan untuk mengingatkan “tadi sintaksnya bagaimana, ya?”. Itulah lembar contekan (cheat sheet). Lampiran ini memberi tiga lembar, satu per tingkat buku: Basic, Intermediate, dan Advanced. Tiap butir menyebut bab asalnya, jadi bila satu baris belum jelas, Anda tahu ke mana harus membaca.

Semua skrip pemeriksa lampiran ini ada di folder kode/bab72/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).

Cara membaca lembar

Setiap baris kode di lembar dijalankan sungguhan oleh sebuah skrip. Angka di belakang tanda -> bukan diketik tangan, melainkan hasil Python saat skrip dijalankan. Bila sebuah baris salah (misalnya fungsi berganti nama pada versi baru), skrip berhenti dan lembar tidak jadi dibuat.

%%{init: {"flowchart": {"diagramPadding": 40}}}%%
flowchart TD
    A["Isi lembar:<br/>kode + komentar"] --> B["Dijalankan satu per satu<br/>di folder sementara"]
    B --> C["Hasil asli<br/>(setelah tanda ->)"]
    C --> D["Teks lembar di buku"]
    C --> E["PDF A4 siap cetak"]

Dari isi lembar sampai teks di buku dan PDF.

Tanda Artinya
(tanpa tanda) Baris dijalankan sungguhan; hasil setelah -> dibuat Python. Angka numpy ditulis tanpa pembungkus np.float64(...) supaya ringkas.
[cek] Opsi perintah atau fungsi diperiksa ada (lewat --help atau inspect), tetapi tidak dijalankan penuh.
[ref] Referensi: tidak dijalankan di sini. Alasannya tertulis di komentarnya (misalnya Docker tidak terpasang, butuh internet, atau butuh unduhan besar).
$ Perintah untuk terminal, bukan kode Python.
PeringatanAWAS

Lembar ini dibuat di komputer penulis (Windows 11, Python 3.14, kartu grafis NVIDIA). Baris yang bergantung pada perangkat, misalnya cupy (butuh GPU NVIDIA) atau torch pada 'cuda', akan berbeda atau gagal di komputer tanpa perangkat itu. Angka seperti jarak 1563 m atau akurasi 0,93 berasal dari versi pustaka yang terpasang di sini; di komputer Anda bisa bergeser sedikit.

Lembar 1: Basic (Bab 1-15)

PDF siap cetak (A4, satu halaman). Isinya: terminal, variabel dan tipe, operator, kondisi, perulangan, struktur data, fungsi, modul, error, file, CSV/JSON, Excel dan PDF.

=== Lembar contekan Basic (Bab 1-15) ===

## Terminal dan Python  [Bab 3-4]
$ cd folder_proyek  # pindah folder [ref]
$ python halo.py  # jalankan skrip [ref]
$ python --version  # cek versi
print('Halo', 3, sep=', ', end='!')  # cetak -> Halo, 3!
# komentar: catatan untuk manusia

## Variabel dan tipe data  [Bab 5]
n, h, ok = 42, 1.5, True  # int, float, bool
nama = 'Jati'  # str (teks)
type(h).__name__  # nama tipe -> 'float'
int('42') + 1  # teks ke angka -> 43
float('2.5')  # teks ke desimal -> 2.5
str(7) + ' kg'  # angka ke teks -> '7 kg'
bool(0), bool('a')  # kosong/nol = False -> (False, True)
f'{3.14159:.2f}'  # dua desimal -> '3.14'
f'{1234567:,}'  # pemisah ribuan -> '1,234,567'
x = None  # tidak ada nilai

## Input  [Bab 5-6]
umur = int(input('Umur? '))  # input() selalu teks!
umur + 1  # setelah diubah ke int -> 43

## Operator  [Bab 6]
7 / 2, 7 // 2, 7 % 2  # bagi, bulat, sisa -> (3.5, 3, 1)
2 ** 10  # pangkat -> 1024
divmod(1000, 30)  # (hasil bagi, sisa) -> (33, 10)
round(3.14159, 2)  # pembulatan -> 3.14
1 < 2 < 3  # perbandingan berantai -> True
(1 > 2) or not (3 > 4)  # and, or, not -> True
'at' in 'Jati'  # ada di dalam? -> True
n = 5  # tambah lalu simpan
n += 2

## Kondisi  [Bab 7]
n = 520  # jika / atau jika / selain itu
if n >= 500:
    s = 'siap'
elif n >= 400:
    s = 'hampir'
else:
    s = 'belum'
s  # -> 'siap'
'siap' if n >= 500 else 'belum'  # satu baris -> 'siap'
jenis = 'sapi'  # pencocokan pola
match jenis:
    case 'sapi' | 'kambing':
        k = 'ruminansia'
    case _:
        k = 'lain'
k  # -> 'ruminansia'

## Perulangan  [Bab 8]
total = 0  # penampung
for b in [3, 5, 7]:
    total += b
total  # -> 15
list(range(0, 10, 3))  # awal, akhir (tak ikut), langkah -> [0, 3, 6, 9]
list(enumerate('ab', 1))  # nomor + isi -> [(1, 'a'), (2, 'b')]
g = 1800  # while + break
while True:
    g += 150
    if g >= 2400:
        break
g  # -> 2400
[i for i in range(5) if i % 2 == 0]  # list comprehension -> [0, 2, 4]

## List dan slicing  [Bab 9]
xs = [3, 1, 2]  # list
xs.append(4)  # tambah di akhir
xs[0], xs[-1]  # pertama, terakhir -> (3, 4)
xs[1:3]  # irisan [awal:akhir) -> [1, 2]
sorted(xs)  # urut (asli tetap) -> [1, 2, 3, 4]
xs.sort(reverse=True)  # urutkan di tempat
xs[::-1]  # dibalik -> [1, 2, 3, 4]
len(xs), sum(xs), max(xs)  # banyak, jumlah, terbesar -> (4, 10, 4)

## Dict, tuple, set  [Bab 9]
d = {'Jati': 32.5, 'Mahoni': 28.0}  # dict: kunci -> nilai
d['Meranti'] = 45.2  # tambah/ubah
d.get('Sengon', 0)  # nilai bawaan bila tak ada -> 0
{k: v for k, v in d.items() if v > 30}
    # dict comprehension -> {'Jati': 32.5, 'Meranti': 45.2}
lintang, bujur = (-6.6, 106.8)  # tuple: tak bisa diubah
{1, 2, 3} & {2, 3, 4}  # set: irisan -> {2, 3}
{1, 2} | {2, 3}  # set: gabungan -> {1, 2, 3}

## Fungsi  [Bab 10]
def luas(r, pi=3.14):  # nilai bawaan pi
    return pi * r * r
luas(2)  # pakai nilai bawaan -> 12.56
luas(r=2, pi=3)  # argumen bernama -> 12
kali = lambda a, b: a * b  # fungsi mini
kali(3, 4)  # -> 12
sorted(['bb', 'a'], key=len)  # kunci urutan -> ['a', 'bb']
# tanpa return, hasilnya None

## Modul  [Bab 11]
import math  # import modul
round(math.pi, 3)  # -> 3.142
from datetime import date, timedelta  # ambil sebagian
date(2026, 1, 15) + timedelta(days=270)
    # hitung tanggal -> datetime.date(2026, 10, 12)
date(2026, 10, 12).strftime('%d-%m-%Y')  # format tanggal -> '12-10-2026'
import random  # acak berbenih
random.seed(7)
random.sample(range(1, 13), 3)  # -> [6, 3, 7]

## Error  [Bab 12]
def ke_angka(t):  # tangkap error
    try:
        return float(t)
    except ValueError:
        return None
ke_angka('12.5'), ke_angka('abc')  # aman dari teks salah -> (12.5, None)
try:  # raise, except, finally
    raise ValueError('rusak')
except (ValueError, KeyError) as e:
    pesan = str(e)
finally:
    pass
pesan  # -> 'rusak'
# error buatan sendiri
class DataError(Exception):
    pass
assert 1 + 1 == 2, 'harus 2'  # periksa asumsi
import logging  # catat peringatan
logging.warning('pH aneh')

## File dan Path  [Bab 13]
from pathlib import Path  # jalur berkas
p = Path('a.txt')
# w timpa, a tambah, r baca
with open(p, 'w', encoding='utf-8') as f:
    f.write('1\n2\n')
# baca per baris
with open(p, encoding='utf-8') as f:
    total = sum(int(b) for b in f)
total  # -> 3
p.read_text(encoding='utf-8')  # baca semua -> '1\n2\n'
q = Path('data/b.txt')  # bagian nama jalur
q.name, q.suffix, q.stem  # -> ('b.txt', '.txt', 'b')
p.exists()  # ada berkasnya? -> True

## CSV dan JSON  [Bab 14]
import csv, json  # modul bawaan
# newline='' wajib
with open('t.csv', 'w', newline='',
          encoding='utf-8') as f:
    w = csv.DictWriter(f, ['k', 'n'])
    w.writeheader()
    w.writerow({'k': 'A', 'n': 800})
# semua nilai jadi teks!
with open('t.csv', newline='',
          encoding='utf-8') as f:
    baris = list(csv.DictReader(f))
baris  # -> [{'k': 'A', 'n': '800'}]
json.dumps({'a': 'Ø'}, ensure_ascii=False)
    # objek ke teks JSON -> '{"a": "Ø"}'
json.loads('{"a": [1, 2]}')['a']  # teks JSON ke objek -> [1, 2]
# JANGAN pickle.load dari sumber asing!

## Excel dan PDF  [Bab 15]
import openpyxl as xl  # pustaka Excel
wb = xl.Workbook()  # tulis .xlsx
ws = wb.active
ws.append(['kolam', 'kg'])
ws.append(['A', 432.4])
wb.save('a.xlsx')
wb2 = xl.load_workbook('a.xlsx')  # baca sel
wb2.active['B2'].value  # -> 432.4
# tulis PDF
from reportlab.pdfgen import canvas
c = canvas.Canvas('a.pdf')
c.drawString(72, 750, 'Laporan')
c.save()
from pypdf import PdfReader  # baca PDF
len(PdfReader('a.pdf').pages)  # -> 1

Bukti: 14 kelompok; 80 baris dijalankan sungguhan,
0 baris [cek] (opsi/API diperiksa ada), 2 baris [ref] (tidak dijalankan).

Lembar 2: Intermediate (Bab 16-43)

PDF siap cetak (A4, satu halaman). Isinya: OOP, lingkungan, pytest, ruff, git, API, regex, pandas, SQL dan SQLAlchemy, statistik dan statsmodels, generator, decorator, type hint, Pydantic, async, FastAPI dan Flask, GeoPandas dan Rasterio, ditambah sedikit automasi.

=== Lembar contekan Intermediate (Bab 16-43) ===

## OOP: kelas dan pewarisan  [Bab 16]
class Pohon:  # cetakan objek
    def __init__(self, jenis, d):
        self.jenis, self.d = jenis, d
    def __str__(self):
        return f'{self.jenis} {self.d}'
str(Pohon('Jati', 32.5))  # buat objek -> 'Jati 32.5'
class Besar(Pohon):  # pewarisan
    def __str__(self):
        return 'BESAR ' + super().__str__()
str(Besar('Jati', 45))  # -> 'BESAR Jati 45'
class Kolam:  # properti: tanpa ()
    @property
    def ph(self):
        return 7.0
Kolam().ph  # -> 7.0
# dataclass
from dataclasses import dataclass
@dataclass
class Ikan:
    nama: str
Ikan('lele')  # __repr__ otomatis -> Ikan(nama='lele')

## Lingkungan, pytest, ruff, logging  [Bab 17-19]
$ python -m venv .venv  # kamar Python sendiri [cek]
$ .venv\Scripts\activate  # Linux: source .venv/bin/activate [ref]
$ uv add pandas  # tambah dependensi [cek]
$ uv run pytest  # jalan di lingkungan proyek [cek]
$ uv sync  # samakan dengan uv.lock [cek]
$ pytest -x -k nama  # berhenti di gagal pertama [cek]
import pytest  # tes_*.py
P = [(1, 1), (2, 4)]
@pytest.mark.parametrize('n,h', P)
def test_k(n, h):
    assert n * n == h
def test_e():
    with pytest.raises(ZeroDivisionError):
        1 / 0  # -> 3 passed
$ ruff check . --fix  # periksa gaya + perbaiki [cek]
$ ruff format .  # rapikan kode [cek]
import logging, sys  # log berjenjang
logging.basicConfig(level=20, force=True,
                    stream=sys.stdout)
logging.info('mulai')  # -> INFO:root:mulai

## Git dan GitHub  [Bab 20]
$ git init -b main  # mulai repo (a.txt sudah ada)
$ git add -A  # siapkan perubahan
$ git commit -m 'pesan'  # simpan satu titik waktu
$ git switch -c fitur  # cabang baru
$ git switch main  # kembali
$ git merge fitur  # gabung cabang
$ git log --oneline  # riwayat singkat
$ git push -u origin main  # kirim ke GitHub [cek]
# .gitignore: .venv/ .env (rahasia!)

## API dengan requests  [Bab 21]
import requests  # url = alamat API (di sini: server lokal)
# selalu beri timeout
r = requests.get(url, params={'q': 1},
                 timeout=10)
r.status_code, r.json()  # -> (200, {'n': 3})
# tak ada = 404
r2 = requests.get(url + '/x', timeout=10)
r2.status_code  # -> 404
r.raise_for_status()  # error bila 4xx/5xx

## Regex  [Bab 22]
import re
t = 'JT-0042, MR-0107, xx-12'
re.findall(r'\b[A-Z]{2}-\d{4}\b', t)  # semua kode -> ['JT-0042', 'MR-0107']
re.search(r'd=(\d+)', 'd=45 cm')[1]  # ambil grup () -> '45'
re.sub(r'\s+', ' ', 'a   b  c')  # ganti pola -> 'a b c'
# grup bernama
m = re.search(r'(?P<k>\w+)=(?P<n>\d+)', 't=14')
m.groupdict()  # -> {'k': 't', 'n': '14'}
re.fullmatch(r'\d{3}', '123') is not None  # cocok seluruhnya? -> True

## numpy dan pandas  [Bab 23]
import numpy as np
a = np.array([480, 500, 510])
a[a > 490]  # topeng boolean -> array([500, 510])
import pandas as pd  # tabel
df = pd.DataFrame({'k': list('ABC'),
                   'j': ['lele', 'nila', 'lele'],
                   'n': [800, 1250, 600]})
df.shape  # -> (3, 3)
df['kg'] = df.n * 0.5  # kolom baru
df[df.kg > 300].k.tolist()  # saring baris -> ['A', 'B']
df.groupby('j')['kg'].sum().to_dict()
    # ringkas per kelompok -> {'lele': 700.0, 'nila': 625.0}
df.sort_values('kg').k.iloc[-1]  # terbesar -> 'B'
# gabung tabel
h = pd.DataFrame({'j': ['lele', 'nila'],
                  'rp': [3, 2]})
df.merge(h, on='j').shape  # -> (3, 5)
df.to_csv('a.csv', index=False)  # simpan CSV
pd.read_csv('a.csv').shape  # baca CSV -> (3, 4)

## SQL: sqlite3 dan SQLAlchemy  [Bab 24]
import sqlite3  # tanda ? anti injeksi
c = sqlite3.connect(':memory:')
c.execute('CREATE TABLE p (j TEXT, d REAL)')
r = [('Jati', 32.5), ('Mahoni', 28.0)]
c.executemany('INSERT INTO p VALUES (?,?)', r)
# kueri berparameter
q = 'SELECT j FROM p WHERE d > ?'
c.execute(q, (30,)).fetchall()  # -> [('Jati',)]
# ORM: kelas = tabel
import sqlalchemy as sa
from sqlalchemy.orm import (DeclarativeBase,
    Mapped, Session, mapped_column)
class B(DeclarativeBase):
    pass
class Sektor(B):
    __tablename__ = 's'
    id: Mapped[int] = mapped_column(
        primary_key=True)
    nama: Mapped[str]
e = sa.create_engine('sqlite://')
B.metadata.create_all(e)
with Session(e) as s:
    s.add(Sektor(nama='Ikan'))
    s.commit()
    n = len(s.scalars(sa.select(Sektor)).all())
n  # -> 1
with e.connect() as k:  # SQL mentah
    q = sa.text('SELECT COUNT(*) FROM s')
    jml = k.execute(q).scalar()
jml  # -> 1

## Statistik dan statsmodels  [Bab 26-27]
# uji t dua kelompok
from scipy import stats
a, b = [30, 32, 31, 29], [35, 36, 34, 37]
stats.ttest_ind(a, b).pvalue < 0.05  # -> True
round(stats.norm.cdf(30, 25, 5), 3)  # peluang normal -> 0.841
round(stats.pearsonr(a, b)[0], 2)  # korelasi -> -0.4
# OLS: [konstanta, lereng]
import statsmodels.api as sm
X = sm.add_constant([1, 2, 3, 4, 5])
y = [2.1, 3.9, 6.2, 7.8, 10.1]
res = sm.OLS(y, X).fit()
res.params.round(2).tolist()  # -> [0.05, 1.99]
round(res.rsquared, 3)  # R kuadrat -> 0.997
# logistik: rasio odds
import numpy as np, pandas as pd
import statsmodels.formula.api as smf
d = pd.DataFrame({'x': range(1, 9),
                  'y': [0, 0, 1, 0, 1, 0, 1, 1]})
r = smf.logit('y ~ x', d).fit(disp=0)
round(float(np.exp(r.params['x'])), 2)  # -> 1.81

## Generator, decorator, closure  [Bab 30-31]
def genap(n):  # yield = satu per satu
    for i in range(2, n + 1, 2):
        yield i
list(genap(10))  # -> [2, 4, 6, 8, 10]
# ekspresi generator
g = (i * i for i in range(3))
next(g), next(g)  # -> (0, 1)
def pengali(k):  # closure mengingat k
    return lambda x: x * k
pengali(3)(7)  # -> 21
import functools  # decorator + wraps
def hitung(f):
    @functools.wraps(f)
    def b(*a):
        b.n += 1
        return f(*a)
    b.n = 0
    return b
@hitung
def tambah(a, b):
    return a + b
tambah(1, 2), tambah.n  # n = jumlah panggilan -> (3, 1)

## Type hint dan Pydantic  [Bab 33-34]
# int atau None
def cari(xs: list[str], x: str) -> int | None:
    return xs.index(x) if x in xs else None
cari(['a', 'b'], 'b'), cari(['a'], 'z')  # -> (1, None)
$ mypy --strict app.py  # periksa tipe sebelum jalan [cek]
# model = penjaga pintu
from pydantic import BaseModel, Field
class Pohon(BaseModel):
    jenis: str
    d: float = Field(gt=0)
Pohon(jenis='Jati', d='32.5')
    # '32.5' diubah ke angka -> Pohon(jenis='Jati', d=32.5)
try:  # galat rapi
    Pohon(jenis='Jati', d=-5)
except Exception as e:
    pesan = e.errors()[0]['msg']
pesan  # -> 'Input should be greater than 0'
# pengaturan (bisa dari env)
from pydantic_settings import BaseSettings
class Atur(BaseSettings):
    folder: str = 'hasil'
Atur().folder  # -> 'hasil'

## Async dan thread  [Bab 35]
import asyncio  # serempak
async def f(n):
    await asyncio.sleep(0.01)
    return n * 2
async def main():
    return await asyncio.gather(f(1), f(2))
asyncio.run(main())  # -> [2, 4]
async def g():  # TaskGroup
    async with asyncio.TaskGroup() as tg:
        t = tg.create_task(f(1))
    return t.result()
asyncio.run(g())  # -> 2
# thread untuk tunggu I/O
import concurrent.futures as cf
with cf.ThreadPoolExecutor(2) as p:
    hasil = list(p.map(abs, [-1, -2]))
hasil  # -> [1, 2]

## Web: FastAPI dan Flask  [Bab 36]
# jalur + parameter
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get('/kolam/{n}')
def kolam(n: str, ph: float = 7.0):
    return {'n': n, 'ph': ph}
c = TestClient(app)
c.get('/kolam/A?ph=6.8').json()  # -> {'n': 'A', 'ph': 6.8}
c.get('/kolam/A?ph=x').status_code  # tipe salah = 422 -> 422
$ uvicorn app:app --reload  # jalankan server [cek]
# Flask: yang ringkas
from flask import Flask
w = Flask(__name__)
@w.get('/halo')
def halo():
    return {'p': 'hai'}
w.test_client().get('/halo').json  # -> {'p': 'hai'}

## GeoPandas dan Rasterio  [Bab 38-39]
import geopandas as gpd  # titik + CRS
from shapely.geometry import Point
pts = [Point(110, -7), Point(110.01, -7.01)]
g = gpd.GeoDataFrame({'n': [1, 2]}, geometry=pts,
                     crs='EPSG:4326')
g.crs.to_epsg()  # -> 4326
u = g.to_crs(32749)  # ke meter (UTM 49S)
round(u.geometry[0].distance(u.geometry[1]))  # jarak (m) -> 1563
round(u.buffer(100).area[0])  # luas penyangga (m2) -> 31365
# simpan/baca GeoPackage
g.to_file('a.gpkg')
gpd.read_file('a.gpkg').shape  # -> (2, 2)
# tulis GeoTIFF
import numpy as np, rasterio
from rasterio.transform import from_origin
a = np.arange(4, dtype='float32').reshape(2, 2)
t = from_origin(110, -7, 0.1, 0.1)
with rasterio.open('a.tif', 'w', driver='GTiff',
        height=2, width=2, count=1,
        dtype='float32', crs='EPSG:4326',
        transform=t) as d:
    d.write(a, 1)
# baca pita 1 + CRS
with rasterio.open('a.tif') as s:
    b, e = s.read(1), s.crs.to_epsg()
(b.mean().item(), e)  # -> (1.5, 4326)
# NDVI
n, r = np.array([.8, .6]), np.array([.2, .3])
np.round((n - r) / (n + r), 2).tolist()  # -> [0.6, 0.33]

## Context manager dan automasi  [Bab 32, 41-43]
# abaikan satu jenis error
from contextlib import suppress
with suppress(FileNotFoundError):
    open('tidak_ada').close()
# jalankan program lain
import subprocess, sys
cmd = [sys.executable, '-c', 'print(2)']
p = subprocess.run(cmd, text=True,
                   capture_output=True)
p.stdout.strip()  # -> '2'
import argparse  # argumen baris perintah
ap = argparse.ArgumentParser()
ap.add_argument('--n', type=int, default=3)
ap.parse_args(['--n', '5']).n  # -> 5
$ streamlit run app.py  # dasbor di browser [cek]

Bukti: 14 kelompok; 79 baris dijalankan sungguhan,
11 baris [cek] (opsi/API diperiksa ada), 1 baris [ref] (tidak dijalankan).

Lembar 3: Advanced (Bab 44-68)

PDF siap cetak (A4, satu halaman). Isinya: profiling, pengemasan, Python 3.12 sampai 3.14, Docker dan CI (referensi), keamanan, Polars dan DuckDB, STAC dan Dask, scikit-learn, PyTorch dan GPU, YOLO, Optuna dan NSGA-II, SHAP dan MLflow, RAG, agent dan MCP, deskriptor dan bytecode, numba, Cython dan CuPy, serta Celery.

=== Lembar contekan Advanced (Bab 44-68) ===

## Performa dan profiling  [Bab 44]
$ python -m cProfile -o out.prof app.py  # siapa yang lambat? [cek]
$ kernprof -l -v app.py  # profil per baris [cek]
import timeit  # ukur, jangan tebak
tm = timeit.timeit
l = tm('99 in x', 'x = list(range(100))')
s = tm('99 in x', 'x = set(range(100))')
s < l  # -> True
import tracemalloc  # pantau memori
tracemalloc.start()
x = [0] * 100_000
tracemalloc.get_traced_memory()[0] > 10**5  # -> True

## Pengemasan  [Bab 45]
# pyproject.toml (dari Bab 45) [ref]
[build-system]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
name = "pantau-lapangan-contoh"
version = "0.1.0"
[project.scripts]
pantau-lapangan = "pantau_lapangan.cli:main"
$ python -m build  # buat sdist + wheel [cek]
$ twine check dist/*  # periksa metadata [cek]

## Python 3.12 - 3.14  [Bab 47]
# 3.12: generik + type
def pertama[T](xs: list[T]) -> T:
    return xs[0]
type Daftar[T] = list[T]
pertama([3, 1])  # -> 3
# 3.12: kutip sama di f-string
j = ['Jati', 'Mahoni']
f"{", ".join(j)}"  # -> 'Jati, Mahoni'
import copy, datetime  # 3.13: copy.replace
copy.replace(datetime.date(2026, 1, 1), month=6)
    # -> datetime.date(2026, 6, 1)
nama = 'Jati'  # 3.14: t-string
t = t'Pohon {nama}'
type(t).__name__, t.strings  # -> ('Template', ('Pohon ', ''))
# 3.14: acuan maju tanpa kutip
class Simpul:
    def __init__(self, nxt: Simpul | None):
        self.nxt = nxt
Simpul(Simpul(None)).nxt.nxt is None  # -> True
# 3.14: subinterpreter [cek]
from concurrent.futures import (
    InterpreterPoolExecutor)

## Docker dan CI (referensi)  [Bab 48]
# Dockerfile (dari Bab 48) [ref]
FROM python:3.14-slim
WORKDIR /app
USER appuser
EXPOSE 8000
$ docker build -t pantau .  # Docker tak terpasang di sini [ref]
$ docker run -p 8000:8000 pantau  # buka localhost:8000 [ref]
on:  # GitHub Actions (dari Bab 48) [ref]
  push:
jobs:
  uji:
    runs-on: ubuntu-latest

## Keamanan  [Bab 49]
# token acak aman
import hashlib, hmac, secrets
len(secrets.token_hex(16))  # -> 32
hashlib.sha256(b'abc').hexdigest()[:12]  # sidik jari data -> 'ba7816bf8f01'
# kata sandi: scrypt
k = dict(salt=b'garam', n=2**14, r=8, p=1)
h = hashlib.scrypt(b'rahasia', **k)
h2 = hashlib.scrypt(b'rahasia', **k)
hmac.compare_digest(h, h2)  # -> True
# tolak ../ (traversal)
from pathlib import Path
d = Path('data').resolve()
(d / '../rahasia.txt').resolve().is_relative_to(d)  # -> False
import html  # cegah injeksi HTML
html.escape('<script>')  # -> '&lt;script&gt;'
$ pip-audit -r requirements.txt  # cari paket rentan [cek]
$ bandit -r src  # cari pola berbahaya [cek]

## Polars, DuckDB, Parquet  [Bab 50]
import polars as pl  # tabel cepat
d = pl.DataFrame(dict(k=list('aba'), v=[1, 2, 3]))
g = d.group_by('k').agg(pl.col('v').sum())
g.sort('k').to_dicts()  # -> [{'k': 'a', 'v': 4}, {'k': 'b', 'v': 2}]
d.lazy().filter(pl.col('v') > 1).collect().shape
    # rencana malas (lazy) -> (2, 2)
# Parquet: kolom, terkompres
d.write_parquet('a.parquet')
pl.read_parquet('a.parquet').shape  # -> (3, 2)
import duckdb  # SQL pada berkas
q = "SELECT k, SUM(v) FROM 'a.parquet' GROUP BY k"
sorted(duckdb.sql(q).fetchall())  # -> [('a', 4), ('b', 2)]

## STAC, COG, Dask, xarray  [Bab 51, 53]
import pystac  # satu adegan = satu Item
from datetime import datetime, timezone
t = datetime(2026, 1, 1, tzinfo=timezone.utc)
i = pystac.Item('s2-1', None, None, t, {})
i.to_dict()['type']  # -> 'Feature'
from pystac_client import Client  # cari di katalog STAC [cek]
s.read(1, window=Window(2, 2, 3, 3))
    # COG: baca sepotong (butuh jaringan) [ref]
# potongan + hitung malas
import dask.array as da
a = da.ones((1000, 1000), chunks=500)
float(a.sum().compute())  # -> 1000000.0
# larik berlabel
import numpy as np, xarray as xr
a = np.arange(6).reshape(2, 3)
x = xr.DataArray(a, dims=('t', 'k'))
x.isel(t=1).sum().item()  # -> 12

## scikit-learn dan boosting  [Bab 54]
# data tiruan
from sklearn.datasets import make_classification
X, y = make_classification(200, random_state=0)
# validasi silang 5 lipatan
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
m = RandomForestClassifier(50, random_state=0)
s = cross_val_score(m, X, y, cv=5)
round(float(s.mean()), 2)  # -> 0.93
int(m.fit(X, y).feature_importances_.argmax())  # fitur terpenting -> 14
import xgboost as xgb  # boosting
g = xgb.XGBClassifier(n_estimators=20)
g.fit(X, y).score(X, y) > 0.9  # -> True

## PyTorch dan GPU  [Bab 55]
# latih: nol, hitung, mundur, langkah
import torch
x = torch.tensor([[1.], [2.], [3.]])
net = torch.nn.Linear(1, 1)
opt = torch.optim.SGD(net.parameters(), lr=.05)
for _ in range(200):
    opt.zero_grad()
    loss = ((net(x) - 2 * x) ** 2).mean()
    loss.backward()
    opt.step()
round(net.weight.item(), 1)  # -> 2.0
d = 'cuda' if torch.cuda.is_available() else 'cpu'  # GPU bila ada
net.to(d)(x.to(d)).shape  # model + data satu perangkat -> torch.Size([3, 1])

## Computer vision: YOLO  [Bab 56]
# IoU: tumpang tindih kotak
ar = lambda b: (b[2] - b[0]) * (b[3] - b[1])
def iou(a, b):
    w = min(a[2], b[2]) - max(a[0], b[0])
    h = min(a[3], b[3]) - max(a[1], b[1])
    i = max(w, 0) * max(h, 0)
    return i / (ar(a) + ar(b) - i)
round(iou((0, 0, 10, 10), (5, 5, 15, 15)), 3)  # -> 0.143
from ultralytics import YOLO  # deteksi objek [cek]
m = YOLO('yolo26n.pt')  # bobot diunduh [ref]
m.train(data='data.yaml', epochs=50)  # latih (GPU disarankan) [cek]
m.predict('foto.jpg', conf=0.25)  # deteksi [cek]

## Optuna dan NSGA-II  [Bab 57]
import optuna  # x terbaik (benar = 2)
optuna.logging.set_verbosity(40)
def f(t):
    return (t.suggest_float('x', -5, 5) - 2) ** 2
s = optuna.samplers.TPESampler(seed=0)
st = optuna.create_study(sampler=s)
st.optimize(f, 30)
round(st.best_params['x'], 1)  # -> 2.0
# 2 tujuan = front Pareto
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.optimize import minimize
p = get_problem('zdt1')
r = minimize(p, NSGA2(pop_size=20), ('n_gen', 20), seed=1)
r.F.shape[1]  # -> 2

## SHAP dan MLflow  [Bab 58]
# data tiruan
from sklearn.datasets import make_classification
X, y = make_classification(200, random_state=0)
# kontribusi tiap fitur
import numpy as np, shap
from sklearn.ensemble import RandomForestClassifier
m = RandomForestClassifier(20, random_state=0)
e = shap.TreeExplainer(m.fit(X, y))
np.shape(e.shap_values(X[:5]))  # -> (5, 20, 2)
import mlflow  # catat percobaan
mlflow.set_tracking_uri('sqlite:///m.db')
with mlflow.start_run():
    mlflow.log_metric('akurasi', 0.9)
r = mlflow.search_runs(search_all_experiments=True)
len(r)  # -> 1

## RAG, vektor, agent, MCP  [Bab 60, 62]
# tetangga terdekat
import faiss, numpy as np
v = np.random.default_rng(0).random((9, 4))
v = v.astype('float32')
ix = faiss.IndexFlatL2(4)
ix.add(v)
int(ix.search(v[:1], 2)[1][0][0])  # -> 0
import chromadb  # basis data vektor
c = chromadb.Client().create_collection('kolam')
c.add(ids=['1', '2'], documents=['padi', 'ikan'],
      embeddings=[[1, 0], [0, 1]])
q = dict(query_embeddings=[[1, .1]], n_results=1)
c.query(**q)['documents']  # -> [['padi']]
# RAG: konteks masuk prompt
konteks, tanya = 'Kolam A: pH 7.2', 'pH kolam A?'
f'Jawab dari konteks saja.\n{konteks}\n{tanya}'
    # -> 'Jawab dari konteks saja.\nKolam A: pH 7.2\npH ko...
# resp = klien.generate(prompt)
    # butuh kunci [disamarkan]; tidak dijalankan [ref]
# agent: putar sampai selesai/batas
def agen(model, alat, maks=5):
    for _ in range(maks):
        a = model()
        if 'jawab' in a:
            return a['jawab']
        alat[a['alat']](a['arg'])
# model tiruan menjawab
s = iter([{'alat': 't', 'arg': 2}, {'jawab': '5'}])
agen(lambda: next(s), {'t': lambda a: a + 3})  # -> '5'
# server + klien MCP (di memori)
import asyncio
from mcp import Client
from mcp.server import MCPServer
sv = MCPServer('kolam')
@sv.tool()
def biomassa(n: int, g: float) -> float:
    return round(n * g / 1000, 2)
async def main():
    async with Client(sv) as k:
        a = {'n': 800, 'g': 540.5}
        h = await k.call_tool('biomassa', a)
        return h.structured_content
asyncio.run(main())  # -> {'result': 432.4}

## Deskriptor, bytecode, internals  [Bab 64-65]
# deskriptor: penjaga atribut
class Positif:
    def __set_name__(self, o, n):
        self.n = '_' + n
    def __get__(self, o, t=None):
        return getattr(o, self.n)
    def __set__(self, o, v):
        if v <= 0:
            raise ValueError('harus > 0')
        setattr(o, self.n, v)
class Pohon:
    d = Positif()
    def __init__(self, d):
        self.d = d
Pohon(32.5).d  # -> 32.5
type('B', (), {'x': 1}).x  # kelas dibuat saat jalan -> 1
import dis  # bytecode
f = lambda a: a + 1
[i.opname for i in dis.get_instructions(f)][-2:]
    # -> ['BINARY_OP', 'RETURN_VALUE']
import sys  # hitung referensi (x + argumen)
x = []
sys.getrefcount(x)  # -> 2

## numba, Cython, CuPy  [Bab 66]
from numba import njit  # JIT: for secepat C
@njit
def jumlah_kuadrat(n):
    s = 0
    for i in range(n):
        s += i * i
    return s
jumlah_kuadrat(1000)  # -> 332833500
$ cythonize -i ndvi_cy.pyx  # kompilasi .pyx (butuh compiler C) [cek]
# GPU NVIDIA seperti numpy
import cupy as cp
float(cp.asarray([1., 2., 3.]).sum())  # -> 6.0

## Celery (antrean tugas)  [Bab 67]
# mode eager: uji tanpa broker
from celery import Celery
app = Celery('t', broker='memory://',
             backend='cache+memory://')
app.conf.task_always_eager = True
@app.task
def tambah(a, b):
    return a + b
tambah.delay(2, 3).get()  # -> 5
# coba ulang otomatis
@app.task(bind=True, max_retries=3)
def rapuh(self, x):
    return x
rapuh.delay(7).get()  # -> 7
$ celery -A tugas worker --concurrency=3  # tiga proses worker [cek]
redis.Redis.from_url(url, protocol=2)  # Redis 5 (butuh server) [ref]

Bukti: 16 kelompok; 46 baris dijalankan sungguhan,
13 baris [cek] (opsi/API diperiksa ada), 9 baris [ref] (tidak dijalankan).

Yang sengaja tidak ada di lembar

Satu halaman A4 memuat terbatas. Bab berikut tidak punya baris di lembar (isinya lebih cocok dibaca di bab): Bab 1-2 (konsep), Bab 25 (grafik dan scikit-learn dasar; scikit-learn ada di lembar Advanced), 28-29 (metode kualitatif dan riset yang dapat direproduksi), 37 (Python di browser), 40 (PostGIS dan QGIS), 46 (pola desain), 52 (IoT dan drone), 59 (ML sumber daya alam), 61 (transformer), 63 (lisensi dan hukum), dan 68 (portofolio). Bab 32 dan 41 sampai 43 hanya diwakili sebagian kecil.

Cara memeriksa ulang lembar

Skrip pemeriksa tiap lembar pendek (01_basic.py di bawah). Isi lembarnya ada di berkas data di folder yang sama (data_basic.py, data_intermediate.py, data_advanced.py), dan mesin pemeriksanya di lembar_jalan.py.

01_basic.py
# Bab 72 - Lembar contekan Basic: setiap baris dijalankan sungguhan
from data_basic import GRUP
from lembar_jalan import cetak_laporan

cetak_laporan(GRUP, "id", "Lembar contekan Basic (Bab 1-15)", "data_basic")
CatatanTIPS

Ingin lembar sendiri? Salin berkas data, ganti isinya dengan baris kode yang sering Anda lupa, lalu jalankan skripnya. Baris yang salah langsung ketahuan karena skrip berhenti dengan pesan galat. Pembuat PDF-nya adalah alat/lembar_contekan.py di folder proyek buku.