Dekoratörleri Anlamak için 12 Kavram

Transkript

Dekoratörleri Anlamak için 12 Kavram
Dekoratörleri Anlamak için 12 Kavram
Not 1: Bu yazının büyük kısmı (~%90) şuradaki makalenin tercümesidir.
Not 2: Aşağıdaki kod örneklerinde Python’ın doctest modülü kullanılmıştır. Bu sözcüğü
içeren yorumları gözardı edebilirsiniz.
Not 3: Bu yazıda, dekoratör kavramını anlamak için gerekenler, temelden başlayarak ve
üzerlerine ekleme yapılarak sunulmuştur. Amacınız dekoratör yaratmayı öğrenmek olmasa
bile bu kavramların çoğunu (özellikle closure) bilmeniz gerekir.
Dekoratörler kesinlikle bu şekilde uygulanır diye bir koşul yoktur. Ama fikri anlamak için işe
yarar bir yol haritasıdır.
Not 4: Cümleleri okuyarak bu kavramları anlamış olmazsınız. Durup düşünmeniz, kendinizi
inandırmanız gerekir. Atatürk der ki; “İlim tercüme ile olmaz, inceleme ile olur.”
-doruk
1. Fonksiyonlar
Python’da fonksiyonlar def komutu ile yaratılırlar. Bir isim ve de opsiyonel bir
parametre listesi alırlar. Bir fonksiyondan değer döndürmek için return komutu
kullanılır. Olabilecek en basit fonksiyon şuna benzer;
Fonksiyon deklarasyonunda içerik (Python’daki diğer bütün tek satırdan uzun
deklarasyon için olduğu gibi) gereklidir ve satır başı boşluklarıyla seviyesi belirlenir.
Fonksiyonları, isimlerinin sonuna () ekleyerek çağırabiliriz.
2. Kapsam (Scope)
Python’da, her yeni fonksiyon bir kapsam yaratır. Pythonista’lar bu kavrama isim
uzayı da (namespace) derler. Bunun anlamı, Python’ın bir fonksiyonu çözümlerken
ilk önce o fonksiyona ait isim uzayına bakarak değişkeni bulmaya çalışmasıdır.
(Kapsam kelimesiyle devam edeceğim.) Kapsama bakacağımız bir takım fonksiyon
Python içerisinde
gelmektedir. Yerel (local) ve genel (global) kapsamlar arasındaki farkı anlamak için
şu kodu inceleyin;
Yerleşik (built-in) globals() fonksiyonu, Python’ın o kapsamda varlığından haberdar
olduğu bütün değişken isimlerini bir dictionary olarak verir. #2’de foo()
fonksiyonunu çağırıyoruz ve fonksiyon içerisindeki yerel kapsamdaki değişkenlerin
listesini alıyoruz. Bu örnekte, foo() fonksiyonunun kendine mahsus ayrı, boş bir
kapsamı olduğunu görüyoruz.
3.
Değişken Çözümleme Sırası Kuralları (Variable Resolution Rules)
Bu demek değil ki fonksiyonun içinden genel kapsamdaki değişkenlere
ulaşamıyoruz. Python’ın kapsam kurallarına göre; bir değişken her zaman yerel
kapsamda yaratılır ancak değişkene ulaşmaya çalışırken (içeriğini değiştirirken de
ulaşılması gerekir), uyan bir tane bulunana kadar sırasıyla bir üstteki kapsama
bakılır. Eğer foo() fonksiyonunu değiştirip genel kapsamdaki değişkeni yazdırmak
istersek, çalışacaktır.
#1 adımında, Python değişkeni önce fonksiyonun yerel kapsamında arıyor.
Bulamadığı için bir sonraki adımda (aynı değişken ismi için) genel kapsama bakıyor.
Ancak, aynı isimli bir değişkeni fonksiyonun içinde tanımlarsak istediğimiz sonucu
alamıyoruz.
Gördüğümüz üzere, genel kapsamdaki değişkenlere fonksiyon içlerinden ulaşabiliriz
ama (bu yazı için mühim olmayan istisnalar dışında) içeriklerini değiştiremeyiz. #1
adımında, fonksiyonun içinde, genel kapsamdaki (aynı isimli) değişkeni gölgeliyoruz
(shadow).
Bunu görmek için foo() fonksiyonunun içindeki yerel kapsamı (locals) yazdırıyoruz.
Dikkat ederseniz, bu sefer kapsam boş değil ve değişkenin yereldeki değeri
listeleniyor. Sonrasında, #2 adımında, genel kapsama çıkmış oluyoruz ve değişkenin
bu noktadaki (üst kapsamdaki/kümedeki) değeri de değişmiş oluyor.
4. Değişken Ömrü (Lifetime)
Son kullanım tarihi olarak da düşünebiliriz. Şöyle ki;
#1 adımında, hata sadece kapsam kurallarına aykırı gelindiğinden kaynaklanmıyor.
Konsol NameError hatasını bu yüzden veriyor ancak ana nedeni Python’da (ve diğer
bir çok programlama dilinde) fonksiyon çağırma kurallarının şekli. Fonksiyondan
çıktıktan sonra (yerelde tanımladığımız) değişkene erişmek için kullanabileceğimiz
bir sözdizimi (syntax) yok. Kapsam dışında, yani varlığı sona ermiş. Fonksiyonumuz
foo() için tanımlanan kapsam her çağrılışında baştan yaratılıyor ve fonksiyon
sonlandığında yok oluyor.
5. Fonksiyonlara Beslenen Argümanlar ve Deklarasyondaki Parametreler
Fonksiyonlara argümanlar besleyebiliyoruz. Argümanlar fonksiyon tanımında
parametre adıyla geçiyorlar ve deklarasyon içinde (fonksiyon metninde) yerel
kapsamda kullanılan
değişkenler oluyorlar. Basit bir şekilde anlatılırsa; etrafa saçılan değerler; kodu
yazarken parametre, kullanırken (değerlendirirken) argüman.
Python’da fonksiyon parametrelerini deklere etmek için birden fazla yöntem vardır.
Çok zor bir kavram değil ama detaylı bir açıklamasını (fonksiyon yaratma
kurallarının açıklamasını) okumanızı tavsiye ederim. Özeti; parametreler konumsal
(positional) ya da isimlendirilmiş (named) olabilirler. Konumsal parametreler
zorunludur, yani fonksiyonu çağırırken verilmesi (beslenmesi) gerekirler.
İsimlendirilmiş parametreler ise verilmek zorunda değildirler. İsimlendirilmiş
parametrelere, fonksiyon tanımlanırken bir varsayılan (default) değer verilir.
#1 adımında deklere ettiğimiz fonksiyonun bir tane konumsal parametresi (x) ve bir
tane de isimlendirilmiş parametresi (y) var.
#2 adımına bakacak olursak, bu fonksiyonu daha önceden gördüğümüz şekilde
çağırabiliyoruz: beslediğimiz değerler konumlarına göre fonksiyon tanımındaki
parametrelere bağlanıyorlar ve yerel kapsamda bu şekilde işlem görüyorlar.
#3 adımında ise, isimlendirilmiş parametre için bir değer beslemiyoruz. Bu
durumda, y parametresi için fonksiyon deklere edilirken verilmiş varsayılan değer
(0) kullanılıyor.
Ancak konumsal parametre için bir değer beslemek zorundayız. #4 adımı da bunu
gösteriyor. TypeError: foo() en az 1 argüman alır (0 verildi)
#5 adımında iş biraz karışıyor ama bu da aslında kavramın doğal bir uzantısı.
Fonksiyonu çağırırken parametrelere değer belirtebiliriz. Sonuçta parametrelerin
isimlerini biliyoruz; x ve y. Değerlerini çağırma esnasında belirliyoruz ve fonksiyonun
yerel kapsamında da bunlar kullanılıyor. Dikkatli inceliyorsanız (kül yutmam!)
parametrelerin sıraları ters. Değer vererek çağırırsanız, parametre isimlerini de
belirtmeniz gerektiği için, konumun önemi kalmıyor.
Mantıklı ama dikkat edilmesi gereken bir husus: #2 adımında isimlendirilmiş
parametreye (varsayılanı 0 olan) değer atadık ve istediğimiz sonucu aldık. Çünkü
konumsal parametrelere değer belirttikten sonra argüman listesi hala devam
ediyorsa, sırada hangi parametre (bu durumda isimlendirilmiş bir tane) varsa değer
ona atanır.
Özetle, fonksiyon tanımlama kurallarında iki tip parametre var; konumsal ve
isimlendirilmiş. Fonksiyon tanımlama ve çağırma adımlarında bu kavramların
anlamları biraz farklılık gösteriyor.
6. İçiçe (Nested) Fonksiyonlar
Fonksiyonların içinde başka fonksiyonlar tanımlayabiliriz. Kapsam ve ömür (son
kullanım) kuralları aynı şekilde burada da geçerli.
#1 adımında, x adındaki yerel değişken aranıyor. Kapsamda bulunamayınca, bir
üsttekine bakıyor. Bu örnekte bir üst kapsam başka bir fonksiyon. Outer()
fonksiyonunun yerel kapsamında olan x değişkenine inner() fonksiyonu da
erişebiliyor.
#2 adımında, (hala outer() fonksiyonunun deklarasyonu aşamasındayız) inner()
fonksiyonu çağırılıyor.
Fonksiyon deklarasyonu bittikten sonra, genel kapsama çıkıyoruz. Burada outer()
fonksiyonunu çağırıyoruz. Fonksiyonun tanımında “inner() çağır” var. Değişkeni (x)
bulabiliyor çünkü bir üst kapsamı da görebiliyor.
7.
Python’da Fonksiyonlar Üst Seviye (First Class) Objelerdir
Basit bir kavram; Python’da fonksiyonlar (diğer her şey gibi) bir objedir.
Python’da, fonksiyonlar programda kullanılan diğer bütün değerler gibi
değerlendirilir (görülür). Bu da demek oluyor ki, bir fonksiyonu bir başka fonksiyona
argüman olarak besleyebilirsiniz ya da bir fonksiyonun dış kapsama döndürdüğü
(return) şey bir başka fonksiyon olabilir.
#1 adımında bir fonksiyon deklere ediyoruz. Konumsal olarak ilk sıradaki parametre
bir fonksiyon besleyeceğimizi varsayıyor ancak deklere edilirken aynı diğer
parametreler gibi tanımlanıyor. Bir fonksiyona başka bir fonksiyonu beslemek için
Python’da özel bir sözdizimi yapmanıza gerek yok.
#2 adımında, parantezleri kullanarak func parametresini çağırıyoruz. Bunu iyi
kavramanız lazım; fonksiyonun bir ismi var (func) ve argüman olarak besleniyor.
Arkasına parantezleri ekleyerek bu ismin içindekini çağırıyoruz. Her şey bir obje
sonuçta. Verilen ismin içinde bir şeyler var. Bu örnekte, verilen ismin içerisinde
işlemler yapan bir fonksiyon var. Parantezleri eklediğimizde, Python’a “Bu ismin
içindekileri değerlendir.” (evaluate) diyoruz. İçindekinin ne olduğunu ve ne şekilde
kurallarla çağırılması gerektiğini bildiğimiz için (ve x ile y parametreleri de yerel
kapsamımızda olduğu için) çağırma işlemini usulüne göre yapabiliyoruz.
#3 adımında bunun uygulamasını görebilirsiniz. Farklı bir sözdizimine gerek olmadan
argüman olarak bir fonksiyon besliyoruz.
Üst kapsama bir fonksiyon döndürmek (return) de şöyle;
#1 adımında, outer() fonksiyonunun döndürdüğü değer inner() fonksiyonu: return
inner
inner() fonksiyonunu, outer() döndürmediği sürece göremiyoruz. Değişken ömrü
kurallarına istinaden, inner() fonksiyonu, outer()’ın her çağırılışında baştan
yaratılıyor ve bittiğinde de bellekten siliniyor. Ancak fonksiyonun ismi
döndürüldüğü için, bir üstteki kapsam inner()’ın varlığından haberdar.
#2 adımında, outer() tarafından döndürülen değeri (inner) foo değişkenine
atıyoruz. Konsola “foo değişkeninde ne var?” diye sorduğumuzda, görüyoruz ki
inner isimli fonksiyona işaret ediyor. Sonra da parantezleri kullanarak “foo
değişkeninin içindekileri değerlendir.” diyoruz.
Kavramı anlamak için mühim değil ama şunun farkında olun; foo = outer()
dediğimiz zaman Python’a “outer isminin içindekileri değerlendir ve foo içerisinde
sakla” diyoruz. Böylece şunu yapmış oluyoruz; inner() fonksiyonunu daha
değerlendirmiyoruz (çalıştırmıyoruz) ama bir sonraki adımda foo sonuna parantez
eklediğimizde “Bazı işlemlere işaret ediyorsun ya, şimdi onları değerlendir.”
diyoruz. İhtiyacımız olana kadar, bellekte (ya da isim uzayı içerisinde) inner() ve
kapsamı olmayacak.
8. Hatırlanan/Taşınan Kapsamlar (Closures)
Bu kavram biraz karışık. Nasıl bir resim bin kelime anlatırsa, burada da aşağıdaki
kod altındaki açıklamayı çok iyi tasvir ediyor. Dikkatli inceleyin ve lütfen her satırın
ne yapmaya çalıştığını (kapsam kümelerini aklınızda tutarak) düşünün.
Üstte verilen koddaki outer() deklerasyonuna bir satır ekleyip bir başka satırı da
değiştiriyoruz;
Kapsam ve değişken ömrü kurallarını göz önünde bulundurduğumuzda, kodda
çalışmamasını bekleyeceğimiz bir şey yapıyoruz. Görmediyseniz bir daha üzerinden
geçin. (Eyvah, Mahmut Hoca!)
Ama kod çalışıyor. Şöyle ki;
Python’ın kapsam kurallarına göre her şey düzgün: x, outer() fonksiyonunun yerel
kapsamındaki bir değişken. Önceden gördüğümüz gibi, #1 adımında inner()
fonksiyonu bu değişkeni yazdırmaya çalıştığı zaman, Python önce yerel kapsama
bakıyor, orada bulamıyor ve bir üst seviyedeki kapsama (outer) geçiyor ve orada
buluyor.
Ama işe değişken ömrü tarafından baktığımız zaman kurallara aykırı bir durum
görüyoruz. Değişkenimiz (x) outer() fonksiyonunun yerel kapsamında, yani sadece
outer() değerlendirilirken var. inner’ı sadece outer() değerlendirilmesi bittikten
sonra çağırabiliyoruz. Şöyle ki; inner ismi üst kapsama outer() tarafından
döndürülüyor, evet, ama değerlendirileceği sırada bizim outer() ile işimiz bitmiş
oluyor.
Şu ana kadar gördüğümüz kapsam kurallarına aykırı durum da burada oluşuyor:
Değişkenin (x) ömrü outer() değerlendirildikten sonra bitiyorsa, niye inner’la işimiz
olduğu zaman kapsam içinde olsun?
Ama içinde. Closure kavramı da bu. İçiçe geçmiş fonksiyonlardan bahsettiğimizde,
bir yerel kapsamın içinde bir başka yerel kapsam oluyor. Alt küme. İçteki kapsam,
üsttekinin hangi isimleri barındırdığını hatırlıyor. Üst kapsamın içeriğinin bir
kopyasını yanında taşımıyor, ama o kapsamın hangi isimlerden haberi olduğunu
biliyor.
Dikkat etmeniz gereken bir nüans var burada: içerideki kapsam, dışarıdakinin
deklarasyon sonucunda neye benzediğini biliyor. Eğer üst kapsamın içeriğinde
değerlendirilme sırasında (veya başka bir nedenden) bir değişiklik olursa, iç kapsam
bunu göremez. Tanımlananı bilir, değişiklikleri takip etmez.
Hangi kapsamın hatırlandığını görmek için de func_closure niteliğini (attribute)
kullanabiliriz. Hatırlarsanız foo isminin içinde inner ismine bir işaret var.
foo.func_closure dediğimizde, bellekte nelerden haberdar olunduğunun bir listesini
alabiliriz.
-- Devam edeceğim. Çay molası. -Hatırlayalım: outer() her çağırıldığında, inner ismi (ve haliyle kapsamı) silbaştan yaratılıyor.
Yukarıdaki kodda, x değişkeninin değeri hep aynı. Bu nedenle, inner fonksiyonunun her misali
(instance) diğerleriyle tıpatıp aynı şeyi yapıyor.
Bir closure tanımı daha şöyle olabilir; fonksiyonlar üst kapsamlarını hatırlarlar. Yani üst
kapsamdaki bir değişken, alt kapsamdaki fonksiyona görünen (efektif olarak o fonksiyona
bağlanmış) olan bir objedir. Inner’a 1 ya da 2 argümanlarını direkt beslemiyoruz. Fonksiyonun
(inner) özelleştirilmiş (custom) türevlerini (misallerini) yaratıyoruz. Bu özelleştirilmiş misaller de
(üst kapsamlarını görebilmeleri sayesinde) verilen değişkeni (outer tarafından) kullanabiliyorlar.
Bu kavramın tek başına önemli olmasının ana nedeni de bu. Tam bir OOP bakış açısı aslında.
outer(), inner() için bir constructor. Aldığı x argümanı da inner()’ın bir niteliği.
Kullanım alanları da çok. Python’ın sorted() fonksiyonundaki key parametresinin nasıl çalıştığına
aşinaysanız, bir closure yazmışsınızdır. Mesela; argüman olarak bir liste alınıyor; bu listenin
elemanları başka listeler. Bir lambda fonksiyonu kullanarak her listenin 2. konumdaki elemanına
göre sıralıyorsunuz üst listenizi. Bu da closure kullanmaktır.
OOP tarzında bir getter fonksiyonu yazarken şöyle yapabilirsiniz; önce değişken/obje alım işlerinin
yönetimini yapan bir motor yazarsınız. Sonra da bu motoru bir başka fonksiyonun içine koyup
etrafını başka bir kapsamla sarmalarsınız. Üstteki fonksiyona beslediğiniz argümanlara göre
oluşturulan bir kapsam ile birlikte içerideki fonksiyonu kullanan, özelleştirilmiş bir fonksiyon
yaratmış olursunuz.
9.
Dekoratörler
Tanım: argüman olarak başka bir fonksiyon alan (beslenen) ve özelleştirilmiş/farklı bir fonksiyon
döndüren, çağırılabilen (evaluate edilebilen) bir obje.
Yorum: Python’ın tasarımcıları bu kavramın önemli olduğunu düşünmüş olmasalardı dilin içine
standart desteğini koymazlardı.
İlk satırda, outer() fonksiyonunu tanımlıyoruz. Bu fonksiyon tek bir argüman alıyor ve o da bir
başka fonksiyon.
İkinci satırda fonksiyonun metnini yazmaya başlıyoruz. Burada da inner() fonksiyonunu
tanımlamaya başladık. Bu içerideki fonksiyon, önce ekrana bir string yazdıracak, sonra da
some_func parametresiyle gelen ismi değerlendirecek (sonundaki parantezler sayesinde).
#1 adımında, some_func’ın döndürdüğü değeri ret değişkeni içerisinde saklıyoruz.
some_func’ın değeri outer()’I her çağırdığımızda farklı olabilir. Bunda bir mahsur yok. Biz ne değer
gelirse onu değerlendireceğiz.
Sonraki adımda ise inner() tarafından beslenen fonksiyonun değeri + 1 döndürülüyor.
foo 1 rakamını döndürüyor. Bu fonksiyonu kullanarak outer()’ı besliyoruz ve bunu da decorated
değişkeni içerisinde saklıyoruz. Parantezleri kullanarak decorated değişkeninin değerlendirilmesini
talep ettiğimiz zaman da son 2 satırı alıyoruz.
Beklediğimiz string ekrana yazılmış, demek ki inner()’ın kapsamına girmişiz. Son satırda da 2
rakamı döndürülmüş. Yani foo’nun döndürdüğü değer (1) + 1.
Şimdi de dekoratör kelimesiyle ne kastedildiğini görebiliriz; decorated değişkeni, foo değişkenini
dekore eden bir obje. Verdiği sonuç, (bu örnekte) foo + 1.
Bu kavramı biraz daha optimize eder (bir Pythonista gibi düşünür) ve takip etmemiz gereken
kodun hacmini düşürürsek, foo değişkenini, onu dekore eden başka bir değişkenin içine atmak
yerine, foo değişkeninin içeriğini dekorasyon sonrasındaki ile güncellemeyi tercih edebiliriz. Şöyle
ki;
Böylece, foo() fonksiyonunu çağıranlar original foo değişkenini almayacaklar, dekore edilmiş olanı
alacaklar. foo değişkeninin dekorasyonunu otomize etmiş olduk. Kodumuzun üzerine eklemeler
yaparak devam edelim.
Koordinat objeleri aldığımız bir kütüphane (library) kullandığımızı varsayalım. Velev ki, x ve y
sayılarından oluşan bir obje. Ancak, aldığımız koordinat değerleri matematik işlemlerini
desteklemiyorlar ve de kaynak koduna müdahale edemiyor: yani bu desteği kütüphanenin içine
ekleyemiyoruz. Ancak, bu koordinatlarla bir sürü matematik işlemi yapacağız o yüzden bu desteği
kodumuza eklememiz lazım. Toplama ve çıkarma yapan iki fonksiyonla işe başlayalım.




__init__: Objenin x ve y niteliklerini tanımlıyoruz.
__repr__: Python’a, “Bu objeyi temsil ederken (represent) şöyle bir şey döndür.” diyoruz.
add: Verdiğim iki koordinatın x ve y niteliklerini topla ve yeni bir Coordinate() objesi
döndür.
sub: Verdiğim iki koordinatın x ve y niteliklerini birbirinden çıkart ve yeni bir Coordinate()
objesi döndür.
Buna ek olarak, add() ve sub() fonksiyonlarımızın beslenen argümanları belirli sınırlar içinde
değerlendirmelerini istiyoruz. Mesela; toplama ve çıkarma işlemlerini sadece pozitif koordinat
değerleriyle yapmak istiyoruz. Döndürülen objeler de (koordinatlar) pozitif değerlerle
sınırlanmalı. Yukarıdaki kod şunu veriyor;
Ama;


sub(one, two) sonucunda Coord: {y: 0, x: 0}
add(one, three) sonucunda Coord: {y: 200, x: 100}
almak istiyoruz ve bunu da one, two, three değerlerini değiştirmeden yapmak istiyoruz.
Fonksiyona girilecek argümanları beslemeden önce teker teker limitleri kontrol etmek ve de
fonksiyon işini bitirdikten sonra dönülen değeri bir kere daha limitler için kontrol etmek yerine,
bunu yapan bir dekoratör yazabiliriz.
Bu dekoratör aynen bu maddenin başındaki örnekteki gibi; verilen bir fonksiyonun özelleştirilmiş
bir versiyonunu döndürüyor. Bu sefer, farklı olarak, işe yarar bir şey yapıyor. Girdi olarak verilen
argümanları ve döndürülen objeyi (bu örnekte girdiler de aynı obje) limitler için control ediyor ve
negatif bir x ya da y değerini 0 ile değiştiriyor.
Böyle yapmanın kodumuzu daha temiz yapıp yapmadığı tartışmaya açık: limitleri kontrol etmeyi
kendi fonksiyonu içinde izole ediyoruz ve kontrol edilmesini istediğimiz her fonksiyona
uyguluyoruz.
Alternatifi şöyle olabilirdi; her girdi argüman (input argument) için ve çıktı (output) için çağırılacak
bir fonksiyon. Bu fonksiyon çağırmalarını her matematik fonksiyonumuz (add ve sub) içinde
birden fazla kere yapmamız gerekirdi. Bu iki olasılığı karşılaştırdığımızda, görüyoruz ki dekoratör
kullanmak bizi aynı işlevi gören kodu birden fazla yerde kullanmaktan kurtarıyor.
10. @ Sembolü Fonksiyona Dekoratör Atar
Python 2.4 ile birlikte, fonksiyonların başlarına @ işareti ve dekoratörün ismini (isim uzayı?)
koyarak, dekoratör ile etrafını sarma (dekorasyon atama) özelliği geldi. Yukarıdaki örneklerde,
fonksiyonumuzu sarmalanmış (wrap edilmiş) versiyonuyla değiştirerek dekorasyon efektini
sağladık.
Bu yöntemi herhangi bir fonksiyonu sarmalamak için kullanabiliriz. Ama yapılmışı var; bir
fonksiyon deklere ederken @ sembolünü kullanarak dekoratör atayabiliriz;
Üstteki kodun, add ismini wrapper ile sarmalayıp, sonrasında da özelleştirilmiş versiyonunu add
ismi içine döndürmekten bir farkı yok. Python biraz syntactic sugar ekleyerek kodun akışını daha
anlaşılır kılıyor.
Dekoratörleri kullanmak kolay bir şey. İşe yarar staticmethod ve classmethod gibi dekoratörleri
yazmak zor olsa bile, kullanması sadece @dekoratörismi ibaresini fonksiyonun başına eklemekten
ibaret.
11. *args ve **kwargs
İşe yarayan bir dekoratör yazdık ama sadece belirli bir (iki argüman alan) fonksiyon tipinde
çalışıyor. İçerideki checker isimli kontrol fonksiyonumuz, girdi olarak iki argüman kabul ediyor ve
bu değerleri işledikten sonra da closure içinde hatırladığı fonksiyona (üst kapsama) iletiyor.
Velev ki, olası bütün fonksiyonlarda işe yarar bir şey yapmasını istediğimiz bir dekoratör lazım.
Mesela, her fonksiyon çağırılmasında bir sayıcıyı (counter) 1 arttıran bir dekoratöre ihtiyaç var. Bu
dekoratöre beslenen fonksiyonlar zaten dekore edilmiş olabilir. Bizim ekleyeceğimiz sayıcı
dekoratörünün, kendisine beslenen her fonksiyonun deklere edilen argüman imzasını (signature)
alabilip, işini yaptıktan sonra da üst kapsama (bu isimleri) geçirebilmesi lazım. Var olan
dekorasyonun içeriğini değiştirmeden.
Python’da bu işlem için syntactic sugar var. Detayları şurada. Özetle, * operatörü fonksiyon
deklere ederken kullanılırsa, tanımlanmamış bütün konumsal parametreleri * sembolünden sonra
verilen ismin (argümanın) içine atar. Şöyle ki;
İlk satırda tanımlanan one() fonksiyonu, beslenen bütün (eğer varsa) konumsal argümanları
ekrana yazdırıyor. #1 adımında görüldüğü gibi, args parametresini fonksiyonun içinde (sonuçta
kapsamda) kullanıyoruz. *args ibaresi sadece fonksiyon deklere edilirken (imzasında) kullanılıyor.
#2 adımında da, tanımladığımız konumsal parametrelere ek olarak, “Beslenen ilave argüman
olursa onları da args ismi içine at.” diyoruz.
* operatörü, fonksiyonları çağırırken de kullanılabilir. Argümanın adının başında kullanılan *
sembolü, “bunun içindeki değerleri ayrıştır ve de konumsal parametreler olarak kullan.” anlamına
gelir. Şöyle ki;
#1 adımında yapılan ile #2 adımında yapılan aynı şeyler. Python, bizim #1 adımında elle yaptığımız
şeyi #2 adımında otomatikman yapıyor. Yani; *args şeklindeki bir tanımlama;


Fonksiyon çağırırken: yinelebilen (iterable) bir objeyi (mesela list) elementlerine ayırarak
konumsal değişkenlere kullan
Fonksiyon deklere ederken: verilen konumsal değişkenlerden, deklerasyon sırasında
özellikle belirtilmemiş olanları şu ismin içinde depola
demek.
** operatörü de buna benzeyen bir mantık. * operatörünün yinelenebilen objelerde yaptığını,
sözlük (dictionary) objelerinde yapıyor. * operatörü konumsal parametrelerle ilgili, ** operatörü
de key/value çiftleri (opsiyonel parametreler) ile ilgili.
Bir fonksiyon deklere ederken, **kwargs tanımını kullandığımızda şöyle demiş oluyoruz:
“Özellikle tanımlanmamış olan bütün anahtar kelimeli parametreleri (keyword arguments)
kwargs isminin içinde depola.” Ne args ismi ne de kwargs ismi Python’da rezerve edilmiş isimler
değiller. Ama, başkalarının kodlarını incelerken çokça karşınıza çıkacak isimler.
Aynı * gibi, ** operatörünün de fonksiyon deklere ederken ve çağırırken ayrı anlamları var.
12. Başka Başka Dekoratörler
Öğrendiklerinizi şöyle bir uygulama için kullanabilirsiniz; Bir fonksiyona beslediğiniz argümanların
çetelesini tutan (günlüğe kaydeden) bir sarmalayıcı (wrapper) dekoratör tasarlayabilirsiniz. İşleyişi
göstermek adına örneği basit tutacağız ve stdout’a (terminale) çıktı alacağız;
Görüldüğü üzere, inner isimli fonksiyon, #1 adımında, herhangi bir sayıda argüman alıyor. #2
adımında, argümanları sarmaladığı func fonksiyonuna aktarıyor. Böylece, argüman imzası ne
olursa olsun, herhangi bir fonksiyonu sarmalayabiliyoruz. Bir başka tanımla, dekore edebiliyoruz.
Fonksiyonları çağırdığımızda, günlük tutucunun (logger) yazdırdığı satırı (argümanların listesi)
alıyoruz. Argümanlar da fonksiyona dekoratör tarafından doğru aktarılmış ki, beklediğimiz return
değerini alıyoruz.
Tebrikler :-)

Benzer belgeler

Bilgisayar Bilimcisi Gibi Düşünmek: Python İle Öğrenme

Bilgisayar Bilimcisi Gibi Düşünmek: Python İle Öğrenme 1.7 Deneysel hata ayıklama.................................................................................................................11 1.8 Biçimsel (Formal) ve doğal diller.....................

Detaylı