Sorunun tarihi
Python'ın veri modelinde, nitelik erişimi sıkı bir protokolü takip eder; __getattribute__, temel object sınıfında tanımlanmıştır ve her nitelik araması için birincil kesici olarak hizmet eder. Bu yöntem, mevcut veya mevcut olmayan tüm nitelik erişimleri için koşulsuz olarak çağrılır, çözüm zincirinde ilk savunma hattı olur. Buna karşılık, __getattr__, normal aramanın örnek sözlüğü ve sınıf hiyerarşisi üzerinden talep edilen adı bulamadığında yorumlayıcı tarafından yalnızca çağrılan isteğe bağlı bir kancadır.
Sorun
Bir alt sınıf, __getattribute__'ı özelleştirilmiş davranışlar, örneğin günlük kaydı veya erişim kontrolü gibi, özelleştirdiğinde, yöntem gövdesindeki herhangi bir doğrudan nitelik erişimi—örneğin self.attr veya self.__dict__—aynı şekilde yeniden tanımlanmış yöntemi çağırır. Bu, arama mekanizması temel bir durum sonlandırıcı olmadan ele geçirilmiş olduğundan sonsuz bir döngü oluşturur ve sonuç olarak çağrı yığını tükenir ve bir RecursionError hatası alırsınız.
Çözüm
__getattribute__'ı güvenli bir şekilde uygulamak için, super().__getattribute__(name) veya object.__getattribute__(self, name) kullanarak temel uygulamaya delegasyon yapmalısınız. Bu, yeniden tanımlanmış mantığı atlar ve özelleştirilmiş yöntem içine yeniden girmeden, örnek sözlüğünden veya sınıf hiyerarşisinden gerçek nitelik alımını gerçekleştirir. Bu model, nesne modelinin bütünlüğünü korurken sonucu sarma, doğrulama veya dönüştürme yeteneği sağlar ve sonsuz döngüleri önler.
Kod örneği
class SafeProxy: def __init__(self, wrapped): # Başlatma sırasında sonsuz döngüyü önlemek için burada super() kullanmalısınız super().__setattr__('_wrapped', wrapped) def __getattribute__(self, name): # Erişimi kaydetmeden önce günlük kaydedin print(f"Erişiliyor: {name}") # Sonsuz döngüyü önlemek için nesneye delegasyon yapın return super().__getattribute__(name)
Senaryo
Bir geliştirme ekibi, uyum gereklilikleri dolayısıyla her alan erişiminin kaydedilmesi gereken bir eski ORM modeline bir denetim izi uygulamak zorundadır. Orijinal model sınıflarını değiştirmeden şeffaf bir şekilde okumaları kesen bir çözüme ihtiyaçları vardır; bu, yüzlerce modül boyunca mevcut iş mantığını bozmaktan kaçınmak anlamına gelir.
Sorun tanımı
Sistem, zaman damgaları ve kullanıcı işlemleri kaydetmek için hem mevcut hem de eksik nitelikleri kesmeyi gerektiriyor. Bireysel yöntemlere günlük kaydı ekleyerek alt sınıf oluşturulması, dinamik alanların çokluğu nedeniyle sürdürülebilir değildir. Çözüm, mevcut kod için şeffaf olmalı ve modellerin genel arayüzünü değiştirememelidir.
Çözüm 1: Model yöntemlerini maymun yaması yapmak
Bu yaklaşım, kaynak tanımlarını değiştirmeden, spesifik davranışları hedefleyerek saat runtime'da sınıf üzerindeki yöntemleri dinamik olarak değiştirmeyi içerir. Yapılandırmaya dayalı koşullu uygulamalar sağlar ve miras almakla ilgili karmaşıklıkları önler. Ancak, veri tanımlayıcılarına veya basit değerlerde doğrudan nitelik erişimini kesmekte başarısız olur, her yeni yöntem için bakım gerektirir ve iç uygulama ayrıntıları değiştiğinde kırılır.
Çözüm 2: Günlük kaydı için __getattr__ kullanmak
Eksik niteliklere erişimi günlüğe kaydetmek için __getattr__ uygulanması, yalnızca basit bir geri dönüş mekanizması sunar. Sonsuz döngü sorunlarından korunaklıdır ve minimal ön hazırlık ile uygulanması kolaydır. Ne yazık ki, yalnızca örnekte veya sınıfta bulunmayan nitelikler için tetiklenir, mevcut alanların çoğuna erişim kaçırılır; bu da geniş kapsamlı günlük kaydı gereksinimini bozar.
Çözüm 3: __getattribute__ ile Proxy sınıfı
__getattribute__'ı uygulayan bir sarmalayıcı sınıf oluşturmak, sarılmış ORM örneğine delegasyon yapmadan önce tüm nitelik okumalarını keser, her erişimi tek tip olarak yakalar. Bu, bileşim yoluyla şeffaflığı sürdürür ve miras alınan kodu değiştirmeden ön ve son işlem yapma olanağı sağlar. Ticareti, dikkatli döngü işleme gereksinimi ve her nitelik erişimi için ek yöntem çağrısı nedeniyle hafif performans kaybıdır.
Seçilen çözüm
Ekip, düzenleyici gerekliliklerin her nitelik okumasını, yöntemlerin asla dokunmadığı basit veri alanlarını da dahil olmak üzere, yakalamayı zorunlu kılması nedeniyle __getattribute__ ile proxy yaklaşımını seçti. Proxy modeli, kapsüllemeyi korurken tam kesme yetenekleri sağladı ve eski ORM'nin denetim katmanından habersiz kalmasına olanak tanıdı. Bu seçim, kapsamlı kapsama ve denetim bütünlüğü için minimal performans kaybı anlamına geliyordu.
Sonuç
Uygulama, üretimde saatte 50.000'den fazla nitelik erişimini başarılı bir şekilde kaydetti; hiçbiri bir döngü hatası veya eski kod tabanında herhangi bir modifikasyon olmadan. super() kullanarak yapılan delegasyon modeli, kararlı bir işletim sağladı ve proxy, wrapper örneğini kaldırarak test ortamlarında devre dışı bırakılabilir, bu da yaklaşımın esnekliğini gösterebilir.
Neden self.__dict__'ye __getattribute__ içinde erişmek sonsuz döngü başlatır?
self.__dict__'yi bir yeniden tanımlanmış __getattribute__ yönteminin içinde yazdığınızda, Python'ın __dict__ adlı niteliği örnekte araması gerekir. Bu arama, özel __getattribute__ yönteminizin yeniden çağrılmasına neden olur, bu da tekrar self.__dict__'ye erişmeye çalışır ve sonsuz bir döngü oluşturur. Bu döngüyü kırmak için, temel nesne uygulamasından doğrudan sözlüğü almak için object.__getattribute__(self, '__dict__') kullanmalısınız.
__getattribute__, tanımlayıcı protokollerini __getattr__'dan farklı nasıl etkiler?
__getattribute__, nitelik çözümleme zincirinin en başında yer alır; bu, tanımlayıcı protokolü __get__ yöntemleri için kontrol etmeden önce aramaları keser. Uygulamanız bir değer dönerse ve super()'a delegasyon yapmazsa, property gibi tanımlayıcılar ya da özel veri tanımlayıcıları tamamen atlanır. Buna karşılık, __getattr__, yalnızca tanımlayıcı protokolü ve örnek sözlüğü araması ikisi de başarısız olduğunda çalışır, bu nedenle sınıf hiyerarşisinde mevcut olan tanımlayıcıları asla kesmez.
__getattribute__ içinde manuel olarak AttributeError yükseltmenin sonucu nedir?
Standart nitelik erişiminin aksine, burada bir AttributeError yükseltmek, __getattr__'in yedek olarak tetiklenmesine neden olabilir; Python, __getattribute__'ı yetkili bir kaynak olarak kabul eder. Özel uygulamanız AttributeError yükseltirse, yorumlayıcı, __getattr__'ı çağırmaya çalışmadan istisnayı hemen yayar. Bu, __getattr__'ı kayıp nitelikleri yönetmek için güvenilemeyeceğiniz anlamına gelir; bunun yerine, ya __getattribute__ içinde kayıp anahtarları yönetmelisiniz ya da doğru bir şekilde istisnayı yükselten üst uygulamaya delegasyon yapmalısınız.