Fonksiyon ve Sınıflarda Bezeyicilerin İşlevi

Merhaba arkadaşlar, bu kez açılan bir başlıkta belirlenen bir konuyu örneklerle anlatmaya çalışmak istiyorum. Evet böyle bir talep kimseden gelmedi ama forum kurallarına da aykırı değil herhalde. Belki, konuyu okuyan arkadaşlara faydası olur diye umuyorum.

Evet konuyu işlemeye geçelim:

# -*- coding: utf-8 -*-

# Fonksiyon ve Class Bezeyicileri
# -------------------------------

# 1. Fonksiyon Bezeyicileri
# -------------------------


# Örnek-1:
# --------
def f1(f):
    def g1():
        return "{}".format(f()).lower()
    return g1  # <-- g1


# Fonksiyonumuzu test edelim:
@f1
def testf1():
    return "TEST"


print(testf1())


# Aynı işlem, bezeyici olmadan aşağıdaki yöntemle de yapılabilir:
def f1_(f):
    def g1():
        return "{}".format(f()).lower()
    return g1()  # <-- g1()


def testf1_():
    return "TEST"


print(f1_(testf1_))


# Örnek-2:
# --------
def f2(f):
    def g2():
        return {
            i: i**2
            for i in range(f())
        }
    return g2


@f2
def testf2():
    return 10


print(testf2())


# Peki bir fonksiyon üzerinde birden fazla bezeyici
# kullanabilir miyiz?
# Aşağıdaki örneği inceleyelim:


# Örnek-3:
# --------
def f3(f):
    def g3():
        return [i for i in f()]
    return g3


def f4(f):
    def g4():
        return f().lower().replace("e", ".")
    return g4


@f3
@f4
def testf3f4():
    return "MERHABA"

print(testf3f4())


# 2. Class Bezeyicileri

# classlarda kullanılan bezeyiciler fonksiyon bezeyicileriyle
# birbirlerine benzerler.
# Öncelikle aşağıdaki __init__() fonksiyonu içindeki
# '__x' değişkeninin sol tarafında iki tane alt çizgi
# olduğuna dikkatinizi çekmek isterim. Bu alt çizginin işlevi, x niteliğinin
# kullanımını kısıtlamaktır. x niteliğine erişmek
# istediğimizde daha farklı yollardan ona erişme imkanı
# sağlamak amacıyla bezeyiciler kullanacağız.


# Örnek-1:
# --------
class Sinif1:
    def __init__(self):
        self.__x = None

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, value):
        """x.setter bezeyicisi __x örnek niteliğini 'value'
        isimli, kullanıcının gireceği bir değerle değiştiriyor"""
        self.__x = value

    @x.deleter
    def x(self):
        """__x niteliğini silinebilir hale getiriyoruz."""
        del self.__x


inst1 = Sinif1()
print(inst1.x)

inst1.x = 20
print(inst1.x)

del inst1.x


# Yukarıdaki sınıf içinde tanımlanan fonksiyonların yaptıkları
# işlemleri daha farklı yollardan da yapabilirdik tabiki.
# Aşağıdaki sinifin örnek fonksiyonlarının yaptığı iş
# yukarıdaki sinifin örnek fonksiyonlarının yaptığı işle
# aynıdır.


# Örnek-2:
# --------
class Sinif2:
    def __init__(self):
        self.__x = None

    @property
    def x(self):
        return self.__x

    def degistir(self, value):
        self.__x = value

    def sil(self):
        del self.__x


inst2 = Sinif2()
print(inst2.x)
inst2.degistir(value=40)
print(inst2.x)
inst2.sil()


# Aşağıdaki sınıfın örnek fonksiyonları da
# diğer iki sınıfın örnek fonksiyonlarının
# yaptığı işlemin aynısını yapar.


# Örnek-3:
# --------
class Sinif3:
    def __init__(self):
        self.__x = None

    def geri_donder(self):
        return self.__x

    def degistir(self, value):
        self.__x = value

    def sil(self):
        del self.__x

    # class içinde kullanılan bezeyicilerin
    # birer fonksiyon olduğunu görüyorsunuz.

    x = property(geri_donder)
    x = x.setter(degistir)
    x = x.deleter(sil)


inst3 = Sinif3()
print(inst3.x)  # inst3.x = inst3.geri_donder()
inst3.degistir(value=60)
print(inst3.geri_donder())
inst3.sil()

# Yukarıdaki tüm classlar aşağıdaki class'ın
# benzeridir. Diğer sınıflarla birlikte aşağıdaki
# sınıf incelendiği taktirde, @property bezeyicisinin
# tam olarak ne işe yaradığının görülebileceğini düşünüyorum.


# Örnek-4:
# --------
class Sinif4:
    def __init__(self):
        self.__x = None

    def geri_donder(self):
        return self.__x

    def degistir(self, value):
        self.__x = value

    def sil(self):
        del self.__x


inst4 = Sinif4()
print(inst4.geri_donder())
inst4.degistir(value=10)
print(inst4.geri_donder())
inst4.sil()
print(inst4.geri_donder())


# Peki class'ımızda bir __init__ fonksiyonu
# tanımlamamış olsaydık, self.__x isimli örnek
# niteliği yerine __x isimli bir sınıf niteliği
# tanımlamış olsaydık?


# Örnek-5:
# --------
class Sinif5:
    __x = None

    def geri_donder(self):
        return self.__x

    def degistir(self, value):
        self.__x = value

    def sil(self):
        del self.__x

# Sizce, bu durumda __x değişkenini tıpkı yukarıdaki
# sınıflarda olduğu gibi tamamen ortadan kaldırma gibi
# bir ihtimal olabilir mi?
# Hemen örnek üzerinde göstermeye çalışalım:


inst5 = Sinif5()
print(inst5.geri_donder())  # None değerini geri döndürdük.

inst5.degistir(value=10)  # __x'in değerini değiştiriyoruz.
print(inst5.geri_donder())  # Bu kez değiştirdiğimiz değeri geri döndürdük.

inst5.sil()  # _x'i silmeyi deneyelim.
print(inst5.geri_donder())  # __x silinmiş mi silinmemiş mi bir bakalım.


# Silme işlemi, __x niteliğinin değiştirdiğimiz özelliğini sildi,
# ancak __x'in kendisini silmedi.
# Halbuki yukarıdaki diğer sınıflarda __x niteliği siliniyordu.
# Bunun sebebi bu sınıfta silinenin __x'in örnek niteliği olmasıdır.
# İyi ama biz __init__ içinde bir örnek niteliği tanımlamadık.
# Biliyorsunuz sınıf niteliğine hem sınıf hem de örnek üzerinden erişilebilir.
# Yukarıdaki sınıfta da bu niteliğe örnek üzerinden erişiyoruz ve onu değiştirirken
# bir tane örnek oluşturuyoruz. Dolayısıyla silme işleminin örnek üzerinde geçerli
# olduğunu ancak sınıf üzerinde geçerli olmadığını söyleyebiliriz.


# Peki biz bir sınıf niteliğini nasıl silebiliriz?
# Hemen aşağıdaki örneğe bakalım.


# Örnek-6:
# --------
class Sinif6:
    __x = None

    @classmethod
    def get(cls):
        return cls.__x

    @classmethod
    def set(cls, value):
        cls.__x = value

    @classmethod
    def sil(cls):
        del cls.__x


cls1 = Sinif6
print(cls1.get())

cls1.set(value=10)
print(cls1.get())

cls1.sil()
print(cls1.get())
6 Beğeni

Fonksiyonlarda bezeyici kullanırken, bezeyicilere argüman da atanabilir:

def f(x):
    def g(func):
        def h():
            return func() * x
        return h
    return g


@f(x=10)
def a():
    return 5


print(a())
# Sonuç: a = 50

Mesela flask modülünü kullandığımızda aşağıdaki gibi bezeyicilerle karşılaşıyoruz:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello, World!'

Yukarıdaki kullanıma benzer ancak elbette farklı işlemler yapan bir sınıfa ait bezeyiciyi şöyle tanımlayabiliriz:

class App:
    @staticmethod
    def route(x):
        def f(func):
            def g():
                return func() + x
            return g
        return f


app = App()


@app.route(x="world")
def hello_world():
    return "hello "


print(hello_world())
# Sonuç: hello world.
3 Beğeni

Yukarıdaki bezeyici, a fonksiyonu argüman almadığında kullanılabilir. Ancak a fonksiyonuna hem argüman vererek hem de bezeyici kullanarak çağırmak için bezeyici şöyle değiştirilebilir.

def f(func):
    def g(*args, **kwargs):
        def h():
            return func(*args, **kwargs)
        return h
    return g


@f
def fonksiyon(a, b, c=3):
    print(a, b, c)

Bu fonksiyonu çağırmak için, her zamanki fonksiyon çağırma şeklini kullanamayız.

fonksiyon(3, 4)  # Bu çağrı bize bir şey döndürmez.

Bu yüzden fonksiyonu şu şekilde çağırmak gerekir.

fonksiyon(3, 4)()

Genelde hangi durumlarda böyle bezeyiciler kullanmamız gerekiyor peki? Genelde sarıcı fonksiyonlara veya bezeyicilere tkinter ile yaptığım uygulamalarda ihtiyaç duyduğumu söyleyebilirim. Bazen, bir fonksiyonun içinde bir for döngüsü kullanarak birçok tkinter widgeti oluştururken, widgetlere for döngüsünden gelen bir elemanı komut olarak veya widget’in bir özelliği olarak atamak istediğimizde, oluşturacağımız widgetlerin hepsine yinelenebilir verinin son elemanı aktarılıyor. Böyle durumlarda sarıcı bir fonksiyon kullanarak sorunu çözebiliyoruz. Veya yukarıda gösterdiğim bezeyiciler de böyle sorunların çözülmesini sağlıyor.

3 Beğeni

Son anlattığım konuyla alakalı bir örnek:

import tkinter as tk

from os import listdir

root = tk.Tk()
filenames = listdir(".")


def yazdir(arg):
    print(arg)


def create_buttons(*args):
    for i in args:
        button = tk.Button(master=root, text=i, command=lambda: yazdir(i))
        button.pack()
        yield button
        
        
buttons = list(create_buttons(*filenames))
root.mainloop()

Yukarıdaki kodların oluşturduğu button isimleri farklı olsa da hepsinin ekrana yazdırdığı isim aynı. Sanırım burada lambda fonksiyonu içindeki yazdir fonksiyonunun arg parametresine i değeri atanmadığı için böyle bir sonuç oluşuyor. Ve hangi tuşa basarsanız basın i değişkeninin en son değeri, yazdir fonksiyonunun parametre değeri olarak atanıyor.

Bunu önlemek için yazdir fonksiyonu şöyle değiştirilebilir:

def wrapper(arg):
    def yazdir():
        print(arg)
    return yazdir

button'un command parametesi de şöyle değiştirilir:

        button = tk.Button(master=root, text=i, command=wrapper(i))

Burada wrapper sarıcı fonksiyonumuz oluyor.

Veya bir üst mesajdaki bezeyiciyi kullanabiliriz:

def f(func):
    def g(*args):
        def h():
            return func(*args)
        return h
    return g

Ve yazdir fonksiyonunu bezeyici ile birlikte tanımlarız:

@f
def yazdir(arg):
    print(arg)

button widgetini de şöyle tanımlarız:

        button = tk.Button(master=root, text=i, command=yazdir(i))
2 Beğeni

Python’da fonksiyonlarda late binding var, çağırıldıklarında değişkenlerin tuttuğu değerleri oluşturuldukları scope’dan çekiyorlar.

1 Beğeni

Aynen, yukarıdaki örnekte i döngü sonunda hangi değeri almışsa o kullanılıyor.

1 Beğeni

Herkese merhaba.

Bu aralar bezeyiciler ile ilgili bir kaç soru sorulduğu için, işe yarar bir örnek ile bu konu hakkında bir tekrar yapalım istiyorum.

Öncelikle örneğimizin neyle ilgili olduğundan bahsedeyim.

Bu örnek, kabaca bir kişiye ait wikipedia sayfasına bağlanarak, ilk paragrafın ilk cümlesini bulur, bu cümlenin bazı istenmeyen karakterlerini siler ve kullanıcıya temiz bir metin sağlar.

Bu programda kullanacağımız kütüphaneleri şöyle indirebilirsiniz:

pip3 install bs4 requests

Önce örneğimizi, bezeyici olmadan nasıl yazabiliriz buna bir bakalım:

import re

from requests import get
from bs4 import BeautifulSoup as BS


# Ünlü bir kişinin wikipedia sayfasına bağlanan, 
# bütün paragrafların içinden hangisi bu ünlü kişinin ismini 
# içeriyorsa o paragrafların metin değerlerini bir listeye
# alan ve listenin ilk elemanını geri döndüren bir fonksiyon yazalım.
# Bu dönen eleman wikipedia sayfasının ilk paragrafı olacaktır.
def get_text(url: str):
    return [
        p.text
        for p in BS(get(url).content, "html.parser").find_all("p") 
        if all(i in p.text for i in url.split("/")[-1].split("_"))
    ][0]
    
    
# Paragrafı elde ettikten sonra onu cümlelere ayırmamız gerekiyor.
# Cümleye ayırma işlemi "spaCy", "nltk" gibi nlp kütüphaneleri 
# ile de her zaman istenen sonucu vermeyebiliyor. Bu örnekte algoritmayı
# ben oluşturacağım. Mükemmel bir algoritma değil. Ama wikipedia için
# işe yarama ihtimali fazla. Çünkü cümleler genelde nokta ile birbirlerinden
# ayrılıyorlar. Daha önce spacy ve nltk ile istenmeyen sonuçlar
# aldığım için cümle ayırma işlemini yapan fonksiyonu kendim yazmak istedim.
# Bir paragrafı cümlelere ayırırken dikkat edilecek
# kriterler şunlar olacaktır:
#   1. Cümle nokta işareti ile bitecek.
#   2. Nokta işaretinden önce bir tane karakter gelecek.
#   3. Nokta işaretinden sonra bir tane boşluk karakteri gelecek.
#   4. Boşluk karakterinden sonra bir tane büyük harf gelecek.
def get_first_sentence(string: str):
    start = 0
    sentences = []
    for i in re.findall(".\.\s[A-Z]", string):
        end = string.index(i) + 2
        sentences += [string[start: end]]
        start = end + 1
    return sentences[0]
    
    
# Paragrafın, cümlelere ayrılabilmesi için, bazı düzenlemeler yapmak
# gerekebilir. Çünkü wikipedia'da yer alan cümlelerde, nokta karakterinden
# önce [1], [2] gibi kaynak belirtmeye yarayan köşeli parantezler içinde
# yer alan sayılar oluyor. Bunları ortadan kaldıran bir fonksiyon yazalım.
def remove_brackets(string: str, brackets: str):
    while brackets[0] in string:
        index = {1: None, 2: None}
        for i, j in enumerate(string):
            if brackets[0] == j:
                index[1] = i
            elif brackets[1] == j:
                index[2] = i
            if all(index.values()):
                string = string.replace(
                    string[index[1]: index[2] + 1], 
                    ""
                )
                break
    return string.replace("  ", " ").strip()

Yukarıdaki kodlarla artık bir deneme yapabiliriz:

url = "https://en.wikipedia.org/wiki/Gerolamo_Cardano"
first_sentence = get_first_sentence(
    remove_brackets(
        string=get_text(url=url),
        brackets="[]"
    )
)
print(first_sentence)

Yukarıdaki kodları çalıştırdığım zaman şöyle bir çıktı almamız gerekiyor:

Gerolamo (also Girolamo or Geronimo) Cardano (Italian: ; French: Jérôme Cardan; Latin: Hieronymus Cardanus; 24 September 1501 – 21 September 1576) was an Italian polymath, whose interests and proficiencies ranged through those of mathematician, physician, biologist, physicist, chemist, astrologer, astronomer, philosopher, writer, and gambler.

Yukarıdaki ifade tek bir cümle. Ama bu parantezlerden ve içerisinde yer alan ifadelerden de kurtulmak istiyorum. Bu yüzden remove_brackets fonksiyonunu, first_sentence için çağırıyorum.

print(remove_brackets(first_sentence, brackets="()"))

Bu kodları çalıştırdığım zaman şöyle bir çıktı alıyorum:

Gerolamo Cardano was an Italian polymath, whose interests and proficiencies ranged through those of mathematician, physician, biologist, physicist, chemist, astrologer, astronomer, philosopher, writer, and gambler.

Evet, gayet düzgün bir çıktı elde etmiş olduk.

Peki, bu örnek için bezeyicileri nasıl kullanırdık buna bakalım:

import re

from requests import get
from bs4 import BeautifulSoup as BS


def remove_brackets(brackets: str):
    def inner(func):
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            while brackets[0] in result:
                index = {1: None, 2: None}
                for i, j in enumerate(result):
                    if brackets[0] == j:
                        index[1] = i
                    elif brackets[1] == j:
                        index[2] = i
                    if all(index.values()):
                        result = result.replace(
                            result[index[1]: index[2] + 1], 
                            ""
                        )
                        break
            return result.replace("  ", " ").strip()
        return wrapper
    return inner
    
    
def get_first_sentence(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        start = 0
        sentences = []
        for i in re.findall(".\.\s[A-Z]", result):
            end = result.index(i) + 2
            sentences += [result[start: end]]
            start = end + 1
        return sentences[0]
    return wrapper


@remove_brackets(brackets="()")
@get_first_sentence
@remove_brackets(brackets="[]")
def get_text(url: str):
    return [
        p.text
        for p in BS(get(url).content, "html.parser").find_all("p") 
        if all(i in p.text for i in url.split("/")[-1].split("_"))
    ][0]
    

first_sentence = get_text("https://en.wikipedia.org/wiki/Gerolamo_Cardano")
print(first_sentence)

Yukarıdaki örnekte mevcut fonksiyonları birer bezeyici olarak kullanabilmek için fonksiyonları biraz değiştirmek dışında hiçbir şey yapmadım. remove_brackets bezeyicisi, farklı argümanlarla iki defa yazıldı. Bezeyicileri yazarken, öncelik sırasına dikkat etmek gerekiyor.

Vakit ayırıp okuduğunuz için teşekkür ederim. Sormak istediğiniz sorularınız varsa çekinmeden sorabilirsiniz, cevaplamaya çalışırım, benim cevaplayamadığım yerde, diğer arkadaşlar cevaplayabilirler.

Herkese sağlıklı günler dilerim.

Edit:
Aşağıdaki fonksiyon

def remove_brackets(string: str, brackets: str):
    previous_index = 0 if brackets[0] == "[" else 1
    while brackets[0] in string:
        start = string.index(brackets[0]) - previous_index
        end = string.index(brackets[1]) + 1
        string = string.replace(string[start: end], "")
    return string

şu şekilde değiştirildi.

def remove_brackets(string: str, brackets: str):
    while brackets[0] in string:
        index = {1: None, 2: None}
        for i, j in enumerate(string):
            if brackets[0] == j:
                index[1] = i
            elif brackets[1] == j:
                index[2] = i
            if all(index.values()):
                string = string.replace(
                    string[index[1]: index[2] + 1], 
                    ""
                )
                break
    return string.replace("  ", " ").strip()
4 Beğeni