Python 'descriptor' - giriş
Python'a taa 2.2 sürümünde girse de, çok fazla kullanılmayan bir özellik bu descriptor. Peki ne işe mi yarıyor bu? Muhtemelen bilirsiniz, java ve c++ gibi nesne bazlı dillerde "protected" denen bir nane var. Bir nesne'nin protected özelliklerini ancak kendisi değiştirebiliyor. Bunu da getter ve setter diye nitelendirilen public fonksiyonlar ile yapıyor.
Yine muhtemelen bilirsiniz, Python'da "protected" diye bir olay yok. Her nesnenin her özelliğini herkes kafasına göre değiştirebiliyor. İşte descriptor'ler burada devreye giriyor. Bu 'descriptor' denen şey bizim bunların erişimi kontrol altında tutmamıza yardımcı oluyor.
Peki, tam olarak nedir bu descriptor? Özetle, aşağıdaki metotlardan herhangi birini tanımlamış objelere descriptor diyoruz;
Şimdi uygulamalı olarak, çok basit bir descriptor görelim;
Bu kodu çalıştırdığımızda, alacağımız çıktı şu şekilde olacak;
12 Traceback (most recent call last): File "C:\Users\muhammed\workspace\adsensebomb\main.py", line 22, in b = Insan("osman") File "C:\Users\muhammed\workspace\adsensebomb\main.py", line 18, in __init__ self.yas = yas File "C:\Users\muhammed\workspace\adsensebomb\main.py", line 7, in __set__ raise ValueError("%s bir sayi degil." % value) ValueError: osman bir sayi degil.
Gördüğünüz gibi, a objesinin yas degeri 12 oldu, ancak b objesini yas
degeri osman olmadı. Ancak, "osman bir sayi degil." diye bir hata aldık.
İstediğimiz de buydu. Kodları incelersek, öncelikle SayiDescriptor
isimli bir sınıf oluşturduk. İşte, descriptor dediğimiz şey bu sınıf,
çünkü bu sınıf yukarıda bahsi geçen 3 fonksiyondan iki tanesini
tanımlıyor. Bir de __init__(self, name)
var. Bunu, her descriptor'un
kendini tanımlayacağı bir ismi olması için kullanacağız.
__set__(self, instance, value)
metodu, bu descriptor'a bir değer
atanırken Python tarafından çağırılan fonksiyon. Aldığı instance
argümanı, bu değişim hangi objeyi hedef alıyorsa o. Yukarıdaki
örneğimizde, a
ve b
objeleri, bu __set__(self, instance, value)
fonksiyonuna instance
değişkeniyle gönderilecek. value
ise, yeni
değer. Bu fonksiyonda, ilk önce value
ile gelen yeni değer bir sayı mı
diye kontrol ediyoruz. Eğer değilse, bir hata veriyoruz. Aksi takdirde
ise, instance
'ın bize verilen isimdeki değerini değiştiriyoruz. Bizim
örneğimizde bunun karşılığı, a._yas = 12
olarak görülebilir. b
objesi için ise, bu satıra gelmeden hata verilecek.
__get__(self, instance, owner)
ise, bu descriptor'dan değer almak için
kullanılıyor. instance
argümanı, yukarıdakiyle aynı. owner
ise, bu
objenin oluşturulduğu sınıf. Yani örneğimizde owner
olarak alacağı
argüman Insan
sınıfı olacak. Bu metodun yaptığı iş ise, bu
descriptor'un değerini döndürmek. Örneğimizde, print a.yas
satırında,
her ne kadar biz görmesek de, bu metot çağırılıyor. Bu metot ise
a._yas
değerini döndürüyor. Dikkat ederseniz, yukarıdaki __set__
metodu da bu değeri bu değişkende saklamıştı.
Bkz: isinstance, setattr, getattr
Dikkat etmemiz gereken bir diğer nokta da, bu descriptor Insan
sınıfında, fonksiyonları içinde değil de, en dışta örneklendi. Buna
dikkat etmezseniz, descriptor düzgün çalışmayacaktır.
Açıklaması biraz uzunca görünse de, aslında kullanım açısından çok zor değil. Her ne kadar yukarıdaki basitçe bir örnek olsa da, bu örneğe bakarak email veya telefon numarası için descriptor yapabilirsiniz.
Descriptor oluşturmanın bir diğer yolu ise, property fonksiyonu. Aşağıdaki örnek, hem yukarıdakiyle eşdeğer, hem de daha temiz görünüyor;
Bu örnek de, yukarıdakine benzer bir çıktı verecek. Burada, descriptor
için ayrı bir sınıf kullanmak yerine, property()
fonksiyonu ile bir
descriptor oluşturduk. Bu yöntem ilk yönteme göre biraz daha derli
toplu, ancak, yukarıdaki descriptor, birkaç farklı sınıf tarafından
kolayca örneklenebilir. Bu ise, oluşturulduğu sınıfın sınırları içine
sıkışmış bir descriptor.
Son olarak da, decorator kullanarak nasıl descriptor oluşturabileceğimize bakalım;
Bu fonksiyonda, yas
isimli fonksiyon için property
decorator'u
kullandık. Böylece, bu aynı isimle erişilebilecek bir özellik ve bu
özelliğin değerini verecek bir fonksiyon olmuş oldu. Daha sonra, yine
aynı isimle bir fonksiyon için yas.setter
decoratoru kullanarak, bu
fonksiyonu da değerin atanmasından sorumlu kıldık.
Böylece vereceğim örneklerin sonuna gelmiş olduk. Kolaylıklar...