PythonProgramlamaPython Geliştirici

**Python**'in ana sınıf seviyesindeki kancası, alt sınıf bildirimlerinden ana sınıfların anahtar kelime argümanlarını nasıl yakaladığını ve bu yürütme aşamasının metaclass müdahalesinden nasıl farklı olduğunu açıklayın.

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

Sorunun yanıtı

Sorunun tarihi

__init_subclass__ kancası, Python 3.6'da PEP 487 çerçevesinde tanıtıldı. Bundan önce, alt sınıflara geçişte herhangi bir işlem gerçekleştirmek isteyen bir sınıf—örneğin kayıt, doğrulama veya otomatik alan toplama—özel bir metaclass tanımlamak zorundaydı. Metaclass'lar güçlüdür, ancak çoklu kalıtım senaryolarında, dikkatlice koordine edilmedikçe çelişkiler yaratır. Yeni kanca, ana sınıfların alt sınıf başlatmaya katılmasına izin verir; bütün hiyerarşinin belirli bir metaclass benimsemesini zorunlu kılmadan, daha karmaşık metaclass uygulamalarına ihtiyaç duymadan, Django ORM ve SQLAlchemy gibi çerçevelerin basitleştirilmesine olanak tanır.

Problem

Bir B sınıfı bir ana sınıf A'dan türediğinde, çerçeve geliştiricileri genellikle B sınıfı tanımlandığında (örneğin, bir örnek oluşturulmadan önce) mantık yürütmek zorundadır. Örneğin, bir ORM, B'den tüm sütun tanımlarını toplamak ve bir kayıtta saklamak isteyebilir. Bir metaclass kullanmak, A'nın metaclass'ı olarak type veya özel bir metaclass'a sahip olmasını gerektirir; bu, B'nin de farklı bir metaclass kullanması gerektiğinde (örneğin, bir ABC'den veya başka bir çerçeveden) problem oluşturur. Bu, çözülmesi zor olan metaclass çelişki hatalarına yol açar. Ayrıca, metaclass __new__ sınıf ad alanı tam olarak doldurulmadan önce çalışır, bu da son sınıf özelliklerini denetlemeyi zorlaştırır.

Çözüm

Python, __init_subclass__ sınıf yöntemini sağlar. Bir sınıf bu yöntemi tanımladığında, bu yöntem, tanımlayıcı sınıfı doğrudan ebeveyn olarak içeren bir sınıf oluşturulduğunda otomatik olarak çağrılır. Kanca, ilk argümanı olarak yeni oluşturulan alt sınıfı alır ve ardından sınıf tanımı satırında (örneğin, class B(A, keyword=value)) geçirilen herhangi bir anahtar kelime argümanını takip eder.

class RegistryBase: _registry = {} def __init_subclass__(cls, category="default", **kwargs): super().__init_subclass__(**kwargs) print(f"Registering {cls.__name__} under category '{category}'") cls._registry[cls.__name__] = {"class": cls, "category": category} class Plugin(RegistryBase, category="audio"): pass class Effect(Plugin, category="reverb"): pass

Metaclass __new__'in aksine, sınıf nesnesi var olmadan önce çalıştığı için, __init_subclass__ sınıf nesnesi tamamen oluşturulduktan sonra çalışır. Bu, kancanın cls.__dict__, yöntemler ve anotasyonları güvenle denetlemesine olanak tanır. Kanca ayrıca MRO'yu da göz önünde bulundurarak, super() çağrıldığında ebeveyn sınıf kayıtlarının çocuk sınıf mantığından önce gerçekleşmesini sağlar.

Gerçek hayattan bir durum

Büyük bir ses işleme SaaS platformunda, mühendislik ekibi, üçüncü taraf geliştiricilerin alt sınıf oluşturarak ses efektleri tanımlayabileceği bir eklenti sistemi uygulamak zorunda kaldı. Her alt sınıfın, effect_name, latency_ms ve category gibi meta verilerle global efekt kataloğuna otomatik olarak kaydedilmesi gerekiyordu. Sorun, platformun zaten veritabanı modelleri için SQLAlchemy açıklamalı temeller kullandığı ve bazı ses efektlerinin AudioEffect ve SQLAlchemy modellerinden miras alması gerektiğiydi. AudioEffect için özel bir metaclass tanıtmak, SQLAlchemy'nin DeclarativeMeta ile metaclass çelişkilerine neden oldu ve uygulamanın başlangıcını kırdı.

İlk yaklaşım, her sınıf tanımının üstüne @register_effect yazmakla manuel kayıt yapmaktı. Bu çalıştı ama hataya açıktı; geliştiriciler sık sık dekoratörü unutarak üretimde eksik efektler bıraktı. Ayrıca, dekoratör argümanları ve sınıf tanımlarıyla meta verileri tekrar etmelerini gerektirerek DRY ilkelerini ihlal etti.

İkinci yaklaşım, DeclarativeMeta ve EffectMeta'dan türeyen ortak bir metaclass kullanmayı denedi. Bu, hemen çelişkiyi çözdü ama kırılgan bir bağımlılık yarattı. SQLAlchemy içindeki metaclass mantığı her güncellendiğinde, platform bozuldu. Ayrıca, tüm efekt sınıflarını veritabanı modelleri olmaya zorladı ki bu da hafif istemci tarafı efektleri için uygun değildi.

Üçüncü yaklaşım, __init_subclass__'ı kullanmayı içeriyordu. AudioEffect ana sınıfı, sınıf tanımı sırasında geçirilen anahtar kelime argümanlarını yakalamak için __init_subclass__'ı tanımladı; örneğin effect_id ve version. Bir geliştirici class Reverb(AudioEffect, effect_id="rvb-01", version=2) yazdığında, kanca otomatik olarak ID'nin benzersizliğini doğruladı ve sınıfı iş parçacığı güvenli bir WeakValueDictionary kaydına kaydetti. Bu, __init_subclass__'ın normal bir sınıf yöntemi olduğu için metaclass çelişkilerinin tamamen önlenmesine olanak tanıdı ve herhangi bir metaclass ile işbirliği yapar.

Ekip üçüncü çözümü seçti. Bu, SQLAlchemy ile uyumluluğu korudu, dekoratörlere olan ihtiyacı ortadan kaldırdı ve kaydın otomatik olarak import zamanında gerçekleşmesini sağladı. Sonuç, sadece çalıştığı bir eklenti sistemiydi—geliştiricilerin yalnızca alt sınıflar oluşturmaları ve parametreleri inline olarak bildirmeleri yeterliydi. Sistem, hiç metaclass çelişkisi olmadan 150+ efekti başarıyla kaydetti ve metaclass yaklaşımına göre azalmış MRO hesaplama karmaşıklığı sayesinde başlangıç zamanı %40 iyileşti.

Adayların sıkça gözden kaçırdığı noktalar

__init_subclass__'ın her zaman super().__init_subclass__() çağrılması neden zorunludur?

Adaylar genellikle object'in __init_subclass__ tanımlamadığı için bu çağrının isteğe bağlı olduğunu varsayarlar. Ancak, çoklu kalıtım senaryolarında, super()'u çağırmayı başaramamak, kanca uygulayan kardeş sınıflar için zinciri kırabilir. Python'un işbirlikçi çoklu kalıtımı, hiyerarşinin tüm dallarının başlatma mantığını yürütmesini sağlamak için her katılımcının super() çağrısını yapmasını gerektirir. Eğer A ve B her ikisi de __init_subclass__ tanımlıyorsa ve C(A, B) yalnızca A'nın kancasını çağırıyorsa, B'nin kayıt mantığı sessizce atlanır ve eklenti sistemlerinde ince hatalara yol açar.

__init_subclass__, yöntemleri dinamik olarak değiştirebilir veya ekleyebilir mi ve bunun __slots__ üzerindeki etkileri nelerdir?

Adaylar genellikle __init_subclass__'ı metaclass __new__ ile karıştırırlar. __init_subclass__, sınıf tamamen oluşturulduktan sonra çalıştığı için, sınıf sözlüğünü oluşturulmadan önce değiştiremez (metaclass __prepare__ veya __new__ gibi). Ancak, setattr(cls, name, value) ile dinamik olarak nitelikler ekleyebilir. Tehlike, __slots__ ile ortaya çıkar: Eğer bir üst sınıf __slots__ kullanıyorsa, alt sınıf bu kısıtlamayı devralır. __init_subclass__ içinde setattr ile slotted bir sınıfa yeni bir nitelik eklemeye çalışmak, alt sınıf __slots__ veya __dict__ tanımlamadıkça AttributeError hatası verecektir. Bu sınırlama, mimarların sınıf gövdesinin gerçek yapısal değişimi için metaclass kullanmak ile kayıt/meta veriler için __init_subclass__ kullanmak arasında bir seçim yapmasını zorunlu kılar.