Ders 01 / 16
Listeler
Liste öğeleri değil onlara giden bağları tutar; bin öğe koymak 1000 nesne var eder ama listeyi ikinci bir ada bağlamak 0, aynı öğeyi beş kez eklemek 1 var eder ve sekiz büyütme biçiminden yalnız üçü yeni kap kurar.
İçindekiler
Python Temelleri kursu on altı derste tek bir iddiayı ölçtü: sözdizim bir kısaltmadır ve her
biçim adı belli bir özel yöntemi çağırır. Kapanışında bıraktığı ayrım şuydu: protokolü
kullanmakla protokolü yazmak ayrı şeylerdir. Orada ölçülen nesne kurgu bir nesneydi;
hiçbir yerleşik koleksiyondan inmiyordu ve for içine girebilmesinin tek gerekçesi ilgili
yöntemleri tanımlamış olmasıydı.
Bu kursta aynı protokolleri dilin kendi kapları taşır. Onlar da aynı yöntemleri tanımlar, ama artık bir gösteri için değil, gerçek veri için. Ve gerçek veriyle birlikte soru da değişir: bir kabın ne yapabildiği değil, neye mal olduğu. Bu dersin sorusu budur — bir liste kurulduğunda ortaya kaç nesne çıkar, bunların kaçı listenin kendi ürünüdür ve liste onların kaçını canlı tutar?
Kap Neyi Tutar
Bir liste (list) öğelerini içine almaz. Sıralı bir yuva dizisi tutar ve her yuva
bir nesneye giden bağdır. Bu ayrım Python’a özgü değil, nesne modelinin doğrudan sonucudur:
bir ada değer bağlamak nesneyi kopyalamaz, ona bir ad daha verir. Programlama Temelleri
kursunda atamanın bir bağlama olduğu, is ile == ayrımı ve takma ad kavramı zaten
kuruldu ve burada tekrarlanmaz. Yeni olan şey, aynı bağlamanın bir ada değil bir yuvaya
yapılabilmesidir.
Sonucu şudur: listeyi büyüten her işlem yuva sayısını artırır, ama nesne sayısını artırmayabilir. Aynı nesne beş yuvada durabilir; o zaman liste beş uzunluktadır ve içinde tek bir nesne vardır.
Sıralı kapların kendi yapı kuramı — dizi, dinamik dizi, bağlı liste, büyüme oranı — Veri Yapıları kursunda kuruldu ve burada tekrarlanmaz. Orada düzenek kuruldu; burada Python’un o düzeneği hangi kapla verdiği ve bunun kaç nesneye mal olduğu ölçülüyor.
Ortak Kurgu: Her Üretimini Sayan Öge
Ölçüm için tek bir sınıf yeter. Oge, her üretiminde ortak bir sayacı bir artırır; başka
hiçbir şey yapmaz. Bir işlemin kaç nesne var ettiğini bilmek için sayacı sıfırlamak, işlemi
yapmak ve sayacı okumak yeter. Kâhin kurgunun kendisidir: üretimi nesne kendi saydığı için,
paylaşımı is gösterdiği için biliriz. Kurs boyunca üç sayı ayrı ayrı yazılır — var
edilen nesne, tutulan yuva ve bunların kaçının ayrı nesneye gittiği.
Nesnelerin kimliği hiçbir yerde sayı olarak basılmaz. İki yuvanın aynı nesneye gidip
gitmediği yalnız is ile sınanır; ayrı nesne sayısı da bu sınamaların toplamıdır.
- KL1 — Sayaç yalnız
Ogeüretimini sayar; listenin, demetin ya da başka bir kabın kendisi sayılmaz. Kap sayısı ayrıca, kimlik sınamasıyla okunur. - KL2 — Her satırda sayaç işlemden hemen önce sıfırlanır; ölçülen şey yalnız o satırın var ettiğidir.
- KL3 — Ayrı nesne sayısı
iskarşılaştırmalarıyla bulunur; hiçbir kimlik değeri basılmaz ve sayıya bakılarak kimlik hakkında başka bir çıkarım yapılmaz. - KL4 — Bin öğelik ölçüm listenin büyüme davranışını değil, yalnız var edilen nesne sayısını sınar; büyüme sınıfı Algoritmalar kursunun konusudur ve burada ölçülmez.
- KL5 — Çarpma ölçümünde çarpılan liste tek öğeliktir; böylece çarpımın yuva çoğaltmakla nesne çoğaltmak arasındaki farkı tek başına görünür.
"""Liste bir kaptir: yuvalari tutar, ogeleri kendisi var etmez.""" SAYAC = {"uretilen": 0} class Oge: """Her uretimi sayan oge.""" def __init__(self, deger): SAYAC["uretilen"] += 1 self.deger = deger def __repr__(self): return f"Oge({self.deger})" def sifirla(): SAYAC["uretilen"] = 0 def uretilen(): return SAYAC["uretilen"] def ayri(kap): """Kimlikce ayri nesne sayisi. Kimlik `is` ile sinanir, sayisi basilmaz.""" benzersiz = [] for x in kap: if not any(x is y for y in benzersiz): benzersiz.append(x) return len(benzersiz) N = 1000 print(f"{'işlem':<32s}{'var edilen':>11s}{'yuva':>6s}{'ayrı nesne':>12s}") sifirla() liste = [] for i in range(N): liste.append(Oge(i)) print(f"{'bin öğeyi listeye koymak':<32s}{uretilen():>11d}{len(liste):>6d}" f"{ayri(liste):>12d}") sifirla() takma = liste print(f"{'listeyi ikinci bir ada bağlamak':<32s}{uretilen():>11d}" f"{len(takma):>6d}{ayri(takma):>12d}") sifirla() tek = Oge(0) elle = [tek, tek, tek, tek, tek] print(f"{'aynı öğeyi beş kez eklemek':<32s}{uretilen():>11d}{len(elle):>6d}" f"{ayri(elle):>12d}") sifirla() carpim = [tek] * 5 print(f"{'tek öğelik listeyi beşle çarpmak':<32s}{uretilen():>11d}" f"{len(carpim):>6d}{ayri(carpim):>12d}") print() print(f"takma is liste -> {takma is liste} | " f"carpim[0] is carpim[4] -> {carpim[0] is carpim[4]} | " f"carpim[0] is tek -> {carpim[0] is tek}") print(f"liste[0] is liste[1] -> {liste[0] is liste[1]}")
işlem var edilen yuva ayrı nesne bin öğeyi listeye koymak 1000 1000 1000 listeyi ikinci bir ada bağlamak 0 1000 1000 aynı öğeyi beş kez eklemek 1 5 1 tek öğelik listeyi beşle çarpmak 0 5 1 takma is liste -> True | carpim[0] is carpim[4] -> True | carpim[0] is tek -> True liste[0] is liste[1] -> False
Dört Satırın Söylediği
Birinci satır beklendiği gibi: bin öğe koymak 1000 nesne var eder, 1000 yuva tutar
ve bunların hepsi ayrı nesnedir. Ama bu 1000 nesneyi var eden şey liste değildir. Onları
Oge(i) çağrısı üretti; liste yalnız yuvaları tuttu. Aynı bin nesne hiç listeye konmadan da
var edilebilirdi ve sayaç yine 1000 gösterirdi.
İkinci satır bunun kanıtıdır: aynı listeyi ikinci bir ada bağlamak 0 nesne var eder.
Yuva sayısı değişmez, ayrı nesne sayısı değişmez, sadece nesneye giden ad sayısı bir artar —
ve takma is liste bunu doğrular. Bin öğelik bir listeyi bir işleve geçirmek de aynı şeydir
ve maliyeti aynıdır: sıfır.
Üçüncü satır ayrımı tersinden gösteriyor. Beş yuva var, ama var edilen nesne 1 ve ayrı
nesne 1. Liste beş uzunlukta, içinde tek bir nesne. Dördüncü satır aynı sonucu
sözdizimin kendisiyle üretiyor: [tek] * 5 hiçbir şey var etmiyor — 0 — ve beş yuvanın
beşi de aynı nesneye gidiyor. Çarpma yuva çoğaltır, nesne çoğaltmaz.
Son iki satır kimlik sınamalarını veriyor. carpim[0] is carpim[4] True: çarpımın
ürettiği yuvalar aynı nesneyi paylaşıyor. liste[0] is liste[1] ise False: bin öğelik
listede yuvaların hepsi ayrı nesneye gidiyor, çünkü her biri ayrı bir Oge(i) çağrısından
geldi. İki liste de beş ya da bin uzunluktadır; farkı uzunluk değil, yuvaların nereye
baktığı belirler.
Büyütmenin Biçimleri
Listeyi büyütmenin birden çok yazımı vardır ve hepsi aynı uzunluğu vermez, aynı kabı
vermez. Python Temelleri kursunda n + m ile n += m ayrımı hangi yöntemi çağırdıklarıyla
ölçülmüştü: ilki __add__, ikincisi __iadd__. Burada ölçülen şey aynı ayrımın kaç kap
var ettiğidir. İkinci ölçüm sekiz yazımı yan yana koyar ve her birine üç soru sorar: kaç
yeni öğe var edildi, sonuç aynı kap mı kaldı, ve yuvaların kaçı eskiden var olan bir
nesneye gidiyor.
- KL6 — Sekiz biçim de aynı beş öğelik havuzun tazeden kurulmuş bir listesiyle başlar; havuzun öğeleri ölçümden önce var edilir ve sayaç her biçimden önce sıfırlanır.
- KL7 — “Aynı kap” sütunu, işlemden önce tutulan bağla sonucun
iskarşılaştırmasıdır; kap sayısı basılmaz, yalnız aynılık sınanır. - KL8 — “Eski nesneye giden yuva” sütunu, sonuçtaki her yuvanın havuzdaki ya da ek
listedeki bir nesneyle
iskarşılaştırılmasıyla bulunur. - KL9 — Sıralama ölçümü bir anahtar işleviyle yapılır; öğe sınıfına karşılaştırma yöntemi eklenmez, böylece sayma düzeni değişmez. Sıralamanın kaç karşılaştırma yaptığı bu derste ölçülmez.
"""Listeyi buyutmenin bicimleri: hangisi yeni kap kurar, hangisi var olani buyutur.""" SAYAC = {"uretilen": 0} class Oge: def __init__(self, deger): SAYAC["uretilen"] += 1 self.deger = deger def __repr__(self): return f"Oge({self.deger})" def sifirla(): SAYAC["uretilen"] = 0 def uretilen(): return SAYAC["uretilen"] def deger(o): return o.deger HAVUZ = [Oge(i) for i in (3, 1, 4, 1, 5)] EK = [Oge(i) for i in (9, 2, 6)] ESKILER = HAVUZ + EK def sona_ekle(a): a.append(Oge(0)) return a def basa_ekle(a): a.insert(0, Oge(0)) return a def uzat(a): a.extend(EK) return a def toplama(a): return a + EK def yerinde_toplama(a): a += EK return a def carpma(a): return a * 2 def siralama(a): return sorted(a, key=deger) def yerinde_siralama(a): a.sort(key=deger) return a BICIMLER = (("a.append(yeni)", sona_ekle), ("a.insert(0, yeni)", basa_ekle), ("a.extend(b)", uzat), ("a = a + b", toplama), ("a += b", yerinde_toplama), ("a = a * 2", carpma), ("a = sorted(a, ...)", siralama), ("a.sort(...)", yerinde_siralama)) print(f"{'biçim':<20s}{'yeni öğe':>9s}{'aynı kap':>10s}{'yuva':>6s}" f"{'eski nesneye giden yuva':>25s}") for ad, islem in BICIMLER: a = list(HAVUZ) once = a sifirla() sonuc = islem(a) eski = sum(1 for x in sonuc if any(x is y for y in ESKILER)) print(f"{ad:<20s}{uretilen():>9d}{str(sonuc is once):>10s}" f"{len(sonuc):>6d}{eski:>25d}")
biçim yeni öğe aynı kap yuva eski nesneye giden yuva a.append(yeni) 1 True 6 5 a.insert(0, yeni) 1 True 6 5 a.extend(b) 0 True 8 8 a = a + b 0 False 8 8 a += b 0 True 8 8 a = a * 2 0 False 10 10 a = sorted(a, ...) 0 False 5 5 a.sort(...) 0 True 5 5
Aynı Uzunluk, Ayrı Bedel
“Yeni öğe” sütunu sekiz satırın altısında 0. Yalnız append ve insert birer nesne var
ediyor, o da eklenmek üzere yazılan Oge(0) çağrısından. Geri kalan altı biçim tek bir öğe
bile üretmiyor: hepsi var olan nesnelere giden yeni yuvalar açıyor ya da mevcut yuvaları
yeniden düzenliyor. Son sütun bunu satır satır doğruluyor — yuvaların tamamı, eklenen taze
öğe dışında, ölçümden önce var edilmiş nesnelere gidiyor.
Asıl ayrım “aynı kap” sütununda. a.extend(b) ile a = a + b aynı sekiz öğeli sonucu
veriyor, ama biri var olan kabı büyütüyor, öbürü yeni bir kap kurup adı ona bağlıyor.
a += b ise adına rağmen toplama değil, uzatma yapıyor: sonuç aynı kaptır. Python
Temelleri kursunda bu ikisinin ayrı özel yöntem çağırdığı sayılmıştı; buradaki sayı o
çağrının sonucudur. Farkın gözlenebilir olması için listeye başka bir ad bağlı olması yeter:
a += b yazıldığında o ad da uzamış listeyi görür, a = a + b yazıldığında eski beş öğeli
listede kalır.
sorted ile sort aynı ikiliği sıralama tarafında tekrarlıyor. İkisi de 0 yeni öğe
üretiyor ve ikisinin de sonucunda 5 yuva var; fark, birinin yeni bir kap döndürüp öbürünün
var olan kabı yerinde yeniden dizmesi. sort bir liste döndürmez, None döndürür — çünkü
döndürecek yeni bir kap yoktur.
Bir şey bu tablonun dışındadır ve tabloya bakarak bulunamaz: append ile insert(0, ...)
aynı sayıları veriyor, oysa yaptıkları iş aynı değildir. Başa eklemek kendinden sonraki bütün
yuvaları kaydırmayı gerektirir, sona eklemek gerektirmez. Bu fark nesne sayısında görünmez,
çünkü kaydırma nesne var etmez — yalnız yuvaları yeniden yazar. Kaydırmanın büyüme sınıfı
Algoritmalar kursunda kuruldu ve burada tekrarlanmaz; bu dersin ölçtüğü eksende iki biçim
gerçekten eşittir.
Silmek Neyi Serbest Bırakır
Ekleme tarafı bir kabın nesne var etmediğini gösterdi. Silme tarafı bunun karşılığını gösterir: bir kap nesne yok da etmez. Silme işlemleri yalnız yuvayı kapatır; nesnenin yaşamaya devam edip etmediğini ona bağlı kalan başka adlar ve başka yuvalar belirler. Üçüncü ölçüm bir listeyi dört ayrı yazımla boşaltır ve her adımda aynı öğeye bağlı ikinci bir kaba bakar.
- KL10 — Ölçüm tek bir liste üzerinde sırayla ilerler; her satır bir öncekinin bıraktığı durumdan devam eder ve yuva sayıları bu yüzden azalarak gider.
- KL11 — İkinci kap ölçüm başında kurulur ve ölçüm boyunca hiç dokunulmaz; ondaki değişiklik yalnız birinci kaptan gelebilirdi.
- KL12 — Eşitliğe bakan silme, öğe sınıfı eşitlik tanımlamadığı için kimliğe düşer; bu ders eşitlik tanımlayan bir öğeyle ölçüm yapmaz.
"""Silmek nesneyi yok etmez, bagi koparir. Paylasilan oge oteki kapta durur.""" SAYAC = {"uretilen": 0} class Oge: def __init__(self, deger): SAYAC["uretilen"] += 1 self.deger = deger def __repr__(self): return f"Oge({self.deger})" def sifirla(): SAYAC["uretilen"] = 0 def uretilen(): return SAYAC["uretilen"] sifirla() a = [Oge(i) for i in range(5)] elde = a[2] c = [a[0], a[2]] kurulum = uretilen() print(f"{'işlem':<20s}{'var edilen':>11s}{'a yuva':>8s}{'c yuva':>8s}" f"{'c[1] is elde':>14s}") print(f"{'beş öğeyle kurmak':<20s}{kurulum:>11d}{len(a):>8d}{len(c):>8d}" f"{str(c[1] is elde):>14s}") def sondan_al(): a.pop() def bastan_sil(): del a[0] def esitle_sil(): a.remove(elde) def bosalt(): a.clear() for ad, islem in (("a.pop()", sondan_al), ("del a[0]", bastan_sil), ("a.remove(elde)", esitle_sil), ("a.clear()", bosalt)): sifirla() islem() print(f"{ad:<20s}{uretilen():>11d}{len(a):>8d}{len(c):>8d}" f"{str(c[1] is elde):>14s}") print() print(f"a boşaldıktan sonra elde tutulan öğe: {elde} | c: {c}")
işlem var edilen a yuva c yuva c[1] is elde beş öğeyle kurmak 5 5 2 True a.pop() 0 4 2 True del a[0] 0 3 2 True a.remove(elde) 0 2 2 True a.clear() 0 0 2 True a boşaldıktan sonra elde tutulan öğe: Oge(2) | c: [Oge(0), Oge(2)]
Dört silme yazımı da 0 nesne var ediyor — bu beklenirdi. Söylediği asıl şey sağdaki iki
sütunda. Birinci kabın yuvası beşten sıfıra inerken ikinci kabın yuvası 2’de sabit
kalıyor ve c[1] is elde her satırda True. Birinci liste tamamen boşaldıktan sonra bile
paylaşılan öğe okunabiliyor: son satır Oge(2) basıyor.
Buradan çıkan kural şudur: bir kaptan silmek, o kabın o nesneye giden bağını koparır; nesneyi değil. Nesne, ona bağlı kalan başka bir ad ya da başka bir yuva olduğu sürece durur. Bin öğelik bir listeyi boşaltmak, o bin nesnenin serbest kalacağı anlamına gelmez — öğelerin bir kısmı başka bir kapta duruyorsa orada durmayı sürdürür. Bir kabın “tuttuğu” nesne bu yüzden ölçülen üç sayıdan biridir: var edilen nesne sayısı işi kimin yaptığını, tutulan yuva sayısı kabın büyüklüğünü, paylaşım ise silmenin gerçekte neyi serbest bıraktığını söyler.
Eşitliğe bakan silme bir ayrıntı ekliyor. remove bir yuvayı kimliğine göre değil,
eşitliğine göre bulur; ölçümdeki öğe sınıfı eşitlik tanımlamadığı için eşitlik burada
kimliğe düşüyor ve iki ölçüt aynı sonucu veriyor. Eşitlik tanımlayan bir öğede ikisi
ayrışırdı: remove eşit görünen ilk yuvayı silerdi, elde tutulan nesneyi değil.
Özet
- Liste öğeleri içine almaz; her yuva bir nesneye giden bağdır. Bin öğe koymak 1000 nesne var eder, listeyi ikinci bir ada bağlamak 0.
- Yuva sayısı ile nesne sayısı ayrı sayılardır: aynı öğeyi beş kez eklemek 5 yuva ve
1 ayrı nesne verir,
[tek] * 5ise 0 nesne var edip beş yuvayı aynı nesneye bağlar. - Büyütmenin sekiz biçiminden yalnız
appendveinsertyeni öğe var eder; kalan altısı yalnız yuva açar ve yuvaların hepsi eskiden var olan nesnelere gider. a = a + b,a = a * 2vesortedyeni bir kap kurar;extend,a += bvesortvar olan kabı yerinde değiştirir — aynı uzunluk, ayrı kap.- Nesne sayısı her farkı göstermez:
appendileinsert(0, ...)bu eksende eşittir, çünkü yuva kaydırmak nesne var etmez.
Sonraki Adım
Bu dersin bütün ölçümleri listenin değiştirilebilir olmasına dayanıyordu: yuva eklenebiliyor, silinebiliyor, yeniden dizilebiliyordu. Peki bir kap bunların hiçbirine izin vermezse ne kazanır? Sonraki ders aynı ölçümleri değişmez bir sıralı kapta yineler ve iki soruyu sorar: değişmezlik hangi işlemi gerçekten imkânsız kılar, ve bu imkânsızlığın karşılığında kabın kazandığı yetki nedir? Yanıtın bir ucu, listenin yapamadığı bir işte görünecek — bir kabın başka bir kabın anahtarı olması.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.