Python oop listeye eklenmiş nesne özellikleri değişiyor

class RandomData:

    

    def __init__(self):

        self.id=0

    def rand_prod(self):


        self.id+=1

        self.name,self.surname=unique_names_generator.get_random_name().split()

        self.address=real_random_address()

        self.birthday=datetime.date(randint(1950,2021),randint(1,12),randint(1,28))

        self.latitude=(18,16)

        self.longtitude=(18,16)

Bir RandomData nesnesi oluşturup for döngüsü içinde rand_prod() metodunu çağırıyorum ve döngü içinde bu verileri objeismi.dict diyerek bir listeye append ediyorum

for i in range(5):
    
    current_rd.rand_prod()
    data_list.append(current_rd.__dict__)

ancak en son listeyi yazdırdığımda liste içindeki 5 dict’in hepsi son for döngüsünde elde edilen veriyi almış oluyor. Nesnenin özelliklerini her değiştirdiğimde liste içine önceden eklediğim eski özellikleri de değişiyor. Bunun sebebi nedir ve nasıl çözebilirim?

Merhaba, current_rd.__dict__ bir sözlük, değil mi? current_rd objesinin (yazılabilir) niteliklerini barındırıyor. Bu sözlüğe S diyelim. Sorun, bu S’nin for döngüsünün her dönüşünde aynı sözlük olması. current_rd değişmiyor, aynı obje: ve eklediğiniz de hep onun sözlüğü.

Peki neden bu sözlüğün o noktadaki "snapshot"ı alınmıyor da listenin en başındaki en sonda yapılan değişikliklerden etkileniyor? Bunun sebebi Python’daki sözlüklerin “mutable” yani yerinde-değiştirilebilir olmaları. Şimdi ilk dönüşte listeye S’yi ekledik. Sonra tekrar eklemeden S üzerinde bazı değişiklikler yapıyoruz (bazı anahtarların değeri değişiyor .rand_prod sayesinde). Bu işlem yeni bir sözlük oluşmasına sebep olmuyor: bildiğimiz S durduğu yerde etkileniyor. Yani S’nin *içeriği* biraz değişti ama S yine aynı S; bu da veri yapısının mutable olması sayesindedir.

Şöyle bir durum:

Listeye ilk eleman ekleniyor: S’nin içeriği "isim": "evet", "zayi": False şeklinde.

Listeye başka bir eleman ekleniyor: bu arada S’nin içeriği değişti: "isim": "evet", "zayi": True, "sira": 12 oldu.

Böyle gidiyor. Tüm eklenen elemanlar aynı tek bir S objesine “bakıyor”. S’nin içeriği değişirse de, hepsi etkileniyor. Dolayısıyla en son dönüşteki değişikler hepsinde aynı şekilde beliriyor. (Şurada (çok) daha iyi bir görselleştirme var, kodu da değiştirebilirsiniz siz kendiniz. Next> ile ilerliyor.)

S’nin her defasında kopyasını alarak. current_rd.__dict__ yerine current_rd.__dict__.copy()'yi .append edebilirsiniz. Ama bu “shallow” bir kopya meydana getiriyor; sizde bazı nitelikler birtakım fonksiyonların arkasına gizlendiği için sizde gideri olur mu bilemiyorum :d Neyse, from copy import deepcopy deyip deepcopy(current_rd.__dict__)'i ekleyebilirsiniz listeye: bunun kopyası derindir.


Bu olay, sözlük yerine mesela tam sayı veya string ekliyor olsaydınız listeye, meydana gelmeyecekti. Çünkü onlar immutable veri yapıları oluyor. Yerinde değişmeleri gibi bir durumları yok; yenileri oluşturuluyor.

a = type("A", (), {})()  # bir nesne
liste = []
for asal in (5, 7, 11):
    a.sayi = asal
    liste.append(a.sayi)

print(liste)
# [11, 11, 11] değil, [5, 7, 11].

Diğer mutable veri yapılarına ise listeleri ve set’leri örnek verebiliriz sözlüklerin yanında.


.copy'nin sığlığı hakkında bir örnek: mesela liste-içinde-liste gibi elemanlar varsa sözlükte, ancak en dıştaki liste kopyalanıyor, içeridekiler yine aynı yere bakıyorlardır:

# bir sözlük
>>> d = {"deger": [12, [13, 14], 15]}

# kopyasını alıyoruz
>>> e = d.copy()

# `d` üzerinde bir değişiklik yapıyoruz
>>> d["deger"][1].insert(1, 13.5)

# `e`'nin etkilenmemesini bekliyoruz, `.copy()` dedik ne de olsa...
# gelin görün ki etkileniyor.
>>>  e
{'deger': [12, [13, 13.5, 14], 15]}

Buna çözüm e = deepcopy(d).

4 Beğeni

Çok iyi bir anlatım teşekkür ederim

1 Beğeni

Bunun sebebi current_rd.__dict__'in bir referans olmasi. Atama (=) baska bir objeye refere etmesini saglayacak, obje uzerinde yapilan islemler (.) ise ayni objeyi refere eden diger degerlere de yansiyacak—klasik referans semantigi.

Ayni sey immutable objeler icin de gecerli, cunku onlar da referans semantigiyle calisiyor:

>>> a = (1, 2, [])
>>> b = a
>>> b[2].append(42)
>>> a
(1, 2, [42])

Onun disinda, copy'den resme, cok guzel bir aciklama; takdir ettim.

1 Beğeni

Teşekkür ederim. Demeye çalışıp da diyemediğim şuydu aslında:

>>> a = 3,
>>> b = a
>>> a += 4,
>>> b
(3,)

Sizdeki örnekteki a tuple’ı hakiki bir şekilde immutable olamıyor elemanlarından birisi mutable olduğu için. Yani a yerinde-değiştirilebilir bir durumda o eleman sayesinde, arada yeni bir tuple oluşturulmuyor diye. Dolayısıyla a ne hakiki immutable ne de hakiki mutable oldu, ve mevzuyu mutability üzerinden aktarmanın da değeri düştü diye anladım. Yapılan değişiklikler sonrası ortamdaki obje sayısı arttı mı artmadı mı sorusuyla ilerlemek gerekiyor diyebiliriz galiba. Zira

bunun olabilmesi için bir çeşit mutability gerekiyor, değil mi? Sözlüklerde hakiki olarak, tuple gibilerinde ise hakiki olan bir eleman barındırarak. Sayılar ve string’lerde namümkün.

Aynen. Mutable–Immutable ayrimi yetmiyor, “hakiki immutable” gibi (dogruyu soyleyelim, komik) ara terimler icat etmek gerekiyor.

Oysa deger–referans semantigi olarak baktigimizda cok acik: Tuple immutable oldugu icin hangisinin oldugunun bir onemi yok. Ucuncu eleman bir dict’e referans ve hangi objeye refere ettigi hic degismiyor. Refere ettigi obje baska bir referans uzerinden degistirilirse, degisiklik bu referans uzerinden de gorunur oluyor.

Evet. Immutable objelerde (veya sadece okuma islemi yapildiginda) referans ile deger semantiklerinin hic bir farki yok.