Sorunun geçmişi:
Python'da parametre geçişi "nesne referansı ile çağırma" prensibini (bazen paylaşma ile çağırma olarak adlandırılır) uygular. Bu, işlev içindeki herhangi bir değişkenin, dışarıdan geçirilen argümanla aynı bellek nesnesine işaret etmeye başladığı anlamına gelir.
Sorun:
Eğer işlev, geçirilen değiştirilebilir nesneyi (örneğin bir liste veya sözlük) değiştirirse, değişiklikler işlevin dışında da görünür. Bu, özellikle işlevin giriş verilerini değiştirmeyeceği beklendiğinde, zor anlaşılır hatalara neden olabilir.
Çözüm:
Yan etkilerden kaçınmak için işlev içinde nesnenin bir kopyasını oluşturmak veya değiştirilemez veri yapıları kullanmak gerekir. Kopyalamak için standart yöntemler kullanılır (örneğin, listeler için list.copy(), sözlükler için dict.copy() veya copy.deepcopy()).
Kod örneği:
def append_one(xs): xs.append(1) return xs lst = [0] append_one(lst) print(lst) # [0, 1] # Değişikliklerden nasıl kaçınılır? Kopya yapmak: def safe_append_one(xs): ys = xs.copy() ys.append(1) return ys lst2 = [0] safe_append_one(lst2) print(lst2) # [0]
Önemli özellikler:
list.copy() ile bir listenin kopyasının tamamen bağımsız olduğuna güvenilir mi?
Hayır — .copy() yüzeysel bir kopya yaratır. Eğer içinde değiştirilebilir alt nesneler varsa, onlardaki değişiklikler orijinalde de görünür.
import copy lst = [[1, 2], [3, 4]] shallow = lst.copy() shallow[0][0] = 42 print(lst) # [[42, 2], [3, 4]] deep = copy.deepcopy(lst) deep[0][0] = 100 print(lst) # [[42, 2], [3, 4]]
Girişe dayalı yeni bir nesne döndürmek, orijinalde değişiklik olmaması garantisi midir?
Her zaman değil. Yeni nesne içinde orijinalin parçaları kullanılıyorsa (örneğin, iç içe geçmiş bir listeye referans), orijinal nesne değiştirilebilir.
def duplicate_list(xs): return xs * 2 lst = [[1], [2]] res = duplicate_list(lst) res[0][0] = 999 print(lst) # [[999], [2]]
Değiştirilebilir nesneler için varsayılan argüman değerleri, işlevin tekrar tekrar çağrılmasında sorunlara yol açabilir mi?
Evet — varsayılan değer yalnızca işlev tanımlandığında bir kez hesaplanır.
def add_item(item, container=[]): container.append(item) return container print(add_item(1)) # [1] print(add_item(2)) # [1, 2]
Olumsuz Durum
Konfigürasyon işleme kütüphanesinde varsayılan değer olarak bir liste kullanıldı ve bu farklı işlev çağrıları arasında elemanların birikmesine yol açtı. Davranış belirsizdi ve uzun süre ortaya çıkmadı.
Artılar:
Tekrar çağrılar için daha az kod, görünür bellek tasarrufu.
Eksiler:
Gizli davranış, hata ayıklamada zorluklar, uzun vadeli hatalar.
Olumlu Durum
Her çağrıda yeni bir nesnenin açıkça oluşturulması için None değerinin varsayılan olarak kullanılması.
Artılar:
Öngörülebilirlik, beklenmedik yan etkilerin olmaması, güvenilirlik.
Eksiler:
Dikkat gerektirir ve biraz daha fazla kod yazmayı gerektirir.