Python @Süslü Fonksiyonlar

Python  Linux  Programlama 

 08 Eylül 2018 - Türkiye



Follow
Follow
Follow

123 dakikalık okuma

Decorators

Python • Decorators.


Süslü fonksiyonlar


Süslü fonksiyonlarla ilgili bu eğiticide, neye, nasıl oluşturulacağına ve kullanılacağına bakacağız. Süslü fonksiyonlar, yüksek mertebeden fonksiyonları çağırmak için basit bir sözdizimi sağlar.

Matematik ve bilgisayar bilimlerinde, daha yüksek mertebeden bir fonksiyon (aynı zamanda fonksiyonel, fonksiyonel form) aşağıdakilerden en az birini yapan bir fonksiyondur:

  1. ifade olarak bir veya daha fazla fonksiyon alır (örn. Yordamsal parametreler) ,
  2. sonuç olarak bir fonksiyonu döndürür.

Tanım olarak, bir Süslü fonksiyon, başka bir fonksiyon alan ve açık bir şekilde değiştirmeden aldığı fonksiyonun davranışını genişleten bir fonksiyondur.

Esas olarak, süslü fonksiyonlar çevreleyici olarak fonksiyon görürler, fonksiyonun kendisini değiştirmeye gerek kalmadan, orijinal fonksiyonselliği arttırarak ve böylece süslemesini yaparak, kodun bir hedef fonksiyon yürütme işleminden önce ve sonra davranışlarını değiştirirler.

Bu kafa karıştırıcı gibi geliyor, ancak özellikle de süslü fonksiyonların nasıl çalıştığına dair birkaç örnek gördükten sonra değil. Bu makaledeki tüm örnekleri burada bulabilirsiniz.

Bu kafa karıştırıcı gibi geliyor, ancak özellikle de süslü fonksiyonların nasıl çalıştığına dair birkaç örnek gördükten sonra değil.


1 Fonksiyonlar

Süslü fonksiyonları anlayabilmeniz için öncelikle fonksiyonların nasıl çalıştığını anlamanız gerekir. Bizim amacımız için, bir fonksiyon verilen ifadelere dayalı bir değer döndürür. İşte çok basit bir örnek:

def ekle_bir(numara):
     return numara + 1


Python

In [1]: ekle_bir(2)
Out[1]:  3

Genel olarak, Python’daki fonksiyonlar, bir girdiyi bir çıktıya dönüştürmek yerine, yan etkilere de sahip olabilir. print() fonksiyonu bunun temel bir örneğidir: Konsola bir şey vermenin yan etkisine sahipken, hiçbiri’ni döndürmez. Ancak, süslü fonksiyonlar anlamak için, fonksiyonları ifadeleri bir değere dönüştüren bir şey olarak düşünmek yeterlidir.


Bilgisayar biliminde, Fonksiyonel programlama, hesaplamayı matematiksel fonksiyonların değerlendirilmesi olarak hesaplamayı işleyen ve değişen durum ve değişebilir verileri önleyen bir programlama paradigmasıdır. Bir bildirimsel programlama paradigmasıdır, yani programlama, ifadeler yerine deyim veya bildirimlerle yapılır.

Fonksiyonel programlamada beyanlar-statements- yerine ifadeler-expressions- ve bildirimler-declarations- kullanılır; bir programlama dilinde bir ifade, programlama dilinin başka bir değer üretmesi için yorumladığı ve hesapladığı bir veya daha fazla sabit, değişken, operatör ve fonksiyonlarin birleşimidir. Bu süreç, matematiksel ifadeler için değerlendirmeye denir.


1.1 Birinci Sınıf Öbekler

Python’da fonksiyonlar birinci sınıf öbeklerdir. Bu fonksiyonlar diğer öbekler (string, int, float, list, vb.) gibi, fonksiyonların içinden geçirilip bir ifade olarak kullanılabileceği anlamına gelir. Aşağıdaki üç fonksiyonu göz önünde bulundurun:

def merhaba_de(ad):
    return "Merhaba " + ad

def harika_ol(ad): 
    return "Hey " + ad + ", biz birlikte harikayız!"

def merhaba_kedi(selamlama_fonk):
    return selamlama_fonk("Kedi")

Burada, merhaba_de() ve harika_ol(), bir dizge olarak verilen bir -ad- ismini bekleyen normal fonksiyonlardır. Ancak merhaba_kedi() fonksiyonu, kendi ifadesi olarak bir fonksiyon -selamlama_fonk- bekler. Örneğin, merhaba_de() veya harika_ol() fonksiyonuna iletebiliriz:

Python

In [1]: merhaba_kedi(merhaba_de)
Out[1]: 'Merhaba Kedi'
In [2]: merhaba_kedi(harika_ol)
Out[2]: 'Hey Kedi, biz birlikte harikayız!'

merhaba_kedi(merhaba_de)‘nin iki fonksiyona, ancak farklı yollara başvurduğunu unutmayın: harika_ol() ve merhaba_de. merhaba_de fonksiyonu parantez içermiyor. Bu, fonksiyona yalnızca bir atfın geçirildiği anlamına gelir. Fonksiyon yürütülmez. Öte yandan, merhaba_kedi() fonksiyonu parantez ile yazıldığından, her zamanki gibi çağrılır.


1.2 İç fonksiyonlar

Diğer fonksiyonların içinde fonksiyonlar tanımlamak mümkündür. Bu gibi fonksiyonlar iç fonksiyonlar içinde çağrılır. İki iç fonksiyonlu bir fonksiyon örneği buradadır:

def ebeveyn():
    print("ebeveyn() fonksiyonundan okunuyor ...")

    def ilk_veled():
        print("ilk_veled() fonksiyonundan okunuyor ...")

    def ikinci_veled():
        print("ikinci_veled() fonksiyonundan okunuyor ...")

    ikinci_veled()
    ilk_veled()

ebeveyn() fonksiyonu çağırdığınızda ne olur? Bir dakikalığına düşün bunu. Çıkış aşağıdaki gibi olacaktır:

In [1]: ebeveyn()
ebeveyn() fonksiyonundan okunuyor ...
ikinci_veled() fonksiyonundan okunuyor ...
ilk_veled() fonksiyonundan okunuyor ...

İç fonksiyonların tanımlandığı sıranın önemli olmadığını unutmayın. Diğer fonksiyonlarda olduğu gibi, yazdırma yalnızca iç fonksiyonlar yürütüldüğünde gerçekleşir.

Ayrıca, üst fonksiyon çağrılana kadar iç fonksiyonlar tanımlanmamıştır. Bunlar yerel olarak ebeveyn() ile kapsama alınıp araştırılır: bunlar yalnızca yerel değişkenler olarak ebeveyn() fonksiyonu içinde bulunur. ilk_veled() öğesini çağırmayı deneyin. Bir hata almalısınız:

Python

In [2]: ilk_veled()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    ilk_veled()
NameError: name 'ilk_veled' is not defined

ebeveyn() çağırdığınızda, ilk_veled() ve ikinci_veled() iç fonksiyonları da çağrılır. Ancak yerel kapsamı nedeniyle, ebeveyn() fonksiyonu dışında mevcut değildir.


1.3 Fonksiyonlardan Dönen Fonksiyonlar

Python, fonksiyonları dönüş değeri olarak kullanmanıza da izin verir. Aşağıdaki örnek, dış ebeveyn() fonksiyonundan iç fonksiyonların birini döndürür.

def ebeveyn(num):
    def ilk_veled():
        return "Merhaba ben Emma"

    def ikinci_veled():
        return "Bana Liam deyin"

    if num == 1:
        return ilk_veled
    else:
        return ikinci_veled

Parantez olmadan ilk_veled döndürdüğünüzü unutmayın. Bunun, ilk_veled fonksiyonuna bir atıf döndürdüğünüz anlamına geldiğini hatırlayın. Aksine, ilk_veled() parantez içinde, fonksiyonun değerlendirilmesi sonucunu ifade eder. Bu, aşağıdaki örnekte görülebilir:


Python

In [1]: ilk=ebeveyn(1)
In [2]: ilk
Out[2]: <function __main__.ebeveyn.<locals>.ilk_veled>

In [4]: ikinci=ebeveyn(2)
In [5]: ikinci
Out[5]: <function __main__.ebeveyn.<locals>.ikinci_veled>

Bir miktar şifreli çıktı, basitçe, ebeveyn() içindeki, ikinci değişkeni ikinci_veled() fonksiyonuna işaret ederken ilk değişkeni yerel ilk_veled() fonksiyonuna başvurur.

İşaret ettikleri fonksiyonlara doğrudan erişilemese bile, artık normal fonksiyonlarmış gibi şimdi ilk ve ikinci kullanabilirsiniz:


Python

In [8]: ilk=ebeveyn(1)
In [9]: ilk()
Out[9]: 'Merhaba ben Emma'
In [10]: ikinci=ebeveyn(2)
In [11]: ikinci()
Out[11]: 'Bana Liam deyin'

Son olarak, daha önceki örnekte, iç fonksiyonları ebeveyn fonksiyon içinde gerçekleştirdiğinizi unutmayın,ilk_veled() özdeşi. Bununla birlikte, bu son örnekte, -return- den sonra iç fonksiyonlara parantez eklemediniz -ilk_veled-. Bu şekilde, gelecekte çağırabileceğiniz her bir fonksiyona bir atıf aldın. Mantıklı olmak?


1.4 Fonksiyonlar Hakkında Bilmeniz Gerekenler

Dalış yapmadan önce, net olması gereken bazı önkoşullar vardır. Python’da, fonksiyonlar birinci sınıf vatandaşlar, onlar öbeklerdir ve bu da onlarla çok yararlı şeyler yapabileceğimiz anlamına gelir.

Değişkenlere fonksiyon atama

def greet(name):
    return "hello "+name

greet_someone = greet
print greet_someone("John")

# Outputs: hello John


1.4.1 Fonksiyonları diğer fonksiyonlar içinde tanımlayın


def greet(name):
    def get_message():
        return "Hello "

    result = get_message()+name
    return result

print greet("John")

# Outputs: Hello John


1.4.2 Fonksiyonlar diğer fonksiyonlara parametre olarak geçirilebilir


def greet(name):
    def get_message():
        return "Hello "

    result = get_message()+name
    return result

print greet("John")

# Outputs: Hello John


1.4.3 Fonksiyonlar diğer fonksiyonlari döndürebilir


Başka bir deyişle, diğer fonksiyonları üreten fonksiyonlar.

def compose_greet_func():
    def get_message():
        return "Hello there!"

    return get_message

greet = compose_greet_func()
print greet()

# Outputs: Hello there!


1.4.4 İç fonksiyonlar bildirim alanını çevreleyerek erişime sahip olurlar


Daha çok bir kapanma olarak bilinir. Süslü fonksiyonlar inşa ederken karşılaşacağımız çok güçlü bir desen. Unutulmaması gereken bir başka şey ise, Python sadece dış bildirim alanını okumaya izin verir ve atamaya izin vermez. Yukarıdaki örneği, iç fonksiyonun kapalı kapsamından bir ‘name’ ifadeyi okumak ve yeni fonksiyonu döndürmek için nasıl değiştirdiğimizi fark ettik.

def compose_greet_func(name):
    def get_message():
        return "Hello there "+name+"!"

    return get_message

greet = compose_greet_func("John")
print greet()

# Outputs: Hello there John!


1.4.5 Süslü fonksiyonların Bileşimi


Süslü fonksiyonlar, mevcut fonksiyonları basit bir şekilde çevrelerler. Yukarıda bahsi geçen fikirleri bir araya getirerek bir süslü fonksiyon yapabiliriz. Bu örnekte, başka bir fonksiyonun string çıktısını p etiketleriyle saran bir fonksiyonu düşünelim.

def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

my_get_text = p_decorate(get_text)

print my_get_text("John")

# <p>Outputs lorem ipsum, John dolor sit amet</p>

Bu bizim ilk süslü fonksiyonumuzdu. Başka bir fonksiyonu ifade olarak alan bir fonksiyon, yeni bir fonksiyon üretir, orijinal fonksiyonun çalışmasını çoğaltır ve oluşturulan fonksiyonu döndürerek her yerde kullanabiliriz. get_text’in kendisi p_decorate tarafından donatılması için, sadece get_text ‘i p_decorate sonucuna atamak zorundayız.

get_text = p_decorate(get_text)

print get_text("John")

# Outputs lorem ipsum, John dolor sit amet

Dikkat edilmesi gereken diğer bir şey, süslü fonksiyonumuzun bir isim ifadesini almasıdır. Süslü fonksiyonda yapmamız gereken her şey, get_text’in çevreleyicisinin bu ifadeyi geçmesine izin vermektir.


1.5 Kısmi fonksiyonlar

functools kütüphanesinden kısmi fonksiyonu kullanarak python’da kısmi fonksiyonlar oluşturabilirsiniz.

Kısmi fonksiyonlar, daha az parametre ve daha sınırlı fonksiyon için ayarlanan sabit değerler ile bir fonksiyona x parametresiyle bir fonksiyon türettirilmesini sağlar.

İçe aktarma gerekli:

from functools import partial

def multiply(x,y):
        return x * y

# create a new function that multiplies by 2
dbl = partial(multiply,2)
print(dbl(4))

Bu kod 8’e dönecek.


Python

8

Önemli bir not: varsayılan değerler, değişkenleri soldan değiştirmeye başlar. 2 x’in yerini alacak. dbl(4) çağrıldığında y eşittir. Bu örnekte bir fark yaratmaz, ancak aşağıdaki örnekte yer almaktadır.


1.6 Functools.wraps Ne Yapar?

Bir süslü fonksiyon kullandığınızda, bir fonksiyonu bir diğeriyle değiştirirsiniz. Başka bir deyişle, bir süslü fonksiyonunuz varsa

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

o zaman siz

@logged
def f(x):
   """does some math"""
   return x + x * x

dersiniz.

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

söylemekle tamamen aynı şeydir.

ve fonksiyonunuzun f, with_logging fonksiyonuyla değiştirilir. Ne yazık ki, bu demek oluyor ki,

print f.__name__

with_logging yazdıracağız, çünkü bu yeni fonksiyonunuzun adı. Aslında, eğer f için docstring belgesi değerine bakarsanız, boş olacaktır çünkü with_logging’ın hiçbir docstring belgesi değeri yoktur ve yazdığınız dokümanlar artık orada olmayacaktır. Ayrıca, bu fonksiyon için pydoc sonucuna bakarsanız, bir x ifadesi alarak listelenmeyecek; bunun yerine *args ve **kwargs olarak listelenecektir çünkü bu, with_logging’in ele aldığı şeydir.

Bir süslü fonksiyon kullanmak her zaman bir fonksiyon hakkında bu bilgiyi kaybetmek anlamına gelirse, ciddi bir sorun olur. Bu yüzden functools.wraps var. Bu, bir süslü fonksiyonda kullanılan bir fonksiyonu alır ve fonksiyon adı, docstring belgesi, ifade listesi vb. üzerine kopyalama fonksiyonelliğini ekler. Ve wraps kendisi bir süslü fonksiyon olduğundan, aşağıdaki kod doğru şeyi yapar:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print (func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print (f.__name__)  # prints 'f'
print (f.__doc__)   # prints 'does some mat

print (f(5))  # prints 'f was called' 


Python

f
does some math

f was called
30


1.7 Functools.wraps Nasıl Kullanılır?

Bugün hakkında konuşmak istediğim az bilinen bir araç var. Buna wraps denir ve functools modülünün bir parçasıdır. Doküman dizelerini ve süslü işlevlerin adlarını docstring belgelerini düzeltmek için bir @süslü fonksiyon olarak wraps kullanabilirsiniz. Bu neden önemli? Bu ilk başta garip bir durum gibi görünüyor, ancak bir API veya kendinizden başka birisinin kullanacağı herhangi bir kod yazıyorsanız, bu önemli olabilir. Nedeni, Python’un bir başkasının kodunu bulmak için içbakış kullandığınızda, süslü bir fonksiyon yanlış bilgileri döndürecektir. example.py olarak adlandırdığım basit bir örneğe bakalım:

# example.py
 
#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """
 
    def wrapper():
        """
        A wrapping function
        """
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return wrapper
 
#----------------------------------------------------------------------
@another_function
def a_function():
    """A pretty useless function"""
    return "1+1"
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    print (a_function.__name__)
    print (a_function.__doc__)


help(example):

wrapper

        A wrapping function


Help on module example:

NAME
    example - # example.py

FUNCTIONS
    a_function = wrapper()
        A wrapping function
    
    another_function(func)
        A function that accepts another function

FILE
    ~/example.py


Bu kodda, ‘’‘a_function’’’ olarak adlandırılan fonksiyonu bir another_function ile donatırız. Fonksiyonun ‘’‘__name__’’’ ve ‘’‘__doc__’’’ özelliklerini kullanarak baskı yaparak ‘'’a_function’’‘’ın adını ve ‘'’docstring’’’ belgesi belgesini kontrol edebilirsiniz. Bu örneği çalıştırırsanız, çıktı için aşağıdakini alırsınız:

Bu doğru değil! Bu programı IDLE veya yorumlayıcıda çalıştırırsanız, bunun gerçekten kafa karıştırıcı, gerçekten hızlı bir şekilde nasıl elde edilebileceği daha da açık hale gelir. Bu gerçekten nasıl kafa karıştırıcı, gerçekten hızlı olabilir daha da belirgin hale geliyor.

Temel olarak burada olan şey, süslü fonksiyonun adını ve ‘'’docstring’’’ belgesini kendi başına değiştirmesidir.


1.7.1 Yardım için Wraps!

Bu küçük karışıklığı nasıl düzeltiriz? Python geliştiricileri bize ‘'’functools.wraps’’’ çözümü verdi! Hadi kontrol edelim:

from functools import wraps
 
#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """
 
    @wraps(func)
    def wrapper():
        """
        A wrapping function
        """
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return wrapper
 
#----------------------------------------------------------------------
@another_function
def a_function():
    """A pretty useless function"""
    return "1+1"
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    #a_function()
    print (a_function.__name__)
    print (a_function.__doc__)

Burada ‘'’functools’’’ modülünden ‘'’wraps’’’ ları içe aktarıyoruz ve bir ‘'’another_function’’’ içindeki iç içe ‘'’wrapper’’’ fonksiyonu için bir süslü fonksiyon olarak kullanıyoruz. Bu kez onu çalıştırırsanız, çıktı değişmiş olacaktır:


Python

a_function
A pretty useless function

Şimdi bir kez daha doğru isim ve docstring belgesi sahibiz. Python yorumlayıcınıza giderseniz, yardım fonksiyonu artık doğru şekilde çalışacaktır. Burada çıktısını koymaktan vazgeçip, denemeniz için onu bırakacağım.

help(example):

Help on module example:

NAME
    example

FUNCTIONS
    a_function()
        A pretty useless function
    
    another_function(func)
        A function that accepts another function

FILE
    ~/example.py


Özet


wraps süslü fonksiyonu hemen hemen bir numaradır, ama ihtiyaç duyduğunuzda oldukça kullanışlıdır. Eğer fonksiyonlarınızın size doğru ismi veya docstring belgesi vermediğini fark ederseniz, artık nasıl kolayca düzeltileceğini biliyorsunuz. Mutlu bir kodlama yapın!


2 Basit Süslü Fonksiyonlar

Artık, bu fonksiyonların Python’daki diğer herhangi bir öbek gibi olduğunu gördüğünüze göre, Python süslü fonksiyonu olan büyülü yaratığa geçmeye ve görmeye hazırsınız. Bir örnekle başlayalım:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_whee():
    print("Whee!")

say_whee = my_decorator(say_whee)

say_whee() çağırdığınızda ne olacağını tahmin edebilir misiniz? Dene:


Python

In [1]: say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

Burada neler olduğunu anlamak için önceki örneklere bakın. Tam anlamıyla şimdiye kadar öğrendiğiniz her şeyi gerçekten uyguluyoruz.

Sözde fonksiyon süsleme şu satırda gerçekleşir:

say_whee = my_decorator(say_whee)

Aslında, say_whee ismi şimdi wrapper() iç fonksiyonuna işaret etmektedir. Çağrı yaptığınızda wrapper fonksiyon olarak döndürdüğünüzü hatırlayın.

my_decorator(say_whee):


Python

In [3]: say_whee
Out[3]: <function __main__.my_decorator.<locals>.wrapper>

Ancak, wrapper() fonksiyonu, orijinal say_whee() fonksiyonune func olarak bir atıfta bulunur ve iki print() çağrı arasında bu fonksiyonu çağırır.

Basitçe söylemek gerekirse: süslü fonksiyonlar, onların davranışlarını değiştirerek bir fonksiyonu çevrelemektedir.

Devam etmeden önce, ikinci bir örneğe bakalım. wrapper() normal bir Python fonksiyonu olduğu için bir süslü fonksiyonun bir fonksiyonu değiştirdiği yöntem dinamik olarak değişebilir. Komşularınızı rahatsız etmeyecek şekilde, aşağıdaki örnek yalnızca gün boyunca süslü kodu çalıştıracaktır:

from datetime import datetime

def not_during_the_night(func):
    def wrapper():
        if 7 <= datetime.now().hour < 22:
            func()
        else:
            pass  # Hush, the neighbors are asleep
    return wrapper

def say_whee():
    print("Whee!")

say_whee = not_during_the_night(say_whee)


Yatma zamanı sonrası say_whee() çağırmayı denerseniz, hiçbir şey olmaz:


Python

In [1]: say_whee()
Whee!

In [4]: say_whee()
In [5]: 


2.1 Sözdizimsel Şeker!

Yukarıda say_whee() ile fonksiyonu süslediğiniz yol biraz biçimsiz ve hantal. Her şeyden önce, say_whee isminin üç kere yazımını bitirirsiniz. Buna ek olarak, fonksiyon süsleme, fonksiyon tanımının altında gizlenmiştir.

Bunun yerine, Python, bazen 'pie' sözdizimi olarak da adlandırılan @ sembolüyle daha basit bir şekilde süslü fonksiyonlar kullanmanıza izin verir. Aşağıdaki örnek, ilk Süslü fonksiyon örneğiyle tamamen aynı şeyi yapar:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_whee():
    print("Whee!")

Bu yüzden, @my_decorator sadece, say_whee = my_decorator(say_whee) demenin daha kolay bir yoldur. Bu bir fonksiyona bir süslü fonksiyonun nasıl uygulandığıdır.


2.1.1 Süslü fonksiyonları Yeniden Kullanmak

Bir süslü fonksiyonun sadece normal bir Python fonksiyonu olduğunu hatırlayın. Kolay tekrar kullanılabilirlik için tüm genel araçlar mevcuttur. Süslü fonksiyonu diğer birçok fonksiyonda kullanılabilecek kendi modülüne taşıyalım.

Aşağıdaki içerikle decorators.py adlı bir dosya oluşturun:

def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice


Bu yeni süslü fonksiyonu, normal bir içe aktarma yaparak diğer dosyalarda kullanabilirsiniz:

from decorators import do_twice

@do_twice
def say_whee():
    print("Whee!")


yada doğruda çalıştırabilirsiniz:

def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice

@do_twice
def say_whee():
    print("Whee!")


Bu örneği çalıştırdığınızda, orijinal say_whee() öğesinin iki kez yürütüldüğünü görmelisiniz:


Python

In [1]: say_whee()
Whee!
Whee!


2.1.2 Biraz Daha Faydalı


Şimdi, ilk örneğe dönelim ve uygulayalım. Burada, daha tipik olanı yapacağız ve aslında kodu süslü fonksiyonlarda kullanacağız:

class entryExit(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print ("Entering", self.f.__name__)
        self.f()
        print ("Exited", self.f.__name__)

@entryExit
def func1():
    print ("inside func1()")

@entryExit
def func2():
    print ("inside func2()")

func1()
func2()


Python

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2

süslü fonksiyonlarin artık çağrı etrafında 'Entering' ve 'Exited' izleme ifadelerine sahip olduğunu görebilirsiniz.

Kurucu, fonksiyon nesnesi olan bağımsız değişkeni saklar. Çağrıda, fonksiyonun adını göstermek için fonksiyonun __name__ özniteliğini kullanırız, daha sonra fonksiyonun kendisini çağırırız.


2.1.3 Meta sınıflar ve süslü fonksiyonlar: uzayda yapılmış bir eşleme


Meta sınıflar karmaşık bir konudur ve çoğu zaman ileri düzey programcılar, onlar için çok çeşitli pratik kullanımları görmez.

Gerçekten metasınıflar genellikle çok sayıda otomasyonun sağlanması gereken gelişmiş kütüphaneleri veya çerçeveleri programlarken oyuna girer. Örneğin, Django Forms sistemi, tüm sihrini sağlamak için meta sınıflara dayanır.

Bununla birlikte, genel olarak bilmediğimiz tüm teknikleri 'büyü' ya da 'hileler' olarak adlandırdığımızı ve Python’un bir sonucu olarak birçok şeyin bu şekilde çağrıldığını, bunun diğer dillere kıyasla genellikle kendine özgü bir uygulama olduğunu belirtmek zorundayız.

Biraz Python sihirbazlığı yapalım ve dilin gücünü kullanalım!

Bu yazıda size süslü fonksiyonlar ve meta sınıfların ilginç bir ortak kullanımını göstermek istiyorum. Yöntemleri işaretlemek için süslü fonksiyonların nasıl kullanılacağını size göstereceğim, böylece belirli bir işlemi gerçekleştirirken sınıf tarafından otomatik olarak kullanılabilirler.

Daha ayrıntılı olarak, bir dizgeye ‘işleyebilmek’ için çağrılabilecek bir sınıf uygulayacağım ve basit bir şekilde süslü yöntemlerle farklı ‘filtreleri’ nasıl uygulayacağınızı göstereceğim. Benim elde etmek istediğim şunun gibi:

class MyStringProcessor(StringProcessor):
    @stringfilter
    def capitalize(self, str):
        [...]

    @stringfilter
    def remove_double_spaces(self, str):
        [...]

msp = MyStringProcessor()
"A test string" == msp("a test string")


Bu yazı içerisinde kullanılan kaynak kodun tamamını içerir.

Modül, bir standart imza (self, str) içeren ve stringfilter ile donatılan ekleme yordamlarını alıp özelleştirebileceğim bir StringProcessor sınıfı tanımlar. Bu sınıf daha sonra örneklenebilir ve örnek, bir dizeyi doğrudan işlemek ve sonucu döndürmek için kullanılır. Dahili olarak sınıf, tüm süslü yöntemleri art arda otomatik olarak yürütür. Ayrıca, sınıfın, filtreleri tanımladığım sıraya uymasını istiyorum: ilk önce, ilk olarak çalıştırılır.

Metaclasses bu hedefe ulaşmak için nasıl yardımcı olabilir?

Basitçe söylemek gerekirse, meta sınıflar, sınıfları almak için örneklendirilen sınıflardır. Bu, bir dersi her kullandığımda, örneğin, onu örneklendirmek için ilk Python’un, yazdığımız meta sınıfı ve sınıf tanımını kullanarak bu sınıfı oluşturduğu anlamına gelir. Örneğin, sınıf üyelerini __dict__ özniteliğinde bulabileceğinizi biliyorsunuz: bu özellik, tür olan standart metaclass tarafından oluşturulur.

Verilen bir metaclass, sınıf tanımındaki fonksiyonların bir alt kümesini tanımlamak için bir kod eklememiz için iyi bir başlangıç ​​noktasıdır. Başka bir deyişle, meta sınıfın (yani, sınıfın) çıktısının tam olarak standart durumda olduğu gibi oluşturulmasını istiyoruz, ama ek olarak: stringfilter ile süslenmiş tüm yöntemlerin ayrı bir listesi.

class StringFilter(object):

    @staticmethod
    def split(string):
        return string.split(" ")

    @staticmethod
    def capitalize(string):
        return string.capitalize()

    @staticmethod
    def strip(string):
        return string.strip()

    def __new__(cls, string, *functions):
        result = string
        for function in functions:
            result = getattr(cls, function)(result)

        return result

if __name__ == "__main__":
    print(StringFilter("i am a cat  help me", "strip", "capitalize", "split"))


Python

['I', 'am', 'a', 'cat', '', 'help', 'me']


2.2 İfadelerle Donatılan Fonksiyonlar

Bazı ifadeleri kabul eden bir fonksiyonunuz olduğunu varsayalım. Hala donatabilir misin? Hadi deneyelim:

def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice

@do_twice
def greet(name):
    print("Hello" + name)


Maalesef, bu kodu çalıştırmak bir hataya yol açıyor:


Python

In [1]: greet("World")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    greet("World")
TypeError: wrapper_do_twice() takes 0 positional arguments but 1 was given

Sorun şu ki, içsel fonksiyon wrapper_do_twice() herhangi bir ifade almaz, ancak name = 'World' ona aktarıldı. wrapper_do_twice() öğesinin bir ifadeyi kabul etmesine izin vererek bunu düzeltebilirsiniz, ancak daha önce oluşturduğunuz say_whee() fonksiyonu için çalışmaz.

Çözüm, iç çevreleyici fonksiyonunda *args ve **kwargs kullanmaktır. Ardından, rastgele sayı ve konumsal ifadeyi kabul eder. decorators.py dosyasını aşağıdaki gibi yeniden yazınız:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

wrapper_do_twice iç fonksiyonu artık herhangi bir sayıdaki ifadeyi kabul eder ve bunları süslü fonksiyona iletir. Şimdi hem say_whee() ve greet() öbekleri çalışır:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def greet(name):
    print("Hello" + name)
    
@do_twice
def say_whee():
    print("Whee!")


Python

In [1]: greet("World")
HelloWorld
HelloWorld

In [2]: say_whee()
Whee!
Whee! 


2.3 Süslü Fonksiyonlardan Dönen Değerler

Süslü fonksiyonların dönüş değeri ne olur? Eh, karar vermek süslü fonksiyona kalmış. Basit bir fonksiyonu aşağıdaki gibi donattığınızı varsayalım:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return "Hi" + name


Kullanmayı dene:


Python

In [1]: hi_adam = return_greeting("Adam")
Creating greeting
Creating greeting

In [2]: print(hi_adam)
None


Maalesef, süslü fonksiyonunuz fonksiyondan dönüş değerini yedi.

do_twice_wrapper() fonksiyonu açıkça bir değer döndürmediğinden, return_greeting('Adam') çağrısı, None döndürerek sona ermiştir.

Bunu düzeltmek için, çevrelenen fonksiyonunun süslü fonksiyonun dönüş değerini döndürdüğünden emin olmanız gerekir. decorators.py dosyanızı 4. satırını ekleyerek değiştirin:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice


fonksiyonun son yürütülmesinden döndürülen değer döndürülür:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return "Hi " + name

@do_twice
def say_whee():
    print("Whee!")


Kullanmayı dene:


Python

In [3]: return_greeting("Adam")
Creating greeting
Creating greeting
Out[3]: 'Hi Adam'


2.4 Kimsin sen, Gerçekten mi?

Özellikle etkileşimli kabukta, Python ile çalışırken büyük kolaylık, güçlü içebakış yeteneğidir. İçebakış, bir nesnenin çalışma zamanında kendi öz niteliklerini bilmesidir. Örneğin, bir fonksiyon kendi adını ve belgelemesini bilir:


Python

In [1]: print
Out[1]: <function print>

In [2]: print.__name__
Out[2]: 'print'

In [3]: help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    <full help message>


İçebakış, kendinizin tanımladığı fonksiyonlar için de çalışır:


Python

In [6]: say_whee
Out[6]: <function __main__.do_twice.<locals>.wrapper_do_twice>

In [7]: say_whee.__name__
Out[7]: 'wrapper_do_twice'

In [8]: help(say_whee)
Help on function wrapper_do_twice in module __main__:


Ancak, fonksiyon süslendikten sonra, say_whee() kendi kimliği konusunda çok kafa karıştırdı. Şimdi do_twice() süslü fonksiyonunun içinde wrapper_do_twice() iç fonksiyonu olduğunu bildiriyor. Teknik olarak doğru olsa da, bu çok yararlı bir bilgi değildir.

Bunu düzeltmek için, süslü fonksiyonlar orijinal fonksiyonla ilgili bilgileri muhafaza eden @functools.wraps süslü fonksiyonunu kullanmalıdır. Decorators.py betiğini tekrar güncelleyin:

import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice



süslü say_whee() fonksiyonu hakkında bir şey değiştirmeniz gerekmez:


Python

In [9]: say_whee
Out[9]: <function __main__.say_whee>

In [10]: say_whee.__name__
Out[10]: 'say_whee'

In [11]: help(say_whee)
Help on function say_whee in module __main__:

say_whee()


Çok daha iyi! Şimdi say_whee(), dekorasyondan sonra hala kendisidir.


3 Birkaç Gerçek Dünya Örneği

Süslü fonksiyonların birkaç yararlı örneğine bakalım. Şimdiye kadar öğrendiklerinizle aynı kalıbı takip edeceklerini fark edeceksiniz.

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Do something before
        value = func(*args, **kwargs)
        # Do something after
        return value
    return wrapper_decorator


Bu formül, daha karmaşık süslü fonksiyonlar oluşturmak için iyi bir standart kalıptır.


3.1 Zamanlama Fonksiyonları

Bir timer süslü fonksiyon oluşturarak başlayalım. Bir fonksiyonu yürütmek ve süreyi konsola yazdırmak için gereken süreyi ölçer. İşte kod:


import functools
import time

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()    # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()      # 2
        run_time = end_time - start_time    # 3
        print('Finished  waste_some_time in  {:.4f} secs'.format(run_time))
        return value
    return wrapper_timer

@timer
def waste_some_time(num_times):
    for _ in range(num_times):
        sum([i**2 for i in range(10000)])


Bu süslü fonksiyon, fonksiyonun çalışmaya başlamasından hemen önceki süreyi saklayarak çalışır (# 1 olarak işaretlenmiş satırda) ve fonksiyon bittikten hemen sonra (# 2’de). Fonksiyonun aldığı zaman ikisinin arasındaki farktır (# 3’de). Zaman aralıklarını ölçmek için iyi bir iş yapan time.perf_counter() fonksiyonunu kullanırız.


Python

In [14]: waste_some_time(1)
Finished  waste_some_time in  0.0029 secs

In [34]: waste_some_time(999)
Finished  waste_some_time in  2.8798 secs


Kendi kendinine çalışır. Kod boyunca satır satır çalışır. Nasıl çalıştığını anladığınızdan emin olun. Yine de anlamadıysan endişelenme. Süslü fonksiyonlar gelişmiş yapılardır. Ertelemeyi deneyin veya program akışını çizi.

Python2’de, print bir ifade sunan bir anahtar kelime oldu: print "Hi"

Python3’te print, çağrılabilecek bir fonksiyondur: print ("Hi").

Her iki sürümde, %, sol tarafta bir dize ve sağ tarafta bir değer veya bir değer sayısı veya bir eşleme nesnesi gerektiren bir operatördür.

Satır böyle görünür: print(“a=%d,b=%d” % (f(x,n),g(x,n)))

Python3 için olan öneri, %-style biçimlendirme yerine {} -style biçimlendirme kullanmaktır: print('a={:d}, b={:d}'.format(f(x,n),g(x,n)))

Python 3.6, başka bir dizgi formatlama paradigmasını sunar: f-dizeleri. print(f'a={f(x,n):d}, b={g(x,n):d}')



3.2 Hata Ayıklama Kodu

Aşağıdaki @debug süslü fonksiyon argümanları yazdıracaktır, fonksiyon çağrıldığında her seferinde bir fonksiyon, dönüş değeriyle birlikte çağrılır:


import functools

def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)           # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           # 4
        return value
    return wrapper_debug

@debug
def make_greeting(name, age=None):
    if age is None:
        return f"Howdy {name}!"
    else:
        return f"Whoa {name}! {age} already, you are growing up!"


Python

In [1]: make_greeting("Benjamin")

Calling make_greeting('Benjamin')
'make_greeting' returned 'Howdy Benjamin!'

In [2]: make_greeting("Richard", age=112)

Calling make_greeting('Richard', age=112)
'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'

In [3]: make_greeting(name="Dorrisile", age=116)

Calling make_greeting(name='Dorrisile', age=116)
'make_greeting' returned 'Whoa Dorrisile! 116 already, you are growing up!'


{signature}, tüm argümanların dize gösterimlerine katılarak oluşturulur. Aşağıdaki listedeki sayılar, koddaki numaralı yorumlara karşılık gelir:

  1. Konumsal ifadelerin bir listesini oluşturun. Her ifadeyi temsil eden güzel bir dize almak için repr() öğesini kullanın.

  2. keyword ifadelerinin bir listesini oluşturun. value ifade etmek için kullanılan repr()‘yi ifade eden !r belirtecinin olduğu yerde key=value olarak f-string her bir ifadeyi formatlar.

  3. Konumsal ve keyword ifadelerinin listeleri, bir virgülle ayrılan herbir ifade ile signature dizisinin birine beraber katılır.

  4. Fonksiyon çalıştırıldıktan sonra geri dönüş değeri yazdırılır.

Süslü fonksiyonun, bir konum ve bir anahtar kelime ifadesiyle basit bir fonksiyona uygulayarak pratikte nasıl çalıştığını görelim:

@debug süslü fonksiyonunun make_greeting() fonksiyonunun signature ve dönüş değerini nasıl yazdığını not alın:

Bu örnek, @debug süslü fonksiyonu az önce yazdıklarınızı tekrarladığından beri kullanışlı görünmeyebilir. Doğrudan kendinize çağrı yapmadığınız küçük uygun fonksiyonlara uygulandığında daha güçlüdür.

Aşağıdaki örnek, matematik sabiti e’ye bir yaklaşım hesaplar:


import math
import functools

def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)           # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           # 4
        return value
    return wrapper_debug

# Apply a decorator to a standard library function
math.factorial = debug(math.factorial)

@debug
def approximate_e(terms=18):
    return sum(1 / math.factorial(n) for n in range(terms))

approximate_e(5) 


Bu örnek ayrıca, bir süslü fonksiyonun önceden tanımlanmış bir fonksiyona nasıl uygulanabileceğini gösterir. e‘nin yaklaşımı, aşağıdaki dizi genişlemesine dayanmaktadır:

e’nin değeri de 1/0! + 1/1! + 1/2! + 1/3! + 1/4! + 1/5! + 1/6! + 1/7! + ... (etc) eşittir

Approximate_e() fonksiyonunu çağırırken, @debug süslü fonksiyonunu iş başında görebilirsiniz:


Python

Calling approximate_e(5)
Calling factorial(0)
'factorial' returned 1
Calling factorial(1)
'factorial' returned 1
Calling factorial(2)
'factorial' returned 2
Calling factorial(3)
'factorial' returned 6
Calling factorial(4)
'factorial' returned 24
'approximate_e' returned 2.708333333333333


Bu örnekte, sadece 5 terim ekleyerek, e = 2.718281828 gerçek değerine doğru bir yaklaşım elde edersiniz.


3.2.1 Python Sınıflarınızda “Dize” Dönüştürme Nasıl Desteklenir?


Python’da bir özel sınıf tanımladığınızda ve örneklerinden birini konsolda yazdırmaya çalıştığınızda (veya bir yorumlayıcı oturumunda inceleyin), göreceli olarak tatmin edici olmayan bir sonuç elde edersiniz.

Varsayılan ‘dizgi’ dönüştürme davranışı temeldir ve ayrıntılardan yoksundur:


class Car:
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage


Python

In [1]: my_car = Car('red', 37281)

In [2]: print(my_car)
<__main__.Car object at 0x7ff90a6c1550>

In [3]: print(my_car.color, my_car.mileage)
red 37281


Varsayılan olarak, aldığınız tek şey sınıf adını ve nesne örneğinin kimliğini içeren bir dizedir (bu, nesnenin CPython’daki bellek adresidir.) Bu, hiçbir şeyden daha iyi değildir, ancak aynı zamanda çok kullanışlı değildir.


3.2.2 Neden Her Python Sınıfının bir __repr__ ihtiyacı var?


Sınıfın özniteliklerini doğrudan yazdırarak veya sınıflarınıza özel bir to_string() yöntemi ekleyerek, bu konu üzerinde çalışmaya deneyebilirsiniz.


class Car(object):
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage

    def __repr__(self):
       return '{}({!r}, {!r})'.format(
           self.__class__.__name__,
           self.color, self.mileage)


Python

In [8]: my_car = Car('red', 37281)

In [9]: print(my_car)
Car('red', 37281)


Genel fikir burada doğru olanıdır -ancak Python’un nesnelerin dizge olarak nasıl temsil edildiğini işlemek için kullandığı sözleşmeleri ve yerleşik mekanizmaları yok sayar.


3.3 Kod Aşağı Çekiliyor

Bu sonraki örnek çok kullanışlı görünmeyebilir. Neden Python kodunuzu yavaşlatmak istersiniz? Muhtemelen en yaygın kullanım durumu, - bir web sayfası gibi - bir kaynağın olup olmadığını sürekli kontrol eden bir fonksiyonun hızını limitlemek istemenizdir. @slow_down süslü fonksiyon, süslü fonksiyonu çağırmadan önce bir saniye uyuyacaktır:

“With “Why would you want to slow down your Python code? “ you’re questioning the other person’s motivations, which implies that a) it was deliberate and b) they might do it again c) you’re simply asking about someone’s motivation or opinions.”


import functools
import time

def slow_down(func):
    """Sleep 1 second before calling the function"""
    @functools.wraps(func)
    def wrapper_slow_down(*args, **kwargs):
        time.sleep(1)
        return func(*args, **kwargs)
    return wrapper_slow_down

@slow_down
def countdown(from_number):
    if from_number < 1:
        print("Liftoff!")
    else:
        print(from_number)
        countdown(from_number - 1)

countdown(3)

@slow_down süslü fonksiyonunun etkisini görmek için, örneği kendiniz uygulamanız gerekiyor:


Python

3
2
1
Liftoff!


@slow_down süslü fonksiyon her zaman bir saniye uyur. Daha sonra, süslü fonksiyona bir ifadeyi geçirerek oranı nasıl kontrol edeceğinizi göreceksiniz.


3.4 Eklentileri Kaydetmek

Süslü fonksiyonların, süsledikleri fonksiyonu çevrelemesi gerekmez. Ayrıca bir fonksiyonun mevcut olduğunu kolayca kaydedebilirler ve çevrelenmemiş olarak döndürebilirler. Bu kullanılabilir, örneğin, hafif bir eklenti mimarisi oluşturmak için kullanılabilir:


import random
PLUGINS = dict()

def register(func):
    """Register a function as a plug-in"""
    PLUGINS[func.__name__] = func
    return func

@register
def say_hello(name):
    return f"Hello {name}"

@register
def be_awesome(name):
    return f"Yo {name}, together we are the awesomest!"

def randomly_greet(name):
    greeter, greeter_func = random.choice(list(PLUGINS.items()))
    print(f"Using {greeter!r}")
    return greeter_func(name)


randomly_greet("Alice")

print(f"{PLUGINS}")

print(globals())

@register süslü fonksiyonu, global PLUGINS dict içindeki süslü fonksiyona bir atfı basitçe saklar. Bir iç işlev yazmanız veya bu örnekte @functools.wraps kullanmanız gerekmez unutmayın, çünkü değiştirilmemiş orijinal işlevi döndürüyorsunuz.

randomly_greet() fonksiyonu, kayıtlı fonksiyonlardan birini kullanmak için rasgele seçer. PLUGINS sözlüğünün zaten eklenti olarak kaydedilen her bir fonksiyon öbeğine atıflar içerdiğini unutmayın:


Python

Using 'say_hello'

{'say_hello': <function say_hello at 0x7f0105032730>, 
'be_awesome': <function be_awesome at 0x7f01050327b8>}


Bu basit eklenti mimarisinin temel faydası, eklentilerin bulunduğu bir listeye bakımınızın gerekmemesidir. Bu liste, eklentiler kendilerini kaydettirdiğinde oluşturulur. Bu yeni bir eklenti eklemeyi önemsiz kılar: sadece fonksiyonu tanımlayın ve @register ile süsleyin.

Python’da globals() ile tanışıyorsanız, eklenti mimarisinin nasıl çalıştığıyla ilgili benzerlikler görebilirsiniz. globals(), eklentileriniz de dahil olmak üzere geçerli kapsamdaki tüm global değişkenlere erişim sağlar:


Python

{'__name__': '__main__', '__doc__': None, '__package__': None, 
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7ffa9d26bc50>, 
'__spec__': None, '__annotations__': {}, 
'__builtins__': <module 'builtins' (built-in)>, 
'__file__': '/tmp/sessions/1e22d25eab5bc3c7/main.py', 
'__cached__': None, 'random': <module 'random' from '/usr/lib/python3.6/random.py'>,

'PLUGINS':
{'say_hello': <function say_hello at 0x7ffa9bcb46a8>, 
'be_awesome': <function be_awesome at 0x7ffa9bcb4730>},

'register': <function register at 0x7ffa9d290e18>, 
'say_hello': <function say_hello at 0x7ffa9bcb46a8>, 
'be_awesome': <function be_awesome at 0x7ffa9bcb4730>, 
'randomly_greet': <function randomly_greet at 0x7ffa9bcb47b8>}

@register süslü fonksiyonunu kullanarak, globals() fonksiyonlarından bazılarını etkin bir şekilde seçerek kendi seçilmiş ilginç değişkenler listenizi oluşturabilirsiniz.

web çerçevesiyle çalışırken yaygın olarak kullanılır


3.5 Kullanıcı Oturumu Açıldı mı?

Bir web çatısıyla çalışırken yaygın olarak kullanılan bazı daha süslü fonksiyonlar için yola devam etmeden önce son örnek. Sadece giriş yapan kullanıcılar tarafından görülen veya başka şekilde doğrulanmış bir /secret web sayfası kurmak için Flask kullanıyoruz:


from flask import Flask, g, request, redirect, url_for
import functools
app = Flask(__name__)

def login_required(func):
    """Make sure user is logged in before proceeding"""
    @functools.wraps(func)
    def wrapper_login_required(*args, **kwargs):
        if g.user is None:
            return redirect(url_for("login", next=request.url))
        return func(*args, **kwargs)
    return wrapper_login_required

@app.route("/secret")
@login_required
def secret():
    ...

Web çatınıza kimlik doğrulamanın nasıl ekleneceği hakkında bir fikir verirken, genellikle bu tür süslü fonksiyonları kendiniz yazmamalısınız. Flask için daha fazla güvenlik ve işlevsellik ekleyen Flask-Login uzantısını kullanabilirsiniz.


4 Çok Süslü Fonksiyonlar


Şimdiye kadar, basit süslü fonksiyonların nasıl oluşturulduğunu gördünüz. Süslü fonksiyonların ne olduğunu ve nasıl çalıştıklarını çok iyi biliyorsunuz. Öğrendiğiniz herşeyi uygulamak için bu makaleden bir ara vermekte özgür hissedin.

Bu eğiticinin ikinci bölümünde, aşağıdakileri nasıl kullanacağınız da dahil olmak üzere daha gelişmiş özellikleri inceleyeceğiz:


Sınıflardaki Süslü Fonksiyonlar

Süslü Fonksiyon Sınıfları

Sınıflarda süslü fonksiyonlar kullanmanın iki farklı yolu vardır. İlki, zaten fonksiyonlarla yaptığınız şeye çok yakın: Bir sınıfın yordamlarını süsleyebilirsiniz. Süslü fonksiyonların gün içinde desteklenen tanıtım motivasyonlarından biriydi.

Python’da bile yerleşik olan bazı yaygın olarak kullanılan süslü fonksiyonlar @classmethod, @staticmethodv ve @property‘dir. @classmethod ve @staticmethod süslü fonksiyonları, bu sınıfın belirli bir özdeşine bağlanmayan bir sınıf aduzayı içindeki yordamları tanımlamak için kullanılır. @property süslü fonksiyonu, sınıf öznitellikleri için getters and setters özelleştirmek için kullanılır. Bu süslü fonksiyonları kullanılarak bir örnek için aşağıdaki kutucuğu genişletin.

Bir Circle sınıfının aşağıdaki tanımı @classmethod, @staticmethod ve @property süslü fonksiyonlarını kullanır:


class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        """Get value of radius"""
        return self._radius

    @radius.setter
    def radius(self, value):
        """Set radius, raise error if negative"""
        if value >= 0:
            self._radius = value
        else:
            raise ValueError("Radius must be positive")

    @property
    def area(self):
        """Calculate area inside circle"""
        return self.pi() * self.radius**2

    def cylinder_volume(self, height):
        """Calculate volume of cylinder with circle as base"""
        return self.area * height

    @classmethod
    def unit_circle(cls):
        """Factory method creating a circle with radius 1"""
        return cls(1)

    @staticmethod
    def pi():
        """Value of π, could use math.pi instead though"""
        return 3.1415926535


Bu sınıfta:

Circle sınıfı örneğin aşağıdaki gibi kullanılabilir:


Python

In [1]: c = Circle(5)
In [1]: c.radius
Out[1]: 5

In [2]: c.area
Out[2]: 78.5398163375

In [3]: c.radius = 2
In [4]: c.area
Out[4]: 12.566370614

In [5]: c.area = 100
Out[5]: AttributeError

In [5]: c.cylinder_volume(height=4)
Out[5]: 50.265482456

In [6]: c.radius = -1
Out[6]: ValueError: Radius must be positive

In [7]: c = Circle.unit_circle()
In [8]: c.radius
Out[8]: 1

In [9]: c.pi()
Out[9]: 3.1415926535

In [11]: Circle.pi()
Out[11]: 3.1415926535

Önceden @debug ve @timer süslü fonksiyonlarını kullanarak bazı yordamlarını süslediğimiz bir sınıfı tanımlayalım:


from decorators import debug, timer

class TimeWaster:
    @debug
    def __init__(self, max_num):
        self.max_num = max_num

    @timer
    def waste_time(self, num_times):
        for _ in range(num_times):
            sum([i**2 for i in range(self.max_num)])

Bu sınıfı kullanarak, süslü fonksiyonların etkisini görebilirsiniz:


import functools
import time

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()    # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()      # 2
        run_time = end_time - start_time    # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer

def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)           # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           # 4
        return value
    return wrapper_debug

class TimeWaster:
    @debug
    def __init__(self, max_num):
        self.max_num = max_num

    @timer
    def waste_time(self, num_times):
        for _ in range(num_times):
            sum([i**2 for i in range(self.max_num)])
            
            
tw = TimeWaster(1000)
tw.waste_time(999)


Python

Calling __init__(<__main__.TimeWaster object at 0x7f5b9708b4e0>, 1000)
'__init__' returned None
Finished 'waste_time' in 0.2627 secs

Süslü fonksiyonları sınıflarda kullanmanın diğer yolu tüm sınıfı süslemektir. Bu, örneğin, Python 3.7’deki yeni dataclasses modülünde yapılır:

from dataclasses import dataclass

@dataclass
class PlayingCard:
    rank: str
    suit: str

Sözdiziminin anlamı, fonksiyon süslemelerine benzer. Yukarıdaki örnekte, Fonksiyon süslemesini PlayingCard = dataclass (PlayingCard) yazarak yapabilirdiniz.

Sınıf süslü fonksiyonlarının yaygın bir kullanımı, metasınıfların bazı kullanım durumlarına daha basit bir alternatif olmaktır. Her iki durumda da, bir sınıfın tanımını dinamik olarak değiştiriyorsunuz.

Bir sınıf süslüsünün yazılması, bir fonksiyon süslüsünün yazılmasına çok benzer. Tek fark, süslü fonksiyonun bir ifade olarak bir fonksiyon değil bir sınıf alacağıdır. Aslında, yukarıda gördüğünüz tüm süslü fonksiyonlar, sınıf süslüleri olarak çalışacak. Onları bir fonksiyon yerine bir sınıfta kullanırken, bunların etkisi istediğiniz gibi olmayabilir. Aşağıdaki örnekte, @timer süslü fonksiyonu bir sınıfa uygulanır:

from decorators import timer

@timer
class TimeWaster:
    def __init__(self, max_num):
        self.max_num = max_num

    def waste_time(self, num_times):
        for _ in range(num_times):
            sum([i**2 for i in range(self.max_num)])

Bir sınıfın süslenmesi yordamlarını süslemez. Hatırlayın, @timer sadece TimeWaster = timer (TimeWaster) için kısaltılmıştır.

Burada, @timer sadece sınıfı eşleneği için gereken süreyi ölçer:


import functools
import time

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()    # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()      # 2
        run_time = end_time - start_time    # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer

@timer
class TimeWaster:
    def __init__(self, max_num):
        self.max_num = max_num

    def waste_time(self, num_times):
        for _ in range(num_times):
            sum([i**2 for i in range(self.max_num)])         
            
tw = TimeWaster(1000)
tw.waste_time(999)


Python

Finished 'TimeWaster' in 0.0000 secs

Sonra, uygun bir sınıf dekoratörünü tanımlayan, bir sınıfın sadece bir özdeşini sağlayan, @singleton olarak adlandırılan bir örnek göreceksiniz.


4.1 İç İçe Süslü Fonksiyonlar

Birbirlerine üst üste istifleyerek birkaç süslü fonksiyonu bir fonksiyona uygulayabilirsiniz:

from decorators import debug, do_twice

@debug
@do_twice
def greet(name):
    print(f"Hello {name}")

Dekoratörlerin listelendikleri sırayla yürütüldüklerini düşünün. Başka bir deyişle, @debug, greet() veya debug(do_twice(greet()))‘i çağıran @do_twice çağırır:


import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)           # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           # 4
        return value
    return wrapper_debug

@debug
@do_twice
def greet(name):
    print(f"Hello {name}")


Python

Calling greet('Eva')
Hello Eva
Hello Eva
'greet' returned None

@debug ve @do_twice sırasını değiştirirsek farkı gözlemleyin:


import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)           # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           # 4
        return value
    return wrapper_debug

@do_twice
@debug
def greet(name):
    print(f"Hello {name}")

Bu durumda, @do_twice ayrıca @debug‘a da uygulanacaktır:


Python

Calling greet('Eva')
Hello Eva
'greet' returned None
Calling greet('Eva')
Hello Eva
'greet' returned None


4.2 İfadeler ile Süslü Fonksiyonlar

Bazen, ifadeleri süslü fonksiyonlara iletmek yararlıdır. Örneğin, @do_twice bir @repeat(num_times) süslü fonksiyonuna genişletilebilir. Süslenen fonksiyonun yürütme sayısı, daha sonra bir ifade olarak verilebilir.

Bu, böyle bir şey yapmanıza izin verirdi:

@repeat(num_times=4)
def greet(name):
    print(f"Hello {name}")


import functools

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat 

@repeat(num_times=4)
def greet(name):
    print(f"Hello {name}")
    
greet("World")


Python

Hello World
Hello World
Hello World
Hello World

Bunu nasıl başarabileceğinizi düşünün.

Şimdiye kadar, @ ‘den sonra yazılan ad, başka bir fonksiyonla çağrılabilen bir fonksiyon öbeğine atfedildi. Tutarlı olmak için, o zaman bir süslü fonksiyon gibi davranabilen bir fonksiyon öbeğini döndürmek için repeat(num_times=4) gerekir. Neyse ki, zaten fonksiyonların nasıl döndüğünü biliyorsunuz! Genel olarak, aşağıdaki gibi bir şey istersiniz:

def repeat(num_times):
    def decorator_repeat(func):
        ...  # Create and return a wrapper function
    return decorator_repeat

Tipik olarak, süslü fonksiyon bir iç çevreleyici fonksiyonu oluşturur ve döndürür, bu yüzden örnek yazmak size içsel bir fonksiyon içinde bir iç fonksiyon kazandırır.

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat

Biraz dağınık görünüyor, ama şimdiye kadar ifadeleri süslü fonksiyona işleyen bir ek def içinde birçok kez gördüğünüz aynı süslü fonksiyon modelini sadece ekledik. En içteki fonksiyonla başlayalım:

def wrapper_repeat(*args, **kwargs):
    for _ in range(num_times):
        value = func(*args, **kwargs)
    return value

Yine, decorator_repeat(), farklı adlandırılması dışında daha önce yazmış olduğunuz süslü fonksiyonlar gibi görünür. Bunun nedeni, kullanıcının arayacağı, en dıştaki fonksiyon için temel adı —repeat()— ayırmamızdır.

Daha önce gördüğünüz gibi, en dıştaki fonksiyon, süslü fonksiyona bir atıf döndürür:

def repeat(num_times):
    def decorator_repeat(func):
        ...
    return decorator_repeat

repeat() fonksiyonunda gerçekleşen birkaç ince şey vardır:

Ayarlanan her şeyle, sonuçların beklendiği gibi olup olmadığını görelim:

@repeat(num_times=4)
def greet(name):
    print(f"Hello {name}")


Python

Hello World
Hello World
Hello World
Hello World

Sadece hedeflediğimiz sonuç.


4.3 Her ikisi de Lütfen, Ama Asla Zarar Vermeyin

Biraz dikkatli olursanız, hem ifadeli hem de ifadesiz olarak kullanılabilecek süslü fonksiyonlar da tanımlayabilirsiniz. Büyük ihtimalle buna ihtiyacınız yok, ancak esnekliğe sahip olmak güzel.

Bir önceki bölümde gördüğünüz gibi, bir süslü fonksiyon ifadeleri kullandığınızda, ilave bir dış fonksiyon eklemeniz gerekir. Meydan okuma, süslü fonksiyonların argümanlarla veya argümanlar olmadan çağrılıp çağrılmadığını anlamak içindir.

Süslü fonksiyonların ifadelerle veya ifadeler olmadan çağrılıp çağrılmadığını, kodunuzun çözmesi için meydan okumadır.

Süslü fonksiyon ifadeler olmadan çağrıldığında, fonksiyon, süslü fonksiyona yalnızca doğrudan iletildiğinden beri, fonksiyon isteğe bağlı bir ifade olmalıdır. Süslü fonksiyon ifadelerinin keyword ile belirtilmesi gerektiği anlamına gelir. Bunu, aşağıdaki tüm parametrelerin yalnızca keyword olduğu anlamına gelen özel * sözdizimi ile uygulayabilirsiniz:

def name(_func=None, *, kw1=val1, kw2=val2, ...):  # 1
    def decorator_name(func):
        ...  # Create and return a wrapper function.

    if _func is None:
        return decorator_name                      # 2
    else:
        return decorator_name(_func)               # 3

Süslü fonksiyonların ifadeler ile çağrılıp çağrılmadığını belirterek burada, _func ifadesi bir işaretçi olarak davranır:

  1. Eğer name ifadesiz çağrıldıysa, süslenmiş fonksiyon _func olarak aktarılacaktır. İfadeler ile çağrıldıysa, o zaman _func None olacaktır ve bazı keyword ifadeleri varsayılan değerlerinden değişime uğramış olabilir. İfade listesindeki *, arda kalan ifadelerin konumsal ifadeler olarak çağrılamayacağı anlamına gelir.

  2. Bu durumda, süslü fonksiyonlar, ifadeler ile çağrıldı. Bir fonksiyonu okuyabilen ve döndüren bir süslü fonksiyonu döndür.

  3. Bu durumda, süslü fonksiyon, ifadeler olmadan çağrıldı. Süslü fonksiyonu hemen fonksiyona uygulayın.


4.3.1 Positional ifade ve keyword ifadesi

Fonksiyonu çağırırken bir fonksiyona (veya yordama) geçirilen bir değer. İki tür ifade var:


complex(real=3, imag=5)
complex(**{'real': 3, 'imag': 5})



complex(3, 5)
complex(*(3, 5))


İfadeler, bir fonksiyon gövdesindeki adlandırılmış yerel değişkenlere atanır. Sözdizimsel olarak, herhangi bir bildirim bir ifadeyi temsil etmek için kullanılabilir; değerlendirilen değer yerel değişkene atanır.

Eşit işaretin anlamı, tanımda mı yoksa çağrıda mı olduğuna bağlı olarak değişir. Tanımda, ifadeyi isteğe bağlı olarak işaretler ve varsayılan bir değere ayarlar. Çağrıda, istediğiniz ifadeleri ve değerleri belirtmenize izin verir.

Bir anahtar kelime ifadesi, varsayılan bir değere sahip bir konumsal ifadedir. Varsayılan değere sahip olmayan tüm ifadeleri belirtmelisiniz. Diğer bir deyişle, anahtar kelime ifadeleri yalnızca ‘isteğe bağlı’ dır, çünkü özellikle sağlanmadıkları takdirde varsayılan değerlerine ayarlanırlar.


Önceki bölümdeki @repeat süslü fonksiyonunda bu şablonu kullanarak aşağıdakileri yazabilirsiniz:

def repeat(_func=None, *, num_times=2):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat

    if _func is None:
        return decorator_repeat
    else:
        return decorator_repeat(_func)

Bunu orijinal @repeat ile karşılaştırın. _func parametresi ve sonunda if-else eklenmiş tek değişikliklerdir.

Bu örnekler, @repeat‘un artık ifadelerle birlikte veya bağımsız olarak kullanılabileceğini gösterir:


@repeat
def say_whee():
    print("Whee!")

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")


import functools

def repeat(_func=None, *, num_times=2):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat

    if _func is None:
        return decorator_repeat
    else:
        return decorator_repeat(_func)   
        
@repeat
def say_whee():
    print("Whee!")

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")
 
say_whee()
greet("Penny")

num_times varsayılan değerinin 2 olduğunu hatırlayın:

Python

Whee!
Whee!
Hello Penny
Hello Penny
Hello Penny


4.4 Durumsal Süslü Fonksiyonlar

Bazen, durumu takip edebilen bir süslü fonksiyon olması yararlı olur. Basit bir örnek olarak, bir fonksiyonun çağrılma sayısını sayan bir süslü fonksiyon oluşturacağız.



Bir sonraki bölümde, durumu korumak için sınıfları nasıl kullanacağınızı göreceksiniz. Ancak basit durumlarda, fonksiyon özniteliklerini kullanarak da uzaklaşabilirsiniz:


import functools

def count_calls(func):
    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)
    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

@count_calls
def say_whee():
    print("Whee!")

Durum - fonksiyona yapılan çağrı sayısı - çevreleyici fonksiyondaki .num_calls fonksiyon özniteliğinde saklanır. İşte kullanmanın etkisi:


import functools

def count_calls(func):
    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)
    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

@count_calls
def say_whee():
    print("Whee!")
     
say_whee()
say_whee()
say_whee()

print(say_whee.num_calls)


Python

Call 1 of 'say_whee'
Whee!
Call 2 of 'say_whee'
Whee!
Call 3 of 'say_whee'
Whee!

3


4.5 Süslü Fonksiyonlar gibi Sınıflar

Durumu sürdürmenin tipik yolu, sınıfları kullanmaktır. Bu bölümde, bir süslü fonksiyon gibi bir sınıfı kullanarak bir önceki bölümden @count_calls örneğinin nasıl yeniden yazılacağını göreceksiniz.

func = my_decorator(func) demenin daha kolay bir yolu olan süslü fonksiyon sözdizimini @my_decorator hatırlayın. Bu nedenle, eğer my_decorator bir sınıfsa, onun .__init__ () yordamında bir ifade olarak func alması gerekir. Dahası, sınıfın, süslü fonksiyonun içinde durabilmesi için, istenebilmesi gerekir.

Bir sınıfın istenebilir olması için, özel .__call__() yordamını uygularsınız:


class Counter:
    def __init__(self, start=0):
        self.count = start

    def __call__(self):
        self.count += 1
        print(f"Current count is {self.count}")

Bir sınıf özdeşini çağırmaya çalıştığınızda, __call__() yordamı her seferinde yürütülür:


class Counter:
    def __init__(self, start=0):
        self.count = start

    def __call__(self):
        self.count += 1
        print(f"Current count is {self.count}")
        
counter = Counter()
counter()

counter()
 
print(f"counter.count is {counter.count}")


Python

Current count is 1
Current count is 2
counter.count is 2

Bu nedenle, bir süslü fonksiyon sınıfının tipik bir uygulaması, .__init__() ve .__call__() uygulanması gerekir:


import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__!r}")
        return self.func(*args, **kwargs)

@CountCalls
def say_whee():
    print("Whee!")
    
    
say_whee()
say_whee()

print(f"say_whee.num_calls {say_whee.num_calls}")


.__init__() yordamı, fonksiyona bir atfı kaydetmeli ve diğer gerekli başlatma işlemlerini yapmalıdır. Süslü fonksiyonun yerine .__call__() yordamı çağrılacak. Aslında daha önceki örneğimizde ‘'’wrapper()’’’ fonksiyonuyla aynı şeyi yapar. @functools.wraps yerine functools. update_wrapper() fonksiyonunu kullanmanız gerektiğini unutmayın.

@CountCalls süslü fonksiyonu önceki bölümdeki ile aynı çalışır:

Python

Call 1 of 'say_whee'
Whee!
Call 2 of 'say_whee'
Whee!
say_whee.num_calls 2


5 Daha Gerçek Dünya Örnekleri

Her türlü süslü fonksiyonu nasıl oluşturacağımızı anlayarak, şu ana kadar çok yol kat ettik. Yeni bir bilgi birikimimizi, gerçek dünyada gerçekten faydalı olabilecek birkaç örnek oluşturarak bir araya getirelim.


5.1 Slowing Down Code

Daha önce belirtildiği gibi, daha önce de belirtildiği gibi, Önceki @slow_down uygulamamız her zaman bir saniye uyur. Artık, süslü fonksiyonlar için parametrelerin nasıl ekleneceğini biliyorsunuz. Bu nedenle, ne kadar süre uyuduğunu kontrol eden isteğe bağlı bir rate ifadesi kullanarak @slow_down‘u yeniden yazalım:


import functools
import time

def slow_down(_func=None, *, rate=1):
    """Sleep given amount of seconds before calling the function"""
    def decorator_slow_down(func):
        @functools.wraps(func)
        def wrapper_slow_down(*args, **kwargs):
            time.sleep(rate)
            return func(*args, **kwargs)
        return wrapper_slow_down

    if _func is None:
        return decorator_slow_down
    else:
        return decorator_slow_down(_func)
        
        
@slow_down(rate=2)
def countdown(from_number):
    if from_number < 1:
        print("Liftoff!")
    else:
        print(from_number)
        countdown(from_number - 1)
               
countdown(3)

@slow_down‘u ifadeli ve ifadesiz olarak kullanılabilir hale getirmek için, bölümünde tanıtılan katmanı kullanıyoruz. Aynı özyineli countdown() fonksiyonu, daha önce olduğu gibi her sayım arasında iki saniye uyur:

Daha önce olduğu gibi, süslü fonksiyonun etkisini görmek için örneği kendiniz uygulamanız gerekir:

Python

3
2
1
Liftoff!


5.2 Singletons Oluşturma

Singleton, tek bir özdeşe sahip bir sınıftır. Python’da, None, True ve False da dahil olmak üzere sıkça kullandığınız birkaç singleton vardır. Gerçekte None, is anahtar sözcüğünü kullanarak None‘yu karşılaştırmanıza olanak veren bir singleton‘dur


if _func is None:
    return decorator_name
else:
    return decorator_name(_func)

is, yalnızca tamamen aynı özdeş öbekler için True‘yu döndürür.

Aşağıdaki @singleton süslü fonksiyonu, sınıfın ilk özdeşini bir öznitelik olarak saklayarak bir sınıfı bir singletona dönüştürür.

Daha sonra saklanan özdeşi basitçe döndüren bir özdeş oluşturmaya çalışır:


import functools

def singleton(cls):
    """Make a class a Singleton class (only one instance)"""
    @functools.wraps(cls)
    def wrapper_singleton(*args, **kwargs):
        if not wrapper_singleton.instance:
            wrapper_singleton.instance = cls(*args, **kwargs)
        return wrapper_singleton.instance
    wrapper_singleton.instance = None
    return wrapper_singleton

@singleton
class TheOne:
    pass
  
first_one = TheOne()
another_one = TheOne()

id(first_one)
id(another_one)

first_one is another_one


print(f"id.first_one {id(first_one)}")

print(f"id.another_one {id(another_one)}")

print(f"first_one is another_one {first_one is another_one}")

Gördüğünüz gibi, bu sınıf donatıcı, fonksiyon donatıcılarımızla aynı şablonu takip eder. Tek fark, func yerine cls kullanmanın, bir sınıf donatıcı olması gerektiğine işaret etmek için parametre adı olarak kullanılmasıdır.


Python

id.first_one 140378732397792
id.another_one 140378732397792
first_one is another_one True

first_one’un gerçekten de bir another_one ile aynı özdeş olduğu açıkça görünüyor.



5.3 Dönüş Değerlerini Önbelleğe Alma

Süslü fonksiyonlar, önbelleğe alma ve hafızalama için güzel bir mekanizma sağlayabilir. Örnek olarak, Fibonacci dizisinin yinelemeli bir tanımına bakalım:


import functools

def count_calls(func):
    """Count the number of calls made to the decorated function"""

    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)

    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

@count_calls
def fibonacci(num):
    print(f"Call {fibonacci.num_calls}")
    if num < 2:
        return num
    return fibonacci(num - 1) + fibonacci(num - 2)

Uygulama basitken, çalışma zamanı performansı korkunç:


Python

In [1]: fibonacci(10)
Out[3]: 55

In [4]: fibonacci.num_calls
Out[4]: 177

Onuncu Fibonacci sayısını hesaplamak için, gerçekten sadece önceki Fibonacci sayılarını hesaplamanız gerekir, ancak bu uygulama bir şekilde 177 hesaplamaya ihtiyaç duyar. Daha da kötüye gidiyor: Fibonacci(20) için 22068 hesaplaması ve 30’uncu sayı için yaklaşık 2.7 milyon hesap. Bunun nedeni, kodun zaten bilinen Fibonacci sayılarını yeniden hesaplamasıdır.

Genel çözüm, bir for döngü ve bir arama tablosu kullanarak Fibonacci sayılarını uygulamaktır. Hesapların basit bir şekilde önbelleğe alınması da iş görecek:


import functools

def count_calls(func):
    """Count the number of calls made to the decorated function"""

    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)

    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

def cache(func):
    """Keep a cache of previous function calls"""
    @functools.wraps(func)
    def wrapper_cache(*args, **kwargs):
        cache_key = args + tuple(kwargs.items())
        if cache_key not in wrapper_cache.cache:
            wrapper_cache.cache[cache_key] = func(*args, **kwargs)
        return wrapper_cache.cache[cache_key]
    wrapper_cache.cache = dict()
    return wrapper_cache

@cache
@count_calls
def fibonacci(num):
    if num < 2:
        return num
    return fibonacci(num - 1) + fibonacci(num - 2)

Önbellek bir arama tablosu olarak çalışır, bu yüzden şimdi fibonacci() sadece gerekli hesaplamaları bir kez yapar:


Python

In [1]: fibonacci(10)
Call 1 of 'fibonacci'
Call 2 of 'fibonacci'
Call 3 of 'fibonacci'
Call 4 of 'fibonacci'
Call 5 of 'fibonacci'
Call 6 of 'fibonacci'
Call 7 of 'fibonacci'
Call 8 of 'fibonacci'
Call 9 of 'fibonacci'
Call 10 of 'fibonacci'
Call 11 of 'fibonacci'
Out[1]: 55

In [2]: fibonacci(8)
Out[2]: 21

In [15]: fibonacci(5)
Out[15]: 5

In [16]: fibonacci(8)
Out[16]: 21

Son çağrıda fibonacci(8), sekizinci Fibonacci numarası zaten fibonacci(10) için hesaplanmış olduğundan, yeni hesaplamalara gerek yoktu.

Standart kitaplıkta, en az kullanılan (LRU) cache, @functools.lru_cache olarak kullanılabilir.

Bu süslü fonksiyon, yukarıda gördüğünüzden daha fazla özelliğe sahiptir. Kendi önbellek süslü fonksiyonunuzu yazmak yerine @functools.lru_cache kullanmalısınız:


import functools

def count_calls(func):
    """Count the number of calls made to the decorated function"""

    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)

    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls
    
@functools.lru_cache(maxsize=4)
def fibonacci(num):
    print(f"Calculating fibonacci({num})")
    if num < 2:
        return num
    return fibonacci(num - 1) + fibonacci(num - 2)

maxsize parametresi, son aramaların kaç kere önbelleğe alındığını belirtir. Varsayılan değer 128’dir, ancak tüm fonksiyon çağrılarını önbelleğe almak için maxsize = None değerini belirleyebilirsiniz. Ancak, birçok büyük nesneyi önbelleğe aldığınızda bunun bellek sorunlarına neden olabileceğini unutmayın.

Önbelleğin nasıl çalıştığını görmek için .cache_info() yöntemini kullanabilir ve gerekirse bunları ayarlayabilirsiniz. Örneğimizde, önbellekten kaldırılan öğelerin etkisini görmek için yapay olarak küçük bir boyut belirledik:


Python

In [1]: fibonacci(10)
Calculating fibonacci(10)
Calculating fibonacci(9)
Calculating fibonacci(8)
Calculating fibonacci(7)
Calculating fibonacci(6)
Calculating fibonacci(5)
Calculating fibonacci(4)
Calculating fibonacci(3)
Calculating fibonacci(2)
Calculating fibonacci(1)
Calculating fibonacci(0)
Out[1]: 55

In [2]: fibonacci(8)
Out[2]: 21

In [3]: fibonacci(5)
Calculating fibonacci(5)
Calculating fibonacci(4)
Calculating fibonacci(3)
Calculating fibonacci(2)
Calculating fibonacci(1)
Calculating fibonacci(0)
Out[3]: 5

In [4]: fibonacci(8)
Calculating fibonacci(8)
Calculating fibonacci(7)
Calculating fibonacci(6)
Out[4]: 21

In [5]: fibonacci(5)
Out[5]: 5

In [6]: fibonacci.cache_info()
Out[6]: CacheInfo(hits=17, misses=20, maxsize=4, currsize=4)


5.4 Birimler Hakkında Bilgi Ekleme

Aşağıdaki örnek, daha önce, Eklenti Kaydetme örneğine benzemektedir, çünkü bu, süslü fonksiyonun davranışını gerçekten değiştirmez. Bunun yerine, yalnızca bir fonksiyon öznitelliği olarak unit ekler:


def set_unit(unit):
    """Register a unit on a function"""
    def decorator_set_unit(func):
        func.unit = unit
        return func
    return decorator_set_unit

Aşağıdaki örnek yarıçapı ve santimetre cinsinden yüksekliği temelinde bir silindirin hacmini hesaplar:


import math

@set_unit("cm^3")
def volume(radius, height):
    return math.pi * radius**2 * height

Bu .unit fonksiyon özniteliğine daha sonra gerektiğinde erişilebilir:

import math

def set_unit(unit):
    """Register a unit on a function"""
    def decorator_set_unit(func):
        func.unit = unit
        return func
    return decorator_set_unit

@set_unit("cm^3")
def volume(radius, height):
    return math.pi * radius**2 * height


Python

In [3]: volume(3, 5)
Out[3]: 141.3716694115407

In [4]: volume.unit
Out[4]: 'cm^3'

Fonksiyon ek açıklamalarını kullanarak benzer bir şeye ulaşabileceğinizi unutmayın:


import math

def volume(radius, height) -> "cm^3":
    return math.pi * radius**2 * height

Ancak, ek açıklamalar, tip ipuçları için kullanıldığı için statik tip kontrolü ile ek açıklama gibi birimleri birleştirmek zor olurdu

Birimler, birimler arasında dönüşebilen bir kütüphaneye bağlandığında daha da güçlü ve eğlenceli hale gelir. Böyle bir kütüphane de pint. pint yüklü olduğunda (pip install pint), örneğin hacmi; kübik inç veya galona dönüştürebilirsiniz:

Doğrudan bir pint Niceliği döndürmek için süslü fonksiyonu de değiştirebilirsiniz. Böyle bir Nicelik, bir değeri birim ile çarparak yapılır. pint de, birimler bir UnitRegistry‘e bakılmalıdır. Kayıt defteri, ad alanının karışıklığını önlemek için bir fonksiyon özniteliği olarak saklanır:


import pint

def use_unit(unit):
    """Have a function return a Quantity with given unit"""
    use_unit.ureg = pint.UnitRegistry()
    def decorator_use_unit(func):
        @functools.wraps(func)
        def wrapper_use_unit(*args, **kwargs):
            value = func(*args, **kwargs)
            return value * use_unit.ureg(unit)
        return wrapper_use_unit
    return decorator_use_unit

@use_unit("meters per second")
def average_speed(distance, duration):
    return distance / duration

@use_unit süslü fonksiyonuyla, dönüştürme birimleri pratik olarak zahmetsizdir:


5.5 JSON Doğrulama

Son kullanım örneğine bakalım. Aşağıdaki Flask route rota fonksiyonuna hızlıca bakın:


import pint

@app.route("/grade", methods=["POST"])
def update_grade():
    json_data = request.get_json()
    if "student_id" not in json_data:
        abort(400)
    # Update database
    return "success!"

Burada, student_id anahtarının request isteğin bir parçası olduğundan emin oluyoruz. Bu doğrulama çalışmasına rağmen, gerçekten fonksiyonun kendisine ait değildir. Ayrıca, belki de aynı doğrulamayı kullanan başka routes rotalarda vardır. Yani, hadi DRY’yi koruyalım ve bir süslü fonksiyon ile gereksiz bir mantığı soyutlayalım. Aşağıdaki @validate_json süslü fonksiyon, işi yapacak:


from flask import Flask, request, abort
import functools
app = Flask(__name__)

def validate_json(*expected_args):                  # 1
    def decorator_validate_json(func):
        @functools.wraps(func)
        def wrapper_validate_json(*args, **kwargs):
            json_object = request.get_json()
            for expected_arg in expected_args:      # 2
                if expected_arg not in json_object:
                    abort(400)
            return func(*args, **kwargs)
        return wrapper_validate_json
    return decorator_validate_json

Yukarıdaki kodda, süslü fonksiyon, ifade olarak bir değişken uzunluk listesi alır; böylece, her biri JSON verilerini doğrulamak için kullanılan bir anahtarı temsil eden, gerektiği kadar dize ifadesini iletebiliriz:

  1. JSON’da bulunması gereken anahtarların listesi, süslü fonksiyona ifadeler olarak verilir.
  2. Çevreleyen fonksiyon, her beklenen anahtarın JSON verilerinde olduğunu doğrular.

Daha sonra, route rota işleyicisi, JSON verilerinin geçerli olduğunu güvenli bir şekilde varsaydığı için, gerçek işine (notları güncelleme) odaklanabilir:


@app.route("/grade", methods=["POST"])
@validate_json("student_id")
def update_grade():
    json_data = request.get_json()
    # Update database.
    return "success!"


6 SONUÇ

Bu tam bir yolculuk oldu! Özellikle diğer fonksiyonlar içinde nasıl tanımlanabileceklerine ve diğer herhangi bir Python öbeği gibi nasıl geçirilebileceklerine; fonksiyonlara biraz daha yakından bakarak bu eğiticiye başladık. Sonra süslü fonksiyonları ve bunları nasıl yazacağınızı öğrendiniz:

Öğreticinin ikinci bölümünde daha gelişmiş süslü fonksiyonlar gördünüz ve nasıl yapılacağını öğrendiniz:

Bir süslü fonksiyon tanımlamak için, genellikle bir çevreleyici fonksiyon döndüren bir fonksiyon tanımladığını gördünüz. Çevreleyici fonksiyon, süslü fonksiyona ifadeleri iletmek için *args ve **kwargs kullanır. Süslü fonksiyonunuzunda ifadeler almasını istiyorsanız, çevreleyici fonksiyonu başka bir fonksiyonun içine yerleştirmeniz gerekir. Bu durumda, genellikle üç dönüş ifadesiyle sonuçlandırırsınız.



Son Değişim: 15 Eylül 2018

Paylaş:



En Yeni İçerikler

İlgili İçerikler