İ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.
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.
{[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?
| İşlem | Operatör | Metot | Anlamı |
|---|---|---|---|
| Birleşim | a | b | a.union(b) | Herhangi birinde olanlar |
| Kesişim | a & b | a.intersection(b) | İkisinde de olanlar |
| Fark | a - b | a.difference(b) | a'da olup b'de olmayanlar |
| Simetrik fark | a ^ b | a.symmetric_difference(b) | Yalnızca birinde olanlar |
| Alt küme | a <= b | a.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?
| Özellik | Liste | Demet | Küme |
|---|---|---|---|
| Yazım | [1, 2] | (1, 2) | {1, 2} |
| Sıralı | Evet | Evet | Hayır |
| Değiştirilebilir | Evet | Hayır | Evet (elemanlar değişmez) |
| Tekrar | Olabilir | Olabilir | Olamaz |
| İndeks | Var | Var | Yok |
| Tipik iş | Sıralı kayıtlar | Sabit yapılı kayıt, çoklu dönüş | Benzersizlik, üyelik, karşılaştırma |
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.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.
"İ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"))))
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öntem | Ne yapar? | Dönen değer | Eleman yoksa |
|---|---|---|---|
k.add(x) | Tek eleman ekler | None | - |
k.update(liste) / k |= b | Birçok eleman ekler | None | - |
k.remove(x) | Siler | None | KeyError |
k.discard(x) | Siler | None | Sessiz geçer |
k.pop() | Rastgele bir eleman çıkarır | Çıkan eleman | Boşsa KeyError |
k -= b / k.difference_update(b) | b'dekileri çıkarır | None | - |
k &= b / k.intersection_update(b) | Yalnız ortakları tutar | None | - |
a.isdisjoint(b) | Ortak eleman yok mu? | bool | - |
a.issuperset(b) / a >= b | b'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
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.