Python metaclass

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)
1 Beğeni

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:

  1. Özelleştirilmiş bir type sınıfı oluşturup, bu sınıfın __call__ metodunu yeniden tanımladık.
  2. Ö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. :slight_smile:

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)
1 Beğeni