Yüzeysel ve Derin Kopyalama

Giriş

Deep Sea Squid

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:

In [2]:
x = 3
y = x
print(id(x), id(y))
140717961224656 140717961224656
In [3]:
y = 4
print(id(x), id(y))
140717961224656 140717961224688
In [4]:
print(x,y)
3 4

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

In [2]:
renk1 = ["kırmızı", "mavi"]
renk2 = renk1
print(renk1)
['kırmızı', 'mavi']
In [3]:
print(renk2)
['kırmızı', 'mavi']
In [4]:
print(id(renk1),id(renk2))
2208872202056 2208872202056
In [5]:
renk2 = ["kızıl", "yeşil"]
print(renk1)
['kırmızı', 'mavi']
In [6]:
print(renk2)
['kızıl', 'yeşil']
In [7]:
print(id(renk1),id(renk2))
2208872202056 2208870468040

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.

Deep Copy Detailed

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:

In [11]:
renk1 = ["kırmızı", "mavi"]
renk2 = renk1
print(id(renk1),id(renk2))
2076299166792 2076299166792
In [12]:
renk2[1] = "yeşil"
print(id(renk1),id(renk2))
print(renk1)
2076299166792 2076299166792
['kırmızı', 'yeşil']
In [13]:
print(renk2)
['kırmızı', 'yeşil']

Copying a simple list

Ş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:

In [16]:
liste1 = ['a','b','c','d']
liste2 = liste1[:]
liste2[1] = 'x'
print(liste2)
['a', 'x', 'c', 'd']
In [17]:
print(liste1)
['a', 'b', 'c', 'd']

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.

In [1]:
lst1 = ['a','b',['ab','ba']]
lst2 = lst1[:]
lst2
Out[1]:
['a', 'b', ['ab', 'ba']]

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:

Copying a list with sublists

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.

In [18]:
lst1 = ['a','b',['ab','ba']]
lst2 = lst1[:]
lst2[0] = 'c'
print(lst1)
['a', 'b', ['ab', 'ba']]
In [19]:
print(lst2)
['c', 'b', ['ab', 'ba']]

Copying lists containing sublists

Alt listenin öğlerinden birini değiştirirseniz sorunlar çıkmaya başlayacaktır:

In [20]:
lst2[2][1] = 'd'
print(lst1)
['a', 'b', ['ab', 'd']]
In [21]:
print(lst2)
['c', 'b', ['ab', 'd']]

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.

Changing a value within a sublist

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:

In [22]:
from copy import deepcopy
lst1 = ['a','b',['ab','ba']]
lst2 = deepcopy(lst1)
lst1
Out[22]:
['a', 'b', ['ab', 'ba']]
In [23]:
lst2
Out[23]:
['a', 'b', ['ab', 'ba']]
In [24]:
id(lst1)
Out[24]:
2076299944648
In [25]:
id(lst2)
Out[25]:
2076298959752
In [26]:
id(lst1[0])
Out[26]:
2076256889904
In [27]:
id(lst2[0])
Out[27]:
2076256889904
In [28]:
id(lst2[2])
Out[28]:
2076299152712
In [29]:
id(lst1[2])
Out[29]:
2076299888072

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:

Copy a list by using deepcopy from the module

In [30]:
lst2[2][1] = "d"
lst2[0] = "c"
print(lst1)
['a', 'b', ['ab', 'ba']]
In [31]:
print(lst2)
['c', 'b', ['ab', 'd']]

Şimdi, veri yapımız böyle görünüyor:

Final Structure