Yüzeysel ve Derin Kopyalama¶
Giriş¶
Bu bölümde listeleri ve iç içe geçmiş listeleri nasıl kopyalayacağımız sorunu cevaplayacağız. Listeleri kopyalamaya çalışmak yeni başlayanlar için zorlu bir deneyim olabilir. Ancak şimdi, önceki konumuz olan “Veri Türleri ve Değişkenler” bölümünden bazı fikirleri özetleyeceğiz. Python bile, dile yeni başlayanlar için – daha geleneksel programlama dilleriyle karşılaştırınca – tamsayılar ve karakter dizileri gibi basit veri türlerini tanımlarken ve kopyalarken garip bir davranış sergiler. Yüzeysel ve derin kopyalama arasındaki fark sadece bileşik nesneler için geçerlidir, yani başka nesneleri içeren nesnelerde geçerlidir (listeler veya sınıflar gibi).
Aşağıdaki kod parçasında, y ile x aynı hafıza konumuna gönderme yapıyor. Bunu anlamak için x ve y üzerinde id() fonksiyonunu kullanabiliriz. Ancak C ve C++’daki gibi “gerçek” işaretçilerden farklı olarak, y değişkenine yeni bir değer atadığımızda durumlar değişir. “Veri Türleri ve Değişkenler” bölümünde gördüğümüz gibi y ayrı bir hafıza konumu elde edecektir. Aşağıdaki örnekleri inceleyiniz:
x = 3
y = x
print(id(x), id(y))
y = 4
print(id(x), id(y))
print(x,y)
Bu içsel davranış C, C++ ve Perl gibi programlama dilleriyle karşılaştırıldığında garip görünse bile, atamaların gözlenebilen sonuçları beklentilerimizi karşılıyor. Değişebilir nesneler olan listeler ve sözlükleri kopyalarsak sorunlar ortaya çıkabilir.
Python yalnızca zorunlu ise gerçek kopyaları oluşturur, yani kullanıcı veya programcı bunu talep ederse yapar. Değiştirilebilir nesneler olan listeleri ve sözlükleri kopyalarken oluşabilecek çok önemli hataları size göstereceğiz.
Bir Listeyi Kopyalamak¶
renk1 = ["kırmızı", "mavi"]
renk2 = renk1
print(renk1)
print(renk2)
print(id(renk1),id(renk2))
renk2 = ["kızıl", "yeşil"]
print(renk1)
print(renk2)
print(id(renk1),id(renk2))
Yukarıdaki örnekte basit bir liste renk1 değişkenine atandı. Bu listeyi “yüzeysel liste” olarak tanımlıyoruz, çünkü girintili bir yapısı yok, yani listede alt listeler bulunmuyor. Sonraki adımda renk1’i renk2’ye atıyoruz.
Id() fonksiyonu, her iki değişkenin aynı liste nesnesine gönderme yaptığını gösteriyor, yani bu nesneyi paylaşmış oluyorlar.
Image translation: colours1: renk1, colours2: renk2, list object: liste nesnesi, string objects: karakter dizisi nesneleri, “red”: “kırmızı”, “blue”: “mavi”
Şimdi renk2’ye yeni bir liste nesnesi atarsak neler olacağını görelim. Beklediğimiz gibi, renk1’in değerleri değişmedi. "Data Types and Variables" bölümündeki örnekte olduğu gibi, renk2’ye yeni bir hafıza konumu ayarlandı, çünkü tamamen yeni bir liste oluşturduk, yani bu değişkene yeni bir liste nesnesi atadık.
Görsel için bazı açıklamalar yapalım: Yeşil renkli kutular olarak gösterdiğimiz iki değişkenimiz var (renk1 ve renk2). Mavi kutu liste nesnesini temsil ediyor. Bir liste nesnesi diğer nesnelere başvuru içerir. Örneğimizde liste nesnesi her iki değişken tarafından başvuruya alınıyor ve “kırmızı” ile “mavi” isimli iki karakter dizisi nesnesini içeriyor. Şimdi renk2 veya renk1 listelerinden birer öğe değiştirirsek neler olacağını görelim:
renk1 = ["kırmızı", "mavi"]
renk2 = renk1
print(id(renk1),id(renk2))
renk2[1] = "yeşil"
print(id(renk1),id(renk2))
print(renk1)
print(renk2)
Şimdi de önceki kodda neler olduğuna bakalım. Renk2 listesinin ikinci öğesine yeni bir değer atadık, yani 1 indeksine öğe yerleştirdik. Yeni başlayan pek çok kişi renk1 listesinin de “otomatik olarak” değişmesi karşısında çok şaşırır. Elbette, iki listemiz yok; aynı liste için iki farklı adımız var!
Açıklama, renk2 değişkenine yeni bir nesne atamamış olmamızdır. renk2’nin içini değiştirdik ya da bilindiği adı ile “yerinde” değişiklik yaptık. “renk1” ve “renk2” değişkenleri aynı liste nesnesine gönderme yapar.
Dilimleme Operatörü İle Kopyalama¶
Yukarıda bahsettiğimiz olumsuz etkilerden tamamen kurtulacak şekilde, dilimleme operatörü kullanarak yüzeysel liste yapılarını tamamen kopyalamamız mümkündür:
liste1 = ['a','b','c','d']
liste2 = liste1[:]
liste2[1] = 'x'
print(liste2)
print(liste1)
Bir liste alt listeler içerdiği andan itibaren yeni bir zorluk karşımıza çıkıyor: Alt listeler kopyalanmaz, ancak sadece alt listelere göndermeler işlenir.
lst1 = ['a','b',['ab','ba']]
lst2 = lst1[:]
Örnek listemiz olan “lst2” bir alt liste barındırıyor. Dilimleme operatörü ile yüzeysel bir kopya oluşturuyoruz.
lst1 = ['a','b',['ab','ba']]
lst2 = lst1[:]
lst2
Aşağıdaki diyagram, kopyalama işleminin ardından veri yapılarının nasıl değiştiğini gösteriyor. Lst1[2] ve lst[2] aynı nesneye, yani alt listeye işaret ediyor:
Eğer bu iki listenin birine ait sıfırıncı veya birinci indekse yeni bir değer atarsanız olumsuz bir etkisi olmayacaktır.
lst1 = ['a','b',['ab','ba']]
lst2 = lst1[:]
lst2[0] = 'c'
print(lst1)
print(lst2)
Alt listenin öğlerinden birini değiştirirseniz sorunlar çıkmaya başlayacaktır:
lst2[2][1] = 'd'
print(lst1)
print(lst2)
Aşağıdaki diyagram, yukarıdaki kodu çalıştırdıktan sonraki durumu gösteriyor. lst2[2][1] = ‘d’ atamasından lst1 ve lst2’nin etkilendiğini görüyoruz.
Copy Modülünden Deepcopy Yöntemini Kullanmak¶
Yukarıda bahsettiğimiz sorunlara bir çözüm olarak “copy” modülünü kullanabiliriz. Bu modülde “deepcopy” yöntemi vardır ve yüzeysel ve diğer listeler için tam veya derin bir kopya oluşturmanızı sağlar. Önceki listemiz için deepcopy yöntemini kullanalım:
from copy import deepcopy
lst1 = ['a','b',['ab','ba']]
lst2 = deepcopy(lst1)
lst1
lst2
id(lst1)
id(lst2)
id(lst1[0])
id(lst2[0])
id(lst2[2])
id(lst1[2])
Id fonksiyonu, alt listenin kopyalandığını gösteriyor, çünkü id(lst2[2]) değeri id(lst1[2]) değerinden farklı. İlginç olan durum, karakter dizilerinin kopyalanmamış olması: lst1[0] ve lst2[0] aynı karakter dizisine gönderme yapıyor. Bu elbette lst1[1] ve lst2[1] için de geçerli. Aşağıdaki diyagram, listeyi kopyaladıktan sonraki durumu gösteriyor:
lst2[2][1] = "d"
lst2[0] = "c"
print(lst1)
print(lst2)
Şimdi, veri yapımız böyle görünüyor: