Begitu subprocess.run() dipahami, menjalankan program eksternal menjadi soal pola pemakaian: bagaimana mengurai output tool, mengikuti proses berjalan lama, menjaga terminal tetap bersih, dan menemukan executable di berbagai mesin. Chapter ini mengumpulkan resep-rujukan tersebut dalam bentuk kode yang siap dipakai, melanjutkan fondasi Chapter subprocess.

Kontrak Dasar: List, Bukan String #

Setiap resep di chapter ini mengikuti satu aturan: perintah ditulis sebagai list, satu elemen untuk nama program dan satu elemen per argumen. Bentuk string tunggal seperti "python3 info_stok.py --format json" menggoda, tetapi bentuk tersebut hanya aman bila dipecah manual dan berbahaya bila digabung dengan shell. List membuat batas antar argumen eksplisit: nilai apa pun — termasuk yang mengandung spasi, titik koma, atau tanda kutip — tetap utuh sebagai satu argumen, tidak pernah ditafsirkan ulang.

Menjalankan Tool dan Mengurai Output JSON #

Pola paling produktif: tool eksternal kita sendiri mencetak JSON, program utama mengurainya kembali. JSON menjadi kontrak yang rapi karena tipe data terjaga. Berkas info_stok.py berperan sebagai tool:

python
import json

stok = {"jeruk": 12, "apel": 30, "pisang": 7}
print(json.dumps(stok))

Program utama jalankan_parser.py menjalankan tool tersebut lalu mengolah hasilnya:

python
import json
import subprocess

hasil = subprocess.run(
    ["python3", "info_stok.py"],
    capture_output=True,
    text=True,
    check=True,
)

stok = json.loads(hasil.stdout)
print("total buah:", sum(stok.values()))
print("stok menipis:", sorted(k for k, v in stok.items() if v < 10))

Output yang Dihasilkan:

text
total buah: 49
stok menipis: ['pisang']

Mari kita bedah alurnya:

  1. Baris 8 (check=True): bila info_stok.py gagal, eksekusi berhenti di CalledProcessError alih-alih memproses output setengah jadi.
  2. Baris 11 (json.loads(hasil.stdout)): output stdout berupa string JSON berubah menjadi dictionary dalam satu panggilan.
  3. Baris 13 (sorted(k for k, v in stok.items() if v < 10)): hasil difilter lalu diurutkan sehingga keluaran stabil dan mudah dibandingkan antar eksekusi.

Pola ini juga berlaku untuk tool pihak ketiga: banyak tool modern menyediakan mode --json untuk alasan yang sama.

Streaming Output Proses Panjang #

subprocess.run() menunggu anak selesai sebelum memberi apa pun. Untuk proses berjalan lama — build, backup, import data — kita ingin memproses setiap baris begitu tersedia. Itu pekerjaan Popen dengan iterasi langsung pada proses.stdout. Tool stream_log.py mensimulasikan proses lambat:

python
import time

for nomor in range(1, 4):
    print(f"memproses batch ke-{nomor}", flush=True)
    time.sleep(0.1)

Parameter flush=True wajib: tanpa parameter tersebut, output menumpuk di buffer anak dan datang sekaligus di akhir. Pemrosesnya, baca_stream.py:

python
from subprocess import PIPE, Popen

proses = Popen(
    ["python3", "stream_log.py"],
    stdout=PIPE,
    text=True,
)

for baris in proses.stdout:
    print("terima:", baris.strip())

proses.wait()
print("returncode:", proses.returncode)

Output yang Dihasilkan:

text
terima: memproses batch ke-1
terima: memproses batch ke-2
terima: memproses batch ke-3
returncode: 0

Loop for baris in proses.stdout menerima tiap baris segera setelah anak mencetaknya — dua baris berjarak 0,1 detik pada eksekusi nyata. Panggilan proses.wait() setelah loop memastikan sumber daya anak dibersihkan dan returncode terisi.

Menyembunyikan Output dan Mencari Executable #

Dua resep kecil yang sering dibutuhkan. Pertama, menjalankan tool yang cerewet tanpa mengotori output program kita: arahkan stdout dan stderr ke DEVNULL, tempfile milik sistem yang membuang semua yang ditulis ke dalamnya:

python
import subprocess
from subprocess import DEVNULL

hasil = subprocess.run(
    ["python3", "stream_log.py"],
    stdout=DEVNULL,
    stderr=DEVNULL,
)
print("output disembunyikan, returncode:", hasil.returncode)

Output yang Dihasilkan:

text
output disembunyikan, returncode: 0

Kedua, memastikan sebuah program ada sebelum memanggilnya — subprocess.run() memicu FileNotFoundError bila executable tidak ditemukan. Fungsi shutil.which() mencari nama program di folder PATH dan mengembalikan path lengkapnya:

python
import shutil
from pathlib import Path

lokasi = shutil.which("python3")
print("python3 ditemukan :", lokasi is not None)
print("nama berkas       :", Path(lokasi).name)
print("tool tidak ada    :", shutil.which("alat-tidak-ada"))

Output yang Dihasilkan:

text
python3 ditemukan : True
nama berkas       : python3
tool tidak ada    : None

Kembalian None berarti program tidak ada di PATH, sehingga program kita bisa menampilkan pesan “ffmpeg belum terpasang” yang jauh lebih ramah daripada traceback.

Kontrak Exit Code #

Exit code adalah satu-satunya kanal hasil yang dijamin hadir dari semua program eksternal. Tabel kontrak yang lazim:

Exit codeArtiPenanganan di Python
0sukseslanjutkan proses berikutnya
1 (umumnya)galat aplikasibaca stderr untuk alasan galat
2kesalahan penggunaan (argumen)perbaiki argumen, jangan ulangi
127program tidak ditemukanperiksa dengan shutil.which()

Jika tool yang kita tulis sendiri, dokumentasikan arti tiap exit code dan pertahankan konsisten — pemanggil, termasuk skrip CI, menulis logika berdasarkan kontrak tersebut. Untuk tool pihak ketiga, perlakukan exit code selain nol sebagai kegagalan memakai check=True, lalu tangkap CalledProcessError dan bacalah galat.stderr.

Dengan resep parsing, streaming, pembungkian, dan pencarian executable, program Python kita dapat mengorkestrasi tool lain dengan aman dan terkendali. Selanjutnya kita berpindah ke masalah waktu: menjalankan pekerjaan secara terjadwal.