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:
import json
stok = {"jeruk": 12, "apel": 30, "pisang": 7}
print(json.dumps(stok))Program utama jalankan_parser.py menjalankan tool tersebut lalu mengolah hasilnya:
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:
total buah: 49
stok menipis: ['pisang']Mari kita bedah alurnya:
- Baris 8 (
check=True): bilainfo_stok.pygagal, eksekusi berhenti diCalledProcessErroralih-alih memproses output setengah jadi. - Baris 11 (
json.loads(hasil.stdout)): output stdout berupa string JSON berubah menjadi dictionary dalam satu panggilan. - 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:
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:
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:
terima: memproses batch ke-1
terima: memproses batch ke-2
terima: memproses batch ke-3
returncode: 0Loop 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:
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:
output disembunyikan, returncode: 0Kedua, 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:
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:
python3 ditemukan : True
nama berkas : python3
tool tidak ada : NoneKembalian 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 code | Arti | Penanganan di Python |
|---|---|---|
0 | sukses | lanjutkan proses berikutnya |
1 (umumnya) | galat aplikasi | baca stderr untuk alasan galat |
2 | kesalahan penggunaan (argumen) | perbaiki argumen, jangan ulangi |
127 | program tidak ditemukan | periksa 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.