Python'daki Classları Anlayamıyorum

Python’daki nesne kavramı ile sözlük kavramı birbirine benzeyen kavramlardır. Python nesnelerinin nitelikleri ve metodları varken, Python’daki sözlüklerin anahtarları ve bu anahtarların değerleri vardır. Python nesnelerinin nitelik ve metodlarının isimlerini bir sözlüğün anahtarlarıymış gibi, nitelik ve metodların değerlerini de bu sözlüğün değerleri gibi düşünebilirsiniz.

Sözlük yapısının temelinde C dilindeki struct’lar yer alıyor aslında. C, object oriented bir dil olarak geçmez ama nesne tabanlı dillerde yer alan “nesne” kavramını, C’deki struct yapısını kullanarak oluşturabiliriz. Yani struct veri yapısı, yeni sınıflar oluşturmak için gerekli özellikleri sağlar. Benzer şekilde dict veri tipi de bir sınıf ve o sınıfa ait nitelikleri ve metodları oluşturmak için yeterlidir. Python’da struct’a en çok benzeyen veri tipi dict veri tipidir dersek herhalde abartmış olmayız.

Şimdi gelin, bir Python sözlüğü tanımlayalım:

sozluk = {
    "x": {
        "x_x1": {
            "x_x1_x1": 1,
            "x_x1_x2": lambda x: print(x)
        },
        "x_x2": lambda x: print(x)
    },
    "y": 2
}

Bu sözlük, bir sınıfın niteliklerinin ve metodlarının nasıl bir düzende yer aldığını gösteren bir eşleştirme olarak da kabul edilebilir aslında. Şimdi bu sözlüğü nesneye çeviren bir fonksiyon yazalım ve bu fonksiyon da aşağıdaki işlemleri yapsın;

Sözlüğün anahtarlarının değerlerinin tipleri eğer dict ise, o anahtarlar nesnelere işaret etsin. Aksi durumda, yani sözlüğün anahtarlarının değerlerinin tipleri dict dışında başka bir veri tipiyse, o anahtarlar da nesnenin niteliği veya metodu haline gelsin. Sonuç olarak sözlük içinde sözlük, nesne içinde nesne olarak dönüştürülsün.

Fonksiyonu yazalım:

def from_dict_to_obj(d: dict) -> object:
    obj = type("", (), {})
    for key, value in d.items():
        if isinstance(value, dict):
            setattr(obj, key, from_dict_to_obj(value))
        else:
            setattr(obj, key, value)
    return obj

Şimdi from_dict_to_obj isimli fonksiyonu, sozluk isimli sözlüğü argüman olarak yazarak çağıralım. Sonra da sözlükte anahtar olarak yer alan x_x1_x2 ismini, bir metod ismi olarak kullanalım.

obj = from_dict_to_obj(sozluk)
obj.x.x_x1.x_x1_x2("hello")

Çıktı:

hello

dict tipinden object’e dönüştürme yapabiliyoruz, peki object’ten dict’e bir dönüşüm yapacak olsaydık, nasıl yapardık?

def from_obj_to_dict(o: object) -> dict:
    dictionary = {}
    for key, value in {k: getattr(o, k) for k in dir(o) if not k.startswith("_")}.items():
        if isinstance(value, type):
            dictionary[key] = from_obj_to_dict(value)
        else:
            dictionary[key] = value
    return dictionary

Hemen bir örnek yapalım:

obj = from_dict_to_obj(sozluk)
print(from_obj_to_dict(obj))

Çıktı:

{'x': {'x_x1': {'x_x1_x1': 1, 'x_x1_x2': <function <lambda> at 0x000001B81B6A9760>}, 'x_x2': <function <lambda> at 0x000001B81B6A9C60>}, 'y': 2}

Başka bir örnek:

class Test:
    def __init__(self, x):
        self.x = x

    class It:
        y = 1

        def print(self, a):
            print(a)


test = Test(x=10)
print(from_obj_to_dict(test))

Çıktı:

{'It': {'print': <function Test.It.print at 0x000001F82DD65EE0>, 'y': 1}, 'x': 10}

Şöyle deneyelim:

test = Test(x=10)
_dict = from_obj_to_dict(test)
_dict["It"]["print"]("1", "hello")

Çıktı:

hello

Son paylaştığım kodlardaki son satırdaki ifade şöyle:

_dict["It"]["print"]("1", "hello")

Bu kısmı açıklayayım izninizle:

Test sınıfı içinde yer alan It sınıfında tanımlı print metoduna dikkat ederseniz, metodun ilk argümanının self olduğunu görürsünüz. Buradaki self, sınıftan oluşturacağımız bir nesneyi işaret eder. Dolayısıyla print metodu bir sınıf metodu değil, bir örnek metodudur.

İyi ama, biz yukardaki kodda It sınıfından bir örnek oluşturmadan, print fonksiyonunu kullanabildik? Bu nasıl oldu?

nesneyi, sözlüğe dönüştürdükten sonra, print metodunun ilk argümanı, herhangi bir sınıfın örneğini argüman olarak kabul eder, özellikle It sınıfından oluşturulan bir örneğe ihtiyaç duyulmaz.

Yani, bu kod da aynı çıktıyı üretir:

_dict["It"]["print"]([1, 2, 3], "hello")

Bu kod da:

_dict["It"]["print"](1, "hello")

Peki, bundan nasıl kurtuluruz?

class Test:
    def __init__(self, x):
        self.x = x

    class It:
        y = 1

        def print(a):
            print(a)

print fonksiyonunu, yukardaki gibi bir örnek veya sınıf metodu olmaktan çıkarırsak, artık _dict["It"]["print"](1, "hello") ifadesi yerine, _dict["It"]["print"]("hello") ifadesini kullanabiliriz.

Bu arada, metod, bir sınıf metodu olsaydı, yani, print fonksiyonu şöyle olsaydı:

class Test:
    def __init__(self, x):
        self.x = x

    class It:
        y = 1

        def print(cls, a):
            print(a)

Bu sefer, print fonksiyonunu çağırırken, ilk argüman olarak herhangi bir sınıfın kendisini argüman olarak verebilirdik:

_dict["It"]["print"](int, "hello")

Bu mesajda Python’daki nesneleri dinamik bir yolla oluşturmamızı sağlayan bir yaklaşımdan bahsetmeye çalıştım. Şimdilik anlatacaklarım bu kadar. Sorusu olan arkadaşların, sorularını yanıtlamaktan memnuniyet duyarım. Ayrıca katkı sağlamak isteyen arkadaşlara da şimdiden teşekkür ederim.

Konuyla bağlantılı olarak aşağıdaki başlığın da belki faydası olabilir.

Herkese iyi çalışmalar.