Örneklerle Python Metasınıflar
Bugün Eli Bendersky'ye ait Python metaclasses by example makalesini okudum. Metasınıflar benim için hep üç aşağı beş yukarı havada kalan kavramlar olmuşlardır. Bahsettiğim makaleyi okuduktan sonra, kafamda bir nebze daha iyi oturtabildim bunları. Bunun üzerine bu makaleyi Türkçe'ye çevirip paylaşmaya karar verdim. Tabi ki, Eli Bendersky'nin izni ile. Aşağıda makalenin tam metninin çevirisini bulabilirsiniz:
Python, çalışma yapısını ve özelliklerini gizleyen birçok "sihir" olmayışından ve diğerlerinden daha açık bir dil olduğundan haklı olarak gurur duyar. Diğer yandan, bazen, ilgi çekici soyutlamalara imkan sağlamak için olağandandan daha sihirli dil yapıları bulunan Python’un daha kirli ve anlaşılması güç kısımları deşilebilir. Metasınıflar böyle özelliklerdir.
Malesef, metasınıfların namı, "sorun arayan çözümler" olarak bilinir. Bu makalenin amacı, sıkça kullanılan Python kodlarının içerisinde, metasınıfların gerçek kullanımından birkaç örnek göstermektir.
İnternette Python metasınıflarıyla ilgili birçok materyal var (ÇN: Türkçe yok malesef), yani bu sadece metasınıflar üzerine başka bir ders değil (Referanslar bölümünde faydalı bulduğum linklere bakabilirsiniz.). Metasınıfların ne olduğu konusuna biraz değineceğim fakat, benim amacım örnekler. Bunu söylemişken, bu makale kendi yağında kavrulmayı hedefler – metasınıfların ne olduğunu bilmiyorsanız bile okumaya başlayabilirsiniz.
Başlamadan önce bir diğer hatırlatma – bu makale Python 2.6 & 2.7’e göre yazılmıştır, çünkü internette bulacağınız birçok kod hala bu sürümler içindir. Python 3.x’de metasınıflar benzer şekilde çalışsa da bunları belirtmenin söz dizimi birazcık farklıdır. Sonuç olarak, bu makalenin büyük bir kısmı 3.x’e de uygundur.
Sınıflar da objedir
metasınıflar’ı anlamak için sınıflar hakkındaki bazı şeyleri açıklığa kavuşturmalıyız. Python’da herşey bir objedir. Sınıflar da dahil. Aslına bakarsanız, Python’da sınıflar birinci-sınıf objelerdir – çalışma anında yaratılabilir, parametre olarak gönderilebilir ve fonksiyonlardan döndürülebilir ve değişkenlere atanabilir. Sınıfların bu özelliklerini gösteren interaktif komut satırından bir örnek:
Burada type yerleşik fonksiyonunun 3 argümanlı şeklini MyKlass
isimli, object’den miras alan, bazı özellikleri argüman olarak
sağlanmış bir sınıfı dinamik olarak oluşturmak için kullanıyoruz. Daha
sonra böyle bir sınıf yaratabiliriz. Görebileceğiniz gibi,
myklass_foo_bar şuna eşittir:
Ancak çalışma anında oluşturulmuş, fonksiyondan döndürülmüş ve bir değişkene atanmıştır.
Sınıf’ın sınıf’ı
Python’daki her obje (yerleşikler de dahil) bir sınıfa sahiptir. Biraz
önce sınıfların da obje olduklarını gördük, yani sınıfların da bir
sınıfı olmak zorunda, değil mi? Kesinlikle. Python __class__
özelliği ile bir objenin sınıfını incelememize izin verir. Bunu
çalışırken görelim:
|
>>> class SomeKlass(object): pass
|
|
...
|
|
>>> someobject = SomeKlass()
|
|
>>> someobject.__class__
|
|
<class __main__.SomeKlass>
|
|
>>> SomeKlass.__class__
|
|
<type 'type'>
|
Bir sınıf ve bu sınıfın bir objesini yarattık. someobject objesinin
__class__ özelliğini inceleyerek, bunun SomeKlass olduğunu gördük.
İlginç kısım şimdi geliyor. SomeKlass’ın sınıfı ne? __class__ ile
bunu tekrar inceleyebiliriz ve bunun type olduğunu görüyoruz.
Yani type, Python sınıflarının sınıfıdır. Diğer bir deyişle,
yukarıdaki örnekteki someobject bir SomeKlass objesiyken,
SomeKlass’ın kendisi de bir type objesidir.
Sizi bilmem ama ben bunu güven tazeleyici buluyorum. Madem sınıfların Python’da obje olduklarını öğrendik, bunların da bir sınıflarının olması mantıklı ve bir yerleşik sınıfın (type) sınıfların sınıfı rolünü oynaması güzel.
Metasınıf
Metasınıf "sınıfın sınıfı" olarak tanımlanır. Kendi örnekleri de bir
sınıf olan her sınıfa metasınıf denir. Öyleyse, yukarıda gördüklerimize
göre bu type’ı bir metasınıf yapar – aslında, sınıfların öntanımlı
metasınıfı olduğu için en çok kullanılan metasınıftır.
Metasınıf bir sınıfın sınıfı olduğu için, sınıf oluşturmakta kullanılır
(sınıfların obje oluşturmakta kullanıldığı gibi). Bir dakika, biz
sınıfları standart class tanımıyla oluşturmuyor muyuz? Kesinlikle, ama
Python merdiven altında şunları yapıyor:
-
classtanımı gördüğünde Python bunu çalıştırarak özellikleri (metotlar da dahil) bir sözlüğe toplar. -
classtanımı bittiğinde Python sınıfın metasınıfını belirler. Şimdilik bunaMetadiyelim. -
Daha sonra Python
Meta(name, bases, dct)deyimini çalıştırır ki burada:-
Metametasınıftır ki bunu çağırmak bir örneğini oluşturmaktır. -
nameyeni oluşturulan sınıfın ismidir. -
basessınıfın temel sınıflarının bir demetidir. -
dctSınıf’ın tüm özelliklerini listeleyerek özellik isimlerini objelerle eşletirir.
-
Bir sınıfın metasınıfını nasıl belirleriz? Basitçe eğer sınıf veya
temelleri __metaclass__ özelliği tanımlıyorsa, bu metasınıf olarak
alınır. Aksi takdirde metasınıf typedır.
O halde şunu tanımladığımızda olan nedir:
Şudur: MyKlass’ın __metaclass__ özelliği yok, yani bunun yerine
type kullanılıyor, ve sınıf oluşumu şu şekilde yapılıyor:
Ki bu, makalenin başında gördüğümüzle tutarlı. Ancak diğer yandan eğer
MyKlass bir metasınıf tanımlasaydı:
O zaman sınıf oluşumu şöyle olurdu:
O halde, MyMeta bu şekilde çağırılmayı destekleyecek ve bir sınıf
döndürecek şekilde uygulanmalı. Aslına bakarsanız bu, önceden
tanımlanmış yapıcı imzası olan normal bir sınıf yazmaya benzer.
Metasınıf’da __new__ ve __init__
Sınıfın metasınıf içinde oluşturulmasını ve ilklenmesini (ÇN:
initialization) kontrol etmek için, metasınıf’da __new__ metodunu
ve/veya __init__ yapıcısını uygulayabilirsiniz (ÇN: implement). Çoğu
gerçek-hayat metasınıfları muhtelemen sadece bir tanesinin üstüne
yazar.__new__ yeni obje yaratılmasını kontrol etmek istediğinizde
(bizim durumumuzda bu bir sınıf) ve __init__ yeni obje yaratıldıktan
sonra, ilklenmesini istediğinizde uygulanmalıdır.
Yani, MyMeta çağırıldığında, merdiven altında yapılan şey:
Burada olup biteni göstermek için daha sağlam bir örnek var. Haydi bir metasınıf için şu tanımı yazalım:
Python aşağıdaki class tanımını çalıştırdığında:
Ekrana basılan şudur (güzel görünmesi için biraz düzenlenmiştir):
-----------------------------------
Sınıf için hafıza ayırılıyor MyKlass
(,)
{'barattr': 2, '__module__': '__main__',
'foo': ,
'__metaclass__': }
-----------------------------------
Sınıf başlatılıyor MyKlass
(,)
{'barattr': 2, '__module__': '__main__',
'foo': ,
'__metaclass__': }
Bu örneği iyice anlayın ve metasınıf yazmakla ilgili bilinmesi gerekenlerin çoğunu kavrayacaksınız.
Bu ekrana yazdırmaların sınıf oluşturma anında yapıldığını hatırlatmak burada önem arz ediyor, yani, bu sınıfı içeren modülün içe aktarıldığı zamanda. Bunu aklınızın bir köşesine yazın.
Metaclass’da __call__
Bazen üzerine yazılması faydalı olabilecek bir diğer metasınıf metodu
__call__’dur. Bunu __new__ ve __init__ metotlarında ayrı ele
almamın nedeni, __call__ metodunun, bu ikilinin sınıf oluşturulması
anınında çalışmasının aksine, zaten oluşturulmuş sınıfın, yeni bir obje
örneklenmesi (ÇN:instantiate) için "çağırıldığında" çalıştırılması. İşte
bunu açıklığa kavuşturmak için biraz kod:
Bu şunu yazdırır:
Şimdi foo oluşturacağım __call__ of __call__ *args= (1, 2) MyKlass object with a=1, b=2
Burada MyMeta.__call__ sadece bizi argümanlardan haberdar edip, işi
type.__call__ metoduna devrediyor. Ama aynı zamanda işleme müdahale
edip, sınıflardan nasıl obje yaratıldığını etkileyebilir. Bir bakıma,
sınıfın kendisindeki __new__ metodunu yazmaya benzer, ancak yinede
birkaç farkı vardır.
Örnekler
Şimdiye kadar metasınıflar nedir ve nasıl yazılır konularını anlamak için yeteri kadar teori işledik. Bu noktada, konuyu açacak örneklerin zamanı geldi. Yukarıda bahsettiğim gibi, sentetik örnekler yazmaktansa, gerçek Python kodu içerisindeki metasınıf kullanımını incelemeyi tercih ederim.
string.Template
İlk metasınıf örneği Python standart kütüphanesinden alınmıştır. Python ile birlikte gelen çok az sayıdaki metasınıf örneklerinin bir tanesidir.
string.Template kullanımı kolay, yazı doldurma (ÇN: string
substitution) özelliği sağlar ve çok basit bir şablonlama sistemi görevi
yapar. Eğer bu sınıfla aşina değilseniz, şimdi belgeleri okumak için
güzel bir zaman. Ben sadece metasınıfları nasıl kullandığını
açıklayacağım.
İşte class Template içerisinden ilk birkaç satır:
Ve bu da _TemplateMetaclass :
Bu makalenin ilk kısmında sağlanan açıklama _TemplateMetaclass’ın
nasıl çalıştığını anlamaya yeterli olmalı. Bunun __init__ metodu bazı
sınıf özelliklerine bakar (özellikle pattern, delimeter ve
idpattern) ve bunu kullanarak (veya kendi sağladığı öntanımlıları)
derlenmiş bir düzenli ifade oluşturur ki, bu daha sonra sınıf’ın kendi
pattern özelliğine depolanır.
Belgelerine göre, Template miras alınarak, özel delimeter ve ID
yapısı, veya tam bir düzenli ifade sağlanabilir. Metasınıf sınıf
oluşturma anında bunları derlenmiş düzenli ifadelere dönüştürür, yani
bu bir nevi optimizasyondur.
Demek istediğim, aynı özelleştirme metasınıf kullanmadan, basitçe,
derlenmiş düzenli ifadeyi __init__ içerisinde oluşturarak
yapılabilir. Ancak, bunun anlamı, derlenme işleminin her Template
objesi örneklendiğinde yapılacak olmasıdır.
Şu kullanımı düşünün, ki bu kendi dürüst fikrime göre string.Template
için sıkça görülür:
|
>>> from string import Template
|
|
>>> Template("$name is $value").substitute(name='me', value='2')
|
|
'me is 2'
|
Düzenli ifade derlenmesini Template oluşturma zamanına bırakmak, böyle
bir kod her çalıştığında bunun oluşturulması ve derlenmesi demektir. Bu
bir ayıp, çünkü düzenli ifade şablondaki yazıya bağlı değil, sadece
sınıfın özelliklerine bağlı.
Bir metasınıf ile sınıfın pattern özelliği modül yüklenip
class Template (veya bir alt sınıfı) tanımı çalıştırıldığında
oluşturuluyor. Bu Template objesi yaratıldığında zaman kazandırır, ve
anlamlıdır çünkü sınıf oluşturma zamanında düzenli ifadeyi derlemek için
gerekli tüm bilgilere sahibiz – öyleyse neden bekleyelim?
Bunun henüz olgunlaşmamış bir optimizasyon olduğu iddia edilebilir, ve bu doğru olabilir. Metasınıfın bu (veya herhangi bir) kullanımını savunmayı düşünmüyorum. Buradaki niyetim çeşitli görevler için metasınıfların gerçek kodlarda nasıl kullanıldığını sergilemek. Yani, eğitsel amaçlar için iyi bir örnek, çünkü ilginç bir kullanımı gösteriyor. Olgunlaşmamış bir optimizasyon veya değil, metasınıf bir hesaplamayı kod çalıştırma sürecinde bir adım önceye alarak kodu daha etkin hale getiriyor.
twisted.python.reflect.AccessorType
Aşağıdaki örnek metasınıfları gösterirken/anlatırken sıkça kullanılır. Bunun belgelerinden bir alıntı:
Kendiliğinden sınıf özellikleri üreten bir metasınıf. Bu metasınıfı kullanmak, sınıfınıza açık erişici metotları (ÇN: explicit accessor methods) sağlar; set_foo isimli bir metot, kendi kendine, set_foo metodunu setter olarak kullanan ‘foo’ özelliğini oluşturacak. get_foo ve del_foo için de aynı şekilde. (ÇN: bakınız)
İşte metasınıf, önemli kısımlara vurgu yapmak için biraz kısaltılmış olarak:
Bunun yaptığı dümdüz bir işlem:
- Sınıfın
get_,set_veyadel_ile başlayan tüm özelliklerini bul. - Kontrol etmeyi hedefledikleri özelliklere göre sınıflandır (altçizgiden sonra gelen kısım)
- Böylelikle bulunan her getter, setter, deleter üçlüsü için
- Üçünün hepsinin var olduğundan emin ol, veya uygun öntanımlılar oluştur.
- Bunları sınıf içinde bir
propertyolarak ayarla
Böyle bir metasınıf ne kadar faydalıdır? Söylemesi zor aslında. Twisted’ın kendisi bunu kullanmıyor ama bunu public API olarak sağlıyor. Eğer birçok property ile birkaç sınıf yazacaksanız, bu metasınıf sizi birçok kodlamadan kurtarabilir.
pygments Lexer ve RegexLexer
pygments kütüphanesi metasınıf kullanımının ilginç bir deyimini
sunar. Bir temel sınıf özel bir metasınıf ile oluşturulmuş. Kullanıcı
sınıfları bu temel sınıfdan miras alıp, metasınıf’ı yanında bir bonus
olarak alırlar. Öncelikle LexerMeta metasınıfına bir bakalım. Bu
Lexer için bir metasınıf olarak kullanılır – pygments içindeki
lexerlar için temel sınıf:
Bu metasınıf, analyse_text mesajının tanımını yakalayıp, bunu herzaman
kayan noktalı bir değer döndüren statik bir metot’a çevirmek için
__new__ metodunun üstüne yazar (make_analysator fonksiyonunun
yaptığı budur.).
Burada __init__ yerine __new__ kullanımına dikkat edin. Neden
__init__ kullanılmadı? Benim düşüncem, bunun sadece bir tercih
meselesi olduğu – aynı etki __init__ metodunun üstüne yazarak da
başarılabilirdi.
pygments’den ikinci örnek biraz daha karmaşık, ama daha önceki
örneklerde görmediğimiz birkaç özelliği içerdiği için açıklama zahmetine
değer. RegexLexerMeta için kod bir hayli uzun, bu yüzden ilgili
kısmı bırakmak için kodu keseceğim:
Çoğunlukla, kod oldukça temiz; metasınıf tokens sınıf özelliğini
inceliyor, ve bundan _tokens oluşturuyor. Bu sadece sınıfın
oluşturulması sırasında yapılıyor. Burada özellikle ilgilendiğimiz iki
şey var:
-
RegexLexerMetaLexerMeta’dan miras alır, böylece bunun kullanıcıları daLexerMeta’nın sağladığı hizmetleri alır. Metasınıfların miras alınması, bunların Python’daki en güçlü dil yapılarından birisi olmasının nedenlerinden birisidir. Bunları sınıf dekoratörleriyle yanyana koyun mesela. Bazı basit işler için, sınıf dekoratörleri metasınıfların yerine geçebilir, ama metasınıfların miras ilişkisi kurabilmesi dekoratörlerin yapamaycağı birşeydir. -
process_tokendefhesaplamaları__call__içerisinde yapılıyor – ve özel bir kontrol bunun sadece sınıfın ilk örneklenmesinde çalıştığından emin oluyor. (__call__’un kendisi tüm örneklenmelerde çalışsa da). Neden böyle yapılsın, bunu sınıf üretimi anında yapmak varken (metasınıf’ın__init__’inde mesela)? Bana öyle geliyor ki, bu bir çeşit optimizasyon olabilir. pygments bir çok lexer ile birlikte gelir, ama belirli bir kod için bunların sadece bir veya ikisini kullanmak isteyebilirsiniz. Sadece kullandığınız lexer yerine, neden kullanmadığınız lexerlara yükleme zamanı harcayasınız? Gerçek sebep bu olsa da olmasada, bence yine de bu, metasınıfların kafa yorulması gereken ilginç bir yönü – meta-işlerinin nerede ve ne zaman yapıldığını seçmenize olanak sağlayan muhteşem esnekliği.
Sonuç
Bu makaleyi yazmaktaki amacım Pythondaki metasınıfların nasıl çalıştığını açıklamak ve gerçek Python kodundan sağlam metasınıf kullanım örnekleri göstermekti. Metasınıfların kötü bir şöhreti olduğunu biliyorum, çoğu insan bunları olması gerektiğinden daha fazla sihirli olarak görüyor. Benim bu konudaki düşüncem, diğer dil yapılarında olduğu gibi, metasınıfların bir araç olduğu ve programcının sonuçta bunu doğru kullanmaktan sorumlu olduğu. Her zaman iş gören en basit kodu yazın, ama ihtiyacınız olanın metasınıflar olduğunu hissederseniz, bunları kullanmakta özgürsünüz.
Umarım bu makale sınıfların oluşturulması ve kullanılması için
metasınıfların sağladığı esnekliği göstermiştir. Örnekler metasınıfların
uygulanması ve kullanılmasında çeşitli yönlerini göstermiştir;
__init__,__new__ ve __call__ metotlarının üstüne yazılması,
metasınıfın miras alınması, sınıflara özellikler eklenmesi, obje
metotlarının statik metotlara dönüştürülmesi ve gerek sınıf tanımında
gerekse de örneklenme zamanında optimizasyonlar yapılması.
Python içinde metasınıfların kullanılmasının en dikkate değer örnekleri muhtemelen ORM (Object Relational Mapping) çatılarıdır, Django’nun modellerinde olduğu gibi. Gerçekten, bunlar metasınıfların neler yapabileceğini göstermekte güçlü örneklerdir, ancak ben bunları burada göstermemeye karar verdim çünkü onların kodları karmaşık ve birçok alana-özel detaylar metasınıfları sergilemek olan asıl amaca zarar verirdi. Diğer yandan, bu makaleyi okumuş olmakla, daha karmaşık örnekleri anlamak için gereken herşeye sahip oldunuz.
Ekleme: Eğer metasınıfların diğer ilginç örneklerini bulursanız lütfen bana bildirin. Daha fazla gerçek-hayat kullanımı görmekle bir hayli ilgileniyorum.
Referanslar
- Resmi belgelerdeki Data model sayfası
- Python’da metasınıf programlama – metasınıfları açıklayan bir dizi makale
- What is a metaclass in Python? – muhteşem bir StackOverflow tartışması.
- Python 2.2 içinde type ve sınıfları birleştirme – Guido von Rossum tarafından yazılan metasınıflara da dokunan bir hayli bilgi verici makale
-
__new__ve__init__arasındaki fark konusunda bir StackOverflow tartışması . - metasınıfların sağlam kullanımı hakkında bir diğer StackOverlflow tartışması. .