Arkadaşlar metaclass nedir?
Örneği bir sınıf olan sınıflara metaclass
denir ve bütün metaclasslar type
sınıfını miras olarak alır.
type
3 tane argüman alır. Bunlardan birincisi sınıfın ismi. İkinci argüman miras alınan sınıflar, üçüncüsü ise özelliklerdir.
Birinci argüman bir string verisidir. İkinci argüman bir tuple verisidir, üçüncü argüman ise bir sözlük verisidir.
Bir metaclass’ın oluşturulma şekli genelde şöyledir:
class MetaClass(type):
def __new__(mcs, classname, superclasses, attributes):
print("metaclass: ", mcs)
print("classname: ", classname)
print("superclasses: ", superclasses)
print("attributes: ", attributes)
return super().__new__(mcs, classname, superclasses, attributes)
Aşağıda da iki tane sıradan sınıflar var.
class SuperClass1:
pass
class SuperClass2:
pass
Bunların isimlerini SuperClass
şeklinde yazdım, çünkü başka bir sınıf daha tanımlayacağız, bu iki sınıf miras alınan sınıflar olacak ve yukarıdaki MetaClass
sınıfını tanımlayacağımız sınıfın metaclass’ı haline getireceğiz.
class Class(SuperClass1, SuperClass2, metaclass=MetaClass):
pass
Class
sınıfı SuperClass1
ve SuperClass2
sınıflarını miras olarak alıyor. Ve bir sınıf tanımlarken metaclass
isimli bir parametreye MetaClass
’ın ismi yazılır. Bu sınıfı tanımlar tanımlamaz, daha örneğini oluşturmadan şöyle bir yazı yazdırılması gerekiyor:
metaclass: <class '__main__.MetaClass'>
classname: Class
superclasses: (<class '__main__.SuperClass1'>, <class '__main__.SuperClass2'>)
attributes: {'__module__': '__main__', '__qualname__': 'Class'}
Şimdi aşağıdaki gibi bir sorgu yaparsanız, bu kodun AssertionError
yükseltmediğini görürsünüz. Bu da demektir ki Class
sınıfı, MetaClass
sınıfının bir örneğidir. Aslında bütün sınıfların metaclass’ı type
’dır.
assert isinstance(Class, MetaClass)
assert isinstance(MetaClass, type)
Tekrar merhaba,
Konuyu hortlatıyorum çünkü metasınıflarla alakalı daha açıklayıcı örnekler yapabilirmişiz.
Önce tanımla başlayalım.
Metaclass, bir sınıfın özelliklerini o sınıfın içinde değil de, o sınıfı aşan bir alanda tasarlamamıza imkan veren bir sınıf tipidir.
Metasınıflar ya doğrudan ya da dolaylı olarak type
'ı miras alırlar, çünkü Python’da temel metasınıf tipi type
'tır.
class X:
pass
print(type(X)) # <class 'type'>
Şimdi yavaş yavaş bu söylediklerimizin altını doldurmaya başlayalım:
Nesne tabanlı programlamada Override
adında “geçersiz kılmak” anlamına gelen bir kavram var. Bu kavram, sınıfların miras aldıkları metodları geçersiz kılıp, aynı isimlerde yeni metodlar tasarlamak ile ilgilidir.
Madem ki bütün sınıfların metaclass’ı type
, o halde type
'ın bir takım özelliklerini değiştirerek (override
) farklı metaclasslar oluşturabiliriz.
O halde şimdi X
sınıfını bir metaclass’a dönüştürelim, yani özelleştirilmiş bir type
sınıfı oluşturalım ve type
'ı değil de X
'i metasınıf olarak kullanan bir Y
sınıfı yazalım.
class X(type):
pass
class Y(metaclass=X):
pass
print(type(Y)) # <class '__main__.X'>
Gördüğünüz gibi, bir önceki örnekte type(X)
, <class 'type'>
döndürüyordu. Oysa bu örnekte type(Y)
, <class '__main__.X'>
döndürüyor. Henüz type
'ın herhangi bir özelliğini override
etmedik. Ama gelin şimdi bir sınıfı bir fonksiyon gibi çağırmamızı sağlayan __call__
metodunu override
edelim.
class X(type):
def __call__(cls, *args, **kwargs):
pass
class Y(metaclass=X):
pass
print(Y()) # None
Yukardaki Y
sınıfı hiç bir şekilde bir nesneye işaret etmez artık. Yani Y
'nin a
gibi bir niteliği olsaydı, bu a
niteliğine ulaşamazdık.
class X(type):
def __call__(cls, *args, **kwargs):
pass
class Y(metaclass=X):
def __init__(self, a):
self.a = a
y = Y(a=1)
print(y.a)
Son ifade AttributeError: 'NoneType' object has no attribute 'a'
hatası yükseltecektir. Ama biz Y(a=1)
yazdık, ayrıca self.a = a
yazdık, neden a
diye bir niteliğimiz yok? Yok, çünkü biz Y
sınıfının kurallarını type
metaclass’ı ile kurmadık, X
metaclassı ile kurduk.
Başka bir örnek yapalım. Örneğin, bir sınıftan oluşturacağımız bütün nesnelerin aynı id
'ye sahip olması isteniyor. Yani o sınıftan sadece bir tane orijinal örnek oluşturabilelim, diğer örnekler ise, kopya örnekler olsun.
class X(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, ""):
setattr(cls, "", super().__call__(*args, **kwargs))
return getattr(cls, "")
class Y(metaclass=X):
def __init__(self, x):
self.x = x
y1 = Y(x=1)
y2 = Y(x=2)
print(y1 is y2)
print(y1.x)
print(y2.x)
Yukardaki örnekte, y1
ve y2
aynı nesneye işaret etmektedir.
Özetle yukarda, bir sınıfın __call__
metodunun davranışının nasıl olacağını aşağıdaki aşamalarla yeniden tanımladık:
- Özelleştirilmiş bir
type
sınıfı oluşturup, bu sınıfın__call__
metodunu yeniden tanımladık. - Özelleştirilmiş
type
sınıfını,__call__
metodunun davranışını değiştirmek istediğimiz sınıfın metaclass’ı olarak seçtik.
Peki bir sınıftan örnek oluşturmayı yasaklayıp, başka bir sınıfın örneği gibi davranmasını sağlayabilir miyiz?
class X(type):
def __new__(cls, *args, **kwargs):
return []
class Y(metaclass=X):
pass
for i in range(20):
Y += [i]
print(Y)
print(Y()) # TypeError: 'list' object is not callable
Bu son yazdığımı kullanma ihtiyacım olur mu? Şu ana kadar olmadı, olacağını da sanmıyorum. Ama kurcalıyoruz işte.
Başka hangi durumlarda metaclass kullanabiliriz?
Örneğin, bir sınıftan örnek oluştururken yazılan argümanların sadece belirli tiplerde olmasını metaclass kullanarak şart koşabiliriz. Gerçi, argüman tipi kontrolünü, örnek oluşturulurken de tanımlayabiliriz, ama, böyle bir kontrolün gerekli olduğu birden çok sınıf tanımlayacaksak ve her bir sınıfta aynı işlemleri tekrar yazmamak istiyorsak, bir metaclass kullanabiliriz.
class X(type):
def __call__(cls, *args, **kwargs):
if "x" in kwargs and not isinstance(kwargs["x"], str):
raise ValueError("'x' argument must be a str.")
return super().__call__(*args, **kwargs)
class Y(metaclass=X):
def __init__(self, x):
self.x = x
y1 = Y(x=1)