Ana içeriğe geç
Kesit Python
Temel · Veri yapıları

Listeler

Sipariş ve tutar listelerini indeks ve dilimle okur; append, insert, pop, remove ile değiştirir; sort ve sorted ile sıralar; iç içe liste ve kopyalama tuzağını öğrenirsin.

Uygulamada aç, 20 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Tek değişken yetmeyince

Anadolu Market'e bugün 5 sipariş geldi. Tutarları t1, t2, t3... diye ayrı değişkenlerde tutmak hem yorucu hem de yarın 500 sipariş gelince imkânsız. Liste (list), birden fazla değeri sıralı biçimde tek değişkende tutar. Köşeli parantezle yazılır, elemanlar virgülle ayrılır ve her türden değer içerebilir.

tutarlar = [1499.9, 250, 89.5, 3200, 475]
sehirler = ["İzmir", "Ankara", "İzmir", "Bursa", "İstanbul"]
karisik = ["AM-00417", 1499.9, True, None]
print(tutarlar)
print(len(tutarlar), "sipariş")
print(sum(tutarlar), max(tutarlar), min(tutarlar))
print(round(sum(tutarlar) / len(tutarlar), 2), "TL ortalama")

len, sum, max, min hazır fonksiyonları listelerle doğrudan çalışır. Beş satırda bir günün özet raporu çıktı; analistin ilk Python kazancı budur.

İndeks ve dilim

Metinlerdeki kuralların aynısı: 0'dan başlar, negatif sondan sayar, dilimde bitiş hariçtir.

sehirler = ["İzmir", "Ankara", "İzmir", "Bursa", "İstanbul"]
print(sehirler[0], sehirler[-1])
print(sehirler[1:3])
print(sehirler[:2], sehirler[3:])
print("Bursa" in sehirler)
print(sehirler.index("Bursa"))
print(sehirler.count("İzmir"))

in elemanın var olup olmadığını, index ilk konumunu, count kaç kez geçtiğini verir. Var olmayan bir indeks IndexError, var olmayan bir değer için index ValueError verir; önce in ile sormak güvenlidir.

Listeyi değiştirmek

Metinlerden en büyük farkı burada: liste değiştirilebilir (mutable). Metotların çoğu listeyi yerinde değiştirir ve hiçbir şey döndürmez (None).

bekleyen = ["AM-101", "AM-102", "AM-103"]
bekleyen.append("AM-104")          # sona ekle
bekleyen.insert(0, "AM-100")       # başa ekle (konum, değer)
print(bekleyen)
islenen = bekleyen.pop(0)          # konumdaki elemanı çıkar ve döndür
print("İşlendi:", islenen, "| kalan:", bekleyen)
bekleyen.remove("AM-102")          # değere göre ilk eşleşeni sil
print(bekleyen)
bekleyen.extend(["AM-105", "AM-106"])   # başka listeyi ekle
print(bekleyen, len(bekleyen))
bekleyen[0] = "AM-101-DUZELTME"    # yerinde değiştir
print(bekleyen[0])
MetotNe yaparDöndürür
append(x)Sona tek eleman eklerNone
insert(i, x)i konumuna eklerNone
extend(liste)Başka listenin elemanlarını eklerNone
pop(i)i konumundakini çıkarır (varsayılan: son)Çıkarılan eleman
remove(x)x değerinin ilkini silerNone
sort()Yerinde sıralarNone
reverse()Yerinde ters çevirirNone
append bir liste eklemez, elemanı eklera.append([1, 2]) listenin içine bir liste koyar (iç içe). İki listeyi birleştirmek için extend ya da a + b kullan.

sort ve sorted

En sık karıştırılan ikili. liste.sort() listeyi yerinde sıralar ve None döndürür. sorted(liste) ise orijinale dokunmaz, sıralı yeni bir liste döndürür.

tutarlar = [1499.9, 250, 89.5, 3200, 475]
sirali = sorted(tutarlar)
print(sirali)
print(tutarlar)                       # orijinal aynı
tutarlar.sort(reverse=True)           # yerinde, büyükten küçüğe
print(tutarlar)
print("En büyük 3:", tutarlar[:3])
sehirler = ["İzmir", "ankara", "Bursa"]
print(sorted(sehirler))
print(sorted(sehirler, key=str.lower))

Son iki satır önemli bir ayrıntıyı gösterir: sıralama büyük-küçük harfe duyarlıdır, büyük harfler önce gelir. key=str.lower ile "küçük harfli hâline göre sırala" dersin. Türkçe karakterler için tam alfabetik sıralama locale modülü gerektirir; raporda genellikle key=str.lower yeterlidir.

Hangisini seçmeli?Orijinal sıralamaya tekrar ihtiyacın olacaksa sorted, olmayacaksa sort (bellek kullanmaz). Zincirleme bir ifadede, örneğin sorted(x)[:3], yalnızca sorted çalışır.

İç içe liste: küçük bir tablo

Her sipariş birden fazla bilgi taşıyorsa her satır bir liste, tüm satırlar da bir liste olur. Bu, bir tablonun en basit hâlidir.

siparisler = [
    ["AM-101", "İzmir", 1499.9],
    ["AM-102", "Ankara", 250],
    ["AM-103", "İzmir", 89.5],
]
print(siparisler[1])            # ikinci satır
print(siparisler[1][2])         # ikinci satırın tutarı
siparisler[2][2] = 99.5         # tutarı düzelt
print(siparisler[2])
siparisler.append(["AM-104", "Bursa", 3200])
print(len(siparisler), "satır")
print(siparisler[0][1], siparisler[-1][1])

İlk indeks satırı, ikinci indeks sütunu seçer. Döngüleri öğrenince bu yapıyı satır satır gezip şehir bazlı toplam çıkaracaksın; sözlükler dersinde ise sütunlara isimle erişmenin daha okunur yolunu göreceksin.

Listeyi listeyle birleştirmek ve karşılaştırmak

sabah = ["AM-101", "AM-102"]
ogle = ["AM-103"]
gun = sabah + ogle              # yeni liste
print(gun)
print(sabah * 2)                # tekrar
print(sabah == ["AM-101", "AM-102"])   # içerik karşılaştırma
print([] == [], len([]))
print(gun[1:] == ["AM-102", "AM-103"])

+ iki listeden yeni bir liste üretir; extend ise var olanı yerinde büyütür. == sıra ve içerik aynıysa True verir. Boş liste [], koşullarda False gibi davranır; "sipariş var mı" sorusunu if siparisler: ile soracaksın.

Kopyalama tuzağı

Burası herkesin bir kere başına gelen hata. b = a yazmak listeyi kopyalamaz; aynı listeye ikinci bir etiket takar. Birini değiştirdiğinde diğeri de değişir çünkü ikisi de aynı nesnedir.

onayli = ["AM-101", "AM-102"]
yedek = onayli                 # kopya DEĞİL, aynı liste
yedek.append("AM-103")
print("onayli:", onayli)       # o da değişti!
print(onayli is yedek)

gercek_yedek = onayli.copy()   # ya da onayli[:] ya da list(onayli)
gercek_yedek.append("AM-999")
print("onayli:", onayli)
print("gercek_yedek:", gercek_yedek)
print(onayli is gercek_yedek)

is operatörü "aynı nesne mi" diye sorar; == ise "içerikleri eşit mi". copy(), [:] ve list() üçü de yüzeysel kopya (shallow copy) alır: dış liste yeni, ama içindeki listeler hâlâ paylaşılır. İç içe listelerde tam bağımsız kopya için copy.deepcopy gerekir.

Neden metinlerde bu sorun yoktu?Metin değişmezdir; onu "değiştiren" her işlem yeni nesne üretir, dolayısıyla paylaşılan nesne hiç bozulmaz. Liste yerinde değiştiğinden paylaşım görünür hâle gelir. Aynı tuzak sözlük ve kümelerde de vardır.
Derinleş

Demet ve küme Demetler ve kümeler, iç içe veri yapıları İç içe veri, tek satırda liste üretme ise Orta seviyedeki Kapsamlı ifadeler dersinde. Bu bölüm listenin kendisini derinleştiriyor: dilimle değiştirme, yıldızlı açma, anahtarla sıralama, en sık tuzaklar ve hangi işlemin ne kadar pahalı olduğu.

Dilimle okuma, dilimle yazma

Dilim yalnızca okumak için değildir; bir dilime atama yaparak listenin bir bölümünü tek seferde değiştirebilir, silebilir ya da araya eleman ekleyebilirsin. Üçüncü parametre olan adım da listelerde metinlerdeki gibi çalışır.

kuyruk = ["AM-101", "AM-102", "AM-103", "AM-104", "AM-105", "AM-106"]
print(kuyruk[::2])          # ikide bir: tek sıradakiler
print(kuyruk[::-1])         # ters sıra (yeni liste)
print(kuyruk[-3:])          # son üç

kuyruk[1:3] = ["AM-102-D"]  # iki elemanı bir elemanla değiştir
print(kuyruk)
kuyruk[1:1] = ["ACIL-1", "ACIL-2"]   # araya ekle (hiçbir şey silmeden)
print(kuyruk)
del kuyruk[-2:]             # son ikisini sil
print(kuyruk, len(kuyruk))
kuyruk.clear()              # tamamını boşalt (aynı liste nesnesi)
print(kuyruk)

Dilime atamada sağ tarafın uzunluğu dilimle aynı olmak zorunda değildir; liste kendiliğinden büyür ya da küçülür. del liste[i] tek elemanı, del liste[a:b] bir aralığı siler. clear() ile liste = [] arasındaki fark ise kopyalama tuzağıyla ilgilidir: clear aynı nesneyi boşaltır, dolayısıyla o listeyi gösteren diğer adlar da boş listeyi görür; = [] ise yalnızca bu adı yeni bir listeye taşır.

Liste oluşturmanın yolları ve yıldızlı açma

sira_no = list(range(1, 6))           # [1, 2, 3, 4, 5]
harfler = list("ABC")                 # her karakter bir eleman
sehirler = "İzmir;Ankara;Bursa".split(";")
bos = [0] * 4
print(sira_no, harfler, sehirler, bos)

ilk, *orta, son = [120, 340, 95, 410, 60]
print(ilk, orta, son)
basliklar, *veri = ["ad,tutar", "Ayşe,120", "Ali,95"]
print(basliklar, "|", veri)
print(*sehirler, sep=" > ")           # listeyi argümanlara aç

Yıldızlı ad (*orta) "geri kalan her şeyi bir listede topla" demektir; kalan yoksa boş liste olur. CSV'de başlık satırını veriden ayırmak gibi işlerde çok okunur bir kalıptır. Fonksiyon çağrısında *liste ise ters yönde çalışır ve elemanları ayrı ayrı argüman olarak verir.

Anahtarla sıralama: key

sort, sorted, min ve max bir key fonksiyonu alabilir. Python her elemanı bu fonksiyondan geçirir ve karşılaştırmayı sonuca göre yapar; elemanların kendisi değişmez. Henüz kendi fonksiyonunu yazmadan da hazır fonksiyonlarla çok şey yaparsın:

urunler = ["Çay", "Kahve Makinesi", "Su", "Filtre Kahve"]
print(sorted(urunler, key=len))               # kısadan uzuna
print(max(urunler, key=len), min(urunler, key=len))
farklar = [-120, 45, -15, 300, -310]
print(sorted(farklar, key=abs))               # mutlak değere göre
print(max(farklar, key=abs))                  # en büyük sapma
etiketler = ["b-ürün", "A-ürün", "c-ürün"]
print(sorted(etiketler), sorted(etiketler, key=str.lower))
print(list(reversed(urunler)))                # ters sırada yeni liste

Python'ın sıralaması kararlıdır (stable): anahtarları eşit olan elemanlar, orijinal sıralarını korur. Yukarıda "Çay" ve "Su" ikisi de kısa ama "Su" daha kısa olduğu için önce gelir; uzunluğu eşit olsaydı listedeki sırayla dizilirlerdi. Birden çok ölçüte göre sıralamayı (önce şehir, sonra tutar) fonksiyonlar dersinden sonra lambda ve demet anahtarlarla yapacaksın.

Çarpmayla iç içe liste tuzağı

[0] * 4 güvenlidir çünkü 0 değiştirilemez. Ama [[0] * 3] * 2 yazdığında dış liste aynı iç listeyi iki kez içerir; birini değiştirince hepsi değişir. Tablo kurarken her satır için yeni bir liste oluşturman gerekir:

yanlis = [[0] * 3] * 2
yanlis[0][0] = 99
print(yanlis)                     # iki satır birden değişti
print(yanlis[0] is yanlis[1])

dogru = [[0] * 3 for _ in range(2)]   # her satır ayrı liste
dogru[0][0] = 99
print(dogru)
print(dogru[0] is dogru[1])

İkinci yazımdaki for _ in range(2) kısmı bir liste kavrayışıdır (list comprehension); döngüleri öğrendikten sonra tam olarak ne yaptığını anlayacaksın. Şimdilik kalıp olarak bil: "her satır için yeni [0] * 3 üret".

Hangi işlem ne kadar pahalı?

Liste, elemanları bellekte art arda tutan bir dizidir. Bu yüzden sondaki işlemler ucuz, baştaki ve ortadaki işlemler pahalıdır: başa eleman eklemek, diğer bütün elemanları bir kaydırmayı gerektirir. Algoritmalar dersinde bunu büyük O gösterimiyle (Big O) tartışacağız; şimdilik tabloyu tanı:

İşlemMaliyetAçıklama
liste[i], liste[i] = xSabit, O(1)Listenin boyundan bağımsız
append(x), pop()Sabit, O(1)Sondan ekle/çıkar
len(liste)Sabit, O(1)Uzunluk saklanır, sayılmaz
insert(0, x), pop(0)Doğrusal, O(n)Tüm elemanlar kayar
x in liste, index, count, removeDoğrusal, O(n)Baştan sona tarama
liste[a:b], copy()Dilim boyu kadarYeni liste oluşturur
sort(), sorted()O(n log n)Çok verimli Timsort algoritması
Yüz bin elemanda fark ederOn elemanlı bir listede bu tablonun hiçbir önemi yok. Ama 100.000 müşteri numarasında in ile arama yapan bir döngü saniyeler sürer, aynı iş kümeyle (set) anında biter. Kuyruk gibi iki uçtan çalışan yapılar için collections.deque vardır. Önce doğru çalışan kodu yaz, yavaşlık görürsen bu tabloya dön.
Gerçek işteWeb uygulamaları ve API'ler sonuçları sayfa sayfa döndürür: "2. sayfa, sayfa başına 20 kayıt". Bunun arkasında tam olarak dilimleme vardır: kayitlar[(sayfa - 1) * boyut : sayfa * boyut]. Dilim sınırları aşınca hata yerine boş liste döndüğü için son sayfanın ötesine geçen istekler de kendiliğinden güvenli olur. Veritabanında aynı iş LIMIT ve OFFSET ile, pandas'ta df.iloc[a:b] ile yapılır; mantık birebir aynıdır.
Uzmanlaş

Fonksiyona liste vermek

Bir fonksiyon parametre olarak liste alıp append yaparsa çağıranın listesi de değişir; bu kopyalama tuzağının fonksiyon hâlidir. İyi uygulama: fonksiyon ya listeyi değiştirdiğini adıyla belli eder (listeye_ekle) ya da hiç dokunmayıp yeni liste döndürür. Varsayılan parametre olarak def f(x=[]) yazmak ise klasik bir tuzaktır; fonksiyonlar dersinde göreceksin.

Liste ne zaman yanlış araçtır?

Liste "var mı" sorusunu baştan sona tarayarak yanıtlar; yüz bin müşteri numarasının içinde arama yapacaksan küme (set) ya da sözlük (dict) çok daha hızlıdır. Listenin başından sürekli eleman çıkarmak (pop(0)) da yavaştır; kuyruk için collections.deque vardır. Sayısal hesaplar içinse numpy dizileri hem hızlı hem bellek dostudur. Liste, sıralı ve küçük-orta boyutlu veri için doğru araçtır.

Comprehension'a hazırlık

Orta seviyede [t * 1.2 for t in tutarlar] gibi tek satırda yeni liste üreten liste kavrayışlarını (list comprehension) öğreneceksin. Şimdi append ile adım adım yaptığın her şey orada tek ifadeye inecek; temel sağlamsa geçiş bir gün sürer.

Alıştırmalara geç (20)