Giriş
Dekoratör fonksiyonları yazılım tasarım desenleridir. Bir işlevin, yöntemin veya sınıfın işlevselliğini doğrudan alt sınıflar kullanmadan dinamik olarak değiştirirler. Ya da dekore edilmiş işlevin kaynak kodunu değiştirirler. Doğru kullanıldığında, dekoratörler geliştirme sürecinde güçlü araçlar haline gelebilir. Bu konu Python’da dekoratör işlevlerinin uygulamaları kapsar.
Syntax
def decorator_function(f): pass
# decorator_function isminde bir fonksiyon tanımlar@decorator_function
def decorated_function(): pass
# bu fonksiyon decorator_fonksiyon dekoratörü ile kapsanırdecorated_function = decorator_function(decorated_function)
# Bu gösterim yukarıdaki ile, dekorator kullanılan yöntem ile, aynıdır
Dekoratörler diğer fonksiyonların veya yöntemlerin davranışını destekler. Parametre olarak bir işlev alan ve bu işlevin bir miktar değiştirilmiş halini döndüren herhangi bir işlev dekoratör olarak kullanılabilir.
Dekoratör fonksiyonlar
# Bu en basit dekoratör dekore edilen fonksiyona hiçbir şey yapmaz.
# Bu tür dekoratörler nadiren kullanılır
def super_secret_function(f):
return f
@super_secret_function
def my_function():
print("Süper gizli fonksiyon.")
@
-gösterimi ile aşağıdaki kod parçasına eş değerde (sentetik) bir sonuç oluşturulur:
my_function = super_secret_function(my_function)
Dekoratörlerin nasıl çalıştığını anlamak için yukarıdaki bilgiyi akılda tutmak önemli olacaktır. Bu sentetik olmayan yazım tarzı, dekoratör işlevlerinin bir argüman olarak başka bir işlevi aldığını ve sonuçta neden başka bir işlevi geri döndürmesi gerektiğini söyler. Bir işlevi geri döndürmezsek ne olacağına bakalım:
def disabled(f):
"""
Bu fonksiyon hiçbir şey döndürmez, dolayısıyla dekore edilen fonksiyonu yerel kapsamdan çıkarır.
"""
pass
@disabled
def my_function():
print("Bu fonksiyon artık çağrılamaz...")
my_function()
# TypeError: 'NoneType' object is not callable
Bu nedenle, genellikle dekoratörün içinde yeni bir işlevi tanımlanır ve geri döndürülür. Bu yeni işlev ilk olarak tasarladığınız şeyi yapar, sonra orijinal işlevi çağırır ve bu işlevin döndürdüğü değeri işler. Aşağıda orijinal işlevin aldığı argümanları yazdıran, sonra da işlevi çalıştıran bir dekoratör örneğine bakalım:
# dekokratör fonksiyon:
def print_args(func):
def inner_func(*args, **kwargs):
print(args)
print(kwargs)
return func(*args, **kwargs) # Orijinal fonksiyonu argümanları ile çağırır
return inner_func
@print_args
def multiply(num_a, num_b):
return num_a * num_b
print(multiply(3, 5))
# Çıktı:
# (3,5) - Bu çıktı aslında fonksiyonun aldıdğı 'args'
# {} - 'kwargs', boş, çünkü anahtar argüman vermedik
# 15 - Fonksiyon sonucu
Dekoratör sınıfı
Girişte belirtildiği gibi, dekoratörler işlevlerin özelliklerini artırmak için işlevlere uygulanabilir işlevlerdir. Bu sentetik yapı my_func = dekorator(my_func)
ile eşdeğerdir. Ancak dekoratörler fonksıyon değil de bir class olsaydı ne olurdu? Syntax yine çalışırdı ancak şimdi my_func dekoratör sınıfının bir objesi ile değiştirilirdi. Bu sınıf call() yöntemini barındırıyorsa my_func’ı bir fonksiyon gibi kullanmak hala mümkün olacaktı:
class Decorator(object):
"""Basit bir dekoratör class."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Fonksiyon çağrısından önce.')
res = self.func(*args, **kwargs)
print('Fonksiyon çağrısından sonra.')
return res
@Decorator
def testfunc():
print('Fonksiyon içinde.')
testfunc()
# Fonksiyon çağrısından önce.
# Fonksiyon içinde.
# Fonksiyon çağrısından sonra.
Bir sınıf dekoratörü ile dekore edilmiş bir işlevin artık bir “fonksiyon” olarak kabul edilmeyeceğini unutmayın:
import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>
Dekoratör sınıfı için işlevler
Sınıf olduğunda dekorasyon yöntemleri için ek bir get-methodu tanımlamanız gerekir:
from types import MethodType
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Fonksiyon içinde.')
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
# Bir objeden çağrılmış gibi bir işlev döndür
return self if instance is None else MethodType(self, instance)
class Test(object):
@Decorator
def __init__(self):
pass
a = Test()
#Fonksiyon içinde.
Uyarı
Sınıf dekoratörleri sadece belirli bir işlev için bir örnek üretirler, bu nedenle bir sınıf dekoratörü ile bir yöntem dekore edersek bu sınıfın tüm örnekleri arasında aynı dekoratör paylaşılır:
from types import MethodType
class CountCallsDecorator(object):
def __init__(self, func):
self.func = func
self.ncalls = 0 # Bu yönteme yapılan çağrı sayısı
def __call__(self, *args, **kwargs):
self.ncalls += 1 # Çağrı sayısını artır
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
return self if instance is None else MethodType(self, instance)
class Test(object):
def __init__(self):
pass
@CountCallsDecorator
def do_something(self):
return 'bir şeyler oldu'
a = Test()
a.do_something()
a.do_something.ncalls # 1
b = Test()
b.do_something()
b.do_something.ncalls # 2
Dekoratör ile argüman kullanımı
Bir dekoratör sadece bir argüman alır: dekore edilecek işlevi. Başka argüman vermenin bir yolu yoktur.
Bununla birlikte ek argümanları kullanmak da genellikle arzu edilir. Bunun için bir hile keyfi sayıda argüman alan ve dekoratör döndüren bir işlev oluşturmaktır: dekoratör fabrikası.
Dekoratör fonksiyonları
def decoratorfactory(message):
def decorator(func):
def wrapped_func(*args, **kwargs):
print('Bu dekoratörün size bir mesajı var: {}'.format(message))
return func(*args, **kwargs)
return wrapped_func
return decorator
@decoratorfactory('Hello World')
def test():
pass
test()
#Bu dekoratörün size bir mesajı var: Hello World
Önemli Not:
Bu tür dekoratör fabrikalarıyla çalışırken dekoratörü bir çift parantez ile çağırmanız gerekir. Aşağıdaki kullanım hata verecektir:
@decoratorfactory # Parantez olmadan
def test():
pass
test()
TypeError: decorator() missing 1 required positional argument: ‘func’
Dekoratör Sınıfı
def decoratorfactory(*decorator_args, **decorator_kwargs):
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Dekoratör içinde, argümanlar: {}'.format(decorator_args))
return self.func(*args, **kwargs)
return Decorator
@decoratorfactory(10)
def test():
pass
test()
#Dekoratör içinde, argümanlar: (10,)
Bir dekoratörün dekore edilmiş bir işlev gibi görünmesini sağlamak
Dekoratörler normalde fonksiyonun metadatasını siler, çünkü artık aynı değildirler. Bu durum, meta-programlamada dinamik olarak metadataya erişirken sorunlara neden olabilir. Metadata ayrıca işlevin docstrings
ve isim verisini de içerir. functools.wraps
orijinal fonksiyonun birkaç özelliğini kopyalayarak dekore edilmiş işlevi orijinal işlev gibi gösterir.
from functools import wraps
Fonksiyon
def decorator(func):
# docstring, isim, yorum ve modülleri dekoratöre kopyalar
@wraps(func)
def wrapped_func(*args, **kwargs):
return func(*args, **kwargs)
return wrapped_func
@decorator
def test():
pass
test.__name__
#‘test’
Sınıf
class Decorator(object):
def __init__(self, func):
# docstring, isim, yorum ve modülleri sınıf örneğine kopyalar
self._wrapped = wraps(func)(self)
def __call__(self, *args, **kwargs):
return self._wrapped(*args, **kwargs)
@Decorator
def test():
"""Docstring.."""
pass
test.__doc__
#Docstring..
Dekoratörler ile fonksiyonun çalışma süresini ölçme
import time
def timer(func):
def inner(*args, **kwargs):
t1 = time.time()
f = func(*args, **kwargs)
t2 = time.time()
print 'Çalışma {0} saniye sürdü'.format(t2-t1)
return f
return inner
@timer
def example_function():
#işlemler
example_function()
Dekoratör ile tekil sınıf (singleton) oluşturma
Singleton, bir sınıfın örneğinin/objesinin oluşmasını kısıtlayan bir desendir. Dekoratör kullanarak bir sınıfın mevcut bir örneğini döndürecek veya (yoksa) yeni bir örneğini oluşturacak bir singleton sınıfı tanımlayabiliriz.
def singleton(cls):
instance = [None]
def wrapper(*args, **kwargs):
if instance[0] is None:
instance[0] = cls(*args, **kwargs)
return instance[0]
return wrapper
Bu dekoratör herhangi bir sınıf tanımına eklenebilir ve sınıfın en çok bir örneğinin oluşturulacağını söyleyebilir. Herhangi bir sonraki çağrı, mevcut olan sınıf örneğini döndürecektir.
@singleton
class SomeSingletonClass:
x = 2
def __init__(self):
print("oluşturuldu!")
instance = SomeSingletonClass() # çıktı: oluşturuldu!
instance = SomeSingletonClass() # çıktı yok
print(instance.x) # 2
instance.x = 3
print(SomeSingletonClass().x) # 3
Böylece sınıf örneğinizi yerel değişkeniniz aracılığıyla çağırsanız da başka bir sınıf öğesi oluştursanız da her zaman aynı nesneyi alırsınız.