Python, tanımlayıcı nesneler üzerinde isteğe bağlı olarak __set_name__(self, owner, name) yöntemini, sınıf oluşturulurken otomatik olarak, özellikle sınıf gövdesi çalıştırıldığında ama sınıf nesnesi metaclass tarafından nihai bir şekilde oluşturulmadan hemen önce çağırır. type.__new__ isim alanı sözlüğünü işlerken, __set_name__ niteliğine sahip olan değerleri tespit eder ve bu kancayı çağırır, oluşturulmakta olan sınıfı ve karşılık gelen nitelik anahtarını geçirir. Bu mekanizma, geliştiricilerin tanımlayıcıyı tekrar eden bir dize argüman olarak geçirip geçirmelerine gerek kalmadan, tanımlayıcının kendi adını araştırmasına ve depolamasına olanak tanır. PEP 487 ile Python 3.6'da tanıtılan bu protokol, ORM veya veri doğrulayıcılar gibi serileştirme ya da veritabanı haritalama adlarını bilmesi gereken deklaratif çerçevelerin oluşturulması için gereklidir.
class AutoNamedField: def __set_name__(self, owner, name): self.name = name self.owner = owner def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) class Model: user_id = AutoNamedField() # __set_name__ otomatik olarak name='user_id' ile çağrıldı
Hafif bir veri doğrulama kütüphanesi tasarlarken, ekip, geliştiricilerin email = Validator('email') kullanarak şema alanlarını tanımladıkları ve yeniden yapılandırma sırasında niteliği yeniden adlandırdıkları ancak dize literali güncellememeleri nedeniyle hataların sıkça ortaya çıktığı bir soruna maruz kaldı; bu da API ile veritabanı arasında çalışma zamanı uyuşmazlıklarına yol açıyordu. Bu açık tekrar, DRY ilkesini ihlal ediyor ve yüzlerce model kod tabanında bakım sürtüşmesi yaratıyordu.
Değerlendirilen bir çözüm, sınıf oluşturulurken sınıf sözlüğünü yineleyen özel bir metaclass uygulamaktı; Validator örneklerini tür kontrolü ile tanımlayıp, noktalar arasında nesne kimliğini karşılaştırarak niteliğin adını manuel olarak enjekte ediyordu. Bu yaklaşım doğru çalışıyor ancak, kullanıcılar birden çok çerçeve sınıfından miras alırken dikkatli metaclass anlaşmazlık çözümleri gerektiriyor ve her sınıf tanımı için ithalat aşamasında gereksiz yük getiriyordu.
Düşünülen bir diğer alternatif, sınıf yaratımından sonra uygulanan bir sınıf dekoratörü kullanarak __dict__ üzerinden vars() ile yürüyüp tanımlayıcı nesneleri geriye dönük olarak yanıt niteliği eklemekti. Bu, metaclass artışını önlüyordu ancak adlandırma mantığını tanımlayıcı bildiriminden ayırdığı için kod tabanını anlamayı ve bakımını zorlaştırıyordu ve sınıf yaratımından dinamik olarak eklenen tanımlayıcıları ek kancalar olmadan işleme almayı başaramıyordu.
Seçilen çözüm, __set_name__ protokolünü doğrudan Validator sınıfına uyguladı. Bu, açık dize argümanları kullanımını tamamen ortadan kaldırarak email = Validator() gibi temiz bildirimler yapılmasını sağladı ve karmaşık metaclasslara veya dekoratörlere olan bağımlılığı kaldırdı. Sonuç, niteliğin adlarının değişken tanımlayıcılarıyla senkronize kalmasını sağlayarak yeniden yapılandırma riskini azaltan, sağlam, deklaratif bir API oldu; aynı zamanda kütüphanenin mimarisini önemli ölçüde basitleştirerek farklı kullanıcı miras kalıpları ile uyumluluğu artırdı.
Tam olarak ne zaman yorumlayıcı __set_name__ çağırır?
Birçok aday, kancanın tanımlayıcının kendi __new__ veya __init__ yöntemleri sırasında ya da alternatif olarak örnek oluşturma sırasında tetiklendiğini yanlışlıkla düşünmektedir. Aslında, Python'ın type.__new__ sınıf gövdesi çalıştıktan sonra __set_name__'i tetikler—bu isim alanı sözlüğünü doldurur—ama tam olarak oluşturulmuş sınıf nesnesini döndürmeden önce. Özellikle, yorumlayıcı isim alanı öğeleri üzerinde yineleme yapar, hasattr ile __set_name__'in varlığını kontrol eder ve onu sahip sınıf ve nitelik anahtarı ile birlikte çağırır. Bu zamanlama kritik önem taşır çünkü tanımlayıcının alt sınıflar veya örnekler oluşturulmadan önce nihai adını bilmesine olanak tanır, ancak tüm sınıf düzeyi atamalarının işlenmesinden sonra yapılır.
Sınıf oluşturulduktan sonra dinamik olarak bir tanımlayıcı sınıfa atanırsa ne olur?
Ortak bir yanılgı, __set_name__'in, altında herhangi bir koşulda bir tanımlayıcı sınıf niteliğine bağlandığında çağrıldığını düşünmektir. Ancak, kanca yalnızca type metaclass tarafından yönetilen başlangıç sınıf oluşturma sürecinde çağrılır. Eğer daha sonra mevcut bir sınıfa setattr(MyClass, 'new_attr', MyDescriptor()) ifadesi çalıştırırsanız, Python otomatik olarak __set_name__'i tetiklemez. Sonuç olarak, tanımlayıcı, nitelik adını bilmeden kalır; manuel olarak descriptor.__set_name__(MyClass, 'new_attr') çağırmadıkça, bu genellikle dinamik şema oluşturma senaryolarında gözden kaçmaktadır ve tanımlayıcının sınıf hiyerarşisinde kendini bulamamasına yol açan ince hatalara neden olur.
__set_name__ üst sınıflardan miras alınan tanımlayıcılarda nasıl davranır?
Adaylar genellikle __set_name__'in alt sınıflardaki miras alınan tanımlayıcılar için tekrar tetiklenip tetiklenmeyeceğiyle mücadele ederler. Yöntem, tanımlayıcının sınıf gövdesinde atandığı anda yalnızca bir kez çağrılır. Bir alt sınıf tanımlayıcıyı miras aldığında, hali hazırda ebeveyninde adlandırılmış aynı örnek nesnesini alır; Python, tanımlayıcı nesnesinin alt sınıf isim alanında yeni bir şekilde atanmadığı için alt sınıf için __set_name__'i yeniden çağırmaz—sadece MRO aracılığıyla erişilir. Bu, __set_name__'deki owner argümanının, tanımlayıcıyı gelecekte erişecek tüm sınıfları temsil ettiği varsayımı yerine, her sınıf için sınıf başına meta verileri depolamak için zayıf referanslar veya sahibinin sınıfı ile anahtarlandırılmış ayrı depolama kullanması gerektiği anlamına gelir.