PythonProgramlamaKıdemli Python Geliştirici

Bir **Python** örneği, Yöntem Çözüm Sırası hesaplamasına katılacak mantıksal temel sınıflarını hangi yollarla belirtebilir?

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

Sorunun cevabı

Soru Tarihçesi

__mro_entries__ protokolü, Python 3.7'de PEP 560 üzerinden tanıtılmıştır ("Tip modülü ve genel türler için temel destek"). Bu geliştirmeden önce, typing.List[int] gibi genel takma adlar, type.__new__'un tüm temellerin type örnekleri olmasını kesin olarak gerektirmesi nedeniyle sınıf tanımlarında temel sınıflar olarak kullanılamıyordu. Bu kısıtlama, typing modülünün sürdürülebilirliği zor olan kırılgan metaclass hack'lerine bağlı kalmasına ve performans sorunları yaşamasına neden oldu. Protokol, bir temel ifadesinin kalıtım grafiğine olan anlamsal katkısından ayrılmasını sağlamak için tasarlandı ve genel yapılar ile fabrika desenleri için daha temiz bir destek sağladı.

Problem

CPython bir sınıf tanımını işlediğinde, tutarlı ve öngörülebilir bir yöntem arama hiyerarşisini sağlamak için C3 lineerleştirme algoritmasını kullanarak Yöntem Çözüm Sırasını (MRO) hesaplamalıdır. Eğer bir temel nesne bir sınıf değilse (örneğin, parameterize edilmiş genel bir nesne veya bir yapılandırma nesnesi), yorumlayıcı, yeni sınıfın kalıtım ağacında doğru bir şekilde yerleştirilmesi için gerekli olan tür bilgisine sahip değildir. Bu tür nesneleri görmezden gelmek isinstance kontrollerini ve super() zincirlerini kırarken, onları reddetmek güçlü metaprogramlama desenlerini engellerdi. Ana zorluk, bu sınıf olmayan nesnelerin sınıf oluşturma aşamasında mantıksal olarak temsil ettikleri somut sınıfları duyurmalarına izin vermekti.

Çözüm

Python şimdi sınıf oluşturma sırasında bases demetindeki her bir öğeyi __mro_entries__(self, bases) metodu için inceliyor. Eğer bu metod mevcutsa, orijinal bases demeti ile çağrılır ve MRO hesaplamasında nesne yerine geçmek üzere gerçek sınıflardan oluşan bir demet döndürmelidir. Döndürülen sınıflar, sanki açıkça temel olarak listelenmiş gibi muamele görür. Bu mekanizma, bir örneğin tanım aşamasında somut sınıflara çözülmek üzere saydam bir yer tutucu gibi hareket etmesine olanak tanır.

class ConfigurableMixin: def __init__(self, feature): self.feature = feature def __mro_entries__(self, bases): # Yapılandırmaya dayalı olarak temel sınıfları dinamik olarak enjekte et if self.feature == "logging": return (LoggingSupport,) return (BaseFeature,) class LoggingSupport: def log(self, msg): print(msg) class BaseFeature: pass # Örnek, MRO'da LoggingSupport ile değiştirilir class Service(ConfigurableMixin("logging")): pass print(LoggingSupport in Service.__mro__) # True

Hayattan bir durum

Büyük bir asenkron web çerçevesinde geliştiriciler, belirli bir veritabanı URL'siyle (örneğin, DatabaseMixin("postgresql://")) instantiye edildiğinde, ConnectionPool ve AsyncSession'ı kullanıcının hizmet sınıfına temel sınıflar olarak otomatik olarak enjekte eden bir DatabaseMixin fabrikası oluşturmak zorundaydılar. Zorluk, DatabaseMixin(...)'in bir sınıf değil, düz bir nesne örneği döndürmesiydi, yine de geliştiricinin class UserService(ConnectionPool, AsyncSession) şeklinde açıkça yazmış gibi MRO'da yer alması gerekiyordu.

Çözüm 1: Özel Metaclass Bir yaklaşım, __new__'de bases demetini tarayan, DatabaseMixin örneklerini tespit eden ve super().__new__ çağrısından önce hedef sınıflarla değiştiren bir metaclass oluşturmayı içeriyordu. Bu, kesin kontrol sağlıyordu ancak "metaclass çakışması" sorununu ortaya çıkarıyordu: bu metaclass'ı kullanan herhangi bir hizmet, kendi metaclass'larını tanımlayan diğer sınıflardan miras alamıyordu, bu nedenle belirli ORM temel sınıfları gibi. Ayrıca hata ayıklamak zorlaştı çünkü sınıf tanım sözdizimi karmaşık dönüşümleri gizliyordu ve yığın izleri metaclass içlerine yönlendiriliyordu, kullanıcı koduna değil.

Çözüm 2: Sınıf Oluştuktan Sonra Sınıf Dekorasyonu Başka bir seçenek, sınıf oluşturulduktan sonra uygulanan bir sınıf dekoratörü kullanmaktı. Dekoratör, yöntemleri ConnectionPool ve AsyncSession'dan yeni sınıfa manuel olarak kopyalardı ya da type.__setattr__ kullanarak enjekte ederdi. Bu, metaclass virüsünü önlese de, temel olarak Python'un kalıtım modelini bozuyordu: isinstance(UserService(), ConnectionPool) yanıtı False dönecek ve kopyalanmış yöntemlerdeki super() çağrıları yanlış bir şekilde çözümlenecekti çünkü MRO aslında ebeveyn sınıflarını içermiyordu. Bu, çerçeve yardımcı programlarının hizmetleri veritabanına uygundur olarak tanımadığı ince hatalara neden oldu.

Çözüm 3: __mro_entries__ Protokolü Ekip, DatabaseMixin tarafından döndürülen nesnede __mro_entries__'i uygulamayı seçti. Metod, ayrıştırılan URL'ye dayalı olarak (ConnectionPool, AsyncSession) değerini döndürdü. Bu çözüm, CPython'ın yerel sınıf oluşturma mekanizması ile pürüzsüz bir şekilde entegre oldu. MRO doğru şekilde hesaplandı, isinstance kontrolleri doğal olarak çalıştı ve metaclass çakışmaları olmadı. Fabrika örneği, sınıf oluşturma sırasında düzgün kalıtım yapısını oluşturan açıklayıcı bir yer tutucu gibi hareket etti, super() anlamsını ve çoklu kalıtımla uyumluluğu korudu.

Sonuç, geliştiricilerin class OrderService(DatabaseMixin(postgres_url)): yazıp bağlantı havuzlama ve oturum yönetimi yeteneklerini otomatik olarak doğru yöntem çözümü, tam IDE desteği ve sıfır çalışma zamanı maliyeti veya kalıtım çakışmaları ile alabilecekleri temiz, sezgisel bir API oldu.

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

C3 lineerleştirmesi, __mro_entries__ bir tabanı başka yerlerde yer alan sınıflara genişlettiğinde potansiyel tekrarları nasıl ele alıyor?

__mro_entries__ bir sınıf döndürdüğünde ve bu da temellerde bir başka yerde görünüyor ise (örneğin, bir fabrika (BaseA,) dönerken, başka bir açık temel Derived(BaseA)), Python'un C3 algoritması genişletilen demeti etkin temel listesi olarak kabul eder. Algoritma, bu listeleri yerel öncelik sırasını koruyarak ve monotonluğu sağlayarak birleştirir. C3, ortak ataları ele almak üzere tasarlandığı için, BaseA son MRO'da yalnızca bir kez görünür, ama ona bağlı tüm sınıflardan sonra ve object'ten önce yer alır. Adaylar sıkça bunun bir çakışma veya tekrar kaydı oluşturduğunu düşünürler, ancak lineerleştirme süreci doğal olarak tekrarları kaldırırken "çocuklar önce ebeveynler" kuralını korur, tutarlı bir yöntem çözümünü garanti eder.

Neden __mro_entries__ oluşturulan sınıfa erişemez ve buna erişmeye çalıştığında ne tür bir özel hata meydana gelir?

Sınıf oluşturulması sırasında, type.__new__, sınıf nesnesi henüz oluşturulmadan önce temel nesneler üzerinde __mro_entries__'i çağırır. İsim alanı sözlüğü mevcut, ancak sınıf nesnesinin henüz bir kimliği yoktur. Eğer uygulama, öngörülen sınıfın (örneğin, dış bir kapsamdan sınıf adını referans göstererek veya bases'ı yeni sınıfa bağlıymış gibi denetleyerek) özelliklerine erişmeye çalışırsa, bir NameError veya AttributeError hatası alır çünkü bağlama henüz mevcut değildir. Adaylar sıkça, sınıfın son durumunu veya __dict__'yi dinamik kararlar almak için denetleyebileceklerini varsayıyor, ancak metod yalnızca orijinal temellerin demetini bir argüman olarak alır ve geri dönüş değerini belirlemek için kendi iç durumuna güvenmek zorundadır.

__mro_entries__ ile bir nesnenin, abc.ABCMeta.register() üzerinden sanal bir alt sınıf olarak kaydedilmesi, ABC'nin MRO'da görünmesini sağlar mı?

Hayır. Sanal alt sınıf kaydı, isinstance() ve issubclass() kontrolleri için ABC içinde bir iç cache'i dolduran bir çalışma zamanı mekanizmasıdır. Bu, alt sınıfın __mro__ niteliğini değiştirmez. MyClass(MyObject()) tanımlandığında ve MyObject() __mro_entries__ aracılığıyla (ConcreteBase,) döndürdüğünde, yalnızca ConcreteBase, MyClass.__mro__'da görünür. Eğer ConcreteBase, MyABC'nin sanal bir alt sınıfı olarak kaydedilirse, isinstance(MyClass(), MyABC) True döner, ancak MyABC MyClass.__mro__'da mevcut olmayacaktır. Adaylar sıkça sanal alt sınıflandırmayı gerçek kalıtım ile karıştırarak, neden super() çağrılarının veya MRO denetimlerinin ABC ilişkisini yansıtmadığı ya da neden ABC üzerinde tanımlanan yöntemlerin kalıtım yoluyla mevcut olmadığı konusunda kafa karışıklığına neden olurlar.