Python Dekoratörleri: Nasıl Kullanılır ve Neden?

Bir dekoratör bir işlevi alır, bazı işlevler ekler ve onu döndürür. Bu eğitimde, nasıl bir dekoratör yaratabileceğinizi ve onu neden kullanmanız gerektiğini öğreneceksiniz.

Python'da Dekoratörler

Python, mevcut bir koda işlevsellik eklemek için dekoratörler adı verilen ilginç bir özelliğe sahiptir .

Bu aynı zamanda metaprogramlama olarak da adlandırılır, çünkü programın bir bölümü derleme zamanında programın başka bir bölümünü değiştirmeye çalışır.

Dekoratörleri öğrenmek için ön koşullar

Dekoratörleri anlamak için önce Python'da birkaç temel şeyi bilmeliyiz.

Python'daki her şeyin (Evet! Sınıflar bile) nesne olduğu gerçeğiyle rahat olmalıyız. Tanımladığımız isimler, bu nesnelere bağlı basit tanımlayıcılardır. Fonksiyonlar istisna değildir, onlar da nesnelerdir (özniteliklerle). Aynı işlev nesnesine çeşitli farklı adlar bağlanabilir.

İşte bir örnek.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Çıktı

 Merhaba Merhaba

Eğer kod, her iki fonksiyonu çalıştırdığınızda firstve secondaynı çıktıyı verir. Burada isimler firstve secondaynı işlev nesnesine atıfta bulunur.

Şimdi işler daha da tuhaflaşmaya başlıyor.

Fonksiyonlar, başka bir işleve argüman olarak aktarılabilir.

Eğer gibi kullanılan fonksiyonlara varsa map, filterve reducePython, o zaman zaten bunu biliyor.

Diğer işlevleri bağımsız değişken olarak alan bu tür işlevlere yüksek dereceli işlevler de denir . İşte böyle bir işleve bir örnek.

 def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Fonksiyonu aşağıdaki gibi çağırıyoruz.

 >>> operate(inc,3) 4 >>> operate(dec,3) 2

Ayrıca, bir işlev başka bir işlevi döndürebilir.

 def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()

Çıktı

 Merhaba

Burada, is_returned()her çağırdığımızda tanımlanan ve dönen iç içe geçmiş bir işlev var is_called().

Son olarak, Python'daki Kapanışlar hakkında bilgi sahibi olmalıyız.

Dekoratörlere Geri Dönmek

İşlevler ve yöntemler, çağrılabildikleri için çağrılabilir olarak adlandırılır.

Aslında, özel __call__()yöntemi uygulayan herhangi bir nesne çağrılabilir olarak adlandırılır. Bu nedenle, en temel anlamda, bir dekoratör, bir çağrılabilir döndüren bir çağrılabilirdir.

Temel olarak, bir dekoratör bir işlevi alır, bazı işlevler ekler ve onu döndürür.

 def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Aşağıdaki kodları kabukta çalıştırdığınızda,

 >>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

Yukarıda gösterilen örnekte make_pretty(), bir dekoratördür. Atama adımında:

 pretty = make_pretty(ordinary)

İşlev ordinary()dekore edildi ve döndürülen işleve ad verildi pretty.

Dekoratör işlevinin orijinal işleve bazı yeni işlevler eklediğini görebiliriz. Bu, hediye paketlemeye benzer. Dekoratör bir sarmalayıcı görevi görür. Süslenen nesnenin doğası (içindeki gerçek hediye) değişmez. Ama şimdi güzel görünüyor (dekore edildiğinden beri).

Genel olarak, bir işlevi dekore ederiz ve şu şekilde yeniden atarız:

 ordinary = make_pretty(ordinary).

Bu yaygın bir yapıdır ve bu nedenle Python'un bunu basitleştirmek için bir sözdizimi vardır.

@Sembolü, dekoratör işlevinin adı ile birlikte kullanabilir ve onu dekore edilecek işlevin tanımının üstüne yerleştirebiliriz. Örneğin,

 @make_pretty def ordinary(): print("I am ordinary")

eşdeğerdir

 def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)

This is just a syntactic sugar to implement decorators.

Decorating Functions with Parameters

The above decorator was simple and it only worked with functions that did not have any parameters. What if we had functions that took in parameters like:

 def divide(a, b): return a/b

This function has two parameters, a and b. We know it will give an error if we pass in b as 0.

 >>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero

Now let's make a decorator to check for this case that will cause the error.

 def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

This new implementation will return None if the error condition arises.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

In this manner, we can decorate functions that take parameters.

A keen observer will notice that parameters of the nested inner() function inside the decorator is the same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameters.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Ilginç makaleler...