PythonProgramlamaKıdemli Python Geliştirici

**Python**'un `pickle` modülünün hangi yeniden yapılandırma mekanizması, sınıfların `__init__`'i atlayarak `__new__`'e doğrudan argümanlar sağlamasına olanak verir?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun Cevabı

pickle modülünün protokolü, __init__'in yan etkileri veya pahalı hesaplamalar gerçekleştirdiği nesneleri işlemeyi geliştirdi. Erken protokoller, unpickle sırasında __init__'in çağrılmasını gerektiriyordu ve bu da dosya tanıtıcıları veya veritabanı bağlantıları gibi kaynaklarla sorunlara yol açıyordu. Protokol 2, __getnewargs__'i tanıttı ve Protokol 4, anahtar kelime argümanlarını desteklemek için __getnewargs_ex__ ile bunu genişletti, böylece nesne yeniden yapılandırması üzerinde daha ince kontrol sağladı.

Unpickle sırasında Python genellikle nesne durumunu yeniden oluşturmak zorundadır. Eğer __init__ doğrulama yapıyorsa, ağ soketlerini açıyorsa veya küresel durumu değiştiriyorsa, unpickle sırasında yeniden yürütmek hatalı veya verimsiz olabilir. Zorluk, nesnenin durumunu yalnızca saklanan verileri kullanarak ve __new__ yapılandırıcısı aracılığıyla yeniden oluştururken bu başlangıç yan etkilerini tetiklememektir.

__getnewargs_ex__ dunder metodu (veya daha eski protokoller için __getnewargs__), bir sınıfın pickle'in doğrudan __new__'e ilettiği (args, kwargs) demetini döndürmesine olanak tanır ve böylece tamamen __init__'i atlar. Bu yöntem yeniden yapılandırma aşamasında çağrılır ve döndürdüğü değer, örneğin seri hale getirilmiş baytlardan nasıl yaratılacağını belirler. Bu yaklaşım, nesnenin doğru başlangıç durumu ile örneklendiğini güvence altına alır ve onarılan bir nesne için uygunsuz olabilecek herhangi bir başlatma mantığını tetiklemez.

import pickle class DatabaseConnection: def __new__(cls, dsn, timeout=30): instance = super().__new__(cls) instance.dsn = dsn instance.timeout = timeout return instance def __init__(self, dsn, timeout=30): # Unpickle sırasında atlamak istediğimiz pahalı işlem self.socket = create_socket(dsn, timeout) def __getnewargs_ex__(self): # __new__ için args ve kwargs döndür return ((self.dsn,), {'timeout': self.timeout}) def __getstate__(self): # Socket’i pickle yapma return {'dsn': self.dsn, 'timeout': self.timeout} def __setstate__(self, state): self.dsn = state['dsn'] self.timeout = state['timeout'] # Gerektiğinde socket’i yeniden oluştur ya da tembel başlatmaya bırak # Kullanım conn = DatabaseConnection('postgresql://localhost', timeout=60) serialized = pickle.dumps(conn, protocol=4) restored = pickle.loads(serialized) # __init__ çağrılmadı

Gerçek hayattan bir durum

Bir veri işleme hattı, açık TCP soketlerini ve kimlik doğrulama belirteçlerini tutan Redis bağlantı nesnelerini önbelleğe alır. Uygulama yeniden başlatmaları arasında kalıcılık için bu önbellek girdilerini diske seri hale getirirken, unpickle sırasında __init__’i çağırmak, hemen yeni soket bağlantıları oluşturmayı denemekte ve bu da çevrimdışı ortamlarda başarısız olmakta veya kaynak sızıntılarına yol açmaktadır. Bu senaryo, bağlantı parametrelerini koruyan ve gerçek ağ kurulumunu uygulama açıkça talep edene kadar erteleyen bir seri hale getirme stratejisi gerektirir.

__getstate__'i yalnızca bağlantı parametrelerini (ana makine, port, kimlik doğrulama) döndürmek ve __setstate__'i özellikleri manuel olarak ayarlamak ve isteğe bağlı olarak bağlantıyı yeniden açmak için uygulayın. Bu yaklaşım, daha eski pickle protokolleriyle uyumludur ve açıktır. Ancak, __reduce__ ile dikkatlice kaçınılmadıkça, varsayılan unpickle sürecinde hâlâ __init__’i tetikler, bu da __setstate__'in temizlemeden önce yan etkileri tetikleme olasılığını artırır.

__reduce__'yi (çağrılabilir, args, state) demetini döndürecek şekilde uygulayın; burada çağrılabilir bir sınıf yöntemi veya __new__'nin kendisi olabilir. Bu, yeniden yapılandırma üzerinde tam kontrol sağlar ancak ayrıntılıdır ve durum sözlüğünün manuel yönetimini gerektirir. Bu da kod karmaşıklığını artırır ve sınıf yapısı ile pickle edilmiş veri arasındaki sürüm uyuşmazlığı riskini yükseltir.

__getnewargs_ex__'i ((host, port), {'auth': token}) döndürmek için uygulayın, bu sayede pickle doğrudan __new__(host, port, auth=token) çağırabilirken __init__'i atlar. Bu çözüm, modern protokol 4 özelliklerini kullanması, 'boş örnek oluşturma' aşamasını 'kaynakları başlatma' aşamasından temiz bir şekilde ayırması ve __reduce__ olasılıklarından kaçınması nedeniyle seçilmiştir. Sonuç, bağlantı nesnelerinin yapılandırmaları yerinde geri yüklendiği sağlam bir önbellekleme sistemidir, ancak soketler ihtiyaç duyulana kadar kapalı kalır, böylece toplu unpickle işlemleri sırasında kaynak tükenmesini önler.

Adayların Sıklıkla Gözden Kaçırdığı Noktalar

__getnewargs_ex__ neden __init__'in çağrılmasını engellerken, sadece __setstate__ bunu yapmaz?

pickle, bir nesneyi yeniden yapılandırırken __getnewargs_ex__ (veya __getnewargs__) arar. Mevcutsa, unpicker, döndürülen değerlerle __new__(*args, **kwargs) çağrısını yapar ve mevcutsa __setstate__ ile durumu hemen uygular, tamamen __init__'i atlar. Buna karşın, bu yöntemler olmadan pickle, her zaman __new__'den sonra __init__'i çağıran varsayılan yapılandırma yolunu kullanır. Adaylar, __setstate__'in başlatmayı geçersiz kıldığını varsayıyor, ancak __setstate__ yalnızca __init__ zaten yürütüldükten sonra örneği yamanmaktadır ki bu, yan etkileri önlemek için çok geçtir.

Eğer __getnewargs_ex__ iki elemanlı bir demet olmayan bir değer döndürürse ne olur?

pickle protokolü, __getnewargs_ex__'in 2 uzunluğunda bir demet döndürmesini kesinlikle gerektirir: (args_tuple, kwargs_dict). Eğer yalnızca bir argüman demeti döndürürse (örneğin __getnewargs__), Python unpickle sırasında bir TypeError hatası verir çünkü sonucu __new__(*args, **kwargs)'ye yaymaya çalışır. Eğer None veya diğer türler dönerse, unpicker çökebilir veya belirsiz bir şekilde davranabilir, bu da yalnızca bir argüman demeti bekleyen __getnewargs__'den farklıdır.

__getnewargs_ex__ ve __reduce_ex__ birlikte tanımlandığında nasıl etkileşimde bulunur?

__reduce_ex__, seri hale getirmeyi düzenleyen daha yüksek seviyeli bir protokol yöntemidir. Eğer bir sınıf __getnewargs_ex__ tanımlıyorsa, __reduce_ex__ (özellikle protokol 4+’te), otomatik olarak döndürdüğü değeri indirim demetine NEWOBJ_EX opcode'u kullanarak dahil eder. Eğer her ikisi de mevcutsa ama __reduce_ex__, standart yeniden yapılandırma yolunu kullanmayan özel bir çağrılabilir döndürürse, bu öncelikli olur ve potansiyel olarak __getnewargs_ex__'i tamamen göz ardı eder.