koddla

Yazılımcıları bilgi ile güçlendirir.

Dekoratörler

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ır
  • decorated_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.

Bir yanıt yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Back to top