Python dataclass, yalnızca veri taşıyan sınıflarda tekrar tekrar yazılan __init__, __repr__ ve __eq__ gibi metotları azaltmak için kullanılan standart bir Python yaklaşımıdır. Sınıf alanlarını tür ipuçlarıyla tanımladığınızda @dataclass dekoratörü, varsayılan seçenekleriyle bu metotların temel sürümlerini sizin için oluşturabilir.
Bu nedenle dataclass, iş kurallarından çok bir nesnenin verilerini düzenleyen sınıflarda özellikle anlamlıdır. Örneğin bir ürünün adı, fiyatı ve stok miktarı tutuluyorsa, bu alanların kurulumu ve karşılaştırılması için aynı kodu elle yazmak yerine sınıfın veri yapısını açıkça tanımlayabilirsiniz.
dataclass Nedir ve Hangi Tekrarı Ortadan Kaldırır?
Python’daki dataclass, veri odaklı sınıfları daha kısa ve okunabilir biçimde tanımlamaya yarayan bir mekanizmadır. Kullanmak için Python standart kütüphanesinin parçası olan dataclasses modülünden dataclass dekoratörünü içe aktarırsınız:
from dataclasses import dataclass
Ardından sınıfın hangi alanlara sahip olduğunu sınıf gövdesinde belirtirsiniz. @dataclass dekoratörü, bu alan tanımlarını temel alarak nesne oluşturma, nesneyi temsil eden metin üretme ve nesneleri karşılaştırma gibi tekrar eden işlemleri otomatikleştirmeye yardımcı olur. Python’ın resmî dokümantasyonunda Python dataclasses dokümantasyonu, dekoratörün varsayılan olarak __init__, __repr__ ve __eq__ metotlarını üretmek üzere kullanılabildiğini açıklar.
dataclass hangi problemi çözer?
Bir sınıf yalnızca birkaç alanı saklıyorsa, klasik yöntemle yazılan kodun önemli bir bölümü aynı amaca hizmet eder: alanları parametre olarak almak, nesneye atamak, nesneyi okunabilir biçimde yazdırmak ve iki nesnenin alanlarını karşılaştırmak.
Aşağıdaki klasik Product sınıfı, bir ürünün adını, fiyatını ve stok miktarını tutuyor:
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def __repr__(self):
return (
f"Product(name={self.name!r}, "
f"price={self.price!r}, stock={self.stock!r})"
)
def __eq__(self, other):
if not isinstance(other, Product):
return NotImplemented
return (
self.name == other.name
and self.price == other.price
and self.stock == other.stock
)
product_a = Product("Klavye", 129.90, 12)
product_b = Product("Klavye", 129.90, 12)
print(product_a)
print(product_a == product_b)
Bu kod çalıştırıldığında beklenen çıktı şöyledir:
Product(name='Klavye', price=129.9, stock=12)
True
Buradaki üç metot da geçerli ve gerektiğinde tamamen özelleştirilebilir. Ancak sınıfın temel görevi yalnızca ürün verilerini taşımak olduğu için, yazılan kodun önemli bölümü iş kuralı içermez. __init__ alanları atar, __repr__ alanları gösterir, __eq__ ise aynı alan değerlerine sahip nesneleri eşit kabul eder.
Boilerplate kod ne demektir?
Boilerplate kod, farklı sınıflarda benzer amaçlarla tekrar tekrar yazılan, yapısı büyük ölçüde değişmeyen kod parçalarını ifade eder. Bu kod gereksiz olmak zorunda değildir; çoğu zaman programın çalışması için gereklidir. Sorun, basit bir veri sınıfında aynı kalıbın çok sayıda alana ve sınıfa yayılmasıdır.
Örneğin Product sınıfına category, sku veya is_active gibi yeni alanlar eklendiğinde yalnızca __init__ metodunu değiştirmek yetmez. Beklenen davranışa göre __repr__ ve __eq__ metotlarının da güncellenmesi gerekir. Bu metotlardan biri unutulursa nesnenin ekrana yazdırılması ile eşitlik kontrolü farklı alan kümelerini kullanabilir.
Dataclass yaklaşımı, sınıfın veri modelini tek bir yerde toplamayı amaçlar. Alanları tanımladığınızda bu alanların yapıcı metoda dahil edilmesi, temsil çıktısında gösterilmesi ve eşitlik kontrolünde dikkate alınması varsayılan davranışlarla yönetilebilir. Böylece sınıfın veri şeması ile sınıf davranışı arasındaki uyumsuzluk riski azalır.
Dataclass yalnızca kısa yazım biçimi değildir
Bir dataclass’ı sadece “daha az satır kod” olarak düşünmek eksik olur. Asıl fayda, sınıfın niyetini daha açık ifade etmesidir. Aşağıdaki tanım, bu sınıfın esas olarak üç veri alanından oluştuğunu doğrudan gösterir:
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
stock: int
product_a = Product("Klavye", 129.90, 12)
product_b = Product("Klavye", 129.90, 12)
print(product_a)
print(product_a == product_b)
Bu kodun beklenen çıktısı şöyledir:
Product(name='Klavye', price=129.9, stock=12)
True
Dataclass sürümünde __init__, __repr__ ve __eq__ metotlarını elle yazmadığınız hâlde aynı temel sonuç elde edilir. Oluşturulan temsil metni sınıf adını ve alanları tanımlandıkları sırayla gösterir. Eşitlik karşılaştırmasında ise aynı türden iki nesnenin karşılık gelen alan değerleri karşılaştırılır.
Burada önemli bir ayrım vardır: @dataclass sınıfa sihirli biçimde her davranışı eklemez. Dekoratör, özellikle veri alanlarıyla ilişkili standart davranışları üretmeye yardımcı olur. Ürünün stok azaltma, indirim uygulama veya satışa uygunluk kontrolü gibi iş kuralları varsa bunları sınıf metotları olarak yine sizin yazmanız gerekir.
Hangi tekrarlar otomatikleşir?
@dataclass dekoratörünün parametresiz kullanımında temel davranışlar varsayılan seçeneklerle etkinleştirilir. init=True olduğunda alanlardan bir __init__ metodu oluşturulur. repr=True olduğunda nesnenin alanlarını gösteren bir __repr__ metodu üretilir. eq=True olduğunda ise alan değerlerini temel alan bir __eq__ metodu oluşturulur.
__init__: Nesne oluşturulurken alan değerlerini alır ve nesneye atar.__repr__: Nesnenin okunabilir temsilini üretir.__eq__: Aynı türdeki nesnelerin karşılaştırılmasını sağlar.order: Varsayılan olarak etkin değildir; etkinleştirilirse sıralama karşılaştırmaları için ek metotlar üretilebilir.frozen: Varsayılan olarak etkin değildir; etkinleştirildiğinde alanlara sonradan atama yapılmasını engelleyen değiştirilemezlik davranışı sağlar.
Bu seçenekler, dataclass’ın her durumda aynı şekilde davranmadığını gösterir. Örneğin yalnızca eşitlik karşılaştırmasını kapatmak, sıralama desteği eklemek veya değiştirilemez bir veri nesnesi oluşturmak için dekoratör parametreleri kullanılabilir. Ayrıca sınıfta ilgili metotlardan birini kendiniz tanımlarsanız, otomatik üretim davranışı o metot ve seçeneğe göre değişebilir.
Dataclass kullanmak, tüm sınıfları kısa hâle getirme hedefi değildir. Veri alanlarının çoğunlukta olduğu ve standart nesne davranışlarının yeterli kaldığı sınıflarda güçlü bir sadeleştirme sağlar. Buna karşılık nesnenin oluşturulması karmaşık kurallar gerektiriyorsa, alanlara erişim kontrollü olmalıysa veya sınıfın davranışı verilerinden daha önemliyse normal class yaklaşımı daha anlaşılır olabilir.
Temel Python sınıfları, dekoratörler ve nesne yönelimli programlama konularını uygulamalı biçimde pekiştirmek isteyenler, birebir Python dersleri kapsamında benzer veri modellerini adım adım çalışabilir.
Klasik Class ile dataclass Arasındaki Farkı Aynı Örnekle Görün

Klasik class ile dataclass arasındaki farkı anlamanın en iyi yolu, aynı Product örneğini iki farklı biçimde yazmaktır. Her iki sınıf da ürün adı, fiyatı ve stok miktarını tutacak; iki ayrı nesnenin temsilini yazdıracak ve bu nesnelerin eşit olup olmadığını kontrol edecektir.
Klasik class sürümü
Klasik yöntemde sınıfın bütün temel davranışlarını açıkça yazarsınız. Bu yöntem size tam kontrol verir; ancak her yeni alan için birden fazla metodu gözden geçirmeniz gerekir.
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def __repr__(self):
return (
f"Product(name={self.name!r}, "
f"price={self.price!r}, stock={self.stock!r})"
)
def __eq__(self, other):
if not isinstance(other, Product):
return NotImplemented
return (
self.name == other.name
and self.price == other.price
and self.stock == other.stock
)
product_a = Product("Klavye", 129.90, 12)
product_b = Product("Klavye", 129.90, 12)
product_c = Product("Mouse", 89.90, 5)
print(product_a)
print(product_a == product_b)
print(product_a == product_c)
Beklenen çıktı şöyledir:
Product(name='Klavye', price=129.9, stock=12)
True
False
product_a ve product_b farklı nesneler olsa da aynı sınıfa aittir ve ilgili alanları aynı değerlere sahiptir. Bu nedenle eşitlik karşılaştırması True sonucunu verir. product_c farklı alan değerlerine sahip olduğu için product_a == product_c ifadesi False döndürür.
dataclass sürümü
Aynı veri modelini dataclass ile tanımladığınızda alanları sınıf gövdesinde tür bilgileriyle belirtmeniz yeterlidir:
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
stock: int
product_a = Product("Klavye", 129.90, 12)
product_b = Product("Klavye", 129.90, 12)
product_c = Product("Mouse", 89.90, 5)
print(product_a)
print(product_a == product_b)
print(product_a == product_c)
Bu sürümde de beklenen çıktı aynıdır:
Product(name='Klavye', price=129.9, stock=12)
True
False
Dataclass’ın oluşturduğu repr çıktısı, alanların tanımlanma sırasını izler. Bu nedenle çıktı Product(name='Klavye', price=129.9, stock=12) biçimindedir. Eşitlik davranışı da alan tabanlıdır: product_a ile product_b aynı alan değerlerine sahip olduğu için eşittir; product_c ise farklı olduğu için eşit değildir.
Karşılaştırma tablosu
| Kriter | Klasik class | dataclass |
|---|---|---|
| Yazılan kod miktarı | Temel metotlar ve alan atamaları elle yazılır. | Alan tanımları çoğu temel davranış için yeterlidir. |
| Otomatik üretilen metotlar | Otomatik üretim yoktur; metotları geliştirici yazar. | init, repr ve eq seçeneklerine bağlı olarak temel metotlar üretilebilir. |
| Okunabilirlik | Davranış ayrıntıları açıkça görünür; veri sınıflarında tekrar artabilir. | Sınıfın veri alanları daha kısa ve doğrudan okunur. |
| Özelleştirme ihtiyacı | Karmaşık kurallar için doğrudan ve esnek bir yapı sunar. | Standart davranışlar otomatikleşir; özel kurallar gerektiğinde metotlar ayrıca yazılabilir. |
| Immutability desteği | Değiştirilemezlik ayrıca tasarlanmalıdır. | frozen=True seçeneğiyle değiştirilemezlik davranışı tanımlanabilir. |
Bu tablo, dataclass’ın klasik class’ın yerine her zaman geçmesi gerektiğini göstermez. Fark daha çok sorumluluk dağılımındadır. Klasik class, nesnenin yaşam döngüsünü ve davranışlarını ayrıntılı biçimde kontrol etmek istediğinizde avantajlıdır. Dataclass ise alanları belirgin, davranışları sınırlı ve karşılaştırması veri değerlerine dayanan nesnelerde tekrar eden kodu azaltır.
Otomatik davranışların sınırları
Dataclass kullanıldığında __init__, __repr__ ve __eq__ üretimi varsayılan davranış olsa da bunlar zorunlu değildir. Örneğin @dataclass(repr=False) kullanarak otomatik temsil üretimini kapatabilir veya kendi __repr__ metodunuzu yazabilirsiniz. Benzer biçimde init=False, yapıcı metodun otomatik oluşturulmasını devre dışı bırakır.
eq seçeneği de bilinçli kullanılmalıdır. Varsayılan eşitlik, nesnelerin kimliğini değil, dataclass alanlarının değerlerini karşılaştırmaya yöneliktir. Bir nesnenin eşitliği yalnızca belirli alanlara bağlı olmalıysa alan bazında karşılaştırma ayarları yapılabilir veya özel bir __eq__ davranışı tasarlanabilir.
Başka bir önemli nokta da dataclass’ın varsayılan hâliyle değiştirilemez olmamasıdır. Aşağıdaki atama, normal dataclass nesnesinde geçerlidir:
product_a.stock = 10
print(product_a.stock)
Bu nedenle veri nesnesinin oluşturulduktan sonra değişmemesi gerekiyorsa ayrıca frozen=True seçeneği değerlendirilmelidir. Bu seçeneğin etkisi, mutable alanlar ve nesnenin içindeki listeler gibi daha ileri durumlarla birlikte ele alınmalıdır. Diğer bir ifadeyle, dataclass seçmek tek başına bütün iş kurallarını çözmez; hangi davranışların otomatikleşeceğine ve hangilerinin sizin tarafınızdan tanımlanacağına karar vermeniz gerekir.
Varsayılan Değerler ve field(default_factory=...) Nasıl Kullanılır?

Python dataclass alanlarına basit varsayılan değerler doğrudan atanabilir. Örneğin stok: int = 0 veya aktif: bool = True gibi immutable değerlerde bu kullanım doğrudan ve anlaşılırdır. Ancak liste, sözlük ve küme gibi değiştirilebilir nesnelerde aynı yaklaşım güvenli değildir; çünkü nesneler arasında ortak bir mutable nesne paylaşılması beklenmeyen sonuçlar doğurabilir.
Bu ayrımın temel nedeni, bir listenin veya sözlüğün içeriğinin sonradan değiştirilebilmesidir. İki farklı Ürün nesnesi aynı listeyi kullanıyorsa, bir ürünün etiketlerine yapılan ekleme diğer ürünün etiketlerinde de görünür. Aşağıdaki örnekte klasik bir sınıfın sınıf seviyesinde tanımlanan listeyi nasıl paylaştırdığı görülür:
class Urun:
etiketler = []
urun_a = Urun()
urun_b = Urun()
urun_a.etiketler.append("kampanya")
print(urun_a.etiketler)
print(urun_b.etiketler)
print(urun_a.etiketler is urun_b.etiketler)
Beklenen çıktı şöyledir:
['kampanya']
['kampanya']
True
Burada urun_a ve urun_b için ayrı listeler oluşturulmamıştır. Her iki nesne de sınıfın ortak etiketler listesini görür. Bu davranış bazı durumlarda bilinçli olarak istenebilir; fakat her ürünün kendi etiketlerine sahip olması gereken bir uygulamada ciddi bir veri hatasına dönüşür.
Dataclass kullanırken items: list = [], ayarlar: dict = {} veya etiketler: set = set() gibi doğrudan mutable varsayılanlar kullanmak yerine field(default_factory=...) tercih edilir. Python’un resmi dataclasses dokümantasyonuna göre default_factory, varsayılan değere ihtiyaç duyulduğunda sıfır parametreyle çağrılan bir callable olmalıdır. Bu mekanizma, her nesne için yeni bir liste veya sözlük oluşturmak amacıyla kullanılabilir. Python dataclasses dokümantasyonu ayrıca yaygın mutable varsayılanları yakalamak için list, dict ve set türlerinde hata üretilebildiğini belirtir. ([docs.python.org](https://docs.python.org/tr/3/library/dataclasses.html?utm_source=openai))
field(default_factory=list) ile her nesneye ayrı liste oluşturma
default_factory=list ifadesinde parantezsiz olarak list yazılır. Bunun nedeni, listenin sınıf tanımlanırken hemen oluşturulmasını değil, her yeni Ürün nesnesi oluşturulduğunda ayrı ayrı üretilmesini istememizdir. Yani list fonksiyonu bir fabrika gibi çalışır.
from dataclasses import dataclass, field
@dataclass
class Urun:
ad: str
etiketler: list[str] = field(default_factory=list)
ayarlar: dict[str, str] = field(default_factory=dict)
urun_a = Urun("Klavye")
urun_b = Urun("Fare")
urun_a.etiketler.append("kampanya")
urun_a.ayarlar["renk"] = "siyah"
print(urun_a)
print(urun_b)
print(urun_a.etiketler is urun_b.etiketler)
print(urun_a.ayarlar is urun_b.ayarlar)
Beklenen çıktı şu şekildedir:
Urun(ad='Klavye', etiketler=['kampanya'], ayarlar={'renk': 'siyah'})
Urun(ad='Fare', etiketler=[], ayarlar={})
False
False
Bu örnekte urun_a için eklenen "kampanya" etiketi urun_b nesnesine yansımaz. Aynı şekilde ayarlar sözlükleri de birbirinden bağımsızdır. Resmi dokümantasyondaki davranış tanımına göre fabrika fonksiyonu, alana varsayılan değer gerektiğinde sıfır parametreyle çağrılır; dolayısıyla her nesne kendi mutable değerini alır. ([docs.python.org](https://docs.python.org/tr/3/library/dataclasses.html?utm_source=openai))
default ile default_factory arasındaki fark
field() yalnızca liste ve sözlük oluşturmak için kullanılmaz. Bir alanın kurucu metoda dahil edilip edilmeyeceğini, repr çıktısında görünüp görünmeyeceğini veya karşılaştırmalara katılıp katılmayacağını da belirlemek için kullanılabilir. Bu bölümdeki temel ayrım ise default ile default_factory arasındadır.
- default, kullanılacak varsayılan değeri doğrudan belirtir.
- default_factory, varsayılan değeri oluşturacak sıfır parametreli callable nesnesini belirtir.
default_factory=listyeni ve boş bir liste üretir.default_factory=dictyeni ve boş bir sözlük üretir.defaultiledefault_factoryaynı alan için birlikte kullanılmamalıdır.
Örneğin aşağıdaki iki alanın amacı aynı değildir:
from dataclasses import dataclass, field
@dataclass
class Siparis:
durum: str = "bekliyor"
urunler: list[str] = field(default_factory=list)
durum alanı her nesne için kullanılabilecek immutable bir metin değeri taşır. urunler ise her siparişe özel olması gereken değiştirilebilir bir koleksiyondur. Bu nedenle ilk alan için doğrudan varsayılan değer, ikinci alan için fabrika kullanılmıştır.
Mutable varsayılan değer seçiminde pratik ölçütler
Bir dataclass alanının varsayılanını belirlerken aşağıdaki kısa kontrol listesini kullanabilirsiniz:
- Değer bir sayı, metin, boolean veya
Noneise doğrudan varsayılan değer çoğu basit senaryoda uygundur. - Değerin içeriği nesne oluşturulduktan sonra değişecekse liste, sözlük veya küme gibi mutable bir yapı olup olmadığını kontrol edin.
- Her nesnenin kendine ait boş koleksiyonu olması gerekiyorsa
field(default_factory=list),field(default_factory=dict)veyafield(default_factory=set)kullanın. - Varsayılan değer sabit bir tuple ise doğrudan
defaultkullanılabilir; tuple’ın içindeki nesnelerin de değiştirilebilir olup olmadığını ayrıca değerlendirin. - Varsayılan değer hesaplanarak üretilecekse, hesabı sınıf tanımlanırken değil nesne oluşturulurken gerçekleştirecek bir fabrika fonksiyonu yazın.
- Bir alanın varsayılanı başka bir alanın değerine bağlıysa, doğrudan sınıf gövdesinde referans vermek yerine kurucu sonrası kurulumu veya özel bir metodu değerlendirin.
Özellikle iç içe veri yapılarında yalnızca dış kabın türüne bakmak yeterli değildir. Örneğin tuple değiştirilemez olsa da tuple içinde bulunan bir liste hâlâ değiştirilebilir. Benzer biçimde, default_factory dış koleksiyonu her nesne için ayırır; fakat fabrika fonksiyonunun içine önceden oluşturulmuş ortak bir mutable nesne yerleştirilirse aynı paylaşım problemi yeniden ortaya çıkabilir.
Bu nedenle default_factory kullanımı bir ezber kuralından çok sahiplik sorusuna verilen cevaptır: “Bu alanın değeri bütün nesneler tarafından mı paylaşılmalı, yoksa her nesne kendi kopyasına mı sahip olmalı?” Ürün etiketleri, sepet kalemleri, hata kayıtları ve yapılandırma sözlükleri çoğunlukla nesneye özel olmalıdır.
Bu konudaki bilgilerinizi kısa sorularla sınamak ve mutable varsayılanlar, sınıflar ile veri modeli kurma konularındaki mevcut seviyenizi görmek için Python bilgi testi sayfasını kullanabilirsiniz.
frozen=True ile Değiştirilemez Nesne Ne Zaman Anlamlıdır?
@dataclass(frozen=True), dataclass örneği oluşturulduktan sonra alanlara yeniden atama yapılmasını engellemek için kullanılır. Bu seçenek, nesnenin kimlik niteliğindeki değerlerinin yaşam döngüsü boyunca sabit kalması gerektiğinde anlamlıdır. Örneğin koordinat, para birimi tanımı, yapılandırma kaydı veya değişmemesi gereken basit bir değer nesnesi frozen=True ile modellenebilir.
Python’un resmi dokümantasyonuna göre frozen=True kullanıldığında dataclass, alan atama ve silme işlemlerini yöneten metotlar ekler. Bu metotlar atama veya silme girişiminde FrozenInstanceError üretir. Resmi açıklamada bunun gerçek anlamda mutlak değişmezlik değil, değişmezlik davranışını taklit eden bir yaklaşım olduğu da vurgulanır. ([docs.python.org](https://docs.python.org/tr/3/library/dataclasses.html?utm_source=openai))
Alan ataması neden hata verir?
Aşağıdaki örnekte bir noktanın koordinatları oluşturma sırasında verilir. Nesne oluşturulduktan sonra x alanını değiştirmeye çalıştığımızda Python hata üretir:
from dataclasses import dataclass
@dataclass(frozen=True)
class Nokta:
x: int
y: int
nokta = Nokta(10, 20)
print(nokta)
nokta.x = 15
Nesne oluşturma ve ilk print satırı çalışır; beklenen temsil şöyledir:
Nokta(x=10, y=20)
Son satırda ise dataclasses.FrozenInstanceError oluşur. Hatanın anlamı, dataclass örneğinin x alanına yeni bir değer atanmasına izin verilmediğidir. Bu davranış, yazım hatalarıyla bir nesnenin kimlik değerinin yanlışlıkla değiştirilmesini önlemek veya veri modelinin belirli bir noktadan sonra sabit kalmasını sağlamak için yararlı olabilir.
frozen=True hangi veri nesnelerinde anlamlıdır?
frozen=True seçimi, nesnenin değişmemesi gerektiğine dair açık bir tasarım kararıdır. Aşağıdaki durumlarda değerlendirilebilir:
- Bir koordinat nesnesi oluşturulduktan sonra
xveydeğerlerinin sabit kalması gerekiyorsa. - Bir para birimi kaydında kod, sembol veya ölçek gibi alanlar nesne boyunca değişmiyorsa.
- Yapılandırma değerleri okunup nesne oluşturulduktan sonra uygulama içinde yalnızca okunacaksa.
- Bir kimlik nesnesinin değerinin sonradan değiştirilmesi veri tutarlılığını bozacaksa.
- Bir fonksiyondan diğerine aktarılan küçük veri nesnesinin yanlışlıkla güncellenmesini istemiyorsanız.
Buna karşılık nesnenin yaşam döngüsü boyunca alanlarının güncellenmesi gerekiyorsa normal bir dataclass daha uygun olabilir. Örneğin bir ürünün stok miktarı, siparişin durumu veya bir görevin tamamlanma yüzdesi uygulama çalışırken değişebilir. Böyle bir nesneyi frozen yapmak, doğal güncelleme akışını gereksiz yere zorlaştırır.
frozen=True derin değişmezlik sağlamaz
frozen=True ile korunan şey, dataclass alanına yeni bir nesne atamaktır. Bu seçenek her alanın içindeki tüm verileri otomatik olarak derin biçimde immutable hâle getirmez. Özellikle alanın içinde liste veya sözlük bulunuyorsa, alanın kendisine yeni bir liste atamak engellenebilir; ancak mevcut listenin içeriğini değiştirmek ayrı bir işlemdir.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Urun:
ad: str
etiketler: list[str] = field(default_factory=list)
urun = Urun("Klavye")
urun.etiketler.append("kampanya")
print(urun.etiketler)
Bu örnekte urun.etiketler = [] biçimindeki alan ataması engellenir; fakat listenin append() ile değiştirilmesi, listenin kendisinin mutable olması nedeniyle mümkün olabilir. Beklenen çıktı ['kampanya'] değeridir. Bu nedenle gerçekten derin değişmezlik gerekiyorsa yalnızca frozen=True seçeneğine güvenmek yerine alan türlerini de değerlendirmek gerekir. Örneğin içeriği değişmemesi gereken koleksiyonlarda uygun bir immutable veri yapısı tercih edilebilir.
Pratik karar şudur: Nesnenin alanlarına yeniden değer atanmasını engellemek istiyorsanız frozen=True kullanın; nesnenin içindeki tüm yapıların da değişmemesi gerekiyorsa alanların kendi mutability özelliklerini ayrıca tasarlayın. Böylece “frozen” davranışını gerçek bir derin değişmezlik garantisiyle karıştırmamış olursunuz.
Ne Zaman dataclass, Ne Zaman Normal Class Kullanılmalı?
Veri ağırlıklı ve tekrar eden metotlara sahip sınıflar için dataclass iyi bir adaydır. Sınıfın asıl görevi veri taşımak yerine karmaşık kuralları uygulamak, nesne yaşam döngüsünü yönetmek veya yoğun iş mantığı yürütmekse normal class yapısı daha açık ve kontrollü olabilir.
Buradaki karar, “dataclass her zaman daha iyi midir?” sorusuyla verilmemelidir. Doğru soru, sınıfın sorumluluğunun ne olduğudur. Aşağıdaki kontrol listesi, kendi projenizde iki yaklaşım arasında seçim yapmanıza yardımcı olur.
1. Sınıfın ana görevi veri taşımak mı?
Bir sınıf çoğunlukla belirli alanları bir arada tutuyor, bu alanları oluşturuyor ve başka katmanlara aktarıyorsa dataclass kullanmak mantıklıdır. Örneğin bir ürünün adı, fiyatı, stok miktarı ve etiketleri; bir uygulamanın ayarları; bir API isteğinin parametreleri veya bir işlemin sonucu veri taşıyan nesnelerdir.
Bu tür sınıflarda genellikle şu ihtiyaçlar öne çıkar:
- Nesneyi alan değerleriyle kolayca oluşturmak,
- Nesneyi okunabilir biçimde yazdırmak,
- İki nesneyi alanlarına göre karşılaştırmak,
- Bazı alanlara varsayılan değer vermek,
- Veriyi tek bir anlamlı nesne altında toplamak.
__init__, __repr__ ve __eq__ gibi metotları elle yazmak yalnızca birkaç satırlık bir tekrar değildir. Alan sayısı arttıkça bu metotların güncel tutulması da gerekir. Yeni bir alan eklediğinizde kurucuyu, temsil biçimini ve eşitlik davranışını ayrı ayrı gözden geçirmek zorunda kalıyorsanız sınıfınız veri ağırlıklı olabilir.
2. Alanlar ve varsayılanlar açıkça tanımlanabiliyor mu?
Bir sınıfın hangi verileri taşıdığı açıkça görülebiliyorsa dataclass yaklaşımı daha anlamlı hâle gelir. Tür ipuçları ve alan tanımları, nesnenin yapısını sınıfın üst bölümünde görünür kılar.
Örneğin şu sorulara net cevap verebiliyorsanız dataclass için uygun bir tasarımınız olabilir:
- Nesnenin zorunlu alanları hangileri?
- Hangi alanların varsayılan değeri var?
- Liste veya sözlük gibi değişebilir alanlar nasıl başlatılmalı?
- Hangi alanlar karşılaştırmaya veya temsil çıktısına dâhil edilmeli?
- Nesne oluşturulduktan sonra alanlar değiştirilebilir mi?
Bu soruların cevapları sınıf tanımında sade biçimde ifade edilebiliyorsa dataclass, veri modelinin kendisini okunabilir hâle getirir. Özellikle ayar nesneleri, basit domain kayıtları ve DTO benzeri yapılar bu kategoriye girer.
Buna karşılık nesnenin geçerli bir duruma ulaşması için çok sayıda koşulun sırayla uygulanması gerekiyorsa yalnızca alan tanımlamak yeterli olmayabilir. Bu durumda doğrulamanın nerede ve hangi aşamada yapılacağı tasarımın merkezine yerleşir.
3. Standart repr ve eşitlik davranışı yeterli mi?
dataclass, nesnelerin temsil edilmesi ve eşitlik karşılaştırması için çoğu veri taşıma senaryosunda yeterli olan varsayılan davranışlar sunar. Alan değerlerini gösteren bir repr çıktısı, hata ayıklama sırasında nesnenin içeriğini hızlıca görmenizi sağlar. Eşitlik karşılaştırması da genellikle aynı sınıftaki alan değerlerini temel alır.
Ancak projenizde eşitlik “bütün alanlar aynı mı?” sorusundan farklı bir anlama geliyorsa dikkatli olmalısınız. Örneğin iki sipariş nesnesi yalnızca sipariş numarası aynı olduğu için eşit kabul edilecekse, diğer alanların karşılaştırmaya katılıp katılmayacağı ayrıca tasarlanmalıdır.
Benzer biçimde, standart repr çıktısı yerine güvenlik veya okunabilirlik nedeniyle özel bir gösterim gerekiyorsa normal sınıf ya da özelleştirilmiş dataclass davranışı tercih edilebilir. Parola, erişim anahtarı veya kişisel veri içeren alanların doğrudan temsil çıktısında görünmesi istenmeyebilir.
4. Karmaşık doğrulama veya yaşam döngüsü kuralları var mı?
Sınıfın oluşturulması, değiştirilmesi ve yok edilmesi belirli kurallara bağlıysa normal class daha uygun bir başlangıç noktası olabilir. Burada sorun dataclass’ın bu işlemleri yapamaması değil, davranışın veri tanımından daha önemli hâle gelmesidir.
Aşağıdaki durumlarda normal class kullanmayı ciddi biçimde değerlendirin:
- Alanların geçerli olması için birden fazla adımda doğrulama yapılması,
- Bir alan değiştiğinde başka alanların otomatik olarak güncellenmesi,
- Nesnenin yalnızca belirli metotlar üzerinden değiştirilmesine izin verilmesi,
- Nesnenin kullanılmadan önce bağlantı, dosya veya başka bir kaynak açması,
- Nesne kapatılırken özel temizleme işlemlerinin zorunlu olması,
- Kurucu metodun basit alan atamasından çok bir iş akışı yürütmesi.
Örneğin bir bağlantı yöneticisi, dosya yöneticisi veya işlem oturumu sınıfı yalnızca veri taşımaz. Kaynak açma, kullanma, hata durumunu yönetme ve kapatma sorumlulukları vardır. Böyle bir yapıda nesne yaşam döngüsünü açıkça kontrol eden normal bir class daha anlaşılır olabilir.
5. Miras hiyerarşisi tasarımın merkezinde mi?
Dataclass, mirasla birlikte kullanılabilir; fakat çok katmanlı bir sınıf hiyerarşisinde alan sırası, varsayılan alanlar ve kurucu davranışı dikkatli tasarım gerektirir. Özellikle alt sınıflar üst sınıfların alanlarını genişletiyor, bazı davranışları değiştiriyor ve farklı nesne oluşturma kuralları uyguluyorsa normal class yaklaşımı daha fazla kontrol sağlayabilir.
Miras yapısı birkaç ortak veri alanını paylaşmak için kullanılıyorsa dataclass yeterli olabilir. Fakat hiyerarşinin asıl amacı polimorfik davranış, farklı yaşam döngüleri veya karmaşık metot sözleşmeleri oluşturmaksa kararınızı yalnızca boilerplate kod miktarına göre vermeyin.
Pratik karar çerçevesi
Aşağıdaki adımları bir sınıfı tasarlamadan önce uygulayabilirsiniz:
- Sınıfın temel sorumluluğunu tek cümleyle yazın. Cümle “şu verileri taşır” şeklindeyse dataclass için olumlu bir işarettir.
- Sınıfın alanlarını, türlerini ve varsayılanlarını listeleyin. Bu liste açık ve kısa ise dataclass yaklaşımı uygundur.
- Elle yazacağınız
__init__,__repr__ve__eq__metotlarının gerçekten özel bir davranış gerektirip gerektirmediğini kontrol edin. - Geçersiz durumları ve doğrulama kurallarını yazın. Kurallar birkaç basit kontrolden fazlaysa normal class seçeneğini değerlendirin.
- Nesnenin oluşturulması veya kapatılması sırasında kaynak yönetimi, sıralı işlem ya da özel yaşam döngüsü gerekip gerekmediğine bakın.
- Miras hiyerarşisinin veri paylaşımı mı, yoksa davranış tasarımı mı amacı taşıdığını belirleyin.
- Son olarak nesnenin zaman içinde nasıl değişeceğini düşünün. Salt veri kaydıysa dataclass; kontrollü geçişler yapan bir iş nesnesiyse normal class daha uygun olabilir.
| İhtiyaç | Daha uygun yaklaşım | Neden |
|---|---|---|
| Alanları belirli bir veri kaydı oluşturmak | dataclass |
Tekrarlayan kurucu, temsil ve eşitlik kodunu azaltır. |
| DTO benzeri nesneler taşımak | dataclass |
Veri yapısı sınıf tanımında açıkça görünür. |
| Uygulama ayarlarını toplamak | dataclass |
Varsayılan değerler ve alanlar düzenli biçimde ifade edilebilir. |
| Karmaşık iş kuralları yürütmek | Normal class |
Davranış, veri tanımının önüne geçer. |
| Kaynak açma ve kapatma yaşam döngüsünü yönetmek | Normal class |
Özel oluşturma, kullanım ve temizleme adımları gerekir. |
| Geniş ve davranış merkezli miras hiyerarşisi | Genellikle normal class |
Metot sözleşmeleri ve davranış kontrolü daha belirleyici olur. |
Özetle, karar ölçütü yazılan kod satırlarının sayısı değil, sınıfın neyi temsil ettiğidir. Veri ağırlıklı, alanları belirgin ve tekrar eden metotlara sahip sınıflarda dataclass sade ve okunabilir bir seçimdir. Sınıfın değeri veri taşımaktan çok kuralları uygulamasından, durum değişimlerini yönetmesinden veya kaynak yaşam döngüsünü kontrol etmesinden geliyorsa normal class daha doğru bir tasarım olabilir.
Sık Sorulan Sorular
dataclass ile normal class arasındaki temel fark nedir?
Normal class içinde kurucu, temsil ve eşitlik gibi davranışları çoğunlukla kendiniz tanımlarsınız. dataclass ise alan bildirimlerini temel alarak bu tekrar eden davranışların uygun olanlarını otomatik üretir. Bu nedenle dataclass özellikle veri taşıyan sınıflarda daha az kodla daha açık bir yapı sunar. Normal class ise karmaşık davranış, doğrulama ve yaşam döngüsü kontrolünün öne çıktığı durumlarda daha fazla tasarım esnekliği sağlar.
dataclass __init__, __repr__ ve __eq__ metotlarını gerçekten otomatik oluşturur mu?
Evet, varsayılan ayarlar kullanıldığında @dataclass dekoratörü alan tanımlarından yararlanarak bu metotları oluşturabilir. Ancak bu davranış sınıfın ihtiyaçlarına göre yapılandırılabilir; bazı metotların oluşturulması kapatılabilir veya özel bir metot yazılarak varsayılan davranış değiştirilebilir. Ayrıca otomatik oluşturulan eşitlik, genellikle aynı sınıftaki alan değerlerinin karşılaştırılmasına dayanır.
Python dataclass içinde liste veya sözlük varsayılan değeri nasıl tanımlanır?
Liste ve sözlük gibi değişebilir nesneler doğrudan varsayılan değer olarak yazılmamalıdır. Bunun yerine field(default_factory=list) veya field(default_factory=dict) kullanılır. Böylece her nesne oluşturulduğunda ayrı bir liste ya da sözlük üretilir ve farklı nesneler aynı değişebilir nesneyi yanlışlıkla paylaşmaz.
frozen=True kullanınca dataclass nesnesi tamamen değiştirilemez mi?
Hayır. frozen=True, dataclass alanlarına normal atama yapılmasını engeller ve nesnenin yeniden atanabilirliğini sınırlar. Ancak bu durum derin veya mutlak bir değişmezlik garantisi vermez. Nesnenin içinde liste ya da sözlük gibi değişebilir bir alan bulunuyorsa, alanın kendisi yeniden atanamasa bile içeriği değiştirilebilir. Bu nedenle frozen kullanırken alanların da değişmez türlerden oluşup oluşmadığını ayrıca değerlendirmek gerekir.
Hangi durumlarda dataclass yerine normal class tercih edilmelidir?
Sınıf karmaşık doğrulama mantığı yürütüyorsa, alan değişikliklerini sıkı biçimde kontrol ediyorsa, kaynak açma-kapatma gibi özel bir yaşam döngüsüne sahipse veya davranış merkezli bir miras hiyerarşisinin parçasıysa normal class tercih edilebilir. Aynı şekilde standart repr ve eşitlik davranışı alan değerlerine göre çalışmıyorsa normal class daha doğrudan bir kontrol sunar.
Doğru seçim, sınıfın taşıdığı veri ile uyguladığı davranış arasındaki dengeye bağlıdır.