Python'da @property, bir metodu öznitelik gibi okunan bir "yönetilen öznitelik"e çevirir: t.derece yazıldığında arka planda bir getter fonksiyonu çalışır, @derece.setter ile tanımlanan fonksiyon da t.derece = 30 atamasında devreye girer. Böylece dışarıya sade bir öznitelik arayüzü sunarken içeride doğrulama, hesaplama ya da dönüştürme yapabilirsiniz. property'nin kendisi, Python'un betimleyici (descriptor) protokolünün en bilinen örneğidir; aynı protokolü __get__ ve __set__ metotlarıyla kendi sınıflarınızda da uygulayabilirsiniz.
Bu yazıda property'nin dekoratör ve klasik kullanımını, salt okunur ve hesaplanan öznitelikleri, betimleyici protokolünü, veri ve veri olmayan betimleyici farkını, __set_name__ ile yeniden kullanılabilir doğrulayıcılar yazmayı, functools.cached_property'yi ve __slots__ ile bellek düzenini Python 3.15.2 ile çalıştırılmış örneklerle anlatıyoruz. Sınıf temelleri için class ve nesne yazısına, seri haritası için Python öğrenme rehberine bakabilirsiniz.
@property ile getter ve setter

Java gibi dillerden gelenler her alan için get_x() ve set_x() yazmaya alışkındır. Python'da bunun gereği yoktur: önce düz bir öznitelikle başlarsınız, ileride doğrulama gerekirse aynı adı property'ye çevirirsiniz ve özniteliği kullanan kodun hiçbir satırı değişmez. Aşağıdaki termometre, değeri _derece adlı "iç" öznitelikte saklıyor ve atamaları denetliyor:
class Termometre:
def __init__(self, derece):
self.derece = derece # setter burada da çalışır
@property
def derece(self):
"""Santigrat cinsinden sıcaklık."""
return self._derece
@derece.setter
def derece(self, deger):
if deger < -273.15:
raise ValueError('mutlak sıfırın altı olamaz')
self._derece = deger
@property
def fahrenheit(self): # yalnızca okunur, hesaplanan
return self._derece * 9 / 5 + 32
t = Termometre(25)
print(t.derece, t.fahrenheit)
t.derece = 30
print(vars(t))
try:
t.derece = -300
except ValueError as e:
print(e)
try:
t.fahrenheit = 100
except AttributeError as e:
print(e)
print(Termometre.derece.__doc__)
# çıktı: 25 77.0
# çıktı: {'_derece': 30}
# çıktı: mutlak sıfırın altı olamaz
# çıktı: property 'fahrenheit' of 'Termometre' object has no setter
# çıktı: Santigrat cinsinden sıcaklık.Birkaç noktaya dikkat edin. __init__ içindeki self.derece = derece ataması da setter'dan geçer, yani doğrulama nesne kurulurken de çalışır. Getter'ın belge dizesi, property'nin belge dizesi olur. Setter'ı olmayan fahrenheit salt okunurdur; Python 3.15.2 atama denemesinde hangi özelliğin hangi sınıfta setter'ı olmadığını açıkça yazıyor. Belgeler setter ve deleter fonksiyonlarına property ile aynı adı vermenizi ister; çünkü @derece.setter mevcut property'nin setter eklenmiş bir kopyasını döndürür ve bu kopya aynı ada bağlanmalıdır.
property() fonksiyonunun klasik biçimi
Dekoratör söz dizimi aslında property(fget, fset, fdel, doc) çağrısının kısaltmasıdır. Klasik biçimde getter, setter ve deleter fonksiyonlarını ayrı yazar, sonra hepsini tek bir sınıf özniteliğinde birleştirirsiniz. Oluşan nesnenin fget, fset ve fdel öznitelikleri bu fonksiyonları gösterir; Python 3.13 ile property nesnelerine bir de __name__ özniteliği eklendi:
class Kutu:
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, 'x özelliği')
k = Kutu()
k.x = 5
print(k.x, Kutu.x.fget is Kutu.getx, Kutu.x.__name__)
del k.x
print(vars(k))
print(type(Kutu.__dict__['x']).__name__)
# çıktı: 5 True x
# çıktı: {}
# çıktı: propertySınıfın sözlüğüne bakınca x'in bir property nesnesi olduğunu görürüz. Değerin kendisi örneğin sözlüğünde, _x adıyla duruyor. Property'nin tek yaptığı, x adına yapılan okuma, yazma ve silme işlemlerini bu fonksiyonlara yönlendirmektir.
Betimleyici (descriptor) protokolü
Betimleyici, __get__, __set__ ya da __delete__ metotlarından en az birini tanımlayan nesnedir. Bir betimleyici örneği sınıf özniteliği olarak yerleştirildiğinde, nokta işleci o adı aradığında değeri doğrudan döndürmek yerine betimleyicinin metodunu çağırır. Descriptor Guide'daki en basit örnek her okumada 10 döndüren bir betimleyicidir:
class On:
def __get__(self, obj, objtype=None):
return 10
class A:
x = 5 # sıradan sınıf özniteliği
y = On() # betimleyici (descriptor) örneği
a = A()
print(a.x, a.y, A.y)
print(type(vars(A)['y']).__name__) # vars() betimleyiciyi tetiklemez
a.y = 99 # __set__ yok: örnek sözlüğüne yazılır
print(a.y, vars(a))
# çıktı: 5 10 10
# çıktı: On
# çıktı: 99 {'y': 99}__get__'e gelen parametreler şunlardır: betimleyicinin kendisi, özniteliğin okunduğu örnek (sınıf üzerinden okunuyorsa None) ve sahip sınıf. vars(A)['y'] ile sözlükten doğrudan okumak betimleyiciyi tetiklemez, nesnenin kendisini verir. Betimleyiciler yalnızca sınıf özniteliği olarak çalışır; bir örneğin sözlüğüne konulan betimleyicinin hiçbir etkisi olmaz.
Veri ve veri olmayan betimleyici: öncelik sırası

Belgeler betimleyicileri ikiye ayırır. Yalnızca __get__ tanımlayanlar veri olmayan (non-data), __set__ ya da __delete__ da tanımlayanlar veri (data) betimleyicisidir. Fark, örneğin sözlüğünde aynı adlı bir kayıt olduğunda ortaya çıkar: veri olmayan betimleyiciyi örnek sözlüğü ezer, veri betimleyicisi ise her zaman önce gelir. Yukarıdaki On sınıfında a.y = 99 ataması sözlüğe yazıldı ve sonraki okumalarda betimleyici devre dışı kaldı. property ise veri betimleyicisidir:
class Ozellik:
@property
def deger(self):
return 'property'
o = Ozellik()
try:
o.deger = 'yeni'
except AttributeError as e:
print(e)
o.__dict__['deger'] = 'sözlükten' # sözlüğe zorla yazsak bile
print(o.deger) # veri betimleyicisi önce gelir
# çıktı: property 'deger' of 'Ozellik' object has no setter
# çıktı: property| Tür | Tanımladığı metotlar | Örnek sözlüğü karşısında | Örnekler |
|---|---|---|---|
| Veri betimleyicisi | __get__ ve __set__ ve/veya __delete__ | Betimleyici kazanır | property, __slots__ üyeleri |
| Veri olmayan betimleyici | Yalnızca __get__ | Örnek sözlüğü kazanır | Fonksiyonlar (metotlar), classmethod, staticmethod, cached_property |
Bu tablo Python'un birçok davranışını açıklar. Metotlar veri olmayan betimleyici olduğu için tek bir örneğe aynı adla farklı bir fonksiyon atayıp o örneğin davranışını değiştirebilirsiniz. Property ise veri betimleyicisi olduğu için örnek düzeyinde ezilemez. Salt okunur bir veri betimleyicisi yazmak için belgelerin önerisi, __set__'i tanımlayıp içinde AttributeError fırlatmaktır.
Fonksiyonlar nasıl metoda dönüşür?
Descriptor Guide'a göre Python'un nesne yönelimli özellikleri fonksiyon tabanlı bir ortamın üzerine kuruludur ve ikisini birleştiren şey veri olmayan betimleyicilerdir. Her fonksiyon nesnesi bir __get__ metoduna sahiptir ama __set__'i yoktur. Sınıf sözlüğündeki bir fonksiyon örnek üzerinden okunduğunda nokta işleci bu __get__'i çağırır ve sonuç, örneği ilk argümana bağlayan bir bağlı metottur. Sınıf üzerinden okunduğunda ise (obj değeri None) fonksiyonun kendisi döner. Bu adımları elle yapmak mekanizmayı somutlaştırır:
import types
class D:
def f(self):
return 'f çalıştı'
d = D()
fonk = vars(D)['f'] # sınıf sözlüğündeki düz fonksiyon
print(type(fonk).__name__, hasattr(fonk, '__get__'), hasattr(fonk, '__set__'))
bagli = fonk.__get__(d, D) # nokta işlecinin yaptığı
print(type(bagli).__name__, bagli.__self__ is d, bagli())
print(fonk.__get__(None, D) is fonk) # sınıf üzerinden: fonksiyonun kendisi
elle = types.MethodType(fonk, d) # bağlı metodu elle oluşturmak
print(elle())
# çıktı: function True False
# çıktı: method True f çalıştı
# çıktı: True
# çıktı: f çalıştıtypes.MethodType ile bağlı metodu doğrudan da oluşturabilirsiniz. Metotların veri olmayan betimleyici olması, tek bir örneğe aynı adla başka bir fonksiyon atandığında örnek sözlüğündeki değerin kazanmasının da nedenidir. classmethod ve staticmethod aynı protokolün farklı __get__ uygulamalarıdır: biri sınıfı bağlar, diğeri hiçbir şey bağlamaz (class ve nesne yazısında üç metot türünü karşılaştırdık).
Öznitelik aramanın tam sırası ve __getattr__
Descriptor Guide, bir örnek üzerindeki obj.x aramasının öncelik sırasını şöyle özetler: önce veri betimleyicileri, sonra örnek değişkenleri (örneğin __dict__'i), sonra veri olmayan betimleyiciler, sonra sıradan sınıf değişkenleri ve en son, tanımlıysa __getattr__. Sınıf tarafındaki arama sınıfın MRO'su boyunca yapılır, yani taban sınıflardaki betimleyiciler de bu sıraya dahildir. Bu mantığın tamamı object.__getattribute__() içinde yaşar; __getattribute__'i geçersiz kılmak bu yüzden betimleyicilerin otomatik çağrılmasını da devre dışı bırakır.
__getattr__ ile __getattribute__ sık karıştırılır. Veri modeli belgesine göre __getattribute__ her öznitelik erişiminde koşulsuz çağrılır; __getattr__ ise yalnızca normal arama AttributeError ile başarısız olduğunda devreye girer. Bu bilinçli bir asimetridir: normal yoldan bulunan özniteliklerde __getattr__ hiç çalışmaz, bu sayede metodun içinden self.ozel gibi diğer özniteliklere sonsuz döngüye girmeden erişebilirsiniz. Eksik ayarlar için varsayılan değer döndüren küçük bir örnek:
class Ayarlar:
varsayilan = {'dil': 'tr', 'tema': 'koyu'}
def __init__(self, **ozel):
self.ozel = ozel
def __getattr__(self, ad): # yalnızca normal arama başarısız olunca
if ad in self.ozel:
return self.ozel[ad]
if ad in self.varsayilan:
return self.varsayilan[ad]
raise AttributeError(f'{type(self).__name__} içinde {ad!r} yok')
a = Ayarlar(tema='açık')
print(a.tema, a.dil, a.ozel) # ozel normal yoldan bulunur
try:
a.boyut
except AttributeError as e:
print(e)
print(getattr(a, 'boyut', 'yok'))
# çıktı: açık tr {'tema': 'açık'}
# çıktı: Ayarlar içinde 'boyut' yok
# çıktı: yok__getattr__ bulamadığı adlar için mutlaka AttributeError fırlatmalıdır; aksi hâlde hasattr() ve getattr(nesne, ad, varsayilan) gibi araçlar yanlış sonuç verir. Son satırda getattr()'ın üçüncü argümanı tam da bu istisna sayesinde devreye girdi. Kendi __getattribute__'inizi yazmanız gerekirse, belgeler sonsuz özyinelemeyi önlemek için diğer öznitelikleri object.__getattribute__(self, ad) üzerinden okumanızı ister. Atama tarafında da benzer bir kanca vardır: __setattr__ her atamada çağrılır ve değeri gerçekten saklamak için object.__setattr__(self, ad, deger) çağırmalıdır. Dondurulmuş dataclass'lar tam olarak bu kancayı kullanır.
__set_name__ ile yeniden kullanılabilir doğrulayıcı
Property tek bir öznitelik için harikadır, ama aynı kuralı on alana uygulamak gerektiğinde tekrara düşer. Betimleyici sınıfı bu tekrarı ortadan kaldırır. Sınıf oluşturulurken Python, sınıf gövdesindeki her nesnenin __set_name__(sahip, ad) metodunu (varsa) çağırır; böylece betimleyici hangi ada atandığını öğrenir ve değeri saklayacağı iç adı kendisi belirler. Descriptor Guide'daki doğrulayıcı fikrinin Türkçe uyarlaması:
class Dogrulayici:
def __set_name__(self, sahip, ad):
self.ad = ad
self.gizli = '_' + ad
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.gizli)
def __set__(self, obj, deger):
self.dogrula(deger)
setattr(obj, self.gizli, deger)
class Pozitif(Dogrulayici):
def dogrula(self, deger):
if not isinstance(deger, (int, float)) or deger <= 0:
raise ValueError(f'{self.ad} pozitif sayı olmalı: {deger!r}')
class Secenek(Dogrulayici):
def __init__(self, *secenekler):
self.secenekler = secenekler
def dogrula(self, deger):
if deger not in self.secenekler:
raise ValueError(f'{self.ad} şunlardan biri olmalı: {self.secenekler}')
class Siparis:
adet = Pozitif()
fiyat = Pozitif()
kargo = Secenek('standart', 'hızlı')
def __init__(self, adet, fiyat, kargo):
self.adet = adet
self.fiyat = fiyat
self.kargo = kargo
s = Siparis(2, 49.9, 'hızlı')
print(vars(s))
for hatali in [(0, 10, 'standart'), (1, 10, 'uçak')]:
try:
Siparis(*hatali)
except ValueError as e:
print(e)
print(vars(Siparis.__dict__['kargo'])['gizli'])
# çıktı: {'_adet': 2, '_fiyat': 49.9, '_kargo': 'hızlı'}
# çıktı: adet pozitif sayı olmalı: 0
# çıktı: kargo şunlardan biri olmalı: ('standart', 'hızlı')
# çıktı: _kargo__get__ içindeki if obj is None: return self satırı, öznitelik sınıf üzerinden okunduğunda betimleyicinin kendisini döndürür; araçların ve help()'in sınıfı incelemesini kolaylaştırır. Belgeler bir uyarı da yapar: sınıf oluşturulduktan sonra eklenen betimleyiciler için __set_name__ otomatik çağrılmaz, gerekiyorsa elle çağırmanız gerekir. Betimleyiciler dataclass alanlarında da kullanılabilir; dataclasses yazısında bu birleşimin kurallarına değiniyoruz.
functools.cached_property: bir kez hesapla
Hesaplaması pahalı ve nesne ömrü boyunca değişmeyen değerler için functools.cached_property vardır. İlk okumada fonksiyonu çalıştırır ve sonucu örneğin sözlüğüne aynı adla yazar. Veri olmayan betimleyici olduğu için sonraki okumalarda artık sözlükteki değer kazanır ve fonksiyon çalışmaz. Önbelleği temizlemek için özniteliği silmek yeterlidir:
from functools import cached_property
class Rapor:
def __init__(self, sayilar):
self.sayilar = sayilar
@cached_property
def toplam(self):
print('hesaplanıyor...')
return sum(self.sayilar)
r = Rapor([1, 2, 3])
print(r.toplam)
print(r.toplam) # ikinci okuma sözlükten gelir
print(vars(r))
r.sayilar.append(4)
print(r.toplam) # veri değişti ama önbellek eski
del r.toplam # önbelleği temizle
print(r.toplam)
# çıktı: hesaplanıyor...
# çıktı: 6
# çıktı: 6
# çıktı: {'sayilar': [1, 2, 3], 'toplam': 6}
# çıktı: 6
# çıktı: hesaplanıyor...
# çıktı: 10Örnekteki dördüncü satır en sık yapılan hatayı gösteriyor: veri değiştiği hâlde önbellek eski sonucu döndürdü. cached_property yalnızca "fiilen değişmeyen" nesneler için uygundur. Belgelere göre normal property atamaları setter yoksa engellerken cached_property atamaya izin verir. Örneğin yazılabilir bir __dict__'i olması gerekir, bu yüzden __dict__ içermeyen __slots__ sınıflarında çalışmaz. Python 3.12'den beri iş parçacıkları arasında kilit de kullanmaz; çok iş parçacıklı kodda fonksiyon aynı örnek için birden fazla kez çalışabilir (threading yazısı).
__slots__: sözlüksüz nesneler
Varsayılan olarak her örnek, özniteliklerini bir __dict__ sözlüğünde tutar. __slots__ sınıf değişkeni, örneklerin hangi öznitelikleri taşıyacağını önceden bildirir ve her örnek için __dict__ ile __weakref__ oluşturulmasını engeller. Belgeler bunun önemli bellek tasarrufu sağlayabileceğini ve öznitelik erişimini hızlandırabileceğini söyler. Bedeli esnekliktir: listede olmayan bir öznitelik eklenemez.
class Nokta:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Nokta(1, 2)
print(p.x, hasattr(p, '__dict__'))
try:
p.z = 3
except AttributeError as e:
print(e)
print(type(Nokta.__dict__['x']).__name__)
try:
class Hatali:
__slots__ = ('x',)
x = 0 # varsayılan değer için sınıf özniteliği kullanılamaz
except ValueError as e:
print(e)
# çıktı: 1 False
# çıktı: 'Nokta' object has no attribute 'z' and no __dict__ for setting new attributes
# çıktı: member_descriptor
# çıktı: 'x' in __slots__ conflicts with class variable__slots__ sınıf düzeyinde her ad için bir betimleyici (member_descriptor) oluşturarak çalışır. Bu yüzden aynı ada sınıf özniteliğiyle varsayılan değer veremezsiniz; sınıf özniteliği betimleyicinin yerine geçeceği için Python sınıfı oluşturmayı reddeder. Varsayılanı __init__'te atayın. Diğer kurallar: __slots__ tanımlamayan bir sınıftan türetirseniz örneklerde __dict__ yine bulunur; alt sınıf kendi __slots__'ını tanımlamazsa onun örnekleri de __dict__ kazanır, tanımlıyorsa yalnızca ek adları yazmalıdır. Zayıf referans (weakref) gerekiyorsa '__weakref__''i listeye ekleyin.
Sık yapılan hatalar

class Hatali:
@property
def ad(self):
return self.ad # kendi kendini çağırır
try:
Hatali().ad
except RecursionError as e:
print(type(e).__name__, '-', e)
# çıktı: RecursionError - maximum recursion depth exceeded- Property içinde aynı adı okumak: Getter'da
self.adokumak getter'ı yeniden çağırır veRecursionErrorile biter. Değeri farklı bir iç adda (self._ad) saklayın. Aynı hata setter'daself.ad = degeryazınca da olur. - Setter fonksiyonuna farklı ad vermek:
@x.setteraltındaki fonksiyonxadını taşımazsa, setter'lı kopya başka ada bağlanır vexsalt okunur kalır. - Betimleyiciyi örneğe koymak:
self.alan = Dogrulayici()hiçbir şey yapmaz; betimleyiciler sınıf gövdesinde tanımlanmalıdır. - Değeri betimleyici nesnesinde saklamak: Betimleyici tüm örnekler arasında paylaşılır. Değeri
self.degergibi betimleyicide tutarsanız bütün nesneler aynı değeri görür; değeriobj'nin kendi sözlüğüne yazın. - Değişen veride cached_property kullanmak: Önbellek kendiliğinden yenilenmez; ya
delile temizleyin ya da sıradan property kullanın. - __slots__'ı erken optimizasyon olarak eklemek: Dinamik öznitelik eklemeyi,
cached_property'yi ve bazı kütüphane davranışlarını kısıtlar. Çok sayıda küçük nesne üretmiyorsanız gerek olmayabilir.
Sık Sorulan Sorular
Her öznitelik için property yazmalı mıyım?
Hayır. Python'da düz öznitelikle başlamak normaldir. Doğrulama ya da hesaplama ihtiyacı doğduğunda property'ye geçmek, öznitelik erişim söz dizimini değiştirmediği için kullanıcı kodunu bozmaz.
Property ile descriptor arasındaki fark nedir?
Property, belirli bir öznitelik için getter, setter ve deleter fonksiyonlarını bir araya getiren hazır bir veri betimleyicisidir. Kendi betimleyici sınıfınızı yazmak, aynı mantığı birçok özniteliğe ve sınıfa uygulamanızı sağlar.
Metotlar neden betimleyici sayılır?
Fonksiyon nesneleri __get__ tanımlar. Sınıf özniteliği olarak bir örnek üzerinden okunduklarında bu metot, örneği ilk argümana bağlayan bir bağlı metot döndürür. classmethod, staticmethod ve super() da aynı protokol üzerine kuruludur.
__slots__ ile dataclass birlikte kullanılabilir mi?
Evet, @dataclass(slots=True) parametresi __slots__'ı sizin yerinize üretir. Elle __slots__ tanımlanmış bir sınıfta slots=True kullanmak ise TypeError verir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · Class ve nesne · Özel metotlar · dataclasses · Decorator yazma