
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
- Ana kaynak: How To Use: “@dataclass” In Python (Tutorial 2023)
- Kapak Görseli: Photo by Patrick Lindenberg on Unsplash