Python OOP #3 – Classmethod ve Staticmethod

Nesne yerine sınıfa bağlı olan metodlara “class method” yani sınıf metodu denir. Parametreleri sınıflardan oluşur. Programımıza bir classmethod ekleyelim:

@classmethod
def set_raise_amount(cls, amount):
    cls.raise_amount = amount

Fark ettiyseniz metodu tanımlarken @classmethod etiketi kullandım. Bu kullanımı daha iyi anlamak için decoratorlarla ilgili olan yazımı inceleyebilirsiniz. Metodumu çağırırken normal bir fonksiyon çağırır gibi çağırıyorum.

Employee.set_raise_amount(1.05)

print(Employee.raise_amount)
print(emp1.raise_amount)

Çıktı iki tane 1.05 şeklinde olur çünkü artık sınıflarla çalışıyoruz. Bu nedenle sınıf değişkeninde yaptığım bir değişiklik nesneleri de etkileyecektir. Daha önceki yazımda bundan bahsetmiştim.

Fonksiyonun tanımını incelersem bizim alışık olduğumuz self yerine cls parametresine sahip olduğunu görürüm. Bu argüman olarak sınıf aldığını belirtmek için kullanılır. cls yerine başka bir şey de yazabilirdim ancak geleneksel kullanım bu şekilde.

Fonksiyonu çağırdığımda sınıf adı otomatik olarak cls parametresine verilir. İkinci parametre olan amount değişkeni sınıfa ait raise_amount değişkenine atanır.

Sınıf metodları çoklu nesne oluşturmak için de kullanılabilir. Bu örnekte string olarak verilen işçilere ait özellikler ile tek tek örnek oluşturmak yerine bir classmethod yardımıyla bu işlemi tek seferde yapıyoruz.

@classmethod
def from_string(cls, emp_str):
    fname, lname, pay = emp_str.split("-")
    return cls(fname, lname, pay)

emp3 = Employee.from_string("John-Doe-7000")

Eğer bu metodu kullanmasaydım şöyle bir tanımlama yapmam gerekecekti ve her bir string için ayrı ayrı örnekleme yapacaktım.

emp_str1 = "John-Doe-7000"
emp_str2 = "Steve-Smith-6000"
emp_str3 = "Jane-Doe-9000"

first, last, pay = emp_str1.split("-")

new_emp1 = Employee(first, last, pay)

Normal metodlar örnekleri ilk argüman olarak alır (self), sınıf metodları sınıfları ilk argüman olarak alır (cls), static metodlar hiçbirini ilk argüman olarak almaz. Yani oluşturduğunuz metod ne sınıf ne de nesne kullanmıyor sadece aldığı argümanlar ile ilgileniyorsa bu durumda static metodları kullanmalısınız.

Sınıf ve örneklerle hiçbir ilgisi olmayan is_weekday() metodu oluşturacağım:

@staticmethod
def is_workday(day):
    if day.weekday() == 5 or day.weekday() == 6:
        return False
    return True

Bu metod çalışmak için herhangi bir örneğe veya sınıfa ihtiyaç duymuyor. Sadece argüman olarak aldığı tarih değerinin çalışma günü olup olmadığını kontrol ediyor ve ona göre True veya False döndürüyor. Kodumuzun tamamı şu şekilde:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Employee():

    number_of_emps = 0
    raise_amount = 1.04

    def __init__(self, fname, lname, pay):
        self.fname = fname
        self.lname = lname
        self.pay = pay
        self.mail = fname + "." + lname + "@company.com"

        Employee.number_of_emps += 1

    def fullname(self):
        return self.fname + " " + self.lname

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amount)

    @classmethod
    def set_raise_amount(cls, amount):
        cls.raise_amount = amount

    @classmethod
    def from_string(cls, emp_str):
        fname, lname, pay = emp_str.split("-")
        return cls(fname, lname, pay)

    @staticmethod
    def is_workday(day):
        if day.weekday() == 5 or day.weekday() == 6:
            return False
        return True

emp1 = Employee("Bob", "Hawkes", 5000)
emp2 = Employee("Alice", "Banas", 3500)

Employee.set_raise_amount(1.05)

print(Employee.raise_amount)
print(emp1.raise_amount)

emp3 = Employee.from_string("John-Doe-7000")
print(emp3.mail)

import datetime
date = datetime.date(2017, 6, 30)

print(Employee.is_workday(date))

Serinin diğer yazıları:
1 – Sınıflar ve Örnekleme
2 – Sınıf Değişkenleri

Reklamlar

First-Class Function, Closure ve Decorator

Bu yazıyı hazırlarken Corey Schafer‘ın video derslerinden yararlandım.

Bir fonksiyonu değişkene atarsak ne olur? First class function (kaba bir çeviri olacak ama) -birinci sınıf fonksiyonlar- terimi bu işe yarıyor. Yani fonksiyonları değişkenlere atayıp değişkeni fonksiyon olarak kullanabiliyoruz. Şu örneği inceleyelim:

def square(num):
    print (num * num)

x = square
x(5)

Burada kendisine argüman olarak verilen sayının karesini ekrana basan bir fonksiyonum var. Bu fonksiyonu x değişkenine atıyorum. Ancak atama yaparken dikkat ettiyseniz parantez kullanmadım. Çünkü parantez kullanırsam atama işlemi yerine fonksiyonu koşmaya çalışır. Ben burada fonksiyonu çalıştırmadan bir nevi fonksiyonu değişkene kopyaladım.

Artık x değişkenini fonksiyon çağırır gibi kullanabilirim. Kodu çalıştırdığınızda ekrana 25 yazdırdığını görebilirsiniz.

Temelde closure ve decorator kavramları da buradan çıkıyor. Bunların hepsi birbiriyle ilişkili yapılar. Closure (çev. kapatma!) iç içe fonksiyonlarda içteki fonksiyonun dıştaki fonksiyon değişkenlerine erişimini sağlayan bir özellik. Burada da birinci sınıf fonksiyonları kullanacağız. İşte örnek:

def outer_func():
    message = "Penguen"
    
    def inner_func():
        print(message)

    return inner_func()

outer_func()

Kodu çalıştırdığımda neler oluyor bakalım. outer_func() ile dıştaki fonksiyonu çağırıyorum. Bu fonksiyonun içinde message adında bir değişken ve inner_func fonksiyonu yer alıyor. Geriye ise inner_func fonksiyonunu döndürüyor. inner_func fonksiyonunun içeriğine bakarsak message değişkenini ekrana yazdırdığını görürüz. Ancak aslında message değişkeni dıştaki fonksiyona ait.

Gerçekten de kodu çalıştırdığımda ekrana Penguen yazdığını görüyorum. Burada eğer return inner_func() satırını kullanmasaydım programım çalışmazdı. Closure yapısı bu açıdan bize büyük avantajlar sağlayabiliyor. Bir de şuna bakın:

def outer_func(msg):
    message = msg

    def inner_func():
        print(message)

    return inner_func

my_msg = outer_func("Penguen")
my_msg()

Bu örnekte de first-class yapısını kullandım. Sonuç aynı.

Decorator (Türkçe karşılığını bulamadım), argüman olarak fonksiyon alan ve aldığı fonksiyonu çalıştırıp geriye fonksiyon döndüren yapıdır. Biraz karışık oldu. Örnek kodla göstermeliyim.

def decorator_func(original_func):
    def wrapper_func():
        return original_func()
    return wrapper_func

def display():
    print("display function run")

my_display = decorator_func(display)
my_display()

display fonksiyonunu decorator_func fonksiyonuna argüman olarak verdim. wrapper_func fonksiyonu içinde display fonksiyonumu çalıştırdım ve geriye döndürdüm. Son olarak da wrapper_func fonksiyonumu geri döndürerek my_display değişkenine atadım. my_display değişkenini fonksiyon gibi çağırarak ekrana display function run yazısı yazdırdım. Bu kullanımı yazının başında göstermiştim.

Ancak bu kullanım şekli çok tercih edilmez. Bunun yerine decorator fonksiyonlar için “@” işareti kullanılır. Yani

def decorator_func(original_func):
    def wrapper_func():
        print("wrapper executed")
        return original_func()
    return wrapper_func

@decorator_func
def display():
    print("display function run")

my_display = decorator_func(display)
my_display()

Burada ek olarak wrapper_func fonksiyonuna bir işlev daha ekledim. Bunu @decorator_func tanımlamasınının farkını anlamak için test edeceğiz. Bu tanımlamayı yaptıktan sonra artık display fonksiyonunu kendi başına çağırabilirim. Yani şöyle bir kullanım da aynı sonucu verecektir:

def decorator_func(original_func):
    def wrapper_func():
        print("wrapper executed")
        return original_func()
    return wrapper_func

@decorator_func
def display():
    print("display function run")

display()

Burada çıktı

wrapper executed
display function run

şeklinde olur. Görüldüğü üzere @decorator_func tanımlamasını çıkarırsam herhangi bir ilişkilendirme olmaycağından ekrana sadece display function run yazılacaktır. Yani display fonksiyonu sıradan bir şekilde tek başına koşulur.

Decorator yapısı fonksiyonlar yerine sınıflar için de kullanılabilir.

class decorator_class(object):
    def __init__(self, original_func):
        self.original_func = original_func

    def __call__(self, *args, **kwargs):
        print("__call__ method executed")
        return self.original_func()

@decorator_class
def display():
    print("display function run")

display()

Farklı olarak burada __call__ metodunu kullandım. Bu metod önceki örnekte yer alan wrapper_func fonksiyonu gibi düşünülebilir. İsimler ve ufak değişikliklerle beraber bildiğimiz sınıf yapısıyla aynı.

Akılda kalan son soru işaretini cevaplayıp örnek bir kod vererek yazımı sonlandıracağım. Biz bunu nerede kullanacağız? Genelde loglama işlemleri, stream gerektiren uygulamalar, fonksiyon zamanlaması, erişim kontrolü gibi konularda decorator yapısı kullanılır. İşte örnek kod:

def my_logger(orig_func):
    import logging
    logging.basicConfig(filename = "{}.log".format(orig_func.__name__), level = logging.INFO)

    def wrapper(*args, **kwargs):
        logging.info("Ran with args: {}, and kwargs: {}".format(args, kwargs))
        return orig_func(*args, **kwargs)

    return wrapper

@my_logger
def display_info(name, age):
    print("display_info ran with arguments ({},{})".format(name, age))

display_info("John", 25)

Basit bir loglama işlemi yapan bu kodu çalıştırdığımda display_info.log adında bir dosya oluşturacak. Ve içine parametre olarak verdiğim argümanları kaydedecek. display_info() fonksiyonuna verdiğim argümanları değiştirip tekrar çalıştırdığımda yeni argümanları log dosyasına ekleyecektir. Log dosyası çıktısı şu şekilde olur:

INFO:root:Ran with args: ('John', 25), and kwargs: {}
INFO:root:Ran with args: ('Hank', 30), and kwargs: {}

Eksik veya hatalı bir bilgi varsa üstteki sosyal paylaşım linklerinden bana ulaşıp bildirim yapabilirsiniz.

Python OOP #2 – Sınıf Değişkenleri

apply_raise() adında yeni bir fonksiyon tanımlayalım. Bu fonksiyon işçilerin maaşlarını artıracak. Artırma miktarını raise_amount değişkeninde saklayacağım.

class Employee():

    number_of_emps = 0
    raise_amount = 1.04

    def __init__(self, fname, lname, pay):
        self.fname = fname
        self.lname = lname
        self.pay = pay
        self.mail = fname + "." + lname + "@company.com"

        Employee.number_of_emps += 1

    def fullname(self):
        return self.fname + " " + self.lname

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amount)

emp1 = Employee("Bob", "Hawkes", 5000)
emp2 = Employee("Alice", "Banas", 3500)

Eğer

print(emp1.pay)
emp1.apply_raise()
print(emp1.pay)

dersem çıktı 5000 ve 5200 şeklinde olacaktır. Burada tanımladığımız fonksiyon yardımıyla pay değişkenini değiştirdik. apply_raise() fonksiyonu tanımlamasını şu şekilde de yapabilirdik:

def apply_raise(self):
        self.pay = int(self.pay * Employee.raise_amount)

Burada raise_amount değişkenine erişmek için sınıf adını kullandık. Çünkü sınıf değişkenlerine sınıf veya nesne ile erişilebilir. Her iki kullanımda doğrudur.

Peki

Employee.raise_amount=1.05

print(emp1.raise_amount)
print(Employee.raise_amount)

yazdığımda çıktı ne olurdu? Burada açıkça görüldüğü gibi değişkeni sınıfla kullandığım zaman değişkenin yeni değeri o sınıftan türeyen tüm nesneleri etkileyecektir. Yani çıktı iki tane 1.05 olur. Ancak

emp1.raise_amount=1.05

print(emp1.raise_amount)
print(Employee.raise_amount)

yazdığımda bu çağırım sadece emp1 nesnesini etkileyecektir. Yani çıktı 1.05 ve 1.04 olur.

Sınıfımıza bir de number_of_emps değişkeni ekledim. Başlangıç fonksiyonunu her çağırdığımda bu değer bir artırılacak böylece toplam işçi sayısını saklamız olacağız. Bunu test etmek için şöyle bir kod parçacığı yazılabilir:

print(Employee.number_of_emps)

emp1 = Employee("Bob", "Hawkes", 5000)
emp2 = Employee("Alice", "Banas", 3500)

print(Employee.number_of_emps)

Çıktıda ne elde ettiniz?

Python OOP #1 – Sınıflar ve Örnekleme

Bu seriyi hazırlarken Corey Schafer‘ın video derslerinden yararlandım.

Python’da nesne tabanlı programlama diğer diller ile neredeyse aynı. C++ bilen biri Python ile sınıf-nesne ilişkili kodlar yazmakta hiç zorlanmayacaktır. Tüm kavramlar burada da geçerli. Her zamanki gibi öncelikle sınıfla başlıyoruz. Sınıf, aynı temel özelliklere sahip nesneleri ifade eden bir kategori gibi düşünülebilir. Örneğin bu ve muhtemel bundan sonraki yazılarımda kullanacağım örnek kod üzerinden gidecek olursam, Employee() yani “İşçi” bir sınıftır. Ama bu çok genel bir tanımlama. Birçok kişi işçi sınıfına dahil olabilir. Bob ve Alice birer işçidir. Ancak cinsiyet, yaş, isim, boy gibi bir çok farklı özelliklere sahiptirler. Ama sonuçta ikisi de işçi sınıfına ait. Bu nedenle örneğimizde Bob ve Alice birer nesne oluyor.

class Employee():
    def __init__(self, fname, lname, pay):
        self.fname = fname
        self.lname = lname
        self.pay = pay
        self.mail = fname + "." + lname + "@company.com"

    def fullname(self):
        return self.fname + " " + self.lname

emp1 = Employee("Bob", "Hawkes", 5000)
emp2 = Employee("Alice", "Banas", 3500)

print(emp1.mail)
print(Employee.fullname(emp1))

Kod temelde iki bölümden oluşuyor. class Employee(): kısmında işçi sınıfını tanımlıyoruz. Ama bu sadece bir tanımlama. Yani henüz bu sınıfı kullanmış değiliz. Oluşturduğumuz sınıfı kullanmak için bu sınıfın özelliklerini taşıyan nesneler oluşturacağım ki buna da Pythonca’da “örnekleme” deniyor.
emp1 = Employee("Bob", "Hawkes", 5000) satırında emp1 adında ismi Bob Hawkes olan ve 5000 maaş alan bir işçi nesnesi üretiyoruz.

Görüldüğü üzere örnekleme işlemi fonksiyon kullanımına benziyor. Nesnenin özelliklerini sınıfa argüman olarak veriyoruz. Bu örnekleme işlemini daha zor yapabilirdim. Nasıl mı?

class Employee():
    pass

emp1 = Employee()

emp1.fname = "Bob"
emp1.lname = "Hawkes"
emp1.pay = 5000
emp1.mail = "Bob.Hawkes@company.com"

Diğerinden farklı olarak burada __init__() fonksiyonunu kullanmadım. Sonuçta nesnenin özelliklerini tek tek elimle girmek zorunda kaldım. Bu şekilde aynı sınıfa ait yüzlerce nesne olduğunu düşünün. Kod yazmak oldukça zor olurdu.

İlk örnekte kullandığım __init__ fonksiyonu bu işe yarıyor. Adını initialization yani “başlangıç” kelimesinden alan bu fonksiyon nesnelerin başlangıç değerleri almasını sağlıyor. C++ bilenler constructor dediğimde ne demek istediğimi anlayacaklardır.

Göze çarpan Self kelimesi başlangıç değeri ataması yaparken argüman olarak verdiğimiz nesneyi ifade ediyor. Eğer bunu kullanmazsam Python neye değer vereceğini bilemez.

Örnekte bir de fullname() fonksiyonu var ki bunu ben tanımladım. İşlevi nesnenin tam adını geri döndürüyor olması. Fonksiyonu kullanırken önce sınıfın adını sonra fonksiyon adını araya”.” koyarak kullandım. Bu Employee sınıfından fullname fonksiyonunu çağır anlamına geliyor.

Sınıfa ait fonksiyon kullanırken argüman olarak direk nesneyi verdim. Fonksiyon tanımlaması incelenirse nesnenin, Self yerine kullanıldığı görülebilir. Bu Self mantığını anlamak için güzel bir kullanım şekli ancak genelde sınıfa ait fonksiyonlar çağırılırken şu kullanım tercih edilir.

print(emp1.fullname())

Bu daha kısa bir kullanım ancak yeni başlayanlar için biraz kafa karıştırıcı olabilir. Burada değişkenlerde olduğu gibi nesneye ait bir fonksiyonu çağırıyoruz.Unutulmamalıdır ki bir sınıftan üretilen nesne, sınıfa ait her şeyi -değişkenler, fonksiyonlar vs.- alır.

Bir sonraki yazıda sınıf değişkenlerinden bahsedeceğim.