CSV (comma-separated values) adalah format pertukaran data paling universal: ekspor Excel, hasil download dari sistem kantor, dataset untuk analisis. Formatnya tampak sederhana — nilai dipisah koma, baris dipisah baris baru — tetapi aturan sesungguhnya punya detail berbahaya. Chapter split() dan join() sudah memperlihatkan bagaimana split(",") hancur saat satu kolom berisi koma di dalam tanda kutip; module csv dari standard library menangani semua detail tersebut untuk kita.

writer dan reader #

Pasangan csv.writer dan csv.reader bekerja di atas berkas yang kita buka sendiri:

python
import csv

with open("penjualan.csv", "w", newline="", encoding="utf-8") as berkas:
    penulis = csv.writer(berkas)
    penulis.writerow(["nama", "produk", "jumlah"])
    penulis.writerow(["Budi", "keyboard mekanik", 2])
    penulis.writerow(["Siti", "mouse wireless", 1])
    penulis.writerow(["Joko", "kabel USB-C, panjang 1 meter", 5])

with open("penjualan.csv", encoding="utf-8", newline="") as berkas:
    pembaca = csv.reader(berkas)
    for baris in pembaca:
        print(baris)

Output yang Dihasilkan:

text
['nama', 'produk', 'jumlah']
['Budi', 'keyboard mekanik', '2']
['Siti', 'mouse wireless', '1']
['Joko', 'kabel USB-C, panjang 1 meter', '5']

Isi berkas penjualan.csv:

text
nama,produk,jumlah
Budi,keyboard mekanik,2
Siti,mouse wireless,1
Joko,"kabel USB-C, panjang 1 meter",5

Mari kita bedah baris-baris kuncinya:

  1. Baris 3 (with open("penjualan.csv", "w", newline="", encoding="utf-8") ...): berkas dibuka dengan newline="" — syarat wajib module csv pada mode teks. Module csv mengatur akhir barisnya sendiri; tanpa newline="", penerjemahan newline dari sistem operasi membuat akhir baris ganda di Windows (perilaku lengkapnya dibahas pada Chapter Encoding dan Newline).
  2. Baris 4 (penulis = csv.writer(berkas)): object writer dibuat di atas berkas yang sudah terbuka; semua penulisan lewat object ini.
  3. Baris 8 (penulis.writerow(["Joko", "kabel USB-C, panjang 1 meter", 5])): kolom yang mengandung koma otomatis dibungkus tanda kutip oleh writer, dan reader membongkarnya kembali menjadi satu nilai — inilah pekerjaan yang mustahil bagi split(",").
  4. Baris 10-12 (pembaca = csv.reader(...) dan for baris in pembaca:): setiap baris terbaca sebagai list string; angka 2 dan 5 yang kita tulis sebagai int terbaca kembali sebagai "2" dan "5" — konversi ke angka tetap tugas kita, seperti saat membaca input().

Module csv menulis akhir baris sebagai \r\n sesuai standar RFC 4180 — pada blok isi berkas di atas, karakter \r memang tidak terlihat, tetapi karakter tersebut ada di dalam berkas.

DictWriter dan DictReader #

Bekerja dengan indeks kolom (baris[0], baris[1]) rapuh terhadap perubahan urutan. Pasangan DictWriter/DictReader memakai dictionary dengan kunci dari baris header:

python
import csv

stok = [
    {"kode": "P001", "nama_barang": "beras 5 kg", "jumlah": 40},
    {"kode": "P002", "nama_barang": "minyak goreng 1 L", "jumlah": 25},
]

with open("stok.csv", "w", newline="", encoding="utf-8") as berkas:
    kolom = ["kode", "nama_barang", "jumlah"]
    penulis = csv.DictWriter(berkas, fieldnames=kolom)
    penulis.writeheader()
    penulis.writerows(stok)

with open("stok.csv", encoding="utf-8", newline="") as berkas:
    for baris in csv.DictReader(berkas):
        print(baris)
        print(baris["nama_barang"], "->", baris["jumlah"])

Output yang Dihasilkan:

text
{'kode': 'P001', 'nama_barang': 'beras 5 kg', 'jumlah': '40'}
beras 5 kg -> 40
{'kode': 'P002', 'nama_barang': 'minyak goreng 1 L', 'jumlah': '25'}
minyak goreng 1 L -> 25

DictWriter membutuhkan daftar nama kolom lewat fieldnames sebelum writeheader() menuliskan baris judul, dan writerows() menerima list of dictionary. Di sisi pembaca, DictReader otomatis menjadikan baris pertama sebagai kunci, sehingga baris["nama_barang"] jauh lebih terbaca daripada baris[1] dan tetap benar walau urutan kolom berubah.

Delimiter dan Dialect #

Tidak semua CSV memakai koma. Berkas dari regional Eropa (yang memakai koma sebagai tanda desimal) lazim memakai titik koma:

python
import csv

with open("penjualan-titik-koma.csv", "w", newline="", encoding="utf-8") as berkas:
    penulis = csv.writer(berkas, delimiter=";")
    penulis.writerow(["kota", "total"])
    penulis.writerow(["Jakarta", 1500])
    penulis.writerow(["Bandung", 900])

print(open("penjualan-titik-koma.csv", encoding="utf-8").read(), end="")

with open("penjualan-titik-koma.csv", encoding="utf-8", newline="") as berkas:
    pembaca = csv.reader(berkas, delimiter=";")
    for baris in pembaca:
        print(baris)

Output yang Dihasilkan:

text
kota;total
Jakarta;1500
Bandung;900
['kota', 'total']
['Jakarta', '1500']
['Bandung', '900']

Kombinasi aturan — delimiter, aturan kutip, akhir baris — bisa juga dipilih sekaligus lewat dialect bawaan seperti csv.excel (default) atau csv.excel_tab yang memakai tab, dan dialect kustom bisa didaftarkan lewat csv.register_dialect(). Dalam praktik, menyetel delimiter satu per satu seperti di atas sudah menutup mayoritas kebutuhan.

Aturan Kutip #

Konstanta quoting mengendalikan kapan writer membungkus nilai dengan tanda kutip:

KonstantaPerilaku saat menulis
csv.QUOTE_MINIMAL (default)hanya mengutip nilai yang perlu (mengandung koma, kutip, atau baris baru)
csv.QUOTE_ALLmengutip semua nilai
csv.QUOTE_NONNUMERICmengutip semua nilai non-angka
csv.QUOTE_NONEtidak pernah mengutip (gagal bila nilai memuat delimiter)

QUOTE_NONNUMERIC punya bonus manis di sisi pembaca — angka yang tidak dikutip otomatis dikonversi menjadi float:

python
import csv

with open("produk.csv", "w", newline="", encoding="utf-8") as berkas:
    penulis = csv.writer(berkas, quoting=csv.QUOTE_NONNUMERIC)
    penulis.writerow(["nama", "harga", "stok"])
    penulis.writerow(["gula pasir", 16500, 120])
    penulis.writerow(["tepung terigu", 12000, 85])

print(open("produk.csv", encoding="utf-8").read(), end="")

with open("produk.csv", encoding="utf-8", newline="") as berkas:
    for baris in csv.reader(berkas, quoting=csv.QUOTE_NONNUMERIC):
        print([type(nilai).__name__ for nilai in baris], baris)

Output yang Dihasilkan:

text
"nama","harga","stok"
"gula pasir",16500,120
"tepung terigu",12000,85
['str', 'str', 'str'] ['nama', 'harga', 'stok']
['str', 'float', 'float'] ['gula pasir', 16500.0, 120.0]
['str', 'float', 'float'] ['tepung terigu', 12000.0, 85.0]

Baris judul terbaca sebagai str karena semua nilainya dikutip, sedangkan kolom angka terbaca sebagai float. Perhatikan konsekuensinya: harga 16500 menjadi 16500.0, sehingga mode ini paling pas untuk data yang memang pecahan.