Tarih: copy modülü, basit referans atamasının ötesinde standartlaşmış nesne çoğaltma sağlamak amacıyla erken Python sürümlerinde tanıtıldı. Geliştiriciler, iç içe yapılar içeren karmaşık nesne grafiklerini çoğaltmak istediklerinde, nesnelerin kendilerini doğrudan veya dolaylı olarak referans göstermesi durumunda sonsuz döngüye neden olan ilk yinelemeli kopyalama uygulamaları, nesne kimliklerini koruma konusunda başarısız oldu.
Problem: Zaten kopyalanmış nesnelerin kaydını tutan bir kayıt defteri olmadan, deepcopy döngüsel referanslarla karşılaştığında sonsuz döngüye girerdi (örneğin, bir üst düğümün bir çocuğu referans göstermesi ve çocuk düğümün tekrar üst düğüme referans göstermesi). Ayrıca, kimlik eşleştirmesi olmadan, grafikteki aynı nesneye olan birden fazla referans, referans eşitliğini korumadan farklı kopyalarla sonuçlanır ve nesne kimliği anlamsını bozar.
Çözüm: Algoritma, id(original_object)'i yeni oluşturulan kopyaya eşleştiren bir memo sözlüğü kullanır. Herhangi bir nesne için kopyalama işlemine başlarken algoritma id(obj)'nin memoda var olup olmadığını kontrol eder; eğer bulunursa, mevcut kopyayı hemen döndürür. Aksi takdirde, yeni bir örnek oluşturur, hemen orijinalin kimliği altında memoya kaydeder (yinelemeli popülasyondan önce) ve ardından özellikleri kopyalamaya devam eder. Bu, döngüsel referansların aynı kopyalanmış örneğe çözülmesini sağlar. Kullanıcı tanımlı sınıflar, bu davranışı özelleştirmek için __deepcopy__(self, memo) yöntemini uygulayabilir ve yinelemeli çağrılara iletmek için memo sözlüğünü alabilir.
Senaryo: Bir bulut altyapı yönetim aracı, veri merkezi topolojisini Server nesneleri grafiği olarak modellemektedir. Her Server, yük dengeleme için bir peers listesi tutar ve kesinti durumunda devreden çıkma için primary düğümüne bir referans içerir. Bu ilişkiler, iki yönlü referanslar oluşturur (Server A, Server B'yi bir akran olarak listeler, Server B ise Server A'yı listeler) ve nesne grafiğinde döngüler oluşturur. Operasyon ekibi, üretim yapılandırma durumunu etkilemeden bu topolojinin bir kopyasını simülasyon testleri için çoğaltmak istemektedir.
Problem Tanımı: Sunucu grafiğini manuel yinelemeli kopyalama ile çoğaltma girişimleri, algoritmanın döngüsel akran referanslarıyla karşılaştığında RecursionError hatasına yol açmıştır. Dahası, bazı paylaşılan yapılandırma nesneleri (örneğin SSL sertifika bağlamları) birden fazla kez kopyalanarak bellek israfına yol açmış ve singleton benzeri bir davranış bekleyen kimlik kontrollerini kırmıştır.
Değerlendirilen Çözümler:
Ziyaret Edilmiş Set ile Manuel Gezinme: Server sınıfında visited sözlüğünü kabul eden özel bir clone() yöntemi uygulamak. Bu yöntem, sunucunun daha önce ziyaret edilip edilmediğini kontrol eder, eğer öyleyse mevcut kopyayı döndürür, ya da yeni bir tane oluşturur ve akranları yinelemeli olarak kopyalar. Artıları: Kopyalama süreci üzerinde tam kontrol, dış bağımlılık yok. Eksileri: Hiyerarşide her sınıf için karmaşık gezinme mantığını uygulamak gerekir, yeni ilişki türleri eklenirse hata yapma olasılığı yüksek ve kopyalama mantığını alan mantığıyla karıştırarak Tek Sorumluluk Prensibi'ni ihlal eder.
JSON Serileştirme Gidiş-Dönüş: Sunucu grafiğini döngüleri işlemek için özel kodlayıcılar kullanarak JSON'a serileştirip, ardından yeni nesnelere deseralize etmek. Artıları: Standart kütüphanelerle basit uygulama. Eksileri: Python'a özgü türleri kaybeder (kümeler listelere, demetler listelere dönüşür), yöntemleri ve davranışı kaybeder, büyük grafikler için performans düşüktür ve kritik olarak döngüsel olmayan paylaşılan referansların nesne kimliğini koruyamaz (aynı yapılandırma nesnesini paylaşan iki sunucu, deseralizasyon sırasında ayrı kopyalar alır).
Standart copy.deepcopy ile Özel Kancalar: Tüm kopyalanamaz kaynakları ele almak için Server sınıfında özel __deepcopy__ uygulamaları ile Python'un copy.deepcopy'ini kullanmak. Artıları: Döngüsel referansları otomatik olarak içsel memo sözlüğü aracılığıyla işler, paylaşılmış nesneler için Python türlerini ve kimliği korur, iyi test edilmiştir ve standarttır. Eksileri: Kopyalama sırasında memo sözlüğü yüzünden hafifçe daha fazla bellek yükü, döngü algısını bozmayacak şekilde memo sözlüğünü doğru bir şekilde iletmek için dikkatli __deepcopy__ uygulamaları gerektirir.
Seçilen Çözüm: Ekip, copy.deepcopy (Seçenek 3) kullanmayı seçti. Server sınıfında yeni bir örnek oluşturmak için self.__class__ kullanarak memo sözlüğünde hemen kaydedilen ve yalnızca seri hale getirilebilen yapılandırma özelliklerini derin kopyalayan __deepcopy__'yi uyguladılar ve soket bağlantılarını ilk kullanımlarında tembel bir şekilde yeniden başlattılar.
Sonuç: Sistem, karmaşık döngüsel akran ilişkileri içeren binlerce sunucu içeren veri merkezi yapılandırmalarını başarıyla çoğalttı. memo sözlüğü, birden fazla sunucu tarafından referans verilen paylaşılan SSL bağlamlarının kopyada paylaşılmasını sağladı ve bellek verimliliğini korudu, döngüsel akran referansları ise döngü hataları olmadan çözüldü.
Neden copy.deepcopy, özel liste veya dict alt sınıflarının örneklerini kopyalarken alt sınıfa özgü özellikleri koruyamaz, oysa elemanları doğru bir şekilde kopyalar?
deepcopy, yerleşik konteyner türleri (liste gibi) veya (dict dahil) sınıfların alt sınıflarıyla karşılaştığında, içerilen elemanları kopyalayıp tam alt sınıfın yeni bir örneğini oluşturan optimize edilmiş hızlı bir yol kullanır. Ancak bu hızlı yol, alt sınıfın __init__ yöntemini atlar ve örneğin __dict__ içinde saklanan özellikleri kopyalamaz. Sonuçta, bir class MyList(list) örneğine eklenen veri veya önbellek gibi özellikler kopya sırasında kaybolur. Bunları korumak için alt sınıfın ek özellikleri ele almak üzere __deepcopy__ yöntemini açıkça uygulaması gerekir veya alternatif olarak, örnek üzerindeki copy.copy'yi kullanarak, ardından özellikleri manuel olarak derin kopyalaması gerekir, böylece alt sınıfa özgü verilerin yeni örneğe aktarılması sağlanır.
memo sözlüğü mekanizması döngüsel nesne grafiklerinde sonsuz döngüyü nasıl önler ve neden bu aynı sözlük nesnesini tüm yinelemeli deepcopy çağrılarına geçirmek kritik öneme sahiptir?
memo sözlüğü, her orijinal nesnenin id()'sini karşılık gelen kopyaya eşleştiren bir eşleme tutar. Herhangi bir nesneyi işlemeye başlamadan önce, deepcopy, id(obj)'nin memoda var olup olmadığını kontrol eder; eğer bulunursa, mevcut kopyayı hemen döndürür, potansiyel döngüleri kırar. Yeni bir kopya oluşturulurken, algoritma memo[id(original)] = new_copy eşlemesini hemen kayıt altına alır ve ardından nesnenin içeriklerini yinelemeli olarak kopyalamaya geçer. Bu, orijinal nesne yinelemeli keşif sırasında tekrar karşılaşıldığında (bir döngüsel referans) kısmen inşa edilmiş olan kopyanın döndürülmesini sağlar, bu da sonsuz döngüyü önler. Tüm yinelemeli çağrılara aynı memo sözlüğünü iletmek esastır çünkü bu, tüm nesne grafiği boyunca kopyalama ilerlemesine dair küresel bir bakış sağlar; yeni sözlükler oluşturmak, grafiğin dallarını izole edeceğinden döngülerin gözden kaçmasına neden olur ve paylaşılan referanslar için nesnelerin kopyalanmasına yol açar.
Özel bir __deepcopy__ uygulaması içinde bir istisna meydana gelirse ve yöntem, nesnenin özelliklerini tamamlamadan önce yeni örneği memo sözlüğünde kaydettikten sonra ne ince hata meydana gelebilir?
__deepcopy__ uygulaması için standart desen, yeni örneği oluşturduktan hemen sonra ( memo[id(self)] = result kullanarak) memo sözlüğüne kaydetmeyi gerektirir ve özellikleri yinelemeli olarak kopyalamadan önce yapılır. Eğer özellik kopyalama aşamasında bir istisna meydana gelirse, memo sözlüğü, kısmen inşa edilmiş (ve muhtemelen tutarsız) bir nesneye bir referans tutar. Eğer çağrılan kod bu istisnayı yakalayıp grafik üzerinde diğer bölümleri kopyalamaya devam ederse veya aynı nesne grafikte başka bir yol üzerinden referans alındığında, memo'daki sonraki aramalar bu bozulmuş, yarı inşa edilmiş nesneyi döndürür. Bu, bazı referansların tamamen inşa edilmiş kopyalara, bazılarının ise eksik bir istisna kalıntısına işaret etmesiyle sessiz veri bozulmalarına yol açabilir. Bunu azaltmak için, __deepcopy__ uygulamaları, atomik özellik kopyalamayı sağlamalı veya başarısızlık durumunda memo sözlüğünü temizleyecek şekilde istisna yönetimini dikkatlice yapmalıdır, ancak Python'un standart kütüphanesi bu senaryo için otomatik geri alma sağlamaz.