Kerangka dari Bagian 1 masih berupa folder kosong dengan metadata. Pada bagian ini kita mengisi src/example_package/__init__.py dengan tiga function yang sesungguhnya — lengkap dengan type hints dan docstring, karena library yang tidak terdokumentasi sama tidak bergunanya dengan library yang tidak ada. Setiap baris pada bagian ini dijalankan dan diuji nyata.
Menjaga Seluruh API di Satu Module #
Package ini memuat seluruh kode di __init__.py. Pilihan itu memang sengaja: untuk library sekecil tiga function, memaksa pembaca membolak-balik beberapa module hanya menambah hambatan. Konsekuensinya jelas — begitu package bertambah besar, chapter Merancang Struktur Package pada Section Modules dan Packages menunjukkan cara memecahnya menjadi module-module terpisah dengan tetap menjaga API publik lewat __init__.py.
Berukuran kecil bukan berarti kualitasnya boleh longgar. Tiga kebiasaan profesional tetap kita terapkan pada module sekecil apa pun: type hints pada setiap parameter dan return value, docstring untuk setiap function publik, serta daftar __all__ yang menegaskan mana bagian package yang memang dimaksudkan untuk publik.
Kode Lengkap Module #
Simpan kode berikut sebagai src/example_package/__init__.py:
"""Utilitas teks sederhana untuk mengolah string sehari-hari.
Package ini memuat tiga function: ``slugify`` untuk membuat slug URL,
``hitung_kata`` untuk menghitung jumlah kata, dan ``ringkas`` untuk
memotong teks panjang.
"""
import re
import unicodedata
__version__ = "0.1.0"
__all__ = ["hitung_kata", "ringkas", "slugify"]
def slugify(text: str) -> str:
"""Mengubah teks menjadi slug yang aman untuk URL.
Function ini menormalisasi teks ke bentuk NFKD, membuang tanda
diakritik (misalnya ``é`` menjadi ``e``), mengubah seluruh huruf
menjadi lowercase, lalu mengganti setiap rangkaian karakter yang
bukan huruf kecil atau angka dengan satu tanda hubung.
Args:
text: Teks yang akan diubah menjadi slug.
Returns:
Slug dalam lowercase yang dipisah tanda hubung. String kosong
dikembalikan apabila ``text`` tidak memuat huruf atau angka
sama sekali.
Examples:
>>> slugify("Belajar Python Dasar!")
'belajar-python-dasar'
"""
normalized = unicodedata.normalize("NFKD", text)
ascii_text = normalized.encode("ascii", "ignore").decode("ascii")
lower_text = ascii_text.lower()
slug = re.sub(r"[^a-z0-9]+", "-", lower_text)
return slug.strip("-")
def hitung_kata(text: str) -> int:
"""Menghitung jumlah kata pada sebuah teks.
Kata didefinisikan sebagai rangkaian karakter yang dipisahkan
whitespace apa pun — spasi, tab, maupun newline — sehingga
whitespace berulang di awal, tengah, atau akhir teks diabaikan.
Args:
text: Teks yang akan dihitung jumlah katanya.
Returns:
Jumlah kata pada ``text``; 0 untuk teks kosong atau teks yang
hanya berisi whitespace.
Examples:
>>> hitung_kata("Belajar Python setiap hari")
4
"""
return len(text.split())
def ringkas(text: str, panjang: int, akhir: str = "...") -> str:
"""Memotong teks agar tidak melebihi panjang tertentu.
Teks yang panjangnya tidak melebihi ``panjang`` dikembalikan tanpa
perubahan. Teks yang lebih panjang dipotong lalu diberi akhiran
``akhir`` sehingga panjang total hasilnya tepat ``panjang``.
Args:
text: Teks sumber yang akan dipotong.
panjang: Panjang maksimum teks hasil.
akhir: Akhiran yang ditempelkan pada teks yang terpotong.
Returns:
Teks asli apabila ``len(text) <= panjang``; teks terpotong
berakhiran ``akhir`` pada kondisi lainnya.
Examples:
>>> ringkas("Belajar Python di Course ini sampai tuntas", 20)
'Belajar Python di...'
"""
if len(text) <= panjang:
return text
if panjang <= len(akhir):
return text[:panjang]
return text[: panjang - len(akhir)].rstrip() + akhirDua anggota module di luar function perlu diperkenalkan: __version__ menyimpan versi package yang bisa dibaca pengguna lewat example_package.__version__, dan __all__ menegaskan nama-nama yang diekspor — from example_package import * hanya mengimpor tiga function itu, bukan re atau unicodedata yang kebetulan ikut ter-import di tingkat module.
Bedah Function slugify #
slugify() adalah function paling menarik pada module ini karena menangani Unicode. Mari kita ikuti pola empat langkah.
Aturannya: slug URL hanya boleh memuat huruf kecil, angka, dan tanda hubung. Masalahnya, teks nyata memuat huruf kapital, tanda baca, spasi berulang, bahkan karakter beraksen seperti é.
Mekanismenya bekerja dalam lima tahap pada string. Pertama, unicodedata.normalize("NFKD", text) mengurai karakter beraksen menjadi pasangan huruf dasar plus tanda diakritik terpisah — é menjadi e dan tanda aksen menempel sendiri. Kedua, encode("ascii", "ignore") membuang semua yang bukan ASCII — termasuk tanda aksen yang sudah terpisah — lalu decode("ascii") mengembalikannya menjadi string Python. Ketiga, .lower() menyeragamkan huruf. Keempat, re.sub(r"[^a-z0-9]+", "-", ...) mengganti setiap rangkaian karakter non-alfanumerik dengan satu tanda hubung. Kelima, .strip("-") membuang tanda hubung di ujung.
Contoh konkretnya — "Belajar Python Dasar!" menjadi "belajar-python-dasar" — sudah tertulis sebagai contoh doctest pada docstring. Dua function lainnya jauh lebih sederhana: hitung_kata() memanfaatkan perilaku str.split() tanpa argumen yang memotong semua jenis whitespace sekaligus, sedangkan ringkas() mengurangi len(akhir) dari batas potongan supaya panjang total hasil tetap eksak.
Mencoba Tanpa Instalasi #
Package belum dipasang, tetapi kita tetap bisa mencobanya dengan memasukkan folder src ke sys.path lewat environment variable PYTHONPATH — mekanisme yang sama dengan yang dibahas pada Chapter Module Search Path:
PYTHONPATH=src python3 -c "
from example_package import hitung_kata, ringkas, slugify
print(slugify('Belajar Python Dasar!'))
print(hitung_kata('Belajar Python setiap hari'))
print(ringkas('Belajar Python di Course ini sampai tuntas', 20))
"Output yang Dihasilkan:
belajar-python-dasar
4
Belajar Python di...Tiga baris output itu membuktikan seluruh API bekerja. Cara PYTHONPATH ini cocok untuk percobaan singkat; untuk kebutuhan sehari-hari, pengujian pada bagian berikutnya sudah menyiapkan jalur yang lebih rapi lewat konfigurasi pytest.
Module example_package kini hidup: tiga function, type hints, docstring, __all__, dan __version__ semuanya terpasang. Berkas lengkapnya tersedia pada bagian Berkas Sumber di bawah halaman ini. Pada bagian berikutnya kita mengunci perilaku ketiganya dengan test suite.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
src/example_package/__init__.py
Unduh berkas"""Utilitas teks sederhana untuk mengolah string sehari-hari.
Package ini memuat tiga function: ``slugify`` untuk membuat slug URL,
``hitung_kata`` untuk menghitung jumlah kata, dan ``ringkas`` untuk
memotong teks panjang.
"""
import re
import unicodedata
__version__ = "0.1.0"
__all__ = ["hitung_kata", "ringkas", "slugify"]
def slugify(text: str) -> str:
"""Mengubah teks menjadi slug yang aman untuk URL.
Function ini menormalisasi teks ke bentuk NFKD, membuang tanda
diakritik (misalnya ``é`` menjadi ``e``), mengubah seluruh huruf
menjadi lowercase, lalu mengganti setiap rangkaian karakter yang
bukan huruf kecil atau angka dengan satu tanda hubung.
Args:
text: Teks yang akan diubah menjadi slug.
Returns:
Slug dalam lowercase yang dipisah tanda hubung. String kosong
dikembalikan apabila ``text`` tidak memuat huruf atau angka
sama sekali.
Examples:
>>> slugify("Belajar Python Dasar!")
'belajar-python-dasar'
"""
normalized = unicodedata.normalize("NFKD", text)
ascii_text = normalized.encode("ascii", "ignore").decode("ascii")
lower_text = ascii_text.lower()
slug = re.sub(r"[^a-z0-9]+", "-", lower_text)
return slug.strip("-")
def hitung_kata(text: str) -> int:
"""Menghitung jumlah kata pada sebuah teks.
Kata didefinisikan sebagai rangkaian karakter yang dipisahkan
whitespace apa pun — spasi, tab, maupun newline — sehingga
whitespace berulang di awal, tengah, atau akhir teks diabaikan.
Args:
text: Teks yang akan dihitung jumlah katanya.
Returns:
Jumlah kata pada ``text``; 0 untuk teks kosong atau teks yang
hanya berisi whitespace.
Examples:
>>> hitung_kata("Belajar Python setiap hari")
4
"""
return len(text.split())
def ringkas(text: str, panjang: int, akhir: str = "...") -> str:
"""Memotong teks agar tidak melebihi panjang tertentu.
Teks yang panjangnya tidak melebihi ``panjang`` dikembalikan tanpa
perubahan. Teks yang lebih panjang dipotong lalu diberi akhiran
``akhir`` sehingga panjang total hasilnya tepat ``panjang``.
Args:
text: Teks sumber yang akan dipotong.
panjang: Panjang maksimum teks hasil.
akhir: Akhiran yang ditempelkan pada teks yang terpotong.
Returns:
Teks asli apabila ``len(text) <= panjang``; teks terpotong
berakhiran ``akhir`` pada kondisi lainnya.
Examples:
>>> ringkas("Belajar Python di Course ini sampai tuntas", 20)
'Belajar Python di...'
"""
if len(text) <= panjang:
return text
if panjang <= len(akhir):
return text[:panjang]
return text[: panjang - len(akhir)].rstrip() + akhir