15
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

Dekoratörler

  • Upload
    noipana

  • View
    213

  • Download
    0

Embed Size (px)

DESCRIPTION

rwrewtwerwet

Citation preview

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 :-)