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

Demet (tuple) ve küme (set)

Değişmez kayıtlar için demeti, benzersiz değerler ve küme işlemleri için kümeyi öğrenir; liste, demet ve küme arasında ne zaman hangisini seçeceğini kavrarsın.

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ç.

İki yeni kap

Liste çoğu işi görür ama iki durumda eksik kalır. Birincisi: bazı veriler asla değişmemeli; bir şubenin koordinatı, bir siparişin (kod, tarih) kimliği. İkincisi: bazen sırayla değil "hangileri var" ile ilgilenirsin; Anadolu Market'ten alışveriş yapmış benzersiz şehirler, hem kredi kartı hem mevduat müşterisi olanlar. İlk iş için demet (tuple), ikincisi için küme (set) var.

Demet: değişmez liste

Demet, parantezle yazılan ve oluşturulduktan sonra değiştirilemeyen sıralı bir koleksiyondur. İndeks, dilim, len, in hepsi listeyle aynı çalışır; sadece ekleme, silme ve atama yoktur.

sube = ("Ilgaz Bank Konak", 38.4189, 27.1287)
print(sube[0])
print(sube[1:])
print(len(sube), "Konak" in sube[0])
tek = (42,)             # tek elemanlı demet: virgül şart
degil = (42)            # bu sadece 42 sayısı
print(type(tek), type(degil))
parantezsiz = 1, 2, 3   # parantez bile gerekmez
print(parantezsiz)

Tek elemanlı demette virgül şarttır; (42) yalnızca bir sayıdır. Aslında demeti yapan şey parantez değil virgüldür: 1, 2, 3 tek başına bir demettir. Sayılar dersinde 1499,90 yazınca demet oluşmasının sebebi buydu.

sube = ("Konak", 38.4189, 27.1287)
try:
    sube[0] = "Alsancak"
except TypeError as hata:
    print("Değiştirilemez:", hata)
yeni_sube = ("Alsancak",) + sube[1:]
print(yeni_sube)

Değiştirmek istersen metinlerde olduğu gibi yeni bir demet kurarsın. Buradaki try/except yapısını Hatalar dersinde öğreneceksin; şimdilik "hata verir ama program durmasın" diye oku.

Açma (unpacking)

Demetin en güçlü yanı: elemanlarını tek satırda ayrı değişkenlere dağıtabilirsin. Buna açma (unpacking) denir ve fonksiyonlar birden fazla değer döndürürken sürekli karşına çıkar.

sube = ("Konak", 38.4189, 27.1287)
ad, enlem, boylam = sube
print(ad, enlem, boylam)
a, b = 10, 20
a, b = b, a                    # takas, geçici değişkensiz
print(a, b)
bolum, kalan = divmod(10000, 3)
print(bolum, kalan)
ilk, *orta, son = [1, 2, 3, 4, 5]
print(ilk, orta, son)

Sol taraftaki değişken sayısı eleman sayısıyla eşleşmeli, yoksa ValueError alırsın. Yıldızlı ad (*orta) "geri kalan hepsi" demektir ve liste olarak gelir. Açma yalnızca demet için değil liste ve her yinelenebilir için çalışır.

Demet ne zaman?Elemanların sayısı ve anlamı sabitse demet kullan: (enlem, boylam), (ad, soyad), (yıl, ay, gün). Elemanlar aynı türden ve sayısı değişkense liste: sipariş tutarları, şehir adları. Demet ayrıca sözlük anahtarı olabilir; liste olamaz.

Küme: benzersiz elemanlar

Küme süslü parantezle yazılır, sırasızdır ve her elemanı yalnızca bir kez tutar. Bir listeyi set()'e vermek tekrarları anında kaldırır.

sehirler = ["İzmir", "Ankara", "İzmir", "Bursa", "İzmir", "Ankara"]
benzersiz = set(sehirler)
print(benzersiz)
print(len(benzersiz), "farklı şehir")
benzersiz.add("İstanbul")
benzersiz.add("Ankara")            # zaten var, bir şey olmaz
benzersiz.discard("Bursa")         # yoksa da hata vermez
print(sorted(benzersiz))           # sıralı görmek için listeye çevir
print("İzmir" in benzersiz)
bos = set()                        # {} boş sözlük olur, dikkat

Küme sırasız olduğundan indeksle erişemezsin (benzersiz[0] hata verir) ve yazdırınca sıra her çalıştırmada farklı olabilir. Sıralı görmek için sorted kullan. Kümenin asıl gücü hızdır: in sorgusu listede baştan sona tarama yaparken kümede eleman sayısından bağımsız, neredeyse anında yanıtlanır. Yüz binlerce müşteri numarası içinde arama yapacaksan küme kullan.

Küme elemanları değişmez olmalıKümeye sayı, metin ve demet koyabilirsin ama liste ya da sözlük koyamazsın; {[1, 2]} TypeError verir. Sebep: küme elemanları için karma (hash) hesaplar ve değişebilen nesnelerin karması tutarlı olmaz.

Demeti listeye, listeyi demete

İki yapı arasında geçiş ucuzdur: list() ve tuple() dönüşüm yapar. Bir kaydı değiştirmen gerektiğinde listeye çevirir, düzeltir, sonra tekrar demete dönersin.

kayit = ("AM-101", "İzmir", 1499.9)
gecici = list(kayit)
gecici[2] = 1399.9
kayit = tuple(gecici)
print(kayit, type(kayit))
print(kayit.count("İzmir"), kayit.index(1399.9))
print(tuple("abc"))

Demetin yalnızca iki metodu vardır: count ve index. Daha fazlasına ihtiyaç duyuyorsan muhtemelen liste istiyorsundur; bu da demetin sana verdiği bir sinyal.

Küme işlemleri

Ilgaz Bank'ta iki müşteri grubu var: kredi kartı olanlar ve mevduat hesabı olanlar. Pazarlama ekibi şunu soruyor: hangileri ikisine de sahip, hangileri yalnızca kredi kartına sahip, toplam kaç farklı müşteri var?

kredi_karti = {"M-101", "M-102", "M-103", "M-104"}
mevduat = {"M-103", "M-104", "M-105"}
print("Her ikisi:", kredi_karti & mevduat)       # kesişim
print("Toplam farklı:", kredi_karti | mevduat)    # birleşim
print("Sadece kart:", kredi_karti - mevduat)      # fark
print("Yalnız birinde:", kredi_karti ^ mevduat)   # simetrik fark
print(kredi_karti.intersection(mevduat))         # aynı şey, metot hâli
print({"M-103"} <= mevduat)                       # alt küme mi?
İşlemOperatörMetotAnlamı
Birleşima | ba.union(b)Herhangi birinde olanlar
Kesişima & ba.intersection(b)İkisinde de olanlar
Farka - ba.difference(b)a'da olup b'de olmayanlar
Simetrik farka ^ ba.symmetric_difference(b)Yalnızca birinde olanlar
Alt kümea <= ba.issubset(b)a'nın tamamı b'de mi

Bu dört işlem, SQL'deki JOIN ve EXCEPT sorgularının Python karşılığıdır. "Geçen ay alışveriş yapıp bu ay yapmayan müşteriler" sorusu tek bir fark işlemidir: gecen_ay - bu_ay.

Hangisini ne zaman?

ÖzellikListeDemetKüme
Yazım[1, 2](1, 2){1, 2}
SıralıEvetEvetHayır
DeğiştirilebilirEvetHayırEvet (elemanlar değişmez)
TekrarOlabilirOlabilirOlamaz
İndeksVarVarYok
Tipik işSıralı kayıtlarSabit yapılı kayıt, çoklu dönüşBenzersizlik, üyelik, karşılaştırma
frozensetDeğişmez küme istersen frozenset var; kümelerin kümesini yaparken ya da sözlük anahtarı olarak küme kullanırken gerekir. Nadir ama var olduğunu bil.
Derinleş

Demetleri karşılaştırmak ve sıralamak

İki demet karşılaştırılırken Python soldan sağa eleman eleman bakar: ilk elemanlar farklıysa sonuç oradan belli olur, eşitse ikinci elemana geçer. Buna sözlük sırası (lexicographic order) denir. Bu kural sayesinde demet listesini sorted ile sıraladığında önce ilk alana, eşitlikte ikinci alana göre sıralama kendiliğinden olur.

print((2025, 3) < (2025, 11))      # ilk eşit, 3 < 11
print(("Bursa", 900) < ("Ankara", 50))   # "B" > "A", gerisine bakılmaz

siparisler = [("AM-104", "İzmir", 450.0), ("AM-101", "Ankara", 1200.0),
              ("AM-103", "İzmir", 1890.5), ("AM-102", "Ankara", 75.0)]

def sehir_sonra_tutar(s):
    # şehir artan, eşit şehirde tutar AZALAN: sayıyı eksiyle çevir
    return (s[1], -s[2])

for kod, sehir, tutar in sorted(siparisler, key=sehir_sonra_tutar):
    print(kod, sehir, tutar)

key parametresine verdiğin fonksiyon her eleman için bir "sıralama anahtarı" üretir; anahtar demet olunca çok kriterli sıralama tek satıra iner. Azalan sıra gereken sayısal alanı eksiyle çevirmek yaygın bir hiledir. Fonksiyonu tek satırda lambda ile yazmayı Fonksiyonel programlama dersinde göreceksin.

Türkçe harflerMetin karşılaştırması harflerin Unicode numarasına göre yapılır; "İ", "Ç", "Ş" gibi harfler Z'den sonra gelir. Bu yüzden "İzmir", "Zonguldak"'tan büyük sayılır. Doğru Türkçe alfabetik sıra için Unicode ve metin dersindeki teknikleri kullanırsın.

İç içe açma ve yıldızlı açma

Açma (unpacking) yalnızca düz demetlerde değil, iç içe yapılarda da çalışır. Sol tarafı verinin şekline benzetmen yeterli. Döngü başlığında yapıldığında kayıtları okumak çok kolaylaşır.

kayitlar = [("AM-1", ("İzmir", 120)), ("AM-2", ("Bursa", 80))]
for kod, (sehir, tutar) in kayitlar:
    print(f"{kod}: {sehir} / {tutar} TL")

ilk, *_, son = (5, 8, 13, 21, 34)   # _ : "umursamıyorum" adı
print(ilk, son)

basla, bitir = ("09:00", "18:00")
mesai = (basla, "12:30", bitir)
print(*mesai)                      # yıldız, demeti argümanlara dağıtır
print(*mesai, sep=" | ")

Alt çizgi (_) Python için sıradan bir değişken adıdır ama ekip içinde "bu değeri kullanmayacağım" anlamına gelen ortak bir işarettir. Çağrıdaki *mesai ise demeti açıp her elemanı ayrı argüman olarak verir; Fonksiyonlar dersinde *args ile bunun tersini göreceksin.

Değişmezlik yüzeyseldir

Demet, içinde tuttuğu referansları değiştirmene izin vermez; ama referansın gösterdiği nesne değişebilir türdeyse (liste, sözlük) o nesne değişebilir. Bu yüzden içinde liste olan bir demet "değişmez" görünse de içeriği değişir ve karması (hash) hesaplanamaz.

kayit = ("AM-7", ["kalem"])
kayit[1].append("defter")       # demeti değil, içindeki listeyi değiştiriyoruz
print(kayit)
try:
    hash(kayit)
except TypeError as h:
    print("Anahtar/küme elemanı olamaz:", h)
sabit = ("AM-7", ("kalem", "defter"))  # iç kısım da demet: tamamen değişmez
print(hash(sabit) == hash(("AM-7", ("kalem", "defter"))))
Tek elemanlı demet ekleme tuzağıkayit[:2] + (yeni,) doğru, kayit[:2] + (yeni) yanlış: virgülsüz parantez yalnızca gruplar ve "demete sayı eklenemez" anlamında TypeError alırsın.

Kümeyi yerinde güncellemek

İlk bölümde |, &, -, ^ ile yeni küme üretmeyi gördün. Büyük bir kümeyi adım adım daraltırken her seferinde yenisini kurmak yerine kümeyi yerinde (in-place) güncelleyebilirsin.

YöntemNe yapar?Dönen değerEleman yoksa
k.add(x)Tek eleman eklerNone-
k.update(liste) / k |= bBirçok eleman eklerNone-
k.remove(x)SilerNoneKeyError
k.discard(x)SilerNoneSessiz geçer
k.pop()Rastgele bir eleman çıkarırÇıkan elemanBoşsa KeyError
k -= b / k.difference_update(b)b'dekileri çıkarırNone-
k &= b / k.intersection_update(b)Yalnız ortakları tutarNone-
a.isdisjoint(b)Ortak eleman yok mu?bool-
a.issuperset(b) / a >= bb'nin tamamı a'da mı?bool-
hedef = set(range(1001, 1011))          # 10 müşteri
kara_liste = [1003, 1007, 1099]
izin_yok = {1001, 1007}
hedef -= set(kara_liste)                 # 1099 hiç yoktu, sorun değil
hedef.difference_update(izin_yok)
print(len(hedef), sorted(hedef))
print(hedef.isdisjoint(izin_yok))        # artık ortak yok: True
sonuc = hedef.update({2001})             # update None döndürür!
print(sonuc)

Metot adının sonunda _update görüyorsan ya da add, remove, discard gibi bir fiilse işlem yerinde yapılır ve None döner. hedef = hedef.update(...) yazmak kümeni kaybettirir.

Sırayı koruyarak tekrar kaldırmak

set(liste) tekrarları siler ama sırayı bozar. Raporda ilk görülme sırası önemliyse "görülenler kümesi" kalıbını kullan: kümeyi hızlı üyelik testi için, listeyi sıra için tutarsın.

sehirler = ["İzmir", "Ankara", "İzmir", "Bursa", "Ankara", "Adana"]
gorulen = set()
sirali = []
for s in sehirler:
    if s not in gorulen:
        gorulen.add(s)
        sirali.append(s)
print(sirali)
print(list(dict.fromkeys(sehirler)))   # aynı sonuç, tek satır

İkinci satırdaki dict.fromkeys hilesini Sözlükler dersinden sonra daha iyi anlayacaksın: sözlükler ekleme sırasını korur ve anahtarları benzersizdir. Küme kurmanın tek satırlık yolu olan küme comprehension'ı ({s.strip() for s in sehirler}) ise Comprehension dersinde göreceksin.

Kayıt tekilleştirme ve frozenset

Demetler değişmez olduğu için kümeye girebilir; bu, aynı (müşteri, ürün) kaydının iki kez yüklenmesi gibi hataları yakalamanın en kısa yoludur. Sıranın önemsiz olduğu ikililerde (ör. "birlikte alınan iki ürün") ise frozenset kullanırsın: frozenset({"çay", "şeker"}) ile frozenset({"şeker", "çay"}) eşittir.

yuklenen = [("M-1", "çay"), ("M-2", "kahve"), ("M-1", "çay"), ("M-3", "çay")]
print(len(yuklenen), "satır,", len(set(yuklenen)), "benzersiz kayıt")

ikili_1 = frozenset({"çay", "şeker"})
ikili_2 = frozenset({"şeker", "çay"})
print(ikili_1 == ikili_2, len({ikili_1, ikili_2}))
print(("çay", "şeker") == ("şeker", "çay"))   # demette sıra önemli
Gerçek işteVeri ekiplerinin en sık yaptığı işlerden biri mutabakattır (reconciliation): CRM'deki müşteri numaralarıyla muhasebe sistemindekiler tutuyor mu, depodaki SKU listesi ile e-ticaret sitesindeki aynı mı? Bu soruların ilk cevabı neredeyse her zaman iki kümenin farkıdır. Excel'de DÜŞEYARA ile saatler süren karşılaştırma, kümeye çevrilmiş iki listeyle saniyeler sürer; çıkan "eksik" ve "fazla" listeleri ilgili ekibe gönderilir.
Uzmanlaş

Demet neden sözlük anahtarı olabilir?

Sözlükler ve kümeler elemanı bulmak için karma (hash) değeri kullanır. Karma, nesnenin içeriğinden hesaplanır; içerik değişirse karma da değişir ve nesne kaybolur. Demet değişmez olduğundan karması sabittir, bu yüzden {("İzmir", 2025): 1500} gibi bileşik anahtarlar kurabilirsin. Şehir-yıl bazlı özetlerde çok işe yarar.

namedtuple ve dataclass'a doğru

sube[1] okunur değildir; enlem mi boylam mı? collections.namedtuple demete alan adı verir: sube.enlem. Python 3.7'den beri dataclass aynı işi daha güçlü yapar. Orta seviyede ikisini de göreceksin; şimdilik demet, "küçük ve sabit kayıt" için yeterli.

Büyük veri setlerinde küme

İki büyük listeyi "hangi elemanlar ortak" diye iç içe döngüyle karşılaştırmak eleman sayısının karesiyle büyür: on bin eleman için yüz milyon karşılaştırma. Kümeye çevirip kesişim almak doğrusal zamanda biter. Analist olarak öğreneceğin ilk performans kuralı budur: üyelik ve karşılaştırma işlerinde listeyi kümeye çevir.

Alıştırmalara geç (20)