Skip to content

Python Protokol Kavramı

Sıklıkla duyduğumuz ve bildiğimiz üzere python dinamik tipli bir programlama dilidir, dinamik tipli dillerde bir değişkenin veya nesnenin türünün atanması ve kontrolü çoğunlukla runtimeda yapılır. Statik tipli dillerde ise bu işlem derleme anında yapılır ve tür ataması gibi hatalar derleme aşamasında kontrol edilir, aynı zamanda statik tipli dillerde genellikle sınıf, fonksiyon veya değişkenlere tür bildirimleri eklemek genellikle zorunlu olduğundan bir dizi daha sıkı kurallara uyulması gerekmektedir. Diğer taraftan dinamik tipli dillerde nesne ve değişkenlerin türünün atanması runtimeda gerçekleştiğinden bu türler farklı türlere dönüştürülebilir dolayısıyla bu durum esneklik sağlamaktadır.

Go ve python ile bir örnek üzerinden bu durumu görelim.

package main

import "fmt"

func main() {
	fmt.Println(sum(2, 3))
	// fmt.Println(sum("kadir ", "karagoz"))
}

func sum(a, b int) int {
	return a + b
}

----------------------------------------------------------------
Output:
5

7. satırdaki commenti kaldırdığımızda ise çıktı.

./prog.go:7:18: cannot use "kadir" (untyped string constant) as int value in argument to sum
./prog.go:7:24: cannot use "karagoz" (untyped string constant) as int value in argument to sum

Go build failed.

şeklinde olacaktır, dikkat ettiyseniz üst satırda doğru bir çıktı veren 2 ile 3 sayılarının toplamınıda yapamadı çünkü sum() fonksiyonu integer tipli bir değer beklerken string tipinde bir değer vermeye çalıştık, buda derleme anında bir hata oluşmasına ve programın çalışmamasına sebep oldu. Aynı örneği python için yapalım.

def sum(a, b):
    return a + b


print(sum(2, 3))
print(sum("kadir ", "karagoz"))

----------------------------------------------------------------
Output:
5
kadir karagoz

Görüldüğü üzere python için türün integer veya string olması herhangi bir hata oluşturmadı, tabiki bu esnekliğin getirdiği dezavantajlarda bulunmaktadır. Genellikle statik tipli dillerde türleri belirtirken sıkı kurallara uyulması gerektiğinden hata yapma şansı daha az olacaktır, gözden kaçan önemsiz bir tür hatası yapılsa bile bu programın derlenmesine engel olacağından sorunu görüp çözümü kolayca uygulanabilecektir tabiki bu durumunda dezavantajı çoğunlukla dinamik tipli bir dile göre geliştirme daha yavaş olacaktır. Dinamik tipli bir dilin en önemli dezavantajlarından birisi ise doğaları gereği statik tipli benzer özellikleri sağlayan bir dile göre daha yavaş olacaklardır ek olarak esnek olmak ve statik dillerde zorunlu olan kuralların bulunmaması büyük projelerde kodun anlaşılmasını ve bakımını zorlaştıracaktır.

Dinamik ve statik tipli dillerin temel farklarından bahsettik ve pythonun dinamik tipli bir dil olduğunu anladık, sıkça duyulan diğer bir kavram olan duck typing konusundan bahsedecek olursak,

If it walks like a duck and it quacks like a duck, then it must be a duck.

“Ördek gibi yürüyorsa ve ötüyorsa ördektir.” olarak çevirebiliriz bu cümle anlatılmak istenen ana felsefe objelerin türlerine değil özelleliklerine ve davranışlarına odaklanılması gerektiğidir, yani python programcısı gözüyle bakacak olursak, plastik ördek ve yeşil başlı gövel ördek temelde birer ördektir çünkü her ikiside hem ötebilir hemde hareket edebilir.(plastik ördeğin tekerlekleri olduğunu ve bu sayede hareket edebildiğini hayal edin)

Tamda bu noktada python gibi güçlü esnek bir dil ile çalışırken programcılar olarak belirli kurallara koyma ve uygulama ihtiyacı duyarız. Bu sayede hem projede çalışan herkes ortak bir dili konuşma ve ortak kod alışkanları edinerek yazma konusunda hemfikir olurken, gelecekteki geliştirme ve bakım maliyetleride otomatik olarak azalmış olur.

Pythonda daha önceki yazılarımda da sıkça kullandığım “abc” (Abstract Base Classes) modülünü farketmişsinizdir, bu modüle soyut temel sınıfları tanımlamak ve kullanmak için genellikle başvururuz. Soyut temel sınıflar, diğer sınıfların belirli yöntemleri veya davranışları uygulamalarını zorunlu kılar. Bu sayede de Python’da tür güvenliğini artırmak ve belirli davranışların sağlandığını kontrol etmek için kullanılır. Yin bir örnek üzerinden gidecek olursak.

from abc import ABC, abstractmethod


class IAnimal(ABC):
    @abstractmethod
    def eat(self):
        pass

    @abstractmethod
    def walk(self):
        pass


class Duck(IAnimal):
    def eat(self):
        print("Duck eats")

    def walk(self):
        print("Duck walks")


class RubberDuck(IAnimal):
    def walk(self):
        print("RubberDuck walks")


def feed_animals(animals: list[IAnimal]):
    for animal in animals:
        animal.eat()


feed_animals([Duck(), RubberDuck()])

----------------------------------------------------------------
Output:
TypeError: Can't instantiate abstract class RubberDuck with abstract method eat

RubberDuck classımızın bir Animal olabilmesi için eat() methodunu uygulaması zorunlu ancak, plastik ördek tabiki yemek yiyemez, aslında burdaki konumuz bu olmasada olması gerken yapı ve mimari bakış açısı, IAnimal base classını IPlasticAnimal ve ILiveAnimal gibi ayrıştırıp üst seviyeli sınıfların özelliklerine göre interfaceleri oluşturmak ve üst sınıfları sadece davranışlarını uygulayacağı interfacelerden inherit olmasıydı. (bkz: Interface Segregation Principle (ISP))

abc modülünden de kısaca bahsettiğimize göre protokollere giriş yapabiliriz. Protokoller, bir sınıfın veya nesnenin belirli davranışları uygulayıp uygulamadığını kontrol etmek ve bunu bir kural dahilinde yapabilmek için kullanılan basit arayüzlerdir, bu sayede de python’un dinamik tür sistemini daha güçlü bir şekilde kullanmanıza olanak tanırlar. abc modülüyle oluşturduğumuz arayüzlerden farklı olarak protokollerin bir sınıftan türetilmesi veya kaydolması gerekmez. (evet burası ilginç ama sesi ördek gibiyse ördektir!) Biraz önceki örneği biraz değiştirerek tekrar yazacak olursak.

# main.py

from typing import Protocol, runtime_checkable

@runtime_checkable
class AnimalProtocol(Protocol):
    def speak(self) -> str:
        pass

    def eat(self) -> None:
        pass


class Duck:
    def speak(self) -> str:
        return "Quack!"

    def eat(self) -> None:
        print("Duck is eating")


class Human:
    def speak(self) -> str:
        return "Hello, world!"

    def eat(self) -> None:
        print("Human is eating")


class RubberDuck:
    def speak(self) -> str:
        return "Squeak!"


duck = Duck()
human = Human()
rubber_duck = RubberDuck()

d: AnimalProtocol = duck
h: AnimalProtocol = human
r: AnimalProtocol = rubber_duck

----------------------------------------------------------------
~> mypy main.py
----------------------------------------------------------------
Output:
main.py:41: error: Incompatible types in assignment (expression has type "RubberDuck", variable has type "AnimalProtocol")  [assignment]
main.py:41: note: "RubberDuck" is missing following "AnimalProtocol" protocol member:
main.py:41: note:     eat
Found 1 error in 1 file (checked 1 source file) 

Sonuç olarak, Python’da protokoller(3.5 ve sonrasında kullanılabilir 3.8 ile tam olarak desteklenmeye başlamıştır.), tür sistemine statik bir bakış açısı getirerek(static duck typing), bir türün belirli bir davranışı uygulayıp uygulamadığını kontrol etmeye yönelik araçlar olarak alet çantamızda yerini alıyor. Dikkat ettiyseniz Protocol sınıfı üzerinde runtime_checkable decoratorü yer alıyor bu decarator sayesinde, runtimeda tip kontrolü isinstance(obj, Protocol) gibi kontroller yapabilmemize olanak tanır.

Ek olarak statik subtyping kontrolü için mypy gibi bir statik tip denetleme aracı kullanmamız gerekmektedir. Ayrıca protokollerle ilgili daha fazla bilgi için aşağıdaki bağlantıyı kullanabilirsiniz.


Published inPython

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *