Python - Etkileşimli Program Kullanmak

Merhaba,

Bildiğiniz gibi tkinter ana penceresi, widgetlerden herhangi birinin mainloop fonksiyonu çağrıldığında görünür hale geliyor. mainloop fonksiyonunu çağırmak bir döngünün oluşmasına neden oluyor. Ve bu döngüden çıkmadan başka bir döngünün çalışmasına geçmek mümkün olmuyor.

Döngüleri eşzamanlı çalıştırmanın birden çok yöntemi var;

Örneğin, eşzamanlı iş parçacıkları oluşturmak için threading kütüphanesini kullanabilirsiniz.

Veya asenkron görevler oluşturmak için asyncio kütüphanesini de kullanabilirsiniz.

Ama eşzamanlı görevler yazabilmek için özellikle bir kütüphane kullanmanıza gerek yok aslında.

Gelin, sizle herhangi bir kütüphane kullanmadan, nasıl tkinter ana penceresini eşzamanlı çalışacak bir şekilde programlarız, buna odaklanmaya çalışalım.

Eşzamanlı programlar yazabilmek için generator (üreteç) ve coroutine (eşyordam) kavramlarını iyi anlamak gerekiyor. Bir generator, yinelendiğinde bir dizi değer üreten bir fonksiyondur ve boyutu görece büyük olan verilerde oldukça da kullanışlıdır çünkü verinin tamamı hafızaya alınmaz, bunun yerine yineleme ile elde edilen değer hafızaya alınır.

Bir coroutine ise istendiği zaman durdurulabilen ve sonra da kaldığı yerden devam ettirilebilen ve eşzamanlı görevlerde kullanılmaya oldukça müsait bir fonksiyon tipidir.

Python’da her coroutine bir generator’dür de aynı zamanda, ama her generator bir coroutine değildir. Coroutine’lerin yield ifadeleri değer kabul ederler, yani send ile argüman alabilirler. Generator’lerde ise, yield edilecek değerler fonksiyonun içinde üretilirler.

Aşağıdaki başlıkta konuyla alakalı bazı yazılar var, bakın isterseniz:

Şimdi burada anlattıklarıma göre eşzamanlı çalışan bir tkinter penceresi oluşturmaya çalışalım. Ama önce bir husustan bahsetmeme izin verin. Normalde, tkinter ana penceresini şöyle görünür hale getiriyoruz:

import tkinter as tk

root = tk.Tk()
root.mainloop()

Bu yönteme alternatif olarak, aşağıdaki gibi bir yöntem de kullanabilirsiniz:

import tkinter as tk

root = tk.Tk()
while True:
    root.update()

Tabi bu ikinci yaklaşımda bazı protokolleri elle tanımlamanız da gerekir. Örneğin programın kapanma protokolü nasıl olacak gibi…

Şimdi, ister mainloop’u kullanın, ister kendi mainloop’unuzu yazın fark etmiyor gerçekten. Her iki durumda da uzun süre aktif olacak birden çok görevi aynı anda çalıştırmak için coroutineler oluşturmamız lazım.

Aşağıdaki örnekte kaç tane coroutine varsa o kadar coroutine eşzamanlı olarak çalıştırılır. Örnekte iki tane coroutine var. Bunlardan ilki tkinter’in ana penceresini güncelliyor, güncelleme işleminden sonra görev sırası diğer coroutine’e geçiyor. Bir coroutine, kendisine send ile gönderilen ve yield edilen değer ile bir görevi tamamladıktan sonra (bu değeri işlemde kullanmak zorunda değil elbette) durur ve sırasını başka bir coroutine’e devreder, sırayı devralan coroutine kendisine send ile gönderilen ve yield edilen değer ile bir görev yapar ve durur, bu kez başka bir coroutine devreye girer…

Aşağıdaki kodları inceleyin lütfen:

import tkinter as tk


# async_mainloop bir coroutinedir.
def async_mainloop(r):
    # Sonsuz bir döngü oluşturalım.
    while True:
        try:
            # Tkinter ana penceresini X düğmesine basarak
            # kapatmaya çalıştığımızda aşağıdaki ifade hata yükseltecek.
            r.winfo_exists()
            # Tkinter ana penceresini görünür hale getirelim.
            r.update()
            # send ile gönderilen None değerini alalım.
            yield
        # Beklediğimiz hatayı yakalayalım.
        except tk.TclError:
            # Döngüden çıkalım.
            break


# async_loop bir coroutinedir.
def async_loop():
    # x isminde bir tane dummy variable tanımladık. 
    x = 0
    # Sonsuz döngümüzü tanımlayalım.
    while True:
        # dummy değişkenimizi değiştirelim.
        x += 1
        # Değeri değişen değişkeni ekrana yazdıralım.
        print(x)
        # send ile gönderilen None değerini alalım.
        yield


# multiloop bir coroutine değil, sıradan bir fonksiyondur.
def multiloop(*coroutines):
    # coroutineleri sırayla ziyaret etmek için
    # x isminde bir değişken tanımlıyoruz.
    x = 0
    # Sonsuz bir döngü oluşturalım.
    while True:
        try:
            # her bir coroutine'e None değerini gönderelim.
            # Bu örnekte gönderdiğimiz değer ile bir işlem
            # yapılmadığı için, değeri None olarak gönderdik.
            # Eğer gönderilecek değer ile bir işimiz olsaydı,
            # None yerine o değeri gönderirdik.
            coroutines[x].send(None)
            # İndisi 1 birim artıralım.
            x += 1
            # Eğer indis, coroutines'in uzunluğuna eşitse;
            if x == len(coroutines):
                # Sıfırıncı indise tekrar dönelim.
                x = 0
        # Tkinter ana penceresini yok ettiğimizde, async_mainloop
        # fonksiyonu duracak ve StopIteration hatası yükseltecek.
        # Bu hatayı yakalayalım.
        except StopIteration:
            # Döngüden çıkalım.
            break


# Eşzamanlı döngülerimizi çalıştıralım.
multiloop(async_mainloop(tk.Tk()), async_loop())

Yukardaki koddaki async_loop fonksiyonundaki döngüyü çalıştıran bir start düğmesi ve durduran bir stop düğmesi tanımlanabilirdi ve bu durumda fonksiyon içinde bir takım değişiklikler yapmak gerekebilirdi.

Mesela şöyle:

import tkinter as tk


# async_mainloop bir coroutinedir.
def async_mainloop(r):
    # Ana döngümüzü tanımlayalım.
    while mainloop:
        # Tkinter ana penceresini görünür hale getirelim.
        r.update()
        # send ile gönderilen None değerini alalım.
        yield


# async_loop bir coroutinedir.
def async_loop():
    # x isminde bir tane dummy variable tanımlayalım.
    x = 0
    # Ana döngümüzü tanımlayalım.
    while mainloop:
        # Start ve Stop düğmelerine duyarlı alt döngümüzü tanımlayalım.
        while subroutine:
            # dummy değişkenimizi değiştirelim.
            x += 1
            # Değeri değişen değişkeni ekrana yazdıralım.
            print(x)
            # send ile gönderilen None değerini alalım.
            yield
        # send ile gönderilen None değerini alalım.
        yield


# multiloop bir coroutine değil, sıradan bir fonksiyondur.
def multiloop(*coroutines):
    # coroutineleri sırayla ziyaret etmek için
    # x isminde bir değişken tanımlıyoruz.
    x = 0
    # Coroutineler arasında geçiş yapabilmek için
    # ana döngümüzü tanımlayalım.
    while mainloop:
        # her bir coroutine'e None değerini gönderelim.
        # Bu örnekte gönderdiğimiz değer ile bir işlem
        # yapılmadığı için, değeri None olarak gönderdik.
        # Eğer gönderilecek değer ile bir işimiz olsaydı,
        # None yerine o değeri gönderirdik.
        coroutines[x].send(None)
        # İndisi 1 birim artıralım.
        x += 1
        # Eğer indis, coroutines'in uzunluğuna eşitse;
        if x == len(coroutines):
            # Sıfırıncı indise tekrar dönelim.
            x = 0


# start bir coroutine değil, sıradan bir fonksiyondur.
def start():
    # Alt döngüyü kontrol edecek global değişkeni dahil edelim.
    global subroutine
    # Döngüyü başlatalım.
    subroutine = True


# stop bir coroutine değil, sıradan bir fonksiyondur.
def stop():
    # Alt döngüyü kontrol edecek global değişkeni dahil edelim.
    global subroutine
    # Döngüyü sonlandıralım.
    subroutine = False


# close bir coroutine değil, sıradan bir fonksiyondur.
def close():
    # Ana döngülerimizi kontrol edecek global değişkeni dahil edelim.
    global mainloop
    # Döngüyü sonlandıralım.
    mainloop = False


# Ana döngülerimizi kontrol edecek global değişkeni tanımlayalım.
mainloop = True
# Alt döngüyü başlatacak ve durduracak değişkeni tanımlayalım.
subroutine = False
# Tkinter ana penceresini tanımlayalım.
root = tk.Tk()
# Ana pencereyi kapatma protokolünü yeniden tanımlayalım.
root.protocol("WM_DELETE_WINDOW", close)
# Buttonlarımızı tanımlayalım.
start_button = tk.Button(root, text="Start", command=start)
start_button.pack()
stop_button = tk.Button(root, text="Stop", command=stop)
stop_button.pack()
# Eşzamanlı döngülerimizi çalıştıralım.
multiloop(async_mainloop(root), async_loop())

Sorununuzu threading veya asyncio ile de çözebilirsiniz elbette, ama bu yaklaşımı da görmenizi ve anlamaya çalışmanızı isterim. Konuyla alakalı sorularınız olursa elimden geldiğince cevaplamaya çalışırım.

Tekrar görüşmek üzere, kendinize iyi bakın.

4 Beğeni