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:

python
"""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

Dua 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:

bash
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:

text
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
python
"""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