PythonProgramlamaPython Geliştirici

**Python** tanımlayıcıları, sınıf oluşturulması sırasında atanan özellik adlarını ve içeren sınıfı otomatik olarak hangi protokol yöntemi ile alır ve tanımlayıcılar sınıflara ilan sonrası eklendiğinde hangi kısıtlama ortaya çıkar?

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

Cevap.

Tarihçe.

Python 3.6'dan önce, özellik adlarını bilmesi gereken tanımlayıcılar, sınıf sözlüğünü taramak ve adları yerleştirmek için özelleştirilmiş metaclass'lara veya manuel sınıf dekoratörlerine dayanıyordu. Bu yaklaşım ayrıntılıydı, hata almaya açıktı ve karmaşık hiyerarşilerde metaclass çakışmalarına neden oluyordu. PEP 487, Python 3.6'da __set_name__ protokolünü tanıtarak bu gereksiz kodu ortadan kaldırmak için yorumlayıcının tanımlayıcılara otomatik olarak bildirimde bulunmasını sağladı.

Problem.

Bir tanımlayıcı örneği, sınıf gövdesi çalıştırılırken oluşturulur; ancak o anda bağlı olduğu değişken adı veya bulunduğu sınıfa dair içsel bir bilgiye sahip değildir. Bu bilgi, anlamlı hata mesajları oluşturmak, ORM sistemlerinde alanları kaydetmek veya serileştirme şemaları oluşturmak için gereklidir. Dışsal bir bildirim olmadan, tanımlayıcı anonim kalır ve geliştiricilerin özellik adını bir dize argümanı olarak tekrar etmelerini zorunlu kılarak DRY ilkelerine aykırı davranır.

Çözüm.

type.__new__ bir sınıf oluştururken, __prepare__ tarafından döndürülen ad alanı eşlemesini iter. __set_name__ yöntemine sahip her değer için yorumlayıcı value.__set_name__(owner_class, attribute_name) çağrısını yapar. Bu yöntem yapım aşamasındaki sınıfı ve özellik dizesini alarak tanımlayıcının bu meta verileri saklamasına olanak tanır. Ancak, bir tanımlayıcı sınıf oluşturma süreci tamamlandıktan sonra (monkey-patching) sınıf özelliğine atanırsa, __set_name__ otomatik olarak çağrılmaz çünkü tipe ilişkin mekanizma artık aktif değildir.

class TrackedDescriptor: def __set_name__(self, owner, name): self.owner = owner self.name = name def __get__(self, instance, owner): if instance is None: return self return f"{self.owner.__name__}.{self.name}" class Model: field = TrackedDescriptor() # Model.field.name == 'field' # Model.field.owner == Model

Hayattan bir durum

Konu.

Bir yapılandırma yönetimi kütüphanesi geliştirirken, çevresel değişkenleri temsil etmek için tanımlayıcılara ihtiyaç duydum. Bir değer eksik veya geçersiz olduğunda, hata, sınıftaki tam özellik adını belirtmeli (örneğin, Config.database_url is required), sadece genel bir mesaj değil.

Problem.

Başlangıçta, kullanıcıların ismi manuel olarak belirtmesi gerekiyordu: database_url = EnvVar('database_url'). Bu, yeniden yapılandırma sırasında dize sabiti ve değişken adı farklılaştığında hatalara neden oldu ve karmaşık çalışma zamanı hataları ortaya çıktı.

Farklı çözümler düşünüldü:

Metaclass enjeksiyonu. attrs'ı inceleyen bir ConfigMeta oluşturduk ve her tanımlayıcı için attr.set_name(name) çağrısında bulunduk. Bu çalıştı, ancak tüm kullanıcı sınıflarının metaclass'ımızdan miras almasını zorunlu kıldı ve bu da kendi metaclass’larını kullanan diğer kütüphanelerle uyumluluğu bozdu. Ayrıca, metaclass'larla tanışık olmayan kullanıcılar için zihinsel yük ekledi.

Sınıf dekoratörü ile yamanın yapılması. Sınıf oluşturulduktan sonra cls.__dict__ üzerinde dolaşan bir @config dekoratörü yarattık ve isimleri yama yaptık. Bu, metaclass çakışmalarını önledi ama bu isteğe bağlıydı; dekoratörü unutmak, bozuk tanımlayıcılara yol açtı. Ayrıca, sınıf oluşturulduktan sonra çalıştığı için, tanımlayıcılar __init_subclass__ kancaları sırasında isimlerini kullanamazdı, bu da içgörü yeteneklerini sınırladı.

__set_name__ protokolü. EnvVar tanımlayıcımıza __set_name__ ekledik. Bu, kullanıcı kodunda herhangi bir değişiklik gerektirmedi, sınıf tanımı sırasında otomatik olarak çalıştı ve tanımlayıcının ismini __init_subclass__ tamamlanmadan bilmesini sağladı, böylece erken doğrulama mümkün oldu.

Seçilen çözüm.

__set_name__'ı benimsedik çünkü bu, kullanıcılar için sıfır maliyetli bir soyutlama sağladı ve Python'un yerel veri modeli ile entegre oldu. Metaclass çakışma problemini tamamen ortadan kaldırdı.

Sonuç.

API açıklayıcı hale geldi: database_url = EnvVar(). Yeniden yapılandırma araçları, özelliklerin isimlerini güvenli bir şekilde değiştirebildi ve hata mesajları doğru kaldı. Kod tabanı, 150 satırlık metaclass gereksizliğinden kurtuldu ve yapılandırma anahtarları ile ilgili daha az hata raporu gözlemledik.

Adayların sıklıkla atladığı noktalar

__set_name__ sınıf oluşturma yaşam döngüsü sırasında tam olarak ne zaman çağrılır?

Sınıf gövdesinin yürütülmesi tamamlandıktan ve ad alanı sözlüğü doldurulduktan hemen sonra type.__new__ tarafından çağrılır, ancak üst sınıflar üzerinde __init_subclass__ çağrılmadan önce. Bu zamanlama kritik öneme sahiptir çünkü tanımlayıcıların alt sınıflar başlatılmadan önce durumlarını sonlandırmalarına olanak tanır. Zaten oluşturulmuş bir sınıfa nitelikler eklemek (örneğin, setattr(MyClass, 'new_attr', descriptor())) durumunda çağrılmaz, çünkü sınıf oluşturma protokolü sona ermiştir. Bu ayrımın anlaşılması dinamik sınıf manipülasyonu için hayati önem taşır.

Neden __set_name__ hem sahip sınıfı hem de ismi argüman olarak alır, kendisinden türetmek yerine?

Tanımlayıcı örneği sınıftan bağımsız olarak var olur; sınıf oluşturulmadan önce oluşturulabilir ve teorik olarak birden fazla sınıfa atanabilir (çok nadir olsa da). owner argümanı, tanımlayıcının atamanın gerçekleştiği belirli sınıfı bilmesini sağlar; bu, mirası doğru bir şekilde yönetmek için gereklidir. Bir tanımlayıcı bir üst sınıfta tanımlandığında, __set_name__ üst sınıfla çağrılır; alt sınıfta yeni bir örnek ile geçersiz kılındığında, alt sınıf ile çağrılır. Bu, taban ve türetilmiş sınıflar arasında çapraz kirlenmeyi önleyen per-sınıf kayıtları sağlar.

__set_name__ ile __set__ ve __get__ tanımlayıcı protokol yöntemleri arasında nasıl bir etkileşim vardır?

__set_name__ tamamen bir başlangıç kancasıdır ve özellik erişim protokolüne (__get__/__set__) katılmaz. Ancak, bu yöntemlerin işlevselliğini sağlamak için gerekli bağlamı sağlayarak o yöntemlerin doğru çalışmasını sağlar. Yaygın bir hata, bir tanımlayıcının, onu geçersiz kılmayan bir alt sınıfa miras alınması durumunda __set_name__'in yeniden çağrılacağını varsaymak. Aynı tanımlayıcı örneği yeniden kullanılacağından, __set_name__ yeniden çağrılmaz; bu nedenle, sınıf başına durum izleyen tanımlayıcıların mirası yönetmek için __init_subclass__ kullanmaları veya __get__ içinde owner'ı kontrol etmeleri gerekir; yalnızca __set_name__'e güvenmek yeterli değildir.