Veri Bilimi Okulu

Python dataclass
data_classes_kapak_960x640

Loading

Veri sınıflarının Python’da nasıl çalıştığını ve neden geleneksel sınıflara göre daha avantajlı olduklarını hiç merak ettiniz mi? Bu blog yazısı, Python’daki veri sınıflarının (dataclasses) hızlı bir rehberini sunacak. Veri sınıflarını nasıl kullanacağınızı, beraberinde gelen özel özellikleri, geleneksel bir sınıf oluşturmaktan neden daha hızlı olduklarını ve verilerinizi modelleme konusunda size nasıl daha fazla işlevsellik sağladıklarını örneklerle göstereceğim.

Başlamak için, dataclasses modülünden dataclass‘ı içe aktaracağız. Bu, Python’da veri sınıflarını kolayca oluşturmamızı sağlayacak.

Python

from dataclasses import dataclass

Geleneksel Sınıf ile Veri Saklama

Öncelikle, veri saklamak için geleneksel bir sınıfı nasıl oluşturacağımızı göstereyim. Diyelim ki, sadece bazı verileri tutmasını istediğimiz bir Meyve sınıfı oluşturmak istiyoruz.

Python

class Meyve:
    def __init__(self, ad: str, kalori: float):
        self.ad = ad
        self.kalori = kalori

muz = Meyve("Muz", 10.0)
elma = Meyve("Elma", 20.0)

print(muz.ad)       # Muz
print(elma.kalori)  # 20.0

Bu, değerlerle bir meyveyi başlatan basit bir sınıf oluşturur ve onu bir veri sınıfı olarak kullanabiliriz çünkü artık bu alanlara sahibiz. Ancak, muz veya elma‘yı doğrudan yazdırdığımızda ne olur?

Python

print(muz)
print(elma)

Çıktı, objenin bellek adresini gösteren pek kullanışlı olmayan bir temsil olacaktır:

<__main__.Meyve object at 0x...>
<__main__.Meyve object at 0x...>

Bu, genellikle istediğimiz bir şey değildir çünkü objenin içeriğini görmemizi engeller.

Veri Sınıfı (Dataclass) ile Veri Saklama

Şimdi, aynı örneği veri sınıfı içe aktarmasını kullanarak nasıl oluşturacağımızı göstereyim. Python’da bunu yapmak için, dataclass adlı bir dekoratör kullanmanız gerekir ve normalde yaptığınız gibi doğrudan altına bir sınıf oluşturursunuz:

Python

from dataclasses import dataclass

@dataclass
class Meyve:
    ad: str
    kalori: float

muz_dataclass = Meyve("Muz", 10.0)
elma_dataclass = Meyve("Elma", 20.0)

print(muz_dataclass)
print(elma_dataclass)

Bu sefer çıktımız çok daha okunaklı olacak:

Meyve(ad='Muz', kalori=10.0)
Meyve(ad='Elma', kalori=20.0)

Bu gerçekten çok güzel, çünkü daha önce de gösterdiğim gibi, normal bir sınıfla sadece objenin temsilini alırsınız. Burada ise, veriyi modellememizi ve yazdırdığımızda doğru bir şey geri almamızı söylüyoruz, tıpkı aşağıda yazdırdığımız muz gibi, bunun gerçekte ne olduğuna dair doğru bir temsil aldık.

Veri Sınıfları ve Karşılaştırma

Veri sınıfları ile normal sınıflar arasındaki bir diğer fark, onları karşılaştırdığınızda ortaya çıkar. Meyve sınıfımızla değerlerini karşılaştırmak ekstra kurulum gerektirecektir. Örneğin, aynı değerleri içeren iki muzumuz varsa:

Python

muz1 = Meyve("Muz", 10.0)
muz2 = Meyve("Muz", 10.0)

print(muz1 == muz2)

Çıktı False olacaktır. Bunun nedeni, meyveleri karşılaştırdığınızda, örneklerin aynı olup olmadığını kontrol etmesidir. Yani, aynı değerlere sahip olsalar bile aynı obje değillerdir.

Ancak, bir veri sınıfıyla durum farklıdır:

Python

muz_dataclass1 = Meyve("Muz", 10.0)
muz_dataclass2 = Meyve("Muz", 10.0)

print(muz_dataclass1 == muz_dataclass2)

Bu sefer çıktı True olacaktır. Çünkü muz_dataclass1 ve muz_dataclass2‘nin değerleri aynıdır, karşılaştırma doğru dönecektir.

Elbette, geleneksel bir sınıfla da bunu yapabilirsiniz. __eq__ Dunder metodunu tanımlamanız gerekir:

Python

class Meyve:
    def __init__(self, ad: str, kalori: float):
        self.ad = ad
        self.kalori = kalori

    def __eq__(self, other):
        if not isinstance(other, Meyve):
            return NotImplemented
        return self.ad == other.ad and self.kalori == other.kalori

muz1 = Meyve("Muz", 10.0)
muz2 = Meyve("Muz", 10.0)
print(muz1 == muz2) # True

Ancak bu, veri sınıfının varsayılan olarak sağladığı bir şey için ek işlevsellik eklememiz gerektiği anlamına gelir. Veri sınıfı kullanmanın ilk nedeni, tüm bu gereksiz koddan kaçınmaktır. Elbette neyi uyguladığınızı görmek güzel, ancak bunu her seferinde yapmanıza gerek yok. dataclass anahtar kelimesini ekleyerek, bu kalıp kodu (boilerplate code) gerçekten azaltmamıza yardımcı oluyor.

Veri Sınıflarında Varsayılan Değerler

Tıpkı normal sınıflarda olduğu gibi, veri sınıflarında da varsayılan değerler sağlayabilirsiniz:

Python

@dataclass
class Meyve:
    ad: str
    kalori: float = 10.0 # Varsayılan değer

muz_varsayilan = Meyve("Muz")
print(muz_varsayilan)

Çıktı: Meyve(ad='Muz', kalori=10.0)

Veri Sınıfı Değerlerini Değiştirme

Veri sınıflarındaki değerleri değiştirebileceğinizi anlamak da önemlidir:

Python

muz_dataclass = Meyve("Muz", 10.0)
print(muz_dataclass) # Meyve(ad='Muz', kalori=10.0)

muz_dataclass.kalori = 60.0
print(muz_dataclass) # Meyve(ad='Muz', kalori=60.0)

Belki de bunu istemiyorsunuzdur. Belki meyvenizin verilerini değiştirememesini, sadece okunabilir olmasını istiyorsunuzdur. Özellikle çevrimiçi bir yerden veri okurken bu sıkça karşılaşılan bir durum olabilir.

frozen=True ile Sabit Veri Sınıfları

Bilmeniz gereken çok önemli bir anahtar kelime, veri sınıfındaki frozen anahtar kelimesidir. Bunu True olarak ayarladığımızda, veri sınıfını salt okunur yapar:

Python

@dataclass(frozen=True)
class Meyve:
    ad: str
    kalori: float

muz_frozen = Meyve("Muz", 10.0)
# muz_frozen.kalori = 60.0 # Bu satır hata verecektir (FrozenInstanceError)
print(muz_frozen)

Bu kodu çalıştırmaya çalıştığınızda dataclasses.FrozenInstanceError hatası alırsınız, çünkü artık değerleri değiştiremezsiniz.

slots=True ile Performans İyileştirmeleri

Muhtemelen bildiğiniz gibi, her sınıf veya veri sınıfı oluşturduğunuzda, bazı nitelikleri daha kolay almak için o sınıf için bir sözlük oluşturur. Bu nedenle genellikle “slot” oluşturmak iyi bir fikir olabilir. Geleneksel olarak, __slots__ Dunder metodunu kullanırsınız ve bu slotları bir liste olarak tanımlarsınız:

Python

class Meyve:
    __slots__ = ["ad", "kalori"]

    def __init__(self, ad: str, kalori: float):
        self.ad = ad
        self.kalori = kalori

muz_slots = Meyve("Muz", 10.0)
print(muz_slots.ad)

Bu, programın her yeni örnek oluşturduğumuzda bu sözlüğü yeniden oluşturmasını önleyecektir. Bu, daha hızlı öznitelik erişimi sağlar ve RAM kullanımınızı azaltmaya yardımcı olabilir.

Python 3.10’da veri sınıfına yeni bir anahtar kelime tanıtıldı: slots. Sadece slots=True olarak ayarlamanız yeterlidir:

Python

@dataclass(slots=True)
class Meyve:
    ad: str
    kalori: float

muz_slots_dataclass = Meyve("Muz", 10.0)
print(muz_slots_dataclass.ad)

Burada aynı şeyi basit bir anahtar kelimeyle başarıyoruz. Programı yeniden çalıştırdığınızda her şey normal çalışacaktır. Bu slotları manuel olarak oluşturmanın tek nedeni, geriye dönük uyumluluktur. Tüm programlar 3.10 üzerinde çalışmıyor, bu yüzden diğer yolu kullanmanız gerekebilir. Ancak 3.10 ve üzeri sürümlerde çalışıyorsanız, slots anahtar kelimesini kullanmak daha değerli olabilir.

Veri Sınıflarında __str__ ve __repr__

Daha önce bahsettiğim gibi, bu meyveyi yazdırdığınızda, meyvenin veya bu sınıfın oldukça doğru bir dize temsilini alırsınız. Ancak, bunu değiştirmek isteyebilirsiniz, çünkü belki de meyveyi yazdırdığınızda görmek istediğiniz bu değildir. Bunun için __str__ adlı bir Dunder metodu vardır. Yapmanız gereken tek şey, dizenin ne olmasını istediğinizi döndürmektir:

Python

@dataclass
class Meyve:
    ad: str
    kalori: float

    def __str__(self):
        return f"{self.ad}: {self.kalori} kalori"

muz_str = Meyve("Muz", 10.0)
print(muz_str)

Çıktı: Muz: 10.0 kalori

Tamamen bir temsil döndürmek isterseniz, bunu kendiniz tanımlayabilirsiniz. __repr__ adlı bir Dunder metodu kullanırsınız ve temsilinizi döndürürsünüz. Daha sonra bu temsili almak için repr(muz) yazmanız yeterlidir.

Python

@dataclass
class Meyve:
    ad: str
    kalori: float

    def __repr__(self):
        return f"Meyve(isim='{self.ad}', kalori={self.kalori})"

muz_repr = Meyve("Muz", 10.0)
print(repr(muz_repr))

Çıktı: Meyve(isim='Muz', kalori=10.0)

kw_only=True ile Anahtar Kelime Zorunluluğu

Bazen bir veri sınıfına sahip olursunuz ve kullanıcının ne eklediğini bildiğinden emin olmak istersiniz. Bu durumda, onları anahtar kelimelere veya kw_only=True olarak ayarlamak istersiniz. Bu, Python’ın bunları argüman olarak geçmenizden memnun olmayacağı anlamına gelir:

Python

@dataclass(kw_only=True)
class Meyve:
    ad: str
    kalori: float

# muz_kw_only = Meyve("Muz", 10.0) # Bu satır hata verecektir (TypeError)
muz_kw_only = Meyve(ad="Muz", kalori=10.0) # Anahtar kelimelerle geçirilmelidir
print(muz_kw_only)

Bu, meyvenin her örneğini oluştururken anahtar kelimeleri kullandığımızı zorunlu kılar. Bu, programcıya “Hey, neyi geçirdiğiniz konusunda çok net olmanız gerekiyor, böylece kullanıcı neyi geçirdiğine dair daha iyi bir resme sahip olur” demenin bir yoludur.

Gerçek Dünya Örneği: JSON Verilerini Modelleme

Bu, veri sınıfının arkasındaki teorinin çoğuydı. Şimdi size veri sınıflarını kullanmayı sevdiğim gerçek bir örnek göstereyim ve bu, örneğin JSON’dan aldığınız verileri modellemekle ilgilidir.

Diyelim ki internetten bazı veriler alıyoruz ve bunu bir sözlükle simüle edeceğim:

Python

import json
from dataclasses import dataclass, field

@dataclass(slots=True)
class Kisi:
    ad: str
    yas: int
    meslek: str = None
    arkadaslar: list[str] = field(default_factory=list) # Değiştirilebilir varsayılan değerler için field ve default_factory

veri_json = {
    "ad": "Bob",
    "yas": 30,
    "meslek": "Satıcı",
    "arkadaslar": ["Mario", "Luigi"]
}

# Geleneksel JSON erişimi
# print(veri_json["ad"])

# Yanlış anahtar girişi kolaylığı
# print(veri_json["nom"]) # KeyError verebilir

Genel olarak, JSON’a sahip olduğunuzda, print(veri_json["ad"]) gibi bir şey söyleyebilirsiniz. Ve ben şahsen bu yaklaşımdan nefret ediyorum, çünkü birçok kez, ya yorgun olduğunuzda ya da çok hızlı yazdığınızda, bir şeyi yanlış yazarsınız. isim yerine isim yazabilirsiniz ve çalıştırdığınızda bir KeyError alırsınız. JSON’dan öğelere erişmeye çalışırken hata yapmak çok kolaydır. Bu öğeler hatta var olmayabilir. Bu yüzden bu bilgileri kolayca erişebilmek için bir veri sınıfı oluşturarak bunu mümkün olan en kısa sürede ortadan kaldırmayı seviyorum.

Şimdi bu verileri Kisi veri sınıfımızda modelleyelim:

Python

bob = Kisi(
    ad=veri_json["ad"],
    yas=veri_json["yas"],
    meslek=veri_json["meslek"],
    arkadaslar=veri_json["arkadaslar"]
)

print(bob.meslek)
print(bob)

Çıktı:

Satıcı
Kisi(ad='Bob', yas=30, meslek='Satıcı', arkadaslar=['Mario', 'Luigi'])

Burada bilgiyi yakaladık, ancak faydası, bunu sadece bir kez yapmak zorunda kalmamız ve bu noktadan itibaren Bob’dan bir şeye erişmemiz gerektiğinde, sadece nokta notasyonunu kullanmamız ve ona erişebilmemizdir. Bu, bence bir sözlükten veya bu tür bir modelden öğelere erişmenin en kolay yoludur. Genellikle bir insan listesi içeren bir JSON’a sahip olursunuz, bu yüzden sadece bir tane olmazdı ve bu modelleri her kişiyle kullanmanızı sağlayacak bir model oluşturursunuz.

Şimdi, bu örneği daha da geliştirerek __str__ metodunu ekleyelim ki Kisi nesnesini yazdırdığımızda daha anlamlı bir çıktı alalım:

Python

from dataclasses import dataclass, field

@dataclass(slots=True)
class Kisi:
    ad: str
    yas: int
    meslek: str = None
    arkadaslar: list[str] = field(default_factory=list)

    def __str__(self):
        job_info = f"ve {self.meslek} olarak çalışıyor." if self.meslek else ""
        friends_info = f"Arkadaşları: {', '.join(self.arkadaslar)}." if self.arkadaslar else ""
        return f"{self.ad}, {self.yas} yaşında {job_info} {friends_info}"

veri_json = {
    "ad": "Bob",
    "yas": 30,
    "meslek": "Satıcı",
    "arkadaslar": ["Mario", "Luigi"]
}

bob = Kisi(
    ad=veri_json["ad"],
    yas=veri_json["yas"],
    meslek=veri_json["meslek"],
    arkadaslar=veri_json["arkadaslar"]
)

print(bob)

# Mesleği olmayan bir kişi örneği
jane_json = {
    "ad": "Jane",
    "yas": 25,
    "arkadaslar": ["Alice"]
}

jane = Kisi(
    ad=jane_json["ad"],
    yas=jane_json["yas"],
    meslek=jane_json.get("meslek"), # .get() kullanarak anahtar yoksa None döndürür
    arkadaslar=jane_json["arkadaslar"]
)
print(jane)

Çıktı:

Bob, 30 yaşında ve Satıcı olarak çalışıyor. Arkadaşları: Mario, Luigi.
Jane, 25 yaşında Arkadaşları: Alice.

Gördüğünüz gibi, __str__ metodu sayesinde Kisi objesini yazdırdığımızda çok daha okunabilir ve anlamlı bir çıktı alıyoruz.

Ayrıca karşılaştırmalar yapmaya devam edebiliriz:

Python

bob2 = Kisi(
    ad="Bob",
    yas=30,
    meslek="Satıcı",
    arkadaslar=["Mario", "Luigi"]
)

print(bob == bob2)

Çıktı: True

Çünkü aynı ada, aynı yaşa, aynı işe ve aynı arkadaşlara sahipler, aynı kişi olmalılar.

Sonuç

Umarım bu, Python’daki veri sınıflarını nasıl kullanabileceğiniz hakkında iyi bir fikir vermiştir. Verilerimizi, çok fazla hata oluşturmadan yeniden kullanmamıza yardımcı olacak şekilde modellememizi sağlayan çok güçlü bir sözdizimi. Ve geleneksel bir sınıf oluşturmayla birlikte gelen birçok kalıp kodu gerçekten önlememize yardımcı oluyor. Veri sınıflarını oluşturmanın başka yolları da vardı, sanırım isimlendirilmiş bir Tuple veya başka bir konvansiyon kullanabilirdiniz, ancak bu, Python’da etkili bir veri sınıfı oluşturmanın açık ara en kolay yoludur.

Veri sınıfları hakkında ne düşündüğünüzü veya gelecekteki bir videoda açıklamamı istediğiniz başka bir şey olup olmadığını yorum bölümünde bana bildirin. İzlediğiniz için her zaman teşekkürler ve bir sonraki videoda görüşmek üzere!

Kaynaklar

0

Bir yanıt yazın

Password Requirements:

  • At least 8 characters
  • At least 1 lowercase letter
  • At least 1 uppercase letter
  • At least 1 numerical number
  • At least 1 special character