C ve Sistem Programcıları Derneği Python Uygulamaları Sınıfta Yapılan Örnekler ve Özet Notlar Eğitmen: Kaan ASLAN Bu notlar Kaan ASLAN tarafından oluşturulmuştur. Kaynak belirtmek koşulu ile her türlü alıntı yapılabilir. (Notları sabit genişlikli font kullanan programlama editörleri ile açınız.) (Editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.) Son Güncelleme: 08/04/2023 - Salı #------------------------------------------------------------------------------------------------------------------------------------ 1. Ders 23/02/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da tarih zaman işlemleri için standart kütüphane içerisindeki time ve datetime modülleri (paketleri) kullanılmaktadır. time modülü aslında C Programlama Dilindeki prototipleri dosyası içerisinde olan çeşitli standart C fonksiyonlarının adeta Python karşılıklarını barındırmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki time fonksiyonu epoch'tan (01/01/1970'ten) geçen saniye sayısına geri döner. Epoch olarak 01/01/1970 00:00 alınmaktadır. (Bu epoch C Programalama Dilinin bir çeşit doğum tarihi gibi düşünülmüştür.) time fonksiyonu float bir değere geri dönmektedir. Yani elde edilen saniye sayısı noktalı bir değer olabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import time result = time.time() print(result) #------------------------------------------------------------------------------------------------------------------------------------ time fonksiyonu ile programın iki noktası arasında geçen zaman saniye cinsinden ölçülebilir. #------------------------------------------------------------------------------------------------------------------------------------ import time t1 = time.time() for _ in range(100000000): pass t2 = time.time() result = t2 - t1 print(result) #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki ctime fonksiyonu epoch'tan (01/01/1970'ten) geçen saniye sayısını parametre olarak alır, onu yazısal biçime dönüştürerek bir yazı biçiminde bize verir. Örneğin: import time t = time.time() s = time.ctime(t) print(s) ctime fonksyonun verdiği yazı belli bir formattadır. Biz o formatı değiştiremeyiz. Format şöyledir: Sun Feb 23 19:39:40 2025 ctime fonksiyonu bilgisayarın saatine bakmaz. Yalnızca verilen saniye sayısı üzerinde ayrıştırmalar yaparak onu parçalara ayırır. ctime her zaman yerel saati (local time) vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import time t = time.time() s = time.ctime(t) print(s) #------------------------------------------------------------------------------------------------------------------------------------ Eğer ctime fonksiyonuna argüman girilmezse o andaki zaman dikkate alınmaktadır. Yani bu durumda fonksiyon önce time.time fonksiyonu ile o andaki zamanı elde edip onu kullanmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import time s = time.ctime() print(s) #------------------------------------------------------------------------------------------------------------------------------------ Şu andaki tarih zamanı tek bir ifade ile aşağıdaki gibi yazdırabiliriz: print(time.ctime()) #------------------------------------------------------------------------------------------------------------------------------------ print(time.ctime()) #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki localtime fonksiyonu 01/01/1970'ten geçen saniye sayısını parametre olarak alır ve struct_time türünden bir sınıf nesnesine geri döner. Bu sınıf nesnesinin tm_xxx biçiminde isimlendirilmiş olan elemanlarından ilgili zaman bilgilerini alabiliriz. struct_time sınıfının önemli elemanları şunlardır: tm_gmtoff tm_hour tm_isdst tm_mday tm_min tm_mon tm_sec tm_wday tm_yday tm_year tm_zone Bu fonksiyon da parametresiz kullanımda o andaki zamana ilişkin bilgiyi vermeketedir. localtime fonksiyonun da bilgisayarın saatine bakmadığına yalnızca ayrıştırma işlemini yaptığına dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import time t = time.time() st = time.localtime() print(f'{st.tm_mday:02d}/{st.tm_mon:02d}/{st.tm_year:04d}-{st.tm_hour:02d}:{st.tm_min:02d}:{st.tm_sec:02d}') #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki perf_counter fonksiyonu daha duyarlıklı zaman ölçmek için bulundurulmuştur. Bu fonksiyon bize float türünden bir saniye sayısı verir. Ancak bu sayının nereden itibaren bir saniye sayısı belirttiği belli değildir. (Örneğin bu değer 01/01/1970'ten geçen saniye sayısı değildir.) Dolayısıyla bu fonksiyon ancak iki kere çağrılıp aradaki farkı hesaplamak için anlamlıdır. #------------------------------------------------------------------------------------------------------------------------------------ import time t1 = time.perf_counter() for _ in range(100_000_000): pass t2 = time.perf_counter() result = t2 - t1 print(result) #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki perf_counter_ns bize geçen zaman bilgisini nano saniye (saniyenin milyarda biri) cinsinden verir. Dolayısıyla bu fonksiyon noktadan sonraki yuvarlama hataları ile ilgili sorun oluşturmaz. #------------------------------------------------------------------------------------------------------------------------------------ import time t1 = time.perf_counter_ns() for _ in range(100_000_000): pass t2 = time.perf_counter_ns() result = (t2 - t1) print(result) result = (t2 - t1) / 1_000_000_000 print(result) #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki sleep fonksiyonu CPU zamanı harcamadan ilgili akışı (thread'i) belirtilen saniye kadar bekletir. #------------------------------------------------------------------------------------------------------------------------------------ import time for i in range(10): print(i, end=' ') time.sleep(0.5) print() #------------------------------------------------------------------------------------------------------------------------------------ sleep gibi bir bekleme fonksiyonunu meşgul döngü (busy loop) oluşturacak biçimde gerçekleştirmeye çalışmayınız. Çünkü time.sleep fonksiyonu thread'ler konusunda da göreceğimiz gibi o anda çalışmakta olan thread'i CPU zamanı harcamayacak biçimde bloke etmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import time def bad_sleep(seconds): t1 = time.perf_counter() while True: t2 = time.perf_counter() if t2 - t1 >= seconds: break for i in range(10): print(f'{i} ', end='') bad_sleep(0.5) #------------------------------------------------------------------------------------------------------------------------------------ time modülündeki diğer fonksiyonlar için "Python Standard Library" dokümanına başvurabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ datetime isimli standart modül faydalı birkaç sınıf barındırmaktadır. datetime modülündeki date isimli sınıf bir tarih bilgisini (zaman değil) tutarak bazı temel işlemleri yapmamıza olanak sağlamaktadır. date sınıfının __init__ metodu bizden yıl, ay, gün bilgisini alarak nesnenin year, month, day özniteliklerinde saklar. datetime.date(year, month, day) Nesne yaratılırken geçersiz tarih girilirse exception oluşur. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date(2022, 10, 21) print(d) print(f'{d.day:02d}/{d.month:02d}/{d.year:04d}') #------------------------------------------------------------------------------------------------------------------------------------ datetime sınıfının static today isimli metodu o andaki tarih bilgisini date nesnesi olarak verir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date(2019, 12, 27) print(d) print(f'{d.day}/{d.month}/{d.year}') #------------------------------------------------------------------------------------------------------------------------------------ O anki tarihi Python'da yazdırmanın pratik bir yolu aşağıdaki gibidir. import datetime print(datetime.date.today()) Tabii bunu yukarıda da belrttiğimiz gibi time.ctime fonksiyonuyla da yapabiliriz: import time print(time.ctime()) #------------------------------------------------------------------------------------------------------------------------------------ import datetime print(datetime.date.today()) #------------------------------------------------------------------------------------------------------------------------------------ date sınıfının weekday isimli metodu bize tarihin haftanın kaçıncı günü olduğu bilgisini verir. Burada 0 = Pazartesidir. Örneğin weekday metodu bize 4 verdiyse bu ilgili günün Cuma olduğu anlamına gelecektir. Biz burada elde ettiğimiz değeri günlerin isimlerinin bulunduğu bir listeye indeks yaparak gün ismini elde edebiliriz. Örneğin: d = datetime.date.today() daytext = ('Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar') print(daytext[d.weekday()]) #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date.today() daytext = ('Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar') print(daytext[d.weekday()]) #------------------------------------------------------------------------------------------------------------------------------------ 2. Ders 01/03/2025 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ date sınıfının isoweekday isimli metodu yine bize nesnenin tuttuğu tarihin haftanın kaçıncı günü olduğu bilgisini verir. Ancak haftanın başlangıç günü Pazar'dır. Yani burada 0 = Pazar'dır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date.today() daytext = ('Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazartesi') print(daytext[d.isoweekday()]) #------------------------------------------------------------------------------------------------------------------------------------ date sınıfının isocalendar isimli metodu bize nesnenin tuttuğu tarihi sırasıyla yıl, yılın haftası, haftanın günü biçiminde bir "isimli demet (named tuple) nesnesi olarak vermektedir. Biz bu bilgilere [] operatörü ile ya dağrudan year, week ve weekday öznitelikleriyle erişebiliriz. Haftanın günü için başlangıç 0 = Pazar biçimindedir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date.today() ic = d.isocalendar() print(ic) print(ic.year, ic.week, ic.weekday) print(ic[0], ic[1], ic[2]) #------------------------------------------------------------------------------------------------------------------------------------ date sınıfının isoformat isimli metodu nesnenin tuttuğu tarihi "yyyy-mm-dd" formatında bir yazı biçiminde verir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date(2020, 10, 5) result = d.isoformat() print(result) #------------------------------------------------------------------------------------------------------------------------------------ date sınıfının iki tarihi karşılaştıran karşılaştırma operatör netotları bulunmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d1 = datetime.date(2020, 10, 5) d2 = datetime.date(2020, 9, 5) if d1 > d2: print('d1 > d2') elif d1 < d2: print('d1 < d2') elif d1 == d2: print('d1 == d2') #------------------------------------------------------------------------------------------------------------------------------------ datetime modülündeki date sınıfının fromisoformat isimli sınıf metodu ISO formatındaki yazısal tarihten date nesnesi oluşturarak bu date nesnesine geri dönmektedir. Örneğin: s = input('yyyy-mm-dd formatında bir tarih giriniz:') d = datetime.date.fromisoformat(s) date sınıfının fromisocalender isimli sınıf metodu ise bizden sırasıyla yıl, yılın haftası ve haftanın gününü (0 = Pazar) parametre olarak alır ve bize ilgili tarihe ilişkin date nesnesini verir. Örneğin: d = datetime.date.fromisocalendar(2025, 6, 3) print(d) d = datetime.date.fromisocalendar(2025, 6, 3) print(d) #------------------------------------------------------------------------------------------------------------------------------------ import datetime s = input('yyyy-mm-dd formatında bir tarih giriniz:') d = datetime.date.fromisoformat(s) print(d) #------------------------------------------------------------------------------------------------------------------------------------ datetime modülündeki time sınıfı zaman bilgisini tutmak ve onun üzerinde işlemler yapmak için kullanılmaktadır. Sınıfın __init__ metodu bizden sırasıyla saat, dakika, saniye ve mikro saniye değerlerini almaktadır. Bu parametreler default 0 değerini almıştır. Sınıfın __str__ ve __repr__ metotları yine zaman bilgisini bize bir yazı biçiminde vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime t = datetime.time(13, 42, 54) print(t) #------------------------------------------------------------------------------------------------------------------------------------ time sınıfının hour, minute, second ve microsecond örnek öznitelikleri nesnenin tuttuğu zamanın saat, dakika, saniye ve mikro saniye bileşenlerini bize verir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime t = datetime.time(13, 42, 6) print(f'{t.hour:02d}:{t.minute:02d}:{t.second:02d}') #------------------------------------------------------------------------------------------------------------------------------------ time sınıfın iki time nesnesini karşılaştıran karşılaştırma operatör metotları vardır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime t1 = datetime.time(13, 42, 6) t2 = datetime.time(9, 43, 6) if t1 > t2: print('t1 > t2') elif t1 < t2: print('t1 > t2') elif t1 == t2: print('t1 == t2') #------------------------------------------------------------------------------------------------------------------------------------ İki time nesnesi toplanıp çıkartılamamaktadır. (Ancak ileride göreceğimiz iki timedelta nesnesi toplanıp çıkartılabilmektedir.) #------------------------------------------------------------------------------------------------------------------------------------ import datetime td = datetime.timedelta(hours=30, minutes=5, seconds=10) print(td.seconds) # 6 * 60 * 60 + 5 * 60 + 10 = 2190 print(td.total_seconds()) # 108310.0 #------------------------------------------------------------------------------------------------------------------------------------ datetime modülünün timedelta isimli sınıfı bir süre yani bir zaman aralığı bilgisini tutmak için kullanılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0) Sınıfın __str__ ve __repr__ metotları zaman aralığını bir yazı olarak verecek biçimde yazılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime td = datetime.timedelta(hours=2, minutes=12, seconds=5) print(td) #------------------------------------------------------------------------------------------------------------------------------------ timedelta sınıfının days örnek özniteliği tutulan süredeki tam gün sayısını, seconds örnek özniteliği gündeki toplam saniye sayısını vermektedir. (Yani seconds örnek özniteliği her zaman gece saat 00:00:00'dan itibaren geçen saniye sayısını verir.) Sınıfın microseconds örnek özniteliği ise zaman aralığının yalnızca mikroseniye bileşenini vermektedir. Sınıfın total_seconds metodu tüm zaman aralığının saniye değerini vermektedir. total_seconds float bir değer geri döndürmektedir ve mikro saniye de değerin içerisinde bulunmaktadır. Ancak sınıfın seconds örnek özniteliği int türdendir. Onun içerisinde mikro saniye değeri yoktur. Sınıfın minutes, hours gibi örnek özniteliklerinin olmaıdğına dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt = datetime.timedelta(days = 2, hours=5, minutes=2, seconds=10, microseconds = 35) print(dt) print(dt.days) # 2 print(dt.seconds) # 18130 print(dt.microseconds) # 35 print(dt.total_seconds()) # 190930.000035 #------------------------------------------------------------------------------------------------------------------------------------ timedelta sınıfında ilgili değerler herhangi biçimde verilebilir. (Örneğin hours değeri 0 ile 24 arasında olmak zorunda değildir.) Bu değerler float olarak da verilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime td = datetime.timedelta(hours=1.5, minutes=12.5, seconds=5) print(td) #------------------------------------------------------------------------------------------------------------------------------------ timedelta nesnesinin seconds özniteliği toplam saniye sayısını değil zaman aralığının son gün günündeki gece saat 00:00:00'dan geçen saniye sayısını vermektedir. Örneğin: td = datetime.timedelta(hours=30, minutes=5, seconds=10) print(td.seconds) # 6 * 60 * 60 + 5 * 60 + 10 = 2190 print(td.total_seconds()) # 108310.0 Başka bir deyişle nesnenin seconds özniteliği td.total_seconds() % 86400 değerini vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime td = datetime.timedelta(hours=30, minutes=5, seconds=10) print(td.seconds) # 6 * 60 * 60 + 5 * 60 + 10 = 2190 print(td.total_seconds()) # 108310.0 #------------------------------------------------------------------------------------------------------------------------------------ İki time nesnesi toplanıp çıkartılamaz ancak iki timedelta nesnesi toplanıp çıkartılabilir. Bu durumda elde edilen değer yine timedelta nesnesi olur. Örneğin: td1 = datetime.timedelta(hours=30, minutes=5, seconds=10) td2 = datetime.timedelta(hours=17, minutes=2, seconds=5) result = td1 - td2 print(result) #------------------------------------------------------------------------------------------------------------------------------------ import datetime td1 = datetime.timedelta(hours=4, minutes=12, seconds=5) td2 = datetime.timedelta(hours=3, minutes=55, seconds=58) result = td1 + td2 print(result) result = td1 - td2 print(result) #------------------------------------------------------------------------------------------------------------------------------------ time nesnesi timedelta nesnesi ile toplanıp çıkartılamaz ancak date nesnesi timedelta nesnesiyle toplanıp çıkartılabilir. Sonuç date sınıfı türünden olur. Tabii bu durumda timedelta nesnesindeki tam günler işleme sokulmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date(2020, 12, 6) td = datetime.timedelta(days=3, hours=25) result = d + td print(result) # 2020-12-10 result = d - td print(result) # 2020-12-02 #------------------------------------------------------------------------------------------------------------------------------------ datetime modülünün datetime isimli sınıfı hem tarih hem de zaman bilgisini bir arada aynı nesnede tutmak için bulundurulmuştur. Sınıfın __init__ metodunun parametrik yapısı şöyledir: datetime.datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0) Yine sınıfın __str__ ve __repr__ metotları datetime nesnesindeki tarih ve zaman değerlerini bize bir yazı biçiminde vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt = datetime.datetime(2019, 12, 5, 12, 7, 34) print(dt) # 2019-12-05 12:07:34 #------------------------------------------------------------------------------------------------------------------------------------ datetime nesnesinin year, month, day, hour, minute, second, microsecond öznitelikleri nesne içerisindeki tarih/zaman bilgisinin bileşenlerini elde etmek için kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt = datetime.datetime(2019, 12, 5, 12, 7, 34) print(f'{dt.day:02d}/{dt.month:02d}/{dt.year:04d} {dt.hour:02d}:{dt.minute:02d}:{dt.second:02d}') #------------------------------------------------------------------------------------------------------------------------------------ Yine datetime sınıfının iki datetime nesnesini karşılaştıran operatör metotları vardır. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt1 = datetime.datetime(2019, 12, 5, 12, 7, 34) dt2 = datetime.datetime(2019, 12, 5, 12, 7, 34) if dt1 > dt2: print('dt1 > dt2') elif dt1 < dt2: print('dt1 < dt2') elif dt1 == dt2: print('dt1 == dt2') #------------------------------------------------------------------------------------------------------------------------------------ İki datetime nesnesi toplanamaz ancak çıkartılabilir. Elde edilen sonuç timedelta türünden olur. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt1 = datetime.datetime(2019, 12, 5, 12, 7, 34) dt2 = datetime.datetime(2018, 6, 4, 12, 6, 45) td = dt1 - dt2 print(td) #------------------------------------------------------------------------------------------------------------------------------------ datetime sınıfının now isimli statik metodu tarih zaman bilgisini datetime nesnesi olarak bize verir. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt = datetime.datetime.now() print(dt) print(datetime.datetime.now()) #------------------------------------------------------------------------------------------------------------------------------------ datetime nesnesi ile timedelta nesnesi toplanıp çıkartılabilir. Elde edilen sonuç datetime nesnesi olur. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt = datetime.datetime.now() result = dt + datetime.timedelta(days=60, hours=3, minutes=27) print(dt) print(result) #------------------------------------------------------------------------------------------------------------------------------------ datetime nesnesi oluşturulurken geçersiz bir tarih ya da zaman bilgisi exception oluşmasına yol açar. #------------------------------------------------------------------------------------------------------------------------------------ import datetime dt = datetime.datetime(2021, 2, 30) result = dt + datetime.timedelta(days=1) print(result) #------------------------------------------------------------------------------------------------------------------------------------ datetime sınıfının combine isimli statik metodu date ve time nesnelerini alıp bir datetime nesnesi vermektedir. Örneğin: d = datetime.date(2020, 12, 23) t = datetime.time(13, 56, 34) dt = datetime.datetime.combine(d, t) #------------------------------------------------------------------------------------------------------------------------------------ import datetime d = datetime.date(2020, 12, 23) t = datetime.time(13, 56, 34) dt = datetime.datetime.combine(d, t) print(dt) # 2020-12-23 13:56:34 #------------------------------------------------------------------------------------------------------------------------------------ datetime sınıfının diğer metotları için "Python Standard Library" dokğmantasyonuna başvurabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ datetime modülü dışında tarih ve zaman işlemleriyle ilgili diğer bir modülde calander isimli modüldür. calendar modülündeki isleap isimli fonksiyon belli bir yılın artık olup olmadığını anlamakta kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ def isleap(year): return year % 4 == 0 and year % 100 != 0 or year % 400 == 0 print('Artık yıl' if isleap(2020) else 'Artık yıl değil') import calendar print('Artık yıl' if calendar.isleap(2020) else 'Artık yıl değil') #------------------------------------------------------------------------------------------------------------------------------------ calendar modülündeki TextCalendar isimli sınıfın prmonth isimli metodu belli bir yılın ayının takvimini ekrana basar. Metodun yıl ve ay dışında boşluk miktarlarına ilişkin iki parametresi daha vardır. #------------------------------------------------------------------------------------------------------------------------------------ import calendar tc = calendar.TextCalendar() tc.prmonth(2021, 9) #------------------------------------------------------------------------------------------------------------------------------------ TextCalendar sınıfının formatmonth isimli metodu prmonth gibi yılın belli bir ayının takvimini çıkartır. Ancak bunu ekrana basmaz da yazı olarak (str nesnesi oalrak) verir. #------------------------------------------------------------------------------------------------------------------------------------ import calendar tc = calendar.TextCalendar() s = tc.formatmonth(2021, 9) print(s) #------------------------------------------------------------------------------------------------------------------------------------ TextCalendar sınfının pryear metodu yılın tüm aylarının takvimini ekrana basar, formatyear metodu ise bunu yazı olarak (str nesnesi olarak) verir. #------------------------------------------------------------------------------------------------------------------------------------ import calendar tc = calendar.TextCalendar() tc.pryear(2021) s = tc.formatyear(2021) print(s) #------------------------------------------------------------------------------------------------------------------------------------ Calendar modülünün içerisindeki sınıfların burada ele almadığımız başka metotları da vardır. Bunları "Python Standard Library" dokümanlarından inceleyebilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Düzenli ifadeler (regular expressions) bir yazı içerisinde belli bir kalıbın bulunması için kullanılan küçük bir dildir. Düzenli ifadeler sayesinde örneğin biz bir yazı içerisindeki gg/aa/yyyy gibi bir kalıba uyan tüm tarihleri elde edebiliriz. Ya da örneğin biz 192.168.2.3 gibi bir formatta bulunan tüm IP adreslerini elde edebiliriz. Pek çok programlama dilinin standart kütüphanesinde düzenli ifadeler için sınıflar bulunmaktadır. Bazı dillerde ise düzenli ifadeler dilin sentaks yapısı içerisinde (built-in biçimde) bulunmaktadır. Yani bu dillerde yorumlayıcılar zaten düzenli ifadeleri sentaktik bir öğe olarak tanımaktadır. Python'da düzenli ifadeler için re isiminde bir modül bulundurulmuştur. Çeşitli kurumlar ve proje grupları tarafından düzenli ifade işlemlerini yapan aşağı seviyeli kütüphaneler oluşturulmuştur. Bunlara "düzenli ifade motorları (regular expression engines)" denilmektedir. Düzenli ifadelerin belli bir standardı yoktur. Ancak pek çok düzenli ifade motoru birbiriyle belli bir düzeyde uyumludur. Düzenli ifadelerin bir dil olduüunu belirtmiştik. Dolayısıyla düzenli ifadelerde kalıpları ifade etmenin bir sentaksı vardır. Python'daki düzenli ifadelere ilişkin gramer "Python Standard Library" dokümanlarında açıklanmıştır: https://docs.python.org/3/library/re.html Düzenli ifadeler ile ilgili denemeler yapmak için https://regex101.com/ sitesinden faydalanabilirsiniz. Eskiden text editörlerin ve kelime işlem programlarının düzenli ifade arama özellikleri yoktu. Zamanla bu özellikler pek çok text editöre ve kelime işlem programına eklendi. Bugün Microsoft Word gibi kelime işlemciler, VisualStudio, VSCode gibi IDE'ler ve editörler düzenli ifadelerin kullanılmasına olanak sağlamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 3. Ders 02/03/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Düzenli ifadelere ilişkin kalıp oluşturma kuralları özet olarak şöyledir. (Örneklerimizde iki tırnak içerisindeki kalıplar bir Python string'i olarak değerlendirilmemelidir. Yani örneğin biz "ka\." biçiminde bir kalıp yazdığımızda buradaki ters bölü gerçekten ters bölü karakteridir. Bilindiği gibi Python'da ters bölü karakterlerinin gerçek ters bölü karakteri olarak ele alınması için string'e yapışık r ya da R harfi getirilmektedir.) - Kalıp oluştururken kullanılan karakterler ikiye ayrılmaktadır: Normal karakterler ve meta karakterler. Meta karakterler özel anlama gelen karakterlerdir. Dolayısıyla düzenli ifadeler büyük ölçüde meta karakterlerin kullanımı ile ilgilidir. - Nokta bir meta karakterdir \n (new line) dışındaki herhangi bir karakter anlamına gelir. Örneğin "al." kalıbı "al" ile başlayan ve üçüncü karakteri herhangi bir karakter olan üç karakteri belirtir. Dolayısıyla "al." kalıbı "ali" ile de uyuşabilir "alm" ile de uyuşabilir, "alr" ile de uyuşabilir. (Burada uyuşmak İnglizce "match" sözcüğünün karşılığı olarak kullanılmaktadır.) - * meta karakteri "solundaki karakterden sıfır tane ya da daha fazla" anlamına gelmektedir. Bu durumda örneğin "ka*" kalıbı "k" ile, "ka" ile, "kaa" ile, "kaaa" ile "kaaaaa" ile uyuşur. Örneğin "k.*" kalıbı başı "k" ile başlayan satır sonuna kadar tüm karakterle uyuşur. Nokta meta karakterinin \n karakterini içermediğine dikkat ediniz. Bu durumda "k.*" kalıbı satır sonunda etkisini kaybedecektir. - + meta karakteri "solundaki karakterden bir ya da daha fazlasını" belirtir. * ile + meta karakterlerini birbirine karıştırmayınız. * solundaki karakterden sıfır tane ya da çok tane ile uyuşurken, + solundaki karakterden bir tane ya da çok taneyle uyuşmaktadır. Örneğin "ka+" kalıbı "k" ile uyuşmaz. Ancak "ka" ile "kaaaa" ile uyuşur. Fakat "ka*" kalıbı "k" ile "ka" ile "kaaaa" ile uyuşur. - ? meta karakteri "solundaki karakterden 0 tane ya da 1 tane" anlamına gelmektedir. Örneğin "ka?" kalıbı "ka" ile uyuşur "k" ile de uyuşur. - Bir meta karakter normal karakter olarak kullanılacaksa ters bölülenmelidir. Örneğin "ka\." gibi bir kalıp "ka." ile uyuşur. "kal" ile "kar" ile uyuşmaz. Tabii ters bölünün kendisi için de \\ kullanılmalıdır. Eğer \ karakterinin sağındaki karakter özel bir ters bölü karekteri değilse ve bir meta karakter değilse ifade geçersizdir. - Köşeli parantezler, "içerisindeki karakterlerin herhangi biri" anlamına gelir. Örneğin "[abc]+" kalıbı "abcaaaabc" ile ya da "aaaabbbbbcccbcbcbca" ile uyşur. "a[bcd]?" kalıbı "a" ile "ab" ile "ac" ile "ad" ile uyuşur. - Köşeli parantez içerisinde '-' karakteri kullanılırsa "solundaki karakterden sağındaki karaktere kadar herhangi biri" anlamına gelir. Örneğin "[a-z]+" kalıbı "ali" ile "veli" ile "selami" ile uyuşur. Örneğin "[a-zA-Z]+" kalıbı sözcükleri bulmak için kullanılabilir. Ancak default durumda Türkçe karakter bulunamayacaktır. Lokal spesifik davranış fonksiyonların flags parametresi ile ayarlanabilmektedir. Normal olarak "-" karakteri bir meta karakter değildir. Ancak köşeli parantezler içerisindeki "-" karakteri meta karakter olarak ele alınmaktadır. (Bir istisna olarak köşeli parantezler içerisinde - karakteri son karakter olarak bulunuyorsa meta karakter olarak ele alınmamaktadır.) Tabii köşeli parantezler meta karakter olduğu için gerçekten köşeli parantezleri arayacaksak ters bölülemek gerekir. Örneğin amacımız köşeli parantezlerin içindekileri köşeli parantezlerle aramak ise "\[.*\]" kalıbını kullanabiliriz. - Köşeli parantezin başında ^ karakteri varsa bu durum "köşeli parantezin içerisinde olmayan herhangi bir karakter anlamına gelir. - Düzenli ifadelerde uyum sağlayan en uzun karakter kümesi elde edilmektedir. Örneğin "abcabxyz" gibi bir yazıda "[abc]+" kalıbı "abc" ve "ab" ile uyuşacaktır. - Küme parantezleri dört biçimde kullanılabilir: {n}, {n,k} ve {n,}, {, n}. {n} kalıbı "solundaki karakterden tam olarak n tane" anlamına gelmektedir. Örneğin "a{3}" "aaa" ile uyuşur. Eğer yazıda "aaaaaaaa" biçiminde a varsa bu durumda bunun ilk 3 tanesi ile uyuşum sağlayacaktır. {n,k} kalıbı "solundaki karakterden n ile k arasında herhangi tane (n ve k dahil)" anlamına gelmektedir. Örneğin "a{3,5}" kalıbı "aaa" ile uyuşur, "aaaa" ile uyuşur, "aaaaa" ile uyuşur. Ancak örneğin "aa" ile uyuşmaz. "aaaaaaaaaa" daki ilk 5 a ile uyuşur. {n,} kalıbı "solundaki karakterden en az n tane" anlamına gelmektedir. {,n} kalıbı ise "solundaki karakterden en fazla n tane" anlamına gelmektedir. Örneğin "a{,5}" kalıbı "en fazla 5 tane a ile uyuşum sağlar. Bu durumda 0 tane a ile de uyuşum sağlanır. Normal olarak küme parantezlerinin içinde boşluk karakterleri bir soruna yol açmamaktadır. Yani örneğin "a{3,5}" kalıbını "a{3, 5}" biçiminde yazsak da bir sorun oluşmaz. Ancak siz küme parantezlerinin içerisinde SPACE karakteri kullanmayınız. - ^ meta karakteri "yalnızca yazının başını" dikkate alır. Örneğin "^a+" kalıbı yazının başındaki a'larla uyuşur. Örneğin "^[0-9]+" kalıbı yazının başındaki sayıları bulmaktadır. - $ meta karakteri yazının sonunu temsil etmektedir. Örneğin "abc$" kalıbı yazının sonundaki "abc" ile uyuşur. Örneğin "[0-9]+$" kalıbı yazının sonundaki sayıları bulur. - \w meta karakterleri "herhangi bir alfabetik, nümerik ya da _ karakteri" anlamına gelmektedir. Default durumda bu kalıp "[a-zA-Z_0-9]" anlammına gelmektedir. Yani Türkçe karakterler kalıba dahil değildir. Ancak pek çok regex kütüphanesinde bir lokal ayarı yapılabilmektedir. Eğer regex ortamı izin veriyorsa bu ayar yapılarak bu kalıba Türkçe karakterler de dahil edilebilir. Tabii Türkçe karakterleri dahil edecek biçimde kalıp manuel olarak [\wşçğüöıŞÇĞÜÖİ] biçiminde de oluşturulabilir. Düzenli ifade motorlarının "UNICODE" seçeneği de bulunabilmektedir. Bu seçenek aktif hale getirildiğinde artık \w meta karakteri UNICCODE tablodaki tüm alfabetik karakterleri kapsar hale gelmektedir. Yani UNICODE ayarı adeta "tüm lokalleri içerir" anlamına gelmektedir. "\w+" kalıbı sözcükleri bulmak için sık sık kullanılan bir kalıptır. \W meta karakterleri ise "herhangi bir alfabetik, nümerik karakter ya da _ karakterinin dışındaki karakter" anlamına gelir. Yani \w meta karakterinin tersini belirtir. - \s meta karakterleri "herhangi bir boşluk karakteri (white space)" anlamına gelmektedir. Benzer biçimde \S meta karakterleri ise "herhangi bir boşluk karakteri dışındaki karakter" anlamına gelir. - \d meta karakterleri "sayısal karakterlerden herhangi birisi" \D kalıbı ise "sayısal karakter olmayan herhangi bir karakter" anlamına gelmektedir. Örneğin "\d+" kalıbı ile sayıları bulabiliriz. O halde örneğin "[\-+]?\d+" kalıbı tamsayıları bulabilir. Nokatalı sayıları da bulabilen kalıp "[\-+]?\d*\.?\d+" bçiminde olabilir. - \b meta karakteri "sözcük başları" anlamına gelmektedir. Örneğin "\ba+" gibi bir kalıp "a" ile başlayan sözcüklerle uyuşur. - Parantezler gruplama için kullanılmaktadır. Örneğin "(abc)+" kalıbı ile "ab"c uyuşur, "abcabc" uyuşur, "abcabcabc" uyuşur. - '|' karakteri "veya" anlamına gelmektedir. Örneğin "ali|veli" kalıbı "ali" ya da "veli" ile uyuşabilmektedir. Örneğin #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tipik bazı kalıplar aşağıda verilmiştir: E-Posta kalıbı: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}" Noktalı sayı kalıbı: [+-]?[0-9]+\.[0-9]+|\.[0-9]+|[0-9]+\. Tamsayı kalıbı: [+-]?[0-9]+ Değişken atom kalıbı: [_a-z-A-Z][_a-zA-Z-0-9]+ day/month/year tarih kalıbı: (0?[1-9]|1[0-2])\/(0?[1-9]|1\d|2\d|3[01])\/(19|20)\d{2} dd/mm/yyy tarih kalıbı: (0[1-9]|1\d|2\d|3[01])\/(0[1-9]|1[0-2])\/(19|20)\d{2} IP adresi kalıbı: ([0-9]{1,3}\.){3}[0-9]{1,3} #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Her türlü ayrıştırma işlemi düzenli ifadelerle yapılamayabilir. Bu tür durumlarda belli noktaya kadar düzenl ifadeleri kullanıp sonra manuel kontrollerle hedefe ulaşılabilir. Örneğin bir programlama dilindeki programı atomlarına ayırmak düzenli ifadeler yeterli olamayabilmektedir. Yani düzenli ifadeler bizim ayrıştırma ve bulma işlemlerimizde tüm isteklerimizi yerine getiremeyebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da düzenli ifadelerle işlemler re modülündeki fonksiyonlar ve sınıflarla yapılmaktadır. re modülündeki fonksiyonlar genel olarak önce kalıp yazısını sonra da asıl yazıyı parametre olarak alırlar. Yine modüldeki fonksiyonların son paramatreleri düzenli ifadeler üzerindeki bazı seçenekleri belirtmektedir. Bu paramtre flags olarak isimlendirilmiştir. Programcının kalıp yazısını oluşturken string'i "r" ya da "R" öneki ile oluşturması "\" karakterlerinin gerçekten ters bölü karakteri olarak ele alınmasını garanti edecektir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki findall isimli fonksiyonu yazıdaki regex kalıbına uyan tüm parçaları bir liste olarak bize verir. findall fonksiyonun parametrik yapısı şöyledir: findall(pattern, string, flags=0) Örneğin: text = '17 asal bir sayıdır. 101 de asaldır. Ancak 115 asal değildir.' pattern = r'\d+' result = re.findall(pattern, text) print(result) Burada görüldüğü gibi findall fonksiyonuna önce kalıp yazısı sonra da asıl yazı argüman olarak geçirilmiştir. flags parametresi default değer aldığı için hiç kullanılmamıştır. findall fonksiyonu parantezli grupları da ayırıp onları birer demet olarak da vermektedir. İzleyen paragraflarda gruplama konusu üzerinde de duracağız. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli 123 selami 628 ayşe fatma 876' pattern = r'\d+' result = re.findall(pattern, text) print(result) # ['123', '628', '876'] numbers = list(map(int, result)) print(numbers) # [123, 628, 876] #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda findall fonksiyonu ile tarihlerin elde edilmesine yönelik bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli 10/12/2009 selami 05/07/1998 ayşe fatma 23/11/2014' pattern = r'\d\d/\d\d/\d\d\d\d' result = re.findall(pattern, text) print(result) # ['10/12/2009', '05/07/1998', '23/11/2014'] #------------------------------------------------------------------------------------------------------------------------------------ str sınıfının split metodu pek çok ayrıştırma işlemi için yetersiz kalmaktadır. Örneğin: text = 'ali, veli, selami, fatma' biçiminde bir yazıdaki isimleri split ile tek hamlede elde edemeyiz. Çünkü split metodu ayıraç olarak tek bir yazı almaktadır. Eğer biz burada text.split(', ') çağrısıyla ayrıştırma yapmak istersek bazı isimlerin başında boşluk karakterleri de bulunur. Burada düzenli ifadeler basit bir biçimde istediğimizi yapmamıza olanak sağlayabilmektedir. Örneğin: text = 'ali, veli,, selami , ayşe , fatma' pattern = '\w+' result = re.findall(pattern, text) #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali, veli,, selami , ayşe , fatma' pattern = '\w+' result = re.findall(pattern, text) print(result) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda findall fonksiyonu ile bir yazıdaki e-posta adreslerinin bulunmasına örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = """bana e-posta atabilrsin. E-posta adresim aslank@csystem.org. Eğer bana ulaşamazsan info@csystem.org'ye de e-posta gönderebilirsin.' """ pattern = r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' result = re.findall(pattern, text) print(result) #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki split fonksiyonu str sınıfının split metodundan çok daha güçlü bir biçimde ayrıştırma yapabilmektedir. Bu fonksiyon Bir regex kalıbını ayıraç olarak kabul ederek yazıyı parçalarına ayırır. split fonksiyonunda verilen kalıp ayıraçların kalıbıdır. Fonksiyon bu ayıraçları diğer yazının parçalarını ayırmak için kullanacaktır. Fonksiyonun parametrik yapısı şöyledir: split(pattern, string, maxsplit=0, flags=0) split fonksiyonu her kalıba uygun ayıracı bulduğunda onun solundaki ve sağındaki yazı parçasını elde eder. Eğer yazının başında ve sonunda ayıraç kalıbı varsa bu ayıraç kalıbının solunda ve sağında bir yazı olmadığı için boş string verilecektir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali, veli, selami,ayşe' pattern = r', *' result = re.split(pattern, text) print(result) # ['ali', 'veli', 'selami', 'ayşe'] #------------------------------------------------------------------------------------------------------------------------------------ 4. Ders 08/03/2025 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ str sınıfının split metodu tek bir ayrıça kabul ettiği için çok basit ayrımaları yapabilmektedir. Örneğin: text = 'ali-veli,selami-ayşe,fatma' Burada isimler bazen '-' ile bazen de ',' ile ayrılmıştır. Bu ayrmayı biz str sınıfının split metoduyla yapamayız. Ancak re modülündeki split ile yapabiliriz: text = 'ali-veli,selami-ayşe,fatma' pattern = r'-|, ' result = re.split(pattern, text) print(result) Burada önemli bir noktaya dikkat ediniz. Bir ayıraç bulunduğunda ondan sonra başka bir ayırç gelirse sani iki ayıraç arasında bir yazı olması gerekiyormuş da bulunamamış gibi bir durum oluşur. Bu da boş string'lerin oluşmasına yol açar. Örneğin: text = 'ali-veli, selami-ayşe, fatma' pattern = r'[-, ]' result = re.split(pattern, text) print(result) Burada kalıp '-', ',' ve ' ' karakterlerinin herhangi birisidir. Dolayısıyla ',' karakter bulunduğunda ondan hemen sonra ' ' karakteri geldiği için arada sanki bir şey yokmu gibi bir durum oluşacaktır. Burada elde edilen liste de şöyle olacaktır: ['ali', 'veli', '', 'selami', 'ayşe', '', 'fatma'] Şimdi aşağıdaki kalıba dikkat ediniz: pattern = r'-|, ' Burada kalıp '-' karakteri ya da ', ' karakteri biçimindedir. Tabi biz aşağıdaki gibi de bir kalığ oluşurabilirdik: pattern = '[\- ,]+' Bu durumda -, boşluk ve virgül yan yana ne kadar çok gelirse gelsin ayıraç olarak ele alınırdı. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte sayılar arasındaki isimler ayrıştırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali21761veli123123selami987987ayşe9989898fatma' pattern = '\d+' result = re.split(pattern, text) print(result) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki split fonksiyonunda yazının başında ya da sonunda ayıraç bulunursa boş string oluşurulmaktadır. Örneğin: text = ' ,,ali,,, veli,, selami ' pattern = "[ ,]+" result = re.split(pattern, text) print(result) Burada yazının başı ayırçlarla başlamıştır. O halde yazının başında bir tane boş string bulunacaktır. Yazının sonu da ayırçlarla bitmiştir o halde yazının sonunda da boş string bulunacaktır. Bu kod parçası çalıştırıldığında ekrana şunlar basılacaktır: ['', 'ali', 'veli', 'selami', ''] #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda re.split fonksiyonun kullanımına başka bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali+-veli-+selami--ayşe' pattern = '[\-+]+' result = re.split(pattern, text) print(result) # ['ali', 'veli', 'selami', 'ayşe'] #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki search fonksiyonu yazı içerisinde bir regex kalıbını arar. Eğer bulursa ilk bulduğu kalıba ilişkin re modülündeki Match türünden bir sınıf nesnesiyle geri döner. Fonksiyonun parametrik yapısı şöyldir: re.search(pattern, string, flags=0) Yine fonksiyonun birinci parametresi aranacak kalıbı, ikinci parametresi aramanın yapılacağı yazıyı belirtmektedir. Fonksiyon başarı durumunda bir Match nesnesine başarısızlık durumunda None değerine geri dönmektedir. Match sınıfının start netodu kalıbın bulunduğu karakterin başlangıç index numarasını end metodu ise bitiş index numarasından bir fazlasını verir. Böylece biz dilimleme yoluyla bulunan kalıbı elde edebiliriz. Örneğin: text = ',,,ali, veli, selami' pattern = r'\w+' m = re.search(pattern, text) Burada aslında yazı içerisindeki "ali" karakterleri uyuşum sağlamaktadır. Ancak search bize "ali" yazısını değil bir Match nesnesi vermektedir. Bu match nesnesinin start metodu kalıbınyazı içerisindeki başlangıç index numarasını end metodu bitiş indeks numarasından bir sonrak indeks numarasını verir. Dolayısıyla biz bu aralığı dilimlemede kullanabiliriz. Örneğin: if m: print(m.start(), m.end()) print(text[m.start():m.end()]) #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'my email addres is aslank@csystem.org but your email address is serce@gmail.com' pattern = r'[a-zA-Z0–9+_.-]+@[a-zA-Z0–9.-]+' m = re.search(pattern, text) if m: result = text[m.start():m.end()] print(result) else: print('cannot find match!..') #------------------------------------------------------------------------------------------------------------------------------------ Bir kalıp parantezler kullanılarak oluşturulmuşsa kalıbın içerisindeki parantezli kısımlara grup denilmektedir. search fonksiyonu yalnızca ana kalıbı değil parantezler içeirsindeki parçalar olan grup'ları da bulabilmektdir. Örneğin şöyle bir kalıp olsun: "(\d+)@(\d+)". 123@456 gibi karakter öbeği bu kalıp ile uyuşmaktadır. İşte biz Match nesnesi ile uyuşan kısmı bir bütün olarak elde edebilceğimiz gibi bunun 123 ve 456'dan oluşan gruplarını da elde edebilmekteyiz. Match sınıfının group metodu regex kalıbındaki grupları bize vermektedir. group metodu grubun numarasını parametre olarak alır. Group numaraları 1'den başlamaktadır. 0'ıncı grup numarası kalıbın tamamını belirtir. group metodu ile Match sınıfının [] operatör metodu aynı işlemi yapmaktadır. Örneğin: text = 'ali 123@567 veli 135@854 selami' pattern = r'(\d+)@(\d+)' m = re.search(pattern, text) Buradaki kalıp sayı@sayı biçimindeki karakterle uyuşur. Ancak bu kalıpta @ karakterinin iki yanı birer grup biçiminde oluşturulmuştur. Bu sayede biz hem uyuşan kalıbın tamamını elde edebiliriz hem de onun parçalarını elde edebiliriz. Buradaki örnekte search ilk uyuşan kalıbı yani 123@567 karakterini bulacaktır. Bize bunu bir Match nesnesi biçiminde verecektir. Bu Match nesnesinin 1'inci grubu 123 yazısından 2'inci grubu 567 yazısından oluşacaktır. İşte biz m[1] ya da m.group(1) ifadeseiyle 1'inci grubu m[2] ya da m.group(2) ifadseiyle de 2'inci grubu elde edebiliriz. m[0] ya da m.group(0) ifadesi ile de biz tüm kalıba erişebiliriz. İster gruplu bir kalıp olsun isterse grupsuz bi rkalıp olsun m[0] her zaman uyuşan kalıbı vermektedir. Match sınıfının string örnek özniteliği bize search fonksiyonun çağrılmasında kullanılan asıl yazıyı da vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli selami 123@789 ayşe fatma' m = r'(\d+)@(\d+)' result = re.search(pattern, text) if result: print(m[0]) print(m[1]) print(m[2]) else: print('kalıp bulunamadı!..') #------------------------------------------------------------------------------------------------------------------------------------ Aslında daha önce görmüş olduğumuz findall metodu da grupla çalışmaktadır. Eğer kalıpta parantezler varsa findall bu grupları bir demet listesi olarak bize vermektedir. Örneğin: text = 'ali veli selami 123@789 ayşe fatma 478@456' pattern = r'(\d+)@(\d+)' result = re.findall(pattern, text) print(result) # [('123', '789'), ('478', '456')] Ancak kalıpta tek bir grup varsa findall bize bunları tek elemanlı demet listesi olarak değil düz bir liste olarak vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli selami 123@789 ayşe fatma 478@456' pattern = r'(\d+)@(\d+)' result = re.findall(pattern, text) if result: print(result) # [('123', '789'), ('478', '456')] else: print('kalıp bulunamadı!..') #------------------------------------------------------------------------------------------------------------------------------------ Gruplama hem bir kalıbın bulunmasına hem de o kalıp içerisindeki bir parçanın elde edilmesine yol açacaktır. Örneğin birisi bize yazı içerisindeki tek tırnak içerisinde bulunan isimleri elde etmemizi istesin. Yazı aşağıdaki gibi olsun: text = "ali veli 'selami' ayşe 'fatma' hasan" Burada bizden tüm isimlerin değil 'selami' ve 'fatma' isimlerinin ulunması istenmiştir. Kalıbı aşağıdaki oluşturmuş olalım: pattern = r"'\w+'" Bu kalıpla biz tek tırnak içerisindeki isimleri tek tırnaklarıyla buluruz. Halbuki bizden tek tırnak içerisindeki isimlerin tırnaksız bir biçimde bulunması istenmiştir. Tabii biz bu isimleri tırnaklı bulduktan sonra bu trınakları atabiliriz. Ancak bu da ek bir çaba gerektirir. İşte bu durumda gruplama pratik çözüm oluşturmaktadır. Örneğin: pattern = r"'(\w+)'" result = re.findall(pattern, text) print(result) # ['selami', 'fatma'] #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Klavyeden (stdin dosyasından) bir sayı okumak isteyelim. Ancak sayı geçersiz girilmişse hatayı belirtip yeniden girilmesini isteyelim. Ta ki doğru girilene kadar. Bunu nasıl yapabiliriz? Aslında biz buu Python kursunda exception mekanizmasından faydalanarak yapmıştık. int fonksiyonu eğer dönüştürmeyi yapamazsa exception fırlatıyordu. Biz de exception'ı ele alarak sayının yeniden girilmesini istiyorduk. Örneğin: while True: try: val = int(input('Bir sayı giriniz:')) break except: print('geçersiz sayı!..') print(val * val) Bu işlemi regex kullanarak da yapabiliriz: while True: text = input('Bir sayı giriniz:') m = re.search(r'^\d+', text) if m: break else: print('invalid number!..') val = int(text[m.start():m.end()]) print(val * val) Aslında regex mekanizması exception mekanizmasından daha hızlıdır. Ancak Python'da bu tür hızlandırmaların çoğunlukla önemi yoktur. Dil genel olarak zaten yavaştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki match fonksiyonu search fonksiyonu gibidir. Yani yine aranan kalıba ilişkin Match nesnesini verir. Ancak kalıbın yazının başında olması gerekir. Yani bu fonksiyon her zaman kalıp sanki yazının başındaymış gibi arama yapar. Aşağıdaki örnekte uyuşum sağlanamayacaktır. text = 'ali veli selami 123@789 ayşe fatma 478@456' pattern = r'(\d+)@(\d+)' m = re.match(pattern, text) print(m) # None Tabii aslında yazının başında aramayı sağlamak için kalıbın başına da ^ meta karakteri getirilebilir. ^ meta karakterinin zaten yazının başından itibaren uyuşama bakacağını daha önce belirtmiştik. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli selami 123@789 ayşe fatma 478@456' pattern = r'(\d+)@(\d+)' result = re.match(pattern, text) if result: print(result[0]) else: print('kalıp bulunamadı!..') #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki fullmatch fonksiyonu yazının tamamının kalıba uygun olmadığını belirlemekte kullanılır. Bu fonksiyon da başarı durumunda Match nesnesine, başarısızlık durumunda None değerine geri dönmektedir. Fonksiyonun parametrik yapısı diğerleriyle aynıdır: re.fullmatch(pattern, string, flags=0) #------------------------------------------------------------------------------------------------------------------------------------ import re text = '10/12/1997' pattern = r'\d\d/\d\d/\d\d\d\d' a = re.fullmatch(pattern, text) if a: print('Uyuşum var') else: print('Uyuşum yok') text = '10/12/1997 ' a = re.fullmatch(pattern, text) if a: print('Kalıba uygun') else: print('Uyuşum yok') #------------------------------------------------------------------------------------------------------------------------------------ Düzenli ifadeler çeşitli girdilerin belirlenen sentaksa uygunluğunun kontrol edilmesi için sıklıkla kullanılmaktadır. Örneğin kullanıcıdan bir e-posta adresi girmesi istenebilir. Kişinin girdiği yazının geçerli bir e-posta adresi olup olmadığı düzenli ifadedelerle sınanabilir. Bu tür durumlarda fullmatch fonksiyonu tercih edilmektedir. Girilen yazıyı fullmatch fonksiyonuna sokmadan önce baştaki ve sondaki boşluk karakterlerini de str sınıfının splitmetoduyla atabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ import re pattern = r'[a-zA-Z0–9+_.-]+@[a-zA-Z0–9.-]+' email = input('Bir e-posta adresi giriniz:').strip() if re.fullmatch(pattern, email): print('işlemler yapılıyor') else: print('geçersiz bir e-posta adresi') #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda bir yazının float bir sayı formatına uygun olup olmadığı test edilmeye çalışılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import re pattern = r'([\+\-]?\d+\.?\d*|\.\d+)([eE]\d+)?' s = input('Yazı giriniz:') m = re.fullmatch(pattern, s) if m: print('float formatına uygun') else: print('float formatına uygun değil') #------------------------------------------------------------------------------------------------------------------------------------ Tabii her türlü sınama (validation) işlemi düzenli ifadelerle yapılamaz. Pek çok sınama işlemi düzenli ifadelerle yapılabiliyorsa da oluşturulan kalıp çok karmaşık olabilmektedir. Bu tür durumlarda programcılar basit bir kalıpla temel sınamayı yapıp sonra daha ayrıntılı sınamayı kodla yapabilirler. Örneğin klavyeden girilen bir tarihin geçerli bir tarih olup olmadığının sınanması tek bir düzenli ifadeyle yapılamaz. Çünkü 20/02/2009 gibi bir tarihin sentaksı düzgündür ama bu tarih 2009 yılı artık olmadığı için geçerli değildir. Bu tür durumlarda temel sentaks düzenli ifadelerle kontrol edilebilir. Diğer ayrıntılı kontroller ise manuel biçimde kodla yapılabilir. Aşağıdaki örnekte bir tarih bilgisinin temel sentaksı düzenli ifadelerle sınanmış, geri kalan sınama da bu amaçla yazılmış başka bir fonksiyonla yapılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import re import datetime def validate_date(day, month, year): try: datetime.date(year, month, day) return True except: return False pattern = r'(\d\d)/([01][0-9])/(\d\d\d\d)' email = input('Lütfen gg/aa/yyyy biçiminde bir tarih giriniz:').strip() if m := re.fullmatch(pattern, email): if validate_date(int(m[1]), int(m[2]), int(m[3])): print('geçerli tarih') else: print('geçersiz bir tarih') else: print('geçersiz bir tarih') #------------------------------------------------------------------------------------------------------------------------------------ 5. Ders 09/03/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ finditer fonksiyonu bir iterator nesnesi verir. Bu iterator nesnesi dolaşıldıkça bulunan kalıplara ilişkin Match nesneleri elde edilmektedir. iterator yoluyla dolaşma kalıba uygun çok sayıda öğenin bulunduğu durumda yer ve zaman bakımından avantaj sağlayabilmektedir. Örneğin çok büyük metnin içerisinde kalıpları buldukça bir işlem yapacak olun. Baştan tüm kalıpların bulunması kullanıyı rahatsız edecek derecede gecikme oluşturabilir. Aynı zamanda tüm kalıpların saklanması da önemli bir alanın tahsis edilmesini gerektirecektir. Kalıpları buldukça onlar üzerinde işlem yapmak bu tür durumlarda daha etkindir. finditer fonksiyonun parametrik yapısı benzerdir: re.finditer(pattern, string, flags=0) Örneğin: text = 'ali -veli-, selami -ayşe- fatma -hayri- sibel -hasan-' pattern = r'-(\w+)-' for m in re.finditer(pattern, text): print(m[1]) #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli selami 123@789 ayşe fatma 7890@112234 süreyya' pattern = r'(\d+)@(\d+)' for m in re.finditer(pattern, text): print(m[0], m[1], m[2]) #------------------------------------------------------------------------------------------------------------------------------------ re modğlğndeki sub fonksiyonu belli bir kalıbın yerine başka bir yazı yerleştirmek için kullanılmaktadır. Bu fonksiyon string sınıfının replace metodunun düzenli ifade alan versiyonu gibi düşünülebilir. Fonksiyonun parametrik yapısı şöyledir: re.sub(pattern, repl, string, count=0, flags=0) Buradaki count parametresi kaç uyuşumun değiştirileceğini belirtmektedir. 0 değeri herpsinin değiştirileceği anlamına gelmektedir. Tabii fonksiyon asıl yazıda bir değişiklik yapmaz. Bize değiştirilmiş yeni bir yazıyı vermektedir. Örneğin: text = 'ali -veli-, selami -ayşe- fatma -hayri- sibel -hasan-' pattern = r'-(\w+)-' result = re.sub(pattern, 'xxx', text) print(result) # ali xxx, selami xxx fatma xxx sibel xxx Burada tireler arasındaki isimler xxx karakterleri ile yer değiştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli 12/11/1990 selami 03/05/2009 ayşe 07/11/1997 fatma' pattern = r'\d\d/\d\d/\d\d\d\d' result = re.sub(pattern, '----------', text) print(result) #------------------------------------------------------------------------------------------------------------------------------------ sub fonksiyonun count parametresi ilk n tane bulunan kalıp için yer değiştirme sağlamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali veli 12/11/1990 selami 03/05/2009 ayşe 07/11/1997 fatma' pattern = r'\d\d/\d\d/\d\d\d\d' result = re.sub(pattern, '----------', text, 2) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi biz bir kalıbı başka bir kalıpla nasıl yer değiştirebiliriz. Örneğin tireler arasındaki isimleri yine tireler arasında fakat ters yüz ederek yer değiştirmek isteeyelim. İşte bu tür işlemlerin yapılabilmesine olanak sağlamak için özel bir kullanım da oluşturulmuştur. Eğer yer değiştirilecek yazı yerine çağrılabilen bir nesne girilirse kalıp bulundukça bulunan kalıbı temsil eden bir Match nesnesi ile verilen fonksiyon çağrılır. Programcı bu gonksiyondan bir yazaıyla geri dönmelidir. Bu yazı değiştirilecek yazı olur. Örneğin: text = 'ali -veli-, selami -ayşe- fatma -hayri- sibel -hasan-' pattern = r'-(\w*)-' result = re.sub(pattern, lambda m: f'-{m[1][::-1]}-', text) Burada değiştirilecek yazı yerine lambda ifadesi girilmiştir. Bu lambda ifadesi aşağıdaki fonksiyonla eşdeğerdir: def lambda_func(m): return f'-{m[1][::-1]}-' #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ali -veli-, selami -ayşe- fatma -hayri- sibel -hasan-' pattern = r'-(\w*)-' result = re.sub(pattern, lambda m: f'-{m[1][::-1]}-', text) print(result) #------------------------------------------------------------------------------------------------------------------------------------ re modülündeki tüm fonksiyonlarda flags isimli bir ekstra son parametre vardır. Bu flags parametresi regex araması için bazı seçenekleri belirlemekte kullanılmaktadır. Örneğin bu flags parametresine re.IGNORECASE girilirse bu durumda büyük harf küçük harf duyarlılığı olmadan karakter değerlendirilir. Örneğin: text = 'ali veli Ahmet, ayşe, sacit, AYLİN' pattern = r'\b(a\w+)' names = re.findall(pattern, text, re.IGNORECASE) print(names) # ['ali', 'Ahmet', 'ayşe', 'AYLİN'] Burada başı "a" ya da "A" ile başlayan isimler bulunmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'bu bir denemedir. ANKARA-06 evet denemedir İStanbul-34 ' pattern = r'[a-z]+-\d\d' result = re.findall(pattern, text, re.IGNORECASE) print(result) # ['ANKARA-06', 'İStanbul-34'] #------------------------------------------------------------------------------------------------------------------------------------ Fonksiyonlardaki flags parametresi | operatörü ile birleştirilebilmektedir. Bu duurmda birden fazla flag etkili olabilmektedir. Örneğin re.IGNORECASE ile re.DOTALL birlikte kullanılabilir. re.DOTALL bayrağı '.' meta karakterinin \n ile de uyuşum sağlayacağı anlamına gelmektedir. Örneğin normal olarak ".+" kalıbı satır sonuna kadarki tüm karakterle uyuşur. Ancak biz bu kalıbı re.DOALL bayrağı ile kullanırsak "." meta karakteri artık \n ile de uyuşacağı için yazının tüm karakterleri elde edilir. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'ANkara12345\n6789İZMİR' pattern = r'ankara.*izmir' m = re.fullmatch(pattern, text, re.IGNORECASE|re.DOTALL) if m: print('uyuşum var') else: print('uyuşum yok') #------------------------------------------------------------------------------------------------------------------------------------ Düzenli ifadelerle işlemler göreli biçimde yavaş işlemlerdir. İşte eğer programcı düzenli ifadelerle daha hızlı işlem yapmak isterse compile fonksiyonunu kullanabilir. Özellikle compile fonksiyonu aynı kalıbın birden fazla yerde kullanılacağı durumlarda bir hız kazancı sağlamaktadır. Örneğin biz programımız içerisinde pek çok yerde bir e-postanın geçerliliğini test etmeye çalışıyor olabiliriz. Bu durumda compile işlemi bir hızlanma sağlayabilmektedir. compile fonksiyonun parametrik yapısı şöyledir: re.compile(pattern, flags=0) compile fonksiyonu bize re modülündeki Pattern isimli bir sınıf türünden bir nesne verir. Şimdiye kadar görmüş olduğumuz tüm regex fonksiyonları aynı zamanda bu sınıfın metotları olarak da yazılmıştır. Dolayısıyla programcının yapacağı şey bu compile fonksiyonundan elde ettiği nesneyi kullanarak daha önce görmüş olduğumuz fonksiyonlarla aynı isimlere sahip sınıfın metotlarını çağırmak olacaktır. Tabii bu metotlarda pattern parametresi girilmez. Zaten compile edilmiş kalıp nesnenin içerisinde bulunmaktadır. Buradaki "compile" teriminin programlama dillerinde kullanılan compile terimi ile bir ilgisi yoktur. compile fonksiyonu düzenli ifade kalıbını daha kolay işleme sokulacak bir formata dönüştürmektedir. Örneğin: text = 'işte benim e-posta adresim aslan@csystem.org biçiminde ama Ali Hocanınki de ali@csystem.org biçiminde.' pattern = re.compile(r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}') names = pattern.findall(text) print(names) # ['aslan@csystem.org', 'ali@csystem.org'] Burada compile metodundan bir Pattern nesnesi elde edilmiştir. Sonra bu nesne ile Pattern sınıfının findall metodu çağrılmıştır. findall fonksiyonuna artık ayrıca bir kalıp yazısının verilmediğine dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import re text = 'işte benim e-posta adresim aslan@csystem.org biçiminde ama Ali Hocanınki de ali@csystem.org biçiminde.' pattern = re.compile(r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}') names = pattern.findall(text) print(names) m = pattern.search(text) if m: print(m[0]) else: print('Kalıp bulunamadı!..'); #------------------------------------------------------------------------------------------------------------------------------------ Programlamada bir programın çalışma süresinin çeşitli bakımlardan incelenmesi sürecine İngilizce "profiling" denilmektedir. Bu işlemleri yapan araçlara ise "profiler" denilmektedir. İşte bir programın ya da bir program parçasının hızını tespit etmek için Python Standart Kütüphanesinde "timeit" isimli bir modül bulunmaktadır. Bu modül özellikle küçük kod parçalarının çalışma sürelerinin karşılaştırılması amacıyla kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ timeit modülündeki timeit isimli fonksiyon belli bir Python kod parçasının çalışma süresini kolay bir biçimde ölçmek için bulundurulmuştur. Kod parçası fonksiyonun number parametresinde belirtilen sayıda art arda çalıştırılmaktadır. Bu number parametresinin default değeri 1000000'dur.Fonksiyon saniye cinsinden çalışma süresini bize vermektedir. Eğer kod parçası birden fazla satırdan oluşuyorsa bu durumda kod parçasının üç tırnaklı bir biçimde yazabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ import timeit result = timeit.timeit("for i in range(100000000): pass", number=1) print(result) #------------------------------------------------------------------------------------------------------------------------------------ 6. Ders 15/03/2025 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir kodun çalışma süresi sistemin o anki yüküne bağlı olarak değişebilmektedir. Bu durum thread'lrin ele alındığı bölümde verilecek bilgilerle daha iyi anlaşılacaktır. Ancak kodların birbirlerine göre göreli çalışma süreleri bazen daha önemli olabilmektedir. Aşağıdaki örnekte bir liste içerisine 0'dan 100_000_000'a kadar sayıların kareleri yerleştirilmiştir. Bu işlem alternatif üç yöntemle yapılmıştır. İlk yöntemde boş bir diziye sayıların kareleri append metoduyla eklenmiştir. İlinci yöntemde liste içlemi kullanılmıştır. Üçüncü yöntemde ise map fonksiyonu kullanılmıştır. Denemenin yapıldığı bilgisayarda elde edilen sonuçlar şöyledir: Diziye eleman ekleme yoluyla: 7.447103999991668 Liste içlemi yoluyla: 6.196832599991467 map Fonksiyonu ile: 9.464428100007353 Buradan "liste içlemlerinin" diğer alternatif yöntemlere göre daha hızlı olduğu sonucu çıkarılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import timeit option1 = """ a = [] for i in range(100_000_000): a.append(i * i) """ option2 = """ a = [i * i for i in range(100_000_000)] """ option3 = """ a = list(map(lambda i: i * i, range(100_000_000))) """ result = timeit.timeit(option1, number=1) print(f'Diziye eleman ekleme yoluyla: {result}') result = timeit.timeit(option2, number=1) print(f'Liste içlemi yoluyla: {result}') result = timeit.timeit(option3, number=1) print(f'map Fonksiyonu ile: {result}') #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte 0-1-2...-98-99 biçiminde bir yazının oluşturulmasına ilişkin üç alternatif yöntem karşılaştırılmıştır. Elde edilen sonuçlar şmyledir: for döngüsü yoluyla: 13.376206700020703 join ve map yoluyla: 9.183207199996104 join ve liste içlemi yoluyla: 7.749387199990451 join ve üretici ifade yoluyla: 7.744632400019327 #------------------------------------------------------------------------------------------------------------------------------------ import timeit option1 = """ s = '' for i in range(100): if i != 0: s += '-' s += str(i) """ result = timeit.timeit(option1) print(f'for döngüsü yoluyla: {result}') option2 = """ s = '-'.join(map(str, range(100))) """ result = timeit.timeit(option2) print(f'join ve map yoluyla: {result}') option3 = """ s = '-'.join([str(i) for i in range(100)]) """ result = timeit.timeit(option3) print(f'join ve liste içlemi yoluyla: {result}') option4 = """ s = '-'.join(str(i) for i in range(100)) """ result = timeit.timeit(option3) print(f'join ve üretici ifade yoluyla: {result}') #------------------------------------------------------------------------------------------------------------------------------------ Anımsayacağınız gibi Python'da Python'un çeşitli versiyonlarında formatlı yazı oluşturmak için yöntemler eklenmiştir. En eskiden % operatörü ile bu işlem yapılıyordu. Sonra str sınıfına format metodu eklendi. Nihayet 3.6 ile birlikte string enterpolasyonu (f'li string'ler) eklenmiştir. Pekiyi bunların hangisi daha hızlıdır. Aşağıda üç yöntemi de timeit fonksiyonu ile karşılaştırdık. Çıkan sonuçlar şöyşedir: str.format yoluyla: 0.49234870000509545 string enterpolasyonu yoluyla: 0.40875050000613555 % operatör metodu yoluyla: 0.427367799973581 Buradan en hızlı yöntemin string enterpolasyonu olduğu görülmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import timeit option1 = """ a = 10; b = 20; c = 30; d = 40; e = 50 s = 'a = {}, b = {}, c = {}, d = {}, e = {}'.format(a, b, c, d, e) """ result = timeit.timeit(option1) print(f'str.format yoluyla: {result}') option2 = """ a = 10; b = 20; c = 30; d = 40; e = 50 s = f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}' """ result = timeit.timeit(option2) print(f'string enterpolasyonu yoluyla: {result}') option3 = """ a = 10; b = 20; c = 30; d = 40; e = 50 s = 'a = %d, b = %d, c = %d, d = %d, e = %d' % (a, b, c, d, e) """ result = timeit.timeit(option3) print(f'% operatör metodu yoluyla: {result}') #------------------------------------------------------------------------------------------------------------------------------------ timeit fonksiyonun birinci parametresiyle belirtilen kod bizim programımızdan bağımsız bir biçimde çalıştırışmaktadır. Yani bu kod içerisinde biz global değişkenleri, kendi fonksiyonlarımızı kullanamyız. İşte bazen ölçülmek istenen çalışma süresi öncesinde birtakım hazırlık işlemlerinin yapılması gerekebilmektedir. Örneğin biz "a = {}, b = {}, c = {}.format(a, b, c)" biçiminde oluşturulmuş bir kodun çalışma süresini ölçmek isteyebiliriz. Burada önce a, b, ve c'ye değer atanması gerekir. Bu değer atamaları kodun içerisinde yapılırsa bu durumda süreye dahil olacaktır. İşte bunun için setup isimli parametre bulundurulmuştur. setup parametresiyle belirtilen kod süresi ölçülecek asıl koddan önce çalıştırılır ancak ölçüm süresine dahil edilmez. Ölçülecek kod böylece setup kodunda yaratılmış olan değişkenleri kullanbilecektir. Örneğin: setup_code = """ a = 10; b = 20; c = 30; d = 40; e = 50 """ measure_code = """ s = 'a = {}, b = {}, c = {}, d = {}, e = {}'.format(a, b, c, d, e) """ result = timeit.timeit(measure_code, setup=setup_code) Burada setup_code çalıştırılacak ancak ölçüme dahil edilmeyecektir. #------------------------------------------------------------------------------------------------------------------------------------ import timeit setup_code = """ a = 10; b = 20; c = 30; d = 40; e = 50 """ measure_code = """ s = 'a = {}, b = {}, c = {}, d = {}, e = {}'.format(a, b, c, d, e) """ result = timeit.timeit(measure_code, setup=setup_code) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi zaten var olan birtakım global değişkenleri ve fonksiyonları setup parametresini kullanmadan ölçüm kodunda doğrudan kullanabilir miyiz? İşte bunun için timeit fonksiyonunda globals parametresi de bulundurulmuştur. Bu paramete bir sözlük biçimindedir. Sözlüğün anahtarları kullanılacak global değişkenlere bizim verdiğimiz isimlerden değeri de onların değerlerinde oluşmaktadır. Örneğin: result = timeit.timeit("for i in range(count): pass", globals={'count': 100}) globals isimli built-in fonksiyonun tüm global değişkenleri bir sözlük biçiminde verdiğini anımsayınız. Bu durumda biz çağrıyı aşağıdaki gibi de yapabiliriz: result = timeit.timeit(code, globals=globals()) Burada kod içerisinde artık biz tüm global değişkenleri kullanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import timeit import math count = 10 code = """ for i in range(count): a = [math.sqrt(i) for i in range(count)] """ result = timeit.timeit(code, globals=globals()) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden de belirttiğimiz gibi timeit fonksiyonu ile ölçülen zaman sistemin o anki durumuna bağlı olarak ve belki de kodun bazı parametrelerine bağlı olarak farklı farlılıklar gösterebilmektedir. Bu nuedenle bazen bu çölüm işleminin kendisini belli bir miktarda tekrarlayıp bir ortalama alınabilir. İşte timeit modülündeki repeat fonksiyonu repeat parametresiyle belirtilen sayıda timeit.timeit fonksiyonunu çağırarak elde edilen zamanları repeat uzunlukta bir listeye yerleştirip bu listeyle geri dönmektedir. Fonksiyonun parametrik yapısı şöyledir: timeit.repeat(stmt='pass', setup='pass', timer=, repeat=5, number=1000000, globals=None) Fonksiyonun repeat dışındaki parametreleri timeit fonksiyonundaki gibidir. Yani bu parametreler aslında timeit fonksiyonuna geçirilmektedir. Default repeat değerinin 5 olduğuna dikkat ediniz. Örneğin: code = """ for i in range(count): a = [math.sqrt(i) for i in range(count)] """ result = timeit.repeat(code, repeat=5, number=1000, globals=globals()) print(result) Burada biz 5 ayrı ölçüm yapmış olduk. repeat fonksiyonu bize bu 5 ayrı ölçüm değerini bir liste halinde vermektedir. Denemeden makinede elde edilen liste şöyledir: [0.6159047000110149, 0.6150840999907814, 0.6073781999875791, 0.6185584000195377, 0.6033079999906477] #------------------------------------------------------------------------------------------------------------------------------------ import timeit import statistics result = timeit.repeat("'-'.join(str(i) for i in range(10))", repeat=5) print(result) # [3.0629887000000053, 2.8896216000002823, 2.7409239999997226, 2.750181899999916, 2.742352899999787] average_time = statistics.mean(result) #------------------------------------------------------------------------------------------------------------------------------------ Aslında timeit modülündeki timeit ve repeat fonksiyonları Timer isimli bir sınıf kullanılarak yazılmıştır. Yani asıl ölçme işlemleri bu Timer sınıfının metotları tarafıdan yapılmaktadır. Biz istersek doğrudan Timer sınıfı türünden bir nesne yaratıp ölçümü Timer sınıfının timeit ve repeat fonksiyonlarıyla yapabiliriz. Timer sınıfının __init__ metodunun parametrik yapısı şöyledir: class timeit.Timer(stmt='pass', setup='pass', timer=, globals=None) Örnek bir kullanım şöyle olabilir: code = """ s = '-'.join([str(i) for i in range(100)]) """ timer = timeit.Timer(code) result = timer.timeit(number=100000) Görüldüğü gibi burada önce Time sınıfı türünden nesne yaratılmıştır. Çalıştırılacak kod bu nesne yaratılırken verilmiştir. Bundan sonra asıl ölçüm işlemi Timer sınıfının timeit metoduyla yapılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import timeit code = """ s = '-'.join([str(i) for i in range(100)]) """ timer = timeit.Timer(code) result = timer.timeit(number=100000) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Standart kütüphanenin orijinal kaynak kodlarında global timeit ve repeat fonksiyonları şöyle yazılmıştır: def timeit(stmt="pass", setup="pass", timer=default_ timer,number=default_number, globals=None): """Convenience function to create Timer object and call timeit method.""" return Timer(stmt, setup, timer, globals).timeit(number) def repeat(stmt="pass", setup="pass", timer=default_timer, repeat=default_repeat, number=default_number, globals=None): """Convenience function to create Timer object and call repeat method.""" return Timer(stmt, setup, timer, globals).repeat(repeat, number) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir program çalıştırılırken komut satırında kullanılan argümanlara "komut satırı argümenları (command line arguments)" denilmektedir. Komut satırı argümanları Python'da da benzer biçimde kullanılmaktadır. Örneğin "sample.py" isimli bir Python programını komut satırından aşağıdaki gibi çalıştırmış olalım: python sample.py ali veli selami Burada çalıştırılmak istenen program "sample.py" isimli programdır. "ali veli selami" ise programın komut satırı argümanlarıdır. Komut satırı argümanları programın içerisinden alınıp kullanılabilmektedir. Böylece programlar çalıştırılırken dış dünyadan programa parametre aktarımı yapılabilmektedir. Komut satırı argümanlarını elde etmek için sys modülündeki argv isimli list türünden değişken kullanılmaktadır. Bu değişken her zaman komut satırı argümanlarını bize bir liste biçiminde verir. Listenin ilk elemanı çalıştırılan Python kaynak dosyasının ismi, diğer elemanlar da sırasıyla girilen komut satırı argümanı olur. Örneğin "sample.py" programımız şöyle olsun: import sys print(sys.argv) Biz de programı komut satırından şöyle çalıştırçalıştırmış olalım: python sample.py ali veli selami argv listesi şöyle oluşturulacaktır: ['sample.py', 'ali', 'veli', 'selami'] Komutu satırı argümanları boşluk karakterlerinden ayrıştırılmaktadır. Boşluk içeren yazılar tek bir argüman olarak aktarılmak istenirse tırnaklanmalıdır. Örneğin: python sample.py "ali veli selami" Burada "ali veli selami" artık tek bir argümandır. Aşağıdaki örnekte komut satırı argümanı ile alınan ifade Python'un built-in eval fonksiyonuna sokulup ifadenin değeri elde edilmiş ve ekrana yazıdırılmıştır. Programı şöyle çalıştırabilirsiniz: python sample.py "sqrt(12 - 6 * 3 + 30)" #------------------------------------------------------------------------------------------------------------------------------------ import sys from math import * if len(sys.argv) != 2: print('wrong number of arguments!') sys.exit(1) result = eval(sys.argv[1]) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Komut satırı arümanları IDE'li ortamlarda da IDE'lerin menüleriyle oluşturulabilmektedir. Yani biz IDE'lerde çalışırken sanki programı komut satırından çalıştırıyormuşuz gibi argümanlar da girebiliriz. Spyder IDE'sinde "Run/configuration per file" diyalog penceresinde "General settings/Command line options" edit alanında programın komut satırı argümanları girilebilmektedir. PyCharm IDE'sinde de komut satırı argümanları "Run/Edit Configurations" diyalog pencersinde "Parameters" edit alanında girilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'un standart kütüphanesindeki modüller (yani .py dosyaları) bilindiği gibi birtakım faydalı fonksiyonları ve sınıfları bulundurmaktadır. Ancak bu modüllerin bazıları (hepsi değil) aynı zamanda bir program gibi de çalıştırılabilmektedir. Yani bu modüllerin bazıları hem bir kütüphane gibi kullanılmakta hem de bir program gibi çalıştırılabilmektedir. Tabii bu modüllerdeki programlar bu modülleri yazanlar tarafından aşağıdaki gibi bir kontrolle çalıştırılmıştır: # some_module.py ... ... ... if __name__ == '__main__: .... Mademki bazı kütüphane modülleri aynı zamanda birer program gibi çalıştırılabilmektedir. Pekiyi onları program gibi nasıl çalıştırabiliriz? Öncelikle çalıştırma için bu modüllerin ".py" dosyalarının nerede olduğunu bilmemiz gerekir. Kurulum programı Python'u kurarken Python standart kütüphanesindeki ".py" dosyalarını bazı dizinlerin içerisine çekmektedir. Her ne kadar bunların yerlerini biz bulabilirsek de buradaki programları bu yöntemle çalıştırmak zahmetlidir. Mademki python yorumlayıcısı zaten bunların yerlerini bilmektedir. O halde bizim python yorumlayıcısına çalıştırmak istediğimiz modülün ismini vermemiz yeterlidir. Bu işlem de python yorumlayıcısının komut satırında "-m" seçeneği ile yapılmaktadır. Örneğin: python -m timeit "'-'.join(str(i) for i in range(10))" Biz "-m" seçeneği ile Python yorumlayıcısına şunu söylemekteyiz: "Python sen kendi modüllerinin nerede olduğunu biliyorsun. Beni uğraştırma. Ama onun yerini bul ve onu bir program gibi çalıştır". Tabii biz bu biçimde çalıştırdığımız modüllere de komut satırı argümanları da geçirebilmekteyiz. CPython install edildiğinde genellikle standart kütüphane bileşenleri kurulum dizininin altındaki "lib" dizininde bulunmaktadır. pip programı ile install edilen paketler ise genellikle "site-packages" dizininde bulıunur. Bir modülün nerede bulunduğunu modülün __file__ özel değişkeni ile de öğrenebiliriz. Örneğin: >>> import timeit >>> timeit.__file__ 'C:\\Program Files\\msys64\\mingw64\\lib\\python3.9\\timeit.py' Aynı işlemi Linux'ta yaptığımızda şöyle bir sonuç elde ediyoruz: >>> import timeit >>> timeit.__file__ '/usr/lib/python3.8/timeit.py' Tabii Python standart kütüphanesindeki modüllerde bulunan kodları "-m" seçeneği ile çalıştırmak çok daha pratiktir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ timeit modülü bir program gibi de çalıştırılabilmektedir. Yani komut satırında (kabuk üzerinde) biz "-m" seçeneği ile bu timeit modülündeki programı çalıştırabiliriz. Modülün komutu satırı argümanları şunlardır: -n N, --number=N: How many times to execute ‘statement’ -r N, --repeat=N: How many times to repeat the timer (default 5) -s S, --setup=S: Statement to be executed once initially (default pass) -p, --process: Measure process time, not wallclock time, using time.process_time() instead of time.perf_counter(), which is the default -v, --verbose: Print raw timing results; repeat for more digits precision -h, --help Örneğin: python -m timeit -n 100000 "'-'.join(str(i) for i in range(100))" Eğer komut satırında "-n" seçeneğini kullanmazsak modüldeki program kendisi çalıştırılmak istenen kodun harcadığı zamana göre ölçüm için döngü değerini kendisi ayarlamaktadır. "-r" parametresi girilmezse kod default olarak 5 kez çalıştırılmaktadır. Pekiyi timeit modülünü komut satırında bir program gibi çalıştırmanın bize sağladığı fayda nedir? İşte bu sayede bir editör bile kullanmadan Python kod parçalarının çalışma zamanlarını komut satırından elde edebilmekteyiz. #------------------------------------------------------------------------------------------------------------------------------------ python -m timeit -n 100000 "'-'.join(str(i) for i in range(100))" #------------------------------------------------------------------------------------------------------------------------------------ Kursumuzun bu bölümünde Python'da veritabanı işlemlerin nasıl yapıldığı üzerinde duracağız. Biz bu bölümde önce "veritabanı", "veritabanı yönetim sistemler" kavralarını göreceğiz. Sonra işimizi görecek kadar SQL dilini tanıtacağız. Sonra veritabanı işlemlerinin Python'da nasıl gerçekleştirildiğini açıklayacağız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 7. Ders 16/03/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ İçeriisnde belli bir düzende bilgilrin tutulduğu dosyalara "veritabanı (database)" denilmektedir. Örneğin bir kitapların bilgileri, şahıslara ilişkin bilgiler, ürünlere ilişkin bilgiler veritabanlarında saklanabilmektedir. Veritabalarından amaç belli koşulu sağlayan bilgilere hızlı bir erişimin sağlanmasıdır. Uygulama programcılığında birtakım bilgilere hızlı bir biçimde erişilmesi en önemli unsurlardan birini oluşturmaktadır. Bu nedenle uygulama programcılığı için veritabanları önemli bir konudur. Örneğin biz binlerce öğrencinin bilgilerini bir veritabanında saklayabiliriz. Sonra "Eskişehir'de doğan yaşı 15'ten büyük olan Lise 4'üncü sınıf öğrencilerinin" listesini hızlı bir biçimde elde edebiliriz. Veritabanlarında belli koşulları sağlayan kayıtların elde edilmesine "sorgulama (query)" denilmektedir. Veritabanı işlemleri uzmanlık gerektiren işlemlerdir. Programcının bir dosya açarak kayıtları onun içerisine yerleştirmesi ve dosya içerisinden sıralı bir biçimde bunlara erişmesi oldukça ilkel bir yaklaşımdır. Veritabanı işlemleri karmaşık ve uzmanlık gerektiren işlemler olduğu için bu işlemleri yapma iddiasında olan bağımsız yazılımlar oluşturulmuştur. Bunlara "Veritabanı Yönetim Sistemleri VTSY (Database Management Systems - DBMS)" denilmektedir. Çeşitli firmaların paralı VTYS'leri vardır. Bazı VTYS'ler ise açık kaynak kodlu dolayısıyla bedav durumdadır. Günümüzde en çok kulanılan VTYS'ler şunlardır: - Oracle firmasının "Oracle" isimli ürünü (paralı). - Microsoft firmasının "SQL Server" isimli ürünü (paralı). - IBM firmasının "DB2 isimli ürünü (paralı). - MySQL isimli VTYS açık kaynak kodludur ancak dağıtımı ve isim hakkı Oracle firmasındadır (açık kaynak kodlu ancak gelecekteki durumu şüpheli). - MariaDB MySQL projesinde çalışanların oluşturduğu MySQL'in devamı niteliğindeki açık kaynak kodlu üründür. - PostGreSQL açık kaynak kodlu oldukça popüler bir VTYS'dir. - SQLite açık kaynak kodlu gömülü (embedded) bir VTYS'dir. Eskiden VTYS kavramı yoktu. Bu kavram zamanla yaygın kazandı. VTYS kavramını IBM firması ortaya atmıştır ve dünyanın ilk VTYS'si de IBM'in DB2 denilen ürünüdür. Oracle firması geliştirdiği VTYS ile dünyanın en büyük bilişim firmaları arasına girmiştir. VTYS'lerin olmadığı zamanlarda veritabanı işlemleri özel kütüphanelerle yürütülüyordu. Bu kütüphanelerin kullanılması da oldukça zordu. Bir ürünün VTYS olarak adlandırılabilmesi için kabaca şu özelliklere sahip olması gerekir: - VTYS'lerde kullanıcı yüksek seviyeli bir biçimde işlemlerini yürütür. VTYS'ler alçak seviyeli işlemlerle kullanıcının ilgisini kesmiştir. Yani VTYS'lerin kayıtları hangi dosyalarda ve nasıl sakladığını kullanıcılar bilmek zorunda değildir. - VTYS'ler "client-server" tarzı bir mimariye sahiptir. Yani VTYS'lere birden fazla client bağlanıp eşzamanlı olarak işlemler yapabilmektedir. Bu bağlamda VTYS'lerin çoğu uzak bağlantılara izin verebilmektedir. - VTYS'ler kullanıcılarına güvenli bir kullanım sunmaktadır. Buradaki güvenlik iki yönlüdür. Hem bilgilerin bozulmaması konusunda bir direnci belirtmektedir. Güvenlik aynı zamanda bir kullanıcının başka bir kullanıcının bilgilerine erişimini engellemek anlamına da gelmektedir. Genellikle bu sistemlerde kullanıcılara "kullanıcı ismi" ve "parola" verilir. Kullanıcılar bu bilgilerle işlemlerini yaparlar. - Pek çok VTYS'de birtakım hazır utility araçlar da bulunmaktadır. Örneğin backup, restore araçları gibi. Pek çok VTYS'nin işlemleri GUI ortamda fare ve klavye ile yapmaya olanak sağlayan yüksek seviyeli yönetim araçları da bulunmaktadır. - VTYS'lerin en önemli özelliklerinden biri onlara iş yaptırmak için yüksek seviyeli bazı dillerin kullanılmasıdır. Örneğin VTYS'lerin çoğu SQL (Structured Query Language) denilen bir dili desteklemektedir. Kullanıcı yapmak istediği şeyi SQL denilen bir dille VTYS'ye söyler. SQL veritabanı işlemlerini yapan dil değildir, kullanıcı ile arayüz oluşturan bir dildir. Asıl veritabanı işlemleri VTYS'lerin motor (engine) kısımlarıyla C/C++ gibi dillerde yazılmış kodlarla yapılmaktadır. Bu nedenle programcının VTYS'lere iş yaptırabilmesi için belli düzeyde SQL bilmesi gerekmektedir. Biz kursumuzda aşağıdaki VTYS'ler üzerinde çalışmalar yapacağız: - MySQL - Microsoft SQLServer - SQLite Gömülü VTYS #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ VTYS'ler kapasiteli oldukça güçlü ve büyük yazılımlardır. Halbuki bazen birkaç tablodan oluşan küçük veritabanlarının oluşturulması gerekebilmektedir. İşte aslıdna bir VTYS olmadığı halde bir VTYS'y takit eden sisteme önemli bir yük bindirmeyen tek bir kütüphane dosyası biçiminde bulunan ancak SQL kullanımına da izin veren yazılımlar oluşturulmuştur. Bunlara "Gömülü VTYS (Embedded DBMS)" denilmektedir. Bunların en yaygın kullanılanı "SQLite" isimli programdır. Microsoft'un Access programında kullandığı "Access Jet Motor" da bir gömülü VTYS'dir. Gömülü VTYS'ler çok az sistem kaynağı gerektirdikleri için gömülü aygıtlarda yaygın biçimde kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bu bölümde VTYS'lerin nasıl kullanıma hazır hale getirildiği üzerinde duracağız. MySQL kullanabilmek için "MySQL Server" isimli programı makinenize kurmanız gerekir. MySql server aşağıdaki bağlantıdan indirilip kurulabilir: https://dev.mysql.com/downloads/mysql/ Kurulum yapıldığında server çalışır durumda olur. Aynı zamanda "MySql Workbench" denilen bir GUI aracı da kullanıma hazır hale gelir. "MySQL Workbench" MySQL VTYS'siüzerinde fare klavye yoluyla işlemler yapmayı kolaylaştıran bir GUI yönetim aracıdır. Microsoft'un "SQLServer" isimli ürünü paralıdır. Ancak bunun "Express Edition" ismi altında bedava bir versiyonu da bulunmaktadır. "Express Edition" aşağıdaki bağlantıdan indirilip kurulabilir. Kurulumu biraz zaman almaktadır kurulum sonrasında ve diskte 4GB civarı yer kaplamaktadır. https://www.microsoft.com/en-us/download/details.aspx?id=101064 SqlServer'ın da "SqlServer Management Studio" isminde bir GUI aracı bulunmaktadır. SQLite en çok kullanılan Gömülü VTYS'dir. SQLite aslında tek bir DLL'den oluşmaktadır. Gerçek bir server değildir. Server taklidi yapan bir kütüphane gibidir. Bu nedenle küçük aygıtlarda server'sız SQL kullanımına izin vermektedir. Dolayısıyla SQLite'ı kurmak aslında birkaç dosyayı indirmek anlamındadır. SQLite parasız açık kaynak kodlu bir üründür. İndirimi aşağıdaki bağlantıdan yapılabilir: https://www.sqlite.org/download.html Burada "Precompiled Binaries for XXX" bağlantısına (XXX işletim sistemini belirtiyor) tıklanırsa bir ".zip" dosyası indirilecektir. Bu ".zip" dosyası açıldığında ilgili kütüphane dosyası elde edilecektir. Ancak Python'un standart kütüphanesi zaten SQLite'ı desteklemektedir. Dolayısıyla SQLite kütüphaneleri zaten sizin Python paketlerinizin içerisinde bulunuyor durumdadır. Python'da SQLite için bir şey indirmenize gerek yoktur. SQLite için pek çok basit GUI aracı geliştirilmiştir. Bu GUI araçlarının bazıları o kadar iyi olmasa da iş görür niteliktedir. "SQLite Studio" isimli GUI aracını aşağıdaki bağlantıdan indirebilirsiniz: https://sqlitestudio.pl/ "DB Browser for SQLite" isimli aracı da şu bağlantıdan indirebilirsiniz: https://sqlitebrowser.org/dl/ Bunların dışında pek çok VTYS ile çalışan genel GUI araçlar da vardır. Örneğin "DBeaver" bedava ve güzel bir araçtır. DBeaver aşağıdaki bağlantıdan indirilebilir: https://dbeaver.io/ Biz kurusumuzda önce zaten Python'a entegre edilmiş olan SQLite üzerinde işlemler yapacağız. Daha sonra MySQL ve Microsoft SqlServer üzerinde duracağız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Veritabanı dünyasında çeşitli "modeller (paradigms)" bulunmaktadır. Bugün endüstride en yaygın kullanılam model "ilişkisel veritabanı (relational database)" modelidir. Ancak bu modelin yanı sıra "nesne tabanlı (object based)", "hiyerarşik (hiearchical)", "NoSql" modelleri de bulunmaktadır. Özellikle son on yıldır NoSql veritabanı modelleri yaygın kullanılmaya başlamıştır. Biz kursumuzda yalnızca ilişkisel veritabanları üzerinde işlem yapacağız. Bazı veri türleri için diğer modeller daha uygun olabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ İlişkisel veritabanları tablolardan, tablolar sütun ve satırlardan oluşmaktadır. Tabloların sütunlarına "alan (field)" satırlarına ise "kayıt (record)" ya da "satır (row)" denilmektedir. Ugulamacı tipik olarak önce veritabanını boş bir biçimde yaratır. Sonra onun içerisinde tabloları oluşturur. Tabloları oluştururlen sütunları (yani alanları) belirler. Sonra da tablolara kayıt (yani satır) ekler. Tabii gerektiğinde tablolardaki koşulu sağlayan kayıtları elde edip görüntüler. Bütün bunları SQL denilen bir arayüz dille gerçekleştirir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SQL'de tabloların sütunları yaratılırken onlara birer tür bilgisi atanmaktadır. Yani her sütun farklı türlerden bilgileri tutabilmektedir. SQL ISO tarafından standardize edilmiş olan bir dildir. ISO standartlarında sütunlar (yani alanlar) için çeşitli türler belirtilmiştir. Ancak SQL'in de ISO standartlarının dışında pek çok varyantı vardır. Microsoft'un SQL varyantına "Transact-SQL" ya da kısaca "TSQL" denilmektedir. Oracle firmasının SQL'ine ise "PL/SQL" denilmektedir. Dolayısıyla çeşitli SQL server yazılımları bazı standart sütun türlerini destekliyor olsa da farklı türlere de sahiptir. Örneğin MySQL'deki sütun türleri çok çeşitlidir. Ancak SQLite aslında içsel olarak çok az türü desteklemektedir. Bugün Veritabanı işlemleri artık uzamanlık gerektiren bir iş kolu haline gelmiştir. Bu tür işlerle uğraşan kişilere "Veritabanı Yöneticisi (DB Admin)" denilmektedir. Veritabanı yöneticileri belli bir VTYS'yi üzerinde onların araçlarını kullanbilecek biçimde uzamanlaşmışlardır. Veritabanı yöneticileri veritabanlarını oluşturup, onların sürüdürümünü yaparlar ve gerekli raporların alınması için programcılara yardımcı olurlar. Tabii bir programcının veritabanı yöneticisi kadar veritabanı bilmesine gerek yoktur. Pek çok VTYS'nin desteklediği standart sütun türleri şunlardır: INTEGER: Bu tür tamsayıları turmak için kullanılan genel bir tamsayı türüdür. INT: Pek çok VTYS'de bu tür 4 byte'lık tamsayı türünü temsil etmektedir. SMALLINT: Tipik olarak 2 byte'lık tamsayı türünü temsil eder. BIGINT: Tipik olarak 8 byte'lık tamsayı türünü temsil eder. FLOAT: 4 byte'lık noktalı sayı türünü temsil etmektedir. DOUBLE: 8 byte'lık noktalı sayı türünü temsil etmektedir. NUMERIC ya da REAL: Belli bir tamsayı kısmı ve noktadan sonarki duyarlılık eşliğinde noktalı sayıyı temsil etmektedir. TIME: Zaman bilgisini temsil etmektedir. DATE: Tarih bilgisini temsil etmektedir. DATETIME: Her tarih hem de zamanı temsil etmektedri. CHAR(N): N karakterli yazıları temsil etmektedir. VARCHAR(N): En fazla N karakterden oluşan değişken uzunlukta yazısal bilgiyi temsil etmektedir. TINYTEXT: 256 karaktere kadar düz metin tutmak için kullanılmaktadır. TEXT: 64K'ya kadar yazıları temsil etmektedir. LONGTEXT: 4 GB'ye kadar yazıları tutmak için kullanılmaktadır. TINYBLOB: 256 byte'a kadar binary veri turmak için kullanılmaktadır. BLOB: 64K'ya kadar binary veri tutmak için kullanılaktadır. LONGBLOB: 4GB'ye kadar binary veri tutmak için kullanılmaktadır. BOOLEAN: True False biçiminde ikili verileri tutmak için kullanılmaktadır. Uygulamacı olarak hangi VTYS ile çalışıyorsanız ona özgü türleri gözden geçirmenizi tavsiye ederiz. Örneğin MySQL'de çok fazla tür vardır. MySQL'in türlerini aşağıdaki bağlantıdan inceleyebilirsiniz: https://dev.mysql.com/doc/refman/8.0/en/data-types.html SQLite ISO standartlarındaki pek çok türü destekliyor gibi görünse de kendi içerisinde aslında birkaç tür kullanmaktadır. (Yani başka bir deyişle SQLite pek çok türü kabul etmekle birlikte bunları aslında ortak birkaç tür ile ifade etmektedir.) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Veritabanları için GUI araçları pek çok işlemin görsel biçimde fare ve klavye yoluyla yapılmasına izin vermektedir. Fakat aslında bu GUI araçlar kullanıcının görsel yaptığı işlemleri SQL komutlarına dönüştürüp bu SQL cümlelerini VTYS'ye göndererek işlemlerini yapmaktadır. Örneğin biz bir GUI araçta bir düğmeye basarak veritabanı yaratıyor olabiliriz. Aslında GUI aracı veritabanı yaratan "CREATE DATABASE" isimli SQL komutunu VTYS'ye gönderip işlemini yapmaktadır. Yani GUI araçlar yalnızca kullanım kolaylığı sağlamaktadır. Şimdi temel SQL komutlarını görelim. GUI araçlar aynı zamanda SQL editörlerine de sahiptir. Yani biz SQL editöründe bir komutu yazıp onu çalıştırabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Genel olarak SQL'de yazısal bilgiler tek tırnak içerisine alınmaktadır. Her komut ';' ile sonlandırılır. Ancak pek çok VTYS bu ';' ile sonlandırmayı zorunlu tutmamaktadır. SQL büyük harf küçük harf duyarlılığı olan bir dil (case sensitive) değildir. Pek çok uygulamacı anahtar sözcükleri büyük harflerle diğer isimleri küçük harflerle yazma eğilimindedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir veritabaını yaratmak için "CREATE DATABASE" komutu kullanılmaktadır. Komutun genel biçimi şöyledir: CREATE DATABASE ; Örneğin: CREATE DATABASE school; Veritabanı silmek için DROP DATABASE komutu kullanılmaktadır. Komutun genel biçimi şöyledir: DROP DATABASE ; MySQL'de "veritabanı" yerine "schema" terimi tercih edilmiştir. SQLite'ta veritabanı yaratmak için CREATE DATABASE komutu kullanılmaz. SQLite veritabanları tek bir dosya biçiminde oluşturulmaktadır. Boş bir veritabanı içi boş bir dosya ile oluşturulabileceği gibi GUI araçlarıyla ya da programlama yoluyla oluşturulabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir veritabanı (schema) üzerinde işlem yapmadan önce o veritabanının aktif hale getirilmesi gerekir. (Başka bir deyişle bizim VTYS'ye hangi veritabanı üzerinde işlem yapmak istediğimizi bildirmemiz gerekir.) Bunun için USE komutu kullanılmaktadır. USE komutunun genel biçimi şöyledir: USE ; Örneğin: USE school; SQLite'ta yine bu biçimde bir USE komutu yoktur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Veritabanı yaratıldıktan sonra sıra tabloların yaratılmasına gelmiştir. Karmaşık bir veritabanı yüzlerce hatta binlerce tabloya sahip olabilmektedir. Tablo yaratmak için "CREATE TABLE" komutu kullanılmaktadır. Komutun genel biçimi şöyledir: CREATE TABLE (sütun_ismi1 tür, sütun_ismi2 tür, sütun_ismi3 tür, ....); Tablo yaratırken sütun isimleri ve onların türleri belirtilmektedir. Genellikle tablolara "birincil anahtar (primary key)" denilen bir sütunun eklenmesi iyi bir tekniktir. Bu sütun aynı zamanda otomatik artırımlı (AUTO INCREMENT) yapılmaktadır. Örneğin: CREATE TABLE student_info(student_id INTEGER PRIMARY KEY AUTO_INCREMENT student_name VARCHAR(64), student_no INTEGER); Tablodaki bir sütun eğer birbirinden farklı satır değerlerine sahip olmak zorundaysa bu tür sütunlara "PRIMARY KEY" denilmektedir. Örneğin student_id isimli sütun PRIMARY KEY ise Biz kayıt insert ederken aynı numaraya sahip birden fazla kaydı insert edemeyiz. Ancak örneğin student_name PRIMARY KEY değilse bu durumda aynı isme sahip birden fazla kayıt tabloya eklenebilmektedir. Yukarıda da belirttiğimiz gibi tavsiye edilen durum her tablonun bir sütununun PRIMARY KEY yapılmasıdır. Eğer tabloda PRIMARY KEY olmaya aday bir sütun yoksa bu durumda uygulamacı dummy bir sütun yaratıp (genellikle ismine id verilir) onu PRIMARY KEY yapabilir. MySQL'de sütunu PRIMARY KEY yapmak için sütun türünden sonra PRIMARY KEY anahtar sözcükleri yerleştirilebilir ya da CREATE TABLE komutundaki sütun listesinde PRIMARY KEY(isim) biçiminde bir eleman bulundurulur. Örneğin: CREATE TABLE student_info(student_id INTEGER PRIMARY KEY AUTO_INCREMENT student_name VARCHAR(64), student_no INTEGER); ya da örneğin: CREATE TABLE student_info(student_id INTEGER PRIMARY KEY AUTO_INCREMENT student_name VARCHAR(64), student_no INTEGER, PRIMARY KEY(student_id)); Diğer pek çok SQL varyantlarında PRIMARY KEY ilk biçimde oluşturulmaktadır. Bir sütun AUTO_INCREMENT yapılırsa satır insert etme sırasında bu sütun değeri otomatik olarak önceki değerden bir fazla olacak biçimde artırılır. Genellikle PRIMARY KEY olan sütunlar zaten otomatik olarak belirtilmese bile AUTO_INCREMENT kabul edilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 8. Ders 22/03/2025 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir tabloya satır (yani kayıt) eklemek için INSERT INTO komutu kullanılmaktadır. Komutun genel biçimi şöyledir: INSERT INTO () VALUES(); Örneğin: INSERT INTO student(student_no, student_name) VALUES(123, 'Ali Serçe'); Tabloya satır eklerken tüm sütun sütunlar için değer girilmesi zorunda değildir. Bu nedenle önce hangi sütun bilgilerinin girileceği belirtilmektedir. Ondan sonra VALUES anahtar sözcüğü ile bu sütunların değerleri girilir. Sıra veritabanındaki sıra olmak zorunda değildir. Ancak komuttaki sıra tutarlı olmalıdır. Yazısal sütunlar için değerler tek tırnak içerisinde girilmek zorundadır. Örneğin: INSERT INTO student_info(student_name, student_no) VALUES('Ali Serçe', 123); INSERT INTO student_info(student_name, student_no) VALUES('Sacit Süzülmüş', 765); INSERT INTO student_info(student_name, student_no) VALUES('Sibel Tektaş', 654); #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ WHERE cümleciği pek çok SQL komutuna monte edilebilen bir kalıptır. WHERE cümleciğinin amacı koşul oluşturmaktır. Cümlecikte temel karşılaştırma operatörleri benzer biçimde kullanılır. SQL'de AND, OR ve NOT mantıksal operatörleri koşullarda kullanılabilmektedir. Örneğin: WHERE student_no > 700 AND student_no < 800 Koşul oluştururken ilginç bir operatör de LIKE operatörüdür. LIKE operatörü iki operand'lı araek bir operatördür. Operatörün solundaki operand bir yazısal bilgi içeren bir sütun ismi sağındaki operand ise kalıp yazısıdır. % karakteri "geri kalan hepsi" anlamına gelmektedir. Örneğin: WHERE student_name LIKE 'A%' Burada "ismi A ile başlayan öğrenciler" biçiminde bir koşul oluşturulmuştur. Örneğin: WHERE student_name LIKE '%A%' Bu kalıp içinde A geçenler anlamına gelmektedir. Yazısal sütunlarda default durumda büyük harf küçük harf duyarlılığı yoktur. Yani yukarıdaki koşulda içerisinde 'a' karakteri geçen isimler de koşula dahildir. Ancak bu durum VTYS'den VTYS'ye değişebilmektedir. Pek çok VTYS'nin kendine özgü operatörleri de vardır. Çalıştığınız VTYS'deki SQL'de kullanılan operatörleri gözden geçiriniz. Örneğin SQLite'ta REGEXP biçiminde bir operatör bulunmaktadır: WHERE student_name REGEXP 'A.*L' Bu REGEXP operatörü ile biz başı A ile başlayan sonu L ile biten koşulunu oluşturmuş olduk. IN operatörü birden fazla seçeneği belirtmek için kullanılmaktadır. ÖrneğiN: WHERE student_birth_place IN ('Eskişehir', 'İzmir', 'Manisa') Burada doğum yeri "Eskişehir", "İzmir" ve "Manisa" olan öğrencilere yönelik bir koşul oluşturulmuştur. Tabii bu koşul OR operatörüyle de yapılabilirdi. Pek çok VTYS'de o VTYS'ye özgü "built-in" fonksiyonlar da bulunmaktadır. WHERE koşulları bu fonksiyonlar kullanılarak da oluşturulabilmektedir. Örneğin: WHERE length(student_name) > 6 Burada length fonksiyonu built-in bir fonksiyondur. Dolayısıyla yukarıdaki koşulda "ismi 6 karakterden büyükler" ifade edilmiştir. Çalıştığınız VTYS'nin built-in fonksiyonlarını inceleyiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SQL "DELETE FROM" komutu belli koşulu sağlayan kayıtların silinmesi amacıyla kullanılmaktadır. Komutunb genel biçimi şöyledir: DELETE FROM [WHERE ]; Komutta WHERE cümleciği bulundurulmazsa tablodaki tüm kayıtlar silinmektedir. Bu nedenle dikkat edilmesi gerekir. DELETE FROM komutunu güvenli kullanabilmek için koşu cümleceğini katı hale getirebilirsiniz. Ya da silme işlemini PRIMARY KEY sütununu kullanarak yapabilirsiniz. Örneğin: DELETE FROM student_info WHERE student_name = 'Ali Serçe'; Bu komut adı "Ali Serçe" olan tüm öğrencileri silecektir. Koşulu şçyle daha katı hale getirebiliriz: DELETE FROM student_info WHERE student_name = 'Ali Serçe' AND student_no = 123; Ya da önce silinecek kaydın PRIMARY KEY sütunundaki bilgi elde edilip silme buna göre de yapılabilir. Örneğin: DELETE FROM student_id = 123; #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bazen belli bir satırın (kaydın) belli bir alanını değiştirmek isteyebiliriz. Örneğin bir öğrencinin ismi veritabanına yanlış kaydedilmiş olabilir. Bu öğrencinin diğer bilgilerine dokunmadan onun ismini değiştirmek isteyebiliriz. Kayıt üzerinde değişiklikler UPDATE isimli SQL komutuyla yapılmaktadır. UPDATE komutunun genel biçimi şöyledir: UPDATE SET , , ... [WHERE ] Örneğin: UPDATE student_info SET student_name = 'Mehmet Kömcü' WHERE student_name = 'Timur Kömcü'; Burada "Timur Kömcü" olan öğrencinin ismi "Mehmet Kömcü" biçiminde değiştirilmiştir. UPDATE komutu dikkatle uygulanmalıdır. Komutun WHERE kısmı unutulursa tüm kayıtlarda değişiklik yapılmaktadır. Yani tüm kayıtların koşulu sağladığı varsayılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Koşulu sağlayan kayıtların elde edilmesi SELECT komutuyla yapılmaktadır. SELECT komutunun genel biçimi ayrıntılıdır. Çünkü komuta çeşitli cümlecikler monte edilebilmektedir. Komutun temel biçimi şöyledir: SELECT FROM [WHERE 600; Burada numarası 600'den büyük olan öğrencilerin isimleri ve numaraları elde edilmektedir. Sütun istesi yerine * kullanılırsa bu durum "tüm sütunlar" anlamına gelmektedir. Örneğin: SELECT * FROM student_info WHERE student_no > 600; Burada öğrenci numarası 600'den büyük olan öğrencilerin tüm sütun bilgileri elde edilmiştir. Eğer SELECT edilen kayıtlar belli bir sütuna göre sıralı biçimde elde edilmek istenirse ORDER BY cümleciği komuta eklenir. Örneğin: SELECT * FROM student_info WHERE student_id > 600 ORDER BY stdent_name; ORDER BY default olarak kayıtları küçükten büyüğe (ASC) vermektedir. Ancak DESC ile büyükten küçüğe de sıralama yapılabilir. Örneğin: SELECT * FROM student WHERE student_no > 600 ORDER BY student_name DESC; ORDER BY cümleciğinde birden fazla sütun belirtilebilir. Bu durumda ilk sütun değerleri aynıysa diğer sütunlar dikkate alınır. Örneğin: SELECT * FROM student WHERE student_no > 600 ORDER BY student_name DESC, student_no ASC; Burada ismi aynı olanlar numaralarına göre küçükten büyüğe elde edilecektir. Bazen select edilen satırlar çok fazl aolabilir. Programcı elde edilecek atır sayısı üzerinde bir kısıtlama oluşturabilir. Bunun için LIMIT cümleceği kullanılmaktadır. LIMIT anahtar sözcüğünün yanında bir sayı bulunur. Bu durumda koşulu sağlayan kayıtların en fazla burada belirtilen kadarı elde edilir. Örneğin: SELECT * FROM student_info WHERE student_no > 600 ORDER BY student_name DESC, student_no ASC LIMIT 10; WHERE cümleciğinde built-in fonksiyonlar kullanılabilir. Örneğin: SELECT * FROM city WHERE char_length(city) = 6; Built-in fonksiyonlar SELECT cümlesinde sütun isimlerinde de kullanılabilir. Örneğin: SELECT SUBSTR(student_name, 1, 3) FROM student_info; Burada öğrencilerin isimlerinin ilk üç karakteri elde edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ İlişkisel veritabanlarında tablolarda veri tekrarı istenmez. Örneğin bir öğrenci veritabanı oluşturacak olalım. Bir öğrencinin çeşitli bilgilerinin yanı sıra onun okulu hakkında da bilgileri tutmak isteyelim. Aşağıdaki gibi bir tablo tasarımı uygun değildir: Adı Soyadı No Okul Adı Okulun Bulunduğu Şehir Okulun Türü -------------------------------------------------------------------------------------- Ali Serçe 123 Tarsus Amerikan Lisesi Mersin Devlet Lisesi Kaan Aslan 745 Eskişehir Atatürk Lisesi Eskişehir Devlet Lisesi Hasan Bulur 734 Tarsus Amerikan Lisesi Mersin Devlet Lisesi ... ... ... ... Burada Okul bilgileri gereksiz bir biçimde tekrarlanmaktadır. Bu tekrarı engellemek için iki tablo oluşturabiliriz. Öğrenci Tablosu Adı Soyadı No Okul ID'si ---------------------------------- Ali Serçe 123 100 Kaan Aslan 745 150 Hasan Bulur 734 100 ... ... ... Okul Tablosu Okul Id'si Okul Adı Okulun Bulunduğu Şehir Okulun Türü ------------------------------------------------------------------------------------ ... ... ... ... 100 Tarsus Amerikan Lisesi Mersin Devlet Lisesi 150 Eskişehir Atatürk Lisesi Eskişehir Devlet Lisesi ... ... ... ... Burada veri tekrarı oratadan kaldırılmıştır. Tabii bu tablolarda da Okul ID'si ortak bir sütundur. Bu ortak sütun tablolar arasında ilişki kurmak için gerekmektedir. Bu tür sütunlara "foreign key" de denilmektedir. Ancak yukarıdaki gibi tekrarlar engellendiğinde gerekli bilgiler artık tek bir tablodan değil çeşitli tabolardan çekilip alınacaktır. İşte çeşitli tabolardan bilgilerin çekilip alınması işlemine "JOIN" işlemi denilmektedir. JOIN işleminin birkaç biçimi vardır (INNER JOIN, OUTER JOIN, LEFT JOIN, RIGHT JOIN gibi). Ancak en fazla kullanılan JOIN işlemi "INNER JOIN" denilen işlemdir. JOIN denildiğinde de zaten default olarak INNER JOIN anlaşılmaktadır. INNER JOIN işleminde eğer iki tablo söz konusu ise önce iki tablonun kartezyen çarpımları elde edilir. Her kaztezyen çarpım iki tablonun birleştirilmesi biçiminde ("join" ismi oradan geliyor) elde edilmektedir. Sonra kartezyen çarpımlarda yalnızca belli koşulu sağlayan satırlar elde edilir. Bu koşul da genellikle "foreign key" eşitliğine dayalı olur. Böylece tablolar "ilişkisel (relational)" biçimde birleştirilmektedir. INNER JOIN sentaksı iki biçimde oluşturulabilmektedir. Birinci sentaks klasik eski tip sentakstır. İkinci sentaks daha modern biçimdir. Klasik eski tip sentaks şöyledir: SELECT FROM INNER JOIN ON ; Örneğin: SELECT student.student_name, student.student_no, school.school_name, school.school.city FROM student_info INNER JOIN school_info ON student.school_id = school.school_id WHERE student.student_no > 600; Sütun isimleri belirtilirken eğer çakışma yoksa yalnızca isimler yazılabilir. Ancak çakışma varsa tablo ismi ve nokta operatörü ile sütunun hangi tabloya ilişkin olduğu belirtilmelidir. Bazı uygulamacılar çakışma olsa da olmasa da niteliklendirme yaparlar. Bazı uygulamacılar yalnızca çakışan sütunlarda niteliklendirme yaparlar. Yukarıdaki örnekte tüm sütunlar niteliklendirilerek belirtilmiştir. Bu örnek şöyle de yapılabilirdi: SELECT student_name, student_no, school_name, school_city FROM student INNER JOIN school ON student.school_id = school.school_id WHERE student_no > 600; Modern INNER JOIN sentaksında SELECT komutunun FROM kısmında birden fazla tablo ismi belirtilir. Koşul da yine WHERE cümleciğine taşınır. Bu sentaks hem dolay hem de daha anlaşılabilirdir. Örneğin: SELECT student_name, student_no, school_name, school_city FROM student_info, school_info WHERE student.school_id = school.school_id AND student_no > 600; Daha çok bu modern biçim tercih edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SQL birtakım ayrıntıları olan bir dildir. Biz kursumuzda yalnızca temel işlemleri yapabilecek kadar SQL gördük. Siz bu dilin ayrıntılarına ayrıca çalışabilirsiniz ya da Dernemizde SQL'in yarıntılarıyla anlatıldığı kurslara katılabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 9. Ders 23/03/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Şimdi de bütün veritabanı işlemlerinin Python'da bir program içerisinden nasıl yapıldığını göreceğiz. Python'da veritabanı işlemlerinin tipik yapılış biçimi şöyledir: 1) VTYS'ler client-server yazılım mimarisi kullanarak gerçekleştirilmiştir. Dolayısıyla önce programcının (burada programcı client oluyor) VTYS'ye bağlanması gerekir. SQLite aslında bir VTYS gibi kullanılan bir kütüphanedir. Dolayısıyla SQLite'ta gerçek bağlanma söz konusu değildir. Ancak MySQL gibi, SQLServer gibi, Oracle gibi VTYS'lere bağlanılırken "kullanıcı adı ve parola ve gerekli başka bilgilere" gereksinim duyulmaktadır. 2)Programcı bağlantı sağlandıktan sonra SQL komutlarını VTYS'ye gönderir. VTYS de bu komutları çalıştırır. VTYS komutun sonuçları hakkında programcıyı bilgilendirmektedir. Örneğin SQL komutu bir SELECT komutu ise VTYS bize SELECT cümlesinde istenen bilgileri verecektir. 3) VTYS bağlantısı sonlandırılır. VTYS'lerin client-server haberleşmede kullandıkları uygulama katmanı protokolleri farklıdır. Bu protokollerin aşağı seviyeli biçimde uygulanması programcılariçin çok zordur. Dolayısıyla programcıların işlerini kolaylaştırmak için çeşitli yüksek seviyeli kütüphaneler oluşturulmuştur. Genel olarak bu tür kütüphanelere VTYS terminolojisinde "connector" denilmektedir. Python'un yalnızca SQLite için standart kütüphanesinde hazır bir modül bulundurmuştur. Başka bir deyişle Python'un standart kütüphanesinde SQLite için "connector" hazır biçimde bulunmaktadır. Ancak MySql, SQlServer, Oracle, Postgre gibi VTYS'lerin connector'leri Python Standart Kütüphanesinde bulunmamaktadır. Bu VTYS'lerle çalışmak isteyen programcıalrın connector'leri indirerek kurması gerekmektedir. Pekiyi üçüncü parti kütüphane durumunda olan bu VTYS connector'leri kimler tarafından yazılmaktadır? Aslında genellikle connector'ler VTYS'leri oluşturan kurumlar tarafından yazılırlar. Ancak bu bir kural değildir. Örneğin MySQL için farklı connector kütüphaneler vardır. MySQL için connector aşağıdaki gibi pip komutuyla indirilip kurulabilir: pip install mysql-connector-python Ya da örneğin SqlServer için connector de şöyle indirilebilir: pip install pyodbc Genel olarak VTYS'ler için Python connector'lerini yazanlar API tasarımını standart kütüphanedeki SQLite sınıflarını temel alarak yapmaktadırlar. Yani örneğin işlemlerin genel yapılış biçimleri bakımındamn SQLite ile MySQL arasında ya da SqlServer arasında önemli farklılıklar yoktur. Çünkü bu kütüphaneleri yazanlar API tasarımını standart kütüphanedeki "sqlite3" modülündekine benzetmişlerdir. SQLite modülünün dokümantasyonu "Python Standard Library" içerisinde yaplımıştır. Bu dokümantasyona aşağıdaki bağlantıdan erişebilirsiniz: https://docs.python.org/3/library/sqlite3.html #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da SQLite ile işlemler tipik olarak şu adınmlardan geçilerek yapılmaktadır: 1) Önce sqlite3 modülü import edilir. Örneğin: import sqlite3 2) Bundan sonra sqlite3 modülündeki connect fonksiyonu çağrılarak VTYS ile bağlantı sağlanır. (Tabii aslında SQLite gerçek bir VTYS değildir. Ancak API tasarımı sanki SQLite da bir VTYS imiş gibi yapılmıştır.) connect fonksiyonun parametrik yapısı şöyledir: sqlite3.connect(database, timeout=5.0, detect_types=0, isolation_level='DEFERRED', check_same_thread=True, factory=sqlite3.Connection, cached_statements=128, uri=False, *, autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL) Görüldüğü gibi fonksiyonun birinci parametresinin dışındaki parametrelerin hepsi default değer almıştır. Birinci parametre SQLite veritabanı dosyasının yol ifadesini belirtmektedir. Eğer bu parametre için var olmayan bir dosyanın yol ifadesi girilirse o dosya bir SQLite veritabanı dosyası biçiminde oluşturulmaktadır. connect fonksiyonu bize sqlite3 modülü içerisinde bulunan Connection isimli bir sınıf türünden bir nesne vermektedir. Diğer işlemler artık bu Connection nesnesi ile yapılır. Örneğin: conn = sqlite3.connect('school.sqlite') connect fonksiyonuna biz geçersiz dizin içeren yol ifadesi verirsek connect fonksiyonu exception fırlatmaktadır. 3) sqlite3.Connection sınıfının cursor isimli metodu çağrılarak sqlite3.Cursor sınıfı türünden bir cursor nesnesi elde edilir. Örneğin: cur = conn.cursor() Programcı geri kalan işlerini bu cursor nesnesi ile görür. Cursor sınıfının execute ve executemany isimli metotları SQL cümlesini VTYS'ye göndererek ona işlettirmektedir. execute metodunun parametrik yapısı şöyledir: execute(sql, parameters=(), /) Metodun birinci parametresi işletilecek SQL cümlesini belirtmektedir. İkinci parametresi ise ileride ele alacak olduğumuz komut parametrelerine ilişkindir. Örneğin: cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(1783, 'Abit Süzülmüş', 1)") execute metodunda verdiğimiz SQL cümlesi geçersiz ise metot exception fırlatmaktadır. execute komutu ile biz veritabanı üzerinde bir değişiklik yapıyorsak bu değişiklik Connection sınıfının commit metodu çağrılmadan henüz veritabanına yansıtılmaz. Bu nedenle yukarıdaki örnekte "INSERT INTO" SQL komutuyla veritabanına kayıt eklenmiş olsa bile bu ekleme henüz gerçek anlamda yapılmamaktadır. İşlemlerin veritabanına gerçek anlamda yansıtılması için Connection sınıfının commit metodu çağrılmalıdır. commit metodu parametresizdir (yani yalnızca self parametresi vardır). Örneğin: conn.commit() 4) İşlemler bittikten sonra VTYS bağlantısı Connection sınıfının close metoduyla kapatılır. Örneğin: conn.close() 5) Birtakım problemli durumlarda metotlar sqlite3 içerisindeki çeşitli exception sınıflarıyla exception fırlatırlar. Bu exception sınıflarının hepsi sqlite3.Error isimli sınıftan türetilmiş durumdadır. O halde programcı oluşabilecek exception'ları da dikkate alarak kodunu organize etmelidir. Tabii VTYS bağlantısının her durumda kapatılması gerekir. Bu nedenle close metodu tipik olarak try, except bloklarının finally bölümünde çağrılır. Örneğin: import sqlite3 conn = None try: conn = sqlite3.connect('school.sqlite') cur = conn.cursor() cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(1783, 'Abit Süzülmüş', 1)") conn.commit() except sqlite3.Error as e: print(e) finally: if conn: conn.close() Connection sınıfı "bağlam yönetim protokolünü (context management protocol)" desteklemektedir. Dolayısıyla with deyimi ile kullanılabilir. Sınıfın __exit__ metodu zaten bağlantıyı kapatmaktadır. O halde biz yukarıdaki kodu with deyimi ile şöyle de organize edebiliriz: import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(1783, 'Abit Süzülmüş', 1)") conn.commit() except sqlite3.Error as e: print(e) Tabii aslında biz SQL komut yazısını Python'da oluşturup execute metodunu çağırabiliriz: import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() while True: no = int(input('No:')) if no == 0: break name = input('Adı Soyadı:') school_id = input("Okul id'si:") cmd = f"INSERT INTO student(student_no, student_name, school_id) VALUES({no}, '{name}', {school_id})" cur.execute(cmd) conn.commit() except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SQL SELECT cümlesinin Python'da uygulanması üzerinde ayrıca durmak gerekir. Çünkü SELECT cümlesi ile biz VTYS'den kayıt çekmekteyiz. SELECT cümlesi yine diğer cümlelerde olduğu gibi Cursor sınıfının execute metodu ile işletilir. Kayıtlar Cursor sınıfının fetchone ve fetchmany isimli metotlarıyla elde edilmektedir. fetchone metodu koşulu sağlayan kayıtlardan yalnızca bir tanesini, fetchmany ise n tanesini elde etmekte kullanılmaktadır. Tabii bu metotlar birden fazla kez çağrılabilirler. fetchone kaydı tek bir demet olarak fetchmany ise kayıtları bir demet listesi olarak varmektedir. Demetlerin elemanları SELECT komutunda belirtilen sütun sıralarına göredir. execute metodu ile SELECT komutu uygulandığında execute metodu select edilen tüm kayıtları VTYS'den transfer etmeyebilir. Genellikle bu amaçla kullanılan kütüphaneler belirli miktardaki kaydı VTYS'den çekip bir tamponda saklamaktadır. İşte fetch metotları da her çağrıldığında aslında bu tampondan kayıtları vermektedir. Tabii tamponda kayıt kalmadığında arka planda tampon yeniden doldurulmaktadır. Bu mekanizma Cursor nesnesi tarafından arka planda sağlandığı için biz bir döngü içerisinde fetchone ve fetchmany çağrılarıyla tüm select edilen kayıtları elde edebiliriz. Örneğin: import sqlite3 try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no, student_school_id FROM student_info" cur.execute(sqlcmd) while t := cur.fetchone(): name, no, school_id = t print(name, no, school_id) except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no, student_school_id FROM student_info" cur.execute(sqlcmd) while t := cur.fetchone(): name, no, school_id = t print(name, no, school_id) except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Tabii biz SELECT ile join işlemi de yapabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no, school_name, school_city FROM student_info, school_info WHERE student_school_id = school_id" cur.execute(sqlcmd) while t := cur.fetchone(): print(t) except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirttiğimiz gibi SELECT eedilen kayıtlar Cursor sınıfının fetchmany isimli metoduyla da n'er n'er elde edilebilir. Metodun parametrik yapısı şöyledir: fetchmany(size=cursor.arraysize) Burada cursor sınıfının arraysize isimli property elemanı default olarak 1 değerindedir. Dolayısıyla metoda parametre geçilmezse sanki 1 değeri geçilmiş gibi işlem görür. fetchmany metodu bize n tane kaydı bir demet listesi biçiminde vermektedir. Tabii eğer elde n taneden daha az kayıt kalmışsa metot bize kalan kaydın hepsini verecektir. Tüm kayıtlar elde edildikten sonra fetchmany metodu boş bir listeye geri dönmektedir. Örneğin: while rows := cur.fetchmany(5): print(rows) burada fetchmany metodu her çağrıda 5 tane kaydı bir demet listesi biçiminde verecektir. Tabii son listede 5 tane kayıt olmak zorunda değilfitr. Örneğin: try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no FROM student_info WHERE student_no > 600" cur.execute(sqlcmd) while rows := cur.fetchmany(3): for name, no in rows: print(name, no) except sqlite3.Error as e: print(e) Burada bir döngü içerisinde fetchmany metodu çağrılmıştır. fetchmany bize satırları üçer elemanlı listeler biçiminde verecektir. Biz de bu listeyi demet açımı yaparak dolaştık. Aşağıda SELECT edilen tüm kayıtları fetchmany metodu ile alıp ekrana yazdıran bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no FROM student_info WHERE student_no > 600" cur.execute(sqlcmd) while rows := cur.fetchmany(3): for name, no in rows: print(name, no) except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Cursor sınıfının fetchall isimli metodu parametresizdir. Çağrıldığında tüm SELECT edilmiş olan kayıtların hepsi bir demet listesi biçiminde elde edilmektedir. Eğer SELECT edilen hiçbir kayıt yoksa fetchall metodu boş listeye geri dönmektedir. Örneğin: try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no FROM student_info WHERE student_no > 600" cur.execute(sqlcmd) for name, no in cur.fetchall(): print(name, no) except sqlite3.Error as e: print(e) Biz burada fetchall metodu ile select edilen kayıtların hepsini tek hamlede elde edip onları dolaştık fetchone, fetchmany ve fetchall metotları birlikte de kullanılabilir. Örneğin SELECT işlemi sonrasında önce bir fetchone yapıp tek kayıt elde edilebilir, sonra fetchmany yapılıp birkaç kayıt daha elde edilebilir. Sonra da fetchall yapılıp geri kalan tüm kayıtlar elde edilebilir. Aşağıda fetcall metodunun kullanımına ilişkin bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no FROM student_info WHERE student_no > 600" cur.execute(sqlcmd) for name, no in cur.fetchall(): print(name, no) except sqlite3.Error as e: print(e) s #------------------------------------------------------------------------------------------------------------------------------------ Aslında Cursor nesnesinin kendisi de "iterator" nesnesi olarak kullanılabilektedir. Yani biz Cursor nesnesini for döngüsü ile dolaştığımızda yine tek tek kayıtları elde edebilriz. Örneğin: try: with sqlite3.connect('student.db') as conn: cur = conn.cursor() sqlcmd = "SELECT student_name, student_no FROM student_info WHERE student_no > 600" for name, no in cur.execute(sqlcmd): print(name, no) except sqlite3.Error as e: print(e) Cursor nesnesi bir kez dolaşıldıktan sonra yeniden dolaşılamaz. Çünkü artık dolaşımın sonuna gelinmiştir. Cursor nesnesi ile dolaşmak yerine fetchone, fatchmany ya da fetchall metotlarını kullanmak çoğu kez daha uygun olur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 10. Ders 06/04/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir Cursor nesnesi kullanıldıktan sonra Cursor sınıfının close metodu ile onun tuttuğu kaynaklar boşaltılıp kapatılabilir. Ancak açıkça kapatma yapılmadığı durumda Cursor nesnesi çöp durumuna geldiğinde sınıfın __del__ metodu yoluyla zaten nesnenin kaynakları boşaltılıp kapatma işlemi yapılmaktadır. Dolayısıyla çoğu durumda Cursor nesnesinin kapatılmaması bir soruna yol açmamaktadır. Cursor nesneleri "bağlam yönetim protokolüne (resource management protocol)" uymamaktadır. Dolayısıyla Cursor nesnelerini with deyimi ile kullanamayız. Programcı birden fazla Cursor nesnesi ile çalışabilir. Connection sınıfının cursor metodu her çağrıldığında yeni bir Cursor nesnesi elde edilmektedir. Aslında execute metodu (executemany ve executescript metotları da) Cursor nesnesinin kendisine geri dönmektedir. Aşağıdaki gibi bir döngü geçerlidir: for name, no, school_id in conn.cursor().execute("SELECT * FROM student"): print(name, no, school_id) Burada conn.cursor() çağrısı bize bir Cursor nesnesi verir. Sonra o nesne ile execute metodunu çağırdığımızda Cursor nesnesinin yine kendisini elde ederiz. İşte biz de yukarıdaki örnekte bu Cursor nesnesini dolaşmış olmaktayız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ INSERT, UPDATE, DELETE işlemleri sonucunda bu işlemlerden etkilenen kayıt sayısı Cursor sınıfının rowcount isimli örnek özniteliğinden elde edilebilmektedir. Örneğin biz DELETE komutu ile bir grup kaydı sildiğimizde rowcount bize silinen kayıtların sayısını verecektir. rowcount örnek özniteliği SELECT komutu uygulandığında set edilmemektedir. Yani biz SELECT edilen satırların sayısını rowcount ile elde edemeyiz. Aşağıdaki örnekte biz veritabanına 1 kayıt insert ediyoruz. Dolayısıyla rowcount bize 1 değerini verecektir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(2145, 'Fehmi Özışık', 2)") print(cur.rowcount) # 1 conn.commit() except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Cursor sınıfının connection isimli örnek özniteliği bize o Cursor nesnesinin yaratıldığı connection nesnesini vermektedir. Bu sayede eğer elimizde bir Cursor nesnesi varsa biz connection nesnesini de elde edebiliriz. Böylece bizim bir fonksiyona hem Cursor nesnesini hem de Connection nesnesini paramete olarak geçirmemize gerek kalmaz. Zaten Cursor nesnesinden hareketle Connection nesnesini elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def insert_record(cur, student_no, student_name, school_id): sqlcmd = f"INSERT INTO student(student_no, student_name, school_id) VALUES({student_no}, '{student_name}', {school_id})" cur.execute(sqlcmd) cur.connection.commit() try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() student_no = int(input('No:')) student_name = input('Adı Soyadı:') school_id = int(input('School Id:')) insert_record(cur, student_no, student_name, school_id) except sqlite3.Error as e: print(e) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla tablo ile ilgili INSERT işlemleri yapılırken dikkatli olmak gerekir. Örneğin "student" tablosunun bir sütunu "school_id" biçiminde olsun. Bu sütunun da "school" tablosunun bilgilerine erişmek için "foreign key" olarak kullanıldığını düşünelim. Biz şimdi bir öğrenciyi INSERT ederken school tablosunda olmayan bir school_id girmemeliyiz. Bu tür kontoller şüphesiz manuel biçimde school tablosu sorgulanarak yapılabilir. Ancak VTYS'ler bu tür işlemleri kendi içlerinde yapabilmektedir. Bunlara "yabancı anahtar kısıtları (foreign key constraints)" denilmektedir. Yabancı anahtar kısıtları tablo yaratılırken CREATE TABLE komutunda komutun sonunda belirtilmektedir. VTYS'ler arasında bu konuda farklılıklar bulunmaktadır. Örneğin SQLite'ta CREATE TABLE komutunda komutun sonuna aşağıaki gibi kısıt girilebilir: CREATE TABLE student ( ..... FOREIGN KEY (school_id) REFERENCES school(school_id) ) Burada student tablosunaki school_id sütunu school tablosundaki school_id sütunu ile "foreign key" temelinde ilişkilendirilmiştir. Artık biz bir örenciyi eklerken school tablosunda olmayan bir scool_id girersek işlme başarısızlıkla sonuçlanacaktır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bazı tablolarda bazı sütunlar "yabancı anahtar (foreign key)" durumundadır. Öğrenğin school tablosunun school_id elemanı hem PRIMARY KEY durumundadır hem de student tablosu için FOREIGN KEY durumundadır. school tablosuna bir okul eklerken ekleyen kişinin school_id vermesi zor bir kullanımdır. İşte bu tür durumlarda anımsanacağı gibi ilgili sütuna AUTOINCREMENT özelliği verilebilmektedir. AUTOINCREMENT bir sütun söz konusu olduğunda eğer INSERT işleminde bu sütun belirtilmezse bu durumda VTYS en büyük numaranın bir fazlasını almaktadır. SQLite'ta aslında PRIMARY KEY olan tamsayı alanları aynı zamanda AUTOINCREMENT durumdadır. Ancak uygulamacı AUTOINCREMENT belirlemesini yine yapabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bazen veritabanı üzerinde birbirleriyle ilişkili olan işlemler yapılıyor olabilir. Bu tür işlemlerin "ya hep ya hiç" biçiminde gereçekleştirilmesi gerelebilmektedir. İşte VTYS'lerde bir grup eylemin sanki tek bir eylemmiş gibi peşi sıra gerçekleştirilmesine "transaction" denilmektedir. Örneğin bir grup bilginin birbirleriyle tutarlı bir biçimde üç farklı tabloya insert edilmek istendiğini düşünelim. Biz bu üç insert işlemi başarılıysa en sonunda commit yaparak bu işlemlerin veritabanına yansıtılmasını isteriz. Örneğin: INSERT INTO ... INSERT INTO ... INSERT INTO ... COMMIT Pekiyi bu işlemlerin herhangi birinde bir sorun çıkarsa ne olacaktır? Genel olarak bu tür sorunlarda exception oluşacağı için akış commit işlemini görmeyecektir. Dolayısıyla değişikliklerin hiçbiri veritabanına yansıtılmayacaktır. Ancak exception'a yol açmayan önceki INSERT INTO işlemleri eğer geri alınmazsa ilk commit işleminde veritabanına yansıtılır ki bunun sonucunda ilgili bilgi tutarsız bir biçimde tablolara eklenmiş olur. İşte peşi sıra gerçekleştirilen bir grup işlemde bir işlem başarısız olduğunda önceki başarılı olan işlemlerin de geri alınmasına "rollback" denilmektedir. Rollback işlemi Connection sınıfının rollback isimli metoduyla yapılmaktadır. rollback metodunun self parametresinin dışında parametresi yoktur. Eğer rollback metodu çağrıldığında hiçbir transaction içerisinde bulunulmuyorsa metodun bir etkisi olmaz. Yukarıda da belirttiğimiz gibi VTYS'lerde bir grup komutun sanki tek komutmuş gibi "atomik" olarak işletilmesine "transaction" denilmektedir. Eskiden VTYS'ler transcation işlemlerini desteklemiyordu. Sonra VTYS'ler bu özelliklere sahip oldular. Bugün SQLite da dahil olmak üzere VTYS'lerin hemen hepsi "transaction" işlemlerini desteklemektedir. Pek çok VTYS'de transaction işlemleri SQL ile de desteklenmektedir. Tipik olarak transaction BEGIN ya da BEGIN TRANSACTION gibi bir SQL komutuyla başlatılır. Eğer bir sorun oluşmazsa COMMIT işlemi sorun oluşursa ROLLBACK işlemi yapılır. Örneğin: BEGIN TRANSACTION; BEGIN TRY INSERT INTO tablo_adı (kolon1, kolon2) VALUES (değer1, değer2); INSERT INTO tablo_adı (kolon1, kolon2) VALUES (değer3, değer4); INSERT INTO tablo_adı (kolon1, kolon2) VALUES (değer5, değer6); COMMIT; END TRY BEGIN CATCH ROLLBACK; END CATCH; SQL'deki transaction komutları VTYS'den VTYS'ye farklılıklar gösterebilmektedir. Python'da biz çoğu kez transaction'ları SQL komutlarıyla değil metotlarla oluşutururuz. Cursor sınıfının "execute" ve "executemany" isimli metotları eğer bir transaction başlatılmamışsa otomatik olarak transaction'ı başlatılırlar. Yani bizim transaction'ı başlatmak için Python programcısı olarak SQL komutu kullanmamıza ya da özel bir işlem yapmamıza gerek yoktur. Bu durumda tipik bir transaction Python'da şöyle yürütülmelidir: try: cur.execute(...) cur.execute(...) cur.execute(...) cur.commit() except sqlite3.Error as e: conn.rollback() Özel bir durum olarak execute işleminde eğer SELECT komutu uygulanmışsa bu durumda transcation otomatik başlatılmamaktadır. Transaction kavramı ve rollback işlemi birden fazla INSERT, UPDATE ve DELETE komutlarının peşi sıra geldiği durumlarda önemli olmaktadır. Yoksa tek bir INSERT, UPDATE ya da DELETE komutu için rollback uygulamaya gerek yoktur. Yukarıda da belirttiğimiz gibi rollback işlemi bir grup peşi sıra yapılan işlemin bir tanesi başarısız ise onların hiçbirini yapmamayı sağlamaktadır. commit işlemi ise onların hepsini tek bir işlemmiş gibi atomik yapmayı sağlar. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Cursor sınıfının executescript isimli metodu birden fazla SQL cümlesini alarak onları tek tek çalıştırmaktadır. Yani örneğin biz 5 kayıt insert edeceksek onları ayrı ayı INSERT INTO komutu oluşturup execute metodu ile insert etmek yerine tek hamlede executescript metodu ile insert edebiliriz. Örneğin: cur.executescript(""" CREATE TABLE school (school_id INTEGER PRIMARY KEY AUTOINCREMENT, school_name TEXT(64), school_type TEXT(64)); CREATE TABLE student(student_no INTEGER, student_name VARCHAR(64), school_id INTEGER, FOREIGN KEY("school_id") REFERENCES "school"("school_id"), PRIMARY KEY("student_no" AUTOINCREMENT)); """) Biz executescript metodu otomatik transaction başlatmamaktadır. Eğer bu metot ile transcation yapılacaksa SQL komutunun içerisine transaction'ı başlatan BEGIN (ya da BEGIN TRANSACTION) komutu eklenmelidr. Örneğin: cur.executescript(""" BEGIN TRANSACTION; CREATE TABLE person(firstname, lastname, age); CREATE TABLE book(title, author, published); CREATE TABLE publisher(name, address); COMMIT; """) Burada biz üç farklı tablo yaratmak istedik. Eğer bu üç tablonun hepsi yaratılırsa SQL içerisinde commit işlemi yapılmıştır. Eğer burada bu komutlardan herhangi birinde bir sorun oluşursa bu durumda exception fırlatılır biz de bu exception içerisinde rollback yapabiliriz. Örneğin: try: cur.executescript(""" BEGIN; CREATE TABLE person(firstname, lastname, age); CREATE TABLE book(title, author, published); CREATE TABLE publisher(name, address); COMMIT; """) except sqlite3.Error as e: conn.rollback() Tabii yukarıda da belirttiğimiz gibi execute, executemany metotları zaten otomatik trnsaction oluşturmaktadır. Python programcıları da genellikle commit ve rollback gibi işlemleri SQL komutlarının içerisinde değil metot çağrılarıyla yapmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('test.sqlite') as conn: cur = conn.cursor() cur.executescript(""" CREATE TABLE person(person_id INTEGER PRIMARYKEY AUTO_INCREMENT person_name VARCHAR(20), person_no INTEGER); CREATE TABLE book(book_id INTEGER PRIMARYKEY AUTO_INCREMENT book_title VARCHAR(128), book_author VARCHAR(128)); CREATE TABLE WRONG_COMMAND publisher(name, address); """) except sqlite3.Error as e: conn.rollback() print(e) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Bir uygulamada eğer veritabanı zaten yoksa onu yaratan kodlar da uygulamanın içerisinde bulundurulabilir. Yukarıda da belirttiğimiz gibi connect isimli fonksiyon SQLite'ta eğer veribanı dosyası varsa olanı açmakta yoksa içi boş olarak sıfırdan yaratmaktadır. Veritabanı tabloları yaratılırken eğer tablo zaten varsa exception oluşur. Ancak CREATE TABLE komutuna IF NOT EXISTS cümleceği eklenirse eğer tablo varsa komut etki göstermez ancak tablo yoksa yaratılır. Örneğin: def create_tables(cur): cur.executescript(""" CREATE TABLE IF NOT EXISTS school (school_id INTEGER PRIMARY KEY AUTOINCREMENT, school_name TEXT(64), school_type TEXT(64)); CREATE TABLE IF NOT EXISTS student(student_no INTEGER, student_name VARCHAR(64), school_id INTEGER, FOREIGN KEY("school_id") REFERENCES "school"("school_id"), PRIMARY KEY("student_no" AUTOINCREMENT)); """) Burada create_tables fonksiyonu veritabanı tablolarını yaratmaktadır. Ancak tablolar zaten yaratılmışsa buradaki komutların bir etkisi olmayacaktır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 11. Ders 12/04/2025 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda "school" veritabanı üzerinde temel işlemler yapan konsol tabanlı basit bir program örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def create_tables(cur): try: script = """ BEGIN; CREATE TABLE IF NOT EXISTS school(school_id INTEGER PRIMARY KEY AUTOINCREMENT, school_name VARCHAR(128), school_city VARCHAR(32)); CREATE TABLE IF NOT EXISTS student(student_id INTEGER PRIMARY KEY AUTOINCREMENT, student_no INTEGER, student_name VARCHAR(64), school_id INTEGER); """ cur.executescript(script) except Exception as e: print(e) def disp_menu(items_list): while True: for menu_no, (menu_item, _) in enumerate(items_list, 1): print(f'{menu_no}) {menu_item}') print() try: option = int(input('Seçiminiz:')) if option >= 1 and option <= 9: break except: pass print('\nGeçersiz seçenek!..\n') return option def add_school(cur): try: school_name = input('Okul ismi:').strip() if school_name == '': raise ValueError('Okulş ismi boş girilemez') school_city = input('Okulun bulunduğu şehir:').strip() if school_city == '': raise ValueError('Şehir boş girilemez') sql_cmd = f"INSERT INTO school(school_name, school_city) VALUES('{school_name}', '{school_city}')" cur.execute(sql_cmd) cur.connection.commit() print('\nOkul başarılı bir biçimde eklendi...\n') except sqlite3.Error as e: print(f'Eklemede sorun oluştu: {e}\n') except Exception as e: print(f'Hatalı giriş: {e}\n') return True def add_student(cur): try: student_name = input('Öğrencinin adı:').strip() if student_name == '': raise ValueError('Öğrenci boş girildi') student_no = int(input('Öğrencinin numarası:')) school_id = int(input("Öğrencinin okul Id'si:")) sql_cmd = f"INSERT INTO student(student_name, student_no, school_id) \ VALUES('{student_name}', {student_no}, {school_id})" cur.execute(sql_cmd) cur.connection.commit() print('\nÖğrenci başarılı bir biçimde eklendi...\n') except sqlite3.Error as e: print(f'Eklemede sorun oluştu: {e}\n') except Exception as e: print(f'Hatalı giriş: {e}\n') return True def list_student(cur): try: condition = input('Öğrenci için koşul:').strip() sql_cmd = "SELECT student_id, student_name, student_no, school_name, school_city \ FROM student, school WHERE student.school_id = school.school_id" if condition != '': sql_cmd += f" AND {condition}" cur.execute(sql_cmd) print() print(f"{'İd':5s}{'Adı Soyadı':20s}{'No':<5s}{'Okul İsmi':<30s}{'Şehir'}") print('-' * 80) for student_id, student_name, student_no, school_name, school_city in cur.fetchall(): print(f'{student_id:<5d}{student_name:20}{student_no:<5d}{school_name:<30s}{school_city}') print('-' * 80) print() except sqlite3.Error as e: print(f'Aramada sorun oluştu: {e}\n') except Exception as e: print(f'Hatalı giriş: {e}\n') return True def erase_student(cur): try: student_id = int(input("Silinecek öğre3ncinin id'si:")) sql_cmd = f"""SELECT student_id, student_name, student_no, school_name, school_city FROM student, school WHERE student_id = {student_id} AND student.school_id = school.school_id""" cur.execute(sql_cmd) t = cur.fetchone() if t is None: print("\nBu id'ye ilişkin bir öğrenci yok!\n") return student_id, student_name, student_no, school_name, school_city = t print(f'\n{student_id:<5d}{student_name:20}{student_no:<5d}{school_name:<30s}{school_city}\n') confirm = input('Yukarıdaki öğrenciyi silmek istediğine emin misiniz (E)/(H):').lower() if confirm == 'e': sql_cmd = f"DELETE FROM student WHERE student_id = {student_id}" cur.execute(sql_cmd) cur.connection.commit() print('\n1 kayır silindi...\n') else: print() except sqlite3.Error as e: print(f'Aramada sorun oluştu: {e}\n') except Exception as e: print(f'Hatalı giriş: {e}\n') return True def list_school(cur): try: condition = input('Okul için koşul:').strip() sql_cmd = "SELECT school_id, school_name, school_city FROM school"; if condition != '': sql_cmd += f" WHERE {condition}" cur.execute(sql_cmd) print() print(f"{'id':<5s}{'Okul İsmi':<40s}{'Şehir'}") print('-' * 80) for school_id, school_name, school_city in cur.fetchall(): print(f'{school_id:<5d}{school_name:<40s}{school_city}') print('-' * 80) print() except sqlite3.Error as e: print(f'\nListelemede sorun oluştu: {e}\n') except Exception as e: print(f'\nHatalı giriş: {e}\n') return True def erase_school(cur): try: school_id = int(input("Silinecek okulun id'si:")) sql_cmd = f"SELECT school_id, school_name, school_city FROM school WHERE school_id = {school_id}" cur.execute(sql_cmd) t = cur.fetchone() if t is None: print("\nBu id'ye ilişkin bir okul yok!\n") return school_id, school_name, school_city = t print(f'\n{school_id:<5d}{school_name:<40s}{school_city}\n') confirm = input('Bir okulu sildiğiniz zaman o okuldaki tüm öğrencileri de silersiniz' + \ 'Yukarıdaki okulu silmek istediğine emin misiniz (E)/(H):').lower() if confirm == 'e': try: sql_cmd = f""" BEGIN TRANSACTION; DELETE FROM student WHERE school_id = {school_id}; DELETE FROM school WHERE school_id = {school_id}; COMMIT; """ cur.executescript(sql_cmd) except sqlite3.Error as e: cur.connection.rollback() print(f'Silme işleminde hata oluştu: {e}\n') print('\n1 kayıt silindi...\n') else: print() except sqlite3.Error as e: print(f'Aramada sorun oluştu: {e}\n') except Exception as e: print(f'Hatalı giriş: {e}\n') return True def exit_prog(cur): return False def main(): items_list = [ ('Okul ekle', add_school), ('Öğrenci ekle', add_student), ('Öğrenci listele', list_student), ('Okul listele', list_school), ('Öğrenci sil', erase_student), ('Okul sil', erase_school), ('Çıkış', exit_prog), ] try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() create_tables(cur) while True: option = disp_menu(items_list) _, f = items_list[option - 1] if not f(cur): break except sqlite3.Error as e: print(f'DB Error: {e}') except Exception as e: print(e) main() #------------------------------------------------------------------------------------------------------------------------------------ 12. Ders 13/04/2025 - Pazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pek çok VTYS kütüphanesi SQL komutlarında "yer tutucu" ya da başka bir deyişle "parametre" kullanımına izin vermektedir. Python'da yer tutucular iki biçimde kullanılmaktadır: "?" biçiminde ya da ":isim" biçiminde. "?" yer tutucusu isimsizdir. Ancak ":isim" yer tutucusu isimlidir. Bu yer tutucular komut içerisinde bir öğe gibi kullanılabilirler. Örneğin: sqlcmd = "INSERT INTO student(student_no, student_name) VALUES(?, ?)" Burada ilk "?" student_no için yerleştirilecek değeri, ikinci "?" ise student_name için yerleştirilecek değeri belirtmektedir. Aynı komut şöyle de oluşturulabilirdi: sqlcmd = "INSERT INTO student(student_no, student_name) VALUES(:no, :name)" Burada :no yer tutucusu student_no için, :name yer tutucusu ise student_name için yerleştirilecek değeri belirtmektedir. Pekiyi bu yer tutuculara değerleri nasıl yerleştirilmektedir? İşte aslında Cursor sınıfının execute metodu işletilecek SQL cümlesinin yanı sıra ikinci parametresi ile yer tutuculara yerleştirilecek değerleri de bizden istemektedir. Eğer yer tutucular "?" işareti ile belirtilmişse bu durumda execute metodunun ikinci parametresi bir demet ya da liste olmalıdır. Demet ya da liste içerisindeki elemanlar sırasıyla SQL komutundaki "?" yer tutucularının yerine yerleştirilmektedir. Örneğin: sqlcmd = "INSERT INTO student(student_no, student_name, school_id) VALUES(?, ?, ?)"; cur.execute(sqlcmd, (523, 'Rasim Özcan', 3)) conn.commit() Burada 525 değeri ilk "?" yerine, "Rasim Özcan" değeri ikinci "?" yerine ve 3 değeri ise üçüncü "?" yerine yerleştirilir. execute metodu bu yerleştirmeyi yaptıktan sonra komutu VTYS'ye göndermektedir. Yerleştirme yapıldıktan sonra SQL komutu şu hale gelecektir: "INSERT INTO student(student_no, student_name, school_id) VALUES(523, 'Rasim Özcan', 3)" Yazsısal değerlerin SQL komutunda tek tırnak içerisine alınmasını execute metodu zaten sağlamaktadır. Eğer yer tutucular isimliyse bu durumda execute metodunun ikinci parametresi bir sözlük nesnesi olmak zorundadır. Sözlüğün anahtarları yer tutucuların isimlerinden değerleri ise ter tutuculara yerleştirilecek değerlerden oluşur. Tabii artık yer tutucuların girilme sırasının bir önemi kalmamaktadır. Örneğin: sqlcmd = "INSERT INTO student(student_no, student_name, school_id) VALUES(:no, :name, :sid)"; cur.execute(sqlcmd, {'no': 523, 'name': 'Rasim Özcan', 'sid': 3}) conn.commit() Buradaki isimlerde ':' karakterinin kullanılmadığına dikkat ediniz. Pekiyi yer tutucu kullanmanın ne avantajı vardır? İşte her defasında yeniden bir komut yazısı oluşturmak yerine yer tutucularla bir tane komut yazısı oluşturup hep onu kullanmak çok daha pratik ve etkindir. Yani programcı işin başında programında gerekli SQL komutlarını yer tutucularla oluşturur sonra da onlar için değerleri execute metodunda belirtir. Bir kez daha anımsatmak istiyoruz: Örneklerden de gördüğünüz gibi yer tutucu eğer bir string ise onu tek tırnak içerisine programcı almamaktadır. Yer tutucuyu yerleştiren execute metodu bunu yapmaktadır. Yer tutucuların işlevlerinin Python'a son versiyonlarla eklenen f'li string'lerle (string enterpolasyonuyla) sağlanabileceğini düşünebilirsiniz. Ancak f'li string'ler programın akışı o noktaya geldiğinde bir kez oluşturulmaktadır. Örneğin: student_no = 123 sql_cmd = f"SELECT * FROM student WHERE student_no = {student_no}" print(sql_cmd) # SELECT * FROM student WHERE student_no = 123 student_no = 321 print(sql_cmd) # SELECT * FROM student WHERE student_no = 123 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ execute ve executescript metotlarının dışında Cursor sınıfının bir de executemany isimli metodu vardır. Bu metodun kullanılabilmesi için SQL komut yazısının yer tutucuyla oluşturulması gerekir. executemany metodunun ikinci parametresi demetlerden ya da sözlüklerden oluşan dolaşılabilir bir nesne olabilir. Metot çağrıldığında dolaşılabilir nesnedeki elemanlar tek tek yer tutuculara yerleştirilip komut birden fazla kez uygulanmaktadır. Örneğin: sql_cmd = "INSERT INTO student(student_name, student_no, school_id) VALUES(?, ?, ?)" cur.executemany(sql_cmd, [('Tayyar Altınkulaç', 456, 3), ('Salim Dündar', 632, 2)]) conn.commit() Yukarıdaki executemany birden fazla execute ile aynı işleve sahiptir: sql_cmd = "INSERT INTO student(student_name, student_no, school_id) VALUES(?, ?, ?)" cur.execute(sql_cmd, ('Tayyar Altınkulaç', 456, 3)) cur.execute(sql_cmd, ('Salim Dündar', 632, 2)) conn.commit() Yer tutucular isimliyse executemany metodunda bir sözlük listesi ya da sözlüklerden oluşan bir demet (aslında genel olarak sözlüklerden oluşan dolaşılabilir bir nesne) verilebilir. Örneğin: sql_cmd = "INSERT INTO student(student_name, student_no, school_id) VALUES(:name, :no, :sid)" cur.executemany(sql_cmd, [{'name': 'Gökhan Abur', 'no': 387, 'sid': 5}, {'name': 'Timur Selçuk', 'no': 743, 'sid': 2}]) conn.commit() executemany sayesinde bazı işlemler pratik biçimde yapılabilmektedir. Örneğin bir CSV dosyasından okuma yapan standart csv modülündeki reader fonksiyonu bize dolaşılabilir bir nesne verir. Sonra her dolaşımda CSV dosyasındaki bir satır elde edilir. Biz doğrudan bu reader fonksiyonunun verdiği nesneyi executemany metodunun ikinci parametresine geçirebiliriz. Örneğin: import sqlite3 import csv try: with sqlite3.connect('school.sqlite') as conn, open('student.csv') as f: cur = conn.cursor() sql_cmd = "INSERT INTO student(student_name, student_no, school_id) VALUES(?, ?, ?)" cur.executemany(sql_cmd, csv.reader(f)) conn.commit() except sqlite3.Error as e: print(e) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tabii yer tutucular yalnızca INSERT INTO komutunda değil diğer komutlarda da kullanılabilmektedir. Örneğin: import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() sqlcmd = "DELETE FROM student WHERE student_no == ?"; cur.executemany(sqlcmd, [(9981, ), (8984, )]) conn.commit() except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden de belirttiğimiz gibi VTYS'lerin pek çok built-in fonksiyonları vardır. Bu fonksiyonları sorgulamalarda kullanabiliriz. Aşağıda built-in length fonksiyonunun kullanımına ilişkin bir örnek görüyorsunuz: sql_cmd = "SELECT * FROM student WHERE length(student_name) = ?" cur.execute(sql_cmd, (10, )) students = cur.fetchall() for student in students: print(student) Burada adı soyadı 10 karakter olan öğrencilerin listesi elde edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() sql_cmd = "SELECT * FROM student WHERE length(student_name) = ?" cur.execute(sql_cmd, (10, )) students = cur.fetchall() for student in students: print(student) except sqlite3.Error as e: print(e) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Tabii biz SELECT cümlesinde veirtabanındaki sütun isimlerini bu fonksiyonlara sokarak sütun isimleri yerine bunların çıktılarını da elde edebiliriz. Örneğin "school.sqlite" veritabaınındaki öğrencilerin ad ve soyadlarının uzublukları aşağıdaki gibi elde edilebilir: sql_cmd = "SELECT length(student_name) FROM student" cur.execute(sql_cmd) students = cur.fetchall() for student in students: print(student) #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() sql_cmd = "SELECT length(student_name) FROM student" cur.execute(sql_cmd) students = cur.fetchall() for student in students: print(student) except sqlite3.Error as e: print(e) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Bazı built-in fonksiyonlar pek çok VTYS'de aynı biçimde bulunmaktadır. Ancak pek çok fonksiyon da VTYS'ye özgüdür. Bu nedenle hangi VTYS'de çalışıyorsanız o VTYS'ye özgü fonksiyonları o VTYS'nin kendi dokümanlarından elde edebilirsiniz. Kendimizin yazdığı bir Python fonksiyonunu SQL komutunda da kullanabiliriz. Bunun için kullanılacak olan fonksiyonun Connection nesnesinin create_function isimli metoduyla yaratılması gerekir. create_function metodunun parametrik yapısı şöyledir: create_function(name, narg, func, *, deterministic=False) Metodun birinci parametresi fonksiyonun SQL içerisinden kullanılacak ismini belirtmektedir. Bu ismin fonksiyonun gerçek ismiyle aynı olması gerekmez. İkinci parametre fonksiyonun parametre sayısını, üçüncü parametre ise Python fonksiyon nesnesini belirtmektedir. Biz bu üçüncü parametreye çağrılmasını istediğimiz fonksiyonun ismini girebiliriz. create_function fonksiyonunu çağırdıktan sonra artık biz belirlediğimiz bu fonksiyon ismini SQL komutunda kullanabiliriz. Fonksiyon SQL komutunda kullanıldığında komutta belirtilen sütun bilgileri belirlenen fonksiyona parametre olarak aktarılacaktır. Örneğin biz komutta bu fonksiyona student_name ve student_no sütunlarını argüman olarak verirsek student_name sütunu fonksiyona str olarak, student_no sütunu ise int olarak aktarılacacaktır. Bu biçimde belirlediğimiz fonksiyon SQL komutu uygulandığında fonksiyonun geri döndürdüğü değer komutta kullanılmaktadır. SQL büyük harf küçük harf duyarlılığı olan (case sensitive) bir dil değildir. Yani örneğin biz create_function metoduna fonksiyon ismini küçük harfle versek bile onu komut içerisimde büyük harfle kullanabiliriz. Örneğin: sql_cmd = "SELECT myfunc(student_name), student_no FROM student" Bu SQL cümlesi çalıştırıldığında student_name sütunundaki her isim önce myfunc fonksiyonuna parametre olarak geçirilecek, sonra bu fonksiyonun geri dönüş değeri elde edilip SELECT tarafından işlenecektir. Aşağıdaki örnekte foo isimli fonksiyon SQL içerisinde "myfunc" ismiyle kullanılmaktadır. Bu örnekte öürencinin ismi ve numarası birleştirilerek tek bir sütunmuş gibi elde edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def foo(name, no): return name + ', ' + str(no) try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() sql_cmd = "SELECT myfunc(student_name, student_no) FROM student" conn.create_function('myfunc', 2, foo) cur.execute(sql_cmd) students = cur.fetchall() for name, in students: print(name) except sqlite3.Error as e: print(e) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte zaten var olan length built-in fonksiyonunun mylength ismiyle başka bir versiyonu yazılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def mylength(name): return len(name) try: with sqlite3.connect('school.sqlite') as conn: conn.create_function('mylength', 1, mylength) cur = conn.cursor() sqlcmd = "SELECT student_name FROM student WHERE mylength(student_name) == 10" for n, in cur.execute(sqlcmd): print(n) except sqlite3.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Tabii VTYS'de zaten var olan built-in fonksiyonları gereksiz bir biçimde yeniden yazmayınız. Çalıştığınız VTYS'deki built-in fonksiyonların listesini dokümanlardan elde edip incelemelisiniz. Örneğin substr isimli SqLite fonksiyonu belli bir indeksten itibaren (ilk indeks 1'dir) n tane karakteri elde etmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() for record in cur.execute("SELECT upper(student_name) FROM student WHERE substr(lower(student_name), 1, 2) = 'ka'"): print(record) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Connection sınıfının text_factory isimli örnek özniteliği bir Python fonksiyonu alır. Select edilen yazısal alanlar için o sütunun değerini önce bu fonksiyona parametre yaparak fonksiyonu çağırır. Fonksiyonun geri dönüş değerini bize sanki select edilen değer gibi verir. Ancak her zaman yazısal alanlar fonksiyona bytes nesnesi olarak geçirilmektedir. text_factory isimli örnek özniteliğine girilen fonksiyon yalnızca yazısal sütunlar için devreye girmektedir. Sayısal sütunlar için devreye girmemektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def foo(name): return '"' + str(name, encoding='utf-8') + '"' try: with sqlite3.connect('school.sqlite') as conn: conn.text_factory = foo cur = conn.cursor() for school_id, school_name, school_type in cur.execute("SELECT school_id, school_name, school_type FROM school"): print(school_id, school_name, school_type) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ text_factory örnek özniteliği default durumda str fonksiyonunu almaktadır. Yani SELECT edilen yazısal sütunlar str fonksiyonuna sokulmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: conn.text_factory = str # gerek yok, default durum zaten böyle cur = conn.cursor() for school_id, school_name, school_type in cur.execute("SELECT school_id, school_name, school_type FROM school"): print(school_id, school_name, school_type) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Connection sınıfının row_factory isimli örnek özniteliği aslında text_factory örnek özniteliğinin daha genel bir biçimidir. SELECT edilen tüm değerler bir demet nesnesi olarak burada girilen fonksiyonun ikinci parametresine geçirilir. Programcı SELECT edilen değerleri almak istediğinde artık ona bu değerler bir demet biçiminde değil bu fonksiyonun geri dönüş değeri ile belirtilen biçimde verilecektir. row_factory örnek özniteliğine girilen fonksiyon her zaman iki parametreli olmalıdır. Fonksiyonun birinci parametresine cursor nesnesi, ikinci parametresine ise SELECT edilen satır bilgileri demet olarak geçirilmelidir. Yani SELECT işlemi yapıldığında SEELCT edilen satır önce row_factory ile belirtilen fonksiyona geçirilir, sonra bu fonksiyondan elde edilen değer adeta SELECT işleminin sonucu gibi verilmektedir. Aşağıdak,i örnekte "sakila" veritabanında altı karakterli seşhirler ve onların ilişkin olduğu ülkeler elde edilmiştir. Ancak elde edilen kayıtlar önce foo fonksiyonuna sokulmuş, foo fonksiyonundan elde edilen değer sanki SELECT cümlesinden elde edilmiş gibi işlem görmüştür. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def foo(cur, row): return str(row[0]) + ' --- ' + str(row[1]) try: with sqlite3.connect('sakila.sqlite') as conn: conn.row_factory = foo cur = conn.cursor() for row in cur.execute("SELECT city, country FROM city, country WHERE city.country_id = country.country_id AND length(city) = 6 "): print(row) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Cursor nesnesinin description isimli örnek özniteliği son SELECT işlemindeki select edilen sütunların isimlerini bize vermektedir. Ancak bir nedenden dolayı bu örnek özniteliği demetlerden oluşan bir demet verir. Bu demetlerin yalnızca ilk elemanında sütun bilgisi string olarak bulunur. Diğer elemanları None değerdedir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('sakila.sqlite') as conn: cur = conn.cursor() cur.execute("SELECT city, country_id FROM city WHERE length(city) = 6") for t in cur.description: print(t[0]) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Select edilen bilgileri bir demet olarak değil de bir sözlük nesnesi olarak da elde edebiliriz. Bu durum bazen istenmektedir. Bu işlemi yapabilmek için connection nesnesinin row_factory örnek özniteliğine bir fonksiyon gireriz. Fonksiyon içerisinde Curor sınıfının description örnek özniteliğinden faydalanarak işlemimizi yapabiliriz. Aşağıdaki örnekte "sakila" veritabanında ismi 6 karakter uzunluğunda olan şehirler ve onların ilişkin olduğu ülkeler bir sözlük nesnesi biçiminde SELECT işleminden elde edilmiştir. Fonksiyon içerisinde "sözlük içlemi" uygulanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def rowdict(cur, row): return {cur.description[index][0]: val for index, val in enumerate(row)} try: with sqlite3.connect('sakila.sqlite') as conn: conn.row_factory = rowdict cur = conn.cursor() cur.execute("SELECT city, country FROM city, country WHERE city.country_id = country.country_id AND length(city) = 6") for d in cur.fetchall(): print(d['city'], d['country']) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki örnekte fonksiyon sözlük içlemi olmadan da yazılabilirdi. Aşağıda alternatif bir yazım örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 def rowdict(cur, row): d = {} for index, val in enumerate(row): d[cur.description[index][0]] = val return d try: with sqlite3.connect('sakila.sqlite') as conn: conn.row_factory = rowdict cur = conn.cursor() cur.execute("SELECT city, country FROM city, country WHERE city.country_id = country.country_id AND length(city) = 6") for d in cur.fetchall(): print(d['city'], d['country']) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Tabii aslında text_factory ve row_factory örnek öznitelikleri için herhangi bir callable nesne kullanabiliriz. Örneğin bu amaçla fonksiyon yerine __call__ operatör metodu yazılmış olan sınıf nesnelerini kullanmak durumsal bilgiyi nesneler tutabildikleri için daha geniş olanaklar sunmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 class RowFactory: def __init__(self, text): self.text = text def __call__(self, cur, row): return f'{self.text}: {row[0]} ---> {row[1]}' try: with sqlite3.connect('sakila.sqlite') as conn: conn.row_factory = RowFactory('result') cur = conn.cursor() cur.execute("SELECT city, country FROM city, country WHERE city.country_id = country.country_id AND length(city) = 6") for s in cur.fetchall(): print(s) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Aslında select edilen nesneleri sözlük olarak vermek için özel bir sqlite3.Row sınıfı hazır bulunakmatdır. Tek yapılacak şey row_factory örnek özniteliğine bu sınıfın ismini girmektir. sqlite3.Row sınıfı tamamen bir sözlük nesnesi gibi davranmaktadır. sqlite. Row sınıfı SELECT edilen elemanlara sözlük biçiminde erişmek için özel olarak hızlı çalışacak biçimde yazılmıştır. Aşağıdaki örnekte SELECT işleminden elde edilen nesneler aslında sqlite3.Row sınıfı türünden nesnelerdir. Ancak bu Row nesneleri sözlük givi davranmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('sakila.sqlite') as conn: conn.row_factory = sqlite3.Row cur = conn.cursor() cur.execute("SELECT city, country FROM city, country WHERE city.country_id = country.country_id AND length(city) = 6") for row in cur.fetchall(): print(row['city'], row['country']) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Aslında sqlite3.Row sınıfının bir benzeri aşağıdaki gibi kolay biçimde yazılabilir. Burada biz MyRow sınıfının bir sözlük gibi davranabilmesi için onun __getitem__ metodunu yazdık. Bu örnekte her kayıt SELECT edildiğinde aslında MyRow(...) çağrısı yapılacağı için bize MyRow sınıfı üründen bir nesne verilecektir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 class MyRow: def __init__(self, cur, row): self.rowdict = {cur.description[index][0]: val for index, val in enumerate(row)} def __getitem__(self, key): return self.rowdict[key] try: with sqlite3.connect('sakila.sqlite') as conn: conn.row_factory = MyRow cur = conn.cursor() cur.execute("SELECT city, country FROM city, country WHERE city.country_id = country.country_id AND length(city) = 6") for mr in cur.fetchall(): print(mr['city'], mr['country']) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Bir kayıt insert ederken o kaydın içerisinde foreign key olabilir. Bu durumda bu foreign key değerinin diğer tablodan SELECT edilmesi gerekir. Bu işlem INSERT INTO içerisinde SELECT kullanılarak pratik bir biçimde yapılabilmektedir. Örneğin: cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(?, ?, (SELECT school_id FROM school WHERE school_name = ?))", (no, name, school)) Tabii foreign key alanına ilişkin bilgi ilgili tabloda bulunmayabilir. Bu durumda önce o kaydın ilgili tabloya insert edilmesi gerekebilir. Bu tür durumlarda iki tabloya insert yapmak tasarım olarak iyi bir fikir değildir. Kullanıcıdan tablolardaki kayıtları ayrı ayrı girmesi istenmelidir. Ancak yine de bu tür durumlar için özel INSERT INTO komutları da bulundurulmuştur. Örneğin SQLite'te bir sütun yaratılırken UNIQUE belirlemesi yapılmışsa artık biz o sütun için INSERT OR IGNORE INTO komutunu kullanabiliriz. Bu komut o sütunda zaten o değer varsa kaydı hiç insert etmemektedir. Diğer VTYS'lerde de buna benzer INSERT INTO komutları bulunmaktadır. Aşağıdaki örnekte öğrencinin okulu önce okul tablosuna INSERT OR IGNORE INTO ile eklenmiş daha sonra buradaki değer alınarak student tablosuna çğrenci bilgisi kaydedilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 try: with sqlite3.connect('school.sqlite') as conn: cur = conn.cursor() while True: no = int(input('No:')) if not no: break name = input('Adı Soyadı:') school = input('Okulu:') cur.execute("INSERT OR IGNORE INTO school(school_name) VALUES(?)", (school, )) cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(?, ?, (SELECT school_id FROM school WHERE school_name = ?))", (no, name, school)) conn.commit() for row in cur.execute("SELECT student.student_no, student.student_name, school.school_name FROM student, school WHERE student.school_id = school.school_id"): print(row) except Exception as e: conn.rollback() print(e) #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla tabloya foreign key eşliğinde insert işlemi VTYS'lerin bu amaçla bulundurulmuş özel fonksiyonları kullanılarak da yapılabilir. Örneğin SQLite'ta last_insert_rowid isimli fonksiyon son insert işlemindeki PRIMARY KEY değerini bize vermektedir. Dolayısıyla biz önce bir tabloya insert işlemi yapıp foreign key değerini bu yolla elde edebiliriz. Örneğin: cur.execute("INSERT INTO school(school_name) VALUES(?)", (school, )) cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(?, ?, last_insert_rowid())", (no, name)) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SqLite'da bellek tabanlı veritabanları oluşturulabilir. Bunun için dosya ismi yerine ":memory:" biçiminde özel bir yazı girilmelidir. Bu biçimde oluşturulan veritabanı tabii ki boştur. Dolayısıyla bizim tabloları ekleyip kayıtları insert etmemiz gerekir. Ancak program sonlandığında bellekte yaratılmış olan tüm bilgiler yok olacaktır. Çünkü o bilgiler diskte bir dosyada değil bellekte bulunmaktadır. Aşağıdaki tamamen bellekte bir veritabanıu oluşturulup ona kayıtlar eklenmniştir. Sonra da SELECT işlemi yapılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 script = """CREATE TABLE student(student_id INTEGER, student_no INTEGER, student_name VARCHAR(64), PRIMARY KEY(student_id)); INSERT INTO student(student_no, student_name) VALUES(123, 'Ali Serçe'); INSERT INTO student(student_no, student_name) VALUES(456, 'Kaan Aslan'); INSERT INTO student(student_no, student_name) VALUES(289, 'Ayşe Er'); """ try: with sqlite3.connect(':memory:') as conn: cur = conn.cursor() cur.executescript(script) conn.commit() for row in cur.execute("SELECT * FROM student"): print(*row) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Bellekte oluşturulan bir veritabanını belli bir noktada Connection sınıfının backup isimli metodu ile bir veritabanı dosyası biçiminde de save edebiliriz. Ancak backup fonksiyonu bizden doaysnın ismini değil bir Connection nesnesi istemektedir. O halde bizim sıfır bir veritabanını sqlite3.connect fonksiyonuyla yaratıp, yeni bir Conenction nesnesi elde edip onu kullanmamız gerekir. Aşağıdaki örnekte bellek tabanlı bir SqLite veritabanı backup metoduyla disk tabanlı bir biçimde save edilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 script = """CREATE TABLE student(student_id INTEGER, student_no INTEGER, student_name VARCHAR(64), PRIMARY KEY(student_id)); INSERT INTO student(student_no, student_name) VALUES(123, 'Ali Serçe'); INSERT INTO student(student_no, student_name) VALUES(456, 'Kaan Aslan'); INSERT INTO student(student_no, student_name) VALUES(289, 'Ayşe Er'); """ try: with sqlite3.connect(':memory:') as conn: cur = conn.cursor() cur.executescript(script) conn.commit() with sqlite3.connect('backup.sqlite') as conn_backup: conn.backup(conn_backup) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ 12. Ders 18/01/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi bellekte bir veritabanı oluşturmanın ne anlamı olabilir. İşte biz bu sayede SQL'in bize sunduğu arama faaliyetlerini SELECT cümleleriyle yapabiliriz. Aşağıdaki örnekte belli bir kök dizinden hareket edilerek dizin ağacı dolaşılıp dosyaların bilgileri bellekte oluşturulmuş olan bir veritabanına kaydedilmiştir. Sonra da SELECT işlemi yapılarak belli koşulu sağlayan dosya bilgileri elde edilmiştir. Bu örnekte bir iki tabloya sahip bir veritabanı yaratmış olduk. Tablo bilgileri şöyledir: file Tablosu: file_id file_name file_size directory_id directory Tablosu: directory_id directory_path Biz bu programda bir dizin elde ettiğimizde o dizini directory tablosuna o dizninin içerisindeki dosyaları da file tablosuna yerleştirdik. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 import os ROOT_PATH = '/Users/kaanaslan/Dropbox/Shared/Kurslar/Python-App' script = """ CREATE TABLE file(file_id INTEGER PRIMARY KEY AUTOINCREMENT, file_name VARCHAR(256), file_size INTEGER, directory_id INTEGER); CREATE TABLE directory(directory_id INTEGER PRIMARY KEY AUTOINCREMENT, directory_path VARCHAR(1024)); """ try: with sqlite3.connect(':memory:') as conn: cur = conn.cursor() cur.executescript(script) conn.commit() for dirname, subdirs, files in os.walk(ROOT_PATH): cur.execute("INSERT INTO directory(directory_path) VALUES(?)", (dirname, )) for file in files: cur.execute("INSERT INTO file(file_name, file_size, directory_id) VALUES(?, ?, last_insert_rowid())", (file, os.path.getsize(f'{dirname}/{file}'))) cur.execute("SELECT file_name, file_size, directory_path FROM file, directory WHERE file.directory_id = directory.directory_id AND file_name LIKE 'sample%'") for file_name, file_size, directory_path in cur.fetchall(): print(file_name, file_size, '----->', directory_path) print() except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Normal durum önce Connection nesnesinin yaratılması, sonra bu connection nesnesinden Cursor nesnesinin yaratılması ve işlemlerin bu cursor nesnesiyle yapılmasıdır. Ancak kolaylık olsun diye Connection sınıfının içerisine de execute, axecutemany ve executescript metotları eklenmiştir. Aslında bu metotlar kendi içerisinde Cursor nesnesi yaratıp bu nesne ile ilgili metodu uygulamaktadır. Bu execute metotları kendi yarattıkları Cursor nesnesine geri dönerler. #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 script = """CREATE TABLE student(student_id INTEGER, student_no INTEGER, student_name VARCHAR(64), PRIMARY KEY(student_id)); INSERT INTO student(student_no, student_name) VALUES(123, 'Ali Serçe'); INSERT INTO student(student_no, student_name) VALUES(456, 'Kaan Aslan'); INSERT INTO student(student_no, student_name) VALUES(289, 'Ayşe Er'); """ try: with sqlite3.connect(':memory:') as conn: conn.executescript(script) conn.commit() for row in conn.execute("SELECT * FROM student"): print(row) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Python'da MySQL ile işlemler ana hatlarıyla tamamen SQLite'a benzer biçimde yapılmaktadır. Ancak MySQL "connector"ü Python standart kütüphanesinde bulunmamaktadır. Öncelikle bizim yerel makinemize MySQL için bir connector indirip kurmamız gerekir. Python'da MySql için çeşitli üçüncü parti paketler bulunmaktadır. En yaygın kullanılanı mysql-connector-python isimli pakettir. Bu paketin kullanımı SQLite'taki genel kullanıma çok benzerdir. Bu paket şöyle kurulabilir: pip install mysql-connector-python Biz kursumuzda bu paketin kurulmuş olduğunu varsayacağız. Tabii bizim bir MySQL VTYS'sine erişebilir durumda olmamaız da gerekmektedir. MySQL'i sitesinden indirip kurabilrisiniz. Kurulan bu programa "MySQL server programı" denilmektedir. MySQL kurulurken "root" isimli ana bir kullanıcı için bir parola istenmektedir. MySQL gerçek bir VTYS olduğundan farklı kullanıcılar yaratılıp onların biribirinden izole edilmesi sağlanabilmektedir. Tabii MySQL'in yerel makine de bulunuyor olması gerekmez. VTYS'ler uzaktan bağlantılara izin vermektedir. Bir MySQL VTYS'sine bağlanmak için mysql.connector modülündeki connect fonksiyonu kullanılır. connect fonksiyonunda şu parametreler girilmelidir: host: VTYS'nin IP adresini belirtir. Eğer yerel makinedeki MySQL server'a bağlanılacaksa bu parametre "localhost" biçiminde girilebilir. user: MySQL server'a her bağlanacak kullanıcının bir kullanıcı ismine sahip olması gerekmektedir. Yukarıda da belirttiğimiz gibi "root" isimli kullanıcı kurulum sırasında yaratılmaktadır. password: Kullanıcıya ilişkin parola bilgisini belirtir. database: VTYS'deki hangi veritabanının kullaılacağını belirtir. host='localhost', user='root', password='csd1993', database='world' MySQL'de tüm exception'lar mysql.connector modülündeki Error sınııfndan türetilmiş sınıflarla temsil edilmektedir. Aşağıdaki örnekte tipik olarak MySql Server'a bağlantının nasıl yapıldığı görülmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import mysql.connector as mysqlc try: with mysqlc.connect(host='localhost', user='root', password='csd1993', database='world') as conn: cur = conn.cursor() cur.execute("SELECT * FROM city WHERE CountryCode='TUR'") for row in cur.fetchall(): print(row) except mysqlc.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ mysql'deki Cursor sınıfının execute metodu cursor nesnesinin kendisine değil None değerine geri dönmektedir. Cursor nesnesiyle dolaşım yaparken dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import mysql.connector as mysqlc try: with mysqlc.connect(host='localhost', user='root', password='csd1993', database='world') as conn: cur = conn.cursor() cur.execute("SELECT * FROM city WHERE CountryCode='TUR'") for row in cur: print(row) except mysqlc.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Insert işlemi de benzer biçimdedir. Yine deafult durumda insert işleminden sonra commit uygulamak gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import mysql.connector as mysqlc try: with mysqlc.connect(host='localhost', user='root', password='kaanaslan', database='school') as conn: cur = conn.cursor() cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(196565, 'Fehmi Özcan', 1)") conn.commit() except mysqlc.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Çeşitli connector'lerde yer tutucular farklı biçimlerde oluşturulabilmektedir. Örneğin mysql-connector-python connector'ü üzerinde yer tutucu olarak %s karakterleri kullanılmaktadır. Yer tutuculara karşılık demetler getirilebilir. Ya da sözlük nesneleri geetirilebilir. Eğer sözlük kullanılacaksa sözlüğün anahtarları %(isim)s biçiminde oluşturulmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import mysql.connector as mysqlc try: with mysqlc.connect(host='localhost', user='root', password='kaanaslan', database='school') as conn: cur = conn.cursor() cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(%s, %s, %s)", (7643, 'Rasim Özcan', 1)) conn.commit() cur.execute("INSERT INTO student(student_no, student_name, school_id) VALUES(%(no)s, %(name)s, %(id)s)", {'no': 7269, 'name': 'Hasan Umut', 'id': 2}) conn.commit() except mysqlc.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ MySql'de de INNER JOIN işlemleri benzer biçimde yapılır. #------------------------------------------------------------------------------------------------------------------------------------ import mysql.connector as mysqlc try: with mysqlc.connect(host='localhost', user='root', password='kaanaslan', database='school') as conn: cur = conn.cursor() cur.execute("SELECT student_no, student_name, school_name FROM student, school WHERE student.school_id = school.school_id") for record in cur: print(record) except mysqlc.Error as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Homework 3-1'in Çözümü #------------------------------------------------------------------------------------------------------------------------------------ import sqlite3 import statistics import PIL import IPython import io def disp_menu(): while True: print('1) Ders Girişi') print('2) Öğrenci Girişi') print('3) Not Girişi') print('4) Öğrenci Sorgulama'), print('5) Çıkış') print() try: option = int(input('Seçiminiz:')) if option < 1 or option > 5: raise ValueError break except Exception: print('Geçersiz giriş!\n') return option def class_input(cur): text = input('Dersin ismini ve haftalık ders saatini giriniz:\n') try: class_name, class_week_hours = text.split(',') class_name = class_name.strip().lower().title() class_week_hours = int(class_week_hours) cur.execute("INSERT INTO class(class_name, class_week_hours) VALUES(?, ?)", (class_name, class_week_hours)) cur.connection.commit() print() except sqlite3.Error as e: print(e) except Exception: print('Geçersiz giriş!\n') def student_input(cur): text = input('Öğrencinin adı soyadını, numarasını ve fotoğrafına ilişkin dosyanın yol ifadesini giriniz:\n') try: student_name, student_no, student_photo_path = text.split(',') student_name = student_name.strip().lower().title() student_no = int(student_no) student_photo_path = student_photo_path.strip() with open(student_photo_path, 'rb') as f: student_photo = f.read() cur.execute("INSERT INTO student(student_no, student_name, student_photo) VALUES(?, ?, ?)", (student_no, student_name, student_photo)) cur.connection.commit() print() except sqlite3.Error as e: print(e) except OSError: print('Dosya bulunamadı ya da açılamadı') except Exception: print('Geçersiz giriş!\n') def grade_input(cur): text = input('Öğrencinin adı soyadı, ders ismi, sınav numarası ve not bilgisini giriniz:\n') try: student_name, class_name, class_exam_no, class_grade = text.split(',') student_name = student_name.strip().lower().title() class_name = class_name.strip().lower().title() class_exam_no = int(class_exam_no) class_grade = int(class_grade) cur.execute("SELECT student_no FROM student WHERE student_name = ?", (student_name, )) t = cur.fetchone() if t == None: print(f'"{student_name}" isimli bir öğrenci yok! Önce öğrenciyi kaydediniz!\n') return student_no, = t cur.execute("SELECT class_id FROM class WHERE class_name = ?", (class_name, )) t = cur.fetchone() if t == None: print(f'"{class_name}n isminde bir ders yok! Önce dersi kaydediniz!\n') return class_id, = t cur.execute("INSERT INTO grade(student_no, class_id, class_exam_no, class_grade) VALUES(?, ?, ?, ?)", (student_no, class_id, class_exam_no, class_grade)) cur.connection.commit() print() except sqlite3.Error as e: print(e) except Exception: print('Geçersiz giriş!\n') def student_query(cur): try: condition = '' text = input('Öğrencinin Numarasını giriniz:') print() if text.strip() != '': condition = f'WHERE student_no = {str(int(text))}' for student_no, student_name, student_photo in cur.execute("SELECT student_no, student_name, student_photo FROM student " + condition): print(f'Adı Soyadı: {student_name}') print(f'Numarası: {student_no}') cur2 = cur.connection.cursor() cur2.execute("SELECT DISTINCT class.class_id, class.class_name, class.class_week_hours FROM class, grade WHERE grade.student_no = ? and class.class_id = grade.class_id", (student_no, )) weighted_total = 0 class_week_hours_total = 0 for class_id, class_name, class_week_hours in cur2.fetchall(): print(f'{class_name}: ', end='') cur2.execute("SELECT grade.class_grade FROM grade WHERE student_no = ? and class_id = ? ORDER BY grade.class_exam_no", (student_no, class_id)) grades = cur2.fetchall() print(*[t[0] for t in grades], sep=', ') weighted_total += statistics.mean([t[0] for t in grades]) * class_week_hours class_week_hours_total += class_week_hours bio = io.BytesIO(student_photo) image = PIL.Image.open(bio) image.thumbnail((200, 300)) IPython.display.display(image). print() print(f'Ağırlıklı Not ortalaması: {weighted_total / class_week_hours_total}') print() except sqlite3.Error as e: print(e) except Exception as e: print(f'Error: {e}') def main(): table_create = """CREATE TABLE IF NOT EXISTS student(student_no INTEGER, student_name VARCHAR(64), student_photo BLOB, PRIMARY KEY(student_no)); CREATE TABLE IF NOT EXISTS class(class_id INTEGER, class_name VARCHAR(32), class_week_hours INTEGER, PRIMARY KEY(class_id)); CREATE TABLE IF NOT EXISTS grade(student_no INTEGER, class_id INTEGER, class_exam_no INTEGER, class_grade INTEGER, PRIMARY KEY(student_no)); """ try: with sqlite3.connect('student_notes.sqlite') as conn: cur = conn.cursor() cur.executescript(table_create) conn.commit() while True: option = disp_menu() if option == 5: break {1: class_input, 2: student_input, 3: grade_input, 4: student_query}[option](cur) except Exception as e: conn.rollback() print(e) main() #------------------------------------------------------------------------------------------------------------------------------------ 13. Ders 23/01/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python ile Microsoft'un SQLServer isimli VTYS'si üzerinde işlem yapmak için pyodbc isimli connector paket kullanılmaktadır. Paketin yüklenmesi şöyle yapılabilir: pip install pyodbc Bu paketteki fonksiyonların ve sınıfların genel kullanımı yine sqlite3 standart modülündekilerle çok benzerdir. Veritabanı bağlantısı için pyodbc modülündeki connect fonksiyonu kullanılmaktadır. connect fonksiyonunda şu parametreler girilmelidir: driver: Kullanılacak ODBC sürücüsünün versiyonunu belirtir. Tipik olarak bu parametre "{ODBC Driver 17 for SQL Server}" biçiminde girilir. server: SQLServer'ın IP adresini belirtmektedir. IP adresi yerine "host ismi" de kullanılabilir. database: Kullanılacak veritabanının ismini belirtir. uid: Kullanıcı belirtir. password: Kullanıcının parolasını belitmektedir. pyodbc paketi aslında Microsoft'un geliştirdiği ODBC sürücücü programını kullanmaktadır. Dolayısıyla pyodbc'yi kullanırken aynı zamanda makinenizde ODB sürücüsünün yüklü olması gerekmektedir. Microsoft2un ODBC sürücüsünü Internet'ten indirebilirsiniz. Ancak Windows sistemlerinde zaten sistem kurulduğu zaman bu sürücü de kurulmaktadır. Linux, macOS gibi sistemlerde bu sürücüsünün programcı tarafından kurulması gerekebilmektedir. Ayrıca Microsoft'un ODBC sürücülerinin çeşitli versiyonları da vardır. Sisteminizde yüklü olan ODBC sürücülerinin listesini pyodbc.drivers() çağrısı ile elde edebilirsiniz. Bu durumda "driver" parametresine buradaki yüklü olan sürücülere ilişkin yazıyı girmelisiniz. Aşağıda SQLServer VTYS'sine bağlantı yapan örnek bir kod parçası verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import pyodbc try: with open(r'c:\password.txt') as f: password = f.read() with pyodbc.connect(driver='{ODBC Driver 17 for SQL Server}', server='terapikulubu.com', database='AVS', uid='aslank', password=password) as conn: cur = conn.cursor() cur.execute("SELECT username, member_id FROM members WHERE username LIKE ?", ('ka%', )) for row in cur: print(row) except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Python standart kütüphanesindeki DBM isimli veritabanı ilişkisel olmayan (NoSQL) anahtar-değer çiftlerini tutulan basit bir veritabanıdır. Aslında bu DBM modülü "Berkeley DB" denilen veritabanı kütüphanesini gerçekleştirmektedir. Yani buradaki veritabanının orijinal tasarımcısı "Berkeley California" Üniversitesidir. Daha sonra bu veritabanı çeşitli kurumlar tarafından birbirine benzer biçimde gerçekleştirilmiştir. Örneğin GNU projesi kapsamında "GNU dbm" ismiyle, Orcacle firması tarafından "ndbm" ismiyle gerçekleştirimleri vardır. Aslında Python standartd kütüphanesindeki dbm modülü bu kurumların gerçekleştirimlerini kullanmaktadır. Yani ayrıca bu kütüphenin motor kısmı yazılmamıştır. Mevcut kütüphaneler için ortak bir arayüz oluşturulmuştur. DBM veritabanı da bir kütüphane gibi gerçekleştirilmiştir. Dolayısıyla "gömülü bir veritabanı" gibidir. DBM veritabanı anahtar-değer çiftBlerini tutmaktadır. Burada anahtarlar ve değerler string ya da bytes nesnesi biçiminde girilmek zorundadır. Veritabanı kendi içerisinde anahtar ve değerleri bize bytes nesnesi olarak vermektedir. DBM veritabanı standart kütüphanede "dbm" isimli modülde bulunmaktadır. Kütüphane kullanılırken önce open fonksiyonu ile veritabanı dosyası açılır. open fonksiyonunun parametrik yapısı şöyledir: dbm.open(file, flag='r', mode=0o666) Fonksiyonun birinci parametresi veritabanı dosyasının yol ifadesini belirtir. İkinci parametresi yapılacak işlemi belirtmektedir. İkinci parametre tek karakterli bir yazı biçiminde girilir. Seçenekler şunlardır: 'r': Ancak varolan bir dosya açılabilir. Burada veritabanından okuma yapılabilir. Ancak yazma yapılmaz. 'w': Ancak varolan bir dosya açılabilir. Burada veritabanından hem okuma yapılabilir hem de ona yazma yapılabilir. 'c': Dosya varsa olan dosyayı açar. Dosya yoksa yaratır ve açar. Veritabanından hem okuma hem de yazma yapılabilir. 'n': Dosya varsa exception oluşur. Dosya yoksa yaratılır ve açılır. Veritabanından hem okuma hem de yazma yapılabilir. open fonksiyonu başarı durumunda bize veritabanı işlemlerini yapmak üzere bir sınıf nesnesi verir. Biz bu sınıf nesnesine dbm nesnesi diyeceğiz. Başarısızlık durumunda exception oluşmaktadır. Programcı işi bittiği zaman dbm nesnesinin close metodu ile veritabanı dosyasını kapatmalıdır. Örneğin: import dbm db = dbm.open('test', 'c') ... db.close() Dosya yaratılırken "db" uzantısı eklenmektedir. dbm nesnesine ilişkin sınıf "bağlam yönetim protokolünü" desteklemektedir. Yani biz bu işlemi with deyimi ile yapabiliriz. with deyimi bittiğinde otomatik olarak close metodu çağrılacaktır. Örneğin: import dbm with dbm.open('test', 'c') as db: pass Artık Dbm nesnesi yaratıldıktan sonra bu nesne adeta bir sözlük gibi kullanılmaktadır. Ancak bu nesnenin Python'daki sözlükten farkı anahtar ve değerleri diskte belirttiğimiz dosyada kalıcı biçimde tutmasıdır. Anahtarlar ve değerler "string" ya da "bytes" nesnesi biçiminde olabilmektedir. Yani biz anahtar ve değer olarak "string" ve "bytes" nesnesi dışında bir şey veremeyiz. DBM veritabanı aslında anahtarlar ve değerlerin hepsi "binary" bir byte dizisi biçiminde tutulmaktadır. Biz bir anahtar verdiğimizde DBM bize her zaman onun değerini bytes nesnesi biçiminde vermektedir. DBM modülünde bazı durumlarda dbm.error sınıfı ile raise yapılmıtır. Ancak pek çok durumda da standart Exception sınıfları kullamnılmıştırr. Programcı tüm exception'ları standart exception sınıfı ile ile elabilir. Ya da bunları ayrı ayrı ele almak isteyebilir. #------------------------------------------------------------------------------------------------------------------------------------ try: with dbm.open('test', 'c') as db: db['selami'] = 'eskişehir' db['veli'] = 100 except dbm.error as e: print(f'DBM error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Olmayan bir anahtar kullanıldığında sözlüklerde olduğu gibi KeyError exception'ı oluşmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: val = db['xxxx'] print(val) except dbm.error as e: print(f'DBM Error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Tıpkı sözlüklerde olduğu get metodu anahtara karşı gelen değeri elde etmek için kullanılabilmektedir. Yine get metodu eğer anahtar yoksa ikinci parametresinde belirtilen değerleri geri döndirmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: val = db.get('süleyman', 'Not found') print(val) except dbm.error as e: print(f'DBM error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Tıpkı sözlüklerde olduğu gibi veritabanındaki tüm anahtarları keys isimli metotla elde edebiliriz. keys metodu bize anahtarlardan oluşan bir liste verir #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: keys = db.keys() print(keys) except dbm.error as e: print(f'DBM Error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Anahtarları ve değerleri biz yalnızca string ve bytes nesneleri biçiminde verebilmekteyiz. Ancak biz anahtar ve değerleri string olarak versek bile DBM bize her zaman bunları bytes nesnesi olarak vermektedir. Çünkü DBM her şeyi kendi içerisinde bytes nesneleri biçiminde saklamaktadır. Yani biz DBM veritabanına anahtar ya da değeri string olarak verdiğimizde o zaten bunları bytes nesnesine dbüştürüp saklamaktadır. Biz eğer değerleri yine string olarak almak istersek bytes nesnesini string'e dönüştürmemiz gerekir. bytes sınıfının decode metodunun bunu yaptığını anımsayınız. Eğer decode metodunda "encoding" belirtilmezse "utf-8" kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: val = db['ali'].decode() print(val) except dbm.error as e: print(f'DBM error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ in operatörü sözlüklerde olduğu gibi ilgili anahtarın veritabanında olup olmadığı bilgisini bize verir. in operatörünün sol tarafındaki operand bir string ya da bytes nesnesi olabilir. Tabii bu durumda eğer sol taraftaki operand string ise anahtarla karşılaştırılırken bytes nesnesine dönüştürülecektir. #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: if 'ali' in db: print('var') else: print('yok') except dbm.error as e: print(f'DBM Error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Tıpkı sözlüklerde olduğu gibi bir anahtar-değer çifti del deyimi ile anahtar verilerek silinebilmektedir. Örneğin: del db['ali'] #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: del db['ali'] except dbm.error as e: print(f'DBM Error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Sözlük nesnelerinin dolaşılabilir olduğunu anımsayınız. Ancak dbm nesneleri dolaşılabilir değildir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DBM veritabanının anahtar çiftllerini bytes nesneleri biçiminde tuttuğuna dikkat ediniz. Biz int gibi float gibi değerleri bu veritabanlarında saklayacaksak onları yazıya ya da bytes nesnelerine dönüştürmemiz gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import dbm try: with dbm.open('mydb', 'c') as db: while True: no = int(input('Numarası:')) if not no: break name = input('Adı Soyadı:') db[str(no)] = name except dbm.error as e: print(f'DBM error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi DBM veritabanı hangi durumlarda kullanılabilir? Ne de olsa sqlite çok daha genel amaçlı bir veritabanı ortamını bize sunmaktadır. Eğer elimizde anahtar-değer çiftleri varsa bunların saklanması ve geri alınması DBM veritabanıyla daha hızlı ve daha etkin bir biçimde yapılabilmektedir. Ayrıca DBM veritabanının kullanımı da ilişkisel veritabanlarına göre daha kolaydır. Örneğin çeşitli konfigürasyon bilgileri, ayar bilgileri kolay bir biçimde bu DBM veritabanı sayesinde dosyalarda saklanabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında yukarıda kullandığımız dbm modülündeki fonksiyonlar ve metotlar genel bir arayüzdür. Yani bunlar arka planda DBM veritabanının çeşitli gerçekleştirimlerini kullanmaktadır. Biz dbm.open fonksiyonunu çağırdığımızda bu open fonksiyonu sırasıyla dbm.gnu, dbm.ndbm ve dbm.dumb gerçekleştirimlerinden hangisi sistemde varsa onu kullanmaktadır. dbm.dumb gerçekleştirimi genel olarak dbm.gnu ve dbm.dbm gerçekleştirimlerinden daha yavaş olma eğilimindedir. Biz yarattığımız DBM veritabanının hangi DBM gerçekleştirimi ile yaratılmış olduğunu dbm.whichdb fonksiyonu ile öğrenebiliriz. Örneğin: import dbm print(dbm.whichdb('mydb')) Burada macOS sistemlerinde default kullanılan gerçekleştirimin dbm.ndbm olduğu görülmektedir. Default gerçekletirim bu olduğuna göre demek ki macOS sistemlerinde dbm.gnu gerçekleştirimi yoktur. Tabii bu gerçekleştirim yüklenebilmektedir. Tabii programcı isterse ve o sistemde yüklü ise spesifik gerçekleştirimi kullanabilir. Örneğin: import dbm.ndbm try: with dbm.ndbm.open('mydb', 'c') as db: pass except dbm.error as e: print(f'DBM error: {e}') except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 14. Ders 25/01/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da işletim sistemine ilişkin bazı önemli işlemleri gerçekleştirebilmek için stnadart kütüphane içerisinde "os" isimli bir modül bulundurulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ os.getcwd fonksiyonu prosesin çalışma dizinini (current working directory) alır, os.chdir fonksiyonu ise çalışma dizinini değiştirir. #------------------------------------------------------------------------------------------------------------------------------------ import os cwd = os.getcwd() print(cwd) os.chdir('c:\\') cwd = os.getcwd() print(cwd) #------------------------------------------------------------------------------------------------------------------------------------ os.remove fonksiyonu yol ifadesiyle belirtilen dosyayı silmek için kullanılmaktadır. Dosya yoksa ya da dosyaya erişim hakkı yeterli değilse exception oluşmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import os try: os.remove(r'C:\Users\CSD\Desktop\test.csv') except Exception as e: print(s) #------------------------------------------------------------------------------------------------------------------------------------ os.rename fonksiyonu dosyanın ismini değiştirmek için kullanılır. Yine çeşitli sorunlada exception fırlatılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import os try: os.rename('mydb.dat', 'mydb.dbm') except Exception as e: print(s) #------------------------------------------------------------------------------------------------------------------------------------ Dosyanın ismini değiştirmek aslında dosyayı taşımak (move) anlamına gelmektedir. Yani eğer biz dosyanın ismini başka bir dizindeki bir dosya olarak değiştirirsek onu taşımış oluruz. #------------------------------------------------------------------------------------------------------------------------------------ import os try: os.rename('mydb.dbm', 'f:\\mydb.dbm') except Exception as e: print(s) #------------------------------------------------------------------------------------------------------------------------------------ Boş bir dizin yaratmak için os.mkdir fonksiyonu kullanılmaktadır. Yine yaratım başarısızs olursa exception fırlatılır. #------------------------------------------------------------------------------------------------------------------------------------ import os try: os.mkdir('mydir') except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Dizin silmek için os.rmdir fonksiyonu kullanılmaktadır. Ancak boş bir dizin silinebilir. İçerisinde dosya ya da başka bir dizin bulunan dizileri os.rmdir fonksiyonu ile silemeyiz. #------------------------------------------------------------------------------------------------------------------------------------ import os try: os.rmdir('mydir') except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ os.truncate fonksiyonu bir dosyayı sondan kırparak küçültmek ya da büyütmek amacıyla kullanılmaktadır. eğer dosya büyütülürse büyütülen kısmı 0'larla dolduurlmaktadır. os.truncate fonksiyonu dosyanın yol ifadesini ve dosyanın yeni uzunluğunu parametre olarak almaktadır. Dosya büyütüldüğünde pek çok dosya sisteminde büyütme işlemi "dosya deliği (file hole)" oluşturularak sağlanmaktadır. Ayrıca bu fonksiyonun dosya betimleyicisi (file descriptor) alan ftruncate biçiminde bir benzeri de vardır. #------------------------------------------------------------------------------------------------------------------------------------ import os try: os.truncate('test.db', 1000000) print('Ok') except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Komut satırında (kabuk üzerinde) yazabildiğimiz her komutu biz Python'da os.system fonksiyonu ile programlama yoluyla çalıştırabiliriz. Örneğin UNIX/Linux ve macOS sistemlerinde dizin listesini almak için "ls -l" komutu kullanılmaktadır. Biz bu komutu programlama yoluyla Python içerisinden aşağıdaki gibi de çalıştırabiliriz: os.system('ls -l') Komut satırında verebildiğiniz tüm komutlar bu biçimde işletilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Belli bir dosyanın metadata bilgilerini elde eetmek için os.stat fonksiyonu kullanılmaktadır. Bu fonksiyon bizden dosyanın yol ifadesini alır, bize stat_result isimli bir sınıf nesnesi verir. Bu stat_result sınıfının st_xxxx biçiminde isimlendirilmiş olan bir grup örnek özniteliği vardır. Biz dosyanın çeşitli bilgilerini bu örnek özniteliklerinden elde edebiliriz. Buradaki örnek özniteliklerinin bazıları işletim sistemine bağlı aşağıda seviyeli bilgiler içermektedir. Biz bu ayrıntılara grimeyeceğiz. Örneğin bu sınıfı nesnesinin st_size isimli özniteliği bize dosyanın uzunluğunu vermektedir. Tabii söz konusu dosya yoksa yine exceptyion oluşcaktır. #------------------------------------------------------------------------------------------------------------------------------------ import os try: result = os.stat('db.py') print(result.st_size) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ os.listdir fonksiyonu bir dizin içerisindeki tüm girişlerin (yani dizin ve dosyaların) isimlerini bize bir liste olarak vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import os try: entries = os.listdir('.') for entry in entries: print(entry) except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ os.listdir fonksiyonuyla elde ettiğimiz dizin girişlerini os.stat fonksiyonuna sokarak o girişlere ilişkin dosya ve dizinlerin meta data bilgilerini elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import os dentries = os.listdir('.') for dentry in dentries: info = os.stat(dentry) print(info) #------------------------------------------------------------------------------------------------------------------------------------ os.scandir fonksiyonu da dizin içerisindeki dizin girişlerini bize verir. Ancak bu fonksiyon dizin girişlerini bir liste olarak değil dolaşılabilir bir nesne olarak vermektedir. Böylelikle dizin girişleri aslında dolaşım sırasında elde edilmiş olur. Ayrıca scandir dizin girişlerini yalnızca bir isim olarak değil os.DirEntry isimli bir sınıf nesnesi olarak vermektedir. os.DirEntry sınıfının name isimli örnek özniteliğinden biz dizin girişinin ismini alabiliriz. Sınıfın stat metoduyla o girişin metadata bilgilerini de elde edebiliriz. Ayrıca o girişin bir dizin mi yoksa dosya mı olduğunu da os.DirEntry sınıfının is_file ve is_dir fonksiyonlarıyla öğrenebiliriz. Sınıfın path isimli örnek özniteliği bize dosyanın yol ifadesini scandir fonksiyonunda girdiğimiz dizin ifadesini başa ekleyerek vermektedir. Büyük dizinlerde dizin girişlerinin listdir fonksiyonu ile bir listeye doldurulması gecikmeye yol açabilmektedir. Bu tür durumlarda scandir fonksiyonu tercih edilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import os dentries = os.scandir('.') for de in dentries: print(f'{de.name:<30s}' if de.is_dir() else de.name) #------------------------------------------------------------------------------------------------------------------------------------ os.walk fonksiyonu özyinelemeli olarak dizin ağacını dolaşılabilir bir nesne yoluyla dolaşmaktadır. Aslında fonksiyon üretici fonksiyon (generator) biçiminde yazılmıştır. Her yinelemede iç bir dizinin bilgisi elde edilmektedir. Üretici fonksiyon bize dolaşım sırasında üçlü bir demet verir. Demetin ilk elemanı o anda girilen dizinin yol ifadesini, ikinci elemanı o dizindeki dizinlerin isimlerini, üçüncü elemanı ise o dizindeki dosyaların isimlerini vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import os for root, dirs, files in os.walk(r'f:\dropbox\kurslar\python-app'): print(root) #------------------------------------------------------------------------------------------------------------------------------------ Dizin ağacında belli dosyaları bulmak gibi işlemlerle sık karşılaşılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import os for root, dirs, files in os.walk(r'f:\\'): for file in files: if file.lower() == 'test.txt': print(root) #------------------------------------------------------------------------------------------------------------------------------------ 15. Ders 30/01/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tabii aslında dizin ağacını kendimiz de özyinelemeli (recursive) bir fonksiyonla dolaşabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import os def mywalk(path): for de in os.scandir(path): if de.is_dir(): print(de.name) mywalk(path + '\\' + de.name) mywalk(r'f:\dropbox\kurslar\python-app') #------------------------------------------------------------------------------------------------------------------------------------ Dizin ağacını özyinelemeli dolaşırken bir level parametresi ekleyebiliriz. Böylece bu parametre sayesinde dizin ağacını kademeli biçimde görüntüleyebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import os def mywalk(path, level = 0): for de in os.scandir(path): print(' ' * (level * 4) + de.name) if de.is_dir(): mywalk(path + '\\' + de.name, level + 1) mywalk('.') #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki kodu üretici fonksiyon haline de getirebiliriz. Ancak üretici fonkisyonların özyinelemeli olarak çağırırken yield from uygulamak gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import os def mywalk(path, level = 0): for de in os.scandir(path): yield de.name, level if de.is_dir(): yield from mywalk(path + '\\' + de.name, level + 1) for name, level in mywalk(r'f:\dropbox\kurslar\python-app'): print(' ' * (level * 4) + name) #------------------------------------------------------------------------------------------------------------------------------------ Standart kütüphanedeki glob modülü de dizin içerisinde dosyaları elde etmek için kullanılmaktadır. Ancak bu modül joker karaktelerinin (wild cards) kullanımına izin vermektedir. glob.glob fonksiyonunun parametrik yapısı şöyledir: glob.glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False) glob.glob fonksiyonu bir listeye geri dönmektedir. glob normalde dizini özyinelemeli biçimde dolaşmaz. Ancak fonksiyonun recursive parametresi True geçilirse özyinelemeli dolaşım yapılabilir. glob.glob fonksiyonunun birinci parametresi bir dizin olarak geçilmez. Bu parametreye dosya belirten joker karakterli bir kalıp geirilmelidir. Örneğin: r'c:\windows\*.exe' (Windows dizinin altındaki .exe uzantılı dosyalar) r'c:\windows\*' (Windows dizininin altındaki tüm dosyalar) r'a*' (Bulunulan dizindeki başı "a" ile başlayan tüm dosyalar) #------------------------------------------------------------------------------------------------------------------------------------ import glob result = glob.glob(r'c:\windows\*.exe') print(result) result = glob.glob(r'c:\windows\t*.exe') print(result) #------------------------------------------------------------------------------------------------------------------------------------ Özyinelemeli dolaşmak için kalıbın "**" biçiminde olması gerekmektedir. Özyinelemeli dolaşımda tüm dosyaların bir liste biçiminde geri döndürülmesi uzun bekleme zamanına yol açabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import glob files = glob.glob(r'.\**', recursive=True) for file in files: print(file) #------------------------------------------------------------------------------------------------------------------------------------ glob.iglob fonksiyonu glob.glob fonksiyonu gibidir. Ancak üretici fonksiyon olarak yazılmıştır. Yani dizin içerisindeki dosyaları bir liste olarak olarak değil, dolaşılabilir bir nesne biçiminde vermektedir. Böylece fonksiyon uzun süre bekleme yapmamaktadır. glob.iglob fonksiyonunun parametrik yapısı benzer biçimdedir: glob.iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False) #------------------------------------------------------------------------------------------------------------------------------------ import glob result = glob.iglob(r'c:\windows\*.exe') for path in result: print(path) #------------------------------------------------------------------------------------------------------------------------------------ glob.iglob fonksiyonu fonksiyonu ile de özyinelemeli dolaşım yapabiliriz. Ancak kalıp ** biçiminde olmak zorundadır. #------------------------------------------------------------------------------------------------------------------------------------ import glob files = glob.iglob(r'c:\**', recursive=True) for file in files: print(file) #------------------------------------------------------------------------------------------------------------------------------------ Nesneleri başka bir ortamda (tipik olarak dosyalarda) saklayıp geri alma işlemine "nesnelerin seri hale getirilmesi (object serialization)" denilmektedir. Nesnelerin seri hale getirilmesi kolay bir işlem değildir. Çünkü nesneler nesneleri tutabildiği için seri hale getirme sırasında pek çok kontrolün yapılması gerekmektedir. Seri hale getirilerek örneğin bir dosyada saklanmış olan nesne yeniden oradan alınıp bellekte oluşturulabilmektedir. Pek çok programlama dilinin standrat kütüphanesinde bu işi yapacak öğeler bulunmaktadır. Python'da seri hale getirme işlemi pickle ("pickle" turşu anlamına gelmektedir) isimli bir modülle gerçekleştirilmektedir. pickle modülünün dump isimli fonksiyonu bizden seri hale getirilecek nesneyi ve dosyayı (dosya open ile açılmış olmalıdır) alır. Dosya göstericisinin gösterdiği yerden itibaren nesnenin bilgilerini dosyaya yazar. Normal olarak dosyanın "binary modda ve yazılabilir biçimde (tipik olarak 'wb' modeunda)" açılmış olması gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import pickle a = ['ali', ['veli, selami'], {6: 'ankara', 26: 'eskişehir', 34: 'istanbul'}, 'sacit'] with open('test.dat', 'wb') as f: pickle.dump(a, f) #------------------------------------------------------------------------------------------------------------------------------------ pickle modlünün load fonksiyonu dump ile yapılanın tersini yapmaktadır. Yani dosya göstericisinin gösterdiği yerden itibaren dosyadaki bilgileri okur. Onu orijinal nesne olarak bize verir. Tabii dosyanın yine binary modda ve okunabilir biçimde (tipik olarak 'rb' modunda) açılmış olması gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import pickle with open('test.dat', 'rb') as f: a = pickle.load(f) print(a) #------------------------------------------------------------------------------------------------------------------------------------ Tabii her nesneyi ayrı bir dosyada saklamaya gerek yoktur. Bir dosyada peşi sıra birden fazla nesne saklanabilmektedir. Ancak bunların yine aynı sırada geri alınması gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import pickle import datetime a = ['ali', ['veli, selami'], {6: 'ankara', 26: 'eskişehir', 34: 'istanbul'}, 'sacit'] b = {1, 2, 'fehmi', 'ercan'} c = (10, 20, 30, 40, 50) d = datetime.datetime(2021, 11, 16, 20, 25, 12) with open('test.dat', 'wb') as f: pickle.dump(a, f) pickle.dump(b, f) pickle.dump(c, f) pickle.dump(d, f) with open('test.dat', 'rb') as f: x = pickle.load(f) y = pickle.load(f) z = pickle.load(f) k = pickle.load(f) print(x) print(y) print(z) print(k) #------------------------------------------------------------------------------------------------------------------------------------ Deserialize işlemi sırasında dosya göstericisinin aynı konumda olması gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import pickle import datetime a = ['ali', ['veli, selami'], {6: 'ankara', 26: 'eskişehir', 34: 'istanbul'}, 'sacit'] b = {1, 2, 'fehmi', 'ercan'} c = (10, 20, 30, 40, 50) d = datetime.datetime(2021, 11, 16, 20, 25, 12) with open('test.dat', 'w+b') as f: pickle.dump(a, f) pickle.dump(b, f) pickle.dump(c, f) pickle.dump(d, f) f.seek(0) x = pickle.load(f) y = pickle.load(f) z = pickle.load(f) k = pickle.load(f) print(x) print(y) print(z) print(k) #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla nesneyi aynı dosyaya seri hale getirirken bunları karışık sırada alacaksak onların dosya içerisindeki offset numaralarını bir biçimde tutup, sonra dosya göstericisini o offset'e çekip pickle.load işlemi yapılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import pickle import datetime a = ['ali', ['veli, selami'], {6: 'ankara', 26: 'eskişehir', 34: 'istanbul'}, 'sacit'] b = {1, 2, 'fehmi', 'ercan'} c = (10, 20, 30, 40, 50) d = datetime.datetime(2021, 11, 16, 20, 25, 12) with open('test.dat', 'wb') as f: pos_a = f.tell() pickle.dump(a, f) pos_b = f.tell() pickle.dump(b, f) pos_c = f.tell() pickle.dump(c, f) pos_d = f.tell() pickle.dump(d, f) with open('test.dat', 'rb') as f: f.seek(pos_c, 0) c = pickle.load(f) print(c) f.seek(pos_a, 0) a = pickle.load(f) print(a) f.seek(pos_d, 0) d = pickle.load(f) print(d) f.seek(pos_b, 0) b = pickle.load(f) print(b) #------------------------------------------------------------------------------------------------------------------------------------ Kendi sınıf nesnelerimizi de aynı biçimde seri hale getirip alabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import pickle class Student: def __init__(self, name, no): self.name = name self.no = no def __repr__(self): return f'{self.name}, {self.no}' ali = Student('Ali Serçe', 123) veli = Student('Veli Durmuş', 678) selami = Student('Selami Özışık', 721) with open('test.dat', 'wb') as f: pickle.dump(ali, f) pickle.dump(veli, f) pickle.dump(selami, f) with open('test.dat', 'rb') as f: x = pickle.load(f) y = pickle.load(f) z = pickle.load(f) print(x) print(y) print(z) #------------------------------------------------------------------------------------------------------------------------------------ Seri hale getirme sayesinde karmaşık birtakım nesneleri tek hamlede dosyalarda saklayıp geri alabilmekteyiz. #------------------------------------------------------------------------------------------------------------------------------------ import pickle graph = {'A': {'B': 70, 'D': 30}, 'B': {'C': 60, 'G': 50}, 'C': {'E': 40}, 'D': {'A': 30, 'C': 50}, 'E': {'G': 30, 'D': 60}, 'F': {'E': 40}} with open('graph.dat', 'wb') as f: pickle.dump(graph, f) with open('graph.dat', 'rb') as f: graph_loaded = pickle.load(f) print(graph_loaded) #------------------------------------------------------------------------------------------------------------------------------------ Birtakım veri yapıları çok daha karmaşık görünümde olabilir. Ancak pickle modülü ile biz tek hamlede karmaşık veri yapılarını saklayıp geri alabiliriz. Aşağıdaki örnekte bir graph veri yapısı Vertex, Edge ve Graph sınıflarıyla oluşturulmuştur. Vertex isim tutmakta, Edgeiki vertex'i ve bir değeri tutmakta, Graph ise belli bir vertex'e bağlı edge'leri tutmaktadır. Tüm graf tek hamlede saklanıp geri yüklenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import pickle class Vertex: def __init__(self, name): self.name = name def __repr__(self): return self.name class Edge: def __init__(self, v1, v2, val): self.v1 = v1 self.v2 = v2 self.val = val def __repr__(self): return f'{self.v1.name} ---> {self.v2.name}' class Graph: def __init__(self): self.vertexes = {} def add_edge(self, edge): if not self.vertexes.get(edge.v1): self.vertexes[edge.v1] = set() self.vertexes[edge.v1].add(edge) def __repr__(self): result = '' for vertex in self.vertexes: result += f'{vertex.name}: {self.vertexes[vertex]}\n' return result a = Vertex('A') b = Vertex('B') c = Vertex('C') d = Vertex('D') e = Vertex('E') f = Vertex('F') g = Vertex('G') graph = Graph() graph.add_edge(Edge(a, b, 70)) graph.add_edge(Edge(a, d, 30)) graph.add_edge(Edge(b, g, 50)) graph.add_edge(Edge(d, c, 50)) graph.add_edge(Edge(f, e, 40)) graph.add_edge(Edge(g, c, 50)) with open('graph.dat', 'wb') as f: pickle.dump(graph, f) with open('graph.dat', 'rb') as f: graph_loaded = pickle.load(f) print(graph_loaded) #------------------------------------------------------------------------------------------------------------------------------------ pickle modülünün dumps isimli fonksiyonu nesneyi byte yığını olarak seri hale getirir. dumps bize seri hale getirilmiş nesnenin byte'larını bytes nesnesi olarak vermektedir. Modülün loads fonksiyonu ise bizden bytes nesnesini alır onu deserialize ederek orijinll nesneyi oluşturur. Bu iki fonksiyon özellikle nesnenin dosyada saklanması yerine başka bir yere aktarılması amacıyla kullanılmaktadır. Örneğin biz bir nesneyi seri hale getirip soketten yollamak isteyebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import pickle a = ['ali', ['veli, selami'], {6: 'ankara', 26: 'eskişehir', 34: 'istanbul'}, 'sacit'] b = pickle.dumps(a) print(b) c = pickle.loads(b) print(c) #------------------------------------------------------------------------------------------------------------------------------------ Seri hale getirme ve geri alma işlemleri için pickle modülünde Pickler ve Unpickler isimli iki sınıf da bulundurulmuştur. Yani biz bu işlemleri nesne yönelimli biçimde de yapabiliriz. Pickler ve Unpickler sınıfları __init__ metotlarında biz dosya nesnesini alıpnesnenin özniteliğinde saklar. Pickler sınıfının dump metodu ve Unpickler sınıfının load metoduseri hale getirme ve geri alma işlemlerini yapmaktadır. Aslında dump ve load fonksiyonları bu sınıfları kullanan sarma (wrapper) fonksiyonlardır. Bu fonksiyonlar tipik olarak şöyle yazılmıştır: def dump(obj, f): p = pickle.Pickler(f) p.dump(obj) def load(f): up = pickle.Unpickler(f) return up.load(up) #------------------------------------------------------------------------------------------------------------------------------------ import pickle a = ['ali', ['veli, selami'], {6: 'ankara', 26: 'eskişehir', 34: 'istanbul'}, 'sacit'] with open('test.dat', 'wb') as f: pickler = pickle.Pickler(f) pickler.dump(a) with open('test.dat', 'rb') as f: unpickler = pickle.Unpickler(f) b = unpickler.load() print(b) def dump(o, f): pickler = pickle.Pickler(f) pickler.dump(o) def load(f): unpickler = pickle.Unpickler(f) return unpickler.load() #------------------------------------------------------------------------------------------------------------------------------------ shelve modülü aslında dbm ile pickle modüllerinin birleşimi gibidir. Anımsanacağı gibi dbm modülünde anahtarlar ve değerler yalnızca str ve bytes nesnesi olabiliyordu. İşte shelve modülü de kullanım bakımından tamamen dbm modülü gibidir. Ancak shelve modülünde değerler herhangi bir türden olabilmektedir. Aslında shelve modülü kendi içerisinde nesneleri önce dumps ile seri hale getirip onları bytes nesnesine dönüştürmekte ve bytes nesnesine dönüştürülmüş olan nesneleri dbm ile dosyaya yazamaktadır. Benzer biçimde shelve modülünde biz anahtarı verdiğimizde shleve önce anahtarın değerini bytes nesneleri alır ancak onu loads fonksiyonu ile orijinal türe dönüştürerek verir. shelve.open fonksiyonunun bize verdiği nesne dolaşılabilir bir nesnedir. shelve nesnesi dolaşıldığından anahtarlar elde edilmektedir. shelve modülünde değerler herhangi bir türden olabilir. Ancak anahtarlar str türünden olmak zorundadır. #------------------------------------------------------------------------------------------------------------------------------------ import shelve s = shelve.open('shelve.dat', 'c') s['ali'] = 128 s['veli'] = [10, 20, 30, 40] s['selami'] = {'eskişehir', 'ankara', 'izmir'} s.close() s = shelve.open('shelve.dat', 'c') result = s['selami'] print(result) s.close() #------------------------------------------------------------------------------------------------------------------------------------ Yine shleve nesnesinde de dbm'de olduğu gibi sözlük işlemleri uygulanabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import shelve with shelve.open('shelve.dat', 'c') as s: for key in s: print(key) #------------------------------------------------------------------------------------------------------------------------------------ shelve modülünün dbm ve pickle modülleri kullanılarak nasıl gerçekleştirilmiş olabileceği konusunda aşağıdaki kod bir fikir verecektir. #------------------------------------------------------------------------------------------------------------------------------------ import dbm import pickle def myopen(*args, **kwargs): return myshelve(dbm.open(*args, **kwargs)) class myshelve: def __init__(self, db): self.db = db def __setitem__(self, key, value): self.db[key] = pickle.dumps(value) def __getitem__(self, key): return pickle.loads(self.db[key]) def __iter__(self): for key in self.db.keys(): yield key.decode() def __len__(self): return len(self.db) ms = myopen('y.dat', 'c') ms['ali'] = 123 val = ms['ali'] print(val) #------------------------------------------------------------------------------------------------------------------------------------ Python'da pencereli programlar yazabilmek için çeşitli seçenekler söz konusudr. Python'ın standart kütüphanesinde Tkinter ("ti key inter" ya da "tikintır" diye okunuyor) isimli bir modül bulunmaktadır. Bu modül "Tk" ismi verilen GUI framework'ün kullanımını mümkün hale getirmektedir. Tk framework'ü aslında "Tcl (tikıl diye okunuyor)" denilen bir script dili için oluşturulmuştur. Ancak bu kütüphane Tcl dışından da farklı programlama dillerinden kullanılabilmektedir. Python çeşitli gerekçelerle (open source olması gibi) bu GUI framework'ünü standart kütüphanesine dahil etmiştir. Her ne kadar Tkinter Python'ın standart bir öğesi olarak bulunuyorsa da aslında profesyonel uygulamalarda pek tercih edilmemektedir. Python dünyasında en fazla tercih edilen frameowrk Qt (genellikle "kyut" diye okunmaktadır) isimli framework'tür. Qt de aslında bir C++ framework'üdür. Cross platform olma özelliği nedeniyle son 20 senedir C++ dünyasında en çok tercih edilen framework haline gelmiştir. Bu Qt framework'ü Python içerisinden de kullanılabilmektedir. Qt framework'ünün Python'dan kullanılabilen biçimine "PyQt" denilmektedir. Thinter zaten Python'un standart kütüphanesinde bulunduğna göre onu install etmek için herhangi bir şey yapmaya gerek yoktur. Ancak PyQt'nin bizim tarafımızda kurulması gerekir. PyQt5 ya da PyQt6 aşağıdaki gibi kurulabilir: pip install pyqt5 pip install pyqt6 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 16. Ders 01/02/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Biz şimdiye kadar "konsol tabanlı" çalışma modelini uyguladık. Bu modelde program kaynak kodun tepesinden başlamaktadır. Girdileri almak için input fonksiyonu çıktıları yazdırmak için print fonksiyonu kullanılmaktadır. Halbuki GUI (Graphical User Interface) çalışma modeli konsol çalışma modelinden farklıdır. Burada önce GUI çalışma modelinden bahsedeceğiz. GUI çalışma modelinde ekranda bağımsız olarak kontrol edilebilen dikdörtgensel bölgelere "pencere (window)" denilmektedir. Bir GUI programın en az bir penceresi bulunur. Bo modelde kullanıcı bir programın penceresi ile ilgili bir işlem yaptığında işletim sistemi ya da GUI alt sistem devreye girerek yapılan işlemi belirler. Bunu bir mesaj adı altında uygulamanın "mesaj kuyruğu (message queue)" denilen kuyruğuna bırakır. Yani girdi olaylarını işletim sistemi ya da GUI alt sistem belirleyip uygulamaya bildirmektedir. Bir mesaj "fare ilgili bir olay" olabildiği gibi "klavyeyele ilgili bir olay" ya da başka birtakım olaylar olabilmektedir. Örneğin biz bir pencere üzerinde bir noktaya fare tıklayalım. İşletinm sistemi ya da GUI alt sistem bu olayı bir mesaj formatına dönüştürür ve uygulamaın mesaj kuyruğuna ekler. Bu mesaj içerisinde "fare ile tıklandığı bilgisi ve pencerenin neresine tıklandığı bilgisi" kodlanmaktadır. İşletim sistemleri ya da GUI alt sistemler mesaja ilişkin olaylara birer numara vermektedir. Örneğin Windows sisteminde farenin sol tuşu ile pencereye tıklandığında WM_LBUTTONDOWN isimli bir mesj olulşturulmaktadır. Örneğin bir menüden bir eleman seçildiğinde WM_COMMAND isimli mesaj oluşturulur. Bir uygulmanaın mesaj kuyruğunda yalnızca o uygulamayı ilgilendiren mesajlar bulunmaktadır. Yani işletim sistemi ve GUI alt sistem olay kimi ilgilendiriyorsa o olayı onun mesaj kuyruğuna yerleştirir. Mesaj kuyruğunu aşağıdaki gibi temsil edebiliriz: Mesaj <-- Mesaj <-- Mesaj .... <--- Mesaj Mesaj kuyrukları aslında uygulamaya özgü değil thread'e özgü oluşturulmaktadır. Thread konusu ileride ele alınacaktır. İşletim sistemi ya da GUI alt sistem yapılan işlemleri mesaj adı altında uygulmanaın mesaj kuyruğuna eklemektedir. Bu mesajların kuyruktan alınarak işlenmesi uygulamanın yani programcının sorumluluğundadır. Programcının mesaj kuyruğundan mesajı alıp, o mesajın neden gönderildiğini anlaması ve gerekli işlemleri yapması gerekir. Yani programcının tipik olarak aşağıdaki gibi bir dönü oluşturması gerekmektedir: while True: Bir GUI program yaşamını böyle bir dmngüde geçirmektedir. Bu döngüye GUI terminolojisinde programın "mesaj döngüsü" denilmektedir. Ancak programın mesaj döngüsünü oluşturup mesajları kuyruktan alıp işleyen bir programı oluşturmak çok zahmetlidir. GUI sistemlerin ilk zamanlarında programcılar bu biçimde programlar yazmak zorundaydı. Ancak daha sonra bu arka plandaki karmaşık işlemleri programcının üzerinden alıp modeli çok daha basit bir biçimde programcıya gösteren "framework"ler oluşturuldu. GUI framework'ler mesaj döngülerini kendisi oluşturmakta, mesaj kuruğundan mesajları kendisi almakta ve mesaja göre programcının belirlediği fonksiyonları çağırmaktadır. Bazı GUI framework'ler prosedürel programalama tekniği ile oluşturulmuştur. Bazıları ise nesne yönelimli teknikle oluşturulmuştur. GUI programlama modeli nesne yönelimli tekniğe çok uygun olmaktadır. Dolayısıyla bu alandaki güçlü framework'ler sınıflar kullanılarak oluşturulmuş durumdadır. Tkinter ve PyQt nesne yönelimli bir framework görünümündedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Grafik ekranlada ekranda görüntülenebilen en küçük görsel birime "piksel (pixel)" denilmektedir. Aslında tüm yazılar resimler, çizimler piksellerin bir araya getirilmesiyle oluşturulmaktadır. Modern grafik kartlarında her piksel 16 milyon civarındaki bir renkle diğerlerinden bağımsız olarak renklendirilebilmektedir. Dijital fotoğraf makineleri de aslında görüntüyü piksellerle ifade etmektedir. Tabii bu noktada "ekranın çözünürlüğü" de önemli bir unsur olmaktadır. Eğer çözünürlük düşük olursa pikseller büyür, çözünürlük yüksek olursa pikseller küçülür. Şüphesiz çözünürlüğün yüksek olması görüntü kalitesini artırmaktadır. Ancak belli bir çözünürlükten sonrasını göz ayırt edememektdir. Yani gözün donanımının da bir kısıtı vardır. Bilgisayarımızın çözünürlüğünün 1920X1080 olması demek adeta monitörümüzde 1920x1080'lik pkisel matrisi olması demektir. Piksellerin koordinarları da vardır. Bir pikselin hangi sütunda ve hangi satırda bulunduğu onun koordinat bilgisini oluştumaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ GUI dünyasında ekranda bağımsız olarak kontrol edilebilen dikdörtgensel bölgelere "pencere (window)" denilmektedir. Tipi bir pencerenin bir başlık kısmı (caption) olur, sınır çizgileri (borders) olur. Başlık kısmının altındaki sınır çizgilerinin içindeki alana "çalışma alanı (client area)" denilmektedir. Programcı görsel öğeleri bu alana yerleştirir. Ancak pencerelerin başlık kısmı olmak zornda dğeildir. Sınır çizgileri olmak zorunda da değildir. Masaüstüne açılan pencerelere "ana pencereler (top level windows)" denilmektedir. Her GUI uygulamasının genel olarak bir ana pneceresi vardır. Bir ana pencerenin çalışma alanının içerisinde bulunan ve onun dışına çıkamayan pencerelere "alt pencereler (child windows)" denilmektedir. Alt pencerelerin alt pencereleri olabilir. Her pencerenin bir "üst penceresi (parent window)" vardır. Aynı üst pencereye sahip olan pencerelere de "kardeş pencereler (sibling windows)" denilmektedir. Üst penceresinin her zaman yukarısında görüntülenen, ancak üst penceresinin sınırları dışına çıkabilen pencerelere "sahiplenilmiş pencereler (owned window)" yad aprogramcılar arasında "diyalog pencereleri (dialog boxes)" denilmektedir. Sahiplenilmiş pencereler aslında hem bir ana pencere gibi hem de bir alt pencere gibi düşünülebilir. Bir pencere yaratılmış ancak o anda ekranda "görünmüyor (invisible)" durumda olabilir. Programcı pencereleri istediği zaman "görünür (visible)" yapıp istediği zaman "görünmez (invisible)" yapabilmektedir. Bazı işletim sistemlerinde ve framework'lerde "pencere (windows)" terimi yerine "widget" terimi de kullanılmaktadır. Widget sözcüğü "window gadget" sözcüklerinden uydurulmuştur. Örneğin PyQt ve Tkinter pencere yerine "widget" sözcüğünü tercih etmektedir. Ancak Microsoft "pencere (windows)" terimini kullanmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir GUI programda programcı tipik olarak çalışma alanının içerisine çeşitli GUI elemanlar yerleştirir. Bu GUI elemanlar aslında birer alt penceredir. Framework'ler yaygın kullanılan pek çok hazır GUI elemanlara sahiptir. Aslında programcılar da içi boş bir pencereden hareketler çizim işlemleriyle düğmeler, edit alanları, listeleme kutuları vs. oluşturabilmektedir. Ancak bir GUI elemanı tasarlayıp oluşturmak uzmanlık isteyen bir iştir. Microsoft hazır olarak bulundurulan bu GUI elemanlara ilişkin alt pencerelere "control" demektedir. Pek çok frameowrk ise bunları yine "widget" biçiminde isimlendirmektedir. Programcı bu hazır alt pencereleri ana pencerenin içerisine yerleştirerek görsel arayüzü oluşturmaktadır. GUI programcısı tipik olarak önce bu alt pencereleri ana pencerenin içerisine yerleştirir. Sonra da bunlarda olan olayları ele alıp uygun işlemleri yapar. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Qt framework'ünde tüm sınıf isimleri Q harfliyle başlatılmaktadır. Bu Q harfinin nereden geldiği bilinmemektedir. Kütüphanede genel olarak sınıfların metotları ve yerel değişkenler "deve notasyonu (camel casting)" ile harflendirilmiştir. Sınıf isimlerine ise Pascal tarzı harflendirme (Pascal casting) uygulanmıştır. Biz şimdiye kadar Python'da hep klasik alt tireli (yılan notasyonude) harflendirme kullandık. Ancak PyQt söz konusu olduğunda kendimizi framework'ün harflendirme stiline uydurabilmek için biz de sınıf isimleri için Pascal notasyonunu, diğer isimler için deve notasyonunu tercih edeceğiz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ PyQt dokümantasyonu için aslında çok iyi bir site yoktur. Python programcı orijinal C++ dokğmantasyonundan faydalanabilir. Ancak bu dokümantasyonda C++ sentaksı kullanılmıştır. Famework'ün orijinal C++ dokümantasyonuna aşağıdaki bağlantıdan erişilebilir: https://doc.qt.io/ Qt'nin PyQt dışında Python için diğer bir gerçekleştirimi de PySide denilen gerçekleştirimidir. Qt'nin sahibi olan firma bu PySide gerçekleştirimini dokümantasyon olarak desteklemektedir. Dolayısıyla aşağıdaki bağlantı Qt'nin Python dokümantasyonu olarak da kullanılabilir: https://doc.qt.io/qtforpython/ Bunların dışında aşağıdaki bağlantı da Python için dokümantasyon sağlamaktadır: https://www.riverbankcomputing.com/static/Docs/PyQt5/ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ GUI uygulamalarını öğrenebilmek için başlangıç noktası ekranda içi boş bir ana pencerenin çıkartılmasıdır. Bu işlem adeta GUI uygulamalarının "merhana Dünya" programı gibidir. Qt'de uygulamanın kendisi QApplication isimli bir sınıfla temsil edilmiştir. Bu sınıf komut satırı argümanlarını __init__ metodunda argüman olarak almaktadır. Pencere kavramı QWidget sınıfıyla temsil edilmiştir. Tüm GUI elemanlar QWidget sınıfından türetilmiş durumdadır. Programın ana penceresi QWidget sınıfı kullanılarak yaratılabilir. Mesaj döngüsü QApplication sınıfının exec metoduyla oluşturulmaktadır. Mesaj döngüsüne girmeden önce programcının uygulamanın ana penceresini yaratmış olması gerekir. QWidget türünden nesne yaratıldığında ana pencere de yaratılmış olur. Ancak bu ana pencere henüz görünür durumda değildir. Bunu görünür hale getirmek için QWidget sınıfının show metodu çağrılmalıdır. Aşağıda ekrana boş bir pencere çıkartan minimal PyQt programı verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * app = QApplication(sys.argv) widget = QWidget() widget.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 17. Ders 15/02/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi bir Qt GUI programı nasıl sonlanmaktadır? En tipik sonlanma şöyle olur: Kullanıcı ana pencerenin kapatma simgesine tıklar (ya da bunun için kalvyeden kısa yol tuşlarını kullanır). Bu durumda işletim sistemi mesaj kuyruğuna özel bir mesaj bırakır (örneğin Windows sistemlerinde bu mesaja WM_CLOSE mesajı denilmektedir). QApplication sınıfının exec metodu da bu mesajı aldığında mesaj döngüsünden çıkar ve sonlanır. Yani ana pencere kapatılınca akış exec metodunun aşağısından devam edecektir. Genellikle programcılar ana pencere kapatıldıktan sonra özel bir şey yapmazlar. Dolayısıyla ana pencerenin kapatılmasıyla GUI programları genellikle sonlanmaktadır. Aşağıda ana pencere kapatıldıktan sonra exec metodununun sonlandığını gösteren bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * app = QApplication(sys.argv) mainWindow = QWidget() mainWindow.show() app.exec() print('continues...') #------------------------------------------------------------------------------------------------------------------------------------ Qt'de pencere kavramı QWidget isimli sınıfla temsil edilmiştir. Her GUI eleman bir sınıf biçiminde oluşturulmuştur. Bir GUI elemana ilişkin sınıf başka sınıflkardan türetildiği için o GUI eleman sınıfında taban sınıfın elemanları da kullanılabilmektedir. Türetme şemasının en tepesinden QWidget sınıfı bulunmaktadır. QWidget sınıfı ana pencere oluştururken de alt pencere oluşturulurken de kullanılabilmektedir. Örneğin düğme GUI elemanı QPushButton isimli sınıfla temsil edilmiş durumdadır. Bu sınıf QAbstractButton sınıfından, QAbstractButton sınıfı da QWidget sınıfınından türetilmiş durumdadır: QWidget <--- QAbstractButton <--- QPushButton Bu duurmda elimizde bir QPushButton nesnesi varsa biz o nesne ile hem QPushButton sınıfının hem QAbstractButton sınıfının hem de QWidget sınıfının elemanlarını kullanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir GUI program oluşturabilmek için programcı önce uygulaması için bir ana pencere yaratır. Sonra bu ana pencerenin içerisine GUI elemanlarını yerleştirir. Sonra da kullanıcı o GUI elemanları üzerinde işlem yaptıkça programcı yapılan işlemlere göre çeşitli kodları çalıştırmaktadır. Bir GUI uygulamasında pek çok GUI eleman vardır. Bu GUI elemanların bazıları çok yaygın kullanılmaktadır. Bazıları ise seyrek kullanılmaktadır. Qt dünyasında GUI elemnlara "widget (window gadget sözcüklerinden uydurulmuştur)" da denilmektedir. Ancak genel olarak aşağı seviyeli terminolojide GUI eleman yerine "alt pencere (child window)" ya da "kontrol (control)" terimleri tercih edilmektedir. Örneğin Microsoft GUI elemanlar için "control" terimini kullanmaktadır. Qt'de her GUI eleman bir sınıfla temsil edilmiş durumdadır. Bir GUI eleman yaratmak için tek yapılacak şey o GUI eleman sınıfı türünden bir nesne yaratmaktır. Biz bu GUI eleman sınıfları türünden nesneler yarattığımız zaman bu GUI elemanlarını da yaratmış oluruz. Pek çok GUI eleman sınıfının __init__ metodunun ilk parametresi bir yazı almaktadır. Çünkü pek çok GUI elemanın "bir yazısı" vardır. __init__ metotlarının ikinci parametreleri de genellikle bu GUI elemanın yerleştirileceği üst pencere nesnesini belirtir. Ana pencerenin içerisine GUI eleman yerleşltirilecekse ana pencere yaratıldıktan hemen sonra bunun yapılması uygun olur. Aşağıdaki örnekte bir ana pencerenin içerisaine bir düğme GUI elemanı eklenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * app = QApplication(sys.argv) mainWindow = QWidget() pushButtonOk = QPushButton('Ok', mainWindow) mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Programın ana penceresine GUI elemanlar yerleştirileceği için bir içerme (composition) oluşturmak amacıyla ana pencere QWidget sınıfından türetilmiş olan bir sınıfla temsil edilebilir. Böyelce nesne yönelimli teknik daha iyi bir biçimde uygulanır. Yani programcılar programın ana penceresini doğurdan QWidget sınıfıyla yaratmak yerine QWidget sınıfından bir sınıf türetip ana pencereyi o sınıfla yaratmayı tercih ederler. Aşağıdaki örnekte QWidget sınıfından MainWindow isimli bir sınıf türetilmiş programın ana penceresi bu sınıf yoluyla oluşturulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencere içerisine GUI elemanları GUI eleman sınıflarından nesneler yaratarak da yerleştirebiliriz. Bu biçim nesne yönelimli programlama tekniği ile daha iyi örtüşmektedir. Aşağıdaki örnekte ana pencere içerisine bir düğme (pushbutton) yerleştirilmiştir. Bu işlem MainWindow sınıfının __init__ metodu içerisinde yapılmıştır. GUI eleman nesnesi de ana pencerenin örnek özniteliğinde saklanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.pushButtonOk = QPushButton('Ok', self) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt her ne kadar bir GUI framework'ü ise de genel amaçlı pek çok sınıfa da sahiptir. Bu genel amaçlı sınıfların bazıları GUI işlemlerinde yardımcı sınıflar olarak kullanılmaktadır. Biz de burada kısaca GUI işlemlerinde kullanılan bazı yardımcı sınıfları tanıtacağız. Örneğin QPoint isimli bir sınıf ekrandaki bir noktayı temsil etmektedir. QSize sınıfı genişlik-yükseklik kavramlarını temsil etmektedir. QRect sınıfı ise dikdörtgensel bir bölgeyi temsil etmektedir. Qt'deki yardımcı sınıfların sınıfların çoğu PyQt5.QtCore alt paketinde bulunmaktadır. Dolayısıyla buı sınıfları kullanabilmek için aşağıdaki import işlemini yapabiliriz: from PyQt5.QtCore import * #------------------------------------------------------------------------------------------------------------------------------------ Bilinciği gibi Qt aslında bir C++ framework'üdür. PyQt bu C++ framework'ünün Python'dan kullanımını sağlayan bir wrapper gibidir. C++'ta sınıfın veri elemanlarının private bölümde dışarıdan gizlenmesi onlara public üye fonksiyonlarla erişilmesi yani "data hiding" özelliği yoğun kullanılmaktadır. Bu nedenle PyQt'te de (orijinali öyle olduğu için) bir nesnenin x veri elemanına getter/setter metotlarıyla erişilir. Genel olarak x bir nesnenin bir özelliği ise bu x değerinbi elde etmek için x() isimli metot kullanılır. Ancak bu özelliği set etmek için setX isimli metotlar oluşturulmuştur. Burada getter metodunun başında "get" olmadığına ancak setter metodunun başında "set" olduğuna dikkat ediniz. Qt'nin orijinl dokümanlarında "get" ve "set" edilebilen sınıf özelliklerine "property" denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QPoint sınıfı ekranda bir pixel'in koordinatlarını temsil etmektedir. QPoint nesnesi x ve y değerleri verilerek yaratılır. Örneğin: pt = QPoint(10, 20) Bu x ve y değerleri x() ve y() metotlarıyla alınıp setX ve setY metotlarıyla set edilebilir. Örneğin: print(pt.x(), pt.y()) # 10 20 pt.setX(100) pt.setY(200) print(pt.x(), pt.y()) # 100 200 İki QPoint nesnesi toplanabilir, çıkartılabilir. #------------------------------------------------------------------------------------------------------------------------------------ QSize sınıfı bir pencerenin genişlik-yüksek bilgisini tutmak için düşünülmüştür. Nesne yaratılırken bu bilgi verilir. Sonra istenirse width ve height metotlarıyla bu bilgiler nesnenin içerisinden alınabilir. Örneğin: size = QSize(10, 20) print(size.width(), size.height()) # 10 20 width ve height özellikleri setWidth ve setHeight metotlarıyla değiştirilebilmektedir. Yine iki QSize nesnesi toplanıp çıkartılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ from PyQt5.QtCore import * pt = QPoint(10, 5) print(pt) print(f'x = {pt.x()}, y = {pt.y()}') pt2 = QPoint(3, 5) pt3 = pt + pt2 print(pt3) #------------------------------------------------------------------------------------------------------------------------------------ QRect sınıfı bir dikdörtgenin korrdinatlarını tutmak ve onun üzerinde bazı işlemleri yapabilmek için düşünülmüştür. QRect nesnesi sol-üst köşe koordinatları ve genişlik-yükseklik değerleriyle yaratılabilir. Örneğin: rect = QRect(10, 10, 100, 100) Buradaki dikdörtgenin sol üst köşe koordinatı 10, 10, genişliği ve yüksekliği 100, 100 biçimindedir. Bir QRect nesnesi QPoint ve QSize ikilisiyle de yaratılabilmektedir. Örneğin: pt = QPoint(10, 10) size = QSize(100, 100) rect = QRect(pt, size) QRect nesnesinin temsil ettiği dikdörtgene ilişkin çeşitli bilgiler çeşitliş metotlarla elde edilmektedir. Bu metotların listesini Qt dokğmanlarından elde edebilirsiniz: rect = QRect(10, 11, 12, 13) print(rect.x(), rect.y()) # 10 11 print(rect.width(), rect.height()) # 12 13 print(rect.left(), rect.right()) # 10 21 print(rect.top(), rect.bottom()) # 11 23 Bu koordinatlar QPoint ve QSize olarak da elde edilebilmektedir: rect = QRect(10, 11, 12, 13) print(rect.topLeft()) # PyQt5.QtCore.QPoint(10, 11) print(rect.bottomRight()) # PyQt5.QtCore.QPoint(21, 23) print(rect.size()) # PyQt5.QtCore.QSize(12, 13) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Ana pencerenin başlık kısmındaki yazı QWidget sınıfının setWindowTitle metoduyla set edilebilir, windowTitle metoduyla da get edilebilir. Biz QWidget sınıfından MainWindow sınıfını türettiğimize göre __init__ içerisinde self aslında MainWindow türündendir. türemiş sınıf taban sınıfın elemanlarını kullanabildiğine göre ana pencerenin başlık yazısı MainWindow sınıfının __init__ metodunda self ile set edilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Sample GUI') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencereler için konumlandırma orijini masaüstünün sol-üst köşesidir. Ancak alt pencereler için konumlandırma orijini o alt pencerenin üst penceresinin çalışma alanının (client area) sol üst köşesidir. Pencereleri konumlandırmak için QWidget sınıfının move isimli metodu kullanılmaktadır. move metodu x ve y değerlerini alır. Yukarıda da belirtildiği gibi bu x ve y değerlerinin orijin noktası ana pencereler için masaütünün sol-üst köşesi, alt pencereler için onların üst pencerelerinin çalışma alanının sol üst köşesidir. Aşağıdaki örnekte hem ana pencere hem de push button alt penceresi move metodu ile konumlandırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Sample GUI') self.move(100, 100) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.move(100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte iki tane push button GUI elemanı yaratılmış, bunlar yan yana konumlandırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Sample GUI') self.move(100, 100) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.move(10, 10) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.move(70, 10) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Pencerelerin boyutlarını değiştirmek için QWidget sınıfındaki resize metodu kullanılmaktadır. resize metodu genişlik-yükseklik değerlerini bizden ister. Eğer alt pencereler resize edilmezse Qt onları default olarak belli bir genişlik ve yükseklik ile yaratmaktadır. Örneğin bir QPushButton nesnesi default durumda içerisindeki yazıyı kapsayacak genişlikte yaratılır. resize metodu çalışma alanının genişlik ve yüksekliğini belirtilen değerlere getirmektedir. Yani ana pencereler için bu genişlik ve yüksekliğe pencere başlığı ve sınır çizgileri dahil değildir. resize metodu "çalışma alanının (client area)" genişlik ve yüksekliğini ayarlamaktadır. Örneğin bir ana pencereye resize metodunu w ve h dğerleriyle uygularsak tüm pencerenin geniliği ve yükseliği w ve h değerlerinde olmaz. Çalışma alanının genişlik ve yüksekliği w ve h değerlerinde olur. Yani buna pencere başlığı ve sınır çizgileri dahil değildir. Genellikle alt pencereler sınıf çizgilerine ve başlık kısımlarına sahip olmazlar. Böylece genellikle zaten alt pencerelerin çalışma alanıyla onların bütünü aynı olur. Ancak istenirse (nadir de olsa) alt pencereler de sınır çizgilerine ve başlık kısımlarına sahip olabilmektedir. Aşağıdaki örnekte hem ana pencere hem de push button GUI elemanları resize metoduyla istenilen bir büyüklüğe getirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Sample GUI') self.move(100, 100) self.resize(480, 350) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.move(10, 10) self.pushButtonOk.resize(100, 30) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.move(110, 10) self.pushButtonCancel.resize(100, 30) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Penecereleri konumlandırmak ve boyutlarını ayarlamak için yalnızca move ve resize metotları yoktur. QWidget sınıfının birbirleriyle ilişkili pek çok metodu vardır. Bu metotlar Qt'yi öğrenen kişilerde biraz kafa karıştırıcı olabilmektedir. Aşağıdkai bağlantıdaki şekli kafa karışıklığınızı gidermekte kullanabilirsiniz: https://doc.qt.io/qt-6/application-windows.html#window-geometry x ve ye metotları pencerenin bütününe ilişkin sol üst köşe koordinatlarının x ve y değerlerini vermektedir. (Burada bütününe ilişkin demekle ana pencerelerde başlık kısmı ve sınır çizgilerinin dahil olduğu anlatılmaktadır.) Bu iki metot bize bu bilgileri int türden vermektedir. x ve y değerlerini QPoint olarak tek hamlede pos metoduyla da elde edebiliriz. Tabii bu metotlarla elde edilen değerlerin orijin noktası ana pencereler için masaüstünün sol-üst köşesi, alt pencereler için onların üst pencerelerinin sol-üst köşesidir. Pencereyi konumlandırmak için kullanılan QWidget sınıfının move metodu konumlandırmayı her zaman pencerenin bütününün sol-üst köşesi için yapmaktadır. QWidget sınıfının geometry isimli metodu bize pencerenin çalışma alanının dikdörtgensel bölgesini QRect nesnesi olarak vermektedir. Tabii buradaki orijin ana pencereler için masaüstünün sol-üst köşesi, alt pencereler için onların üst pencerelerinin sol-üst köşesidir. Bu metodunun setGeometry isminde set eden bir biçimi de vardır. Tabii setGeometry de çalışma alanının dikdörtgenini set etmektedir. Eğer pencere başlığı ve sınır çizgileri dahil olmak üzere tüm pencerenin dikdörtgeni alınmak isteniyorsa frameGeometry metodu kullanılır. Bunu set eden setFrameGeometry metodu yoktur. Pencerenin çalışma alanının genişliği width metodu ile, yükseliği ise height metodu ile alınmaktadır. setWidth ve setHeight biçiminde metotlar yoktur. size metodu yine çalışma alaının genişlik ve yüksekliğini bize QSize nesnesi olarak verir. frameSize isimli metot pencerenin bütünün genişliğini ve yüksekliğini QSize nesnesi olarak vermektedir. Pencerenin bütünün boyutunu ayarlayabilmek için bir metot yoktur. Bu işlem dolaylı bir biçimde yapılabilmektedir. QWidget sınıfının rect isimli metodu çalışma alanının dikdörtgenini çalışma alanı orijinli biçimde vermektedir. Dolayısıyla rect metodu ile alınan dikdörtgensel bölgenin sol-üst köçşesi her zaman (0, 0) olmaktadır. rect ile geometry metotları birbirine benzer olmakla birlikte geometry çalışma alanının dikdörtgenini üst pencere ya da masaüstü orijinli verirken rect yine çalışma alanı orijinli vermektedir. rect ile elde edilen dikdörtgenin sol-üst köşesinin her zaman (0, 0) olması kişilere anlamsız gelebilmektedir. Ancak bazı konularda bu metot faydalı bir biçimde kullanılmaktadır. Konumlandırmaya ilişkin QWidget sınıfının metotlarını özet olarak aşağıda yeniden veriyoruz x y pos move size resize width height geometry frameGeometry frameSize rect #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QWidget sınıfının setGeometry özellikle metodu move ve resize yapmadan tek hamlede pencereyi konumlandırmak için kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Sample GUI') self.setGeometry(100, 100, 480, 350) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.setGeometry(120, 10, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir pencerenin en büyük genişlik ve yüksekliğini ayarlamak için QWidget sınıfının setMaximumWidth, setMaximumHeight ve setMaximumSize metotları kullanılmaktadır. Böylece kullanıcı pencereyi burada belirtilen değerlerden daha fazla büyütemeyecektir. Bu değerleri almak için ise maximumWidth, maximumHeight ve maximumSize metotları bulundurulmuştur. Pencerenin küçültülebilecek en küçük boyutu da benzer isimdeki metotlarla belirlenip ayarlanabilmektedir. setMinimumWidth, SetMinimumHeight ve setMinimumSize en küçük boyutu ayarlamak için, minimumWidth, minimumHeight ve minimumSize ek boyutları elde etmek için kullanılmaktadır. Tabii bu metotlar özellikle ana pencereler için anlamlı olmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setMaximumSize(400, 200) self.setMinimumSize(250, 120) self.setWindowTitle('Sample GUI') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.setGeometry(120, 10, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir ana pencereyi programalama yoluyla maximize, minimize, restore ve full secreen haline getirebilmek için QWidget sınıfının sırasıyla showMAximum, showMinimum, showNormal ve showFullScreen metotları bulundurulmuştur. Pencerenin o anda minimum durumda olup olmadığı QWidget sınıfının isMinimized metodu ile, maksimum durumda olup olmadığı isMaximized metodu ile ve full screen durumunda olup olmadığı isFullScreen metodu ile elde edilebilmektedir. Bu metotlar bize bool türden değer vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.showMinimized() print(self.isMaximized()) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.setGeometry(120, 10, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt dünyasında bir GUI elemanda oluşan olaya "sinyal (signal)" bu olay sonucunda çağrılacak fonksiyona da "slot (slot)" denilmektedir. Bir olay gerçekleştiğinde bir fonksiyonun (ya da metodun) çağrılmasını sağlamak için Qt terminolojisinde "sinyale slotun bağlanması" gerekir. GUI eleman sınıflarının hangi sinyallerinin ve slotlarının olduğu dokümanlarda belirtilmiştir. Tabii custom sinyaller de oluşturulabilmektedir. Tipik olarak bir sinyale bir slot bağlanmaktadır. Ancak sinyale sinyal de bağlanabilmektedir. Bir A sinyaline bir B sinyali bağlanırsa A olayı olduğunda sanki B olayı olmuş gibi bir etki yaratılmaktadır. Bir sinyale birden fazla slot ve sinyal de bağlanabilir. Bu durumda olay gerçekleştiğinde bu bağlanmış olan sinyal ve slotların hepsi sırasıyla devreye girer. Qt programcısının hangi GUI elemanı için hangi sinyallerin bulunduğunu biliyor olması gerekir. Bu bilgi dokümantasyonlardan elde edilebilir. Bir sinyale bir slot bağlanmasının genel biçimi PyQt'de şöyledir: widget.signal_name.connect(handler) Burada signal_name sinyalin ismidir. connect metodu bağlantıyı yapan metottur. handler ise çağrılacak fonksiyon ya da metodu belirtmektedir. Tabii burada çağrılacak metot ya da fonksiyonun parametrik yapısı istenildiği biçimde olamaz. Dokümantasyondaki sinyalin parametrik yapısını uygun olmalıdır. Yani her sinyale bağlanacak slot fonksiyonunun ya da metodunun parametrik yapısı o sinyale özgü olarak değişebilmektedir. Qt framework'ünde pek çok türetme yapılmıştır. Bir sınıfın kendi sinyali olmasa da onun taban sınıflarının siyali de kaltım gereği kullanılabilmektedir. Örneğin QPushButton sınıfının kendi sinyali yoktur. Ama bu sınıf QAbstractButton sınıfından türetilmiştir. Programcı o sınıftaki sinyalleri sanki QPushButton sinyali imiş gibi kullanabilmektedir. Nesne yönelimli bir modelle çalışılıyorsa slotların da ilgili sınıfın bir metodu yapılması uygun olmaktadır. Bir sinyalin oluşmasına Qt'nin kendi terminolojisinde "sinyalin emit" edilmesi denilmektedir. Biz kursumuzda "sinyalin emit edilmesi" yerine "sinyalin tetiklenmesi" terimini kullanacağız. Bir GUI eleman öğrenilirken o GUI elemana ilişkin sinyallerin neler olduğu da öğrenilmelidir. Bazı sinyaller çok seyrek kullanılmaktadır. Örneğin QPushButton GUI elemanının en önemli sinyali clicked isimli sinyalidir. Bu GUI elemanı için diğer sinyallere neredeyse hiç gereksinim duyulmamaktadır. Aşağıdaki örnekte iki QPushButton GUI elemanı kullanılmıştır. QPushButton sınıfının QAbstractButton sınıfından gelen clicked isimli bir sinyali vardır. Biz düğme üzerinde farenin tuşuna basıp yine onu düğme üzerinde çekersek clicked sinyali tetiklenir ve slot fonksiyonu ya da metodu çağrılır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Signal/SLot Mechanism') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.setGeometry(120, 10, 100, 100) self.pushButtonCancel.clicked.connect(self.pushButtonCancelHandler) def pushButtonOkHandler(self): print("Ok") def pushButtonCancelHandler(self): print("Cancel") app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir sinyal tetiklendiğinde çalıştırılacak slot kodunun uzun bir zaman almaması gerekir. Aksi takdirde QApplication sınıfının exec metodu GUI mesaj kuyruğundan sıradaki mesajı alamaz. Bu da programın sanki donmuş olduğu gibi bir durum oluşturur. Bir sinyal tetiklendiğinde slot fonksiyonu hızlı bir biçimde gerekeni yapıp sonlanmalıdır. Aşağıdaki örnekte Ok düğmesine basıldığında uzun bir işlem yapılmaktadır. Bu yüzden program sanki donmuş gibi bir etki oluşacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * import time class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Signal/SLot Mechanism') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): for i in range(100): print(i) time.sleep(1) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir GUI eleman (yani bir widget) "enabled" ya da "disabled" durumda olabilir. GUI elemanın "enabled" olması demek klavye ve fare işlemlerine tepki verebilmesi demektir. GUI elemanlar default durumda "enabled" biçimdedir. GUI eleman "disabled" duruma getirilirse biz onun üzerine tıklasak bile bu işlem sinyal oluşturmaz. Yani GUI eleman dış dünyanın eylemlerine kapatılmış olur. Pek çok GUI eleman "disabled" duruma getirildiğinde sönük bir renge boyanmaktadır. Böylece kullancılar sezgisel olarak onun disabled olduğunu anlarlar. GUI elemanı enabled/disabled yapmak için QWidget sınıfından gelen setEnabled metodu kullanılmaktadır. Bu metodun parametresi bool türdendir. GUI elemanın enabled/disabled olup olmadığını anlamak için ise enabled metodu kullanılmaktadır. Bu metot da bize bool bir değer geri vermektedir. Aşağıdaki örnekte ana pencere üzerinde iki düğme vardır. Bu düğmelerin bir makineyi çalıştırdığı varsayılmaktadır. Başlangıçta makine çalışmadığına göre Stop düğmesine basılmasının bir anlamı yoktur. Start düğmesine basıldığında makine çalışacaktır. Makine çalışırken Start düğmesine basmanın da anlamı yoktur. Yani bu düğmelerin belli bir anda yalnızca biri "enabled" yapılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Enabled/Disables') self.pushButtonStart = QPushButton('Start', self) self.pushButtonStart.setGeometry(10, 10, 100, 100) self.pushButtonStart.clicked.connect(self.pushButtonStartHandler) self.pushButtonStop = QPushButton('Stop', self) self.pushButtonStop.setGeometry(130, 10, 100, 100) self.pushButtonStop.setEnabled(False) self.pushButtonStop.clicked.connect(self.pushButtonStopHandler) def pushButtonStartHandler(self): self.pushButtonStart.setEnabled(False) self.pushButtonStop.setEnabled(True) print('machine starts...') def pushButtonStopHandler(self): self.pushButtonStop.setEnabled(False) self.pushButtonStart.setEnabled(True) print('machine stops...') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ GUI uygulamalarında print ve input fonksionlarının kullanılması uygun değildir. Çünkü bu fonksiyonlar konsol ekranına yazıp oradan okuma yapmaktadır (teknik anlamda stout ve stdin dosyaları). GUI programlarında kullanıcıya birtakım mesajlar vermek için genel olarak "messagebox" denilen "owned" pencereler (diyalog pencereleri) kullanılmaktadır. Qt'de messagebox oluşturmanın birkaç yolu vardır Birinci yolu QMessageBox sınıfını kullanmaktır. QMessageBox sınıfı ile messagebox oluşturmanın adımları şöyledir: 1) QMessageBox sınıfı türünden bir nesne yaratılır. Yaratım sırasında messagebox'ın üst penceresi de belirtilmektedir. Örneğin: mb = QMessageBox(self) 2) Bu nesne ile sınıfın setWindowTitle metodu çağrılarak pencerenin başlık yazısı oluşturulur. Örneğin: mb.setWindowTitle('Sample MessageBox') 3) Sınıfın setText metodu ile messagebox içerisindeki yazı oluşturulur. Örneğin: mb.setText('This is a test') 4) Messagebox üzerinde görüntülenecek düğmelerin neler olacağı setStandardButtons isimi metotla ayarlanabilmektedir. Bu metta programcı QMessageBox sınıfının içerisindeki çeşitli sınıf değişkenlerini bit düzeyinde | operatörü ile birleştirebilir. Örneğin: mb.setStandardButtons(QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) Tabii burada programcı kısıtlı sayıda düğme seçeneklerini kullanabilmektedir. Burada kullanabilecek düğmeler şunlardır: MessageBox.Ok QMessageBox.Open QMessageBox.Save QMessageBox.Cancel QMessageBox.Close QMessageBox.Discard QMessageBox.Apply QMessageBox.Reset QMessageBox.RestoreDefaults QMessageBox.Help QMessageBox.SaveAll QMessageBox.Yes QMessageBox.YesToAll QMessageBox.No QMessageBox.NoToAll QMessageBox.Abort QMessageBox.Retry QMessageBox.Ignore QMessageBox.NoButton 5) Sınıfın setIcon metodu ile messagebox içerisinde görüntülenecek olan ikon ayarlanabilir. Tabii biz her ikonu burada görüntüleyemeyiz. Görüntüleyebileceğimiz ikonlar şunlardır: QMessageBox.NoIcon QMessageBox.Question QMessageBox.Information QMessageBox.Warning QMessageBox.Critical Örneğin: mb.setIcon(QMessageBox.Information) 6) En sonunda sınıfın exec metodu çağrılmalıdır. Messagebox bir modal diyalog penceresidir. Dolayısıyla akış exec içerisinde alır. Pencere kapatıldıktan sonra akış exec metodundan çıkar. Tuş takımlarından istenilen bir tanesi default tuş olarak da belirlenebilir. Bunun için sınıfın setDefaultButton metodu çağrılmalıdır. Bu belirleme yapılmazsa ilk düğme default olur. Örneğin: mb.setDefaultButton(QMessageBox.Cancel) Tabii programcının kullanıcının hangi tuşa basarak messsabox penceresini kapattığını bilmesi gerekir. İşte exec metodunun geri dönüş değeri bize bu bilgiyi vermektedir. Bu değer bizim belirlediğimiz düğmelerden birine eşit olacaktır. Örneğin: result = mb.exec() if result == QMessageBox.Yes: print('yes') elif result == QMessageBox.No: print('no') elif result == QMessageBox.Cancel: print('cancel') Default durumda ESC tuşu ile de messagebox penceresi kapatılabilmektedir. Aslında ESC tuşuna basıldığında hangi tuşa basılmış gibi davranılacağı da değiştirilebilmektedir. Bunun için sınıfın setEscapeButton metodu kullanılmaktadır. Örneğin: mb.setEscapeButton('No') #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QMessageBox Sample') self.resize(320, 200) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): mb = QMessageBox(self) mb.setWindowTitle('Sample MessageBox') mb.setText('Save changes?') mb.setStandardButtons(QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) mb.setDefaultButton(QMessageBox.Cancel) mb.setIcon(QMessageBox.Information) result = mb.exec() if result == QMessageBox.Yes: print('yes') elif result == QMessageBox.No: print('no') elif result == QMessageBox.Cancel: print('cancel') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aslında bir messagebox çıkartmak için yapılması gerekenler bir fonksiyona da yaptırılabilir. İşte Qt'de QMessageBox sınıfının static bazı metotları zaten yukarıda açıkladığımız işlemleri yaparak mesaj penceresi çıkarmaktadır. Bu metotlar şunlardır: about(parent, title, text) critical(parent, title, text[, buttons=QMessageBox.StandardButton.Ok[, defaultButton=QMessageBox.StandardButton.NoButton]]) information (parent, title, text[, buttons=QMessageBox.StandardButton.Ok[, defaultButton=QMessageBox.StandardButton.NoButton]]) question (parent, title, text[, buttons=QMessageBox.StandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)[, defaultButton=QMessageBox.StandardButton.NoButton]]) standardIcon (icon) warning (parent, title, text[, buttons=QMessageBox.StandardButton.Ok[, defaultButton=QMessageBox.StandardButton.NoButton]]) Aslında metotların arasındaki farklar tuş takımı ve özellikle de ikonik görüntü ile ilgilidir. Genel olarak bu metotların hepsinin ilk parametreleri üst pencere nesnesini belirtir. Sonraki iki parametre ise pencere başlık yazısı ve pencerenin iç yazısıdır. Sonraki parametreler ise tuş takımı ve default tuş takımına ilişkindir. Örneğin: result = QMessageBox.information(self, 'Sample MessageBox', 'Sample text', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Yes: print('yes') elif result == QMessageBox.No: print('no') elif result == QMessageBox.Cancel: print('cancel') Aslında yukarıdaki metotlar arasındaki tek farklılık ikonik görüntüdür. Yani biz QMessagex.information yerine örneğin QMessageBox.Warning kullansak argümanları hiç değiştirmeyebiliriz. Tek farklılık messagebox'ta görüntülenecek ikon olacaktır. Yukarıdaki metotların hepsi yine çıkılan düğme ile geri dönmektedir. about isimli metotot yalnızca üç parametre alır. Bu metot yalnızca "Ok" düğmesi çıkartır. Biz yazının görüntülenmesi için kullanılır. Örneğin: QMessageBox.about(self, 'Message', 'this is a message') #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QMessageBox Sample') self.resize(320, 200) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Yes: print('yes') elif result == QMessageBox.No: print('no') elif result == QMessageBox.Cancel: print('cancel') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Şimdi artık tek tek GUI elemanlarını inceleyeceğiz. Ancak araya başka konular da alacağız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QPushButton sınıfı GUI uygulamalarında en fazla karşılaşılan düğme GUI elemanını temsil etmektedir. Sınıfın türetme şeması şöyledir: QWidget QAbstractButton QPushButton QCheckBox QRadioButton Görüldüğü gibi QPushButton, QCheckBox ve QReadioButton sınıflarının ortak elemanları AAbstractButton sınıfında toplanmıştır. Tüm pencerelerin ortak elemanları ise QWidget sınıfında bulunmaktadır. Bir QPushButton nesnesi genellikle üzerindeki yazıyla yaratılır. Örneğin: pushButtonOk = QPushButton('Ok', self) Bu GUI elemanının en önemli sinyali "clicked" isimli sinyaldir. Bu sinyali yukarıda tanıtmıştır. İstediğimiz zaman düğmenin içerisindeki yazıyı text metoduyla alıp setText metodu ile değiştirebiliriz. Bu metotlar QAbstractButton sınıfından geldiği için QCheckBox ve QRadioButton sınıflarıyla da kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Checkbox denilen GUI elemanı QCheckBox sınıfıyla temsil edilmektedir. Bu sınıf da QAbstractButton sınıfından türetilmiştir. Bu GUI eleman bir küçük kare ve onun yanında bir yazı ile görüntülenir. Kullanıcı bu pencereye tıklandığında karenin içi çarpılı değilse çarpılanır, çarpılıysa çarpısı kaldırılır. Karenin çarpılı olmasına İngilizce "checked" durumu çarpısız olmasına ise "unchecked" durumu denilmektedir. Programcı bu GUI elemandan karenin çarpılı olup olmadığı bilgisini elde etmek ister. Elemanın çarpılı olup olmadığı QAbstractButton sınıfından gelen isChecked metoduyla belirlenir. GUI elemanının yazısı yine QAbstractButton sınıfından gelen text metoduyla alınıp setText metoduyla set edilebilmektedir. Yine aynı sınıfta bulunan clicked, toggled isimli sinyaller GUI elemanına tıklandığında ve çarpılanma durumu değiştirildiğinde emit edilmektedir. GUI elemanını programalama yoluyla çarpılamak için setChecked metodu kullanılmaktadır. setChecked metodu bool bir parametre bekler. True "checked" durumunu False ise "unchecked" durumunu belirtir. toggled isimli sinyale bağlanacak slot fonksiyonun bir bool türden parametresi olmak zorundadır. Bu parametre checkbox GUI elemanının yeni durumun "checked" mi "unchecked" mi lduğunu belirtir. Aşağıdaki örnekte bir QPushButton ve bir QCheckBox GUI elemanı pencereye yerleştirilmiş ve düğmeye basınca checkbox'ın çarpılı olup olmadığı bilgisi görüntülenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QCheckBox Sample') self.resize(320, 200) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.checkBoxTest = QCheckBox('Test', self) self.checkBoxTest.move(120, 45) self.checkBoxTest.setChecked(True) self.checkBoxTest.toggled.connect(self.checkBoxToggledHandler) def pushButtonOkHandler(self): QMessageBox.information(self, 'Checked Status', 'Checked' if self.checkBoxTest.isChecked() else 'Unchecked') def checkBoxToggledHandler(self, checked): print(f'New status: {"Checked" if checked else "Unchecked"}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir checkbox üç konumlu (tristate) olabilir. Checkbox GUI elemanını üç konumlu moda sokmak için setTristate metodu kullanılır. Üç konumlu checkbox GUI elemanının konumunu almak için ise checkState metodu kullanılmaktadır.Bu checkState metodu şu üç değeren birini vermektedir: Qt.Checked Qt.Unchecked Qt.PartiallyChecked Üç konumlu check box elemanını belli bir konuma programalama yoluyla getirilebilmek için setCheckState metodu kullanılmaktadır. Bu metot da yukarıdaki üç parametreden birini almaktadır. Aşağıdaki örnekte bir üç konumlu QCheckBox nesnesi bir de QPushButton nesnesi yaratılmıştır. Düğmeye tıklandığında üç konumlu check box nesnesinin konumu message box ile görüntülenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QCheckBox Sample') self.resize(320, 200) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.checkBoxTest = QCheckBox('Test', self) self.checkBoxTest.setTristate(True) self.checkBoxTest.move(120, 45) self.checkBoxTest.setCheckState(Qt.PartiallyChecked) def pushButtonOkHandler(self): state = self.checkBoxTest.checkState() if state == Qt.Checked: QMessageBox.information(self, "Check Status", "Checked") elif state == Qt.Unchecked: QMessageBox.information(self, "Check Status", "Unchecked") else: QMessageBox.information(self, "Check Status", "Partially checked") app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 27/02/2023 - 20.ders #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Radyo düğmeleri (Radio Buttons) çok karşılaşılan GUI elemanlardandır. Bu GUI eleman bir küçük yuvarlak ve yanında bir yazıyla görüntülenmektedir. Tek bir radyo düğmesinin bir anlamı yoktur. Radyo düğmeleri bir grup olarak kullanılmaktadır. Bir radyo düğmesi grubunda bir radyo düğmesine tıklandığında daha çnce çarpılı olan düğmenin çarpısı kaldırılır tıklanan düğme çarpılı hale getirilir. Böylece belli bir anda yalnızca tek bir düğme çarpılı olabilmektedir. Radyo düğmeleri bir grup seçenekten kullanıcının yalnızca bir tanesini seçmesinin istendiği durumlarda kullanılmaktadır. Radyo düğmeleri Qt'de QRadioButton sınıfı ile temsil edilmiştir. Bu sınıf da QAbstractButton sınıfından türetilmiştir. QWidget <--- QAbstractButton <--- QPushButton, QCheckBox, QRadioButton Bir grup radyo düğmesinde hangi düğmenin çarpılı olduğunu anlamak için tüm radyo düğmelerine (çarpılı olanı bulana kadar) QAbstractButton sınıfınından gelen isChecked metodu uygulanmalıdır. Bnezer biçimde belli bir düğmeyi programlama yoluyla çarpılı hale getirmek için yine setCheck metodu kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QRadioButton Sample') self.resize(320, 200) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.radioButtonA = QRadioButton('A', self) self.radioButtonA.move(120, 10) self.radioButtonB = QRadioButton('B', self) self.radioButtonB.move(120, 35) self.radioButtonC = QRadioButton('C', self) self.radioButtonC.move(120, 60) self.radioButtonD = QRadioButton('D', self) self.radioButtonD.move(120, 85) self.radioButtonE = QRadioButton('E', self) self.radioButtonE.move(120, 110) def pushButtonOkHandler(self): text = 'None of them checked' if self.radioButtonA.isChecked(): text = 'A checked' elif self.radioButtonB.isChecked(): text = 'B checked' elif self.radioButtonC.isChecked(): text = 'C checked' elif self.radioButtonD.isChecked(): text = 'D checked' elif self.radioButtonE.isChecked(): text = 'E checked' QMessageBox.information(self, 'Radio buttons status', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Çok fazla radyo düğmesi söz konusu olduğunda bunlar bir listeye yerleştirilip hangisinin çarpılandığı bir döngü içerisinde belirlenebilir. Aşağıdaki örnekte bu yöntem kullanılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QRadioButton Sample') self.resize(320, 200) self.radioButtonList = [None] * 5 self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.radioButtonList[0] = QRadioButton('A', self) self.radioButtonList[0].move(120, 10) self.radioButtonList[1] = QRadioButton('B', self) self.radioButtonList[1].move(120, 35) self.radioButtonList[2] = QRadioButton('C', self) self.radioButtonList[2].move(120, 60) self.radioButtonList[3] = QRadioButton('D', self) self.radioButtonList[3].move(120, 85) self.radioButtonList[4] = QRadioButton('E', self) self.radioButtonList[4].move(120, 110) def pushButtonOkHandler(self): text = 'None of them is checked' for rb in self.radioButtonList: if rb.isChecked(): text = rb.text() + ' checked' break QMessageBox.information(self, 'Radio buttons status', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir pencerenin bütün alt pencereleri QWidget sınıfının children metoduyla elde edilebilir. Bu metot bize o pencerenin içerisindeki tüm GUI elemanları bir liste biçiminde verecektir. Aşağıdaki örnekte ana pencere içerisindeki QRadioButton nesneleri tespit edilip onların üzerinde isChecked metodu uygulanmış ve hangi radyo düğmesinin çarpılı olduğu bilgisi elde edilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QRadioButton Sample') self.resize(320, 200) self.radioButtonList = [None] * 5 self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.radioButtonList[0] = QRadioButton('A', self) self.radioButtonList[0].move(120, 10) self.radioButtonList[1] = QRadioButton('B', self) self.radioButtonList[1].move(120, 35) self.radioButtonList[2] = QRadioButton('C', self) self.radioButtonList[2].move(120, 60) self.radioButtonList[3] = QRadioButton('D', self) self.radioButtonList[3].move(120, 85) self.radioButtonList[4] = QRadioButton('E', self) self.radioButtonList[4].move(120, 110) def pushButtonOkHandler(self): text = 'No button checked' for widget in self.children(): if type(widget) == QRadioButton and widget.isChecked(): text = widget.text() + ' checked' break QMessageBox.information(self, 'Radio buttons status', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aynı üst pencerenin kardeş radyo düğmeleri aynı grubu oluşturmaktadır. Bu nedenle iki farklı radyo düğmesi grubu oluşturabilmek için radyo düğme gruplarını başka bir dummy pencereye yerleştirmek gerekir. İşte bu amaçla kullanılan pencerelere "group box" denilmektedir. Qt'de group box pencereleri QGroupBox sınıfı ile temsil edilmiştir. Aşağıdaki örnekte iki ayrı grup box içerisinde iki ayrı dayo düğmesi grubu oluşturulmuştur. QGroupBox pencerelerinin içerisindeki radyo düğmeleri konumlandırılırken orijin noktalarının kendi üst pencerelerinin (burada grroup box pencereleri) çalışma alaının sol üst köşesi oldacağına dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QRadioButton Sample') self.resize(480, 350) self.groupBoxFruit = QGroupBox('Meyveler', self) self.groupBoxFruit.setGeometry(10, 10, 100, 150) self.groupBoxTree = QGroupBox('Ağaçlar', self) self.groupBoxTree.setGeometry(130, 10, 100, 150) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 170, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.radioButtonBanana = QRadioButton('Muz', self.groupBoxFruit) self.radioButtonBanana.move(10, 20) self.radioButtonCherry = QRadioButton('Kiraz', self.groupBoxFruit) self.radioButtonCherry.move(10, 45) self.radioButtonApple = QRadioButton('Elma', self.groupBoxFruit) self.radioButtonApple.move(10, 70) self.radioButtonApricot = QRadioButton('Kayısı', self.groupBoxFruit) self.radioButtonApricot.move(10, 95) self.radioButtonDate = QRadioButton('Hurma', self.groupBoxFruit) self.radioButtonDate.move(10, 120) self.radioButtonPine = QRadioButton('Çam', self.groupBoxTree) self.radioButtonPine.move(10, 20) self.radioButtonOak = QRadioButton('Meşe', self.groupBoxTree) self.radioButtonOak.move(10, 45) self.radioButtonWillow = QRadioButton('Söğüt', self.groupBoxTree) self.radioButtonWillow.move(10, 70) self.radioButtonPlane = QRadioButton('Çınar', self.groupBoxTree) self.radioButtonPlane.move(10, 95) self.radioButtonPoplar = QRadioButton('Kavak', self.groupBoxTree) self.radioButtonPoplar.move(10, 120) def pushButtonOkHandler(self): selectedFruit = 'Hiçbir meyve seçilmedi' selectedTree = 'Hiçbir ağaç seçilmedi' for widget in self.groupBoxFruit.children(): if widget.isChecked(): selectedFruit = widget.text() + ' seçildi' break for widget in self.groupBoxTree.children(): if widget.isChecked(): selectedTree = widget.text() + ' seçildi' break QMessageBox.information(self, 'Seçilen Meyve Ve Ağaç', selectedFruit + '\n' + selectedTree) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi GUI uygulamalarında bir pencerenin içerisine sadece yazı yazı yerleştirmek istesek bunu nasıl yapabiliriz? Aslında ileride pencerelerin içerisine doğrudan yazı yazmanın nasıl yapılacağını göreceğiz. Ancak genellikle GUI uygulamalarında pencere içerisine yazı yazdırmak alt pencere yoluyla dolaylı bir biçimde yapılmaktadır. İsmine "label" denilen bir alt pencere bu amaçla kullanılmaktadır. Label penceresinin sınır çizgileri çizilmez. Dolayısıyla kişiler orada bir alt pencere olduğunu anlamayabilirler. Label pencerelerinin içi de default olarak üst pencereyle aynı renkte boyanmaktadır. Label penceresinin tek işlevi içerisindeki yazının görüntülenmesini sağlamaktır. Label pencereleri Qt'ede QLabel sınıfı ile temsil edilmiştir. Programcı QLable sınıfı türünden bir nesne yaratır. Pencerede belirttiği yazı görüntülenecek olan yazıdır. Bu yazıyı istediği zaman text metouyla alıp setText değiştirebilmektedir. Oluşturulan label penceresi sadece bu yazıyı görüntüleyeceğine göre pencerenin konumu aslında yazının sol üst köşesi olacaktır. QLabel nesnesini resize ya da setGepmetry ile boyutlandırmazsak default olarak pencere genişlik ve yüksekliği yazıyı içerecek kadar olur. Yazı birden fazla satırdan oluşsun isteniyorsa yazı içerisinde \n karakteri kullanılmalıdır. Qt'de Font kavramı QFont isimli bir sınıfla temsil edilmiştir. QLabel sınıfı QWidget sınıfından türetilmiş durumdadır. QWidget sınıfının font ve setFont metotları bize o anda kullanılan fontu get ve set eder. Bu durumda biz bir label (ya da diğer herhangi bir GUI eleman) içerisindeki yazının büyüklüğünü ve biçimini değiştirmek için o nesne ile setFont metodunu çağırmalıyız. Ancak setFont metodu bizden QFont nesnesi istemektedir. Bir QFont nesnesi ise font ailesinin ismi ve punto büyüklüğü ile yaratılır. Örneğin: font = QFont('Timer New Roman', 20) QFont nesnesi yaratılırken diğer iki parametre de sırasıyla yazının "boltluk durumunu" ve italiklik durumunu belirtir. Bolt'luk durumu şunlardan biri olabilmektedir: QFont.Thin QFont.ExtraLight QFont.Light QFont.Normal QFont.Medium QFont.DemiBold QFont.Bold QFont.ExtraBold QFont.Black İtalikli durumu ise True, False biçiminde belirtilmektedir. Örneğin: font = QFont('Times New Roman', 20, QFont.Bold, True) Tabii QFont nesnesi yaratıldıktan sonra da fontun özellikleri QFont sınıfının setXXX metotlarıyla da set edilebilmektedir. QFont sınıfı PyQt5.QtGui modülü içerisindedir. Dolayısıyla programcının şu import işlemini de koda dahil etmesi gerekir: form PyQt5.QtGui import * Renk bilgisi fontun bir özelliği değildir. Aşağıdaki örnekte bir QLabel nesnesi yoluyla bir yazı ana pencereye yazılmıştır. Ok düğmesine her basıldığında bu yazı değiştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLabel Sample') self.resize(480, 350) self.count = 0 self.labelMessage = QLabel('Sayaç: 0', self) self.labelMessage.setFont(QFont('Times New Roman', 20, QFont.Bold, True)) self.labelMessage.setGeometry(10, 10, 150, 50) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 170, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): self.count += 1 self.labelMessage.setText(f'Sayaç: {self.count}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLabel nesnesinin bir alt pencere oluşturduğuna yazının alt pencerenin içerisine yazıldığına dikkat ediniz. Aşağıdaki örnekte henüz görmemiş olsak da Qt'nin "stylesheet" özelliği ile QLabel alt penceresinin zemin rengini ve QLabel içerisindeki yazının rengini aşağıdaki gibi bir metot çağrısıyla değiştirdik: self.labelMessage.setStyleSheet('QLabel {background-color: yellow; color: blue}') #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QLabel sınıfının setAlignment isimli metodu yazıyı QLabel penceresinin içerisinde hizalamakta kullanılır. Hizalama için yatay ve düşey iki değerin bit düzeyinde | operatörü ile OR'lanması gerekmektedir. Yatay hizalama için kullanılacak değerler şunlardır: Qt.AlignLeft Qt.AlignRight Qt.AlignHCenter Qt.AlignJustify Düşey hizalama için kullanılacak değerler de şunlardır: Qt.AlignTop Qt.AlignBottom Qt.AlignVCenter Qt.AlignBaseline O halde biz örneğin label penceremiz içerisindeki yazıyı dikdörtgensel alanın içerisine şöyle ortalayabiliriz: self.labelMessage.setAlignment(Qt.AlignHCenter|Qt.AlignVCenter) Default alignment Qt.AlignLeft|Qt.AlignVCenter biçimindedir. Aşağıdaki örnekte QLable penceresinin sınırları anlaşılsın diye pencere sarı renge boyanmıştır ve yazı pencere içerisinde sağ alta hizalanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLabel Sample') self.resize(480, 350) self.labelMessage = QLabel('Ankara', self) self.labelMessage.setFont(QFont('Times New Roman', 14, QFont.Bold, True)) self.labelMessage.setGeometry(10, 10, 100, 100) self.labelMessage.setStyleSheet('QLabel {background-color: yellow; color: blue}') self.labelMessage.setAlignment(Qt.AlignRight|Qt.AlignBottom) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 01/03/2023 Çarşamba - 21. Ders #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bazı framework'lerde resim görüntülemek için özel GUI elemanlar kullanılmaktadır. Genellikle de bu tür elemanlar PictureBox gibi, PictureWidget gibi isimler alırlar. Qt'de resim görüntülemek için QLabel GUI elemanından faydalanılabilmektedir. Programcı önce resmi bir QPixmap nesnesi biçiminde oluşturur. Sonra QLabel sınıfının setPixmap metodu ile resmi QLabel nesnesine iliştirir. QPixmap sınıfı pek çok resim formatını desteklemektedir. Sınıfın width ve height metotları bize resmin genişlik ve yüksekliğini int olarak size metodu ise QSize nesnesi olarak vermektedir. Tabii biz QLael nesnesini resim göstermek için kullanacaksak bu durumda ona yazı iliştirmeyiz. Aslında pek çok GUI elemanı tek parametreyle yaratıldığında o tek parametre PyQt'de zaten üst pencere nesnesi olarak ele alınmaktadır. Örneğin: labelPicture = QLabel(self) Yani bizim aşağıdaki gibi boş bir yazı oluşturmamaıza gerek yoktur: labelPicture = QLabel('', self) Bir QLabel nesnesi resim göstermek için kullanıldığında eğer QLabel GUI elemanı boyutlandırılmadıysa zaten default olarak resim boyutuna getirilmektedir. GUI elemanların default konumlarının (0, 0) olduğunu anımsayınız. Qt'te resmin pixel verileri QPixmap sınıfyla temsil edilmiştir. Bir QPixmap nesnesi resim dosyası belirtilerek yaratılmaktadır. Örneğin: image = QPixmap('AbbeyRoad.jpg') Aşağıdaki örnekte ana pencere resim boyutuna getirilmiştir. QLabel üzeinde ise herhangi bir konumlandırma ya da boyutlandırma yapılmamıştır. Bu durumda QLabel nesnesinin konumu (0, 0) boyutları da zaten resim kadar olacaktır. Böylece aşağıdaki örnekte ana pencere içerisinde tüm resim görüntülenecektir. Bu örnekte ana pencerenin de minimum ve maximum genişlik ve yüksekliği resim yüksekliğine getirilmiş böylece ana pencerenin boyutlarının değiştirilmesine izin verilmemiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLabel Sample') pixmap = QPixmap('AbbeyRoad.jpg') self.resize(pixmap.size()) self.setMinimumSize(pixmap.size()) self.setMaximumSize(pixmap.size()) self.labelPicture = QLabel(self) self.labelPicture.setPixmap(pixmap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Eğer QLabel nesnesi boyutlandırılmışsa ve boyutu resimden küçükse bu durumda QLabel resmin sol üst köşesindeki kısmı gösterir. Eğer resim QLabel penceresindne küçük olursa resim QLabel penceresinin içerisinde alignment ile belirtilen hizda görüntülenmektedir. Aşağıdaki örnekte biz belli büyüklükte bir QLabel GUI elemanı oluşturup bu GUI elemanının daha küçük bir resmi görüntülemesini sağladık. QLabel penceresinin zemin rengi kasten sarıya boyanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLabel Sample') self.resize(1000, 800) pixmap = QPixmap('Daisy.jpg') self.labelPicture = QLabel(self) self.labelPicture.setPixmap(pixmap) self.labelPicture.setGeometry(10, 10, 700, 700) self.labelPicture.setStyleSheet('QLabel {background-color: yellow}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Ancak daha çok istenen şey QLabel nesnesini resme öre boyutlandırmak değil de resmi QLabel nesnesinin boyutuna getirmektir. Bu tür işlemlere resmin "scale" edilmesi ya da "stretch" edilmesi denilmektedir. Bunun için QPixmap sınıfının scaled isimli metodu kullanılmaktadır. Bu metot bize scale edilmiş yeni bir QPixmap nesnesi verir. Örneğin: pixmap = QPixmap('AbbeyRoad.jpg') pixmapScaled = pixmap.scaled(200, 200) Tabii bu işlemi tek hamlede de aşağıdaki gibi yapabiliriz: pixmap = QPixmap('AbbeyRoad.jpg').scaled(200, 200) Ancak resimleri scale ederken dikkat etmek gerekir. Çünkü scale işlemi ile resmin en boy oranı bozulabilir. Bu bozulma sonucunda resim istenmeyen biçimde görünebilir. En boy oranını koruma işlemi manuel bir biçimde yapılabilir. Ancak bunun için zaten scale metodunda üçüncü bir parametre bulundurulmuştur. Bu üçüncü parametre şunlardan biri olabilmektedir: Qt.IgnoreAspectRatio Qt.KeepAspectRatio Qt.KeepAspectRatioByExpanding Burada Qt.KeepAspectRatio verilen boyutlardan birini sağlayıp deiğerini küçülterek en boy oranını korur. Qt.KeepAspectRatioByExpanding ise verilen boyutlardan birini koruyup diğerini yükselterek en boy oranını korumaktadır. Genellikle burada seçilmesi gereken Qt.KeepAspectRatio değeridir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(480, 320) self.setWindowTitle('QPixmap Example') pixmap = QPixmap('AbbeyRoad.jpg') pixmap = pixmap.scaled(320, 200, Qt.KeepAspectRatio) self.labelImage = QLabel(self) self.labelImage.setGeometry(10, 10, 320, 200) self.labelImage.setPixmap(pixmap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir resinm dikdörtgensel bir büyüklüğe sahiptir. Bu dikdörtgensel büyüklükte resmin içinde olan ancak resmin zeminini oluşturan kısım o resmi yerleştirdiğimiz zemini bozabilmektedir. İşte bu tür durumlarda zemini bozan kısımların transparan yapılması gerekir. Belli bir rengin transparan yapılması mümkünse de aslında resmin neresinin transparan yapılacağı resmi oluşturan kişi tarafından daha iyi bir biçimde bilinir. Dolayısıyla resmi oluşturan kişiler nerelerin transparan yapılması gerektiğini belirleyebilirler. İşte PNG dosya format ı kendi içerisinde bu transparanlık bilgisini bulundurduğu için PNG resimlerin başka bir zemine yerleştirilmesi tam olarak onların o zemine nüfuz etmiş gibi görünmelerini sağlamaktadır. Bu tür durumlarda PNG formatını tercih etmelisiniz. Aşağıdaki örnekte bir satranç taşının hem BMP hem de PNG versiyonları yan yana çizdirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Picture Sample') self.resize(320, 200) pixmap1 = QPixmap(r'ChessFigures\BlackBishop.png').scaled(100, 100, Qt.KeepAspectRatio) pixmap2 = QPixmap(r'ChessFigures\BlackBishop.bmp').scaled(100, 100, Qt.KeepAspectRatio) self.label1 = QLabel(self) self.label1.setPixmap(pixmap1) self.label1.setGeometry(10, 10, 100, 100) self.label2 = QLabel(self) self.label2.setPixmap(pixmap2) self.label2.setGeometry(130, 10, 100, 100) # self.labelAbbeyRoad.setStyleSheet('QLabel {background-color: yellow}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 60x60'lık PNG formatındaki satranç taşlarını 60x60 olmayan kareler üzerinde çizdirebilmek için önce kareyi çizeriz sonra taşı çizeriz. #------------------------------------------------------------------------------------------------------------------------------------- import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * FIGURE_SIZE = 60 class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Picture Sample') self.resize(320, 200) pixmap1 = QPixmap(r'ChessFigures\WhiteSquare.bmp').scaled(FIGURE_SIZE, FIGURE_SIZE) pixmap2 = QPixmap(r'ChessFigures\BlackKing.png') self.label1 = QLabel(self) self.label1.setPixmap(pixmap1) self.label1.setGeometry(10, 10, FIGURE_SIZE, FIGURE_SIZE) self.label2 = QLabel(self) self.label2.setPixmap(pixmap2) self.label2.setGeometry(10, 10, FIGURE_SIZE, FIGURE_SIZE) # self.labelAbbeyRoad.setStyleSheet('QLabel {background-color: yellow}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Şimdi de bir satranç tahtasını taşlarıyla birlikte çizdirelim. Bu çizim için gereken BMP ve PNG resimler kurtaski ChessFigures klasörü içerisindedir. Aşağıdaki programdaki ana noktalar şunlardır: - Satranç tahtasını pencere içerisinde marjinle ortalamak için TOP_MARGIN ve LEFT_MARGIN isimli iki sembolik sabit kullanılmıştır. - Programda karelerin ve dolayısıyla taşların büyüklükleri FIGURE_SIZE sembolik sabitiyle ayarlanabilmektedir. - Önce kareler QLabel GUI elemanı ile oluşturulmuş sonra onun üzerine yine QLabel ile taş resimleri bindirilmiştir. - Satranç tahtasındaki her karenin matriste bir satır sürun numarası vardır. Numaralar sol üst kareden başlatılmaktadır. - Her karenin sol üst köşe noktası squarePos isimli bir sözlükte saklanmıştır. Bu sözlüğe biz karenin koordinatını bir demet biçiminde verdiğimizde bu sözlük bize o karenin sol üst köşesinin x ve y değerlerini verir. - Burada henüz taşlara ilişkin QLabel nesneleri herhangi bir yerde tutulmamıştır. Eğer bu nesneler tutuluyor olsaydı biz bu QLabel nesnesi üzerinde setGeometry metodunu uyguladığımızda taşı hareket ettirebilidik. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * LEFT_MARGIN = 20 TOP_MARGIN = 20 FIGURE_SIZE = 80 class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Chess Board') self.resize(800, 600) self.resize(LEFT_MARGIN * 2 + 8 * FIGURE_SIZE, TOP_MARGIN * 2 + 8 * FIGURE_SIZE) self.setMaximumSize(LEFT_MARGIN * 2 + 8 * FIGURE_SIZE, TOP_MARGIN * 2 + 8 * FIGURE_SIZE) self.setMinimumSize(LEFT_MARGIN * 2 + 8 * FIGURE_SIZE, TOP_MARGIN * 2 + 8 * FIGURE_SIZE) self.squarePos = {} self.loadPixmap() xpos = LEFT_MARGIN ypos = TOP_MARGIN for row in range(8): for col in range(8): self.squarePos[(row, col)] = xpos, ypos label = QLabel(self) label.setGeometry(xpos, ypos, FIGURE_SIZE, FIGURE_SIZE) label.setPixmap(self.pixmapWhiteSquare if (row + col) % 2 == 0 else self.pixmapBlackSquare) xpos += FIGURE_SIZE xpos = LEFT_MARGIN ypos += FIGURE_SIZE blackPieces = [self.blackRook, self.blackKnight, self.blackBishop, self.blackQueen, self.blackKing, self.blackBishop, self.blackKnight, self.blackRook] whitePieces = [self.whiteRook, self.whiteKnight, self.whiteBishop, self.whiteQueen, self.whiteKing, self.whiteBishop, self.whiteKnight, self.whiteRook] for col in range(8): xpos, ypos = self.squarePos[(1, col)] label = QLabel(self) label.setGeometry(xpos, ypos, FIGURE_SIZE, FIGURE_SIZE) label.setPixmap(self.blackPawn) xpos, ypos = self.squarePos[(6, col)] label = QLabel(self) label.setGeometry(xpos, ypos, FIGURE_SIZE, FIGURE_SIZE) label.setPixmap(self.whitePawn) xpos, ypos = self.squarePos[(0, col)] label = QLabel(self) label.setGeometry(xpos, ypos, FIGURE_SIZE, FIGURE_SIZE) label.setPixmap(blackPieces[col]) xpos, ypos = self.squarePos[(7, col)] label = QLabel(self) label.setGeometry(xpos, ypos, FIGURE_SIZE, FIGURE_SIZE) label.setPixmap(whitePieces[col]) def loadPixmap(self): self.pixmapWhiteSquare = QPixmap(r'ChessFigures\WhiteSquare.bmp').scaled(FIGURE_SIZE, FIGURE_SIZE) self.pixmapBlackSquare = QPixmap(r'ChessFigures\BlackSquare.bmp').scaled(FIGURE_SIZE, FIGURE_SIZE) self.whitePawn = QPixmap(r'ChessFigures\WhitePawn.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.whiteKnight = QPixmap(r'ChessFigures\WhiteKnight.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.whiteBishop = QPixmap(r'ChessFigures\WhiteBishop.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.whiteRook = QPixmap(r'ChessFigures\WhiteRook.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.whiteQueen = QPixmap(r'ChessFigures\WhiteQueen.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.whiteKing = QPixmap(r'ChessFigures\WhiteKing.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.blackPawn = QPixmap(r'ChessFigures\BlackPawn.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.blackKnight = QPixmap(r'ChessFigures\BlackKnight.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.blackBishop = QPixmap(r'ChessFigures\BlackBishop.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.blackRook = QPixmap(r'ChessFigures\BlackRook.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.blackQueen = QPixmap(r'ChessFigures\BlackQueen.png').scaled(FIGURE_SIZE, FIGURE_SIZE) self.blackKing = QPixmap(r'ChessFigures\BlackKing.png').scaled(FIGURE_SIZE, FIGURE_SIZE) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 06/Mart/2023 - 22. Ders #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ PyQt'de tek satırlı edit penceresi QLineEdit sınıfıyla temsil edilmiştir. Sınıf nesnesi üst pencere belirtilerek yaratılır. Örneğin: self.lineEditName = QLineEdit(self) İstersek edit alanında belli bir yazı görüntülenecek biçimde de GUI elemanı yaratabiliriz. Örneğin: self.lineEditName = QLineEdit('This is a test', self) Konumlandırma yine move, resize ya da setGeometry metotlarıyla yapılır. Programcı edit alanı içerisine girilmiş olan yazıyı almak ister. Bunun için sınıfın text isimli metodu kullanılmaktadır. Yine edit alanı içerisindeki yazı sınıfın setText metodu ile set edilebilir. QLineEdit sınıfı doğrudan QWidget sınıfından türetilmiştir: QWidget <--- QLineEdit #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) self.lineEditName = QLineEdit(self) self.lineEditName.move(10, 10) self.lineEditName.resize(200, 20) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 40, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): text = self.lineEditName.text() QMessageBox.information(self, 'Message', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Edit alanı içerisindeki yazı default durumda sola dayılı biçimde oluşturulmaktadır. Ancak programcı sınıfın setAlignment metodu ile yazının hizalanmasını değiştirebilir. setAlignment metodunun parametresi şunlardan biri olabilir: Qt.AlignLeft Qt.AlignRight Qt.AlignHCenter Qt.AlignJustify Aşağıdaki örnekte edit alanındaki yazı ortalanmıştır. Edit alanı içerisindeki yazının hizalanma biçimi alignment isimli metotla elde edilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) self.lineEditName = QLineEdit(self) self.lineEditName.move(10, 10) self.lineEditName.resize(200, 20) self.lineEditName.setAlignment(Qt.AlignCenter) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 40, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): text = self.lineEditName.text() QMessageBox.information(self, 'Message', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden QWidget sınıfının içerisinde default bir font nesnesinin olduğunu belirtmiştik. Bu font nesnesi GUI elemanlar sınıflarının QWidget sınıfından gelen font metoduyla elde edilip setFont metoduyla set edilebilir. font metodu bize QWidget nesnesinin içerisindeki font'un bir kopyasını vermektedir. Örneğin biz edit alanı içerisindeki yazının yalnızca büyüklüğünü değiştirmek istiyorsak önce font metodu ile font nesnesini alıp pointSize metodu ile punto büyüklüğünü ayarlayıp setFont metodu ile nesnesiy yeniden set etmeliyiz: font = self.lineEditName.font() font.setPointSize(14) self.lineeditName.setFont(font) Aşağıdaki örnekte GUI elemanların font'ları ana pencerenin default font nesnesindne hareketle büyütülmüştür. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.labelName = QLabel('Adı Soyadı', self) self.labelName.move(10, 5) self.labelName.setFont(font) self.lineEditName = QLineEdit(self) self.lineEditName.move(10, 30) self.lineEditName.resize(300, 25) self.lineEditName.setFont(font) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 70, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): text = self.lineEditName.text() QMessageBox.information(self, 'Message', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ GUI elemanlarının fontları QWidget sınıfından gelen setFont metoduyla değiştirilmektedir. Bu metot bizden QFont türünden bir nesne ister. QFont sınıfının da __init__ metodu sırasuyla font ailesinin ismini (boş string geçilebilir), punto büyüklüğünü sonraki iki parametresi bold'luk ve italik durumunu belirtmektedir. Son parametre True geçilirse italik font oluşturulmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.labelName = QLabel('Adı Soyadı', self) self.labelName.move(10, 5) self.labelName.setFont(font) self.lineEditName = QLineEdit(self) self.lineEditName.move(10, 30) self.lineEditName.resize(300, 25) self.lineEditName.setFont(font) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 70, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): text = self.lineEditName.text() QMessageBox.information(self, 'Message', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ setInputMask metodu edit alanına girilecek karakterleri sınırlnadırmak ve biçimlendirmek için kullanılmaktadır. Bu metodun parametresi mask yazısını alır. Mask karakterleri için Qt dokümanlarına başvurabilirsiniz. Mask karakterlerinin tipik olanlarından biri 9'dur. Buradaki 9 aslında herhangi bir sayısal karakteri temsil etmektedir. Mask karakterlerinin dışındaki karakterler ilgili pozisyonda görüntülenmektedir. Örneğin: self.lineEditDate.setInputMask('99/99/9999) Bu tipik bir tarih bilgisi almak için oluşturulmuş mask'tir. > karakteri alfabetik karakterlerin her zaman büyük harfle < karakteri ise küçük harfle görüntüleneceğini belirtir. Örneğin: self.lineEdit.setInputMask('>99-AAA-999') Burada bir plake girişi için mask oluşturulmuştur. Oratadaki üç alfabetik karakter her zaman büyük harf ile görüntülenecektir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEditDate = QLineEdit(self) self.lineEditDate.move(10, 10) self.lineEditDate.resize(300, 25) self.lineEditDate.setFont(font) #self.lineEditDate.setInputMask('99/99/9999') self.lineEditDate.setInputMask('>99-AAA-999') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): text = self.lineEditDate.text() QMessageBox.information(self, 'Message', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLineEdit GUI elemanı "read-only" moda sokulabilir. Bunun için sınıfın setReadOnly metodu True ile çağrılmalıdır. GUI Elemanın read-only modda olup olmadığı isReadOnly metodu ile elde edilebilir. Nesne read-only moddayken bir klavye ile edit alanına yazı giremeyiz. Ancak programlama yoluyla setText metodu ile yazı girebiliriz. Aşağıdaki örnekte QLineEdit nesnesi read only moda sokulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.lineEdit.setReadOnly(True) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): self.lineEdit.setText('This is a test') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Edit alanı içerisine girilebilecek karakter sayısı setMaxLength metodu ile sınırlandırılabilir. Örneğin: self.lineEdit.setMaxLength(10) Burada klavyeyele ya da setText metodu ile en fazla edit alanına 10 karakter girilebilir. Set edilen bu değer maxLength metoduyla alınabilir. Aşağıdaki örnekte edit alanına girilecek karakter sayısı 10 ile sınırlandırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.lineEdit.setMaxLength(10) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', self.lineEdit.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLineEdit GUI elemanı default durumda sınır çizgilerine sahiptir. Ancak biz setFrame metodunu False argümanı ile çağrırısak sınır çizgilerini kaldırabiliriz. hasFrame metodu ise sınır çizgilerinin var olup olmadığı bilgisini bize verecektir. Aşağıdaki örnekte sınır çizgileri olmayan bir eidt alanı oluturulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.lineEdit.setFrame(False) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', self.lineEdit.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLineEdit sınıfının setPlaceholderText isimli metodu edit alanı boşken gösterilecek ipucu yazısını belirtmektedir. Örneğin: self.lineEditName.setPlaceholderText('Adınızı soyadınızı giriniz') placeholderText metodu ise bu yazıyı get etmek için kullanılmaktadır. Aşağıdaki örnekte böyle bir edit alanı oluşturulmuştur #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.lineEdit.setPlaceholderText('Adınızı soyadınızı giriniz') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', self.lineEdit.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLineEdit sınıfının setEchoMode metodu edit alanına yazı girilirken gösterilecek karakterleri belirlemekte kullanılır. Metodun parametresi şunlardan biri olabilir: QLineEdit.Normal (Her yazılan görüntülenir) QLineEdit.NoEcho (Yazılanlar hiç görüntülenmez) QLineEdit.Password (Yazılanlar yerine * gibi bir sembol basılır) QLineEdit.PasswordEchoOnEdit (Yazılanlar görüntülenir ama klavye odağı başka bir pencereye geçirildiğinde orası password girişi gibi görüntülenir) Aşağıdaki örnekte bir passord girişi oluşturulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.lineEdit.setPlaceholderText('Enter password') self.lineEdit.setEchoMode(QLineEdit.Password) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', self.lineEdit.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Edit alanındaki yazının bir bölümünü programalama yoluyla seçmek için setSelection metodu seçimin başlangıç karakter pozisyonu ve uzunluğu ile çağrılır. Örneğin: self.lineEdit.setSelection(5, 10) Burada edit alanındaki yazı 5'inci pozisyondan itibaren 10 karakter select edilecektir. Sınıfın selectionStart, selectionEnd ve selectionLength isimli metotları select edilmiş olan bölge hakkında bilgileri bize vermektedir. selectedText metodu ise select edilen yazıyı bize verir. Aşağıda selection için bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): self.lineEdit.setSelection(1, 10) selectedText = self.lineEdit.selectedText() QMessageBox.information(self, 'Message', selectedText) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLineEdit sınıfının isModified metodu edit alanı içerisindeki yazının güncellenip güncellenmediği bilgisini bize vermektedir. Sınıfın setModified metodu False ile çağrılırsa sanki güncelleme yapılmamış gibi durum oluşur. Default durumda henüz edit alanına yazı girilmediyse isModified metodu False değerini vermektedir. Programcı önce setModifed metodunu False ile çağırır. Bir süre sonra edit alanında bir güncelleme olup olmadığını isModified metodu ile belirler. Aşağıda metodun kullanımına bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): if self.lineEdit.isModified(): print('Modified') self.lineEdit.setModified(False) else: print('Not modified') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QLineEdit GUI elemanının 7 farklı sinyali vardır. Bunlardan textChanged ve returnPressed isimli sinyaller sırasıyla edit alanındaki yazı değiştiğinde ve edit alanı içerisinde ENTER tuşuna basıldığında emit edilmektedir. textChanged sinyali edit alanında tuşa basıldıktan sonraki yazıyı veren bir parametreye sahiptir. Sınıfın diğer sinyalleri için Qt dokümanlarına başvurabilriisniz. Aşağıda QLineEdit sınıfının textChanged ve returnPressed sinyallerinin kullanımına örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QLineEdit Sample') self.resize(800, 600) font = self.font() font.setPointSize(14) self.lineEdit = QLineEdit(self) self.lineEdit.move(10, 10) self.lineEdit.resize(300, 25) self.lineEdit.setFont(font) self.lineEdit.textChanged.connect(self.lineEditTextChangedHandler) self.lineEdit.returnPressed.connect(self.lineEditReturnPressedHandler) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 50, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonOk.setFont(font) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', self.lineEdit.text()) def lineEditTextChangedHandler(self, newText): print(newText) def lineEditReturnPressedHandler(self): QMessageBox.information(self, 'returnPressedHandler', self.lineEdit.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir grup elemanı listelemek için kullanılan GUI elemanına Qt'de "list widget" denilmektedir. POek çok framework'te bu GUI eleman "listbox" biçiminde isimlendirilmektedir. List widget Qt'de QListWidget sınıfıyla temsil edilmiştir. Tpik kullanımda QListWidget sınıfı türünden nesne yaratılır, nesne konumlandırılır sonra da elemanlar eklenir. QListWidget GUI elemanına eleman eklemek için additem metodu kullanılabilir. Bu addItem metoduna biz argüman olarak bir yazı verebiliriz. Birden fazla elemanı eklemek için ise addItems metodu kullanılmaktadır. addItems metodu dolaşılabilir bir string nesnesini alır ve onları tek tek list widget nesnesine ekler. QListWidget sınıfının insertItem isimli metodu belli bir indekse eleman insert etmektedir. Örneğin: self.listWidget.insertItem(3, Kastamonu) Burada eleman 3'üncü indekste olacak biçimde insert edilmektedir. Aşağıdaki örnekte bir liste widget nesnesine elemanlar eklenmiştir. Burada düğmenin henüz bir işlevi yoktur. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): self.listWidget.insertItem(3, 'Kastamonu') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Tabii yine listwidget elemanları için font QListWidget sınıfının setFont metodu ile değiştirilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.setFont(QFont('Arial', 12, QFont.Bold)) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 23. Ders 08/03/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Daha önce yapmış olduğumuz satranç tahtası örneğini nesne yönelimli biçimde yeniden yazalım. Bu tasarımda tüm satranç tahtası Board isimli bir sınıfla, kareler Square sınıfıyla ve Taşlar da Figure sınıfıyla temsil edilmiştir. Karelerin içinde ya bir taş vardır ya da yoktur. Eğer Square sınıfının Figure örnek özniteliği None ise o karede taş yoktur. None değilse o karede taş vardır. Taşın bilgileri de Figure sınıfının içeriinde bulunmaktadır. Bu örnekte farenin bir tuşuna basıldığında ve çekildiğinde iki fonksiyon çağrılmaktadır. Bu konuyu henüz ele almadık. İzleyen bölümlerde bu konu ele alınacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * LEFT_MARGIN = 50 TOP_MARGIN = 50 SQUARE_SIZE = 80 class Figure: def __init__(self, labelFigure, color, name): self.color = color self.name = name self.labelFigure = labelFigure class Square: def __init__(self, labelSquare, pos): self.labelSquare = labelSquare self.pos = pos self.figure = None def setFigure(self, figure): self.figure = figure self.figure.labelFigure.setGeometry(self.pos[0], self.pos[1], SQUARE_SIZE, SQUARE_SIZE) class Board: def __init__(self, widget): pixmapWhiteSquare = QPixmap(r'ChessFigures/WhiteSquare.bmp').scaled(SQUARE_SIZE, SQUARE_SIZE) pixmapBlackSquare = QPixmap(r'ChessFigures/BlackSquare.bmp').scaled(SQUARE_SIZE, SQUARE_SIZE) self.squares = {} xpos = LEFT_MARGIN ypos = TOP_MARGIN for row in range(8): for col in range(8): labelSquare = QLabel(widget) labelSquare.setPixmap(pixmapWhiteSquare if (row + col) % 2 == 0 else pixmapBlackSquare) labelSquare.setGeometry(xpos, ypos, SQUARE_SIZE, SQUARE_SIZE) square = Square(labelSquare, (xpos, ypos)) self.squares[(row, col)] = square xpos += SQUARE_SIZE xpos = LEFT_MARGIN ypos += SQUARE_SIZE whitePawn = QPixmap(r'ChessFigures/WhitePawn.png').scaled(SQUARE_SIZE, SQUARE_SIZE) whiteKnight = QPixmap(r'ChessFigures/WhiteKnight.png').scaled(SQUARE_SIZE, SQUARE_SIZE) whiteBishop = QPixmap(r'ChessFigures/WhiteBishop.png').scaled(SQUARE_SIZE, SQUARE_SIZE) whiteRook = QPixmap(r'ChessFigures/WhiteRook.png').scaled(SQUARE_SIZE, SQUARE_SIZE) whiteQueen = QPixmap(r'ChessFigures/WhiteQueen.png').scaled(SQUARE_SIZE, SQUARE_SIZE) whiteKing = QPixmap(r'ChessFigures/WhiteKing.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackPawn = QPixmap(r'ChessFigures/BlackPawn.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackKnight = QPixmap(r'ChessFigures/BlackKnight.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackBishop = QPixmap(r'ChessFigures/BlackBishop.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackRook = QPixmap(r'ChessFigures/BlackRook.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackQueen = QPixmap(r'ChessFigures/BlackQueen.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackKing = QPixmap(r'ChessFigures/BlackKing.png').scaled(SQUARE_SIZE, SQUARE_SIZE) blackPieces = [('Siyah', 'Kale', blackRook), ('Siyah', 'At', blackKnight), ('Siyah', 'Fil', blackBishop), ('Siyah', 'Vezir', blackQueen), ('Siyah', 'Şah', blackKing), ('Siyah', 'Fil', blackBishop), ('Siyah', 'At', blackKnight), ('Siyah', 'Kale', blackRook)] whitePieces = [('Beyaz', 'Kale', whiteRook), ('Beyaz', 'At', whiteKnight), ('Beyaz', 'Fil', whiteBishop), ('Beyaz', 'Vezir', whiteQueen), ('Beyaz', 'Şah', whiteKing), ('Beyaz', 'Fil', whiteBishop), ('Beyaz', 'At', whiteKnight), ('Beyaz', 'Kale', whiteRook)] for col in range(8): square = self.squares[(1, col)] xpos, ypos = square.pos labelFigure = QLabel(widget) labelFigure.setGeometry(xpos, ypos, SQUARE_SIZE, SQUARE_SIZE) labelFigure.setPixmap(blackPawn) figure = Figure(labelFigure, 'Siyah', 'Pawn') square.setFigure(figure) square = self.squares[(6, col)] xpos, ypos = square.pos labelFigure = QLabel(widget) labelFigure.setGeometry(xpos, ypos, SQUARE_SIZE, SQUARE_SIZE) labelFigure.setPixmap(whitePawn) figure = Figure(labelFigure, 'Beyaz', 'Pawn') square.setFigure(figure) square = self.squares[(0, col)] xpos, ypos = square.pos labelFigure = QLabel(widget) labelFigure.setGeometry(xpos, ypos, SQUARE_SIZE, SQUARE_SIZE) labelFigure.setPixmap(blackPieces[col][2]) figure = Figure(labelFigure, blackPieces[col][0], blackPieces[col][1]) square.setFigure(figure) square = self.squares[(7, col)] xpos, ypos = square.pos labelFigure = QLabel(widget) labelFigure.setGeometry(xpos, ypos, SQUARE_SIZE, SQUARE_SIZE) labelFigure.setPixmap(whitePieces[col][2]) figure = Figure(labelFigure, whitePieces[col][0], whitePieces[col][1]) square.setFigure(figure) def move(self, sourcePos, targetPos): sourceSquare = self.squares[sourcePos] targetSquare = self.squares[targetPos] if targetSquare.figure: targetSquare.figure.labelFigure.close() targetSquare.setFigure(sourceSquare.figure) sourceSquare.figure = None class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Chess Board') self.resize(800, 600) self.resize(LEFT_MARGIN * 2 + 8 * SQUARE_SIZE, TOP_MARGIN * 2 + 8 * SQUARE_SIZE) self.setMaximumSize(LEFT_MARGIN * 2 + 8 * SQUARE_SIZE, TOP_MARGIN * 2 + 8 * SQUARE_SIZE) self.setMinimumSize(LEFT_MARGIN * 2 + 8 * SQUARE_SIZE, TOP_MARGIN * 2 + 8 * SQUARE_SIZE) self.board = Board(self) def pointToPos(self, point): col = (point.x() - LEFT_MARGIN) // SQUARE_SIZE if col > 7 or col < 0: return None row = (point.y() - TOP_MARGIN) // SQUARE_SIZE if row > 7 or row < 0: return None return row, col def mousePressEvent(self, me): self.sourcePos = self.pointToPos(me.pos()) if self.sourcePos: square = self.board.squares[self.sourcePos] if square.figure: print(square.figure.color + ' ' + square.figure.name) def mouseReleaseEvent(self, me): if not self.sourcePos: return targetPos = self.pointToPos(me.pos()) if targetPos: if self.sourcePos == targetPos: return sourceSquare = self.board.squares[self.sourcePos] targetSquare = self.board.squares[targetPos] if targetSquare.figure and sourceSquare.figure.color == targetSquare.figure.color: return if sourceSquare.figure: self.board.move(self.sourcePos, targetPos) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 24. Ders 13/03/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında QListWidget nesnesi satırlarında yazıları tutmaz. QListWidgetItem nesnelerini tutar. Yani biz aslında QListWidgetItem nesnelerini yaratıp addItem ya da insertItem metotlarıyla ekleyebiliriz. Bu sayede biz listbox içerisindeki satırlardaki elemanları daha ayrıntılı formatlayarak oluşturabiliriz. Aslında biz bu metotlara yazı versek de bu metotlar o yazılardan QListWİdgetItem nesnesi oluşturup eklemeyi yapmaktadır. QListWidgetItem sınıfı QListWidget sınıfındaki satırları temsil etmektedir. Bir satır yalnızca bir yazıdan oluşmak zorunda değildir. Orada ikonik bir resim de bulundurulabilir. Ayrıca her satır diğerlerinden bağımsız olarak ayrı bir biçimde renklendirilebilir ve ayrı font ile görüntülenebilir. Programcı QListWidgetItem sınıfı türünden bir nesne yaratır. Nesneyi yaratırken satırda görüntülenecek yazıyı verebilir. Sonra sınıfın çeşitli set metotlarıyla satırın özelliklerini değiştirir. Yarattığı bu QListWidgetItem nesnesini yine addItem ya da insertItem metotlarıyla QListWidget nesnesine ekleyebilir. Aşağıdaki örnekte Ok düğmesine basıldığında bir QListWidgetItem nesnesi yaratılıp onun bazı özellikleri set edilmiş ve bu nesne QListWidget nesnesine eklenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): lwi = QListWidgetItem('Kastamonu') lwi.setFont(QFont('Arial', 20)) lwi.setForeground(QBrush(Qt.red)) pixmap = QPixmap('button.png') lwi.setIcon(QIcon(pixmap)) self.listWidget.addItem(lwi) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QListWidget penceresinin içerisindeki elemanların (satırların) sayısı count isimli metot ile elde edilebilir. Ayrıca QListWidget sınıfının item isimli metodu bize ilgili indeksteki elemanı (satırı) QListWidgetItem nesnesi olarak vermektedir. Aşağıdaki örnekte Ok düğmesine basıldığında QListWidget nesnesi içerisindeki bütün elemanlar dolaşılıp tek tek yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): for i in range(self.listWidget.count()): lwi = self.listWidget.item(i) text = lwi.text() print(text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QListWidgetItem penceresinde seçili olan elemanın (satırın) indeks numarası currentRow metodu ile elde edilebilir. Eğer henüz hiçbir satır seçili değilse bu metot -1 değerini vermektedir. Benzer biçimde o anda seçili olan elemanın (satırın) bilgisi de currentItem metodu ile elde edilebilir. currentItem metodu bize QListWidgetItem nesnesi vermektedir. Eğer hiçbir eleman seçili değilse metot None değeriyle geri döner. Aşağıdaki örnekte Ok düğmesine basıldığında seçili olan elemanın yazısı messagebox ile yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): lwi = self.listWidget.currentItem() if lwi: QMessageBox.information(self, 'Message', lwi.text()) """ index = self.listWidget.currentRow() if index != -1: lwi = self.listWidget.item(index) QMessageBox.information(self, 'Message', lwi.text()) """ app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Programlama yoluyla belli bir elemanı (satırı) seçili duruma getirmek için setCurrentRow metodu ya da setCurrentItem metodu kullanılmaktadır. setCurrentRow metodu seçilecek elemanın satır indeksinşi alır. setCurrentItem metodu ise seçilecek elemanın QListWidgetItem nesnesini almaktadır. Genellikle setCurrentRow metodu bunun için kullanılmaktadır. setCurrentRow metodu -1 değeriyle setCurrentItem metodu None değeriyle çağrılırsa seçili elemanın seçimi kaldırılmış olur. Aşağıdaki örnekte işin başında "İstanbul" satırı seçili olacak biçimde QListWidget nesnesi görüntülenmektedir. Ok d #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.listWidget.setCurrentRow(3) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): self.listWidget.setCurrentRow(-1) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Default durumda QListWidget penceresinde yalnızca tek eleman seçilebilir. Ancak seçim modeu sınıfın setSelectionMode metoduyla değiştirilebilmektedir. Bu metot QListWidget sınıfındaki aşağıdaki sabitlerden bir tanesini parametre olarak almaktadır: QAbstractItemView.SingleSelection Bu modda tek eleman seçilebilir (default) QAbstractItemView.ContiguousSelection Bu modda ayrık seçim yapılamaz ancak ardışık seçim yapılabilir QAbstractItemView.ExtendedSelection Bu modda hem ardışık hem de ayrık seçimler yapılabilmektedir QAbstractItemView.MultiSelection Bu modda yaşnızca ayrık seçim yapılabilmektedir QAbstractItemView.NoSelection Bu modda seçim hiç yapılamaz, yalnızca elemanlar görüntülenir Birden fazla seçim yapıldığında currentRow ya da currentItem metotları son seçilmiş olanı bize vermektedir. Tüm seçilen elemanları almak için selectedItems metodu kullanılmaktadır. Bu metot bize QListWidgetItem nesnelerinden oluşan bir liste vermektedir. Eğer hiçbir eleman seçilmemişse boş bir liste verilmektedir. Aşağıdaki örnekte QListWidgert nesnesi çoklu seçim (ExtendedSelection) moduna sokulmuştur. Seçilen elemanlar selectedItems metodu ile elde edilip messagebox ile yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidget = QListWidget(self) self.listWidget.setGeometry(10, 10, 200, 300) self.listWidget.setSelectionMode(QListWidget.ExtendedSelection) self.listWidget.addItem('Adana') self.listWidget.addItem('Ankara') self.listWidget.addItem('İzmir') self.listWidget.addItem('İstanbul') self.listWidget.addItem('Kahraman Maraş') self.listWidget.addItem('Adıyaman') self.listWidget.addItem('Hatay') self.listWidget.addItem('Malatya') self.listWidget.addItem('Gaziantep') self.listWidget.addItems(['Kars', 'Ardahan', 'Eskişehir', 'Bolu', 'Samsun', 'Siirt', 'Sinop', 'Sakarya', 'Bartın', 'Zonguldak']) self.listWidget.setCurrentRow(3) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(240, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): text = '' for lwi in self.listWidget.selectedItems(): text += lwi.text() + '\n' QMessageBox.information(self, 'Message', text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QListWidget sınıfının birkaç önemli sinyali vardır. currentRowChanged isimli sinyal o anda seçili olan eleman değiştirildiğinde emit edilmektedir. Bu sinyalin int bir parametresi vardır. Bu parametre yeni seçilen satıırn indeks numarasını bize verir. Aşağıdaki örnekte iki tane QListWidget nesnesi oluşturulmuştur. Soldaki nesne şehirleri sağdaki nesne ise onun ilçelerini göstermektedir. Ne zaman soldaki QListWidget penceresinde bir şehir seçilse onun ilçeleri yandaki QListWidget penceresinde görüntülenmektedir. QListWidget nesnesinin içindeki tüm elemanları silmek için clear metodu kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidgetCities = QListWidget(self) self.listWidgetCities.setGeometry(10, 10, 200, 300) font = self.font() font.setPointSize(20) self.listWidgetCities.setFont(font) self.listWidgetCities.addItem('Eskişehir') self.listWidgetCities.addItem('Ankara') self.listWidgetCities.addItem('İzmir') self.listWidgetCities.addItem('İstanbul') self.listWidgetCities.currentRowChanged.connect(self.listWidgetCitiesCurrentRowChangedHandler) self.listWidgetVillages = QListWidget(self) self.listWidgetVillages.setGeometry(230, 10, 200, 300) font = self.font() font.setPointSize(20) self.listWidgetVillages.setFont(font) self.cities = { 'Eskişehir': ['Mihalıççık', 'Sivrihisar', 'Alpu', 'Mahmudiye', 'Çifteler'], 'Ankara': ['Ulus', 'Sincan', 'Keçiören', 'Kızılcahamam', 'Çankaya'], 'İzmir': ['Konak', 'Urla', 'Karşıyaka', 'Bergama', 'Bornova'], 'İstanbul': ['Beşiktaş', 'Ataşehir', 'Sultanbeyli', 'Kadıköy', 'Bakırköy'] } def listWidgetCitiesCurrentRowChangedHandler(self, rowIndex): self.listWidgetVillages.clear() lwi = self.listWidgetCities.currentItem() self.listWidgetVillages.addItems(self.cities[lwi.text()]) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QListWidget sınıfının currentItemChanged isimli sinyali de seçili eleman değiştiğinde tetiklenmektedir. Ancak bu sinyale ilişkin sinyal fonksiyonun iki QListWidgetItem parametresi vardır. Birinci parametre sonraki elemanı ikinci parametre önceki elemanı belirtmektedir. Yukarıdaki örnek aşağıda currentItemChanged sinyali kullanılarak oluşturulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidgetCities = QListWidget(self) self.listWidgetCities.setGeometry(10, 10, 200, 300) font = self.font() font.setPointSize(20) self.listWidgetCities.setFont(font) self.listWidgetCities.addItem('Eskişehir') self.listWidgetCities.addItem('Ankara') self.listWidgetCities.addItem('İzmir') self.listWidgetCities.addItem('İstanbul') self.listWidgetCities.currentItemChanged.connect(self.listWidgetCitiesCurrentItemChangedHandler) self.listWidgetVillages = QListWidget(self) self.listWidgetVillages.setGeometry(230, 10, 200, 300) font = self.font() font.setPointSize(20) self.listWidgetVillages.setFont(font) self.cities = { 'Eskişehir': ['Mihalıççık', 'Sivrihisar', 'Alpu', 'Mahmudiye', 'Çifteler'], 'Ankara': ['Ulus', 'Sincan', 'Keçiören', 'Kızılcahamam', 'Çankaya'], 'İzmir': ['Konak', 'Urla', 'Karşıyaka', 'Bergama', 'Bornova'], 'İstanbul': ['Beşiktaş', 'Ataşehir', 'Sultanbeyli', 'Kadıköy', 'Bakırköy'] } def listWidgetCitiesCurrentItemChangedHandler(self, newItem, oldItem): self.listWidgetVillages.clear() self.listWidgetVillages.addItems(self.cities[newItem.text()]) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 25. Ders 15/03/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QListWidget penceresindeki bir satıra çicft tıklandığında itemDoubleClicked isimli sinyal oluşturulmaktadır. Bu sinyalin tek bir parametresi vardır. O da çift tıklanan elemana ilişkin QListWidgetItem nesnesidir. Belli bir satırdaki QListWidgetItem nesnesini silmek için takeItem metodu kullanılabilir. Aşağıdaki örnekte bir QListWidget nesnesine çaşitli elemanlar yerleştirilmiştir. Bir satıra çift tıklanınca önce o staırdaki eleman yazdırılmış sonra da takeItem meodu ile eleman silinmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QListWidget Sample') self.resize(800, 600) self.listWidgetCities = QListWidget(self) self.listWidgetCities.setGeometry(10, 10, 200, 300) font = self.font() font.setPointSize(20) self.listWidgetCities.setFont(font) self.listWidgetCities.addItem('Eskişehir') self.listWidgetCities.addItem('Ankara') self.listWidgetCities.addItem('İzmir') self.listWidgetCities.addItem('İstanbul') self.listWidgetCities.addItem('Bolu') self.listWidgetCities.addItem('Bursa') self.listWidgetCities.addItem('Adıyaman') self.listWidgetCities.addItem('Mersin') self.listWidgetCities.itemDoubleClicked.connect(self.listWidgetItemDoubleClickedHandler) def listWidgetItemDoubleClickedHandler(self, lwi): QMessageBox.information(self, 'Message', lwi.text()) self.listWidgetCities.takeItem(self.listWidgetCities.currentRow()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Çok satırlı edit penceresi QTextEdit sınıfıyla temsil edilmiştir. Pencere yine diğer alt pencerelerde olduğu gibi yaratılır: self.textEdit = QTextEdit(self) QTextEdit içerisindeki yazı sınıfın toPlainText metodu ile elde edilebilir. Aşağıdaki örnekte Ok düğmesine basıldığında çok satırlı edit alanının içerisindeki yazı alınıp konsol ekranına yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QTextEdit Sample') self.resize(800, 600) self.textEdit = QTextEdit(self) self.textEdit.setGeometry(10, 10, 320, 200) self.textEdit.setFont(QFont('Arial', 16)) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(340, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): text = self.textEdit.toPlainText() print(text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Yine yazının rengi setTextColor metodu ile değiştirilebilir. Yine pencerenin tüm zemin rengi "style sheet" yoluyla değiştirilebilmektedir. Aşağıdaki örnekte edit alanı içerisindeki yazı kırmızı biçimde, edit alanının zemini ise sarı biçimde görüntülenmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QTextEdit Sample') self.resize(800, 600) self.textEdit = QTextEdit(self) self.textEdit.setGeometry(10, 10, 320, 200) self.textEdit.setFont(QFont('Arial', 16)) self.textEdit.setTextColor(Qt.red) self.textEdit.setStyleSheet('QTextEdit {background-color: yellow}') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(340, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): text = self.textEdit.toPlainText() print(text) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Edit alanının içerisindeki yazı setPlainText metodu ile set edilebilir. Aşağıdaki örnekte Ok tuşuna basıldığında QLineEdit içerisindeki yol ifadesine ilişkin dosya açılmış ve onun içerisindekiler çok satırlı edit alanının içerisine yerleştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('QTextEdit Sample') self.resize(800, 600) self.textEdit = QTextEdit(self) self.textEdit.setGeometry(10, 10, 780, 400) self.textEdit.setFont(QFont('Consolas', 16)) self.textEdit.setTextColor(Qt.red) self.textEdit.setStyleSheet('QTextEdit {background-color: yellow}') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 420, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.lineEditPath = QLineEdit(self) self.lineEditPath.setGeometry(120, 430, 300,20) self.lineEditPath.setPlaceholderText('Dosyanın yol ifadesini giriniz') def pushButtonOkHandler(self): try: with open(self.lineEditPath.text()) as f: text = f.read() self.textEdit.setPlainText(text) except Exception as e: QMessageBox.warning(self, 'Error', str(e)) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Pencere default durumda wrapping yapmaktadır. Wrapping'i kaldırmak için setLineWrapMode isimli metot QLineEdit.NoWrap parametresi ile çağrılmalıdır. Satır tabanlı wrapping için aynı metot QLineEdit.WidgetWidth parametresiyle çağrılabilir. Biz pencerenin wrapping modunu lineWrapMode metoduyla elde edebiliriz. Aşağıdaki programda bir QTextEdit penceresi bir de güğme vardır. Düğmeye her basıldığında wrapping mod değiştirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("QTextEdit Example") self.resize(640, 480) self.textEdit = QTextEdit(self) self.textEdit.setGeometry(10, 10, 620, 350) self.textEdit.setFont(QFont('Consolas', 16)) self.textEdit.setLineWrapMode(QTextEdit.NoWrap) self.pushButtonWrapping = QPushButton('Line Wrap', self) self.pushButtonWrapping.setFont(QFont('', 16, QFont.Bold)) self.pushButtonWrapping.setGeometry(300, 380, 140, 80) self.pushButtonWrapping.clicked.connect(self.buttonWrappingHandler) def buttonWrappingHandler(self): if self.textEdit.lineWrapMode() == QTextEdit.WidgetWidth: self.textEdit.setLineWrapMode(QTextEdit.NoWrap) self.pushButtonWrapping.setText('Line Wrap') else: self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth) self.pushButtonWrapping.setText('No Wrap') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Çok kullanılan bir GUI eleman da "combobox" denilen elemandır. Qt'de combobox QComboBox sınıfı ile temsil edilmiştir. Combobox aslında edit alanı ile listbox'ın birleşimi gibi düşünülebilir. Pencere tıklandığında açıldığı için form üzerinde daha az yer kaplamaktadır. Bu nedenle programcılar çoğu kez seçim amacıyla listbox yerine combobox tercih ederler. Pencerenin geometrisi yalnızca kapalı kısmı ile belirlenmektedir. Elemanlar yine addItem, addItems, insertItem ve insertItems metotlarıyla eklenir. Ancak QCombox elemanları gerçekten yazılardan oluşmaktadır. Yani ListBox'taki gibi QListWidgetItem gibi nesnelerden oluşmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("QComboBox Sample") self.resize(640, 480) self.comboBox = QComboBox(self) self.comboBox.setGeometry(10, 10, 200, 30) self.comboBox.addItem('Adana') self.comboBox.addItem('Ankara') self.comboBox.addItem('İzmir') self.comboBox.addItem('İstanbul') self.comboBox.addItem('Kars') self.comboBox.addItem('Mersin') self.comboBox.addItem('Eskişehir') self.comboBox.addItems(['Sivas', 'Trabzon', 'Edirne', 'Manisa', 'Muğla']) self.comboBox.addItems(['Gaziantep', 'Konya', 'Bilecek', 'Antalya', 'Ordu']) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QComboBox penceresinde seçili olan yazı sınıfın currentText metodu ile elde edilebilir. Aşağıdaki örnekte Ok düğmesine basıldığında combox'ta seçili olan yazı messagebox ile yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("QComboBox Sample") self.resize(640, 480) self.comboBox = QComboBox(self) self.comboBox.setGeometry(10, 10, 200, 30) self.comboBox.addItem('Adana') self.comboBox.addItem('Ankara') self.comboBox.addItem('İzmir') self.comboBox.addItem('İstanbul') self.comboBox.addItem('Kars') self.comboBox.addItem('Mersin') self.comboBox.addItem('Eskişehir') self.comboBox.addItems(['Sivas', 'Trabzon', 'Edirne', 'Manisa', 'Muğla']) self.comboBox.addItems(['Gaziantep', 'Konya', 'Bilecek', 'Antalya', 'Ordu']) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(220, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', self.comboBox.currentText()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QComboBox nesnesinde seçilmiş olan elemanın index numarası currentIndex metodu ile elde edilmektedir. Seçili elemanı programlama yoluyla değiştirebilmek için setCurrentIndex metodu kullanılmaktadır. Aşağıdaki örnekte seçili olan elemanın indeksi yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("QComboBox Sample") self.resize(640, 480) self.comboBox = QComboBox(self) self.comboBox.setGeometry(10, 10, 200, 30) self.comboBox.addItem('Adana') self.comboBox.addItem('Ankara') self.comboBox.addItem('İzmir') self.comboBox.addItem('İstanbul') self.comboBox.addItem('Kars') self.comboBox.addItem('Mersin') self.comboBox.addItem('Eskişehir') self.comboBox.addItems(['Sivas', 'Trabzon', 'Edirne', 'Manisa', 'Muğla']) self.comboBox.addItems(['Gaziantep', 'Konya', 'Bilecek', 'Antalya', 'Ordu']) self.comboBox.setCurrentIndex(6) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(220, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', str(self.comboBox.currentIndex())) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Combobox GUI elemanının edit alanındaki yazı değiştirilebilir duruma getirilebilir. Bunun için sınıfın setEditable metodu True değeri ile çağrılmalıdır. Bu yazıyı programlama yoluyla değiştirmek için ise setCurrentText metodu kullanılmaktadır. Edit alanındaki yazının değiştirilmesi seçili elemanı etkilememektedir. currentIndex her zaman bize seçili elemanın indeksini verir. Aşağıdaki örnekte combobox penceresi editable hale getirilmiştir. Ok düğmesine basılınca edit alanındaki yazı programlama yokuyla değiştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("QComboBox Sample") self.resize(640, 480) self.comboBox = QComboBox(self) self.comboBox.setGeometry(10, 10, 200, 30) self.comboBox.addItem('Adana') self.comboBox.addItem('Ankara') self.comboBox.addItem('İzmir') self.comboBox.addItem('İstanbul') self.comboBox.addItem('Kars') self.comboBox.addItem('Mersin') self.comboBox.addItem('Eskişehir') self.comboBox.addItems(['Sivas', 'Trabzon', 'Edirne', 'Manisa', 'Muğla']) self.comboBox.addItems(['Gaziantep', 'Konya', 'Bilecek', 'Antalya', 'Ordu']) self.comboBox.setCurrentIndex(6) self.comboBox.setEditable(True) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(220, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) def pushButtonOkHandler(self): QMessageBox.information(self, 'Message', str(self.comboBox.currentText())) self.comboBox.setCurrentText('Madrid') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QComboBox nesnesinin en önemli iki sinyali currentIndexChanged ve currentTextChanged sinyalleridir. currentIndexChanged yeni bir eleman seçildiğinde tetiklenmektedir. currentIndexChanged sinyalinin parametresi yeni seçilmiş olan satırın numarasıdır. Aşağıdaki örnekte combobox'tan bir şehir seçildiğinde onun ilçeleri yandaki listbox'a yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.cities = { 'Eskişehir': ['Mihalıççık', 'Sivrihisar', 'Alpu', 'Mahmudiye', 'Çifteler'], 'Ankara': ['Ulus', 'Sincan', 'Keçiören', 'Kızılcahamam', 'Çankaya'], 'İzmir': ['Konak', 'Urla', 'Karşıyaka', 'Bergama', 'Bornova'], 'İstanbul': ['Beşiktaş', 'Ataşehir', 'Sultanbeyli', 'Kadıköy', 'Bakırköy'] } self.setWindowTitle("QComboBox Sample") self.resize(640, 480) self.comboBox = QComboBox(self) self.comboBox.setGeometry(10, 10, 200, 30) self.comboBox.addItems(self.cities) self.comboBox.currentIndexChanged.connect(self.comboBoxCurrentIndexChangedHandler) self.listWidgetVillages = QListWidget(self) self.listWidgetVillages.setGeometry(230, 10, 200, 300) font = self.font() font.setPointSize(20) self.comboBox.setCurrentIndex(0) self.comboBoxCurrentIndexChangedHandler(self) def comboBoxCurrentIndexChangedHandler(self, index): self.listWidgetVillages.clear() self.listWidgetVillages.addItems(self.cities[self.comboBox.currentText()]) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü sisteminde bir "menü çubuğu (menu bar)", menü çubuklarına iliştirilmiş "popup pencereler (popup windows)", popup pencerelere iliştirilmiş menü elemanları bulunur. Qt'de menü çubuğu QMenuBar sınıfı ile, popup pencereler QMenu sınıfı ile ve menü elemanları da QAction sınıfı ile temsil edilmiştir. Qt'de menülü uygulamalar için programın ana penceresinin QWidget sınıfı ile değil QMainWindow sınıfı ile oluşturulması daha uygun olmaktadır. QMainWindow sınıfı QWidget sınıfından türetilmiş durumdadır. Dolayısıyla menü uygulamaları için bizim kendi ana pencere sınıfımızı QWidget sınıfından değil QMainWindow sınıfından türetmemiz daha uygundur. Örneğin: class MainWindow(QMainWindow): def __init__(self): super().__init__() QMainWindow sınıfında zaten hazır bir biçimde yaratılmış olan bir menü çubuğu nesnesi vardır. Bu menu çubuğu nesnesi QMainWindow sınıfının menuBar metoduyla elde edilebilmektedir. Örneğin: menuBar = self.menuBar() Tabii biz istersek yine QWidget sınıfınında türetme yapıp QMenuBar nesnesini kendimiz de yaratabiliriz. Örneğin: class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) self.menuBar = QMenuBar(self) Ancak biz menülü uygulamalarda ana pencere sınıfımızı QWidget sınıfından değil QMainWindow sınıfınından türeteceğiz ve zaten QMainWindow sınıfında bulunan QMenuBar nesnesini kullanacağız. Örneğin: class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 26. Ders 20/03/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir menü çubuğu oluşturulduktan sonra ya da QManinWindow sınıfında zaten var olan menü çubuğu elde edildikten sonra artık sıra bu çubuğa popup pencerelerin eklenmesine gelmiştir. Qt'de popup menü pencereleri QMenu sınıfıyla temsil edilmektedir. Bir popup pencereyi oluşturabilmek için önce QMenu sınıfından bir nesne yaratılır. Nesne yaratılırken popup pencerede görünecek başlık yazısı belirtilir. Sonra QMenu nesnesi QMenuBar sınıfının addMenu metoduyla menü çubuğuna eklenir. Örneğin: class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('File') menuBar.addMenu(self.filePopup) Aslında alternatif olarak biz QMenuBar sınıfının addMenu metoduna bir yazı verirsek zaten addMenu bizim için QMenu nesnesini yaratıp yarattığı nesneyi bize de vermektedir. Örneğin: class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = menuBar.addMenu('File') Artık bu örnekte elimizde bir menü çubuğu ve bir de popup pencere vardır. Artık popup pencerelere menü elemanları eklenebilir. Menü elemanları QAction sınıfıyla temsil edilmektedir. O halde bizim bir QAction nesnesi yaratıp bu nesneyi QMenu sınıfının addAction metodu ile popup pencereye eklememiz gerekmektedir. QAction nesnesi yaratılırken menü elemanına bir iism de verilmektedir. Örneğin: class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('File') menuBar.addMenu(self.filePopup) self.openAction = QAction('Open') self.filePopup.addAction(self.openAction) self.closeAction = QAction('Close') self.filePopup.addAction(self.closeAction) self.exitAction = QAction('Exit') self.filePopup.addAction(self.exitAction) Alternatif olarak biz QMenu sınıfının addAction metoduna doğrudan isim verebiliriz. Bu durumda addAction metodu QAction nesnesini kendisi yaratıp onu ekledikten sonra bize bu yarattığı QAction nesnesini veröektedir. Yukarıdaki işlemler şöyle kısaltılabilir: class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = menuBar.addMenu('File') self.filePopup.addAction('Open') self.filePopup.addAction('Close') self.filePopup.addAction('Exit') Ancak biz kurusumuzda genel olarak nesneleri kendimiz yaratacağız. Bir menü elemanı seçildiğinde bir şeyler yapabilmek için QAction sınıfının triggered isimli sinyali kullanılmaktadır. Bu sinyal bir isteğe bağlı parametreye sahiptir. Sinyal menü elemanı seçildiğinde emit edilmektedir. Sinyalin parametresi elemanın checked olup olmadığını bize vermektedir. (Qt'de aslında daha sonra sinyalin parametresi seçilen menü elemanına ilişkin QAction nesnesi olacak biçimde değştirilmiştir. Ancak PyQt henüz bunu desteklememktedir.) Aşağıdaki örnekte File popup penceresine Open, Close ve Exit menü elemanları eklenmiştir. Bu menü elemanları seçildiğinde bir messagebox çıakrtılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('File') menuBar.addMenu(self.filePopup) self.openAction = QAction('Open') self.filePopup.addAction(self.openAction) self.openAction.triggered.connect(self.openActionHandler) self.closeAction = QAction('Close') self.filePopup.addAction(self.closeAction) self.closeAction.triggered.connect(self.closeActionHandler) self.exitAction = QAction('Exit') self.filePopup.addAction(self.exitAction) self.exitAction.triggered.connect(self.exitActionHandler) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): QMessageBox.information(self, 'Message', 'Exit item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ İkonlar Qt'de QIcon sınıfıyla temsil edilmektedir. Bir ikon nesnesi yaratmak için tipik olarak QIcon sınıfının __init__ metodunda ikona ilişkin dosyanın yol ifadesi belirtilmektedir. Örneğin: icon = QIcon('test.png') İkon'lar çeşitli dosya formatlarıyla yaratılabilirler. Ancak ikonlar için en uygun dosya formatı "png" formatıdır. Çünkü png formatında transparanlık bilgisi formatın kendi içerisinde bulunmaktadır. Pekiyi biz ikonik resimleri nasıl elde edebiliriz? Aslında ikonik resimler birer sanat eseri statüsünde olduğu için telif haklarına sahip olabilmektedir. Ancak kişiler telif haklarına sahip olmayan pek çok ikonik resim oluşturmuştur. Dolayısıyla para vermeden ikonik resimler elde edebiliriz. Bunun için çeşitli siteler bulunmaktadır. Aşağıdaki iki site bunun için kullanılabilir: iconfinder.com flaticon.com #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Menü elemanlarına ikon iliştimek için iki yol vardır. Birincisi QAction nesnesi yaratıldıktan sonra QAction sınıfının setIcon metodunun kullanılmasıdır. Örneğin: self.openAction = QAction('Open') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setIcon(QIcon('open.png')) self.filePopup.addAction(self.openAction) İkinci yol doğrudan QAction nesnesini yaratırken birinci parametre olarak ikon'u belirtmektedir. QAction nesnesi tek argümanla yaratılırsa o argüman menü elemanının yazısı olmalıdır. Ancak yaratımda iki argüman kullanılırsa bu durumda birinci argüman ikonu ikinci argüman menü elemanının yazısını belirtmelidir. Örneğin: self.openAction = QAction(QIcon('open.png'), 'Open') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) Menü elemanlarına iliştirilecek ikonlar tipik olarak 16x16 boyutlarındadır. png, bmp gibi formatlar ölçeklendirlirken görüntünün bozulması yol açarlar. Bu nedenle mümkün olduğunca menü elemanları için ikonları menü elemanının boyutuna uygun bir biçimde seçmelisiniz. Menülerde pek çok ikon kullanıldığına göre bu ikonlra ilişkin dosyalar programda kırılganlık yaratmaz mı? Yani bunlarda biri kaybolsa program çalışmayacaktır. İşte gerçekten GUI framework'lerini tasarlayan kişiler bu kırılganlığı fark etmiş ve bunu azaltmak için "resource" denilen bir kavram geliştirmişlerdir. Resource bir grup görüntü, ses vs gibi öğelerin tek bir dosyada birleştirilmesine denilmektedir. Ancak resource kullanmasanız bile hiç olmazsa menü resimlerini ayrı bir klasöre yerleştirip daha düzenli hale getirebilirsiniz. Aşağıdaki örnekte menü elemanlarına ikonlar iliştirilmiştir. Uygulamaya ilişkin ikonların hepsi flaticon.com sitesinden indirilmiştir ve "MenuIcons" isimli bir klasöre yerleştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), 'Open') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'Close') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), 'Exit') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), 'Copy') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'Cut') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), 'Paste') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir popup pencere açıkken bir tuşa bastığımızda belli bir menü elemanının seçilmesi sağlanabilir. Bunun için menü elemanının isminde istenilen harfin önüne & sembolü getirilmelidir. Örneğin: self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) Burada & sembolü O'nun önüne getirilmiştir. Bu durumda File popup penceresi açıldığında O tuşuna basılırsa sanki Open menü elemanı seçilmiş gibi işlem yapılacaktır. Bazı ortamlarda bu ampersand'lanmış karakter altı çizgili bir biçimde görüntülenmektedir. Örneğin Windows'ta eskiden bu karakterlerin altı çizili gözüküyordu. Sonra Windows stil değiştirdi. Ancak denetim masasından eski stil aktif hale geitirilebilmektedir. Popup pencere başlık yazıları da ampersand'lanabilir. Örneğin: self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) Popup yazıları böyle ampersand'lanırsa popup alt ve ilgili karaktere basılarak klavye yoluyla açılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Kısa yol tuşu (Shortcut key / Accelarator) menü hiç açılmadan kalvyeden belli bir tuş kombinasyonuna basıldığında sanki o menü elemanı seçilmiş gibi işlem yapılması işlemine denilmektedir. Qt'de menü elemanına kısa yol tuşu girmek için QAction sınıfının setShortcut metodu kullanılmaktadır. Bu metoda argüman olarak kısa yol yazısı aralarına '+' karakteri getirilerek verilir. Örneğin: self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) Burada kısayol yazısı "Ctrl+O" biçiminde girilmiştir. Yani kısa yol tuşu Ctrl ile O tuşuna basmak biçiminde tanımlanmıştır. Kısa yol tuşu "Ctrl+Alt+O" gibi başka tuş kombinasyonlarını da içerebilmektedir. Bir menü elemanına kısa yol tuşu atandığında bu kısa yol tuşu otomatik olarak menü elemanının sağında görüntülenmektedir. Aşağıdaki örnekte menü elemanlarına kısa yol tuşları atanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü elemanı "checkable" olabilir ya da olmayabilir. Default durumda menü elemanları checkable değildir. Menü elemanlarını checkable yapmak için QAction sınıfının setCheckable metodu True ile çağrılır. Eğer menü elemanı checkable yapılırsa menü elemanı her seçildiğinde elemanın solunda (ikon alanında) checked sembolü konup kaldırılmaktadır. Örneğin: self.fullScreenAction = QAction('Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.setCheckable(True) Aşağıdaki örnekte View popup penceresindeki "Full Screen" menü elemanı checkable hale getirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.fullScreenAction = QAction('&Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.setCheckable(True) self.viewPopup.addAction(self.fullScreenAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 27. Ders 22/03/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tabii menü elemanının checked olup olmadıı programın belli bir noktasında sorgulanmak istenebilir. Bunun için QAction sınıfının isChecked metodu kullanılmaktadır. Checkable bir menü elemanını programlama yoluyla checked ya da unchecked yapabiliriz. Bunun için QAction sınıfının setChecked metodu ile yapılabilir. Aşağıdaki örnekte Ok düğmesine basıldığığında Full Screen menü elemanının o anda checked olup olmadığı messagebox ile yazdırılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.fullScreenAction = QAction('&Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.setCheckable(True) self.viewPopup.addAction(self.fullScreenAction) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(100, 100, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkClickedHandler) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def pushButtonOkClickedHandler(self): QMessageBox.information(self, 'Message', 'Full Secreen checked' if self.fullScreenAction.isChecked() else 'Full Screen not checked') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü elemanı hem bir ikona sahipse hem de checkable biçimdeyse ikonun çevresine dikdörtgen çıkartılmaktadır. Aşağıdaki örnekte Full Screem menü elemanı hem ikona sahiptir hem de checkable durumdadır. Ancak genellikle checkable elemanlara ikon yerleştirilmemektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.fullScreenAction = QAction(QIcon('MenuIcons/fullscreen.png'), '&Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.setCheckable(True) self.viewPopup.addAction(self.fullScreenAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü elemanı "enabled" ya da "disabled" olabilir. Default durumda menü elemanları "enabled" biçimdedir. Menü elemanlarını "disabled" hale getirmek için QAction sınıfının setEnabled metodu False parametresi ile çağrılır. Tabii bu metot tekrar True parametresi ile çağrılırsa menü elemanı da tekrar "enabled" hale getirilir. Belli bir noktada bmenü elemaının aktif olmasının bir anlamı yoksa onun disabled getirilmesi uygun olur. Aşağıdaki örnekte File popup penceresindeki Open menü elemanı seçildiğinde Open disabled, Close enabled yapılmıştır. Benzer biçimde Close menü elemanı seçildiğinde Open enabled, close disabled yapılmıştır. Tabii işin başında Open enabled, close disabled biçimdedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.setEnabled(False) self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.fullScreenAction = QAction('&Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.setCheckable(True) self.viewPopup.addAction(self.fullScreenAction) def openActionHandler(self): self.openAction.setEnabled(False) self.closeAction.setEnabled(True) def closeActionHandler(self): self.closeAction.setEnabled(False) self.openAction.setEnabled(True) def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Uygulama ayarları daha önce görmüş olduğumuz shelve sınıfı yardımıyla saklanabilir. Aşağıdkai örnekte Full Screen menü elemanının checked olup olmadığı kaydedilmiş, sonra onun değeri elınarak program yeniden açıldığında kapanıştaki duruma getirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * import shelve class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) self.settings = shelve.open('gui', 'c') menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.fullScreenAction = QAction('&Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.triggered.connect(self.fullScreenActionHandler) self.fullScreenAction.setCheckable(True) if self.settings.get('FullScreenCheckable'): self.fullScreenAction.setChecked(True) self.viewPopup.addAction(self.fullScreenAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def fullScreenActionHandler(self): self.settings['FullScreenCheckable'] = self.fullScreenAction.isChecked() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü elemanı bir popu menü olabilir. Bu duruma "alt menü (sub menu)" de denilmektedir. Örneğin View menüsünü açtığımızda Theme isimli menü elemanı temaları belirten bir popup pencere olabilir. Bu biçimdeki alt menüleri oluştumak için yine alt menüler QMenu sınıfı kullanılarak oluşturulur. Ancak QMenu sınıfının addAction metoduyla değil de addMenu metoduyla ekleme yapılır. Aşağıdaki örnekte View popup penceresine Themes isimli bir popup penceresi eklenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * import shelve class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.fullScreenAction = QAction('&Full Screen') self.fullScreenAction.setShortcut('Ctrl+F') self.fullScreenAction.setCheckable(True) self.viewPopup.addAction(self.fullScreenAction) self.themesPopup = QMenu('&Themes') self.viewPopup.addMenu(self.themesPopup) self.forestAction = QAction('&Forest') self.forestAction.triggered.connect(self.forestActionHandler) self.themesPopup.addAction(self.forestAction) self.fruitAction = QAction('&Fruit') self.fruitAction.triggered.connect(self.fruitActionHandler) self.themesPopup.addAction(self.fruitAction) self.treeAction = QAction('&Tree') self.treeAction.triggered.connect(self.treeActionHandler) self.themesPopup.addAction(self.treeAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def forestActionHandler(self): QMessageBox.information(self, 'Message', 'Forest item selected') def fruitActionHandler(self): QMessageBox.information(self, 'Message', 'Fruit item selected') def treeActionHandler(self): QMessageBox.information(self, 'Message', 'Treeitem selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QAction sınıfının setFont metoduyla menü elemanının fontu değiştirilebilir. Ancak font değiştirken genel görünümün bozulmamasına dikkat edilmelidir. Fare ile bir menü elemanın üzerine gelindiğinde (hover işlemi) çıkan yazıya "ipucu yazısı (tooltip text)" denilmektedir. İpucu yazısını set etmek için QAction sınıfının setToolTip metodu kullanılmaktadır. Ancak ipucu yazısının çıkması için QMenu sınıfında setToolTipsVisible metodunun True ile çağrılması gerekmektedir. Aşağıdaki örnekte File popup penceresi için ipucu yazılarının görüntülenmesi seToolTipsVisible metoduyla sağlanmıştır. Ondan sonra File popup içerisindeki menü elemanları için QAction sınıfının setToolTip metotlarıyla ipucu yazıları set edilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') font = self.font() font.setPointSize(16) self.openAction.setFont(font) self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ İşlevsel olarak ilişkili menü elemanlarını gruplamak için "separator" denilen özel menü elemanları kullanılmaktadır. Bazı framework'lerde bu separator'ler ayrı bir sınıf isimle temsil edilmişlerdir. Ancak Qt'de separtor'ler de QAction sınıfıyla temsil edilmiş durumdadır. Biz bir QAction nesnesi yaratıp QAction sınıfının setSeparator metodunu True parametresiyle çağrırısak artık o menü elemanı bir separtor haline gelir. Tabii menü elemanın artık isminin de bir önemi kalmamaktadır. Aşağıdaki örnekte File opoup penceresinde Open ve Close menü elemanları bir separator yardımıyla Exit menü elemanından ayrılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Menu Sample") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Menülü ve araç çubuklu uygulamalarda menüler ve araç çubukları ana pencerenin çalışma alanı (client area) içerisinde yer kaplamaktadır. Bu durumda ana pencerenin çalışma alanının (0, 0) orijininde menü konumlanır. Programcının ana pencerenin içerisine yerleştireceği alt pencereler için konum belirlemesi zorlaşmaktadır. İşte GUI dünyasında bu problemi engellemek için menülerin ve araç çubuklarının dışında kalan çalışma alanını tamamen kaplayan bir dummy pencere kullanılmaktadır. Bazı framework'lerde bu amaçla kullanılan dummy pencerelere "view pencereleri" denilmektedir. Qt'de ise buna "merkei pencere (central widget)" denir. Biz bir pencereyi merkezi pencere yaparsak artık o pencere çalışma alanının kalan kısmını kaplar duruma gelir. Biz de kendi alt pencerelerimizi bu pencereye yerleştirirsek sanki menü ve araç çubuklarının aşağısını (0, 0) orijini olarak kullanabiliriz. Merkezi pencere herhangi bir pencere olabilir. Merkezi pencereyi set etmek için QMainWindow sınıfının setCentralWidget metodu kullanılmaktadır. Aşağıdaki örnekte ana pencereye bir merkezi pencere iliştirilmiş ve düğme de bu merkezi pencerenin içerisinde yaratılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Central Widget") self.resize(640, 480) self.cw = QWidget(self) self.setCentralWidget(self.cw) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.pushButtonOk = QPushButton('Ok', self.cw) self.pushButtonOk.setGeometry(10, 10, 100, 100) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte ana pencere içerisine merkezi pencere olarak QLineEdit penceresi yerleştirilmiştir. Sonra "word wrap" işlemi için checkable bir menü elemanı bulundurulmuştur. Program bu haliyle "notepad" uygulamasına benzemektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Central Widget") self.resize(640, 480) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(16) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('MenuIcons/open.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('MenuIcons/close.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('MenuIcons/exit.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('MenuIcons/copy.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('MenuIcons/cut.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('MenuIcons/paste.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 28. Ders 27.03.2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Genellikle menülerin hemen altında görüntülenen, düğmelerden oluşan, bu düğmelere tıklandığında birtakım faydalı işlemlerin gerçekleşmesini sağlayan alt pencerelere "araç çubukları (toolbars)" denilmektedir. Araç çubukları Qt'de QToolBar sınıfıyla temsil edilmiştir. Araç çubukları şöyle oluşturulmaktadır: 1) Önce QToolBar sınıfı türünden bir nesne yaratılır. 2) Bu nesne QMainWindow sınıfının addToolBar metodu ile ana pencereye iliştirilir. 3) QToolBar sınıfının addAction metodu ile araç çubuğuna QAction nesneleri eklenir. Yani hem menü elemanları hem de araç çubuğu elemanları QAction sınıfı ile temsil edilmiştir. Dolayısıyla biz zaten menüler için oluşturduğumuz QAction nesnelerini hemen araç çubuklarına ekleyebiliriz. QToolBar sınıfının addSeparator metodu ile de araç çubuğu ayıracı eklenebilmektedir. QManinWindow penceresine iliştirilen ve o pencereyi kaplayan "centralWidget" araç çubuğunun altından başlatılmaktadır. Araç çubuklarındaki düğmeler default durumda 16x16 biçimindedir. Buradaki düğmelerin büyüklüğünü değiştirmek için QToolBar sınıfının setIconSize metodu kullanılmaktadır. Bu metot QSize türünden bir parametre alır. Ancak bu metotla araç çubuğu düğmeleri büyütüldüğünde onun icon resmi otomatik olarak büyütülmemektedir. Halbuki menü elemanlarında menü ikonları 16x16 olacak biçimde otomatik olarak küçültülmektedir. O halde iki seçenek söz konusu olabilir. Kullanılacak ikonları araç çubuğu düğmeleri büyüklüğünde almak ya da menü elemanlarıyla araç çubuklarının QAction nesnelerini farklı büyüklükte ikon kullanacak biçimde birbirinden ayırmak Aşağıdaki örnekte 32x32'lik araç çubuğu düğmeleri oluşturulup bu düğmelere 32x32'lik ikon'lar iliştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Toolbar") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(16) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Araç çubuklarına yalnızca QAction nesneleri değil herhangi bir widget da eklenebilmektedir. Bunun için QToolBar sınıfının addWidget metodu kullanılır. Örneğin bir araç çubuğuna bir combobox ekleyebiliriz. Bu combobox'tan seçim yapıldığında combobox'sın currentIndexChanged sinyalinden hareketle otomatik işlemler yapabiliriz. Aşağıdaki örnekte araç çubuğuna bir combox yerleştirilmiştir. combobox'tan seçim yapıldığında penceredeki QLineEdit penceresinin fontu değiştirilmiştir. Aynı zamanda bu örnekte View/Tool Bar menü elemanı ile araç çubuğu gizlenip görünür hale getirilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * DEFAULT_FONT_SIZE = 16 class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Toolbar") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.viewPopup = QMenu('&View') menuBar.addMenu(self.viewPopup) self.toolBarAction = QAction('Toolbar') self.toolBarAction.setCheckable(True) self.toolBarAction.setChecked(True) self.toolBarAction.triggered.connect(self.toolBarActionHandler) self.viewPopup.addAction(self.toolBarAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.toolBar.addSeparator() self.comboBoxSize = QComboBox() self.comboBoxSize.addItems([str(i) for i in range(10, 25)]) self.comboBoxSize.currentIndexChanged.connect(self.currentIndexChangedHandler) self.toolBar.addWidget(self.comboBoxSize) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(DEFAULT_FONT_SIZE) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) self.comboBoxSize.setCurrentIndex(DEFAULT_FONT_SIZE - 10) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def toolBarActionHandler(self): self.toolBar.setVisible(self.toolBarAction.isChecked()) def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) def currentIndexChangedHandler(self, index): currentText = self.comboBoxSize.currentText() font = self.textEdit.font() font.setPointSize(int(currentText)) self.textEdit.setFont(font) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt'de bazı mesajlar sinyal/slot mekanizması yoluyla değil doğrudan fonksiyon çağırma yoluyla (C++'ta sanal fonksiyon mekanizmasıyla) işlenmektedir. Sinyal/Slot mekanizması biraz yavaş bir mekanizmadır. Halbuki temel pencere mesajlarının hızlı bir biçimde doğurdan işlenmesi gerekebilmektedir. Bu pencere mesajlarını işleyebilmek için pencere sınıfının içerisinde xxxEvent isimli özel metotlar yazılmalıdır. Bu metotların isimleri ve parametrik yapıları önceden belirlenmiştir. Dolayısıyla programcının bu metotları belirlenen isimlerle ve parametrik yapıyla sınıfta tanımlaması gerekir. Doğrudan fonksiyon çağırma yoluyla işlenen özel pencere mesajlarının önemli olanları şunlardır: - mousePressEvent - mouseReleaseEvent - mouseMoveEvent - resizeEvent - keyPressEvent - closeEvent - mouseDoubleClickEvent - paintEvent paintevent çizim konusu ile ilgilidir. Burada diğer mesajlar üzerinde kısaca duracağız. Custom Widget yazmak için bu mesajların kullanılması gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Fare ile bir pencere üzerinde tıklandığında tıklanır tıklanmaz mousePressEvent isimli metot, el farenin tuşşundan çekildiğinde de mouseReleaseEvent isimli metot otomatik çağrılmaktadır. Bu metotların self parametresinin dışında QMouseEvent denilen bir sınıf türünden parametreleri vardır. Programcı bu sınıfın içerisinden basım ya da çekim koordinatlarını x, y ve pos metotlarıyla elde edebilmektedir. Aşağıdaki örnekte farenin herhangi bir tuşuna basıldığında ve çekildiğinde farenin o anki koordinatları yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) def mousePressEvent(self, me): print(f'Pressed: {me.x()}, {me.y()}') def mouseReleaseEvent(self, me): print(f'Release: {me.x()}, {me.y()}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Fare mesajları farenin herhangi bir tuşuna basıldığında oluşmaktadır. Programcı eğer spesifik bir tuşla ilgileniyorsa bu durumda metot çağrıldığında QMouseEvent parametresi ile button metodunu çağırmalı ve bu metodun Qt.LeftButton, QtRightButton ya da Qt.MiddleButton olup olmadığını kontrol etmelidir. Aşağıdaki örnekte farenin özellikle sol tuşuna basılıp çekildiğinde koordinatlar yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) def mousePressEvent(self, me): if me.button() == Qt.LeftButton: print(f'Pressed: {me.x()}, {me.y()}') def mouseReleaseEvent(self, me): if me.button() == Qt.LeftButton: print(f'Release: {me.x()}, {me.y()}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Çok kullanılan temel mesajlardan biri de "Mouse Move" denilen mesajdır. Kullanıcı fareyi pencerenin çalışma alaını içerisinde hareket ettirdiğinde işletim sistemi ya da pencere yöneticisi bu mesajı kuyruğua bırakmaktadır. Qt framework de bu mesajı kuyruktan alınca pencere sınıfınındaki mouseMoveEvent isimli metodu çağırmaktadır. Bu metodun da yine self dışındaki parametresi QMouseEvent sınıfı türündendir. Yani farenin hareket ettirilmesi sırasında biz fareyi izleyebiliriz. Farnenin iki noktas arasında hareket ettirilmesi sırasında bu mesajın hangi sıklıkla gönderileceği sistemin o anki yüküne göre değişebilmektedir. Bütün çizim programları bu mesajın işlenmesiyle yapılmaktadır. Qt'de mouseMoveEvent metodu normal olarak farenin herhangi bir tuşuna basıkken fare hareket ettirildiğinde çağrılmaktadır. Ancak eğer farenin tuşuna basılmadan da fare hareket ettirildiğinde bu metodun çağrılması isteniyorsa bir kez QWidget sınıfınından gelen setMaouseTracking metodu True parametresiyle çağrılmalıdır. Aşağıdaki örnekte fare hareket ettrildiğinde sürekli olarak farenin pozisyonu print ettrilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) def mousePressEvent(self, me): if me.button() == Qt.LeftButton: print(f'Pressed: {me.x()}, {me.y()}') def mouseReleaseEvent(self, me): if me.button() == Qt.LeftButton: print(f'Release: {me.x()}, {me.y()}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Biz örneğin bir ana pencere içerisinde mouseMoveEvent ve mousePressEvent gibi metotları yazmış olalım. Ancak bu ana pencerede başka alt pencereler de olsun. İşte her pencere kendinden sorumludur. Fare olayı hangi pencere üzerinde oluşmuşsa mesaj o pencereye gönderilmektedir. Aşağıdaki örnekte ana pencere üzerinde bir düğme vardır. Fare ana düğme üzerinde getirildiğinde artık fare mesajları ana pencereye değil düğmeye gönderilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.setMouseTracking(True) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) def mousePressEvent(self, me): print(f'Pressed: {me.x()}, {me.y()}') def mouseMoveEvent(self, me): print(f'MouseMove: {me.x()}, {me.y()}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi biz zaten var olan widget pencerelerinde fare mesajlarını işlemek istesel bunu nasıl yapabiliriz? Bun en normal yolu mevcut widget sınıflarından türetme yapmaktır. Türetme yapılıp xxxEvent metotları türemiş sınıfta yazılırsa biz standarty widget'larda da bu mesajları işlemiş oluruz. Ancak burada dikkat edilmesi gereken önemli bir nokta bizim aynı zamanda asıl widget'ın ilgili metodunu super fonksiyonuyla çağırmamızdır. Eğer bunu yapmazsak bu xxxEvent metotları asıl widget elde edemez. O widget'lar bu meetotlar kullanılarak yazılmıştır. Aşağıdaki örnekte biz QPushButton sınıfınından türetme yaparak mousePressEvent ve mouseMoveEvent metotlarını oluşturduk. Ancak bu metotlarda taban QPushButton sınıfının ilgili metotlarını da çağırdık. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MyButton(QPushButton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMouseTracking(True) def mousePressEvent(self, me): print(f'Pressed: {me.x()}, {me.y()}') super().mousePressEvent(me) def mouseMoveEvent(self, me): print(f'MouseMove: {me.x()}, {me.y()}') super().mousePressEvent(me) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.myButtonOk = MyButton('Ok', self) self.myButtonOk.setGeometry(10, 10, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte farenin sol tuşuna basıldığında o noktada QPushButton pencereleri yaratılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.count = 1 def mousePressEvent(self, me): if me.button() == Qt.LeftButton: pushButton = QPushButton(str(self.count), self) pushButton.setGeometry(me.x() - 50, me.y() - 50, 100, 100) pushButton.setVisible(True) self.count += 1 app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 29. Ders 29/03/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir widget'ı sürükleyip bırakabilmek için tipik olarak yukarıda sözünü ettiğimiz fare mesajları kullanılır. mousePressEvent metodunda farenin tıklanma yeri saklanır. Sonra mouseMoveEvent metodunda bu ilk tıklanma yerinden farenin ne kadar ötelendiği hesaplanır. Sonra da o öteleme miktarı kadar widget hareket ettirlir. Aşağıda böyle bir uygulama yapımıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class DragButton(QPushButton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dragFlag = False def mousePressEvent(self, me): if me.button() == Qt.LeftButton: self.dragFlag = True self.firstPos = me.pos() super().mousePressEvent(me) def mouseReleaseEvent(self, me): self.dragFlag = False super().mouseReleaseEvent(me) def mouseMoveEvent(self, me): if self.dragFlag: pos = me.pos() deltaPos = pos - self.firstPos self.move(self.pos() + deltaPos) super().mouseMoveEvent(me) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) db = DragButton('Ok', self) db.setGeometry(10, 10, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Şimdi de yukarıdaki örneğin biraz daha karmaşık bir biçimini yapalım. Aşağıdaki örnekte ana pencere üzerinde farenin sol tuşuna basıldığında o noktada bir düğme yaratılmaktadır. Sonra yaratılan her düğme sürüklenip bırakılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * BUTTON_SIZE = 100 class DragButton(QPushButton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dragFlag = False def mousePressEvent(self, me): if me.button() == Qt.LeftButton: self.dragFlag = True self.firstPos = me.pos() super().mousePressEvent(me) def mouseReleaseEvent(self, me): self.dragFlag = False super().mouseReleaseEvent(me) def mouseMoveEvent(self, me): if self.dragFlag: pos = me.pos() deltaPos = pos - self.firstPos self.move(self.pos() + deltaPos) super().mouseMoveEvent(me) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.count = 1 def mousePressEvent(self, me): db = DragButton(str(self.count), self) db.setGeometry(me.x() - BUTTON_SIZE // 2, me.y() - BUTTON_SIZE // 2, BUTTON_SIZE, BUTTON_SIZE) db.setVisible(True) self.count += 1 app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir pencerenin alt pencereleri olan kardeş pencereler çakıştığında altta ya da üste gözükme durumuna "Z sırası (Z order)" denilmektedir. Sondadan yaratılan kardeş pencere önceden yaratılmış olan kardeş pencerenin üzerinde görüntülenir. Ancak bu sıra değiştirilebilmektedir. GUI elemanı kardeş pencereler arasındaki Z sırasında en yukarıya taşımak için QWidget sınıfınından gelen raise metodu kullanılmaktadır. Ancak Python'da raise bir anahtar sözcük olduğu için PyQt'de bu metodun ismi raise_ yapılmıştır. Benzer biçimde widget'ı Z sırasına göre en aşağıya almak için lower metodu kullanılır. Aşağıdaki örnekte oyun kartları pencere içerisine rastgele biçimde yerleştirilmiştir. Sonra da onların fare ile sürüklenmesi sağlanmıştır. Oyun kartları .png uzantısıyla kurs klasörünün CardImages dizininde bulunmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import random import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * CARD_WIDTH = 72 CARD_HEIGHT = 96 class DragLabel(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dragFlag = False def mousePressEvent(self, me): if me.button() == Qt.LeftButton: self.dragFlag = True self.firstPos = me.pos() self.raise_() super().mousePressEvent(me) def mouseReleaseEvent(self, me): self.dragFlag = False super().mouseReleaseEvent(me) def mouseMoveEvent(self, me): if self.dragFlag: pos = me.pos() deltaPos = pos - self.firstPos self.move(self.pos() + deltaPos) super().mouseMoveEvent(me) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(1200, 800) width = self.frameSize().width() height = self.frameSize().height() for i in range(1, 52 + 1): pixmap = QPixmap(f'CardImages/{i}.png').scaled(CARD_WIDTH, CARD_HEIGHT, Qt.KeepAspectRatio) label = DragLabel(self) label.setPixmap(pixmap) x = random.randint(0, width - CARD_WIDTH) y = random.randint(0, height - CARD_HEIGHT) label.setGeometry(x, y, pixmap.width(), pixmap.height()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte oyun kartlarının arka yüzü gösterilmiştir. Onlara tıklandığında kart açılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import random import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * CARD_WIDTH = 72 CARD_HEIGHT = 96 class DragLabel(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dragFlag = False self.backFlag = True def mousePressEvent(self, me): if me.button() == Qt.LeftButton: if self.backFlag: self.setPixmap(self.savedPixmap) self.backFlag = False self.dragFlag = True self.firstPos = me.pos() self.raise_() super().mousePressEvent(me) def mouseReleaseEvent(self, me): self.dragFlag = False super().mouseReleaseEvent(me) def mouseMoveEvent(self, me): if self.dragFlag: pos = me.pos() deltaPos = pos - self.firstPos self.move(self.pos() + deltaPos) super().mouseMoveEvent(me) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(1200, 800) width = self.frameSize().width() height = self.frameSize().height() backPixmap = QPixmap('CardImages/back-blue.png').scaled(CARD_WIDTH, CARD_HEIGHT, Qt.KeepAspectRatio) for i in range(1, 52 + 1): pixmap = QPixmap(f'CardImages/{i}.png').scaled(CARD_WIDTH, CARD_HEIGHT, Qt.KeepAspectRatio) label = DragLabel(self) label.savedPixmap = pixmap label.setPixmap(backPixmap) x = random.randint(0, width - CARD_WIDTH) y = random.randint(0, height - CARD_HEIGHT) label.setGeometry(x, y, pixmap.width(), pixmap.height()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte 52 oyun kartı her satırda belli miktar kart olacak biçimde dizilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import math import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * CARD_WIDTH = 72 CARD_HEIGHT = 96 CARDS_PER_ROW = 7 CARDS_SPACE = 2 LEFT_MARGIN = 10 TOP_MARGIN = 10 class DragLabel(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dragFlag = False self.backFlag = True def mousePressEvent(self, me): if me.button() == Qt.LeftButton: if self.backFlag: self.setPixmap(self.savedPixmap) self.backFlag = False self.dragFlag = True self.firstPos = me.pos() self.raise_() super().mousePressEvent(me) def mouseReleaseEvent(self, me): self.dragFlag = False super().mouseReleaseEvent(me) def mouseMoveEvent(self, me): if self.dragFlag: pos = me.pos() deltaPos = pos - self.firstPos self.move(self.pos() + deltaPos) super().mouseMoveEvent(me) class MainWindow(QMainWindow): def __init__(self): super().__init__() rows = math.ceil(52 / CARDS_PER_ROW) width = CARDS_PER_ROW * CARD_WIDTH + (CARDS_PER_ROW - 1) * CARDS_SPACE + 2 * LEFT_MARGIN height = rows * CARD_HEIGHT + (rows - 1) * CARDS_SPACE + 2 * TOP_MARGIN self.resize(width, height) self.setMaximumSize(width, height) self.setMinimumSize(width, height) backPixmap = QPixmap('CardImages/back-blue.png').scaled(CARD_WIDTH, CARD_HEIGHT, Qt.KeepAspectRatio) x = LEFT_MARGIN y = TOP_MARGIN for i in range(1, 52 + 1): pixmap = QPixmap(f'CardImages/{i}.png').scaled(CARD_WIDTH, CARD_HEIGHT, Qt.KeepAspectRatio) label = DragLabel(self) label.savedPixmap = pixmap label.setPixmap(backPixmap) label.setGeometry(x, y, pixmap.width(), pixmap.height()) x += CARD_WIDTH + CARDS_SPACE if i % CARDS_PER_ROW == 0: y += CARD_HEIGHT + CARDS_SPACE x = LEFT_MARGIN app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir pencerenin boyutu değiştirildiğinde framework tarafından sınııfın resizeEvent isimli metodu çağrılmaktadır. Metodun self dışındaki parametresi QResizeEvent sınıfı türündendir. Bu sınııfn size metodu pencerenin yeni boyutlarını bize vermektedir. Burada verilen genişlik ve yükseklik çalışma alanının genişlik ve yüksekliğidir. Bu metot sayesinde birtakım widget'lar pencere genişletme ve daraltma durumunda otomatik olarak büyütülüp küçültülebilemktedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Ne zaman bir pencere kapatılmak istense ilgili sınııfın closeEvent isimli metodu çağrılmaktadır. Bu metot sayesinde programcı program kapatılmadan önce bazı uyarıları ekrana çıkartabilir ve bazı son işlemleri yapabilir. Örneğin bir text editör uygulamasında ana pencereyi kapatmak istediğimizde bize eğer değişiklik yapılmışsa dosyayı save edip etmeyeceğimiz sorulmaktadır. closeEvent metodunun self dışında bir parametresi vardır. O parametre QCloseEvent sınıfı türündendir. QCloseEvent sınıfının ignore metodu ile pencerenin kapatılma işlemi iptal edilebilir. Aşağıdaki örnekte text editör içerisine bir yazı girildikten sonra program kapatılmak istendeiğinde değişikliklerin kaydedilip kaydedilmeyeceğini sormak için bir messagebox çıkartılmıştır. Bu messagebox'ta "Cancel" tuluna basıldığında pencerenin kapatılması QCloseEvent sınıfının ignore metodu ile iptal edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(16) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 30. Ders 03/04/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Programın ana penceresi kapatılmak istendiğinde ya da herhangi bir pencere kapatılmak istendiğinde o pencereyi temsil eden sınıfın closeEvent isimli metodu çağrılmaktadır. Bu metodun QCloseEvent sınıfı türünden bir parametresi vardır. Eğer pencerenin kapatılması isteniyorsa bir şey yapılmaz. Default durumda pencere kapatılır. Ancak pencerenin kapatılması istenmiyorsa bu durumda QCloseEvent nesnesi ile ignore metodu çağrılmalıdır. Aşağıdaki örnekte bir editör uygulamasında ana pencere kapatılmak istendiğinde eğer editör içerisinde daha önceden bir değişiklik yapılmışsa bir diyalog penceresi ile dokümanın save edilip edilmeyeceği sorulmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(16) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) def openActionHandler(self): QMessageBox.information(self, 'Message', 'Open item selected') def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) def closeEvent(self, ce): if self.textEdit.document().isModified(): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Cancel: ce.ignore() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Her zaman üst penceresinin üzerinde görüntülenen owned pencerelere diyalog pencereleri denilmektedir. Örneğin daha önce görmüş olduğumuz messagebox pencereleri birer diyalog penceresidir. Messagebox pencereleri hazır bir biçimde bulunmaktadır ve önceden belirlenmiş olan bir biçimde görüntülenmektedir. Halbuki programcı kendi diyalog pencerelerini de sıfırdan oluşturabilmektedir. Diyalog pencereleri "modal" ve "modeless" olmak üzere ikiye ayrılmaktadır. Modal diyalog pencerelerinde diyalog penceresi açıldığında arka plan etileşmi yapılamaz. Ancak diyalog penceresi kapatıldığında arka plan etkileşimi yapılabilir. Halbuki modeless diyal pencerelerinde pencere yine üst penceresinin üzerinde görüntülense de arka plan etkileşim yapılabilmektedir. Messagebox penceresi model bir diyalog penceresidir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Modal bir diyalog penceresinin custom bir biçimde oluşturulması şu adımlardan geçilerek yapılmaktadır: 1) QDialog sınıfındna bir sınıf türetilir. Örneğin: class AddRecordDialog(QDialog, parent): def __init__(self): super().__init__(parent) Sınıfın __init__ metodunda GUI elemanlar pencereye yerleştirilir. 2) Oluşturduğumuz sınıf türünden bir nesne yaratılır ve sınıfın QDialog sınıfından gelen exec metodu çağrılır. Artık modal diyalog penceresi açılmıştır. 3) Diyalog penceresinin kapatılması diyalog penceresi tarafından QDialog sınıfından gelen done metodu ile yapılır. Bu done metodunun parametresi QDialog.Accepted ya da QDialog.Rejected biçiminde girilmelidir. Tipik olarak programcı modal diyalog penceresine iki düğme yerleştirir. Düğmelerden birine tıklandığında QDialog.Accepted ile diğerine tıklandığında QDialog.Rejected ile done işlemi yapar. 4) Modal diyalog penceresinde girilen bilgilerin işleme sokulması iki biçimde yapılabilmektedir. Programcı bu bilgileri QDialog sınıfından türettiği sınıfın içerisinde de işleme sokabilir, diyalog penceresi kapatıldıktan sonra diyalog penceresini açtığı yerde de işleme sokabilir. Genellikle diyalog penceresinin açıldığı yerde bilgilerin işleme sokulması tercih edilmektedir. Pekiyi diyalog penceresi kapatıldıktan sonra pencere oluşan bilgileri nasıl alabiliriz? Aslında pencere kapatılmış olsa da pencere içerisindeki GUI eleman nesneleri yaşıyor durumdadır. Dolayısıyla biz onların bilgisini alabiliriz. Tabii nesne yönelimli programlama tekniğini daha sıkı uygulayan C++ gibi programlama dillerinde genellikle programcılar GUI elemanları sınıfın private bölümünde gizleyip getter/setter üye fonksiyonlarla bunları alma yoluna gitmektedir. Python'da da istenirse done işlemi yapılmadna önce bu bilgiler sınıfın veri elemanlarına aktarılabilir, oradan daha rahat alınabilir. Diyalog penceresinde oluşan bilgilerin "geçerliliğinin sınanması (validate edilmesi)" gerekebilir. Bu sınama işleminni diyalog penceresi kapatılmadan yapılması daha uygun olabilmektedir. Aşağıdaki örnekte bir modal diyalog penceresi çıkartılmış. Pencere kapatılırken bazı sınama işlemleri yapılmış ve pencere Add düğmesi ile kapatıldıktan sonra diyalog penceresindeki bilgiler elde edilerek yazdırılımıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class AddRecordDialog(QDialog): def __init__(self, parent): super().__init__(parent) self.setWindowTitle('Add Record') self.resize(400, 170) self.labelName = QLabel('Adı Soyadı:', self) self.labelName.move(10, 10) self.lineEditName = QLineEdit(self) self.lineEditName.setGeometry(10, 30, 200, 20) self.labelNo = QLabel('No:', self) self.labelNo.move(10, 65) self.lineEditNo = QLineEdit(self) self.lineEditNo.setGeometry(10, 85, 200, 20) self.labelColor = QLabel('Renk Tercihiniz:', self) self.labelColor.move(245, 10) self.comboBoxColor = QComboBox(self) self.comboBoxColor.setGeometry(245, 30, 100, 20) self.comboBoxColor.addItems(['Kırmızı', 'Yeşil', 'Mavi', 'Turuncu', 'Siyah', 'Beyaz', 'Mor']) self.comboBoxColor.setCurrentIndex(-1) self.pushButtonAdd = QPushButton('Add', self) self.pushButtonAdd.setGeometry(140, 130, 70, 25) self.pushButtonAdd.clicked.connect(self.pushButtonAddClickedHandler) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.setGeometry(220, 130, 70, 25) self.pushButtonCancel.clicked.connect(self.pushButtonCancelClickedHandler) def pushButtonAddClickedHandler(self): self.name = self.lineEditName.text().strip() if len(self.name) == 0: QMessageBox.warning(self, 'Error', 'Name must be specified') self.lineEditName.setFocus() return try: self.no = int(self.lineEditNo.text().strip()) except ValueError: QMessageBox.warning(self, 'Error', 'invalid number') self.lineEditNo.setFocus() self.lineEditNo.selectAll() return if self.comboBoxColor.currentIndex() == -1: QMessageBox.warning(self, 'Error', 'You must choose a color') self.comboBoxColor.setFocus() return self.color = self.comboBoxColor.currentText() self.done(QDialog.Accepted) def pushButtonCancelClickedHandler(self): self.done(QDialog.Rejected) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(800, 600) menuBar = self.menuBar() self.filePopup = QMenu('&Database') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.addRecordAction = QAction('&Add Record') self.addRecordAction.setIcon(QIcon('AddRecord.png')) self.addRecordAction.setShortcut('Ctrl+A') self.addRecordAction.triggered.connect(self.addRecordActionHandler) self.filePopup.addAction(self.addRecordAction) toolBar = QToolBar() toolBar.setIconSize(QSize(32, 32)) toolBar.addAction(self.addRecordAction) self.addToolBar(toolBar) def addRecordActionHandler(self): ard = AddRecordDialog(self) result = ard.exec() if result == QDialog.Accepted: name = ard.name # ard.lineEditName.text() no = ard.no # ard.lineEditNo.text() color = ard.color # ard.comboBox.currentText print(name) print(no) print(color) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aslında framework'lerde bazı standart diyalog pencereleri zaten ghazır biçimde bulunmaktadır. Dosya seçmek için, renk seçmek için, font seçmek için Qt'de hazır diyalog pencereleri vardır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QFileDialog isimli sınıf QDialog sınıfından türetilmiştir. Bu sınııfn amacı "open" ve "save as" gibi işlemlerde dosya seçimini sağlamaktır. QFileDialog sınıfının kullanımı şöyledir: 1) Önce QFileDialog sınıfı türünden bir nesne yaratılır. QFileDialog sınıfının __init__ metodunda biz üst pencerenin pencere nesnesini veririz. Bunun dışında istersek pencere başlık yazısı, diyalog penceresi açıldığında default olarak görüntülenecek dizin ve filtre yazıları verilebilir. Aslında bunların hiçbiri de verilmeyebilir. Bu durumda default seçenekler devreye girer. Aslında bu bilgileri __init__ metodunda vermek yerine nesne yaratıldıktan sonra setXXX metotlarıyla da verebiliriz. Örneğin: fd = QFileDialog(self, 'Choose file') işlemi ile aşağıdaki işlem aynıdır: fd = QFileDialog(self) fd.setWindowTitle('Choose file') Ya da örneğin: fd = QFileDialog(self, 'Choose file', r'c:\windows') bu işlem şmyle de ypılabilirdi: 2) Diyalog penceresini açmak için yine QDialog sınıfından gelen exec metodu kullanılır. Bu diyalog penceresi kapandığında yine programcı Ok düğümesiyle kapama yapıldığında seçilen dosyayı dikkat almalıdır. Örneğin: fd = QFileDialog(self) fd.setWindowTitle('Choose file') if fd.exec() == QDialog.Accepted: pass Diyalog penceresinden Ok düğmesi ile çıkıldığında exec metodu QDialog.Accepted değeri ile, Cancel düğmesiyle çıkıldığında QDialog.Rejected değeriyle geri dönmektedir. 3) QFileDialog penceresi kapatıldığında programcının elde ettiği tek şey seçilen dosyanın yol ifadesidir. QFileDialog poenceresi birden fazla dosya seçilebilecek biçimde de kullanıldığından dolayı seçilen dosyaların yol ifadeleri sınıfının selectedFiles metodu ile elde edilmektedir. Bu metot bize bir string listesi vermektedir. selectedFiles bize yalnızca seçilen dosyaların yol ifadelerini verir. Dosyanın açılması gibi işlemler programcının sorumluluğundadır. Default olarak QFileDialog penceresi tek bir dosyanın seçilmesine olanak sağlamaktadır. Dolayısıyla programcı seçilen dosyayı şöyle alabilir: fd = QFileDialog(self) fd.setWindowTitle('Choose file') if fd.exec() == QDialog.Accepted: path = fd.selectedFiles()[0] 4) Diyalog penceresi açıldığında her dosyanın gösterilmesi uygun olmaz. Örneğin bir resim gösterme programı yalnızca resim dosyalarının seçilmesine olanak sağlamalıdır. Bir text editör de örneğin yalnızca text dosyalarının seçilmesini olanak sağlamalıdır. Buna filtreleme işlemi denilmektedir. Filtreleme işlemi QFileDialog sınıfının __init__ metodunda da belirtilebilir. Ya da nesne yaratıldıktan sonra sınıfın setNameFilter metoduyla da belirtilebilir. Bu metoda biz filtreleme yazısını veriririz. Filtreleme yazısı iki parçadan oluşmaktadır. Birinci parça diyalog penceresinde görüntülenecek yazıyı belirtir. İkinci parça parantez içerisinde belirtilen herçek filtrelemeyi yapan kısımdır. Birden fazla filtre yerleştirilebilir. Bu duurmda filtreler arasında iki tane ';' karakteri olmaldır. Örneğin: fd = QFileDialog(self, filter='All Files (*.*') fd.setWindowTitle('Choose file') fd.setNameFilter('Text files (*.txt *.py);;All Files (*.*)') Burada iki tane filtre oluşturulmuştur. Genellikle programcılar "All files (*.*)" filtresini de fitrelerinin sonuna eklerler. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(16) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) def openActionHandler(self): fd = QFileDialog(self, filter='All Files (*.*') fd.setWindowTitle('Choose file') fd.setNameFilter('Text files (*.txt *.py);;All Files (*.*)') if fd.exec() == QDialog.Accepted: path = fd.selectedFiles()[0] try: with open(path) as f: text = f.read() self.textEdit.setPlainText(text) except Exception as e: QMessageBox.warning(self, 'Error', str(e)) def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) def closeEvent(self, ce): if self.textEdit.document().isModified(): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Cancel: ce.ignore() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 31. Ders 05/04/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QFileDialog sınıfına default durumda tek bir dosya seçilebilmektedir. Dosya seçimi konusunda belirleme yapmak için QFileDialog sınıfının setFimeMode metodu kullanılmaktadır. Bu metoda girilecek parametreler ve anlamları şunlardır: QFileDialog.AnyFile: Burada herhangi tek bir dosya seçilebilir. Dosyanın var olması gerekmez. Kullanıcı dosyayı klavyeden de girebilir. QFileDialog.ExistingFile: Burada olan tek bir dosya seçilebilir. Olmayan bir dosya seçilememekktedir. Yani dosyanın olup olmadığı kontrol edilmektedir. QFileDialog.ExistingFiles: Olan birden fazla dosyanın seçilmesine olanak sağlamaktadır. QFileDialog.Directory: Bu seçenkle yalnızca dizin seçilmesine olanak sağlanır. Bazı uygulamalarda kullanıcının dosya değil dizin seçmesi de istenmektedir. Aşağıdaki örnekte Open düğmesine tıklanıldığında dosya seçme diyalog penceresi açılmamaktadır. Ancak çoklu seçime olanak sağlanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.pushButtonOpen = QPushButton('Open', self) self.pushButtonOpen.setGeometry(10, 10, 100, 100) self.pushButtonOpen.clicked.connect(self.pushButtonOpenClickedHandler) def pushButtonOpenClickedHandler(self): fd = QFileDialog(self) fd.setFileMode(QFileDialog.ExistingFiles) if fd.exec() == QDialog.Accepted: QMessageBox.information(self, 'Selected files', '\n'.join(fd.selectedFiles())) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QFileDialog sınıfının da çeşitli sinyalleri vardır. Örneğin bir dosya seçildikten sonra sinyal emit edilmektedir. Ya da seçim sırasında sinyaller edimit edilebilmektedir. Ancak bu sinyaller seyrek kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Dosya seçme işlemini kolaylaştırmak için QFileDialog sınıfının çeşitli static metotları bulundurulmuştur. Bu metotlar getXXX biçiminde isimlendirilmiştir. Bütün bu metotlar aslında QFileDialog nesnesi yaratıp exec işlemiş yapmaktadır. Yalnızca bu işlemler bu metotların içerisinde yapılmıştır. getOpenFileName metodu bizden sırasıyla üst pencerenin nesnesini, pencere başlık yazısını, açılışta görüntülenecek dizini, filtreleme yazısını ister ve dosya seçme diyalog penceresini çıkartır. Metot seçilen dosyanın yol ifadesi ve teltreden oluşan ikili bir demete geri dönmektedir. Tabii metodun parametreleri default değerlerle geçilebilir. Eğer diyalog penceresi Cancel tuşuyla kapatılmışsa getOpenFileName metodu iki elemanı da boş string'ten oluşan bir demet vermektedir. Aşağıdaki örnekte QFileDialog sınıfının getOpenFileName metodu kullanılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.pushButtonOpen = QPushButton('Open', self) self.pushButtonOpen.setGeometry(10, 10, 100, 100) self.pushButtonOpen.clicked.connect(self.pushButtonOpenClickedHandler) def pushButtonOpenClickedHandler(self): path, _ = QFileDialog.getOpenFileName(self, 'Choose file', '/Users/kaanaslan/Dropbox/Shared/Kurslar', 'Text files (*.txt);;All files (*.*)') if path: QMessageBox.information(self, 'Selected file', path) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QFileDialog sınıfının getOpenFileNames metodu birden fazla dosya seçilmesine olanak vermektedir. Metot yine bize bir demet vermektedir. Ancak demetin ilk elemanı bir string listesi, ikinci elemanı yine o andaki aktif filtreleme yazısıdır. eğer diyalog penceresi Cancel düğmesi ile kapatılırsa bize boş bir liste ve boş bir string'ten oluşan bir demet verilmektedir. Aşağıdaki örnekte getOpenFileNames metodu ile birden fazla dosya seçilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.pushButtonOpen = QPushButton('Open', self) self.pushButtonOpen.setGeometry(10, 10, 100, 100) self.pushButtonOpen.clicked.connect(self.pushButtonOpenClickedHandler) def pushButtonOpenClickedHandler(self): paths, _ = QFileDialog.getOpenFileNames(self, 'Choose file', '/Users/kaanaslan/Dropbox/Shared/Kurslar', 'Text files (*.txt);;All files (*.*)') if paths: QMessageBox.information(self, 'Selected file', '\n'.join(paths)) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QFileDialog sınıfının getSaveFileName metodu "save as" diyalog penceresi çıkartmaktadır. Yine metot ikili bir demete geri dönmektedir. Demetin birinci elemanı save as işlemi için seçilen dosya isminden, ikinci elemanı aktif filtreleme yazısından oluşmaktadır. Diyalog penceresinde eğer kullanıcı zaten olan bir dosyayı seçerse diyalog penceresi "üzerine yazılsın mı" biçiminde bir uyarı yazısı çıkartmaktadır. Ancak bu uyarı yazısının dosya üzerinde bir etkisi yoktur. Aşağıdaki örnekte getSaveSileName metoduna bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.pushButtonOpen = QPushButton('Open', self) self.pushButtonOpen.setGeometry(10, 10, 100, 100) self.pushButtonOpen.clicked.connect(self.pushButtonOpenClickedHandler) def pushButtonOpenClickedHandler(self): path, _ = QFileDialog.getSaveFileName(self, 'Choose file', '/Users/kaanaslan/Dropbox/Shared/Kurslar', 'Text files (*.txt);;All files (*.*)') if path: QMessageBox.information(self, 'Selected file', path) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aslında QFileDialog sınıfının getXXX isimli daha fazla metodu vardır. Biz bu metotların hepsi üzeerinde burada durmayacağız. Bunları ilgili dokümanlardan inceleyebilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Renk seçme işlemi için QColorDialog sınıfı kullanılmaktadır. Sınıfın genel kullanımı diğer diyalog penceresi sınıflarına benzemektedir. Programcı önce QColorDialog sınıfı türünden bir nesne yaratır. Sonra sınıfın exec metodunu çağırır. Yine exec metodu QColorDialog.Accepted ya da QColorDialog.Rejected değerleriyle geri dönemktedir. Diyalog penceresi kapatıldıktan sonra seçilmiş olan renk sınıfın currentColor metoyla elde edilebilir. currentColor metodu bizebir QColor nesnesi vermektedir. Renk seçme diyalog penceresi açıldığında belli bir rengin seçili olması sağlanabilir. Bunun için sınıfın setCurrentColor metodu kullanılmaktadır. Aşağıdaki örnekte bir renkte seçme diyalog penceresi çıkartılıp seçilen renkle pencerenin zemin rengi boyanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Choose Color") self.resize(640, 480) self.cw = QWidget() self.setCentralWidget(self.cw) menuBar = self.menuBar() self.optionsPopup = QMenu('&Options') self.optionsPopup.setToolTipsVisible(True) menuBar.addMenu(self.optionsPopup) self.chooseColorAction = QAction('Choose color...') self.chooseColorAction.setIcon(QIcon('Color.png')) self.chooseColorAction.triggered.connect(self.chooseColorActionHandler) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.chooseColorAction) def chooseColorActionHandler(self): cd = QColorDialog(self) if cd.exec() == QColorDialog.Accepted: color = cd.currentColor() style = f'QWidget {{background-color: rgb({color.red()}, {color.green()}, {color.blue()});}}' self.cw.setStyleSheet(style) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte daha önce yapmış olduğumuz text editörün yazı rengini değiştiriyoruz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.textColorAction = QAction(QIcon('Icons/color.png'), '&TextColor') self.textColorAction.setCheckable(True) self.textColorAction.triggered.connect(self.textColorActionHandler) self.formatPopup.addAction(self.textColorAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.toolBar.addSeparator() self.toolBar.addAction(self.textColorAction) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(16) self.textEdit.setFont(font) self.setCentralWidget(self.textEdit) def openActionHandler(self): fd = QFileDialog(self, filter='All Files (*.*') fd.setWindowTitle('Choose file') fd.setNameFilter('Text files (*.txt *.py);;All Files (*.*)') if fd.exec() == QDialog.Accepted: path = fd.selectedFiles()[0] try: with open(path) as f: text = f.read() self.textEdit.setPlainText(text) except Exception as e: QMessageBox.warning(self, 'Error', str(e)) def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) def textColorActionHandler(self): cd = QColorDialog(self) cd.setCurrentColor(QColor('blue')) if cd.exec() == QColorDialog.Accepted: self.textEdit.setTextColor(cd.currentColor()) def closeEvent(self, ce): if self.textEdit.document().isModified(): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Cancel: ce.ignore() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 32. Ders 10/04/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında QColorDialog sınıfının getColor isimli static metoduyla da renk seçme işlemini yapabiliriz. Tabii bu metot aslında kendi içerisinde QColorDialog sınıfı türünden nesne yaratıp exec işlemi yapmaktadır. Örneğin: color = QColorDialog.getColor(parent=self, title='Choose Color') getColor metodu yine seçilen renge ilişkin QColor nesnesi ile geri dönmektedir. Ancak diyalog penceresi Cansel tuşu ile kapatılırsa bu durumda metot geçersiz bir QColor nesnesi verir. Bunu anlayabilmek için ise QColor sınıfının isValid metodunun çağtılması gerekmektedir. Örneğin: color = QColorDialog.getColor(parent=self, title='Choose Color') if color.isValid(): # color selected Aşağıdaki örnekte getColor metodu ile renk seçme diyalog penceresi açılmıştır. Sonra diyalog penceresinden Cancel tuşu ile çıkılıp çıkılmadığına bakılmıştır. En nihayetinde de pencerenin zemin rengi seçilen renk ile boyanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Choose Color") self.resize(640, 480) self.cw = QWidget() self.setCentralWidget(self.cw) menuBar = self.menuBar() self.optionsPopup = QMenu('&Options') self.optionsPopup.setToolTipsVisible(True) menuBar.addMenu(self.optionsPopup) self.chooseColorAction = QAction('Choose color...') self.chooseColorAction.setIcon(QIcon('Color.png')) self.chooseColorAction.triggered.connect(self.chooseColorActionHandler) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.chooseColorAction) def chooseColorActionHandler(self): color = QColorDialog.getColor(parent=self, title='Choose Color') if color.isValid(): style = f'QWidget {{background-color: rgb({color.red()}, {color.green()}, {color.blue()});}}' self.cw.setStyleSheet(style) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Font seçmek için de hazır bir diyalog penceresi bulunmaktadır. Bu diyalog penceresi QFontDialog sınıfıyla temsil edilmiştir. Bu sınıf da QDialog sınıfınından türetilmiştir. Diyalog penceresinin genel kullanımı diğerlerinde olduğu gibidir. Yani QFontDialog sınıfı türünden bir nesne yaratılır ve sınıfın exec metodu çağrılır. Seçilmiş olan font sınıfın currentFont metodu ile elde edilir. Örneğin: fd = QFontDialog(self) if fd.exec() == QDialog.Accepted: # ... Aşağıdaki örnekte daha önce yapmış olduğumuz text editöre font değiştirme özelliğini ekledik. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open...') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.textColorAction = QAction(QIcon('Icons/color.png'), '&TextColor...') self.textColorAction.triggered.connect(self.textColorActionHandler) self.formatPopup.addAction(self.textColorAction) self.textFontAction = QAction(QIcon('Icons/font-48x48.png'), '&Font...') self.textFontAction.triggered.connect(self.textFontActionHandler) self.formatPopup.addAction(self.textFontAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.toolBar.addSeparator() self.toolBar.addAction(self.textColorAction) self.toolBar.addSeparator() self.toolBar.addAction(self.textFontAction) self.textEdit = QTextEdit(self) self.textEdit.setFont(QFont('Cascadia Mono', 14)) self.setCentralWidget(self.textEdit) def openActionHandler(self): fd = QFileDialog(self, filter='All Files (*.*') fd.setWindowTitle('Choose file') fd.setNameFilter('Text files (*.txt *.py);;All Files (*.*)') if fd.exec() == QDialog.Accepted: path = fd.selectedFiles()[0] try: with open(path) as f: text = f.read() self.textEdit.setPlainText(text) except Exception as e: QMessageBox.warning(self, 'Error', str(e)) def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) def textColorActionHandler(self): cd = QColorDialog(self) cd.setCurrentColor(QColor('blue')) if cd.exec() == QColorDialog.Accepted: self.textEdit.setTextColor(cd.currentColor()) def textFontActionHandler(self): fd = QFontDialog(self) fd.setCurrentFont(QFont('Cascadia Mono', 14)) if fd.exec() == QDialog.Accepted: self.textEdit.setCurrentFont(fd.currentFont()) def closeEvent(self, ce): if self.textEdit.document().isModified(): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Cancel: ce.ignore() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QFontDialog sınıfının da getFont isimli static metodu bulunmaktadır. Bu metot kendi içerisinde QFontDialog nesnesini yaratıp exec işlemi uygulamaktadır. Ancak bu metodun kullanılamsı biraz Python dilinin dışında sağlanan özellikleri de barındırmaktadır. Metot ikili bir demetle geri dönmektedir. Demetin birinci elemanı seçilen fontu ikinci elemanı Ok ile mi Cancel ile diyalog penceresinden çıkıldığını belirtmektedir. Örneğin: font, ok = QFontDialog.getFont(QFont('Cascadia Mono'), self, 'Choose Font' ) if ok: self.textEdit.setCurrentFont(font) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden de belirtildiği gibi modeless diyalag pencereleri arka plan etkileşiminin yapılabildiği özel özel diyalog pencereleridir. Modeless diyalog pencerelerinde de yine diyalog penceresi onun üst penceresinin üstünde gözükür ancak arka plan etkilemişi mümkündür. Modeless diyalog pencereleri şöyle oluşturulmaktadır: 1) Önce yine QDialog sınıfınından bir sınıf türetilir. Ancak taban sınııfn __init__ metodu çağrılırken üst pencere olarak diyalog penceresinin üzerine açıldığı pencere verilmeliri. Örneğin: class FindDialog(QDialog): def __init__(self, parent): super.__init__(parent) self.parent = parent ... Modeless diyalog pencerelerinde genellikle üst pencere üzerinde arka planda bazı işlemler yapılır. Bu nedenle üst pancerenin pencere nesnesinin modeless diyalog pencere sınıfında saklanması uygun olur. Yukarıdaki örnekte biz bunu self.parent = parent yaparak saklamış olduk 2) Pencerenin açlması exec metoduyla değil show metoduyla yapılmalıdır. Örneğin: self.fd = FindDialog(self) self.fd.show() Modeless diyalog pencereleri pencere açıldıktan sonra akışı bekletmediği için pencere nesnesinin sınıfın örnek özniteliğinde saklanması uygun olur. 3) Modeless diyalog pencereleri genellikle kullanıcılar tarafından X simgesine basılarak kapatılmaktadır. Ancak diyalog penceresi içerisinde kapatma için ayrı bir düğme de bulundurulabilmektedir. Modeless diyalog pencereleri doğrudan sınıfın close metodu ile kapatılmalıdır. (Anımsanacağı gibi modal diyalog pencereleri done metodu ile kapatılıyordu). Modelesss diyalog pencereleri açıldığında akış modal diyalog pencerelerinde olduğu gibi bir yerde bekletilmemektedir. Bu nedenle kullanıcının hangi tuşla modeless oencereyi kapattığının genellikle bir önemi yoktur. Aşağıdaki örnekte text editöre Find modeless diyalog penceresi eklenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class FindDialog(QDialog): def __init__(self, parent): super().__init__(parent) self.setWindowTitle('Find') self.parent = parent self.resize(300, 100) self.setMaximumSize(300, 100) self.setMinimumSize(300, 100) self.labelFind = QLabel('Text to find:', self) self.labelFind.setGeometry(10, 10, 100, 20) self.lineEditFind = QLineEdit(self) self.lineEditFind.setGeometry(10, 30, 150, 20) self.pushButtonNext = QPushButton('Next', self) self.pushButtonNext.setGeometry(200, 20, 70, 30) self.pushButtonNext.clicked.connect(self.pushButtonNextHandler) self.pushButtonClose = QPushButton('Close', self) self.pushButtonClose.setGeometry(200, 60, 70, 30) self.pushButtonClose.clicked.connect(self.pushButtonCloseHandler) self.lastPos = 0 def pushButtonNextHandler(self): findText = self.lineEditFind.text() allText = self.parent.textEdit.toPlainText() pos = allText.find(findText, self.lastPos) if pos == -1: if self.lastPos == 0: QMessageBox.warning(self, 'Warning', 'Cannot find!') return self.lastPos = 0 pos = allText.find(findText, self.lastPos) textCursor = self.parent.textEdit.textCursor() textCursor.setPosition(pos) textCursor.setPosition(pos + len(findText), QTextCursor.KeepAnchor) self.parent.textEdit.setTextCursor(textCursor) self.lastPos = pos + len(findText) def pushButtonCloseHandler(self): self.close() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Text Editor") self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('&File') self.filePopup.setToolTipsVisible(True) menuBar.addMenu(self.filePopup) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open...') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionHandler) self.openAction.setToolTip('Opens a file') self.filePopup.addAction(self.openAction) self.closeAction = QAction(QIcon('Icons/close-32x32.png'), 'C&lose') self.closeAction.setShortcut('Ctrl+L') self.closeAction.triggered.connect(self.closeActionHandler) self.closeAction.setToolTip('Closes a file') self.filePopup.addAction(self.closeAction) self.fileSepartorAction = QAction('') self.fileSepartorAction.setSeparator(True) self.filePopup.addAction(self.fileSepartorAction) self.exitAction = QAction(QIcon('Icons/exit-32x32.png'), '&Exit') self.exitAction.setShortcut('Ctrl+E') self.exitAction.triggered.connect(self.exitActionHandler) self.exitAction.setToolTip('Exits program') self.filePopup.addAction(self.exitAction) self.editPopup = QMenu('&Edit') menuBar.addMenu(self.editPopup) self.copyAction = QAction(QIcon('Icons/copy-32x32.png'), '&Copy') self.copyAction.setShortcut('Ctrl+C') self.copyAction.triggered.connect(self.copyActionHandler) self.editPopup.addAction(self.copyAction) self.cutAction = QAction(QIcon('Icons/cut-32x32.png'), 'C&ut') self.cutAction.setShortcut('Ctrl+X') self.cutAction.triggered.connect(self.cutActionHandler) self.editPopup.addAction(self.cutAction) self.pasteAction = QAction(QIcon('Icons/paste-32x32.png'), '&Paste') self.pasteAction.setShortcut('Ctrl+V') self.pasteAction.triggered.connect(self.pasteActionHandler) self.editPopup.addAction(self.pasteAction) self.editPopup.addSeparator() self.findAction = QAction(QIcon('Icons/find-32x32.png'), '&Find...') self.findAction .setShortcut('Ctrl+F') self.findAction .triggered.connect(self.findActionHandler) self.editPopup.addAction(self.findAction) self.formatPopup = QMenu('Format') menuBar.addMenu(self.formatPopup) self.wordWrapAction = QAction('&Word Wrap') self.wordWrapAction.setCheckable(True) self.wordWrapAction.triggered.connect(self.wordWrapActionHandler) self.wordWrapAction.setChecked(True) self.formatPopup.addAction(self.wordWrapAction) self.textColorAction = QAction(QIcon('Icons/color.png'), '&TextColor...') self.textColorAction.triggered.connect(self.textColorActionHandler) self.formatPopup.addAction(self.textColorAction) self.textFontAction = QAction(QIcon('Icons/font-48x48.png'), '&Font...') self.textFontAction.triggered.connect(self.textFontActionHandler) self.formatPopup.addAction(self.textFontAction) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.addToolBar(self.toolBar) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.closeAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyAction) self.toolBar.addAction(self.cutAction) self.toolBar.addAction(self.pasteAction) self.toolBar.addSeparator() self.toolBar.addAction(self.findAction) self.toolBar.addSeparator() self.toolBar.addAction(self.textColorAction) self.toolBar.addSeparator() self.toolBar.addAction(self.textFontAction) self.textEdit = QTextEdit(self) self.textEdit.setFont(QFont('Cascadia Mono', 14)) self.setCentralWidget(self.textEdit) def openActionHandler(self): fd = QFileDialog(self, filter='All Files (*.*') fd.setWindowTitle('Choose file') fd.setNameFilter('Text files (*.txt *.py);;All Files (*.*)') if fd.exec() == QDialog.Accepted: path = fd.selectedFiles()[0] try: with open(path) as f: text = f.read() self.textEdit.setPlainText(text) except Exception as e: QMessageBox.warning(self, 'Error', str(e)) def closeActionHandler(self): QMessageBox.information(self, 'Message', 'Close item selected') def exitActionHandler(self): self.close() def copyActionHandler(self): QMessageBox.information(self, 'Message', 'Copy item selected') def cutActionHandler(self): QMessageBox.information(self, 'Message', 'Cut item selected') def pasteActionHandler(self): QMessageBox.information(self, 'Message', 'Paste item selected') def findActionHandler(self): self.fd = FindDialog(self) self.fd.show() def wordWrapActionHandler(self): self.textEdit.setLineWrapMode(QTextEdit.WidgetWidth if self.wordWrapAction.isChecked() else QTextEdit.NoWrap) def textColorActionHandler(self): cd = QColorDialog(self) cd.setCurrentColor(QColor('blue')) if cd.exec() == QColorDialog.Accepted: self.textEdit.setTextColor(cd.currentColor()) def textFontActionHandler(self): font, ok = QFontDialog.getFont(QFont('Cascadia Mono'), self, 'Choose Font' ) if ok: self.textEdit.setCurrentFont(font) def closeEvent(self, ce): if self.textEdit.document().isModified(): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Cancel: ce.ignore() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt'de GUI elemanların pencere içerisine koordinat kullanılarak yerleştirilmeleri oldukça zahmetlidir. Biz şimdiye kadar böyle yaptık. Ancak GUI elemanların yerleştirilmesi işlemi görsel bir biçimde ismine "Qt Designer" denilen bir araçla da yapılabilmektedir. Qt daha önce de belirttiğimiz gibi bir C++ GUI framework'tür. C++ programcıları için Qt'nin ismine "Qt Creator" denilen bir IDE'si de vardır. C++ için Qt Creator IDE'si kendi içerisinde Qt Designer aracını da barındırmaktadır. Ancak Qt Creator IDE'si oldukça büyük bir yer kaplamaktadır. Dolayısıyla Python programcıları sadece designer için bu IDE'!yi kurmak istemezler. Qt designed aşağıdkai bağlantıdan bağımısz bir program olarak da indirilebilir: https://build-system.fman.io/qt-designer-download Qt Creator IDE'sinin son versiyonlarında artık IDE Python uygulamalarını da destekler hale getirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 33. Ders 12/04/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Qt Designer açıldığında bize nasıl bir pencere oluşturmak istediğimizi sormaktadır. Biz menülü bir ana pencere (yani QMainWindow sınıfından türetilmiş olan bir pencere), menüsüz bir ana pencere (yani QWidget sınıfından türetilmiş bir pencere) ya da bir diyalog penceresi oluşturabiliriz. Soldaki "Widget Box"tan GUI elemanlar sürüklenerek pencereye bırakılabilmektedir. Bir GUI elemanı sürüklenip pencereye bırakıldıktan sonra onun üzerine gelinip sağ taraftaki "Property Editotor"den onun özellikleri set edilebilir. GUI elemanların programda kullanılacak isimleri Propert Editor'deaki Object Name sekmesinde belirtilmektedir. Burada oluşturulan GUI tasarımı Designer'da save edildiği zaman .ui uzantılı bir XML dosya elde edilir. Bu XML dosyası içerisinde GUI tasarımındaki her öğe bulunmaktadır. Bu XML dosyasının bir python dosyasına dönüştürülmesi gerekmektedir. Bu işlemi yapan programa "user interface compiler" da denilmektedir. Bu program "pyuic5" ya da pyuic6" isimli programdır. Program şöyle kullanılmaktadır: pyuic5 -o Buradaki -o seçeneği çıktı dosyasını isimlendirmek için kullanılmaktadır. Örneğin Designer'da save ettiğimiz dosya "testui.ui" isimli dosya olsun. Bu programı şöyle kullanabiliriz: pyuic5 -o testui.py testui.ui pyuic5 ya da pyuic6 programı pyqt kurulduğunda zaten hazır biçimde bulunur halde olmaktadır. Artık elimizde Designer'da tasarladığımız GUI ekranı oluşturan bir Python programı v ardır. Bu programın içerisinde Ui_XXX biçiminde bir sınıf bulunur. Bu sınıfın ismindeki XXX aslında ana pencerenin ObjectName property'sinde belirtilen isimdir. Biz bu ismin Ui_MainWindow olduğunu varsayalım. Bu işlemlerden sonra iskelet bir PyQt programı yazılır. Programda oluşturulan Python dosyası import edilir. Örneğin: import testui Sonra bu modüldeki Ui_XXX sınıfı türünden bir nesne yaratılırp bu nesne sınıfın örnek özniteliğinde saklanır. Örneğin: self.ui = testui.Ui_MainWİndow() Sonra Ui_XXX sınıfının setUI isimli metodu ana pencere nesnesi verilerek çağrılır. Örneğin: self.ui = testui.Ui_MainWindow() self.ui.setupUi(self) Asıl işlemi yapan bu setUI metodudur. Bu metot bizim GUI ekranda oluşturduğumuz GUI elemanları verdiğimiz pencere içerisine yerleştirmektedir. Yani tüm kod aşağıdaki gibidir: import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import testui class MainWindow(QMainWindow): def __init__(self): super().__init__() self.ui = testui.Ui_MainWindow() self.ui.setupUi(self) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() Burada dikkat edilmesi gereken en önemli noktalardan biri Designer'da yapılan her değişikliğin save edilmesi ve yeniden pyuic5 ya da pyuic6 programıyla oluşturulamsıdır. Pekiyi sinyal işlemeleri nasıl yapılmaktadır? Biz Designer'da bir GUI elemanı sürükleyip pencereye bıraktığımızda bu GUI eleman aslında Ui_XXX sınıfının bir örnek özniteliği durumunda olmaktadır. Örneğin biz Designer'da bir düğmeyi sürükleyip bırakıp "ObjectName" ismi olarak pushButtonOk vermiş olalım. Bu durumda biz bu nesneye self.ui.pushButtonOk biçiminde erişebiliriz. Dolayısıyla sinyal bağlantısını kod üzerinde yapabiliriz. Örneğin: self.ui.pushButtonOk.clicked.connect(self.pushButtonOkHandler) Ayrıca istenirse sinyal slot bağlantısı Designer üzerinden yapılabilir. Zaten var olan sinyallerin slot bağlantıları için bu yöntem uygun olabilir. Ancal slot'u biz yazacaksak bu yöntem fazlaca bir kolaylık sağlamamaktadır. Bunun için menü ya da araç çubuğundan Edit Signal/Slot seçilir. Sonra fare hareketi ile sinyal slot bağlantısı kurulabilir. Bir GUI elemanın bir sinyali başka bir GUI elemanın slotuna bu sayede bağlanabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 34. Ders 17/04/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ GUI elemanlarının otomatik yerleştirilmesi için Qt'de ismine layout nesneleri denilen bir grup nesne kullanılmaktadır. Layout nesneleri birer pencere değildir. Programcı bir layout nesnesini yaratır. Sonra GUI elemanlarını bu layout sınıflarının addWidget ve insertWidget metotları ile layout içerisine yerleştirir. Programcı layout nesnelerinin içerisine layout nesnelerini de yerleştirebilmektedir. Bunun için layout sınıflarının addLayout ve insertLayout metotları kullanılır. Qt'deki tüm layout nesneleri QLayout isimli bir sınıftan türetilmiştir. Türetme şeması şmyledir: QLayout qBoxLayout QFormLayout QGridLayout QStackedLayout qHBoxLayout qHBoxLayout qVBoxLayout Layout nesneleri birer pencere (yani widget) değildir. Dolayısıyla bunların pencerelere ilişkin özellikleri yoktur. Anımsanacağı gibi tüm pencereler QWidget sınıfından türetilmiş durumdadır. Halbuki QLayout sınıfı QWidget sınıfından türetilmemiştir. Bir pencereye (yani widget'a) tek bir layout nesnesi iliştirilebilir. Pencereye layout nesnesini iliştirebilmek için QWidget sınıfının setLayout metodu kullanılmaktadır. Programcı tipik olarak layout nesnelerinin içerisine GUI elemanlarını ve başka layout nesnelerini yerleştire yerleştire tek bir layout nesnesi elde eder. Bunu da programın ana penceresine setLayout metodu ile iliştirir. Ancak QMainWindow sınıfının içerisine bir centralWidget yerleştirilmesi ve o centralWidget ile seyLayout yapılması gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ HBoxLayout nesnesi ona iliştirilmiş olan pencerereleri yatay bir biçimde görüntülemektedir. Default olarak bir pencereye setLayout ile bir HBoxLayout iliştirilirse bu HBoxLayout düşey bakımdan pencerenin ortasında görüntülenir. Ancak yatay bakımdan pencere genişlrtilip darakltıldıkça tüm genişliği kaplayacak biçimde kendini genişletip daraltır. Aşağıdaki örnekte beş tane QPushButton nesnesi addWidget metodu ile bir HBoxLayout nesnesine yerleştirilmiştir. En sonunda bu HBoxLayout nesnesi QWidget sınıfının setLayout metodu ile ana pencereye iliştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.resize(640, 480) self.pushButton1 = QPushButton('Button1', self) self.pushButton2 = QPushButton('Button2', self) self.pushButton3 = QPushButton('Button3', self) self.pushButton4 = QPushButton('Button4', self) self.pushButton5 = QPushButton('Button5', self) self.hBoxLayout = QHBoxLayout() self.hBoxLayout.addWidget(self.pushButton1) self.hBoxLayout.addWidget(self.pushButton2) self.hBoxLayout.addWidget(self.pushButton3) self.hBoxLayout.addWidget(self.pushButton4) self.hBoxLayout.addWidget(self.pushButton5) self.setLayout(self.hBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QVBoxLayout nesnesi QHBoxLayout nesnesinin düşey yerleştirme yapan biçimidir. QVBoxLayout nesnesine yerleştirme yapılırsa onun içerisindeki öğeler düşey bir biçimde konumlandırılır. Aşağıdaki örnekte bu kez beş tane QPushButton nesnesi QVobLayout içerisine yerleştirilmiştir. Sonra da bu nesne ana pencereye setLayout ile iliştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.vBoxLayout = QVBoxLayout() self.pushButtonOk = QPushButton('Ok', self) self.pushButtonCancel = QPushButton('Cancel', self) self.vBoxLayout.addWidget(self.pushButtonOk) self.vBoxLayout.addWidget(self.pushButtonCancel) self.setLayout(self.vBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Tipik olarak programcı layout nesnelerinin içerisine GUI elemanlarını (yani pencerfeleri) yerleştirir. Sonra layout nesnelerini layout nesnelerinin içerisine yerleştire yerleştire tek bir layout nesnesi elde eder. Onu da ana pencereye setLayout metodu ile iliştirir. Layout nesnelerinin genellikle sınıfın örnek özniteliği olarak saklanmasına gerek kalmamaktadır. Ancak bazı durumlarda layout nesnelerine farklı metotlardan erişim istenebilir. Aşağıdaki örnekte üç QHBoxLayout nesnesi bir QVBoxLayout nesnesi içerisine yereştirilmiş en sonunda bu QVBoxLayout nesnesi setLayout metodu ile ana pencereye iliştirilmiştir. Aşağıdaki örnekte QLabel nesneleri ile QLineEdit nesneleri aynı hizada gözükmeyecektir. Halbuki QLineEdit nesnelerinin aynı hizada ve aynı genişlikte görüntülenmesi istenir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.labelName = QLabel('Adı Soyadı', self) self.lineEditName = QLineEdit(self) self.labelNo = QLabel('No', self) self.lineEditNo = QLineEdit(self) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonCancel = QPushButton('Cancel', self) hBoxLayout1 = QHBoxLayout() hBoxLayout1.addWidget(self.labelName) hBoxLayout1.addWidget(self.lineEditName) hBoxLayout2 = QHBoxLayout() hBoxLayout2.addWidget(self.labelNo) hBoxLayout2.addWidget(self.lineEditNo) hBoxLayout3 = QHBoxLayout() hBoxLayout3.addWidget(self.pushButtonOk) hBoxLayout3.addWidget(self.pushButtonCancel) vBoxLayout = QVBoxLayout() vBoxLayout.addLayout(hBoxLayout1) vBoxLayout.addLayout(hBoxLayout2) vBoxLayout.addLayout(hBoxLayout3) self.setLayout(vBoxLayout) self.setMaximumSize(300, 100) self.setMinimumSize(300, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki form'da QLabel nesnelerinin ve QLineEdit nesnelerinin hizalı görünmesi isteniyorsa şöyle bir yöntem de izelenebilir: İki QLable nesnesi bir VBoxLayout içerisine, iki QLineEdit nesnesi başka bir VBocLayout içerisine yerleştirilir. Sonra bu iki VBoxLoyout nesnesi bir HBoxLayout nesiesi içerieine yerleştirilir. İki düğme de bir HBoxLoayout içerieine yerleştirilirse nihai olarak eleimizde iki HBoxLayout nesnesi bulunur. Bunları teke düşürmek için onları bir QVBoxLayout içerisine yerleştirebiliriz. En nihayetinde onu da ana pencereye iliştirebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.labelName = QLabel('Adı Soyadı', self) self.lineEditName = QLineEdit(self) self.labelNo = QLabel('No', self) self.lineEditNo = QLineEdit(self) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonCancel = QPushButton('Cancel', self) vBoxLayout1 = QVBoxLayout() vBoxLayout1.addWidget(self.labelName) vBoxLayout1.addWidget(self.labelNo) vBoxLayout2 = QVBoxLayout() vBoxLayout2.addWidget(self.lineEditName) vBoxLayout2.addWidget(self.lineEditNo) hBoxLayout1 = QHBoxLayout() hBoxLayout1.addLayout(vBoxLayout1) hBoxLayout1.addLayout(vBoxLayout2) hBoxLayout2 = QHBoxLayout() hBoxLayout2.addWidget(self.pushButtonOk) hBoxLayout2.addWidget(self.pushButtonCancel) vBoxLayout3 = QVBoxLayout() vBoxLayout3 .addLayout(hBoxLayout1) vBoxLayout3 .addLayout(hBoxLayout2) self.setLayout(vBoxLayout3) self.setMaximumSize(300, 100) self.setMinimumSize(300, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki gibi form'ları daha pretik oluşturmak için QFormLayout isimli bir layout nesnesi de bulundurulmuştur. Bu layout nesnesinin ikişerlli elemanları eklemeye yarayan addRow metodu vardır. addRow metodu iki widget ya da tek bir layout nesnesi almaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.labelName = QLabel('Adı Soyadı', self) self.lineEditName = QLineEdit(self) self.labelNo = QLabel('No', self) self.lineEditNo = QLineEdit(self) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonCancel = QPushButton('Cancel', self) hBoxLayout = QHBoxLayout() hBoxLayout.addWidget(self.pushButtonOk) hBoxLayout.addWidget(self.pushButtonCancel) formLayout = QFormLayout() formLayout.addRow(self.labelName, self.lineEditName) formLayout.addRow(self.labelNo, self.lineEditNo) formLayout.addRow(hBoxLayout) self.setLayout(formLayout) self.setMaximumSize(300, 100) self.setMinimumSize(300, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Layout nesnelerinin oluşturulması Designer'da oldukça kolaydır. Bir grup widget ve/veya layout nesnesi seçilir. Farenin sağ tuşuna basılır. Bağlam menüsünden Layout elemanı seçilerek hangi layout nesnesi kullanılacaksa o seçilir. En sonunda ana pencerede sağ tuşa basılarak benzer biçimde setLayout işlemi menü yoluyla yapılır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ En çok kullanılan layout nesnelerinden biri de QGridLayout nesnesidir. Bu nesne sanki hücrelerden oluşan bir grid oluşturup ilgili widget ya da layout nesnelerini bu gridlere yerleştirmektedir. Yine QGridLayout sınıfının addWidget ve addLayout metotları vardır. Ancak bu metotlar grid içerisinde hücreyi belirlemek için satır ve sütun numaralarını ve yatay ve düşey yayılım (span) miktarlarını bizden almaktadır. Aşağıdaki örnekte QGridLayout nesnesinin hücrelerine iki QLabel, iki QLineEdit ve bir de QHobxLayout yerleştirilmiştir. İki QPushButton nesnesi bir QHBoxLayout içerisine yerleştirilip span özelliği ile grid'in iki sütununun kaplanması sağlanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.labelName = QLabel('Adı Soyadı', self) self.lineEditName = QLineEdit(self) self.labelNo = QLabel('No', self) self.lineEditNo = QLineEdit(self) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonCancel = QPushButton('Cancel', self) hBoxLayout = QHBoxLayout() hBoxLayout.addWidget(self.pushButtonOk) hBoxLayout.addWidget(self.pushButtonCancel) gridLayout = QGridLayout() gridLayout.addWidget(self.labelName, 0, 0) gridLayout.addWidget(self.lineEditName, 0, 1) gridLayout.addWidget(self.labelNo, 1, 0) gridLayout.addWidget(self.lineEditNo, 1, 1) gridLayout.addLayout(hBoxLayout, 2, 0, 1, 2) self.setLayout(gridLayout) self.setMaximumSize(300, 100) self.setMinimumSize(300, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 35. Ders 09/04/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Az kullanılan diğer bir layout nesnesi de QStackedLayout nesnesidir. Bu layout nesnesi aynı alanı kullanan bir grup pencerenin yalnızca birinin görüntülendiği ve görüntülenen pencerenin de değiştirilebildiği gibi nesnedir. StackedLayout sınıfının setCurrentIndex ve SetCurrentWidget metotları nesnenin göstereceği pencereyi değiştirmekte kullanılmaktadır. Aşağıdaki örnekte bir QHBoxLayout içerisinde bir StackedLayout bir de QComboBox nesneleri yerleştirilmiştit. Combobox'tan bir seçim yapıldığında doğrudan combobox'ın currentIndexChanged sinyali stackedlayout'un setCurrentIndex metoduna bağlanmıştır. Böylece combobox'tan bir seçim yapıldığında ilgili pushButton ön planda görünür hale getirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.pushButton1 = QPushButton('1', self) self.pushButton2 = QPushButton('2', self) self.pushButton3 = QPushButton('3', self) self.pushButton4 = QPushButton('4', self) self.pushButton5 = QPushButton('5', self) sl = QStackedLayout() sl.addWidget(self.pushButton1) sl.addWidget(self.pushButton2) sl.addWidget(self.pushButton3) sl.addWidget(self.pushButton4) sl.addWidget(self.pushButton5) self.comboBox = QComboBox() self.comboBox.addItem('1'); self.comboBox.addItem('2'); self.comboBox.addItem('3'); self.comboBox.addItem('4'); self.comboBox.addItem('5'); self.comboBox.currentIndexChanged.connect(sl.setCurrentIndex) hbl = QHBoxLayout() hbl.addLayout(sl) hbl.addWidget(self.comboBox) self.setLayout(hbl) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt'de layout nesnelerinin içerisine yerleştirilmiş olan widget'ların ve layout nesnelerinin "size policy" denilen bir özelliği vardır. size policy yatay ve düşeyde ayrı ayrı belirlenmektedir. Bir widget yaratıldığında onun yatay ve düşey size policy'si default bir değerdedir. Size policy ilgili pencere büyütülüp küçültüldüğünde layout nesnelerinin içerisindeki GUI elemanların nasıl davranacağını belirtmektedir. Örneğin size policy'nin QSizePolicy.Expanding olması demek layout nesnesi içerisindeki tüm size policy'si QSizePolicy.Expanding olan GUI elemanların yatay ya da düşey bakımdan eşit oranda büyütülüp küçültülmesi demektir. Örneğin QPushButton nesnesinin düşey size policy'si QSizePolicy.Fixed durumdadır. Ancak yatay size policy'si QSizePolicy.Expanding durumdadır. Bu durumda pencere yatay olarak büyütüldüğünde pusbh button nesnelerinin genişlikleri artacak ancak yükseklikleri sabit kalacaktır. Ancak düşey olarak push button nesnelerinin yükseklikleri aynıo kalacaktır. Aşağıdaki örnekte pencereyi yatay ve düşey bakımdan genişletip daraltınız ve push button nesnelerinin davranışını gözlemleyiniz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.pushButton1 = QPushButton('1', self) self.pushButton2 = QPushButton('2', self) self.pushButton3 = QPushButton('3', self) self.pushButton4 = QPushButton('4', self) self.pushButton5 = QPushButton('5', self) hBoxLayout = QHBoxLayout() hBoxLayout.addWidget(self.pushButton1) hBoxLayout.addWidget(self.pushButton2) hBoxLayout.addWidget(self.pushButton3) hBoxLayout.addWidget(self.pushButton4) hBoxLayout.addWidget(self.pushButton5) self.setLayout(hBoxLayout) print(self.pushButton1.sizePolicy()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir widget'ın iyi gözükmesi için olması gereken genişlik ve yüksekliğe "size hint" denilmektedir. Minimum genişlik ve yüksekliğe ise minimum size hint denilmektedir. . sizeHint değeri widget’ın size değeriyle aynı anlama gelmemektedir. size widget’ın o andaki gerçek genişlik ve yüksekliğidir. sizeHint ise onun iyi bir biçimde gözükmesi için tavsiye edilen ideal genişlik ve yüksekliğidir. İşte sizePolicy ayarlanırken hep sizeHint değeri dikkate alınarak ayarlamalar yapılmaktadır. Örneğin: Yatay ve düşey policy değerlerinin anlamları şöyledir: Fixed: Bu durumda widget her zaman sizeHint ile belirtilen boyutta tutulur. Büyütülmez ya da küçültülmez. Minimum: Bu seçenekte widget sizeHint’ten daha fazla küçültülemez, fakat büyütülebilir. Maximum: Bu seçenekte widget sizeHint değerinden daha fazla büyütülemez ancak küçültülebilir. Fakat küçültme de ancak minimumSizeHint değerine kadar yapılabilmektedir. Preferred: Bu seçenekte widget minimumSizeHint değerine kadar küçültülebilir, ancak istenildiği kadar büyütülebilir. Expanding: Bu seçenekte widget minimumSizeHint değerine kadar küçültülebilir, ancak istenildiği kadar büyütülebilir. Bu seçeneğin Preferred seçenğinden farkı, bu widget’ların yerleştirildiği layout’lar büyütülünce eldeki boş alanı Preferred değil Expanding widget’ların paylaşmasıdır. Minimum Expanding: Bu seçenekte widget sizeHint’ten daha fazla küçültülemez, fakat büyütülebilir. Bu özelliğe sahip widget’ların yerleştirildiği layout’lar büyütülünce eldeki boş alanı Preferred değil Expanding widget’lar paylaşmaktadır. Ignored: Widget her zaman geri kalan alanı almaya çalışır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Layout Examples') self.pushButton1 = QPushButton('1', self) self.pushButton2 = QPushButton('2', self) self.pushButton3 = QPushButton('3', self) self.pushButton4 = QPushButton('4', self) self.pushButton5 = QPushButton('5', self) self.pushButton1.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.pushButton2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.pushButton3.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.pushButton4.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.pushButton5.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) hBoxLayout = QHBoxLayout() hBoxLayout.addWidget(self.pushButton1) hBoxLayout.addWidget(self.pushButton2) hBoxLayout.addWidget(self.pushButton3) hBoxLayout.addWidget(self.pushButton4) hBoxLayout.addWidget(self.pushButton5) self.setLayout(hBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Biz bir layout nesnesinin içerisindeki GUI elemanların birbirlerine yakınlığını ve layout nesnesinin diğer layout nesnelerine ve GUI elemanlarına yakınlığını margin set ederek ayarlayabiliriz. QWidget sınıfının ve layout sınıflarının setContentMargins metotları bu işi yapmaktadır. Aynı zamanda layout sınıflarının setSpacing metotları layout içerisindeki elemanların arasındaki açıklığı belirlemek için de kullanılabilmektedir. Ayrıca Qt'de spacer denilen özel layout nesneleri de bulundurulmuştur. Spacer nesneler default olarak QSizePolicy.Expanding özelliğine sahiptir. Bu nedenle pencere genişletildiğinde ve daraltıldığında kalan alanı bunlar işgal eder. Spacer nesnelerin bir görüntüsü yoktur. Pencere genişlediğinde genişleyten alanı paylaştıkları için widget'ların hizalanmasına katkı sağlamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Ana pencere QMainWindow sınıfı ile oluşturulduğunda ana pencerenin zemininde bir centralWidget olması gerektiği unutulmamalıdır. Layout nesneleri bu centralWidget'a setLayout yapılmalıdır. Designer bu işlemi arka planda otomatik yapmaktadır. Aşağıda ana pencere olarak QMainWindow kullanılması durumunda layout nesnesinin ana pencereye değil de central widget'A iliştirilmesine bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.cw = QWidget() self.setCentralWidget(self.cw) self.pushButton1 = QPushButton('Button1', self.cw) self.pushButton2 = QPushButton('Button2', self.cw) hBoxLayout = QHBoxLayout() hBoxLayout.addWidget(self.pushButton1) hBoxLayout.addWidget(self.pushButton2) self.cw.setLayout(hBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt'de bazı istisnalar dışında Widget sınıflarında renklendirmeye ilişkin metotlar yoktur. Örneğin bir QLabel sınıfında setForeground gibi yazı rengini değiştiren bir metot bulunmamaktadır. Bu tür renk değiştirmeleri eskiden QPalette sınıfı yoluyla yapılıyordu. Sonra Qt tasarımcıları renk konusunu içeren GUI elemanlarında özelleştirmelerin yapılmasına olanak veren "stylesheet" sistemini oluşturdular. Bugün artık renklendirme için stylesheet tercih edilmektedir. Ancak eski QPalette sistemi geçmişe doğru uyumu korumak için muhafaza edilmektedir. QPalette kullanımı tipik olarak şöyle yapılmaktadır: Programcı önce QPalette türünden bir nesne yaratır. Sonra o nesne ile sınıfın setXXX metotlarını çağırır. Daha sonra da bu palet nesnesini QWidget sınıfından gelen setPalette metoduyla GUI elemanına iliştirir. Örneğin QPalette sınıfının setColor metodu GUI elemanın renklendirilmesi için kullanılmaktadır. Bu metot üç parametre alır. İlk parametre GUI elemanının hangi durumu için renklendirme yapılacağını belirtir. Bu parametre QPalette.Active girilirse kontrol normal durumdaykenki renk anlaşılır. İkinci parametre neyin renklendirileceğini belirtir. Burada QPalette.Window zemin rengi için QPalette.WindowText yazı rengi için kullanılmaktadır. Düğmelerin üzerindeki renklerin değiştirilmesi için QPalette.ButtonText rolünün kullanılması gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(640, 480) self.setWindowTitle('QPalette Example') self.label = QLabel('This is a test', self) self.label.move(20, 20) palette = QPalette() palette.setColor(QPalette.Active, QPalette.WindowText, Qt.red) palette.setColor(QPalette.Disabled, QPalette.WindowText, Qt.blue) self.label.setPalette(palette) self.checkBox = QCheckBox('Test', self) self.checkBox.move(20, 50) self.checkBox.setPalette(palette) palette = QPalette() palette.setColor(QPalette.Active, QPalette.ButtonText, Qt.blue) self.buttonOk = QPushButton('Ok', self) self.buttonOk.setGeometry(20, 100, 50, 50) self.buttonOk.clicked.connect(self.buttonOkClickedHandler) self.buttonOk.setPalette(palette) def buttonOkClickedHandler(self): self.checkBox.setEnabled(False) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencerenin zemin rengini aşağıdaki gibi değiştirebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(640, 480) self.setWindowTitle('QPalette Example') palette = QPalette() palette.setColor(QPalette.Active, QPalette.Window, Qt.yellow) self.setPalette(palette) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt 5 ile birlikte widget'ların görsel ayarlamaları için "style sheet" özelliğine sahip olmuştur. Bu sayede GUI elemanlar üzerinde pek çok ayarlamalar yapılabilmektedir. Stylesheet sentaksı şöyledir: [Seçiciler (Selectortors)] { <Özellik (Attribute) > : ; ...} Stylesheet bir yazıdır. Bu yazı oluşturulduktan sonra QWidget sınıfının setStyleSheet metodu ile GUI elemana set edilir. Bu genel biçimdeki "seçiciler (selectors)" çeşitli olabilmektedir. Ayrıca GUI elemana göre pek çok "özellik (attribute)" bulunmaktadır. Örneğin tüm GUI elemanlarda "color" ve "background-color" isimli iki özellik bulunmaktadır. Bu özelliklerin değerleri birer renk olmalıdır. Seçiciler çeşitli biçimlerde olabilmektedir. Ancak en basit seçici bir sınıf ismidir. Bu durumda yalnızca o sınıf türünden ve o sınıftan türetilmiş türden nesneler bu seçiciden etkilenirler. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import testui3 class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(640, 480) self.textEdit = QTextEdit(self) font = self.font() font.setPointSize(14) self.textEdit.setFont(font) styleSheet = """ QTextEdit {color: red; background-color: yellow; selection-color: #FF0000; selection-background-color: magenta; margin-top: 10px; margin-left: 10px; margin-bottom: 10px; margin-right: 10px; border-style: solid; border-width: 10px; border-color: red} """ self.textEdit.setStyleSheet(styleSheet) self.comboBox = QComboBox(self) self.comboBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.comboBox.setStyleSheet('QComboBox {color: red; font: 14px}') self.comboBox.addItems(['Ankara', 'İzmir', 'Eskişehir', 'Bolu']) hBoxLayout = QHBoxLayout() hBoxLayout.setContentsMargins(3, 3, 3, 2) hBoxLayout.addWidget(self.textEdit) hBoxLayout.addWidget(self.comboBox) self.setLayout(hBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 36. Ders 24/04/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Genel olarak stylesheet yazılarında background-image özelliği ilgili pencerenin içerisinde bir resim göstermek için kullanılmaktadır. Burada background-image şöyle belirtilmektedir: "QLabel {background-image: url()}" Dosyanın yeri bir URL de olabilir. Yerel diskte bir yol ifadesi de olabilir. Yani bir resim göstermek için setPixmap yapmak yerine doğrudan style sheet de kullanılabilmektedir. Aşağıdaki örnekte QLabel içerisine stylesheet kullanılarak bir resim iliştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(640, 480) ss = 'QLabel {background-image: url(AbbeyRoad.jpg); background-repeat: no-repeat;}' labelImage = QLabel(self) hboxLayout = QHBoxLayout() hboxLayout.addWidget(labelImage) self.setLayout(hboxLayout) self.setStyleSheet(ss) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Mademki setStyle metodu QWidget sınıfından gelmektedir. O halde her widget için ayrı ayrı stylesheet set edilebilir. Ancak tıpkı web'te olduğu gibi üst pencereler için set edilen stylesheet belirlemeleri eğer seçici (selector) uygunsa alt pencereler için de geçerlidir. Örneğin elimizde iki tane QLabel nesnesi olsun. Her iki nesnenin de zemininde aynı resmin gözükmesini isteyelim. Bizim stylesheet yazısını ayrı ayrı bu iki QLabel nesnesine set etmemiz gerekmez. Biz setylesheet yazısını yalnızca ana pencereye set edebiliriz. Ana pencerenin Tümalt pencerelerinde eğer seçici (seleector) uygunsa bu etki gösterir. Aşağıdaki örnekte 2x2'lik bir QGridLayout oluşturulmuştur. Bu QGridLayout nesnesinin her hücresine ayrı bir QLabel iliştirilmiştir. Burada setStyleSheet metodu ana pencereye uygulanmıştır. QLabel pencereleri ana pencerenin alt pencereleri olduğu için bu belirlemeler alt pencereleri de içine katmaktadır. Tabii bunun için seçicinin (selector) uygun olması gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(640, 480) ss = 'QLabel {background-image: url(AbbeyRoad.jpg); background-repeat: no-repeat;}' hboxLayout = QGridLayout() labelImage1 = QLabel(self) hboxLayout.addWidget(labelImage1, 0, 0 ) labelImage2 = QLabel(self) hboxLayout.addWidget(labelImage2, 0, 1) labelImage3 = QLabel(self) hboxLayout.addWidget(labelImage3, 1, 0) labelImage4 = QLabel(self) hboxLayout.addWidget(labelImage4, 1, 1) self.setLayout(hboxLayout) self.setStyleSheet(ss) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt'nin "stylesheet reference" dokümanı incelendiğinde her özelliğin her widget'a uygulanamadığı görülmektedir. Bir grup GUI elemana "box modele uygun" elemanlar denilmektedir. Bu elemanlara uygulanacak özellikler bellidir. Örneğin QLabel, QLineEdit, QTextEdit box modele uygundur. Aşağıda bir QLabel nesnesinin sınır çizgilerinin çizilmesi örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(640, 480) label = QLabel('this is a test', self) label.setGeometry(10, 10, 200, 200) label.setStyleSheet(' QLabel {background-color: yellow; border-width: 3px; border-style: solid; font-size: 14px}') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Styşesheet yazısındaki "seçici (selector)" birkaç biçimd eoluşturulabilmektedir: Sınıf İsmi: Burada özellik o sınıf ve o sınıftan türetilmiş tüm sınıflara uygulanır. .Sınıf ismi: Burada özellik yalnızca ilgili sınıfa uygulanır. Ancak ondan türetilmiş sınıflara uygulanmaz. Sınıf ismi#nesne ismi: Eğer özellikler birden fazla sınıfta ortak ise ortak özellikler birden fazla seçici ile se set edilebilmektedir. Örneğin: "QLineEdit, QLabel {border-style: solid}" Aşağıda iki farklı QLabel nesnesine farklı özellikler verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(640, 480) label1 = QLabel('MyLabel', self) label1.setObjectName('MyLabel') label1.setGeometry(10, 10, 100, 100) label2 = QLabel('this is a test', self) label2.setObjectName('YourLabel') label2.setGeometry(10, 120, 100, 100) ss = """ QLabel#MyLabel {background-color: yellow; border-width: 3px; border-style: solid; font-size: 14px} QLabel#YourLabel {background-color: red; border-width: 3px; border-style: solid; font-size: 14px} """ self.setStyleSheet(ss) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Belli bir GUI elemanın belli bir kısmına ilişkin özellikleri set etmek için "sınıf_ismi.kısım_ismi" sentaksı kullanılmaktadır. Aşağıdaki örnekte bir checkbox'ın bazı özellikleri değiştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(640, 480) checkBox = QCheckBox('Test', self) checkBox.setGeometry(10, 10, 200, 200) ss = """ QCheckBox {background-color: yellow; font-size: 14px; color: red; border-radius: 10px; spacing: 10px} """ self.setStyleSheet(ss) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir seçici (selctor) sonunda bir tane ':' sembolü ile durumsal özellik oluşturulabilmektedir. Örneğin: QCheckBox.indicator:unchecked { ...} Burada checkbox penceresinin kutucuğu (indicator) unchecked olduğu durumdaki özellikler belirtilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda bir menü üzerinde bazı özelliklerin style sheet yoluyla değiştirilmesi örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(640, 480) menuBar = self.menuBar() self.filePopup = QMenu('File') menuBar.addMenu(self.filePopup) self.openAction = QAction('Open') self.openAction.setCheckable(True) self.filePopup.addAction(self.openAction) self.closeAction = QAction('Close') self.filePopup.addAction(self.closeAction) self.exitAction = QAction('Exit') self.filePopup.addAction(self.exitAction) ss = """ QMenu {background-color: #ABABAB; border: 1px solid black; margin: 10px} QMenu.item:selected { background-color: #654321; } QMenu.icon:checked { background: gray; border: 1px inset gray; position: absolute; top: 1px; right: 1px; bottom: 1px;left: 1px; } """ self.filePopup.setStyleSheet(ss) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir GUI elemanın görsel özelliklerini değiştirmek için gereken style sheet özelliklerini Internet'ten arayarak bulabilirsiniz. Örneğin aşağıdaki bağlantıda çeşitli temel GUI elemanlar için önemli özelliklerin nasıl değiştirileceğinin listesi verilmiştir: https://het.as.utexas.edu/HET/Software/html/stylesheet-examples.html #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SLider pek çok GUI framework'ünde bulunan yaygın kullanılan bir GUI elemandır. Bu GUI elemanda bir yürütüç vardır. Kullanıcı bu yürüteci çekip bırakarak bir ayarlama yapar. Genellikle volüm kontrolü için, renk kontrolü için slider;'lar kullanılmaktadır. Slider GUI elemanı QSlider sınıfıyla temsil edilmiştir. Bu sınıf QAbstractSlider isimli sınıftan türetilmiş durumdadır. Yürütecin minimum ve maksimum değerleri başlangıçta [0, 99] biçimindedir. Ancak bu değerler istenirse setMinimum ve setMaximum metotlarıyla değiştirilebilir. Slider'ın yatay mı düşey mi olacağı nesne yaratılırken __init__ metodunda belirlenmektedir. Burada Qt.Horizontal yatay yönelim için Qt.Verrtical düşey yönelim için kullanılmaktadır. Yürütecin konumu value metouyla alınıp, setValue metoduyla programlama yoluyla değiştirilebilmektedir. Widget başlangıçta tick'sizdir. Tick'li hale getirmek için setTickPosition metodu kullanılır. Tick periyodu da setTickInterval metodu ile ayarlanmaktadır. GUI elemanının en önemli sinyali valueChanged isimli sinyaldir. Bu sinyal ne zaman yürüteç hareket ettirlse emit edilmektedir. Benzer biçimde yürüteç bırakıldığında sliderMoved sinyali emit edilmektedir. Klavye odağı QSlider nesnesi üzerindeyken ok tuşlarıyla küçük ilerlemeler, PageUp, PageDown tuşlarıyla büyük ilerlemeler yapılabilmektedir. Bunun için sınıfın setSingleStep ve setPageStep metotları kullanılmaktadır. Aşağıda slider kullanımına bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 300) gridLayout = QGridLayout() self.labelRed = QLabel('Red', self) self.labelGreen = QLabel('Green', self) self.labelBlue = QLabel('Blue', self) self.sliderRed = QSlider(Qt.Horizontal, self) self.sliderRed.setMaximum(255) self.sliderRed.setTickPosition(QSlider.TicksAbove) self.sliderRed.setTickInterval(5) self.sliderRed.setSingleStep(2) self.sliderRed.setPageStep(20) self.sliderRed.valueChanged.connect(self.sliderRedValueChangedHandler) self.sliderGreen = QSlider(Qt.Horizontal, self) self.sliderGreen.setMaximum(255) self.sliderGreen.setTickPosition(QSlider.TicksAbove) self.sliderGreen.setTickInterval(5) self.sliderGreen.setSingleStep(2) self.sliderGreen.setPageStep(20) self.sliderGreen.valueChanged.connect(self.sliderGreenValueChangedHandler) self.sliderBlue = QSlider(Qt.Horizontal, self) self.sliderBlue.setMaximum(255) self.sliderBlue.setTickPosition(QSlider.TicksAbove) self.sliderBlue.setTickInterval(5) self.sliderBlue.setSingleStep(2) self.sliderBlue.setPageStep(20) self.sliderBlue.valueChanged.connect(self.sliderBlueValueChangedHandler) gridLayout.addWidget(self.labelRed, 0, 0) gridLayout.addWidget(self.sliderRed, 0, 1) gridLayout.addWidget(self.labelGreen, 1, 0) gridLayout.addWidget(self.sliderGreen, 1, 1) gridLayout.addWidget(self.labelBlue, 2, 0) gridLayout.addWidget(self.sliderBlue, 2, 1) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.clicked.connect(self.pushButtonClickedHandler) self.label = QLabel(self) self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) ss = """ QLabel {background-color: rgb(0, 0, 0)} """ self.label.setStyleSheet(ss) vBoxLayout = QVBoxLayout() vBoxLayout.addWidget(self.label) vBoxLayout.addLayout(gridLayout) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) def pushButtonClickedHandler(self): red = self.sliderRed.value() green = self.sliderGreen.value() blue = self.sliderBlue.value() ss = f""" QLabel {{background-color: rgb({red}, {green}, {blue})}} """ self.label.setStyleSheet(ss) print(self.sliderRed.value(), self.sliderGreen.value(), self.sliderBlue.value()) def sliderRedValueChangedHandler(self, value): self.changeColor(value, self.sliderGreen.value(), self.sliderBlue.value()) def sliderGreenValueChangedHandler(self, value): self.changeColor(self.sliderRed.value(), value, self.sliderBlue.value()) def sliderBlueValueChangedHandler(self, value): self.changeColor(self.sliderRed.value(), self.sliderGreen.value(), value) def changeColor(self, red, green, blue): ss = f""" QLabel {{background-color: rgb({red}, {green}, {blue})}} """ self.label.setStyleSheet(ss) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 37. Ders 26/04/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Ok tuşlarına basıldıkça bir değerin artıp azaldığı GUI elemanlara "spinner" ya da "spinbox" denilmektedir. Qt'de spinbox GUI elemanı QSpinBox sınıfıyla temsil edilmiştir. QSpinBox kullanımı şöyledir: 1) Önce QSpinBox sınıfı türünden bir nesne yaratılır ve konumlandırma yapılır. 2) Spinbox'ın maksimum ve minimum değerleri setMaximum ve setMinimum metotlarıyla ayarlanabilmektedir. Bu değerler default durumda 99 ve 0 biçiminddir. 3) GUI eleman default olarak klavyeden de girişi kabul etmektedir. Ancak sınıfın setReadOnly metodu True argümanıyla çağrılırsa GUI eleman read-only olur. GUI elemanın içerisindeki yazı setAlignment metodu ile ayarlanabilmektedir. spinbox üzerindeki sayının sonuna ve başına setSuffix ve setPrefix metotları ile yazılar iliştirilebilmektedir. 4) Spinbox içerisindeki değer value metodu ile alınıp setValue metodu ile set edilebilmektedir. 5) Klavye odağı spinbox üzerindeyken ok tuşlarıyla küçük ilerleme, PageUp, PageDown tuşlarıyla büyük ilerleme yapılabilir. Küçük ilerlemeler setSingleStep metoduyla set edilebilmektedir. Büyük ilerlemeler default olarak küçük ilerlemelerin 10 katıdır. Sınıfın setWrapping metodu True ile çağrılırsa en yüksek ve en düşük değerden sarmalama yapılmaktadır. Aşağıda SpinBox kullanımına bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(320, 200) self.setWindowTitle('SpinBox') ss = "QWidget {font-size: 16px}" self.setStyleSheet(ss) gridLayout = QGridLayout() self.spinBox = QSpinBox(self) self.spinBox.setMaximum(200) self.spinBox.setAlignment(Qt.AlignCenter) self.spinBox.setSuffix(' TL') self.spinBox.setValue(5) self.spinBox.setSingleStep(2) self.spinBox.setWrapping(True) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.clicked.connect(self.pushButtonClickedHandler) gridLayout.addWidget(self.spinBox, 0, 0) gridLayout.addWidget(self.pushButtonOk, 0, 1) self.setLayout(gridLayout) def pushButtonClickedHandler(self): print(self.spinBox.value()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Sekmelerden oluşan sekmelere tıklandığında o sekmeye ilişkin pencerenin görüntülendiği GUI elemanlara Tab GUI elemanları ya da "property sheet" denilemktedir. PyQt'de Tab GUI elemanı QTabWidget sınıfıyla temsil edilmiştir. QTabWidget Sınıfı şöyle kullanılmaktadır. 1) Önce QTabWidget türünden bir nesne yaratılır ve konumlandırılır. 2) QTabWidget sınıfının addTab isimli metotları ile sekmeler QTabWidget içerisine eklenir. addTab metodu bizden bir pencere nesnesi ve bir de sekme yazısını istemektedir. Programcı sekmelerdeki pencere nesnelerini kendisi konumlandırmaz. Bu pencere nesneleri QTabWidget tarafından sekme aktif hale getirildiğinde otomatik olarak pencereyi kaplamaktadır. 3) QTabWidget penceresine sekmeler eklendikten sonra oradaki pencereler üzerine GUI elemanlar yerleştirilebilir. (Tabii önce pencerelere GUI elemanlar yerleştirilip sonra da sekme eklenebilir. 5) İki sekme arasına insertTab metodu ile sekme insert edilebilir. Belli bir sekme index numarası vereilerek removeTab metoduyla silinebilir. O anda aktif olan sekmenin indeks numarası currentIndex metodu ile alınabilir, aktif sekme programlama yolu ile setCurrentIndex metoduyla değiştirilebilir. currentWidget metodu o andaki aktif sekmedeki pencere nesnesini bize vermektedir. Sekmelerin görünümü setTabShape metodu ile üçgensel hale getirilebilir. GUI elemanın diğer metotlarına Qt dokümanlarından erişebilirsiniz. 6) GUI elemanın en önemli sinyali bir sekme aktif hale getirildiğinde tetiklenen currrentChanged isimli sinyaldir. Bu sinyal parametre yoluyla bize aktif hale gelmiş olan sekmenin indek numarasını vermektedir. Aşağıda örnek bir QTabControl kullanımı verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(320, 200) self.setWindowTitle('QTabWidget Sample') ss = "QWidget {font-size: 14px}" self.setStyleSheet(ss) gridLayout = QGridLayout() self.tabWidget = QTabWidget(self) self.tabWidget.setTabShape(QTabWidget.Triangular) self.tabWidget.currentChanged.connect(self.tabWidgetCurrentChangedHandler) self.tabWidget1 = QWidget() self.lineEditName = QLineEdit() self.lineEditBirtPlace = QLineEdit() formLayout1 = QFormLayout() formLayout1.addRow('Adı Soyadı', self.lineEditName) formLayout1.addRow('Doğum Yeri', self.lineEditBirtPlace) self.tabWidget1.setLayout(formLayout1) self.tabWidget2 = QWidget() self.lineEditEMail = QLineEdit() self.lineEditTelNo = QLineEdit() formLayout2 = QFormLayout() formLayout2.addRow('E-Posta', self.lineEditEMail) formLayout2.addRow('Telefon Numarası', self.lineEditTelNo) self.tabWidget2.setLayout(formLayout2) self.tabWidget.addTab(self.tabWidget1, 'Kişisel Bilgiler') self.tabWidget.addTab(self.tabWidget2, 'İletişim Bilgileri') self.pushButtonOk = QPushButton('Tamam', self) self.pushButtonOk.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.pushButtonOk.clicked.connect(self.pushButtonOkClickedHandler) self.pushButtonQuit = QPushButton('Çık', self) self.pushButtonQuit.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.pushButtonQuit.clicked.connect(self.pushButtonQuitClickedHandler) gridLayout.addWidget(self.tabWidget, 0, 0, 3, 3) gridLayout.addWidget(self.pushButtonOk, 3, 1, 1, 1) gridLayout.addWidget(self.pushButtonQuit, 3, 2, 1, 1) self.setLayout(gridLayout) def pushButtonOkClickedHandler(self): name = self.lineEditName.text() bplace = self.lineEditBirtPlace.text() email = self.lineEditEMail.text() telno = self.lineEditTelNo.text() QMessageBox.information(self, 'Information', name + '\n' + bplace + '\n' + email + '\n' + telno) def pushButtonQuitClickedHandler(self): self.close() def tabWidgetCurrentChangedHandler(self, index): print(f'{index} numaralı sekme aktif hale geldi') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 38. Ders 03/05/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte çok sekmeli bir text editör örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import os class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('Tabbed Editor') self.count = 1 self.docs = {} self.tabWidget = QTabWidget() self.tabWidget.setTabShape(QTabWidget.Triangular) self.setCentralWidget(self.tabWidget) ss = """ QWidget {font-size: 14px} QTextEdit {font-family: Consolas; font-size: 18px} """ self.setStyleSheet(ss) menuBar = self.menuBar() filePopup = QMenu('&File', self) self.newAction = QAction(QIcon('Icons/new-48x48.png'), '&New') self.newAction.setShortcut('Ctrl+N') self.newAction.triggered.connect(self.newActionTriggeredHandler) filePopup.addAction(self.newAction) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open...') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionTriggeredHandler) filePopup.addAction(self.openAction) self.saveAction = QAction(QIcon('Icons/save-48x48.png'), '&Save') self.saveAction.setShortcut('Ctrl+S') self.saveAction.triggered.connect(self.saveActionTriggeredHandler) self.saveAction.setEnabled(False) filePopup.addAction(self.saveAction) self.saveAsAction = QAction(QIcon('Icons/saveas-48x48.png'), '&Save as...') self.saveAsAction.triggered.connect(self.saveAsActionTriggeredHandler) self.saveAsAction.setEnabled(False) filePopup.addAction(self.saveAsAction) filePopup.addSeparator() self.closeAction = QAction(QIcon('Icons/close-32x32.png'), '&Close') self.closeAction.triggered.connect(self.closeActionTriggeredHandler) self.closeAction.setEnabled(False) filePopup.addAction(self.closeAction) menuBar.addMenu(filePopup) def newActionTriggeredHandler(self): textEdit = QTextEdit() self.tabWidget.addTab(textEdit, f'Untitled{self.count}') self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1) self.docs[self.tabWidget.currentIndex()] = (None, ) self.tabWidget.setCurrentIndex(self.count - 1) self.count += 1 if self.tabWidget.count() == 1: self.saveAction.setEnabled(True) self.saveAsAction.setEnabled(True) self.closeAction.setEnabled(True) def openActionTriggeredHandler(self): fd = QFileDialog(self, 'Choose file', '.', 'Text files (*.txt);; Python Files (*.py);;All Files (*.*)') if fd.exec() == QDialog.Accepted: selectedFile = fd.selectedFiles()[0] try: with open(selectedFile) as f: text = f.read() textEdit = QTextEdit() self.tabWidget.addTab(textEdit, os.path.basename(selectedFile)) self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1) textEdit.setPlainText(text) self.docs[self.tabWidget.currentIndex()] = (selectedFile, ) self.count += 1 if self.tabWidget.count() == 1: self.saveAction.setEnabled(True) self.saveAsAction.setEnabled(True) self.closeAction.setEnabled(True) except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) def saveActionTriggeredHandler(self): self.saveTab() def saveAsActionTriggeredHandler(self): currentIndex = self.tabWidget.currentIndex() textEdit = self.tabWidget.currentWidget() savePath, _ = QFileDialog.getSaveFileName(self, 'Save As', '.', 'Text files (*.txt);; Python Files (*.py);;All Files (*.*)') if savePath == '': return try: with open(savePath, 'w') as f: f.write(textEdit.toPlainText()) textEdit.document().setModified(False) self.tabWidget.setTabText(currentIndex, os.path.basename(savePath)) self.docs[currentIndex] = (savePath, ) except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) def closeActionTriggeredHandler(self): textEdit = self.tabWidget.currentWidget() if textEdit.document().isModified(): result = QMessageBox.warning(self, 'Warning', 'Save changes?', QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if result == QMessageBox.Cancel: return if result == QMessageBox.Yes: self.saveTab() self.tabWidget.removeTab(self.tabWidget.currentIndex()) if self.tabWidget.count() == 0: self.saveAction.setEnabled(False) self.saveAsAction.setEnabled(False) self.closeAction.setEnabled(False) def saveTab(self): currentIndex = self.tabWidget.currentIndex() path = self.docs[currentIndex][0] textEdit = self.tabWidget.currentWidget() if path == None: savePath, _ = QFileDialog.getSaveFileName(self, 'Save As', '.', 'Text files (*.txt);; Python Files (*.py);;All Files (*.*)') if savePath == '': return else: savePath = path try: with open(savePath, 'w') as f: f.write(textEdit.toPlainText()) textEdit.document().setModified(False) if path == None: self.tabWidget.setTabText(currentIndex, os.path.basename(savePath)) self.docs[currentIndex] = (savePath, ) except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Çok sütunlu ve satırlı tablo oluşturmak QTableWidget isimli sınıf kullanılmaktadır. Nesne yaratılırken başlangıçtaki sütun ve satır sayıları QTableWidget sınıfının __init__ metodunda verilebilir. Ya da bu değerler setColumnCount ve setRowCount metotlarıyla belirlenebilir. Aslında GUI elemanında her hücre diğerinden bağımsızdır ve her hücre QTableWidgetItem türündne bir nesneyle temsil edilmektedir. Sütun hücreleri de aynı biçimdedir. Biz sütun isimlerini tek hamlede setHorizontalHeaderLabels metodu ile set edebiliriz. Özetle QTableWidgert kullanırkan önce nesne yaratılır. Sonra sütun sayısı belirlenir. GUI elemanın sütun sayısını setColumnCount metodu ile belirleyebiliriz. Ya da nesne yaratılırken __init__ metodunda belirleyebiliriz. GUI elemanın sütun sayısı belirlendikten sonra artık sütunların yazıları set edilir. Bunun için setHorizontalHeaderLabels metodu kullanılmaktadır. Örneğin üç sütuna sahip bir QTableWidget nesnesi şöyle oluşturulmaktadır: self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(['Adı Soyadı', 'No', 'Cinsiyet']) #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('QTableWidget') vBoxLayout = QVBoxLayout() self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(['Adı Soyadı', 'No', 'Cinsiyet']) vBoxLayout.addWidget(self.tableWidget) self.pushButtonOk = QPushButton('Ok', self) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aslında sütun başlıkları QTableWidgetItem nesneleri olarak oluşturulup QTableWidget sınıfının setHorizontalHeaderItem metoduyla da set edilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('QTableWidget') vBoxLayout = QVBoxLayout() self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) columnName = QTableWidgetItem('Adı Soyadı') columnNo = QTableWidgetItem('No') columnGender = QTableWidgetItem('Cinsiyet') self.tableWidget.setHorizontalHeaderItem(0, columnName) self.tableWidget.setHorizontalHeaderItem(1, columnNo) self.tableWidget.setHorizontalHeaderItem(2, columnGender) vBoxLayout.addWidget(self.tableWidget) self.pushButtonOk = QPushButton('Ok', self) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QTableWidget GUI elemanın bir hücresini set etmek için tipik olarak programcı önce bir QTableWidgetItem nesnesi yaratır. Sonra bu nesnenin çeşitli özelliklerini setXXX metotlarıyla set eder. Daha sonra bu elemanı QTableWidget sınıfının setItem metoduyla hücreye iliştirir. İstediği zaman item metoduyla o hücreye iliştirilmiş olan QTableWidget nesnesini de programcı elde edebilmektedir. Başlangıçta hiçbir hücrede QTableWidget nesnesi yoktur. Ancak klavyeye ile hücereye giriş yapılırsa bu nesneler oluşturulmaktadır. O anda seçili olan hücrenin QTableWidget nesnesi currentItem metodu ile elde edilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('QTableWidget') vBoxLayout = QVBoxLayout() self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(['Adı Soyadı', 'No', 'Cinsiyet']) self.tableWidget.setRowCount(1) twi = QTableWidgetItem('Ali Serçe') self.tableWidget.setItem(0, 0, twi) twi = QTableWidgetItem('123') self.tableWidget.setItem(0, 1, twi) twi = QTableWidgetItem('Erkek') self.tableWidget.setItem(0, 2, twi) vBoxLayout.addWidget(self.tableWidget) self.pushButtonOk = QPushButton('Ok', self) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QTableWidget hücrelerini set etmek biraz zahmetlidir. Ancak programcı genellikle zaten elinde olan birtakım bilgilerden hareketle bir döngü içerisinde bu işlemi yapar. Programcı setRowCount ile GUI elemanındaki satırları artırabileceği gibi insertRow metoduyla da bu işi yapabilir. insertRow aynı azamanda append işlemini de yapabilmektedir. Aşağıdaki örnekte bir demetin her elemanı bir döngü içerisinde QTableWidgetItem nesnesine eklenmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import os class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('QTableWidget') vBoxLayout = QVBoxLayout() self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(['Adı Soyadı', 'No', 'Cinsiyet']) items = [('Ali Serçe', 123, 'Erkek'), ('Kaan Aslan', 456, 'Erkek'), ('Jale Kanlıdere', 965, 'Kadın'), ('Gül Şanlı', 843, 'Kadın')] for item in items: row = self.tableWidget.rowCount() self.tableWidget.setRowCount(row + 1) twi = QTableWidgetItem(item[0]) self.tableWidget.setItem(row, 0, twi) twi = QTableWidgetItem(str(item[1])) self.tableWidget.setItem(row, 1, twi) twi = QTableWidgetItem(item[2]) self.tableWidget.setItem(row, 2, twi) self.tableWidget.setRowCount(self.tableWidget.rowCount() + 1) vBoxLayout.addWidget(self.tableWidget) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.clicked.connect(self.pushButtonClickedHandler) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) def pushButtonClickedHandler(self): twi = self.tableWidget.currentItem() print(twi.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir CSV dosyasından hareketle QTableWidget nesnesini de benzer biçimde doldurabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import csv class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('QTableWidget') vBoxLayout = QVBoxLayout() self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(['Adı Soyadı', 'No', 'Cinsiyet']) with open('test.csv', encoding='utf-8') as f: reader = csv.reader(f) for name, no, gender in reader: row = self.tableWidget.rowCount() self.tableWidget.setRowCount(row + 1) twi = QTableWidgetItem(name) self.tableWidget.setItem(row, 0, twi) twi = QTableWidgetItem(no) self.tableWidget.setItem(row, 1, twi) twi = QTableWidgetItem(gender) self.tableWidget.setItem(row, 2, twi) self.tableWidget.setRowCount(self.tableWidget.rowCount() + 1) vBoxLayout.addWidget(self.tableWidget) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.clicked.connect(self.pushButtonClickedHandler) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) def pushButtonClickedHandler(self): twi = self.tableWidget.currentItem() print(twi.text()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QTableWidget nesnesinin en önemli sinyalleri currentCellChanged ve currentItemChanged sinyalleridir. Bu sinyaller yeni bir hücreye tıklandığında emit edilmektedir. Aşağıdkai örnekte bir hücreye tıklandığında onun fontı büyütülmüştür. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import csv class MainWindow(QWidget): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('QTableWidget') vBoxLayout = QVBoxLayout() self.tableWidget = QTableWidget() self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(['Adı Soyadı', 'No', 'Cinsiyet']) self.tableWidget.currentCellChanged.connect(self.tableWidgetCurrentCellChangedHandler) self.tableWidget.currentItemChanged.connect(self.tableWidgetCurrentItemChangedHandler) font = self.tableWidget.font() font.setPointSize(10) self.tableWidget.setFont(font) with open('test.csv', encoding='utf-8') as f: reader = csv.reader(f) for name, no, gender in reader: row = self.tableWidget.rowCount() self.tableWidget.setRowCount(row + 1) twi = QTableWidgetItem(name) self.tableWidget.setItem(row, 0, twi) twi = QTableWidgetItem(no) self.tableWidget.setItem(row, 1, twi) twi = QTableWidgetItem(gender) self.tableWidget.setItem(row, 2, twi) self.tableWidget.setRowCount(self.tableWidget.rowCount() + 1) vBoxLayout.addWidget(self.tableWidget) self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.clicked.connect(self.pushButtonClickedHandler) vBoxLayout.addWidget(self.pushButtonOk) self.setLayout(vBoxLayout) def pushButtonClickedHandler(self): twi = self.tableWidget.currentItem() print(twi.text()) def tableWidgetCurrentCellChangedHandler(self, crow, ccol, orow, ocol): print(f'Selection changed, active cell is {crow}, {ccol}') twi = self.tableWidget.item(crow, ccol) print(twi.text()) def tableWidgetCurrentItemChangedHandler(self, newItem, oldItem): font = oldItem.font() font.setPointSize(10) oldItem.setFont(font) font = newItem.font() font.setPointSize(16) newItem.setFont(font) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow .show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 39. Ders 08/05/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ GUI ortamlarında "resource" denilen bir kavram sıkça kullanılmaktadır. Resource birtakım küçük görsel öğelerin, seslerin vs. bir dosya içerisine yerleştirilmesi ve mümkünse executable dosyanın içerisine gömülmesi anlamına gelmektedir. Gerçekten de aslında Qt'yi C++'tan kullanırken programcılar genel olarak menülerdeki ikonları, resimleri vs. resource olarak ele alıp onu executable dosyanın içerisine gömerler. Ancak Python yorumlayıcı sistemle çalıştığı için bu tür öğelerin executable dosya içerisine gömülmesi mümkün olmamaktadır. Ancak yine de PyQt'de designer kullanılarak resource oluşturulabilmektedir. Programcı designer'daki "resource browser"ı kullanarak uzantası .qrc olan bir xml dosyasını yaratır. Sonra yine designer'dan bir prefix belirterek (bu prefix'in önemi şimdilik yoktur. / biçiminde belirlenebilir) görsel öğeleri bu resource dosyasının içerisine yerleştirir. Bu resource dosyasından yine bir python dosyasının elde edilmesi gerekmektedir. Bu işlem pyrcc5 programıyla aşağıdaki gibi yapılmaktadır: pyrcc5 -o myresources_rc.py myresources.qrc Buradaki py dosyası designer'da yaratılan isimdeki .qrc dosyasının sonuna _rc getirilierek oluşturulabilir. Çünkü pyuic5 programı bu resource dosyasını kendi içerisinde bu isimle import etmektedir. Böylece artık programcı bütün küçük resimcikleri vs. bu py dosyasının içerisine gömmüş olur. Tabii programı konuşlandırırken bu .py dosyasının da ilgili yere götürülmesi gerekmektedir. Python programları bir yorumlyaıcı sistemle çalıştırıldığı için resource kavramı C++'taki gibi önemli ve etkin değildir. Genellikle pyqt programcıları bu tarzda resource dosyalarını kullanmamaktadır. Sonuç olarak resource kullanımı için yapılacak işlemler şunlardır: 1) Küçük resimcikler vs. QT Designer'daki "Resource Browser"dan .rc dosyasına görsel olarak eklenir. 2) Daha sonra Qt Designer'da ne zaman bir resim, ikon vs. kullanılacak olsa "Choose File" yerine "Choose Resource" seçilmelidir. 3) Programcı artık .ui dosyasını da .qrc dosyasını da Python programı haline getirmelidir. .qrc dosyasını python programı haline getirirken dosya ismine "_rc" eklemelidir. Örneğin .ui dosyasının ismi "samplegui.ui", .qrc dosyasının ismi ise "myresource.qrc" olsun. Dönüştürmeler şöyle yapılmalıdır: pyuic5 -o samplegui.py samplegui.ui pyrcc5 -o myresource_rc myresource.qrc 4) Program taşınırken üç dosya taşınmalıdır: Asıl .py dosyası (sample.py), ui dosyasından dönüştürülen python dosyası (samplegui.py) ve resource dosyasından dönüştürülen python dosyası (myresource_rc.py) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ PyQt'de çizim yapmak için paintEvent isimli bir metodu kullanmak gerekir. Windows işletim sistemi ve bazı pencere alt sistemleri sistemler bir pencere başka bir pencerenin üzerine getirilip çekildiğinde arka plandaki pencerenin görüntüsünü otomatik olarak saklayıp programcı için yeniden basmamaktadır. Bu tür durumlarda örneğin Windows bir pencerenin görünmeyen bir kısmı görünür hale geldiğinde çizimi kendisi saklayıp geri basmak yerine onun yeniden çizilmesi sorumluluğunu programcıya bırakmaktadır. Pencerenin görünmeyen bir kısmı görünür hale geldiğinde Windows bu pencereye WM_PAINT denilen bir mesaj gönderir. Bu mesajı alan programcı çizimi yeniden yaparak bozulan görüntüyü oluşturmaktadır. Bu durumda Windows gibi sistemlerde çizimlerin bu mesajda yapılması uygun olmaktadır. Aksi takdirde pencere içerisindeki görüntü bozulmaktadır. Gerçi her sistemdeki pencere yöneticisi alt sistemi böyle davranmamaktadır. Hatta çrneğin Windows 8 versiyonundan itibaren artık bazı durumlarda Windows bile pencere içeriğini kendisi saklayıp WM_PAINT mesajını daha az göndermektedir. Bazı sistemler tamamen bütün çizimi kendisi saklamakta böyle bir mesaj mekanizması bulundurmamaktadır. Ancak Qt framework'ü cross platform olduğu için bu mesaj sistemi uygulanacakmış gibi programcının kodunu organize etmesi gerekmektedir. Qt'de WM_PAINT gibi bir mesaj Qt tarafından alındığında Qt buna karşılık ilgili sınıfın paintEvent isimli metodunu çağırmaktadır. Bu metoda QPaintEvent isimli bir nesneyi de parametre olarak geçirmektedir. İşte Qt'de çizimlerin bu paintEvent metodu içerisinde yapılması gerekir. Eğer çizimler paintEvent metodunun içerisinde yapılmazsa yapılan çizimler pencerenin bir kısmı "invalidate" olduğunda kaybolmaktadır. Pekiyi biz kendi çizimlerimizi paintEvent metodunda yapacağız. Ya penceremizin içerisinde QPushButton gibi, QListWidget gibi alt pencereler varsa onların görüntüsü bozulmayacak mı? İşte işletim sistemleri ya da pencere yönetici alt sistemler ister üst pencere olsun isterse alt pencere olsun görüntüsü bozulan her pencereye bunu bildiren mesajı göndermektedir. Yani her pencere kendi görüntüsünü yeniden oluşturmaktan sorumludur. Örneğin QPushButton sınıfını yazanlar kendi sınıflarında paintEvent metoduu yazarak zaten bozulan düğme görüntüsünü kendileri yeniden oluşturmaktadır. Bizim onların o widget'lar için kaygılanmamaıza gerek kalmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi painEvent metodu içerisinde biz çizimleri nasıl yapacağız? İşte QPainter isimli bir sınıf vardır. Programcı çizimleri hangi pencereye yapacaksa o pencere nesnesini vererek QPainter nesnesi elde eder ve çizileri bu QPainter sınıfının metotlarıyla yapar. Örneğin: def paintEvent(self, pe): painter = QPainter(self) ... QPainter sınıfının drawXXX, fillXXX gibi metotları vardır. Çizimler bu metotlarla yapılmaktadır. Örneğin drawLine isimli metot iki nokta arasında doğru çizer. drawEllipse isimli metot bir elips çizmektedir. Örneğin: def paintEvent(self, pe): painter = QPainter(self) painter.drawLine(0, 0, 100, 100) painter.drawEllipse(200, 200, 100, 100) Benzer biçimde drawRectangle bir dikdörtgen çizmekte, drawText ise bir yazı yazmaktadır. Örneğin QLabel GUI elemanının aslında yaptığı şey kendi içerisinde verilen yazıyı paintEvent metodunda dfrawText ile yazdırmaktır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) def paintEvent(self, pe): painter = QPainter(self) painter.drawLine(0, 0, 100, 100) painter.drawLine(100, 100, 300, 100) painter.drawEllipse(300,200, 100, 100) painter.drawRect(200, 200, 100, 100) painter.drawText(10, 30, 'this is a test') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Qt programcısının QPainter sınıfının drawXX ve fillXXX metotlarının neler olduğunu ve bu metotların parametrelerinin nasıl kullanıldığını bilmesi gerekir. Kullanılan drawXX metotlarından önemli olanları şunlardır: drawLine(pt1, pt2) drawLine(x1, x2, y1, y2) drawRectangle(qrect) drawRectangle(x1, y1, width, heightg) drawPolyline(pointList) drawPolygon(pointList) drawEllipse(qrect) # dikdörtgenin iç teğet elipsi darwEllipse(x1i y1, width,i height) # dikdörtgenim iç teğet elipsi drawArch(qrect, startAngle, spanAngle) # derecenin 16'da biri drawArch(x1, y1, width, height, startAngle, spanAngle) # derecenin 16'da biri drawPie(qrect, startAngle, spanAngle) # derecenin 16'da biri drawPie(x1, y1, width, height, startAngle, spanAngle) # derecenin 16'da biri drawChord(qrect, startAngle, spanAngle) # derecenin 16'da biri drawChord(x1, y1, width, height, startAngle, spanAngle) # derecenin 16'da biri drawPoint(qpoint) drawPoint(x, y) drawText(x, y, text) drawText(qpoint, text) drawText(rect, flag, text) # dikdörtgensel bir bölgenin çeşitli yerlerine yazar #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 40. Ders 10/05/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ QPainter sınıfının drawXXX metotları çizimi kalem nesnesiyle yapmaktadır. Default kalem 1 kalınlıklı, siyah, düz (solid) bir kalemdir. Kalemler QPen sınıfıyla temsil edilmiştir. Yeni bir kalem kullanmak için QPen sınıfı türünden bir nesne yaratılır. Sonra kalemin özellikleri QPen sınıfının __init__ metodunda ya da daha sonra setXXX metotlarıyla set edilir. Daha sonra da QPainter sınıfının setPen metoduyla bu kalem aktif hale getirilir. Bazı çizimlerin değişik kalemlerle yapılması isteniyorsa kalemler oluşturulup setPen ile değiştirilmelidir. Kalemin yazış biçimi setStyle metodu ile belirlenir. Yazış biçimleri şunlardır: Qt.NoPen Qt.SolidLine Qt.DashLine Qt.DotLine Qt.DashDotLine Qt.DashDotDotLine Qt.CustomDashLine #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drwaing Example') self.resize(640, 420) self.lines = [] def paintEvent(self, pe): painter = QPainter(self) pen = QPen() pen.setColor(Qt.red) pen.setWidth(4) pen.setStyle(Qt.DotLine) painter.setPen(pen) painter.drawLine(0, 0, 100, 100) painter.drawLine(100, 100, 300, 100) pen.setColor(Qt.blue) painter.setPen(pen) painter.drawEllipse(300,200, 100, 100) painter.drawRect(200, 200, 100, 100) painter.drawText(10, 30, 'this is a test') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Kapalı şekillerin için fırçayla boyanmaktadır. Fırça kavramı QBrush sınıfı ile temsil edilmiştir. Bir fırçanın pek çok özelliği vardır. Bu özelliklerin bazıları QBrush nesnesi yaratılırken __init__ metodunun parametrelerinde set edilebilir. Genel olarak bütün özellikleri set eden setXXX metotları vardır. Fırçalar oluşturulduktan sonra QPainter nesnesinin ilgili fırçayı kullanabilmesi için setBrush metodu ile set edilmesi gerekir. Artık kapalı şekillerin içi otomatik olarak bu set edilen fırça ile boyanacaktır. Default fırça bir boyama yapmayan fırçadır. Fırçaların boyama biçimleri QBrush sınıfının setStyle metodu ile set edilebilir. Aşağıda fırça kullanmaya bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) self.lines = [] def paintEvent(self, pe): painter = QPainter(self) pen = QPen() pen.setColor(Qt.red) pen.setWidth(4) pen.setStyle(Qt.DotLine) painter.setPen(pen) gradient = QLinearGradient(0, 0, self.width(), self.height()) gradient.setColorAt(0.0, Qt.blue) gradient.setColorAt(0.5, Qt.green) gradient.setColorAt(1.0, Qt.red) gbrush = QBrush(gradient) painter.setBrush(gbrush) painter.drawEllipse(300,200, 100, 100) sbrush = QBrush(Qt.yellow) sbrush.setStyle(Qt.FDiagPattern) painter.setBrush(sbrush) pen.setColor(Qt.blue) painter.setPen(pen) painter.drawRect(200, 200, 100, 100) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QBrush sınıfının setTexture metodu ile bir resmi fırçanın boyama resmi olarak set edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) self.lines = [] def paintEvent(self, pe): painter = QPainter(self) brush = QBrush() pixmap = QPixmap('Icons/Open.png') brush.setTexture(pixmap) painter.setBrush(brush) painter.drawRect(0, 0, self.width(), self.height()) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ QPainter sınıfının fillXX biçimindeki metotları kapalı bir çizimin sınırları olmadan yalnızca boyamasını yapmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) self.lines = [] def paintEvent(self, pe): painter = QPainter(self) painter.fillRect(100, 100, 100, 100, QBrush(Qt.blue)) painter.fillRect(210, 100, 100, 100, QBrush(Qt.red)) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir resmi QLabel kullanmadan doğrudan da QPainter sınıfının drawPixmap metoduyal da çizdirebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.pixmap = QPixmap('AbbeyRoad.jpg') self.resize(self.pixmap.size()) def paintEvent(self, pe): painter = QPainter(self) painter.drawPixmap(0, 0, self.pixmap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir resmi çizdirmek için QPainter sınıfının drawPixmap metodu kullanılmaktadır. drawPixmap çeşitli biçimlerde kullanılabilmektedir: drawPixmap(x, y, pixmap) # Resmin tamamını sol üst köşesi x, y koordinatında olacak biçimde çizer drawPixmap(qpoint, pixmap) # Resmin tamamını sol üst köşesi x, y koordinatında olacak biçimde çizer drawPixmap(x, y, width, height, pixmap) # Resmin tamamını dikdörtgen içerisine scale ederek çizer drawPixmap(qrect, width, height, pixmap) # Resmin tamamını dikdörtgen içerisine scale ederek çizer drawPixmap(x1, y1, width1, height1, pixmap, x2, y2, width2, height2) # Resmin belli bir dikdörtrgensel bölgesini belli bir dikdörtgensel # bölgeye scale ederek çizdirir. Birinci dikdöertgen hedef ikincisi kaynak belirtir. drawPixmap(rect1, pixmap, rect2) # Resmin belli bir dikdörtrgensel bölgesini belli bir dikdörtgensel # bölgeye scale ederek çizdirir. Birinci dikdöertgen hedef ikincisi kaynak belirtir. Diğer özellikler için Qt dokümanlarına başvurabilirsiniz. Biz daha önce resimleri QLabel kullanarak çizdirdik. Aslında QLabel kendi içerisinde paintEvent metodunda drawPixmap ile çizimi yapmaktadır. Ressimlerin doğrudan drawPixmap ile çizilmesi daha prtaik bir yöntemdir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) self.pixmap = QPixmap('AbbeyRoad.jpg') def paintEvent(self, pe): painter = QPainter(self) painter.drawPixmap(10, 10, 100, 100, self.pixmap) painter.drawPixmap(150, 10, 100, 100, self.pixmap, 0, 0, 300, 300) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Resmin genişilik yükseklik oranını korumak için QPixmap sınıfının scaled metodundan faydalanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) self.pixmap = QPixmap('AbbeyRoad.jpg').scaled(100, 100, Qt.KeepAspectRatio) def paintEvent(self, pe): painter = QPainter(self) painter.drawPixmap(10, 10, self.pixmap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Aslında en boy oranının korunması manuel de yapılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) self.pixmap = QPixmap('AbbeyRoad.jpg') def paintEvent(self, pe): painter = QPainter(self) ratio = self.pixmap.width() / self.pixmap.height() painter.drawPixmap(10, 10, int(100 * ratio), 100, self.pixmap) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Ekranlar belli bir çözünürlüktedir. Bu nedenle geometrik şekiller kırıklı gözükürler. İşte kırıklı görüntünün sınır pixellerinin belli renklerde boyanması ile insan gözü bu kırıkları daha az hissetmektedir. Bu algoritmik yönteme "antialising" denilmektedir. Qt'de çizim yaparken antialiasing özelliğini açmak için şu QPainter sınıfının setRenderHints metodu aşağıdaki gibi çağrılmalıdır: def paintEvent(self, pe): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing|QPainter.SmoothPixmapTransform) Aşağıdkai örnekte iki çemberi gözle karşılaştırınız. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) def paintEvent(self, pe): painter = QPainter(self) pen = QPen() pen.setWidth(3) painter.setPen(pen) painter.drawEllipse(100, 100, 200, 200) painter.setRenderHints(QPainter.Antialiasing|QPainter.SmoothPixmapTransform) painter.drawEllipse(320, 100, 200, 200) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir yazboz tahtası uygulaması Qt'de tipik olarak şöyle yapılır: Fare hareket ettirildiğinde çizim o noktada yapılmaz. Çizim bilgisi biriktirilir. Her fare hareketinde paintEvet oluşması sağlanır. Bunu sağlamak için QWidget sınıfının update metodu kullanılmalıdır. (paintEvent metodunun manuel çağrılması update region boş küme olduğundan bir fayda sağlamaz.) Tabii kullanıcı elini fareden çektiğinde o ayrı bir çizim birimi olduğu için oradaki noktaların başka bir listede saklanması gerekmektedir. Aşağıdaki programda self.line farenin basılıp çekiline kadarki noktaları tutmaktadır. Parmak fareden çekilince bu noktalar başka bir listede toplanmış ve paitntEvent sırasında toplanan tüm noktalar yeniden çizgilerle birleştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('Scratchpad Example') self.resize(640, 420) self.lines = [] self.line = [] self.pen = QPen() self.pen.setWidth(3) def mousePressEvent(self, me): self.prevPoint = me.pos() self.line.append(me.pos()) self.dragFlag = True def mouseMoveEvent(self, me): if self.dragFlag: self.line.append(me.pos()) self.update() def mouseReleaseEvent(self, me): if self.dragFlag: self.lines.append(self.line) self.line = [] def paintEvent(self, pe): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) painter.setPen(self.pen) for line in self.lines: painter.drawPolyline(line) painter.drawPolyline(self.line) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki yazboz tahtası uygulamasındaki çizimler nasıl save edilebilir? En pratik yöntem "pickle" modülü ile self.lines listesinin seri hale getirilmesidir. Aşağıdaki örnekte menünün yalnızca "open" ve "save as" elemanları işlenmiştir ve pek çok kontrol yapılmamıştır. Burada "save as" seçildiğinde çizim seri hale getirilerek dosyada saklanmış ve open ile geri yüklenmiştir. Bu uygulamayı geliştirerek paint benzeri bir program haline getirebilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * import pickle class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('Scratchpad Example') self.resize(640, 420) self.lines = [] self.line = [] menuBar = self.menuBar() filePopup = QMenu('&File', self) self.newAction = QAction(QIcon('Icons/new-48x48.png'), '&New') self.newAction.setShortcut('Ctrl+N') self.newAction.triggered.connect(self.newActionTriggeredHandler) filePopup.addAction(self.newAction) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open...') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionTriggeredHandler) filePopup.addAction(self.openAction) self.saveAction = QAction(QIcon('Icons/save-48x48.png'), '&Save') self.saveAction.setShortcut('Ctrl+S') self.saveAction.triggered.connect(self.saveActionTriggeredHandler) self.saveAction.setEnabled(False) filePopup.addAction(self.saveAction) self.saveAsAction = QAction(QIcon('Icons/saveas-48x48.png'), '&Save as...') self.saveAsAction.triggered.connect(self.saveAsActionTriggeredHandler) filePopup.addAction(self.saveAsAction) filePopup.addSeparator() self.closeAction = QAction(QIcon('Icons/close-32x32.png'), '&Close') self.closeAction.triggered.connect(self.closeActionTriggeredHandler) self.closeAction.setEnabled(False) filePopup.addAction(self.closeAction) menuBar.addMenu(filePopup) self.pen = QPen() self.pen.setWidth(3) def mousePressEvent(self, me): self.prevPoint = me.pos() #self.line.append(me.pos()) self.dragFlag = True def mouseMoveEvent(self, me): if self.dragFlag: nextPoint = me.pos() self.line.append(QLineF(self.prevPoint, nextPoint)) self.prevPoint = nextPoint self.update() def mouseReleaseEvent(self, me): if self.dragFlag: self.lines.append(self.line) self.line = [] def newActionTriggeredHandler(self): pass def openActionTriggeredHandler(self): path, _ = QFileDialog.getOpenFileName(self, 'Open', '.', 'CSD files (*.csd);;All files (*.*)') if path: try: with open(path, 'rb') as f: self.lines = pickle.load(f) self.update() except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) def saveActionTriggeredHandler(self): pass def saveAsActionTriggeredHandler(self): path, _ = QFileDialog.getSaveFileName(self, 'Save', '.', 'CSD files (*.csd);;All files (*.*)') if path: try: with open(path, 'wb') as f: pickle.dump(self.lines, f) except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) def closeActionTriggeredHandler(self): pass def paintEvent(self, pe): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) painter.setPen(self.pen) for line in self.lines: painter.drawLines(line) painter.drawLines(self.line) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Bir resmin fare ile taşınması tipik olarak şöyle yapılmaktadır: Resmin sol üst köşesi sınıfın bir örnek özniteliğinde tutulur. Resmin üzerine tıklanıp tıklanmadığı belirlenir. Sonra farenin kaydırılma miktarı (delta) kadar resmin pozisyonu ötelenir. Sonra yeni pozisyonda resim yeniden çizilir. update metodu paintEvent oluştururken zeminin de silinmesine yol açmaktadır. Aşağıdaki örnekte satrançtaki vezir taşı fare sürüklenip bırakılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('Image Dragging Example') self.resize(640, 420) self.figure = QPixmap('ChessFigures/WhiteQueen.png') self.dragFlag = False self.figurePos = QPoint(10, 10) def mousePressEvent(self, me): if QRect(self.figurePos, self.figure.size()).contains(me.pos()): self.dragFlag = True self.prevPoint = me.pos() def mouseReleaseEvent(self, me): if self.dragFlag: self.dragFlag = False def mouseMoveEvent(self, me): if self.dragFlag: delta = me.pos() - self.prevPoint self.figurePos += delta self.update() self.prevPoint = me.pos() def paintEvent(self, pe): painter = QPainter(self) painter.drawPixmap(self.figurePos, self.figure) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi biz ekran yerine bir dosyaya çizim yapabilir miyiz? Evet yapabiliriz. Bunun için şu adımları uygulamak gerekir: 1) Öncelikle çizim alanını bir QPixmap nesnesiyle oluşturmalıyız. Örneğin: pixmap = QPixmap(640, 480) pixmap ilk yaratıldığında içi sıfırlarla doludur. Dolayısıyla zemini siyah gibidir. Biz bu pixmap'in zeminini fill metoduyla boyayabiliriz: pixmap.fill(Qt.white) 2) Bu QPixmap nesnesini kullanarak bir QPainter nesnesi oluştururuz. Örneğin: painter = QPainter(pixmap) 4) Artık bu QPainter nesnesi ile çizim yaptığımızda aslında bu pixmap'e çizim yapmış oluruz. painter.drawEllipse(10, 10, 100, 100) 5) Artık bu pixmap nesnesini istediğimiz formatta save edebiliriz: pixmap.save('test.png', 'png') 6) Burada paintEvent dışında oluşturduğumuz QPainter nesnesinin yok edilmesi gerekmektedir. Bunun için QPainter sınıfının end isimli metodu kullanılmalıdır. painter.end() Aşağıda bir çizimim pixmap'e yapılıp, png dosyası olarak save edilmesine bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drawing Example') self.resize(640, 420) pixmap = QPixmap(640, 480) pixmap.fill(Qt.white) painter = QPainter(pixmap) painter.drawEllipse(10, 10, 100, 100) pixmap.save('test.png', 'png') painter.end() app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ 41. Ders 15/05/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte bir yazboz tahtası uygulaması yapılmıştır. Bu uygulamada amaç çeşitli çizim bilgilerini tutup o çizim bilgileriyle çizimleri save ve open yapmaktır. Aynı zamanda program her zaman son kapatılan durumdaki kalem kalınlığı ve rengiyle açılmaktadır #------------------------------------------------------------------------------------------------------------------------------------ import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * import pickle import shelve class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('Scratchpad Example') self.resize(640, 420) self.lines = [] self.line = [] try: self.settings = shelve.open('settings', 'c') if not (activeColor := self.settings.get('activeColor')): self.settings['activeColor'] = QColor(0, 0, 0) self.activeColor = QColor(0, 0, 0) else: self.activeColor = activeColor if not (activeWidth := self.settings.get('activeWidth')): self.settings['activeWidth'] = 3 self.activeWidth = 3 else: self.activeWidth = activeWidth except Exception as e: QMessageBox.warning(self, 'Error', str(e)) menuBar = self.menuBar() filePopup = QMenu('&File', self) self.newAction = QAction(QIcon('Icons/new-48x48.png'), '&New') self.newAction.setShortcut('Ctrl+N') self.newAction.triggered.connect(self.newActionTriggeredHandler) filePopup.addAction(self.newAction) self.openAction = QAction(QIcon('Icons/open-32x32.png'), '&Open...') self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.openActionTriggeredHandler) filePopup.addAction(self.openAction) self.saveAction = QAction(QIcon('Icons/save-48x48.png'), '&Save') self.saveAction.setShortcut('Ctrl+S') self.saveAction.triggered.connect(self.saveActionTriggeredHandler) self.saveAction.setEnabled(False) filePopup.addAction(self.saveAction) self.saveAsAction = QAction(QIcon('Icons/saveas-48x48.png'), '&Save as...') self.saveAsAction.triggered.connect(self.saveAsActionTriggeredHandler) filePopup.addAction(self.saveAsAction) filePopup.addSeparator() self.closeAction = QAction(QIcon('Icons/close-32x32.png'), '&Close') self.closeAction.triggered.connect(self.closeActionTriggeredHandler) self.closeAction.setEnabled(False) filePopup.addAction(self.closeAction) menuBar.addMenu(filePopup) optionsPopup = QMenu('&Options', self) self.colorAction = QAction(QIcon('Icons/color.png'), 'Choose Color...') self.colorAction.triggered.connect(self.colorActionTriggeredHandler) optionsPopup.addAction(self.colorAction) menuBar.addMenu(optionsPopup) self.toolBar = QToolBar() self.toolBar.setIconSize(QSize(32, 32)) self.toolBar.addAction(self.colorAction) self.comboBox = QComboBox(self) self.comboBox.addItems([str(i) for i in range(1, 11)]) self.comboBox.currentIndexChanged.connect(self.comboBoxCurrentIndexChangesHandler) self.comboBox.setCurrentIndex(self.activeWidth - 1) #self.spinBox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.toolBar.addSeparator() self.toolBar.addWidget(self.comboBox) self.addToolBar(self.toolBar) def mousePressEvent(self, me): self.prevPoint = me.pos() #self.line.append(me.pos()) self.dragFlag = True def mouseMoveEvent(self, me): if self.dragFlag: nextPoint = me.pos() self.line.append(QLineF(self.prevPoint, nextPoint)) self.prevPoint = nextPoint self.update() def mouseReleaseEvent(self, me): if self.dragFlag: self.lines.append((self.line, self.activeColor, self.activeWidth)) self.line = [] def newActionTriggeredHandler(self): pass def openActionTriggeredHandler(self): path, _ = QFileDialog.getOpenFileName(self, 'Open', '.', 'CSD files (*.csd);;All files (*.*)') if path: try: with open(path, 'rb') as f: self.lines = pickle.load(f) self.update() except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) def saveActionTriggeredHandler(self): pass def saveAsActionTriggeredHandler(self): path, _ = QFileDialog.getSaveFileName(self, 'Save', '.', 'CSD files (*.csd);;All files (*.*)') if path: try: with open(path, 'wb') as f: pickle.dump(self.lines, f) except Exception as e: QMessageBox.warning(self, 'Warning', str(e)) def closeActionTriggeredHandler(self): pass def colorActionTriggeredHandler(self): cd = QColorDialog(self) cd.setCurrentColor(self.activeColor) if cd.exec() == QColorDialog.Accepted: self.activeColor = cd.selectedColor() self.settings['activeColor'] = cd.selectedColor() def comboBoxCurrentIndexChangesHandler(self, newIndex): self.activeWidth = newIndex + 1 self.settings['activeWidth'] = self.activeWidth self.update() def paintEvent(self, pe): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) for line, color, width in self.lines: pen = QPen(color) pen.setWidth(width) painter.setPen(pen) painter.drawLines(line) pen = QPen(self.activeColor) pen.setWidth(self.activeWidth) painter.setPen(pen) painter.drawLines(self.line) app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Ekrana boş bir ana pencere çıkartan minimal bir Tkinter programı aşağıdaki gibi oluşturulabilir. Burada tk.Tk sınıfı türünen nesne yaratıldığında ana pemcere yaratılmış olmaktadır. Tk sınıfının mainloop isimli metodu mesaj döngüsünü oluşturur. Mesaj döngüsünden ana pencere kapatılınca çıkılacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencerenin boyutlandırılması ve konumlandırılması Tk sınıfının geometry isimli metoduyla yapılmaktadır. Bu metada "widthxheight" biçimind ebir yazı girilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('600x480') root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Eğer ana pencere için konum belirtilmezse konum pencere yönetici istem tarafından otomatik bir biçimde atanmaktadır. Konum belirlemek için yazıda "+x+y" biçiminde belirleme yapılır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('600x480+400+100') root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ İstenirse yalnızca konum da belirtilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('+400+100') root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencere başlığındaki yazıyı set etmek için Tk sınıfının title isimli metodu kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('640x480') root.title('Sample Window') root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Normal olarak ana pencere boyutlandırılabilir (resizable) biçimdedir. Ana pencereyi boyutlandırılabilir olmaktan çıkarmak için Tk sınıfının resizable metodu kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('640x480') root.title('Sample Window') root.resizable(width=False, height=False) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencerenin maksimum ve minimum boyutları Tk sınıfının maxsize ve minsize metotlarıyla belirlenebilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('640x480') root.title('Sample Window') root.maxsize(800, 800) root.minsize(200, 200) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Boş bir ana penere bir işe yaramaz. Onun içerisine "widget" denilen GUI elemanlar yerleştirmek gerekir. GUI elemanlar tkinter modülünğn içerisinde çeşitli sınıflarla temsil edilmektedir. Örneğin tk.Button düğme GUI elemanını, tk.Label yazı GUI elemanını, tk.Entry editbox GUI elemanını temsil eder. Ancak bir GUI eleman yaratıldığında henüz görünür değildir. Onu görünür yapmak için "geometri yöneeticisi" denilen sınıflardan faydalanışmaktadır. Üç geometri yöneticisi sınıf vardır: Place, Pack ve Grid. Tüm GUI eleman sınıfları tk.Widget isimli bir sınıftan türetilmiştir. tk.Widget sınıfı da tk.Place, tk.Pack ve tk.Grid sınıfların çoklu türetilmiş durumdadır. Böylece her GUI elemanı potansiyel olarak bu geometri manager sınıflarının metotlarını kullanabilir durumdadır. Place geometri yöneticisi pixel cinsinden konumlandırma yapar. Place geometri yönetici sınıfının place isimli metodu x ve y parametrelerini alarak konumlandırmayı pixel cinsinden yapmaktadır. Ancak alt pnecereler için orijin noktası her zaman üst pencerenin çalışma alanının (client area) sol üst köşesidir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('640x480') root.title('Sample Window') button_ok = tk.Button(root, text='Ok') button_ok.place(x=200, y=100) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Ana pencereye istediğimiz kadar GUI eleman yerleştirebiliriz #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('200x150') root.title('Sample Window') button_ok = tk.Button(root, text='Ok') button_ok.place(x=10, y=10) button_cancel = tk.Button(root, text='cancel') button_cancel.place(x=50, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ place metodunda biz GUI elemanının genişlik ve yüksekliğini pixel cinsinden ayarlayabiliriz. Bunun için w,dth ve height parametreleri kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok') button_ok.place(x=10, y=10, width=100, height=100) button_cancel = tk.Button(root, text='Cancel') button_cancel.place(x=120, y=10, width=100, height=100) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ place metodunun relx ve rely parametreleeri konumlandırmayı yüzdesel olarak yapmaktadır. Yani bu parametrelere 0 ile 1 arasında bir değer gireriz. Pencereyi genişletip daraltsak bile bu oran korunacak biçimde GUI eleman yeniden otomatik konumlandırılır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok') button_ok.place(relx=0.2, rely=0.2, width=100, height=100) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Benzer biçimde plcae metodunun relwidth ve relheight parametreleri de benişlik ve yüksekliği yüzdesel olarak almaktadır. Bu durumda pencere büyütülük küçültüldüğünde GUI eleman da oranı korumak için büyütülüp küçültülür. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok') button_ok.place(x=10, y=10, relwidth=0.8, relheight=0.8) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir GUI elemanın çeşitli özellikleri vardır. Bu özelliklere Tkinter'da "seçenk (option)" denilmektedir. Seçenekler "standart seçenekler" ve "widget spesifik seçenkler" olmak üzere ikiye ayrılmaktadır. Standart seçenekler her widget için geçerli seçeneklerdir. Widget specific seçenekler belli bir widget'a özgü seçenekleridr. Widget seçenekleri üç biçimde değiştirilebilir. Birinci biçim nesne yaratılırken parametre yoluyla belirtmedir. Örneğin text seçeneğini biz böyle belirledik. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok') button_ok.place(x=10, y=10, width=50, height=50) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Seçenek değiştirmenin ikinci yolu nesne yaratıldıktan sonra config isimli metodu kullanmaktır. Yine bu metottta seçenek = değer biçiminde parametre yoluyla seçenkler belirtilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root) button_ok.config(text='Ok') button_ok.place(x=10, y=10, width=50, height=50) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Üçüncü yöntem köşeli parantez yoluyla seçeneğe değer atamaktır. Burada köşeli parantezin içerisindeki seçenek ismi string türünden olmak zorundadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root) button_ok['text'] = 'Ok' button_ok.place(x=10, y=10, width=50, height=50) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Snır çizgileri olmayan yalnızca bir yazıyı göstermek için kullanılan GUI elemanlara GUI dünyasında "label" denilmektedir. Tkinter'da label tk.Label sınıfı ile temsil edilmiştir. Dolayısıyla bir ana pencerede bir yazı göstermek istediğimizde bu Label penceresini kullanırız. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test') label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Label'ın yazı rengini "foreground" ya da "fg" seçeneği ile değiştirebiliriz. Bu seçeneği doğrudan rengin ismi yazı olarak girilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red') label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Renk belirtirken '#rrggbb' biçiminde RGB olarak da giriş yapılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='#ff0000') label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Label'ın "background" ya da "bg" standart seçeneği zemin rengini belirlemek için kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red', bg='yellow') label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ font isimli standart seçenek yazının fontunu belirlemekte kullanılabilir. Font bir yazı biçiminde girilebilir. Bu durumda font ailesi, büyüklük ve diğer font özellikleri yazıda belirtilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red', bg='yellow', font='"Times New Roman" 20 bold italic') label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Font üçlü demet biçiminde de girilebilir. Burada sıralama esnektir. Ancak tipik olarak önce font ailesi, sonra punto büyüklüğü, sonra da diğer özellikler belirtilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red', bg='yellow', font=('Times New Roman', 30, 'bold italic')) label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Default fonla işlem yapılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red', bg='yellow', font=('', 30, 'bold italic')) label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aslında Tkinter'da font tkinter.font modülündeki Font isimli sınıfla temsil edilmiştir. Dolayısıyla font seçeneğine bu sınıf türünden bir nesne de atanabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter.font import Font root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red', bg='yellow') label.config(font=Font(family='Times New Roman', size=30, weight='bold')) label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Eğer pencere büyük ise yazı pencereye göre hizalanabilir. Bunun "anchor" isimli standart seçenek kullanılmaktadır. anchor seçeneğinde n (north), s(south), w (west), e (east) harfleri kombine edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test', fg='red', bg='yellow') label.place(x=10, y=10, width=100, height=100) label['anchor'] = 'nw' root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yazı içerisinde \n ile aşağı satıra geçiş yapılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test, yes\nthis is a test', fg='red', bg='yellow') label.place(x=10, y=10, width=100, height=100) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Label'da label'a özgü width ve height seçenekleri place metodundan farklı olarak karakter sayısı teemelinde genişlik yükseklik belirlemesi yapar. Anımsanacağı gibi place metodunda width ve height pixel cinsinden genişlik yükseklik belirtmekteydi. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk root = tk.Tk() root.geometry('320x200') root.title('Sample Window') label = tk.Label(root, text='This is a test, yes\nthis is a test', fg='red', bg='yellow', width=25, font='18') label.place(x=10, y=10) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Button GUI elemanında (widget) düğmeye fare ile tıklanıp parmak kaldırılınca command isimli widget spesifik seçenkte belirtilen fonksiyon çalıştırılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk def main(): root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok', command=button_ok_handler) button_ok.place(x=10, y=10, width=50, height=50) root.mainloop() def button_ok_handler(): print('Ok button clicked') main() #------------------------------------------------------------------------------------------------------------------------------------ Daha önce Label GUI elemanı için söz ettiğimiz standart seçeneklerin hepsi Button için de geçerlidir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk def main(): root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok', fg='red', bg='light blue', anchor='nw', command=button_ok_handler) button_ok.place(x=10, y=10, width=100, height=100) root.mainloop() def button_ok_handler(): print('Ok button clicked') main() #------------------------------------------------------------------------------------------------------------------------------------ Örneğn yine düğme üzerindeki yazının biçimi font seçeneği ile belirtilebilir. width ve heiaght seçenekleri o andaki font karakteri cinsinden genişlik ve yükseklik belirtir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk def main(): root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_ok = tk.Button(root, text='Ok', fg='red', bg='light blue', font='16', width=10, height=4, command=button_ok_handler) button_ok.place(x=10, y=10) root.mainloop() def button_ok_handler(): print('Ok button clicked') main() #------------------------------------------------------------------------------------------------------------------------------------ Button GUI elemanının widget spesifik state isimli seçeneği "normal" ya da "disabled" olabilir. Normal durum fare mesajlarının alınabildiği, disabled durum GUI elemanın fare mesajlarına kapatıldığı durumdur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk def main(): global button_start, button_stop root = tk.Tk() root.geometry('320x200') root.title('Sample Window') button_start = tk.Button(root, text='Start', font='16', command=button_start_handler) button_start.place(x=10, y=10, width=100, height=100) button_stop = tk.Button(root, text='Stop', font='16', state='disabled', command=button_stop_handler) button_stop.place(x=130, y=10, width=100, height=100) root.mainloop() def button_start_handler(): button_start['state'] = 'disabled' button_stop['state'] = 'normal' def button_stop_handler(): button_stop['state'] = 'disabled' button_start['state'] = 'normal' main() #------------------------------------------------------------------------------------------------------------------------------------ GUI uygulamaları prosedürel tekniğe çok uygun değildir. Nesne yönelimli teknikle çok iyi örtüşmektedir. Dolayısıyla Tkinter programcıları genellikle programlarını sınıfal bir biçimde oluştururlar. Bunun için iki yöntem çokça tercih edilmektedir. Birinci yöntemde bir sınıf oluşturulup ana pencere nesnesi bu sının __init__ metoduna parametre olarak geçirilir. Tüm handler fonksiyonlar sınıfın metodu yapılır. Yaratılan GUI elemanlar da sınıfın örnek özniteliğinde saklanır. Aşağıda bu tekniğin uygulanması görülmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class GUI: def __init__(self, root): root.geometry('320x200') root.title('Sample Window') self.button_start = tk.Button(root, text='Start', font='16', command=self.button_start_handler) self.button_start.place(x=10, y=10, width=100, height=100) self.button_stop = tk.Button(root, text='Stop', font='16', state='disabled', command=self.button_stop_handler) self.button_stop.place(x=130, y=10, width=100, height=100) def button_start_handler(self): self.button_start['state'] = 'disabled' self.button_stop['state'] = 'normal' def button_stop_handler(self): self.button_stop['state'] = 'disabled' self.button_start['state'] = 'normal' root = tk.Tk() gui = GUI(root) root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ GUI programını nesne yönelimli hale getirmek için diğer bir yöntem doğrudan ana pencere sınıfını tk.Tk sınıfından türetmektir. Böylece biz türemiş sınıfta self değişkeni yoluyla zaten tk.Tk sınıfının elemanlarını kullanabilriz. Biz kursumuzda daha çok bu yöntemi tercih edeceğiz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.title('Sample Window') self.button_start = tk.Button(self, text='Start', font='16', command=self.button_start_handler) self.button_start.place(x=10, y=10, width=100, height=100) self.button_stop = tk.Button(self, text='Stop', font='16', state='disabled', command=self.button_stop_handler) self.button_stop.place(x=130, y=10, width=100, height=100) def button_start_handler(self): self.button_start['state'] = 'disabled' self.button_stop['state'] = 'normal' def button_stop_handler(self): self.button_stop['state'] = 'disabled' self.button_start['state'] = 'normal' root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Düğmelerin command seçeneklerinde doğrudan lambda ifadeleri kullanılabilir. Ancak Python'da lambda ifadeleri tek bir ifade olmak zorundadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.title('Sample Window') self.button_ok = tk.Button(self, text='Ok', font='16', command=lambda: print('Ok')) self.button_ok.place(x=10, y=10, width=100, height=100) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir döngü içerisinde birden çok GUI elemanı yaratabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('310x310') self.title('Sample Window') self.button_lists = [] y = 10 for i in range(5): buttons = [] x = 10 for k in range(5): button = tk.Button(self, text=f'{i * 5 + k}') button.place(x=x, y=y, width=50, height=50) buttons.append(button) x += 50 + 10 self.button_lists.append(buttons) y += 50 + 10 root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla düğmenin bir döngü içerisinde yaratıldığı durumda bu düğmelerin her birine farklı bir command fonksiyonu atamak zor olabilmektedir. Aşağıdaki örnekte command metotları bir listede toplanıp nesne yaratılırken listenin içerisinden alınarak verilmiştir. Dolayısıyla her düğmenin handler fonksiyonu birbirinden farklı olacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('310x70') self.title('Sample Window') self.button_handlers = [self.button_handler1, self.button_handler2, self.button_handler3, self.button_handler4, self.button_handler5] x = 10 for k in range(5): button = tk.Button(self, text=f'{k + 1}', command=self.button_handlers[k]) button.place(x=x, y=10, width=50, height=50) x += 50 + 10 def button_handler1(self): print('bir') def button_handler2(self): print('iki') def button_handler3(self): print('üç') def button_handler4(self): print('dört') def button_handler5(self): print('beş') #------------------------------------------------------------------------------------------------------------------------------------ GUI programlarında kullanıcıdan bir giriş almak için Entry isimli GUI elemanı kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('310x70') self.title('Sample Window') self.label_name = tk.Label(self, text='Adı Soyadı') self.label_name.place(x=10, y=10) self.entry_name = tk.Entry(self) self.entry_name.place(x=10, y=30) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Entry GUI elemanının da standart seçenkleri diğerlerinde olduğu gibidir. Yani biz editbox içerisindeki yazının şekil ve zemin rengini değiştirebiliriz. Fontunu istediğimiz gibi belirleyebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('310x90') self.title('Sample Window') self.label_name = tk.Label(self, text='Adı Soyadı', font='10') self.label_name.place(x=10, y=10) self.entry_name = tk.Entry(self, font='10', fg='red', bg='light blue') self.entry_name.place(x=10, y=40, width=200) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Entry GUI eelemanındaki yazı Entry sınıfının get metoduyla string olarak alınabilmektedir #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('310x90') self.title('Sample Window') self.label_name = tk.Label(self, text='Adı Soyadı', font='10') self.label_name.place(x=10, y=10) self.entry_name = tk.Entry(self, font='10', fg='red', bg='light blue') self.entry_name.place(x=10, y=40, width=200) self.button_ok = tk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.place(x=220, y=40, width=50) def button_ok_handler(self): name = self.entry_name.get() print(name) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Entry ve Button nesneleri kullanılarak Veritabanına kayıt ekleyen GUI uygulaması #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import sqlite3 class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('350x225') self.title('Database Insert GUI App') self.resizable(width=False, height=False) self.label_no = tk.Label(self, text='No', font=('', 14)) self.label_no.place(x=10, y=10) self.entry_no = tk.Entry(self, font=('', 14)) self.entry_no.place(x=10, y=40, width=250) self.label_name = tk.Label(self, text='Adı Soyadı', font=('', 14)) self.label_name.place(x=10, y=75) self.entry_name = tk.Entry(self, font=('', 14)) self.entry_name.place(x=10, y=110, width=250) self.button_record = tk.Button(self, text='Kaydet', font=('', 14), command=self.button_record_handler) self.button_record.place(x=125, y=160, width=80) self.button_clear = tk.Button(self, text='Temizle', font=('', 14), command=self.button_clear_handler) self.button_clear.place(x=225, y=160, width=80) self.conn = sqlite3.connect('student.sqlite') def button_record_handler(self): student_no = self.entry_no.get().strip() student_name = self.entry_name.get().strip() cursor = self.conn.cursor() cursor.execute('INSERT INTO student(student_no, student_name) VALUES(?, ?)', (student_no, student_name)) self.conn.commit() self.button_clear_handler() def button_clear_handler(self): self.entry_no.delete(0, tk.END) self.entry_name.delete(0, tk.END) self.entry_no.focus() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla düğme için command seçeneğinde aynı fonksiyon belirtilebilir. Ancak bu durumda söz konusu fonksiyonun hangi düğmeden dolayı çalıştırılmış olduğu fonksiyon içersinde anlaşılamaz. Bu sorunu gidermenin en basit yolu command seçeneği için lambda fonksiyonu kullanıp orada ortak handler fonksiyonunu farklı parametrelerle çağırmaktır. Anımsanacağı gibi her lambda ifadesi aslında farklı bir fonksiyon belirtmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('400x300') self.button1 = tk.Button(self, text='Button1', command=lambda: self.button_handler(self.button1)) self.button1.place(x=10, y=10, width=100, height=100) self.button2 = tk.Button(self, text='Button2', command=lambda: self.button_handler(self.button2)) self.button2.place(x=120, y=10, width=100, height=100) self.button3 = tk.Button(self, text='Button3', command=lambda: self.button_handler(self.button3)) self.button3.place(x=230, y=10, width=100, height=100) def foo(self, button): pass def button_handler(self, button): print(button['text']) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Farklı düğmeler için aynı command fonksiyonun kullanılması durumunda bu fonksiyonun hangi düğmeden dolayı çağrıldığının anlaşılması döngü içerisinde aynı lambda ifadesinin kullanıldığı durumlarda problemlidir. Bu tür durumlarda Pythonic bir hileye başvurmak gerekmektedir. lambda x = y: button_handler(x) gibi bir işlemde lambda fonksiyonun parametresi aslında artık o andaki y'ye bağlanmış durumdadır. Dolayısıyla farklı y'ler için aslında button_handler farklı y değerlerini parametre olarak alacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('310x310') self.title('Sample Window') self.button_lists = [] y = 10 for i in range(5): buttons = [] x = 10 for k in range(5): button = tk.Button(self, text=f'{i * 5 + k}') button.place(x=x, y=y, width=50, height=50) button['command'] = lambda b = button: self.button_handler(b) buttons.append(button) x += 50 + 10 self.button_lists.append(buttons) y += 50 + 10 def button_handler(self, button): print(button['text']) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Veri bağlama (data binding) pek çok GUI framework'ta GUI eleman içerisindeki bilgileri alıp değiştirmeyi kolaylaştırmak için bulunan bir mekanizmadır. Bu mekanizma tkinter'da tk.StringVar, tk.IntVar, tk.DoubleVar, tk.BooleanVar ieimli özel nesnelerle yapılmaktadır. Bu sınıflar türünden nesneler yaratıldıktan sonra bunlar textvariable ya da benzeri özel seçeneklere girilirler. Artık bu nesnelerin get metotları bize GUI eleman içerisindeki bilgiyi verir, set metotları o GUI elemanın değerin değiştirilmesine yol açar. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.entry_text = tk.StringVar() self.entry = tk.Entry(self, font=('', 14), textvariable=self.entry_text) self.entry.place(x=10, y=10, width=200) self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=10, y=50, width=100, height=100) def button_ok_handler(self): self.entry_text.set('this is a test') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tk.Checkbutton "checkbox" denilen GUI elemanını oluşturmaktadır. Bu GUI eleman fare ile tıklanarak checked ya da unchecked duruma geçirilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.check_button = tk.Checkbutton(self, text='Test') self.check_button.place(x=10, y=10) self.check_button.select() self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=10, y=50, width=100, height=100) def button_ok_handler(self): self.check_button.deselect() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ CheckButton nesnesinin durumunu almak için tipik olarak veri bağlama yapılır. Veri bağlama işlemi için sınıfın "variable" isimli seçeneği kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.check_button_stat = tk.BooleanVar() self.check_button = tk.Checkbutton(self, text='Test', font=('', 14), variable=self.check_button_stat) self.check_button.place(x=10, y=10) self.check_button.select() self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=10, y=50, width=100, height=100) def button_ok_handler(self): stat = self.check_button_stat.get() print(stat) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Checkbutton nesneleri, için de command belirtilebilir. Bu durum özellikle check button nesnesi checked ya da unchecked yapıldığında diğer GUI elemanlarının durumlarını ayarlamak için kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.check_button_stat = tk.BooleanVar() self.label_text = tk.StringVar() self.check_button = tk.Checkbutton(self, text='Test', font=('', 14), variable=self.check_button_stat, command=self.check_button_handler) self.check_button.place(x=10, y=10) self.check_button.select() self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=10, y=50, width=100, height=100) self.label = tk.Label(self, font=('', 14), textvariable=self.label_text) self.label.place(x=120, y=75) def button_ok_handler(self): stat = self.check_button_stat.get() print(stat) def check_button_handler(self): self.label_text.set('Checked' if self.check_button_stat.get() else 'Unchecked') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Radyo düğmeleri bir grup olarak kullanılmaktadır. Bunlar yaratılırken value parametresi birbirinden farklı olarak int türden ya da str türünden girilmelidir. İşin başında bir tane düğmenin select ile seçilmesi gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.radio_buttonA = tk.Radiobutton(self, text='A', font=('', 14), value=1) self.radio_buttonA.place(x=10, y=10) self.radio_buttonB = tk.Radiobutton(self, text='B', font=('', 14), value=2) self.radio_buttonB.place(x=10, y=40) self.radio_buttonC = tk.Radiobutton(self, text='C', font=('', 14), value=3) self.radio_buttonC.place(x=10, y=70) self.radio_buttonD = tk.Radiobutton(self, text='D', font=('', 14), value=4) self.radio_buttonD.place(x=10, y=100) self.radio_buttonE = tk.Radiobutton(self, text='E', font=('', 14), value=5) self.radio_buttonE.place(x=10, y=130) self.radio_buttonA.select() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Hangi radyo düğmesinin seçikdiği veri bağlaması ile anlaşılmaktadır. Bağlama işleminde IntVar ya da StringVAr kullanılır. Bu nesneler Radiobutton sınıfının "variable" seçeneğine girilmelidir. Ne zaman bir radyo düğmesi seçilse value seçeneğindeki değer bağlanan nesneye yerleştirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.radio_button_var = tk.IntVar() self.radio_buttonA = tk.Radiobutton(self, text='A', font=('', 14), value=1, variable=self.radio_button_var) self.radio_buttonA.place(x=10, y=10) self.radio_buttonB = tk.Radiobutton(self, text='B', font=('', 14), value=2, variable=self.radio_button_var) self.radio_buttonB.place(x=10, y=40) self.radio_buttonC = tk.Radiobutton(self, text='C', font=('', 14), value=3, variable=self.radio_button_var) self.radio_buttonC.place(x=10, y=70) self.radio_buttonD = tk.Radiobutton(self, text='D', font=('', 14), value=4, variable=self.radio_button_var) self.radio_buttonD.place(x=10, y=100) self.radio_buttonE = tk.Radiobutton(self, text='E', font=('', 14), value=5, variable=self.radio_button_var) self.radio_buttonE.place(x=10, y=130) self.radio_buttonA.select() self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=120, y=10, width=100, height=100) def button_ok_handler(self): value = self.radio_button_var.get() print(value) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ value seçeneklerine yazı girebiliriz. Bu durumda bağlama StringVAr nesnesiyle yapılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.radio_button_var = tk.StringVar() self.radio_buttonA = tk.Radiobutton(self, text='A', font=('', 14), value='A', variable=self.radio_button_var) self.radio_buttonA.place(x=10, y=10) self.radio_buttonB = tk.Radiobutton(self, text='B', font=('', 14), value='B', variable=self.radio_button_var) self.radio_buttonB.place(x=10, y=40) self.radio_buttonC = tk.Radiobutton(self, text='C', font=('', 14), value='C', variable=self.radio_button_var) self.radio_buttonC.place(x=10, y=70) self.radio_buttonD = tk.Radiobutton(self, text='D', font=('', 14), value='D', variable=self.radio_button_var) self.radio_buttonD.place(x=10, y=100) self.radio_buttonE = tk.Radiobutton(self, text='E', font=('', 14), value='E', variable=self.radio_button_var) self.radio_buttonE.place(x=10, y=130) self.radio_buttonA.select() self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=120, y=10, width=100, height=100) def button_ok_handler(self): value = self.radio_button_var.get() print(value) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aynı pencere içerisinde farklı radyo düğme gruplarını oluşturabilmek için grupları farklı nesnelere bağlamak gerekmektedir. Bu yöntem diğer framework'tekilerden farklıdır. Örneğin PyQt'de bir pencerenin bütün kardeş radyo düğmeleri aynı grubu oluşturmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('300x200') self.radio_button_var1 = tk.StringVar() self.radio_button_var2 = tk.StringVar() self.radio_buttonA = tk.Radiobutton(self, text='A', font=('', 14), value='A', variable=self.radio_button_var1) self.radio_buttonA.place(x=10, y=10) self.radio_buttonB = tk.Radiobutton(self, text='B', font=('', 14), value='B', variable=self.radio_button_var1) self.radio_buttonB.place(x=10, y=40) self.radio_buttonC = tk.Radiobutton(self, text='C', font=('', 14), value='C', variable=self.radio_button_var1) self.radio_buttonC.place(x=10, y=70) self.radio_buttonD = tk.Radiobutton(self, text='D', font=('', 14), value='D', variable=self.radio_button_var1) self.radio_buttonD.place(x=10, y=100) self.radio_buttonE = tk.Radiobutton(self, text='E', font=('', 14), value='E', variable=self.radio_button_var1) self.radio_buttonE.place(x=10, y=130) self.radio_buttonA.select() self.radio_buttonX = tk.Radiobutton(self, text='X', font=('', 14), value='X', variable=self.radio_button_var2) self.radio_buttonX.place(x=120, y=120) self.radio_buttonY = tk.Radiobutton(self, text='Y', font=('', 14), value='Y', variable=self.radio_button_var2) self.radio_buttonY.place(x=160, y=120) self.radio_buttonZ = tk.Radiobutton(self, text='Z', font=('', 14), value='Z', variable=self.radio_button_var2) self.radio_buttonZ.place(x=200, y=120) self.radio_buttonX.select() self.button_ok = tk.Button(self, text='Ok', font=('', 14), command=self.button_ok_handler) self.button_ok.place(x=120, y=10, width=100, height=100) def button_ok_handler(self): value = self.radio_button_var1.get() print(f'First Group: {value}') value = self.radio_button_var2.get() print(f'SecondGroup: {value}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da bir resim göstermek için genellikle Label nesneleri kullanılır. Label sınıfının "image" isimli seçeneğine tk.PhotoImage nesnesi atanırsa Label bu fotoğrafı göstermektedir. Aslında image seçeneği Button gibi sınıflarda da bulunmaktadır. Burada kullanım konusunda bir noktaya dikkat etmek gerekir. Widget sınıfları PhotoImage nesnesinin referans sayacını artırmamaktadır. Bu nedenle PhotoImage nesnesinin kalıcılığının programcı tarafından sağlanması gerekmektedir. Bunun bir yolu Label nesnesi için bir örnek özniteliği yaratıp PhotoImage nesnesini bunun içerisine atamaktır. Buradaki örnek özniteliğinin isminin bir önemi yoktur. Ancak maalesef tk.PhotoImage yalnızca "gif", "png", "pgm" ve "ppm" formatlarını desteklemektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') pi = tk.PhotoImage(file='AbbeyRoad.png') self.label_picture = tk.Label(self, image=pi) self.label_picture.image = pi self.label_picture.place(x=10, y=10, width=200, height=200) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Eğer JPG ve pek çok formatı göstermek istiyorsak bu durumda PIL (pillow) isimli üçüncü parti bir kütüphaneden faydalanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage IMAGE_PATH = 'KızKulesi.jpg' class Root(tk.Tk): def __init__(self): super().__init__() self.title(IMAGE_PATH) image = Image.open(IMAGE_PATH) pi = PhotoImage(image) self.geometry(f'{image.width}x{image.height}') self.resizable(width=False, height=False) self.label_picture = tk.Label(self, image=pi) self.label_picture.image = pi self.label_picture.place(x=0, y=0, width=image.width, height=image.height) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ GUI uygulamalarında kullanıcıya mesajlar print fonksiyonuyla konsol ekranına yazdırma yoluyla verilmez. Bunun için "messagebox" denilen modal diyalg pencereleri kullanılmaktadır. tk.messagebox modülü içerisinde bir grup fonksiyon bazı tuş takımlarıyla messagebox pencereleri çıkarmaktadır. Örneğin showinfo bir mesajı göstermek için kullanılır. Yalnızca Ok tuşu pencerede bulunur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=10, y=10, width=100, height=100) def button_ok_handler(self): tk.messagebox.showinfo('Title', 'Message Text') tk.messagebox.showerror('Title', 'Message Text') tk.messagebox.showwarning('Title', 'Message Text') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ askxxxx biçimindeki messagebox fonksiyonları bize ilgili tuş takımını çıkartıp bizden bir seçim yapmamızı ister. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=10, y=10, width=100, height=100) def button_ok_handler(self): result = tk.messagebox.askyesno('Title', 'Message Text') print(result) result = tk.messagebox.askokcancel('Title', 'Message Text') print(result) result = tk.messagebox.askyesnocancel('Title', 'Message Text') print(result) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox GUI elemanı bir grup bilgiyi listelemek için kullanılır. Listelenmiş bilgilerden bir ya da birden fazlasını kullanıcı seçebilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self) self.listbox.place(x=10, y=10, width=100, height=200) for i in range(100): self.listbox.insert(tk.END, i) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox sınıfının get metodu iki indeks arasındaki elemanları almak için kullanılmaktadır. Eğer tek parametre girilirse o indeksteki eleman alınır. tk.ACTIVE ya da 'active' özel değeri o anda aktif elemanın indeksi anlamına gelmektedir. Hiçbir eleman seçili değilse tk.ACTIVE değeri default 0'dır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self) self.listbox.place(x=10, y=10, width=100, height=200) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=150, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): items = self.listbox.get(tk.ACTIVE) print(items) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox sınıfının curselection isimli netodu bize o andaki aktif elemanların indekslerini bir demet olarak verir. Eğer hiç bir eleman seçilmemişse (yani aktif değilse) bu durumda metot bize boş bir demet verecektir. Seçilen elemanı bu metotla almak daha uygun olmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self) self.listbox.place(x=10, y=10, width=100, height=200) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=150, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): selections = self.listbox.curselection() print(selections) if len(selections) == 0: print('Hiçbir eleman seçili değil!') else: selected_item = self.listbox.get(selections[0]) print(selected_item) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki örnekteki bilgileri messagebox ile de yazdırabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self) self.listbox.place(x=10, y=10, width=100, height=200) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=150, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): selections = self.listbox.curselection() print(selections) if len(selections) == 0: tk.messagebox.showerror('Bilgi', 'Hiçbir eleman seçilmedi') else: selected_item = self.listbox.get(selections[0]) tk.messagebox.showerror('Bilgi', f'Seçilen eleman: {selected_item}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Standart seçenekler Listbox için de geçerlidir. Yani biz Listbox penceresinin örneğin zemin rengini, yazı rengini, fontunu vs. değiştirebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self, fg='red', bg='light blue', font=('', 14)) self.listbox.place(x=10, y=10, width=150, height=300) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=180, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): selections = self.listbox.curselection() print(selections) if len(selections) == 0: tk.messagebox.showerror('Bilgi', 'Hiçbir eleman seçilmedi') else: selected_item = self.listbox.get(selections[0]) tk.messagebox.showerror('Bilgi', f'Seçilen eleman: {selected_item}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox sonıfının selectmod isimli seçeneği tk.MULTIPLE ya da 'multiple' olarak girilirse birden fazla eeleman seçilebilmektedir. tk.EXTENDED bir noktadan sıralı seçim yapmaya olanak sağlar. Yani tk.MULTIPLE seçeneğini kapsamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self, fg='red', bg='light blue', font=('', 14), selectmode=tk.MULTIPLE) self.listbox.place(x=10, y=10, width=150, height=300) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=180, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): selections = self.listbox.curselection() print(selections) if len(selections) == 0: tk.messagebox.showerror('Bilgi', 'Hiçbir eleman seçilmedi') else: text = '' for selection in selections: item = self.listbox.get(selection) text += item + ' ' tk.messagebox.showinfo('Bilgi', f'Seçilen elemanlar:\n{text}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox üzerinde çift tıklandığında genellijle tıklanan eleman hakkında birtakım işlemler yapılmaktadır. Tkinter'da listbox elemanı üzerinde tıklama işlemi için "event binding" uygulamak gerekmektedir. Bu konu izleyen örneklerde ele alınmaktadır. Farenin sol tuşu için çift tıklama '' yazısıyla temsil edilmektedir. Aşağıdaki örnekte Listbox elemanına çift tıklandığında tıklanan eleman elde edilip mesagebox üzerinde yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self, fg='red', bg='light blue', font=('', 14), selectmode=tk.EXTENDED) self.listbox.place(x=10, y=10, width=150, height=300) self.listbox.bind('', self.listbox_doubleclick_handler) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=180, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): selections = self.listbox.curselection() print(selections) if len(selections) == 0: tk.messagebox.showerror('Bilgi', 'Hiçbir eleman seçilmedi') else: text = '' for selection in selections: item = self.listbox.get(selection) text += item + ' ' tk.messagebox.showinfo('Bilgi', f'Seçilen elemanlar:\n{text}') def listbox_doubleclick_handler(self, event): selection = self.listbox.curselection() item = self.listbox.get(selection[0]) tk.messagebox.showinfo('Bilgi', f'{item} şehri seçildi') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox'taki seçilen eleman değiştiğinde bir fonksiyonumuzun çağrılmasını sağlayabiliriz. Bu işlem de "event binding" mekanizmasıyla yapılmaktadır. Buradaki event yazısı "<>" biçiminde olmalıdır. Aşağıdaki örnekte listbox üzerindeki seçilen eleman değiştiğinde o eleman yandaki label üzerine yazdırılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox = tk.Listbox(self, fg='red', bg='light blue', font=('', 14), selectmode=tk.EXTENDED) self.listbox.place(x=10, y=10, width=150, height=300) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] for city in cities: self.listbox.insert(tk.END, city) self.listbox.bind('<>', self.listbox_select_handler) self.label = tk.Label(self, font=('', 18)) self.label.place(x=170,y=10) def listbox_select_handler(self, event): selection = self.listbox.curselection() item = self.listbox.get(selection[0]) self.label['text'] = item root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Listbox sınıfının "listvariable" isimli veri bağlama seçeneği StringVAr nesnesi ile ilişkilendirilmelidir. Bu nesne get edildiğinde listbox'taki tüm elemanlar string'lerden oluşan bir demet biçiminde verilmektedir. Bu nesne yazılardan oluşan dolaşılabilir bir nesne argümanı verilerek set edildiğinde ise listbox elemanları set edilmektedir. Bu seçenek özellikle bir grup eelemanın tek hamlede listbox'a yerleştirilmesi için kullanılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.listbox_var = tk.StringVar() self.listbox = tk.Listbox(self, font=('', 14), listvariable=self.listbox_var) self.listbox.place(x=10, y=10, width=150, height=300) cities = ['Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Bilecik', 'Bingöl', 'Edirne', 'Eskişehir', 'İstanbul', 'İzmir', 'Sakarya', 'Siirt', 'Sinop', 'Elazığ'] self.button_ok = tk.Button(self, text='ok', command=self.button_ok_handler) self.button_ok.place(x=180, y=10, width=100, height=100) for city in cities: self.listbox.insert(tk.END, city) def button_ok_handler(self): self.listbox_var.set(['Ali', 'Veli', 'Selami']) print(self.listbox_var.get()) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da çok satırlı editbox pencerelerine "Text" denilmektedir. Text aslında Entry penceresinin çok satırlı biçimidir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.text = tk.Text(self, font=('', 12)) self.text.place(x=10, y=10, width=300, height=200) self.button = tk.Button(self, text='Ok') self.button.place(x=330, y=10, width=100, height=100) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Text GUI elemanındaki yazıyı almak için get metodu kullanılmaktadır. get metoduna başlangı ve bitiş indeksi verilir. Başlangıç indeksi dahil, bitiş indeksi dahil değildir. İndeksler yazısal olarak verilir. İndeks vermenin değişik biçimleri vardır. En çok kullanılan biçim "satır.sütun" biçimindedir. Satırlar 1'den, sütunlar 0'dan başlamaktadır. - Tüm yazıyo elde etmek için '1.0' ile tk.END ya da 'end' indeksleri kullanılmaktadır. - 'satır.end' ilgili satırın sonu anlamına gelir. Ancak satırın sonundaki '\n' dahil değildir. - tk.INSERT ya da 'insert' o andaki imlecin konumu anlamına gelir. - tk.SEL_FIRST ya da 'sel.first' seçimin başındaki indeksi tk.SEL_LAST ya da 'sel.last' seçimin sonundaki indeksi verir. Yani tüm seçim 'sel.first', 'sel.last' ile alınabilmektedir. - ' satır.sütun + n chars' n sonraki karakter 'satır.sütun - n chars' n önceki karakter anlamına gelir. - 'satır.sütun + lines' n sonraki satır, 'satır.sütun - n lines' n önceki satır anlamına gelmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12)) self.text.place(x=10, y=10, width=300, height=200) self.button = tk.Button(self, text='Ok', command=self.button_handler) self.button.place(x=330, y=10, width=100, height=100) def button_handler(self): s = self.text.get('1.0', '3.0') print(s) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Text sınııfnın delete metodu aynı indeksleme biçimiyle edit alanındakş karakterlerden silme yapar. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12)) self.text.place(x=10, y=10, width=300, height=200) self.button = tk.Button(self, text='Ok', command=self.button_handler) self.button.place(x=330, y=10, width=100, height=100) def button_handler(self): self.text.delete('1.0', '2.0') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Text sınıfının insert metodu belli bir yazıyı yukarıda belirtildiği biçimde bir indeksten itibaren insert etmektedir. Aşağıdaki örnekte yazı 2'inci satırın 10'uncu karakterinden itibaren insert edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12)) self.text.place(x=10, y=10, width=300, height=200) self.button = tk.Button(self, text='Ok', command=self.button_handler) self.button.place(x=330, y=10, width=100, height=100) def button_handler(self): self.text.insert('2.10', 'Bugün hava çok güzel') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Text sınıfının "wrap" isimli seçeneği tk.NONE ya da 'none' yapılırsa "word wrapping" kaldırılır. Bu seçenek tk.CHAR ya da 'char' olarak girilirse karakter temelli sarma, tk.WORD ya da "word" olarak girilirse sözcük temelli sarma yapılmaktadır. Default durum tk.CHAR biçimindedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.resizable(width=False, height=False) self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12), wrap=tk.CHAR) self.text.place(x=0, y=0, width=480, height=350) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Text GUI elemanının içerisindeki yazının çeşitli bölümleri farklı biçimde formatlanabilir. Bunu yapabilmek için önce bir "tag" oluşturmak gerekir. Tag oluşturma işlemi Text sınıfının tag_add metoduyla yapılmaktadır. Daha sonra Text sınıfının tag_config metoduyla ilgili bölge çeşitli seçenekler kullanılarak formatlanabilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.resizable(width=False, height=False) self.text = tk.Text(self, font=('Consolas', 12), wrap=tk.CHAR) self.text.place(x=0, y=0, width=480, height=350) with open('sample.py') as f: s = f.read() self.text.insert('1.0', s) self.text.tag_add('tag1', '1.0', '1.10') self.text.tag_config('tag1', background='light blue', foreground='red') self.text.tag_add('tag2', '3.0', '3.end') self.text.tag_config('tag2', font=('Consolas', 16)) self.text.tag_add('tag3', '4.0', '4.end') self.text.tag_config('tag3', font=('Consolas', 16, 'italic')) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tkinter.filedialog.askopenfilename isimli fonksiyon dosya seçme diyalog penceresini çıkartır. Fonksiyon bize seçilen dosyanın yol ifadesini verir. Eğer bir seçim yapılmazsa boş string geri döndürmektedir. Filtreleme yazısı için filetypes isimli parametresi kullanlmaktadır. Bu parametreye iki elemanlı demetlerden oluşan girilmelidir. defaultextension isimli parametesi eğer uzantı girilmezse otomatik olarak eklenecek uzantıyı nbelirtmektedir. Default durumda diyalog penceresi çalışma dizinini gösterecek biçimde açılmaktadır. Ancak initialdir isimli parametresi ile açık sırasında gösterilecek dizin belirlenebilir. title isimli parametresi açılacak diyalog penceresinin başlık yazısını belirlemekte kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.resizable(width=False, height=False) self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12), wrap=tk.CHAR) self.text.place(x=10, y=10, width=480, height=350) self.button = tk.Button(self, text='Open', command=self.button_handler) self.button.place(x=500, y=10, width=100, height=100) def button_handler(self): path = tk.filedialog.askopenfilename(filetypes=[('Text files', '*.txt'), ('Python files', '*.py')], defaultextension='.txt', initialdir=r'C:\Users\aslan\Dropbox\Shared\Kurslar\Python-App', title='Lütfen bir dosya seçiniz') if path != '': with open(path) as f: s = f.read() self.text.delete('1.0', 'end') self.text.insert('1.0', s) else: print('hiçbir dosya seçilmedi!') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tkinter.filedialog.asksaveasfilename fonksiyonu save etme amaçlı bir dosya isminin seçilmesi için kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.resizable(width=False, height=False) self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12), wrap=tk.CHAR) self.text.place(x=10, y=10, width=480, height=350) self.button = tk.Button(self, text='Save', command=self.button_handler) self.button.place(x=500, y=10, width=100, height=100) def button_handler(self): path = tk.filedialog.asksaveasfilename(filetypes=[('Text files', '*.txt'), ('Python files', '*.py')], defaultextension='.txt', title='Lütfen bir dosya ismi seçiniz') if path != '': with open(path, 'w') as f: s = self.text.get('1.0', 'end') s = f.write(s) else: print('hiçbir dosya seçilmedi!') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla dosya seçmek için tkinter.filedialog.askopenfilenames fonksiyonu kullanılır. Burada seçilen dosyaların yol ifadeleri string'ler oluşan bir demet biçiminde verilmektedir. Seçim yapılmazsa fonksiyon bıoş bir string'e geri dönmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.button = tk.Button(self, text='Open', command=self.button_handler) self.button.place(x=10, y=10, width=100, height=100) def button_handler(self): paths = tk.filedialog.askopenfilenames() print(paths) print(type(paths)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ askopenfilename fonksiyonunun yanı sıra askopenfile, asksaveasfilename fonksiyonunun yanı sıra da asksaveasfile fonksiyonu vardır. Bu fonksiyonlar bize yol ifadesi vermez bizzat dosyasyı açarak verir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.resizable(width=False, height=False) self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12), wrap=tk.CHAR) self.text.place(x=10, y=10, width=480, height=350) self.button = tk.Button(self, text='Open', command=self.button_handler) self.button.place(x=500, y=10, width=100, height=100) def button_handler(self): f = tk.filedialog.askopenfile(filetypes=[('Text files', '*.txt'), ('Python files', '*.py')], defaultextension='.txt', initialdir=r'C:\Users\aslan\Dropbox\Shared\Kurslar\Python-App', title='Lütfen bir dosya seçiniz') if f is not None: s = f.read() self.text.delete('1.0', 'end') self.text.insert('1.0', s) f.close() else: print('hiçbir dosya seçilmedi!') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Renk seçme seçme diyalog penceresi tkinter.colorchooser.askcolor fonksiyonuyla oluşturulmaktadır. Bu fonksiyon seçim yapıldığında bir demet ile geri döner. Demetin birinci elemanı 3 elemanlı RGB renklerinden, ikinci elemanı #'li renk kodundan oluşmaktadır. Diyalog penceresinde seçim yapılmazsa fonksiyon None değeri ile geri dönmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.colorchooser class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.resizable(width=False, height=False) self.text = tk.Text(self, fg='red', bg='light blue', font=('', 12)) self.text.place(x=10, y=10, width=480, height=350) self.button_background = tk.Button(self, text='Background', command=self.button_background_handler) self.button_background.place(x=500, y=10, width=100, height=100) self.button_foreground = tk.Button(self, text='Foreground', command=self.button_foreground_handler) self.button_foreground.place(x=500, y=120, width=100, height=100) def button_background_handler(self): color = tk.colorchooser.askcolor(color='light blue') self.text['bg'] = color[1] def button_foreground_handler(self): color = tk.colorchooser.askcolor(color='red') self.text['fg'] = color[1] root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Belli bir olay (mesaj) gerçekleştiğinde istediğimiz bir fonksiyonun çağrılmasını sağlayabiliriz. Bu konuya "event binding" denilmektedir. Olay bağlama işlemi ilgili widget sınıfının bind isimli metoduyla yapılır. Bu bind metodunun iki parametresi vardır. Birinci parametre olayın ne olduğunu belirtir. İkinci parametre çağrılacak fonksiyonu belirtmektedir. Çağrılacak fonksiyonun her zaman bir event parametresi vardır. bind fonksiyonun birinci parametresinde olay belirtmenin belli bir sentaksı vardır. Açısal parantezler içerisinde "modifier" denilen özel sözcükler bazı olaylar anlatmaktadır. Örneğin "" farenin sol tuşuna basılma olayını, "" farenin sol tuşuna çift tıklama olayını belirtir. "" olayı diğer framework'lerdeki "MOUSE_MOVE" mesajını belirtmektedir. Yani fare sürüklendiğinde belirtilen fonksiyon çağrılacaktır. "" belli bir tuşa basıldığında, "" tuştan el çekildiğinde tetiklenmektedir. Burada istenirse "detailed" denilen ek bilgi verilebilmektedir. Örneğin "" x tuluna basılması durumunu belirtir. ""kontrol tuşunu belirtmektedir. Örneğin "" control ve o tuşuna basımı belirtir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.frame = tk.Frame(self, bg='yellow') self.frame.place(x=10, y=10, width=100, height=100) self.frame.bind('', self.frame_button_left_press_handler) def frame_button_left_press_handler(self, event): print('frame') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Fare mesajlarında event parametresinin x ve y örnek öznitelikleri farenin o andaki durumunu çalışma alanı orijinli olarak vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.bind('', self.root_button_left_press_handler) def root_button_left_press_handler(self, event): print(f'x = {event.x}, y = {event.y}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki programda fare hareket ettirildikçe farenin korrdinatları yazdırılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.label = tk.Label(self) self.label.place(x=10, y=10) self.bind('', self.root_button_motion_handler) def root_button_motion_handler(self, event): print(f'x = {event.x}, y = {event.y}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir tuşa basıldığında istediğimiz bir fonksiyonun çağrılmasını sağlayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.label = tk.Label(self) self.label.place(x=10, y=10) self.bind('', self.control_o_handler) self.bind('', self.keypress_handler) def control_o_handler(self, event): print('Control-o') def keypress_handler(self, event): print(f'Key pressed: {event.char}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Parmağımızla bir tuşa bastığımızda paramağımızı çeekmnezsek typematic denilen olay gerçekleşir. Typematic sanki tuşa belli bir periyotta hem basılıyormuş etkisine denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('620x350') self.label = tk.Label(self) self.label.place(x=10, y=10) self.bind('', self.keypress_handler) self.bind('', self.keyrelease_handler) def keypress_handler(self, event): print(f'Key pressed: {event.char}') def keyrelease_handler(self, event): print(f'Key released: {event.char}') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Menu çubuğu tk.Menu sınıfyla oluşturulur. Menu çubuğu oluşturuylduktan sonra ana pencereye ana pencere sınıfının "menu" seçeneği ile bağlanması gerekir. Popup pencereler de yime tk.Menu sınıfyla oluşturulmaktadır. Popup pencerelerin menu çubuğuna bağlanması için tk.Menu sınıfının add_cascade metodu kullanılır. Popup pencerelere menü elemanlarının yerleştirilmesi için ise Menu sınıfının add_commmand metotları kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.menu_bar = tk.Menu(self) self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open') self.file_popup.add_command(label='Close') self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut') self.edit_popup.add_command(label='Copy') self.edit_popup.add_command(label='Paste') self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self['menu'] = self.menu_bar root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü elemanı seçildiğinde çağrılacak fonksiyon add_command metodunun "command" isimli parametresiyle belirtilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', command=self.file_open_handler) self.file_popup.add_command(label='Close', command=self.file_close_handler) self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', command=self.edit_cut_handler) self.edit_popup.add_command(label='Copy', command=self.edit_copy_handler) self.edit_popup.add_command(label='Paste', command=self.edit_paste_handler) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self['menu'] = self.menu_bar def file_open_handler(self): path = tk.filedialog.askopenfilename(title='Bir dosya seçiniz') if path != '': print(f'Seçilen dosya: {path}') def file_close_handler(self): self.destroy(); def edit_cut_handler(self): print('Cut') def edit_copy_handler(self): print('Copy') def edit_paste_handler(self): print('paste') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Menü elemanlarının şekil ve zemin renkleri ve fontları değiştirilebilir, menü elemanları "disabled" yapılabili.r #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', command=self.file_open_handler, font=('', 12), foreground='red') self.file_popup.add_command(label='Close', command=self.file_close_handler, background='yellow') self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', command=self.edit_cut_handler, state='tk.DISABLED') self.edit_popup.add_command(label='Copy', command=self.edit_copy_handler) self.edit_popup.add_command(label='Paste', command=self.edit_paste_handler) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self['menu'] = self.menu_bar def file_open_handler(self): path = tk.filedialog.askopenfilename(title='Bir dosya seçiniz') if path != '': print(f'Seçilen dosya: {path}') def file_close_handler(self): self.destroy(); def edit_cut_handler(self): print('Cut') def edit_copy_handler(self): print('Copy') def edit_paste_handler(self): print('paste') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Menü elemanına kısayol tuşu atayabilmek için add_command metodunda "accelerator" isimli parametresi ile kısa yol tuş yazısı belirtilir. Ancak bu belirtmenin yapımnası yalnızca menü elemanında kısayol tuş yazısının çıkmasını sağlamaktadır. İlgili tuşa basıldığında o menü elemanı seçilmiş gibi bir işlmein oluşması için ana pencere nesnesi üzerinde event binding yapılmalıdır. Menü elemanında "command" parametresi ile girilen fonksiyonun parametre sayısı ile event binding için girilen fonksiyonun parametrik yapıları farklıdır. Bunun için lambda işlemlerinden ya da default argüman kullanımından faydalanılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', command=self.file_open_handler, accelerator='Ctrl+O') self.file_popup.add_command(label='Close', command=self.file_close_handler) self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', command=self.edit_cut_handler) self.edit_popup.add_command(label='Copy', command=self.edit_copy_handler) self.edit_popup.add_command(label='Paste', command=self.edit_paste_handler) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self['menu'] = self.menu_bar #self.bind('', lambda event: self.file_open_handler()) self.bind('', self.file_open_handler) def file_open_handler(self, event=0): path = tk.filedialog.askopenfilename(title='Bir dosya seçiniz') if path != '': print(f'Seçilen dosya: {path}') def file_close_handler(self): self.destroy(); def edit_cut_handler(self): print('Cut') def edit_copy_handler(self): print('Copy') def edit_paste_handler(self): print('paste') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Menü elemanlarında ikon göstermek için add_command metodunda "image" ve "compound" isimli parametreleri kullanılır. Menü ikonlarının .png formatı biçiminde seçilmesi otomatik transparanlık için tercih edilmelidir. compound parametresi ikonun yazının neresinde bulunacağını belirtir. Genellikle tk.LEFT (ya da 'left') biçiminde girilmektedir. Aşağıdaki programı çalıştırabilmek için çalışma dizininizde "open.png", "cut.png", "copy.png" ve "paste.png" dosyalarının bulunuyor olması gerekir. Menü ikonları tipik olarak 16x16 biçimdedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.open_image = tk.PhotoImage(file='open.png') self.cut_image = tk.PhotoImage(file='cut.png') self.copy_image = tk.PhotoImage(file='copy.png') self.paste_image = tk.PhotoImage(file='paste.png') self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', accelerator='Ctrl+O', image=self.open_image, compound=tk.LEFT, command=self.file_open_handler) self.file_popup.add_command(label='Close', command=self.file_close_handler) self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', image=self.cut_image, compound=tk.LEFT, command=self.edit_cut_handler) self.edit_popup.add_command(label='Copy', image=self.copy_image, compound=tk.LEFT, command=self.edit_copy_handler) self.edit_popup.add_command(label='Paste', image=self.paste_image, compound=tk.LEFT, command=self.edit_paste_handler) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self['menu'] = self.menu_bar #self.bind('', lambda event: self.file_open_handler()) self.bind('', self.file_open_handler) def file_open_handler(self, event=0): path = tk.filedialog.askopenfilename(title='Bir dosya seçiniz') if path != '': print(f'Seçilen dosya: {path}') def file_close_handler(self): self.destroy(); def edit_cut_handler(self): print('Cut') def edit_copy_handler(self): print('Copy') def edit_paste_handler(self): print('paste') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir menü elemanı başka bir popup olabilir. Aşağıdaki örnekte Options popup penceresinin Fruits elemanı da bir popup elemandır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.open_image = tk.PhotoImage(file='open.png') self.cut_image = tk.PhotoImage(file='cut.png') self.copy_image = tk.PhotoImage(file='copy.png') self.paste_image = tk.PhotoImage(file='paste.png') self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', accelerator='Ctrl+O', image=self.open_image, compound=tk.LEFT, command=self.file_open_handler) self.file_popup.add_command(label='Close', command=self.file_close_handler) self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', image=self.cut_image, compound=tk.LEFT, command=self.edit_cut_handler) self.edit_popup.add_command(label='Copy', image=self.copy_image, compound=tk.LEFT, command=self.edit_copy_handler) self.edit_popup.add_command(label='Paste', image=self.paste_image, compound=tk.LEFT, command=self.edit_paste_handler) self.options_popup = tk.Menu(tearoff=0) self.fruits_popup = tk.Menu(tearoff=0) self.fruits_popup.add_command(label='Apple') self.fruits_popup.add_command(label='Banana') self.fruits_popup.add_command(label='Orange') self.options_popup.add_cascade(label='Fruits', menu=self.fruits_popup) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self.menu_bar.add_cascade(label='Options', menu=self.options_popup) self['menu'] = self.menu_bar #self.bind('', lambda event: self.file_open_handler()) self.bind('', self.file_open_handler) def file_open_handler(self, event=0): path = tk.filedialog.askopenfilename(title='Bir dosya seçiniz') if path != '': print(f'Seçilen dosya: {path}') def file_close_handler(self): self.destroy(); def edit_cut_handler(self): print('Cut') def edit_copy_handler(self): print('Copy') def edit_paste_handler(self): print('paste') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Menu elemanı checked/uncked biçiminde yapılabilir. Bunun için tk.Menu sınıfının add_checkbutton metodu kullanılmaktadır. Bu elemanın checked mi unchecked mi olduğu "data binding" işlemi belirlenebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.checked_wrapping = tk.BooleanVar() self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', compound=tk.LEFT, ) self.file_popup.add_command(label='Close') self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', compound=tk.LEFT) self.edit_popup.add_command(label='Copy', compound=tk.LEFT) self.edit_popup.add_command(label='Paste', compound=tk.LEFT) self.options_popup = tk.Menu(tearoff=0) self.options_popup.add_checkbutton(label='Wrapping', variable=self.checked_wrapping, command=self.options_wrapping_handler) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self.menu_bar.add_cascade(label='Options', menu=self.options_popup) self['menu'] = self.menu_bar def options_wrapping_handler(self): print(f'{"Checked" if self.checked_wrapping.get() else "Unchecked"} yapıldı') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir grup menü elemanı radyo düğmesi gibi de davranabilir. Bunun bu elemanların tk.Menu sınıfının add_radiobutton metodu ile eklenmesi gerekmektedir. Bu elemanların kullaımları tamamen tk.Checkbox GUI elemanında olduğu gibidir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x350') self.selected_fruit = tk.StringVar(value='Elma') self.menu_bar = tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', compound=tk.LEFT, command=self.file_open_handler) self.file_popup.add_command(label='Close') self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut', compound=tk.LEFT) self.edit_popup.add_command(label='Copy', compound=tk.LEFT) self.edit_popup.add_command(label='Paste', compound=tk.LEFT) self.fruits_popup = tk.Menu(tearoff=0) self.fruits_popup.add_radiobutton(label='Apple', value='Elma', variable=self.selected_fruit) self.fruits_popup.add_radiobutton(label='Banana', value='Muz', variable=self.selected_fruit) self.fruits_popup.add_radiobutton(label='Orange', value='Portakal', variable=self.selected_fruit) self.fruits_popup.add_radiobutton(label='Apricot', value='Kayısı', variable=self.selected_fruit) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.edit_popup) self.menu_bar.add_cascade(label='Fruits', menu=self.fruits_popup) self['menu'] = self.menu_bar def options_wrapping_handler(self): print(f'{"Checked" if self.checked_wrapping.get() else "Unchecked"} yapıldı') def file_open_handler(self) : path = tk.filedialog.askopenfile(title=f'Lütfen bir {self.selected_fruit.get()} seçiniz') print(path) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir GUI elemanını sürükleyebilmek için iki event'in binding yoluyla işlenmesi gerekir: '' ve '' Fare le GUI elemana ilk bastığımızda yeri sınıfın örnek özniteliğinde saklarız. Sonra fareyi sürüklediğimizde kaymanın deltax ve deltay miktarlarını hesaplayıp GUI elemanı o miktarda öteleriz. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.button = tk.Button(self, text='Ok') self.button.place(x=10, y=20, width=150, height=150) self.button.bind('', self.button_press_handler) self.button.bind('', self.button_motion_handler) def button_press_handler(self, event): self.first_x = event.x self.first_y = event.y def button_motion_handler(self, event): deltax = event.x - self.first_x deltay = event.y - self.first_y newx = self.button.winfo_x() + deltax newy = self.button.winfo_y() + deltay self.button.place(x=newx, y=newy) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Farenin sağ tuşuna basıldığında basılan yerde bir düğmenin yaratılması örneği. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk BUTTON_SIZE = 100 class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.counter = 1 self.bind('', self.button_right_press_handler) def button_right_press_handler(self, event): button = tk.Button(self, text= f'{self.counter}', font=('', 14)) button.place(x=event.x - BUTTON_SIZE / 2, y=event.y - BUTTON_SIZE / 2, width=BUTTON_SIZE, height=BUTTON_SIZE) self.counter += 1 root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Farenin sağ tuşuna basılınca düğme yaratan ve yaratılmış olan düğmelerin fare ile sürüklenmesini sağlayan örnek program. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk BUTTON_SIZE = 100 class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.counter = 1 self.bind('', self.button_right_press_handler) def button_right_press_handler(self, event): print(dir(event)) button = tk.Button(self, text= f'{self.counter}', font=('', 14)) button.place(x=event.x - BUTTON_SIZE / 2, y=event.y - BUTTON_SIZE / 2, width=BUTTON_SIZE, height=BUTTON_SIZE) button.bind('', self.button_left_press_handler) button.bind('', self.button_left_motion_handler) self.counter += 1 def button_left_press_handler(self, event): self.first_x = event.x self.first_y = event.y def button_left_motion_handler(self, event): deltax = event.x - self.first_x deltay = event.y - self.first_y newx = event.widget.winfo_x() + deltax newy = event.widget.winfo_y() + deltay event.widget.place(x=newx, y=newy) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ PNG formatındaki bir oyun kartının bir düğme biçiminde gösterilmesi örneği #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage IMAGE_WIDTH = 72 IMAGE_HEIGHT = 96 class Root(tk.Tk): def __init__(self): super().__init__() self.button = tk.Button(self) self.image = tk.PhotoImage(file='CardImages/1.png') self.button.pi = PhotoImage(Image.open('CardImages/1.png')) self.button['image'] = self.button.pi self.button.place(x=10, y=10, width=IMAGE_WIDTH, height=IMAGE_HEIGHT) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Oyun kartlarının aralarına boşluk bırakılarak gösterilmesi #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage import glob IMAGE_WIDTH = 72 IMAGE_HEIGHT = 96 PADDING_X = 10 PADDING_Y = 10 class Root(tk.Tk): def __init__(self): super().__init__() self.title('Playing Cards') self.geometry('1200x800') xpos = PADDING_X ypos = PADDING_Y for index, path in enumerate(glob.iglob('CardImages/*.png')): button = tk.Button(self) button.image = PhotoImage(Image.open(path).resize((IMAGE_WIDTH, IMAGE_HEIGHT))) button['image'] = button.image button.place(x=xpos, y=ypos) xpos += IMAGE_WIDTH + PADDING_X if index % 10 == 9: ypos += IMAGE_HEIGHT + PADDING_Y xpos = PADDING_X root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Oyun kartlarının hareket ettirilmesi #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage import glob IMAGE_WIDTH = 72 IMAGE_HEIGHT = 96 PADDING_X = 10 PADDING_Y = 10 class Root(tk.Tk): def __init__(self): super().__init__() self.title('Playing Cards') self.geometry('1200x800') xpos = PADDING_X ypos = PADDING_Y for index, path in enumerate(glob.iglob('CardImages/*.png')): label = tk.Label(self) label.image = PhotoImage(Image.open(path).resize((IMAGE_WIDTH, IMAGE_HEIGHT))) label['image'] = label.image label.place(x=xpos, y=ypos) label.bind('', self.button_left_press_handler) label.bind('', self.button_left_motion_handler) xpos += IMAGE_WIDTH + PADDING_X if index % 10 == 9: ypos += IMAGE_HEIGHT + PADDING_Y xpos = PADDING_X def button_left_press_handler(self, event): self.first_x = event.x self.first_y = event.y event.widget.lift() def button_left_motion_handler(self, event): deltax = event.x - self.first_x deltay = event.y - self.first_y newx = event.widget.winfo_x() + deltax newy = event.widget.winfo_y() + deltay event.widget.place(x=newx, y=newy) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Kartların arka yüzünün gösterilmesi ve double click yapıldığında açılması örneği #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage import glob IMAGE_WIDTH = 72 IMAGE_HEIGHT = 96 PADDING_X = 10 PADDING_Y = 10 class Root(tk.Tk): def __init__(self): super().__init__() self.title('Playing Cards') self.geometry('1200x800') self.back_image = PhotoImage(Image.open('CardImages/blue-back.png').resize((IMAGE_WIDTH, IMAGE_HEIGHT))) xpos = PADDING_X ypos = PADDING_Y for index, path in enumerate(glob.iglob('CardImages/??.png')): label = tk.Label(self) label.image = PhotoImage(Image.open(path).resize((IMAGE_WIDTH, IMAGE_HEIGHT))) label['image'] = self.back_image label.place(x=xpos, y=ypos) label.bind('', self.button_left_press_handler) label.bind('', self.button_left_motion_handler) label.bind('', self.button_left_doubleclick_handler) xpos += IMAGE_WIDTH + PADDING_X if index % 10 == 9: ypos += IMAGE_HEIGHT + PADDING_Y xpos = PADDING_X def button_left_press_handler(self, event): self.first_x = event.x self.first_y = event.y event.widget.lift() def button_left_motion_handler(self, event): deltax = event.x - self.first_x deltay = event.y - self.first_y newx = event.widget.winfo_x() + deltax newy = event.widget.winfo_y() + deltay event.widget.place(x=newx, y=newy) def button_left_doubleclick_handler(self, event): event.widget['image'] = event.widget.image root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Kartlar kapalıysa açan, açıksa kapatan örnek #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage import glob import os.path IMAGE_WIDTH = 72 IMAGE_HEIGHT = 96 PADDING_X = 10 PADDING_Y = 10 class Root(tk.Tk): def __init__(self): super().__init__() self.title('Playing Cards') self.geometry('1200x800') self.back_image = PhotoImage(Image.open('CardImages/back-blue.png').resize((IMAGE_WIDTH, IMAGE_HEIGHT))) xpos = PADDING_X ypos = PADDING_Y for index, path in enumerate(glob.iglob('CardImages/*.png')): if os.path.basename(path).startswith('back'): continue label = tk.Label(self) label.image = PhotoImage(Image.open(path).resize((IMAGE_WIDTH, IMAGE_HEIGHT))) label['image'] = self.back_image label.place(x=xpos, y=ypos) label.closed = True label.bind('', self.button_left_press_handler) label.bind('', self.button_left_motion_handler) label.bind('', self.button_left_doubleclick_handler) xpos += IMAGE_WIDTH + PADDING_X if index % 10 == 9: ypos += IMAGE_HEIGHT + PADDING_Y xpos = PADDING_X def button_left_press_handler(self, event): self.first_x = event.x self.first_y = event.y event.widget.lift() def button_left_motion_handler(self, event): deltax = event.x - self.first_x deltay = event.y - self.first_y newx = event.widget.winfo_x() + deltax newy = event.widget.winfo_y() + deltay event.widget.place(x=newx, y=newy) def button_left_doubleclick_handler(self, event): if event.widget.closed: event.widget['image'] = event.widget.image else: event.widget['image'] = self.back_image event.widget.closed = not event.widget.closed root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ pack geometri yöneticisi ixel belirtmeden otomatik yerleştirme için çoça tercih edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.label1 = tk.Label(self, text='Ali', bg='yellow') self.label1.pack() self.label2 = tk.Label(self, text='Veli', bg='light blue') self.label2.pack() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ pack geometri yöneticisinin side isimli parametresi 'top' (tk.TOP), 'bottom' (tk.BOTTOM), 'right' (tk.RIGHT) ya da 'left' (tk.LEFT) değerlerini alabilmektedir. Bu değerler GUI elemanın hangi köşeye yaslaanacağını belirtir. Yukarı ve aşağı yaslanmada GUI elemanları yatay ortada görünrülenir, sola ve sağa yaslanmada düşey ortada görüntülenmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.label1 = tk.Label(self, text='Ali', bg='yellow') self.label1.pack(side='top') self.label2 = tk.Label(self, text='Veli', bg='light blue') self.label2.pack(side='left') self.label1 = tk.Label(self, text='Selami', bg='yellow') self.label1.pack(side='right') self.label2 = tk.Label(self, text='Ayşe', bg='light blue') self.label2.pack(side='bottom') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Birden fazla GUI elemanı aynı göşeye yaslandırılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.label1 = tk.Label(self, text='Ali', bg='yellow') self.label1.pack(side='top') self.label2 = tk.Label(self, text='Veli', bg='light blue') self.label2.pack(side='top') self.label1 = tk.Label(self, text='Selami', bg='yellow') self.label1.pack(side='left') self.label2 = tk.Label(self, text='Ayşe', bg='light blue') self.label2.pack(side='left') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ pack metodunun anchor parametresi coğrafi yöne ilişkin kısaltmaları parametre olarak alır. ('w', 'e', 'n', 's', ve kombinasyonları. tk.W, tk.E, tk.N, tk.S ve kombinasyonları da kullanılabilmektedir.) Bu parametre side ile belirtilen ters yönde GUI elemanını hizalamakta kullaılmaktadır. anchor parametresi GUI elemanın ilgili köşeye ya da köşelere demirlenmesine yol açmaktadır. Örneğin biz bir GUI elemanını side='top' yaptığımızda o GUI elemanı yukarıya yapışık hale gelir. Ancak pencereyi genişletip daralttığımızda yukarıya ortalı olarak konumlandıırlır. İşte ana pencerenin daraltılması ve genişletilmesinde GUI elemanın konumunun değiştirilmeyeceği anlamına gelir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.label_name = tk.Label(self, text='Adı Soyadı:', font=('', 12)) self.label_name.pack(side='top', anchor='w') self.entry_name = tk.Entry(self, font=('', 12), width=20) self.entry_name.pack(side='top', anchor='w') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ pack metodunun padx parametresi GUI elemana soldan ve sağdan padding vermek için kullanılır. Bu parametre tek bir değer olarak girilirse hem soldan hem sağdan aynı padding kullanılır. Bir demet olarak girilirse sol ve sağ padding'ler değiştirilebilir. pack metodunun pady parametresi de benzerdir. Bu parametre GUI elemanın yukarı ve aşağısana padding vermek için kulanılır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.label_name = tk.Label(self, text='Adı Soyadı:', font=('', 12)) self.label_name.pack(side='top', anchor='w', padx=5, pady=5) self.entry_name = tk.Entry(self, font=('', 12), width=20) self.entry_name.pack(side='top', anchor='w', padx=5) self.label_no = tk.Label(self, text='No:', font=('', 12)) self.label_no.pack(side='top', anchor='w', padx=5, pady=5) self.entry_no= tk.Entry(self, font=('', 12), width=20) self.entry_no.pack(side='top', anchor='w', padx=5) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte Label ve Entry GUI elemanları side='top', Button GUI elemanları side='left' yapılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.resizable(width=False, height=False) self.label_name = tk.Label(self, text='Adı Soyadı:', font=('', 12)) self.label_name.pack(side='top', anchor='w', padx=5, pady=5) self.entry_name = tk.Entry(self, font=('', 12), width=20) self.entry_name.pack(side='top', anchor='w', padx=5) self.label_no = tk.Label(self, text='No:', font=('', 12)) self.label_no.pack(side='top', anchor='w', padx=5, pady=5) self.entry_no= tk.Entry(self, font=('', 12), width=20) self.entry_no.pack(side='top', anchor='w', padx=5) self.button_ok = tk.Button(self, text='ok', width=8, font=('', 12)) self.button_ok.pack(side='left', anchor='nw', padx=(100, 0), pady=20) self.button_cancel = tk.Button(self, text='cancel', width=8, font=('', 12)) self.button_cancel.pack(side='left', anchor='nw', padx=20, pady=20) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aslında GUI elemanları ana pencereye pack yapmak yerine tk.Frame isimli içi boi pencerelere pack yapıp bu Frame pence relerini de kendi aralarında pack yapmak daha kolay bir tasarıma yol açmaktadır. pack metodunun fill isimli parametresi ilgili GUI elemanın ana pencere genişliğiğini ya da yüksekliğini tamamen kaplamasını sağlar. Bu parametre tk.X ('x'), tk.Y ('y') ya da tk.BOTH ('both') değerlerini alabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Pack Manager') self.geometry('320x190') self.resizable(width=False, height=False) self.frame1 = tk.Frame(self) self.label_name = tk.Label(self.frame1, text='Adı Soyadı:', font=('', 12)) self.label_name.pack(side='top', anchor='w', padx=5, pady=5) self.entry_name = tk.Entry(self.frame1, font=('', 12), width=20) self.entry_name.pack(side='top', anchor='w', padx=5) self.label_no = tk.Label(self.frame1, text='No:', font=('', 12)) self.label_no.pack(side='top', anchor='w', padx=5, pady=5) self.entry_no= tk.Entry(self.frame1, font=('', 12), width=20) self.entry_no.pack(side='top', anchor='w', padx=5) self.frame2 = tk.Frame(self) self.button_ok = tk.Button(self.frame2, text='ok', width=8, font=('', 12)) self.button_cancel = tk.Button(self.frame2, text='cancel', width=8, font=('', 12)) self.button_cancel.pack(side='right', padx=10) self.button_ok.pack(side='right') self.frame1.pack(side='top', anchor='w') self.frame2.pack(side='top', anchor='w', pady=20, fill='x') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki örneğin bir alternatifi #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Sample Form') self.resizable(width=False, height=False) self.frame1 = tk.Frame(self) self.label_name = tk.Label(self.frame1, text='Adı Soyadı', width=9, anchor='e') self.label_name.pack(side='left', padx=(0, 5)) self.entry_name = tk.Entry(self.frame1, width=30) self.entry_name.pack(side='left') self.frame1.pack(side='top', anchor='w', padx=(0, 10), pady=(10, 5)) self.frame2 = tk.Frame(self) self.label_no = tk.Label(self.frame2, text='No', width=9, anchor='e') self.label_no.pack(side='left', padx=(0, 5)) self.entry_no = tk.Entry(self.frame2, width=30) self.entry_no.pack(side='left') self.frame2.pack(side='top', anchor='w', padx=(0, 10)) self.frame3 = tk.Frame(self) self.button_ok = tk.Button(self.frame3, text='Ok', width=5) self.button_cancel = tk.Button(self.frame3, text='Cancel', width=5) self.button_cancel.pack(side='right') self.button_ok.pack(side='right', padx=(0, 5)) self.frame3.pack(side='top', anchor='w', fill='x', pady=10, padx=(0, 10)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki formun sağ tarafa şkon eklenmniş biçimi #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL import ImageTk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Sample Form') self.resizable(width=False, height=False) self.frame1 = tk.Frame(self) self.frame2 = tk.Frame(self.frame1) self.frame3 = tk.Frame(self.frame2) self.frame4 = tk.Frame(self.frame2) self.frame5 = tk.Frame(self) self.label_name = tk.Label(self.frame3, text='Adı Soyadı', width=9, anchor='e') self.label_name.pack(side='left', padx=(0, 5)) self.entry_name = tk.Entry(self.frame3, width=30) self.entry_name.pack(side='left') self.label_no = tk.Label(self.frame4, text='No', width=9, anchor='e') self.label_no.pack(side='left', padx=(0, 5)) self.entry_no = tk.Entry(self.frame4, width=30) self.entry_no.pack(side='left') img = Image.open('Copy.png') img = img.resize((60, 50)) img = ImageTk.PhotoImage(img) label_image = tk.Label(self.frame1, image=img) label_image.image = img label_image.pack(side='right', padx=10) self.button_ok = tk.Button(self.frame5, text='Ok', width=7) self.button_cancel = tk.Button(self.frame5, text='Cancel', width=7) self.button_cancel.pack(side='right') self.button_ok.pack(side='right', padx=(0, 5)) self.frame1.pack(side='top', pady=(5, 0), padx=5) self.frame2.pack(side='top') self.frame3.pack(side='top', pady=5) self.frame4.pack(side='top') self.frame5.pack(side='top', fill='y', anchor='e', pady=(15, 10), padx=(0, 20)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ pack metodundaki fill side belirtilen yatay düşey yönün ters yönündeki açıklığı doldurmaktadır. Yani örneğin biz bir düğmeyi side='left' ile sola yaslamışsak bu durumda fill='y' ile onun düşey alanı doldurmasını sağlarız. side = 'left' ile fill='x' beklenen etkiyi yaratmaz. Ancak biz GUI elemanının yasladığımız yatay ya da düşeyle aynı yönde tüm alanı kaplamasını istiyorsak bu durumda expand=True parametresini de kullanmalıyız. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Pack Manager') self.geometry('320x200') self.button1 = tk.Button(self, text='1') self.button1.pack(side='left', fill='x', expand=True) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yatay ve düşey bakımdan birbirlerine ters olarak yaslanan GUI elemanları yatay ve düşeyi kendi aralarında bölüşmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Pack Manager') self.geometry('320x200') self.button1 = tk.Button(self, text='1') self.button1.pack(side='left', fill='y') self.button2 = tk.Button(self, text='2') self.button2.pack(side='top', fill='x') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Eğer GUI elemanının her iki yönde tüm geri kalan alanı kaplaması isteniyorsa fill='both' yapılmalıdır. Pencere daraltılıp genişletildiğinde GUI elemanın yeni boyutta yeniden tüm alanı kaplamasıiçin expand=True da gereklidir. Normal olarak yukarıda da belirtildiği gibi fill parametresi side parametresinin ters yönünde zaten GUI elemanın tüm alanı kaplamasını sağlar. Ancak diğer yönde kaplama için expand=True yapılmalıdır. Aşağıdaki örnekte Text boc GUI elemanının tüm pencerenin çalışma alanınını kaplaması sağlanmıştır. Burada side='left' kullanılmış olsa da fill='both' ce expand=True nedeniyle aslında yaslama herhangi bir yere yapılabilirdi. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Pack Manager') self.geometry('320x190') self.text = tk.Text(self) self.text.pack(side='left', fill='both', expand=True) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir chat ekranının pack geometri yöneticisi kullanılarak oluşturulması #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.scrolledtext class Root(tk.Tk): def __init__(self): super().__init__() self.title('Pack Manager') self.geometry('420x800') self.text = tk.scrolledtext.ScrolledText(self, font=('', 14)) self.text.pack(side='top', fill='both', expand=True, padx=10, pady=10) self.entry = tk.Entry(self, font=('', 14)) self.entry.pack(side='top', fill='x', padx=10) self.button = tk.Button(self, text='Send', width=8, height=3, command=self.button_click_handler) self.button.pack(side='top', pady=10, padx=20, fill='x') self.bind('', self.button_click_handler) def button_click_handler(self, event=0): s = self.entry.get() self.text.insert('end', s + '\n') self.entry.delete(0, 'end') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Basit bir hesap makinesi programı #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Calculator') self.resizable(width=False, height=False) self.frame_main = tk.Frame(self) self.frame_main.pack(side='top', expand=True, fill='both', padx=10, pady=10) self.textvariable = tk.StringVar() self.entry = tk.Entry(self.frame_main, justify='right', font='calibri 12', width=25, textvariable=self.textvariable, state='disabled', disabledbackground='white', disabledforeground='black') self.entry.pack(side='top', fill='x', pady=(0, 15)) self.frame1 = tk.Frame(self.frame_main) self.button_backspace = tk.Button(self.frame1, text='Back Space', width=10) self.button_backspace['command'] = lambda: self.button_handler(self.button_backspace) self.button_backspace.pack(side='left', padx=(0, 10)) self.button_clear = tk.Button(self.frame1, text='Clear All', width=10) self.button_clear['command'] = lambda: self.button_handler(self.button_clear) self.button_clear.pack(side='left') self.frame1.pack(side='top', anchor='w') self.frame2 = tk.Frame(self.frame_main) self.button_7 = tk.Button(self.frame2, text='7', width=4) self.button_7['command'] = lambda: self.button_handler(self.button_7) self.button_7.pack(side='left', padx=(0, 5)) self.button_8 = tk.Button(self.frame2, text='8', width=4) self.button_8['command'] = lambda: self.button_handler(self.button_8) self.button_8.pack(side='left', padx=(0, 5)) self.button_9 = tk.Button(self.frame2, text='9', width=4,) self.button_9['command'] = lambda: self.button_handler(self.button_9) self.button_9.pack(side='left', padx=(0, 5)) self.button_slash = tk.Button(self.frame2, text='/', width=4) self.button_slash['command'] = lambda: self.button_handler(self.button_slash) self.button_slash.pack(side='left', padx=(0, 5)) self.button_sqrt = tk.Button(self.frame2, text='sqrt', width=4) self.button_sqrt['command'] = lambda: self.button_handler(self.button_sqrt) self.button_sqrt.pack(side='left', padx=(0, 5)) self.frame2.pack(side='top', anchor='w', pady=(10, 5)) self.frame3 = tk.Frame(self.frame_main) self.button_4 = tk.Button(self.frame3, text='4', width=4) self.button_4['command'] = lambda: self.button_handler(self.button_4) self.button_4.pack(side='left', padx=(0, 5)) self.button_5 = tk.Button(self.frame3, text='5', width=4) self.button_5['command'] = lambda: self.button_handler(self.button_5) self.button_5.pack(side='left', padx=(0, 5)) self.button_6 = tk.Button(self.frame3, text='6', width=4) self.button_6['command'] = lambda: self.button_handler(self.button_6) self.button_6.pack(side='left', padx=(0, 5)) self.button_multiply = tk.Button(self.frame3, text='*', width=4) self.button_multiply['command'] = lambda: self.button_handler(self.button_multiply) self.button_multiply.pack(side='left', padx=(0, 5)) self.button_inverse = tk.Button(self.frame3, text='1/x', width=4) self.button_inverse['command'] = lambda: self.button_handler(self.button_inverse) self.button_inverse.pack(side='left', padx=(0, 5)) self.frame3.pack(side='top', anchor='w', pady=(10, 5)) self.frame4 = tk.Frame(self.frame_main) self.button_1 = tk.Button(self.frame4, text='1', width=4) self.button_1['command'] = lambda: self.button_handler(self.button_1) self.button_1.pack(side='left', padx=(0, 5)) self.button_2 = tk.Button(self.frame4, text='2', width=4) self.button_2['command'] = lambda: self.button_handler(self.button_2) self.button_2.pack(side='left', padx=(0, 5)) self.button_3 = tk.Button(self.frame4, text='3', width=4) self.button_3['command'] = lambda: self.button_handler(self.button_3) self.button_3.pack(side='left', padx=(0, 5)) self.button_subtract = tk.Button(self.frame4, text='-', width=4) self.button_subtract['command'] = lambda: self.button_handler(self.button_subtract) self.button_subtract.pack(side='left', padx=(0, 5)) self.button_pow = tk.Button(self.frame4, text='pow', width=4) self.button_pow['command'] = lambda: self.button_handler(self.button_pow) self.button_pow.pack(side='left', padx=(0, 5)) self.frame4.pack(side='top', anchor='w', pady=(10, 5)) self.frame5 = tk.Frame(self.frame_main) self.button_0 = tk.Button(self.frame5, text='0', width=4) self.button_0['command'] = lambda: self.button_handler(self.button_0) self.button_0.pack(side='left', padx=(0, 5)) self.button_plusminus = tk.Button(self.frame5, text='+/-', width=4) self.button_plusminus['command'] = lambda: self.button_handler(self.button_plusminus) self.button_plusminus.pack(side='left', padx=(0, 5)) self.button_dot = tk.Button(self.frame5, text='.', width=4) self.button_dot['command'] = lambda: self.button_handler(self.button_dot) self.button_dot.pack(side='left', padx=(0, 5)) self.button_plus = tk.Button(self.frame5, text='+', width=4) self.button_plus['command'] = lambda: self.button_handler(self.button_plus) self.button_plus.pack(side='left', padx=(0, 5)) self.button_equal = tk.Button(self.frame5, text='=', width=4) self.button_equal['command'] = lambda: self.button_handler(self.button_equal) self.button_equal.pack(side='left', padx=(0, 5)) self.frame5.pack(side='top', anchor='w', pady=(10, 5)) self.lastkey_op = False self.prev_val = 0 self.last_op = None def button_handler(self, button): if button['text'] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: if self.lastkey_op: if self.last_op != '=': self.prev_val = float(self.textvariable.get()) self.textvariable.set('') self.lastkey_op = False self.textvariable.set(self.textvariable.get() + button['text']) elif button['text'] in ['+', '-', '*', '/'] and not self.lastkey_op: if self.last_op != None: if self.last_op == '+': result = self.prev_val + float(self.textvariable.get()) elif self.last_op == '-': result = self.prev_val - float(self.textvariable.get()) elif self.last_op == '*': result = self.prev_val * float(self.textvariable.get()) elif self.last_op == '/': result = self.prev_val / float(self.textvariable.get()) self.textvariable.set(int(result) if int(result) == result else result) self.lastkey_op = True self.last_op = button['text'] elif button['text'] == '=' and self.last_op != None: if self.last_op == '+': result = self.prev_val + float(self.textvariable.get()) elif self.last_op == '-': result = self.prev_val - float(self.textvariable.get()) elif self.last_op == '*': result = self.prev_val * float(self.textvariable.get()) elif self.last_op == '/': result = self.prev_val / float(self.textvariable.get()) self.textvariable.set(int(result) if int(result) == result else result) self.last_op = None self.lastkey_op = True self.prev_val = 0 self.start_flag = False elif button['text'] == 'Clear All': self.lastkey_op = False self.prev_val = 0 self.last_op = None self.textvariable.set('') elif button['text'] == 'sqrt': result = float(self.textvariable.get()) ** 0.5 self.textvariable.set(int(result) if int(result) == result else result) elif button['text'] == '1/x': result = 1 / float(self.textvariable.get()) self.textvariable.set(int(result) if int(result) == result else result) elif button['text'] == 'Back Space': self.textvariable.set(self.textvariable.get()[:-1]) elif button['text'] == '+/-': result = float(self.textvariable.get()) * -1 self.textvariable.set(int(result) if int(result) == result else result) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da hazır bir Toolbar GUI eleman sınıfı yoktur. Programcının kendi toolbar'ını kendisinin yapması gerekmektedir. Bunun için bir Frame penceresi alınır. Bu pencere yukarıya yaslanır. Sonra o frame penceresinin içerisine düğmeler yerleştirilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk import tkinter.filedialog import tkinter.scrolledtext class Root(tk.Tk): def __init__(self): super().__init__() self.title('ToolBar Sample') self.geometry('480x320') self.menu_bar= tk.Menu() self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open', command=self.file_open_handler, accelerator='Ctrl+O') self.bind('', self.file_open_handler) self.file_popup.add_command(label='Save') self.file_popup.add_command(label='close') self.edit_popup = tk.Menu(tearoff=0) self.edit_popup.add_command(label='Cut') self.edit_popup.add_command(label='Copy') self.edit_popup.add_command(label='Paste') self.menu_bar.add_cascade(label='File', menu=self.file_popup) self.menu_bar.add_cascade(label='Edit', menu=self.file_popup) self['menu'] = self.menu_bar self.toolbar_frame = tk.Frame(self) self.toolbar_frame.pack(side='top', fill='x') image = tk.PhotoImage(file='open-64.png') self.toolbar_open_item = tk.Button(self.toolbar_frame, image=image, command=self.file_open_handler) self.toolbar_open_item.image = image self.toolbar_open_item.pack(side='left') self.toolbar_frame.pack(side='top', fill='x', padx=1) image = tk.PhotoImage(file='save-64.png') self.toolbar_save_item = tk.Button(self.toolbar_frame, image=image) self.toolbar_save_item.image = image self.toolbar_save_item.pack(side='left', padx=2) image = tk.PhotoImage(file='cut-64.png') self.toolbar_cut_item = tk.Button(self.toolbar_frame, image=image) self.toolbar_cut_item.image = image self.toolbar_cut_item.pack(side='left', padx=2) image = tk.PhotoImage(file='copy-64.png') self.toolbar_copy_item = tk.Button(self.toolbar_frame, image=image) self.toolbar_copy_item.image = image self.toolbar_copy_item.pack(side='left', padx=2) image = tk.PhotoImage(file='paste-64.png') self.toolbar_paste_item = tk.Button(self.toolbar_frame, image=image) self.toolbar_paste_item.image = image self.toolbar_paste_item.pack(side='left', padx=2) self.text = tk.scrolledtext.ScrolledText(self, font=(', 14')) self.text.pack(side='top', fill='both', expand=True) def file_open_handler(self, event=0): path = tk.filedialog.askopenfilename(title='Bir dosya seçiniz', filetypes=[('Python files', '*.py')]) if path != '': with open(path) as f: s = f.read() self.text.insert('1.0', s) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Grid geometri yöneticisi widget sınıflarının grid isimli metodu eşliğinde kullanılmaktadır. Baştan grid'in kaça kaç olacağı belirtilmez. Yerleşim grid metodunda row ve xolumn isimli parametresinde hücrenin yeri belirtilerek yapılır. Hücre yeri belirtilirken tipik olarak row ve column 0'dan başlatılmaktadır. Ancak numaralandırma bu biçimde yapılmak zorunda dğeildir. Fakat numaralar küçükten büyüğe sıraya dizildiğinde aslında bunlar ardışık satır ve sütun numaralarını belirtir. Aşağıdaki örnekte 9 tane düğme 3x3 bir grid'in hücrelerine yerleştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_00 = tk.Button(self, text='Button-00') self.button_01 = tk.Button(self, text='Button-01') self.button_02 = tk.Button(self, text='Button-02') self.button_10 = tk.Button(self, text='Button-10') self.button_11 = tk.Button(self, text='Button-11') self.button_12 = tk.Button(self, text='Button-12') self.button_20 = tk.Button(self, text='Button-20') self.button_21 = tk.Button(self, text='Button-21') self.button_22 = tk.Button(self, text='Button-22') self.button_00.grid(row=0, column=0) self.button_01.grid(row=0, column=1) self.button_02.grid(row=0, column=2) self.button_10.grid(row=1, column=0) self.button_11.grid(row=1, column=1) self.button_12.grid(row=1, column=2) self.button_20.grid(row=2, column=0) self.button_21.grid(row=2, column=1) self.button_22.grid(row=2, column=2) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ widget sınıflarının onların içerisindeki gridlerin özelliklerini ayarlamak için rowconfigure ve columnconfigure isimli metotları vardır. Bu metotların birinci parametreleri ilgili satır ya da sütunun numarasını belirtir. weight isimli parametreleri ise ilgili satır sütunun diğer satır ve sütunlara göre pencere genişletildiğinde ya da daraltıldığında hangi oranda büyütülüp küçültüleceğini belirtmektedir. Burada yalnızca weight değerleri girilmiş olan satır ya da sütunlar üzerinde ağırlıklı ortalama alınmaktadır. Daraltma durumunda yine weight ile belirtilen ağırklılı değerde daraltma yapılmaktadır. Aşağıdaki örnekte 3x3'lük bir grid'in birinci satırı ikinci ve üçüncü satırından genişlitildiğinde iki kat daha geniş gözükecektir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_00 = tk.Button(self, text='Button-00') self.button_01 = tk.Button(self, text='Button-01') self.button_02 = tk.Button(self, text='Button-02') self.button_10 = tk.Button(self, text='Button-10') self.button_11 = tk.Button(self, text='Button-11') self.button_12 = tk.Button(self, text='Button-12') self.button_20 = tk.Button(self, text='Button-20') self.button_21 = tk.Button(self, text='Button-21') self.button_22 = tk.Button(self, text='Button-22') self.button_00.grid(row=0, column=0, sticky='nwes') self.button_01.grid(row=0, column=1, sticky='nwes') self.button_02.grid(row=0, column=2, sticky='nwes') self.button_10.grid(row=1, column=0, sticky='nwes') self.button_11.grid(row=1, column=1, sticky='nwes') self.button_12.grid(row=1, column=2, sticky='nwes') self.button_20.grid(row=2, column=0, sticky='nwes') self.button_21.grid(row=2, column=1, sticky='nwes') self.button_22.grid(row=2, column=2, sticky='nwes') self.rowconfigure(0, weight=2) self.rowconfigure(1, weight=1) self.rowconfigure(2, weight=1) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Üst pencere genişletildiğinde ya da daraltıldığında grid hücerelerinin genişletilmesi ve daraltılması için rowconfigure ve/veya columnconfigure ile wight değerlerini belirtilemsi gerekir. Eğer satır ya da sütun için weight değeri belirtilmezse üst pencere daraltıldığında ya da genişletildiğinde ilgili satır ya da sütunda bir değişiklik olmaz. Aşağıdaki örnekte 3x3 lik grid içerisindeki tüm satırların ve sütunların weight değerleri aynı yapılmıştır. Bu durumda satırlar ve sütunlar pencere daraltıldığında ya da genişletildiğinde aynı oranda büyütülüp küçültülürler. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_00 = tk.Button(self, text='Button-00') self.button_01 = tk.Button(self, text='Button-01') self.button_02 = tk.Button(self, text='Button-02') self.button_10 = tk.Button(self, text='Button-10') self.button_11 = tk.Button(self, text='Button-11') self.button_12 = tk.Button(self, text='Button-12') self.button_20 = tk.Button(self, text='Button-20') self.button_21 = tk.Button(self, text='Button-21') self.button_22 = tk.Button(self, text='Button-22') self.button_00.grid(row=0, column=0, sticky='nwes') self.button_01.grid(row=0, column=1, sticky='nwes') self.button_02.grid(row=0, column=2, sticky='nwes') self.button_10.grid(row=1, column=0, sticky='nwes') self.button_11.grid(row=1, column=1, sticky='nwes') self.button_12.grid(row=1, column=2, sticky='nwes') self.button_20.grid(row=2, column=0, sticky='nwes') self.button_21.grid(row=2, column=1, sticky='nwes') self.button_22.grid(row=2, column=2, sticky='nwes') self.rowconfigure(0, weight=1) self.rowconfigure(1, weight=1) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ grid metodundaki sticky parametresi gridin ilgi hücresi büyütüldüğünde ya da küçültüldüğünde o hücre içerisindeki GUI elemanın hücreye nasıl yerleşeceği üzerinde etkili olmaktadır. Bu parametre 'w', 'e', 'n', 's' değerlerinin bir ya da birden fazlasını alabilmektedir. stciky='nwse' ilgili GUI elemanın hücreyi tam olarak kaplayacağı anlamına gelmektedir. Buradaki yön belirten harfler herhangi bir sırada belirtilebilirler. Aşağıdaki örnekte değişik stick belirlemelri yapılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_00 = tk.Button(self, text='Button-00') self.button_01 = tk.Button(self, text='Button-01') self.button_02 = tk.Button(self, text='Button-02') self.button_10 = tk.Button(self, text='Button-10') self.button_11 = tk.Button(self, text='Button-11') self.button_12 = tk.Button(self, text='Button-12') self.button_20 = tk.Button(self, text='Button-20') self.button_21 = tk.Button(self, text='Button-21') self.button_22 = tk.Button(self, text='Button-22') self.button_00.grid(row=0, column=0, sticky='ewns') self.button_01.grid(row=0, column=1, sticky='ew') self.button_02.grid(row=0, column=2, sticky='ns') self.button_10.grid(row=1, column=0) self.button_11.grid(row=1, column=1) self.button_12.grid(row=1, column=2) self.button_20.grid(row=2, column=0) self.button_21.grid(row=2, column=1) self.button_22.grid(row=2, column=2) self.rowconfigure(0, weight=1) self.rowconfigure(1, weight=1) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ rowconfigure ve columnconfigure metotlarının minsize isimli parametreleri ilgili satır ya da sütununun en küçük uzunluğunu belirlemek için kullanılmaktadır. İşin başında hücrelerin bu uzunluklarda yaratılması da yine minsize parametresiyle sağlanmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_x = tk.Button(self, text='X') self.button_y = tk.Button(self, text='Y') self.button_z = tk.Button(self, text='Z') self.button_k = tk.Button(self, text='K') self.button_x.grid(row=0, column=0, columnspan=2, sticky='nsew') self.button_y.grid(row=0, column=2, rowspan=2, sticky='nsew') self.button_z.grid(row=1, column=0, sticky='nsew') self.button_k.grid(row=1, column=1, sticky='nsew') self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.rowconfigure(0, weight=2, minsize=100) self.rowconfigure(1, weight=1, minsize=50) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ grid metodunda yine pack metodunda olduğu gibi padx ve pady parametreleri vardır. Bu parametreler hücre içerisindeki GUI elemanın saol, sağ, üst ve altında ne kadar boşluk bırakılacağını belritmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.button_x = tk.Button(self, text='X') self.button_y = tk.Button(self, text='Y') self.button_z = tk.Button(self, text='Z') self.button_k = tk.Button(self, text='K') self.button_x.grid(row=0, column=0, columnspan=2, sticky='nsew', padx=10, pady=10) self.button_y.grid(row=0, column=2, rowspan=2, sticky='nsew', padx=20) self.button_z.grid(row=1, column=0, sticky='nsew', padx=10) self.button_k.grid(row=1, column=1, sticky='nsew', padx=10) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.rowconfigure(0, weight=2, minsize=100) self.rowconfigure(1, weight=1, minsize=50) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Veri girişi için basit form penceresinin yalnızca grid kullanılarak gerçekleştirilmesi #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.resizable(width=False, height=False) self.label_name = tk.Label(self, text='Adı Soyadı') self.entry_name = tk.Entry(self, width=30) self.label_no = tk.Label(self, text='No') self.entry_no = tk.Entry(self, width=30) self.button_ok = tk.Button(self, text='Ok', width=5) self.button_cancel = tk.Button(self, text='Cancel', width=5) image = tk.PhotoImage(file='open-64.png') self.label_image = tk.Label(self, image=image) self.label_image.image = image self.label_name.grid(row=0, column=0, sticky='e', padx=(0, 10)) self.entry_name.grid(row=0, column=1, pady=8) self.label_no.grid(row=1, column=0, sticky='e', padx=(0, 10)) self.entry_no.grid(row=1, column=1, pady=(0, 10)) self.label_image.grid(row=0, column=2, rowspan=2, padx=10) self.button_ok.grid(row=2, column=1, sticy='e', pady=(0, 10)) self.button_cancel.grid(row=2, column=2, padx=(0, 10), pady=(0, 10)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki GUI yerleşiminin aynısı değişik biçimlerde de yapılabilir. Örneğin üst kısım bir frame içerisine grid ile yerleştirilirken alt kısım (yani düğmeler) bir frame içeriisne pack ile yerleştirilebilir. Bu iki frame de yine ana pencereye pack ile yerleştirilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.resizable(width=False, height=False) self.frame1 = tk.Frame(self) self.label_name = tk.Label(self.frame1, text='Adı Soyadı') self.entry_name = tk.Entry(self.frame1, width=30) self.label_no = tk.Label(self.frame1, text='No') self.entry_no = tk.Entry(self.frame1, width=30) image = tk.PhotoImage(file='open-64.png') self.label_image = tk.Label(self.frame1, image=image) self.label_image.image = image self.label_name.grid(row=0, column=0, sticky='e', padx=(0, 10)) self.entry_name.grid(row=0, column=1, pady=8) self.label_no.grid(row=1, column=0, sticky='e', padx=(0, 10)) self.entry_no.grid(row=1, column=1, pady=(0, 10)) self.label_image.grid(row=0, column=2, rowspan=2, padx=10) self.frame2 = tk.Frame(self) self.button_ok = tk.Button(self.frame2, text='Ok', width=8) self.button_cancel = tk.Button(self.frame2, text='Cancel', width=8) self.button_cancel.pack(side='right') self.button_ok.pack(side='right', padx=(0, 10)) self.frame1.pack(side='top', fill='x') self.frame2.pack(side='top', fill='x', pady=20, padx=20) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bağlam menüsü oluşturmak için önce yine tk.Menu sınıfı kullanılarak bir popup pencere oluşturulur. Sonra farenin sağ tuşuna basıldığında Menu sınıfının post metodu ile bağlam menüsü açılır. Ancak post metodunda bağlam menüsüün açılacağı koordinat çalışma alanı orijinli değil masaüstü orijinli olarak verilmelidir. Masaüstü orijinli koordinatlar event sınıfının x_root ve y_root örneközniteliklerinden elde edilebilir. Ayrıca handler fonksiyonundan 'break' biiminde bir yazıyla geri dönülmesi bazı nedenlerden dolayı gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.context_menu = tk.Menu(tearoff=0) self.context_menu.add_command(label='Open', command=self.menu_open_handler) self.context_menu.add_command(label='Close', command=self.menu_close_handler) self.context_menu.add_command(label='Exit', command=self.menu_exit_handler) self.bind('', self.context_menu_handler) def context_menu_handler(self, event): self.context_menu.post(event.x_root, event.y_root) return 'break' def menu_open_handler(self): print('Open') def menu_close_handler(self): print('Close') def menu_exit_handler(self): self.destroy() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Farklı yerlere tıklandığında farklı bağlam menülerinin çıkartılması istenebilmektedir. Eğer form üzerinde alt pencereler varsa bind işlemi o pencerelere yönelik nesneler kullanılarak yapılırsa bu işlem kolay olur. Fakat bu biçimde alt pencereler yoksa programcı tıklanan yeri belirleyerek manuel biçimde istediği bağlam menüsünü göstermelidir. Aşağıdaki örnekte pencerenin sol ve sağ bölgesinde farklı bağlam menüleri çıkartılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.context_menu1 = tk.Menu(tearoff=0) self.context_menu1.add_command(label='Open', command=self.menu_open_handler) self.context_menu1.add_command(label='Close', command=self.menu_close_handler) self.context_menu1.add_command(label='Exit', command=self.menu_exit_handler) self.context_menu2 = tk.Menu(tearoff=0) self.context_menu2.add_command(label='Cut', command=self.menu_cut_handler) self.context_menu2.add_command(label='Copy', command=self.menu_copy_handler) self.context_menu2.add_command(label='Paste', command=self.menu_paste_handler) self.bind('', self.context_menu_handler) def context_menu_handler(self, event): if event.x <= self.winfo_width() / 2: self.context_menu1.post(event.x_root, event.y_root) else: self.context_menu2.post(event.x_root, event.y_root) return 'break' def menu_open_handler(self): print('Open') def menu_close_handler(self): print('Close') def menu_exit_handler(self): self.destroy() def menu_cut_handler(self): print('Cut') def menu_copy_handler(self): print('Copy') def menu_paste_handler(self): print('Paste') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da bazı GUI elemanlarının scroll özellikleri kendi içlerinde bulunmamaktadır. (ScrolledText isimli bir GUI elemanının daha sonra eklendiğini anımsayınız) Tkinter'da scroll çubuğu ayrı bir GUI elemandır. Programcı örneğin Listbox GUI elemanına bir scroll çubuğu iliştirecekse bu iki GUI elemanını birbirlerine işlevsel biçimde bağlamalıdır. Bu bağlama sonucunda Listbox'taki kaydırmalar Scrollbar'a Scrollbar'daki kaydırmalar ise Listbox'a yansıtılır. Bunu sağlamak için iki şey yapılmalıdır: 1) Scrollbar sınıfının command isimli konfigürasyon parametresine Listbox sınıfının yview ya da xview metotları atanır. 2) Listbox sınıfının xscrollcommand ya da yscrollcommand konfigürasyon parametrelerine Scrollbar sınıfının set metodu atanır. Tabii görsel olarak iki GUI elemanının pack ile yan yana getirilmesi uygun olur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.listbox = tk.Listbox(self, height=20) self.listbox.pack(side='left', fill='y') for i in range(100): self.listbox.insert(tk.END, i) self.scrollbar = tk.Scrollbar(self, command=self.listbox.yview) self.scrollbar.pack(side='left', fill='y') self.listbox.configure(yscrollcommand=self.scrollbar.set) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tabii ilgili GUI eleman Scroolbar nesnesi ile birleştirilerek bir sınıf biçiminde de oluşturulabilir #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.slb = ScrolledListbox(self, bg='yellow', height=20, width=10, font=('', 14)) self.slb.pack(side='top') for i in range(100): self.slb.listbox.insert(tk.END, i) self.button_ok = tk.Button(self, text='Ok', width=8, command=self.button_ok_handler) self.button_ok.pack(side='top', pady=20) def button_ok_handler(self): s = self.slb.listbox.get('active') print(s) class ScrolledListbox(tk.Frame): def __init__(self, master, *args, **kwargs): super().__init__(master) self.listbox = tk.Listbox(self, *args, **kwargs) self.listbox.pack(side='left', fill='y') self.scrollbar = tk.Scrollbar(self, command=self.listbox.yview) self.scrollbar.pack(side='left', fill='y') self.listbox.configure(yscrollcommand=self.scrollbar.set) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Pencerenin kapatma ikonuna tıklandığında işlem yapabilmek için ana pencere nesnesi ile protocol isimli metoduna 'WM_DELETE_WINDOW' parametresiyle çağrılması gerekir. Ancak bu mesaj işlenirse pencerenin kapatılması artık Programcının sorumluluğunda olur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.protocol('WM_DELETE_WINDOW', self.window_delete_handler) def window_delete_handler(self): print('closing...') self.destroy() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da çizim yapmak için Canvas denilen özel bir pencere (widget) kullanılmaktadır. Programcı tk.Canvas türünden bir nesne yaratır. Sonra bu Canvas sınıfının create_xxx biçiminde isimlendirilmiş olan metotlarını çağırır. Aşağıda bu işlemin bir örneği görülmektedir: #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.place(x=10, y=10, width=200, height=200) self.canvas.create_line(0, 0, 100, 100) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tabii aslında genellikle programcılar canvas'ın tüm çalışma alanını kaplamasını isterler. Böylece çisim sanki ana pencereye yapılıyormuş gibi bir etki oluşur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self) self.canvas.pack(fill='both', expand=True) self.canvas.create_line(0, 0, 100, 100) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yazboz tahtası uygulaması şöyle yapılır: Fare ile tıklandığında tıklanan yer seınıfın örnek niteiklerinde saklanır. Sonra fare her sürüklendiğinde önce noktayla sonraki nokta arasına bir doğru parçası çizilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk LINE_WIDTH = 3 LINE_COLOR = 'red' class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.pack(fill='both', expand=True) self.bind('', self.button_press_handler) self.bind('', self.button_motion_handler) def button_press_handler(self, event): self.prevx = event.x self.prevy = event.y def button_motion_handler(self, event): self.canvas.create_line(self.prevx, self.prevy, event.x, event.y, fill=LINE_COLOR, width=LINE_WIDTH) self.prevx = event.x self.prevy = event.y root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Canvas sınıfının create_arc isimli metodu yay çizmek için kullanılmaktadır. Burada ilk 4 parametre bir dikdörtgen koordinatını alır. Bu dikdörtgenin iç teğet elipsine ilişkin yay çizdirilmektedir. start ile başlangıç açısı, extent ile süpürme açısı verilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.pack(fill='both', expand=True) self.canvas.create_arc(100, 100, 200, 200, width=3, start=0, extent=100, style=tk.ARC) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Canvas sınıfının create_image isimli metodu bizden bir koordinat ve image isimli parametresiyle çizdirilecek resmi alır. Resmi resmin ortası o koordinatta olacak biçimde çizdirir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.pack(fill='both', expand=True) image = Image.open('AbbeyRoad.jpg').resize((200, 200)) self.image = PhotoImage(image) self.canvas.bind('', self.button_press_handler) def button_press_handler(self, event): self.canvas.create_image(event.x, event.y, image=self.image) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir resmin taşınması label içerisindeki bir resmin taşınmasından daha zordur. Çünkü resmin konumunun hit testing için tutulması gerekir. Oysa widget'ların üzerine tıklanıp tıklanmadığı zaten sistem tarafından belirlenmketdir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from PIL import Image from PIL.ImageTk import PhotoImage IMAGE_WIDTH = 200 IMAGE_HEIGHT = 200 class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.pack(fill='both', expand=True) image = Image.open('AbbeyRoad.jpg').resize((IMAGE_WIDTH, IMAGE_HEIGHT)) self.image = PhotoImage(image) self.canvas.bind('', self.button_right_press_handler) self.canvas.bind('', self.button_left_press_handler) self.canvas.bind('', self.button_left_motion_handler) def button_right_press_handler(self, event): self.image_id = self.canvas.create_image(event.x, event.y, image=self.image) self.imagex = event.x - IMAGE_WIDTH / 2 self.imagey = event.y - IMAGE_HEIGHT / 2 def button_left_press_handler(self, event): self.prevx = event.x self.prevy = event.y def button_left_motion_handler(self, event): if self.pt_in_rect(self.imagex, self.imagey, IMAGE_WIDTH, IMAGE_HEIGHT, event.x, event.y): deltax = event.x - self.prevx deltay = event.y - self.prevy self.canvas.move(self.image_id, deltax, deltay) self.prevx = event.x self.prevy = event.y self.imagex += deltax self.imagey += deltay @staticmethod def pt_in_rect(x, y, width, height, ptx, pty): return x < ptx < x + width and y < pty < y + height root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Canvas sınıfının create_polygon isimli metodu bir demet listesini parametre olarak alır. Oradaki noktaları birleştirir. Son nokta ile ilk noktayı da birleştirerek kapalı bir şekil elde eder. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.pack(fill='both', expand=True) self.canvas.create_polygon([(10, 10), (200, 100), (50, 90)], fill='red', outline='black') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Canvas sınıfının create_rectangle metodu dikdörtgen, create_oval metodu da bir dikdörtgenin iç teğet elipsini çizmektedir. Genel olarak bu metotların fill parametreleri iç bölgeyi boyamak için kullanılan rengi, outline metodu ise dış çizgi rengini belitmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.canvas = tk.Canvas(self, bg='yellow') self.canvas.pack(fill='both', expand=True) self.canvas.create_rectangle(100, 100, 200, 200, fill='red', outline='black') self.canvas.create_oval(100, 100, 200, 200, outline='blue') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tkinter.ttk alt paketinde bir grup modern görünüşlü GUI elemanı bulunmaktadır. Bu GUI elemanlarına "ttk GUI elemanları (ttk widgets)" denilmektedir. Buradaki GUI elemanlarının bir bölümü tk paketindeki GUI elemanlarına çok benzerdir. Ancak diğer bölümü tk paketinde olmayan GUI elemanlarından oluşmaktadır. ttk paketindeki GUI elemanlarının foreground, background, font gibi seçenekleri yoktur. Bu işlemler Style denilen bir yöntem ile gerçekleştirilmektedir. Aşağıdaki örnekte ttk.Button GUI elemanı oluştrulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack() self.button_ok = ttk.Button(self, text='Cancel', command=self.button_cancel_handler) self.button_ok.pack() def button_ok_handler(self): print('Ok') def button_cancel_handler(self): print('Cancel') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Entry GUI elemanı tk.Entry GUI elemanına kullanım olarak çok benxemektedir. Ancak foregound, background gibi seçenekler Style yoluyla gerçekleştirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.text_var = tk.StringVar() self.entry = ttk.Entry(self, width=30, font='Arial 12', textvariable=self.text_var) self.entry.pack(side='top', anchor='w', padx=10, pady=10) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', anchor='w', padx=10, pady=10) def button_ok_handler(self): s = self.text_var.get() print(s) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Label GUI elemanı da tk.Label GUI elemanına benzemektedir. Bu elemanda font, foreground ve background seçenekleri de kullanılabilmektedir. Ancak bunlar için "fg" ve "bg" kısaltmaları geçeri değildir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.text_var = tk.StringVar() self.label = ttk.Label(self, text='Adı Soyadı', font='Arial 12', foreground='blue') self.label.pack(side='top', anchor='w', padx=10, pady=10) self.entry = ttk.Entry(self, width=30, font='Arial 12', textvariable=self.text_var) self.entry.pack(side='top', anchor='w', padx=10, pady=10) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', anchor='w', padx=10, pady=10) def button_ok_handler(self): s = self.text_var.get() print(s) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Checkbutton GUI elemanı da tk.Checkbutton GUI elemanı gibidir. Ancak font, background ve foreground seçenekleri bulunmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.check_button_var = tk.BooleanVar() self.check_button = ttk.Checkbutton(self, text='E-posta gönderilsin', variable=self.check_button_var) self.check_button.pack(side='top', anchor='w', padx=10, pady=10) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', anchor='w', padx=10, pady=10) def button_ok_handler(self): print('Checked' if self.check_button_var.get() else 'Unchecked') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Radyo düğmelerinin kullanılması ttk'da tk'dakine benzerdir. Ancak ttk.Radiobutton'da font, background ve foreground gibi bazı seçenekler yoktur. Bu işlemler ttk.Style sınıfı yardımıyla gerçekleştirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.radio_button_var = tk.StringVar() self.radio_button_a = ttk.Radiobutton(self, text='A', variable=self.radio_button_var, value='A') self.radio_button_a.pack(side='top', anchor='w', padx=10, pady=5) self.radio_button_b = ttk.Radiobutton(self, text='B', variable=self.radio_button_var, value='B') self.radio_button_b.pack(side='top', anchor='w', padx=10, pady=5) self.radio_button_c = ttk.Radiobutton(self, text='C', variable=self.radio_button_var, value='C') self.radio_button_c.pack(side='top', anchor='w', padx=10, pady=5) self.radio_button_d = ttk.Radiobutton(self, text='D', variable=self.radio_button_var, value='D') self.radio_button_d.pack(side='top', anchor='w', padx=10, pady=5) self.radio_button_e = ttk.Radiobutton(self, text='E', variable=self.radio_button_var, value='E') self.radio_button_e.pack(side='top', anchor='w', padx=10, pady=5) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', anchor='w', padx=10, pady=10) def button_ok_handler(self): result = self.radio_button_var.get() print(f'{result} selected') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Combobox combobox GUI elemanını oluşturmak için kullanılmaktadır. Combobox içerisindeki yazılar "values" isimli seçenek ile oluşturulur. Bu seçenek bir demet biçimindedir. Combobox'ın edit alanındaki yazı sınıfın get metoduyla elde edilmektedir. Combobox'ın edit alanı default durumda değiştirilebilir biçimdedir. Bu edit alanının değiştirilebilir olması istenmiyorsa state='readonly' kullanılmalıdır. Sınıfın height elemanı en fazla kaç satır içerik gösterileceğii belirtmektedir. Combobox'tan bir eleman seçildiğinde '<>' isimli bu GUI elemanına özgü bir event (virtual event) tetiklenmektedir. wrap seçeneği sona ya da başa gelindiğinde yeniden başa ya da sona dönülüp dönülmeyeceğini belirtmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') cities = ('Ankara', 'Adana', 'İzmir', 'Eskişehir', 'Gaziantep', 'Urfa', 'Trabzon') self.combobox = ttk.Combobox(self, values=cities, height=10, state='readonly') self.combobox.pack(side='left', anchor='n', padx=10, pady=10) self.combobox['values'] += ('İstanbul', 'Kayseri') self.combobox.current(3) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='left', anchor='n', padx=10, pady=10) def button_ok_handler(self): s = self.combobox.get() print(s) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Spinbox yukarı aşağı sayacı biçimindeki GUI elemanlara deniilmektedir. Hem tk paketinde hem de ttk paketinde Spinbox GUI elemanı bulumaktadır. Gösterilecek değerlerin skalası from_ ve to seçenekleriyle belirlenmektedir. incremenet seçeneği artım azaltım miktarını belirtir. wrap seçeneği sona ya da başa gelindiğinde yeniden başa ya da sona dönülüp dönülmeyeceğini belirtmektedir. Spinbox yine edit edilebilir olmaktan state='readonly' seçeneği ile çıkartılabilir. get metodu spinbox içerisindeki değeri string olarak almaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.spinbox = ttk.Spinbox(self, from_=1, to=10, width=10, state='readonly', increment=1, wrap=True, font=('', 12)) self.spinbox.pack(side='left', anchor='n', padx=10, pady=10) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='left', anchor='n', padx=10, pady=10) def button_ok_handler(self): val = self.spinbox.get() print(val) print(type(val)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aslında spinbox yazılardan da oluşturulabilir. Bunun için from_ ve to yerine values seçeneği kullanılır. Spinbox'ta başlangıçta görüntülenecek değer set metoduyla ayarlanabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') categories = ('Çok az', 'Az', 'Orta', 'Çok', 'Çok fazla') self.spinbox = ttk.Spinbox(self, values = categories, width=10, state='readonly', increment=1, wrap=True, font=('', 12)) self.spinbox.pack(side='left', anchor='n', padx=10, pady=10) self.spinbox.set('Orta') self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='left', anchor='n', padx=10, pady=10) def button_ok_handler(self): val = self.spinbox.get() print(val) print(type(val)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tk.Spinbox kullanımı ile ttk.Spinbox kullanımı biribirine çok benzerdir. Bazı küçük farklılıklar için dokğmanlara başvurabilirisniz. (Örneğin tk.Spinbox'ta set metodu yerine setvar metodu kullanılmaktadır.) Genel görüntü olarak ttk.Spinbox, tk.Spinbox'tan daha iyidir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') categories = ('Çok az', 'Az', 'Orta', 'Çok', 'Çok fazla') self.spinbox = tk.Spinbox(self, values = categories, width=10, state='readonly', increment=1, wrap=True, font=('', 12)) self.spinbox.pack(side='left', anchor='n', padx=10, pady=10) self.spinbox.setvar('Orta') self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='left', anchor='n', padx=10, pady=10) def button_ok_handler(self): val = self.spinbox.get() print(val) print(type(val)) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Progressbar GUI elemanı tipik olarak bir sürecin ne kadarının tamamlandığını görsel biçimde göstermek için kullanılmaktadır. Sınıfın "maximum" isimli seçeneği progressbar'ın son değerini belirtir. "value" seçeneği progressbar'ın ne kadarının doldurulacağını belirtmektedir. Buradaki value değeri maximum değeriyle orantılı bir biçimde boyama yapar. Aşağıdaki örnekte henüz görülmemiş olan thread kullanılmıştır. Çünkü progressbarın ilerletilmesi genellikle başka bir thread tarafından yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk import tkinter.messagebox import threading class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.progressbar = ttk.Progressbar(self, maximum=100) self.progressbar.pack(side='top', fill='x', anchor='w', padx=10, pady=10) self.button_start = ttk.Button(self, text='Start', command=self.button_start_handler) self.button_start.pack(side='top', anchor='w', padx=10, pady=10) def button_start_handler(self): self.timer = threading.Timer(0.1, self.timer_handler) self.timer.start() def timer_handler(self): if self.progressbar['value'] >= self.progressbar['maximum']: tk.messagebox.showinfo('Message', 'Installation completed') return self.progressbar['value'] += 5 self.timer = threading.Timer(0.1, self.timer_handler) self.timer.start() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Şiddet ya da miktar ayarlamak çin kullanılan GUI elemanlara genellikle "slider" ya da "trackbar" denilmektedir. Tkinter'da bunun ismi "Scale" biçimindedir. Bir scale nesnesi yine from_ ve to değerleri belirtilerek yaratılır. Yürütecin değeri get metoduyla alınıp set metoduyla set edilebilmektedir. Yine veri bağlama işlemi variable üzerinden IntVar ya da DoubleVar türleriyle yapılabilir. Aslında Scale hem tk paketinde hem de ttk paketinde bulunmaktadır. Ancak ttkçScale daha modern bir görübtüye sahiptir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.scale_var = tk.DoubleVar(value=20) self.scale = ttk.Scale(self, from_=0, to=100, variable=self.scale_var) self.scale.pack(side='top', fill='x', padx=20, pady=20) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', pady=10) def button_ok_handler(self): value = self.scale.get() print(value) value = self.scale_var.get() print(value) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Scale GUI elemanında yürüteci hareket ettirirken şiddet ayarlaması gerçek zamanlı yapılabilmektedir. Bunun için Scale sınıfının command metodu kullanılır. Bu metodun yürütecin o anki konumunu string olarak veren bir parametresi de vardır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('320x200') self.scale_var = tk.DoubleVar(value=20) self.scale = ttk.Scale(self, from_=0, to=100, variable=self.scale_var, command=self.scale_motion_handler) self.scale.pack(side='top', fill='x', padx=20, pady=20) self.label = ttk.Label(self, text='20', font=('', 16)) self.label.pack(side='top', pady=10) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', pady=10) def button_ok_handler(self): value = self.scale.get() print(value) value = self.scale_var.get() print(value) def scale_motion_handler(self, value): self.label['text'] = str(int(float(value))) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Notebook isimli GUI elemanına diğer framework'lerde "Tab Control" ya da "Property Sheet" denilmektedir. Bu GUI elemanında tablar tipik olarak Frame pencerelerindne oluşturulur. Bir taba basıldığında o taba ilişkin Frame penceresi aynı alan içerisinde aktif hale getirilir. Aşağıdaki örnekte Spider'daki bir tab kontrolünün benzeri oluşturulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('640x480') self.resolution_var = tk.IntVar(value=1) self.notebook = ttk.Notebook(self) self.notebook.pack(side='top', fill='both', expand=True) self.button_ok = ttk.Button(self, text='Ok', width=10, command=self.button_ok_handler) self.button_ok.pack(side='top', pady=10) self.frame_interface = ttk.Frame() self.notebook.add(self.frame_interface, text='Interface') self.notebook.bind('<>', self.notebook_tab_changed_handler) self.frame_advanced_settings = ttk.Frame() self.notebook.add(self.frame_advanced_settings, text='Advanced settings') self.label_resolution = ttk.Label(self.frame_interface, text='Screen resolution', font='Arial 10 bold') self.label_resolution.pack(side='top', anchor='w', padx=10, pady=10) self.radiobutton_normal = ttk.Radiobutton(self.frame_interface, text='Normal', value=1, variable=self.resolution_var) self.radiobutton_enable= ttk.Radiobutton(self.frame_interface, text='Enable', value=2, variable=self.resolution_var) self.radiobutton_normal.pack(side='top', anchor='w', padx=10) self.radiobutton_enable.pack(side='top', anchor='w', padx=10) self.frame1 = ttk.Frame(self.frame_interface) self.radiobutton_custom= ttk.Radiobutton(self.frame1, text='Custom', value=3, variable=self.resolution_var, command=lambda: self.entry_dpi.configure(state='normal') ) self.radiobutton_custom.pack(side='left') self.entry_dpi = ttk.Entry(self.frame1, width=50, state='disabled') self.entry_dpi.pack(side='left', padx=(30,), fill='x', expand=True) self.frame1.pack(side='top', anchor='w', padx=10, fill='x') self.label_general = ttk.Label(self.frame_advanced_settings, text='General', font='Arial 10 bold') self.label_general.pack(side='top', anchor='w', padx=10, pady=10) self.frame2 = ttk.Frame(self.frame_advanced_settings) self.label_language = ttk.Label(self.frame2, text='Language:') self.label_language.grid(row=0, column=0, sticky='w') self.combobox_language = ttk.Combobox(self.frame2, values=['Almanca', 'İngilizce', 'Türkçe'], width=10, state='readonly') self.combobox_language.grid(row=0, column=1, padx=10) self.combobox_language.current(2) self.label_rendering = ttk.Label(self.frame2, text='Rendering Engine:') self.label_rendering.grid(row=1, column=0) self.combobox_rendering = ttk.Combobox(self.frame2, values=['Software', 'Desktop', 'Automatic'], width=10, state='readonly') self.combobox_rendering.grid(row=1, column=1, padx=10, sticky='w', pady=10) self.combobox_rendering.current(0) self.frame2.pack(side='top', anchor='w', padx=10) def notebook_tab_changed_handler(self, event): print('Tab changed') def button_ok_handler(self): self.notebook.select(1) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tk ve ttk paketlerindeki GUI elemanlarının hepsinin cursor isimli bir seçeneği vardır. Bu cursor seçeneği fare o GUI eleman penceresinin üzerine getirildiğinde görüntülenecek fare okunu belirtir. (GUI dünyasında fare okuna "cursor" denilmektedir.) cursor seçeneğine atanabilecek cursor ok şekilleri dokümanlarda belirtilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('640x480') self.button_ok = ttk.Button(self, text='Ok', cursor='pencil') self.button_ok.place(x=10, y=10, width=100, height=100) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tabii bir pencere içerisinde de fare okunu o andaki konuma dayalı olarak değiştirebiliriz. Aşağıdaki örnekte fare pencerenin sol bölgesine çekildiğinde ayrı bir fare oku, sağ bölgesine çekildiğinde ayrı bir fare oku gösterilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('640x480') self.bind('', self.motion_handler) def motion_handler(self, event): if event.x < self.winfo_width() / 2: self['cursor'] = 'plus' else: self['cursor'] = 'crosshair' root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk.Treeview GUI elemanı hiyerarşik olguları ağaç biçiminde görüntülemek için kullanılmaktadır. Burada ağaca eklenen her düğümün bir id değeri vardır. Eklemanin neyin altına yapılacağı bu id değeri ile belirlenir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.treeview = ttk.Treeview(self) self.treeview.pack(side='top', anchor='w', padx=10, pady=10) id_department = self.treeview.insert('', index=0, text='Bölümler') self.treeview.insert(id_department , index='end', text='İnsan Kaynakları') self.treeview.insert(id_department , index='end', text='Kalite Kontrol') self.treeview.insert(id_department , index='end', text='Yönetim') self.treeview.insert(id_department , index='end', text='İmalat') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aslında insert metodunun üçüncü parametresi ile insert edilen düğümün id değeri yazı olarak girilebilmektedir. Ancak eğer bir giriş yapılmazsa id değerleri otomatik olarak üretilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Treeview') self.geometry('800x600') self.treeview = ttk.Treeview(self) self.treeview.pack(side='top', anchor='w', padx=10, pady=10, fill='both', expand=True) id_department = self.treeview.insert('', index=0, text='Bölümler') self.treeview.insert(id_department, index='end', text='İnsan Kaynakları') self.treeview.insert(id_department, index='end', text='Kalite Kontrol') self.treeview.insert(id_department, index='end', text='Yönetim') self.treeview.insert(id_department , index='end', iid='id_production', text='İmalat') self.treeview.insert('id_production', index='end', text='Sabah') self.treeview.insert('id_production', index='end', text='Öğle') self.treeview.insert('id_production', index='end', text='Akşam') self.button_ok = ttk.Button(self, text='Ok') self.button_ok.pack(side='top', pady=20) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Treeview bir eleman seçildiğinde '<>' isimli event tetiklenmektedir. O anda seçili elemanlar Treeview sınıfının selection isimli metodu çağrılarak elde edilir. Default durumda bird'en fazla eleman seçilebilmektedir. selection metodu bize seçilmiş elemanların id'lerini bir demet dizisi olarka vermektedir. '<>' bir düğüm açıldığında, '<>' ise bir düğüm kapatıldığında tetiklenmektedir. Bir düğümün tüm alt düğümleri Treeview sınıfının get_children metoduyla elde edilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Treeview') self.geometry('800x600') self.treeview = ttk.Treeview(self) self.treeview.pack(side='top', anchor='w', padx=10, pady=10, fill='both', expand=True) id_department = self.treeview.insert('', index=0, text='Bölümler') self.treeview.insert(id_department, index='end', text='İnsan Kaynakları') self.treeview.insert(id_department, index='end', text='Kalite Kontrol') self.treeview.insert(id_department, index='end', text='Yönetim') self.treeview.insert(id_department , index='end', iid='id_production', text='İmalat') self.treeview.insert('id_production', index='end', text='Sabah') self.treeview.insert('id_production', index='end', text='Öğle') self.treeview.insert('id_production', index='end', text='Akşam') self.treeview.bind('<>', self.treeview_select_handler) self.treeview.bind('<>', self.treeview_open_handler) self.treeview.bind('<>', self.treeview_close_handler) self.button_ok = ttk.Button(self, text='Ok') self.button_ok.pack(side='top', pady=20) def treeview_select_handler(self, event): selected_items = self.treeview.selection() print(selected_items) def treeview_open_handler(self, event): print('Open') def treeview_close_handler(self, event): print('Close') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Seçilen elemanın bilgileri item metouyle elde edilebilmektedir. Örneğin eleman id'si selected_item olmak üzere: text = self.treeview.item(selected_item, option='text') işlemi ile seçilen elemanın yazısı alınabilmektedir. Benzer biçimde item metodu aslında elemanın bilgilerini değiştirmek için de kullanılabilmektedir. Örneğin: self.treeview.item(selected_item, text='xxx') #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Treeview') self.geometry('800x600') self.treeview = ttk.Treeview(self) self.treeview.pack(side='top', anchor='w', padx=10, pady=10, fill='both', expand=True) id_department = self.treeview.insert('', index=0, text='Bölümler') self.treeview.insert(id_department, index='end', text='İnsan Kaynakları') self.treeview.insert(id_department, index='end', text='Kalite Kontrol') self.treeview.insert(id_department, index='end', text='Yönetim') self.treeview.insert(id_department , index='end', iid='id_production', text='İmalat') self.treeview.insert('id_production', index='end', text='Sabah') self.treeview.insert('id_production', index='end', text='Öğle') self.treeview.insert('id_production', index='end', text='Akşam') self.treeview.bind('<>', self.treeview_select_handler) self.treeview.bind('<>', self.treeview_open_handler) self.treeview.bind('<>', self.treeview_close_handler) self.button_ok = ttk.Button(self, text='Ok') self.button_ok.pack(side='top', pady=20) def treeview_select_handler(self, event): selected_item = self.treeview.selection()[0] item_text = self.treeview.item(selected_item, option='text') print(item_text) def treeview_open_handler(self, event): print('Open') def treeview_close_handler(self, event): print('Close') root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da treeview GUI elemanı ile listview elemanı birleştirimiştir. Yani treeview yalnızca treeview olarak, listview gibi hem treeview hem de listviee gibi kullanılabilmektedir. Treeview GUI elemanının columns isimli seçeneği yataılacak sütunların isimlerini bizden bir demet olarak ister. Ana eleman zaten treeview elemanıdır. Sütunlar için değerler insert işleminde values isimli parametresi ile oluşturulmaktadır. Benzer biçimde oluşturulan değerler item metodunda optin='text', option='values' değerleriyle geri alınabilmektedir. İstenirse heading metodunda sütun isimleri '#n' (burada n sütunun index numarasıdır) biçiminde de verilebilir. İlk sütuna isim vermek için mecburen '#0' kullanılmaktadır. Treeview sınıfının column isimli metodu ile de sütunların genişlikleri, hizalamaları vs. ayarlanabilmektedir. Ayrıca insert işlemi sırasında tag isimli parametresiyle elemana istenilen bilgiler iliştirilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Treeview') self.geometry('800x600') self.treeview = ttk.Treeview(self, columns=('No', 'Department')) self.treeview.pack(side='top', fill='both', expand=True) self.treeview.heading('#0', text='Adı Soyadı') self.treeview.heading('No', text='No') self.treeview.heading('Department', text='Bölüm') self.treeview.column('No', width=20, anchor='e') self.treeview.column('Department', anchor='center') self.treeview.insert('', index='end', text='Ali Serçe', values=('123', 'Üretim')) self.treeview.insert('', index='end', text='Sacit Akyol', values=('567', 'İnsan Kaynakları')) self.treeview.insert('', index='end', text='Ayşe Er', values=('345', 'İnsan Kaynakları')) self.treeview.insert('', index='end', text='Hasan Uslu', values=('983', 'Kalite Kontrol')) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', pady=20) def button_ok_handler(self): selected_item = self.treeview.selection()[0] name = self.treeview.item(selected_item, option='text') values = self.treeview.item(selected_item, option='values') print(name, values[0], values[1]) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Treeview GUI elemanında da maalesef builty-in bir scrollbar yoktur. Dolayısıyla scroll çubuğu için daha Listbox örneğinde yapılanlar yapılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Treeview') self.geometry('800x600') self.frame = ttk.Frame(self) self.treeview = ttk.Treeview(self.frame, columns=('No', 'Department')) self.treeview.pack(side='left', fill='both', expand=True) self.treeview.heading('#0', text='Adı Soyadı') self.treeview.heading('No', text='No') self.treeview.heading('Department', text='Bölüm') self.treeview.column('No', width=20, anchor='e') self.treeview.column('Department', width=20, anchor='center') self.treeview.insert('', index='end', text='Ali Serçe', values=('123', 'Üretim')) self.treeview.insert('', index='end', text='Sacit Akyol', values=('567', 'İnsan Kaynakları'), tag=('Ali', 'Veli', 'Selami')) self.treeview.insert('', index='end', text='Ayşe Er', values=('345', 'İnsan Kaynakları')) self.treeview.insert('', index='end', text='Hasan Uslu', values=('983', 'Kalite Kontrol')) for i in range(100): self.treeview.insert('', index='end', text=f'{i}') self.scrollbar = tk.Scrollbar(self.frame, command=self.treeview.yview) self.scrollbar.pack(side='left', fill='y') self.treeview.configure(yscrollcommand=self.scrollbar.set) self.frame.pack(side='top', fill='both', expand=True) self.button_ok = ttk.Button(self, text='Ok', command=self.button_ok_handler) self.button_ok.pack(side='top', pady=20) def button_ok_handler(self): selected_item = self.treeview.selection()[0] name = self.treeview.item(selected_item, option='text') values = self.treeview.item(selected_item, option='values') print(name, values[0], values[1]) tag = self.treeview.item(selected_item, option='tag') print(tag) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk GUI elemanlarının yazı renkleri, fontları vs. genel olarak style denilen bir kavramla değiştirilmektedir. Programcı ttk.Style sınıfı türünden bir nesne yaratır. Sonra bu nesne ile sınıfın configure metodunu çağırır. Buradaki configure metodunun birinci parametresi hangi ttk GUI elemanının stilinin değiştirileceğine yönelik bir isim almaktadır. GUI elemanlarının style isimleri genel olarak sınıf isminin önüne bir T getirilmiş biçimdedir. Örneğin, 'TButton', 'TEntry' gibi. Burada istisna olarak Treeview için bu isim 'TTreeview' değil 'Treeview' biçimindedir. configure metodunun diğer parametreleri ilgili ttk GUI elemanına yönelik stil bilgilerini belirtir. Her GUI elemanın bir stil listesi vardır. Bu liste tkdocs dokümantasyonun öğrenilebilir. Ancak burada önemli bir nokta belli bir GUI elemanın stili değiştirildiğinde artık daha sonra yaraılacak aynı türden tüm GUI elemanların aynı stilden olmasıdır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x320') style = ttk.Style() style.configure('TEntry', foreground='red', background='yellow') style.configure('TButton', font=('', 16), foreground='red') self.entry1 = ttk.Entry(self, font=('', 16)) self.entry1.pack() self.entry2 = ttk.Entry(self, font=('', 16)) self.entry2.pack() self.button = ttk.Button(self, text='ok') self.button.pack() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aynı sınıftan oluşturulan farklı GUI elemanlarına farklı stiller atayabilmek için programcının bir stil ismi uydurması ve o ismi de GUI eleman yaratılırken style parametresinde belirtmesi gerekir. İsim uydurma ilgili GUI elemanın ismi 'TX' olmak üzere 'Name.TX' biçiminde yapılmak zorundadır. Aşağıdaki örnekte farklı ttk.Entry nesnelerinin farklı yazı renkleri olsun diye iki farklı style ismi uydurulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x320') style = ttk.Style() style.configure('Blue.TEntry', foreground='blue') style.configure('Red.TEntry', foreground='red') self.entry1 = ttk.Entry(self, font=('', 16), style='Blue.TEntry') self.entry1.pack() self.entry2 = ttk.Entry(self, font=('', 16), style='Red.TEntry') self.entry2.pack() self.entry3 = ttk.Entry(self, font=('', 16)) self.entry3.pack() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte ttk.Combobox GUI elemanının bazı özellikleri Style yoluyla değiştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('480x320') style = ttk.Style() style.configure('TCombobox', foreground='red', selectforeground='yellow') self.combobox = ttk.Combobox(self, values=['Ali', 'Veli', 'Selami'], state='readonly') self.combobox.pack(pady=10) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ PanedWindow ana pencereyi dinamik olarak genişletip daraltmak için kullanılmaktadır. Hem tk hem de ttk paketlerinde buGUI elemanından bulunmaktadır. Burada programcı bir PanedWindow yarattıktan sonra add ya da insert metodu ile pane'leri oluşturur. Tipik olarak iki pane oluşturulmaktadır. Ancak daha fazla pane de oluşturulabilir. PanedWindow nesnesi yaratılırken genişletilme çubuğuna handle denilmektedir. Default durumda bu handle görüntülenmez. showhandle=True parametresiyle bu çubuk görüntülenir. handlesize parametresi bu çubuğun genişliğini belirlemek için kullanılmaktadır. Programcı tipik olarak önce Frame nesneleri yaratır. Bu frame nesnelerini pane polarak eklenmiştir kendi GUI elemanlarını da bu Frame nesnelerinin içerisine yerleştirir. Aşağıdaki örnekte yan yana iki frame pane olarak PanedWindow içerisine eklenmiştir. Sonra bu frame'lere Text GUI elemanları da eklenmiştir. Tabii aslında hiç Frame oluşturmadan bu Text GUI elemanları da pane olarak eklenebilirdi. Ancak Frame kendi içerisinde pek çok GUI elemanını barındırabildiği için daha genel bir durum oluşturmakttadır. PanedWindow sınıfının add metodunda weight belirtilerek pane'lerin başlangıçta birbirlerine göre genişlikleri belirlenebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('PanedWindow') self.geometry('800x600') self.panedwindow = tk.PanedWindow(self, orient=tk.HORIZONTAL, showhandle=True, handlesize=10) self.panedwindow.pack(fill='both', expand=True) self.frame_left = tk.Frame(self.panedwindow) self.frame_right = tk.Frame(self.panedwindow) self.panedwindow.add(self.frame_left, minsize=100, weight=1) self.panedwindow.add(self.frame_right, minsize=100, weight=1) self.text_left = tk.Text(self.frame_left, font=('', 18), foreground='red') self.text_left.pack(side='top', fill='both', expand=True) self.button_ok_left = tk.Button(self.frame_left, text='Ok') self.button_ok_left.pack(pady=10) self.text_right = tk.Text(self.frame_right, font=('', 18), foreground='blue') self.text_right.pack(side='top', fill='both', expand=True) self.button_ok_right = tk.Button(self.frame_right, text='Ok') self.button_ok_right.pack(pady=10) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki uygulamanın aynısı ttk kullanılarak aşağıdaki oluşturulabilir. ttk.PanedWindow için bazı özellikler style biçiminde belirlenmektedir. Ayrıca her style özelliği de her ttk temasında bulunmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.title('PanedWindow') self.geometry('800x600') self.panedwindow = ttk.PanedWindow(self, orient=tk.HORIZONTAL) self.panedwindow.pack(fill='both', expand=True) self.frame_left = ttk.Frame(self.panedwindow) self.frame_right = ttk.Frame(self.panedwindow) self.panedwindow.add(self.frame_left, weight=1) self.panedwindow.add(self.frame_right, weight=1s) self.text_left = tk.Text(self.frame_left, font=('', 18), foreground='red') self.text_left.pack(side='top', fill='both', expand=True) self.button_ok_left = ttk.Button(self.frame_left, text='Ok') self.button_ok_left.pack(pady=10) self.text_right = tk.Text(self.frame_right, font=('', 18), foreground='blue') self.text_right.pack(side='top', fill='both', expand=True) self.button_ok_right = ttk.Button(self.frame_right, text='Ok') self.button_ok_right.pack(pady=10) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ ttk themed tk anlamına gelmektedir. Çalıştığımız işletim sisteminde GUI elemanların görüntülenme biçimi üzerinde etkili olan temalar vardır. Biz tema değiştirsek aslında bu görüntü biçimlerini bir takım olarak değiştirmiş oluruz. Çalıştığımız sistemdeki temalar ttk.Style sınıfının theme_names isimli metoduyla elde edilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') style = ttk.Style() print(style.theme_names()) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Bir temayı aktif hale getirdiğimizde o anda yaratılmış ve yaratılacak olan tüm GUI elemanlarının görüntüsü o temadan olacak biçimde değişecektir. Tema değiştirmekl için style nesnesi ile use_theme metodu çağrılır. Aşağıdaki örnekte tüm temalar radyo düğmesi olarak oluşturulmuş ve bu düğmeler yoluyla değiştirilmiştir. Default tema use_themes metodunda ilk bulunan temadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk from tkinter import ttk class Root(tk.Tk): def __init__(self): super().__init__() self.geometry('800x600') self.radio_var = tk.StringVar() self.style = ttk.Style() self.frame = ttk.Frame(self) tnames = self.style.theme_names() for i, name in enumerate(tnames): radio = ttk.Radiobutton(self.frame, text=name, command=self.radio_handler, value=name, variable=self.radio_var) radio.grid(row=i, column=0, sticky='w') self.frame.pack(side='left') self.button_ok = ttk.Button(self, text='Ok', width = 10) self.button_ok.pack(side='left', padx=20) self.entry = ttk.Entry(self, width = 20) self.entry.pack(side='left', padx=20) self.radio_var.set(tnames[0]) def radio_handler(self): self.style.theme_use(self.radio_var.get()) root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ tk.Tk sınıfı hem programın ana penceresini hem de uygulamanın kendisini temsil etmektedir. Ancak bir uygulamada birden fazla birbirinden bağımsız ana pencere de olabilir. Ana pencereler tk.Toplevel isimli sınıf ile temsil edilmektedir. Programcı ne zaman bu sınıf türünden nesne yaratsa yeni bir ana pencere oluşturmuş olur. Toplevel sınıfın pek çok elemanı (title gibi, geometry gibi) tk.Tk sınıfı ile aynıdır. Aşağıdaki örnekte menüden her seçim yapıldığında yeni bir ana pencere oluşturulmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Main Application Window') self.geometry('800x600') self.count = 1 self.menu_bar = tk.Menu(self) self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Open Toplevel', command=self.open_toplevel_handler) self.menu_bar.add_cascade(label='File', menu=self.file_popup) self['menu'] = self.menu_bar def open_toplevel_handler(self): toplevel = tk.Toplevel() toplevel.title(f'Toplevel Window {self.count}') toplevel.geometry('800x600') self.count += 1 root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ Tkinter'da modal dialog penceresi oluşturmak biraz zahmetlidir. Dialog penceresinin kapatılmasının beklenmesi için wait_window metodu kullanılır. Aynı zamanda dialog penceresinde aşağıdaki metotlar çağrılmalıdır: self.master = master self.transient(master) self.grab_set() Burada master dialog penceresinin üst penceresini temsil etmektedir. Dialog penceresi kapatıldığında maaleset GUI elemanlarındaki bilgiler yerinde kalmaz. O halde pencere kapatılmadan önce GUI elemanlarındaki bilgilerin dialog pencere sınıfının örnek özniteliklerinde saklanması uygun olur. Yine Tkinter'da dialog penceresinden hangi tuşla çıkıldığını belirlemenin pratik bir yolu yoktur. Programcının da bunu sınıfın bir örnek özniteliğinde oluşturması gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import tkinter as tk class Root(tk.Tk): def __init__(self): super().__init__() self.title('Main Application Window') self.geometry('800x600') self.menu_bar = tk.Menu(self) self.file_popup = tk.Menu(tearoff=0) self.file_popup.add_command(label='Modal Dialog', command=self.modal_dialog_handler, accelerator='Ctrl+M') self.menu_bar.add_cascade(label='File', menu=self.file_popup) self['menu'] = self.menu_bar self.bind('', self.modal_dialog_handler) def modal_dialog_handler(self, event=None): mydialog = MyDialog(self) mydialog.title('My Dialog') self.wait_window(mydialog) if mydialog.reason == 'Ok': print(mydialog.name) print(mydialog.no) print(mydialog.check_stat) else: print('Cancel') class MyDialog(tk.Toplevel): def __init__(self, master): super().__init__() self.master = master self.resizable(width=False, height=False) self.transient(master) self.grab_set() self.reason='Cancel' self.entry_name_var = tk.StringVar() self.entry_no_var = tk.StringVar() self.check_button_var = tk.BooleanVar() self.label_name = tk.Label(self, text='Adı Soyadı') self.label_name.grid(row=0, column=0, pady=(10, 5), padx=(10, 5), sticky='w') self.entry_name = tk.Entry(self, width=30, textvariable=self.entry_name_var) self.entry_name.grid(row=0, column=1, pady=(10, 5)) self.label_no = tk.Label(self, text='No') self.label_no.grid(row=1, column=0, padx=(10, 5), sticky='w') self.entry_no = tk.Entry(self, width=30, textvariable=self.entry_no_var) self.entry_no.grid(row=1, column=1) self.check_button = tk.Checkbutton(self, text='E-Posta', variable=self.check_button_var) self.check_button.grid(row=2, column=0, columnspan=2, sticky='w', padx=10, pady=10) img = tk.PhotoImage(file='person.png') self.label_image = tk.Label(self, image=img) self.label_image.grid(row=0, column=2, columnspan=2, rowspan=2, sticky='wens') self.label_image.image = img self.button_ok = tk.Button(self, text='Ok', width=10, command=self.button_ok_handler) self.button_ok.grid(row=2, column=2, stick='w', padx=(0, 7)) self.button_cancel = tk.Button(self, text='Cancel', width=10, command=self.button_cancel_handler) self.button_cancel.grid(row=2, column=3, sticky='w', padx=(0, 10)) def button_ok_handler(self): self.name = self.entry_name_var.get() self.no = self.entry_no_var.get() self.check_stat = self.check_button_var.get() self.reason = 'Ok' self.destroy() def button_cancel_handler(self): self.reason='Cancel' self.destroy() root = Root() root.mainloop() #------------------------------------------------------------------------------------------------------------------------------------ 41. Ders 15/05/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python genel amaçlı bir programlama dili olmasının yanı sıra veri bilimi ve matematiksel alanlarda da çok tercih edilmektedir. Ancak Python saf haliyle veri işleme konusunda bazı yeteneklerden yoksundur. Veri işleme konusunda kullanılan programlama dilleri genellikle "vektörel işlem" yapma yeteneğine sahiptir. Vektörel işlem demekle biz iki dizinin karşılıklı elemanlarının kolay bir biçimde işleme sokulabilmesini anlatmak istiyoruz. Örneğin Matlab (ya da Open source versiyonu olan Octave) gibi R gibi diller ve ortamlar bu vektörel işlem yapma yeteneğine sahiptir. Vektörel işlem yapabilme yeteneği matematiksel ve istatistiksel pek çok işlemi kısaltmaktadır. Python'a vektörel işlem yapma yeteneğini kazandırmak için bazı üçüncü parti kütüphaneler oluşturulmuştur. Bunların en çok kullanılanı "NumPy" isimli kütüphanedir. NumPy Python'da en çok kullanılan kütüphanelerden biridir. Bazı gerekçelerle Python standart kütüphanesine dahil edilmemiş olsa da standart kütüphendeki pek çok modülden çok daha yoğun kullanılmaktadır. (NumPy hızlı güncellenmektedir. Standart kütüphaneye dahil edilmesinin hantallığa yol açacağı düşünülmüştür.) Numpy kütüphanesi adeta Python'u R ya da Matlab gibi bir dil haline getirmektedir. Numpy C'de yazılmış bir kütüphanedir. Kütüphane paralel programlama tekniklerini kullanmamakla birlikte modern işlemcilerin SIMD (Single Instruction Multiple Data) komutlarını kullanarak etkin bir biçimde yazılmıştır. Dolayısıyla Numpy işlemlerinin Python diline kıyasla daha hızlı yapılacağı söylenebilir. Pekiyi Python'u NumPy kütüphanesi ile R'laştırmak yerine doğrudan R ya da Matlab kullanmak daha uygun değil midir? Matlab ve R genel amaçlı bir dil değildir. Bunlar uzmanlığı programlama olmayan kişilere yönelik hazırlanmış ortamlar ve dillerdir. Halbuki Python genel amaçlı bir programlama dilidir. Python'ın genel amaçlı olması nümerik analiz, veri bilimi ve makine öğrenmesi gibi alanlarda da R ve Matlab'ten daha fazla tercih edilmesine yol açmıştır. (Matlab isimli dil ve ürün "Mathworks" isimli bir firmanın mülkiyetindedir. Dolayısıyla ücretlidir. Tabii Matlab'in de çeşitli açık kaynak kodlu biçimleri zamanla oluşturulmuştur. R ise açık kaynak kodlu bir dildir.) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ NumPy kütüphanesi pip programıyla aşağıdaki gibi indirilip kurulabilir: pip install numpy Anaconda dağıtımında NumPy ve pek çok yaygın kütüphane zaten kurulum sırasında pakete dahil edilmektedir. Yani Anconda için NumPy'ı indirip kurmamıza gerek yoktur. Genellikle NumPy programcıları bu kütüphaneyi "np" ismiyle import etmektedir. Örneğin: import numpy as np Projenin ana web sayfası "numpy.org" biçimindedir. Orijinal dokümanlarına aşağıdaki bağlantıdan erişilebilir: https://numpy.org/doc/ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 42. Ders 17/05/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'a vektörel işlem yapma yeteneği bir sınıf ile kazandırılabilir. Yani örneğin biz bir sınıf yazabiliriz. Bu sınıf için __add__ gibi, __mul__ gibi operatör metotlarını bulundurabiliriz. Bu operatör metotları da listenin karşılıklı elemanlarını işleme sokabilir. Aşağıda bu fikrin uygulanmasına yönelik örnek bir kod bulunmaktadır. Ancak tamamen Python dünyasında kalarak bu tür vektörel işlemleri yapan bir sınıf oluşturmak işlemlerin yavaş yapılmasına yol açacaktır. Oysa sayısal analiz işlemlerinde işlem yükü oldukça fazla olduğu için bu tür vektörel işlemlerin mümkün olduğunca hızlı bir biçimde yapılması istenir. #------------------------------------------------------------------------------------------------------------------------------------ class ndarray: def __init__(self, array = None): if array: self.array = array else: self.array = [] def __add__(self, nd): if isinstance(nd, int): new_nd = ndarray() for i in range(len(self.array)): new_nd.array.append(self.array[i] + nd) return new_nd if len(self.array) != len(nd.array): raise ValueError('arrays are not the same size') new_nd = ndarray() for i in range(len(self.array)): new_nd.array.append(self.array[i] + nd.array[i]) return new_nd def __mul__(self, nd): if len(self.array) != len(nd.array): raise ValueError('arrays are not the same size') new_nd = ndarray() for i in range(len(self.array)): new_nd.array.append(self.array[i] * nd.array[i]) return new_nd def __repr__(self): return str(self.array) x = ndarray([1, 2, 3, 4, 5]) y = ndarray([10, 20, 30, 40, 50]) z = ndarray([5, 2, 3, 1, 6]) k = (x + y) * z print(k) #------------------------------------------------------------------------------------------------------------------------------------ NumPy kütüphanesinin ana veri yapısı ndarray isimli sınıftır. Tıpkı yukarıdaki örnekte olduğu gibi ndarray isimli sınıfa pek çok operatör metodu eklenmiş ve ndarray nesneleri vektörel işlem yapabilir hale getirilmiştir. Tabii NumPy'ın ndarray sınıfı ve diğer globaş fonksiyonlar C'de yazılmış durumdadır. Yukarıda da belirttiğimiz gibi işlemcilrin özel SIMD komutlarını da kullanmaktadır. NumPy kütüphanesinin ndarray sınıfının yetenekleri oldukça geniştir. Dolayısıyla NumPy kütüphanesini iyi bir biçimde öğrenmek için bu ndarray isimli sınıfın nasıl kullanıldığını öğrenmek gerekir. NumPy kütüphanesi bazı bakımlardan eleştirilebilir. Bir işlemin çok değişik biçimlerde yapılabilmesi öğrenmeyi ve akılda tutmayı zorlaştırmakta programcıları tereddüte sevk etmektedir. Yukarıda da belirtildiği gibi NumPy'da ana veri yapısı ndarray sınıfıdır ve ndarray nesnelerine "NumPy dizisi (NumPy array)" de denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Numpy'da ilk öğrenilecek şey ndarray nesnesinin (yani NumPy dizisinin) oluşturulmasıdır. Bunun çeşitli yolları vardır. En yaygın yollarından biri array isimli fonksiyonu kullanmaktadır. array fonksiyonu bir dolaşılabilir nesneyi alıp ondan bir numpy dizisi (yani ndarray nesnesi) oluşturmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([1, 2, 3, 4, 5]) print(a, type(a)) #------------------------------------------------------------------------------------------------------------------------------------ Biz argüman olarak array fonksiyonuna liste listesi verirsek array fonksiyonu bize çok boyutlu bir NumPy dizisi (yani ndarray nesnesi) vermektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi Python'daki list, tuple, gibi veri yapıları aslında değerlerin kendisini tutmamaktadır. Değerlerin tutulduğu nesnelerin adreslerini tutmaktadır. Bu biçimdeki çalışma yoğun sayısal işlemlerde oldukça hantal hale gelmektedir. Bu nedenle NumPy dizileri değerleri Python'un listeleri gibi değil C Programlama Dilindeki diziler gibi tutmaktadır. Örneğin: a = np.array([1, 2, 3, 4, 5]) Burada a değişkenin gösteridği NumPy dizisi tamamen C'deki gibi bir dizidir. Yani NumPy dizisinin elemanları adresleri değil doğrudan değerlerin kendisini tutmaktadır. Bu nedenle NumPy dizileri (yani ndarray nesneleri) birkaç istisna durum dışında homojendir. Yani NumPy dizilerinin elemanları genel olarak aynı türdendir. NumPy dizileri Python'daki türler türünden elemanları değil C Programlama Dilindeki türler türünden elemanları tutarlar. Bir NumPy dizisinin tuttuğu elemanların türlerine bu NumPy dizisinin dtype'ı denilmektedir. Örneğin bir NumPy dizisinin dtype'ı "float32" ise bu C'deki 32 bitlik gerçek sayı türü olan "float" anlamına gelmektedir. Ya da örneğin bir NumPy dizisinin dtype'ı "uint64" ise bu C'deki "unisgned long long int" türüne karşılık gelmektedir. C'de de veri türleri Python'dan çok daha çeşitlidir. İzleyen paragraflarda bir NumPy dizisinin türlerinin neler olabileceğini listeleyeceğiz. NumPy dizilerinin dtype bilgisi sınıfın dtype isimli örnek özniteliğinden elde edilebilir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a.dtype dtype('int32') >>> b = np.array([1, 2.2, 4, 5.2, 7]) >>> b.dtype dtype('float64') #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([1, 2, 3.4, 4.7, 5]) print(a.dtype) #------------------------------------------------------------------------------------------------------------------------------------ array fonksiyonu ile bir NumPy dizisi yaratılırken eğer dtype belirtilmezse default durumda array fonksiyonu eğer tüm değerler Python int türündense ve int32, int64 dtype türlerinden hangisi yeterliyse diziyi o dtype türünden yaratmaktadır. Ancak değerlerden en az biri Python'ın float üründense bu durumda diziyi float64 dtype türüyle yaratmaktadır. array fonksiyonuyla NumPy dizisi (ndarray nesnesi) yaratılırken yaratılacak NumPy dizisinin C'deki dtype türü array fonksiyonun dtype parametresi ile açıkça belirlenebilir. array fonksiyonunun dtype parametresi string biçiminde girilebilir. Aslında her dtype türü NumPy içerisinde bir sınıfla temsil edilmiştir. dtype için doğrudan np.float32 gibi, np.float64 gibi sınıf isimleri de kullanılabilir. Her ne kadar C'de float64 türü (yani double türü) yaygın kullanılıyorsa da pek çok sayısal uygulamada ve makine öğrenmesinde büyük diziler söz konusu olduğu için float32 dtype tercih edilmektedir. Örneğin: >>> a = np.array([1, 2, 3, 4, 5], dtype='float64') >>> a array([1., 2., 3., 4., 5.]) >>> a.dtype dtype('float64') >>> b = np.array([1, 2, 3, 4, 5], dtype=np.int8) >>> b array([1, 2, 3, 4, 5], dtype=int8) >>> b.dtype dtype('int8') #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2.3, 3], [4, 5, 6]], dtype='float32') print(a, a.dtype) import numpy as np b = np.array([[1, 2.3, 3], [4, 5, 6]], dtype=np.float32) print(b, b.dtype) #------------------------------------------------------------------------------------------------------------------------------------ NumPy dizilerinde kullanabileceğimiz dtype türlerinin önemli olanları şunlardır: bool_ /bool8 : bool türü byte / int8 : bir byte'lık işaretli tamsayı türü ubyte / uint8 : bir byte'lık işaretsiz tamsayı türü short / int16 : iki byte'lık işaretli tamsayı türü ushort / uint16: iki byte'lık işaretsiz tamsayı türü int32 : dört byte'lık işaretli tamsayı türü uint32 : dört byte'lık işaretsiz tamsayı türü int64 : sekiz byte2lık işaretli tamsayı türü uint64 : sekiz byte'lık işaretsiz tamsayı türü float32 / single : dört byte'lık gerçek sayı türü float64 / float / double : sekiz byte'lık gerçek sayı türü Diğer türler için Numpy dokümantasyonlarına başvurabilirsiniz. NumPy'da çokça dtype türü olduğu halde en fazla kullanılan türler "float32", "float64", "int32" ve "uint8" türleridir. NumPy default olarak noktalı sayılar için "float64" türünü tamsayılar için "int32" kullanmaktadır. Bir NumPy dizisi bu türlerdense print edilirken genel olarak dtype türü gösterilmemektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisi ndarray sınıfının __init__ metoduyla da oluşturulabilir. Bu durumda oluşturulan NumPy dizisi çöp değerler içermektedir. __init__ metodunun birinci parametresi oluşturulacak NumPy dizisinin boyutlarını bir demetle almaktadır. (Tek boyut için demet kullanmaya gerek yoktur.) Yine yaratım sırasında dtype belirtilebilir. Örneğin: Örneğin: >>> a = np.ndarray(10, dtype=np.int8) >>> a array([111, 0, 117, 0, 100, 0, 45, 0, 115, 0], dtype=int8) >>> b = np.ndarray((3, 3), dtype=np.int8) >>> b array([[114, 0, 111], [ 0, 103, 0], [114, 0, 97]], dtype=int8) NumPy dizilerinin ndarray sınıfının __init__ metoduyla yaratılması pek tavsiye edilen bir yöntem değildir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.ndarray((3, 3), dtype='float32') # a 3x3'lük çöp değerlerden oluşan bir numpy dizisi print(a, a.dtype) #------------------------------------------------------------------------------------------------------------------------------------ İçi sıfırlarla dolu numpy dizilerinin oluşturulması gerekebilmektedir. Bunun için zeros fonksiyonu kullanılmaktadır. zeros fonksiyonun yine birinci parametresi oluşturulacak Numpy dizisinin boyutlarını (shape) belirtir. Genel olarak yaratıcı fonksiyonlardaki boyut belirten shape parametresi bir demet olarak girilir. Ancak demet yerine shape parametresi için int türden düz bir sayı sayı girlirse bu durumda tek boyutlu dizi yaratılır. Örneğin: >>> a = np.zeros(10, dtype='int8') >>> a array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int8) >>> b = np.zeros((10, 10), dtype=np.float32) >>> b array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.zeros(10, dtype='int32') print(a) b = np.zeros((5, 5), dtype=np.float32) print(b) #------------------------------------------------------------------------------------------------------------------------------------ ones isimli fonksiyon içi 1'lerle dolu bir NumPy dizisi oluşturmaktadır. Yine fonksiyonun birinci parametresi oluşturulacak dizinin boyutlarını belirtir. dtype parametresi ise dtype türünü belirtir. Örneğin: >>> a = np.ones(10, dtype='int32') >>> a array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.ones((3, 3), dtype='uint32') # 3x3'lük uint32 türünden birlerle dolu dizi print(a, a.dtype) #------------------------------------------------------------------------------------------------------------------------------------ empty isimli fonksiyon içi herhangi bir biçimde doldurulmamış olan çöp değerlerden oluşan NumPy dizisi oluşturmaktadır. Zaten yukarıda da belirtildiği gibi ndarray sınıfı türünden nesne yaratırken de aynı biçimde çöp değerlerden oluşan numpy dizisi yaratılabiliyordu. Bazen programcı bir NumPy dizisi oluşturup onun içini doldurabilmektedir. İşte bu tür durumlarda zeros gibi bir fonksiyonunun sıfırlama sırasında zaman kaybetmesi istenmeyebilir. Ancak tabii bu tür zaman kayıpları genel olarak Python dünyasında önemli kabul edilmemektedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.empty(10, dtype='uint8') print(a, a.dtype) #------------------------------------------------------------------------------------------------------------------------------------ full isimli fonksiyon NumPy dizisini bizim istediğimiz değerle doldurarak yaratır. (Yani zeros ve ones fonksiyonlarının genel biçimidir.) Bu fonksiyonun yine birinci parametresi NumPy dizisinin boyutlarını, ikinci parametresi doldurulacak değerleri belirtmektedir. Fonksiyonda yine dtype belirtilebilir. Örneğin: >>> a = np.full(10, 5, dtype='int8') >>> a array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5], dtype=int8) >>> b = np.full((5, 5), 1.2, dtype='float32') >>> b array([[1.2, 1.2, 1.2, 1.2, 1.2], [1.2, 1.2, 1.2, 1.2, 1.2], [1.2, 1.2, 1.2, 1.2, 1.2], [1.2, 1.2, 1.2, 1.2, 1.2], [1.2, 1.2, 1.2, 1.2, 1.2]], dtype=float32) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.full((3, 3), 10, dtype=np.float32) print(a, a.dtype) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki gördüğümüz fonksiyonların _like son ekli biçimleri de vardır. Yani zeros_like, ones_like, empty_like, full_like gibi. Bu fonksiyonlar parametre olarak bir NumPy dizisini alır. Yaratılacak numpy dizisinin boyutlarını ve dtype özelliğini parametresiyle aldığı dizideki gibi yapar. Örneğin elimizde 2x4'lük float32 değerlerinden oluşan bir NumPy dizisi olsun. Biz de zeros_like fonksiyonu ile 2x4'lük float32 değerlerine sahip ancak içi 0'lardan oluşan bir numpy dizisi yaratmak isteyebiliriz: >>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype='float32') >>> b = np.zeros_like(a) >>> b array([[0., 0., 0., 0.], [0., 0., 0., 0.]], dtype=float32) Bu _like sonekli fonksiyonlarda biz dtype belirtebiliriz. Bu durumda oluşturulacak NumPy dizisinin dtype özelliği diziden değil bizim belirttiğimiz türden alınır. Örneğin: >>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype='float32') >>> b = np.zeros_like(a, dtype='float64') >>> b array([[0., 0., 0., 0.], [0., 0., 0., 0.]]) >>> b.dtype dtype('float64') #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) print(a, a.dtype) b = np.zeros_like(a) print(b, b.dtype) #------------------------------------------------------------------------------------------------------------------------------------ Rastgele değerlerden NumPy dizisi oluşturabilmek için numpy.random modülünde çeşitli fonksiyonlar bulundurulmuştur. Örneğin numpy.random.random fonksiyonu belli bir boyutta 0 ile 1 arasında rastgele gerçek sayı değerleri oluşturmaktadır. Bu fonksiyon dtype parametresine sahip değildir. Her zaman float64 olarak numpy dizisini yaratmaktadır.Fonksiyonun boyut belirten bir parametresi vardır. Örneğin: >>> a = np.random.random((5, 5)) >>> a array([[0.82089125, 0.08025651, 0.73226155, 0.06222221, 0.12049264], [0.43171059, 0.45946347, 0.2128061 , 0.5012849 , 0.82467442], [0.90062584, 0.82899044, 0.14757077, 0.82961189, 0.67917696], [0.60761068, 0.60724418, 0.69019941, 0.73556308, 0.25155426], [0.4610114 , 0.25969294, 0.09778555, 0.54427295, 0.9985059 ]]) #------------------------------------------------------------------------------------------------------------------------------------ a = np.random.random((3, 3)) print(a) #------------------------------------------------------------------------------------------------------------------------------------ numpy.random.randint fonksiyonu [low, high) aralığında rastgele tamsayı değerlerinden oluşan NumPy dizisi oluşturmaktadır. Bu fonksiyonun üçüncü parametresi yaratılacak NumPy dizisinin boyutunu almakatdır. Bu parametre girilmezse tek bir değer üretilmektedir. Örneğin: >>> a = np.random.randint(10, 20, (5, 5), dtype='int8') >>> a array([[11, 18, 10, 18, 13], [16, 15, 12, 18, 12], [14, 11, 12, 17, 18], [10, 18, 18, 14, 19], [10, 16, 17, 14, 14]], dtype=int8) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.random.randint(0, 10, (3, 3), dtype='int32') print(a) #------------------------------------------------------------------------------------------------------------------------------------ arange fonksiyonu Python'ın built-in range fonksiyonuna benzemektedir. Ancak arange bize dolaşılabilir bir nesne vermez. Doğrudan bir NumPy dizisi verir. start, stop, step parametreleri range fonksiyonunda olduğu gibidir. Ancak Python range fonksiyonunda start, stop ve step değerleri int türünden olmak zorundayken arange fonksiyonunda float türünden de oabilir. Böylelikle biz arange ile noktasal artırımlarla bir numpy dizisi oluşturabiliriz. arange fonksiyonu dtype parametresi de alabilmektedir. Ancak bu fonksiyon her zaman tek boyutlu bir diziyi bize verir. Örneğin: >>> a = np.arange(-1, 1, 0.1) >>> a array([-1.00000000e+00, -9.00000000e-01, -8.00000000e-01, -7.00000000e-01, -6.00000000e-01, -5.00000000e-01, -4.00000000e-01, -3.00000000e-01, -2.00000000e-01, -1.00000000e-01, -2.22044605e-16, 1.00000000e-01, 2.00000000e-01, 3.00000000e-01, 4.00000000e-01, 5.00000000e-01, 6.00000000e-01, 7.00000000e-01, 8.00000000e-01, 9.00000000e-01]) >>> b array([0. , 1.1 , 2.2 , 3.3000002, 4.4 , 5.5 , 6.6000004, 7.7000003, 8.8 , 9.900001 ], dtype=float32) Fonksiyonda dtype belirtilmezse eğer start, stop, step tamsayı türlerindense default dtype 'int32', bunlardan biri float türündense default dtype 'float64' alınmaktadır. arange fonksiyonunu kullanırken dikkat etmek gerekir. Çünkü noktasal artırımlar, noktasal start ve stop değerleri yuvarlama hatalarından dolayı beklenenden fazla ya da az sayıda eleman üretebilir. (Örneğin 0.1 artırımlarla ilerleken yuvarlama hatasından dolayı stop değerine çok yakın ama ondan küçük değer elde edilebilir ve bu değer de dizi içinde bulunabilir.) Zaten Python'daki built-in range sınıfının tamsayı değerler almasının nedeni de budur. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.arange(0, 100, 2, dtype='float32') print(a) a = np.arange(0, 0.8, 0.1, dtype='float32') print(a) #------------------------------------------------------------------------------------------------------------------------------------ 43. Ders 22/05/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ arange fonksiyonun yukarıda belirtilen probleminden dolayı noktasal artırım için genellikle programcılar linspace fonksiyonunu tercih ederler. Bu fonksiyon start, stop ve num parametrelerine sahiptir. Fonksiyon her zaman start ve stop değerlerini de içerecek biçimde eşit aralıklı num tane değeri oluşturarak onu bir NumPy dizisi olarak vermektedir. linspace ile elde edilecek eleman sayısı belli olduğu için arange fonksiyonu yerine genellikle programcılar bunu tercih etmektedir. linsspace fonksiyonun dtype parametresi de vardır. Bu parametre için argüman girilmezse default detype np.float64 olacak biçimde belirlenir. Örneğin: >>> a = np.linspace(0, 10, 10) >>> a array([ 0. , 1.11111111, 2.22222222, 3.33333333, 4.44444444, 5.55555556, 6.66666667, 7.77777778, 8.88888889, 10. ]) >>> a = np.linspace(0, 10, 11) >>> a array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]) >>> a = np.linspace(0, 1, 20, dtype='float32') >>> a array([0. , 0.05263158, 0.10526316, 0.15789473, 0.21052632, 0.2631579 , 0.31578946, 0.36842105, 0.42105263, 0.47368422, 0.5263158 , 0.57894737, 0.6315789 , 0.68421054, 0.7368421 , 0.7894737 , 0.84210527, 0.8947368 , 0.94736844, 1. ], dtype=float32) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.linspace(0, 10, 5) print(a) a = np.linspace(0, 10, 20, dtype='float32') print(a) #------------------------------------------------------------------------------------------------------------------------------------ Burada özellikle linspace(0, 5, 5) gibi çağrıdan [0, 1, 2, 3, 4, 5] değerlerinin elde edilmeyeceğine dikkat ediniz. Eğer bu değerler elde edilmek isteniyorsa bu durumda num parametresi 6 olarak girilmelidir. Örneğin: >>> a = np.linspace(0, 5, 5) >>> a array([0. , 1.25, 2.5 , 3.75, 5. ]) >>> a = np.linspace(0, 5, 6) >>> a array([0., 1., 2., 3., 4., 5.]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.linspace(0, 5, 5) print(a) a = np.linspace(0, 5, 6) print(a) #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisinin boyutlarını (yani kaça kaçlık olduğunu) shape isimli özniteliği ile elde edebiliriz. shape özniteliği bize boyutları belirten bir demet vermektedir. Örneğin: >>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) >>> a array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]]) >>> a.shape (3, 4) >>> a.shape[0] 3 >>> a.shape[1] 4 #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) print(a.shape) print(a.shape[0], a.shape[1]) #------------------------------------------------------------------------------------------------------------------------------------ Numpy'da belli bir işi yapmanın birden fazla yöntemi olabilmektedir. Örneğin bazı işlemler doğrudan ndarray sınıfının metotlarıyla yapılabilmektedir. Ancak aynı işlemler istenirse numpy mofülündeki aynı isimli bir fonksiyonla da yapılabilmektedir. Buradaki tercih programcınındır. Örneğin a bir ndarray nesnesi foo da yapılacak işlemi belirten bir isim olsun biz bu işlemi ndarray sınıfının foo metoduyla şöyle yapabiliriz: a.foo(...) Ancak aynı işlem foo isimli global fonksiyonla da yapılabilmektedir: np.foo(a, ...) İki kullanım biçimi arasındaki farka dikkat ediniz. a.foo(...) çağrısında zaten foo a nesnesi üzerinde işlem yapacaktır. (Buradaki a nesnesinin foo metoduna self parametresi olarak aktarılacağına dikkat ediniz.) Ancak np.foo fonksiyonu işlem yapacağı nesneyi bizden parametre olarak almaktadır. Ancak her türlü işlem için hem ndarray sınıfında bir metot hem de global bir fonksiyon bulunmamaktadır. Bazı işlemler için yalnızca numpy modülünde fonksiyon bulundurulmuştur. Bunların bir metot karşılığı yoktur. Bazı işlemler için de yalnızca ndarray içerisinde metot bulundurulmuştur. Bunların fonksiyon karşılıkları yoktur. Bazı işlemler için de yukarıda belirttiğimiz gibi hem metotlar hem de fonksiyonlar bulunmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisinin boyutları değiştirilebilir. Bunun için ndarray sınıfının reshape metodu ya da reshape fonksiyonu kullanılabilemktedir. Default durumda ndarray elemanları C Programlama Dilindeki gibi satırsal biçimde belleğe tek boyutlu olarak yerleştirilmektedir. reshape işlemi de bellekteki duruma göre yapılmaktadır. Örneğin aşağıdaki gibi bir NumPy dizisi bulunuyor olsun: 1 2 3 4 5 6 7 8 Bu aslında bu dizi bellekte şu sırada tek boyutlu biçimde tutulmaktadır: 1 2 3 4 5 6 7 8 Şimdi biz bu diziyi 4x2 olarak reshape yaparsak dizi şu hale gelir: 1 2 3 4 5 6 7 8 Yani reshape işlemini şöyle düşünmelisiniz: Sanki önce çok boyutlu dizi tek boyuta dönüştürülüp, yeniden diğer boyuta dönüştürülmektedir. reshape işleminden yeni bir NumPy dizisi elde edilmektedir. Örneğin: >>> a = np.random.randint(0, 100, (5, 4)) >>> a array([[27, 32, 69, 5], [92, 69, 31, 16], [60, 97, 25, 72], [72, 53, 82, 14], [20, 84, 67, 23]]) >>> b = a.reshape(2, 10) >>> b array([[27, 32, 69, 5, 92, 69, 31, 16, 60, 97], [25, 72, 72, 53, 82, 14, 20, 84, 67, 23]]) >>> c = np.reshape(a, (4, 5)) >>> c array([[27, 32, 69, 5, 92], [69, 31, 16, 60, 97], [25, 72, 72, 53, 82], [14, 20, 84, 67, 23]]) reshape metodunu kullanırken boyutlar demet olarak verilmek zorunda değildir. Ancak demet olarak da (aslında dolaşılabilir bir nesne olarak da) verilebilmektedir. Ancak global fonksiyonu kullanırken biz boyutları bir demet olarak (aslında dolaşılabilir bir nesne olarak) vermek zorundayız. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.float32) print(a) result = a.reshape((4, 2)) print(result) #------------------------------------------------------------------------------------------------------------------------------------ reshape metodu ya da reshape fonksiyonu bize bir "view" nesnesi vermektedir. Python'da "view" nesnesi demekle "asıl verilere referans eden nesneler" anlaşılmaktadır. Yani bir view nesnesi üzerinde değişiklik yapılırsa aslında değişiklik asıl nesne üzerinde yapılmış olur. Örneğin: >>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) >>> a array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]]) >>> b = a.reshape(4, 3) >>> b array([[ 1, 2, 3], [ 4, 5, 6], [ 7, 8, 9], [10, 11, 12]]) >>> a[0, 0] = 100 >>> b array([[100, 2, 3], [ 4, 5, 6], [ 7, 8, 9], [ 10, 11, 12]]) >>> b[1, 2] = 200 >>> a array([[100, 2, 3, 4], [ 5, 200, 7, 8], [ 9, 10, 11, 12]]) reshape işleminde yeniden boyutlandırma yapılırken orijinal eleman sayısı elde edilmelidir. Örneğin elimizde 5x4'lük bir NumPy dizisi olsun. Biz bu diziyi 2x10'luk hale getirebiliriz. 1x20'lik hale de getirebiliriz. Ancak 3x6'lık hale getiremyiz. Örneğin: >>> a = np.random.randint(0, 100, (5, 4)) >>> a array([[53, 80, 87, 24], [35, 48, 36, 57], [18, 83, 54, 69], [51, 4, 73, 98], [70, 37, 61, 93]]) >>> b = a.reshape(3, 6) Traceback (most recent call last): File "", line 1, in ValueError: cannot reshape array of size 20 into shape (3,6) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında reshape işlemi asıl dizi üzerinde (in place biçimde) onun shape örnek özniteliğine yeni bir demet atayarak da yapılabilir. Örneğin: >>> a = np.random.randint(0, 100, (5, 4)) >>> a array([[89, 31, 87, 92], [ 3, 59, 49, 73], [85, 57, 31, 90], [62, 66, 53, 10], [45, 12, 21, 87]]) >>> a.shape = (2, 10) >>> a array([[89, 31, 87, 92, 3, 59, 49, 73, 85, 57], [31, 90, 62, 66, 53, 10, 45, 12, 21, 87]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.float32) print(a) a.shape = (4, 2) print(a) #------------------------------------------------------------------------------------------------------------------------------------ Çok boyutlu dizilerin tek boyutlu hale getirilmesi çokça gereksinim duyulan bir işlemdir. Örneğin elimizde 5x4'lük bir NumPy dizisi olsun. Biz bunu tek boyutlu bir dizi haline getirmeye çalışalım. Tabii bu işlemi reshap metoduyla ya da fonksiyonuyla yapabiliriz. Örneğin: >> a = np.random.randint(0, 100, (5, 4)) >>> a array([[39, 48, 33, 13], [95, 61, 38, 51], [27, 28, 26, 56], [32, 29, 55, 16], [12, 29, 34, 62]]) >>> b = a.reshape(20) >>> b array([39, 48, 33, 13, 95, 61, 38, 51, 27, 28, 26, 56, 32, 29, 55, 16, 12, 29, 34, 62]) Ancak bu işlem için ravel isimli bir metot ve global bir fonksiyon da bulundurulmuştur. ravel bize reshape işleminde olduğu gibi bir view nesnesi vermektedir. Örneğin: >>> b = a.ravel() >>> b array([39, 48, 33, 13, 95, 61, 38, 51, 27, 28, 26, 56, 32, 29, 55, 16, 12, 29, 34, 62]) >>> b[0] = 100 >>> a array([[100, 48, 33, 13], [ 95, 61, 38, 51], [ 27, 28, 26, 56], [ 32, 29, 55, 16], [ 12, 29, 34, 62]]) Aynı işlem flatten isimli metot ya da fonksiyonla da yapılabilmektedir. Ancak flatten metodu ya da fonksiyonu bir view nesnesi vermez. Gerçek nesnenin bir kopyasını vermektedir. Örneğin: >>> a = np.random.randint(0, 100, (5, 4)) >>> a array([[11, 90, 36, 4], [22, 26, 8, 48], [35, 79, 50, 18], [25, 52, 94, 26], [36, 83, 90, 21]]) >>> b = a.flatten() >>> b array([11, 90, 36, 4, 22, 26, 8, 48, 35, 79, 50, 18, 25, 52, 94, 26, 36, 83, 90, 21]) >>> b[0] = 100 >>> a array([[11, 90, 36, 4], [22, 26, 8, 48], [35, 79, 50, 18], [25, 52, 94, 26], [36, 83, 90, 21]]) >>> a[0, 0] = 200 >>> b array([100, 90, 36, 4, 22, 26, 8, 48, 35, 79, 50, 18, 25, 52, 94, 26, 36, 83, 90, 21]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisi tek boyutlu ya da çok boyutlu olabilir. Hatta boyutsuz NumPy dizileri de söz konusu olabilir. Ancak bu durumda dizi tek bir eleman içerebilir. Yani skaler bir değer sanki bir dizi değil de bağımısz tek bir değer gibi ndarray nesnesi biçiminde de ifade edilebilmektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a.shape (3, 3) >>> b = np.array([1, 2, 3, 4, 5]) >>> b array([1, 2, 3, 4, 5]) >>> b.shape (5,) >>> c = np.array(123) >>> c array(123) >>> c.shape () #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bazen arange ya da linspace fonksiyonlarından elde edilen değerler reshape işlemine sokulabilmektedir. Örneğin amacımız 0'dan 99'a kadar 10x10'luk bir matris elde etmek olsun. Bu işlemi pratik bir biçimde şöyle yapabiliriz: >>> a = np.arange(0, 100, 1).reshape(10, 10) >>> a array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [70, 71, 72, 73, 74, 75, 76, 77, 78, 79], [80, 81, 82, 83, 84, 85, 86, 87, 88, 89], [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]) arange bize tek boyutlu dizi verdiği için biz onu reshape metodu ile boyutlandırdık. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisi ndarray sınıfının tolist metodu ile Python listesine dönüştürülebilir.Örneğin: >>> a = np.arange(0, 20, 1).reshape(5, 4) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]]) >>> b = a.tolist() >>> b [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]] >>> a = np.random.random((2, 3)) >>> a array([[0.89825016, 0.64433664, 0.07580248], [0.62594548, 0.48109102, 0.08182892]]) >>> b = a.tolist() >>> b [[0.8982501574859335, 0.644336642866961, 0.07580248213618257], [0.6259454839761603, 0.4810910207754172, 0.08182892407036302]] #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[4, 2, 3], [7, 1, 9], [5, 10, 6]], dtype='float32') b = a.tolist() print(b, type(b)) # [[4.0, 2.0, 3.0], [7.0, 1.0, 9.0], [5.0, 10.0, 6.0]] #------------------------------------------------------------------------------------------------------------------------------------ NumPy dizileri değiştirilebilir (mutable) nesnelerdir. Biz bir NumPy dizisinin belli bir elemanına erişebiliriz. Onu değiştirebiliriz. Eleman erişimi genellikle tek bir köşeli bir parantez içerisinde boyutlar arasında ',' konarak yapılmaktadır. Bu biçimin Python'daki liste erişimlerden farklı olduğuna dikakt ediniz. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a[1, 2] = 100 >>> a array([[ 1, 2, 3], [ 4, 5, 100], [ 7, 8, 9]]) Aslında NumPy dizileriin elemanlarına Python listelerinde olduğu gibi birden fazla köşeli parantez ile de erişilebilir. Ancak bu biçimdeki erişimler daha yazvaş olma eğilimindedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a[1][2] = 100 >>> a array([[ 1, 2, 3], [ 4, 5, 100], [ 7, 8, 9]]) Burada a[1][2] erişiminde önce a[1] işlemi yapılır. Bu işlemden NumPy dizisinin 1'inci indeksli satırının hepsi bir NumPy dizisi biçiminde bir view nesnesi olarak elde edilmektedir. Sonra o dizinin 2 indesili elemanı değiştirilmiştir. Dolayısıyla daha fazla işlem yapılmış olmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a[1] array([4, 5, 6]) >>> a[1][2] = 100 >>> a array([[ 1, 2, 3], [ 4, 5, 100], [ 7, 8, 9]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32) print(a) val = a[1, 2] print(val) a[1, 2] = 100 print(a) #------------------------------------------------------------------------------------------------------------------------------------ Köşeli parantez ile elemana erişirken n boyutlu bir numpy dizisinde köşeli parantez içerisine k < n biçiminde k tane index girilebilir. Bu durumda geri kalan indexteki elemanlara bir NumPy dizisi olarak erişilecektir. Örneğin 3x3'lük bir a dizimiz olsun. Biz de erişimi a[1] biçiminde yapalım. Biz burada a dizisinin 1'inci indeksli satırınaki tüm değerleri numpy dizisi olarak bir view nesnesi biçiminde alırız. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a[0] array([1, 2, 3]) >>> a[1] array([4, 5, 6]) >>> a[2] array([7, 8, 9]) Başka bir deyişle biz n boyutlu bir NumPy dizisinin elemanına erişirken k < n tane indeks verebiliriz. Bu durumda diğer boyutların tüm elemanları elde edilmektedir. Örneğin: >>> a = np.arange(27).reshape(3, 3, 3) >>> a array([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 9, 10, 11], [12, 13, 14], [15, 16, 17]], [[18, 19, 20], [21, 22, 23], [24, 25, 26]]]) >>> a[0] array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) >>> a[0, 1] array([3, 4, 5]) >>> a[0, 1, 2] 5 Bu örnekte biz 3x3x3'lük bir NumPy dizisi oluşturduk. Biz bu diziyi "her biri 3x3'lük olan 3 elemanı bir dizi" gibi düşüneneliriz. Buradaki a[0] erişimi aslında 3 3l3manlı 3x3'lük dizinin 0'ınci indisli dizisi anlamına gelmektedir. a[0, 1] erişimi ise 3x3^lük 3 tane dizinin 0 indeksteki dizisinin 1'inci indeksteki satırı anlamına gelmektedir. Tabii bu erişimlerin hepsi bize birer view nesnesi vermeketedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32) print(a) b = a[0] print(b) #------------------------------------------------------------------------------------------------------------------------------------ NumPy dizileri üzerinde dilimleme (slicing) yapılabilir. Dilimleme işleminde tamamen Python listelerindeki semantik uygulanmaktadır. Dilimleme her boyut için ayrı ayrı yapılabilmektedir. Dilimleme işleminden yeni bir NumPy dizisi view nesnesi olarak elde edilmektedir. Dilimleme ilk boyuttan başlanarak boyut gerçekleştirilmektedir. Örneğin a dizisi 3x3x3 boyutunda olsun: >>> a = np.arange(27).reshape(3, 3, 3) >>> a array([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 9, 10, 11], [12, 13, 14], [15, 16, 17]], [[18, 19, 20], [21, 22, 23], [24, 25, 26]]]) Biz de a[1:, 0, 2] dilimlemesi yapmış olalım. İşlemler şöyle yürütülür: 1) Önce a[1] ve a[2] dizilerinden oluşan bir NumPy dizisi elde edilir: >>> a[1:] array([[[ 9, 10, 11], [12, 13, 14], [15, 16, 17]], [[18, 19, 20], [21, 22, 23], [24, 25, 26]]]) 2) Sonra bu dizilerin 0'ıncı indeksle elemanlarından bir dizi elde edilir: >>> a[1:, 0] array([[ 9, 10, 11], [18, 19, 20]]) 3) Sonra da bu dizinin 2'indeksli elemanları elde edilir: >>> a[1:, 0, 2] array([11, 20]) Örneğin yine aşağıdaki gibi 3x3x3'lik bir NumPy dizisi olsun: >>> a = np.arange(27).reshape(3, 3, 3) >>> a array([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 9, 10, 11], [12, 13, 14], [15, 16, 17]], [[18, 19, 20], [21, 22, 23], [24, 25, 26]]]) Biz şimdi bu diziden aşağıdaki matris parçasını elde etmek isteyelim: 22 23 25 26 Burada elde etmeye çalıştığımız matris parçası ilk boyutun 2'inci indeksindedir. Bu indeksteki matris 3x3'lüktür. Biz aslında bu matrisin 1, 2 satırlarıyla 1, 2 sütunlarını elde etmekt istemekteyiz: >>> a[2, 1:, 1:] array([[22, 23], [25, 26]]) Şimdi de bu diziden aşağıdaki matirisi elde etmek isteyelim: 1 2 10 11 19 29 İşlemi şöyle yapabiliriz: >>> a[:, 0, 1:] array([[ 1, 2], [10, 11], [19, 20]]) Aşağıdaki örnekte 4X5'lik bir numpy dizisinde satır ve sütun üzerinde ayrı ayrı dilimleme yapılarak bunumpy dizisinden bir alt dizi elde edilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]], dtype=np.float32) print(a) b = a[1:3, 1:3] print(b) #------------------------------------------------------------------------------------------------------------------------------------ 44. Ders 24/05/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir resim aslında pixel'lerden oluşmaktadır. Resim eğer renkli ise her pixel RGB biçiminde üç ayrı byte'tan oluşur. Bu durumda örneğin 32X32'lik renki bir resmin pixel verileri 32X32X3'lük bir NumPy dizisi ile ifade edilebilir. Aşağıdaki örnekte makine öğrenmesinde sınıflandırma amacıyla kullanılan CIFAR10 isimli veri kümesindeki resimlerin ilk 10 tanesi gösterilmiştir. Sonra resimlerden birinin sol üst köşesi dilimleme yapılarak alınıp o kısım görüntülenmiştir. Bu veri kümesi her biri 32X32'lik 60000 tane resimden oluşmaktadır. Bu örnekte kullandığımız imshow isimli fonksiyon resmi orijinal büyüklüğünde değil büyüterek göstermektedir. Örnekte kullandığımız veri kümesi "tensorflow" isimli kütüphanenin içerisindedir. Bu kütüphaneyi aşağıdaki gibi kurabilirsiniz: pip install tensorflow #------------------------------------------------------------------------------------------------------------------------------------ from tensorflow.keras.datasets import cifar10 (training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data() import matplotlib.pyplot as plt for i in range(10): plt.imshow(training_dataset_x[i]) plt.show() plt.imshow(training_dataset_x[7]) plt.show() image = training_dataset_x[7] cropped_image = image[:16, :16, :] plt.imshow(cropped_image) plt.show() from PIL import Image image = Image.frombytes('RGB', (32, 32), training_dataset_x[7].flatten()) image.show() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirtildiği gibi elemana erişim sırasında ilk k tane boyut için indeks girilmiş ancak geri kalan indeksler girilmemişse aslında girilmeyen indekslerin hepsi işleme dahil edilmektedir. Örneğin elimizde iki boyutlu a isimli bir NumPy dizisi olsun. Bu durumda örneğin a[1] biçiminde bir erişim a[1, :] anlamına gelmektedir. Ya da örneğin b üç boyutlu bir NumPy dizisi olsun. Bu durumda a[k] gibi bir ifade ile a[k, :, :] ifadesi eşdeğerdir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]], dtype=np.float32) val = a[1] print(val) val = a[1, :] print(val) val = a[:, 4] print(val) #------------------------------------------------------------------------------------------------------------------------------------ Bir numpy dizisinin elemanları [...] operatörü ile elde edildiğinde ürünün boyutuna dikkat ediniz. Örneğin iki boyutlu matristen bir satırı ya da sütunu çektiğimizde elde ettiğimiz dizi tek boyutlu olmaktadır. Ancak biz iki boyutlu matrisin belli bir kısmını çektiğimizde elde edilen dizi iki boyutlu olmaktadır. Başka bir deyişle elde çok boyutlu bir diziden elde edilen dizi durumda göre tek boyutlu ya da çok boyutlu olabilmektedir. Biz bir numpy dizisinin tek bir elemanını elde ettiğimizde bu eleman Python türünden int, float ya da bool olmaz. C Programlama Dilindeki türlerden yani dtype türlerinden olur. Tek elemanlı değerler skaler türdendir. Bunların shape özellikleri boştur. Tabii bu NumPy skalerlerini biz istersek tür dönüştürmesi ile Python türlerine dönüştürebiliriz. Örneğin: >>> a = np.random.randint(0, 100, (3, 3)) >>> a array([[83, 60, 50], [ 5, 28, 98], [34, 39, 56]]) >>> x = a[0, 0] >>> x 83 >>> type(x) >>> x.shape () >>> int(x) 83 NumPy skalerlerini biz aritmetik işlemlere sokarsak yine NumPy skaleri elde ederiz. Örneğin: >>> x = np.float32(3.14) >>> x 3.14 >>> type(x) >>> y = x + 2 >>> y 5.140000104904175 >>> type(y) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]], dtype=np.float32) val = a[1] print(type(val), val.shape) # (5,) val = a[:, 4] print(type(val), val.shape) # (4,) val = a[2:4, 1:3] print(type(val), val.shape) # (2, 2) val = a[3, 4] print(type(val), val.shape) # () x = val + 2 print(type(x), x.shape) # () y = float(x) print(type(y)) # #------------------------------------------------------------------------------------------------------------------------------------ Dilimleme sırasında bir "view" nesnesinin elde edildiğine dikkat ediniz. Dilimlenmiş bilgi aslında çok küçük bir bilgidir. Biz büyük bir NumPy dizisini dilimleyerek bir dizi elde ettiğimizde aslında bu dilimlenmiş dizi bellek çok az yer kaplayacaktır. Şayet dilimlemede kopya çıkartılacak olsaydı bu durumda dilimlenmiş nesne bellekte çok yer kaplardı. Özetle dilimleme işleminin bellek maliyeti azdır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Programcı bazen bir view nesnesi elde etmek istemeyebilir. Gerçekten orijinal nesnenin bir kopyasını oluşturmak isteyebilir. Bunun için ndarray sınıfının copy metodu ya da copy fonksiyonu kullanılabilir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = a.copy() >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a[0, 0] = 100 >>> a array([[100, 2, 3], [ 4, 5, 6], [ 7, 8, 9]]) >>> b array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]], dtype=np.float32) b = a.copy() a[0, 0] = 100 print(a) print(b) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirttiğimiz gibi biz bir NumPy dizisinden tek bir eleman çektiğimizde o bir NumPy dizisi olmaz, bir NumPy skaleri olur. Bu skaler numpy modülündeki dtype türlerine ilişkin bir nesne biçimindedir. Aslında dtype türleri birer sınıf belirtmktedir ve biz o sınıflar türünden de nesneler yaratabiliriz. Örneğin: x = np.float32(10) Biz buradan C'deki "float" türüne ilişkin (yani 4 byte uzunlukta gerçek sayı türüne ilişkin) bir nesne elde etmiş olduk. Örneğin: >>> x = np.float32(3.14) >>> x 3.14 >>> type(x) >>> x.shape () >>> float(x) 3.140000104904175 #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np x = np.float32(10) print(x, type(x), x.shape) # 10.0 () #------------------------------------------------------------------------------------------------------------------------------------ Elimizde bir NumPy skaleri yani dtype nesnesi varsa o nesnenin içerisindeki değeri Python türü olarak iki biçimde elde edebiliriz: Python türüne tür dönüştürmesi yapmak ve dtype sınıflarının item metotlarını kullamak. Örneğin: >>> x = np.float32(3.14) >>> float(x) 3.140000104904175 >>> x.item() 3.140000104904175 #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np x = np.float32(10) print(x, type(x)) y = x.item() print(y, type(y)) x = np.int8(3) print(x, type(x)) y = x.item() print(y, type(y)) #------------------------------------------------------------------------------------------------------------------------------------ dtype türleri farklı olan bazı NumPy dizileri ya da skalerleri kendi aralarında işleme sokulabilir. Bu durumda işlem elde edilen dizi ya da skaler iki türü büyüklük bakımından kapsayan bir türünden olmaktadır. Örneğin dtype='int32' olan bir dizi ile dtype='float32' olan bir diziyi toplarsak elde ettiğimiz dizinin dtype türü 'float64' olmaktadır: >>> a = np.array([1, 2, 3], dtype='int32') >>> b = np.array([1.1, 2.1, 3.1], dtype='float32') >>> c = a + b >>> c array([2.10000002, 4.0999999 , 6.0999999 ]) >>> c.dtype dtype('float64') Örneğin: >>> x = np.int8(10) >>> y = np.int16(20) >>> z = x + y >>> z.dtype dtype('int16') #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden de belirttiğimiz gibi dilimleme işlemi her zaman bir NumPy view nesnesinin elde edilmesine yol açmaktadır. Ancak dilimleme olmaksızın belli bir elemana erişildiğinde dtype türlerine ilişkin bir skaler nesne elde edilmektedir. Bu dtype nesneleri bir view belirtmezler. Örneğin: >>> a = np.array([1, 2, 3], dtype='int32') >>> x = a[0] >>> x = 12 >>> type(x) >>> a array([1, 2, 3]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi elimizde bir ndarray nesnesi (yani NumPy dizisi) varsa, biz ndarray nesnesinin bir view nesnesi mi yoksa gerçek bir nesne mi olduğunu nasıl anlarız? Bunun birkaç yolu vardır. En dolaysız yolu nesnenin base isimli örnek özniteliğine bakmaktır. Eğer nesne bir view nesnesi değil de gerçek bir nesne ise base örnek özniteliği None değerinde olacaktır. Eğer nesne bir view nesnesi ise base örnek özniteliği gerçek nesnenin referansını verecektir. (View nesneleri aslında bir biçimde gerçek nesnenin adresini ve onun hangi kısmına referans edildiği bilgisini tutmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [5, 6, 7], [7, 8, 9]], dtype='float32') >>> a array([[1., 2., 3.], [5., 6., 7.], [7., 8., 9.]], dtype=float32) >>> b = a[1:, 1:] >>> b array([[6., 7.], [8., 9.]], dtype=float32) >>> print(b.base) [[1. 2. 3.] [5. 6. 7.] [7. 8. 9.]] >>> print(a.base) None >>> id(a) 1897598332432 >>> id(b) 1897598330320 >>> id(b.base) 1897598332432 #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) print(a.base) # None b = a[1, :] print(id(a)) print(id(b.base)) print(a is b.base) # True #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden de belirtildiği gibi aslında reshape işlemi gerçek nesneye referans eden bir view nesnesi oluşturmaktadır. Örneğin: >>> a = np.arange(20) >>> b = a.reshape(4, 5) >>> a array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) >>> b array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) >>> b.base is a True #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.arange(20) b = a.reshape(4, 5) print(a.base) # None print(b.base is None) # False #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte aslında elde edilen a nesnesi bir view nesnesidir: >>> a = np.arange(20).reshape(5, 4) >>> a.base array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.arange(0, 20, dtype='float32').reshape(4, 5) print(a.base is None) # False #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisi dolaşılabilir bir nesneyle indekslenebilir. Bu durumda ilgili indeksteki elemanlar bir NumPy dizisi biçiminde elde edilmektedir. Örneğin: >>> a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> a array([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> b = a[[1, 4, 6]] >>> b array([20, 50, 70]) Burada biz a deizinin sırasıyla 1, 4, ve 6 numaralı indekslerindeki elemanlardna oluşan bir NumPy dizisi elde etmiş olduk. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) b = a[[2, 5, 3, 1]] print(b) # [30 60 40 20] #------------------------------------------------------------------------------------------------------------------------------------ Demetlerin köşeli parantezdeki özel anlamını anımsayınız. Python'd aher zaman a[x, y, z] sentaksı ile a[(x, y, z)] sentakı aynı anlamdadır. Bu nedenle dolaşılabilir nesnenin demet biçiminde oluşturulması error oluşturabilecektir. Örneğin: >>> a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> b = a[(1, 4, 6)] Traceback (most recent call last): File "", line 1, in IndexError: too many indices for array: array is 1-dimensional, but 3 were indexed Fakat yine biz bu tür durumlarda demet kullanabiliriz. Ancak demetten sonra ekstra bir ',' atomu da sentaksta kullanılmalıdır. Örneğin: >>> b = a[(1, 4, 6), ] >>> b array([20, 50, 70]) Anımsanacağı gibi Python'da genel olarak "virgüllü listelerde (comma separeted lists)" son bir virgül atomu hataya yol açmamaktadır. Örneğin: x = [10, 20, 30, ] y = 10, 20, 30, Yani biz bir NumPy dizisinin tek bir elemanına erişirken de eksta ',' atomu kullanak bu sentaks bakımından geçerlidir. İndekslemede kullanılan bu ekstra ',' aslında indeksin bir demet olduğunu belirtmektedir. Yani örneğin a[1, ] ifadesi aslında a[(1, )] ifadesi ile eşdeğerdir. Yukarıda da belirtildiği gibi NumPy dizileri demetle indekslenebilmektedir. Ancak Python listeleri demetlerle indekslenemez. Örneğin: >>> a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> a[1] 20 >>> a[1, ] 20 >>> a[(1, )] 20 Bir NumPy dizisini dolaşılabilir bir nesneyle indekslediğimizde elde edilen dizi bir view belirtmemektedir. Örneğin: >>> a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> a array([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> b = a[[1, 3, 5]] >>> print(b.base) None >>> c = a[[1, 2, 3]] >>> print(c.base) None #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) b = a[(2, 5, 3, 1),] print(b) # [30 60 40 20] #------------------------------------------------------------------------------------------------------------------------------------ Çok boyutlu dizilerde de her boyut için dolaşılabilir bir nesne ile indeks belirtilebilir. Bu durumda indekslerdeki dolaşılabilir nesnelerin elemanlarının eşit uzunlukta olması gerekir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = a[[0, 2], [1, 2]] >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b array([2, 9]) Burada dilimleme yapılmamaktadır. a[[0, 2], [1, 2]] ifadesini görenler sanki burada "0 ve 2 indeksli satırların, 1 ve 2 indeksli sütunların seçildiğini" sanmaktadır. Halbuki burada aslında a[0, 2] ve a[2, 2] elemanları seçilmiştir. Örneğin: >>> a = np.random.randint(0, 100, (10, 10)) >>> a array([[90, 43, 86, 40, 96, 68, 75, 36, 63, 30], [85, 8, 59, 43, 43, 60, 2, 21, 80, 50], [49, 71, 58, 54, 60, 94, 49, 58, 73, 27], [95, 95, 31, 67, 5, 42, 14, 71, 66, 57], [17, 37, 50, 44, 35, 88, 3, 7, 53, 66], [84, 50, 41, 18, 8, 50, 93, 99, 49, 47], [18, 62, 29, 44, 23, 80, 35, 98, 26, 59], [59, 44, 20, 75, 19, 93, 80, 54, 75, 10], [21, 24, 77, 66, 28, 40, 61, 52, 80, 78], [56, 66, 68, 98, 45, 56, 39, 16, 62, 93]]) >>> a[[3, 6, 8, 2], [1, 7, 4, 6]] array([95, 98, 28, 49]) İndeksleme sırasında bir indeks dolaşılabilir nesneyle diğer indeks dilimleme yoluyla da belirtilebilir. Örneğin: >>> a[[3, 6, 8, 2], :] array([[95, 95, 31, 67, 5, 42, 14, 71, 66, 57], [18, 62, 29, 44, 23, 80, 35, 98, 26, 59], [21, 24, 77, 66, 28, 40, 61, 52, 80, 78], [49, 71, 58, 54, 60, 94, 49, 58, 73, 27]]) >>> a[[3, 6, 8, 2], 2:5] array([[31, 67, 5], [29, 44, 23], [77, 66, 28], [58, 54, 60]]) Tabii boyutların hespsinde dolaşılabilir nesne ya da dilimleme yapılmak zorunda değildir. Örneğin: >>> a[[3, 6, 8, 2], 2] array([31, 29, 77, 58]) Ancak birden fazla indekste dolaşılabilir nesne kullanılacaksa bunların eleman sayısının aynı olması gerekmektedir. Örneğin: >>> a[[3, 6, 8, 2], [2, 5]] Traceback (most recent call last): File "", line 1, in IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (4,) (2,) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.arange(100).reshape(10, 10) print(a) b = a[[1, 2, 4], [1, 4, 6]] # a[1, 1], a[2, 4], a[4, 6] print(b) b = a[[1, 2, 4], 5] # a[1, 5], a[2, 5], a[4, 5] print(b) #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisine bool indeksleme uygulanabilir. bool indeksleme için dizi uzunluğu ve boyutu kadar bool türden dolaşılabilir bir nesne girilir. Bu dolaşılabilir nesnedeki True olan elemanlara karşı gelen dizi elemanları elde edilmektedir. Örneğin: array([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) >>> a[[True, False, True, True, False, True, True, False, True, True]] array([ 10, 30, 40, 60, 70, 90, 100]) Tabii çok boyutlu NumPy dizilerinde de benzer biçimde bool indeksleme yapılabilmektedir. Bu durumda her boyut için ayrı bir dolaşılabilir ayrı bir bool nesne verilebilir. Elde edilecek değerler Bu bool nesnelerdeki True olan boyutların kesişimlerindeki değerler olacaktır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a[[True, False, True], [False, True, True]] array([2, 9]) Buradaki seçim mantığı şöyledir: Önce True olan satır ve sütunların yerine onların indeks numaraları yerleştirilir. Sonra daha önce gördüğümüz gibi sanki birden fazla boyutta dolaşılabilir nesnede indeks varmış gibi işlem yapılır. Örneğin: a[[True, False, True], [False, True, True]] Bu işlemin eşdeğeri şöyledir: a[[0, 2], [1, 2]] Bu da a[0, 2] ve a[2, 2] elemanları anlamına gelmektedir. Tabii bu biçimdeki indekslemede boyutlardaki True elemanlarının sayısının aynı olması gerekir. Örneğin: a[[True, True, False], [False, True, True]] Bu indekslemenin eşdeğeri şöyledir: a[[0, 1], [1, 2]] Bu da şu dizinin elde edilmesine yol açacaktır: array([2, 6]) Çok boyutlu NumPy dizileri aynı boyuttaki bool türden NumPy dizileri ile indekslenebilmektedir. Örneğin: >>> a = np.random.randint(0, 100, (3, 3)) >>> a array([[47, 88, 61], [13, 54, 89], [33, 33, 16]]) >>> b = np.array([[True, False, True], [True, True, False], [False, False, True]]) >>> a[b] array([47, 61, 13, 54, 16]) Ancak çok boyutlu NumPy dizileri bool türden liste listeleriyle (yani iki boyutlu listelerle) indekslenememektedir. Örneğin: >>> b = [[True, False, True], [True, True, False], [False, False, True]] >>> a[b] Traceback (most recent call last): File "", line 1, in IndexError: too many indices for array: array is 2-dimensional, but 3 were indexed Burada bu listenin dolaşılmasında listeler elde edilmektedir. Yani b listesi dolaşıldığında şunlar elde edilmektedir: [True, False, True] [True, True, False] [False, False, True] #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bool indeksleme filtreleme yapmak için çokça kullanılmaktadır. Anımsanacağı gibi bir NumPy dizisi işlemlere sokulduğunda aslında dizinin her elemanı işleme sokulmaktadır. a bir NumPy dizisi olmak üzere biz a > 40 gibi bir işlem yaparsak burada dizinin her elemanı 40'tan büyük mü diye kontorl edilecek ve dizi uzunluğu kadar uzunlukta bir bool türdne bir NumPy dizisi elde edilecektir. Örneğin: >>> a = np.array([32, 45, 12, 67, 18, 41, 92, 9, 12]) >>> a array([32, 45, 12, 67, 18, 41, 92, 9, 12]) >>> a > 40 array([False, True, False, True, False, True, True, False, False]) İşte bu tür karşılaştırma operatörlerinden elde edilen dizi bool indekslemede kullanılırsa "belli koşulu sağlayan elemanlar" filtrelenebilir. Örneğin: >>> a[a > 40] array([45, 67, 41, 92]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([3, 5, 34, 12, 9, 37, 32, 10]) result = a > 15 print(result) # [False False True False False True True False] b = a[result] print(b) # [34 37 32] b = a[a > 15] # a dizisi içerisindeki 15'ten büyük olan değerler elde ediliyor print(b) # [34 37 32] #------------------------------------------------------------------------------------------------------------------------------------ 45. Ders 29/05/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Örneğin bir NumPy dizisinin ortalamadan küçük elemanlarını a[a > np.mean(a)] biçiminde elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([3, 5, 34, 12, 9, 37, 32, 10]) print(np.mean(a)) result = a[a < np.mean(a)] print(result) #------------------------------------------------------------------------------------------------------------------------------------ Örneğin bir NumPy dizisinin çift elemanlarını da aynı biçimde elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([3, 5, 34, 12, 9, 37, 32, 10]) print(np.mean(a)) result = a[a % 2 == 0] print(result) #------------------------------------------------------------------------------------------------------------------------------------ Köşeli parantez içerisindeki "... (ellipsis)" sentaksı "ondan önceki ya da sonraki eksenlerin hepsi dahil edilecek" anlamına gelmektedir. Örneğin a dizisinin 4 boyutlu olduğunu düşünelim. a[k, ...] ifadesi tamamen a[k, :, :, :] ile eşdeğerdir. Örneğin a[1, ..., 3] ifadesi ise a[1, :, : 3] ile eşdeğerdir. a[1, 2, ..., 5] ifadesi a[1, 2, :, 5] ile eşdeğerdir. Ancak ellipsis köşeli parantez içerisinde yalnızca bir kez kullanılabilir. Örneğin: >>> a = np.random.randint(0, 100, (3, 3, 3)) >>> a array([[[57, 31, 50], [12, 82, 96], [83, 5, 48]], [[92, 50, 83], [28, 62, 20], [28, 97, 38]], [[78, 91, 48], [76, 9, 54], [43, 59, 76]]]) >>> b = a[..., 2] >>> b array([[50, 96, 48], [83, 20, 38], [48, 54, 76]]) >>> b = a[:, :, 2] >>> b array([[50, 96, 48], [83, 20, 38], [48, 54, 76]]) >>> b = a[2, ...] >>> b array([[78, 91, 48], [76, 9, 54], [43, 59, 76]]) >>> b = a[2, :, :] >>> b array([[78, 91, 48], [76, 9, 54], [43, 59, 76]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.arange(27).reshape(3, 3, 3) print(a) print('----------------') b = a[1, ...] # eşdeğeri a[1, :, :] print(b) #------------------------------------------------------------------------------------------------------------------------------------ Aslında NumPy'da yukarıda ele almadığımız değişik dtype türleri de vardır. Ancak bu konunun ayrıntılarını burada ele almayacağız. Değişik türler array fonksiyonunda hiç dtype belirtilmeden bir araya getirilirse array fonksiyonu eğer mümkünse bunların hepsini yazıya dönştürüp sanki birer yazı gibi saklamaktadır. Örneğin: >>> a = np.array([1, 'ankara', True]) >>> a array(['1', 'ankara', 'True'], dtype='>> a = np.array([12, 34.5, 'ankara'], dtype=object) >>> a array([12, 34.5, 'ankara'], dtype=object) >>> type(a[0]) >>> type(a[1]) >>> type(a[2]) #------------------------------------------------------------------------------------------------------------------------------------ a = np.array([[1, 2, 'Erkek'], [4, 5, 'Kadın'], [3, 6, 'Erkek'], [5, 8, 'Kadın']], dtype=object) print(a, a.dtype) #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisini transpoze etmek için ndarray sınıfının transpose metodu ya da transpose fonksiyonu kullanılmaktadır. Aynı zamanda ndarray nesnesinin T isimli örnek öniteliği de transpose matrisini bize vermektedir. Yani a isimli NumPy dizisinin transpose edilmiş hali aşağıdaki gibi üç biçimde elde edilebilir: b = a.transpose() b = np.transpose(a) b = a.T Transpoze işlemi her zaman bir view nesnesi oluşturularak yapılmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = a.transpose() >>> b array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) >>> b.base array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> c = np.transpose(a) >>> c array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) >>> c.base array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> d = a.T >>> d array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) >>> d.base array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) Çok boyutlu dizilerin transpoze edilmeleri eksen temelinde yapılmaktadır. Yani hangi boyutların hangi boyutlarla transpoze edileceği ayrı ayrı belirtilebilmektedir. Örnein: >>> a = np.random.randint(0, 100, (3, 3, 3)) >>> a array([[[51, 90, 29], [ 7, 20, 73], [33, 89, 21]], [[12, 41, 70], [29, 35, 13], [92, 26, 61]], [[21, 82, 33], [ 3, 3, 44], [22, 42, 37]]]) >>> a.transpose([1, 2, 0]) array([[[51, 12, 21], [90, 41, 82], [29, 70, 33]], [[ 7, 29, 3], [20, 35, 3], [73, 13, 44]], [[33, 92, 22], [89, 26, 42], [21, 61, 37]]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2], [4, 5], [7, 8]], dtype='float32') print(a, a.shape, end='\n\n') b = np.transpose(a) print(b, b.shape, end='\n\n') print(b.base is None) # False #------------------------------------------------------------------------------------------------------------------------------------ İki NumPy dizisi üzerinde toplama, çarpma, çıkarma, bölme ya da karşılaştırma gibi işlemler yapıldığında aslında ndarray sınıfının operatör metotları çağrılmaktadır. Bu operatör metotları da NumPy dizilerinin karşılıklı elemanları üzerinde işlemler yapmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b array([[3, 1, 4], [1, 5, 9], [2, 6, 5]]) >>> c = a * b >>> c array([[ 3, 2, 12], [ 4, 25, 54], [14, 48, 45]]) >>> d = a + b >>> d array([[ 4, 3, 7], [ 5, 10, 15], [ 9, 14, 14]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype='float32') b = np.array([[2, 1, 4], [1, 3, 5], [1, 1, 2]], dtype='float32') c = a + b print(c) c = a * b print(c) c = a / b print(c) c = a - b print(c) #------------------------------------------------------------------------------------------------------------------------------------ İki NumPy dizisinin işleme sokulabilmesi için bunların boyutlarının aynı olması gerekir. Ancak boyutları aynı olmayan NumPy dizileri eksen temelinde de işlemlere sokulabilmektedir. Bu tür işlemlere İngilizce "broadcasting" denilmektedir. Örneğin 3 elemanlı bir NumPy dizisi 3x3'lük bir numpy dizisi ile işleme sokulabilir. Bu durumda aslında 3 elemanlık dizi bu 3x3'lük matrisin her satırı ile işleme sokulacaktır. İşte bu duruma "broadcasting" denilmektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([1, 3, 5]) >>> c = b + a >>> c array([[ 2, 5, 8], [ 5, 8, 11], [ 8, 11, 14]]) Broadcasting sütun temelinde de yapılabilmektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([[1], [3], [5]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b array([[1], [3], [5]]) >>> c = b + a >>> c array([[ 2, 3, 4], [ 7, 8, 9], [12, 13, 14]]) Tabii * ve / operatörlerinin bu bağlamda değişme özellikleri vardır. Yani yukarıdaki örneklerde operand'ları yer değiştirdiğimizde de aynı sonuçları buluruz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([1, 2, 3]) b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype='float32') c = a + b print(c, end='\n\n') c = a * b print(c) #------------------------------------------------------------------------------------------------------------------------------------ Bir skaler bir NumPy dizisi ile işleme sokulabilir. Bu da bir çeşit "broadcasting" işlemidir. Bu durumda skaler NumPy dizisinin her elemanıyla işleme sokulmuş olur. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = 2 * a >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b array([[ 2, 4, 6], [ 8, 10, 12], [14, 16, 18]]) >>> b = a * 2 >>> b array([[ 2, 4, 6], [ 8, 10, 12], [14, 16, 18]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype='float32') c = 3 * a print(c, end='\n\n') #------------------------------------------------------------------------------------------------------------------------------------ İki NumPy dizisinin çarpılmasının bir matris çarpımı olmadığına dikkat ediniz. Bu durumda iki dizinin karşılıklı elemanları birbirleriyle çarpılmaktadır. Eğer gerçekten matris çarpımının yapılması isteniyorsa matmul isimli fonksiyon ya da @ operatörü kullanılmalıdır. @ operatörü Python'a 3.5 versiyonuyla eklenmiştir. Bu operatör aslında sınıfın __matmul__ metodunu çağırmaktadır. Yani a @ b işlemi ile a.__matmul__(b) işlemi eşdeğerdir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 1], [3, 1, 2], [1, 3, 2]]) b = np.array([[3, 2, 1], [1, 1, 1], [1, 2, 3]]) c = np.matmul(a, b) print(c) c = a @ b print(c) #------------------------------------------------------------------------------------------------------------------------------------ Matris çarpımını andıran "dot product" denilen bir işlem de vardır. Dot product iki dizinin karşılıklı elemanlarının çarpılartak toplanması anlamına gelir. Dot product işlemi dot isimli fonksiyonla ya da ndarray sınıfının dot metoduyla yapılabilmektedir. Örneğin: >>> a = np.array([1, 2, 3, 4, 5]) >>> b = np.array([3, 2, 4, 1, 2]) >>> c = np.dot(a, b) >>> c 33 >>> c = a.dot(b) >>> c 33 Eğer diziler iki boyutlu ise dot product işlemi matris çarpımı gibi yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ NumPy kütüphanesi istatistiksel uygulamalarda ve makine öğrenmesi uygulamalarında sıkça kullanılmaktadır. İstatistikte bir varlığına (entity) ilişkin özellikler birbirinden farklı olabilmektedir. Örneğin bir kişinin boy uzunluğu, kilosu, yaşı farklı türden niceliklerdir. İşte varlıklara ilişkin onların farklı özelliklerinin oluşturduğu topluluğa istatistikte "veri kümesi (data set)" ya da "veri tablosu (data table)" denilmektedir. Veri kümeleri ya da veri tabloları daha önce görmüş olduğumuz veritabanı tablolarına oldukça benzemektedir. Bir veri kğmesindeki sattırlara genellikle "satır (row)" denir. Sütunlara ise "sütun (column) ya da daha sıklıkla "özellik (feature)" denilmektedir. Örneğin kişilerin boy uzunluklarından, kilolarından ve yaşlarından oluşan aşağıdaki gibi bir veri kümesi söz konusu olsun: Boy Kilo Yaş 182 90 42 178 82 34 168 71 37 ... ... ... Burada "Boy", "Kilo" ve "Yaş" sütunları bu veri kümesinin özelliklerini oluşturmaktadır. İstatistikteki veri kümeleri NumPy'da iki boyutlu NumPy dizileriyle temsil edilmektedir. Bu tür veri kümelerinde sütun temelinde ya da satır temelinde istatistiksel işlemler yapılmak istenebilir. Örneğin yukarıdaki veri kğmesinde biz kişilerin boy ortalamalarını, Kilo ortalamalarını ve Yaş ortalamalarını bulmak isteyebiliriz. Bunun sütun temelinde ya da satır temelinde işlemlerin yapılması gerekmektedir. İşte bir matirisin satırları üzerinde ya da sütunları üzerinde işlemler yapmaya "eksenli işlemler" denilmektedir. NumPy'da pek çok fonksiyonun ve metodun "axis" isimli bir parametresi vardır. Bu axis parametresi işlemlerin satır temelinde mi sütun temelinde mi yapılacağını belirtir. Tabii aslında NumPy dizisi üç boyutlu ya da daha fazla boyutlu olabilir. Bu durumda eksen işlemleri diğer boyutlara göre de yapılabilir. Ancak uygulamada genellikle iki boyutlu NumPy dizileri üzerinde işlemler yoğun bir biçimde yapılmaktadır. Örneğin sum isimli fonksiyonun parametrik sayısı şöyledir: numpy.sum(a, axis=None, dtype=None, out=None, keepdims=, initial=, where=) Görüldüğü gibi fonksiyonun bir axis parametresi vardır. Eğer fonksiyonlar ve metotlarda axis paranetresi girilmezse fonkssiynlar ve metotlar default durumda tüm elemanları işleme sokmaktadır. Örneğin: >>> dataset = np.array([[182, 90, 42], [178, 82, 34], [168, 71, 27]]) >>> dataset array([[182, 90, 42], [178, 82, 34], [168, 71, 27]]) >>> np.sum(dataset) 874 Burada axis belirtilmediği için matirisn tüm elemanlarının toplamı elde edilmiştir. Pekiyi axis paramtresi nasıl girilmelidir? axis parametresine 0'dan itibaren dizinin boyut sayısından 1 eksiğine kadar tamsayılar girilebilir. Örneğin iki boyutlu bir NumPy dizisi için axis parametresine 0 ya da 1 girebiliriz. Üç boyutlu bir NumPy dizisi için axis parametresine 0, 1 ya da 2 girebiliriz. Axis numaralandırması indekslemedeki sıraya göre yapılmaktadır. Örneğin iki boyutlu a isimli bir NumPy dizisi olsun. Bu dizinin bir elemanına biz a[i, k] biçiminde erişiriz. İşte bu ilk indeksle (i indeksi) belirtilen eksen 0'ıncı eksen ikinci indeksle belirtilen (k indeksi) eksen ise 1'inci eksendir. Eğer a dizisi üç boyutlu olsaydı biz dizinin bir elemanına a[i, k, j] biçiminde erişirdik. Bu duurmda ilk index 0'ıncı eksene, ikinci indeks 1'inci eksene ve üçüncü indeks 2'inci eksene ilişkin olacaktır. İşte eksensel işlemler yaparken işlemlerde handi eksen değiştiriliyorsa axis parametresi o eksseni belirtiecek biçimde verilmelidir. Aşağıdaki veri tablosuna bir daha bakınız: Boy Kilo Yaş 182 90 42 178 82 34 168 71 37 ... ... ... Biz burada sütunsal ortalamaları bulmak istiyorsak bunun için satır indekslerini değiştiririz. Yani a[i, k] erişimlerinde i'leri değiştirerek bunu yapabiliriz. O halde burada sütunsal işlemler için axis parametresi axis=0 biçiminde girilmelldir. Eğer biz satırsal ortalamalrı bulmak isteseydik a[i, k] erişimlerinde k'ları değiştirmemiz gerekirdi. Bunun için axis parametresini axis=1 biçiminde kullanacaktık. Yani işlemde hangi eksende değişik yapılarak işlem yürütülecekse axis parametresi o ekseni belirtecek biçimde girilmelidir. Bir NumPy dizisine satır ya da sütun eklemek, bir NumPy dizisinden satır ya da sütun silmek söz konusu olduğunda eksen değişiliğin hangi boyutta yapılacağına göre belirlenmektedir. Örneğin biz NumPy dizisine satır eklemek istediğimizde toplamda satır düzeyinde bir değişiklik gerçekleşecektir. Bu nedenle burada eksen 0 olarak girilmelidir. Benzer biçimde örneğin biz bir sütunu silmek istesek bunun ekseni 1 olarak girmeliyiz. Yani ekleme ve silme gibi işlemlerde boyutsal değişikliğin hangi eksen üzerinde gerçekleştiğine göre eksen parametresi girilmektedir. Genellikle bu durumda NumPy kullanıcılarının kafası karışmaktadır. Çünkü NumPy kullanıcıları örneğin bir satırın ekleneceği durumda sanki sütunsal bir değişiklik yapılıyormuş gibi düşünmektedir. Halbuki burada satırsal bir boyut değişikliği söz konusudur. Bu nedenle eksen parametresi 1 değil 0 olarak girilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 46. Ders 31/05/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ N boyutlu bir numpy dizisinde indeksleme yapılırken girilen değerler sırasıyla axis numaralarını verir. Örneğin üç boyutlu bir a dizisinde belli bir indekse a[i, k, n] gibi üç indeksle erişiriz. Buradaki i indeksiyle belirttiğimiz ilk boyut axis = 0'dır. k indeksi ile belirttiğimiz ikinci boyut axis=1'dir. Nihayet n indeksiyle belirttiğimiz üçüncü boyut ise axis=2'dir. İşlemler sırasında axis değerinin ne olacağını belirlemek Numpy'ı yeni öğrenenler için zor olabilmektedir. Bir işlem yapılırken satır ya da sütun değeri elde edilirken bu işlem hangi boyuttaki değerleri değiştirmek gerekiyorsa, yani işlem yapılırken hangi boyuttaki indeksi değiştirmek gerekiyorsa axis o değerdir. Örneğin bir np.mean mean fonksiyonunda sütunların kendi aralarındaki ortalamayı bulmak isteyelim. Bu durumda bizim satır değerlerini değiştiriyor olmamız gerekir. Çünkü bir sütunun ortalaması bulunurken satırsal değişimle sütun taranır. Bir satırların kendi aralarındaki ortalamasını alacak olsaydık bu durumda satır ortalaması için sütunlardaki indeksler artırılır. Yani değişim sütunlarda yapılmaktadır. O halde satır ortalamaları için axis=1 olmalıdır. Bazı numpy fonksiyonlarında axis parametresinin default değeri None biçimdedir. Bu durumda tüm dizi sanki tek boyutluymuş gibi tek bir değer elde edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Şimdi eksensel işlemler yapan bazı fonksiyonlara ve metotlara değinelim: - sum fonksiyonu ve ndarray sınıfınıun sum metodu eksensel toplama işlemi yapmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> result = np.sum(a, axis=1) >>> result array([ 6, 15, 24]) >>> result = np.sum(a, axis=0) >>> result array([12, 15, 18]) >>> result = np.sum(a) >>> result 45 >>> result = a.sum(axis=1) >>> result array([ 6, 15, 24]) >>> result = a.sum(axis=0) >>> result array([12, 15, 18]) - mean isimli fonksiyon ve ndarray sınıfının mean metodu eksen temelinde aritmetik ortalama işlemi yapmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> result = np.mean(a, axis=0) >>> result array([4., 5., 6.]) >>> result = a.mean(axis=0) >>> result array([4., 5., 6.]) - Median işlemi median fonksiyonu ile uapılmaktadır. (ndarray sınıfının böyle bir metodu yoktur.) Eğer değerler çift ise ortadaki iki değerin aritmetik ortalaması median olarak elde edilmektedir. Örneğin: >>> a = np.random.randint(0, 100, (10, 10)) >>> a array([[58, 14, 13, 19, 76, 50, 78, 82, 63, 77], [77, 11, 28, 32, 65, 74, 4, 54, 53, 79], [32, 23, 91, 89, 12, 71, 31, 46, 69, 30], [94, 50, 8, 76, 28, 32, 63, 76, 97, 74], [97, 58, 36, 56, 5, 7, 47, 15, 16, 95], [29, 25, 83, 72, 31, 17, 90, 34, 57, 37], [18, 92, 98, 92, 62, 97, 94, 69, 12, 38], [54, 49, 54, 41, 18, 91, 65, 70, 49, 42], [51, 27, 95, 55, 74, 52, 85, 95, 15, 24], [90, 28, 42, 33, 89, 2, 58, 54, 53, 7]]) >>> result = np.median(a, axis=0) >>> result array([56. , 27.5, 48. , 55.5, 46.5, 51. , 64. , 61.5, 53. , 40. ]) - Standart sapma hesabı için std isimli fonksiyon ya da ndarray sınıfının std metodu, varyans hesabı için var isimli fonksiyon ya da ndarray sınıfının var metodu kullanılmaktadır. Burada default olarak bölüm n değerine yapılır. Bu fonksiyonların ve metotların ddof parametreleri bölümün "n - ddof" olarak yapılmasını sağlamaktadır. Bu durumda ddof için default değer 0'dır. Eğer n-1'e bölmek yapılmak isteniyorsa ddof=1 girilmelidir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> result = np.std(a, axis=0) >>> result array([2.44948974, 2.44948974, 2.44948974]) >>> result = np.var(a, axis=0) >>> result array([6., 6., 6.]) >>> result = a.std(axis=0) >>> result array([2.44948974, 2.44948974, 2.44948974]) >>> result = a.var(axis=0) >>> result array([6., 6., 6.]) - max ve min fonksiyonları ve ndarray sınıfının metotları eksensel en büyük ve en küçük değerleri bulmaktadır. Örneğin: >>> a = np.array([[1, 6, 3], [4, 8, 2], [1, 2, 9]]) >>> a array([[1, 6, 3], [4, 8, 2], [1, 2, 9]]) >>> result = np.max(a, axis=0) >>> result array([4, 8, 9]) >>> result = np.max(a, axis=1) >>> result array([6, 8, 9]) >>> result = np.max(a) >>> result 9 >>> result = a.max(axis=0) >>> result array([4, 8, 9]) >>> result = a.min(axis=0) >>> result array([1, 2, 2]) Ayrıca bir de maximum ve minimum isimli iki fonksiyon vardır. (ndarray sınıfının maximum ve minimum isimli metotları yoktur.) Ancak bu fonksiyonlar karşılık iki değeirn ya da karşılıklı iki NumPy dizisinin maksimum ve minimum elemanlarını bulmaktadır. Bu fonksiyonların axis parametreleri yoktur. Örneğin: >>> result = np.maximum(10, 5) >>> result 10 >>> result = np.maximum([1, 3, 7], [2, 1, 9]) >>> result array([2, 3, 9]) - arxmax ve argmin fonksiyonları ve ndarray sınıfının argmax ve argmin metotları eksen temelinde en büyük ve en küçük elemanları değil onların indekslerini vermektedir. Bu işlem özellikle makine öğrenmesinde yaygın biçimde kullanılmaktadır. Örneğin: >>> a = np.array([[1, 9, 3], [8, 7, 1], [6, 4, 5]]) >>> a array([[1, 9, 3], [8, 7, 1], [6, 4, 5]]) >>> result = np.argmax(a, axis=0) >>> result array([1, 0, 2], dtype=int64) >>> result = a.argmax(axis=0) >>> result array([1, 0, 2], dtype=int64) >>> result = a.argmin(axis=0) >>> result array([0, 2, 1], dtype=int64) - prod fonksiyonu ve ndarray sınıfının prod metodu eksen temelinde çarpım değerlerini elde etmek için kullanılmaktadır. Örneğin: >>> a = np.array([[1, 9, 3], [8, 7, 1], [6, 4, 5]]) >>> a array([[1, 9, 3], [8, 7, 1], [6, 4, 5]]) >>> result = np.prod(a, axis=0) >>> result array([ 48, 252, 15]) >>> result = a.prod(axis=0) >>> result array([ 48, 252, 15]) - cumsum isimli fonksiyon ve ndarray sınıfının metodu kümülatif toplamlardan oluşan numpy dizisi vermektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> result = np.cumsum(a, axis=0) >>> result array([[ 1, 2, 3], [ 5, 7, 9], [12, 15, 18]]) >>> result = a.cumsum(axis=0) >>> result array([[ 1, 2, 3], [ 5, 7, 9], [12, 15, 18]]) - sort fonksiyonu ve ndarray sınıfının ssort metodu axis temelinde sıraya dizme işlemi yapar. Bu fonksiyonda axis için değer girilmezse default olarak son eksen değeri alınır. Örneğin: >>> a = np.random.randint(0, 100, (10, 10)) >>> a array([[ 8, 7, 38, 8, 40, 78, 39, 19, 93, 21], [99, 23, 65, 80, 90, 90, 83, 61, 78, 44], [78, 48, 58, 28, 41, 12, 88, 8, 26, 70], [ 6, 31, 51, 71, 75, 14, 60, 81, 93, 50], [ 4, 92, 72, 89, 80, 94, 70, 16, 17, 90], [40, 27, 2, 10, 8, 29, 71, 34, 34, 92], [82, 97, 15, 90, 57, 42, 33, 91, 11, 20], [47, 10, 43, 95, 40, 67, 88, 68, 53, 44], [86, 17, 16, 18, 87, 75, 6, 49, 23, 31], [96, 37, 33, 13, 67, 7, 70, 0, 86, 13]]) >>> result = np.sort(a, axis=0) >>> result array([[ 4, 7, 2, 8, 8, 7, 6, 0, 11, 13], [ 6, 10, 15, 10, 40, 12, 33, 8, 17, 20], [ 8, 17, 16, 13, 40, 14, 39, 16, 23, 21], [40, 23, 33, 18, 41, 29, 60, 19, 26, 31], [47, 27, 38, 28, 57, 42, 70, 34, 34, 44], [78, 31, 43, 71, 67, 67, 70, 49, 53, 44], [82, 37, 51, 80, 75, 75, 71, 61, 78, 50], [86, 48, 58, 89, 80, 78, 83, 68, 86, 70], [96, 92, 65, 90, 87, 90, 88, 81, 93, 90], [99, 97, 72, 95, 90, 94, 88, 91, 93, 92]]) >>> result = np.sort(a) >>> result array([[ 7, 8, 8, 19, 21, 38, 39, 40, 78, 93], [23, 44, 61, 65, 78, 80, 83, 90, 90, 99], [ 8, 12, 26, 28, 41, 48, 58, 70, 78, 88], [ 6, 14, 31, 50, 51, 60, 71, 75, 81, 93], [ 4, 16, 17, 70, 72, 80, 89, 90, 92, 94], [ 2, 8, 10, 27, 29, 34, 34, 40, 71, 92], [11, 15, 20, 33, 42, 57, 82, 90, 91, 97], [10, 40, 43, 44, 47, 53, 67, 68, 88, 95], [ 6, 16, 17, 18, 23, 31, 49, 75, 86, 87], [ 0, 7, 13, 13, 33, 37, 67, 70, 86, 96]]) Ancak sort metodu sıraya dizmewyi "in-place" biçimde yapmaktadır. - argsort fonksiyonu ve ndarray sınıfının argsort metodu dizinin kendisini değil indekslerini sıraya dizer ve sıraya dizilmiş indeks dizisi ile geri döner. Örneğin: >>> a = np.random.randint(0, 100, (10, 10)) >>> a array([[52, 68, 19, 79, 88, 17, 58, 39, 48, 24], [89, 61, 50, 69, 50, 79, 53, 40, 4, 85], [23, 45, 43, 3, 97, 41, 62, 97, 20, 41], [40, 22, 94, 34, 60, 2, 25, 44, 0, 65], [29, 28, 23, 55, 31, 86, 89, 37, 31, 46], [37, 68, 55, 13, 32, 55, 4, 31, 24, 21], [80, 54, 59, 78, 50, 64, 51, 73, 28, 77], [43, 15, 99, 36, 36, 98, 79, 87, 21, 3], [23, 62, 93, 79, 94, 5, 16, 36, 20, 95], [ 4, 26, 92, 71, 66, 23, 11, 99, 66, 98]]) >>> result = np.argsort(a, axis=0) >>> result array([[9, 7, 0, 2, 4, 3, 5, 5, 3, 7], [2, 3, 4, 5, 5, 8, 9, 8, 1, 5], [8, 9, 2, 3, 7, 0, 8, 4, 2, 0], [4, 4, 1, 7, 1, 9, 3, 0, 8, 2], [5, 2, 5, 4, 6, 2, 6, 1, 7, 4], [3, 6, 6, 1, 3, 5, 1, 3, 5, 3], [7, 1, 9, 9, 9, 6, 0, 6, 6, 6], [0, 8, 8, 6, 0, 1, 2, 7, 4, 1], [6, 0, 3, 0, 8, 4, 7, 2, 0, 8], [1, 5, 7, 8, 2, 7, 4, 9, 9, 9]], dtype=int64) >>> result = a.argsort(axis=0) >>> result array([[9, 7, 0, 2, 4, 3, 5, 5, 3, 7], [2, 3, 4, 5, 5, 8, 9, 8, 1, 5], [8, 9, 2, 3, 7, 0, 8, 4, 2, 0], [4, 4, 1, 7, 1, 9, 3, 0, 8, 2], [5, 2, 5, 4, 6, 2, 6, 1, 7, 4], [3, 6, 6, 1, 3, 5, 1, 3, 5, 3], [7, 1, 9, 9, 9, 6, 0, 6, 6, 6], [0, 8, 8, 6, 0, 1, 2, 7, 4, 1], [6, 0, 3, 0, 8, 4, 7, 2, 0, 8], [1, 5, 7, 8, 2, 7, 4, 9, 9, 9]], dtype=int64) NumPy kütüphanesinde çok fazla fonksiyon ve ndarray sınıfının metodu bulunmaktadır. NumPy dokümanlarından bunlara gerektiğinde başvurabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ NumPy kütüphanesinde bir NumPy dizisinin her elemanı üzerinde işlem yapan axis parametresine sahip olmayan pek çok klasik matematiksel f onksiyon ve ndarray sınıfının metodu metot vardır. Bunlardan bazılarrı şunlardır: sqrt, square, power, log, log10, log2, round, exp, sin, cos, tan, arcsin, arccos, arctan, lcm (okek), gcd (obeb). Örneğin: >>> a = np.random.random((5, 5)) >>> a array([[0.7697629 , 0.94185538, 0.39760624, 0.89994749, 0.03884618], [0.95536686, 0.57196936, 0.1364953 , 0.63398182, 0.52060019], [0.46564122, 0.94068844, 0.20854218, 0.77252757, 0.62490003], [0.12142791, 0.8926136 , 0.62890967, 0.37622973, 0.71989936], [0.52172981, 0.20142574, 0.08345288, 0.24866786, 0.04276729]]) >>> result = np.sin(a) >>> result array([[0.695965 , 0.80865099, 0.38721243, 0.78329427, 0.03883641], [0.81652559, 0.54128901, 0.13607185, 0.5923575 , 0.4974009 ], [0.44899584, 0.80796394, 0.20703389, 0.69794758, 0.5850162 ], [0.12112973, 0.77871412, 0.58826339, 0.36741652, 0.659309 ], [0.49838056, 0.20006645, 0.08335605, 0.24611301, 0.04275425]]) >>> result = np.power(a, 2) >>> result array([[0.59253492, 0.88709155, 0.15809072, 0.80990548, 0.00150903], [0.91272584, 0.32714895, 0.01863097, 0.40193295, 0.27102455], [0.21682174, 0.88489475, 0.04348984, 0.59679884, 0.39050004], [0.01474474, 0.79675904, 0.39552737, 0.14154881, 0.51825508], [0.272202 , 0.04057233, 0.00696438, 0.0618357 , 0.00182904]]) >>> result = np.log(a) >>> result array([[-0.26167274, -0.05990354, -0.92229312, -0.10541887, -3.24814547], [-0.04565986, -0.55866985, -1.9914651 , -0.455735 , -0.65277293], [-0.76433986, -0.06114329, -1.56761393, -0.25808759, -0.4701636 ], [-2.1084345 , -0.11360149, -0.46376764, -0.97755534, -0.32864386], [-0.65060542, -1.60233452, -2.48347311, -1.39163717, -3.1519818 ]]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Daha önceden de belirttiğimiz gibi Python operatörleri de NumPy dizilerine uygulandığında karşılıklı elemanlar üzerinde işlemler yapar. Aslında bunların fonksiyon karşılıkları da vardır. Tabii bunlar da axis parametresi almamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[8, 2, 18], [11, 23, 5], [1, 4, 32]], dtype=np.float32) b = np.array([[1, 3, 5], [1, 4, 3], [1, 4, 2]], dtype=np.float32) c = a * b print(c) c = np.multiply(a, b) print(c) #------------------------------------------------------------------------------------------------------------------------------------ where isimli fonksiyon tipik olarak bir bool dizi ve iki de dizi parametresi almaktadır. Bu bool dizinin uzunluğunun bu iki dizinin uzunluğu kadar olması gerekir. Bu fonksiyon eğer bool dizideki eleman True ise birinci dizideki elemanı, False ise ikinci dizideki elemanı vermektedir. Örneğin: >>> a = np.array([3, 6, 3, 8, 9, 5, 8]) >>> b = np.array([8, 4, 2, 7, 5, 6, 4]) >>> a array([3, 6, 3, 8, 9, 5, 8]) >>> b array([8, 4, 2, 7, 5, 6, 4]) >>> result = np.where([True, False, True, True, False, True, False], a, b) >>> result array([3, 4, 3, 8, 5, 5, 4]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tabii aslında where fonksiyonun birinci parametresi genellikle koşul operatörü ile oluşturulmaktadır. where fonksiyonu çok boyutlu dizilerde de benzer biçimde kullanılabilir. Örneğin: >>> a = np.array([3, 6, 3, 8, 9, 5, 8]) >>> b = np.array([8, 4, 2, 7, 5, 6, 4]) >>> a array([3, 6, 3, 8, 9, 5, 8]) >>> b array([8, 4, 2, 7, 5, 6, 4]) >>> result = np.where(a > 4, a, b) >>> result array([8, 6, 2, 8, 9, 5, 8]) Burada eğer a dizisindeki eleman 4'ten büyük ise a dizisindeki eleman, değilse b dizisindeki eleman elde edilmiştir. Aslında where fonksiyonunun ikinci parametresi bir skaler de olabilir. Bu durumda koşulun sağlanmadığı elemanlar belli değerlerle doldurulmuş olur. Yani bu durumda sanki ikinci dizi her elemanı bu skaler değerden oluşan bir dizi gibi ele alınmaktadır. Örneğin: >>> a = np.array([3, 6, 3, 8, 9, 5, 8]) >>> a array([3, 6, 3, 8, 9, 5, 8]) >>> result = np.where(a > 4, a, 0) >>> result array([0, 6, 0, 8, 9, 5, 8]) Burada a dizisinin 4'ten büyük olmayan elemanları 0 ile doldurulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ clip fonksiyonu ve ndarray sınıfının clip metodu bizden bir numpy dizisi ve min, max değerlerini almaktadır. Fonksiyon min değerinden düşük olanları min değerine, max değerinden büyük olanları ise max değerine çekerek bize yeni bir numpy dizisi verir. Örneğin: >>> a = np.array([3, 61, -32, 81, -9, 55, 81, 45, 38, -17]) >>> result = np.clip(a, 0, 50) >>> result array([ 3, 50, 0, 50, 0, 50, 50, 45, 38, 0]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ NumPy dizilerine satır ve sütun eklemek isteyebiliriz. Pek çok veri analizi uygulamasında bu tür işlemler gerekebilmektedir. Benzer biçimde yine NumPy dizilerinden satır ve sütun da silmek isteyebiliriz. Ancak maalesef bu tür işlemler NumPy'da göreli olarak yavaştır. Çünkü bu tür işlemlerde çoğu durumda "view" oluşturulamamaktadır. Dolayısıyla programcının bir döngü içerisinde bir NumPy dizisine sürekli satır eklemesi gibi işlemler (böyle şeyleri Python listeleri üzerinde yapmıştık) iyi teknik kabul edilmemektedir. Bu tür durumlarda programcının mümkün olduğu kadar işin başında BunPy dizisinin boyutlarını tespit etmeye çalışması ve diziyi işin başında bir kez zeros gibi bir fonksiyonla yaratması ve döngü içerisinde satır, sütun eklemek yerine satır güncellemesi yapması uygundur. Tabii her zaman işin başında bir dizinin boyutlarını belirleme olasılığımız olmayabilir. NumPy dizileri üzerinde "in-place" işlemler genellikle yapılamamaktadır. Bu nedenle genellikle bu tür işlemler dizinin kendi üzerinde yapılmaz, işlem sonucunda bu işlemlerin yapılmış olduğu yeni bir dizi verilir. NumPy'da ekleme ve silme gibi işlemlerde axis paraetresi bu işlemin sonucunda değişikliğin oluştuğu ekseni belirtmektedir. Örneğin biz iki boyutlu bir numpy dizisine satır eklemek istersek burada satır bu dizinin ilk boyutu üzerinde değişiklik yaratır. Bu nedenle satır ekleme işleminde axis=0 girilmelidir. axis=1 sütun ekleneceği anlamına gelektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisine eleman (satır, sütun) eklemek için append fonksiyonu kullanılır. append her zaman NumPy dizisinin yeni bir kopyasını oluşturur. Yani in-place işlem yapmaz. append fonksiyonunun birinci parametresi eklemenin yapılacağı NumPy dizisini belirtir. İkinci parametre ise eklenecek değerlerin bulunduğu NumPy dizisini belirtmektedir. append fonksiyonunda biz her zaman ana diziyle aynı satır ya da sütun sayısına sahip bir dizi eklemeliyiz. Örneğin iki bir boyutlu bir diziye tek bir satır ekleyeceksek bu satırın sanki tek satırlık bir matris gibi olması gerekir. Benzer biçimde iki boyutlu bir diziye tek bir sütun ekleyeceksek bu sütunun sanki tek sütunlu bir matris biçiminde olması gerekir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([[10], [20], [30]]) >>> b array([[10], [20], [30]]) >>> result = np.append(a, b, axis=1) >>> result array([[ 1, 2, 3, 10], [ 4, 5, 6, 20], [ 7, 8, 9, 30]]) Burada biz iki boyutlu bir matrise sütun eklemek istedik. Ekleyeceğimiz sütunu da iki boyutlu bir biçimde bir sütun vektörü olarak oluşturmamız gerekmektedir. Aşağıdaki işlem geçersizdir: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([10, 20, 30]) >>> b array([10, 20, 30]) >>> result = np.append(a, b, axis=1) Traceback (most recent call last): File "", line 1, in File "<__array_function__ internals>", line 180, in append File "C:\Users\aslan\anaconda3\lib\site-packages\numpy\lib\function_base.py", line 5444, in append return concatenate((arr, values), axis=axis) File "<__array_function__ internals>", line 180, in concatenate ValueError: all the input arrays must have same number of dimensions, but the array at index 0 has 2 dimension(s) and the array at index 1 has 1 dimension(s) Satır eklemesi de yine aynı boyutta bir dizi ile yapılabilmektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([[10, 20, 30]]) >>> b array([[10, 20, 30]]) >>> result = np.append(a, b, axis=0) >>> result array([[ 1, 2, 3], [ 4, 5, 6], [ 7, 8, 9], [10, 20, 30]]) Tabii tek hamlede birden fazla satır ya da sütun eklenebilmektir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([[10, 20], [30, 40], [50, 60]]) >>> b array([[10, 20], [30, 40], [50, 60]]) >>> result = np.append(a, b, axis=1) >>> result array([[ 1, 2, 3, 10, 20], [ 4, 5, 6, 30, 40], [ 7, 8, 9, 50, 60]]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Mademki append bizden aynı boyutta bir dizi istemektedir. O zaman onu aynı boyuta getirmek için reshape işlemi yapabiliriz. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([10, 20, 30]).reshape(-1, 1) >>> b array([[10], [20], [30]]) >>> result = np.append(a, b, axis=1) >>> result array([[ 1, 2, 3, 10], [ 4, 5, 6, 20], [ 7, 8, 9, 30]]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ append işleminde eksen belirtilmezse (axis parametresinin default değeri None biçimindedir). Eklemenin yapılacağı dizi önce flatten işlemiyle tek boyuta indirgenir. Sonra işlem yapılır. Bu işleme bazen seyrek de olsa gereksinim duyulmaktadır. Bu durumda append yapılzcak değerler herhangi bir boyutta girilebilir. Zaten bu değerler de flatten işlemine sokulmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b = np.array([10, 20, 30]) >>> b array([10, 20, 30]) >>> result = np.append(a, b) >>> result array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30]) >>> b = np.array([[10], [20], [30]]) >>> result = np.append(a, b) >>> result array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ insert isimli fonksiyon bizden bir NumPy dizisini, insert edilecek pozisyonu ve insert edilecek değerleri almaktadır. Insert işlemi insert edilmek istenen değerler insert pozisyonunda olacak biçimde diğer değerlerin kaydırılması yoluyla yapılmaktadır. Ancak insert fonksiyonunda insert edilecek dizi append fonksiyonunda olduğu gibi oluşturulmamaktadır. insert fonksiyonunda insert edilecek dizi dolaşıldığında satırlara ya da sütunlara insert edilecek değerler elde edilmelidir. Insert edilecek dizi tek boyutlu olarak verilebilir. axis parametresi 0 girilirse satır, 1 girilirse sütun insert edilmektedir. Örneğin: >>> import numpy as np >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> print(a) [[1 2 3] [4 5 6] [7 8 9]] >>> b = np.array([10, 20, 30]) >>> print(b) [10 20 30] >>> result = np.insert(a, 1, b, axis=0) >>> print(result) [[ 1 2 3] [10 20 30] [ 4 5 6] [ 7 8 9]] >>> result = np.insert(a, 1, b, axis=1) >>> print(result) [[ 1 10 2 3] [ 4 20 5 6] [ 7 30 8 9]] Eğer birden fazla satır ya da sütun insert edilecekse bu satır ya da sütun bilgileri her zaman iki boyutlu bir nesne biçininde verilmelidir. insert fonksiyonu girilen bu nesneyi dolaşarak insert edilecek satır ya da sütunları elde eder. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> print(a) [[1 2 3] [4 5 6] [7 8 9]] >>> b = np.array([[10, 20, 30], [40, 50, 60]]) >>> print(b) [[10 20 30] [40 50 60]] >>> result = np.insert(a, 1, b, axis=1) >>> print(result) [[ 1 10 40 2 3] [ 4 20 50 5 6] [ 7 30 60 8 9]] >>> result = np.insert(a, 1, b, axis=0) >>> print(result) [[ 1 2 3] [10 20 30] [40 50 60] [ 4 5 6] [ 7 8 9]] Fonksiyon aslında dolaşılabilir herhangi bir nesneyi de alabilmektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> print(a) [[1 2 3] [4 5 6] [7 8 9]] >>> result = np.insert(a, 1, [[10, 20, 30], [40, 50, 60]], axis=1) >>> print(result) [[ 1 10 40 2 3] [ 4 20 50 5 6] [ 7 30 60 8 9]] insert işleminde insert edilecek değer bir skaler de olabilir. Bu durumda ilgili satır ya da sütun o değerle doldurulur. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> result = np.insert(a, 1, 100, axis=1) >>> result array([[ 1, 100, 2, 3], [ 4, 100, 5, 6], [ 7, 100, 8, 9]]) >>> result = np.insert(a, 1, 100, axis=0) >>> result array([[ 1, 2, 3], [100, 100, 100], [ 4, 5, 6], [ 7, 8, 9]]) Buradan hareketle NumPy dizisine bir sütun vektörü insert edilmeye çalışılırsa bu sütun vektörünün her elemanı sütun olarak insert edilmektedir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> print(a) [[1 2 3] [4 5 6] [7 8 9]] >>> b = np.array([[10], [20], [30]]) >>> print(b) [[10] [20] [30]] >>> result = np.insert(a, 1, b, axis=1) >>> print(result) [[ 1 10 20 30 2 3] [ 4 10 20 30 5 6] [ 7 10 20 30 8 9]] Insert işleminde de axis belirtilmezse önce insert işleminin uygulanacağı dizi flatten yapılıp insert o noktaya yapılmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> print(a) [[1 2 3] [4 5 6] [7 8 9]] >>> b = np.array([10, 20, 30]) >>> print(b) [10 20 30] >>> result = np.insert(a, 1, b) >>> print(result) [ 1 10 20 30 2 3 4 5 6 7 8 9] #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 47. Ders 05/06/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisinden belli elemanlar silinebilir. Bunun için delete fonksiyonu kullanılmaktadır. delete fonksiyonunde silinecek NumPy dizisi, silinecek elemanın indeksi ve eksen bilgisi girilmektedir. Eğer axis parametresi için argüman girilmezse önce dizi flatten yapılıp sonra işleme sokulmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> result = np.delete(a, 1, axis=1) >>> result array([[1, 3], [4, 6], [7, 9]]) >>> result = np.delete(a, 1, axis=0) >>> result array([[1, 2, 3], [7, 8, 9]]) >>> result = np.delete(a, 1) >>> result array([1, 3, 4, 5, 6, 7, 8, 9]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Son 10 yıldır giderek artan bir biçimde "veri bilimi (data science)" terimi kullanılmaya başlanmıştır. Veri bilimi "verileri analiz etme, onlardan faydalı bilgiler etme, sonuç çıkarma ve kesitirim yapma" amacıyla uygulanan yöntemleri ve süreçleri betimlemektedir. Aslında veri analizi genel olarak "istatistik" denilen bilimin bir alt dalı idi. Ancak son 20 yıldır verileri üzerinde bilgisayar programlaması yoluyla klasik istatistikte yapılmayan pek çok işlemler yapılmaya başlanmıştır. İşte "veri bilimi" terimi adeta uygulamalı istatistiğin bilgisayar bilimleri ile iç içe geçmiş bir biçimini anlatmaktadır. Programlama ile veri analizine yönelik uygulamalar artık "veri bilimi uygulamaları" biçiminde ele alınmaya başlanmıştır. Verilerin bilgisayar yardımıyla analiz edilmesi veri biliminin önemli bir alanını oluşturmaktadır. Veri analizi uygulamalarında analiz edilecek veriler şu biçimlerde bulunabilmektedir: - Dosyalar içerisinde - Veritabanlarının içerisinde - Sürekli gelen bir akış içerisinde (örneğin TCP portundan gelen veriler gibi) Ancak en çok karşılaşılan durum analiz edilecek verilerin dosyalar içerisinde bulunmasıdır. Pekiyi verilerin içerisinde bulunduğu dosyalar hangi formattadır? İşte en yaygın kullanılan format "CSV (Comma Seperated Values)" denilen formattır. Bu formatta satırların sütunları ',' karakterleriyle satırlar da '\n' karakterleriyle birbirinden ayrılmaktadır. Örneğin: 1.2,3,5.7,4 3.2,16,5.6,8 1.2,7,3.6,8 ... CSV formatında dosyanın başında bir başlık kısmı da bulunabilmektedir. Bu başlık kısmı sütunların ne anlama geldiğine ilişkin isimlerden oluşmaktadır. Örneğin: no,boy,kilo 1,172,72.3 2,182,85.2 3,168,71.6 ... CSV formatında virgüllerden sonra boşlukların bırakılmadığına dikkat ediniz. CSV bir text formattır. Dolayısıyla biz bir CSV dosyasını editöre çekip onun üzerinde değişiklikler yapabiliriz. CSV formatı herhangi bir kurum tarafından standardize edilmiş bir format değildir. Format içerisinde çeşitli belirsizlikler vardır. Bu nedenle CSV formatının çeşitli biçimleri söz konusu olmaktadır. İstatistikte ve veri analizinde veriler "veri kümeleri (datasets)" biçiminde ifade edilmektedir. (Veri kümeri yerine "veri tablosu (data table)" terimi de kullanılmaktadır.) Bir veri kümesi matrisiel bir biçimde satırlardan ve sütunlardan oluşmaktadır. Veri kümesindeki sütunlara "sütun(column)" ya da "özellik (feature)" denilmektedir. Veri kümesindeki satırlara ise "satır (row)" ya da "kayıt (record)" denir. Veri kümelerinin dosyada saklanmasına ilişkin "HDF (hierachical File Format formatı)" gibi çeşitli "binary" formatla da vardır. Biz kursumuzda yalnızca CSV formatı üzerinde duracağız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirtitğimiz gibi verilerle uğraşan temel bilim istatistiktir. Ancak son 20 yıldır veri analizi çok daha dinamik bir boyut kazanmış ve klasik istatisiğin sınırları dışına çıkmıştır. Son 20 yıldır veri işleme ve veri analizi büyük ölçüde bilgisayar bilimlerindeki teknikler yardımıyla yapılır hale gelmiştir. İşte istatistiğin sınırları dışına çıkan dinamik veri analizi üzerinde çalışan bu yeni alana "veri bilimi (data science)" ve bu işlerle uğraşan kişilere de "veri bilimcisi (data scientist)" denilmektedir. Her ne kadar "veri bilimi" terimi hakkında zıt görüşler varsa da bu terim kendini kabul ettirmiştir. Veri bilimcisi verilerle uğraşır, bunlardan anlam çıkarmaya çalışır. Bu işi yaparken de değişik teknikleri programlama teknikleriyle birlikte kullanır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi veri kümeleri nasıl oluşturulmaktadır? Veri kümelerini oluşturmak için verilerin toplanması gerekir. Veri toplama işleminin birkaç yaygın biçimi vardır: - Veriler anket (survey) yoluyla toplanabilir. - Veriler zaten bir biçimde doğal akış içerisind eoluşturulmuş olabilir. (Örneğin sosyal ağlardaki yazışmalar birer veri olarak zaten sosyal ağın organizasyonu yapan kişiler tarafından veritabanlarında saklanmaktadır.) - Veriler otomatik olarak sensörlerden elde ediliyor olabilir. - Bazen veriler çeşitli kurumlar tarafından (örneğin istatistik kurumları) zaten elde edilmiş durumdadır. Örnek veri kümeleri Internet'te pek çok kaynaktan elde edilebilir. Son yıllarda "kaggle.com" isimli organizasyon bu bakımdan çok kullanılır hale gelmiştir. Kaggle "Google Firması" makine öğrenmesi uygulamaları teşvik etmek amacıyla oluşturulmuş olan bir organizasyondur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ NumPy'da CSV gibi text tabanlı dosyalardan verileri okuyarak bir NumPy dizisi biçiminde bize veren loadtxt isimli bir fonksiyon vardır. Veri bilimcisi bu fonksiyonu çok sık kullanmaktadır. loadtxt fonksiyonunun birinci parametresi okunacak dosyanın yol ifadesini alır. Fonksiyonun ikinci patramtresi dtype türünü belirtmektedir. dtype türü belirtilmezse default olarak np.float64 alınmaktadır. Bu dtype türü okunan bilgilerin hangi dtype türüne ilişkin bir NumPy dizisi olarak verileceğini belirtmektedir. Aşağıdaki gibi "points.csv"isminde bir CSV dosyamız olsun: x,y 10,23 5,9 6.1,8.7 12,81 4.2,9.3 11,27 32,72 12.3,7.8 Dosyayı şöyle okuyabiliriz: >>> dataset = np.loadtxt('points.txt', skiprows=1) >>> dataset array([[10. , 23. ], [ 5. , 9. ], [ 6.1, 8.7], [12. , 81. ], [ 4.2, 9.3], [11. , 27. ], [32. , 72. ], [12.3, 7.8]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np dataset = np.loadtxt('points.csv', skiprows=1, delimiter=',') print(dataset) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki örnekte eğer "points.csv" dosyasında satırların sütunları boşluklarla ayrılmış olsaydı "delimiter" parametresine bir şey girmememiz gerekirdi. Çünkü bu durumda değerler arasındaki tüm boşluk karakterleri atılmaktadır. Örneğin "points.txt" dosyası aşağıdaki gibi olsun: >>> dataset = np.loadtxt('points.txt', skiprows=1) >>> dataset array([[10. , 23. ], [ 5. , 9. ], [ 6.1, 8.7], [12. , 81. ], [ 4.2, 9.3], [11. , 27. ], [32. , 72. ], [12.3, 7.8]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np dataset = np.loadtxt('points.csv', skiprows=1) print(dataset) #------------------------------------------------------------------------------------------------------------------------------------ loadtxt fonksiyonunun usecols parametresine dolaşılabilir bir nesne girilir. Bu parametre dosyadan hangi sütunların elde edileceğini belirtir. Biz veri tablolarındaki yalnızca bazı sütunları almak isteyebiliriz. İlk sütun 0 numaralı sütundur. Örneğin covid-19 verilerine ilişkin "covid-19.csv" dosyası aşağıdaki gibi bir içeriğe sahip olsun: Country/Region,Confirmed,Deaths,Recovered,Active,New cases,New deaths,New recovered,Deaths / 100 Cases,Recovered / 100 Cases,Deaths / 100 Recovered,Confirmed last week,1 week change,1 week % increase,WHO Region Afghanistan,36263,1269,25198,9796,106,10,18,3.5,69.49,5.04,35526,737,2.07,Eastern Mediterranean Albania,4880,144,2745,1991,117,6,63,2.95,56.25,5.25,4171,709,17.0,Europe Algeria,27973,1163,18837,7973,616,8,749,4.16,67.34,6.17,23691,4282,18.07,Africa Andorra,907,52,803,52,10,0,0,5.73,88.53,6.48,884,23,2.6,Europe Angola,950,41,242,667,18,1,0,4.32,25.47,16.94,749,201,26.84,Africa Antigua and Barbuda,86,3,65,18,4,0,5,3.49,75.58,4.62,76,10,13.16,Americas ... Bu veri kümesini https://www.kaggle.com/datasets/imdevskp/corona-virus-report bağlantısından indirebilirsiniz. Buradaki "country_wise_latest.csv" dosyasının adını "covid-19.csv" olarak değiştirebilirsiniz" Biz bu veri tablosundan Confirmed, Deaths, Recoverd, Active sütunlarındaki bilgileri almak isteyelim. Bu sütunların indeks numaraları (1, 2, 3, 4) biçimindedir. Okuma işlemi aşağıdaki gibi yapılabilir: >>> dataset = np.loadtxt('covid-19.csv', skiprows=1, delimiter=',', dtype=np.float32, usecols=(1, 2, 3, 4)) >>> dataset array([[3.626300e+04, 1.269000e+03, 2.519800e+04, 9.796000e+03], [4.880000e+03, 1.440000e+02, 2.745000e+03, 1.991000e+03], [2.797300e+04, 1.163000e+03, 1.883700e+04, 7.973000e+03], [9.070000e+02, 5.200000e+01, 8.030000e+02, 5.200000e+01], [9.500000e+02, 4.100000e+01, 2.420000e+02, 6.670000e+02], [8.600000e+01, 3.000000e+00, 6.500000e+01, 1.800000e+01], .... Şimdi veri tablosundaki ilk ve son sütunu atarak geri kalan sütunları almak isteyelim. Bu veri tablosunda toplam 15 sütun vardır. İşlemi şöyle yapapbiliriz: >>> dataset = np.loadtxt('covid-19.csv', dtype='float32', delimiter=',', skiprows=1, usecols=range(1, 14)) >>> dataset array([[3.6263e+04, 1.2690e+03, 2.5198e+04, ..., 3.5526e+04, 7.3700e+02, 2.0700e+00], [4.8800e+03, 1.4400e+02, 2.7450e+03, ..., 4.1710e+03, 7.0900e+02, 1.7000e+01], [2.7973e+04, 1.1630e+03, 1.8837e+04, ..., 2.3691e+04, 4.2820e+03, 1.8070e+01], ..., [1.6910e+03, 4.8300e+02, 8.3300e+02, ..., 1.6190e+03, 7.2000e+01, 4.4500e+00], [4.5520e+03, 1.4000e+02, 2.8150e+03, ..., 3.3260e+03, 1.2260e+03, 3.6860e+01], [2.7040e+03, 3.6000e+01, 5.4200e+02, ..., 1.7130e+03, 9.9100e+02, 5.7850e+01]], dtype=float32) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np dataset = np.loadtxt('covid-19.csv', skiprows=1, delimiter=',', dtype=np.float32, usecols=(1, 2, 3, 4)) print(dataset) #------------------------------------------------------------------------------------------------------------------------------------ Bir veri tablosunda bazı sütunlarda sayısal bilgiler olmayabilir. Bu sütunlarda yazısal bilgiler bulunuyor olabilir. İstatistikte kategori belirten sütunlara "kategorik sütunlar" ya da "nominal sütunlar" denilmektedir. Örneğin yukarıdaki "covid-19.csv" dosyasının ilk sütunu vakalara ilişkin ülkeleri belirtmektedir. Veri analizi yapılırken bu tür kategorik sütunların 0, 1, 2, 3... biçiminde sayısal hale dönüştürülmesi gerekmektedir. Bu tür yazısal sütunların olduğu veri tablolarını loadtxt ile okurken dtype türü default olarak float64 olduğu için okuma soruna yol açacaktır. Burada yöntemlerden biri dtype=object yapmak olabilir. Bu durumda loadtxt tüm sütunları yazısal biçime dönüştürür. Yani biz dosyayı okumuş oluruz fakat onu istediğimiz hale geitrmemiz zor olur. Aşağıdaki gibi "persons.csv" isminde bir dosya bulunuyor olsun: kilo,boy,yaş,cinsiyet 67.2,172,43,kadın 87.2,183,52,erkek 32,142,9,erkek 90,168,48,kadın ... Biz bunu dtype=float32 vererek biçiminde okuyamayız. dtype=object biçiminde okursak tüm sütunlar yazı gibi okunur. Halbuki bizim yapmak istediğimiz şey kadın=0, erkek=1 gibi bu kategorik sütunu sayısal biçime dönüştürmektir. Bunu yapmanın birkaç yolu verdır. loadtxt fonksiyonunun "converter" parametresi bir sözlük nesnesi olarak girilir. Bu sözlüğün anahtarları sütun numaralarını, değerleri çağrılacak fonksiyonları belirtir. loadtxt tarafından dosya okunurken ilgili sütun verisi bir str nesnesi olarak bu fonksiyona parametre yapılır, bu fonksiyonun geri dönüş değeri o sütunun değeri gibi okunur. Yukarıdaki dosyayı okurken cinsiyet sütununu kadın=0, erkek=1 biçiminde sayısallaştırmak isteyelim. Bu işlemi aşağıdaki gibi yapabiliriz. Ancak CSV dosyasında Türkçe karakterler varsa loadtxt fonksiyonunun encoding parametresini 'utf-8' ona uygun yapmalısınız. Yani özetle CSV dosyası hangi encoding'e göre oluşturulmuşsa o encoding'e göre okuma yapılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np def convert_to_number(b): return {'kadın': 0, 'erkek': 1}[b] myconverter = {3: convert_to_number} dataset = np.loadtxt('human.csv', skiprows=1, delimiter=',', converters=myconverter, encoding='utf-8') print(dataset) #------------------------------------------------------------------------------------------------------------------------------------ loadtxt fonksiyonunun max_rows parametresi okunacak satır sayısını belirtmektedir. Böylece biz skiprows ve max_rows parametrelerini ayarlayarak dosyanın ilgili kısmını okuyabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np def convert_to_number(b): return {'kadın': 0, 'erkek': 1}[b] myconverter = {3: convert_to_number} dataset = np.loadtxt('human.csv', skiprows=1, delimiter=',', converters=myconverter, encoding='utf-8', max_rows=2) print(dataset) #------------------------------------------------------------------------------------------------------------------------------------ Maalesef loadtxt fonksiyonu farklı diyaleklere sahip olan CSV dosyalarını okuyamamaktadır. Örneğin bir sütun iki tırnaklı ise bazı CSV okuyucuları bu iki tırnağın içerisindekileri dikkate almadan o iki tırnağın tamamını sütun bilgisi olarak okumaktadır. Ancak loadtxt bunu yapamamaktadır. Bu tür durumlarda CSV dosyalarının önce düzeltilmesi gerekebilmektedir. Ya da CSV dosyası başka okuyucularla okunamaya çalışılabilir. Örneğin Pandas kütüphanesinin read_csv fonksiyonu pek çok CSV diyalektini sorunsuz bir biçimde okuyabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisini bir CSV ya da text dosya olarak diskte saklamak isteyebiliriz. Bunun için savetxt fonksiyonu kullanılmaktadır. Bu fonksiyonun birinci parametresi save edilecek dosyanın yol ifadesini, ikinci parametresi NumPy dizisini belirtir. Yine bu fonksiyonda da delimiter parametresi bulunmaktadır. Fonksiyon default durumda sayıları üstel formatta dosyaya yazmaktadır. fmt parametresi C dilindeki printf fonksiyonunun format parametresi gibi girilebilir. (Örneğin "%d" 10 luk sistemde yuvarlayarak yazdırma anlamındandır. %.5f noktadan sonra 5 basamak yazdır anlamına gelir). Fonksiyonun header parametresi ilk satıra yazdırılacak yazının belirlenmesinde kullanılabilir. Ancak default durumda bu yazının başına # karakteri getirilmektedir. Bu istenmiyorsa comments='' girilmelidir.Başlık kısmında Türkçe karakterler kullanılacaksa yine encoding'e dikkat edilmelidir. Pek çok editörün default durumda "utf-8" encoding'ine göre save işlemi yaptığını anımsayınız. BOM marker eklemek için encoding='utf-8-sig' bçiminde encoding belirtilebilir. Örneğin: >>> a = np.random.randint(0, 100, (10, 10)) >>> a array([[99, 39, 80, 67, 63, 79, 10, 56, 39, 16], [94, 98, 31, 89, 59, 48, 12, 17, 58, 12], [88, 86, 45, 23, 72, 46, 6, 40, 54, 64], [37, 57, 7, 26, 7, 16, 94, 40, 53, 65], [98, 70, 76, 99, 21, 18, 25, 94, 19, 14], [32, 70, 22, 89, 28, 92, 4, 86, 13, 31], [ 3, 66, 3, 19, 50, 9, 86, 91, 19, 60], [83, 25, 98, 88, 19, 70, 66, 74, 95, 28], [94, 43, 33, 97, 11, 13, 17, 19, 39, 87], [29, 88, 38, 46, 15, 44, 63, 16, 50, 90]]) >>> np.savetxt('numbers.csv', a, delimiter=',') >>> b = np.loadtxt('numbers.csv', delimiter=',') >>> b array([[99., 39., 80., 67., 63., 79., 10., 56., 39., 16.], [94., 98., 31., 89., 59., 48., 12., 17., 58., 12.], [88., 86., 45., 23., 72., 46., 6., 40., 54., 64.], [37., 57., 7., 26., 7., 16., 94., 40., 53., 65.], [98., 70., 76., 99., 21., 18., 25., 94., 19., 14.], [32., 70., 22., 89., 28., 92., 4., 86., 13., 31.], [ 3., 66., 3., 19., 50., 9., 86., 91., 19., 60.], [83., 25., 98., 88., 19., 70., 66., 74., 95., 28.], [94., 43., 33., 97., 11., 13., 17., 19., 39., 87.], [29., 88., 38., 46., 15., 44., 63., 16., 50., 90.]]) Burada "numbers.csv" dosyasındaki değerleri e'li üstel formatta yazılmıştır. Ancak biz istersek onu herhangi bir formmat yazdırabilir. Örneğin: >>> np.savetxt('numbers.csv', a, delimiter=',', fmt='%d') Ya da örneğin: >>> np.savetxt('numbers.csv', a, delimiter=',', fmt='%.2f') #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ loadtxt fonksiyonunun binary okuma yapan load isimli, savetxt fonksiyonunun binary yazma yapan save isimli biçimleri de vardır. Bu fonksiyonlar Numpy tarafından oluşturulan ".npy" dosya formatı biçiminde okuma yazma yaparlar. Binary okuma yazma aslında çok tercih edilen bir biçim değildir. Daha çok veri bilimi uygulamalarında text tabanlı CSV dosyası ve türevleri kullanılmaktadır. Ancak dosyalar çok büyük ise maalesef text dosyalar çok yer kaplar hale gelmektedir. Bu tür durumlarda binary dosyalar tercih edilebilmektdir. Örneğin: >>> a = np.random.random((10, 10)) >>> np.save('numbers.npy', a) >>> b = np.load('numbers.npy') >>> b array([[0.41504525, 0.73900245, 0.87994007, 0.42372742, 0.18248782, 0.21365573, 0.55649972, 0.17848196, 0.73611928, 0.9724516 ], [0.27433895, 0.41862453, 0.13569576, 0.38232279, 0.31309785, 0.35080388, 0.76941256, 0.10265489, 0.3285079 , 0.49633494], [0.64133353, 0.4176688 , 0.04389728, 0.56665131, 0.52000697, 0.39996986, 0.42485562, 0.86156759, 0.65911831, 0.18249345], [0.57974244, 0.42641376, 0.94259009, 0.68568807, 0.80802589, 0.63865489, 0.92609716, 0.40839208, 0.78923044, 0.77178028], [0.69317006, 0.84391828, 0.41390904, 0.41241497, 0.08586717, 0.70635019, 0.55724949, 0.38898489, 0.0498975 , 0.46578353], [0.7277121 , 0.71587589, 0.03807775, 0.29366838, 0.57626727, 0.35343511, 0.26394021, 0.75578066, 0.48022862, 0.06775144], [0.34038157, 0.3578412 , 0.87451383, 0.46772534, 0.1250095 , 0.38298588, 0.40117973, 0.14335533, 0.17387924, 0.82760857], [0.40053138, 0.62541083, 0.28137358, 0.66677368, 0.97644308, 0.87811785, 0.21320989, 0.0423645 , 0.61476291, 0.0737971 ], [0.39929663, 0.25341218, 0.57006935, 0.51126301, 0.76697134, 0.60112307, 0.41170669, 0.07727361, 0.34526322, 0.75771434], [0.20297708, 0.94785933, 0.84816366, 0.6668582 , 0.67713031, 0.17386574, 0.68407663, 0.51198724, 0.02975747, 0.04671035]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.random.randint(0, 100, (10, 10)) print(a, end='\n\n') np.save('test.npy', a) a = np.load('test.npy') print(a) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi Python'da bir sınıf nesnesi bool türüne dönüştürüldüğünde eğer sınıfın ya da onun taban sınıfının __bool__ metodu özel olarak yazılmamışsa True değeri elde edilmektedir. Bir NumPy dizisi bool türüne dönüştürülürse bu durum yanlış anlaşılmalara yol açabileceği için ndarray sınıfının __bool__ metodunda exception fırlatılmıştır. Yani biz bir ndarray nesnesini bool türüne dönüştüremeyiz. if, while gibi deyimler bu dönüştürmeyi yaptıklarına göre biz numpy dizilerini bu deyimlerdeki kontrol ifadesi olarak kullanamayız. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([1, 2, 3, 4, 5]) if a: # exception oluşacak! print('True') else: print('False') #------------------------------------------------------------------------------------------------------------------------------------ Bir NumPy dizisinin içerisindeki her elemanı bool türüne dönüştürüp bunların hepsi True ise True değerini veren en az bir tanesi False ise False değerini veren all isimli bir fonksiyon vardır. Bu fonksiyon aynı zamanda ndarray sınıfının metodu olarak da yazılmıştır. Örneğin: >>> a = np.array([3, 7, 8, 4]) >>> a array([3, 7, 8, 4]) >>> result = np.all(a) >>> result True >>> a = np.array([3, 7, 0, 4]) >>> result = np.all(a) >>> result False any isimli fonksiyon ise dizi içerisinde bool türüne dönüştürülen elemanlardan en az bir tanesi True ise True değerini hepsi False ise False değerini vermektedir. Örneğin: >>> a = np.array([3, 7, 0, 4]) >>> a array([3, 7, 0, 4]) >>> result = np.any(a) >>> result True >>> a = np.array([0, 0, 0, 0]) >>> a array([0, 0, 0, 0]) >>> result = np.any(a) >>> result False #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([1, 2, False, -4, 5]) if a.all(): print('True') else: print('False') if a.any(): print('True') else: print('False') #------------------------------------------------------------------------------------------------------------------------------------ NumPy programcıları iki NumPy dizisinin eşitliğini aşağıdaki gibi yanlış bir biçimde karşılaştırma eğilimindedirler: if a == b: pass Burada a ve b bir NumPy dizisi olduğuna göre a == b işleminden her elemanı bool türden olan bir NumPy dizisi elde edilecektir. Bir NumPy dizisi if deyiminde bool türüne dönüştürülürken yukarıda belirttiğimiz sorun ortaya çıkacaktır. O halde bu işlem aşağıdaki gibi yapılmalıdır: if np.all(a == b): pass Özetle a ve b biçiminde iki NumPy dizisinin karşılıklı elemanlarının eşitliğini kontrol etmek için np.all(a == b) ifadesi kullanılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([1, 2, 3, 4, 5, 6]) b = np.array([1, 2, 3, 4, 5, 6]) if np.all(a == b): print('Eşit') else: print('Eşit değil') #------------------------------------------------------------------------------------------------------------------------------------ NumPy kütüphanesinde linalg isimli pakette önemli lineer cebir işlemlerini yapan fonksiyonlar bulunmaktadır. Örneğin det isimli fonksiyon kare matrisin determinantını hesaplar: >>> a = np.array([[1, 2, 7], [4, 1, 6], [4, 8, 9]]) >>> result = np.linalg.det(a) >>> result 133.0 >>> a = np.array([[1, 2, 7], [2, 4, 14], [4, 8, 9]]) >>> result = np.linalg.det(a) >>> result 0.0 #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 2, 1], [6, 9, 3]]) b = np.linalg.det(a) print(b) #------------------------------------------------------------------------------------------------------------------------------------ inv isimli fonksiyon matris tersini bulmaktadır. Örneğin: >>> a = np.array([[1, 2, 3], [4, 2, 1], [6, 9, 3]]) >>> a array([[1, 2, 3], [4, 2, 1], [6, 9, 3]]) >>> result = np.linalg.inv(a) >>> result array([[-0.05263158, 0.36842105, -0.07017544], [-0.10526316, -0.26315789, 0.19298246], [ 0.42105263, 0.05263158, -0.10526316]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np a = np.array([[1, 2, 3], [4, 2, 1], [6, 9, 3]], dtype=np.float64) b = np.linalg.inv(a) print(b) c = np.matmul(a, b) print(c) #------------------------------------------------------------------------------------------------------------------------------------ Bir lineer denklem sistemini AX = b biçiminde ifade edilebilir. Örneğin: 3x1 - 2x2 + x3 = 2 -x1 + 2x2 - x3 = 0 x1 + x2 + x3 = 6 Bu denklem sisteminde A matrisi şöyledir: 3 -2 1 -1 2 -1 1 1 1 b matirisi de şöyledir: 1 0 13 Denklemin çözümü aslında A'nın tersinin b ile çarpımı biçiminde bulunabilir. Tabii buradaki çarpım matris çarpımıdır. Örneğin: >>> A = np.array([[3, -2, 1], [-1, 2, -1], [1, 1, 1]]) >>> b = np.array([[2], [0], [6]]) >>> A array([[ 3, -2, 1], [-1, 2, -1], [ 1, 1, 1]]) >>> b array([[2], [0], [6]]) >>> Ainv = np.linalg.inv(A) >>> np.matmul(Ainv, b) array([[1.], [2.], [3.]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np A = np.array([[3, -2, 1], [-1, 2, -1], [1, 1, 1]]) b = np.array([[2], [0], [6]]) Ainv = np.linalg.inv(A) result = np.matmul(Ainv, b) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Aslında yukarıdaki işlemi tek hamlede yapan solve isimli bir fonkisyon vardır. Örneğin: >>> A = np.array([[3, -2, 1], [-1, 2, -1], [1, 1, 1]]) >>> b = np.array([[2], [0], [6]]) >>> result = np.linalg.solve(A, b) >>> result array([[1.], [2.], [3.]]) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np A = np.array([[3, -2, 1], [-1, 2, -1], [1, 1, 1]]) b = np.array([[2], [0], [6]]) result = np.linalg.solve(A, b) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Sayısal düzeyde matematiksel işlemler yapmak gerekebilmektedir. Örneğin 3x**2 - 5x + 2 gibi fonksiyonun 0 ile 3 arasında integral değerini hesaplamak isteyebiliriz. Matematikte bu işleme belirli integral hesabı denilmektedir. Tabii bu işlem önce bu fonksiyonun integrali sembolik düzeyde bir fonksiyon olarak bulunup sonra değerler yerine konularak yapılabilir. Ancak hiç bunu yapmadan integral, türev gibi işlemleri yapmak için çeşitli sayısal yöntemler de kullanılabilmektedir. Matematiğin bu işlemlerle ilgilenen bölümüne "nümerik analiz" denilmektedir. Pyton'da nümerik analiz işlemleri için SciPy isimli kütüphane kullanılmaktadır. Örneğin biz nümerik analiz yöntemleri ile bir fonksiyonun belli bir noktadaki türevini elde edebiliriz. Ya da biz nümerik analiz yöntemleriyle bir fonksiyonun belli bir aralıktaki "belirli integral" değerini hesaplayabiliriz. NumMpy ekosisteminde SciPy isimli kütüphane bu tür nümerik analiz işlemlerini yapmaktadır. Ancak bazen programcılar tıpkı matematik derslerinde olduğu gibi sembolik tarzda türev, integral işlmi isteyebilirler. Matematikte sembolik tarzda işlem yapmaya "kapalı formda (closed form)" işlem yapmak da denilmektedir. Örneğin biz 3x**2 -5x + 2 fonksiyonun x = 2 noktasındaki türevini tamamen nümerik yöntemlerle bir sayı biçiminde elde edebiliriz. Bunun için SciPy kütüphanesinden faydalabiliriz. Ancak bizim amacımız bu fonksiyonun türevini sembolik biçimde (yani kapalı formda) elde etmek de olabiliriz. Bilinciği bu fonksiyonun türevi 6x - 5 biçimindedir. Biz burada bir fonksiyonun türevini başka bir fonksiyon olarak elde ettik. Tabii burada 2 değerini bu fonksiyonda yerine koyarak hedefimize ulaşabiliriz. Örneğin biz bir fonksiyonu çarpanlarına ayırmak isteyebiliriz. Çarpanlara ayırma işlemi de sembolik düzeyde (kapalı formda) yapılan bir işlemdir. Bu tür işlemler sembolik düzeyde olduğu için programlamaya pek uygun değildir. İşte bu tür sembolik işlemler için NumPy ekosisteminde SymPy isimli üçüncü parti bir kütüphane bulunmaktadır. SymPy kütüphanesinin dokümantasyonu aşağıdaki adreste bulunmaktadır: https://docs.sympy.org/latest/index.html Anaconda dağıtımında SymPy zaten dağıtımın kendi içersinde bulunmaktadır. Ancak diğer dağıtımlarda bunun pip programı ile kurulması gerekir: pip install sympy Biz burada bu kütüphaneyi sp ismiyle import edeceğiz: import sympy as sp #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 48. Ders 07/06/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ SymPy'da önce fonksiyonları oluşturan x, y gibi değişkenlerin birer sembol olarak ifade edilmesi gerekir. Bunun için SymPy'da Symbol isimli bir sınıf bulundurulmuştur. Symbol sınıfı bizden sembolün ismini parametre olarak almaktadır. Her ne kadar aslında SymPy sınıf nesneleri üzerinde işlem yapıyorsa da görüntüleme aşamasında bizim verdiğimiz sembol yazısını kullanır. Örneğin: >>> x = sp.Symbol('x') >>> x >>> type(x) < class 'sympy.core.symbol.Symbol'> #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') print(x, type(x)) # x #------------------------------------------------------------------------------------------------------------------------------------ Symbol sınıfıyla oluşturulan sembollerin bazı özellikleri bazı isimli parametrelerle belirlenebilmektedir. Bu isimli parametreler şöyledir: real imaginary positive negative odd even prime finite infinite Örneğin: x = sp.Symbol('x', real=True) İlgili özelliklerin olup olmadığı aşağıdaki özniteliklerle test edilebilir: is_real is_imaginary is_positive is_negative is_odd is_even is_prime is_finite is_infinite Örnğin: x = sp.Symbol('x', real=True) result = x.is_real result burada True olacaktır. #------------------------------------------------------------------------------------------------------------------------------------ Sembollerin görüntülenmesi eğer grafik bir ekranda matematiksel bir biçimde yapılmak isteniyorsa bu durumda işin başında aşağıdaki çağrı yapılmalıdır: sp.init_printing() Ancak maalesef SymPy'da kullanılan çizim kütüphanesi olan Matplotlib ile bir version uyuşmazlığı nedeniyle DeprecatedWaring mesajları görülebilmektedir. Bu mesajlar görünürse bunların ihmal edilmesi için aşağıdaki çağrıyı yapabilirsiniz: import warnings warnings.filterwarnings('ignore') Buradaki sp.init_printing() çağrısı komut satırında dğeişkenin ismi yazıldığındaki görümtü üzerinde etkili olmaktadır. Yani başka bir deyişle bu çağrı __repr__ metodunu oluşturmaktadır. Değişkenler print edildiğinde yine text karakterlerle print edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import warnings warnings.filterwarnings('ignore') import sympy as sp sp.init_printing() #------------------------------------------------------------------------------------------------------------------------------------ Bir grup sembol tek hamlede symbols isimli fonksiyonla yaratılabilir. symbols fonksiyonu bizden sembol isimlerine ilişkin bir yazı alır. Bu yazıdaki sembol isimleri boşluklarla ya da virgüllerle ayrılmış olabilir. symbols fonksiyonu bize Symbol nesnelerinden oluşan bir demet vermektedir. Biz bu demeti açıp (unpack) değişkenleri daha rahat kullanabiliriz. Örneğin: >>> x, y, z = sp.symbols('x, y, z') >>> x x >>> y y >>> z z >>> x, y, z = sp.symbols('x y z') >>> x x >>> y y >>> z z #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x, y, z, k = sp.symbols('x, y, z, k') print(x, y, z, k) #------------------------------------------------------------------------------------------------------------------------------------ Fonksiyonlarda geçen sabit değerler de aslında SymPy'da sınıflarla temsil edilmiştir. Örneğin Integer isimli sınıf tamsayı sabitini SymPy sabiti biçiminde tutmaktadır. Örneğin: a = sp.Integer(3) Buradaki sabit bir string olarak da verilebilirdi: a = sp.Integer('3') Benzer biçimde float değerler de Float isimli bir sınıfla temsil edilebilmektedir. Örneğin: b = sp.Float('1.23456') Buradaki parametre string olarak girildiğinde bir yuvarlama hatası oluşmayacaktır. Ancak parametre bir float biçiminde de girilebilir: b = sp.Float(1.23456) #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp sp.init_printing() x = sp.Symbol('x') a = sp.Integer(3) b = sp.Float(2.4) y = a * x + b print(y) #------------------------------------------------------------------------------------------------------------------------------------ Bazı özel değerler özel bazı değişken isimleriyle temsil edilmiştir. Örneğin e sayısı sp.E ile, pi sayısı sp.pi ile, sonsuz değeri sp.oo ile temsil edilmiştir. Dipğer özel değerlerin temsili için SymPy dokümanlarına başvurabilirsiniz. Örneğin: >>> sp.pi π >>> sp.E ℯ >>> sp.oo ∞ #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp sp.init_printing() pi = sp.pi print(pi) e = sp.E print(e) inf = sp.oo print(inf) #------------------------------------------------------------------------------------------------------------------------------------ Bir Symbol nesnesi operatörlerle işleme sokulabilmektedir. Bu durumda operatör fonksiyonları devreye girer ve bize çeşitli sınıflar türünden nesneler verir. Örneğin biz sembolü başka bir sembolle ya da sabitle topladığımızda bize Add isimli bir sınıf türünden nesne verilmektedir. Bir sembolü başka bir sembolle ya da bir sabitle çarptığımızda bize Mul isimli bir nesne verilmektedir. Örneğin: >>> x, y = sp.symbols('x, y') >>> z = x * y >>> type(z) >>> z = x * y + 2 >>> type(z) #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') result = x * 2 print(result, type(result)) # 2*x result = x + 2 print(result, type(result)) # x + 2 result = x ** 2 print(result, type(result)) # x**2 #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi işlemler sonucunda elde edilen Add, Sub, Mul türünden sınıf nesnelerin anlamı nedir? İşte SymPy aslında bir ifadeyi bir ağaç biçiminde tutmaktadır. Örneğin: result = 3 * x + 2 Burada aslında result nesnesi bu ifadeyi temsil eden ağacın kök düğümüdür. Bu kök düğüm Add sınıfı türündendir. Add sınıfı iki ayrı düğümün adresini tutmaktadır. Bunlardan biri Mul düğümü diğeri de 2 değerini temsil eden Integer düğümüdür: Add Mul Integer(2) Integer(3) Symbol(x) Programcının aslında bu işlemin ağaç bakımından detaylarını bilmesine gerek yoktur. Programcı yalnızca şunu bilmelidir: Aslında bir ifade oluşturduğumuzda bu ifade bir ağaç veri yapısı biçiminde bellekte oluşturulmaktadır ve o ağacın köküne ilişkin bir nesne bize verilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ İfadelerin sadeleştilmesi simplify isimli fonksiyonla yapılmaktadır. Örneğin: >>> x = sp.Symbol('x') >>> y = (3 * x ** 2 - 3 * x) / (3 * x) >>> result = sp.simplify(y) >>> result x - 1 Örneğin ifademiz (x ** - 1) / (x + 1) olsun. Bu ifade sadeleştirilirse (x - 1) elde edilecektir: >>> x = sp.Symbol('x') >>> y = (x ** 2 - 1) / (x + 1) >>> result = sp.simplify(y) >>> result x - 1 #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = (x ** 2 - 1) / (x + 1) result = sp.simplify(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda 2 sin(x) cos(x) ifadesinin sin(2x) biçiminde sadeleştirildiğini göreceksiniz: >>> y = 2 * sp.sin(x) * sp.cos(x) >>> y 2⋅sin(x)⋅cos(x) >>> result = sp.simplify(y) >>> result sin(2⋅x) #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = 2 * sp.sin(x) * sp.cos(x) result = sp.simplify(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda ÖSS sınavında çıkmış olan bir sadeleştirme sorusu çözülmüştür. #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x, y = sp.symbols('x, y') expression = ((x ** 2 + x - 6) / (x ** 2 + 3 * x - 10)) * ((x ** 2 - x * y + 5 * x - 5 * y) / (x ** 2 + x * y + 3 * x + 3 * y)) result = sp.simplify(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda başka bir ÖSS sadeleştirme sorusu çözülmüştür #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = ((x / (1 + x)) - (1 / (1 - x))) / ((1 / (1 + x)) + (x / (1 - x))) result = sp.simplify(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ expand fonksiyonu simplify fonksiyonunun adeta ters işlemini yapmaktadır. Yani fonksiyon çarpım ifadelerini açar. Örneğini x * (3 + x) ifadesi açıldığında 3x + x ** 2 elde edilecektir: >>> x = sp.Symbol('x') >>> y = x * (3 + x) >>> result = sp.expand(y) >>> result x**2 + 3*x #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = x * (3 + x) result = sp.expand(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ (x + 1) (x - 1) ifadesinin açımı X^2 - 1 biçimindedir. Örneğin: >>> x = sp.Symbol('x') >>> y = (x - 1) * (x + 1) >>> result = sp.expand(y) >>> result 2 x - 1 >>> print(result) x**2 - 1 #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = (x + 1) * (x - 1) result = sp.expand(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Aslında expand fonksiyonlarının özel biçimleri vardır. Bunlar ilgili konuda açım uygularlar. Örneğin biz trigonometrik bir açım yapacaksak expand_trig fonksiyonunu kullanırız. Yani aslında expand fonksiyonu bu özel fonksiyonları kullanıp daha karmaşık açımları yapmaya çalışmaktadır. Bunların listesi şöyledir: expand_log expand_mul expand_multinomial expand_complex expand_trig expand_power_base expand_power_exp expand_func #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = sp.sin(2 * x) result = sp.expand_trig(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Örneğin logaritmik açım için expand_log fonksiyonu kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = sp.log(2 * x) result = sp.expand_log(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Çarpanlara ayırma da çok karşılaşılan işlemlerdendir. Bu işlem factor isimli fonksiyonla yapılmaktadır. Örneğin: >>> x = sp.Symbol('x') >>> y = x ** 2 - 1 >>> result = sp.factor(y) >>> result (x - 1)⋅(x + 1) #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = (x ** 2 - 1) result = sp.factor(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda daha karmaşık bir çarpanlara ayırma örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = x ** 3 + 6 * x ** 2 + 5 * x result = sp.factor(expression) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Bir ifadenin sonucunu hesaplamak için subs (substitute) isimli metot kullanılmaktadır. Metodun basit kullanımında birinci parametre değeri yerleştirilecek değişkeni, ikinci parametre onun değerini belirtir. Örneğin: >>> x = sp.Symbol('x') >>> y = x ** 2 - 3 * x + 2 >>> result = y.subs(x, 1) >>> result 0 >>> result = y.subs(x, 3) >>> result 2 #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = x ** 2 - 1 result = expression.subs(x, 2) print(result) # 3 #------------------------------------------------------------------------------------------------------------------------------------ Eğer ifadede birden fazla değişken için yerleştirme yapılması isteniyorsa bu durumda dolaşılabilir bir nesne içerisinde iki elemanlı dolaşılabilir nesnelerin kullanılması gerekir (örneğin iki elemanlı demetlerden oluşan listeler gibi). Örneğin: >>> x, y, z = sp.symbols('x, y, z') >>> f = 3 * x ** 2 - 5 * y + z >>> result = f.subs(x, 2) >>> result -5⋅y + z + 12 >>> result = f.subs([(x, 2), (y, 1), (z, 3)]) >>> result 10 #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') y = sp.Symbol('y') expression = x ** 2 - 3 * y + 2 result = expression.subs([(x, 1), (y, 2)]) print(result) # -3 #------------------------------------------------------------------------------------------------------------------------------------ evalf isimli metot sp.pi gibi sp.e gibi özel değerleri sayısal değerlerle değiştir. Ancak ifade içerisindeki sembollere dokunmaz. subs metodu ise özel değerleri açmamaktadır. Bu nedenle bazen bu iki metodun bir arada kullanılması gerekebilmektedir. Örneğin: >>> x, y, z = sp.symbols('x, y, z') >>> f = 3 * sp.E ** x - 3 * y + 2 * z >>> f.subs([(x, 1), (y, 2), (z, 3)]) 3⋅ℯ >>> f.evalf() x -3.0⋅y + 2.0⋅z + 3.0⋅ℯ >>> f.subs([(x, 1), (y, 2), (z, 3)]).evalf() 8.15484548537714 #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x, y, z = sp.symbols('x, y, z') f = sp.pi * x ** 2 - 3 * y + 2 * z result = f.subs([(x, 2), (y, 3), (z, -1)]) print(result) result = f.subs([(x, 2), (y, 3), (z, -1)]).evalf() print(result) f = sp.log(x ** 2) result = f.subs(x, 2).evalf() print(result) #------------------------------------------------------------------------------------------------------------------------------------ lambdify isimli fonksiyon bizden sembolü ve ifadeyi argüman olarak alır. Bize bir Python fonksiyonu verir. Biz artık o fonksiyonu çağırdığımızda adeta evalf ve subs işlemleri yapılıp sonuç elde edilecektir. Eğer ifade içerisinde birden fazla sembol varsa bu durumda lambdify fonksiyonunda bu semboller bir demet biçiminde girilmelidir. #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') expression = x ** 2 - 3 * x + 4 f = sp.lambdify(x, expression) result = f(10) print(result) x, y, z = sp.symbols('x, y, z') expression = 3 * x ** 2 - 5 * x + 2 * z f = sp.lambdify((x, y, z), expression) result = f(1, 2, 3) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Sembolik (belirsiz) türev alma işlemi matemetikte sıkça karşımıza çıkmaktadır. Bir fonksiyonun belli bir noktadaki türevinin sayısal değerinin bulunması çeşitli "nümerik analiz" yöntemleriyle kolay bir biçimde elde edilebilmektedir. Ancak bir fonksiyonun türev fonksiyonunu sembolik bir biçimde elde etmek matematikte daha fazla kullanılmaktadır. SymPy'da türev işlemi için diff isimli fonksiyon bulundurulmuştur. diff fonksiyonunun birinci parametresi türevi alınacak ifadeyi, ikinci parametresi türevin neye göre alınacağını belirtmektedir. Örneğin: >>> x = sp.Symbol('x') >>> f = 3 * x ** 2 - 5 * x + 2 >>> result = sp.diff(f, x) >>> result 6⋅x - 5 #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') f = 3 * x ** 2 - 5 * x + 6 result = sp.diff(f, x) print(result) # 6 * x - 5 #------------------------------------------------------------------------------------------------------------------------------------ Çok değişkenli fonksiyonlada türev değişkenlerin herhangi birine göre alınabilmektedir. Bu durumda diğer değişkenler sanki sabit sayılarmış gibi işleme sokulmaktadır. Buna "kısmi türev (partial derivative)" denilmektedir. Özellikle doğrusak olmayan optimizasyon problemlerinde çok değişkenli fonksiyonların her değişkene göre parçalı türevleri alınarak bir vektör elde edilmektedir. Bu vektöre "gradient vektör" denir. Bu gradient vektör fonksiyonun maksimum ve minimum noktalarını bulmak için kullanılmaktadır. Sympy'da parçalı türevler yukarıdaki gibi uygulanmaktadır. Tabii istenirse önce bir değişkene göre türev alıp sonra başka değişkene göre de türev alınabilir. #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') y = sp.Symbol('y') f = 3 * x ** 2 * 2 * y - 5 * x * y + 2 result = sp.diff(f, x) print(result) result = sp.diff(f, y) print(result) result = sp.diff(f, x, y) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Sembolik integral (belirsiz integral de denilmektedir) eğri altında kalan alanın bulunması için sıkça kullanılmaktadır. SymPy'da integrate fonksiyonu ile belirsiz integral elde edilebilmektedir. intergate fonksiyonunun kullanılması diff fonksiyonundaki gibidir. Aşağıda f(x) = (x - 1) fonksyonunun integrali bulunmuştur: >>> x = sp.Symbol('x') >>> f = x - 1 >>> result = sp.integrate(f) >>> print(result) x**2/2 - x #------------------------------------------------------------------------------------------------------------------------------------ import sympy as sp x = sp.Symbol('x') y = sp.Symbol('y') f = x - 1 result = sp.integrate(f, x) print(result) # x**2/2 - x #------------------------------------------------------------------------------------------------------------------------------------ Python'da veri analizi uygulamaları için NumPy'dan sonra en çok kullanılan kütüphane Pandas isimli kütüphanedir. Pandas kütüphanesi NumPy kütüphanesinin üzerine kurulmuş durumdadır. Yani seviye bakımından NumPy'dan biraz daha yüksek seviyededir. Bu nedenle veri analizi ile uğraşan kişiler tarafından pratik kullanımı nedeniyle tercih edilmektedir. Bazen programcı bazı işlemleri Pandas ile yapıp sonra yoluna NumPy ile de devam edebilmektedir. Pandas kütüphanesi Anaconda dağıtımında dağıtımla birlikte zaten install edilmiş durumdadır. Ancak kurulumu pip ile aşağıdaki gibi yapılabilir: pip install pandas Pandas tablo biçiminde organize edilmiş olan veri kümeleri (datasets) üzerinde işlemler yapmak için tasarlanmış bir kütüphanedir. Bu tür işlemler aslında NumPy'la da yapılabilmektedir. Ancak yukarıda da belirttiğimiz gibi Pandas bazı işlemler için daha yüksek seviyeli ve daha kolay bir kullanım sunmaktadır. Genellikle Python'da veri analizi ile uğraşan programcılar NumPy ve Pandas kütüphanelerini bir arada kullanırlar. Pandas kütüphanesi programcılar tarafından geleneksel olarak pd ismiyle import edilmektedir. Örneğin: import pandas as pd #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 49. Ders 14/06/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pandas'ta sütunlardan ve satırlardan oluşan veri kümeleri DataFrame isimli bir sınıf ile temsil edilmektedir. Veri kümesindeki belli bir sütun ise Series isimli sınıfla temsil edilir. DataFrame sınıfını Series nesnelerini tutan bir sınıf olarak düşünebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir Series nesnesi dolaşılabilir bir nesne ile Series sınıfının __init__ metoduyla oluşturulabilir. Buradaki dolaşılabilir nesne bir Python listesi olabileceği gibi bir Numpy dizisi de olabilir. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5]) >>> s 0 1 1 2 2 3 3 4 4 5 dtype: int64 >>> a = np.random.rand(10) >>> s = pd.Series(a) >>> s 0 0.839374 1 0.494721 2 0.386717 3 0.095209 4 0.163746 5 0.549368 6 0.453674 7 0.052371 8 0.868359 9 0.038974 dtype: float64 #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import pandas as pd s = pd.Series([1, 2, 3, 4, 5, 6]) print(s) a = np.array([1, 2, 3, 4, 5]) s = pd.Series(a) print(s) #------------------------------------------------------------------------------------------------------------------------------------ Series nesnelerinin de tıpkı NumPy dizilerinde olduğu gibi bir dtype türü vardır. Bir Series nesnesi yaratılırken nesnenin dtype türü dtype parametresiyle belirtilebilir. Pandas içerisinde ayrı dtype sınıfları yoktur. Aslında Pandas Series bilgilerini NumPy dizisi olarak saklamaktadır. Dolayısıyla Series nesnesi yaratılırken dtype bilgisi NumPy dtype türü olarak belirtilir. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype=np.float32) >>> s 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 Tabii burada dtype belirtilirken np.float32 gibi bir ifade kullanılacaksa NumPy kütüphanesinin de import edilmesi gerekir. Ancak bunun yerine programcı dtype türünü Numpy'da olduğu gibi yazısal da belirtebilir. Bu durumda Numpy kütüphanesini import etmek zorunda kalmaz. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> s 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 Eğer Series nesnesi zaten NumPy dizisi ile oluşturuluyorsa dtype belirtilmezse Numpy dizisindeki dtype kullanılır. Diğer durumlarda NumPy'd aolduğu gibi eğer tüm elemanları int türdense dtype türü 'int64' olarak en az bir eleman float türündense 'flaot64' olarak alınmaktadır. Örneğin: >>> a = np.array([1, 2, 3, 4, 5], dtype='float32') >>> a array([1., 2., 3., 4., 5.], dtype=float32) >>> s = pd.Series(a) >>> s 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 >>> s = pd.Series([1, 2, 3, 4, 5]) >>> s 0 1 1 2 2 3 3 4 4 5 dtype: int64 >>> s = pd.Series([1.2, 2, 3, 4, 5]) >>> s 0 1.2 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float64 Series sınıfının yine dtype isimli örnek özniteliği bize Series nesnesinin dtype bilgisini vermektedir. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> s 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 >>> s.dtype dtype('float32') Series nesnesi yaratılırken verilen değerlerin normal olarak tek boyutlu olması gerekir. Tabii eğer biz Series fonksiyonuna iki boyutlu bir nesne verirsek bu durumda alsında Series nesnesi tek boyutluı olur. Ancak onun her elemanı ilgili dolaşılabilir nesneyi tutar durumda olur. Ancak genel olarak böyle bir şey istenmez. Örneğin: >>> s = pd.Series([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> s 0 [1, 2, 3] 1 [4, 5, 6] 2 [7, 8, 9] dtype: object Pandas'ta iki boyutlu olan veri kümeleri (veri tabloları) DataFRame sınıfıyla, onların sütunları ise Series sınıfıyla temsil edilmektedir. Yani DataFRame iki boyutlu ancak Series tek boyutlu bir nesnedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import pandas as pd s = pd.Series([1, 2.4, 3, 4, 5, 6]) print(s.dtype) # float64 a = np.array([1, 2, 3, 4, 5], dtype=np.float32) s = pd.Series(a) print(s.dtype) # float32 s = pd.Series([1, 2, 3, 4, 5], dtype='float64') print(s.dtype) # float64 #------------------------------------------------------------------------------------------------------------------------------------ Bir Series nesnesi sütun değerlerinin yanı sıra aynı zamanda index değerlerine de sahiptir. İndex değerleri belirtilmediyse 0'dan başlayan ardışal sayılar index değeri olarak kullanılır. Örneğin: >>> import pandas as pd >>> s = pd.Series([10, 20, 30, 40, 50], dtype='float32') >>> s 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 dtype: float32 index'leri istediğimiz set etmek için Series fnksiyonunda (yani Series sınıfının __init__ metodunda) index parametresini kullanabiliriz. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> s a 10.0 b 20.0 c 30.0 d 40.0 e 50.0 dtype: float32 index parametresine biz dolaşılabilir bir nesne olduğu halde bir string giremeyiz. Aşağıdaki yaratım exception oluşturur: s = pd.Series([10, 20, 30, 40, 50], index='abcde', dtype='float32') Ancak bu işlemi şöyle yapabiliriz: s = pd.Series([10, 20, 30, 40, 50], index=list('abcde'), dtype='float32') #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series nesnesinin index bilgisini almak için index örnek özniteliği kullanılabilir. Aynı zamanda index örnek özniteliğine biz daha sonra değer de atayabiliriz. Bu durumda index bilgisini değiştirmiş oluruz. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], dtype='float32') >>> s 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 dtype: float32 >>> s.index RangeIndex(start=0, stop=5, step=1) >>> s.index = ['a', 'b', 'c', 'd', 'e'] >>> s a 10.0 b 20.0 c 30.0 d 40.0 e 50.0 dtype: float32 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series nesnelerinin elemanlarına erişmek için üç yol vardır. Series nesnesi s olmak üzere: 1) Doğrudan köşeli parantez operatörü ile. Yani s[...] biçiminde. 2) loc örnek özniteliği ve köşeli parantez operatörü ile. Yani s.loc[...] biçiminde 3) iloc örnek özniteliği ve kçşeli parantez operatörü ile. Yani s.iloc[...] biçiminde Seeries nesneleri "değiştirilebilir (mutable)" nesnelerdir. Bir Series nesnesine erişip onu değiştirebiliriz. Series nesnesinin index ile belirtilen (ya da index belirten değerlerine) "etiket (label)" da denilmektedir. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['ali', 'veli', 'selami', 'ayşe', 'fatma'], dtype='float32') >>> s ali 10.0 veli 20.0 selami 30.0 ayşe 40.0 fatma 50.0 dtype: float32 Burada indeks belirten "ali", "veli", "selami", "ayşe" ve "fatma" değerlerine "etiket (label)" da denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Doğrudan köşeli parantez ile elemana erişmede köşeli parantez içerisindeki değerin önce etiket olup olmadığına bakılır. Eğer bu değer bir etiket ise o etiketin değerine erişilir. Eğer köşeli parantez "içerisindeki değer bir etiket değilse ve etiketlerin hiçbiri int türden değilse" bu durumda ilk eleman sıfır olmak üzere sıra numarasıyla erişim yapılır. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> s a 10.0 b 20.0 c 30.0 d 40.0 e 50.0 dtype: float32 >>> s['c'] 30.0 >>> s[3] 40.0 Eğer index olarak kullanılan etiketlerde sayısal değerler varsa bu durumda doğrudan köşeli parantez ile sırasal erişim yapılamamktadır. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['ali', 'veli', 1, 'selami', 'ayşe'], dtype='float32') >>> s ali 10.0 veli 20.0 1 30.0 selami 40.0 ayşe 50.0 dtype: float32 >>> s[1] 30.0 >>> s[0] Traceback (most recent call last): File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\indexes\base.py", line 3361, in get_loc return self._engine.get_loc(casted_key) File "pandas\_libs\index.pyx", line 76, in pandas._libs.index.IndexEngine.get_loc File "pandas\_libs\index.pyx", line 108, in pandas._libs.index.IndexEngine.get_loc File "pandas\_libs\hashtable_class_helper.pxi", line 5198, in pandas._libs.hashtable.PyObjectHashTable.get_item File "pandas\_libs\hashtable_class_helper.pxi", line 5206, in pandas._libs.hashtable.PyObjectHashTable.get_item KeyError: 0 Tabii asıl olan elemanlara etiketler kullanılarak erişilmesidir. ---------------------------------------------------------------------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ s.loc[...] biçimindeki erişimde her zaman köşeli parantez içerisindeki ifade index ile belirtilen bir etiket olmak zorundadır. s.loc[...] erişiminde köşeli parantez içerisine sıra numarası yerleştiremeyiz. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=list('abcde'), dtype='float32') >>> s a 10.0 b 20.0 c 30.0 d 40.0 e 50.0 dtype: float32 >>> s.loc['a'] 10.0 >>> s.loc['e'] 50.0 Yani loc örnek özniteliği ile erişimde köşeli parantez içerisinde her zaman etiket bulundurulması gerekmektedir. Bu bakımdan s[...] erişimi ile s.loc[...] erişime arasında farklılık vardır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ s.iloc[...] biçimindeki erişimde ise köşeli parantez içerisine her zaman sıra numarası yerleştirilmek zorundadır. Biz burada köşeli parantez içerisine etiket yerleştiremeyiz. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=list('abcde'), dtype='float32') >>> s[2] 30.0 >>> s[3] 40.0 >>> s.iloc['e'] Traceback (most recent call last): File "", line 1, in File "C:\Users\CSD\anaconda3\lib\site-packages\pandas\core\indexing.py", line 967, in __getitem__ return self._getitem_axis(maybe_callable, axis=axis) File "C:\Users\CSD\anaconda3\lib\site-packages\pandas\core\indexing.py", line 1517, in _getitem_axis raise TypeError("Cannot index by location index with a non-integer key") TypeError: Cannot index by location index with a non-integer key Özetle biz erişimi s.loc biçiminde yapıyorsak köşeli parantez içerisine etiket yerleştirmek zorundayız. Biz erişimi s.iloc[...] biçiminde yapıyorsak köşeli parantez içerisine sıra numarası yerleştirmek zorundayız. Ancak biz erişim s[...] biçiminde yapıyorsa köşeli parantez içerisine etiket ya da sıra numarası yerleştirebiliriz. Ancak bu sıra numarası eğer hiçbir eleman int türdne değilse kullanılabilmektedir. Genellikle programcılar s.loc[...] ve s.iloc[...] erişimlerini kullanırlar. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Doğrudan, loc ile ya da iloc ile indeksleme yaparken birden fazla index dolaşılabilir bir nesne biçiminde verilebilir. Bu durumda bir Series nesnesi elde edilmektedir. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> s[[1, 3, 4]] b 20.0 d 40.0 e 50.0 dtype: float32 >>> s[['c', 'a', 'e']] c 30.0 a 10.0 e 50.0 dtype: float32 >>> s.loc[['a', 'e', 'b']] a 10.0 e 50.0 b 20.0 dtype: float32 >>> s.iloc[[1, 3, 2]] b 20.0 d 40.0 c 30.0 dtype: float32 Bu tür erişimlerden bir view nesnesi elde edilmemektedir. Yeni bir Series nesnesi elde edilmektedir. Anımsanacağı gibi Python'da gibi bir erişim a[(1, 2, 3, 4, 5)] tamamen a[1, 2, 3, 4, 5] erişimi ile yanı anlama gelmektedir. Bu nedenler biz loc ve iloc erişimlerinde dolaşılabilir nesne olarka demet kullanamayız. Doğrudan indekslemede hem etiket hem de sıra numarası bir arada kullanılamz. Ancak bunlardan biri kullanılabilir. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> k = s[['b', 'a', 'e']] >>> k b 20.0 a 10.0 e 50.0 dtype: float32 >>> k = s[[1, 3, 0]] >>> k b 20.0 d 40.0 a 10.0 dtype: float32 >>> k = s[['a', 3, 0]] Traceback (most recent call last): File "", line 1, in File "C:\Users\CSD\anaconda3\lib\site-packages\pandas\core\indexes\base.py", line 5845, in _raise_if_missing raise KeyError(f"{not_found} not in index") ... KeyError: '[3, 0] not in index' #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') print(s) result = s[[1, 3, 4]] print(result) result = s[['a', 'c', 'd']] print(result) result = s.loc[['a', 'd', 'e']] print(result) result = s.iloc[[1, 3, 4]] print(result) #------------------------------------------------------------------------------------------------------------------------------------ Doğrudan, loc ile ya da iloc ile bool indeksleme yapılabilmektedir. Yani biz bir Series nesnesinin belli elemanlarınıo bool değerlerden oluşan dolaşılabilir bir nesne ile elde edebiliriz. Bu durumda indekslemenin doğrudan, loc ile ya da iloc ile yapılmasının bir farklılığı yoktur. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> s[[True, False, True, True, False]] a 10.0 c 30.0 d 40.0 dtype: float32 >>> s.loc[[True, False, True, True, False]] a 10.0 c 30.0 d 40.0 dtype: float32 >>> s.iloc[[True, False, True, True, False]] a 10.0 c 30.0 d 40.0 dtype: float32 NumPy dizilerinde olduğu gibi bool indekslemenin en önemli faydası filtreleme yapılabilmesidir. Belli koşulu sağlayan elemanlar bu biçimde elde edilebilmektedir. Sonraki paragraflarda da ele alacağımız gibi bir Series nesnesi üzerinde karşılaştırma operatörlerini uygularsak bool vir Series nesnesi elde ederiz. Örneğin: >>> s > 30 a False b False c False d True e True dtype: bool Bu sayede buradan elde edilen bool türden Seris nesnesi Series nesnesini filtrelemek için bool indekslemede kullanılabilir. Örneğin: >>> s[s > 30] d 40.0 e 50.0 dtype: float32 >>> s.loc[s > 30] d 40.0 e 50.0 dtype: float32 >>> s.iloc[s > 30] Traceback (most recent call last): File "", line 1, in File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\indexing.py", line 931, in __getitem__ return self._getitem_axis(maybe_callable, axis=axis) File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\indexing.py", line 1552, in _getitem_axis self._validate_key(key, axis) File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\indexing.py", line 1400, in _validate_key raise ValueError( ValueError: iLocation based boolean indexing cannot use an indexable as a mask #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd s = pd.Series([3, 56, 12, 34, 21], dtype='float32') k = s[s > 20] print(k) #------------------------------------------------------------------------------------------------------------------------------------ Series nesnesinin içerisindeki değerler values isimli örnek özniteliği ile bir NumPy dizisi olarak elde edilebilmektedir. Aslında Series nesnesi zaten değerleri NumPy dizisi içerisinde tutmaktadır. values elemanı da bize doğrudan aslıonda bu diziyi verir. Bu dizide değişiklik yaptığımızda Series nesnesinin elemanında değişiklik yapmış oluruz. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], dtype='float32') >>> a = s.values >>> s 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 dtype: float32 >>> a array([10., 20., 30., 40., 50.], dtype=float32) >>> a[0] = 100 >>> s 0 100.0 1 20.0 2 30.0 3 40.0 4 50.0 dtype: float32 Yani values örnek özniteliğ zaten Series nesnesinin içerisinde tutulan NumPy dizisinin adresini bize vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd s = pd.Series([3, 56, 12, 34, 21], dtype='float32') a = s.values print(a) a[2] = 1000 print(s) #------------------------------------------------------------------------------------------------------------------------------------ Series nesnesinin sonuna eleman eklemek için Series sınıfının append metodu kullanıyordu. Sonra bu metot "deprecated" yapıldı. (Yani bu metot ileride kaldırılabilir). Artık Series nesnesinin sonuna ekleme için pd.concat fonksiyonu kullanılmaktadır. Aslında pd.concat DataFrame nesneleriyle de çalışmaktadır. Biz burada concat fonksiyonuna Series nesnelerinden oluşan bir demet veriririz. Fonksiyon da bu Series nesnelerini birleştirerek yeni bir Series nesnesi verir. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], dtype='float32') >>> k = pd.Series([100, 200], dtype='float32') >>> result = pd.concat((s, k)) >>> result 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 0 100.0 1 200.0 dtype: float32 Burada iki Series nesnesinin etiketlerinin de birleştirmeye dahil edildiğine dikkat ediniz. Böylece sonuçta aynı etiketlere sahip birden fazla elemandan oluşan bir Series nesnesi elde edilebilmektedir. Bu durumda etiket verilerek indeksleme yapıldığında (yani doğrudan ya da loc indekslemesi yapıldığında) eynı değerden oluşan birden fazla eleman elde edilmektedir. Örneğin: >>> result[0] 0 10.0 0 100.0 >>> result.loc[1] 1 20.0 1 200.0 dtype: float32 Ancak iloc indekslemesinde sıra numarası veridliği için ve her elemanın sıra numarası farklık olduğu için elemanlara tek tek erişilebilmektedir. Örneğin: >>> result 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 0 100.0 1 200.0 dtype: float32 >>> result.iloc[0] 10.0 >>> result.iloc[5] 100.0 concat işlemiyle elde edilen Series nesnesine yeniden sıfırdan indeks atayabilmek için concat fonksiyonunda ignore_index parametresi True geçilmelidir: >>> s = pd.Series([10, 20, 30, 40, 50], dtype='float32') >>> k = pd.Series([100, 200], dtype='float32') >>> result = pd.concat((s, k), ignore_index=True) >>> result 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 5 100.0 6 200.0 dtype: float32 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ concat biçiminde bir fonksiyon varsa da araya eleman eklemek için bir insert fonksiyonu bulunmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series nesnesinden eleman silmek için Series sınıfının drop metodu kullanılmaktadır (drop isimli bir fonksiyon yoktur). Bu metot her zaman etiket temelinde çalışır. Hiçbir zaman sıra numarasıyla çalışmaz. Biz tek bir etiket de kullanabiliriz. Bir grup etiketi dolaşılabilir bir nesne biçiminde de metoda verebiliriz. Metot default durumda "inplace" silme işlemi yapmaz. Bize silinmiş yeni bir Series nesnesi verir. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> s a 10.0 b 20.0 c 30.0 d 40.0 e 50.0 dtype: float32 >>> result = s.drop('b') >>> result a 10.0 c 30.0 d 40.0 e 50.0 dtype: float32 >>> result = s.drop(['a', 'e']) >>> result b 20.0 c 30.0 d 40.0 dtype: float32 Metodun inplace parametresi True geçilirse silme işlemi nesne üzerinde yapılır. Bu durumda metot None değerine geri döner. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'], dtype='float32') >>> s a 10.0 b 20.0 c 30.0 d 40.0 e 50.0 dtype: float32 >>> s.drop(['a', 'e'], inplace=True) >>> s b 20.0 c 30.0 d 40.0 dtype: float32 Yukarıda da belirttiğimiz gibi Series nesnesinin arasına insert işlemi yapan özel bir fonksiyon ya da metot bulunmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series nesnelerine isim de verilebilmektedir. Bunun için Series nesnesi yaratılırken name isimli parametre kullanılır. Daha sonra biz bu ismi sınıfın name elemanı ile alıp istersek değiştirebiliriz. >>> s = pd.Series([10, 20, 30, 40, 50], name='My Series', dtype='float32') >>> s 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 Name: My Series, dtype: float32 >>> s.name = 'Your Series' >>> s 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 Name: Your Series, dtype: float32 #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd s = pd.Series([3, 56, 12, 34, 21], dtype='float32', name='Numbers') print(s) print(s.name) s.name = 'Test Numbers' print(s) #------------------------------------------------------------------------------------------------------------------------------------ Series sınıfının size örnek özniteliği nesnedeki eleman sayısını bize verir. Tabii biz bu eleman sayısını built-in len fonksiyonuyla da elde edebiliriz. Benzer biçimde sınıfın shape isimli örnek özniteliği bize Series nesnesinin bouyutlarını bir demet biçiminde vermektedir. Yukarıda da belirttiğimiz gibi Series nesneleri genellikle tek boyutlu olur. Örneğin: >>> s = pd.Series([10, 20, 30, 40, 50], name='Numbers', dtype='float32') >>> s 0 10.0 1 20.0 2 30.0 3 40.0 4 50.0 Name: Numbers, dtype: float32 >>> len(s) 5 >>> s.size 5 >>> s.shape (5,) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ İki Series nesnesinin karşılıklı elemanlarını işleme sokabiliriz. Bu konuda davranış Numpy kütüphanesine benzerdir. Örneğin: >>> s = pd.Series([5, 10, 15, 20, 35], dtype='float32') >>> k = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> s 0 5.0 1 10.0 2 15.0 3 20.0 4 35.0 dtype: float32 >>> k 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 >>> result = s + k >>> result 0 6.0 1 12.0 2 18.0 3 24.0 4 40.0 dtype: float32 >>> result = s * k >>> result 0 5.0 1 20.0 2 45.0 3 80.0 4 175.0 dtype: float32 >>> result = s > k >>> result 0 True 1 True 2 True 3 True 4 True dtype: bool Burada karşılıklı elemanlardan kastedilen şey aslında indeks uyuşmasıdır. Elemanların sıra numarası ne olursa olsun etiketleri uyuşanlar kendi aralarında işleme sokulmaktadır. Etiketleri uyuşmayanlar işlem sonucunda NaN biçiminde bulundurulur. Yani işlem sonıcında önce sol taraftaki operand'ın tüm etiketleri sonra sağ taraftaki operand'ın tüm etiketleri oluşturulmaktadır. Karşılıklı etiketler uyuşursa elde edilen sonuçta onlardan yalnızca bir tane bulunur. Ancak uyuşmayan tüm etiketler elde edilen sonuçta NaN olarak bulundurulmaktadır. Burada etiket uyuşmasında karşılıklı elemanların uyuşmasına bakılmamaktadır. import pandas as pd >>> a = pd.Series([1, 2, 3], index=['a', 'b', 'c']) >>> b = pd.Series([10, 20, 30]) >>> c = a + b >>> c a NaN b NaN c NaN 0 NaN 1 NaN 2 NaN dtype: float64 >>> a = pd.Series([1, 2, 3], index=['a', 'b', 'c']) >>> b = pd.Series([1, 2, 3], index=['x', 'b', 'y']) >>> c = a + b >>> c a NaN b 4.0 c NaN x NaN y NaN dtype: float64 >>> a = pd.Series([1, 2, 3], index=['a', 'b', 'c']) >>> b = pd.Series([10, 20, 30], index=['x', 'y', 'a']) >>> c = a + b >>> c a 31.0 b NaN c NaN x NaN y NaN dtype: float64 Aslında NumPy'ın aksine Pandas'ta iki Series nesnesi üzerinde işlem yapılırken bu nesnelerin aynı uzunluğa sahip olması da gerekmemektedir. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> k = pd.Series([10, 20, 30], dtype='float32') >>> result = s + k >>> result 0 11.0 1 22.0 2 33.0 3 NaN 4 NaN dtype: float32 Burada s ve k'nın ilk üç etiketi uyuştuğu için toplanmıştır. Ancak s'teki fazlalık NaN biçiminde sonuca yansıtılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series sınıfının pek çok faydalı metodu vardır. Bu metotlar bize yaptıklar işlem sonucunda yeni bir Series nesnesi verirler. Aslında bu metotlar NumPy metotlarına çok benzemektedir. NumPy'da pek çok işlem hem metotlarla hem de fonksiyonlarla yapılabilmektedir. Ancak Pandas'ta ağırlıklı olarak metotlar bulundurulmuştur. Yani pek çok fonksiyon yalnızca metot biçiminde bulundurulmuştur. abs metodu elemanların mutlak değerlerini elde eder. add metodu karşılıklı elemanları toplar (yani + operatörü ile yapılanı yapar). argmax, argmin, argsort metotoları sırasıyla en büyük elemanın indeksisni, en küçük elemanın indeksini ve sort edilme durumundaki indeksleri vermektedir. Örneğin: >>> s = pd.Series([12, 8, 4, 2, 9], dtype='float32') >>> s.abs() 0 12.0 1 8.0 2 4.0 3 2.0 4 9.0 dtype: float32 >>> s.argmax() 0 >>> s.argmin() 3 >>> s.argsort() 0 3 1 2 2 1 3 4 4 0 dtype: int64 Diğer önemli bazı metotlar da şunlardır: - count - mean - std - var - mode - min - max - median - cumsum - unique #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir Series nesnesinin dtype türünü değiştirmek isteyebiliriz. Bunun için astype metodu kullanılmaktadır. astype metodu bize belirttiğimiz türden yeni bir Series nesnesi vermektedir. Örneğin: >>> s = pd.Series([12, 8, 4, 2, 9], dtype='float32') >>> k = s.astype('int32') >>> k 0 12 1 8 2 4 3 2 4 9 dtype: int32 C'de None biçiminde bir tür yoktur. Python'daki None değeri Numpy ve Pandas'ta eğer dtype 'float32' ya da 'float64' ise NaN (Not a Number) ile ifade edilmektedir. count metodu Nan olmayan elemanların sayısını vermektedir. Örneğin: >>> s = pd.Series([10, None, 30, None, 50], dtype='float32') >>> result = s.count() >>> result 3 dot isimli metot "dot product" yapmaktadır. Yani karşılıklı (etiket bakımından uyuşan) elemanların çarpımını bulmaktadır. Örneğin: >>> a = pd.Series([1, 2, 3], dtype='float32') >>> b = pd.Series([4, 5, 6], dtype='float32') >>> c = a.dot(b) >>> c 32.0 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir veri kümesi üzerinde çalışırken çeşitli biçimlerde "eksik verilerle" karşılaşabiliriz. Örneğin bir anket uygulamasında kişiler bazı sorulara yanıt vermek istememiş olabilir. Ya da verilerin sensörlerle elde edildiği durumda sensör arızalarından dolayı bazı veriler elde edilememiş olabilir. Bu tür durumlarda çeşitli yöntemler izlenebilmektedir. Eksik verilen tamamen atılması (bir veri tablosu söz konusuysa eksik verilerin bulunduğu satırın atılması) ya da eksik verilerin diğer verilerden hareketle dolduurlması (buna İngilizce "imputation" denilmektedir) sık kullanılan yöntemlerdendir. Series sınıfında eksik verilerin ele alınmasına yönelik çeşitli metotlar da bulundurulmuştur. dropna metodu eksik verileri atmak için kullanılmaktadır. Yani NaN değerleri Series nesnesinden silinir. Örneğin: >>> s = pd.Series([3, None, 7, 9, None, 10], dtype='float32') >>> s 0 3.0 1 NaN 2 7.0 3 9.0 4 NaN 5 10.0 dtype: float32 >>> result = s.dropna() >>> result 0 3.0 2 7.0 3 9.0 5 10.0 dtype: float32 fillna isimli metot eksik verileri (yani NaN olan elemanları) spesifik bir değerle doldurmaktadır. Örneğin biz eksik verileri aşağıdaki gibi ortalamayla doldurabiliriz: >>> s = pd.Series([3, None, 7, 9, None, 1], dtype='float32') >>> s 0 3.0 1 NaN 2 7.0 3 9.0 4 NaN 5 1.0 dtype: float32 >>> s.fillna(s.mean()) 0 3.0 1 5.0 2 7.0 3 9.0 4 5.0 5 1.0 dtype: float32 mean metodunun eksik verileri hesaba katmadığına dikkat ediniz. Yukarıdaki s nesnesinin mean ile alınan ortalaması bu yüzden 5 çıkmıştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 50. Ders 19/06/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ hist isimli metot histogram çizmektedir. Ancak histogram için arka planda Matplotlib kütüphanesini kullanmaktadır. Örneğin: import pandas as pd import numpy as np s = pd.Series(np.random.randn(1000)) s.hist() Burada önce standart normal dağılılma ilişkin rastgele değerler elde edilmiş sonra o değerlerden bir Series nesnesi oluşturularak Series sınıfının hist metodu ile histogram çizilmiştir. Eğer komut satırında aynı şeyi yapacaksanız matplotlib kütüphanesinin show metodunu da çağırmalısınız. Örneğin: >>> import pandas as pd >>> import numpy as np >>> import matplotlib.pyplot as plt >>> s = pd.Series(np.random.randn(1000)) >>> s.hist() >>> plt.show() #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series sınıfının isna metodu Nan olanların True olduğu Nan olmayanların False olduğu bir Series nesnesi vermektedir. Bu sayede biz NaN değerler üzerinde filtreleme işlemleri yapabiliriz. Örneğin: >>> s = pd.Series([1, 2, None, 2, None, 5, None], dtype=np.float32) >>> s 0 1.0 1 2.0 2 NaN 3 2.0 4 NaN 5 5.0 6 NaN >>> s[s.isna()] 2 NaN 4 NaN 6 NaN dtype: float32 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series sınıfının max, min, median, mean, mode, std ve var metotları klasik istatistiksel işlemleri yapmaktadır. Bu metotlar NaN değerler sanki yokmuş gibi davranmaktadır. Örneğin: >>> s = pd.Series([1, 2, 3, 5, 8, 9, 4, 5, 2, 5]) >>> s.max() 9 >>> s.min() 1 >>> s.mean() 4.4 >>> s.median() 4.5 >>> s.mode() 0 5 dtype: int64 >>> s.std() 2.590581230363393 >>> s.var() 6.711111111111112 #------------------------------------------------------------------------------------------------------------------------------------ Series sınıfının values örnek özniteliği bize Series nesnesi içerisindeki değerleri bir NumPy dizisi olarak vermektedir. Aslında Pandas zaten değerleri NumPy dizisi olarak kendi içerisinde saklamaktadır. Dolayısıyla bu örnek öznitelikleri aslında Series nesnelerinin değerlerini tutan NumPy dizilerini bize vermektedir. Biz eğer bize verilen NumPy dizisi üzerinde değişiklik yaparsak Series nesnesi üzerinde değişiklik yapmış oluruz. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> a = s.values >>> id(a) 1689465945808 >>> a array([1., 2., 3., 4., 5.], dtype=float32) >>> a[2] = 100 >>> s 0 1.0 1 2.0 2 100.0 3 4.0 4 5.0 dtype: float32 Aynı işlem Series sınıfının array örnek özniteliği ile de yapılabilmektedir. Ancak array örnek özniteliği aynı NumPy dizisine referans eden başka bir sınıf türünden nesne vermektedir. >>> b = s.array >>> id(b) 1689465751568 >>> b[3] = 1000 >>> s 0 1.0 1 2.0 2 100.0 3 1000.0 4 5.0 dtype: float32 Series sınıfının to_numpy metodu values örnek özniteliği gibidir. Ancak to_numpy değişik seçeneklere de sahiptir. Default durumda to_numpy metodu ile values örnek özniteliği aynı Numpy dizisini vermektedir. Fakat örneğin to_numpy metodunda copy=True geçilirse metot bize kopyalama yaparak başka bir Numpy dizisi verir. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> a = s.values >>> a array([1., 2., 3., 4., 5.], dtype=float32) >>> id(a) 1689358507664 >>> b = s.to_numpy() >>> b array([1., 2., 3., 4., 5.], dtype=float32) >>> id(b) 1689358507664 >>> c = s.to_numpy(copy=True) >>> c array([1., 2., 3., 4., 5.], dtype=float32) >>> id(c) 1689358507856 >>> c[2] = 1000 >>> s 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 Özetle bir Series nesnesi içerisindeki değerleri NumPy dizisi olarak almak istersek values örnek özniteliğini ya da to_numpy metodunu kullanabiliriz. Eğer Series nesnesi içerisindeki değerleri bir Python listesi biçiminde elde etmek istersek Series sınıfının to_list metodunu kullanabiliriz. Tabii to_list her çağrıldığında aslında bize farklı bir list nesnesi verecektir. Örneğin: >>> s = pd.Series([1, 2, 3, 4, 5], dtype='float32') >>> s 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 dtype: float32 >>> a = s.to_list() >>> a [1.0, 2.0, 3.0, 4.0, 5.0] >>> id(a) 1689468256384 >>> b = s.to_list() >>> id(b) 1689466572096 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ unique metodu Series nesnesi içerisindeki tek olan elemanlardan oluşan yeni bir NumPy dizisi vermektedir. >>> s = pd.Series([1, 2, 2, 5, 7, 3, 5, 7]) >>> s.unique() array([1, 2, 5, 7, 3], dtype=int64) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Series sınıfının daha pek çok faydalı metodu vardır. Bu metotları Pandas dokümanlarından inceleyebilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pandas'taki en önemli veri yapısı DataFrame denilen veri yapısıdır. DataFrame tipik olarak istatistiksel veri kümesini temsil etmek için düşünülmüştür. DataFrame nesnesinin sütunlardan oluşan matrisel bir yapısı vardır. Aslında DataFrame nesnesi Series nesnelerinden oluşmaktadır. Yani DataFrame nesnelerinin sütunları Series nesneleridir. NumPy dizilerinin elemanları aynı türden olur. Her ne kadar elemanları aynı türden olmayan NumPy dizileri de oluşturulabiliyorsa da (örneğin dtype='object' diyerek) bu biçimde NumPy dizilerinin uygulamada kullanımı yoktur. O halde Pandas kütüphanesi aslında sütunları farklı türlerden olabilen DataFrame denilen bir veri yapısı sunmaktadır. İstatistik ve veri bilimindeki "veri kümeleri (datasets)" ham durumda böyle bir yapıya sahiptir. Bu bakımdan veri kümeleri veritabanlarındaki tablolara da benzetilebilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir DataFrame nesnesi tipik olarak DataFrame sınıfının __init__ metodu yoluyla oluşturulur. DataFrame sütunlardan oluşmaktadır. Bir DataFrame nesnesinde satır index parametresiyle, sütun etiketleri ise columns parametresiyle belirlenebilmektedir. DataFrame nesnesi iki boyutlu bir Python listesi ile oluşturulabilir. Eğer index parametresi ve columns parametresi belirtilmezse oluşturulan DataFrame nesnesinin satır etiketleri ve sütun etiketleri 0, 1, 2, ... biçiminde atanır. Örneğin: >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> df 0 1 2 0 1 2 3 1 4 5 6 2 7 8 9 Biz satırlara ilişkin etiketleri index parametresiyle, sütunlara ilişkin etiketleri ise columns parametresiyle belirleyebiliriz. Örneğin: >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=['a', 'b', 'c'], columns=['x', 'y', 'z']) >>> df x y z a 1 2 3 b 4 5 6 c 7 8 9 Genellikle DataFRame nesnelerine sütun etiketleri (yani isimleri) iliştirilir ancak satır etiketleri iliştirilmez. Tabii DataFrame nesneleri aslıdna farklı türlerden sütunlardan oluşturulabilir. Örneğin: >>> df = pd.DataFrame([['Ali Serçe', 'Erkek', 172, 72], ['Fehmi Ak', 'Erkek', 182, 92], ['Ayşe Er', 'Kadın', 168, 68]], columns=['Adı Soyadı', 'Cinsiyet', 'Boy', 'Kilo']) >>> df Adı Soyadı Cinsiyet Boy Kilo 0 Ali Serçe Erkek 172 72 1 Fehmi Ak Erkek 182 92 2 Ayşe Er Kadın 168 68 Bir DataFrame nesnesi iki boyutlu bir NumPy dizisi ile de yaratılabilir. Örneğin: >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype='float32') >>> df = pd.DataFrame(a, columns=['a', 'b', 'c']) >>> df a b c 0 1.0 2.0 3.0 1 4.0 5.0 6.0 2 7.0 8.0 9.0 Örneğin: >>> a = np.arange(30).reshape(10, 3) >>> df = pd.DataFrame(a) >>> df 0 1 2 0 0 1 2 1 3 4 5 2 6 7 8 3 9 10 11 4 12 13 14 5 15 16 17 6 18 19 20 7 21 22 23 8 24 25 26 9 27 28 29 DataFrame nesnesi bir sözlük ile de yaratılabilir. Bu durumda sözlüğün anahtarları sütun isimlerini, değerleri de sütunlardaki değerleri belirtir. Örneğin: >>> d = {'Adı Soyadı': ['Kaan Aslan', 'Ali Serçe', 'Ayşe Er'], 'Boy': [182, 174, 168], 'Kilo': [78, 69, 56]} >>> d {'Adı Soyadı': ['Kaan Aslan', 'Ali Serçe', 'Ayşe Er'], 'Boy': [182, 174, 168], 'Kilo': [78, 69, 56]} >>> df = pd.DataFrame(d) >>> df Adı Soyadı Boy Kilo 0 Kaan Aslan 182 78 1 Ali Serçe 174 69 2 Ayşe Er 168 56 #------------------------------------------------------------------------------------------------------------------------------------ Bir DataFrame nesnesinin sütunları farklı türlerden olabilir. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d) >>> df a b c 0 1.0 4 True 1 2.0 5 False 2 3.0 6 True Örneğin: >>> df = pd.DataFrame({'A': np.array([1, 2, 3], dtype='int32'), 'B': np.array(['X', 'Y', 'Z'], dtype='str'), 'C': np.array([1.2, 4.5, 7.8], dtype='float32')}) >>> df A B C 0 1 X 1.2 1 2 Y 4.5 2 3 Z 7.8 Bir DataFrame nesnesinin sütunları farklı türlerden olabileceğine göre bu durumda DataFrame nesnesi için tek bir dtype türünden bahsedilemez. Çünkü her sütunun ayrı bir dtype türü vardır. İşte DataFrame sınıfının dtypes örnek özniteliği bize bir Series nesnesi olarak tüm sütunların türlerini vermektedir. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d) >>> df.dtypes a float32 b int32 c bool dtype: object #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir DataFrame nesnesinin elemanlarına erişebiliriz. İndeksleme işlemi Series nesnelerinde olduğu gibi üç yolla yapılmaktadır: 1) Doğrudan [...] operatörü uygulayarak 2) loc örnek özniteliğine [...] operatörü uygulayarak 3) iloc örnek özniteliğine [...] operatörü uygulayarak #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Doğrudan [...] operatörü ile indeskelemede biz sütunları elde ederiz. Bu durumda indeks olarak sütun etiketleri (yani isimleri) verilmelidir. Eğer doğrudan indekslemede tek bir sütun belirtiliyorsa bu durumda bize o sütun bir Series nesnesi biçiminde verilmektedir. Örneğin: >>> a = np.arange(50).reshape((10, 5)) >>> df = pd.DataFrame(a, columns=list('ABCDE')) >>> df A B C D E 0 0 1 2 3 4 1 5 6 7 8 9 2 10 11 12 13 14 3 15 16 17 18 19 4 20 21 22 23 24 5 25 26 27 28 29 6 30 31 32 33 34 7 35 36 37 38 39 8 40 41 42 43 44 9 45 46 47 48 49 >>> s = df['C'] >>> s 0 2 1 7 2 12 3 17 4 22 5 27 6 32 7 37 8 42 9 47 Name: C, dtype: int32 >>> type(s) DataFrame nesnesinin sütunları Series nesneleri olarak bize verilir. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d) >>> df a b c 0 1.0 4 True 1 2.0 5 False 2 3.0 6 True >>> df['b'] 0 4 1 5 2 6 Name: b, dtype: int32 Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d) >>> df a b c 0 1.0 4 True 1 2.0 5 False 2 3.0 6 True >>> s = df['b'] >>> s 0 4 1 5 2 6 Name: b, dtype: int32 >>> type(s) Doğrudan indekslemede birden fazla sütun belirtilirse bu durumda bize Series nesnesi değil DataFrame nesnesi verilir. Örneğin: >>> a = np.arange(50).reshape((10, 5)) >>> df = pd.DataFrame(a, columns=list('ABCDE')) >>> df A B C D E 0 0 1 2 3 4 1 5 6 7 8 9 2 10 11 12 13 14 3 15 16 17 18 19 4 20 21 22 23 24 5 25 26 27 28 29 6 30 31 32 33 34 7 35 36 37 38 39 8 40 41 42 43 44 9 45 46 47 48 49 >>> k = df[['B', 'D']] >>> k B D 0 1 3 1 6 8 2 11 13 3 16 18 4 21 23 5 26 28 6 31 33 7 36 38 8 41 43 9 46 48 >>> type(k) Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d) >>> df a b c 0 1.0 4 True 1 2.0 5 False 2 3.0 6 True >>> k = df[['b', 'c']] >>> k b c 0 4 True 1 5 False 2 6 True >>> type(k) Bir DataFrame'in doğrudan indekslenmesiyle elde edilen Series nesneleri aslında bir view belirtmektedir. Ancak bu view nesnelerinin güncellenmesi tavsiye edilmemektedir. Ancak doğrudan indekslemeden elde edilen DataFrame nesneleri bir vişe belirtmemektedir. Doğrudan indekslemede bool indeksleme uygulanabilir. Ancak bu durumda Dataframe'in sütunları değil satırları elde edilmektedir. Örneğin: >>> a = np.arange(50).reshape((10, 5)) >>> df = pd.DataFrame(a, columns=list('ABCDE')) >>> df A B C D E 0 0 1 2 3 4 1 5 6 7 8 9 2 10 11 12 13 14 3 15 16 17 18 19 4 20 21 22 23 24 5 25 26 27 28 29 6 30 31 32 33 34 7 35 36 37 38 39 8 40 41 42 43 44 9 45 46 47 48 49 >>> df[df['B'] % 2 == 0] A B C D E 1 5 6 7 8 9 3 15 16 17 18 19 5 25 26 27 28 29 7 35 36 37 38 39 9 45 46 47 48 49 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ loc indekslemesinde biz satır elde ederiz. Ancak satırları index etiketlerine göre köşeli parantez içerisinde belirtmek zorundayız. Yani loc indekslemesinde satırlar için satır etiketleri kullanılmaktadır. Eğer indekslemeden tek bir satır elde edilirse bu satır bize Series nesnesi biçiminde verilmektedir. Tabii satırın sütunları eğer farklı dtype türündense Series nesnesinin dtype türü mecburen 'object' biçiminde olacaktır. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d, index=['x', 'y', 'z']) >>> df a b c x 1.0 4 True y 2.0 5 False z 3.0 6 True >>> s = df.loc['y'] >>> s a 2.0 b 5 c False Name: y, dtype: object >>> type(s) Tabii loc indekslemesinde biz [...] operatöründe birden fazla satır da belirtebiliriz. Bu durumda artık bize Series nesnesi verilmez, DataFrame nesnesi verilir. Dolayısıyla elde edilen DataFrame nesnesinin sütunları orijinal sütunlar türünden olacaktır. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d, index=['x', 'y', 'z']) >>> df a b c x 1.0 4 True y 2.0 5 False z 3.0 6 True >>> k = df.loc[['x', 'z']] >>> k a b c x 1.0 4 True z 3.0 6 True >>> type(k) Pandas indekslemelerinde tek bir satır ya da sütunun Series nesnesi olarak ama birden fazla satır ya da sütunun DataFrame nesnesi olarak verildiğine dikkat ediniz. Tabii genellikle satır etiketleri verilmediği için bu etiketler 0, 1, 2, ... gibi sayılardan oluşmaktadır. Bu durumda loc indekslemesinde bunlar kullanılır. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE')) >>> df A B C D E 0 0 1 2 3 4 1 5 6 7 8 9 2 10 11 12 13 14 3 15 16 17 18 19 4 20 21 22 23 24 >>> k = df.loc[[2, 3, 0]] >>> k A B C D E 2 10 11 12 13 14 3 15 16 17 18 19 0 0 1 2 3 4 loc indekslemesinde dilimleme de yapılabilir. Bu durumda dilimlemeye ilk ve son indeksler dahil olur. (Halbuki Python ve NumPy dilimlemelerinde son indeks dahil değildir) Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> k = df.loc['y': 'n'] >>> k A B C D E y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 loc indekslemesinde bool indeksleme de yapılabilir. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> k = df.loc[df['C'] % 2 == 0] >>> k A B C D E x 0 1 2 3 4 z 10 11 12 13 14 n 20 21 22 23 24 Tabii aslında bool indeksleme filtreleme ile yapılmaktadır. Örneğin: loc indekslemesinde biz satırları elde ettikten sonra ikinci boyutta sütun isimlerini kullanarak belli sütunları da çekebiliriz. Bu durumda ikinci boyutta yine loc indeskelemesinde sütun etiketleri bulunmak zorundadır. loc indekslemesi her zaman etiketlerle çalışmaktadır. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> k = df.loc['z', 'C'] >>> k 12 >>> k = df.loc['z', ['C', 'D']] >>> k C 12 D 13 Name: z, dtype: int32 Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d, index=['x', 'y', 'z']) >>> df a b c x 1.0 4 True y 2.0 5 False z 3.0 6 True >>> df.loc[['x', 'y'], ['a', 'c']] a c x 1.0 True y 2.0 False Görüldüğü gibi loc indekslemesinde aslında ikinci boyut verilerek sütunların da filtre edilmesi sağlanabilmektedir. Amaancak yukarıda da belirttiğimiz gibi loc indekslemesinde her iki indeks olarak etiket içermesi gerekmektedir. Tabii loc indesklemesinde istenirse iki boyut üzerinde de dilimleme işlemleri yapılabilir. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> k = df.loc['x':'z', 'B': 'D'] >>> k B C D x 1 2 3 y 6 7 8 z 11 12 13 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ iloc indekslemesi de yine satırları elde etmek için kullanılmaktadır. Ancak köşeli parantezler içerisine satırların etiketleri değil sıra numaraları yazılmaktadır. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> k = df.iloc[[0, 3, 4], [1, 3, 4]] >>> k B D E x 1 3 4 k 16 18 19 n 21 23 24 >>> k = df.iloc[:, :-1] >>> k A B C D x 0 1 2 3 y 5 6 7 8 z 10 11 12 13 k 15 16 17 18 n 20 21 22 23 iloc indekslemesinde negatif indekslemenin de kullanılabildiğine dikkat ediniz. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> df = pd.DataFrame(d, index=['x', 'y', 'z']) >>> df a b c x 1.0 4 True y 2.0 5 False z 3.0 6 True >>> df.iloc[1] a 2.0 b 5 c False Name: y, dtype: object >>> df.iloc[[1, 0]] a b c y 2.0 5 False x 1.0 4 True iloc indekslemesinde de bool indeksleme yapılabilmektedir. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> k = df.iloc[[False, True, True, False, True]] >>> k A B C D E y 5 6 7 8 9 z 10 11 12 13 14 n 20 21 22 23 24 Bool indekslemede loc ile iloc arasında küçk bir fark vardır. Biz iloc ile bool indekslemede Series nesnelerini kullanamayız ancak loc indekslemesinde kullanabiliriz. Bu özelliği Series nesnelerinde de aynı biçimde olduğunu anımsayınız. Ancak bool türden bir Series nesnesi loc indeklemesinde kullanılacaksa Series nesnesinin satır indeksleriyle DataFrame nesnesinin satır indeksleri aynı olmalıdır. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> s = pd.Series([False, True, True, False, True]) >>> s 0 False 1 True 2 True 3 False 4 True dtype: bool >>> df.loc[s, :] Bu indeksleme exception'a yol açacaktır. Ancak örneğin: >>> s = df['C'] % 2 == 0 >>> s x True y False z True k False n True Name: C, dtype: bool >>> df.loc[s, :] A B C D E x 0 1 2 3 4 z 10 11 12 13 14 n 20 21 22 23 24 Burada artık elde edilen Series nesnesinin indeksleriyle DataFrame nesnesinin indeksleri aynıdır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DataFrame nesnesi dolaşılabilir bir nesnedir DataFrame nesnesi dolaşıldığında sütun isimleri elde edilmektedir. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> for x in df: ... print(x) ... A B C D E DataFramenesnesi doğrudan [...] indekslemesi ile dolaşıldığında eğer tek bir sütun belirtilmişse o sütunun satırları elde edilmektedir. Örneğin: >>> for x in df['B']: ... print(x) ... 1 6 11 16 21 Doğrudan [...] ile indesklemede tek bir sütunun belirtidiği durumda elde edilen nesnenin Series nesnesi olduğuna dikkat ediniz. Yani biz burada aslında Series nesnesini dolaşmaktayız. Doğrudan [...] ile birden fazla sütun ismi belirtildiğinde DataFrame nesnesi elde edildiğini anımsayınız. Örneğin: >>> for x in df[['A', 'B']]: ... print(x) ... A B loc ya da iloc indekslemesiyle satırların duruma göre Series ya da DataFrame nesnesi biçiminde elde edildiğini anımsayınız. Örneğin: >>> for x in df.iloc[0]: ... print(x) ... 0 1 2 3 4 Burada df.iloc[0] ile bir Series nesnesi elde edilmiştir. Dolayısıyla Series nesnesi dolaşılmıştır. Örneğin: >>> for x in df.iloc[0, :]: ... print(x) ... 0 1 2 3 4 Burada da df.iloc[0, :] indekslemesi ile bir Series nesnesi elde edilmiş ve o Series nesnesi dolaşılmıştır. Örneğin: >>> for x in df.iloc[:, 2:4]: ... print(x) ... C D Burada df.iloc[:, 2:4] indekslemesi ile bir DataFrame nesnesi elde edilmiştir ve o DataFrame nesnesi dolaşılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DataFrame nesnesinin satır satır dolaşılmasına sıkça gereksinim duyulmaktadır. Bunu yapmanın birkaç yolu olabilir. Örneğin dolaşımı iloc indekslemesi ile aşağıdaki gibi yapabiliriz: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> for i in range(len(df)): ... print(df.iloc[i]) ... A 0 B 1 C 2 D 3 E 4 Name: x, dtype: int32 A 5 B 6 C 7 D 8 E 9 Name: y, dtype: int32 A 10 B 11 C 12 D 13 E 14 Name: z, dtype: int32 A 15 B 16 C 17 D 18 E 19 Name: k, dtype: int32 A 20 B 21 C 22 D 23 E 24 Name: n, dtype: int32 Burada satır numarası ile döngü içerisinde satırları Series nesneleri olarak elde ettik. Örneğin: #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında bir DataFrame nesnesi genellikle elle oluşturulmaz. Bir dosyadan hareketle oluşturulur. Örneğin CSV dosyaları istatistiksel veri tablolarını ifade etmek için en çok kullanılan dosyalardır. Biz nasıl NumPy'da loadtxt fonksiyonu ile dosyalardan okuma yaptıysak Pandas'ta da read_csv fonksiyonu ile CSV dosyalarından okuma yaparak dosyanın içeriğini bir DataFrame nesnesi biçiminde elde edebiliriz. read_csv fonksiyonu NumPy'daki loadtxt fonksiyonuna göre çok daha iyi bir CSV okuyucusudur. NumPy'ın loadtxt fonksiyonu CSV dosyasının farklı biçimlerini (dialect'lerini) iyi bir biçimde okuyamamaktadır. Oysa read_csv genellikle CSV dosyalarını başarılı ve istediğimiz biçimde okuyabilmektedir. Zaten NumPy'ın loadtxt fonksiyonu aslında (ismine dikkat ediniz) özellikle CSV dosyaları için değil diğer tarzda dosyalar için de kullanılabilecek biçimde genel tasarlanmıştır. Oysa Pandas'ın read_csv fonksiyonu yalnızca CSV dosyaları için bu dosyalar dikkate alınarak tasarlanmıştır. Veri bilimi ile uğraşan pek çok kişi CSV dosyalarını bu fonksiyon ile okuyup gerekirse DataFrame nesnesini NumPy nesnesine dönüştürmektedir. Mademki istatistiksel veri tabloları genellikle dosyalarda ve özellikle de CSV dosyalarında bulunmaktadır. O halde Pandas genel olarak onları kullanıma hazır hale getirme sürecinde daha uygun bir araçtır. Pandas'ın daha uygun bir araç olmasının temel nedeni sütunların farklı türlerden olabilmesidir. Halbuki NumPy'da örneğin iki boyutlu bir matrisin tüm elemanları aynı dtype türünden olmak zorundadır. Halbuki istatistiksel veri tablolarında genellikle sütunlar farklı türlerden olma eğilimindedir. Biz farklı türlerden sütunlara sahip CSV dosyalarını loadtxt ile okuamaya çalıştığımızda NumPy dizisinin tek bir dtype türü olduğu için okunan Numpy dizisi "object' türünden ya da string türünden olmaktadır. Sütun türlerinin kaybedilmesi programcının işini zorlaştırmaktadır. Halbuki Pandas özellikle bu aşamada sütun türlerinin farklı olabilmesi nedeniyle daha iyi bir ortam sunmaktadır. read_csv fonksiyonun çok fazla sayıda parametresi vardır. Ancak bu parametrelerin hepsi makul default değerlerle geçilebilir. Çoğu kez tek yapılması gereken şey fonksiyona birinci parametresiyle okunacak CSV dosyasının yol ifadesini vermektir. Aşağıdaki örnekte 'covid-19.csv' dosyası raead_csv fonksiyonu ile okunmuştur. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd df = pd.read_csv('covid-19.csv') print(df.columns) print(df.dtypes) #------------------------------------------------------------------------------------------------------------------------------------ 51. Ders 21/06/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ read_csv fonksiyonun usecols parametresi yine loadtxt fonksiyonunda olduğu gibi okunacak sütunların numaralarını ya da isimlerini bir liste olarak bizden alır. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd df = pd.read_csv('covid-19.csv', usecols=[1, 2, 3]) print(df) df = pd.read_csv('covid-19.csv', usecols=['Confirmed', 'Deaths', 'Recovered']) print(df) #------------------------------------------------------------------------------------------------------------------------------------ read_csv fonksiyonu tırnakları da sütun ayıracı olarak akıllı bir biçimde anlayabilmektedir. Örneğin aşağıdaki gibi bir 'student.csv' isminde bir dosya olsun: "Adı, Soyadı", No,Doğum Yeri "Ali, Serçe",123,Tarsus "Kaan, Aslan",345,Eskişehir "Necati, Ergin",764,Giresun "Hasan, Kılışçaslan",523,Van "Barış, Gök",692,Çorum "Gürbüz, Aslan",823,Eskişehir Bu dosyada iki tırnak içerisindeki virgüllerin dikkate alınmamasını, iki tırnaklı sütunların tek bir sütun olarak ele alınmasını istemiş olalım. Bunun için fonksiyonun quoting parametresini ayarlamamız gerekebilir. Fonksiyon defauklt durumda bunu akıllı biçimde yapabilmektedir. Ancak tırnaklama konusunda bazı ayrıntıları belirleyebilmek için "quoting" isimli parametreden faydalanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd df = pd.read_csv('student.csv') print(df) #------------------------------------------------------------------------------------------------------------------------------------ Bir Dataframe nesnesi istenirse DataFrame sınıfının to_csv metodu ile CSV dosyası olarak da save edilebilir. Yani bu fonksiyon işlevsel olarak read_csv fonksiyonun tersini yapmaktadır. metot default durumda DataFrame'deki satır etiketlerini de da dosyaya yazmaktadır. Eğer bu istenmiyorsa index parametresi False olarak geçilmelidir. ------------------------------------------------------------------------------------------------------------------------------ import pandas as pd df = pd.read_csv('student.csv') print(df) print(df.dtypes) df.to_csv('x.csv', index=False) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DataFrame nesneleri üzerinde de Series nesneleri üzerinde yapılan işlemlerin benzerleri yapılabilmektedir. Tabii DataFrame üzerinde işlem yaparken işlemden tüm sütunlar etkilenmektedir. Buradaki metotların bazılarının yine global fonksiyon karşılıkları da vardır. Bu metotlar ve fonksiyonlar axis parametresi alabilmektedir. Örneğin Series sınıfın mean metodu ya da mean fonksiyonu bir Series nesnesindeki değerlerin ortalamasını bize verir. Ancak DaraFrame sınıfının mean metodu ya da global mean fonksiyonu axis temelinde bize satır ya da sütun temelinde değerler vermektedir. Tabii bu tür metotlar ve foksiyonlar DataFrame nesnesinin tüm sütunları üzerinde etkili olduğu için sütunların türlerinin ilgili işlemlere uygun olması gerekir. Örneğin biz kişilerin ad ve soyaflarının bulunduğu sütunun ortalamasını alamayız. Bu metot ve fonksiyonlarda eksen belirtilmezse default olarak axis=0 alınmaktadır. Yani stunsal işlemler yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} df = pd.DataFrame(d) m = df[['Kilo', 'Boy']].mean() print(m) m = df[['Kilo', 'Boy']].mean(axis=1) print(m) #------------------------------------------------------------------------------------------------------------------------------------ DataFrame sınıfının yine Series sınıfında olduğu gibi min gibi, max, gibi, sum gibi metotları vardır. Bunlar da axis temelinde işlem yapabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} df = pd.DataFrame(d) result = df[['Kilo', 'Boy']].min() print(result) result = df[['Kilo', 'Boy']].min(axis=1) print(result) result = df[['Kilo', 'Boy']].sum() print(result) result = df[['Kilo', 'Boy']].sum(axis=1) print(result) #------------------------------------------------------------------------------------------------------------------------------------ DataFrame nesnesine doğrudan indeksleeme yoluyla atama yapılırsa bu işlem "sütun eklemek" anlamına gelir. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> df['F'] = [10, 20, 30, 40, 50] >>> df A B C D E F x 0 1 2 3 4 10 y 5 6 7 8 9 20 z 10 11 12 13 14 30 k 15 16 17 18 19 40 n 20 21 22 23 24 50 Ancak bu biçimde ekleme yapılırken atanan nesne bir Series nesnesiyle o Series nesnesinin index bilgileriyle DataFrame nesnesinin index bilgilerinin aynı olması gerekir. Aksi takdirde ekleme yapılsa da eklenen değerler Nan olarak gözükecektir. Örneğin: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> df['F'] = pd.Series([10, 20, 30, 40, 50], index=list('xyzkn')) >>> df A B C D E F x 0 1 2 3 4 10 y 5 6 7 8 9 20 z 10 11 12 13 14 30 k 15 16 17 18 19 40 n 20 21 22 23 24 50 >>> df['G'] = pd.Series([10, 20, 30, 40, 50]) >>> df A B C D E F G x 0 1 2 3 4 10 NaN y 5 6 7 8 9 20 NaN z 10 11 12 13 14 30 NaN k 15 16 17 18 19 40 NaN n 20 21 22 23 24 50 NaN #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DataFrame üzerinde bir sütun insert etmek için DataFrame sınıfının insert metodu metodu kullanılabilmektedir. insert metodunun birinci parametresi her zaman insert edilecek sütunun indeks numarasını alır. İkinci parametre indeks edilecek sütunun ismini (ayni etiketini), üçüncü parametre ise sütun bilgilerini almaktadır. insert metodu "in-place" insert işlemi yapmaktadır. Yani DatFrame nesnesinin kendi üzerinde ekleme yapılmaktadır. Örneğin: >>> import pandas as pd >>> d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} >>> >>> df = pd.DataFrame(d) >>> df Adı Kilo Boy 0 Ali 48.3 172 1 Veli 56.7 156 2 Selami 92.3 182 3 Ayşe 65.3 153 4 Fatma 72.3 171 >>> bmi = df['Kilo'] / (df['Boy'] / 100) ** 2 >>> bmi 0 16.326393 1 23.298817 2 27.864992 3 27.895254 4 24.725557 dtype: float64 >>> df.insert(3, 'Vücut Kitle Endeksi', bmi) >>> df Adı Kilo Boy Vücut Kitle Endeksi 0 Ali 48.3 172 16.326393 1 Veli 56.7 156 23.298817 2 Selami 92.3 182 27.864992 3 Ayşe 65.3 153 27.895254 4 Fatma 72.3 171 24.725557 Biz bu insert metoduyla bir Python nesnesini ya da NumPy nesnesini de sütun olarak ekleyebiliri. Ancak eğer insert edilecek bilgi bir Series nesnesi ise bu durumda Series nesnesinin elemanlarının indeksleri ile DataFrame satırlarının indekslerinin aynı olması gerekmektedir. Örneğin: d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} >>> df = pd.DataFrame(d, index=['x', 'y', 'z']) >>> df d a b c x 10.0 1.0 4 True y 20.0 2.0 5 False z 30.0 3.0 6 True >>> s = pd.Series([10, 20, 30], index=['x', 'y', 'z'], dtype='float32') >>> s x 10.0 y 20.0 z 30.0 >>> df.insert(0, 'd', s) >>> df d a b c x 10.0 1.0 4 True y 20.0 2.0 5 False z 30.0 3.0 6 True Biz insert metodu ile bir DatFrame nesnesine başka bir DataFrame nesnesini insert edemeyiz. insert metodu tek bir sütunu insert etmek için kullanılmaktadır. Global bir insert fonksiyonu bulunmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import pandas as pd d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} df = pd.DataFrame(d) bmi = df['Kilo'] / (df['Boy'] / 100) ** 2 df.insert(3, 'Vücut Kitle Endeksi', bmi) print(df) #------------------------------------------------------------------------------------------------------------------------------------ DataFrame nesnesine satır insert etmenin birkaç yolu vardır. Örneğin loc indekslemesiyle olmayan bir indekse satır bilgisi atanırsa bu insert anlamına gelmektedir: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE'), index=list('xyzkn')) >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 >>> df.loc['r'] = [10, 20, 30, 40, 50] >>> df A B C D E x 0 1 2 3 4 y 5 6 7 8 9 z 10 11 12 13 14 k 15 16 17 18 19 n 20 21 22 23 24 r 10 20 30 40 50 Tabii default indeksler eğer satır numaralarıysa son satırın numarasını len fonksiyonu ile elde edip eklemeyi yapabiliriz: >>> a = np.arange(25).reshape(5, 5) >>> df = pd.DataFrame(a, columns=list('ABCDE')) >>> df A B C D E 0 0 1 2 3 4 1 5 6 7 8 9 2 10 11 12 13 14 3 15 16 17 18 19 4 20 21 22 23 24 >>> df.loc[len(df)] = [10, 20, 30, 40, 50] >>> df A B C D E 0 0 1 2 3 4 1 5 6 7 8 9 2 10 11 12 13 14 3 15 16 17 18 19 4 20 21 22 23 24 5 10 20 30 40 50 Bu biçimde bir NumPy dizisini de bir satır olarak ekleyebiliriz. DataFrame nesnesine append metodu ile satır ve sütun eklenebiliyordu. Ancak bu metot artık "deprecated" yapılmış durumdadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ insert işleminin yanı sıra iki DataFrame nesnesini birleştiren concat isimli bir fonksiyon da vardır. Aslında bu concat fonksiyonunu biz zaten Series nesnelerini birleştirmekte de kullanmıştık. Bu fonksiyon DataFrame nesneleri için de kullanılmaktadır. concat fonksiyonunda yine birleştirilecek nesneler bir liste ya da demet biçiminde verilmelidir. concat fonksiyonu DataFrame söz konusu olduğunda axis parametresini kullanmaktadır. Zira biz birleştirmeyi satırsal ya da sütunsal yapabiliriz. İki DataFrame nesnesini sütunsal birleştireceksek axis=1 verilmelidir. Örneğin: >>> d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} >>> df1 = pd.DataFrame(d) >>> df1 Adı Kilo Boy 0 Ali 48.3 172 1 Veli 56.7 156 2 Selami 92.3 182 3 Ayşe 65.3 153 4 Fatma 72.3 171 >>> df2 = pd.DataFrame({'Yaş': [30, 18, 22, 45, 53], 'Doğum Yeri': ['Eskşehir', 'Sakarya', 'Trabzon', 'İzmir', 'Bursa']}) >>> df2 Yaş Doğum Yeri 0 30 Eskşehir 1 18 Sakarya 2 22 Trabzon 3 45 İzmir 4 53 Bursa >>> df3 = pd.concat([df1, df2], axis=1) >>> df3 Adı Kilo Boy Yaş Doğum Yeri 0 Ali 48.3 172 30 Eskşehir 1 Veli 56.7 156 18 Sakarya 2 Selami 92.3 182 22 Trabzon 3 Ayşe 65.3 153 45 İzmir 4 Fatma 72.3 171 53 Bursa Tabii concat işlemi ile sütun birleştirmesi yapılırken iki DataFrame nesnesinin satır indekslerinin (yani etiketlerinin) aynı olması gerekir. Eğer satır indeksleri aynı olmazsa bu durumda her uyuşmayan indeks için ayrı ekleme yapılır. Ancak uyuşmayan elemanlar NaN ile doldurulur. Örneğin: >>> d = {'Adı': ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'], 'Kilo': [48.3, 56.7, 92.3, 65.3, 72.3], 'Boy': [172, 156, 182, 153, 171]} >>> df1 = pd.DataFrame(d) >>> df1 Adı Kilo Boy 0 Ali 48.3 172 1 Veli 56.7 156 2 Selami 92.3 182 3 Ayşe 65.3 153 4 Fatma 72.3 171 >>> df2 = pd.DataFrame({'Yaş': [30, 18, 22, 45, 53], 'Doğum Yeri': ['Eskşehir', 'Sakarya', 'Trabzon', 'İzmir', 'Bursa']}, index=[0, 1, 2, 10, 20]) >>> df2 Yaş Doğum Yeri 0 30 Eskşehir 1 18 Sakarya 2 22 Trabzon 10 45 İzmir 20 53 Bursa >>> df3 = pd.concat([df1, df2], axis=1) >>> df3 Adı Kilo Boy Yaş Doğum Yeri 0 Ali 48.3 172.0 30.0 Eskşehir 1 Veli 56.7 156.0 18.0 Sakarya 2 Selami 92.3 182.0 22.0 Trabzon 3 Ayşe 65.3 153.0 NaN NaN 4 Fatma 72.3 171.0 NaN NaN 10 NaN NaN NaN 45.0 İzmir 20 NaN NaN NaN 53.0 Bursa Burada concat fonksiyonun inplace işlem yapmadığına dikkat ediniz. inplace işlemler genel olarak fonksiyonlar yoluyla değil metotlar yoluyla yapılmaktadır. inplace işlem yapan bir concat metodu yoktur. concat ile tabii biz bir DataFrame'e bir satır da ekleyebiliriz. Bu durumda axis=0 (default durum) yapmamız gerekir. Örneğin: >>> d = {'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [True, False, True]} >>> df1 = pd.DataFrame(d) >>> df1 a b c 0 1 4 True 1 2 5 False 2 3 6 True >>> df2 = pd.DataFrame({'a': [100], 'b': [200], 'c': [True]}) >>> df2 a b c 0 100 200 True >>> df3 = pd.concat([df1, df2], axis=0) >>> df3 a b c 0 1 4 True 1 2 5 False 2 3 6 True 0 100 200 True DataFrame nesneleri ile Series nesneleri de concat işlemine sokulabilmektedir. Bu işleme seyrek bir biçimde gereksinim duyulmaktadır. Biz burada bu işlem üzerinde durmayacağız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DataFrame nesnesinden sütun ya da satır silmek için Series sınıfında olduğu gibi drop metodu kullanılmaktadır. Bu metot bir ya da birden fazla satır ya da sütunu silebilmektedir. inplace parametresi default False durumdadır. Yani default durumda inplace işlem yapılmaz. Ancak inplace parametresi True geçilirse inplace işlem yapılır. Silinecek satır ya da sütunlar birinci parametredeki label parametresiyle girilmektedir. Bu parametre her zamansütun ya da satır etiketlerini (isimlerini) almaktadır, indeks numaralarını değil. Örneğin: >>> d = {'a': np.array([1, 2, 3], dtype='float32'), 'b': np.array([4, 5, 6], dtype='int32'), 'c': np.array([True, False, True], dtype='bool')} >>> d = {'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [True, False, True]} >>> df1 = pd.DataFrame(d) >>> df1 a b c 0 1 4 True 1 2 5 False 2 3 6 True >>> df2 = df1.drop(['b'], axis=1) >>> df2 a c 0 1 True 1 2 False 2 3 True >>> df1.drop(['c'], axis=1, inplace=True) >>> df1 a b 0 1 4 1 2 5 2 3 6 Tabii aslında yukarıdaki işlemler zaten indeksleme yoluyla da yapılabilir. Örneğin: >>> d = {'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [True, False, True]} >>> df1 = pd.DataFrame(d) >>> df1 a b c 0 1 4 True 1 2 5 False 2 3 6 True >>> df2 = df1[['a', 'c']] >>> df2 a c 0 1 True 1 2 False 2 3 True >>> df2 = df1.iloc[[0, 2], :] >>> df a b c 0 1 4 True 1 2 5 False 2 3 6 True >>> df2 a b c 0 1 4 True 2 3 6 True #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ DataFrame sınıfının dropna metodu da çok sık kullanılmaktadır. Bir CSV dosyasını okuduğumuzda satırlarda eksik veriler bulunabilmektedir. Veri bilimcisi bazen bu eksik verilerin bulunduğu satırları tümden atmak isteyebilir. Ya da eksik verileri mevcut veriler temelinde doldurmak (imputation) isteyebilir. Örneğin aşağıdaki gibi "student.csv" dosyası olsun: Adı Soyadı,Kilo,Boy Ali Serçe,78,173 Necati Ergin,95, Ayşe Er,,162 Sacit Kara,82,172 Fehmi Yılmaz,92,1652 Pandas'ın read_csv fonksiyonu eksik verileri default durumda NaN biçiminde okumaktadır. İşte biz eksik verilerin bulunduğu satırı ya da sütunu tümden atmak isteyebiliriz. dropna metodunun axis parametresi de vardır. Bu parametre 0 geçilirse eksik verilen bulunduğu satırlar, 1 geçilirse sütunlar atılır. Örneğin: Burada Necati Ergin'in boy bilgisi, Ayşe Er'in Kilo bilgisi elde edilememiştir. dropna metodu ile axis=0 parametresiyle biz herhangi bir sütun bilgisi olmayan satırları ya da sütunları atabiliriz: import pandas as pd df = pd.read_csv('student.csv') cleaned_df = df.dropna(axis=0) print(cleaned_df) cleaned_df = df.dropna(axis=1) print(cleaned_df) Tabii bazen yukarıda da belirtildiği gibi olmayan verilerin mevcut verilerle doldurulması (imputation) da gerekebilmektedir. Örneğin olmayan boy ve kilo bilgileri ilgili sütunların ortalamaları biçiminde doldurulnak istenebilir. Bu durumda Series sınıfının ya da DataFrame sınıfının fillna metotları kullanılabilir. Örneğin biz yukarıdaki "student.csv" dosyasında olmayan bilgileri o sütunun ortalamasıyla doldurmak isteyelim. Bunun basit bir yolu şöyledir: import pandas as pd df = pd.read_csv('student.csv') print(df, end='\n\n') weight_mean = df['Kilo'].mean() # Series elde ediliyor df['Kilo'].fillna(weight_mean, inplace=True) # Series nesnesi dolduruluyor, ancak view olduğu için bundan dataframe etkilenir print(df, end='\n\n') tall_mean = df['Boy'].mean() # Series elde ediliyor df['Boy'].fillna(tall_mean, inplace=True) # Series nesnesi dolduruluyor, ancak view olduğu için bundan dataframe etkilenir print(df) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da sayısal grafikler çizmek için en çok kullanılan üç kütüphane "Matplotlib" ve "Seaborn" kütüphaneleridir. Başka alternatifler olsa da bu alternatifler henüz bunlarla rekabet edecek düzeye gelmemiştir. Biz burada Matplotlib üzerinde duracağız. Çünkü en yaygın kullanılan agrafik çizme kütüphanesi Matplotlib isimli kütüphanedir. Matplotlib Anaconda dağıtımının doğal bir parçası durumundadır. Bu nedenle bu dağıtım install edildiğinde zaten kurulmuş durumdadır. Ancak diğer dağırımlarda ve IDE'lerde bu kütüphanenin aşağıdaki gibi ayrıca kurulması gerekir: pip install matplotlib Kütüphanenin doğrudan çizim için kullanılan alt paketi "pyplot" isimli pakettir. Dolayısıyla genellikle programcılar doğrudan bu paketi kullanırlar. Biz de çizimlerimizde bu paketi aşağıdaki gibi import edeceğiz: import matplotlib.pyplot as plt Bu kütüphane "klasik prosedürel teknikle" yani fonksiyonlar yoluyla ya da "nesne yönelimli teknikle" yani sınıfların metotlarını çağırarak kullanılabilmektedir. Biz önce prosedürel kullanım üzerinde duracağız. Kütüphanenin dokümantasyonuna aşağıdaki bağlantıdan erişilebilir: https://matplotlib.org/stable/index.html #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Matplotlib kütüphanesinde önemli iki kavram vardır: Figür (figure) ve Eksen (axis). Figür eksenleri tutan bir kap gibi düşünülmüştür. Biz çizimleri eksenlere yaparız. En sık karşılaşılan durum bir figüde bir eksen bulunması durumudur. Ancak bir figürde birden fazla eksen de bulunabilir. Çizim işlemleri prosedürel teknikle yapılacaksa zaten bu teknikte işin başında default bir figür ve eksen yaratılmış durumdadır. Dolayısıyla çizimler zaten var olan figür ve eksen üzerine yapılır. Bizim yaratmadığımız zaten işin başında yaratılmış olan bu figür ve eksene "default figür" ve "default eksen" denilmektedir. Biz çizim fonksiyonu prosedürel teknikte her zaman default figürdeki default eksene çizim yapar. Matplotlib'te değişik grafikler için değişik fonksiyonlar kullanılmaktadır. Çizim işlemlerinden sonra show fonksiyonu çağrılmalıdır. show fonksiyonu görüntülemeyi yapmaktadır. Anaconda'nın Spyder IDE'sinde yapılan çizimlerin doğrudan IPython ekranında görüntülenmesi için yukarıda "Plots/Mute inline plotting" unched yapılmalıdır. Aksi takdirde çizimler "Plots" sekmesinde görüntülenecektir. Text ekranda komut satırında çizim yapılırken çizimler show fonksiyonu çağrıldığında bir popup pencere üzerinde görüntülenmektedir. Spyder'da "Mute inline plotting" unchecked yapıldığında show fonksiyonu görüntülemek için gerekmiyor gibi gözükse de bu fonksiyon başka işlevleri de olduğu için çağrılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ En çok kullanılan çizim fonksiyonu şüphesiz plot isimli fonksiyondur. plot fonksiyonu çizgi grafiği çizen bir fonksiyondur. Tipik olarak bu fonksiyon x ve y değerlerini birer liste ya da numpy dizisi olarak alır. Bu x ve y listelerinin karşılıklı elemanlarını nokta olarak kabul edip onları çizgilerle birleştirir. Tabii bu noktaların sayısı çoksa elde edilen çizgiler kırıklı görülmez. Örneğin aşağıda bir sinüs eğrisi örneği verilmiştir. Bu örneği NPOINTS değerini gittikçe artırarak deneyiniz. Nokta sayısı arttıkça kırıklı görünüm ortadan kalkacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import numpy as np NPOINTS = 20 x = np.linspace(-6.28, 6.28, NPOINTS) y = np.sin(x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ show yapana kadar çizdiğimiz tüm grafikler aynı eksende görüntülenir. Matplotlin otomatik olarak her grafiği farklı renkle çizdirmektedir. Her show işleminden sonra sıfırdan yeni bir eksen baş #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import numpy as np NPOINTS = 1000 x = np.linspace(-6.28, 6.28, NPOINTS) y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda yine birden fazla çizim aynı eksen üzerine yapılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) y = x ** 2 - 4 plt.plot(x, y) y = 3 * x - 2 plt.plot(x, y) y = 0.01 * x ** 3 plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda 2^x üstel fonksiyonunun grafiği çizilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import numpy as np x = np.linspace(0, 10, 10000) y = 2 ** x plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda sigmoid fonksiyonunun grafiği çizdirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import numpy as np x = np.linspace(-10, 10, 1000) y = 1 / (1 + np.e ** -x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Grafiğe bir başlık yazısı eklemek için title isimli fonksiyon kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği") y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ title fonksiyonunda loc parametresi başlığın hizalamasında kullanılır. Default durum 'center' biçimindedir. Ancak biz parametreye 'left' ya da 'right' girebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", loc='left') y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ title fonksiyonunun pad (padding) parametresi başlık yazısının grafiğin ne kadar yukarısında görüntüleneceğini belirtmektedir. Default durum 6 pixeldir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", pad=40) y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ title fonksiyonundaki **kwargs parametresine karşılık gelecek biçimde çok sayıda isimli parametre girilebilir. Bu parametreler plt.text fonksiyonunda dokümante edilmiştir. Önemli olanları şunlardır: color: Yazının rengini belirtir. Renk isim olarak girilebilir. Ya da '#rrggbb' biçiminde hex digitler biçiminde de girilebilir. fontsize: Yazının puntosunu belirler. fontfamily: Yazının font ismini belirtir. fontstyle: 'bold', 'italic' ya da 'normal olabilir. fontweight: Font'un bold'luk durumu ile ilgilidir. Bu parametreye 'bold', 'semibold', 'heavy', 'extrabold' gibi değerler girilebilmektedir. x ve y: Yazının başlatılacağı x ve y pozisyonlarını belirtmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6.28, 6.28, 100) plt.title("Sinus-Kosinüs Grafiği", pad=40, color='red', fontsize=20, fontfamily='arial', fontstyle='italic', fontweight='bold', x=0.5) y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Hangi grafiğin hangi amaçla kullanıldığını belirten simgelere "legend" denilmektedir. Legend oluşturmak için legend fonksiyonu kullanılmaktadır. Fonksiyona yazılardan oluşan dolaşılabilir bir nesne verilir. Fonksiyon da bu yazılardan hareketle legend'ları oluşturur. legend fonksiyonu grafikler çizildikten sonra (örneğin plot çağrılarından sonra) çağrılmalıdır. legend bilgisi otomatik olarak grafiğin uygun bir yerine yerleştirilmektedir. Ancak legend fonksiyonunun loc parametresi ile bu yerleşim yerini biz de belirleyebiliriz. loc parametresi 'upper left', 'upper right', 'lower left', 'lower right' , 'upper center' gibi değerler alabilmektedir. Bu parametrenin değerleri şunlardır: best upper right upper left lower left lower right right center left center right lower center upper center center legend fonksiyonunda legend yazıları çeşitli isimli parametrelerle özlleştirilebilmektedir. Bu isimli parametreleri maptlotlib'teki legend fonksiyonun dokümantasyonundan öğrenebilirsiniz. Örneğin yine fontsize parametresi legend yazılarının büyüklüğünü, labelcolor parametresi yazıların renklerini, edgecolor legend kutucuğunun rengini, facecolor kutucuğun zemin rengini belirlemekte kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=14, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Grafiğin içerisine ızgara çizgileri grid isimli fonksiyonla yerleştirilebilir. Böylece sanki grafik bir çeşit grafik kağıdına çizilmiş gibi gözükür. grid fonksiyonun ızgara çizgilerinin biçimini belirleyen çeşitli isimli parametreleri vardır. Bu parametreler dokümanlardan incelenebilir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=14, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ 52. Ders 03/07/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Çizimdeki x ve y eksenlerine isim vermek için xlabel ve ylabel fonksiyonları kullanılmaktadır. Bu fonksiyonlar da gösterilecek yazının biçimleriyle ilgili isimli parametreler almaktadır. Örneğin: color fontsize fontfamily fontstyle fontweight Kullanabileceğiniz diğer isimli parametreler için Matplotlib dokğmanlarına başvurabilirsiniz. Yine fonksiyonların loc parametreleri yazının yerini belirlemekte kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') plt.xlabel('X Değerleri', fontsize=12, color='blue', loc='left') plt.ylabel('Y Değerleri', fontsize=12, color='blue') y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.grid() plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=12, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ x ve y eksenleri için aralık xlim ve ylim fonksiyonlarıyla belirlenebilir. Böyle bir belirleme yapılmadıysa matplotlib bu belirlemeleri kendisi yapmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') plt.xlabel('X Değerleri', fontsize=12, color='blue',) plt.ylabel('Y Değerleri', fontsize=12, color='blue') plt.xlim(-10, 10) plt.ylim(-2, 2) y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.grid() plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=12, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Eksenlerdeki tick'lerin yerlerini belirlemek için xticks ve yticks fonksiyonları kullanılmaktadır. Yine xticks ve yticks fonksiyonlarının tick'lerin biçimini belirleyen isimli parametreleri vardır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') plt.xlabel('X Değerleri', fontsize=12, color='blue',) plt.ylabel('Y Değerleri', fontsize=12, color='blue') plt.xlim((-8, 8)) plt.ylim((-1, 1)) plt.xticks(np.arange(-8, 9, 1)) plt.yticks(np.arange(-1, 1.20, 0.20)) y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.grid() plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=12, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Grafiğin içerisine text isimli fonksiyonla bir yazı yerleştirebiliriz. Bu yazının konumu grafikteki eksen bilgilerine göre ve yazının sol alt köşesi o konumda olacak biçimde belirlenmektedir. Yine text fonksiyonunda aşağıdaki isimli parametreleri kullanabiliriz: color fontsize fontfamily fontstyle fontweight Kullanabileceğiniz diğer isimli parametreler için Matplotlib dokğmanlarına başvurabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') plt.xlabel('X Değerleri', fontsize=12, color='blue',) plt.ylabel('Y Değerleri', fontsize=12, color='blue') plt.xlim((-8, 8)) plt.ylim((-1, 1)) plt.xticks(np.arange(-8, 9, 1), color='red', fontsize=12) plt.yticks(np.arange(-1, 1.20, 0.20), color='blue', fontsize=12) plt.grid() y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.text(0, 0, 'this is a test', color='green', fontsize=14) plt.text(-7.5, 0.65, 'this is another test', color='red', fontsize=14, rotation=15) plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=12, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Çizim alanının içerisine bir ok çizilebilir. Bunun arrow fonksiyonu kullanılır. Bu fonksiyon okun başlangıç ve bitiş koordinatlarını eksen değerleriyle almaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=14, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.xlabel('X', fontsize=14, color='red') plt.ylabel('sin(x)', fontsize=14, color='red') plt.xlim(-7, 7) plt.ylim(-1, 1) plt.xticks(np.arange(-7, 7, 1)) plt.yticks(np.arange(-1, 1.20, 0.20)) plt.text(-5, -0.2, 'This is a test', fontsize=14, color='blue', fontweight='bold', fontstyle='italic', rotation=45) plt.arrow(0, 0, 3, 0.4, head_length=.2, head_width=.1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Grafiği büyütmek için figürün büyütülmesi gerekir. B unun için figure isimli fonksiyon ile yeni bir figür yaratılıp o figürün aktif figür olması sağlanabilir. Yeni figür yaratılırken figure fonksiyonun figsize parametresi inch cinsinden iki elemanlı bir demet biçiminde figürün genişlik ve yüksek değerlerini alabilir. Örneğin: plt.figure(figsize=(20, 10)) facecolor isimli parametresi ile figürün arka plan rengini de değiştirilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.linspace(-6, 6, 100) plt.figure(figsize=(20, 10), facecolor='#FFFFDD') plt.title("Sinus-Kosinüs Grafiği", color='red', fontsize=14, fontfamily='arial', fontstyle='italic', fontweight='bold') plt.xlabel('X Değerleri', fontsize=12, color='blue',) plt.ylabel('Y Değerleri', fontsize=12, color='blue') plt.xlim((-8, 8)) plt.ylim((-1, 1)) plt.xticks(np.arange(-8, 9, 1), color='red', fontsize=12) plt.yticks(np.arange(-1, 1.20, 0.20), color='blue', fontsize=12) plt.grid() y = np.sin(x) plt.plot(x, y) y = np.cos(x) plt.plot(x, y) plt.text(0, 0, 'this is a test', color='green', fontsize=14) plt.text(-7.5, 0.65, 'this is another test', color='red', fontsize=14, rotation=15) plt.arrow(0, 0, -2, 0, width=0.1, facecolor='yellow') plt.legend(['Sinüs', 'Kosinüs'], loc='upper right', fontsize=12, labelcolor='blue', edgecolor='red', facecolor='#FFFFDE', labelspacing=1) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aslında bir figürde tek bir eksen bulunmak zorunda değildir. Birden fazla eksen bulundurulabilir. Böylece farklı grafikler yan yana görüntülenebilir. Bu işlem çeşitli biçimlerde yapılabilmektedir. En basit biçim subplot fonksiyonunu kullanmaktır. Bir figürde birden fazla eksen varsa bunların bir tanesi aktif eksendir. Dolayısıyla çizimler aktif eksene yapılırlar. subplot fonksiyonu yeni bir eksen yaratıp onu aktif hale getirmektedir. sublot fonksiyonununda ilk iki parametre eksenlerin oluşturacağı matrisin boyutlarını belirtir. Üçünü parametre bu matirsteki hangi eksenin aktif hale getirileceğini belirtmektedir. Burada eksenlerin numaraları 1'den başlatılır ve eksenler satır tabanlı olarak (rowwise) numaralandırılmaktadır. Bir eksen aktif hale getirildiğinde artık yukarıda gördüğümüz çizim ile ilgili fonksiyonların hepsi o eksen üzerinde yapılır. Örneğin plt.title fonksiyonu eksen için bir başlık oluşturmaktadır. Dolayısıyla biz istersek her eksene ayrı bir başlık atayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.figure(figsize=(10, 10), facecolor='yellow') x = np.linspace(-10, 10, 1000) plt.subplot(2, 2, 1) plt.title('Birinci Grafik') y = 2 * x + 1 plt.plot(x, y) plt.subplot(2, 2, 2) plt.title('İkinci Grafik') y = x ** 2 - 1 plt.plot(x, y) plt.subplot(2, 2, 3) plt.title('Üçüncü Grafik') y = -x ** 2 - 1 plt.plot(x, y) plt.subplot(2, 2, 4) plt.title('Dördüncü Grafik') y = -x ** 3 - 1 plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Tabii biz eksenleri bir döngü içerisinde aktif hale getirip çizimleri yapabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.figure(figsize=(10, 10), facecolor='yellow') x = np.linspace(-10, 10, 1000) for i in range(1, 10): plt.subplot(3, 3, i) plt.title(f'{i} Numaralı Grafik') y = x ** 2 + i plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aslında subplot fonksiyonun üç parametresi tek parametreli bir int sayı biçiminde de oluşturulabilmektedir. Örneğin: plt.subplot(331) # eşdeğeri plt.subplot(3, 3, 1) #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.figure(figsize=(10, 10), facecolor='yellow') x = np.linspace(-10, 10, 1000) plt.subplot(221) plt.title('Birinci Grafik') y = 2 * x + 1 plt.plot(x, y) plt.subplot(222) plt.title('İkinci Grafik') y = x ** 2 - 1 plt.plot(x, y) plt.subplot(223) plt.title('Üçüncü Grafik') y = -x ** 2 - 1 plt.plot(x, y) plt.subplot(224) plt.title('Dördüncü Grafik') y = -x ** 3 - 1 plt.plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Çizgi grafiği (plot) yapmak için kullanılan plot fonksiyonun pek çok parametresi vardır. Çizginin rengi color parametresiyle ayarlanabilir. Eğer renk belirtilmezse plot her çizim için bir paletten farklı bir rengi otomatik biçimde seçmektedir. linewidth parametresi ile çizgi kalınlığı değiştirilebilmektedir. marker parametresi noktaların nasıl görüntüleneceğini belirtmektedir. Burada 'o' parametresi küçük bir dairesel simge ile noktaların gösterileceği anlamına gelmektedir. Diğer önemli marker sembolleri şunlardır: 'v', 's', '*', 'x'. linestyle parametresi çizginin kesikliliği üzerinde belirleme yapılmasını sağlar. Burada önemli değerler şunlardır: '-', '--', ':', '-.', none. marker'ların büyüklükleri pixel cinsinden markersize parametresiyle ayarlanabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.figure(figsize=(10, 10)) plt.title('Parabol', fontsize=14) x = np.linspace(-10, 10, 20) y = x ** 2 - 4 plt.plot(x, y, color='red', linewidth=1, marker='x', markeredgecolor='blue', linestyle='-.', markersize=10) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Çeşitli olguların çubuklarla temsil edildiği grafiklere "çubuk grafikleri (bar charts)" denilmektedir. Tabii çubuk grafikleri aslında matematiksel grafik çeşidi değildir. Çubuk grafikleri daha çok betimsel istatistikte kullanılmaktadır. Çubuk grafiklerinde çubukların yüksekliği önemlidir. Çubukların genişliklerinin ve alanlarının bir önemi yoktur. Çubukların yükseklikleri olgular arasındaki niceliksel farklılıkları gözle karşılaştırmalı bir biçimde anlamamıza yardımcı olmaktadır. Çubukların genişliklerinin bir önemi yoksa da görüntünün daha güzel gözükmesinde genişlikler katkı sağlayabilmektedir. Çubuk grafikleri bar isimli fonksiyonla oluşturulmaktadır. bar fonksiyonun ilk iki parametresi zorunlu parametrelerdir. Birinci parametre çubukların x eksenindeki orta noktalarını belirten sayılara ilişikin ya da çubukları betimleyen yazılara ilişkin bir liste biçiminde girilebilir. Eğer bu liste string listesi biçimindeyse çubukların orta noktaları 0'dan başlatılır ve birer artırımlı olarak devam ettirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(5, 5)) plt.title('Performans Puanları') plt.bar([0, 1, 3, 4], [10, 5, 20, 4]) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Birinci parametre bir string listesi olarak girildiğinde aslında olguların yüksekliklerine ilişkin bir grafik edilmiş olur ki genellikle çubuk grafikleri böyle kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(5, 5)) plt.title('Performans Puanları') plt.bar(['Ali', 'Veli', 'Selami', 'Ayşe'], [10, 5, 20, 4]) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Çubukların genişlikleri default olarak 0.8'dir. Ancak biz genişlikleri değiştirebiliriz. Tabii genişlikleri küçülttükçe çubuk aralarındaki boşluklar artar, genişlikleri büyüttükçe bu boşluklar azalır. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(5, 5)) plt.title('Performans Puanları') plt.bar(['Ali', 'Veli', 'Selami', 'Ayşe'], [10, 5, 20, 4], width=0.5) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ bar fonksiyonun color parametresine bir renk girilirse tüm çubuklar o renkte görüntülenir. Bu parametreye bir renk dizisi de girilebilir. Böylelikle biz her çubuğun rengini ayrı ayrı ayarlayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.title('Performans Puanları') plt.bar(['Ali', 'Veli', 'Selami', 'Ayşe'], [10, 5, 20, 4], width=0.5, color=['red', 'green', 'blue', 'yellow']) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ bar fonksiyonun edgecolor parametresi çubukların çizgi renklerini, linewidth parametresi çubukların çizgi kalınlıklarını ayarlamakta kullanılmaktadır. fill parametresi False geçilirse çubukların içleri hiç boyanmamaktadır. hatch çubukların içlerindeki dolgu biçimlerini belirlemekte kullanılmaktadır. Bazı dolgu biçimleri şunlardır: '\\' '/', '|', '+', 'o', 'O', .', '*' #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.title('Performans Puanları') plt.bar(['Ali', 'Veli', 'Selami', 'Ayşe'], [10, 5, 20, 4], width=0.5, color=['red', 'green', 'blue', 'yellow'], edgecolor=['black', 'black', 'red', 'black'], linewidth=3, hatch='O') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ bar fonksiyonunun bottom parametresi çubukların tabalarının y eksenine göre konumunu belirlemekte kullanılır. 0 değeri tabanların x eksenine oturduğu anlamına gelmektedir ve default durumdur. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(5, 5)) plt.title('Performans Puanları') plt.ylim(0, 25) plt.bar(['Ali', 'Veli', 'Selami', 'Ayşe'], [10, 5, 20, 4], color=['red', 'blue', 'magenta', 'yellow'], edgecolor='black', linewidth=3, hatch='//', bottom=2) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ bar fonksiyonunun align parametresi 'center' ya da 'edge' biçiminde olabilmektedir. Default durum 'center' biçimindedir. Bu parametre x değerlerinin çubukların ortasında mı yoksa solunda mı görüntüleneceğni belirtmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.title('Performans Puanları') plt.bar(['Ali', 'Veli', 'Selami', 'Ayşe'], [10, 5, 20, 4], width=0.5, color=['red', 'green', 'blue', 'yellow'], edgecolor=['black', 'black', 'red', 'black'], linewidth=3, hatch='O') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Çubukları sola ve sağa yaslamak için yine xlim fonksiyonu ile x eksenindeki limitleri belirlemek gerekebilir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.title('Performans Puanları') plt.xlim((-0.25, 3.25)) plt.bar([0, 1, 2, 3], [10, 5, 20, 4], width=0.5, color=['red', 'green', 'blue', 'yellow'], edgecolor=['black', 'black', 'red', 'black'], linewidth=3, hatch='O', bottom=[1, 2, 3, 4]) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ bar fonksiyonuyla ilgili başka ayrıntı özellikler vardır. Bunlar için Matplotlib dokümanlarına başvurmalısınız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Diğer çok kullanılan grafik türünden biri de "saçılma (scatter)" grafiğidir. Bu grafikte yalnızca noktalar gösterilir. Noktaların dışında başka bir öğe grafikte bulunmaz. Tabii noktalar bir pixel ile değil küçük dairelerle gösterilemktedir. Bu dairenin büyüklüğü ayarlanabilmektedir. Saçılma grafiği istatistik ve veri biliminde çok sık kullanılmaktadır. Bir dağılma ilişkin noktaların gözle kontrol etmek için uygulamacı onların saçılma grafiğini çizmektedir. Saçılma grafiği için scatter isimli fonksiyon kullanılmaktadır. scatter fonksiyonun ilk iki parametresi zorunlu parametrelerdir. Bunlar noktaların x ve y bileşenlerini belirtir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x = np.array([1, 5, 7, 3, 9, 12, 5, 25, 7, 19]) y = np.array([10, 21, 17, 7, 24, 21, 9, 14, 11, 1]) plt.scatter(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Saçılma grafiğindeki noktaların renkleri yine color isimli parametresiyle değiştirilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x = np.array([1, 5, 7, 3, 9, 12, 5, 25, 7, 19]) y = np.array([10, 21, 17, 7, 24, 21, 9, 14, 11, 1]) plt.scatter(x, y, color='red') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aslında her noktanın rengini istediğimiz gibi de ayarlayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x = np.array([1, 5, 7, 3, 9, 12, 5, 25, 7, 19]) y = np.array([10, 21, 17, 7, 24, 21, 9, 14, 11, 1]) plt.scatter(x, y, color=['red', 'green', 'blue', 'blue', 'red', 'green', 'black', 'blue', 'green', 'black']) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aynı eksenin üzerine show işlemi yapmadan birden fazla saçılma grafiği çizdirilebilir. Bu durumda plot fonksiyonunda olduğu gibi renkler özellikle belirtilmemişse her scatter için otomatik atanmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x1 = np.array([1, 5, 7, 3, 9]) y1 = np.array([10, 21, 17, 7, 24]) x2 = np.array([12, 5, 25, 7, 19]) y2 = np.array([21, 9, 14, 11, 1]) plt.scatter(x1, y1) plt.scatter(x2, y2) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Bazen farklı kategorik değerlerin farklı renklerle saçılma grafiğinin çizilmesi gerekebilmektedir. Örneğin aşağıdaki gibi bir 'scatter.csv' dosyası olsun: kilo,boy,cinsiyet 72,155,erkek 87.3,172,erkek 56.5,167,kadın 47.3,171,kadın 67.4,178,kadın 56.7,174,erkek 72.3,169,erkek 78.5,181,kadın 57.4,164,kadın 91.6,185,erkek Burada yatay eksende "kilo", düşey eksende "boy" özellikleri temsil edilsin. Biz de erkeklerle kadınların noktalarını farklı renklerle göstermek isteyelim. Burada bizim cinsiyete göre filtrelemeler yapmamız gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import pandas as pd df = pd.read_csv('scatter.csv') plt.title('Boy/Kilo-Cinsiyet Saçılma Grafiği') plt.xlabel('Kilo', fontsize=14) plt.ylabel('Boy', fontsize=14) plt.scatter(df[df['cinsiyet'] == 'kadın']['boy'], df[df['cinsiyet'] == 'kadın']['kilo'], color='red') plt.scatter(df[df['cinsiyet'] == 'erkek']['boy'], df[df['cinsiyet'] == 'erkek']['kilo'], color='blue') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki örneği numpy kullanarak da aşağıdakine benzer biçimde yapabilirdik. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import numpy as np data = np.loadtxt('scatter.csv', delimiter=',', converters={2: lambda s: 0 if s == 'kadın' else 1}, skiprows=1, encoding='utf-8') plt.title('Boy/Kilo-Cinsiyet Saçılma Grafiği') plt.xlabel('Kilo', fontsize=14) plt.ylabel('Boy', fontsize=14) plt.scatter(data[data[:, 2] == 0, 0], data[data[:, 2] == 0, 1], color='red') plt.scatter(data[data[:, 2] == 1, 0], data[data[:, 2] == 1, 1], color='blue') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ scatter fonksiyonun s isimli parametresi noktalar için çizilen dairenin büyüklüğünü ayarlamak için kullanılmaktadır. Bu değer büyütülürse daireler de büyüyecektir. Yine aslında noktalar için daire yerine marker parametresiyle başka sembollerin de kullanılması sağlanabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x = np.array([1, 5, 7, 3, 9, 12, 5, 25, 7, 19]) y = np.array([10, 21, 17, 7, 24, 21, 9, 14, 11, 1]) plt.scatter(x, y, s=150, color='blue', marker='v') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Grafikteki marker'ın iç rengi facecolor isimli parametresiyle çizgi kalınlığı linewidth parametresiyle belirlenebilnektedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x = np.array([1, 5, 7, 3, 9, 12, 5, 25, 7, 19]) y = np.array([10, 21, 17, 7, 24, 21, 9, 14, 11, 1]) plt.scatter(x, y, s=150, color='blue', linewidth=3, facecolor='yellow') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Bazen scatter grafiğinde bazı noktalar da bireysel biçimde belirtilmek istenebilir. Tem bir noktayı çizmek için scatter yerine plot fonksiyonunu da kullanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt plt.title('Scatter Graphics') x = np.array([1, 5, 7, 3, 9, 12, 5, 25, 7, 19]) y = np.array([10, 21, 17, 7, 24, 21, 9, 14, 11, 1]) plt.scatter(x, y, s=150, color='blue', linewidth=3, facecolor='yellow') plt.plot(10, 10, marker='x', markersize=14, color='red') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ scatter fonksiyonunun başka ayrıntı parametreleri de vardır. Bunun için Matplotlib dokğmanlarına başvurabiliriniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pasta dilimi grafiği çeşitli olguların bütün içerisindeki oranlarını görsel bir biçimde ifade etmek için kullanılmaktadır. Matplotlib kütüphanesinde pie isimli fonksiyonlar çizdirilmektedir. pie fonksiyonun zorunlu birinci x parametresi pasta dilimi olarak gösterilecek değerleri belirtmektedir. Fonksiyon kendisi bu değerlerden hareketle orantı kullanarak pasta dilimlerinin büyüklerini belirlemektedir. Programcı isterse fonksiyonun labels parametresi yoluyla her bir pasta diliminin anlamı için br yazının gösterilmesini sağlayabilir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Default durumda pasta dilimlerinin renkleri fonksiyonun kendisi tarafından ayarlanmaktadır. Ancak fonksiyonun colors parametresiyle her pasta diliminin rengini biz ayrı ayrı ayarlayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt plt.title('Pie Chart') plt.pie([40, 10, 50, 70, 18], labels=['XPR', 'BTC', 'TRX', 'MINA', 'IOTA'], colors=['red', 'green', 'blue', 'magenta', 'yellow'], fontsize=14) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Belli bir pasta diliminin ana pastadan kopuk biçimde gösterilmesi sık karşılaşılan bir biçimdir. Bunun için fonksiyonun explode parametresi kullanılmaktadır. Bu parametreye pasta dilimi sayısı kadar değer içeren dolaşılabilir bir nesne girilir. İlgili pasta diliminin explode değeri 0 ise bu pasta dilimi kopu değildir. explode değeri yükseldikte pastanın yarıçapı ile oranlı bir biçimde pasta dilimi kopuk gösterilmektedir. Genellikle kopukluk için 0.1 gibi 0.2 gibi değerler dilimi güzel gösteren değerlerdir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] explodes = [0, 0.1, 0, 0, 0.1] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names, explode=explodes) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ pie fonksiyonun autopct parametresi C'nin printf fonksiyonundaki gibi yer tutucu yazısını alır. Yer tutucular % karakteri ile başlatılırlar. (Gerçekten % karakteri %% ile belirtilir). Örneğin %.2f gibi bir yazı pasta dilimlerinin yüzdesini noktadan sonra iki basamak olarak göstermek için kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] explodes = [0, 0.1, 0, 0, 0.2] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names, explode=explodes, autopct='%%%.2f') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Programcı isterse pasta dilimlerinin renklerini kendisi colors parametresiyle kendisi de belirleyebilir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] explodes = [0, 0.1, 0, 0, 0.2] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names, explode=explodes, autopct='%%%.2f', colors=['red', 'green', 'blue', 'magenta', 'gray']) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Default durumda pasta dilimleri ilk belirtilen dilim 0 derecede olacak biçimde saat yönünün ters yönünde konumlandırılır. Ancak ilk dilimin başlangıç açısı startangle parametresi ile değiştirilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] explodes = [0, 0.1, 0, 0, 0] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names, explode=explodes, autopct='%%%.2f', startangle=90) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Pasta dilimlerindeki yazıların birtakım özellikleri textprops isimli parametresiyle değiştirilebilmektedir. Bu parametre bir sözlük biçiminde girilir. Sözlüğün anahtarları text fonksiyonundaki yazı özelliklerini belirten parametre isimlerinin yazılarından, değerleri ise onların değerlerinden oluşmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] explodes = [0, 0.1, 0, 0, 0] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names, explode=explodes, autopct='%%%.2f', startangle=90, textprops={'fontsize': 14, 'color': 'blue', 'fontweight': 'bold'}) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ shadow parametresi dilimleri hafif gölgeli göstermektedir. Özellikle expolde edilmiş dilimlerde gölgeli gösterim önemli olabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21] names = ['Ali', 'Veli', 'Selami', 'Ayşe', 'Fatma'] explodes = [0, 0.1, 0, 0, 0] plt.figure(figsize=(8, 8)) plt.title('Pie Chart', fontsize=14, fontweight='bold') plt.pie(x, labels=names, explode=explodes, autopct='%%%.2f', startangle=90, textprops={'fontsize': 14, 'color': 'blue', 'fontweight': 'bold'}, shadow=True) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ En çok kullanılan grafiklerden biri de "histogram" denilen grafiktir. İstatistikte, veri biliminde ve makine öğrenmesinde histogram oldukça sık kullanılmaktadır. Histogram bir sıklık grafiğidir. Genellikle kişiler tarafından "çubuk grafiği (bar chart)" ile karıştırılmaktadır. Çubuk grafiğinde çubuklar bir aralık belirtmezler. Bir olgu belirtirler. Ancak histogramda çubuklar bir aralıktaki değerlerin kaç tane olduğunu yani o aralıktaki değerlerin sıklığını göstermek için kullanılmaktadır. Belli aralıklardaki sıklıklar pek alanda önemli olabilmektedir. Histogramda çubukların yükseklikleri sıklık değerini, genişlikleri de aralığı belirtmektedir. Genellikle çubuklar aralarında boşluk olmadan yan yana gösterilirler. Histogramın çubuk sayısı ayarlanabilmektedir. Örneğin 0 ile 100 arasında 1000 tane değer olsun. Biz bu 1000 değerin histogramını çizerken çubuk sayısını 10'da tutarsak aralıklar da 10 tane olur. Ancak çubuk sayısını 20 yaparsak aralıklar beşer beşer gider. Histogramdaki çubuklara İngilizce "bin" de denilmektedir. Histogram çizmek için hist isimli fonksiyon kullanılmaktadır. Fonksiyonun tek bir zorunlu parametresi vardır. O da x değerlerini almaktadır. Fonksiyonda default çubuk sayısı 10'dur. Ancak biz bins parametreyle bu çubuk sayısını değiştirebiliriz. Fonksiyon bizim x parametresi olarak girdiğimiz değerlerdeki en büyük ve en küçük değeri tespit eder. O iki değer arasını çubuk sayısına bölerek aralıkları oluşturur. Sonra o aralıklarda kaç değer varsa çubukların yüksekliğini ona göre ayarlar. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt x = [34, 65, 98, 67, 21, 54, 21, 17, 14, 5, 9, 32, 21, 72, 93, 42, 53, 71, 29, 93, 47, 79, 39, 62, 77, 86, 88] plt.title('Histogram', fontsize=14, fontweight='bold') plt.hist(x) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Örneğin biz numpy.random.normal fonksiyonuyla normal dağılıma uygun n tane rastgele sayı üretebiliriz. Sonra bunların histogramını çizdiğimizde çan eğrisine benzer bir görüntüyle karşılaşırız. Gerçekten de doğada gördüğümüz pek olgu aslında sıklık bakımından "normal dağılım" denilen çan eğrisine benzemektedir. Çan eğrisi ortalama etrafında çok fazla değerin toplandığı ortalamadan iki yönlü uzaklaşıldığında değerlerin azaldığı bir görünümdedir. Örneğin boy, zeka, kilo gibi pek çok özellik normal dağılma eğilimindedir. Yani kişilerin bu özellikleri ortalama etrafında çan eğrisinde olduğu gibi yayılmış durumdadır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.random.normal(0, 1, 10000) plt.title('Histogram', fontsize=14, fontweight='bold') plt.hist(x, bins=20) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ hist fonksiyonun range parametresi anormal değerlerin (outliers) atılması için kullanılmaktadır. Bu parametre ikili bir demet olarak girilir. Fonksiyon bu ikili demetin için de kalan değerleri dikkate almaktadır. Yine color parametresi çubukların renklerini ayarlamak için kullanılabilmektedir. orientation parametresi default 'vertical' biçimdedir. Bu parametre 'horizontal' girilirse grafik yana yatmış şekilde görüntülenir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt x = np.random.normal(0, 1, 1000) plt.figure(figsize=(8, 8)) plt.title('Histogram', fontsize=14, fontweight='bold') plt.hist(x, bins=20, range=(-5, 5), orientation='horizontal', color='red') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ hist fonksiyonun burada ele aldığımız özelliklerden daha fazla özellikleri vardır. Bu ayrıntılar için Matplotlib kütüphanesinin dokümantasyonuna başvurabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Biz şimdiye kadar Matplotlib kütüphanesini prosedürel biçimde kullandık. Yani bağımsız fonksiyonları çağırarak grafikleri çizdik. Aslında bu kütüphane "nesne yönelimli" bir biçimde de yani sınıflar ve onların metotları yoluyla da kullanılabilmektedir. Matplotlib kütüphanesinde figür gibi eksen gibi öğeler birer sınıfla temsil edilmiştir. Biz de bu sınıflar türündne nesnelerle bu sınıfların metotlarını çağırarak da çizimler yapabiliriz. Genel olarak global fonksiyonlardaki xxx isminin metot karşılığı set_xxx olarak verilmiştir. Tabii prosedürel teknikle nesne yönelimli teknik birlikte de kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Nesne yönelimli kullanımda ilk yapılacak şey figüe ve eksen nesnelerinin elde edilmesidir. Prosedürel kullanımda zaten bizim için default bir Axis ve Figure nesnesi yaratılmış durumdadır. Ancak istersek biz subplots fonksiyonu ile ayrı bir figüre nesnesi ve Axis nesnesi oluşturabiliriz. subplots fonksiyonu default argümanlarla çağrılırsa bize ikili bir demet biçiminde bir tane figüre ve onun içerisinde bir tane eksen nesnesi verilir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt fig, ax = plt.subplots() #------------------------------------------------------------------------------------------------------------------------------------ Ancak subplots fonksiyonu belli bir sayıda eksen oluşturacak biçimde de çağrılabilir. Bu durumda eksen nesneleri bir Numpy dizisi olarak verilecektir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt fig, ax = plt.subplots(3, 3) #------------------------------------------------------------------------------------------------------------------------------------ subplots fonksiyonundan elde ettiğimiz Figure ve Axis nesneleri ile biz artık bu sınıfın metotlarını çağırarak işlemler yapabiliriz. Örneğin Figure nesnesini büyütmek için set_size_inches metodu kullanılmaktadır. Ya da örneğin figürün zemin rengini değiştirmek için Figure sııfının set_facecolor metodu kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import matplotlib.pyplot as plt fig, ax = plt.subplots(3, 3) fig.set_size_inches(8, 8) fig.set_facecolor('yellow') #------------------------------------------------------------------------------------------------------------------------------------ Grafiğe ilişkin önemli unsurların hepsi Axis sınıfının metotları ile oluşturulmaktadır. Örneğin olot, scatter, hist, bar gibi fonksiyonlar nesne yönelimli kullanımda Axis sınıfının metotları durumundadır. Örneğin legend da aslında Axis sınıfının bir metoduyla çıkartılabilmektedir. Çizim sonrasınd ayine show işlemi fonksiyon yoluyla yapılmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt fig, ax = plt.subplots() fig.set_size_inches(10, 8) fig.set_facecolor('yellow') ax.set_title('Sinus / Cosinus Curve', pad=25) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_xlim((-5, 5)) ax.set_xticks(np.arange(-5, 5, 0.5)) x = np.linspace(-5, 5, 100) y = np.sin(x) ax.plot(x, y) y = np.cos(x) ax.plot(x, y) ax.legend(['Sinus', 'Cosinus']) ax.text(0, 0, 'This is a test') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ subplots fonksiyonunda birden fazla eksen yaratlırsa, artık demetin ikinci elemanı bir Numpy dizisi olur. Aşağıdaki örnekte (2, 2)'lik toplam 4 eksen yaratılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt fig, ax = plt.subplots(2, 2) fig.set_size_inches(10, 8) fig.set_facecolor('yellow') x = np.linspace(-6, 6, 1000) ax[0, 0].set_title('Sinus') y = np.sin(x) ax[0, 0].plot(x, y) ax[0, 1].set_title('Cosinus') y = np.cos(x) ax[0, 1].plot(x, y) ax[1, 0].set_title('Tangent') y = np.tan(x) ax[1, 0].plot(x, y) ax[1, 1].set_title('Parabola') y = x ** 2 ax[1, 1].plot(x, y) plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aslında işin başında default bir Figure nesnesi ve Axis nesnesi zaten yaratılmış durumdadır. Biz plt.subplots ile ueni bir figür ve eksen nesneleri yarattık. Default yaparılmış olan figür nesnesini elde etmek için gcf (get current figure) ve default eksen nesnesini elde etmek için gca (get current axis) fonksiyonları kullanılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt fig = plt.gcf() ax = plt.gca() fig.set_size_inches(10, 8) fig.set_facecolor('yellow') ax.set_title('Sinus / Cosinus Curve', pad=25) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_xlim((-5, 5)) ax.set_xticks(np.arange(-5, 5, 0.5)) x = np.linspace(-5, 5, 100) y = np.sin(x) ax.plot(x, y) y = np.cos(x) ax.plot(x, y) ax.legend(['Sinus', 'Cosinus']) ax.text(0, 0, 'This is a test') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Aslında global fonksiyonlar her zaman o andaki aktif figür ve eksen üzerinde işlemler yaparlar. Bu nedenle biz nesne yönelimi ve prosedürel biçimleri bir arada kullanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt fig = plt.gcf() ax = plt.gca() fig.set_size_inches(10, 8) fig.set_facecolor('yellow') ax.set_title('Sinus / Cosinus Curve', pad=25) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_xlim((-5, 5)) ax.set_xticks(np.arange(-5, 5, 0.5)) x = np.linspace(-5, 5, 100) y = np.sin(x) plt.plot(x, y) y = np.cos(x) ax.plot(x, y) plt.legend(['Sinus', 'Cosinus']) plt.text(0, 0, 'This is a test') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Matplotlib aslında ayrıntıları olan bir kütüphanedir. Ayrıntıları gerektikçe öğrenebilirsiniz. Örneğin eksenleri ortalama, eğrinin altındaki alanı boyamak gibi pek çok ayrıntı işlem yapılabilmektedir. Yapmak istediğiniz şeye ilişkin muhtemelen Matplotlib içerisinde bir fonksiyon ya da metot bulunuyor olacaktır. Bu konuda Internette arama yaparak sonuca daha kolay ulaşabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ import numpy as np import matplotlib.pyplot as plt from scipy.stats import norm fig = plt.gcf() ax = plt.gca() fig.set_size_inches((10, 8)) x = np.linspace(-5, 5, 1000) y = norm.pdf(x) ax.set_ylim((-0.5, 0.5)) ax.spines['left'].set_position('center') ax.spines['bottom'].set_position('center') ax.spines['top'].set_color(None) ax.spines['right'].set_color(None) plt.plot(x, y) x = np.linspace(-5, 2) y = norm.pdf(x) plt.fill_between(x, y, color='gray') plt.show() #------------------------------------------------------------------------------------------------------------------------------------ Thread'ler bir programın (prosesin) ayrı bir biçimde çizelgelenen farklı akışlarıdır. Bir program (proses) çalışmaya tek bir thread'le başlar. Buna prograın (prosesin) ana thread'i (main thread) denilmektedir. Diğer thread'ler daha sonra programcı tarafından yaratılmaktadır. Thread'ler aynı program üzerinde ilerleyen farklı akışlara denilmektedir. Thread'ler işletim sistemlerine 90'lı yıllarda sokulmuştur. Thread kullanımının mümkün olduğu işletim sistemlerine "çok thread'li (multi-threaded)" işletim sistemleri denilmektedir. Windows, Linux, macOS thread kullanımının mümkün olduğu "çok thread'li (multi-threaded)" işletim sistemleridir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Modern çok prosesli ve çok thread'li işletim sistemlerinde programlar "zaman paylaşımlı (time sharing)" bir biçimde çalıştırılmaktadır. İşletim sistemi thread'leri bir kuyrukta tutar. Bu kuyruğua genellikle "çalışma kuyruğu (run queue)" denilmektedir. Sonra kuyruktan bir thread'i alır. Onu CPU'ya atar. O thread'in belli bir süre çalışmasını sağlar. Sonra o süre dolduğunda thread'in çalışmasına ara verir. Sıraki thread'i CPU'ya atar ve çalışma "biraz onu biraz bunu" biçiminde devam ettirilir. Kullanıcılar programların aynı anda çalıştığını sanırlar. Ancak aslında programlar bu biçimde kesikli kesikli çalışmaktadır. Bir thread'in CPU'ya atanıp parçalı bir biçimde çalıştırılması süresine "quanta süresi (time quantum)" denilmektedir. Quanta süresini bitiren (ya da bloke olan) bir thread'in CPU'dan koparılarak sıradaki thread'in CPU'ya atanması sürecine "theradler arası geçiş (context switch)" ya da "görev geçişi (task switch)" denilmektedir. Quanta süreleri işletim sistemi taarafından uygun biçimde belirlenmektedir. Birden fazla CPU'nun ya da çekirdeğin bulunduğu durumda zaman paylaşımlı çalışma benzer biçimde yürütülmektedir. Genellikle işletim sistemleri her CPU ya da çekirdek için ayrı bir çalışma kuyruğu oluştururlar. Böylece toplamda birim zamanda yapılan iş miktarı da artırılmış olur. Bu durumu süpermarketlerdeki birden fazla kasanın bulunmasına benzetebiliriz. Bazı işletim sistemleri tek bir çalışma kuyruğu oluşturup bir CPU ya da çekirdekte thread'ler arası geçiş olacağı zaman o tek kuyruktan çizelgeleme yapmaktadır. Bu tarzda kuyruklar yine gündelik hayatta karşımıza çıkabilmektedir. İşletim sistemlerinde thread'leri CPU kuyruklarına yerleştirilmesi, CPU'ya atanması, thread'ler arası geçiş (context switch) işlemlerini yapan alt sisteme "çizelgeleyici (scheduler)" denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 54. Ders 10/07/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir işin birden fazla akışa yaptırılması ile ilgili çeşitli terimler kullanılmaktadır. Bazen bu terimler yanlış anlaşılmaktadır. Aşağıda bu terimlerin açıklamalarını yapmak istiyoruz: 1) Concurrent Computing / Concurrency: Bir işin birden fazla akışa yaptırılmasına yönelik en genel terimdir. Bu bir şemsiye terim olarak düşünülebilir. 2) Distributed Computing: Bir işin ağ içerisindeki birden fazla bilgisayar tarafından yapılmasına denilmektedir. Burada vurgulanan şey işin bilgisayarlara bölünüp eşzamanlı biçimde yaptırılmasıdır. 3) Parallel Programming (Paralel Programlama): Bir işin aynı makinedeki CPU yada çekirdeklere dağıtılarak eş zamanlı bir biçimde yaptırılma çabasına denilmektedir. Burada vurgulanan şey işin aynı makinede farklı CPU ya da çekirdeklere eş zamanlı yaptırılmasıdır. 4) Multi-Threading Programming (Çok Thread'li Programlama): Bit işin thread'ler yoluyla aynı makinede birden fazla akışa yaptırılmasına ilişkin çabalara çok thread'li programlama denilmektedir. Thread'ler paralel programlama yapabilmek için de gereken unsurlardır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi bir programda birden fazla akışın yani thread'in bulunmasının nasıl bir faydası vardır? Başka bir deyişle thread'lere neden gereksinim duyulmaktadır? Thread'lerin kullanılma nedenlerini maddeler halinde şöyle açıklayabiliriz: 1) Thread'ler arka plan periyodik işlemlerin yapılabilmesi için iyi bir araç oluşturmaktadır. Örneğin bir program çalışırken aynı zamanda saati ekranın bir yerine basacak olsun. Tek bir akışla bu işlemi yapmak çok zordur. Çünkü ana akış input bir fonksiyona girdiğinde artık saati basma imkanı kalmaz. Ana akışın input gibi bir fonksiyona girmese bile hem bir işi yaparken periyok başka bir işlemin yapılması çok zordur. Thread'li sistemlerde bir thread yaratılır ve arka plan periyodik işlemler bu thread'e havale edilir. Böylece programın aka akışı ilerlemeye devam eder. Ondan bağımsız bir biçimde yaratılan thread periyodik işlemleri yapar. Thread'ler biribinden bağımız olarak sanki ayrı programlarmış gibi çalışmaktadır. Bir thread'in bir noktada bekelemsi (teknik olarak "bloke olması") diğer thread'lerin çalışmasını engellemektedir. 2) Thread'ler bir işi hızlandırmak için de kullanılmaktadır. Bir işi tek bir akışa yaptırmak yerine biren fazla akışa yaptırabilirsek o işin daha hızlı bitirilmesini saplayabiliriz. 3) Thread'ler "paralel programlama (parallel programming)" ortamı oluşturmak için de kullanılmaktadır. 4) GUI programlama modelinde bir mesaj oluştuğunda bir işlem yapmak istediğimizde o işlemi uzatmamalıyız. Aksi takdirde mesaj döngüsü işletilmemiş olur ve program donmuş gibi bir etki oluşur. Bu tür durumlarda bir mesaj oluştuğunda uzun süren bir işlem yapılacaksa bir thread yaratılır, uzun sürecek işlem thread'e havale edilir. #------------------------------------------------------------------------------------------------------------------------------------ İşletim sistemlerinde çalışmakta olan programlara "proses (process)" denilmektedir. Bir proses tek bir thread'le çalışmaya başlar. Bu thread'e prosesin "ana thread'i (main thread)" denilmektedir. Diğer thread'ler programcı tarafından yaratoılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da thread işlemleri için standart kütüphanedeki threading modülü kullanılmaktadır. Thread yaratmak için modülü içerisindeki Thread sınıfı türünden bir nesne yaratılır. Bu nesne yaratılırken "target" paraemtresine thread akışının başlatılacağı fonksiyon geçilir. Ancak thread akışı thead nesnesi ile start metodunun çağrılmasıyla başlatılır. (time modülünde sleep fonksiyonu hangi thread akışı tarafından çağrılmışsa yalnızca o thread'i bekletmektedir. ) Thread sınıfının __init__ metodunun target parametresi birinci parametre değildir. Bu nedenle bu parametreyi isimli kullanmalısınız. Örneğin: thread = threading.Thread(target=thread_proc) thread.start() #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def thread_proc(): for i in range(10): print('other thread') time.sleep(1) thread = threading.Thread(target=thread_proc) thread.start() for i in range(10): print('main thread') time.sleep(1) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki örnekte biz thread akışının başlatılacağı fonksiyonu yukarıda yazmak zorunda kaldık. Böyle bir zorunluluğu ortadan kaldırmak için thread'i yaratan kodu da başka bir fonksiyon içerisine alabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def main(): thread = threading.Thread(target=thread_proc) thread.start() for i in range(10): print('main thread') time.sleep(1) def thread_proc(): for i in range(10): print('other thread') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ İşletim sistemlerinde genel olarak thread'ler arasında altlık-üstlük ilişkisi yoktur. Bir thread herhangi bir thread akışı içerisinde yaratılabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Thread çalışmaya başladığında thread fonksiyonuna parametre de aktarılabilmektedir. Bunun için Thread nesnesi yaratılırken Thread sınıfının __init__ metodunda args parametresi kullanılır. args her zaman bir demet olarak girilmektedir. Bu demetteki değerler sırasıyla thread fonksiyonunun parametresine argüman olarak geçirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import time import threading def main(): thread = threading.Thread(target=thread_proc, args=('other thread', )) thread.start() for i in range(10): print(f'Main thread: {i}') time.sleep(1) def thread_proc(name): for i in range(10): print(f'{name}: {i}') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte thread'in başlangıç fonksiyonuna iki parametre geçirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import time import threading def main(): thread = threading.Thread(target=thread_proc, args=('other thread', 8)) thread.start() for i in range(10): print(f'Main thread: {i}') time.sleep(1) def thread_proc(name, count): for i in range(count): print(f'{name}: {i}') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Thread fonksiyonuna belirtilerek de argüman geçilebilir. Bunun için Thread sınıfının kwargs parametresi kullanılmaktadır. Bu parametreye bir sözlük nesnesi girilmelidir. Sözlüğün anahtarları parametre değişkenlerinin isimlerinden değerleri de ona aktarılacak değerlerden oluşmalıdır. Tabii bu durumda thread fonksiyonunun sözlükte belirtilen isimli parametrelere sahip olması gerekir. Yani args demeti argümanları sırasıyla, kwargs sözlüğü ise isimli olarak aktarmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import time import threading def main(): thread = threading.Thread(target=thread_proc, args=('other thread', ), kwargs={'start': 4, 'step': 1, 'stop': 20}) thread.start() for i in range(10): print(f'Main thread: {i}') time.sleep(1) def thread_proc(name, start, stop, step): for i in range(start, stop, step): print(f'{name}: {i}') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi bir thread ne zaman sonlanmaktadır? Bir thread'in en doğal sonlanması thread fonksiyonunun sona ermesiyle olmaktadır. Bu durum zaten en fazla tavsiye edilen sonlanma biçimidir. Bir thread'in sonlanıp sonlanmadığını anlayabilmek için Thread sınıfının is_alive metodu kullanılmaktadır. Aşağıdaki programda önce bu metot çağrıldığında thread sonlanmamış olacaktır. Ancak daha sonra çağrıldığında thread sonlanmış olacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def main(): thread = threading.Thread(target=thread_proc) thread.start() print('Thread Sonlanmadı' if thread.is_alive() else 'Thread sonlandı') time.sleep(10) print('Thread Sonlanmadı' if thread.is_alive() else 'Thread sonlandı') def thread_proc(): time.sleep(5) main() #------------------------------------------------------------------------------------------------------------------------------------ Maalesef Python'da thread kendi akışı tarafından sonlandırılamamaktadır. (Örneğin diğer dillerdeki kütüphanelerde thread'in kendini sonlandırması için exit benzeri fonksiyon bulunmaktadır.) Ancak bir thread'te exception oluşursa ve ele alınmazsa tüm program değil yalnızca o thread yok edilmektedir. O halde kendi thread akışımızı exception yoluyla sonlandırabiliriz. Biz sonlandırmak istediğimiz yerde bir exception oluştururuz. Bunu thread fonksiyonunda yakalayıp thread fonksiyonun bitmesini sağlarız. Aşağıdaki örnekte thread'i akışı bir exception ile sonlandırılmak istenmiştir. Bu örnekte exception'ın yakaalandığına ancak bir şey yapılmadığına dikkat ediniz. Exception'ı yakalamazsak tüm program değil yalnızca exception'ın oluştuğu thread yok edilecektir. Ancak exception yakalanmadığından dolayı ekranda arzu etmediğimiz şeyler görünecektir. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def main(): thread = threading.Thread(target=thread_proc) thread.start() for i in range(10): print(f'Main thread: {i}') time.sleep(1) def thread_proc(): try: foo() except: pass def foo(): for i in range(10): time.sleep(1) print(f'Other Thread: {i}') if i == 5: raise Exception main() #------------------------------------------------------------------------------------------------------------------------------------ Bir thread'i sonlandırmanın diğer bir yolu da flag değişkeni kullanmaktır. Thread bir flag değişkenine arada bir bakarak işlemini yürütebilir. Thread'i sonlandırmak için bu flag değişkeni set edilebilir. Tabii bu modeli uygulamak her zaman mümkün değildir. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time flag = True def main(): global flag thread = threading.Thread(target=thread_proc) thread.start() for i in range(10): print(f'Main thread: {i}') time.sleep(1) if i == 5: flag = False def thread_proc(): while flag: print('Other Thread') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi fonksiyonların yerel değişkenleri fonksiyon o yerel değişkenin yaratıldığı noktaya geldiğinde yaratılmakta akış fonksiyondan çıktığında yok edilmektedir. Bu mekanizma stack denilen bir kavramla sağlanmaktadır. Yerel değişkenler stack'te yaratılırlar. Ancak çok thread'li sistemlerde thread'lerin stack'leri birbirinden ayrılmıştır. Birden fazla thread aynı fonksiyon üzerinde ilerliyor olsa bile her thread o yerel değişkenlerin kendine özgü farklı bir kopyasını kullanıyor olmaktadır. Böylece farklı thread'ler aynı fonksiyon üzerinde ilerlese bile yerel değişkenleri bozmazlar. Aşağıdaki programda hem ana thread hem de yeni yaratılan thread foo fonksiyonu üzerinde ilerlemektedir. Ancak bu thread'ler foo içerisindeki i değişkenin farklı kopyalarını kullanıyor durumdadırlar. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def main(): thread = threading.Thread(target=thread_proc) thread.start() foo('Main Thread') def thread_proc(): foo('Other Thread') def foo(s): i = 0 while i < 10: print(f'{s}: {i}') time.sleep(1) i += 1 main() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda Qt'de bir mesaj oluştuğunda uzun bir işlemin thread'e devredilmesine bir örnek verilmiştir. Burada Ok düğmesine basıldığında thread yaratılıp uzun süren işlem başlatılılmakta Cancel düğmesine basıldığında ise yaratılan thread sonlandırılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import sys import threading from PyQt5.QtWidgets import * class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Signal/SLot Mechanism') self.pushButtonOk = QPushButton('Ok', self) self.pushButtonOk.setGeometry(10, 10, 100, 100) self.pushButtonOk.clicked.connect(self.pushButtonOkHandler) self.pushButtonCancel = QPushButton('Cancel', self) self.pushButtonCancel.setGeometry(120, 10, 100, 100) self.pushButtonCancel.clicked.connect(self.pushButtonCancelHandler) def pushButtonOkHandler(self): self.flag = 0 self.thread = threading.Thread(target=self.do_something) self.thread.start() def pushButtonCancelHandler(self): self.flag = 1 def do_something(self): print('Thread yaratıldı ve işleme başladı') for i in range(1000000000): if (self.flag): break print('Thread sonlanıyor') app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() app.exec() #------------------------------------------------------------------------------------------------------------------------------------ Ancak global değişkenlerin tek kopyası bulunmaktadır. Yani iki thread aynı global değişkeni görmektedir. Bir thread global bir değişkeni değiştirirse diğer thread onu değişmiş olarak görür. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time i = 0 def main(): thread = threading.Thread(target=thread_proc) thread.start() foo('Main Thread') def thread_proc(): foo('Other Thread') def foo(s): global i while i < 10: print(f'{s}: {i}') time.sleep(1) i += 1 main() #------------------------------------------------------------------------------------------------------------------------------------ Bir thread oluşturmanın diğer bir yolu threading.Thread sınıfından türetme yapmaktır. Bu sınıftan türetme yapılırsa threading.Thread sınıfından gelen start metodu çağrıldığında run isimli metot çalıştırılır. Çünkü aslında Thread start metodu run yeni bir thread aışı yaratıp run metodunu çağırmaktadır. Bizim Thread sınıfının target parametresiyle belirttiğimiz fonksiyonu de aslında bu run metodu tarafından çağrılmaktadır. Dolayısyla biz Thread sınıfından sınıf türetip run metodunu yazarsak start metodu artık bizim run metodumuzu çağıracaktır. Tabii bu durumda Thread sınıfının target parametresinin bir etkisi kalmayacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time class MyThread(threading.Thread): def __init__(self): super().__init__() def run(self): for i in range(10): print(f'MyThread: {i}') time.sleep(1) def main(): mt = MyThread() mt.start() for i in range(10): print(f'Main Thread: {i}') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte Thread sınıfından türetmiş olduğumuz MyThread sınıfı target parametresini Thread sınıfının __init__ metoduna geçirmektedir. Ancak artık target parametresi ile belirtilen fonksiyon çağrılmayacaktır. Çünkü target parametresiyle belirtilen fonksiyonu aslında Thread sınıfının run metodu çağırmaktadır. Halbuki aşağıdaki örnekte Thread sınıfının run metodu değil MyThread sınıfının run metodu çalıştırılmaktadır #------------------------------------------------------------------------------------------------------------------------------------ import threading import time class MyThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def run(self): for i in range(10): print(f'Mythread.run: {i}') time.sleep(1) def main(): mt = MyThread(target=thread_proc) mt.start() for i in range(10): print(f'Main Thread: {i}') time.sleep(1) def thread_proc(): for i in range(10): print(f'thread_proc: {i}') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Tabii türetme yapıp yine target parametresi yoluyla belirlediğimiz bir fonksiyonun thread fonksiyonu olmasını sağlayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time class MyThread(threading.Thread): def __init__(self, text, **kwargs): super().__init__(**kwargs) self.text = text def thread_proc(): for i in range(10): print('Other thread') time.sleep(1) mt = MyThread('Other thread', target=thread_proc) mt.start() for i in range(10): print('Main thread') time.sleep(1) #------------------------------------------------------------------------------------------------------------------------------------ 55. Ders 12/07/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Biz kendi sınıfımızdaki run metodunda da super fonksiyonu ile taban sınıfın run metodunu çağırabiliriz. Bu durumda target parametresi ile belirtilen fonksiyonu Thread sınıfının run metodu çağırdığına göre yine target parametresi ile belirtilen fonksiyon çağrılmış olacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import threading class MyThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def run(self): print('thread started') super().run() def main(): mt = MyThread(target=foo) mt.start() def foo(): print('foo') main() #------------------------------------------------------------------------------------------------------------------------------------ Thread sınıfının start metodunun nasıl run metodunu çağırdığı ve bunun nasıl override edildiği aşağıdaki örnekle anlaşılabilir. #------------------------------------------------------------------------------------------------------------------------------------ class Thread: def __init__(self, target=None): self.target = target def start(self): self.run() def run(self): print('Thread.run running...') # new thread calls target if self.target: self.target() class MyThread(Thread): def __init__(self, **kwargs): super().__init__() def thread_proc(): print('thread_proc') mt = MyThread(target=thread_proc) mt.start() class YourThread(Thread): def __init__(self): super().__init__() def run(self): print('YourThread.run running...') yt = YourThread() yt.start() #------------------------------------------------------------------------------------------------------------------------------------ Bazen bir thraed diğer bir thread sonlanana kadar onu beklemek isteyebilir. Örneğin bir thread bir şeyler yapmaktadır. O şeyleri bitirince thread de sona ermektedir. O halde diğer thread o şeyler yapılana kadar beklemek isteyebilir. İşte Thread sınıfının join isimli metodu ilgili thread akışı bitene kadar join metodunu çağıran thread'i blokede bekletir. (Bloke kavramı izleyen bölümde ele alınmaktadır.) join metoduna istenirse saniye cinsinden bir zaman aşımı (timeout) argümanı geçirilebilmektedir. Bu durumda eğer sonlanması beklenen thread bu zaman aşımına kadar sonlanmazsa join metodu beklemeyi bırakır ve akış devam eder. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def main(): thread = threading.Thread(target=foo, args=('Other thread', 10)) thread.start() print('main thread wait for other thread to finish...') thread.join() print('ok, main thread continues...') def foo(name, count): for i in range(count): print(f'{name}: {i}') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi programın ana thread'i diğer thread'lerden önce sonlanırsa ne olur? Bazı dillerde ana thread bittiğinde tüm program sonlandırıldığı için otomatik olarak diğer thread'ler de sonlandırılmaktadır. Halbuki Python'da durum böyle değildir. Python'da programın ana thread'i sonlansa bile program son thread sonlanana kadar devam etmektedir. Yani Python'da program ana thread sonlandığında değil son thread sonlandığında sonlanmaktadır. Aşağıdaki örnekte ana thread yaratılan thread'ten daha sonlanmaktadır. Burada program bu thread'le çalışmaya devam edecek bu thread sonlandığında sonlanacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def main(): thread = threading.Thread(target=thread_proc) thread.start() for i in range(5): print(f'Main thread: {i}') time.sleep(1) def thread_proc(): for i in range(10): print('Other thread') time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Python'ın ana gerçekleştirimi olan CPython yorumlayıcısında GIL (Global Interpreter Lock) denilen problemli bir durum vardır. CPython gerçekleştirimi referans sayacı (reference counting) temelli bir çöp toplayıcı (garbage collection) mekanizması kullandığı için maalesef birden fazla thread aynı anda sistem genelinde çalışamamktadır. Tek CPU'lu ya da tek çekirdekli sistemlerde zaten böyle bir durum söz konusu olmaz. Ancak günümüzde bilgisayarlarımızda kullandığımız işlemcilerde artık çok sayıda çekirdek bulunmaktadır. Python programımızdaki thread'ler işletim sistemi tarafından farklı çekirdeklerin kuyruklarına atanmış olsalar bile maalesef bu GIL mekanizması nedeniyle gerçek anlamda aynı anda çalışamaktadır. Bu da CPython gerçekleştiriminde paralel programlama yapılamayacağı anlamına gelmektedir. Gerçi üçüncü parti bazı kütüphaneler bazı hilelerle bu GIL mekanizmasını bypass edebilmektedir. Ancak bunun resmi bir yolu bulunmamaktadır. CPython gerçekleştiriminde çalışan programcılar bu problemi çözmeye çalışıyor olsalar da henüz bir sonuca varamamışlardır. Bu GIL problemi Jython, Iron Python gibi gerçekleştirlerde yoktur. GIL yüzünden CPython'da çok thread'li programlama maalesef önemli bir yara almıştır. Biz CPython gerçekleştiriminde programımızda ne kadar çok thread kullanırsak kullanalım. Bu tjread'ler farklı CPU ya da çekirdeklerin kuyruklarına atanmış olsalar bile aynı anda çalışamamaktadır. GIL problemi yüzünden çok sayıda thread farklı çekirdekler tarafından aynı anda çalıştırılamadığından çok tread'li programların performansları umulduğu kadar yüksek olmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki program CPython gerçekleştirimi ile çalıştırıldığında tek thread'li kod yaklaşık 22 saniye, çok thread'li kod yaklaşık 21 zaman almıştır. Ancak IronPython gerçekleştirimi ile çalıştırıldığında tk thread'li kod yaklaşık 10 saniye çok thread'li kod yaklaşık 6 saniye zaman almıştır. IronPython gibi bazı Python gerçekleştirimlerinde "GIL" problemi bulunmamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time def test1(): t1 = time.time() for i in range(1_000_000_000): pass t2 = time.time() print(t2 - t1) test1() def test2(): t1 = time.time() thread1 = threading.Thread(target=thread_proc1) thread2 = threading.Thread(target=thread_proc2) thread3 = threading.Thread(target=thread_proc3) thread4 = threading.Thread(target=thread_proc4) thread1.start() thread2.start() thread3.start() thread4.start() thread1.join() thread2.join() thread3.join() thread4.join() t2 = time.time() print(t2 - t1) def thread_proc1(): for i in range(250_000_000): pass def thread_proc2(): for i in range(250_000_000): pass def thread_proc3(): for i in range(250_000_000): pass def thread_proc4(): for i in range(250_000_000): pass test2() #------------------------------------------------------------------------------------------------------------------------------------ Bir thread çalışırken uzun süre beklemeyi gerektiren dışsal olayı başlatmışsa işletim sistemleri böyle thread'leri geçici olarak çizelgeden (run kuyruğundan) çıkartarak bekleme kuyrukları (wait queues) denilen kuyruklarda bekletmektedir. Örneğin klavyeden okuma yapmak, bir dosyadan okuma yapmak, bir soketten okuma yapmak, time.sleep gibi bir fonksiyonu çağırmak bu biçimde thread'in çizelge dışına çıkartılmasına yol açarak uykuya dalmasına nedne olmaktadır. Bu olayları başlatan thread'ler CPU zamanı harcamasın diye çizelge dışına çıkartılmaktadır. Ancak işletim sistemleri bu olayları arka planda kendisi zler. Bu bekleme sona erdiğinde ( örneğin klavyeden bir giriş yapıldığında, diskten ilgili bölüm okunduğunda, network kartına bir bildi geldiğinde vs.) işletim sistemleri yeniden thread'leri çizelgeye alırlar. Burada bekleme işlemini yapan thread'ler için bir sorun yoktur. Onlar zaten ilgili olay gerçekleşene kadar bekleyeceklerdir. Ancak onların çizelge dışına çıkartılması boşuna CPU zamanın harcanmasını engeller. Dolayısıyla çalışma performansını artırır. Örneğin sistemimizde yüzlerece thread bulunuyor olabilir. Aslında bunların büyük çoğunluğu o anda blokede beklemektedir. Bu nedenle sistem sanıldığı kadar yavaşlamamaktadır. İşte dışsal olay başlatan thread'lerin geçici süre çizelgden (run kuyruğundan) çıkartılmasına ilgili thread'in "bloke olması (blocking)" denilmektedir. Thread'leri "CPU yoğun (CPU bound)" ve "IO Yoğun (IO bound)" olmak üzere ikiye ayırabiliriz. CPU yoğun thread'ler kendisine verilen quanta süresinin büyük bölümünü bloke olmadan kullanırlar. Ancak IO yoğun thread'ler kendilerine verilen quanta süresinin çok azını kullanırlar. Genellikle IO yoğun thread'lerle karşılaşılmaktadır. Sistemde çok sayıda IO yoğun thread'in bulunmasının bir zararı yoktur. Ancak çok sayıda CPU yoğun thread sistemde hissedilebilir bir yavaşlık oluşturabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir therad'in yaşam döngüsü nasıldır? Thread yaratılır, quanta kullanmak için CPU'ya atanır. Sonra CPU'dan kopartılıp sonraki quanta için çalışma kuyruğunda bekler. Dışsal bir olayı başlattığında çalışma kuyruğundan çıkartılıp blokede bekletilir. Sonra olay gerçekleşince bekleme kuyruklarından yeniden çalışma kuyruğuna yerleştirilir. Bu yaşam döngüsünü şekilsel biçimde aşağıdaki gibi ifade edebiliriz: Running ---------> Ready | <-------- | | | -----> Wating -->-- #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Thread'ler konusunun en önemli kısmı "thread senkronizasyonudur". Thread senkronizasyonu birlikte birtakım işleri yapan thread'lerin birbirlerini kimi zaman bekleyerek olumsuz durumları bertaraf etmesi anlamına gelmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Thread senkronizasyonundaki en önemli kavramlardan biri "krtik kod (critical section)" denilen kavramdır. Belli bir kod parçasının başından sonuna kadar tek bir thread akışı tarafından çalıştırılması gerektiği durumlarda bu kod kod parçalarına "kritik kod" denilmektedir. Kritik kodlarda kritik koda giren thread bu kod içerisinde quanta süresini doldurup kesilebilir. Ancak başka bir thread bu thread çıkmadan kritik koda girmemelidir. Kritik kod başından sonuna kadar kesilmeden çalışma anlamına gelmez. Başından sonuna kadar hiç thread'ler arası geçiş olmadan çalışmaya "atomik çalışma" denilmektedir. Kritik kodlara giren thread'ler quanta sürelerini bitirip ya da bloke olup kesilebilirler. Ancak başka thread'ler onlar krtik koddan çıkmadan kritik koda girmemelidirler. Aşağıdaki örnekte iki farklı thread aynı global değişkeni birer milyon kez artırmıştır. Ancak sonuç 2 milyon çıkmayabilecektir. #------------------------------------------------------------------------------------------------------------------------------------ import threading count = 0 def main(): thread1 = threading.Thread(target=thread_proc1) thread2 = threading.Thread(target=thread_proc2) thread1.start() thread2.start() thread1.join() thread2.join() print(count) def thread_proc1(): global count for i in range(1000000): count += 1 def thread_proc2(): global count for i in range(1000000): count += 1 main() #------------------------------------------------------------------------------------------------------------------------------------ 56. Ders 17/07/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ İki thread'in bir makineyi sırasıyla 1'den 5'e kadar konumlara soktuğunu varsayalım. Örneğin thread'lerden biri makineyi 2 numaralı konuma soktuktan asonra thread'ler arası geçiş oluşup diğer çalışmaya başlarsa ve diğer thread'te bu makineyi kullanırsa makinenin konumu bozulacaktır. Birinci thread yeniden kaldığı yerden çalışmaya devam ettiğinde makineyi 2 numaralı konumda sanacaktır. Ancak makine artık 2 numaralı konumda değildir. Aşağıda bu örnek simüle edilmiştir. Bu örnekte aslında olması gereken şey thread'lerden biri makineyi kullanmaya başladığı zaman diğerlerinin bu thread kullanımı bitirene kadar bekletilmesidir. #------------------------------------------------------------------------------------------------------------------------------------ import time import random import threading def main(): thread1 = threading.Thread(target=thread_proc1) thread2 = threading.Thread(target=thread_proc2) thread1.start() thread2.start() def thread_proc1(): for _ in range(10): run_machine('thread1') def thread_proc2(): for _ in range(10): run_machine('thread2') def run_machine(name): print(f'{name}: 1. Step') time.sleep(random.random()) print(f'{name}: 2. Step') time.sleep(random.random()) print(f'{name}: 3. Step') time.sleep(random.random()) print(f'{name}: 4. Step') time.sleep(random.random()) print(f'{name}: 5. Step') time.sleep(random.random()) print('-------------------------------') main() #------------------------------------------------------------------------------------------------------------------------------------ Kritik kodlar flag kullanılarak manuel biçimde oluşturulamazlar. Örneğin aşağıdaki gibi bir kritik kod oluşturulamamaktadır: g_flag = False ... while g_flag: pass g_flag = True ... ... ... g_flag = False Bu kodda iki önemli kusur vardır: 1) Thread'ler arası geçiş aşağıda belirtilen noktada olursa birden fazla thread kritik koda girebilir. Yani mekanizma çalışmaz: g_flag = False ... while g_flag: pass -----> DİKKAT! Bu noktada thread'ler arası geçiş oluşursa mekanizma çalışmaz! g_flag = True ... ... ... g_flag = False 2) Bu kodda bekleme döngü içerisinde CPU zamanı harcanarak yapılmaktadır. Halbuki beklemenin "thread'in çalışma kuyruğundan (run queue)" çıkartılarak bekleme kuyruklarına alınması biçiminde uykuya yatırılarak yapılması gerekir. İşte kritik kodlar ancak işletim sisteminin özel mekanizmalarıyla sağlanabilmektedir. Bu özel mekanizmaları kullanan standrat Python sınıfları vardır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Kritik kod oluşturmanın en pratik yolu threading modülündeki Lock isimli sınıfı kullanmaktır. Bu sınıf yoluyla kritik kod şöyle oluşturulur: 1) Önce thread'ler yaratılmadan global bir Lock nenesi yaratılmalıdır. (Ya da yerel düzeyde yaratılıp thread'lere parametre olarak da geçirilebilir.) Örneğin: g_lock = threading.Lock() 2) Kritik kod aşağıdaki gibi oluşturulur: g_lock.acquire() ... ... ... g_lock.release() Burada thread'lerin aynı lock nesnesini kullanması gerekmektedir. Bunun basit bir yolu Lock nesnesinin global düzeyde oluşturulması olabilir. Diğer bir yolu da thread'lere parametre yoluyla aktarılmasıdır. Thread'lerden biri lock.acquire() metodunu kilit açıksa geçer ve kilidi otomatik olarak kilitler. Başka thread'ler aynı nesneyle acquire yapmak istediklerinde bloke olup kilidi almış thread'in kilidi bırakmasını beklerler. Kilidi almış olan thread lock.release() ile kilidi bırakır. Bu durumda bekleyen thread'lerden biri kilidi alır. Bekleyenlerden hangisinin kilidi alacağı konusunda bir garanti verilmemektedir. (Yani ilk bekleyenin kilidi alması gibi bir granti söz konusu değildir.) Bir thread Lock nesnesini lock acquire metodu ile kilitlemişse başka thread release uygulasa bile kilit açılmaz. Bu durumda exception (RuntimeError) Kilidin açılması kilidi alan thread'in release uygulamasıyla mümkün olmaktadır. Yani kilidin thread temelinde sahipliği vardır. Hangi thread aquire yapmış ise o thread release yapabilir. Aşağıdaki daha önce yapmış olduğumuz makine konumlandırma örneği Lock nesnesi ile düzeltilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import time import random import threading g_lock = threading.Lock() def main(): thread1 = threading.Thread(target=thread_proc1) thread2 = threading.Thread(target=thread_proc2) thread1.start() thread2.start() def thread_proc1(): for _ in range(10): run_machine('thread1') def thread_proc2(): for _ in range(10): run_machine('thread2') def run_machine(name): g_lock.acquire(); print(f'{name}: 1. Step') time.sleep(random.random()) print(f'{name}: 2. Step') time.sleep(random.random()) print(f'{name}: 3. Step') time.sleep(random.random()) print(f'{name}: 4. Step') time.sleep(random.random()) print(f'{name}: 5. Step') time.sleep(random.random()) print('-------------------------------') g_lock.release() main() #------------------------------------------------------------------------------------------------------------------------------------ İki thread'in aynı gloıbal değişken üzerinde işlem yapması yukarıda belirtildiği gibi bir senkronizasyon sorununa yol açabilmektedir. Aşağıda bu sorun Lock nesnesiyle giderilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import threading g_lock = threading.Lock() g_count = 0 def main(): thread1 = threading.Thread(target=thread_proc1) thread2 = threading.Thread(target=thread_proc2) thread1.start() thread2.start() thread1.join() thread2.join() print(g_count) def thread_proc1(): global g_count for i in range(1000000): g_lock.acquire() g_count += 1 g_lock.release() def thread_proc2(): global g_count for i in range(1000000): g_lock.acquire() g_count += 1 g_lock.release() main() #------------------------------------------------------------------------------------------------------------------------------------ Bir fonksiyonun ya da metodun "thread güvenli (thread safe)" olması demek bu fonksiyonun ya da metodun aynı anda birden fazla thread tarafından çağrılması durumunda bir sorunun oluşmaması demektir. Global değişkenleri kullanmayan ve ortak kaynakları kullanmayan fonksiyonlar ve meotlar genel olarak thread güvenlidir. Ancak Python programcısı olarak çok thread'li uygulamalar yaparken kullandığınız kütüphanelerdeki fonksiyonların ve metotların thread güvenli olup olmadığı konusunda bilgi sahibi olmalısınız. Pekiyi foo gibi fonksiyon thread güvenli değilse ancak biz birden fazla thread'ten bu fonksiyonu çağırmak istiyorsak ne olacaktır? Bu durumda programcının kendisi her thread'te bu fonnksiyonu çağırırken lock işlemi yapmalıdır. Örneğin: lock.acquire() foo() lock.release() #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'ın list gibi, dict gibi, set gibi mutable sınıfları thread güvenli bir biçimde oluşturulmuştur. Yani örneğin iki thread aynı listeye append yaptığında herhangi bir bozulma söz konusu olmaz. Zaten bu sınıflar yazılırken bu durum dikkate alınıp lock senkronizasyonları bunların içerisinde uygulanmıştır. Aşağıdaki programda iki thread aynı global listeye append ile ekleme yapmaktadır. Eğer list sınıfı thread güvenli olmasaydı hemen program çökerdi. Ancak thread güvenli olduğu için program sorunsuz çalışacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import threading g_a = [] def main(): lock = threading.Lock() thread1 = threading.Thread(target=thread_proc1, args=(lock,)) thread2 = threading.Thread(target=thread_proc2, args=(lock,)) thread1.start() thread2.start() thread1.join() thread2.join() print(len(g_a)) def thread_proc1(lock): for i in range(1000000): g_a.append(i) def thread_proc2(lock): for i in range(1000000): g_a.append(i) main() #------------------------------------------------------------------------------------------------------------------------------------ Event senkronizasyon nesneleri belli bir olay gerçekleşene kadar bir thread'i blokede bekletmek için kullanılmaktadır. Nesnenin kullanım şöyledir: 1) Global düzeyde bir Event nesnesi yaratılır. (Ya da yerel düzeyde yaratılıp thread'lere parametre olarak da geçirilebilir.) 2) Bekleyecek thread event nesnesinin wait metoduyla beklemeyi yapar. Bu metoda zaman aşımı parametresi (saniye cinsinde) verilebilir. 3) Bekleyen thread'i bekleme durumundan çıkartmak için başka bir thread event nesnesi ile sınıfın set metodunu çağırır. Kilit set metodu ile açıldıktan sonra artık wait işlemleri blokeye yol açmaz. Kilidi yeniden kapamak için clear metodu kullanılmalıdır. Tabii diüer thread set işlemini daha önce yapmışsa wait işlemini yapan thread artık beklememektedir. Event senkronizasyon nesneleri tipik olarak iki thread birlikte bir şeyi yaparken birisinin diğerinin bir işlemi bitirmesini beklmesi için kullanılmaktadır. Aşağıda böyle bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time g_event = threading.Event() def main(): thread1 = threading.Thread(target=thread_proc1) thread2 = threading.Thread(target=thread_proc2) thread1.start() thread2.start() thread1.join() thread2.join() def thread_proc1(): print('thread-1 waiting for thread2...') g_event.wait() print('thread-1 continues...') def thread_proc2(): for i in range(10): print('thread-2 running...') if i == 5: g_event.set() time.sleep(1) main() #------------------------------------------------------------------------------------------------------------------------------------ Semaphore'lar pek çok işletim sisteminde ve framework'te bulundurulan temel senkronizasyon nesnelerinden biridir. Semaphore'lar sayaçlı senkronizasyon nesneleridir. Bir kritik koda en fazla n tane thread akışının girmesini sağlamak kullanılmaktadır. (Biz kritik kod tanımını "başından sonuna kadar tek bir thread akışı tarafından çalıştırılması gereken kodlar" biçiminde yapmıştık. Ancak bu bağlamda da kritik kod terimi kullanılabilmektedir.) Kullanımı şöyledir: 1) Global düzeyde bir Semaphore nesnesi yaratılır. (Ya da yerel düzeyde yaratılıp thread'lere parametre olarak da geçirilebilir.) Bu noktada semaphoer sayacı belirtilmektedir. Örneğin: g_sem = threading.Semaphore(3) 2) Kritik kod sınıfın acquire ve release metotları arasında oluşturulmaktadır: sem = threading.Semaphore(3) ... sem.acquire() ... ... ... sem.release() 3) Bir thread akışı acquire netoduna geldiğinde eğer semaphore sayacı sıfırdan büyükse geçiş yapılır ama semaphore sayacı 1 eksiltilir. Eğer semaphore sayacı 0 ise acquire metodu thread'i blokede bekletir. Ta ki semaphore sayacı 0'dan büyük olana kadar. relese metodu ise semaphore sayacını 1 artırmaktadır. Böylece kritik koda en fazla belirlenen sayıda thread girebilir. Örneğin: sem.acquire() ... ... ... sem.release() Burada başlangıçtaki semaphore sayacının 3 olduğunu varsayalım. Bir thread acquire metoduna gelmiş olsun. Semaphore sayacı 0'dan büyük olduğu için thread bloke olmadan kritik koda girecektir. Ancak semaphore sayacı 1 eksiltilecektir. Başka bir thread de yine acquire metodundan geçecektir. Böylece semapore sayacı 1'e düşecektir. Başka bir thread daha acquire metodundan geçecektir. Artık kritik kodda ü threda vardır ve semaphore sayacı 0'a düşmüştür. Artık başka bir threda acquire metoduna geldiğinde blokede bekleyecektir. Ta ki semaphore sayacı 0'dan büyük olana kadar. Eğer kritik koddaki thread'lerden biri release metodunu çağırırsa semaphore sayacı 1 artırılır. Artık semaphore sayacı 0'dan büyük olduğu için bekleyen thread kritik koda girebilir. Tabii yine semaphore sayacı 0'a düşecektir. Semaphore sayacı 1 olan semaphore'lara "binary semaphore'lar" denilmektedir. Binary semaphore'lar kullanım bakımından Lock nesnelerine benzemektedir. Ancak Lock nesneleri ile binary Semaphore nesneleri arasında yine de bir fark vardır: Lock nesnelerinde kilit ancak kilidi alan thread tarafından açılabilir. Halbuki Semaphore'larda kilit başka bir thread tarafından release metodu uygulandığında açılabilmektedir Bu özellik de "üretici tüketici problemi" benzeri problemlerin çözümünde kullanılmaktadır. Pekiyi semaphore nesnelerine nedne gereksinim duyulmaktadır? İşte semaphore nesneleri tipik olarak "kaynak paylaştırmak" amacıyla kullanılmaktadır. Örneğin elimizde üç tane yazıcı olsun. Biz bu üç yazıcıyı 10 tane thread'in kullanımına sunalım. Bir thread yazıcıyı talep ettiğinde ona bu üç yazıcıdan birini tahsis edebiliriz. Diğer bir thread de yazıcıyı talep ettiğinde ona da bir yazıcı tahsis edebiliriz. Başka bir threda yazıcıyı talep ederse ona da kalan son yazıcıyı tahsis ederiz. Artık elimizde yazıcı kalmamıştır. Bu durumda yazıcı talep eden thread'lerin CPU zamanı harcamadna uykuda bekletilmesi gerekir. Ta ki thread'lerden biri yazıcı ile işini bitirene kadar. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Daha önce yapmış olduğumuz makine simülasyonunu bu kez binary semaphore ile yapalım. #------------------------------------------------------------------------------------------------------------------------------------ import time import random import threading def main(): sem = threading.Semaphore(1) thread1 = threading.Thread(target=thread_proc1, args=(sem, )) thread2 = threading.Thread(target=thread_proc2, args=(sem, )) thread1.start() thread2.start() def thread_proc1(sem): for _ in range(10): run_machine('thread1', sem) def thread_proc2(sem): for _ in range(10): run_machine('thread2', sem) def run_machine(name, sem): sem.acquire(); print(f'{name}: 1. Step') time.sleep(random.random()) print(f'{name}: 2. Step') time.sleep(random.random()) print(f'{name}: 3. Step') time.sleep(random.random()) print(f'{name}: 4. Step') time.sleep(random.random()) print(f'{name}: 5. Step') time.sleep(random.random()) print('-------------------------------') sem.release() main() #------------------------------------------------------------------------------------------------------------------------------------ Şimdi de Semaphore kullanarak kaynak paylaştırma örneği verelim. Elimizde 3 tane (NPRINTERS) yazıcı olsun. Ancak 20 (NTHREADS) thread bu yazıcıları kullanmak için rekabet etsin. Biz bu yazıcıları bir liste içerisinde (g_printers) toplayalım. Thread'ler istedikçe bu listeden pop ile onları alıp thread'lere verelim. Therad'ler yazıcıyı kullandıktan sonra onalrı yeniden bu listeye ekleyelim. Böylece bu liste boşta olan yazıcıları tutyor olsun. Toplam 3 yazıcı olduğuna göre ve bizden 20 threda yazıcı talep edeceğine göre elimizde yazıcı kalmayınca diğer thread'leri uykuda bekletmeliyiz. #------------------------------------------------------------------------------------------------------------------------------------ import time import random import threading NTHREADS = 20 NPRINTERS = 3 class Printer: def __init__(self, number): self.number = number def use(self, name): print(f'{name} using printer {self.number}') def release(self, name): print(f'{name} releasing printer {self.number}') g_sem = threading.Semaphore(NPRINTERS) g_printers = [Printer(i) for i in range(1, NPRINTERS + 1)] def main(): threads = [] for i in range(NTHREADS): thread = threading.Thread(target=thread_proc, args=(f'thread-{i + 1}', )) threads.append(thread) thread.start() for thread in threads: thread.join() def thread_proc(name): for _ in range(3): time.sleep(1/random.randint(3, 10)) print(f'{name} is waiting to get the printer') g_sem.acquire() printer = g_printers.pop(0) printer.use(name) time.sleep(1/random.randint(3, 10)) printer.release(name) g_printers.append(printer) g_sem.release() main() #------------------------------------------------------------------------------------------------------------------------------------ 57. Ders 19/07/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Üretici tüketici problemi (producer-consumer problem) gerçek hayatta en çok karşılaşılan senkronizasyon problemlerinden biridir. Bu problemde bir thread döngü içeisinde bir değer elde eder. Diğer thread bir döngü içerisinde bunu alarak işler. Eğer üretici thred tüketici thread'ten hızlı davranırsa tüketici thread eski bilgiyi almadan üretici thread paylaşılan alana yeni bir bilgi yerleştirerek eski bilgiyi ezebilir. Benzer biçimde tüketici thread de üretici thread'ten hızlı davranırsa üretici thread yeni bir bilgiyi paylaşılan alana koymadan eski bilgiyi yeniden paylaşılan alandan alabilir. Problemde bu durumun engellenmesi gerekir. Yani üretici thread tüketici thread eski bilgiyi almadan paylaşılan alana yeni bilgiyi yerleştirmemeli, tüketi thread de üretici thread yeni bilgiyi almdan eski bilgiyi yeniden almamalıdırç Aşağıdaki örnekte üretici thread 0'dan 100'e kadar (100 dahil değil) değerleri rastgele beklemelerle paylaşılan alana (global değişkene) yerleştirmiş, türketici bunları oradan alarak rastgele beklemelerle işlemiş gibi yapmıştır. Bu yukarıda açıkladığımız üretici-tüketici problemi için bir simülasyondur. Programın bir çalışmasında tüketicinin aldığı değerler şöyledir: None None None 1 1 2 3 3 3 3 4 4 6 7 8 10 10 10 14 15 15 15 17 17 19 19 20 21 23 24 25 27 29 31 32 32 33 34 35 36 37 38 40 40 42 43 43 45 45 46 47 49 49 49 50 50 52 53 54 56 60 60 60 62 63 67 68 69 69 71 74 75 75 76 76 76 77 81 81 83 83 84 84 85 88 90 92 92 93 95 96 96 97 99 Buradan görüldüğü gibi tüketici hem bazı değerleri kaçırmış hem de bazı değerleri birden fazla kez almıştır. #------------------------------------------------------------------------------------------------------------------------------------ import random import time import threading g_shared = None def main(): producer = threading.Thread(target=producer_thread_proc) consumer = threading.Thread(target=consumer_thread_proc) producer.start() consumer.start() producer.join() consumer.join() def producer_thread_proc(): global g_shared i = 0 while True: time.sleep(random.random() / 2) g_shared = i if i == 99: break i += 1 def consumer_thread_proc(): while True: val = g_shared time.sleep(random.random() / 2) print(val, end = ' ') if val == 99: break print() main() #------------------------------------------------------------------------------------------------------------------------------------ Üretici-Tüketici problemi tipik olarak semaphore nesneleriyle çzöülmektedir. Üretici ve tüketici için iki ayrı semaphore nesnesi yaratılır. Başlangıçta üretici semaphore'unun değeri 1 olarak, tüketici semaphore'unun değeri ise 0 olarak ayarlanır. Üretici paylaşılan alana bilgi yerleştirince tüketici semaphore'unu tüketici de bilgiyi paylaşılan alanadan alınca üretici semaphore'unu artırır. Problemin çözümünün "sembolik kodu (pseudo code)" şöyledir: g_sem_producer = threading.Semaphore(1) g_sem_consumer = threading.Semaphore(0) ÜRETİCİ while True: g_sem_producer.acquire() g_sem_consumer.release() TÜKETİCİ while True: g_sem_consumer.acquire() g_sem_producer.release() Burada üreticinin tüketiciyi tüketicinin de üreticiyi blokeden kurtardığına dikkat ediniz. Adeta bir tahteravalli gibi işlemler yürütülmektedir. Yuukarıdaki problem aşağıdaki gibi çözülebilir. #------------------------------------------------------------------------------------------------------------------------------------ import random import time import threading g_sem_producer = threading.Semaphore(1) g_sem_consumer = threading.Semaphore(0) g_shared = None def main(): producer = threading.Thread(target=producer_thread_proc) consumer = threading.Thread(target=consumer_thread_proc) producer.start() consumer.start() producer.join() consumer.join() def producer_thread_proc(): global g_shared i = 0 while True: time.sleep(random.random() / 2) g_sem_producer.acquire() g_shared = i g_sem_consumer.release() if i == 99: break i += 1 def consumer_thread_proc(): while True: g_sem_consumer.acquire() val = g_shared g_sem_producer.release() time.sleep(random.random() / 2) print(val, end = ' ') if val == 99: break print() main() #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi üretici-tüketici problemi ile uğraşmaya ne gerek vardır? Neden tek bir thread değeri elde ettikten sonra kendisi işlemeyip başka bir thread'in işlemesi için paylaşılan alana yazmaktadır? Bu işlemin temel amacı hız kazancı sağlamaktır. Bu sayede bir thread yeni bir değeri elde ederken diğeri eş zamanlı biçimde onu işleyebilir ve böylece işlemler çok daha hızlı gerçekleştirilebilir. Tabii CPyton dağıtımında GIL yüzünden birden fazla CPU ya da çekirdek aynı programın farklı thread'lerini eş zamanlı biçimde çalıştıramamaktadır. Bu durumda hız kazancı umulduğu kadar olmayacaktır. Ancak diğer bazı Python gerçekleiştirimlerinde GIL biçiminde bir problem yoktur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Üretici-Tüketici probleminde paylaşılan alan tek elemanlık değil birden fazla elamanı içerecek bir kuyruk sistemi olursa toplam bekleme miktarı azaltılır. Çünkü bu durumda üretici thread yalnızca kuyruk doluyken, tüketici thread ise yalnızca kuyruk boşken bekleyecektir. Aşağıdaki örnekte liste kullanılarak bir kuyruk sistemi oluşturulmuştur. Bu örnekte üretici semaphore sayacının başlangıçta 1 değerine değil kuyruk uzunluğu değerine kurulduğuna dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import random import time import threading QUEUE_SIZE = 10 g_sem_producer = threading.Semaphore(QUEUE_SIZE) g_sem_consumer = threading.Semaphore(0) g_queue = [None] * QUEUE_SIZE g_head = 0 g_tail = 0 def main(): producer = threading.Thread(target=producer_thread_proc) consumer = threading.Thread(target=consumer_thread_proc) producer.start() consumer.start() producer.join() consumer.join() def producer_thread_proc(): global g_tail i = 0 while True: time.sleep(random.random() / 2) g_sem_producer.acquire() g_queue[g_tail] = i g_tail += 1 g_tail %= QUEUE_SIZE g_sem_consumer.release() if i == 99: break i += 1 def consumer_thread_proc(): global g_head while True: g_sem_consumer.acquire() val = g_queue[g_head] g_head += 1 g_head %= QUEUE_SIZE g_sem_producer.release() time.sleep(random.random() / 2) print(val, end = ' ', flush=True) if val == 99: break print() main() #------------------------------------------------------------------------------------------------------------------------------------ Aslında kuyruklu üretici-tüketici problemi zaten Python Standart Kütüphanesinde hazır bir biçimde bulunmaktadır. queue modelündeki Queue sınıfı bu iş için kullanılmaktadır. Sınıfın kullanımı şöyledir: 1) queue.Queue sınıfı türünden global bir nesne yaratılır. Nesne yaratılırken kuyruk uzunluğu verilebilir. Eğer kuyruk uzunluğu verilmezse kuyruk bellek yettiği müddetçe otomatik büyütülmektedir. 2) Üretici thread Queue sınıfının put metoduyla kuyruğa eleman ekler. Kuyruk doluysa bu metot blokeye yol açmaktadır. Yani sınıf kendi içerisinde zaten semaphore nesnelerini yaratıp onları kullanarak işlemlerini yapmaktadır. 3) Tüketici thread kuyruktan queue sınıfının get metoduyla eleman alır. Kuyruk boşsa bu metot blokeye yol açmaktadır. Yukarıdaki örneğin hazır queue.Queue sınıfı ile gerçekleştirimi aşağıdaki verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import random import time import threading import queue g_queue = queue.Queue(10) def main(): producer = threading.Thread(target=producer_thread_proc) consumer = threading.Thread(target=consumer_thread_proc) producer.start() consumer.start() producer.join() consumer.join() def producer_thread_proc(): global g_tail i = 0 while True: time.sleep(random.random() / 2) g_queue.put(i) if i == 99: break i += 1 def consumer_thread_proc(): global g_head while True: val = g_queue.get() time.sleep(random.random() / 2) print(val, end = ' ', flush=True) if val == 99: break print() #------------------------------------------------------------------------------------------------------------------------------------ Aslında queue modülünde yine semaphore nesneleriyle çalışan senkronize LifoQueue ve PriorityQueue isimli iki sınıf da bulunmaktadır. LIFO kuyruk sistemine halk arasında "stack" denilmektedir. Bu kuyruk sisteminde kuyruğa son yerleştirilen eleman ilk alınmaktadır. LIFO kuyruk sistemiyle gerçek hayatta seyrek de olsa karşılaşılmaktadır. Asansöre binme ve inme sırası LIFO sistemini çağrıştırmaktadır. UNDO mekanizması LIFO kuyruk sistemiyle yapılmaktadır. PriorityQueue denilen öncelik temelli kuruk sistemlerine de gerçek yaşamda karşılaşılmaktadır. Bu kuyruk sistemlerinde kuyruğa yerleştirilen elemanlara birer öncelik derecesi verilir. Kuyruktan eleman alınırken alım sırası bu önceliğe göre yapılır. PeiorityQueue sınıfında put metodu ikili bir demet biçiminde aparametre alır. Demetin ilk elemanı öncelik derecesini, ikinci elemanı kuyruğa yerleştirilecek değeri belirtir. get metodu da yine ikili demet vermektedir. Python'daki PriprityQueue sınıfında düşük değer yüksek öncelik belirtmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda LifoQueue sınıfının kullanımına bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import queue lifoq = queue.LifoQueue() lifoq.put('ali') lifoq.put('veli') lifoq.put('selami') lifoq.put('ayşe') lifoq.put('fatma') while not lifoq.empty(): val = lifoq.get() print(val) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda PriorityQueue sınıfının kullanımına bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import queue pq = queue.PriorityQueue() pq.put((1, 'ali')) pq.put((3, 'veli')) pq.put((2, 'selami')) pq.put((10, 'ayşe')) pq.put((8, 'fatma')) while not pq.empty(): prio, val = pq.get() print(prio, val) #------------------------------------------------------------------------------------------------------------------------------------ Program terimi halk arasında "kaynak kod" anlamında ya da "çalıştırılabilen dosyalar" anlamında kullanılmaktadır. işletim sistemleri dünyasında çalışmakta olan programalara "proses" denilmektedir. Bir program çalıştırıldığında işletim sistemi bir proses oluşturur ve programın çalışmasını sürekli izler. Yani programlar işletim sistemlerinin kontrolü altında çalışmaktadır. Bir program başka bir programı çalıştırabilir. Bir programın başka bir programı çalıştırması aslında yeni bir prosesin yaratılması eylemidir. CPython dağıtımında GIL problemi olduğu için çok thread'li uygulamalarda thread'ler birden fazla CPU ya da çekirdekte eş zamanlı olarak çalıştırılamamaktadır. Bu da maalesef daha önceden de belirttiğimiz gibi Python'da thread kullanımını önemli ölçüde olumsuz biçimde etkilemektedir. Normalde thread'ler yokken bir iş hızlandırma amacıyla birden fazla prosese yaptırılıyordu. Ancak thread'ler bunun için çok daha iyi bir mekanizma sunmuşlardır. Fakat CPyton'daki GIL yüzünden neredeyse çok prosesli uygulamalar çok thread'li uygulamalardan daha yavaş çalışır durumdadır. Özetle aslında diğer dillerde bir işin farklı prosrslere bölünerek yaptırılması farklı thread'lere bölünerek yaptırılmasından çok daha yavaş sonuç vermektedir. Ancak CPython gerçekleştiriminde GIL yüzünden neredeyse tersi durum daha hızlı bir çalışmaya yol açmaktadır. Diğer dillerde bir işin proseslere yaptırılması ile thread'lere yaptırılması arasındaki performans farklılığının gerekçesi şunlardır: 1) Thread'ler proseslere göre daha az sistem kaynağı kullanırlar 2) Thread'lerin yarılması ve yok edilmesi proseslere göre çok daha hızlı yapılmaktadır. 3) Prosesler biribirinden izole edilmiştir. Onların haberleşmeleri thread'lerin haberleşmelerinden çok daha zordur. Ancak yukarıda da belirttiğimiz gibi CPython gerçekletiriminde adeta ters bir durum vardır. Yani çoğu kez bir işin proseslere yaptırılması GIL yüzünden thread'lere yaptırılmasından daha hızlı sonuç vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir proses yaratmak (yani bir programı çalıştırmak için) için en çok başvurulan yöntem subprocess modülündeki run fnksiyonunu kullanmaktır. run fonksiyonunun temel kullanımı çok basittir. Fonksiyon birinci parametresiyle bizden çalıştırılacak programı ve onun komut satırı argümanlarını dolaşılabilir bir nesne biçiminde alır ve programı çalıştırır. Eğer komut satırı argümanları kullanılmayacaksa birinci parametreye doğrudan çalıştırılacak programın yol ifadesi de girilebilir. Örneğin: subprocess.run(r'c:\windows\notepad.exe') #------------------------------------------------------------------------------------------------------------------------------------ import subprocess subprocess.run(r'c:\windows\notepad.exe') #------------------------------------------------------------------------------------------------------------------------------------ Windows sistemlerinde çalıştırılabilen programın yol ifadesinde '\' UNIX/Linux ve Mac OS X sistemlerinde '/' kullanılmazsa dosya PATH çevre değişkeni ile belirtilen yerlerde aranır. PATH çevre değişkeni dizinlerden oluşmaktadır. Dolayısıyla biz run fonksiyonunda dosyanın yol ifadesinde Windows sistemlerinde '\' karakterini UNIX/Linux sistemlerinde '/' karakterini hiç kullanmamışsak bu dosya PATH çevre değişkenindeki dizinlerde sırasıyla aranacaktır. Örneğin Windows'ta kurulum sırasında zaten Windows dizini PATH çevre değişkeninde bulunmaktadır. Bu durumda biz "notepad.exe" programını aşağıdaki gibi de çalıştırabilirdik. subprocess.run('notepad.exe') Yani başka bir deyişle run fonksiyonu ile bir programı çalıştırmak istediğimizde eğer program PATH dizinlerinin birinin içerisindeyse onun yalnızca ismini belirtebilriz. Ancak program PATH dizinlerinin herhangi birinin içerisinde değilse onun tüm yol ifadesini belirtmeliyiz. Windows sistemlerinde çalıştırılabilen dosyanın uzantısı belirtilmezse zaten otomatik olarak o isimli ".exe" dosyalar aranmaktadır. Örneğin: subprocess.run('notepad') #------------------------------------------------------------------------------------------------------------------------------------ import subprocess subprocess.run('notepad.exe') #------------------------------------------------------------------------------------------------------------------------------------ Windows sistemlerinde subprocess.run fonksiyonunda yol ifadesi girildiğinde "Access denied" biçiminde bir error oluşabilmektedir. Bu muhtemelen bir bug'dır. Bu bug'ın arkasından dolaşmak için (work around) önce yol ifadesinin bulunduğu dizine geçip sonra yol ifadesi olmadan çalıştırılacak program ismi belirtilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess import os os.chdir(r'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE') subprocess.run(['devenv.exe']) #------------------------------------------------------------------------------------------------------------------------------------ 58. Ders 24/07/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Eğer çalıştırılacak programa komut satırı argümanları da verilecekse bu durumda run fonksiyonunda program ismi ve argümanları dolaşılabilir bir nesne biçiminde verilmelidir. Örneğin: subprocess.run(['notepad.exe', 'sample.py']) #------------------------------------------------------------------------------------------------------------------------------------ import subprocess subprocess.run(['notepad.exe', 'sample.py']) #------------------------------------------------------------------------------------------------------------------------------------ Bir Python programını çalıştırmak için import işlemi yapılabilir ya da built-in exec fonksiyonundan faydalanılabilir. Ancak bu yöntemlerde çalıştırılan kod farklı proses tarafından çalıştırılmamaktadır. Aynı proses ve hatta aynı thread tarafından çalıştırılmaktadır. Biz bir python programını subprocess.run fonksiyonu ile tıpkı komut satırında çalıştırdığımız gibi başka bir proses yaratarak çalıştırabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess subprocess.run(['python', 'testprog.py']) #------------------------------------------------------------------------------------------------------------------------------------ Proses subprocess.run fonksiyonuyla yaratıldığında akış yaratılan alt proses sonlanmadan run fonksiyonundan çıkmaz. run fonksiyonu CompletedProcess isimli bir sınıf türünden nesneyle geri dönmektedir. Aşağıdaki programda Windows'ta "notepad.exe" programı çalıştırılmıştır. Bu "notepad.exe" programı bitmeden kodun aşağıya geçmediğine dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess cp = subprocess.run(['notepad.exe']) print('completed') #------------------------------------------------------------------------------------------------------------------------------------ ComplededProcess sınıfının args örnek özniteliği bizim run fonksiyonuna geçtiğimiz birinci parametreyi (yani kprogram ismini ve komut satırı argümanlarını vermektedir.) Sınıfın returncode isimli örnek özniteliği ise çalıştırılan prosesin "exit kodunu" bize verir. Proseslerin bir tamsayı olan exit kodları vardır. Bu exit kodları proses bittiğinde işletim sistemine iletilir. İşletim sistemi de bu exit kodunu prosesi çalıştıran prosese verir. Exit kodları geleneksel olarak başarılı sonlanmalar için 0, başarısız sonlanmalar için sıfır dışı değerler olarak seçilmektedir. Aşağıdaki örnekte notepad.exe progrfamı çalıştırılmış, programın çalışması bitince onun exit kodu yazdırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess cp = subprocess.run(['notepad.exe', 'sample.py']) print(cp.args) print(cp.returncode) #------------------------------------------------------------------------------------------------------------------------------------ Bildiğiniz gibi Python programlarında akış dosyanın sonuna geldiğinde program sonlanmaktadır. İşte bu durumda default olarak python programımız 0 exit koduyla sonlanmaktadır. Biz bir Python programını herhangi bir fonksiyon içerisinde o noktada da sonlandırabiliriz. Bunun için sys modülündeki exit fonksiyonu kullanılmaktadır. exit fonksiyonun int türden bir parametresi vardır. Bu parametre sonlandırılan prosesin exit kodunu oluşturur. Python programımız ele alınmayan bir exception ile sonlanmışsa bu bir başarısızlık durumu olduğu için 1 exit koduyla proses sonladırılmaktadır. Aşağıdaki programda proses foo fonksiyonu içerisinde sonlandırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import sys def foo(): print('foo') sys.exit(0) print('begins...') foo() print() print('ends...') #------------------------------------------------------------------------------------------------------------------------------------ Komut satırında son çalıştırılmış olan prgramın exit kodu alınabilir. Windows sistemlerinde bunun için aşağıdaki komutu kullanabilirsiniz: echo %errorlevel% UNIX/Linux sistemlerinde aynı şey şöyle yapılmaktadır: echo $? #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Spyder IDE'sinde sağ taraftaki komut satırı programına "IPython" denilmektedir. Aslında IPython bağımsız bir projedir. Spyder IDE'sine de entegre edilmiştir. IPython'ı Spyder olmadan da yükleyip komut satırındna çalıştırabilirsiniz. Yükleme işlemi pip programı ile aşağıdaki gibi yapılabilir: pip install ipython #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ IPython komut satırı progranmının pek çok özelliği vardır. Örneğin hiç dışarı çıkmadan ! öneki getirerek kabuk komutlarını IPython içerisinden çalıştırabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Modern işletim sistemlerinde ekran, klavye gibi donanım birimleri özel kodlar tarafından kullanılmaktadır. Bir donanım birimini bu biçimde yöneten ve ona iş yaptıran aşağı seviyeli programlara "aygıt sürücü (device driver)" denilmektedir. Aygıt sürücüler birer dosya gibi kullanılırlar. Yani biz aygıt sürücüleri dosya gibi açarız. Aygıt sürücü dosyasına bir şeyler yazdığımızda yazılanlar aslında aygıt sürücüye gönderilir. Aygıt sürücü gereğini yapar. Bencer biçimde biz bir aygıt sürücü dosyasından okuma yaptığımızda aslında aygıt sürücü kendi yönettiği aygıttan okumayı yaparak bize vermektedir. Programalamada ekran ve klavye sözcükleri pek kullanılmaz. Ekran yerine "stdout dosyası", klavye yerine ise "stdin dosyası" denilmektedir. Ekran ve klavye birer aygıt sürücü tarafından kontrol edilmektedir. Yani stdout ve stdin aslında aygıt sürücü dosyalarıdır. Biz Python'da print fonksiyonu ile bir şeyler yazdırmak istediğimizde print fonksiyonu yazdırılmak istenen şeyleri stdout dosyasına yani stdout aygıt sürücüsüne gönderir. Onun ekrana çıkartılması bu aygıt sürücünün görevidir. Aynı şey okuma yaparken input fonksiyonunda da benzerdir. İşletim sistemlerinde "IO yönlendirmesi (IO Redirection)" denilen bir olgu vardır. IO yönlendirmesi bir dosyaya yazdığını sanan kişilerin ya da bir dosyadan okuma yaptığını sanan kişilerin aslında başka dosyalara yazma ve başka dosyalardan okuma yapması durumudur. Örneğin print fonksiyonu stdout dosyasına yazma yapar. Default durumda bu stdout dosyası terminal aygıt sürücüsüne yönlendirilmiştir. Ancak biz stdout dosyasını başka bir yere yönlendirirsek ekrana yazdığımız şeyler aslında yönlendirdiğimiz yere yazılacaktır. Örneğin: for i in range(100): print(i) Buradaki Python programı 0'dan 100'e kadar sayıları ekrana yazmamaktadır, stdout dosyasına yazmaktadır. stdout dosyası default durumda ekran işlemlerinş yaoan aygıt sürücüye yönlendirilmiş durumdadır. Bu nedenle bu sayılar ekranda gözükecektir. Ancak biz stdout dosyasını başka bir yagıta ya da diskteki başka bir dosyaya yönlendirebiliriz. Bu durmda bu sayılar ekrana değil o kaynağa yazılacaktır. O halde "print fonksiyonu ekrana yazar" cümlesi yanlıştır. Bunun doğrusu "print fonksiyonu stdout dosyasına yazar" biçimindedir. stdout dosyası ise yaönlendirilebilmektedir. Aynı durum stdin dosyası için de benzerdir. Windows ve UNIX/Linux sistemlerinde komut satırında stdout dosyasını bir disk dosyasına yönlendirmek için ">" sembolü kullanılır. Örneğin: python sample.py > x.txt Burada artık sample.py dosyasının stdout dosyasına yazdıkları "x.txt" dosyasına yazılacaktır. Windows ve UNIX/Linux sistemlerinde stdin dosyasını yönlendirmek için ise "<" sembolü kullanılır. Örneğin: python sample.py < numbers.txt Burada aslında "sample.py" programında input fonksiyonu stdin dosyasından okuma yapar. stdin dosyası default durumda klavyeyi kontrol eden aygıt sürücüsüne yönlendirilmiş durumdadır. Ancak biz "<" sembolü ile stdin dosyasını "numbers.txt" dosyasına yönlendirmiş olduk. Artık bu dosyadaki şeyleri sanki biz klavyeden girmişiz gibi bir etki oluşacaktır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ subprocess.run fonksiyonunun stdout ve stdin parametreleri bir dosya nesnesi olarak girilirse çalıştırılan programın stdout ve stdin dosyaları ilgili dosyaya yönlendirilmiş olur. Aşağıdaki programda "sample.exe" programının ekrana (stdout dosyasına) yazdığı şeyler aslında "test.txt" dosyasına yazılacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess with open('test.txt', 'w') as f: cp = subprocess.run(['sample.exe'], stdout=f) #------------------------------------------------------------------------------------------------------------------------------------ Tabii yukarıdaki işlem aslında bir Python programı üzerinde de uygulanabilirdi. Aşağıdaki örnekte biz python yorumlayıcısı ile "mample.py" programını çalıştırıp onun stdout dosyasını "test.txt" yönlendiriyoruz. #------------------------------------------------------------------------------------------------------------------------------------ # sample.py import subprocess with open('test.txt', 'w') as f: cp = subprocess.run(['python.exe', 'mample.py'], stdout=f) # mample.py for i in range(100): print(i) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirttiğimiz gibi stdin dosyası klavye (terminal) aygıt sürücüsüne başka bir dosyaya yönlendirilmişse stdin dosyasından okuma programlar o dosyadakileri klavyeden girilmiş gibi ele alırlar. Python'daki input fonksiyonu stdin dosyasından okuma yapmaktadır. Aşağıdaki örnekte biz çalıştırdığımız python programının stdin dosyasını test.txt dosyasına yönlendirdik. #------------------------------------------------------------------------------------------------------------------------------------ # sample.py import subprocess with open('test.txt', 'r') as f: cp = subprocess.run(['python.exe', 'mample.py'], stdin=f) # mample.py for i in range(10): s = input() print(s) # test.txt 10 20 30 40 50 60 70 80 90 100 #------------------------------------------------------------------------------------------------------------------------------------ Spyder IDE'sinde biz subprocess.run ile bir programı çalıştırdığımızda maalesef o programın stdout dosyasına yazdığı şeyler IPython konsolunda gözükmemektedir. Ancak PyCharm IDE'sinde böyle bir sorun yoktur. Bu nedenle bu tür denemelerin bazılarını IPython'da ! karakteri ile sanki kabuk üzerindeymişsiniz gibi çalıştırabilirsiniz. Ya da denemelerinizi gerçekten kabuk üzerinden yapabilirsiniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Artık bir bir programı çalıştıran Python programı yazabildiğimize göre IDE benzeri bir program da yazabiliriz. Örneğin bir C IDE'si yazacak olalım. Bu durumda aslında menüden "Compile" seçildiğinde biz C derleyicisini çalıştırarak kodu derleyebiliriz. Sonra IDE'den "Run" seçildiğinde yine biz derlenmiş programı çalıştırabiliriz. Aşağıdaki örnekte Windows'ta "gcc" isimli C derleyicisi ile bir kod derlenip, derlenmiş olan program çalıştırılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess subprocess.run(['gcc.exe', '-o', 'sample.exe', 'sample.c']) subprocess.run('sample.exe') #------------------------------------------------------------------------------------------------------------------------------------ 59. Ders 26/07/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Biz daha önce os odlündeki system fonksiyonunu görmüştük. Bu fonksiyonla komut satırından yapabileceğimiz her şeyi yapıyorduk. Pekiyi bir programı çalıştırmak için os.system ile subprocess.run arasında ne farklılık vardır? Örneğin biz "gcc isimli C derleyicisini" çalıştırarak ilgili C programını iki biçimde de derleyebiliriz: 1) subprocess.run fonksiyonunu kullanarak import subprocess subprocess.run(['gcc.exe', '-o', 'sample.exe', 'sample.c']) subprocess.run('sample.exe') 2) os.system fonksiyonunu kullanarak: import os os.system('gcc.exe -o sample.exe sample.c') Bu iki fonksiyon arasındaki farklılıklar şunlardır: - os.system aslında kabuk programını çalıştırıp kabuk programınının komutu çalıştırmasını sağlamaktadır. Halbuki subprocess.run doğrudan belli bir programı çalıştırmaktadır. Dolayısıyla os.system aslında toplamda program çalıştırmak için daha yavaş bir yöntemdir. - os.system fonksiyonu ile komut satırında yapabilecğeimiz her şeyi yapabiliriz. Örneğin: os.system('ls -l | wc') Burada UNIX/Linux sistemlerinde komut satırından uyguladığımız boru işlemini os.system fonksiyonuna yaptırmış olduk. - Tabii asıl olan subprocess.run fonksiyonudur. Biz aslında os.system fonksiyonunun yaptığı şeyin aynısını kabuk programını çalıştırarak da yapabiliriz. Örneğin UNIX/Linux sistemlerinde os.system fonksiyonu aşağıdakine benzer yazılmıştır: import subprocess def system(cmd): cp = subprocess.run(['/bin/bash', '-c', cmd]) return cp.returncode system('ls -l') #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Kabuk programlarında iki komut türü vardır: İçsel (internal) ve dışsal (external) komutlar. Eğer bir komut doğurdan kabuğun kendisi tarafından çalıştırılıyorsa bu tür komutlara "içsel komutla" denilmektedir. Eğer komut kabuğun kendisi tarafından değil de kabuğun çalıştırdığı bir program tarafından çalıştırılıyorsa bu tür komutlara da "dışsal (external)" komutlar denilmektedir. Windows'un "cmd.exe" kabuk programında komutların çok büyük kısmı içsel komutlardır. Örneğin Windows'taki "dir" komutu bir program değildir. cmd.exe programının kendisi tarafından yorumlanıp işletilmektedir. UNIX/Linux sistemlerinde ise tam tersine kabuk komutlarının çok büyük çoğunluğu dışsal komutlardır. Örneğin bash kabuğundaki "ls" komutu aslında bir programdır. Bu komut bu programın çalıştırılmasıyla yürütülmektedir. Aşağıda içsel ve dışsal komutların ne olduğunu anlamaya yönelik basit bir komut yorumlayıcı örneği verilmiştir. Bu programı UNIX/Linux ve Windows sistemlerinde çalıştırarak deneyiniz. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess while True: cmd = input('CSD>').split() if len(cmd) == 0: continue match cmd: case ['xyz', *args]: print('internal xyz command') case ['exit']: break case [*args]: try: subprocess.run(args) except: print('bad command!') #------------------------------------------------------------------------------------------------------------------------------------ subprocess.run fonksiyonunda stdout parametresi özel bir değer olan subprocess.PIPE biçiminde geçilirse bu durumda çalıştırdığımız programın stdout dosyasına yazdıklarını biz CompletedeProcess nesnesinin stdout örnek özniteliğinden alabiliriz. Buradaki CompletedProcess sınıfının stdout örnek özniteliği bize bytes nesnesi vermektedir. Biz o bytes nesnesini decode metoduyla ya da string sınıfının encoding parametreli __init__ metoduyla string nesnesine dönüştürebiliriz. Örneğin: cp = subprocess.run('ls', stdout = subprocess.PIPE) s = cp.stdout.decode() print(s) Benzer biçimde çalıştırdığımız programın stderr dosyasına yazdıklarını biz CompletedProcess sınıfının stderr örnek özniteliğinden elde edebiliriz. Genellikle porgramlar hata mesajlarını stderr dosyasına yazdırırlar. Örneğin biz "gcc" isimli C derleyicisini çalıştırıp eğer bir hata varsa hata yazısını bu yöntemle elde edebiliriz. Bir IDE yazarken derleyici ya da yorunlayıcının hata mesajlarının alınp pencere içerisine basılması bu biçimde sağlanmaktadır. Örneğin: import subprocess cp = subprocess.run(['gcc', '-o', 'sample', 'sample.c'], stdout = subprocess.PIPE, stderr=subprocess.PIPE) stderr_result = cp.stderr.decode() print(stderr_result) Aağıdaki programda "sample.py" programı "mample.py" programını çalıştırmıştır. Ancak o programın ekrana yazdıklarını alıp kendi ekranına yazdırmıştır. Örneğin IDE'ler aslında buradaki işlemin bir benzerini yapmaktadır. Biz bir Python IDE'sinde programı çalıştır dediğimizde IDE python yorumlayıcısını çalıştırır ancak onun stdout dosyasına yazdıklarını kendi alarak kendi penceresinde gösterir. #------------------------------------------------------------------------------------------------------------------------------------ # sample.py import subprocess cp = subprocess.run(['python', 'mample.py'], stdout=subprocess.PIPE) s = cp.stdout.decode() print(s) # mample.py for i in range(10): print(i) #------------------------------------------------------------------------------------------------------------------------------------ Aslında subprocess.run fonksiyonu subprocess.Popen isimli bir sınıf nesnesini kullanmaktadır. Yani prosesi çalıştıran asıl işlevsellik bu Popen sınıfındadır. Başka bir deyişle aslında subprocess.run bir "sarma (wrapper)" fonksiyondur. subprocess.Popen sınıfının işlevselliği daha fazla olduğu için ayrıntılı işlemlerde subprocess.run yerine bu sınıfın kullanılması tercih edilebilir. Sınıfın temel kullanımı zaten benzedir. Aşağıdaki örnekte subprocess.Poğpen sınıfı kullanılmıştır. Sınıf nesnesi yaratılır yaratılmaz hemen proses çalıştırılır. Ancak Popen sınıfı bu anlamda blokeye yol açmaz. Aşağıdaki örnekte hem programın çalıştırıldığına hem de akışın devam ettiğine dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess popen = subprocess.Popen(['Notepad.exe']) print('Ok') #------------------------------------------------------------------------------------------------------------------------------------ Çalıştırılan proses bitene kadar blokede bekleme yapılmak isteniyorsa Popen sınıfının wait metody kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import subprocess p = subprocess.Popen(['Notepad.exe']) p.wait() print('Ok') #------------------------------------------------------------------------------------------------------------------------------------ Aslında IO yönlendirmeleri subprocess.run yerine doğrudan subprocess.Popen sınıfı kullanılarak daha yeterli düzeyde yapılabilir. Örneğin bu sınıfta stdout parametresi subprocess.PIPE geçilirse Popen sınıfının stdout örnek özniteliği bir dosya nesnesi olur. Biz bu dosyadan okuma yaptığımızda aslında çalıştırdığımız prosesin ekrana yazdırdıklarını okumuş oluruz. #------------------------------------------------------------------------------------------------------------------------------------ # sample.py import subprocess p = subprocess.Popen(['python.exe', 'mample.py'], stdout=subprocess.PIPE) s = p.stdout.read().decode() print(s) # mample.py for i in range(10): print(i) #------------------------------------------------------------------------------------------------------------------------------------ Biz Popen nesnesinin stdout örnek özniteliği ile dosyadan okuma yapmak istediğimizde eğer çalıştırılan proses stdout dosyasına bizim okumak istediğimiz kadar bilgiyi henüz yazmamışsa biz blokede bekleriz. Çünkü boru işlemleri default olarak blokeli işlemlerdir. Aşağıdaki örnekte "sample.py" programı yine "mample.py" programını çalıştırmıştır. "mample.py" programı birer saniye bekleyerek stdout dosyasına sayıları yazdırmaktadır. "sample.py" programı eğer p.stdout.read() ile okuma yapsaydı dosya sonuna gelene kadar bloke oluşacaktı. Çünkü dosya sonuna gelmek borularda ancak diğer prosesin sonlanmasıyla mümkündür. Halbuki burada birer satırlık bilgi okunmuştur. while döngüsündeki not p.poll() işlemi proses sonlanmayana kadar döngüyü devamettirmektedir. #------------------------------------------------------------------------------------------------------------------------------------ # sample.py import subprocess p = subprocess.Popen(['python.exe', 'mample.py'], stdout=subprocess.PIPE) while not p.poll(): s = p.stdout.readline().decode() print(s) # mample.py import time for i in range(10): print(i, flush=True) time.sleep(1) #------------------------------------------------------------------------------------------------------------------------------------ Normal olarak bir işin birden fazla thread tarafından yapılması hız kazancı sağlar. Ancak CPython gerçekleştirimindeki GIL yüzünden çok thread'li çalışma umulduğu kadar hız kazancı sağlamamaktadır. Bir işin farklı thread'ler yaratılarak değil farklı prosesler yaratılarak yapılması alternatif bir yöntemdir. C, C++, Java, C# gibi programlama dillerinde multithreading çözümler multiprocess çözümlere göre çok daha etkindir. Ancak GIL yüzünden CPython gerçekleştiriminde multiprocess çalışma çoğu zaman multithreading çalışmadan daha hızlı olmaktadır. Python'da multiprocess çalışma demekle birden fazla biribirinden bağımsız python yorumlayıcısının ayrı programlar olarak çalıştırılması kastedilmektedir. Daha önceden değindiğimiz gibi nomral olarak multiporocess çalışmanın muştithreading çalışmaya göre şu dezavantajları vardır: - Prosesler thread'lere göre sistem genelinde daha fazla kaynağın kullanılmasına yol açmaktadır. - Proseslerin yaratılması ve yok edilmesi thread'lerin yaratılması ve yok edilmesine göre daha yavaştır. - Thread'ler aynı adres alanı içerisinde çalıştığından dolayı thread'lerin haberleşmesi çok daha kolaydır. Halbuki prosesler pek çok sistemde biribirinden izole edilmiştir. Dolayısıyla proseslerarası haberleşme thread'lerarası haberleşmeden çok daha yavaştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Standart kütüphanedeki multiprocessing modülünde bulunan Process sınıfı genel kullanım itibari ile threading modülündeki Thread sınıfına benzemektedir. Programcı Process sınıfı türünden bir nesne yaratır. Nesneyi yaratırken yine yeni proses akışının başlatılacağı fonksiyonu target parametresiyle belirtir. Yine args parametresi ile parametre aktarımı yapılır. Proses nesnesine yine bir name parametresi ile isim verilebilir. Prosesi çalıştırmak için yine start metodu, sonlanmasını beklemek için join metodu kullanılmaktadır. join metodu alt processin kaynaklarını da boşaltmaktadır. Yani join metodu çağrıldığında üst proses alt proses eğer sonlanmamışsa sonlana kadar bekler ve onun kaynaklarını boşaltır. Bir alt proses yaratılıp join ile beklenmezse bu tür durumlara UNIX/Linux dünyasında "hortlak proses (zombie process)" denilmektedir. Ancak bazen bir alt proses yaratılıp onun sonlanmasını beklemek istemeyebiliriz. Bu tür durumlarda proses yaratılırken beklemenin yapılmayacağı belirtilmelidir. Bunu Python'da sağlamanın iki yolu vardır. Birinci process sınıfında proses yaratılırken daemon parametresini True geçmektir. İkincisi proses yaratıldıktan sonra daemon örnek özniteliğine False yerleştirmektir. Görüldüğü gibi genel çalışma biçimi daha önce görmüş olduğumuz thread'lere benzemektedir. Ancak burada target parametresi ile belirtilen fonksiyonun başka bir Python yorumlayıcısı tarafından tamamen başka bir proses gibi çalıştırıldığına dikkat ediniz. Process sınıfı ile işlemler yapılırken __name__ == '__main__' kontrolünün bazı nedenlerden dolayı yapılması gerekmektedir. Biz burada neden programın başlatılacağı yerde bu kontrolün yapılması gerektiğini açıklamayacağız. #------------------------------------------------------------------------------------------------------------------------------------ import time import multiprocessing def foo(n): for i in range(n): print(f'Other process: {i}') time.sleep(1) if __name__ == '__main__': process = multiprocessing.Process(target=foo, args=(10, )) process.start() for i in range(10): print(f'Parent process: {i}') time.sleep(1) #------------------------------------------------------------------------------------------------------------------------------------ Daha önce CPython'da 1 milyarlık bir döngüyü tek bir thread'le ve 250 milyonluk dört ayrı thread'le dönen bir test programı yazmıştık. O programda her iki test de biribirine yakın sonuç vermişti. Aşağıdaki örnekte 1 milyarlık döngü hem tek bir thread'le, hem dört prosesle hem de dört thread'le döndürülmüş ve sonuçlar ekrana (stdout dosyasına) yazdıırlmıştır. Şu sonuçlar elde edilmiştir: Singlethreding: 13.071293354034424 Multiprocessing: 3.864476442337036 Multithreading: 13.19014859199524 #------------------------------------------------------------------------------------------------------------------------------------ import threading import multiprocessing as mp import time def proc1(): for i in range(250_000_000): pass def proc2(): for i in range(250_000_000): pass def proc3(): for i in range(250_000_000): pass def proc4(): for i in range(250_000_000): pass def test1(): t1 = time.time() for i in range(1_000_000_000): pass t2 = time.time() print(f'Singlethreading: {t2 - t1}') def test2(): t1 = time.time() process1 = mp.Process(target=proc1) process2 = mp.Process(target=proc2) process3 = mp.Process(target=proc3) process4 = mp.Process(target=proc4) process1.start() process2.start() process3.start() process4.start() process1.join() process2.join() process3.join() process4.join() t2 = time.time() print(f'Multiprocessing: {t2 - t1}') def test3(): t1 = time.time() thread1 = threading.Thread(target=proc1) thread2 = threading.Thread(target=proc2) thread3 = threading.Thread(target=proc3) thread4 = threading.Thread(target=proc4) thread1.start() thread2.start() thread3.start() thread4.start() thread1.join() thread2.join() thread3.join() thread4.join() t2 = time.time() print(f'Multithreading: {t2 - t1}') if __name__ == '__main__': test1() test2() test3() #------------------------------------------------------------------------------------------------------------------------------------ Thread'lerin haberleşmesi global nesneler yoluyla yapılabilmektedir. Yani bir thread bir global değişkene bir değer yazdığında diğeri onu görebilir. Gerçi bu tür uygulamalarda senkronizasyon da bir problemdir. Ancak haberleşme kısmı thread'lerde global nesneler yoluyla yapılabilmektedir. Aşağıdaki örnekte ana thread bir thread yaratıp bir global değişkeni set etmiştir. Yaratılan thread de aynı global değişkeni görebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import threading import time val = 0 def thread_proc(): time.sleep(1) print(val) # 100 thread = threading.Thread(target=thread_proc) thread.start() val = 100 thread.join() #------------------------------------------------------------------------------------------------------------------------------------ Prosesler ayrı bellek alanlarına sahiptir. Biz multiprocessing.Process sınıfında target parametresiyle proses akışının hangi fonksiyondan başlatılacağını vermekteyiz. Ancak bu fonksiyon başka bir proses tarafından çalıştırılmaktadır. Dolayısıyla aslında global nesneler farklı proseslerin farklı global nesneleri olur. Yukarıdaki örnek proseslerle yapıldığında üst proses val değişkenini set ettiğinde alt proses bunu görmeyecektir. Çünkü aslında bu iki prosesin val değişkenleri kendilerine özgü ayrı değişkenlerdir. Bu nedenle prosesleri haberleştirmek için "Queue" gibi "Pipe" gibi "Shared Memory" gibi özel yöntemler kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing import time val = 0 def proc(): time.sleep(1) print(val) # 0 if __name__ == '__main__' : process= multiprocessing.Process(target=proc) process.start() val = 100 process.join() #------------------------------------------------------------------------------------------------------------------------------------ Proseslerarası haberleşme threadlerarası haberleşmeden daha maliyetlidir. Proseslerarası haberleşme için multiprocessing modülünde çeşitli sınıflar bulundurulmuştur. multiprocessing.Queue sınıfının kullanımı daha önce thread'ler konusunda üretici-tüketici problemi için kullandığımız queue modülündeki Queue sınıfına çok benzemektedir. queue modülündeki Queue sınıfı thread'ler arası bir haberleşme için kullanılırken multiprocessing modülündeki Queue sınıfı proseslerarası haberleşme için kullanılmaktadır. Proseslerle üretici-tüketici problemi için aşağıdaki gibi bir örnek verilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp import time import random def consumer_proc(q): while True: val = q.get() time.sleep(random.random() / 2) print(f'{val}', end=' ', flush=True) if val == 99: break if __name__ == '__main__': q = mp.Queue() cp = mp.Process(target=consumer_proc, args=(q, )) cp.start() i = 0 while True: time.sleep(random.random() / 2) q.put(i) if i == 99: break i += 1 cp.join() #------------------------------------------------------------------------------------------------------------------------------------ Gerek thread'lerde kullandığımız queue.Queue sınıfı gerekse prosesler için kullandığımız multiprocessing.Queue sınıfı birden fazla tüketici ile çalışabilmektedir. Aşağıdaki örnekte üretici-tüketici problemi iki tane tüketici ile çözülmüştür.Tüketici birden fazla olduğu zaman işlemin ne zaman bitecğini belirlemek ve tüketicileri döngüden çıkartmak ayrı bir sorun olabilir. Çünkü kuyruktaki değere göre prosesler döngüyü sonlandıracaksa bu değer iki tüketici tarafından da alınamamaktadır. Yani tüketicilerden yalnızca biri bu değeir alabilecek ve döngüen çıkabilecektir. Bu tür problemlerin çözümleri için ilave senkronizasyon nesnelerinin (koşul değişkenleri gibi) kullanılması gerekebilir. Aşağıdaki örnekte bu problem ana prosesin işlem bittikten sonra diğer prosesleri kill metoduyla yok etmesiyle sağlanmıştır. Aslında bu sağlam bir çözüm değildir. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp import time import random def consumer_proc1(q): while True: val = q.get() time.sleep(random.random() / 2) print(f'consumer1 ---> {val}') if val == 99: break def consumer_proc2(q): while True: val = q.get() time.sleep(random.random() / 2) print(f'consumer2 ---> {val}') if val == 99: break if __name__ == '__main__': q = mp.Queue() cp1 = mp.Process(target=consumer_proc1, args=(q, )) cp2 = mp.Process(target=consumer_proc2, args=(q, )) cp1.start() cp2.start() i = 0 while True: time.sleep(random.random() / 2) q.put(i) if i == 99: break i += 1 time.sleep(1) cp1.kill() cp1.kill() #------------------------------------------------------------------------------------------------------------------------------------ 60. Ders 31/07/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Proseslerarasında haberleşme için diğer bir yöntem de "boru (pipe) haberleşmesi" denilen yöntemdir. Boru haberleşmesi yöntemi multiprocessing modülündeki Pipe isimli sınıf yoluyla uygulanmaktadır. Aslında Pipe kullanımı ile Queue kullanımı ana hatlarıyla biribirine benzemektedir. Ancak Queue birden fazla üretici-tüketici prosesler arasında kullanılabilirken Pipe nesneleri iki proses arasında kullanılmaktadır. Genel olarak Pipe kullanımı iki proses arasında haberleşme yapılıyorsa Queue kullanımından daha hızlıdır. Pipe kullanımı şöyledir: 1) Pipe sınıfı türünden bir nesne yaratılır. Bu nesne bir demet biçiminde iki Conenction nesnesi verir. Borular tek yönlü (unidirectional) ya da çift yönlü (bidiectional) olabilmektedir. Default durumda yaratılan borular çift yönlüdür. Eğer Pipe nesnesi yaratılırken duplex parametresi False geçilirse bu durumda boru tek yönlü olur. Pipe fonksiyonun bize verdiği demetin ilk elemanı okuma yapmak için, ikinci elemanı yazma yapmak için kullanılmaktadır. Çift yönlü borularda her iki taraf da hem okuma hem yazma yapabilmektedir. Programcı bu connection nesnelerinin birini yarattığı prosese geçirir diğerini kendisi kullanır. 2) Connection sınıfının send metodu ile boruya bilgi yazılabilir, recv metodu ile borudan bilgi okunabilir. Genel olarak send metodu herhangi bir türden bilginin boruya yazılmasını sağlamaktadır. recv metodu da gönderilmiş olan herahngi türden bir bilginin okunmasını sağlamaktadır. Aşağıdaki örnekte iki yönlü bir boru oluşturulmuştur. Üst proses boruya bazı işeyler yazmış alt proses de borudan bunları okumuştur. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp def proc(conn): while True: val = conn.recv() if val == 'exit': break print(val) if __name__ == '__main__': conn1, conn2 = mp.Pipe() process = mp.Process(target=proc, args=(conn2, )) process.start() conn1.send('Ali') conn1.send([1, 2, 3, 4, 5]) conn1.send(1.2) conn1.send('exit') process.join() #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte üst proses boruya send metoduyla 0'dan 100'e kadar sayıları yazmakta ve alt proses de bu sayıları recv metoduyla okuyup ekrana (stdout dosyasına) yazdırmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing def proc(conn): while True: val = conn.recv() print(val, end=' ') if val == 99: break if __name__ == '__main__': conn1, conn2 = multiprocessing.Pipe() process = multiprocessing.Process(target=proc, args=(conn2, )) process.start() for i in range(100): conn1.send(i) process.join() #------------------------------------------------------------------------------------------------------------------------------------ Prosesler arasında bellek izolasyonu olduğu için biz Pipe gibi Queue gibi özel yöntemlerle iki proses arasında veri aktarımı yapmaktayız. İki proses arasında veri aktarımının bir diğer yolu da "shared memory" denilen tekniktir. Python'da "shared memory" Value ve Array sınıflarıyla gerçekleştirilmiştir. Shared memory tekniği aslında zahmetli bir kodlamayı gerektirse de Python'da multiprocessing modülündeki Value ve Array sınıfları ile kolay bir biçimde gerçekleştirilebilmektedir. Value sınıfınun kullanımı şöyleidr: 1) Value sınıfı türünden bir nesne yaratılır. Nesne yaratırken ona bir "type code" ve nesnenin içereceği ilkdeğer verilir. 2) Bu Value nesnesi diğer prosese parametre yoluyla aktarılır. 3) Value sınıfının value isimli örnek özniteliği paylaşılan nesneyi temsil eder. Bir proses ona atama yaptığında diğeri onu atanmış görmektedir. Yani bu value özniteliği aslında "shared memory" içerisindeki nesneyi belirtmektedir. Value nesnesi yaratılırken birinci parametrede belirtilen "type code" özel bazı türleri temsil eden yazılardır. Örneğin 'i' int türünü 'f' float türünü, 'd' double türünü temsil eder. Value sınıfı temelde C ile yazıldığı için buradaki type code C dilindeki türlere ilişkindir. Örneğin Python'ın str türüne ilişkin bir type code yoktur. Buradaki 'i', 'f' ve 'd' C'deki türlerdir. C'deki int türü Python'daki gibi sınırsız uzunluğa sahip değildir. Genellikle 4 byte uzunluktadır. C'de double türü Python'daki float türüne karşı gelmektedir. C'deki float türünün Python'da bir karşılığı yoktur. Ancak tabii bir Value sınıfının value örnek özniteliği ile değeri aldığımızda bu C'deki değer Python türüne dönüştürülerek bize verilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing def proc(val): print(val.value) val.value = 200 if __name__ == '__main__' : val = multiprocessing.Value('i', 100) process= multiprocessing.Process(target=proc, args=(val, )) process.start() process.join() print(val.value) #------------------------------------------------------------------------------------------------------------------------------------ multiprocessing modülündeki Array isimli sınıf aslında Value sınıfının dizisel biçimidir. Bir Array nesnesi yaratıldığında bu nesne yaratılan prosese parametre olarak geçilirse proses hem bu değerleri kullanabilir. Hem de bu dizinin elemanlarını değiştirirse diğer proses onları değişmiş görür. Yani paylaşılan bellek alanında bir dizi yaratılmış olur. Array nesnesi yaratılıken de yine bir "type code" belirtilmektedir. Yine buradaki type code C Programlama Dilindeki türlere ilişkindir. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing def proc(a): for i in range(len(a)): print(i) a[i] = i * 10 if __name__ == '__main__' : a = multiprocessing.Array('i', [1, 2, 3, 4, 5]) process= multiprocessing.Process(target=proc, args=(a, )) process.start() process.join() for x in a: print(x) #------------------------------------------------------------------------------------------------------------------------------------ Shared memory tekniği işletim sistemleri dünyasında en hızlı proseslerarası haberleşme yöntemidir. Ancak bu yöntem kendi içerisinde bir senkronizasyon içermemektedir. Yani proseslerden biri bu paylaşılan bellek alanına bir şeyler yazdığında diğeri bunu uygun zamanda okuması gerekir. Bu biçimde pek çok veri aktarılacaksa "üretici-tükestici" problemi uygulanmalıdır. Ancak zaten modüldeki Queue ve Pipe sınıfları bu tarz bir senkronizasyonu kendiliğinde içermektedir. Python'daki Value ve Array sınıfları genellikle birtakım bilgilerin tek seferlik hızlı ve basit bir biçimde aktarılması için kullanılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Proseslerarasında da senkronizasyonlar gerekebilmektedir. Bunun için threading modülündeki senkronizasyon nesnelerinin tamamen benzerleri multiprocessing modülünde de bulundurulmuştur. Yani biz thread'ler arasında kullandığımız senkronizasyon nesnelernin benzerlerini prosesler arasında da kullanabilmekteyiz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ threading modülündeki Lock sınıfının tamamen benzeri multiprocessing modülünde de prosesleri senkronize etmek için bulunmaktadır. Tabii proseslerarasında kullanım için Lock nesnesinin alt prosese args parametresiyle geçirilmesi gerekmektedir. Aşağıdaki örnekte üst proses alt prosesin kilidi ele geçirmesine izin vermiştir. Alt proses kilidi alarak 5 saniye bekledikten sonra kilidi bırakmıştır. Üst proses alt proses kilidi bıraktıktan sonra kilidi alabilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp import time def proc(lock): lock.acquire() print('child process locked') time.sleep(5) lock.release() if __name__ == '__main__': lock = mp.Lock() process= mp.Process(target=proc, args=(lock, )) process.start() time.sleep(1) lock.acquire() print('parent process locked') lock.release() process.join() #------------------------------------------------------------------------------------------------------------------------------------ Tabii farklı proseslerde senkronizasyonun anlamlı olabilmesi için farklı proseslerin ortak bir kaynağı kullanıyor olması gerekir. Bu ortak kaynak bellekte bir nesne ise bu nesnenin de prosesler arasında paylaşılıyor olması gerekir. Aşağıdaki örnekte multiprocessing.Lock nesnesi kullanılarak kritik kod oluşturulmuştur. Bu koddaki do_something fonksiyonu iki proseste de bulunan ancak iki farklı programın çalıştırdığı bir fonksiyondur. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp import time import random def do_something(s, lock): lock.acquire() print(f'{s}: 1.Step') time.sleep(random.random() / 2) print(f'{s}: 2.Step') time.sleep(random.random() / 2) print(f'{s}: 3.Step') time.sleep(random.random() / 2) print(f'{s}: 4.Step') time.sleep(random.random() / 2) print(f'{s}: 5.Step') time.sleep(random.random() / 2) print('---------------------------') lock.release() def proc(lock): for i in range(10): do_something('Child Process', lock) if __name__ == '__main__': lock = mp.Lock() process= mp.Process(target=proc, args=(lock, )) process.start() for i in range(10): do_something('Parent Process', lock) process.join() #------------------------------------------------------------------------------------------------------------------------------------ threading modülündeki event nesnelerinin de multiprocessing modülünde benzeri bulunmaktadır. Tabii yine Event nesnesinin altg prosese args parametresiyle aktarılması gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp import time def proc(event): print('Child process runs...') time.sleep(5) print('Child reached important point') event.set() if __name__ == '__main__': event = mp.Event() process= mp.Process(target=proc, args=(event, )) process.start() print('Parent waits for child...') event.wait() print('Ok, parent continues...') process.join() #------------------------------------------------------------------------------------------------------------------------------------ Yine threading modülündeki Semaphore sınıfının bir benzeri multiprocessing modülünde de bulundurulmuştur. Aşağıdaki örnekte binary semaphore ile proseslerarasında kritik kod oluşturulmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import multiprocessing as mp import time import random def do_something(s, sem): sem.acquire() print(f'{s}: 1.Step') time.sleep(random.random() / 2) print(f'{s}: 2.Step') time.sleep(random.random() / 2) print(f'{s}: 3.Step') time.sleep(random.random() / 2) print(f'{s}: 4.Step') time.sleep(random.random() / 2) print(f'{s}: 5.Step') time.sleep(random.random() / 2) print('---------------------------') sem.release() def proc(lock): for i in range(10): do_something('Child Process', lock) if __name__ == '__main__': sem = mp.Semaphore(1) process= mp.Process(target=proc, args=(sem, )) process.start() for i in range(10): do_something('Parent Process', sem) process.join() #------------------------------------------------------------------------------------------------------------------------------------ Proseslerarası haberleşme (interprocess communication ya da kısaca IPC) kabaca ikiye ayrılmaktadır. 1) Aynı makinenin prosesleri arasında haberleşme 2) Farklı makinelerin prosesleri arasında haberleşme Aynı makinenin prosesleri arasında haberleşmede işletim sistemleri tarafından sunulan "shared memory", "message queue", "pipe" gibi yöntemler kullanılmaktadır. (Biz de multiprocessing modülü sayesinde Python'da yüksek seviyeli bir biçimde bu mekanizmaları kullababilmekteyiz.) Farklı makinelerin prosesleri arasında haberleşme için önceden belirlenmiş birtakım kurallara uyulması gerekmektedir. Haberleşmede uyulması gereken kurallara "protokol" denilmektedir. Çeşitli protokol aileleri vardır. Bugün için en yaygın kullanılan protokol ailesi "IP Protokol Ailesi (IP Protocol Family)" denilen ailedir. IP prototokol ailesi aynı zamanda Internet'tin de kullandığı protokol ailesidir. Farklı makinelerin prosesleri arasında uzak mesafe haberleşme ilk kez 1969 yılında denenmiştir. Soğuk savaş yıllarında ABD Savunma Bakanlığına bağlı DARPA denilen kurumun ARPA denilen bölümü ismine ARPANET denilen bir proje başlatmıştır. ARPANET ABD savunmasında kullanılan bilgisayarların dağıtık hale getirilmesini hedeflemekteydi. Proje ABD Savunma Bakanlığının yanı sıra birkaç üniversite tarafından yürütülmekteydi. Sonraları bu ARPANET genişlemeye başladı. ABD'dedeki diğer hükümet kurumları ve üniversiteler bu ağa bağlandılar. O zamanlar bu ağ NCP denilen bir protokol kullanıyordu. ARPANET sonraları Avrupa'ya sıçradı ve 80'li yılların ortalarına doğru Türkiye'ye de geldi. 1983 yılında ARPANET NCP protokolünü bırakarak IP protokol ailesine geçmiştir. ARPANET daha sonraları Internet ismini almıştır. Internet ismi "internetworking" sözcüğünden uydurulmuştur. Internetworking ağların birbirlerine bağlanması anlamına gelmektedir. Bugünkü Internet'te aslında çeşitli küçük ağlar birbirleriyle bağlanarak büyük bir ağı oluşturmaktadır. Ağları biribirine bağlamak için "Router" denilen aygıtlar kullanılmaktadır. Bugün evlerimizde kullandığımız modemler aynı zamanda router özelliğine de sahiptir. Protokol aileleri üst üste yığılmış olan protokollerden oluşmaktadır. Her üste yığılmış protokole "katman (layer)" denilmektedir. Her katmandaki protokol "aşağıdaki katmanların zaten var olduğu fikriyle daha yüksek seviyeli" kuralları tanımlamaktadır. Protokol ailelerini oluşturmak için ISO ve bazı kurumlar tarafından ismine "OSI Reference Model" denilen bir model oluşturulmuştur. OSI model bir protokol değildir. Protokol ailelerini oluşturacaklar için bir kılavuz niteliğindedir. OSI modelde toplam 7 katman üst üste yığılmıştır. OSI'nin 7 katmanı şöyledir: Uygulama Katmanı (Application layer) Açıklama Katmanı (Presentation Layer) Oturum Katmanı (Session Layer) Aktarım Katmanı (Transport Layer) Ağ Katmanı (Network Layer) Veri Bağlantı Katmanı (Data Link Layer) Fiziksel Katman (Physical Layer) Fiziksel katman iletişimde kullanılacak tüm donanım birimlerinin speklerini belirtmektedir. Veri Bağlantı Katmanı birimlerin (bilgisayarların) birbirlerini tanımaları için gereken kuralları barındırmaktadır. Veri bağlatı katmanında ağa bağlı her birimin bir fiziksel adresi olmalıdır. Bugün bilgisayarlarımızda kullandığımız Ethernet Kartı diye bilinen network kartlarının protokolü olan Ethernet Protokolü OSI'min Data Link Layer katmanına ilişkindir. Ağ Katmanı "internetworking" için gereken ana protokollerin bulunduğu katmandır. IP Protokol ailesindeki IP (Internet Protocol) protokolü OSI'nin "Ağ Katmanına" ilişkn bir protokoldür. Bu katmandaki protokoller artık ağa bağlı birimlere fiziksel değil mantıksal bir adres vermektedir. Bu katmandaki protokoller bilgilerin nasıl paketlere ayrılıp nasıl iletileceği konusundaki ayrıntıları tanımlamaktadır. Aktarım katmanı aynı birime giden paketlerin orada ayrıştırılması ve bir araya getirilmesine ilişkin kuralları tanımlaayan protokolleri içermektedir. Örneğin IP protokol ailesindeki TCP ve UDP tipik olarak Aktarım Katmanı protokolleridir. İletişimde bir "oturum (session)" oluşturmak gerekebilir. Oturum katmanı bunu oluşturmaktadır. Bilgilerin şifrelenemsi, sıkıştırılması gibi faaaliyetler Açıklama Katmanı protokolleri tarafından yapılmaktadır. Nihayet uygulama katmanı protokolleri kullanıcının kullandığı programların doğrudan kullandığı protokollerdir. Örneğin IP ailesindeki HTTP, Telnet, SSH, POP3, IMAP gibi protokoller OSI'nin uygulama katmanına ilişkindir. Her ne kadar OSI 7 katmanlı bir protokol ailesi tanımlamışsa da IP Protokol ailesi OSI'nin 7 katmanını kullanmamaktadır. IP protokol ailesi 4 katmanlı bir protokol ailesidir. IP Protokol ailesinin temel protokolleri şöyle oluşturulmuştur: HTTP, Telnet, (OSI'nin Application Layer) SSH, Pop3, IMAP, .... TCP UDP (OSI'nin Transport Layer) IP protokolünü (OSI'nin Network Layer) Ethernet/Wireles Protokolü (OSI'nin Physical Layer + Data Link Layer) Tabii IP Protokol ailesinde daha pek çok yardımcı protokoller de vardır. Biz yukarıda yalnızca temel protokolleri belirttik. OP Protokolü aileye ismini veren en önemli protokoldür. Bu protokol bilgilerin paketlere ayrılması, rotalanması gibi tanımlamaları barındırmaktadır. IP protokolünde ağa bağlı olan her birime "host" denilmektedir. Her host'un isminme IP numarası denilen mantıksal bir adresi vardır. IP Protokolünün iki önemli versiyonu vardır: IPV4 ve IPV6. IPV4'te IP numaraları 4 byte uzunluktadır. Anc ak bu 4 byte'lık IP numaraları zamanla yetersiz kalmaya başlamıştır. IPV6'da IP numaraları 16 byte uzunluğundadır. Bugün hem IPV4 hem de IPV6 aynı anda kullanılmaktadır. Yine ağırlıklı kullanım halen IPV4'tür. TCP (Transmission Control Protocol) IP ailesindeki ağırlıklı kullanılan transport protokolüdür. IP ailesinin HTTP, SSH, Telnet, POP3 gibi protokollerinin hepsi TCP üzerine oturtulmuştur. IP protokol ailesinde doğrudan IP protokolü ile işlemler çok seyrek yapılmaktadır. Genellikle uygulamacılar TCP protokolünü kullanmaktadır. TCP kullanımına halk arasında TCP/IP de denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ 62. Ders 02/08/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ IP protokolünün üzerine TCP (Transmission Control Protocol) ve UDP (User Datagram Protocol) protokolleri oturtulmuşur. Yukarıda da belirttiğimiz gibi ağırlıklı olarak TCP protokolü kullanılmaktadır. TCP "stream tabanlı (stream based)" UDP ise "paket tabanlı (datagram)" bir haberleşme sunmaktadır. Stream tabanlı haberleşe demekle "byte'ların kuyruk sistemi gibi sıraya dizildiği ve istenilen miktarda byte'ın peşi sıra okunabildiği" hanerleşme modeli anlaşılmaktadır. TCP aslında IP protokolünü kullanır yani TCP ile bilgi gönderilirken aslında bilgi IP paketlerine bölünür bu IP paketleri gönderilir. Ancak IP paketleri peşi sıra gönderilse bile aynı sırada alınmak zorunda değildir. İşte TCP protokolünde bu IP paketleri içerisindeki TCP verileri hedefte yeniden birleştirlerek sanki bir grup byte'mış gibi okunamaktadır. Dolayısıyla biz TCP'de çalışırken "sanki bir byte yığını varmış da oradan sırasıyla istediğimiz byte'ı okuyormuşuz gibi" bir durum oluşturulmaktadır. UDP ise "paket tabanlı (ya da datagram tabanlı)" bir haberleşme sunmaktadır. UDP aslında IP protokolüne benzemektedir. UDP'de gönderen taraf bir grup bilgiyi bir paket olarak gönderir. Alan taraf da bu paketi alır. Alan taraf "önce 5 byte sonra 10 byte gibi okumalar" yapamaz. Alan taraf gelen paketi bütünsel olarak almaktadır. TCP "bağlantılı (connection oriented)" bir protokoldür. TCP ile haberleşmeden önce iki taraf biribirine bağlanır. Burada bağlanmak demekle "iki tarafın birbirlerinin farkında olması ve hangi durumda olduğunu bilmesi" kastedilmektedir. TCP haberleşmesinin başlaması için bir tarafın karşı tarafa bağlanmayı istesi ve karşı tarafın da bunu kabul etmesi gerekir. Bağlantı "client-server" tarzı bir haberleşme modelini akla getirmektedir. Client-server çalışma modelinde client önce server'a bağlanır. Sonra server'dan birtakım isteklerde bulunur. Server da bu istekleri yerine getirir. Sonuçları client'a yollar. Bu durumda TCP/IP uygulama yazarken "client" ve "server" olmak üzere iki ayrı programın yazılması gerekmektedir. UDP ise "bağlantısız (connectionless)" bir protokoldür. Bağlantısız protokollerde gönderen ve alan arasında özel bir ilişki yoktur. Gönderen paketi gönderir. Alanın da paketi alıp almadığını bilmez. TCP "güvenilir (reliable)" bir protokoldür. Bir protokolün güvenilir olması demek haberleşme sırasında bazı paketlerin yolda kaybolması durumunda bile bunların yeniden istenerek telafi edilmesi demektir. Gönderen taraf alan tarafın bilgiyi aldığını bilmektedir. Eğer alan taraf bilgiyi almamışsa (örneğin bilgi yolda kaybolmuşsa) gönderen taraf onu yeniden göndererek telafi etmektedir. Güvenilirliğin sağlanması için gönderen ve alan tarafın karşılıklı bir "akış kontrolü (flow control)" uygulaması gerekir. Bir taraf sürekli bilgi gönderdiğinde karşı atarfın tamponu dolarsa gönderen taraf artık blgi göndermeyip onun tanponun uygun biçimde boşalmasını beklemektedir. UDP "günenilir olmayan (unreliable)" bir protokdür. UDP'de gönderen paketi gönderir anca alanın bunu alıp almadığını bilmez. Dolayısıyla paket yolda kaybolursa bunun da bir telafisi yapılmaz. TCP protokolü UDP protokolüne göre daha güvenilir ancak daha yavaştır. UDP'de bir akış kontrolü olmadığı için paketlerin karşı tarafta birleştirilmesi gibi işlemler olmadığı için UDP hızlıdır. Ancak UDP'nin güvenilebilir olmaması ve stream tabanlı olmaması onun seyrek kullanılmasına yol açmaktadır. IP protokol ailesinin HTTP gibi, POP3 gibi, IMAP gibi, FTP gibi uygulama katmanı protokolleri hep TCP kullanmaktadır. IP protokülünde ağa bağlı olan birimlere "host" denilmektedir. Bir host bir bilgisayar olabileceği gibi bir yazıcı, kamera vs. olabilir. Her host'un bir IP numarası vardır. IP paketleri kaynak host'tan hedef host'a gönderilmektedir. Ancak hedef host'a gelen bir IP paketi orada hangi programa iletilecektir? IP protokolü bununla ilgilenmemiştir. İşte bu durum TCP ve UDP protokolerrinde "protocol port numarası" denilen yöntemle ele alınmaktadır. TCP ve UDP protokollerinde bir host'a giden bilgiler bilgilere bir port numarası da iliştirilir. Hedef host'a gelen bilgiler o port'la hangi program ilgileniyorsa o programa iletilmektedir. Yani "port numarası" adeta bir şirketin "içsel hat numaralarına" benzemektedir. Biz TCP ve UDP'de yalnızca hedef host'un IP numarası ile bilgiyi göndermeyiz. Bilgiyi belli bir host'un belli port'una göndeririz. Port numaraları [0, 65535] arasındadır. İlk 1024 port numarası IP protokol ailesinin uygulama katmanındaki protokolleri için ayrılmıştır. Dolayısıyla programcıların server yazarken ilk 1024 port nmarasını kullanmaması tavsiye edilir. Bu ilk 1024 port numarasına İngilizce "well known ports" denilmektedir. Örneğin sizin de duyduğunuz bazı uygulama katmanı protokollerin port numaraları şöyledir: HTTP: 80 FTP: 20, 21 TFTP: 69 SSH: 22 TELNET: 23 SMTP: 25 POP3: 110 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Protokol ailelerini işleten kodlar işletim sistemlerinin içerisinde çekirdeğin parçası biçiminde bulunurlar. Ancak onların kullanılabilmesi için kütüphanelere gereksinim vardır. Bunun için kullanılan ve ilk kez BSD UNIX sistemlerinde gerçekleştirilen kütüphaneye "soket kütüphanesi (socket library)" denilmektedir. Windows bu BSD soket kütüphanesini kendine özgü değişik bir biçimini de oluşturmuştur. Microsoft'un BSD soket kütüphanesi temel alınarak oluşturduğu bu soket kütüphanesinde "Winsock Kütüphanesi" denilmektedir. macOS sistemleri BSD soket kütüphanesini aynı biçimde desteklemektedir. Soket kütüphanesi Python'da "nesne yönelimli biçimde" bir modül olarak oluşturulmuştur. Python'ın bu soket kütüphanesi aslında arka planda UNIX/Linux ve macOS sistemlerinde BSD soket kütüphanesini, Windows sistemlerinde ise Winsock kütüphanesini kullanmaktadır. Başka bir deyişle Python'daki soket kütüphanesi aslında BSD soket kütüphanesini sarmalamaktadır. Yukarıda da belirttiğimiz gibi soket kütüphanesi özellikle IP protokol ailesi için oluşurulmuş bir kütüphane değildir. Soket kütüphanesi protokol aileleri için ortak bir arayüz oluşturmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirttiğimiz gibi TCP/IP soket uygulamalarında "server" ve "client" programların ayrı ayrı yazılması gerekmektedir. Biz burada önce TCP server programının sonra da TCP client programının nasıl yazaılacağını göreceğiz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ TCP server programda tipik olarak işlemler aşağıdaki adımlarla gerçekleştirilir: 1) Soket nesnesi yaratılır. 2) Soket bağlanır (bind edilir) 3) Soket dinleme konumuna sokulur 4) Bağlantı istekleri kabul edilir 5) Gönderme ve alma işlemleri yapılır 6) Soket hutdown edilir 7) Soket kapatılır. Soket işlemleri için socket isimli sınıf kullanılmaktadır. soket sınıfının __init__ metodunun parametrik yapısı şöyledir: socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None) Fonksiyonun birinci parametresi hangi protokol ailesi ile çalışılacağını belirtmektedir. Bu parametre socket.AF_INET geçilirse "IPV4" ailesi, socket.AF_INET6 geçilirse "IPV6" ailesi anlaşılmaktadır. Bu parametrenin default değerinin socket.AF_INET olduğuna dikkat ediniz. Fonksiyonun ikinci parametresi soket türünü belirtmektedir. Bu parametre TCP için socket.SOCK_STREAM, UDP için socket.SOCK_DGRAM girilmelidir. Bu parametrenin de default değerinin socket.SOCK_STREAM biçiminde olduğuna dikkat ediniz. Fonksiyonun üçüncü parametresi kullanılacak "aktarım katmanı (transport layer)" protokolünü belirtmektedir. Bu parametre TCP için socket.IPPROTO_TCP, UDP için socket.IPPROTO_UDP biçiminde girilebilir. Aslında fonksiyonun ikinci parametresi zaten IP protokol ailesi için üçüncü parametrenin ne olacağını anlatmaktadır. Dolayısıyla IP protokol ilesi içim bu üçüncü parametrenin girilmesine gerek yoktur. Fonksiyonun son parametresi UNIX/Linux sistemlerindeki dosya betimleyicisini alabilmektedir. Bu parametrenin bizim için şu aşamada önemi yoktur. Eğer biz TCP/Ip kullanacaksak aslında socket fonksiyonunun iki parametresi uygun değerleri almaktadır. Yani örneğin: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ile aslında aşağıdaki çağrı eşdeğerdir: sock = socket.socket() Biz öneklerimizde okunabilirliği artırmak için açıkça protokol ailesini ve soket türünü belirteceğiz. Soket işlemlerinde hata oluştuğunda built-in OSError isimli sınıfla exception fırlatılmaktadır. Programcı kodunu try-except bloğu içerisine yerleştirebilir. Örneğin: try: socket = socket.socket() ... except OSError: .... socket sınıfı "bağlam yönetim protokolünü (context management protocol)" desteklemektedir. Dolayısıyla socket sınıfı with deyimi ile kullanılabilir. Bu durumda with deyiminden çıkışdığında socket nesnesi kapatılacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import socket try: server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) print('Ok') except OSError as oserr: print(oserr) #------------------------------------------------------------------------------------------------------------------------------------ Soket yaratıldıktan sonra bind edilmelidir. Soketin bind edilmesi demek "hangi network kartından gelen bağlantı isteklerinin işleme sokulacağını ve hangi port hedeflenerek gelen bağlantı isteklerinin işleme sokulacağını" belirlemek demektir. bind işlemi socket sınıfının bind metoduyla yapılır. bind metodu iki elemanlı bir demeti parametre olarak alır. Demetin birinci elemanı bağlantı istekleri için kullanılacak network kartının IP adresini, ikinci elemanı ise ilgilenilecek port numarasını belirtir. Birinci parametrede boş string "tüm network kartlarından gelen bağlantı isteklerinin kabul edileceğini" belirtir. Örneğin: sock.bind(('', 55555)) Burada server program 55555 port numarası ile kendi bilgisayarındaki tüm network kartından (network interface) gelen bağlantı isteklerini değerlendiecektir. #------------------------------------------------------------------------------------------------------------------------------------ import socket PORT_NO = 50500 try: server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) server_sock.bind(('', PORT_NO)) print('Ok') except OSError as oserr: print(oserr) #------------------------------------------------------------------------------------------------------------------------------------ Soket bind edildikten sonra artık aktif dinleme konumuna sokulmalıdır. Soketin dinlenmesi "bize gelen bağlantı isteklerinin işletim sistemi tarafından bizim için kuruklanması" anlamına gelmektedir. Soket dinleme konumuna sokulduğunda artık bizi ilgilendiren bağlantı isteklerini işletim sistemi bize iletecektir. listen işlemi blokeye yol açmaz (yani listen işleminde bi rbekleme olmaz). Çünkü burada gelen bağlantı isteklerinin belirlenmesini bizim programımız değil işletim sisteminin kendisi yapmaktadır. Soketi dinleme konumuna sokmak için socket sınıfının listen metodu çağrılır. listen metodunun bir parametresi vardır. Bu parametre bağlantı isteklerinin yerleştirileceği kuyruğun uzunluğunu belirtir. Server program yavaş kalırsa yeni bağlantı istekleri bu kuyruğa yazılır. Eğer kuyruk dolarsa bağlantı istekleri kuyruğa yerleştirilemediği bağlantı için başlarısız olur. Bu parametre için değer girilmezse uygun bir kuyruk uzunluğu metot tarafından belirlenmeketdir. Yoğun server'larda bu değerin yüksek tutulması, yoğun olmayan server'larda düşük tutulması uygundur. Ya da bu uzunluğun listen metodu tarafından default alınması yoluna da gidilebilir. Soket dinleme konumuna sokulurken işletim sistemlerinin "firewall" denilen güvenlik mekanizması devreye girebilmektedir. Örneğin Windows sistemlerinde "listen" işlemi yapıldığında işletim sistemi bir popup pencere çıkartmaktadır. Bu popup pencerede kullanıcı durum hakkında bilgilendirilmektedir. Bazı sistemlerde (örneğin centos gibi) port default olarak firewall tarafından engellenmiş olabilmektedir. Bu durumda server programı çalıştırmadan önce programcının firewall'dan ilgili portu açması gerekebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import socket PORT_NO = 50500 try: server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('Ok') except OSError as oserr: print(oserr) #------------------------------------------------------------------------------------------------------------------------------------ Artık sıra bağlantıların kabul edilmesine gelmiştir. Bu işlem socket sınıfının accept isimli metoduyla yapılır. accept metodu kuyrukta bağlantı için bekleyen bir bağlantı isteği varsa hemen o bağlantıyı sağlar. Böylece bloke oluşmaz. Ancak kuyrukta bir bağlantı isteği yoksa bir bağlantı isteği oluşana kadar accpet metodu thread'i blokede bekletmektedir. Yani accept blokeye (beklemeye) yol açabilen bir metottur. accept metodu bağlantıyı sağladıktan sonra bağlanılan client ile konuşmakta kullanılacak bir soketi ve bağlanılan client'a ilişkin bilgileri geri döndürür. Yani accept bize yeni bir soket yaratıp vermektedir. Biz her accept metodunu çağırdığımızda o spesifik client ile konuşmakta kullanacağımız ayrı bir soket nesnesi elde ederiz. Her client ile farklı bir soket kullanılarak konuşulmaktadır. İşin başında server programın yarattığı sokete halk arasında "pasif soket (passice socket)" ya da "dinleme soketi (listenining socket)" denilmektedir. Bu soket konuşmakta kullanılmaz yalnızca bağlantı yapmak için kullanılır. accept metodunun verdiği konuşmakta kullanılacak sokete ise halk arasında "aktif soket" denilmektedir. accept metodu iki elemanlı bir demet geri döndürmektedir. Demetin ilk elemanı client ile konuşmakta kullanılacak soket, ikinci elemanı ise client'ın IP numarası port numarasını belirten iki elemanlı bir demettir. IP adresi ve port numarasında oluşan bilgiye "end point" de denilmektedir. Örneğin: client_sock, (client_addr, client_port) = sock.accept() accept metodunun geri döndürdüğü "end point"teki IP adresi bir string olarak verilmektedir. Bir IPV4 adresinin bir string olarak noktalı biçimde ifade edilmesine İngilizce "dotted decimal format" denilmektedir. Örneğin: "192.168.1.1" "188.3.183.172" #------------------------------------------------------------------------------------------------------------------------------------ import socket PORT_NO = 50500 try: server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for connection...') client_sock, (client_addr, client_port) = server_sock.accept() except OSError as oserr: print(oserr) #------------------------------------------------------------------------------------------------------------------------------------ 63. Ders 07/08/2023 - Pazartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Soketten bilgi gönderip soketten bilgi okuma işlemleri ileride ele alınacaktır. Soket işlemleri bittiğinde soketin kapatılması gerekir. Soketi kapatmak için socket sınıfının close metodu kullanılmaktadır. Pasif soketler (dinleme soketleri) doğrudan close ile kapatılabilir. Ancak aktif soketlerin close metodu ile kapatılmadan önce shutdown metodu ile "shutdown" yapılmaları uygundur. Shutdown işlemi sırasında TCP/IP için gereken "el sıkışma (hand shaking)" işlemi yapılır ve iletişim kontrollü bir biçimde sonlandırılır. Bir soketin önce shutdown yapılıp sonra close ile kapatılmasına TCP/IP dünyasında "zarif kapatma (graceful close)" denilmektedir. shudown metodunun bir parametresi vardır. Bu parametre aşağıdaki değerlerden biri biçiminde girilmelidir: socket.SHUT_RD socket.SHUT_WR socket.SHUT_RDWR Biz bir sokete bilgi gönderdiğimizde (bunun nasıl yapıldığı izleyen paragraflarda ele alınmaktadır) bu bilgi hemen IP paketine dönüştülerek yollanmaktadır. Programcının göndermek istediği bilgiler önce "network tamponu (network buffer)" denilen sokete özgü bir tampona çekilir. Sonra işletim sistemi bunu kendi döngüsü içerisinde gönderir. İşte biz soket ile karşı tarafa bir bilgi gönderip hemen arkasından soketi close ettiğimizde close işlemi network tamponuyla birlikte tüm soketin kapatılmasına yol açmaktadır. Yani bu durumda network tamponunda gönderilmeyi bekleyen bilgiler de gönderilemeyebilecektir. İşte shutdown işlemi socket.SHUT_WR ya da socket.SHUT_RDWR ile yapılırsa bu durumda shutdown metodu network tamponundaki bilgiler karşı tarafa gönderilene kadar küçük bir bloke oluşturacaktır. Bu sayede biz close işlemi yapmadna önce yerel makinemizdeki tüm bilgilerin karşı tarafa gönderildiğinden emin oluruz. socket.SHUT_RD değeri "ben bir daha bu soketten okuma yapmayacağım fakat yazma yapabilirim" anlamına socket.SHUT_WR değeri ise "ben bir daha bu sokete yazma yapmayacağım ancak soketten okuma yapabilirim" anlamına gelmektedir. Bu tür shutdown işlemlerine TCP dünyasında "half close" da denilmektedir. shutdown metodunda eğer socket.SHUT_RDWR değeri kullanılırsa bu durumda "artık bir daha soketten okuma ya da sokete yazma" yapılamamaktadır. Tipik olarak shutdown parametresi için socket.SHUT_RDWR kullanılmaktadır. Anımsanacağı gibi "exception güvenli" bir kodlama için genellikle nesneler üzerindeki sonlandırma ve kapatma işlemleri try bloğunun finally kısmında yapılıyordu. Ancak henüz açılmamış bir soketin kapatılması da bir exception oluşmasına yol açmaktadır. Bu nedenle socket değişkenlerine başlangıçta None değeri yerleştirip duruma göre bunların shutdown ve close edilmesi uygun olur. Örneğin: PORT_NO = 55555 server_sock = None client_sock = None try: server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) .... client_sock, (client_addr, client_port) = server_sock.accept() ... except OSError as e: print(e) finally: if client_sock: client_sock.shudown(...) client_sock.close() if server_sock: server_sock.close() #------------------------------------------------------------------------------------------------------------------------------------ import socket PORT_NO = 55555 server_sock = None client_sock = None try: server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') except OSError as e: print(e) finally: if client_sock: client_sock.shutdown(...) client_sock.close() if server_sock: server_sock.close() #------------------------------------------------------------------------------------------------------------------------------------ socket sınıfı "bağlam yönetim protokolünü (context management protocol)" desteklemektedir. Dolayısıyla with deyimi ile kullanılabilir. Bu durumda with deyimi sonlanırken close işlemi otomatik yapılacaktır. Bu tür kodlarda with deyimi daha sade bir yazıma olanak sağlamaktadır. Örneğin: try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: .... client_sock, (client_addr, client_port) = server_sock.accept() with client_sock: ... client_sock.shutdown(...) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ import socket PORT_NO = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() with client_sock: print(f'connected with client {client_addr}:{client_port}') client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ TCP client programın yazımı tipik olarak şu aşamalardan geçilerek gerçekleştirilmektedir: 1) Client soket nesnesini yaratır. 2) Client isteğe olarak bind işlemi yapabilir. 3) Client bağlanma işlemi için connect metodunu çağırır. 4) Bağlantı sağlandıktan sonra gönderme ve alma işlemleri yapılır 5) Soket nesnesi ile shutdown metodu çağrılarak zarif sonlanma (graceful close) işlemi başlatılır. 6) Soket close metoduyla kapatılır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Her soket bir portla ilişkilendirilir. İlişkilendirme işlemi bind metoduyla yapılmaktadır. Client program bind işlemini yapmak zorunda değildir. Bu durumda client programın yarattığı soket connect işlemi sırasında işletim sistemi tarafından boş bir portla ilişkilendirilir. İşletim sisteminin client soket için bağlantı sırasında otomatik atadığı porta "ephemeral port" denilmektedir. Genel olarak client'ın bağlanmada kullanacağı soketin port numarasının bir önemi yoktur. Ancak bazı server'lar ya da router'lar kaynak port numarası konusunda bazı koşullar oluşturabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bağlanma işlemi connect metoduyla yapılmaktadır. connect metodu iki elemanlı bir demeti parametre olarak alır. Demetin birinci elemanı server'ın IP adresini, ikinci elemanı port numarasını içermelidir. IPV4'te IP adresleri 4 byte'tır. Bu 4 byte "dotted decimal format" denilen noktalarla ayrılmış bir yazı biçiminde verilebilir. Örneğin '5.25.161.169' gibi. IP protokol ailesinde anımsanması kolay olsun diye host'lara aynı zamanda isimler de karşılık düşürülmüştür. Ancak protokol her zaman IP numaralarıyla işletilmektedir. Internet içerisinde host'lara karşı gelen IP numaraları DNS (Domain Name Server) denilen server'ların veritabanlarında tutulmaktadır. Dolayısıyla eğer biz bir host ismini biliyorsak onu DNS serverler'ına başvurarak IP adresine dönüştürmemiz gerekir. DNS işlemleri için IP protokol ailesinde DNS isimli bir protokol kullanılmaktadır. Tabii programcıların bu DNS protokolünü bilmesine gerek yoktur. Soket sınıfının gethostbyname, gethostbyaddress gibi metotları DNS işlemlerini kendi içerisinde yapmaktadır. Aslında connect metoduna parametre olarak geçtiğimiz demetin birinci elemanı IP adresi yerine doğrudan host ismini de alabilmektedir. Bu durumda connect önce DNS işlemini yapar, host ismini IP adresine dönüştürür ondan sonra bağlantı kurmaya çalışır. DNS veritabanlarında host ismiyle IP numaraları birebir bir ilişki içerisinde değildir. Bir host ismi birden fazla IP numarasıyla ilişkilendirilebileceği gibi bir IP numarası birden fazla host ismiyle de ilişkilendirilebilmektedir. IPV4'te 127.0.0.1 adresi özel bir IP adresidir. Bu adrese "loopback address" de denilmektedir. Bu IP adresi hangi biz host'ta çalışıyorsak o host'un IP adresi anlamına gelir. Windows, UNIX/Linux ve macOS sistemlerinde bulunduğumuz makinenin host ismi "localhost" ile de temsil edilmektedir. Biz connect metodunu çağırdığımızda o anda bizim bağlantımızı kabul edecek bir server programın çalışıyor olması gerekir. Aksi takdirde belli bir zaman aşımından (timeout) sonra exception oluşacaktır. Bezner biçimde server program çalıştığı halde listen metodunda belirtilen accept kuyruğu o anda dolmuş da olabilir. Bu durumda connect metodu belli bir süre bekler. Zaman aşımından dolayı olur ve exception oluşur. #------------------------------------------------------------------------------------------------------------------------------------ #server.py iimport socket PORT_NO = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() with client_sock: print(f'connected with client {client_addr}:{client_port}') # send/recv işlemleri client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) # send/recv işlemleri client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Aslında istenirse client programda da bind işlemi yapılabilir. Bu durumda biz belli bir kaynak porttan hedef makineye bağlanabiliriz. Ancak genel olarak zorunlu olmadıkça client program bind yapmamlıdır. Bu duurmda işletim sistemi client sokete conenct işlemi sırasında boş bir port numarası atayacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import socket PORT_NO = 50500 try: client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) client_sock.bind(('', 3006)) client_sock.connect(('localhost', PORT_NO)) print('connected...') # send/recv işlemleri client_sock.shutdown(SHUT_RDWR) client_sock.close() except OSError as oserr: print(oserr) #------------------------------------------------------------------------------------------------------------------------------------ Soketler "full duplex" bir haberleşme sunarlar. Yani client ile server eş zamanlı olarak birbirlerine bilgi gönderip alabilirler. Bilgi göndermek için socket sınıfının send metodu bilgi almak için recv metodu kullanılmaktadır. Her iki metot da "byte" temelinde çalışmaktadır. send metoduna biz bir bytes nesnesi veririz. (Anımsanacağı gibi bytes nesneleri bir grup byte'ı temsil etmektedir.) recv metodu da bize okunan byte'ları bytes nesnesi olarak vermektedir. send metodu network tamponuna yazılan byte sayısı ile geri döner. recv metodu da okuyabildiği byte'lardan bytes nesnesi yaparak bize o nesneyi geri verir. Biz send metodu ile n byte göndermek istediğimizde send metodu eğer network tamponunda (yani yerel makinedeki gönderme tamponunda) en az 1 byte yer varsa tampona yazabildiği kadar byte'ı yazar ve yazabildiği byte sayısına geri döner. Yani biz send metodu ile n byte göndermek istediğimizde aslında daha az byte göndermiş olabiliriz. Bunu send metodunun geri dönüş değeri ile anlayabiliriz. send metodu eğer network tamponunda hiç boş yer yoksa en az 1 byte newtwork tamponuna yazana kadar blokede bekler. send metodu geri döndüğünde gönderilmek istenen bilgilerinen az 1 byte'ı network tamponuna yazılmıştır. Ancak bu durum network tamponuna yazılan bilgilerin karşı tarafa gönderildiği anlamına gelmez. send metodu gönderilecekleri network tamponuna yazar ve geri döner. Network tamponundaki bilgilerin TCP/IP pakaeti olarak gönderilmesi belli bir süre sonra (tabii çok uzun bir süre değil) işletim sistemi tarafından yapılmaktadır. Örneğin: buf = b'\x01\x02\x\03' result = sock.send(buf) Burada send ile 3 byte gönderilmek istanmiştir. send metodu eğer network tamponunda hiç boş yer yoksa en az 1 byte yer açılana kadar blokede bekler. Örneğin network tamponunda 2 byte boş yer olsun. Bu duurmda send metodu 3 byte'ı değil iki byte'ı yazarak 2 byte ile geri dönecektir. recv metodu eğer network tamponunda hazırda bulunan hiçbir byte yoksa en az 1 byte okuyana kadar blokeye yol açar. Eğer network tamponunda okunmak için bekleyen en az bir byte bilgi varsa recv parametresiyle belirtilen miktarda byte'ın hepsini okuyana kadar bloke oluşturmaz. Okuyabildiği kadar byte'ı okur hemen geri döner. Eğer karşı taraf soketi shutdown ya da close ile kapatmışsa recv boş bir bytes nesnesine geri dönmektedir. Yani recv metodunun boş bir bytes nesnesi ile geri dönmesi başarısızlıktan dolayı değil karişı tarafın soketi kapattığından dolayıdır. Nihayet recv metodu bağlantının kopması gibi anormal olaylar karşısında exception fırtlamaktadır. Örneğin: buf = sock.recv(100) Burada biz 100 byte okumak istiyoruz. Eğer okunacak hiçbir byte yoksa recv en az bir byte okuyana blokede bekler. O sırada örneğin porta 5 byte gelmiş olsun. Bu durumda recv bu 5 byte'ı okur. 5 byte'tan oluşan bir bytes nesnesi ile geri döner. Yani recv metodunun parametresinde belirttiğimiz byte sayısı en fazla okunacak byte'ı belirtmektedir. TCP/IP'de önemli bir noktayı vurgulamak istiyoruz: Bir tarafın tek bir send ile gönderdiği bilgiyi karşı taraf tek bir recv ile okumak zorunda değildir. Biz tek bir send ile 1000 byte bilgi göndermiş olabiliriz. Bu bilgi network tamponunda iki ayrı IP paketi olarak gönderilmiş olabilir. Bu paketlerden biri hedefe geldiğinde recv hemen bunu okuyup 1000 byte'ın bir kısmını elde etmiş olabilir. Aşağıdaki örnekte client program server'a bağlanıp ve ona çeşitli yazılar göndermektedir. Tabii soketten bir yazı gönderilmek istenirse yazının önce bytes nesnesine dönüştürülmesi gerekir. Benzer biçimde elde edilen bytes nesnesi de yeniden yazıya dönüştürülebilir. Bir string'in bytes nesnesine dönüştürülmesi için str sınıfının encode metodu, bytes nesnesinin string'e dönüştürülmesi için ise bytes sınıfının decode metodu kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket PORT_NO = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() with client_sock: print(f'connected with client {client_addr}:{client_port}') while True: b = client_sock.recv(4096) s = b.decode() if s == 'quit': break print(f'{len(b)} btes received: {s}') client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Bir yazı giriniz:') result = client_sock.send(text.encode()) print(f'{result} bytes sent') if text == 'quit': break client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ 64. Ders 09/08/2023 - Çarşamba #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tabii client ile server programların aslında aynı dilde yazılması da gerekmez. Çünkü dil ne olursa olsun aslında arka planda aynı protokol aynı kurallara göre işletilmektedir. Aşağıdaki örnekte server program python'da client program C#'ya yazılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket PORT_NO = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() with client_sock: print(f'connected with client {client_addr}:{client_port}') while True: b = client_sock.recv(4096) s = b.decode() if s == 'quit': break print(f'{len(b)} btes received: {s}') client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) // Client.cs using System; using System.Net; using System.Net.Sockets; using System.Text; namespace CSD { class App { public const string SERVER_NAME = "31.220.81.76"; public const int PORT = 55555; public static void Main() { Socket clientSock; string text; byte[] buf; try { using (clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { clientSock.Connect(new IPEndPoint(IPAddress.Parse(SERVER_NAME), PORT)); Console.WriteLine("connected"); for (; ; ) { Console.Write("Bir yazı giriniz:"); text = Console.ReadLine(); buf = Encoding.UTF8.GetBytes(text); clientSock.Send(buf); if (text == "quit") break; } clientSock.Shutdown(SocketShutdown.Both); } } catch (Exception e) { Console.WriteLine(e.Message); } } } } #------------------------------------------------------------------------------------------------------------------------------------ Yerel makinede deneyerek çalıştırdığımız client ve server programları Internet ağında da aynı biçimde çalıştırabiliriz. Eğer evinizde bir server program bulundurmak istiyorsanız bazı bilgilere sahip olmasınız: - Biz evimizde bir yerel ağa (Local Area Network (LAN)) sahbiz. Evimiz içindeki yerel ağı ayrı bir network olarak düşünebiliriz. Evimizdeki bilgisayarların IP numaraları "yerel IP (local IP)" numarasına sahiptir. Biz evimizdeki yerel ağı Internet'e tek bir router ile bağlamaktayız. Bizim evimizin Internet'teki adresi bu router'ımızın IP adresidir. Yani evimizde birkaç bilgisayarımız olsa da biz Internet ortamında sanki tek host gibi görünmekteyiz. Router'ımızın Internet'teki IP adresi çeşitli biçimlerde elde edilebilir. Bunun basit bir yolu tarayıcan "whatismyip.com" sitesine girip bakmaktır. Ancak maalesef servis sağlayıcılar bize hep aynı IP adresini atamamaktadır. Dolayısıyla "whatismyip.com" sitesinden elde edeceğiniz IP adresleri zamanla değişebilecektir. Bu tür IP adreslerine "dinamik ip adresleri" denilmektedir. Servis sağlayıcının bize hep aynı ip adresine vermesini istiyorsanız servis sağlayıcınızla görüşmelisiniz. Ancak servis sağlayıcılar bu işlemi aylık ücret karşılığında yapmaktadır. Statik IP'nin en önemli faydası client programların hep IP adresi ile server'larınıza erişebilmesidir. Internet hizmeti veren hosting firmaları zaten hep statik ip vermektedir. - Evimizde bir server oluştururken dikkat edeceğimiz bir nokta da "port yönlendirmesi (port forwarding)" yapmaktır. Dış dünyadan bizim evimizdeki server'a bağlanmak isteyen kişiler bizim rouer IP adresini kullanacaklardır. Oysa server'ımız yerel ağdadır ve onun yerel IP'si vardır. İşte bu durumda router'a gelen bağlantı isteklerinin router tarafından yerel ağdaki bilgisayarımıza yönlendirilmesi gerekir. Buna port yönlendirmesi denilmektedir. Poer yönlendirmesi yapabilmek için tarayıcıdan router'a erişmek gerekir. Genellikle router adresi yerel ağda "192.168.1.1" biçimindedir. Ancak artık servis sağlayıcılar port yönlendirmesi için statik IP'yi zorunlu tutmaktadır. Tabii bir hosting şirketinden VPS ya da "dedicated server" kiralamışsanız böyle port yönlendirmesini yapmanıza gerek kalmayacaktır. Zaten bu tür hosting hizmeti veren şirketler VPS ve "dedicated server'lar" için statik IP adresleri vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Çok client'lı server uygulamalarında server her client için döngü içerisinde accept uygulamalıdır. Ancak çok client'lı server uygulamalarında önemli problem şudur: Server bir client ile bağlandığı zaman onunla nasıl konuşacaktır? Tek bir akış söz konusu olduğunda server yeniden accept metodunda beklerken bloke oluşacağından dolayı daha önce bağlanmış olduğu client'lar ile konuşamaz. Benzer biçimde tek bir akış söz konusu olduğunda bir client için recv yapıldığında eğer o client bir bilgi göndermemişse akış bu sefer de recv metodunda bloke olur. Sonuç olarak tek bir akış ile birden fazla client ile konuşmak mümkün olmaz. O zaman ilk akla gelecek yöntem her client için bağlantı yapıldıktan sonra yeni bir thread yaratmak ve o client ile o thread'in konuşmasını sağlamaktır. Böylece bir thread bloke olsa bile diğer thread'ler bundan etkilenmezler. Çok client'lı server uygulamalarında çeşitli modeller kullanılabilmektedir. Thread modeli basitliği nedeniyle bazı uygulamalarda tercih edilir. Ancak thread modelinin de önemli dezavantajları söz konusu olabilmektedir. Aşağıdaki programda server için thread modeli uygulanmıştır. Bağlantı sağlandığında yeni bir thread yaratılmış ve soket bilgileri bu yeni thread'e parametre yoluyla aktarılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import threading PORT_NO = 55555 def main(): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) while True: print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') thread = threading.Thread(target=client_proc, args=(client_sock, client_addr, client_port)) thread.start() except OSError as e: print(e) def client_proc(sock, addr, port): try: with sock: while True: b = sock.recv(4096) s = b.decode() print(f'recieved message from client {addr}:{port} ==> "{s}" ') if s == 'quit': break sock.send(s[::-1].encode()) sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) main() # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Bir yazı giriniz:') result = client_sock.send(text.encode()) if text == 'quit': break buf = client_sock.recv(4096) rev_text = buf.decode() print(rev_text) client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ 65 .Ders 25/08/2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Client'larla konuşmak için thread'ler yerine proseslerden de faydalanabiliriz. Daha önceki konularda da bahsettiğimiz gibi genel olarak prosesler GIL yüzünden thread'lere göre daha iyi performans gösterebilmektedir. Aşağıda çok client'lı uygulama için proses modeline bir örnek verilmiştir. Anımsanacağı gibi zaten multiprocessing modülünün kullanımı threading modülüne oldukça benzemektedir. Dolayısıyla biz thread örneğinde küçük değişikliklerle aşağıdaki proses örneğini oluşturduk. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import multiprocessing PORT_NO = 55555 def main(): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) while True: print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') process= multiprocessing.Process(target=client_proc, args=(client_sock, client_addr, client_port), daemon=True) process.start() except OSError as e: print(e) def client_proc(sock, addr, port): try: with sock: while True: b = sock.recv(4096) s = b.decode() print(f'recieved message from client {addr}:{port} ==> "{s}" ') if s == 'quit': break sock.send(s[::-1].encode()) sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) if __name__ == '__main__': main() #client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Bir yazı giriniz:') result = client_sock.send(text.encode()) if text == 'quit': break buf = client_sock.recv(4096) rev_text = buf.decode() print(rev_text) client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Bir soket nesnesinden o soketi gören bir dosya nesnesi elde edilebilir. Burada "o soketi gören" demekle o dosya nesnesi ile işlem yaptığımızda aslında ilgili soketle işlem yapılacağını" anlatmak istiyoruz. Soketten dosya nesnesi elde etmek için socket sınıfının makefile isimli metodu kullanılmaktadır. makefile metodunun ilgili parametreleri built-in open fonksiyonun parametreleri gibidir. makefile metodu tek parametreyle çağrılabilir. Bu durumda bu paramere dosyanın açış modunu belirtir. Ancak buradaki açış mode yalnızca şu modlardan biri olabilir: 'r', 'w', 'rb', 'wb'. Bu dosya nesnesi yine close edilmelidir. Ancak bu nesnenin close edilmesi socket nesnesinin close edilmesi anlamına gelmez. socket nesnesi ayrıca close edilmelidir. Benzer biçimde socket nesnesinin close edilmesi de bu dosya nesnesinin close edileceği anlamına gelmemektedir. Pekiyi soket için makefile ile dosya nesnesi oluşturmanın ne faydası vardır? İşte normal soket ile send ve recv metotlarını kullanabiliriz. Bu metotlar da byte nesneleriyle çalışmaktadır. Halbuki dosya nesnelerinin kullanımı daha esnektir. Ancak Python'da text dosyalar (yani 'b' belirtilmeden oluşturulan dosyaların) tamponlu çalışmaktadır. Bunun için dosya nesnesinin üzerinde flush uygulanması gerekebilmektedir. Aslında dosyalarda biz dosyaya birşeyler yazdığımızda bu yazılanlar önce bir tamponda biriktirilip tampon dolduğunda asıl hedefe aktarılmaktadır. Bunun nedeni gerçek disk işlemlerinin azaltılmak istenmesidir. İşte makefile ile sokete ilişkin bir dosya nesnesi elde ettiğimizde bu dosya nesnesi ile write yapıldığında yazılmak istenenler arka planda hemen send işlemi ile soketten gönderilmemektedir. Bunlar önce bir tamponda biriktirilip tampon dolduğunda gönderilmektedir. Biz yazılanların hemen gönderilmesini istiyorsak write işleminden sonra flush metodunu çağırmalıyız. flush işlemi tamponda birikenleri o anda hedefe aktarmaktadır. Tabii dosya nesnesi kapatıldığında kapatma sırasında da flush işlemi yapılmaktadır. Aşağıdaki örnekte makefile metodu ile dosya işlemleri yolu ile bir client/server örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import threading PORT_NO = 55555 def main(): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) while True: print('waiting for client...') client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') thread = threading.Thread(target=client_proc, args=(client_sock, client_addr, client_port)) thread.start() except OSError as e: print(e) def client_proc(sock, addr, port): try: with sock: with sock.makefile('w') as fw, sock.makefile('r') as fr: while True: text = fr.readline()[:-1] print(f'recieved message from client {addr}:{port} ==> "{text}"') if text == 'quit': break fw.write(text[::-1] + '\n') fw.flush() sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) main() # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') with client_sock.makefile('w') as fw, client_sock.makefile('r') as fr: while True: text = input('Bir yazı giriniz:') fw.write(text + '\n') fw.flush() if text == 'quit': break response = fr.readline() print(response, end='') client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Bir soketten bir satır (yani '\n' görene kadar) bilgi okumak sanıldığı kadar kolay değildir. Çünkü TCP protokolünde bir tarafın send ile gönderdiği bilgileri diğer tarafın tek bir recv ile okuması garanti değildir. Bunlar karşı tarafa farklı TCP paketleri ile iletilebilir. Dolayısıyla okuyan taraf bir satırlık bilgiyi birden fazla recv ile okumak zorunda kalabilir. Aynı zamanda gönderen taraf da birden fazla satırı peş peşe gönderdiğinde alan taraf da bunu tek bir recv ile alabilir. Yani TCP'de birden fazla send işlemi tek bir recv ile de alınabilmektedir. İşte bu nedenlerden dolayı bir satırlık bilginin etkin bir biçimde okunması o kadar kolay değildir. recv metodu ile her defasında birer karakter soketten okunarak bir satır bilgi okunabilir. Ancak bu da yavaş bir yöntemdir dolayısıyla etkin değildir. İşte makefile metodu sayesinde soketten bir satır okumak oldukça kolaydır. Çünkü makefile bize bir dosya nesnesi verir. O dosya nesnesi üzerinde readline metodunu uyguladığımızda zaten bu okuma işlemi readline tarafından etkin bir biçimde yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Client/Server uygulama geliştirirken client'ın isteklerini server'a iletmesi, server'ın istenilenleri yapması ve sonucu client'a iletmesi gerekir. İşte client ile server arasındaki bu mesajlaşmalar temelde iki biçimde yapılmaktadır: 1) Yazısal (text) biçiminde 2) Binary biçimde (yani byte düzeyinde) Byte düzeyinde (binary) mesajlaşma daha hızlıdır. Ancak daha zordur ve manuel işlemler için (örneğin telnet gibi bir ortam) uygun olmayabilir. Bu nedenle yazısal mesajlaşmalar daha çok tercih edilmektedir. Gerçekten de IP ailesinin uygulama katmanındaki POP3, SMTP, FTP, TELNET, HTTP gibi protokolleri hep metinsel biçimde mesajlaşma uygulamaktadır. Biz de kursumuzda metinsel mesajlaşmalara ilişkin örnekler vereceğiz. Metinselsel mesajlaşmalarda client ve server birbirlerine "bir satırlık" yazılar göndererek mesajlaşmayı sağlarlar. Gerçekten de IP ailesinin uygulama katmanındaki protokoller böyle birer satırlık bilgilerin gönderilip alınması biçiminde mesajlaşma uygulamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Dört işlem yapan çok client'lı bir client/server uygulama yazmak isteyelim. Bu tür uygulamalar yazılırken önce bir "uygualama katmanı protokolünün tasarlanması" gerekir. Yani client server'a nasıl istekte bulunacak, server bunu nasıl yanıtlayacak? Burada mesajlaşmalar nasıl olacak? Bunların belirlenmesi gerekir. Server'lardan herkesin hizmet alamaması için bir user name/password mekanizmasının oluşturulması ve login işleminin yapılması gerekebilir. TCP/IP'de connect ve accept işlemleriyle fiziksel bağlantı sağlanmaktadır. Client'ın server'a fiziksel biçimde bağlanması ondan hizmet alabileceği anlamına gelmez. Server'ın hizmet vermesi için bir "login" mekanizmasının mantıksal biçimde oluşturulması gerekebilir. Bu login mekanizmasına "mantıksal bağlanma" da diyebiliriz. Böyle bir uygulamanın protokolü aşağıdaki gibi olabilir: Client'tan Server'a Gönderilen Mesajlar: "LOGIN \n" "LOGOUT\n" "ADD \n" "SUB \n" "MUL \n" "DIV \n" Server'dan Client'a Gönderilen Mesajlar: "LOGIN_ACCEPTED\n" "ERROR \n "RESULT \n" "LOGOUT_ACCEPTED\n" Genel olarak bu tür protokollerde client'ın her mesajına karşılık server da client'a bir mesaj yollamaktadır. Eğer client geçersiz bir mesaj yollarsa ya da client'ın isteği yerine getirilemezse bizim bu örneğimizde server client'a "ERROR hama mesajı\n" biçiminde yazı gönderecwktir. Bu tür protokollerde mesajlaşmalar yazısal düzeyde yapılıyor olsa da gönderilen bu yazılar parse edilerek istenen bilgi yazıların içerisinden alınmaktadır. Aşağıda böyle bir program örneği verilmiştir. Örneğimizde client'lara ilişkin "user name ve password" bilgileri "credentials.csv" dosyası içerisinde tutulmaktadır. Örnek bir "credentials.csv" dosyası şöyle olabilir: kaan,maviay ali,ankara ahmet,istanbul Bu CSV dosyasında her satırda ilgili kullanıcının kullanıcı adı ve parolası bulunmaktadır. Tabii uygulamada aslında kullanıcıların parolalarının kendisi doğrudan dosyalarda bu biçimde saklanmamaktadır. Kullanıcıların parolaları şifrelendikten sonra onların şifrelenmiş halleri dosyalarda saklanmaktadır. Kullanıcı login olurken kullanıcının parolası yeniden şifrelenir ve iki şifrelenmiş parolanın eşitliğine bakılır. Yani normal olarak aslında server'larda bizim parolalarımızın tutulmaması gerekir. Client program çalıştırıldığında önce klavyeden kullanıcı adı ve parola bilgisini alır. Sonra bunu doğrularsa client server'a mantıksal olarak bağlanmış olur. Bundan sonra artık client bir komut satırında "3 + 4" gini "5 - 3" gibi dörtişelmli bir yazı yazar. Client program bu yazıyı uygun bir mesaja dönüştürerek server programa yollar. Server program da client programa sonucu yollar. Client program bu sonuç mesajını parse ederek sonucu ekrana yazdırır. Örneğin: User name:kaan Password:maviay Logged in... Calc>3 + 2 5.0 Calc>5 * 3 15.0 Calc>logout Logged out #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import threading import csv PORT_NO = 50500 users = {} def main(): try: with open('credentials.csv') as f, socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) as server_sock: for t in csv.reader(f): if len(t) != 2: continue user_name, password = t users[user_name] = password server_sock.bind(('', PORT_NO)) server_sock.listen(8) print('waiting for connection...') while True: (client_sock, (client_addr, client_port)) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}...') client_thread = threading.Thread(target=client_thread_proc, args=(client_sock, client_addr, client_port)) client_thread.start() except OSError as oserr: print(oserr) def client_thread_proc(client_sock, client_addr, client_port): try: with client_sock, client_sock.makefile('r') as fr, client_sock.makefile('w') as fw: if not login_proc(fr, fw): client_sock.shutdown(socket.SHUT_RDWR) return while True: cmd = fr.readline() if cmd == '': break cmd = cmd[:-1] print(f'Command from {client_addr}({client_port}): {repr(cmd)}') if not process_cmd(fw, cmd): break client_sock.shutdown(socket.SHUT_RDWR) except OSError: pass finally: print(f'{client_addr}:{client_port} disconnected...') def process_cmd(fw, cmd): cmd_dict = {'ADD': add_proc, 'SUB': sub_proc, 'MUL': mul_proc, 'DIV': div_proc, 'LOGOUT': logout_proc} params = cmd.split() if params and (proc := cmd_dict.get(params[0])): return proc(fw, cmd, params) fwrite(fw, f'ERROR Invalid command "{cmd}"\n') return True def login_proc(fr, fw): cmd = fr.readline()[:-1] params = cmd.split() if len(params) != 3 or params[0] != 'LOGIN': fwrite(fw, f'ERROR Invalid command "{cmd}"\n') return False password = users.get(params[1]) if password and password == params[2]: fwrite(fw, 'LOGIN_ACCEPTED\n') print(f'{params[0]} logged in...') return True else: fwrite(fw, 'ERROR Invalid user name or password\n') return False def add_proc(fw, cmd, params): if len(params) != 3: fwrite(fw, f'ERROR Invalid command: "{cmd}"\n') return True try: val1 = float(params[1]) val2 = float(params[2]) result = val1 + val2 fwrite(fw, f'RESULT {result}\n') except ValueError: fwrite(fw, f'ERROR incorrect argument: "{cmd}"\n') return True def sub_proc(fw, cmd, params): if len(params) != 3: fwrite(fw, f'ERROR Invalid command: "{cmd}"\n') return True try: val1 = float(params[1]) val2 = float(params[2]) result = val1 - val2 fwrite(fw, f'RESULT {result}\n') except ValueError: fwrite(fw, f'ERROR incorrect argument: "{cmd}"\n') return True def mul_proc(fw, cmd, params): if len(params) != 3: fwrite(fw, f'ERROR Invalid command: "{cmd}"\n') return True try: val1 = float(params[1]) val2 = float(params[2]) result = val1 * val2 fwrite(fw, f'RESULT {result}\n') except ValueError: fwrite(fw, f'ERROR incorrect argument: "{cmd}"\n') return True def div_proc(fw, cmd, params): if len(params) != 3: fwrite(fw, f'ERROR Invalid command: "{cmd}"\n') return True try: val1 = float(params[1]) val2 = float(params[2]) if val2 == 0: fwrite(fw, f'ERROR Divider shall not be zero: "{cmd}"\n') return True result = val1 / val2 fwrite(fw, f'RESULT {result}\n') except: fwrite(fw, f'ERROR incorrect argument: "{cmd}"\n') return True def logout_proc(fw, cmd, params): if len(params) != 1: fwrite(fw, f'ERROR Invalid command: "{cmd}"\n') return True fwrite(fw, 'LOGOUT_ACCEPTED\n') print('{params[0]} logged out...') return False def fwrite(fw, text): fw.write(text) fw.flush() main() # client.py import socket import re PORT_NO = 50500 SERVER = 'localhost' def main(): cmd_dict = {'ERROR': error_proc, 'RESULT': result_proc, 'LOGOUT_ACCEPTED': logout_accepted_proc} try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) as client_sock: client_sock.connect((SERVER, PORT_NO)) with client_sock.makefile('r') as fr, client_sock.makefile('w') as fw: if not login_proc(fr, fw): return while True: cmd_text = input('Calc>').strip() if cmd_text != 'logout': result, operand1, operand2, operator = parse_input(cmd_text) if not result: print('Invalid input!') continue optext = {'+': 'ADD', '-': 'SUB', '*': 'MUL', '/': 'DIV'}[operator] msg = f'{optext} {operand1} {operand2}\n' else: msg = 'LOGOUT\n' param = None fwrite(fw, msg) response = fr.readline() if not response: break cmd, param = parse_cmd(response) proc = cmd_dict.get(cmd) if not proc: print('Invalid server command: {cmd}') continue if not proc(param): break client_sock.shutdown(socket.SHUT_RDWR) except OSError as oserr: print(oserr) def login_proc(fr, fw): user_name = input('User name:') password = input('Password:') fwrite(fw, f'LOGIN {user_name} {password}\n') response = fr.readline() cmd, param = parse_cmd(response) if cmd != 'LOGIN_ACCEPTED': print(f'Error message form server: {param}') return False print('Logged in...') return True def error_proc(param): match = re.search(':\s*(".*")', param) msg_text = param[: match.start()] msg_cmd = match[1] print(f'Error message from server: {msg_text} ({msg_cmd})') return True def result_proc(param): print(param) return True def logout_accepted_proc(param): print('Logged out') return False def fwrite(fw, text): fw.write(text) fw.flush() def get_input(cmd): pass def parse_cmd(s): k = 0 while k < len(s) and s[k].isspace(): k += 1 if k == len(s): return '', '' i = k while i < len(s) and not s[i].isspace(): i += 1 cmd = s[k:i] if i == len(s): return cmd, '' while i < len(s) and s[i].isspace(): i += 1 param = s[i:] return cmd, param.strip() def parse_input(cmd): i = 0 while i < len(cmd) and '+-*/'.find(cmd[i]) == -1: i += 1 if i == len(cmd): return False, None, None, None op1 = cmd[:i].strip() op2 = cmd[i + 1:].strip() op = cmd[i] try: float(op1) float(op2) except: return False, None, None, None return True, op1, op2, op main() #------------------------------------------------------------------------------------------------------------------------------------ Çok client'lı server uygulamalarında aynı anda birden fazla client ile konuşabilmek için thread modelinin dışında çeşitli modeller kullanılabilmektedir. Biz yukarıdaki çok client'lı server örneklerimizde thread modelini ya da benzer proses modelini kullandık. Ancak thread ya da proses modelleri basit olmasına karşın bazı handikapları olan modellerdir. Çok sayıda client söz konusu olduğunda çok fazla sayıda threda ya da prosesin yaratılması ciddi bir sistem kaynağının harcanmasına yol açmaktadır. Bu nedenle client sayısının fazla olduğu durumlarda thread ve proses modelleri uygun model olmaktan çıkmaktadır. Bu tür modeller şüphesiz işletim sisteminin desteği ile sağlanmaktadır. Genel olarak işletim sistemlerinde buna benzer IO modellerine "asenkron IO (asynchronous IO) ve multiplexed IO modelleri" denilmektedir. Asenkron IO modelleri işletim sisteminden işletim sistemine değişebilmektedir. Çünkü bu konu işletim sisteminin çekirdek gerçekleştirimi ile ilgilidir. Bazı modeller birden fazla işletim sisteminde onların çekirdekleri tarafından gerçekleştirilmiş durumdadır. - Windows işletim sisteminde asenkron IO işlemleri için "Overlapped IO", "IO Completion Port" ve "select" modelleri kullanılabilmektedir. Bunlar arasında Windows için en uygun ve en etkin model "IO Completion Port" denilen modeldir. - Linux sistemlerinde "select" modeli, "poll" modeli, "epoll" modeli ve "asyncio" IO modeli kullanılabilmektedir. Ancak en yüksek performans "epoll" modelinden elde edilmektedir. - FreeBSD sistemlerinde "select", "poll" ve "asyncio" modellerinin yanı sıra "kqueue" modeli de kullanılmaktadır. - macOS sistemlerinde de "select" modeli, "poll" modeli ve "kqueue" modelleri kullanılabilmektedir. Python standart kütüphanesinde "select" isimli modülde yukarıdaki asenkron IO modellerinin hepsi bulunmaktadır. Ancak bu modeller işletim sisteminin çekirdeğine bağlı olduğu için her işletim sisteminde kullanılamamaktadır. Örneğin epoll modeli Linux için uygun bir model olmasına karşın bu model Windows sistemlerinde ya da macOS sistemlerinde bulunmamaktadır. Bu bakımdan nispeten en taşınabilir olan model "select" modelidir. Ancak Python standart kütüphanesinde bu aşağı seviyeli asenkron IO modellerini kullanan daha yüksek seviyeli "selectors" denilen bir modül de vardır. Biz kursumuzda önce aşağı seviyeli "select" modeli hakkında bilgi vereceğiz. Sonra da yüksek seviyeli "selectors" modelini tanıtacağız. En sonunda da aynı işlemlerin Python diline eklenen "coroutine" mekanizmasıyla gerçekleştirilmesini göreceğiz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 66. Ders 26/08/2023 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ select modelinde işletim sisteminin "select" isimli bir sistem fonksiyonu bulunur. Programcı bu select fonksiyonuna birden fazla soketi verir. (Tabii aslında select fonksiyonu yalnızca soketlerle değil pek çok aygıtla çalışabilmektedir.) select fonksiyonu bu soketleri izler. Eğer soketlerde hiçbir IO olayı yoksa select fonksiyonu thread'i blokede bekletir. Ancak soketlerden en az birinde bir olay olmuşsa (buradaki olay tipik olarak sokete okunacak bilgi gelmesidir) select blokeyi çözer. Böylece select fonksiyonu geri döner. Programcı da hangi soketlerde hangi olayların olduğunu sorgular ve artık bloke oluşmadan işlemlerini yapar. Sonra yeniden select fonksiyonunu çağırır. Yani select fonksiyonu bir döngü içerisinde çağrılmaktadır. Buradaki olay "okuma", "yazma" ya da "exception" işlemleri olabilir. Python'da select modeli şöyle kullanılır: 1) Bir döngü içerisinde select modülündeki select fonksiyonu çağrılır. Bu fonksiyon tipik olarak üç parametre almaktadır. Fonksiyonun birinci parametresi okuma olayı için izlenecek soket listesini, ikinci parametresi yazma olayı için izlenecek soket listesini, üçüncü parametre ise exception oluşturan eylemler için izlenecek soket listesini belirtir. Genellikle programcılar yalnızca okuma işlemiyle ilgilenirler. Dolayısıyla yalnızca fonksiyonun birinci parametresi için liste oluştururlar. İkinci ve üçüncü parametreye boş liste girerler. (Biz burada liste demekle herhangi dolaşılabilir bir nesneyi kastediyoruz. Fonksiyonun bu üç parametresine askında herhangi bir) dolaşılabilir nesne girilebilmektedir.) Fonksiyona isteğe bağlı olarak bir zaman aşımı değeri de son parametrede girilebilmektedir. Bu durumda fonksiyon soketlerin hiçbirinde bir olay gerçekleşmemişse en kötü olasılıkla burada belirtilen saniye kadar sonra blokeyi çözer. Bu parametrenin girilmemesi zaman aşımı uygulanmayacağı anlamına gelmektedir. Fonksiyonun birinci parametresindeki listeye dinleme soketi (pasif soket) de eklenebilir. Bu durumda bu sokete bir bağlantı isteği geldiğinde bu istek sanki bir okuma olayı gibi değerlendirilmektedir. Programcılar genellikle işin başında bu okuma listesine dinleme soketini yerleştirmektedir. select fonksiyonu üçlü bir demete geri dönmektedir. Demetin birinci elemanı okuma olayı gerçekleşen soketlerin listesini bize verir. Örneğin biz select fonksiyonun birinci parametresine 50 tane soket vermiş olalım. Bunların iki tanesine bilgi gemiş olsun. Şimdi select fonksiyonunun geri döndürdüğü demetin ilk elemanında bu iki soketi bulunacaktır. Yani biz bunlardan okuma yapmak istersek artık bloke oluşmayacaktır. Demetin ikinci elemanı yazma olayı gerçekleşen soketleri, üçüncü elemanı ise exception olayı gerçekleşen soketleri belirten listelerdir. Tabii programcı yazma ve exception için zaten boş liste vermişse demetin bu elemanları da boş liste olacaktır. 2) select fonksiyonu geri döndüğünde programcı olaya yol açan soketleri gözden geçirerek uygun işlemleri yapmalıdır. Örneğin tipik olarak biz soketleri okuma amaçlı izliyorsak bilgi gelen soketler üzerinde recv fonksiyonunu uygulamalıyız. Burada önemli bir nokta dinleme soketi üzerinde okuma olayının gerçekleşip gerçekleşmediğinin sorgulanmasıdır. Eğer dinleme soketi üzerinde okuma olayı gerçekleşmişse bu durum yeni bir client'ın bağlanma isteğini belirtmektedir. Bu durumda bizim dinleme soketi ile accept işlemini yapmamız ve bağlantıdan elde ettiğimiz soketi yeniden okuma olay listesine dahil etmemiz gerekir. 3) Bir client soketi kapattığında bu durum select fonksiyonunda sanki okuma olayı gibi ele alınmaktadır. Tabii bu soketten okuma yapıldığında artık 0 byte okunacak yani recv metodu boş bytes nesnesi ile geri dönecektir. Programcının da bu durumda artık o soketi okuma listesinden çıkarması gerekir. Aşağıdaki örnekte aşağı seviyeli select modelinin örnek uygulaması görülmektedir. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import select PORT_NO = 55555 def main(): read_socks = [] try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) read_socks.append(server_sock) print('waiting for client...') while True: rsocks, _, _ = select.select(read_socks, [], []) for sock in rsocks: if sock == server_sock: client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') read_socks.append(client_sock) else: text = sock.recv(1024).decode() if len(text): print(text) else: read_socks.remove(sock) sock.close() except OSError as e: print(e) main() # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Enter text:') client_sock.send(text.encode()) if text == 'quit': break client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda verdiğimiz örnekte recv işlemi uyguladığımızda okunan byte'ların hangi client'a ilişkin olduğunu bilmemekteyiz. Bu tür durumlarda bağlanana her client için bir veri yapısı oluşturulup onun bilgileri o veri yapısında saklanabilir. Burada bilgilerin bir sözlük içerisinde bulundurulması uygun olacaktır. Sözlüğün anahtarı soket nesnesinden oluşturulur. (Sokey nesneleri hashable nesnelerdir.) Bunların değerleri de client bilgilerinden oluşturulabilir. Aşağıdaki örnekte bir client bağlantısı sağlandığında client'ın bilgileri sözlüğe ikili demet biçiminde yerleştirilmiş ve sonra sözlükten alınarak kullanılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ # server import socket import select PORT_NO = 55555 def main(): read_socks = {} try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) read_socks[server_sock] = (None, None) print('waiting for client...') while True: rsocks, _, _ = select.select(read_socks, [], []) for sock in rsocks: if sock == server_sock: client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') read_socks[client_sock] = (client_addr, client_port) else: text = sock.recv(1024).decode() if len(text): client_addr, client_port = read_socks[sock] print(f'"{text}" read from client {client_addr}:{client_port}') else: read_socks.pop(sock) sock.close() except OSError as e: print(e) main() # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Enter text:') client_sock.send(text.encode()) if text == 'quit': break client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Python'da asenkron soket işlemlerini kolaylaştırmak için kendi içlerinde "select", "poll", "epoll", "kqueue" gibi asenkron IO modellerini kullanan ancak ilgili olay gerçekleştiğinde programcının belirlediği fonksiyonu çağıran daha yüksek seviyeli ismine "selectors" denilen bir modül de bulundurulmuştur. Bu modüldeki SelectSelector arka planda "select" modelini, EPollSelector arka planda "epoll" modelini, PollSelector arka planda "poll" modelini ve KqueueSelector ise arka planda "kqueue" modelini kullanmaktadır. Ayrıca bir de modülde DefaultSelector isimli bir sınıf da vardır. Bu sınıf ilgili sistemdeki en uygun selektör nesnesini seçip kullanmaktadır. Bütün Selector sınıflarının kullanım biçimleri aynıdır. Biz burada en uygun seçenek olan DefaultSelctor nesnesini kullanacağız. Selector nesnelerinin kullanımı şöyledir: 1) Programcı önce selector nesnesini yaratır. Yukarıda da belirttiğimiz gibi bu nesne tipik olarak DefaultSelector sınıfıyla yaratılmalıdır. 2) Programcı bir soket üzerinde olay gerçekleştiğinde çağrılmasını istediği fonksiyonu selector sınıflarının register metotları ile register ettirir. Selector sınıflarının register metotları üç parametreden oluşmaktadır. Birinci parametre izlenecek soketi belirtir. İkinci parametre izlenecek olayı belirtmektedir. Bu olay selectors.EVENT_READ, selectors.EVENT_WRITE ya da selectors.EVENT_READ|selectors.EVENT_WRITE biçiminde girilebilir. Yine dinleme soketi üzerinde okuma olayının takip edilmesi aslında yeni bağlantı isteklerinin takip edilmesi anlamına gelmektedir. register metodunun üçüncü parametresi sonraki maddede açıklanacak olan Selector nesnesiyle çağrılan select metodunun geri döndürdüğü SelectorKey isimli sınıf nesnesinin data örnek özniteliğinden elde edeilecek olan bilgiyi belirtir. Bu üçüncü parametre bir fonksiyon olarak da girilebilir. Bu parametre default olarak None değerini almaktadır. 3) Programcının bir döngü içerisinde selector nesnesinin select metodunu çağırması gerekir. Bu metot yine eğer register ettirilen soketlerin hiçbirinde bir olay gerçekleşmemişse blokede thread'i bekletir. Ancak bu soketlerden en az birinde bir olay gerçekleşmişse geri dönmektedir. select metodunun geri dönüş değeri iki elemanlı demetlerden oluşan bir listedir. Bu listede gerçekleşen olaylara ilişkin bilgiler bulunmaktadır. Listeyi oluşturan demetlerin birinci elemanı SelectorKey isimli bir sınıf türünden bir nesnesidir. Bu demetin ikinci elemanı ise mask elemanıdır. Bu mask elemanı bize olayın türünü vermektedir. Bu olay selectors.EVENT_READ, selectors.EVENT_WRITE ya da bunların her ikisi olabilir. Programcı bu listeyi dolaşıp SelectorKey nesnelerinin içerisinden şu bilgileri alabilir: fileobj: Bu örnek özniteliği register fonksiyonuna girilen izlenecek soketi belirtmektedir. events: register fonksiyonuna girilen ikinci poarametreyi belirtir. data: register metoduna girilen üçüncü parametreyi belirtmektedir. Programcı accept işleminden elde ettiği soketi de yine register ettirerek izlemeye dahil etmelidir. 4) Bir client soketin kapatılması yine bir okuma işlemi gibi değerlendirilmektedir. Bu durumda programcının client soketi kapatıp onu izlemeden çıkartması gerekir. İzlemeden çıkartma işlemi için Selector sınıflarının unregister metotlareı kullanılmaktadır. Bu metodun parametresi izlemeden çıkartılacak soketi belirtir. 5) İşlem bitince selector nesnesi sınıfın close metoduyla kapatılır. Selector sınıfları da bağlam yönetim protokolünü desteklemektedir. Aşağıdaki Selector sınıflarının kullanımına bir örnek verilmiştir. Burada register fonksiyonun üçüncü parametresini hiç kullanmadık. İzleyen paragrafta bu üçüncü parametrenin naısl kullanılacağına yönelik bir örnek de verilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import selectors PORT_NO = 55555 def main(): try: with selectors.DefaultSelector() as selector: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) selector.register(server_sock, selectors.EVENT_READ) print('waiting for client...') while True: result = selector.select() for key, mask in result: if key.fileobj == server_sock: client_sock, (client_addr, client_port) = server_sock.accept() print(f'connected with client {client_addr}:{client_port}') selector.register(client_sock, selectors.EVENT_READ) else: text = key.fileobj.recv(1024).decode() if text: print(text) else: key.fileobj.close() selector.unregister(key.fileobj) except OSError as e: print(e) def accept_proc(): pass main() # client.py import socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Enter text:') client_sock.send(text.encode()) if text == 'quit': break client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Aşağıda Selector sınıfının register metodunda üçüncü parametre olarak fonksiyon kullanılmasına örnek verilmiştir. Tabii bu fonksiyonların Selector nesnelerine erişmesi gerekmektedir. Bunun bir yolu Selector nesnesini global düzeyde oluşturmak olabilir. Diğer bir yolu ise Selector nesnelerini de bu fonksiyonlara parametre olarak aktarmaktır. Biz aşağıdaki örnekte Selecor nesnelerini ve SelectorKey nesnelerini bu fonksiyonlara parametre olarak geçirdik. #------------------------------------------------------------------------------------------------------------------------------------ # server.py import socket import selectors PORT_NO = 55555 def main(): try: with selectors.DefaultSelector() as selector: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock: server_sock.bind(('', PORT_NO)) server_sock.listen(8) selector.register(server_sock, selectors.EVENT_READ, accept_proc) print('waiting for client...') while True: result = selector.select() for key, mask in result: key.data(selector, key) except OSError as e: print(e) def accept_proc(selector, key): client_sock, (client_addr, client_port) = key.fileobj.accept() print(f'connected with client {client_addr}:{client_port}') selector.register(client_sock, selectors.EVENT_READ, read_proc) def read_proc(selector, key): text = key.fileobj.recv(1024).decode() if text: print(text) else: print('closed') key.fileobj.close() selector.unregister(key.fileobj) main() # client.py iimport socket SERVER_NAME = 'localhost' SERVER_PORT = 55555 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') while True: text = input('Enter text:') client_sock.send(text.encode()) if text == 'quit': break client_sock.shutdown(socket.SHUT_RDWR) except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ 67. Ders 01/09/2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 1) Bunun için bir e-posta sunucu programının bulunuyor olması gerekir. Eğer tüm sistemi siz kuruyorsanız bu sunucuyu (server) da sizin kurmanız gerekmektedir. Zaten Windows sistemlerinde, UNIX/Linux sistemlerinde bu sunucular hazır biçimde bulunmaktadır. Tabii eğer domain hizmetini aldığınız bir kurum varsa onlar da zaten e-posta hizmeti vermek için hazır e-posta sunucuları bulundurmaktadır. E-posta gönderebilmek için ya da e-posta alabilmek için bizim e-posta sunucusunun adresini biliyor olmamız gerekir. Gönderme işleminde kullanılacak sunucu ile alma işleminde kullanılacak sunucu farklı olabilmektedir. Örneğin CSD'nin e-posta sunucuna "mail.csystem.org" adresiyle erişilebilmektedir. Bu sunucu hem gönderme hem de alma işlemini yapmaktadır. E-posta gönderebilmek için client ptogram ile server program "SMTP (Simple Mail Transfer Protocol)" denilen bir protokolle haberleşmektedir. O halde gönderim için bizim e-posta sunucuna bağlanarak SMTP protokolü ile göndereceğimiz e-postayı ona iletmemiz gerekir. 2) Biz göndereceğimiz e-postayı SMTP protokolü ile e-posta sunucumuza ilettikten sonra bu sunucu hedef e-posta sunucusuna bu e-postayı yine SMTP protokolü ile iletmektedir. E-postayı alan sunucu bunu bir posta kutusu (mail box) içerisinde saklar. 3) Karşı taraftaki client program POP3 ya da IMAP protokolü ile kendi e-posta sunucuna bağlanarak posta kutusundaki e-postayı yerel makineye indirir. client ---SMTP---> e-posta sunucusu ---SMTP--> e-posta sunucusu ---POP3/IMAP---> client Görüldüğü gibi POP3 ve IMAP protokolleri e-posta sunucusunun posta kutusundaki zaten gelmiş ve saklanmış olan e-postaları yerel makineye indirmek için kullanılmaktadır. POP3 protokolü RFC 1939 dokümanlarında açıklanmıştır. Protokol kabaca şöyle işlemektedir: 1) Client program 110 numaralı (ya da 995 numaralı) porttan server'a TCP ile fiziksel olarak bağlanır. 2) Protokolde mesajlaşma tamamen text tabanlı ve satırsal biçimde yapılmaktadır. Satırlar CR/LF karakterleriyle sonlandırılmaktadır. Protokolde client'ın gönderdiği her komuta karşı server bir yanıt göndermektedir. (Fiziksel bağlantı sağlandığında da server bir onay mesajı gönderir.) Eğer yanıt olumluysa mesaj "+OK" ile, eğer yanıt olumsuzsa mesaj "-ERR" ile başlatılmaktadır. Yani server'ın client'a gönderdiği mesajın genel biçimi şöyledir: +OK [diğer bilgiler] CR/LF -ERR [diğer bilgiler] CR/LF 3) Fiziksel bağlantıdan sonra client program mantıksal olarak server'a login olmalıdır. Login olmak için önce "user name" sonra da "password" gönderilmektedir. User name ve password gönderme işlemi aşağıdaki iki komutla yapılmaktadır. "USER CR/LF" "PASS CR/LF" Kullanıcı adı e-posta adresiyle aynıdır. Örneğin biz "test@csystem.org" için e-posta sunucusuna bağlanıyporsak buradaki kullanıcı ismi "test qcsystem.org" olacaktır. Parola e-postalarınızı okumak için kullandığınız paroladır. Sisteme başarılı bir biçimde login olduğumuzu varsayıyoruz. Tipik olarak server bize şu mesajı iletecektir: +OK Logged in. password yanlış girilmişse yeniden öce user name ve sonra password gönderilmelidir. 4) Client program LIST komutunu göndererek e-posta kutusundaki mesaj bilgilerini elde eder. LIST komutuna karşılık server önce aşağıdaki gibi bir satır gönderir: +OK 6 messages: Burada server e-posta kutusunda kaç e-posta olduğunu belirtmektedir. Sonra her e-postaya bir numarara vererek onların byte uzunluklarını satır satır iletir. Komut yalnızca '.' içeren bir satırla son bulmaktadır. Örneğin: +OK 6 messages: 1 1565 2 5912 3 11890 4 4920 5 9714 6 4932 . 5) Belli bir e-posta RETR komutuyla elde edilmektedr. Bu komuta elde edilecek e-postanın index numarası girilir. Örneğin: "RETR 2 CR/LF" RETR komutuna karşı server önce aşağıdaki gibi bir satır gönderir: +OK 5912 octets Burada programcı bu satırı parse ederek burada belirtilen miktarda byte kadar soketten okuma yapmalıdır. Anımsanacağı gibi porttan tam olarak n byte okumak TCP'de tek bir recv ile yapılamamaktadır. 6) Mesajı silmek için DELE komutu kullanılır. Komuta parametre olarak silinecek mesajın indeks numarası girilmektedir. Örneğin: "DELE 3 CR/LF" Bu komut uygulandığında server henüz e-postayı posta kutusundan silmez. Yalnızca onu "silinecek" biçiminde işaretler. Silme işlemi QUIT komutuyla oturum sonlandırıldığında yapılmaktadır. Eğer client silme eyleminden pişmanlık duyarsa RSET komutuyla ilk duruma gelir. RSET komutu logout yapmaz. Yalnızca silinmiş olarak işaretlenenlerin işaretlerini kaldırır. 7) STAT komutu o anda e-posta kutusundaki e-posta sayısını bize vermektedir. Bu komut gönderildiğinde aşağıdaki gibi bir yanıt alınacaktır: +OK 5 27043 Burada server e-posta kutusunda toplam 5 e-postanın bulunduğunu ve bunların byte uzunluklarının da 27043 olduğunu söylemektedir. 8) Protocol client programın QUIT komutunu göndermesiyle sonlandırılmaktadır. Örneğin: "QUIT CR/LF" 9) POP3 protokolününde client belli bir süre server'a hiç mesaj göndermezse server client'ın soketini kapatıp bağlantıyı koperatmektedır. Her ne kadar RFC 1939'da server'ın en azından 10 dakika beklemesi gerektiği söylenmişse de server'ların çoğu çok daha az bir süre beklemektedir. POP3 protokolünde client programın gönderdiği yazısal komutlar için server programın gönderdiği yanıtlar parse edilerek tam gerektiği kadar okuma yapılabilir. Ancak aşağıraki programda biz basitlik sağlamak amacıyla server'dan gelen mesajları başka bir thread ile ele aldık. Aşağıdaki program POP3 komutlarının manuel bir biçimde uygulanabilmesini sağlamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import socket import threading import time SERVER_NAME = 'mail.csystem.org' SERVER_PORT = 110 def main(): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock: client_sock.connect((SERVER_NAME, SERVER_PORT)) print('connected...') thread = threading.Thread(target=thread_proc, args=(client_sock, )) thread.start() while True: time.sleep(1) cmd = input('POP3>') cmd += '\r\n' if cmd == 'quit\r\n': break client_sock.send(cmd.encode()) client_sock.shutdown(socket.SHUT_RDWR) except Exception as e: print(e) def thread_proc(sock): try: while True: b = sock.recv(4096) if len(b) == 0: break response = b.decode() print(response, end='') except Exception as e: print(e) main() #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki programda biz server'a TCP ile bağlanıp ona CR/LF ile biten satırlar gönderip onun bize gönderdiği satırları yazdırdık. Aslında bu işlemi yapan zaten "telnet" denilen bir protokol ve bir client program vardır. Telnet protokolü TCP ile karşı tarafa bağlanıp oradan gelen yazıları alır. telnet client programı da gelen yazları ekrana yazdırmaktadır. telnet client programının kullanımı şöyledir: telnet Linux ve macOS sistemlerinde zaten telnet programı default biçimde bulunmaktadır. Ancak Windows sistemlerinde Telnet programını yükleyebilmek için "Programlar ve Özellikler / Windows Özelliklerini Aç ya da Kapat" seçeneğinden Telnet'in seçilmesi gerekir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında Python'un standart kütüphanesinde POP3 protokolünü uygulayan "poplib" isimli hazır modül bulunmaktadır. Bu modülün içerisindeki POP3 isimli sınıf zaten yukarıda açıkladığımız komutları server'a gönderip onun yanıtlarını bize vermektedir. Sınıf şöyle kullanılmaktadır: 1) Programcı e-posta sunucusunun adresini ve port numarasını vererek POP3 sınıfı türünden nesne yaratır. Ancak __init__ metodu henüz TCP bağlantısını kurmamaktadır. 2) Programcı POP3 sınıfının user ve pass_ (pass bir anahtar sözcüktür bu nedenle sonuna alt tire eklenmiştir) metotlarıyla login olur. TCP bağlantısı user metodu çarıldığında sağlanmaktadır. user ve pass_ metotları karşı taraftan gelen yanıta ilişkin byte nesneleriyle geri döner. 3) list komutu için list metodu kullanılmaktadır. Bu metot bize üç elemanlı bir demet verir. Demetin ilk elemanı LIST komutunun yanıtındaki ilk satırdır. İkinci elemanı bytes nesnelerinden oluşan bir listedir. Üçüncü elemanı ise server'ın bize verdiği e-posta listesindeki byte sayısıdır (Bu sayıya ilk satırdaki byte'lar dahil değildir ancak sonraki satırlardaki CR/LF'ler de bu sayıya dahildir.) 4) Belli bir mesajı elde etmek için yine sınıfın retr metodu kullanılır. Bu metot da yine üçlü bir demete geri döner. Demetin ilk elemanı yanıtın ilk satırını, diğer elemanı e-posta içeriğini ve son elemanı da ilk satırdan sonraki toplam byte sayısını vermektedir. Burada mesaj içeriği satırlan oluşan bir liste biçiminde verilmiştir. 5) Belli bir e-postayı silmek için dele metodu, silinenleri silinmemiş hale getirmek için rset metodu kullanılmaktadır. 6) İşlem bittiğinde quit metoduyla iletişim sonlandırılır. Aşağıdaki örnekte bir e-posta sunucusuna POP3 protokolü ile bağlanılıp oradan e-postalar çekilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import poplib try: pop3 = poplib.POP3('mail.csystem.org', 110) pop3.user('test@csystem.org') pop3.pass_('TheBeatles-1962') response, msg_list, total_bytes = pop3.list() for index, size in (b.decode().split() for b in msg_list): _, content, _ = pop3.retr(index) print(content) print('-----------------------------------------------------') pop3.quit() except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ GMail çok kullanılan bir e-posta hizmetidir. gmail'in e-posta sunucusunun adresi "pop.gmail.com" biçimindedir. Ancak GMail SSL kullandığı için GMail'den e-posta okumak için POP3 sınıfı yerine POP3_SSL sınıfı kullanılmalıdır. GMail'in POP3 server'ı 995 numaralı portu kullanmaktadır. Ancak GMail son zamanlarda POP3 erişimini isteğe bağlı hale getirmiştir. Bu nedenle E-Posta hesabına girilip e-posta hesabının "POP3" erişimine e-posta hesabının açılması gerekmektedir. E-Posta hesabının POP3 protokolüne açılması için GMail'den Ayarlara girilir. Oradan üst menüden "Yönlendirme ve POP/IMAP" seçilir. #------------------------------------------------------------------------------------------------------------------------------------ import poplib try: pop3 = poplib.POP3_SSL('pop.gmail.com', 995) pop3.user('csystem1903@gmail.com') pop3.pass_('TheLongAndWindingRoad-1969') response, msg_list, total_bytes = pop3.list() for index, size in (b.decode().split() for b in msg_list): _, content, _ = pop3.retr(index) print(content) print('-----------------------------------------------------') pop3.quit() except Exception as e: print(f'Error: {e}') #------------------------------------------------------------------------------------------------------------------------------------ POP3 ya da IMAP protokolü ile e-postaları elde etmek oldukça kolaydır. Ancak asıl zor olan kısım e-posta içerisindeki bilgilerin parse edilmesi ve uygun biçimde gösterilmesidir. POP3 ve IMAP protokolleri e-postayı yazısal olarak gönderip almaktadır. Pekiyi bir e-postanın içerisinde resim varsa, ses versa yani başka türden bilgiler varsa bu iletim nasıl yapılmaktadır? İşte e-postadaki yazı olmayan olmayan öğeler aslında yazısal biçime dönüştürülüp gönderilmektedir. Alınan e-posta yazısının içerisinde hangi parçanın hangi türden bilgiyi içerdiği bilgisi MIME denilen bir formata göre kodlanmaktadır. MIME (Multipurpose Internet Mail Extensions) çeşitli öğelerin yazısal olarak kodlanması için kullanılan bir tekniktir. O halde e-postanın yazısını alan programcı bunu MIME standardına göre parse etmelidir. Bu parse işlemi şöyle yapılmaktadır: 1) Programcı email.parser modülündeki Parser sınıfı türünden ya da BytesParser nesnesi türünden bir nesne yaratır. 2) Parser nesnesi ile sınıfın parserbytes metodunu çağırır. Bu metot e-posta mesajının içeriğini parametre olarak almaktadır. Bu metot bize e-posta mesajını temsil eden Message türünden bir nesne vermektedir. 3) Artık programcı bu Message türünden MIME kodlanmış mesajın birden fazla parçadan oluşup oluşmadığını kontrol etmelidir. MIME mesajları "text/plain", "text/html", "image/jpeg", "image/gif" gibi parçalardan oluşmaktadır (Tüm MIME türlerini Internet'te bulabilirsiniz.) 4) Eğer mesaj birden fazla parçadan oluşuyorsa bu parçalar Message sınıfının walk isimli metoduyla elde edilebilir. Mesajın tamamı Message isimli sınıfla temsil edilmiştir. Ancak onun parçaları da Message isimli sınıfla temsil edilmiştir. Programcının artık walk işlemiyle ilgili parçanın türünü tespit etmesi gerekir. Bunun için Message sınıfının get_content_type isimli metodu kullanılmaktadır. 5) Mesajın türü tespit edildikten sonra o türün bilgileri Message sınıfının get_payload metodu ile elde edilir. 6) E-posta mesajındaki From, To, Subject gibi başlık alanlarını Message sınıfının [...] operatör metodu ile elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import poplib from email.parser import BytesParser from PIL import Image pop3 = poplib.POP3('mail.csystem.org') pop3.user('test@csystem.org') pop3.pass_('Csystem-1993') response, msg_list, total_bytes = pop3.list() parser = BytesParser() for index, size in (b.decode().split() for b in msg_list): _, content, _ = pop3.retr(index) b = b'\n'.join(content) message = parser.parsebytes(b) from_text = message['From'] subject_text = message['Subject'] to_text = message['To'] print(f'From: {from_text}') print(f'Subject: {subject_text}') print(f'To: {to_text}') for part in message.walk(): part_type = part.get_content_type() if part_type == 'text/plain': text = part.get_payload() print(f'Plain Text: {text}') elif part_type == 'text/html': text = part.get_payload() pass elif part_type == 'image/jpeg': file_name = part.get_filename() data = part.get_payload(decode=True) with open(file_name, 'wb') as f: f.write(data) """ image = Image.open(file_name) image.show() """ print('-------------------------------------------') pop3.quit() #------------------------------------------------------------------------------------------------------------------------------------ 68. Ders 02/09/2023 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ E-Posta göndermek için SMTP (Simple Mail Transport Protocol) isimli uygulama katmanı protokolü kullanılmaktadır. Bu protokol de yazısal tabanlıdır. Tipik olarak client program e-posta sunucusuna TCP ile fiziksel olarak bağlanır. Sonra mantıksal biçimde login olur. Daha sonra da e-posta mesajını bir yazı olarak karşı tarafa gönderir. Bu e-posta yazısı bir başlık içermektedir. Başlık ile posta içeriği arasında bir satır boşluk bırakılır. E-Posta içeriği karmaşık öğelere sahip olabilir. Bu durumda içerik MIME olarak kodlanır. SMTP protokolünün güncel versiyonu RFC 5321'de dokğmante edilmiştir: https://datatracker.ietf.org/doc/html/rfc5321 SMTP'de de client'ın her bir komutuna karşılık server bir yanıt vermektedir. Verilen yanıtın birer sayısal kodu ve içeriği vardır. Biz bir kullanıcı olarak bir başkasına e-posta gönderebilmek için e-postamızı kendi smtp sunucumuza göndeririz. Bu sunucu e-postayı karşı atarafın sunucusuna göndermektedir. Yani biz e-postayı aslında kendimiz doğrudan karşı tarafa göndermemekteyiz. Bu işlemi bizim hizmet aldığımız sunucu yapmaktadır. Python'da e-posta göndermek için smtplib isimli bir modül bulundurulmuştur. Tipik olarak yapılması gerekenler şunlardır: 1) Önce SMTP sınıfı türünden ya da SMTP_SSL sınıfı türünden bir nesne yaratılır. Bu sınıfların __init__ metotları bizden e-posta sunucusunun adresini ve port numarasını almaktadır. SMTP protokolünün orijinal port numarası 25'tir. Ancak bugün e-posta sunucuları şifrelemeyi de sağlayan e-postalar için genel olarak 587 portunu kullanmaktadır. E-Posta sunucunuzun hangi portu kullandığını öğrenmelisiniz. Örneğin: smtp = smtplib.SMTP('mail.csystem.org', 587) 2) Bundan sonra programcının SMTP nesnesi yoluyla sınıfın login metodunu çağırarak e-posta sunucusuna mantıksal olarak bağlanması gerekir. login metodu kullanıcı adı ve parolayı parametre biçiminde almaktadır. Örneğin: smtp.login('test@csystem.org', 'TheBeatles-1962') 3) Artık e-posta mesajının metni oluşturulur. SMTP protokolünde aslında e-posta tek bir yazıdan oluşmaktadır. Yazının başında bir başlık kısmı bulunur. Başlık kısmının genel formatı şöyledir: FROM: TO: SUBJECT: DATE: Bu başlık kısmından sonra bir satır boşluk bırakılmalıdır. Ondan sonra da mesaj metni bulundurulur. Buradaki FROM ve TO alıcının gördüğü bilgilerdir. Yani kullnıcı aslında sanki e-postayı başka birisi göndermiş gibi yapabilir. Örneğin: mail_text = """FROM: ali@csystem.org TO: csystem1903@gmail.com SUBJECT: Test DATE: 02/09/2023 This is test """ 4) Bundan sonra e-posta SMTP sınıfının sendmail metoduyla yollanır. Bu metot üç parametre almaktadır. Metodun birinci parametresi yollayan kişinin e-posta adresini, ikinci parametresi hedef e-posta adresini ve üçüncü parametresi de e-posta metnini almaktadır. Eğer e-posta birden fazla kişiye yollanacaksa bu durumda metodun ikinci parametresi dolaşılabilir bir nesne (örneğin bir liste) olmalıdır. Birinci parametrede belirtilen e-posta adresi aslında zarfın üzerinde yazılan adrestir. Yani eğer e-posta gönderilemezse bu adrese bildirimde bulunulacaktır. Mesaj başlığında FROM ve TO kısmı tamamen alıcının göreceği bilgilerden oluşmaktadır. Yani bu kısımdaki bu bilgiler doğrulanmamaktadır. Örneğin: smtp.sendmail('test@csystem.org', ['csystem1903@gmail.com', 'aslank@csystem.org'], mail_text) 5) Nihayetinde e-posta gönderimi bittikten sonra sınıfn quit metoduyla işlemler sonlandırılır. Aşağıda e-posta göndermeye bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import smtplib try: smtp = smtplib.SMTP('mail.csystem.org', 587) smtp.login('test@csystem.org', 'TheBeatles-1962') mail_text = """FROM: ali@csystem.org TO: csystem1903@gmail.com SUBJECT: Test DATE: 02/09/2023 CC: aslank@csystem.org; This is test """ smtp.sendmail('test@csystem.org', ['csystem1903@gmail.com', 'aslank@csystem.org'], mail_text) smtp.quit() except Exception as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi göndereceğimiz e-posta içerisinde yazının dışında başka şeyler de olmasını istiyorsak ne yapmamız gerekir? İşte bu durumda POP3 protokolünde de gördüğümüz gibi e-posta içeriğini yazısal biçimde MIME olarak ifade etmemiz gerekir. Bu amaçla email modülünde çeşitli sınıflar bulundurulmuştur. Bunun için şu işlemler yapılmalıdır: 1) Önce email.mime.multipart modülündeki MIMEMultipart isimli sınıf türünden bir nesne yaratılır. Bu nesne parçalardan oluşan e-postanın tamamını temsil etmektedir. Bu nesne üzerinde as_string metodu tüm MIME dokümanını yazısal olarak, as_bytes metodu ise byte dizisi olarak bize vermektedir. E-postaya ilişkin FROM, To, Subject bilgileri bu MimeMultipart nesnesinin [...] operatörü yolu ile belirtilmektedir. Örneğin: mime_multipart = MIMEMultipart() mime_multipart['From'] = 'test@csystem.org' mime_multipart['To'] = 'test@csystem.org;aslank@csystem.org' mime_multipart['Subject'] = 'MIME Test E-Postası' 2) Daha sonra çeşitli modüllerde bulunan MIMEText, MIMEImage, MIMEAudio, MIMEApplication gibi sınıflar yoluyla e-postanın ilgili kısımları MIME olarak ifade edilir. Bu kısımlar ana e-postaya MIMEMultipart nesnesinin attach metodu çağrılarak eklenmektedir. Örneğin: text_msg1 = """ Bugün hava çok güzeldi. Ben de parka gittim. Orada dinlendim. """ mime_text1 = MIMEText(text_msg1) mime_multipart.attach(mime_text1) with open('AbbeyRoad.jpg', 'rb') as f: image_data = f.read() mime_image = MIMEImage(image_data) mime_image.add_header('Content-Disposition', 'attachment', filename='AbbeyRoad.jpg') #------------------------------------------------------------------------------------------------------------------------------------ import smtplib from email.mime.multipart import MIMEMultipart from email.mime.image import MIMEImage from email.mime.text import MIMEText smtp = smtplib.SMTP('mail.csystem.org', 587) response_code, response_text = smtp.login('test@csystem.org', 'Csystem-1993') mime_multipart = MIMEMultipart() mime_multipart['From'] = 'test@csystem.org' mime_multipart['To'] = 'test@csystem.org;aslank@csystem.org' mime_multipart['Subject'] = 'MIME Test E-Postası' text_msg1 = """ Bugün hava çok güzeldi. Ben de parka gittim. Orada dinlendim. """ mime_text1 = MIMEText(text_msg1) mime_multipart.attach(mime_text1) with open('AbbeyRoad.jpg', 'rb') as f: image_data = f.read() mime_image = MIMEImage(image_data) mime_image.add_header('Content-Disposition', 'attachment', filename='AbbeyRoad.jpg') text_msg2 = """ Bu resmi de yolluyorum. Bu fotoğraf Londra'da Abbey Road denilen semtte çekilmiştir.' """ mime_text2 = MIMEText(text_msg2) mime_multipart.attach(mime_text2) mime_multipart.attach(mime_image) result = smtp.sendmail('test@csystem.org', ['test@csystem.org', 'aslank@csystem.org'], mime_multipart.as_string()) smtp.quit() #------------------------------------------------------------------------------------------------------------------------------------ Gmail yoluyla e-posta gönderilecekse SMTP server adresi olarak "gmail.smtp.com" kullanılmalıdır. GMail SSL kullanmaktadır. Dolayısıyla SMTP nesnesini SMTP_SSL sınıfı ile yaratmalıyız. Gmail SMTP için 465 numaralı portu kullanmaktadır. Aşağıda Gmail ile e-posta gönderme örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import smtplib from email.mime.multipart import MIMEMultipart from email.mime.image import MIMEImage from email.mime.text import MIMEText smtp = smtplib.SMTP('mail.csystem.org', 587) response_code, response_text = smtp.login('test@csystem.org', 'TheBeatles-1962') mime_multipart = MIMEMultipart() mime_multipart['From'] = 'test@csystem.org' mime_multipart['To'] = 'csystem1903.gmail.com;aslank@csystem.org' mime_multipart['Subject'] = 'MIME Test E-Postası' text_msg1 = """ Bugün hava çok güzeldi. Ben de parka gittim. Orada dinlendim. """ mime_text1 = MIMEText(text_msg1) mime_multipart.attach(mime_text1) with open('AbbeyRoad.jpg', 'rb') as f: image_data = f.read() mime_image = MIMEImage(image_data) mime_image.add_header('Content-Disposition', 'attachment', filename='AbbeyRoad.jpg') text_msg2 = """ Bu resmi de yolluyorum. Bu fotoğraf Londra'da Abbey Road denilen semtte çekilmiştir.' """ mime_text2 = MIMEText(text_msg2) mime_multipart.attach(mime_text2) mime_multipart.attach(mime_image) result = smtp.sendmail('test@csystem.org', ['csystem1903@gmail.com', 'aslank@csystem.org'], mime_multipart.as_string()) smtp.quit() #------------------------------------------------------------------------------------------------------------------------------------ 69. Ders 08/09/2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ UDP (User Datagram Protocol) anımsanacağı gibi "bağlantısız (connectionless)", "datagram tabanlı (datagram)", "güvenilir olmayan (unreliable)" ancak hızlı bir protokoldür. TCP'ye göre oldukça seeyrek kullanılmaktadır. Özellikle periyodik uzun olmayan mesajların hızlı iletilmesi gerektiğinde tercih edilmektedir. Bu protokolde bir bağlantı yapılmadığı için akış kontrolü uygulanmaz. Gönderici taraf bilginin karşı tarafa gidip gitmediğini bilemez. Zaten bu mekanizmalardan yoksun olması onu hızlı yapmaktadır. UDP haberleşme paket tabanlıdır. Buradaki pakete "datagram" da denilmektedir. Paket tabanlı haberleşmede bir taraf diğer tarafa bir grup bilgiyi bir paket olarak gönderir. Karşı taraf da bunu paket olarak alır. Anımsanacağı gibi TCP client-server tarzı bir çalışma modelini bize uygulatmaktadır. TCP'de bağlantıyı kabul eden (accept yapan) tarafa "server", bağlantı kurmak isteyen tarafa ise "client" denilmektedir. UDP bağlantısız bir protokol olduğuna göre burada client ve server rolleri açık değildir. Ancak yine de genel olarak mesajları alıp iş yapan tarafa "server", mesaj gönderen tarafa ise "client" denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ UDP server programın organizasyonu şöyledir: 1) Server program önce bir UDP soketi yaratır. UDP soket yaratılırken soket fonksiyonun ikinci parametresi socket.SOCK_DGRAM geçilmelidir. Üçüncü parametre girilmeyebilir ya da socket.IPPROTO_UDP biçiminde girilebilir. Örneğin: server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 2) Server program soket bind etmelidir. Her ne kadar bir bağlantı olmasa da client programlar datagram paketlerini gönderirken bir IP adresi ve portu belirtirler. Yani server program yine bir portla kendisini ilişkilendirmelidir. 3) Artık server Socket sınıfının recvfrom isimli metoduyla gelen datagram paketini elde edebilir. recvfrom metodu default durumda blokeye yol açmaktadır. Yani herhangi bir kişiden paket gelene kadar recvfrom akışı bekletmektedir. Metodun birinci parametresi mesajın uzunluğunu belirtir. Eğer mesaj uzunluğu için girilen parametre gönderilen UDP paketinin uzunluğundan kısa ise default durumda belirtilen sayıda byte kırpılararak okuma yapılmaktadır. İkinci parametre mesajın alınma biçimine ilişkin bazı flag değerlerini alabilmektedir. İkinci parametre hiç girilmeyebilir. recvfrom metodunun geri dönüş değeri iki elemanlı bir demettir. Demetin birinci elemanı UDP paketindeki bilgileri içeren bytes nesnesdir. Demetin ikinci elemanı da iki elemanlı bir demettir. Bu iki elemanlı demetin ilk elemanı paketi gönderen tarafın IP adresini, ikinci elemanı ise kaynak port numarasını belirtmektedir. Bu durumda recvfrom metodu aşağıdaki gibi kullanılmalıdır: b, (ip, port) = server_sock.recvfrom(8192) 4) Server işlemler bitirince açtığı soketi kapatmalıdır. UDO soketlerde shutdown işlemi yoktur. Aşağıda bir UDP server örneği verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ PORT_NO = 55555 import socket try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_sock: server_sock.bind(('', PORT_NO)) print('waiting datagrams...') while True: b, (ipaddr, port) = server_sock.recvfrom(8192) print(f'Message received from {ipaddr}:{port} "{b.decode()}"') except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ UDP client programın da organizasyonu şöyledir: 1) UDP client program da önce bir UDP soket yaratır. Yine soket yaratılırken soket türü socket.SOCK_DGRAM girilmelidir. 2) Client program isteğe başlı olarak bind işlemi yapabilir. Örneğin client belli bir yerel porttan bilgiyi göndermek isteyebilir. Ancak genel olarak böyle bir durum sıklıkla arzu edilmemektedir. 3) Client server'a socket sınıfının sendto metoduyla UDP paketini gönderir. sendto metodunun iki parametresi vardır. (sendto üç parametreli bir biçimde de kullanılabilmektedir. Bu durumda metot bir flags parametresi de alır.) Metodun birinci parametresi gönderilecek UDP paketini oluşturan bytes nesnesidir. İkinci parametre server IP adresi ve port numarasını içeren iki elemanlı bir demettir. Örneğin: sock.sendto(b, (SERVER_NAME, PORT_NO)) 4) Client işlemn bitince close metodu ile soketi kapatmalıdır. Aşağıda örnek client program verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ SERVER_NAME = '127.0.0.1' PORT_NO = 55555 import socket try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_sock: while True: text = input('Message Text:') client_sock.sendto(text.encode(), (SERVER_NAME, PORT_NO)) if text == 'quit': break except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ Tabii aslında recvform ve sendto metotları hem client hem de server program tarafından uygulanabilir. Örneğin server recvfrom metodu ile client'tan bir bilgi alıp ona sento metoduyla yanıtı yollayabilir. #------------------------------------------------------------------------------------------------------------------------------------ # server.py PORT_NO = 55555 import socket try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_sock: server_sock.bind(('', PORT_NO)) print('waiting datagrams...') while True: b, (ipaddr, port) = server_sock.recvfrom(8192) text = b.decode() print(f'Message received from {ipaddr}:{port} "{text}"') if text != 'quit': rev_text = text[::-1] server_sock.sendto(rev_text.encode(), (ipaddr, port)) except OSError as e: print(e) # client.py SERVER_NAME = '127.0.0.1' PORT_NO = 55555 import socket try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_sock: while True: text = input('Message Text:') client_sock.sendto(text.encode(), (SERVER_NAME, PORT_NO)) if text == 'quit': break b, (ipaddr, port) = client_sock.recvfrom(8192) print(f'Message received from server: {b.decode()}') except OSError as e: print(e) #------------------------------------------------------------------------------------------------------------------------------------ UDP server programlarında server'a pek çok client UDP paketi gönderiyor olabilir. Server bir UDP paketini aldığında onu kendisi işlerse yeni gelen paketleri kaçırabilir. Kaçırmanın ötesinde paketi gönderen client yanıtı geç alabilir. Bu tür durumlarda server UDP paketini aldıktan sonra onun işlemesini bir thread'e havale edebilir. Bu tür thread'lerin o anda yaratılması yavaşlığa yol açmaktadır. Aynı durum TCP uygulamalarında da karşımıza çıklabilmektedir. TCP server bir client'tan bilgiyi aldığında eğer thread modelini kullanmıyorsa bilgiyi işlerken gecikmeye yol açabilir. İşte bu tür durumlarda genel olarak "thread havuzu (thread pool)" denilen mekanizma kullanılmaktadır. TCP ya da UDP server bilgiyi okuduğunda ona yanıt vermek için bir thread (ya da process) yaratırsa bu thread'in yaratılma ve yok edilme işlemindeki zamansal maliyet faydayı ortadan kaldırabilmektedir. Thread havuzları bu bu maliyeti elimine etmek için düşünülmüş bir organizasyondur. Thread havuzunda zaten yaratılmış olan belli miktarda thread suspend durumda havuzda bekletilmektedir. Server bilgiyi işlemek için havuzdaki zaten yaratılmış ama suspend durumda olan thread'leri kullanır. Böylece gelen mesajın işlenmesi hızlıca yapılabilmektedir. Java, .NET, Qt gibi framework'lerdeki thread havuzları duruma göre suspend durumdaki thread sayısını otomatik artırıp azaltabilmektedir. CPython yorumlayıcısında GIL yüzünden thread'ler bu bakımdan etkin biçimde kullanılamamaktadır. Daha önceden de görüldüğü gibi thread yerine çoğu kez aslında yüksek maaliyeti olan proses yaratmak CPython için daha iyi bir çözüm olabilmektedir. Pyton'a belli bir süreden sonra proses temelinde çalışan bir thread havuzu mekanizması ilkel düzeyde de olsa eklenmiştir. Ancak programcı zaten üretici-tüketici problemleri için hazır bulundurulmuş Queue sınıfından da bu bağlamda faydalanabilir. Yani örneğin belli miktarda proses (ya da thread) yaratılıp ortak bir Queue nesnesini kullanabilir. Böylece server gelen isteği kuyruğa atar. Client'ların herhangi biri kuyruktan mesajı alarak işleyebilir. Tabii proseslerle çalışma thread'lerle çalışmaya göre bazı sıkıntılara da sahiptir. Python'da ayrıca ThreadPoolExecutor isimi thread havuzu düşünülmüş thread mekanizmasını kullanan bir sınıf da bulunmaktadır. Ancak bu sınıf CPython yorumlayıcında CPU yoğun işlemlerde GIL yüzünden zayıf bir performans göstermektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Soket işlemlerini bitirdikten sonra şimdi Python standart kütüphanesindeki itertools modülünde buunan bazı yardımcı fonksiyonları göreceğiz. Ancak ondan önce Python kursunda görmüş olduğumuz birkaç fonksiyonu hatırlatmak istiyoruz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi map built-in fonksiyonu bir fonksiyon ve bir dolaşılabilir nesneyi alıp bize bir dolaşım nesnesi veriyordu. Bu dolaşım nesnesini dolaştığımızda aslında biz map fonksiyonuna verdiğimiz dolaşılabilir nesnenin yine map fonksiyonuna verdiğimiz fonksiyona sokulmasından elde edilen geri dönüş değerlerini elde ederiz. Tabii benzer işlem içlemlerle ya da üretici ifadelerle de yapılabilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ def foo(x): return x * x a = [1, 2, 3, 4, 5] m = map(foo, a) for x in m: print(x, end=' ') print() # eşdeğeri for x in (foo(y) for y in a): print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ Örneğin map fonksiyonuyla biz yazıların uzunluklarını kısa bir ifadeyle elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] for x in map(len, names): print(x, end= ' ') #------------------------------------------------------------------------------------------------------------------------------------ map fonksiyonu üretici bir fonksiyon olarak çok basit biçimde aşağıdaki gibi yazılabilir. #------------------------------------------------------------------------------------------------------------------------------------ def mymap(f, iterable): for elem in iterable: yield f(elem) names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] a = list(mymap(len, names)) print(a) #------------------------------------------------------------------------------------------------------------------------------------ Bu tür fonksiyonları üretici fonksiyon olarak değil de dolaşılabilir bir sınıf olarak yazmak daha hızlı bir çalışmaya yol açmaktadır. Gerçekten de standart kütüphanede map aslında bir sınıf olarak yazılmıştır. Aşağıda map fonksiyonunun bir sınıf olarak yazımına örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ class mymap: def __init__(self, f, iterable): self.f = f self.iterator= iter(iterable) def __iter__(self) : return self def __next__(self): return self.f(next(self.iterator)) names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] iterator = mymap(len, names) a = list(iterator) print(a) a = list(iterator) print(a) #------------------------------------------------------------------------------------------------------------------------------------ filer isimli built-in fonksiyon benzer biçimde çalışmaktadır. Ancak fonksiyonun geri dönüş değeri True olan elemanları bize verir. filter fonksiyonu da bize dolaşılabilir bir nesne vermektedir. Tabii filter eşdeğeir de yine bir üretici ifade ile oluşturulabilir. #------------------------------------------------------------------------------------------------------------------------------------ names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] for x in filter(lambda x: len(x) > 4, names): print(x, end= ' ') print() # eşdeğeri for x in (y for y in names if len(y) > 4): print(x, end= ' ') #------------------------------------------------------------------------------------------------------------------------------------ filer fonksiyonu da basit bir biçimde üretici fonksiyonla aşağıdaki gibi yazılabilir. #------------------------------------------------------------------------------------------------------------------------------------ def myfilter(f, iterable): for elem in iterable: if f(elem): yield elem names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] for name in filter(lambda s: s[0] == 'a', names) : print(name) #------------------------------------------------------------------------------------------------------------------------------------ Tabii filter fonksiyonu da aslında bir sınıf biçiminde yazılmıştır. Aşağıda filet fonksiyonunun dolaşılabilir bir sınıf biçiminde yazımına örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ class myfilter: def __init__(self, f, iterable): self.f = f self.iterator = iter(iterable) def __iter__(self): return self def __next__(self): while True: val = next(self.iterator) if self.f(val): return val names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] for name in myfilter(lambda s: s[0] == 'a', names) : print(name) #------------------------------------------------------------------------------------------------------------------------------------ Built-in zip fonksiyonu ise dolaşılabilir nesneleri alarak onların karşılıklı elemanlarından oluşan demetleri veren bir dolaşım nesnesi geri döndürmektedir. unzip işlemi de yine zip fonksiyonu ile yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ a = [1, 2, 3, 4, 5] b = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'süleyman'] z = zip(a, b) for x, y in z: print(x, y) c = list(zip(a, b)) print(c) numbers, names = zip(*c) print(numbers) print(names) #------------------------------------------------------------------------------------------------------------------------------------ İşte itertools modülü içerisinde built-in map, filter, zip gibi fonksiyonlara bezner bazı dolaşılabilir nesnelerle kullanılabilen faydalı fonksiyonlar bulunmaktadır. itertools modülündeki accumulate fonksiyonu bir dolaşılabilir nesneyi alır bize başka bir dolaşım nesnesi verir. Biz accumulate fonksiyonun verdiği dolaşım nesnesini dolaştığımızda elemanların kümülatif toplamlarını elde ederiz. Fonksiyonunun parametrik yapısı şöyledir: itertools.accumulate(iterable, func = operator.add, *, initial = None) Fonksiyon tipik olarak tek argümanla çağrılmaktadır. Örneğin: a = [1, 2, 3, 4, 5] Buradaki kümülatif toplamlar sırasıyla 1, 3, 6, 10 ve 15'tir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] result = itertools.accumulate(a) for x in result: print(x, end=' ') # 1 3 6 10 15 #------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki örnekte isimlerin karakter uzunluklarının kümülatif toplamları bir liste olarak elde edilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] a = list(itertools.accumulate(map(len, names))) print(a) #------------------------------------------------------------------------------------------------------------------------------------ accumulate fonksiyonun initial parametresi ilk toplam değerini belirtmektedir. Dolaşım sırasında önce initial parametresiyle belirtilen değer elde edilir. Sonra dolaşıldıkça bu değerin üzerine eklenen değerler elde edilir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] result = itertools.accumulate(a, initial=10) for x in result: print(x, end=' ') # 10 11 13 16 20 25 #------------------------------------------------------------------------------------------------------------------------------------ Aslında accumulate fonksiyonun ikinci parametresi iki parametreli bir fonksiyon olarak da girilebilir. Bu durumda bu fonksiyonun ilk parametresi kümülatif değer, ikinci parametresi dolaşılabilir nesnedeki sıradaki değerdir. Böylece biz örneğin kümülatif toplamları değil, kümülatif çarpımları da bulabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] result = itertools.accumulate(a, lambda cum, x: cum * x, initial=10) for x in result: print(x, end=' ') # 10 10 20 60 240 1200 #------------------------------------------------------------------------------------------------------------------------------------ Tabii biz fonksiyon girdiğimizde initial parametresini kullanmak zorunda değiliz. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] result = itertools.accumulate(a, lambda cum, x: cum * x) for x in result: print(x, end=' ') # 1 2 6 24 120 #------------------------------------------------------------------------------------------------------------------------------------ Python'ın "Standard Library" dokümanında accumulate fonksiyonun muhtemel gerçekleştirimi aşağıdaki gibi verilmiştir: #------------------------------------------------------------------------------------------------------------------------------------ def accumulate(iterable, func=lambda cum, val: cum + val, *, initial=None): it = iter(iterable) total = initial if initial is None: try: total = next(it) except StopIteration: return yield total for element in it: total = func(total, element) yield total result = accumulate(a, lambda cum, x: cum * x) for x in result: print(x, end=' ') # 10 10 20 60 240 1200 #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki chain isimli fonksiyon bizden birden fazla dolaşılabilir nesneyi parametre olarak alır. Sonra bize yeni bir dolaşım nesnesi verir. chain fonksiyonun verdiği dolaşım nesnesi dolaşıldığında sanki bizim fonksiyona verdiğimiz dolaşılabilir nesneler tek bir dolaşılabilir nesneymiş gibi sırasıyla dolaşılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] b = ('ali', 'veli', 'selami') c = 'ankara' result = itertools.chain(a, b, c) for x in result: print(x, end=' ') # 1 2 3 4 5 ali veli selami a n k a r a #------------------------------------------------------------------------------------------------------------------------------------ Standart Kütüphane dokümantasyonunda muhtemel bir gerçekleştirim aşağıdaki gibi verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ def chain(*args): for iterable in args: for x in iterable: yield x result = chain(a, b, c) for x in result: print(x, end=' ') # 1 2 3 4 5 ali veli selami a n k a r a #------------------------------------------------------------------------------------------------------------------------------------ Her ne kadar dokümantasyonda chain fonksiyonun muhtemel gerçekleştirimi üretici fonksiyon biçiminde verilmişse de aslında CPython'da gerçekleştirim chain isimli bir dolaşılabilir sınıf biçiminde yapılmıştır. Aşağıda chain işlemini yapan dolaşılabilir bir sınıf örneği de verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ class chain: def __init__(self, *args): self.args = args self.iterator = iter(self.args[0]) if args else iter(args) self.index = 0 def __iter__(self): return self def __next__(self): try: result = next(self.iterator) except StopIteration: self.index += 1 if self.index < len(self.args): self.iterator = iter(self.args[self.index]) result = next(self.iterator) else: raise StopIteration return result a = [1, 2, 3, 4, 5] b = ('ali', 'veli', 'selami') c = 'ankara' d = [1.2, 3.4, 5.5] result = chain(a, b, c, d) for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ Ayrıca chain isimli sınıfın from_iterable isimli bir static metodu da vardır. Bu metodun farkı iterable nesneleri ayrı nesneler olarak değil tek bir iterable nesne biçiminde almasıdır. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] b = ('ali', 'veli', 'selami') c = 'ankara' d = [1.2, 3.4, 5.5] iterables = [a, b, c, d] result = itertools.chain.from_iterable(iterables) for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ Tabii aslında biz aynı şeyi chain fonksiyonunu çağırırken *'lı argüman kullanarak da sağlayabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [1, 2, 3, 4, 5] b = ('ali', 'veli', 'selami') c = 'ankara' d = [1.2, 3.4, 5.5] iterables = [a, b, c, d] result = itertools.chain(*iterables) for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda yazdığımız sınıfa fromm_iterable metodunu aşağıdaki gibi ekleyebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ class chain: def __init__(self, *args): self.args = args self.iterator = iter(self.args[0]) if args else iter(args) self.index = 0 def __iter__(self): return self def __next__(self): try: result = next(self.iterator) except StopIteration: self.index += 1 if self.index < len(self.args): self.iterator = iter(self.args[self.index]) result = next(self.iterator) else: raise StopIteration return result @staticmethod def from_iterable(itreables): return chain(*iterables) a = [1, 2, 3, 4, 5] b = ('ali', 'veli', 'selami') c = 'ankara' d = [1.2, 3.4, 5.5] result = chain.from_iterable(iterables) for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ 70. Ders 15/09/2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki combinations isimli fonksiyonu biz aslında Python derslerinde görmüştük. Bu fonksiyon bir dolaşılabilir nesnenin n'li kombinasyonlarını demet olarak veren bir dolaşım nesnesi vermektedir. Örneğin [1, 2, 3, 4, 5] gibi dolaşılabilir bir nesnede 3'lü kombinasyonlar şöyle elde edilebilir: a = [1, 2, 3, 4, 5] result = itertools.combinations(a, 3) for x in result: print(x) #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = 'ABCDEF' iterator = itertools.combinations(s, 3) for t in iterator: print(*t, sep='') #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki permutations isimli fonksiyon da combinations fonksiyonuyla aynı parametrelere sahiptir. Ancak iterable nesnenin permütasyonlarını elde etmekte kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = 'ABCDEF' iterator = itertools.permutations(s, 3) for t in iterator: print(*t, sep='') #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki combinations_with_replacement isimli fonksiyon yine dolaşılabilir nesnenin kombinasyonlarını verir ancak kombinasyonlar aynı elemanladan da oluşabilmektedir. Örneğin 'ABC' yazısının bu biçimdeki ikili kombinasyonları AA AB AC BB BC CC biçimindedir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = 'ABC' result = itertools.combinations_with_replacement(s, 3) for x in result: print(x) #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki compress isimli fonksiyon bizden iki dolaşılabilir nesne alır. İkinci dolaşılabilir nesne bool değerler içerir. (Eüer bu nesnedeki değerler bool türden değilse bool türe fonksiyon tarafından dönüştürülmektedir.) Fonksiyon bize bir dolaşım nesnesi verir. Ancak bu dolaşım nesnesi dolaşıldığında ikinci dolaşılabilir nesnedeki True olan elemanlara karşı gelen birinci dolaşılabilir nesnedeki elemanlar elde edilecektir. Fonksiyona girilen iki dolaşılabilir nesnenin aynı uzunlukta olması gerekmez. Kısa olan bittiğinde işlem bitirilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] selector = [True, False, True, True, False] result = itertools.compress(s, selector) # ali selami ayşe for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ Her ne kadar standart kütüphanedeki compress bir sınıf olarak yazılmışsa da standart kütüphane dokümantasyonunda muhtemel bir gerçekleştirim aşağıdaki gibi verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ def compress(data, selectors): return (d for d, s in zip(data, selectors) if s) s = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] selector = [True, False, True, True, False] result = compress(s, selector) # ali selami ayşe for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ compress fonksiyonunu biz de dolaşılabilir bir sınıf biçiminde aşağıdaki gibi yazabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ class compress: def __init__(self, iterable, selectors): self.iterable = iterable self.selectors = selectors self.zipped = zip(self.iterable, self.selectors) def __iter__(self): return self def __next__(self): while True: x, y = next(self.zipped) if y: return x s = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] selector = [True, False, True, True, False] result = compress(s, selector) # ali selami ayşe for x in result: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki count fonksiyonu belli bir değerden başlayarak belli artımlarla sonsuz döngü içerisinde değer veren bir dolaşım nesnesi vermektedir. Artım değeri belirtilmezse default 1'dir. Başlangıç değeri verilmezse default 0'dır. Bu fonksiyonun sonsuz döngü oluşturduğuna dikkat ediniz. Sizin bir biçimde bu döngüyü kırmanız gerekebilir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools c = itertools.count(10, 2) for x in c: print(x, end=' ') if x == 50: break #------------------------------------------------------------------------------------------------------------------------------------ count fonksiyonun muhtemel gerçekleştirimi standart kütüphane dokümanlarında aşağıdaki gibi verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools def count(start=0, step=1): n = start while True: yield n n += step for x in count(): print(x, end=' ') if x == 10: break #------------------------------------------------------------------------------------------------------------------------------------ Tabii count fonksiyonu aslında standart kütüphanede üretici fonksiyon olarak değil bir sınıf olarak yazılmıştır. count fonksiyonunu sınıf biçiminde aşağıdaki gibi yazabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ count fonksiyonun muhtemel gerçekleştirimi standart kütüphane dokümanlarında aşağıdaki gibi verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools class mycount: def __init__(self, start = 0, step = 1): self.n = start self.step = step def __iter__(self): return self def __next__(self): self.n += self.step return self.n - self.step names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] for t in zip(names, mycount(0, 0.5)): print(t) #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki cycle isimli fonksiyon parametresiyle aldığı dolaşılabilir nesneyi tekrara tekrar sonsuz döngü içerisinde dolaşan bir dolaşım nesnesi verir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = 'ABC' result = itertools.cycle(s) for index, x in enumerate(result): print(x, end=' ') if index == 10: break #------------------------------------------------------------------------------------------------------------------------------------ Python standart kütüphanesinde fonksiyonun muhtemel bir gerçekleştirimi aşağıdaki gibi verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ def cycle(iterable): saved = [] for element in iterable: yield element saved.append(element) while saved: for element in saved: yield element s = 'ABC' for index, x in enumerate(cycle(s)): print(x, end=' ') if index == 10: break #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki filterfalse isimli fonksiyon iki parametre almaktadır. Birinci parametre bool değere geri dönen bir fonksiyondur. İkinci parametre dolaşılabilir bir nesnedir. Fonksiyon bize dolaşılabilir bir nesne verir. Bu nesne dolaşıldığında sırasıyla ikinci parametresindeki dolaşılabilir nesnedeki elemanlar birinci parametresindeki fonksiyona sokulur, False olanlar bize verilir. Aslında bu fonksiyon built-in fileter fonksiyonunun tersini yapmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import itertools names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] def predicate(name): return len(name) > 4 for name in itertools.filterfalse(predicate, names): print(name, end=' ') # ali veli ayşe #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki islice isimli fonksiyon bizden dolaşılabilir bir nesneyi ve start, stop, step değerlerini parametre olarak alır. Bize yeni bir dolaşım nesnesi verir. Fonksiyonun verdiği dolaşım nesnesini dolaştığımızda fonksiyonda belirttiğimiz start değerinden başlayarak, step artırımlarla stop değerine kadar ana dolaşılabilir nesnedeki elemanlar elde edilir. Bu fonksiyon özellikle elimizdeki dolaşılabilir nesnenin belli bir kısmının dolaşılmasında kullanılmaktadır. islice fonksiyonun dolaşılabilir nesne parametresi dışındaki parametrelerinin kullanımı tamamen range fonksiyonundaki gibidir. Yani fonksiyona yalnızca start değeri girilirse bu değer stop değeri gibi kabul edilmektedir. Eğer start değeri girilip stop değeri de girilirse bu durumda start değerinden stop değerine kadar elemanlar elde edilir. stop değeir None olarak girilirse "sonuna kadar" anlamına gelmektedir. Örneğin: >>> s = 'abcdefghijklmn' >>> import itertools >>> for c in itertools.isclice(s, 5): print(c) ... a b c d e >>> for c in itertools.islice(s, 5, None): print(c) ... f g h i j k l m n >>> for c in itertools.islice(s, 5, 10, 2): print(c) ... f h j #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = {10, 20, 30, 40, 50, 60} a = [3, 6, 8, 3, 8, 9] m = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} result = itertools.islice(s, 3) for x in result: print(x, end=' ') # 50 20 40 sıra belli değil print() result = itertools.islice(a, 3) for x in result: print(x, end=' ') # 3 6 8 print() result = itertools.islice(m, 3) for x in result: print(x, end=' ') # ali veli selami #------------------------------------------------------------------------------------------------------------------------------------ Built-in min ve max fonksiyonlarının bir benzerini yazmaya çalışalım. Bu fonksiyonlar dolaşılabilir bir nesnenin en küçük ve en büyük elemanlarını geri döndürmektedir. En küçük ve en büyük sayıyı bulma algoritmasında ilk sayı en büyük ya da en küçük kabul edilir. Sonra ondan daha büyük ya da daha küçük varsa o değer saklanır. Burada biz manuel iteratör işlemiyle bunu sağlayabiliriz. Nesneyi bir kez iterate ederiz ve ilk elemanı elde ederiz. Geri kalan elemanları for döngüsüyle dolaşabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ def mymax(iterable): iterator = iter(iterable) # iterator = iterable.__iter() maxval = next(iterator) # iterator.__next__() for x in iterator: if x > maxval: maxval = x return maxval s = {56, 34, 12, 63, 8, 41} result = mymax(s) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki programı deneme amacıyla itertools.islice ile yapmak isteyelim. #------------------------------------------------------------------------------------------------------------------------------------ import itertools def mymax(iterable): maxval = list(itertools.islice(iterable, 1))[0] for x in itertools.islice(iterable, 1, None): if x > maxval: maxval = x return maxval s = {56, 34, 12, 63, 8, 41} result = mymax(s) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Hiç itertools.islice kullanmadan herhangi bir dolaşılabilir nesnenin ilk n tane elemanını dolaşmak istersek bildiğimiz yöntemlerle bunu nasıl yapabiliriz. İlk akla gelen enumerate fonksşyonunu kullanmak olabilir: for index, val in iterable: if index == 5: break print(val) zip fonksiyonu "kısa olan kadar" işleme izin verdiğine göre zip fonksiyonunu da kullanbiliriz. Örneğin: for _, val in zip(range(5), iterable): print(val) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Örneğin bir dolaşılabilir nesnenin başından ve sonundan n tane elemanı görüntülemek isteyelim. Dolaşılabilir nesnenin __len__ metodunun bulunduğunu varsayalım. Böyle bir fonksiyonu aşağıdaki gibi yazabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import itertools def disp_head_tail(iterable, n = 5): for x in itertools.islice(iterable, n): print(x, end=' ') print('... ', end='') for x in itertools.islice(iterable, len(iterable) - 5, None): print(x, end=' ') print() s = {12, 45, 23, 11, 8, 67, 34, 23, 98, 46, 79, 34, 36, 90, 23, 78} disp_head_tail(s) a = [12, 45, 23, 11, 8, 67, 34, 23, 98, 46, 79, 34, 36, 90, 23, 78] disp_head_tail(a) #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi itertools.cycle fonksiyonu bir dolaşılabilir nesneyi alarak onu tekrar tekrar sonsuz döngü içerisinde dolaşıyordu. Biz bu fonksiyonla itertools.islice fonksiyonunu bir arada kullanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = {12, 45, 23, 11, 8} for x in itertools.islice(itertools.cycle(s), 8): print(x, end= ' ') #------------------------------------------------------------------------------------------------------------------------------------ Benzer biçimde belli bir değerden belli artırımlarla sonsuz döngü içeisinde değer veren itertools.count fonksiyonu itertools.islice ile birlikte kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools for x in itertools.islice(itertools.count(10, 2), 10): print(x, end= ' ') # 10 12 14 16 18 20 22 24 26 28 #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki product isimli fonksiyon bizden bir grup dolaşılabilir nesneyi ayrı argümanlarla alır. Bunların kartezyen çarpımlarını oluşturup demet biçiminde bize veren bir dolaşım bir nesne geri döndürür. Örneğin: a = ['ali', 'veli'] b = [10, 20] for t in itertools.product(a, b): print(t) işleminden şöyle bir çıktı elde edilecektir: ('ali', 10) ('ali', 20) ('veli', 10) ('veli', 20) Burada product fonksiyonuna verdiğimiz argümanlar üç tane olsaydı bize verilen dolaşım nesnesi dolaşıldıkça üç elemenalı demetler elde edilecekti. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = ['ali', 'veli', 'selami'] b = [10, 20, 30] iterable = itertools.product(a, b) for t in iterable: print(t) print() c = ['ankara', 'izmir', 'bursa'] iterable = itertools.product(a, b, c) for t in iterable: print(t) #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki repeat fonksiyonu bir nesneyi ve tekrar sayısını parametre olarak alır ve bize bir dolaşım nesnesi verir. Fonksiyonun bize verdiği dolaşım nesnesini dolaştığımızda o tekrar sayısı kadar nesneden elde ederiz. Eğer fonksiyonun ikinci parametresi None olarak girilirse bize sonsuz döngü içerisinde aynı değerleri verir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools s = 'ankara' for x in itertools.repeat(s, 3): print(x, end=' ') # ankara ankara ankara #------------------------------------------------------------------------------------------------------------------------------------ Standart kütüphane dokümanlarında repeat fonksiyonunun muhtemel gerçekleştirimi aşağıdaki gibi verilmiştir. Fonksiyonun ikinci parametresi olan tekrar sayısı girilmezse fonksiyon sonsuz sayıda tekrar yapmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ def repeat(val, times=None): if times is None: while True: yield val else: for _ in range(times): yield val s = 'ankara' for x in repeat(s, 3): print(x, end=' ') # ankara ankara ankara #------------------------------------------------------------------------------------------------------------------------------------ Her ne kadar standart kütüphanedeki muhtemel gerçekleştirim üretici fonksiyon biçiminde verilmişse de CPython yorumlayıcısında repeat bir sınıf biçiminde yazılmıştır. Aşağıda sınıfsal yazımı verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools class repeat: def __init__(self, val, times=None): self.val = val self.times = times self.count = 0 def __iter__(self): return self def __next__(self): if self.count == self.times: raise StopIteration self.count += 1 return self.val s = 'ankara' for x in repeat(s, 3): print(x, end=' ') # ankara ankara ankara #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki takewhile isimli fonksiyon bizden bir fonksiyon ve bir de dolaşılabilir nesne alır. Bize bir dolaşım nesnesi verir. Onun verdiği dolaşım nesnesini dolaştığımızda orijinal dolaşılabilir nesnedeki elemanları elde ederiz. Ancak bu elemanlar bizim birinci parametreyle verdiğimiz fonksiyona sokulup bu fonksiyon True geri döndürmüşse verilmektedir. Eleman fonksiyona sokulup ilk False elde edildiğinde tüm işlem sonlandırılır. Başka bir deyişle bu fonksiyon bizim veridğimiz fonksiyon False döndürene kadar dolaşılabilir nesnenin elemanlarını bize vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [12, 24, 46, 8, 9, 40, 21] for x in itertools.takewhile(lambda x: x % 2 == 0, a): print(x, end=' ') # 12 24 46 8 #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki startmap fonksiyonu tamamen built-in map fonksiyonu gibidir. Ancak starmap fonksiyonunda dolaşılabilir nesne dolaşılabilir nesnelerden oluşmalıdır. Bu durumda starmap fonksiyonundaki fonksiyona bu dolaşılabilir nesnenin elemanları olan dolaşılabilir nesne *'lı argüman biçiminde aktarılmaktadır. map ve starmap fonksiyonlarının üretici fonksiyon olarak muhtemel gerçekleştirimi aşağıdaki gibi olabilir: def map(f, iterable): for x in iterable: yield f(x) def starmap(f, iterable): for x in iterable: yield f(*x) #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [12, 24, 46, 8, 9, 40, 21] iterable = map(lambda x: x * x, a) for x in iterable: print(x, end=' ') a = [(1, 2), (3, 4), (5, 6)] iterable = map(lambda t: t[0] + t[1], a) for x in iterable: print(x, end=' ') a = [(1, 2), (3, 4), (5, 6)] iterable = itertools.starmap(lambda x, y: x + y, a) for x in iterable: print(x, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ itertools modülündeki tee isimli fonksiyon bir dolaşılabilir nesneyi alıp bize bir demet biçiminde n tane dolaşım nesnesi vermektedir. Görnüşte fonksiyon anlamsız gibi gelebilmektedir. Ancak iter fonksiyonuyla bir iterator elde edilmişse bu iterator bir kez dolaşıldığında artık sona gelinir ve bir daha dolaşılamaz. İtertör nesnelerinin de dolaşılabilir olduğunu anımsayınız. Örneğin: >>> a = [1, 2, 3] >>> iterator = iter(a) >>> list(iterator) [1, 2, 3] >>> list(iterator) [] >>> import itertools >>> a = [1, 2, 3] >>> iterator = iter(a) >>> t = itertools.tee(a, 3) >>> list(t[0]) [1, 2, 3] >>> list(t[1]) [1, 2, 3] >>> list(t[2]) [1, 2, 3] Özetle tee fonksiyonu bir kez dolaşıldığında biten bir iteratör varsa onu n kez dolaşabilmek için kullanılmaktadır #------------------------------------------------------------------------------------------------------------------------------------ import itertools a = [12, 24, 46, 8, 9, 40, 21] iterator = iter(a) for i in itertools.tee(a, 3): print(list(i)) #------------------------------------------------------------------------------------------------------------------------------------ 71. Ders 16/09/2023 - Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi Python'da dilin içerisine entegre edilmiş olan bazı veri yapıları bulunmaktadır. Programcılar pek çok işleminde bunları kullanırlar: list, tuple, set, dict, str. Ancak bu veri yapıları bazı tür uygulamalar için yetersiz olabilmektedir. İşletim standart kütüphanedeki "collections" isimli modülde bazı ek veri yapıları da bulundurulmuştur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ collections modülü içerisindeki built-in olmayan veriyapılarına yönelik önemli bir sınıf "deque" isimli sınıftır. "deque" sözcüğü "double-ended queue" sözcüklerinden kısaltılarak uydurulmuştur ve "dek" biçiminde telaffuz edilmektedir. deque sınıfı aslında list sınıfına çok benzemektedir. list sınıfında sona eleman çok hızlı eklenir. Ancak başa ve araya ekleme (insert), baştaki ve aradaki elemanın silinmesi göreli olarak yavaştır. Çünkü başa ve araya eleman ekleme sırasında elemanlar mecburen kaydırılır (buna "expand" işlemi denilmektedir), eleman silme işleminde de elemanlar benzer bir işlemle daraltılmaktadır (buna da "shrink" işlemi denilmektedir). Bazı uygulamalarda veri yapısının başına ve sonuna eleman ekleme çok karşılaşılan bir işlemdir. Benzer biçimde baştan ve sonran eleman silinmesi de bazen gerekebilmektedir. İşte "deque" denilen veri yapısının başına ve sonuna çok hızlı eleman eklenebilmekte aynı zamanda başından ve sonundan çok hızlı eleman silinebilmektedir. Algoritmalar dünyasında bir döngü ile yapılan işlemlere O(N) karmaşıklıkta işlemler denir. Döngü olmadan yapılan işlemlere ise O(1) karmaşıklıkta işlemler denilmektedir. İşte listelerde sona ekleme ve sondan eleman silme O(1) karmaşıklıkta yapılabildiği halde başa eleman ekleme ve baştan eleman silme ancak O(N) karmaşıklıkta yapılabilmektedir. Halbuki deque veri yapısında hem sona hem de başa eleman O(1) karmaşıklıkta eklenebilmekte ve hem baştan hem de sondan eleman O(1) karmaşıklıkta silinebilmektedir. O halde özel bazı uygulamalarda iki taraftan da ekleme ve silme çokça yapılıyorsa list yerine deque veri yapısının kullanılması uygun olur. Şüphesiz deque veri yapısı list veri yapısından daha uygun gibi gözküyor olsa da aslında deque veri yapısının da list veri yapısına göre bazı dezavantajları söz konusudur. Genel olarak yalnızca sona eklemenin yapıldığı durumlarda list veri yapısı hem daha hızlı hem de daha az yer kaplamaktadır. Aslında deque sınıfı kullanım bakımından list sınıfına çok benzemektedir. list sınıfının metotlarının çoğu deque sınıfında da vardır. Ancak deque sınıfında sona ekleme yapan append metodunun yanı sıra başa ekleme yapan appendleft metodu da bulunmaktadır. Benzer biçimde deque sınıfında extend metodunun yanı sıra extendleft isimli metot da bulunmaktadır. list sınıfının pop metodu herhangi bir indeksteki elemanı silmek için kullanılıyordu. Eğer pop metoduna argüman geçmezsek bu metot son elemanı siliyordu. Ancak deque sınıfının pop metodu zaten parametresizdir, her zaman son elemanı siler. Ayrıca deque sınıfında parametresiz popleft isimli bir metot da vardır Bu metot ilk elemanı silmektedir. Tabii deque sınıfında da istenilen indeksteki elemanlara listelerde olduğu gibi [...] operatörü ile erişilebilir. #------------------------------------------------------------------------------------------------------------------------------------ import collections d = collections.deque() d.append(1) d.append(2) d.appendleft(3) d.appendleft(4) d.extend([10, 20, 30]) d.extendleft([40, 50, 60]) for x in d: print(x, end=' ') # 60 50 40 4 3 1 2 10 20 30 print() for i in range(len(d)): print(d[i], end=' ') # 60 50 40 4 3 1 2 10 20 30 print() d.popleft() d.pop() print(d) # deque([50, 40, 4, 3, 1, 2, 10, 20]) #------------------------------------------------------------------------------------------------------------------------------------ deque sınıfının döndürme (rotate) işlemi yapan rotate bir metodu vardır. rotate işlemi bir kez yapıldığında en sağdaki eleman en sola alınır ve diğer elemanlar bir kaydırılır. Örneğin deque içerisinde şu elemanlar olsun: 10 20 30 40 50 Bir kez rotate işlemi yapalım: 50 10 20 30 40 rotate işlemi birden fazla kez de yapılabilir. Şimdi yukarıdaki değerleri 2 kez rotate yapalım: 30 40 50 10 20 Aslında bir kez rotate yapmak d.appendleft(d.pop()) ile aynı anlamdadır. Bu biçimdeki rotate işlemine sağa rotate işlemi denilmektedir. rotate metodunun parametresi default olarak 1 değerindedir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = coll.deque([10, 20, 30, 40, 50]) print(d) # deque([10, 20, 30, 40, 50]) d.rotate(1) # sağa bir kez döndürme print(d) # deque([50, 10, 20, 30, 40]) d.rotate(3) print(d) # deque([20, 30, 40, 50, 10]) #------------------------------------------------------------------------------------------------------------------------------------ rotate metodunun parametre negatif girilirse sola döndürme uygulanır. Aslında bu işlem d.append(d.popleft()) işlemi ile eşdeğerdir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = coll.deque([10, 20, 30, 40, 50]) print(d) # deque([10, 20, 30, 40, 50]) d.rotate(-1) # sola bir kez döndürme print(d) # deque([20, 30, 40, 50, 10]) d.rotate(-3) print(d) # deque([50, 10, 20, 30, 40]) #------------------------------------------------------------------------------------------------------------------------------------ deque nesnesini yaratırken maxlen parametresi de verebiliriz. Eğer bu parametre verilirse deque nesnesi maksimum belirtilen uzunlukta olabilir. Bu durumda uzunluk yetmezse sona ekleme durumunda baştaki eleman, başa ekleme durumunda sondaki eleman deque'ten atılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = coll.deque([10, 20, 30, 40], 5) print(d) # deque([10, 20, 30, 40], maxlen=5) d.append(50) print(d) # deque([10, 20, 30, 40, 50], maxlen=5) d.append(60) print(d) # deque([20, 30, 40, 50, 60], maxlen=5) d.append(70) print(d) # deque([20, 30, 40, 50, 60], maxlen=5) d.appendleft(80) print(d) # deque([80, 30, 40, 50, 60], maxlen=5) #------------------------------------------------------------------------------------------------------------------------------------ maxlen parametresi dolaşılabilir bir nesnenin sondaki n değerinin elde edilmesinde kullanılabilir. Örneğin dosyalar dolaşılabilir nesnelerdir. Dosya nesnesi dolaşıldığında dosyanın satırları elde edilir. (Bu satırların sonlarında '\n' karakteri bulunmaktadır.) O halde deque nesnei ile biz örneğin bir dosyanın sonundaki n tane satırı elde edebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll with open('student.csv') as f: d = coll.deque(f, 5) print(d) for x in d: print(x, end='') #------------------------------------------------------------------------------------------------------------------------------------ Örneğin biz tek sayıları bir dizinin başına, çift sayıları sonuna eklemek isteyelim. Bu iş için deque oldukça verimlidir. Aşağıdaki örnekte 100 tane rastgele sayı üretilerek deque içerisine bu biçimde yerleştirilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import collections import random d = collections.deque() for i in range(100): val = random.randint(0, 1000) if val % 2 == 0: d.append(val) else: d.appendleft(val) print(d) #------------------------------------------------------------------------------------------------------------------------------------ deque ile FIFO ya da LIFO kuyruk sistemleri de yapılabilir. Biz daha önce queue ve multiprocessing modülleri içerisindeki Queue sınıflarını görmüştük. Ancak bu Queue sınıfları "üretici-tüketici" problemi için düşünülmüş olan "senkronize" bir kuyruk sistemlerini oluşturmaktadır. Dolayısıyla daha önce görmüş olduğumuz Queue sınıfları "multi-threaded" ve "multi-processing" uygulamalar için düşünülmüştür. Tabii biz bu Queue sınıfını thread'siz (single-threaded) programlarda da kullanabiliriz. Ancak bu durumda performas düşer. İşte collections modülündeki deque sınıfı sayesinde FIFO ve LIFO kuyruk sistemleri kolayca oluşturulabilmektedir. Örneğin biz bir deque nesnesi yaratıp elemanları bu nesnenin sonuna ekleyip başında alırsak ya da başına ekleyip sonundan alırsak bir FIFO kuyruk sistemi oluşturmuş oluruz. Dolayısıyla Python'un standart kütüphanesinde ayrı bir kuyruk ya da stack veri yapısı yoktur. Zaten bu veri yapıları deque kullanılarak gerçekleştirilebilmektedir. Bilindiği gibi FIFO kuyruk sistemleri bilgileri sırası bozulmadan geçici olarak saklamak amacıyla kullanılmaktadır. Aşağıdaki örnekte 1'den 100' kadar sayılar bir kuyruk sistemine eklenip geri alınmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll q = coll.deque() for x in range(100): q.append(x) while len(q): val = q.popleft() print(val, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ LIFO kuyruk sistemlerine "stack" sistemleri de denilmektedir. Doğadaki bazı olaylarda stack yapısı gözlenmektedir. Örneğin üst üste tabakları koyduğumuzda önce en son koyduğumuzu önce alırız. Bir asansöre binenler geri çekildiği için son binen önce iner. Programalamada stack sistemi pek çok yerde kullanılmaktadır. Örneğin "undo" mekanizması bir stack sistemi gibi çalışır. Yani biz "undo" yaptığımızda son yaptığımız değişikliği geri alırız. Ya da örneğin parsing algoritmalarında stack kullanılmaktadır. Stack bir şeyi ters yüz etmek için de kullanılmaktadır. Python'da bir stack sistemi deque sınıfı kullanılarak gerçekleştirilebilir. Örneğin biz başa ekleyip baştan alma ya da sona ekleyip sondan alma işlemi ile bir stack veri yapısını gerçekleştirilebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll q = coll.deque() for x in range(100): q.appendleft(x) while len(q): val = q.popleft() print(val, end=' ') #------------------------------------------------------------------------------------------------------------------------------------ collections modülü içerisindeki diğer bir nesne tutan sınıf da ChainMap isimli sınıftır. ("Chain" zincir anlamına, "Map" ise bu bağlamda sözlük (dictionary) anlamına gelmektedir.) Biz ChainMap nesnesine birden fazla sözlük veriririz. Sonra bu ChainMap üzerinde anahtara dayalı arama yaptığımızda (bunun için yine [...] operatöryle ya da get metodu kullanılabilir) nesne bizim verdiğimiz sözlüklere sırasıyla bakar ve ilk bulduğu sözlükteki değeri bize verir. Böylece ChainMap nesnesi bir "scope" mantığını pratik bir biçimde oluşturmak için kullanılır. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = {'ali': 10, 'veli': 20, 'selami': 30} k = {'ayşe': 40, 'ali': 50, 'fatma': 60} m = {'selami': 70, 'sacit': 80, 'hasan': 90} cm = coll.ChainMap(d, k, m) val = cm['selami'] print(val) # 30 val = cm['hasan'] print(val) # 90 val = cm['ali'] print(val) # 10 val = cm.get('sacit') print(val) # 80 val = cm.get('süleyman') print(val) # None #------------------------------------------------------------------------------------------------------------------------------------ Burada önemli anımsatma yapmak istiyoruz. Eskiden Python'daki built-in dict sınıfında dict nesnesi dolaşılırken ya da anahtarlar keys metodu ile elde edilirken anahtarların elde edilme sırası belirli değildi. Ancak Python 3.7 ile artık sözlük nesnesi dolaşılırken anahtarlar sözlüğe eklenme sırasına göre elde edilmektedir. Yani artık bir sözlüğü dolaşırken anahtarlar kesinlikle biz onları hangi sırada eklediysek o sırada elde edilecektir. #------------------------------------------------------------------------------------------------------------------------------------ d = {'zeynep': 10, 'ali': 20, 'selami': 30} d['sacit'] = 40 d['ayşe'] = 50 d['fatma'] = 60 d.update([('gürbüz', 70), ('necati', 80)]) for key in d.keys(): print(key, end=' ') # 3.7 ve sonrasında zeynep ali selami sacit ayşe fatma gürbüz necati for value in d.values(): print(value, end=' ') # 3.7 ve sonrasında 10 20 30 40 50 60 70 80 #------------------------------------------------------------------------------------------------------------------------------------ ChainMap nesnesi dolaşıldığında ya da keys ve values metotlarıyla anahtar ve değerler elde edildiğinde her zaman son sözlükten ilk sözlüğe doğru bir dolaşma yapılmaktadır. Örneğin ChamMap nesnesi ChanMap(d, k, m) biçiminde oluşturulmuş olsun. Biz bu sözlüğü dolaştığımızda önce m nesnesinin anahtarları, sonra k nesnesinin anahtarları sonra da d nesnesinin anahtarları elde edilecektir. Python 3.7 ve sonrasında sözlük içerisindeki anahtarlarında düz sırada elde edildiğine dikkat ediniz. Ayrıca dolaşım sırasında sözlüklerde birden fazla aynı anahtar varsa yalnızca ilk karşılaşan sözlükteki anahtar verilmektedir. ChainMap sınıfının values metodu ile değerler elde edilirken önce anahtarlar yukarıda belirtilen kurala göre elde edilir. Sonra o anahtarlara ilişkin değerler düz sırada elde edilir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = {'ali': 10, 'veli': 20, 'selami': 30} k = {'ayşe': 40, 'ali': 50, 'fatma': 60} m = {'selami': 70, 'sacit': 80, 'hasan': 90} cm = coll.ChainMap(d, k, m) for key in cm.keys(): print(key, end = ' ') # sondan başa selami sacit hasan ayşe ali fatma veli print() for val in cm.values(): print(val, end = ' ') # 30 80 90 40 10 60 20 print() # yukarıdakinin eşdeğeri for key in cm.keys(): val = cm[key] print(val, end = ' ') # 30 80 90 40 10 60 20 #------------------------------------------------------------------------------------------------------------------------------------ Bir ChainMap nesnesine ekleme yapıldığında ekleme her zaman ilk sözlük nesnesine yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = {'ali': 10, 'veli': 20, 'selami': 30} k = {'ayşe': 40, 'ali': 50, 'fatma': 60} m = {'selami': 70, 'sacit': 80, 'hasan': 90} cm = coll.ChainMap(d, k, m) cm['hüseyin'] = 100 cm['jale'] = 200 print(cm) print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'hüseyin': 100, 'jale': 200} #------------------------------------------------------------------------------------------------------------------------------------ Benzer biçimde ChainMap sınıfından pop metoduyla eleman silinmek istendiğinde silme her zaman sanki yalnızca birinci sözlük varmış gibi yapılmaktadır. (Yani silinecek anahtar birinci sözlükte yoksa ancak diğer sözlğklerin birinde varsa pop başarısız olmaktadır.) Başka bir deyişle eleman ekleme ve silme işlemlerinde sanki yalnızca ilk sözlük varmış gibi işlem yürütülmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = {'ali': 10, 'veli': 20, 'selami': 30} k = {'ayşe': 40, 'ali': 50, 'fatma': 60} m = {'selami': 70, 'sacit': 80, 'hasan': 90} cm = coll.ChainMap(d, k, m) print(cm) cm.pop('selami') print(cm) val = cm.pop('sacit', 'Not Found') print(val) # Not Found #------------------------------------------------------------------------------------------------------------------------------------ ChainMap sınıfının maps örnek özniteliği bizim nesne yaratılırken verdiğimiz sözlükleri bize bir liste biçiminde verir. parents örnek özniteliği ilk sözlük dışındaki tüm sözlükleri bize bir liste olarak vermektedir. Sınıfın new_child metodu bazı "scope" uygulamalarında nesneye yeni bir sözlüğün eklenmesi amacıyla bulundurulmuştur. Metot yeni bir ChainMap nesnesi yaratır ve bizim verdiğimiz sözlüğü bu yeni ChainMap nesnesinin başına ekler. Dolayısıyla cm bir ChainMap nesnesi ve d de bir sözlük nesnesi belirtmek üzere aşağıdaki iki ifade eşdeğerdir: child = cm.new_child(d) child = ChainMap(d, *d.maps) new_child metodu parametresiz de kullaılabilir. Bu durumda boş bir sözlük eklenir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll d = {'ali': 10, 'veli': 20, 'selami': 30} k = {'ayşe': 40, 'ali': 50, 'fatma': 60} m = {'selami': 70, 'sacit': 80, 'hasan': 90} cm = coll.ChainMap(d, k, m) print(cm) child = cm.new_child({'kazım': 100, 'levent': 110}) # eşdeğeri child = ChainMap({'kazım': 100, 'levent': 110}, *cm.maps) print(child) #------------------------------------------------------------------------------------------------------------------------------------ collections modülündeki OrderedDict sınıfı bir çeşit sözlük nesnesi oluşturuyordu. Ancak bu sözlük nesnesi dolaşıldığında dolaşım eklenme sırasına göre yapılıyordu. Fakat zaten artık Python 3.7 ile birlikte built-in dict sınıfı da bu özelliğe sahip olmuştur. Bu nedenle OrderedDict sınıfının birkaç önemsiz özelliğinin dışında bir önemi kalmamıştır. Fakat geçmişe doğru uyumun korunması için bu sınıf muhafaza edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ collections modülünde namedtuple isimli önemli bir fonksiyon vardır. Bu fonksiyon "isimli demet (named tuple)" oluşturmak için kullanılmaktadır. İsimli demet bir demetin bütün özelliklerini kapsayan ancak demet elemanlarına köşeli parantez operatörünün yanı sıra isimlerle de erişmeyi sağlayan ve bunu yaparken de nesne üzerinde ek bir maaliyet oluşturmayan sınıflardır. Aslında isimli demetler built-in tuple sınıfından türetme yapılarak da manuel biçimde oluşturulabilmektedir. Buna ilişkin bir örneği izleyen bölümlerde vereceğiz. Ancak manuel oluşturma zahmetlidir ve modüldeki namedtuple fonksiyonu bunu otomatize etmektedir. İsimli bir demete benzer işlevsellik normal sınıflarla da verilebilir. Örneğin: class Point: def __init__(self, x, y): self.x = x self.y = y pt = Point(10, 20) print(pt.x, pt.y) Ancak normal sınıfların bazı ek maaliyetleri vardır ve aynı zamanda genellik de sağlanamamaktadır. Oysa isimli demetler maaliyetsiz bir biçimde gerçekleştirilmektedir. Bir isimli demet namedtuple fonksiyonu ile oluşturulur. Bunun için fonksiyonun birinci parametresine oluşturulacak sınıfın metadata ismi verilir. Bu isim bizim için önemli olmasa da bazı ileri uygulamalarda gerekebilmektedir. Bu isim istenirse ilgili sınıf türünden nesne ile __class__ örnek özniteliği ile elde edilebilir. namedtuple sınıfının ikinci elemanı demet elemanlarının isimlerini içeren dolaşılabilir bir nesne olmalıdır. Biz namedtuple fonksiyonunun geri dönüş değerini bir değişkene atarız. Artık o değişken bir sınıf ismi gibi nesne yaratmakta kullanılabilir. Normalde metadata ismi ile bu isim farklı olabilir. Ancak genellikle programcılar aynı isimleri kullanmaktadır. namedtuple fonksiyonunun bize verdiği sınıf aslında built-in tuple sınıfından türetilmiş bir sınıftır. Dolaysıyla bu sınıf türünden nesne yaratıldığında aslında demetin tüm işlevselliği kullanılabilir. Ek olarak tabii elemanlara isimlerle de erişebiliriz. Örneğin: import collections as coll Point = coll.namedtuple('Point', ['x', 'y']) pt = Point(3, 5) print(pt.x, pt.y) print(pt[0], pt[1]) Görüldüğü gibi burada namedTuple fonksiyonun geri döndürdüğü değer bir değişkene atanmış o değişken de sanki bir sınıf gibi kullanılmıştır. Örneğimizdeki Point sınıfı aslında tuple sınıfından türetilmiş durumdadır. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll Point = coll.namedtuple('Point', ['x', 'y']) t = Point(10, 20) print(t[0], t[1]) # 10 20 print(t.x, t.y) # 10 20 print(Point.__bases__) # (,) #------------------------------------------------------------------------------------------------------------------------------------ 72. Ders 22/09/2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ namedtuple fonksiyonunun birinci parametresiyle verdiğimiz isim aslında yaratılan nesnenin türüne ilişkin sınıfın ismidir. Örneğin: import collections as coll Z = coll.namedtuple('Complex', ['real', 'imag']) z = Z(10, 2) Burada z "Complex" isimli sınıf türündendir. Ancak bu sınıfın type nesne referansını gösteren değişken Z ismindedir. Complex sınıfı tuple sınıfından türetilmiş durumdadır. Durumu şekilsel olarak şöyle gösterebiliriz: Z ----> type (nesnesi Complex sınıfı türünden tuple sınıfından türetilmiş) Yani yukarıdaki isimli demet oluşturma ifadesinde aslında tuple sınıfından türetilmiş olan Complex isimli bir sınıf oluşturulmuştur. Ancak bu sınıfın referansı Z değişkeninde tutulmaktadır. Bu durum izleyen paragraflarda ele alacağımız "meta sınıflar" konusundan sonra daha iyi anlaşılacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll Z = coll.namedtuple('Complex', ['real', 'imag']) z = Z(10, 2) print(z.real, z.imag) print(type(z)) # Complex print(type(Z)) # type print(type(z).__bases__) # (tuple,) #------------------------------------------------------------------------------------------------------------------------------------ İsimli demetler pek çok kütüphanede özellikle fonksiyonların geri dönüş değerlerinde kullanılmaktadır. Örneğin ikinci derece bir denklemin köklerini veren getroots isimli bir fonksiyon olsun. Biz bu fonksiyonu normal demet yerine sisimli bir demetle geri döndürebiliriz. Aşağıda buna ilişkin bir örnek verilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ import collections import math def getroots(a, b, c): delta = b ** 2 - 4 * a * c if delta < 0: return None x1 = (-b + math.sqrt(delta)) / (2 * a) x2 = (-b - math.sqrt(delta)) / (2 * a) Roots = collections.namedtuple('Roots', ['x1', 'x2']) return Roots(x1, x2) result = getroots(1, 0, -4) if result: print(result.x1, result.x2) #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirttiğimiz gibi aslında biz isimli bir demet etkisini oluşturacak bir sınıf yazabiliriz. Aşağıda buna bir örnek verilmiştir. Ancak böyle bir etki sınıfla oluşturulurken isimli demete göre arka planda daha fazla maaliyet oluşmaktadır. class Point: def __init__(self, x, y): self.x = x self.y = y def __getitem__(self, index): if index == 0: return self.x if index == 1: return self.y raise IndexError() #------------------------------------------------------------------------------------------------------------------------------------ class Point: def __init__(self, x, y): self.x = x self.y = y def __getitem__(self, index): if index == 0: return self.x if index == 1: return self.y raise IndexError() pt = Point(10, 20) print(pt[0], pt[1]) print(pt.x, pt.y) #------------------------------------------------------------------------------------------------------------------------------------ İsimli demetler yaratılırken demet elemanlarının isimleri tek bir string biçiminde de verilebilir. Bu durumda isimler boşluk veya virgüllerle ayrılmalıdır. Örneğin: Roots = collections.namedtuple('Roots', 'x1 x2') Burada isimli demetin elemanlarının isimleri x1 ve x2 biçimindedir. Yani bu işlem aşağıdkaiyle eşdeğerdir: Roots = collections.namedtuple('Roots', ['x1', 'x2']) #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll Roots = coll.namedtuple('Roots', 'x1 x2') r = Roots(1, 2) print(r.x1, r.x2) #------------------------------------------------------------------------------------------------------------------------------------ collections modülündeki Counter isimli sınıf bir çeşit sözlük sınıfıdır. Zaten bu sınıf dict sınıfından türetilmiştir. Sınıfın amacı varlıkların sayısını tutmaktır. Tipik olarak nesne dolaşılabilir bir nesneyle yaratılır. Böylece nesne o dolaşılabilir nesnedeki elemanların sayılarını tutar hale gelir. Counter nesnesiyle elde edilen sözlüğün anahtarları dolaşılabilir nesnedeki elemanlar, değerleri ise onların sayılarıdır. Örneğin: import collections c = collections.Counter('ankara') print(c) # Counter({'a': 3, 'n': 1, 'k': 1, 'r': 1}) Counter sınıfı dict sınıfından türetildiği için tamamen dict sınıfının bütün işlevselliğini barındırmaktadır. Yani örneğimizdeki c nesnesi bir çeşit sözlük nesnesidir. Sözlüklerdeki tüm metotları biz Counter nesnesi ile de kullanabiliriz. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll c = coll.Counter('ankara') print(c) # Counter({'a': 3, 'n': 1, 'k': 1, 'r': 1}) c = coll.Counter([1, 2, 1, 2, 1, 2, 4, 5, 5, 2, 6, 3, 6]) print(c) # Counter({1: 3, 2: 2}) for key, value in c.items(): print(key, '=>', value) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Counter nesnesi bir sözlükle de oluşturulabilmektedir. Bu durumda değerler ve onların sayıları sözlükle elde edilmiş olur. Örneğin: c = collections.Counter({'x': 5, 'y': 3, 'z': 2}) #------------------------------------------------------------------------------------------------------------------------------------ import collections c = collections.Counter({'x': 5, 'y': 3, 'z': 2}) print(c) #------------------------------------------------------------------------------------------------------------------------------------ Counter sınıfının elements isimli metodu bize bir dolaşım nesnesi vernektedir. Bu nesne dolaşıldığında nesnenin tuttuğu elemanlar belirtilen sayıda bize verilmektedir. Örneğin: c = collections.Counter({'x': 5, 'y': 3, 'z': 2}) e = c.elements() a = list(e) print(a) # ['x', 'x', 'x', 'x', 'x', 'y', 'y', 'y', 'z', 'z'] #------------------------------------------------------------------------------------------------------------------------------------ import collections c = collections.Counter({'x': 5, 'y': 3, 'z': 2}) e = c.elements() a = list(e) print(a) # ['x', 'x', 'x', 'x', 'x', 'y', 'y', 'y', 'z', 'z'] #------------------------------------------------------------------------------------------------------------------------------------ Counter sınıfının most_common metodu en çok yinelenen ilk n değeri demetlerden oluşan bir liste biçiminde vermektedir. Eğer metot argümansız çağırılırsa bütün değerler bu biçimde verilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll c = coll.Counter('abrakadabra') mc = c.most_common(3) print(mc) # [('a', 5), ('b', 2), ('r', 2)] mc = c.most_common() print(mc) # [('a', 5), ('b', 2), ('r', 2), ('k', 1), ('d', 1)] #------------------------------------------------------------------------------------------------------------------------------------ Counter sınıfının total metodu toplam eleman sayısını bize vermektedir. Ancak bu metot Python 3.10 ile eklenmiştir. (Spyder kullanıyorsanız sol üst köşeden Spyder'ın kullandığı Python versiyonuna dikkat ediniz.) #------------------------------------------------------------------------------------------------------------------------------------ import collections c = collections.Counter({'a': 5, 'b': 3, 'c': 2}) print(c) # 3 print(c.total()) # 10 #------------------------------------------------------------------------------------------------------------------------------------ dict sınıfının update metodu eklenecek elemanlar zaten sözlükte varsa yalnızca bunların değerlerini güncellemektedir. Ancak Counter sınıfının update metodu dolaşılabilir bir nesne ya da sözlük alabilir. Bu durumda metot üzerine ekleme yapmaktadır. Yani sanki anahtarın sayaçları artmış gibi bir etki oluşmaktadır. Halbuki Counter sınıfının [...] operatörü ile atama yapıldığında davranış böyle değildir. Eski sayaç değeri kaybedilip yeni değer set edilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import collections as coll c = coll.Counter([1, 2, 2, 1, 2, 2, 3, 2, 3, 3]) print(c) # Counter({2: 5, 3: 3, 1: 2}) c.update([1, 1, 2, 2]) print(c) # Counter({2: 7, 1: 4, 3: 3}) c.update({2:100}) print(c) # Counter({2: 107, 1: 4, 3: 3}) c[2] = 10 print(c) # Counter({2: 10, 1: 4, 3: 3}) #------------------------------------------------------------------------------------------------------------------------------------ Bilindiği gibi bir Python listesi ya da demeti aslında değerlerin adreslerini tutmaktadır. Bu tutuş biçimi dinamik tür sistemine uygun ve heterojen nesnelerin tutulmasına olanak sağlıyor olsa da fazla yer kaplaması ve elemanlara yavaş erişilmesi gibi dezavantajları da beraberinde getirmektedir. array modülündeki array isimli sınıf aynı türden nesneleri etkin bir biçimde depolamak ve onlara hızlı bir biçimde erişebilmek için bulundurulmuştur. array sınıfı "aynı türden bir grup değeri C Programlama Dilindeki diziler gibi" tutmaktadır. Solayısıyla array nesnesinin içerisindeki elemanlar list ve tuple sınıflarında olduğu gibi adres tutmazlar doğrudan değerleri tutarlar. array nesneleri NumPy kütüphanesindeki ndarray nesnelerine benzemektedir. Anımsanacağı gibi ndarray nesnelerinin de bir dtype türü vardı. Bu nesneler de elemanların adreslerini değil doğurdan kendisi tutuyordu. İşte array nesneleri aynı türden değerlerin etkin bir biçimde tutulması ve onlara hızlı bir biçimde erişilmesini mümkün hale getirmektedir. Ancak array nesneleri ndarray nesneleri gibi karşılıklı elemanlar üzerinde vektörel işlem yapma yeteneğine sahip değildir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ array nesnesi yaratılırken onun türü de belirtilir. Çünkü bir array nesnesi hep aynı türden değerleri tutmak zorundadır. array nesnesinin tuttuğu değerlerin türleri C Programlama Dili temel alınarak belirlenmiştir. Bu tür array nesnesi yaratılırken belirtilmektedir. Türler şöyledir: Tür Temsili C'deki Tür 'b' signed char 'B' unsigned char 'u' wchar_t 'h' signed short int 'H' unsigned short int 'i' signed int 'I' unsigned int 'l' signed long int 'L' unsigned long int 'q' signed long long int 'Q' unsigned long long int 'f' float 'd' double C Programlama Dilindeki yukarıdaki türlerin uzunlukları aslında char dışında sistemden sisteme değişebilmektedir. Ancak tipik durum şöyledir: char, unsigned char ---> 1 byte'lık tamsayı türü short, unsigned short ---> 2 byte'lık tamsayı türü int, unsigned int ---> 4 byte'lık tamsayı türü long, unsigned long ---> 4 ya da 8 byte'lık tamsayı türü long long, unsigned long long ---> 8 byte'lık tamsayı türü float ---> 4 byte'lık gerçek sayı türü double ---> 8 byte'lık gerçek sayı türü (Python'daki float) Bir array nesnesini yaratırken onun türünü ve isteğe bağlı olarak onun tutacağı değerleri verebiliriz. Örneğin: import array a = array.array('i', [1, 2, 3, 4, 5]) print(a) # array('i', [1, 2, 3, 4, 5]) Yukarıda da belirttiğimiz gibi array sınıfı bizzat değerlerin kendisini tutmaktadır. Yani list ve tuple sınıflarında olduğu gibi onların adreslerini tutmamaktadır. Ancak tabii gibi array nesnesi hep aynı türden değerleri tutabilmektedir. Aslında NumPy kütüphanesindeki ndarray sınıfı da benzer biçimde bir temsil kullanmaktadır. Buradaki array sınıfının NumPy'daki ndarray sınıfıyla bir rekabeti yoktur. NumPy kütüphanesi oldukça geniş ve vektörel işlem yapma yeteneğine sahiptir. NumPy'daki dtype da aslında yukarıda türleri belirtmektedir. #------------------------------------------------------------------------------------------------------------------------------------ import array a = array.array('i', [1, 2, 3, 4, 5]) print(a) # array('i', [1, 2, 3, 4, 5]) #------------------------------------------------------------------------------------------------------------------------------------ Bir array nesnesi oluşturulduktan sonra bu nesnenin kullanımı list nesnelerinin kullanımına çok benzemektedir. Yani array nesneleri adeta elemanları aynı türdne olan, az yer kaplayan ve hızlı işlem yapılabilen listeler gibidir. Örneğin: - array nesnesi de "değiştirilebilir (mutable)" türlendendir. - array nesnesinin elemanlarına yine [...] operatörü ile erişilebilir. - Dilimleme listelerde olduğu gibidir. Dilimlemede yine değerlerin kopyaları çıkartılmaktadır. (NumPy'da dilimleme işleminden bir view nesnesi elde edildiğini anımsayınız.) - array sınıfı fa dolaşılabilir bir sınıftır. - array nesnesi len fonksiyonuna sokulabilir. - count metodu yine belli bir elemanın sayısını bize verir. - append metodu dizinin sonuna ekleme yapar. - extend metodu bir dolaşılabilir nesnenin içerisindekileri diziye ekler. - Yine pop metodu belli bir indeksteki elemanı silmek kullanılır - Yine remove metodu elemanı arar ve bulursa ilk bulduğu elemanı siler. Eleman yoksa exception oluşur. - in ve not in operatörleri yine "içinde var mı" kontrolünü yapar. - insert metodu yine belli bir elemanı belli bir indekse insert etmektedir. - Yine reverse metodu "inplace" biçimde diziy tersyüz etmektedir. - indeks metodu yine bir elemanı arar, onun ilk bulunduğu yerin indeks numarasını verir. Eleman yoksa exception oluşmaktadır. - tolist metodu array içerisindeki elemanları bize Python listesi biçiminde verir. - tobytes metodu dizinin içerisindeki değerleri bir bytes nesnesi olarak bize verir. - array sınıfının typecode isimli örnek özniteliği dizinin türünü bize verir. - itemsize örnek özniteliği dizinin tuttuğu bir elemanın byte uzunluğunu bize vermektedir. - Elimizde bir byte dizisi varsa biz bu byte dizisini bir array nesnesi haline getirebiliriz. - Elimizde bir bytes nesnesi varsa onun içeriğini frombytes metodu ile Array nesnesine ekleyebiliriz. - fromlist metodu bir Python listesini alıp onun elemanlarını array nesnesine eklemektedir. #------------------------------------------------------------------------------------------------------------------------------------ import array a = array.array('i', [10, 20, 30, 40, 50]) print(a[2]) # 30 a[2] = 100 print(a) # array('i', [10, 20, 100, 40, 50]) b = a[2:4] print(b) # array('i', [100, 40]) for x in a: print(x, end=' ') # 10 20 100 40 50 print() a.append(60) print(a) # array('i', [10, 20, 100, 40, 50, 60]) a.extend([70, 80, 90]) print(a) # array('i', [10, 20, 100, 40, 50, 60, 70, 80, 90]) a.pop(2) print(a) # array('i', [10, 20, 40, 50, 60, 70, 80, 90]) a.remove(60) print(a) # array('i', [10, 20, 40, 50, 70, 80, 90]) a.insert(1, 100) print(a) # array('i', [10, 100, 20, 40, 50, 70, 80, 90]) a.reverse() print(a) # array('i', [90, 80, 70, 50, 40, 20, 100, 10]) result = a.index(100) print(result) # 6 x = a.tolist() print(x) # array('i', [90, 80, 70, 50, 40, 20, 100, 10]) y = a.tobytes() print(y) # b'Z\x00\x00\x00P\x00\x00\x00F\x00\x00\x002\x00\x00\x00(\x00\x00\x00\x14\x00\x00\x00d\x00\x00\x00\n\x00\x00\x00' print(a.typecode) # i print(a.itemsize) # 4 b = b'\x10\x00\x00\x00' # 16 sayısının byte karşılığı a.frombytes(b) print(a) # array('i', [90, 80, 70, 50, 40, 20, 100, 10, 16]) a.fromlist([100, 200, 300]) print(a) # array('i', [90, 80, 70, 50, 40, 20, 100, 10, 16, 100, 200, 300]) #------------------------------------------------------------------------------------------------------------------------------------ Tabii biz bir array nesnesin bir elemanına o tür ile temsil edilemeyen bir değeri atayamayız. #------------------------------------------------------------------------------------------------------------------------------------ import array a = array.array('i', [10, 20, 30, 40, 50]) a[1] = 12.3 # exception oluşur #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ C, C++, Java, C# gibi pek çok dilde "enumaration (sayımlama), kısaca enum" denilen türler bulunmaktadır. Enum türleri aslında bu dllerde birtakım sayıların isimsel olarak temsil edilmesini sağlamak için kullanılmaktadır. Bazen kısıtlı sayıda seçeneğe sahip olan olgular söz konusu olabilir ve bunların tamsayılarla ifade edilmesi gerekebilir. Genel olarak yazılar pek çok durumda kategori belirten değerlerin temsil edilmesinde hem zamansal bakımdan hem de hata kontrolü bakımından uygun olmayabilmektedir. Python'da bir enum türü yoktur. Ancak standart kütüphanede enum türünü temsil eden enum isimli modülün içerisinde Enum isimli bir sınıf bulundurulmuştur. Python'da enumaration kullanımı şöyledir: Programcı önce enum.Enum sınıfından bir sınıf türetir. Türemiş sınıfta enum sabitlerini isim = değer biçiminde sınıf değişkeni (sınıf özniteliği) olarak oluşturur. Sonra sınıf ismi ve nokta operatörü ile bu enum sabitlerine erişir. Örneğin: class Direction(enum.Enum): Up = 0 Right = 1 Down = 2 Left = 3 Burada biz Direction isimli bir enum oluşturmuş olduk. Bu enum elemanlarına Direction.Up, Direction.Right, Direction.Down, Direction.Left biçiminde erişebiliriz. Pekiyi bunu sağlama için neden enum.Enum sınıfından türetme yapılmaktadır? Aynı işlemleri aşağıdaki gibi yapamaz mıydık? class Direction: Up = 0 Right = 1 Down = 2 Left = 3 İşte eğer bu biçimde enum oluşturmaya çalışsaydık burada sınıf değişkenleri bir sabit gibi ele alınmazdı. Bu değişkenlere atamaya yapılabilirdi. Halbuki artık bu değişkenlere atama yapılamamaktadır. Ayrıca eğer enum oluşturmayı yukarıdaki gibi yapmış olsaydık bir int değer ile enum değerini karşılaştırıp işleme sokabilirdik. Halbuki enum.Enum sınıfından türetme yaptığımızda ancak aynı türden iki enum değerini anlamlı olarak karşılaştırabilmekteyiz. #------------------------------------------------------------------------------------------------------------------------------------ import enum class Direction(enum.Enum): Up = 0 Right = 1 Down = 2 Left = 3 def move(direction): if direction == Direction.Up: print('yukarıya gidiyor') elif direction == Direction.Right: print('sağa gidiyor') elif direction == Direction.Down: print('aşağıya gidiyor') elif direction == Direction.Left: print('sola gidiyor') move(Direction.Left) move(Direction.Right) move(Direction.Up) move(Direction.Down) #------------------------------------------------------------------------------------------------------------------------------------ Bir enum değişkeninin değeri Enum sınıfının value özniteliği ile elde edilebilmektedir. Örneğin: d = Direction.Right print(d.value) Enum değerinin ismi ise sınıfın name isimli özniteliği ile elde edilebilmektedir. Örneğin: d = Direction.Right print(d.name) # Right Enum sınıfından türetilmiş olan sınıf türünden nesne yaratırken enum değeri verilebilir. Örneğin: e = Direction(2) print(e.name) # Down Türetilerek yaratılmış enum sınıf türleri "dolaşılabilir" nesnelerdir. Dolaşıldıkça enum sabitleri elde edilmektedir. Örneğin: for x in Direction: print(x) enum sınıf ismi ile [...] operatörü kullanılırsa köşeli parantez içerisine enum sabitlerinin ismi yazı olarak verildiğinde onun değerini de elde edebiliriz. Örneğin: x = Direction['Right'] print(x) Bütün bu olanaklar aslında taban Enum sınıfında sağlanmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import enum class Direction(enum.Enum): Up = 0 Right = 1 Down = 2 Left = 3 d = Direction.Down print(d.value) # 2 print(d.name) # Down k = Direction(2) print(k) # Direction.Down for x in Direction: print(x, end=' ') # Direction.Up Direction.Right Direction.Down Direction.Left print() result = Direction['Down'] print(result) # Direction.Down #------------------------------------------------------------------------------------------------------------------------------------ Standart kütüphanedeki "fonksiyonel programlama (functional programming)" paradigmasını desteklemek amacıyla itertools modülünün yanı sıra functools isimli bir modül de bulundurulmuştur. functools modülü genel olarak dekoratör biçiminde kullanılmaya uygun olan fonksiyonlardan ve sınıflardan oluşmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ functools modülü içerisindeki cache isimli dekoratör fonksiyonu bir fonksiyonun bir argümanla çağrılması durumunda bu argümanla geri dönüş değerini bir yerde saklar. Fonksiyon ikinci kez aynı argümanla çağrıldığında boşuna fonksiyonu çağırmadan doğrudan bize geri dönüş değerini verir. Örneğin: import functools @functools.cache def foo(a): print('foo called') return a * a result = foo(10) # fonksiyon gerçekten çağrılacak print(result) result = foo(15) # fonksiyon gerçekten çağrılacak print(result) result = foo(10) # fonksiyon çağrılmadan hemen 100 değerini verecek, çünkü daha önce 10 ile çağrıldığında 100 verilmişti print(result) #------------------------------------------------------------------------------------------------------------------------------------ import functools @functools.cache def foo(a): print('foo called') return a * a result = foo(10) print(result) result = foo(15) print(result) result = foo(10) print(result) #------------------------------------------------------------------------------------------------------------------------------------ functools.cache dekoratörü birden fazla parametre alan fonksiyonlarda da kullanılabilir. Bu durumda geri dönüş değerinin cache'ten alınması için tüm argümanların aynı olması gerekir. #------------------------------------------------------------------------------------------------------------------------------------ import functools @functools.cache def foo(a, b): print('foo called') return a + b result = foo(2, 3) print(result) result = foo(4, 6) print(result) result = foo(2, 3) print(result) #------------------------------------------------------------------------------------------------------------------------------------ 73. Ders 23/09/2023 – Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ functools.cache dekoratörü argüman değeri ile geri dönüş değeri arasında ilişki kurmaktadır. Yani cache sisteminin bir sözlükle gerçekleştirildiğini varsayarsak burada anahtar argüman, değer ise geri dönüş değeridir. Eğer fonksiyon argümana bağlı bir geri dönüş değerine sahip değilse ya da fonksiyon içerisinde bir "yan etki" oluşturacak başka işlemler yapılıyorsa bu dekoratör kullanılmamalıdır. #------------------------------------------------------------------------------------------------------------------------------------ import functools import random @functools.cache def foo(a): print('foo called') return random.randint(0, 100) result = foo(5) print(result) # 81, rastgele bir değer result = foo(5) print(result) # 81 çünkü fonksiyon çağrılmıyor #------------------------------------------------------------------------------------------------------------------------------------ functools modülü içerisindeki cache dekoratörünün bir benzeri basit bir biçimde aşağıdaki gibi yazılabilir. #------------------------------------------------------------------------------------------------------------------------------------ import functools class mycache: def __init__(self, f): self.f = f self.d = {} def __call__(self, *args): if result := self.d.get(args): return result val = self.f(*args) self.d[args] = val return val @mycache def foo(a): print('foo called') return a val = foo(10) print(val) val = foo(20) print(val) val = foo(10) print(val) #------------------------------------------------------------------------------------------------------------------------------------ functools modülündeki lru_cache isimli dekoratör aslında tamamen cache dekoratörü gibidir. Bunun tek farkı son maxsize tane değeri tutmasıdır. Bu maxsize değeri belirtilmezse default 128 alınmaktadır. Buradaki LRU öneki "Least Recently Used" anlamına gelmektedir. Yani son zamanlarda en az kullanılanın cache'ten atılacağını belirtir. Cache'te olan eski bir öğeyi kullanırsak bu öğre son zamanlarda kullanıldığı için öne çekilir ve cache'ten atılmaz. Örneğin cache uzunluğu 3 olsun biz de fonksiyonu sırasıyla şu argümanlarla çağırmış olalım: val = foo(10) print(val) # foo çağrılır, 10 val = foo(20) print(val) # foo çağrılır, 20 val = foo(30) print(val) # foo çağrılır, 30 val = foo(10) print(val) # foo çarıllmaz, 10 val = foo(40) print(val) # foo çağrılır, 40 ve 20 cache'ten atılır val = foo(10) print(val) # foo çağrılmaz, 10 val = foo(20) # foo çarılır, 20 print(val) Burada cache sırasıyla 10, 20, 30 yerleştirilmiştir. cache'in sonunda 10 vardır. Yeni bir eleman cache'e alınacaksa 10 çıkartılacaktır. Ancak burada 10 değeri eklendikten sonra kullanılmış (cache hit) ve cache'te öne çekilmiştir. Böylece cache'ten atılacak olan değer artık 20 olacaktır. Görüldüğü gibi LRU cache algoritmasında her kullanılan eleman cache'te öne çekilmekte ve son zamanlarda en az kullanılan cache'e arkada kalıp atılmaya aday olmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ import functools @functools.lru_cache(3) def foo(a): print('foo called') return a val = foo(10) print(val) # foo çağrılır, 10 val = foo(20) print(val) # foo çağrılır, 20 val = foo(30) print(val) # foo çağrılır, 30 val = foo(40) print(val) # foo çağrılır, 40 ve 10 cache'ten atılır val = foo(10) print(val) # foo çağrılır, 10 val = foo(40) # foo çağrılmaz, 40 print(val) #------------------------------------------------------------------------------------------------------------------------------------ functools modülündeki total_ordering dekoratörü bir sınıfa iliştirilebilir. Bu dekoratör < ve == operatör metotları bulunan sınıfta diğer operatör metotlarını bunları kullanarak yazmaktadır. Böylece biz bir sınıf için yalnızca bu iki operatör metodunu yazabiliriz. Örneğin elimizde < ve == işlemini yapabilen metotlar varsa biz >= işlemini bunları kullanarak yapabiliriz. Aşağıdaki örnekte Number sınıfı < ve == operatör metotlarını bulundurmuştur. Diğer karşılaştırma operatör metotları dekoratör tarafından bunlar oluşturulacaktır. #------------------------------------------------------------------------------------------------------------------------------------ import functools @functools.total_ordering class Number: def __init__(self, val): self.val = val def __lt__(self, number): print('lt') return self.val < number.val def __eq__(self, number): print('eq') return self.val == number.val x = Number(10) y = Number(10) if x <= y: print('ok') else: print('not ok') print('----------------') x = Number(10) y = Number(10) if x > y: print('ok') else: print('not ok') #------------------------------------------------------------------------------------------------------------------------------------ functools modülündeki partial isimli fonksiyon bir fonksiyonu ve ilk n tane argüman değerini parametre olarak alır ve bize başka bir fonksiyon verir. Biz onun verdiği fonksiyonu çağırırken ilk n parametre için argüman girmeyiz. Yalnızca geri kalan k tane parametre için argüman gireriz. İlk n tane parametre için argüman bizim partial fonksiyonuna verdiğimiz argüman olur. #------------------------------------------------------------------------------------------------------------------------------------ import functools def foo(a, b, c): print(a, b, c) bar = functools.partial(foo, 100, 200) bar(20) # 100 200 20 bar(30) # 100 200 30 #------------------------------------------------------------------------------------------------------------------------------------ functools modülündeki partialmethod isimli fonksiyon da partial fonksiyonu gibidir. Ancak sınıfın bir metodunu alarak onun ilk n parametresi için yerleştirme yapar. Bu fonksiyon kullanılırken tipik olarak fonksiyonun geri döndürdüğü değer sınıtaki bir sınıf değişkenine (class attribute) atanmalıdır. Örneğin: class Sample: def foo(self, a, b, c): print(a, b, c) bar = functools.partialmethod(foo, 100, 200) #------------------------------------------------------------------------------------------------------------------------------------ import functools class Sample: def foo(self, a, b, c): print(a, b, c) bar = functools.partialmethod(foo, 100, 200) s = Sample() s.foo(10, 20, 30) # 10 20 30 s.bar(10) # 100 200 10 #------------------------------------------------------------------------------------------------------------------------------------ functools modülündeki reduce fonksiyonu aslında itertools modülündeki accumulate fonksiyonuna çok benzemektedir. Ancak reduce fonksiyonu accumulate gibi her değeri değil yalnızca sonuç değerini bize verir. reduce fonksiyonunun birinci parametresi bir fonksiyon olmalıdır. Bu fonksiyon bir önceki değerle yeni sıradaki değer argüman yapılıp çağrılmaktadır. Aşağıdaki örnekte her iki fonksiyon da birlikte kullanılmıştır. itertools.accumulate bize bir itertor nesnesi verirken functools.reduce bize tek bir değer vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ import itertools iterator = itertools.accumulate([1, 2, 3, 4, 5], lambda a, b: a + b) for x in iterator: print(x) import functools result = functools.reduce(lambda total, item: total + item, [1, 2, 3, 4, 5]) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Bilindiği Python'da tüm atamalar aslında adres atamasıdır. Örneğin: a = [1, 2, 3, 4, 5] b = a Burada b = a atamasıyla yeni bir nesne yaratılmamaktadır. a ve b aynı nesnenin adreslerini tutmaktadır. Yani a is b True verir. Biz bazen değiştirilebilir nesnelerin yeni kopyasını oluşturmak isteyebiliriz. Çünkü birinin bozulması durumunda diğerinin değerini korunması gerekebilir. Tabii temel türlerde olduğu gibi değiştirilemez türlerin kopyasının oluşturulmasının bir anlamı yoktur. Örneğin bir string "değiştirilemez (immutable)" bir nesnedir. Bizim onun kopyasını oluşturmamızın bir anlamı yoktur. Python'da değiştirilebilir olan list gibi, set gibi, dict gibi sınıflara ilişkin nesnelerin kopyalarını çıkartmak için zaten bu sınıflarda copy isimli metotlar bulunmaktadır. Örneğin: >>> a = [1, 2, 3, 4, 5] >>> id(a) 1954630234624 >>> b = a.copy() >>> b [1, 2, 3, 4, 5] >>> id(b) 1954630260032 >>> a = {'ali': 10, 'veli': 20, 'selami': 30} >>> b = a.copy() >>> id(a) 1815000300928 >>> id(b) 1815000319488 >>> a = {1, 2, 3, 4, 5} >>> b = a.copy() >>> id(a) 1814999063840 >>> id(b) 1814999060704 Yine dilimleme, ve yineleme (repition) gibi işlemlerin aslında kopya yoluyla yapıldığını anımsayınız. Ancak biz kendi sınıfımızın kopyasını da oluşturmak isteyebiliriz. İşte bunun için copy modülünde copy ve deepcopy isimli iki fonksiyon bulundurulmuştur. copy fonksiyonu "sığ kopyalama (shallow copy)", deepcopy fonksiyonu ise "derin kopyalama (deep copy)" işlemi yapmaktadır. Sığ kopyalama ana nesnenin kopyasının çıkartılması ama elemanlar için kopya çıkartılmaması anlamına gelir. Derin kopyalama ise "özyinelemeli (recursive)" biçimde tüm nesnenin kopyalarının çıkartılmasını sağlamaktadır. Örneğin biz Sample sınıfı türünden bir nesnenin elemanlarını değiştirmeden önce onun bir kopyasını çıkartarak eski halini saklamak isteyebiliriz: import copy .... s = Sample(10, 20) k = copy.copy() #------------------------------------------------------------------------------------------------------------------------------------ import copy class Sample: def __init__(self, a, b): self.a = a self.b = b def __repr__(self): return f'a = {self.a}, b = {self.b}' s = Sample(10, 20) print(s) k = copy.copy(s) s.a = 100 s.b = 200 print(s) print(k) print(f'id(s): {id(s)}') print(f'id(k): {id(k)}') #------------------------------------------------------------------------------------------------------------------------------------ 74. Ders 29/09/2023 – Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Sığ kopyalamada yalnızca ana nesnenin kopyası çıkartılmaktadır. Dolayısıyla ana nesnenin bir örnek özniteliği "değiştirilebilir (mutable)" bir nesne ise bu nesnenin kopyası çıkartılmaz. Derin kopyalama daha fazla zaman alan ve daha fazla bellek kullanan bir kopyalama biçimidir. Bu nedenle programcı gerekmediği durumda derin kopyalama yapmamalıdır. Sığ kopyalamayı tercih etmelidir. Aşağıdaki örnek Sample sınıfının bir örnek özniteliği bir listedir. Sığı kopyalama yapıldıktan sonra bu listenin elemanları değiştirilirse bundan diğer kopya da etkilenecektir. #------------------------------------------------------------------------------------------------------------------------------------ import copy class Sample: def __init__(self, *args): self.a = list(args) def square(self): for i in range(len(self.a)): self.a[i] *= self.a[i] def __repr__(self): s = '' for x in self.a: if s != '': s += ', ' s += str(x) return s s = Sample(1, 2, 3, 4) print(s) k = copy.copy(s) print(k) s.square() print(s) print(k) print('---------------------------') s = Sample(1, 2, 3, 4) print(s) k = copy.deepcopy(s) print(k) s.square() print(s) print(k) #------------------------------------------------------------------------------------------------------------------------------------ Tabii derin kopyalama sırasında değiştirilemez türlere ilişkin elemanların kopyaları çıkartılmamaktadır. Zaten değiştirilemez türlerin kopyasının çıkartılmasının bir anlamı da yoktur. Aşağıdaki örnekte Sample sınıfının int türden değiştirilemez bir örnek özniteliği vardır. Bu durumda Sample nesnesinin sığ ya da derin kopyalama yöntemiyle kopyalanması arasında bir fark yoktur. #------------------------------------------------------------------------------------------------------------------------------------ import copy class Sample: def __init__(self, a): self.a = a s = Sample(10) k = copy.deepcopy(s) print(id(s), id(k)) print(id(s.a), id(k.a)) #------------------------------------------------------------------------------------------------------------------------------------ Bilindiği gibi Python'un float türü IEEE 754 formatını kullanmaktadır. Zaten bugün hemen her CPU gerçek sayıları bu formatta ele alıp eletrik devreleriyle işlem yapmaktadır. Dolayısıyla float türü de bu bağlamda doğal ve hızlı bir türdür. Ancak maalesef IEEE 754 fotmatının (genel olarak floating point formatların) "yuvarlama hatası (rounding error)" denilen bir problemi vardır. Yuvarlama hatası bazı noktalı sayıların tam olarak ifade edilemeyip ancak onlara yakın bir sayının ifade edilebilmesiyle oluşan hatadır. Yuvarlama hataları pek çok uygulamada önemsiz olarak değerlendirilebilir. Ancak muhasebe gibi, finans gibi, savunma sanayi gibi, uzay hesapları gibi kritik uygulamalarda yuvarlama hataları hiç istenmeyebilir. İşte Python'ın standart kütüphanesinde decimal isimli modüldeki Decimal sınıfı yuvarlama hatasına maruz kalmadan noktalı sayılarla işlemlerin yapılabilmesi için düşünülmüştür. Bir Decimal nesnesi "bir string'le", "int bir değerle" ya da "float bir değerle" oluşturulabilmektedir. Decimal sınıfının çeşitli operatör metotları vardır. Bu operatör metotları sayesinde Decimal türden iki nesne sanki float türünden iki nesneymiş gibi işlemlere sokulabilmektedir. Örneğin: >>> import decimal >>> a = decimal.Decimal('0.3') >>> b = decimal.Decimal('0.2') >>> c = a - b >>> print(c) 0.1 >>> a = 0.3 >>> b = 0.2 >>> c = a - b >>> c 0.09999999999999998 Bu örnekte Decimal nesnenin yuvarlama hatasına yol açmadığına ancak float türün yuvarlama hatasına yol açabildiğine dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tabii bir Decimal nesnenin float bir değerle oluşturulması kullanım alanı bakımından uygun olmayabilir. Örneğin: >>> a = Decimal(1.2) >>> a Decimal('1.1999999999999999555910790149937383830547332763671875') >>> b = Decimal('1.2') >>> b Decimal('1.2') Genel olarak biz iki Decimal nesneyi ya da bir Decimal nesneyle bir int nesneyi işleme sokabiliriz. Ancak bir Decimal nesneyle bir float nesneyi ya da bir Decimal nesneyle bir string'i işleme sokamayız. Bir Decimal nesneyle bir int nesne işleme sokulduğunda sonuç Decimal türden elde edilir. Bunu sembolik olarak şöyle belirtebiliriz: Decimal Decimal => Decimal Decimal int => Decimal Decimal float Geçersiz! Decimaş str Geçersiz! Örneğin: >>> a = Decimal('1.2') >>> b = a + 10 >>> b Decimal('11.2') >>> a + 3.2 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float' >>> a + '1.2' Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'str' Bir Decimal nesne float türüne, int türüne ve bool türüne döüştürülebilir. Çünkü Decimal sınıfı için bu dönüşümleri yapabilecek __float__, __int__ ve __bool__ operatör metotları bulundurulmuştur. Örneğin: >>> d = Decimal('3.141592653589793238462643') >>> d Decimal('3.141592653589793238462643') >>> float(d) 3.141592653589793 >>> int(d) 3 >>> bool(d) T True Normal olarak math modülündeki fonksiyonların Decimal türü ile kullanımı konusunda Standart Kütüphane dokümanlarında bir belirleme yapılmamıştır. Eskiden bu fonksiyonların Decimal türü ile kullanımlarında sorun oluşuyordu. Ancak bu fonksiyonlar daha sonraları kendi içlerinde argümanları float türüne dönüştürmeye başlamıştır. Bu nedenle Decimal türler de math modülündeki fonksiyonlar tarafından kullanılabilir duruma gelmiştir. Ancak bu durum Standart Kütüphanede dokümante edilmemiştir. Bu nedenle Decimal türünü math modülündeki fonksiyonlarla doürudan kullanmayınız. Tabii bir Decimal nesnesini float türüne dönüştürerek math modülündeki fonksiyonlarla kullanabiliriz. Ancak Decimal sınıfında da math modülündeki işlemleri sqrt, ln, log10 gibi metotlar bulundurulmuştur. Tabii bu metotların geri dönüş deperleri Decimal türdendir. Örneğin: >>> d = Decimal('3.141592653589793238462643') >>> math.sqrt(d) 1.7724538509055159 >>> d.sqrt() Decimal('1.772453850905516027298167375') >>> math.log10(d) 0.49714987269413385 >>> d.log10() Decimal('0.4971498726941338543512682353') #------------------------------------------------------------------------------------------------------------------------------------ from decimal import Decimal d = Decimal(2) result = d.sqrt() print(result) result = d.ln() print(result) result = d.log10() print(result) #------------------------------------------------------------------------------------------------------------------------------------ Decimal modülündeki Decimal sınıfı ile işlemler yapılırken bazı belirlemelere uyulmaktadır. Bu belirlemelere "context" denilmektedir. Bu belirlemeler getcontext() fonksiyonu ile elde edilebilir. context nesnesi getcontext fonksiyonuyla elde edildikten sonra nesne üzerinde değişikler yapılabilmektedir. getcontext ile alınan bağlam nesnesi "thread temelinde singleton" bir nesnedir. Yani bu nesnenin toplamda her thread için tek bir kopyası vardır. Biz aynı thread'te birden fazla kez getcontext fonksiyonunu çağırsak bile bu fonksiyon bize hep aynı değeri verir. Bağlam nesnesi getcontext fonksiyonuyla alınıp onun belli elemanları değiştirilebilir. Ya da istenirse yeni context nesnesi oluşturulup setcontext fonksiyonuyla bu nesne set edilebilir. Bağlam nesnesinin en önemli elemanlarından biri "prec" elemanıdır. Bu prec elemanı Decimal sayıların işlem duyarlılığını belirtmektedir. Bu elemanın default değeri 28'dir. Bu değer Decimal sayının toplam mantisini (yani tam kısmı ve noktalı kısmının toplam basamak sayısını) belirtmektedir. Örneğin: >>> import decimal >>> d = decimal.Decimal(3) >>> d.sqrt() Decimal('1.732050807568877293527446342') >>> c = decimal.getcontext() >>> c.prec = 100 >>> d.sqrt() Decimal('1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248576') prec elemanının değerini değiştirdiğimizde Decimal nesne yine bizim verdiğimiz duyarlılıkta sayıyı tutar. Bu prec elemanı işlem sonucundaki elde edilen Decimal nesne üzerinde etkili olmaktadır. Örneğin: >>> import decimal >>> decimal.getcontext().prec = 3 >>> x = decimal.Decimal('1.234567') >>> x Decimal('1.234567') >>> x + 1 Decimal('2.23') Buradaki yuvarlama da aslında birkaç seçenekten biri olarak seçilebilir. Yuvarlamanın biçimi bağlam nesnesinin rounding elemanı ile belirlenmektedir. Bu eleman default olarak decimal.ROUND_HALF_EVEN biçimindedir. ROUND_HALF_EVEN durumunda sayı yuvarlanacağı zaman yuvarlama yerinde 5 varsa ve başka bir digit yoksa bu durumda sayının noktadan sonraki kısmı çift olacak biçimde yuvarlanır. ROUND_HALF_UP durumunda ise klasik biçimde [0,4] aşağıya, [5, 9] yukarıya yuvarlanmaktadır. Örneğin: >>> import decimal >>> decimal.getcontext().prec=3 >>> d = decimal.Decimal('1.265') >>> d + 0 Decimal('1.26') >>> decimal.getcontext().rounding = decimal.ROUND_HALF_UP >>> d + 0 Decimal('1.27') Python'un built-in round fonksiyonu da buradaki ROUND_HALF_EVEN gibi çalışmaktadır. Örneğin: Decimal('1.27') >>> f = 1.265 >>> round(f, 2) 1.26 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Rasyonel sayılar a ve b birer tamsayı ve b 0'a eşit olmamak üzere olmak üzere a / b biçiminde yazılan sayılardır. Matematikte rasyonel sayılar kümesi tamsayılar kümesini kapsamaktadır. Python'ın standart kütüphanesindeki fractions isimli modülde Fraction isimli sınıf rasyonel sayıları temsil etmektedir. Bir Fraction nesnesi pay ve payda belirtilerek yaratılabilir. Örneğin: from fractions import Fraction x = Fraction(3) # 3/1 ile aynı anlamda y = Fraction(2, 3) # 2/3 ile aynı anlamda İngilizce "pay" sözcüğü "numerator" sözcüğü ile "payda" sözcüğü "denominator" sözcüğü ile ifade edilmektedir. Bir Fraction nesnesi float bir nesneyle de yaratılabilmektedir. Örneğin: x = Fraction(0.5) Birs string'ten de Fraction nesnesi oluşturulabilmektedir. Örneğin: x = Fraction('1/2') Fraction sınıfının pek çok operatör metodu rasyonel sayı işlemlerini yapmaktadır. math modülündeki matematiksel fonksiyonlar da Fraction nesneleri ile çalışmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ from fractions import Fraction x = Fraction(1, 2) print(x) y = Fraction(1, 3) print(y) result = x + y print(result) # 5/6 result = x + y * 2 print(result) # 7/6 result = x ** 2 print(result) # 1/4 import math result = math.sqrt(x) print(result) # 0.7071067811865476 result = math.sin(x) print(result) # 0.479425538604203 #------------------------------------------------------------------------------------------------------------------------------------ Bir Fraction nesnesindeki pay ve payda değerleri numerator ve denominator öznitelikleri ile elde edebilir. #------------------------------------------------------------------------------------------------------------------------------------ from fractions import Fraction x = Fraction(1, 2) print(x.numerator, x.denominator) #------------------------------------------------------------------------------------------------------------------------------------ Fraction sınıfının as_integer_ratio metodu bize rasyonel sayının pay ve paydasını iki tane int değerden oluşan bir demet olarak verir. Örneğin: >>> from fractions import Fraction >>> x = Fraction(1, 2) >>> a, b = x.as_integer_ratio() >>> a 1 >>> b 2 Biz Fraction sınıfında bir float sayı verdiğimizde sınıf bunu otomatik olarak rasyonel sayı biçimine getirmektedir. Ancak biz limit_denominator metodu ile bu sayıların basamaklarını sınırlandırabiliriz. Örneğin: >>> from fractions import Fraction >>> Fraction('3.1415926535897932') Fraction(7853981633974483, 2500000000000000) >>> Fraction('3.1415926535897932').limit_denominator(1000) Fraction(355, 113) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Nesne Yönelimli Programlama Tekniğinde (NYPT) "soyut sınıf (abstract class)" kendisinden nesne yaratılamayan içerisindeki soyut metotları sınıftan türetme yapan kişilerin yazmak (override etmek) zorunda olduğu özel sınıflardır. Soyut sınıflar "bir kontrat oluşturmak" için, "arayüz oluşturmak için", birtakım minimal gereksinimlerin karşılanması için kullanılabilmektedir. Java ve C# gibi dillerdeki "arayüzler (interface)" benzer amaçla kullanılıyor olsa da soyut sınıflardan farklıdır. Bu dillerde arayüzler veri elemanlarına (fields) sahip olamazlar ve soyut olmayan metot içeremezler. Halbuki soyut sınıflar veri elemanlarına (fields) sahip olabilmekte ve soyut olmayan metotlar içerebilmektedir. Bir soyut sınıfı Python'da oluşturabilmek için abc isimli modüldeki ABC sınıfından faydalanılmaktadır. (Buradaki abc ismi "abstract base class sözcükleriden" kısaltılmıştır.) Programcı abc.ABC sınıfından türetme yaparsa türettiği sınıf soyut sınıf olur. Örneğin: import abc class Sample(abc.ABC): pass Bir soyut sınıf soyut metotları oluşturmak için kullanılmaktadır. Yani biz bir soyut sınıfa soyut metot yerleştirmezsek soyut sınıf oluşturmamızın bir anlamı kalmaz. Bir soyut sınıfa soyut metot yerleştirmek için soyut metodun abc.abstractmethod dekoratörü ile dekore edilmesi gerekmektedir. Örneğin: import abc class Sample(abc.ABC): def foo(self): print('foo') @abc.abstractmethod def bar(self): pass Soyut sınıftaki soyut metot normal bir metot gibi suit içerebilir. Örneğin: import abc class Sample(abc.ABC): def foo(self): print('Sample.foo') @abc.abstractmethod def bar(self): print('abstract bar') Soyut bir sınıfa soyut bir metot yerleştirilirse artık o soyut sınıf türünden nesneler yaratılamaz. Örneğin: s = Sample() # exception oluşur Soyut bir sınıfın kendisi bir işe yaramaz. Ondan türetme yapılması ve taban sınıftaki soyut metotların türemiş sınıfta yazılması (override edilmesi) gerekir. Örneğin: import abc class Sample(abc.ABC): def foo(self): print('foo') @abc.abstractmethod def bar(self): pass class Mample(Sample): pass Ancak soyut bir sınıftan türetme yapıldığında türemiş sınıf taban soyut sınıftaki soyut metotları barındırmazsa türemiş sınıf da soyut olur ve türemiş sınıf türünden de nesneler yaratılamaz. Yukarıdaki örneğimizde Mample sınıfı Sample soyut sınıfındaki bar isimli soyut metodu tanımlamamıştır. O halde bu Mample sınıfı da soyut bir sınıftır. Biz Mample sınıfı türünden de nesneler yaratamayız. Örneğin: m = Mample() # exception oluşur Şimdi Mample sınıfına bar metodunu ekleyelim: import abc class Sample(abc.ABC): def foo(self): print('foo') @abc.abstractmethod def bar(self): pass class Mample(Sample): def bar(self): print('Mample.bar') Şimdi artık Mample sınıfı türünden nesneler yaratabiliriz: m = Mample() # geçerli m.bar() m.foo() Görüldüğü gibi artık türemiş Mample sınıfı türünden nesneler yaratabilmekteyiz. Yukarıda da belirttiğimiz gibi bir soyut sınıftan türetme yapıldığında türemiş sınıfın soyutluktan kurtulup somut (concrete) hale gelebilmesi için taban soyut sınıftaki bütün soyut metotları barındırması gerekir. Örneğin: import abc class Sample(abc.ABC): def foo(self): print('foo') @abc.abstractmethod def bar(self): pass @abc.abstractmethod def tar(self): pass class Mample(Sample): def bar(self): print('Mample.bar') m = Mample() Burada Mample sınıfı Sample sınıfından türetilmiştir. Ancak Sample sınıfındaki yalnızca bar soyut metodunu barındırmıştır. Bu durumda Mample sınıfı da soyuttur. Mample sınıfı türünden de nesneler yaratamayız. Örneğin: m = Mample() # exception oluşur Ancak buradaki Mample sınıfı Sample sınıfınındaki bar ve tar soyut metotlarının her ikisini barındırırsa (override ederse) Mample sınıfı somut hale getirilebilir. Örneğin: import abc class Sample(abc.ABC): def foo(self): print('foo') @abc.abstractmethod def bar(self): pass @abc.abstractmethod def tar(self): pass class Mample(Sample): def bar(self): print('Mample.bar') def tar(self): print('Mample.tar') m = Mample() m.foo() m.bar() m.tar() #------------------------------------------------------------------------------------------------------------------------------------ 75. Ders 30/09/2023 – Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da "meta class" konusu genellikle programcıların doğrudan kullanmadığı nispeten kişilere soyut gelen bir konudur. Ancak ayrıntılı birtakım modüllerin yazılması gerektiği durumlarda meta. "meta class" konusundan faydalanılması gerekebilmektedir. Meta sözcüğü "bir olgunun kendisini betimleyen olgular için" kullanılmaktadır. Örneğin "meta data" data'yı betimleyen data anlamındadır. Yani bir verinin neresinde ne olduğunu anlatan verilerdir. "Meta language" dilleri betimleyen dildir. İşte Python'daki "meta class" kavramı da benzerdir. Türkçe'de meta sözcüğünün karşılığı olarak genellikle "üst" sözcü kullanılmaktadır. Ancak bir "meta class" terimi için "meta sınıf" terimini kullanacağız. Python'da yorumlayıcı bir sınıf tanımlamasıyla karşılaştığında önce type sınıfı türünden bir nesne yaratır. Sonra bu nesnenin içerisine sınıfın içerisindeki bilgileri yerleştirir. Sonra da bu nesnenin adresini sınıf ismiyle belirtilen değişkene atar. Örneğin: class Sample: pass Burada Sample aslında type türünden bir sınıf nesnesini belirtmektedir. Sample değişkeninin diğer değişeknlerden bir farkı yoktur. Örneğin: >>> class Sample: ... pass ... >>> type(Sample) >>> id(Sample) 1971454719296 >>> print(Sample) Tabii Sample sınıfı türünden bir nesne yatattığımızda onun türü artık Sample olacaktır. Örneğin: >>> class Sample: ... pass ... >>> type(Sample) >>> s = Sample() >>> type(s) Yani burada Sample değişkeni type türündedir, s değişkeni ise Sample türündendir. Sınıf isimleri sıradan birer değişken olduğuna göre başka değişkenlere atanabilirler. Örneğin: >>> class Sample: ... pass ... >>> Mample = Sample >>> s = Mample() >>> print(s) <__main__.Sample object at 0x000001EE96089ED0> Burada biz Sample değişkenini Mample değişkenine atadık. Böylece aslında Sample değişkeni ile Mample değişkeni aynı type nesnesini gösterir hale geldi. Nesneyi Sample() biçiminde yaratmakla Mample() biçiminde yaratmak arasında bir fark kalmadı. Python'da type sınıfında olduğu gibi bir sınıfın bilgilerini tutan sınıfa "meta sınıf (meta class)" denilmektedir. type bir meta sınıftır. Bir sınıf tanımladığımızda yorumlayıcı default olarak meta sınıf olarak type sınıfını kullanmaktadır. Ancak biz kullanılacak meta sınıfı değiştirebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Öncelikle default meta sınıf olan tope sınıfı hakkında bazı bilgiler vermek istiyoruz. type fonksiyonunu biz bir değişkenin türünü elde etmek için kullanmıştık. Örneğin: >>> a = 10 >>> type(a) type fonksiyonu aslında bize ilgili değişkenin türüne ilişkin sınıf bilgilerinin yerleştirilmiş olduğu type nesne referansını vermektedir. Yukarıdaki örnekte type(a) ile int aynı anlamdadır. Çünkü int fdeğişkeni aslında int isimli sınıfın bilgilerinin tutulduğu type nesnesinin adresini tutan bir değişkendir. type(a) ifadesi de bize aynı nesnenin adresini vermektedir. O halde type(a) ile verilen adres ile int değişkeninin içerisindek adres aynı type nesnesi göstermektedir. Yorumlayıcı her sınıf için yalnızca bir tane type nesnesi oluşturmaktadır. Örneğin: >>> a = 10 >>> t = type(a) >>> t is int True >>> id(t) 140704769070640 >>> id(int) 140704769070640 Aynı durum bizim oluşturduğumuz sınıflar için de geçerlidir. Örneğin: >>> class Sample: ... pass ... >>> s = Sample() >>> t = type(s) >>> t is Sample True type sınıfının __str__ ve __repr__ metotları o type nesnesi içerisinde hangi sınııfn bilgileri varsa bize o sınııfn ismini bir string olarak vermektedir. Örneğin: >>> class Sample: ... pass ... >>> print(Sample) >>> print(repr(Sample)) Bu nedenle biz bir sınıf türünden değişkeni type fonksiyonuna sokup onu yazdırdığımızda o değişkenin türü ekrana çıkmaktadır. Örneğin: >>> s = Sample() >>> print(type(s)) Mademki yorumlayıcı bir sınıf tanımlaması gördüğünde aslında type sınıfı türünden bir nesne yaratmaktadır. O halde biz de hiç sınıf tanımlaması yapmadan type sınıfı türünden bir nesne yaratırsak aslında bir sınıf oluşturmuş oluruz. İşte type fonksiyonu üç argümanla çağrılırsa (yani type sınıfının __init__ metodu üç argümanla çağrılırsa) type fonksiyonu bize yeni bir type nesnesi yaratır ve o type nesnesinin nesne adresini verir. O halde bir sınıf yaratmak için sınıf tanımlamak yerine biz doğrudan bu işi type fonksiyonuyla da yapabiliriz. Zaten yorumlayıcı bir sınıf tanımlamasını gördüğünde aslında kendisi type fonksiyonunu çağırıp sınıfı oluşturmaktadır. type fonksiyonun bir argümanla çağrılmasıyla üç argümanla çağrılması arasındaki önemli farka dikkat ediniz. type fonksiyonu bir argümanla çağrıldığında bize o argümanın ilişkin olduğu sınıfın type nesnesinin adresini vermektedir. Ancak type fonksiyonu üç argümanla çağrılırsa bu bir "sınıf yaratma" anlamına gelmektedir. type fonksiyonunun üç argümanı şunları belirtmelidir: type(sınıf_isim, taban_sınıflarını_belirten_demet, sınıfın_elemanlarını belirten_sözlük) Taban sınıflar bir demet biçiminde verilmelidir. Burada boş demet geçilse bile yine sınıf object sınıfından türetilmiş olacaktır. Sınıfın elemanları ise bir sözlük biçiminde verilmelidir. Aslında Python yorumlayıcısı da bir sınıfla karşılaştığında zaten o sınıfı type fonksiyonuyla yaratmaktadır. Sınıfın elemanları type nesnesinin içerisindeki bir sözlükte tutulmaktadır. Örneğin: class Sample: x = 10 def foo(self, a): return a * a Böyle bir sınıf bildirimini gören yorumlayıcı aslında type fonksiyonu ile şöyle bir nesne yaratmaktadır: Sample = type('Sample', (object, ), {'x': 10, 'foo': foo}) Mademki yorumlayıcı bir sınıf tanımlamasını gördüğünde type sınıfı türünden bir nesne yaratmaktadır. O halde aslında aynı şeyi biz de yapabiliriz: def foo(self, a): return a * a Sample = type('Sample', (object, ), {'x': 100, 'foo': foo}) s = Sample() print(s.x) result = s.foo(4) print(result) #------------------------------------------------------------------------------------------------------------------------------------ def foo(self, a): return a * a Sample = type('Sample', (object, ), {'x': 100, 'foo': foo}) s = Sample() print(s.x) result = s.foo(4) print(result) #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi bir sınıf için yaratılan type nesnesinin içerisinde hangi elemanlar vardır? Aslında type sınıfının kendisi de objet sınıfından türetilmiştir. Bu nedenle type nesnesinin içerisinde object sınıfının elemanları olacaktır. Örneğin: >>> type.__bases__ (,) >>> dir(type) ['__abstractmethods__', '__annotations__', '__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__delattr__', '__dict__ ', '__dictoffset__', '__dir__', '__doc__', '__eq__', '__flags__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__instancecheck__', '__itemsize__', '__le__', '__lt__', '__module__', '__mro__', '__name__', '__ne__', '__new__', '__or__', '__prepare__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__setattr__', '__sizeof__', '__str__', '__subclasscheck__', '__subclasses__', '__subclasshook__', '__text_signature__', '__weakrefoffset__', 'mro'] #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Aslında yorumlayıcı bir sınıf nesnesinin içerisine __dict__ isimli bir sözlük elemanı yerleştirmektedir. Yani type fonksiyonuyla verilen sözlük istenirse sınfın __dict__ elemanı ile elde edilebiilir. __dict__ bir sınıf değişkenidir. Bu nedenle sınıf ismiyle ya da o sınıf türünden bir değişkenle kullanılablir. Örneğin: class Sample: def foo(self): pass def bar(self): pass x = 10 d = Sample.__dict__ print(d) Buradan şöyle çıktı elde edilmiştir: {'__module__': '__main__', 'foo': , 'bar': , 'x': 10, '__dict__': , '__weakref__': , '__doc__': None} Burada __doc__ ve __weakref__ gibi bizim bulundurmadığımız iki elemanı da görüyorsunuz. __doc__ sınıfın doküman yazısını belirtir. Tabii aslında bir sınıf türünden nesnenin içerisine de bir __dict__ elemanı yerleştirilmektedir. Örneğin: def foo(self): pass d = {'x': 10, 'foo': foo} Sample = type('Sample', (object, ), d) s = Sample() print(s.__dict__) Burada ekrana şunlar basılmıştır: {'x': 10, 'foo': , '__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None} {} #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 76. Ders 06.10.2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi a bir sınıf türünden değişken olmak üzere a(...) biçiminde bir ifade yazdığımızda aslında bu ifade a.__call__(...) anlamına gelmekteydi. Yani bir sınıf türünden değişkeni (...) operatörü ile kullandığımızda aslında o değişkenin ilişkin olduğu sınıfın __call__ metodu çağrılmaktadır. O halde bir sınıf türünden nesne yaratma işlemi de aslında type sınıfının __call__ metodu ile yapılmaktadır. Örneğin: class Sample: pass s = Sample() # Sample type türünden olduğuna göre nesne type sınıfının __call__ metodu tarafından yaratılır. Burada Sample aslında type türünden bir nesneyi göstermektedir. O halde Sample(...) işleminde aslında type sınıfının __call__ metodu çağrılacaktır. Başka bir deyişle aslında nesneyi yaratmaktan sorumlu olan ana mekanizma type sınıfının __call__ metodudur. Pekiyi type sınıfının __call__ metodu nesneyi nasıl yaratmaktadır? İşte type sınıfının __call__ metodu önce sınıf nesnesini bellekte yaratmak için söz konusu sınıfın ile __new__ isimli statik metodu çağırmaktadır. Eğer bu __new__ metodu ilgili sınıf türünden bir type nesnesi verirse bu durumda o nesneyle bu kez __init__ metodunu çağırmaktadır. Yani tasarımda nesnenin bellekte tahsis edilmesi ile tahsis edilmiş nesneye ilkdeğerlerinin verilmesi biribirinden ayrılmıştır. __new__ metodu nesneyi tahsis etmek için kullanılırken __init__ metodu tahsis edilmiş nesne üzerinde birtakım ilkdeğerlerin verilmesi için kullanılmaktadır. Bu durumda type sınıfının nesne yarqatımını yapan __call__ metodu nesneyi şöyle yaratmaktadır: 1) Önce sınıf türüyle __new__ static metodunu çağırır. Tahsisat bu metot tarafından yapılmalıdır. bu noktada yaratılmak istenen sınıfın __new__ metodu çağrılmaktadır. 2) __new__ metodunun geri döndürdüğü nesne eğer __call__ metodunun çağrıldığı nesne türündense bu kez __init__ metodunu çağırmaktadır. Yani type sınıfının __call__ metodu temsili olarak şöyle yazılmıştır: class type: def __call__(self, *args, **kwargs): if self.__new__ is not object.__new__: o = self.__new__(self, *args, **kwargs) else: o = object.__new__(self) if o is self: o.__init__(*args, **kwargs) return o Buradan görüldüğü gibi bir nesne yaratılırken aslında önce o sınıfın static __new__ isimli static metodu çağrılmaktadır. O metodun geri döndürdüğü nesne eğer tahsis edilmek istenen sınıf türündense o nesne ile __init__ çağrılmaktadır. Pekiyi programcı kendi sınıfı için __new__ metodunu yazmazsa ne olur? Bu tür durumda bildiğiniz gibi o sınıfın __new__ metodu olan ilk taban sınıfın __new__ metodu çağrılacaktır. Aslında tüm tahsisatlar eninde sonunda object sınıfının __new__ metoduyla yapılmaktadır. Programcı kendi sınıfında __new__ metodunu yalnızca "araya girmek" için yazar (override eder). Programcı da aslında kendi yazdığı __new__ metodunda super().__new__(cls) çağrısıyla tahsisatın object sınıfının __new__ metodu tarafından yapılmasını sağlar. Örneğin: class Mample(): pass class Sample: @staticmethod def __new__(cls, *args, **kwargs): print('araya giriyoruz') return super().__new__(cls) def __init__(self, a, b): print('__init__ called') s = Sample(10, 20) Pekiyi __new__ metodu nasıl ve neden yazılır? __new__ metodu static bir metot olmalıdır. __new__ metodunun birinci parametresi tahsis edilecek sınıf türünden type nesnesini belirtir. type sınıfının __call__ metodu __new__ metoduna nesne yaratılırken kullanılan bütün argümanları geçirdiği için __new__ metodunun parametrik yapısının bu argümanları alacak biçimde *args ve **kwargs parametrelerine de sahip olması gerekir. Bu durumda __new__ metodunun tipik parametrik yapısı şöyle oluşturulmalıdır: def __new__(self, *args, **kwargs): pass Yukarıda da belirttiğimiz gibi programcı yalnızca araya girme işlemi yapmalıdır. Gerçek nesne her zaman eninde sonunde object.__new__ metoduyla yaratılmak zorundadır. Ancak bu noktada bir ayrıntıta dikkatinizi çekmek istiyoruz. object sınıfının __new__ metodunun tek parametresi vardır. Yani *args, **kwargs parametrleri yoktur. Dolayısıyla __new__ metodunu yazdığımız sınıf başka bir sınıftan türetilmişse taban sınıfın __new__ metodunu çağırırken dikkat ediniz. Eğer taban sınıfta __new-_ metodu yazılmamışsa object sınıfının __new__ metdou çağrılacaktır. Bazı ileri uygulamalarda __new__ metodunun programcı tarafından yazılması gerekebilmektedir. Örneğin singleton kalıbı Python'da __new__ metodu yoluyla sağlanabilir. NYPT'de bir tasarım kalıbı olarak "singleton" bir sınıf türünden toplamda tek bir nesnenin var olmasını sağlayan kalıptır. Programcı birden fazla nesne yarattığını sansa bile aslında toplamda tek bir nesne yaratılmaktadır. İşte biz de singleton kalıbını aşağıdaki gibi oluşturabiliriz: class Sample: _obj = None @staticmethod def __new__(cls, *args, **kwargs): if Sample._obj is None: Sample._obj = super().__new__(cls) return Sample._obj def __init__(self): print('__init__') s = Sample() print(id(s)) k = Sample() print(id(k)) m = Sample() print(id(m)) __new__ içerisinde nesne daha önce yaratılmışsa yaratılmış olan nesne verilmektedir. Nesne daha önce yaratılmamışsa gerçekten yaratılmaktadır. Singleton nesnenin dekoratör yoluyla da yazılmasını sağlayabiliriz. Bunun klasik yolu dekoratör sınıfında __call__ metodunda nesne yaratımını yapmaktadır. Örneğin: class singleton: def __init__(self, cls): self.cls = cls self._obj = None def __call__(self, *args, **kwargs): if self._obj is None: self._obj = self.cls(*args, **kwargs) return self._obj @singleton class Test: pass t1 = Test() print(id(t1)) t2 = Test() print(id(t2)) t3 = Test() print(id(t3)) Sınıf dekoratörleri yokken aynı işlem türtetme yoluyla da yapılabiliyordu: class Singleton: _obj = None @staticmethod def __new__(cls, *args, **kwargs): if Singleton._obj is None: Singleton._obj = super().__new__(cls) return Singleton._obj def __init__(self): print('__init__') class Sample(Singleton): pass s = Sample() print(id(s)) k = Sample() print(id(k)) m = Sample() print(id(m)) Bazen __new__ metodu içerisinde programcı başka bir sınıf türünden nesneyi tahsis edip verebilir. class A: def __init__(self): print('A.__init__') class B: def __init__(self): print('B.__init__') class Sample: @staticmethod def __new__(cls, name=None): if name == 'A': return A() elif name == 'B': return B() elif name is None: return super().__new__(cls) def __init__(self): print('__init__ called') s = Sample('B') # Aslında B nesneyi yaratılacak, Sample.__init__ çağrılmayacak print(type(s)) s = Sample('A') # Aslında A nesneyi yaratılacak, Sample.__init__ çağrılmayacak print(type(s)) s = Sample() # Sample türünden nesne yaratılır, Sample sınıfının __init__ metodu çağrılır print(type(s)) #------------------------------------------------------------------------------------------------------------------------------------ Anımsanacağı gibi yorumlayıcı bir sınıf tanımlamasıyla karşılaştığında önce type sınıfı türünden bir nesne yaratıyordu ve sınıfın bilgilerini bu nesnenin içerisine yerleştiriyordu. Sonra da bu nesnenin adresini sınıf ismi olan değişkene atıyordu. B urada type sınıfı bir meta senıfıtr. Sınıfları temsil eden sınıflara meta sınıf denilmektedir. Eğer isterse yorumlayıcının sınıf tanımlamasını gördüğünde yaratacağı nesnenin türünü değiştirebilir. Yani meta sınıf type sınıfı yerine başka bir sınıf da olabilmektedir. Meta sınıfı değiştirmek için sınıfı oluştururken parantezler içerisinde "metaclass = " yazılmalıdır. Örneğin: class Sample(metaclas=MyMetaClass): pass Buradaki metaclass normal olarak type sınıfından türetilmelidir. Örneğin: class MyMetaClass(type): pass Daha önceden de belirttiğimiz gibi bir sınıf nesnesi yaratıldığında aslında yorumlayıcı metaclass sınıfına ilişkin bir nesne yaratmaktadır. Eğer metaclass belirtilemzse type sınıfına ilişkin nesne yaratılır. Eğer metaclass belirtilirse metaclass türünden nesne yaratılır. Yukarıda da belirttiğimiz gibi bu yaratım sırasında yorumlayıcı sınıf ismini bir string olarak, sınıfın taban sınıflarını bir demet olarak ve sınıfın içerisindeki elemanları bir sözlük nesnesi olarak parametre yapıp metaclass sınıf nesnesini yaratmaktadır. Yani yaratım adeta şöyle yapılmaktadır: metaclass(sınıf_isim, taban_sınıflar, sınıfın_elemanları) Dolayısıyla eğer biz meta sınıfımız için __init__ metodu yazacaksak bu __init__ metodunun self dışında üç parametresi olmalıdır. Ancak aslında yaratımda type sınıfı kullanılacağı için bu parametrelerin super fonksiyonuyla type sınıfına aktarılması gerekir. Örneğin: class MyMetaClass(type): def __init__(self, name, bases, namespace): super().__init__(name, bases, namespace) print('MyMetaClass instance created...') class Sample(metaclass=MyMetaClass): pass Bu program çalıştırıldığında ekranda "MyMetaclass instance created..." yazısı çıkmalıdır. Tabii biz meta sınıfımız için __init__ metodunu yazmazsak type sınıfının __init__ metodu çağrılacaktır. Bu metot da normal yaratımı yapacaktır. Örneğin: class MyMetaClass(type): pass class Sample(metaclass=MyMetaClass): pass s = Sample() print(type(Sample)) # __main__.MyMetaClass print(type(s)) # Meta sınıfların type sınıfından türetilme zorunda olduğuna dikkat ediniz. Çünkü nesnelerin yaratılması gibi işlevsellikler type sınıfında bulunmaktadır. Bizim yukarıdaki örneğimizde MyMetaClass isimli sınıfımız zaten type sınıfının bütün işlevselliğini barındırmaktadır. Ancak ondan fazlalıkları vardır. #------------------------------------------------------------------------------------------------------------------------------------ class MyMetaClass(type): def __init__(self, name, bases, namespace): super().__init__(name, bases, namespace) print('MyMetaClass instance created') class Sample(metaclass=MyMetaClass): pass #------------------------------------------------------------------------------------------------------------------------------------ Tabii meta sınıfı değiştirdiğimiz sınıfımız başka bir sınıftan türetilmiş olablilir. Bu durumda metaclass belirlemesinin parantez içerisinde sonda yapılması gerekir. Örneğin: class MyMetaClass(type): def __init__(self, name, bases, namespace): super().__init__(name, bases, namespace) print('MyMetaClass instance created') class Sample: pass class Mample(Sample, metaclass=MyMetaClass): pass #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi meta sınıflar hangi tüdendir? Örneğin: class MyMetaClass(type): pass class Sample(metaclass=MyMetaClass): pass s = Sample() Burada s Sample sınıfı türündendir. Sample MyMetaClass türündendir. Pekyi MyMetaClass hangi türdendir? MyMetaClass default olarak type türündendir. Tabii aslında o da bşka meta sınıf türünden olabilirdir. Burada yorumlayıcı MyMetaClass tanımlamasını gördüğünde type sınıfı türünden bir nesne yaratmaktadır. Bu yaratım sırasında da yine sınıfın __new__ ve __init__ metotları devreye girecektir. Sınra yorumlayıcı Sample sınıfını gördiğinde bu kez MyMetaClass türünden bir nesne yaratmaktadır. Yorumlayıcı s = Sample() biçiminde Sample sınıfı türünden bir nesne yaratılmak istendiğinde bu kez MyMetaClass sınıfının __call__ metodunu çağıracaktır. MyMetaClass sınıfı type sınıfından türetildiği için nesnenin yaratılması sırasında yine Sample sınıfının __new__ ve __init__ metotları çağrılacaktır. Özetle türler şöyledir: MyMetaClass -----> type Sample -----> MyMetaClass s ----> Sample #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 77. Ders 13.10.2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi meta sınıflar neden kullanılır? Yani biz neden bir sınıfın type sınıfından değil de kendi sınıfımızdan oluşturulmasını isteriz? İşte aslında meta sınıflar Python'da nadiren programcılar tarafından kullanılmaktadır. Bunlar genellikle bazı framework'leri geliştirenler" tarafından ileri amaçlarla kullanılırlar. Örneğin yorumlayıcı bir sınıfı yaratırken meta sınıflar sayesinde biz araya girip o sınıfa bazı elemanlar ekleyebiliriz. Tabii yorumlayıcı da meta sınıf nesnesini aslında type sınıfı türünden yaratmaktadır.  Yani bizim meta sınıfımız da aslında type türündendir. O halde meta sınıf nesnesi yaratılırken meta sınıf olarak belirttiğimiz sınıfın __new__ ve __init__ metotları çağrılacaktır. Biz de bu metotlarda sınıfa birtakım elemanlar yerleştirebiliriz. Aşağıdaki örnekte meta sınıfın __init__ metodunda yeni yaratılan sınıf nesnesi için x ve foo isimli iki öznitelik yerleştirilmiştir. Burada foo fonksiyonu bir metot gibi işlev görecektir. #------------------------------------------------------------------------------------------------------------------------------------ class MyMetaClass(type): def __init__(self, name, bases, namespace): super().__init__(name, bases, namespace) self.foo = foo self.x = 123 def foo(self): print('foo') class Sample(metaclass=MyMetaClass): pass s = Sample() s.foo() #------------------------------------------------------------------------------------------------------------------------------------ Aslında yukarıdaki gibi araya girerek sınıfa eleman ekleme biçimindeki uygulamalar sınıf dekoratörleriyle de belli biçimlerde yapılabilir. Ancak sınıf dekoratörlerinin çeşitli kısıtları vardır. Aşağıdaki örnekte bir sınıf dekoratörü yoluyla yukarıdaki gibi sınıfa yine foo ve x elemanları eklenmiştir. foo bir metot gibi kullanılabilir. #------------------------------------------------------------------------------------------------------------------------------------ def foo(self): print('Ok') def SampleDecorator(cls): cls.x = 10 cls.foo = foo return cls @SampleDecorator class Sample: pass s = Sample() print(Sample.x) s.foo() #------------------------------------------------------------------------------------------------------------------------------------ Sınıf yaratımında araya girmek için meta sınıflar ve dekoratörler benzer amaçlarla kullanılabiliyor olsa da meta sınıflar daha kapsamlı olanaklara sahiptir. Örneğin biz yazdığımız sınıfı print ettiğimizde istediğimiz bir yazının çıkmasını isteyelim. Bu işlemi aşağıdaki gibi yapamayız: def foo(self): print('Ok') def SampleDecorator(cls): cls.__str__ = lambda self: 'this is a test' return cls @SampleDecorator class Sample: pass print(Sample) s = Sample() # print(s) # this is a test Burada biz sınıfa yerleştirmiş olduğumuz __str__ metodu sınıfın kendisini yazdırılırken devreye girmemektedir. O sınıf türünden bir değişken yazdırılırken devreye girmektedir. Çünkü burada biz yarattığımız bir meta sınıf türünden nesnenin içerisine bu metodu yerleştirmiş olduk. Halbuki bizim bunu yapabilmemiz için bizzat meta sınıfın içerisine bunu yerleştirmemiz gerekir. Bunun için bizim __str__ metodunu meta sınıfın içerisine yazmamız gerekir. Örneğin: class MyMetaClass(type): def __str__(self): return 'this is a test' class Sample(metaclass=MyMetaClass): pass print(Sample) # this is atest Burada Sample değişkeni artık type türünden değil MyMetaClass türündendir. Dolayısıyla artık Sample değişkeni print edildiğinde bu __str__ çağrılacaktır. #------------------------------------------------------------------------------------------------------------------------------------ class MyMetaClass(type): def __str__(self): return 'this is a test' class Sample(metaclass=MyMetaClass): pass print(Sample) #------------------------------------------------------------------------------------------------------------------------------------ Python'ın 3'lü versiyonlarıyla birlikte "tür açıklamaları (type annotations)" konusu çeşitli ayrıntılarla dile eklenmiştir. Ancak bu ekleme versiyondan versiyona genişletilerek bugünkü son haline getirilmiştir. Dolayısıyla biz güncel son durumdaki tür açıklamaları üzerinde duracağız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python dinamik tür sistemine sahip olduğu için değişkenlerin, fonksiyon parametrelerinin, fonksiyonların geri dönüş değerlerinin türleri değişebilmektedir. Dinamik tür sistemine sahip programlama dillerinde en önemli sorunlardan biri tür kontrolünün çalışma zamanı sırasında yapılmasıdır. Örneğin bir fonksiyon yanlış türden bir argümanla çağrıldığında problem kod çalışırken akış o noktaya geldiğinde ortaya çıkmaktadır. Bu da kodu yazanın çok dikkatli olmasını gerektirmektedir. İşte tür açıklamaları bir değişkenin niyet edilen türünün program çalışmadan önce üçüncü parti araçlar tarafından kontrol edilmesini sağlamak amacıyla dile eklenmiştir. Tür açıklamaları yorumlayıcı için bir direktif ya da kontrol sağlamamaktadır. Yalnızca insanlar ve üçüncü parti statik analiz araçları için kontrol imkanları sunmaktadır. Başka bir deyişle tür açıklamaları tamamen yorumlayıcı tarafından görmezden gelinmektedir. Aşağıdaki banner fonksiyonuna dikkat ediniz: def banner(s, ch='-'): print(ch * len(s)) print(s) print(ch * len(s)) Bu fonksiyonun bir string ile çağrılması gerekir. Eğer bu fonksiyon bir string ile çağrılmazsa muhtemelen bir exception oluşacaktır. Pekiyi dalgın bir programcı bu fonksiyonu aşağıdaki gibi int bir değerle çağıramaz mı? banner(123) İşte bu durumda yukarıda da belirttiğimiz gibi kod çalışırken exception oluşacaktır (çünkü int türü len fonksiyonuna sokulamaz). Eğer biz yukarıdaki fonksiyonu örneğin bir listeyle çağırırsak exception oluşmaz ancak fonksiyon istediğimizi de yapmaz. Aslında bu durum exception oluşmasından da kötüdür. Pekiyi biz bir Python programında yukarıdaki gibi hataları nasıl tespit edebiliriz? Bu tür hatalar yazılımın iyi bir biçimde test edilmesiyle büyük ölçüde düzeltilebilmektedir. Sonraki konuda göreceğimiz "birim testleri (unit testing)" test süreci bunun için kullanılabilir. İşte tür uyumluluğu üçüncü parti statik analiz araçları tarafından da belirli koşullar sağlanırsa tür açıklamaları sayesinde kontrol edilebilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tür kontrolü için kullanılan statik analiz araçlarının en yaygınları şunlardır: mypy, Pytype, Pyright, Pyre. Biz kursumuzda mypy kullanacağız. mypy programını şöyle kurabilirsiniz: pip install mypy Bu analiz araçlarının tür kontrollerini yapabilmesi için kodun "tür açıklamaları (type annotations)" ile oluşturulmuş olması gerekir. Bu nedennle bizim tür açıklamalarının nasıl oluşturulacağını bilmemiz gerekmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tür açıklamalarının genel biçimi şöyledir: : [= ] Aslında tür açıklamaları daha genel olarak düşünülmüştür. Yukarıdaki genel biçimde "ifade" yerine tür bilgisi yazılırsa (int, str, float gibi tür isimleri Python'da birer ifadedir) tür açıklaması yapılmış olur. Aslında bu açıklamalar tür açıklaması biçiminde olmak zorunda değildir. Ancak pratikte açıklamaların (annotations) en yaygın kullanımı tür açıklamaları biçimindedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Global ya da yerel değişkenlere tür açıklaması yazılırken onun ilkdeğer verilerek yaratılması zorunlu değildir. Örneğin: x: int x = 10 print(x) x = 2.3 print(x) Burada x değişkenin int türden olduğu belirtilmiştir. Biz bir değişkeni tür açıklamasıyla aşağıdaki gibi belirtmiş olalım: a: int Burada biz bu a değişkenini yaratmış değiliz. Yani a değişkenini henüz kullanamayız. Ancak Python yorumlayıcısı bunun bir tür açıklaması olduğunu anlar ve bunun için herhangi bir error mesajı vermez. Ancak bir değişkeni yaratmadan aşağıdaki gibi bir kullanım geçerli değildi: a Python'da bu tür etkisiz kodların oluşturulmasının yasak olmadığını anımsayınız. Ancak buradaki sorun a'nın yaratılmamış olmasıdır. Yukarıda da belirttiğimiz gibi bir program çalıştırılırken Python yorumlayıcısı tür açıklamalarını dikkate almaz. Bu tür açıklamaları üçüncü parti programlar tarafından (örneğin mypy) dikkate alınmaktadır. Yukarıdaki "sample.py" dosyasını mypy ile şöyle işleme sokabiliriz: mypy sample.py Burada mypy şöyle bir çıktı oluşturmuştur: C:\Study\Python>mypy sample.py sample.py:6: error: Incompatible types in assignment (expression has type "float", variable has type "int") Found 1 error in 1 file (checked 1 source file) Tabii biz tür açıklaması yaparken oma değer atayarak değişkeni yaratabiliriz. Örneğin: x: int = 10 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tür açıklamaları IDE'lere entegre edilmiş araçlar tarafından da dikkate alınabilmektedir. Örneğin PyCharm IDE'sinde "Code/Inspect Code" menüsü ile tür kontrolü yapılabilir. Mypy aynı zamanda PyCharm IDE'sine bir plugin olarak eklenebilmektedir. Ancak henüz Spyder IDE'sine entegre edilmiş bir araç yoktur. Visual Studio Code IDE'sine de bir plugin olarak mypy eklenebilmektedir. mypy programı başarısız olursa sıfırı dışında bir exit kodu üretmektedir. Başarı durumunda mypy programının exit kodu 0'dır. Örneğin: C:\Study\Python>mypy sample.py sample.py:3: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] Found 1 error in 1 file (checked 1 source file) C:\Study\Python>echo %errorlevel% 1 Ancak örneğin: C:\Study\Python>mypy sample.py Success: no issues found in 1 source file :\Study\Python>echo %errorlevel% 0 Nöylece build araçları ya da programcının oluşturduğu make dosyaları önce mypy programını çalıştırıp eğer bir hata varsa programı hiç çalıştırmayabilirler. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Parametre değişkenlerine tür açıklaması yapılırken aynı sentaks kullan kullmaktadır. Örneğin: def banner(s: str, ch: str = '-'): print(ch * len(s)) print(s) print(ch * len(s)) banner('ankara') banner(123) Bu programı mypy programına sokalım: C:\Study\Python>mypy sample.py sample.py:7: error: Argument 1 to "banner" has incompatible type "int"; expected "str" Found 1 error in 1 file (checked 1 source file) Görüldüğü gibi mypy fonksiyonun yanlışlıkla int bir değerle çağrıldığını tespit edip bize bildirebilmiştir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 78. Ders 14/10/2023 – Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Fonksiyonun geri dönüş değeri hakkında açıklama oluşturmak için -> sembolü kullanılmaktadır. Örneğin: def square(a: int) -> int: return a * a Burada biz fonksiyonun parametresinin int türden geri dönüş değerinin de int türden olması gerektiğini belirtiyoruz. Şimdi "sample.py" programının aşağıdaki gibi olduğunu kabul edelim: def square(a: int) -> int: return a * a result = square(1.2) print(result) Programı mypy'a sokalım: C:\Study\Python>mypy sample.py sample.py:4: error: Argument 1 to "square" has incompatible type "float"; expected "int" Found 1 error in 1 file (checked 1 source file) Şimdi program şöyle olsun: def square(a: int) -> int: return a * a result: str result = square(1) print(result) Buradaki hata square'in geri dönüş değrinin str türünden bir değişkene atanmasıdır. Bu kodu aşağıdaki gibi mypy'a sokalım: C:\Study\Python>mypy sample.py sample.py:6: error: Incompatible types in assignment (expression has type "int", variable has type "str") Found 1 error in 1 file (checked 1 source file) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Biz bir değişkenin kendi sınıfımı türünden olmasını sağlayabiliriz. Örneğin: class Sample: pass def foo(a: Sample): print(a) s = Sample() foo(s) Burada foo fonksiyonu Sample sınıfı türünden parametre almaktadır. Biz onu başka türden başka türden bir argümanla çağırırsak mypy hata verecektir. Tabii türemiş sınıf taban sınıf gibi de kullanılabildiği için biz buradaki foo fonksiyonuna Sample sınıfından türetilmiş bir sınıf türünden swğişkwn sw gwçwbiliriz. Örneğin: class Sample: pass class Mample(Sample): pass def foo(a: Sample): print(a) m = Mample() foo(m) Burada mypy herhangi bir hata mesajı vermeyecektir. #------------------------------------------------------------------------------------------------------------------------------------ class Sample: pass class Mample(Sample): pass def foo(a: Sample): print(a) m = Mample() foo(m) #------------------------------------------------------------------------------------------------------------------------------------ Bir değişken liste türünden olması gerekiyorsa list biçiminde tür açıklaması yapılabilir. Örneğin: def foo(a: list): pass Burada a parametre değişkeni elemanları herhangi bir biçimde olan bir list nesnesini alabilir. Ancak istersek listenin elemanlarının belli bir türdne olmasını da sağlayabiliriz. Örneğin: a: list[int] Burada a int eşemanlardan oluşan bir liste olmalıdır. Örneğin: a = [1, 2, 'ali'] Böyle bir atama mypy tarafından hata olarak değerlendirilecektir. Mypy gibi araçların "statik kontrol araçları" olduğuna dikkat ediniz. Bu tür statik kod analizi yapan araçlar programı çalıştırarak bir kontrol yapamadığı için her türlü ihlali kontrol edememektedir. Örneğin: def foo(x): x.append(1.2) a: list[int] a = [1, 2, 3, 4] foo(a) print(a) Burada foo fonksiyonunun a listesine ekleme yaptığını dolayısıyla kuralın ihlal edildiğini mypy gibi statik analiz araçları genellikle tespit edememektedir. Örneğin: def foo(a: list[int]): pass Burada foo parametre olarak int değerlerden oluşan bir liste almak zorundadır. Örneğin "sample.py" programı şöyle olsun: def foo(a: list[int]): pass foo([10, 20]) foo([10, 20.1]) Burada ilk foo çağrısı geçerli olduğu halde ikinci foo çağrısı geçersizdir. Programı mypy'a sokalım: C:\Study\Python>mypy sample.py sample.py:6: error: List item 1 has incompatible type "float"; expected "int" Found 1 error in 1 file (checked 1 source file) list, tuple, dict gibi türlerin köşeli parantezlerle tür açıklamalarında kullanılabilmesi Python 3.9 ile eklenmiştir. Python 8.8 ve öncesinde tür açıklamaları için typing modülü içerisiside List isimli bir sınıf bulunuyordu. Bu sınıf liste işlemlerini yapmaz yalnızca tür açıklamaları için bulundurulmuştur. Örneğin: from typing import List def foo(a: List[int]): pass foo([10, 20]) Artık programcıların tür açıklamaları için List sınıfı yerine doğrudan list sınıfını kullanması tavsiye edilmektedir. Örneğin: def foo(a: list[int]): pass foo([10, 20]) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Benzer biçimde set ve tuple sınıfları da tür açıklamaları için kullanılabilmektedir. Örneğin: def foo(s: set): pass Burada s set türünden olmalıdır. Örneğin: def bar(t: tuple): pass Burada da t tuple türünden olmalıdır. Tabii tıpkı list örneğinde olduğu gibi aslında biz bu set ve tuple türlerinin elemanları hakkında da açıklama yapabiliriz. Örneğin: def foo(s: set[int]): pass Burada s int değerleri tutan bir küme olmalıdır. Demetlerde elemanların türleri sırasıyla tek tek belirtilebilmektedir. Örneğin: def foo(t: tuple[int, str]): pass Burada t parametre değişkenine iki elemanlı demetler atanmalıdır. Bu demetlerin birinci elemanları int türden ikinci elemanları str türünden olmalıdır. Örneğin: foo((10, 'ankara')) Şimdi fonksiyunu şöyle çağıralım: foo((10, 20)) Mypy şöyle bir hata verecektir: C:\Study\Python> mymy sample.py sample.py:4: error: Argument 1 to "foo" has incompatible type "tuple[int, int]"; expected "tuple[int, str]" [arg-type] Found 1 error in 1 file (checked 1 source file) set ve tuple türlerinin bu biçimde doğrudan kullanılması Python 3.9 ile birlikte mümkün hale getirilmiştir. Python 3.8 ve aşağısında bunların yerine typing modülündeki Set ve Tuple sınıfları kullanılıyordu. Bu sınıflar gerçek set ve tuple sınıfları değildir. Yalnızca tür açıklamaları için bulundurulmuş olan sınıflardır. Ancak yukarıda da belirttiğimiz gibi Python 3.9 ile artık bu sınıfların kullanılmasına gerek kalmamıştır. Örneğin: from typing import Set, Tuple def foo(s: Set[int]): pass def bar(t: Tuple[int, str]): pass foo({1, 2, 3}) bar((10, 'ankara')) Örneğin: from typing import Set, Tuple def foo(s: Set[int]): pass def bar(t: Tuple[int, str]): pass s: Set[int] = {1, 2, 3} t: Tuple[int, str] = 10, 'ankara' foo(s) bar(t) Demetler için şöyle bir sentaks da eklenmiştir. Eğer demetleri belirtirken yalnızca tek tür belirtirsek ve sonra da ... (ellipsis) getirirsek bu durum ilk belirttiğimiz türden olmak koşulu ile demetin istenildiği sayıda elemandan oluşabileceği anlamına gelir. Örneğin: def foo(t: tuple[int, ...]): pass Burada aşağıdaki çağrılar tür açıklamalarına uygundur: foo((10, 20, 30)) foo((10, 20)) Ancak aşağıdaki çağrımlar tür açıklamalarına uygun değildir: foo((10.2, 20, 'ali')) foo(('veli', 'selami')) Bu tür durumlarda boş demetler de soruna yol açmamaktadır Köşeli parantez içerisinde birden fazla türden sonra ... kullanımının böyle bir anlamı yoktur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Sözlüklerde de tür açıklamaları benzer biçimde dict sınıfı ile yapılabilmektedir. Örneğin: def foo(d: dict): pass Bu durumda fonksiyonun d parametre değişkenine bir sözlük geçirilmelidir. Ancak sitenirse yine köşeli parantezler içerisinde sözlüğün anahtar ve değer türleri ayrı ayrı belirtilebilir. Örneğin: def foo(d: dict[int, str]): pass Burada sözlüğün anahtarları int, değerleri ise str türünden olmalıdır. Aşağıdkai çağırda mypy bir hata rapor etmeyecektir: d = {10: 'ali', 20: 'veli', 30: 'selami'} foo(d) Python 3.8 ve öncesinde bu işlem typing modülü içerisindeki Dict sınıfı ile yapılıyordu. Örneğin: from typing import Dict def foo(d: Dict[int, str]): pass d = {10: 'ali', 20: 'veli', 30: 'selami'} foo(d) Ancak artık bu Dict sınıfına gerek kalmamıştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ typing modülündeki Any sınıfı tür açıklamalarında "herhangi bir tür olabilir" anlamına gelmektedir. Any kullanmak bazı durumlarda gereksidir. Örneğin: def foo(a: Any): pass Biz zaten burada tür açıklaması yapmasaydık da mypy tarafından foo herhangi bir tür olarak kabul edecekti. Yani aşağıdaki fonksiyon yukarıdakiyle eşdeğerdir: def foo(a): pass Örneğin: def bar(a: list[Any]): pass Burada da aslında yalnızca list biçiminde tür açıklaması yapsaydık da değişen bir şey olmayacaktı: def bar(a: list): pass Ancak bazı durumlarda Any gerçekten gerekebilmektedir. Örneğin: from typing import Any def foo(d: dict[int, Any]): pass Burada artık foo fonksiyonun parametresi sözlük olmalıdır. Ancak sözlüğün anahtarları int olmak zorundayken değeri herhangi bir türden olabilir. Örneğin aşağıdaki çağrı tür açıklamalarına uygundur: foo({10: 'ali', 20: 100, 30: 2.3}) Örneğin: def foo(t: tuple[int, Any, float]): pass Burada parametre değişkeni olan t için bizim üç elemanlı bir demet geçirmemiz gerekir. Bu demetin ilk elemanı int türden, üçüncü elemanı float türden ancak ikinci elemanı herhangi bir türden olabilir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir fonksiyonun geri dönüş değerinin olmadığı ya da None olduğunu belirtmek için tür açıklamalarında None kullanılmalıdır. Örneğin: def foo() -> None: pass Biz burada foo fonksiyonunu geri dönüş değeri varmış gibi kullanırsak mypy bu durumu error ile rapor edecektir. Örneğin: result = foo() Burada mypy şöyle bir error rapor eder: C:\Study\Python> mymy sample.py sample.py:4: error: "foo" does not return a value Found 1 error in 1 file (checked 1 source file) #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ typing modülündeki Optional ilgili türden ya da None (NoneType türünden) anlamına gelmektedir. Örneğin: from typing import Optional a: Optional[int] Burada biz a'ya int ya None atayabiliriz. Ancak başka bir türden değer atayamayız. Örneğin: from typing import Optional def foo(a: int) -> Optional[int]: if a > 0: return a return None result: Optional[int] = foo(10) print(result) Aşağıda ikinci derece denklemin köklerini bulan bir fonksiyon tür açıklamaları kullanılarak yazılmıştır. #------------------------------------------------------------------------------------------------------------------------------------ import math from typing import Optional def get_roots(a: float, b: float, c: float) -> Optional[tuple[float, float]]: delta: float = b ** 2 - 4 * a * c if delta < 0: return None x1 = (-b + math.sqrt(delta)) / (2 * a) x2 = (-b - math.sqrt(delta)) / (2 * a) return x1, x2 result: Optional[tuple[float, float]] = get_roots(1, 0, -4) if result: print(result[0], result[1]) else: print('Kök yok') #------------------------------------------------------------------------------------------------------------------------------------ 79. Ders 21/10/2023 – Cumartesi #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir değişkene bir fonksiyon gibi çağrılabilecek (callable) bir değişken atanacaksa tür açıklamasında typing modülündeki Callable sınıfı kullanılmaktadır. Örneğin: from typing import Callable def foo(): print('foo') class Sample: def __call__(self): print('Sample.__call__') f: Callable f = foo # haata yok, f'ye çağrılabilir (callable) bir nesne atanmış f() s = Sample() f = s # hata yok, s çağrılabilir bir nesne f() Örneğin: from typing import Callable def square(a: int) -> int: return a * a def foo(f: Callable, val: int): result = f(val) print(result) foo(square, 10) Burada bar fonksiyonun parametresi bir fonksiyon nesnesi (callable herhangi bir nesne) olabilir. Ancak istenirse değişkene atanacak çağrılabilen nesnenin parametrik yapısı ve geri dönüş değeri için de tür açıklaması yapılabilmektedir. Bu Callable sınıfına köşeli parantezleri içerisinde "bir liste biçiminde parametre türleri, sonra da geri dönüş değerinin türü" belirtilir. Örneğin: from typing import Callable def foo(a: int, b: str) -> int: return a + int(b) f: Callable[[int, str], int] Burada f değişkenine parametreleri sırasıyla int ve str olan geri dönüş değeri ise int olan çağrılabilen nesneler atanabilir. Eğer buna uygun atama yapılmazsa mypy hata verecektir. Örneğin: f = foo result: int = f(10, 20) print(result) Burada tür açıklamasına uyulmamıştır. mypy şöyle bir hata rapor etmektedir: sample.py:22: error: Argument 2 to "foo" has incompatible type "int"; expected "str" [arg-type] Found 1 error in 1 file (checked 1 source file) Örneğin: def square(a: int) -> int: return a * a def foo(f: Callable[[int], int], val: int): result = f(val) print(result) foo(square, 10) Burada foo fonksiyonunun birinci parametresi "pareametresi int, geri dönüş değeri int olan" bir fonksiyon nesnesi almaktadır. Örneğimizde bu tür açıklamasına uyulduğuna dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ typing modülü içerisindeki Union isimli sınıf birden fazla türü belirtmek için kullanılmaktadır. Örneğin: a: Union[int, str] Burada a'ya int ya da str türündne nesneler atanabilir. Yani a bu iki türden değeri de kabul etmektedir. Örneğin: from typing import Union a : Union[int, str] a = 10 print(a) a = 'ali' print(a) Burada a'ya yapılan atamalar tür açıklamasıyla uyumludur. Ancak örneğin: a = 2.3 print(a) Burada a'ya yapılan atama tür açıklamasıyla uyumlu değildir. Örneğin: from typing import Union def foo(a: Union[int, str]): pass Burada aşağıdaki iki çağrı tür açıklamalarıyla uyumludur. foo(10) foo('ali') Ancak aşağıdaki çağrı tür açıklamalarıyla uyumsuzdur: foo(1.2) Python 3.10 ile birlikte Union işlemi '|' operatörü ile de yapılır hale getirilmiştir. Örneğin: def foo(a: int|str): pass Bu açıklama aşağıdakiyle tamamen eşdeğerdir: def foo(a: Union[int, str]): pass #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bazen bir değişkene ilişkin tür açıklamasını başka bir tür açıklaması ile değiştirmek isteyebiliriz. Aslında ikinci kez tür açıklaması yapmak Python yorumlayıcısı tarafından geçerlidir. Örneğin: a: int # .... a: float # ... Ancak mypy bu durumu eror olarak değerlendirir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ typing modülü içerisindeki cast fonksiyonu atanan değişken üzerinde tür açıklaması yapılmasına olanak sağlayan bir fonksiyondur. cast fonksiyonu birinci parametre olarak bir tür açıklamasını, ikinci parametre olarak bir değeri almaktadır. Bu değer açıklanmış bir değişken de olabilir. Biz cast fonksiyonu sayesinde bir değişkene hem bir açıklama yapıp hem de bir değer arayabiliriz. Örneğin: a: float = 12.2 b = cast(int, a) Örneğimizde b değişkeni artık int olarak açıklanmış durumdadır. Burada önemli bir nokta cast fonksiyonun bir açıklama amacıyla kullanılmasıdır. cast fonksiyonu bir dönüştürme yapmaz. Örneğimizd eher ne kadar b int olarak açıklanmışsa da içerisinde yine 12.2 değeri bulunacaktır. Yani biz burada b'ye hem bir float değer atamış olduk hem de a'yı int olarak açıklamış olduk. Yorumlayıcı cast işleminde dönüştürme yapmamaktadır. . Örneğin: from typing import cast a: float = 12.2 b = cast(str, a) print(b, type(a)) # 12.2 Burada b str olarak açıklanmıştır ancak b içerisine float bir değer atanmıştır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir değişkenin dolaşılabilir bir tür olduğu typing modülündeki Itearable sınıfı ile açıklanmaktadır. Örneğin: from typing import Iterable def foo(a: Iterable): pass Aşağıdaki çağrılar argüman olarak dolaşılabilir nesneler kullanıldığı için tür açıklamasına uygundur. Örneğin: foo(range(10)) foo([1, 2, 3, 4, 5]) foo('ankara') Örneğin: def gen(): for i in range(10): yield i foo(gen()) Üretici fonksiyonlar da dolaşılabilir nesneler olduğu için yukarıdaki tür açıklamasına uygundur. Ancak aşağıdaki çağrılarda argümanlar dolaşılabilir nesne belirtmediği için tür açıklamasına uygun değildir: foo(10) class Sample: pass s = Sample() foo(s) Iterable sınıfı da köşeli parantezler içerisinde tür alabilmektedir. Örneğin Iterable[int] biçiminde bir açıklama değişkenin dolaşılabilir nesne alacağını ancak bu nesne dolaşıldıkça int nesnelerin elde edileceğini belirtmektedir. Örneğin: def foo(a: Iterable[int]): pass Burada aşağıdaki gibi bir çağrı tür açıklamasına uygundur: foo([1, 2, 3, 4, 5]) Çünkü fonksiyona geçirilen dolaşılabilir nesnenin elemanları int türdendir. Ancak aşağıdaki bir çağrı tür açıklamasına uygun değildir: foo([1, 2, 3., 4, 5.0]) Örneğin: a: Iterable[int|str] Burada a değişkenine elemanları int ya da float olabilen dolaşılabilir nesneler atanabilir. Dolayısıyla aşağıdaki atama tür açıklamasına uygundur: a = [1, 2, 3, 4, 'ankara', 'izmit'] Örneğin: a: Iterable[tuple[int, str]] Burada a değişkenine demetlern oluşan dolaşılabilir bir nesne atanabilir. Ancak bu demetlerin de ilk elemanları int ikinci elemanları str türünden olmak zorundadır. Örneğin aşağıdaki atama tür açıklamasına uygundur: a = [(6, 'Anakara'), (26, 'Eskişehir'), (35, 'İzmir')] #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Tür açıklamaları iç içe yapıldığı zaman biraz karmaşık bir görüntü oluşturabilmektedir. Bu biçimdeki tür açıklamalarını oluştururken parantezlere dikkat ediniz. Örneğin: from typing import Callable, Iterable def foo(a: Iterable[tuple[Callable[[int, int], int], float]]): pass Burada a değişkeninin parametresi dolaşılabilir bir nesne olmalıdır. Bu dolaşılabilir nesne bize iki elemanlı demet vermelidir. Demetin birinci elemanı parametreleri int, int olan, geri dönüş değeri nit olan bir çağrılabilir nesneden ikinci elemanı ise float bir nesneden oluşmalıdır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ typing modülü içerisindeki Sequence sınıfı da string gibi liste gibi __getitem__, __len__ metotları bulunan "reversible" "seqeunce" türlerini belirtmek için kullanılmaktadır. Anımsanacağı gibi tipi tipik "seuqence" türleri "list, range, tuple, str" türleridir. dict türünün __getitem__ metodu olsa da dict türü bir "seauence türü değildir. Örneğin: from typing import Sequence def foo(a: Sequence[int]): pass Burada biz foo fonksiyonunu bir listeyle, bir demetle, string'le ya da bir range nesnesiyle çağırabiliriz. Bu durumda tür açıklamasına uygun çağrılar oluşturulmuş oluruz. Örneğin: foo([1, 2, 3, 4, 5]) foo('ali') foo(range(10)) Ancak örneğin biz buradaki foo fonksiyonunu bir kümeyle ya da sözlükle çarırsak bu durum tür açıklamasıyla uyumlu olmaz: foo({1, 2, 3, 4, 5}) foo({1: 'ali', 2: 'veli'}) Squence sınıfı için köşeli parantezler içerisinde tür de belirtilebilir. Örneğin: def foo(a: Sequence[int]): pass Burada a parametre değişkenine biz int elemanlardna oluşan bir "sequence türü" geçirebiliriz. Örneğin: foo([1, 2, 3, 4, 5]) foo(range(100)) Çağrıları tür açıklamasına uygundur. Ancak örneğin: foo(['ali', 'veli', 'selami']) çağrısı tür açıklamasına uygun değildir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 80. Ders 27/10/2023 – Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Yazılımda test süreçleri ürün geliştirmenin önemli bir aşamasını oluşturmaktadır. Programcılar kodlama yaparken böcekler oluşturabilir. Bu böcekler de nihai ürünün kimi zaman yanlış çalışmasına yol açabilir. Bozuk yazılımların kullanıcılara dağıtılmasının önemli zararları vardır. Aslında bu süreç imalat sektöründeki sürece benzemektedir. Bozuk bir ürün kullanıcıya satıldıktan sonra mağduriyetin giderilmesi uzun bir zaman alabilmektedir. Bunun için servis oluşturulmakta, bozuk ürünler maaşlı çalışan kişiler tarafından test edilmekte, duruma göre ürün tamir edilmekte ya da yenisi ile değiştirilmektedir. Bu tür durumlardaki en önemli sorunlardan biri müşteri memnuniyetinin önemli ölçüde zarar görmesidir. Bazı yazılımlarda yazılımın doğru çalışması çok daha önemlidir. Yani bazı yazılımlarda "hata toleransı" çok düşük olabilir. Araştırmalar programcının yaptığı hataların en az %5-%7'sinin tüm test süreçlerini geçerek nihai ürüne yansıdığını göstermektedir. Yine araştırmalar bozuk bir yazılımın müşterinin eline geçtikten sonra düzeltilmesinin çok maliyetli olduğunu göstermektedir. Eskiden yazılımda test süreçleri bir "lüks olarak" değerlendiriliyordu. Bu nedenle yalnızca büyük firmalar test departmanları bulunduruyordu. Ancak günümüzde artık yazılımda kalite bilinci çok daha fazla artmış ve test süreçleri çok daha bilinir hale gelmiştir. Yazılımda test süreçleri için çeşitli stratejiler kullanılabilmektedir. Ancak test işlemi en alt düzeyde programcının kendi yazdığı kodları test etmesi ile başlar. Bu sürece "birim testleri (unit testing)" denilmektedir. Yani örneğin programcı bir fonksiyon yazmış olsun. Bu fonksiyon doğru çalışmakta mıdır? İşte buna yönelik yapılan testlere "birim testleri (unit testing)" denilmektedir. Yazılım parçaları bir araya getirilir. Bu bir araya getirilme işlemi sonucunda genellikle bu parçalar yeniden test edilir. Buna da "entegrasyon testi (integration testing)" denilmektedir. Yazılımın önemli parçalarına modül de denilmektedir. Modüller de ayrı ayrı test edilebilmektedir. Buna da "modül testleri" denmektedir. Nihayet tüm ürün oluşturulur ve ürün bir bütün olarak test edilir. Buna da genellikle "kabul testleri (acceptance testing)" denir. Ürün bütün olarak önce kurumun test departmanı tarafından test edilir. Genellikle test departmanı tarafından yapılan bu testlere "alfa testi (alpha testing)" denmektedir. Sonra ürün seçilmiş bazı son kullanıcılara dağıtılır ve gerçek hayat testine sokulur. Buna da "beta testi (betta testing)" denilmektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi yazılımlar nasıl test edilmektedir? Manuel yöntem çoğu kez yetersiz bir yöntemdir. Yazılımda manuel test yöntemi kullanılıyor olsa da asıl testler "test işlemini yapan kodlarla" yapılmaktadır. Yani yazılımlar çeşitli aşamalarda oluşturulan ortamlarda özel yazılmış kodlarla test edilmektedir. Bazı yazılımlar normal koşullarda sorunsuz çalışırken özel koşullarda ya da uç noktalarda bozukluklar ortaya çıkartabilmektedir. Yazılımın zor koşullar oluşturularak test edilmesine "stres testleri (stress testing)" denilmektedir. Bir programcı için en önemli test aşaması "birim testleridir". Çünkü programcının kendi yazdığı kodların programcı tarafından daha birinci elden test edilmesi en önemli aşamalardan bir tanesidir. Yukarıda da belirtildiği gibi aslında nihai ürünün bozuk olmasının en önemli sebeplerinden biri programcının hatalı kodlama yapmasıdır. Biz kursumuzda "birim test süreci için" assert deyimi ve unittest modülünün kullanımları üzerinde duracağız. Diğer test süreçleri özel bir konudur ve kursumuzun kapsamı dışındadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ assert İngilizce bu bağlamda "iddia etmek", "ileri sürmek" gibi bir anlama gelmektedir. assert deyiminin genel biçimi şöyledir: assert [, ] assert anahtar sözcüğünün yanında doğru ya da yanlış olabilen bir ifade bulunması gerekir. Bu ifade bir iddia belirtmektedir. Eğer iddia doğruysa akış normal olarak devam eder. Ancak iddia yanlışsa "AssertionError" isimli bir exception oluşur. Örneğin: def foo(a): assert a > 0 print(a) Burada foo fonksiyonunun içerisinde "parametrenin sıfırdan büyük olduğu iddiası" vardır. Eğer bu iddia doğru ise bir sorun oluşmaz akış devam eder. Eğer iddia yanlış ise AssertionError isimli bir exception oluşacaktır. Örneğin: foo(10) # iddia doğru, bir şey olmaz foo(-10) # iddia geçersiz! exception oluşur Pekiyi assert gibi bir deyime gerçekten gereksinim var mıdır? Biz aynı etkiyi kendimiz if deyimiyle oluşturamaz mıydık? def foo(a): if not a > 0: raise AssertionError print(a) İşte buradaki kod yukarıdaki ile eş anlamlı gibi gözükse de aslında başka önemli bir nokta daha vardır. Bir Python programı çalıştırılırken eğer "-O seçeneği" kullanılırsa program içerisindeki tüm assert deyimleri yorumlayıcı tarafından sanki yokmuş gibi koddan kaldırılmaktadır. Başka bir deyişle -O ile Python programını çalıştırdığımızda assert deyimleri görmezden gelinmektedir. Böylece biz performans gerekçesiyle tüm assert işlemlerinden basit bir biçimde kurtulabiliriz. Eğer aynı kontrolleri if ile yapsaydık -O ile bu if'lerden kurtulamazdık. Örneğin: # sample.py def foo(a): assert a > 0 print(a) foo(-10) Burada normal bir çalışmada AssertionError oluşacaktır. Ancak komut satırından programı aşağıdaki gibi çalıştırdığımızda exception oluşmayacaktır: python -O sample.py Çünkü bu durumda Python yorumlayıcıları assert deyimleri sanki kodda yokmuş gibi kodu çalıştırmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir program için iki versiyonun bulunduğunu varsayabiliriz: Debug ve Release (Production) versiyonları. Programın debug versiyonu programı geliştirirken nihai üründe gerekmeyen birtakım test kontrolleri içeren versiyonlarıdır. Programcı geliştirmesini debug versiyonunda yapmaktadır. Release versiyonu ise hatasız olduğuna inanılan programın gereksiz test kontrollerinden arındırılmış biçimidir. Son kullanıcılar kullanıcılar programın release versiyonunu kullanacaklardır. İşte Python'da assert kontrollerinin koda dahil edildiği versiyon programın debug versiyonu -O seçeneği ile assert kontrollerinin koddan çıkartıldığı versiyon ise release versiyonudur. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Pekiyi assert deyiminin faydası nedir? Programcı kodunu geliştirirken pek çok yere assert deyimi sayesinde sanki gereksiz gerekebilecek iddialar yerleştirir. Eğer bu iddialar doğrulanmazsa programcı birtakım şeylerin ters gittiğini düşünüp yapmış olduğu bug hakkında bir ip ucu elde eder. Eğer geliştirme sırasında hiç AssertionError oluşmazsa bu durumda kodun sağlam olduğunu düşünür ancak bu gereksiz kontrollerden kodunu arındırmak için onu -O seçeneği ile çalıştırır. Yani başka bir deyişle aslında kodu geliştirirken assert deyimlerini kullanır ancak çalıştırırken bu assert deyimlerini -O seneği ile koddan kaldırır. Programcı kodunu geliştirirken saçma gibi gözükse bile pek çok yere assert deyimlerini yereştirir. Böylece eğer testler sırasında AssertionError oluşursa bir "bug" olduğunu anlar ve bir ip ucu elde ederek bug'ı düzeltir. Ancak assert deyimlerini koddan hiç çıkartmaz. Çünkü buna gerek yoktur. Zaten programı çalıştırırken -O seçeneği ile tek hamlede bu gereksiz assert deyimlerinden kullanıcı kurtulmaktadır. Örneğin çerçeve çizen bir fonksiyonu yazıp kodun çeşitli yerlerinde çağıran programcı parametreler üzerinde iddialarda bulunabilir. Böylece kodunun başka yerlerinde bu fonksiyonu yanlış parametrelerle çağırdığını geliştirme aşamasında anlayabilir: def draw_rect(x1, y1, x2, y2): assert x1 < x2 assert y1 < y2 assert x1 >= 0 and x2 >= 0 and y1 >= 0 and y2 >= 0 print('çerçeve çiziliyor') Bu fonksiyonda x1, y1 çerçevenin sol üst köşe koordinatlarını x2, y2 ise sağ alt köşe koordinatlarını belirtmektedir. Eğer biz kodumuzda bu fonksiyonu kullanırken ona yanlışlıkla hatalı argümanlar geçmişsek kodu geliştirirken bunu assert deyimleri sayesinde anlayabiliriz. Ancak bu kontroller sağlam çalışan bir program için gereksizdir. O halde programı kullanıcıya teslim ederken onun programı -O ile çalıştırmasını sağlamalıyız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Yukarıdaki genel biçimde de görüldüğü gibi assert deyiminde iddianın yanına virgül atomu ile başka bir ifade de yerleştirilebilir. Bu ifade genellikle bir string olur. Bu string AssertionError sınıf nesnesine argüman olarak geçirilmektedir. Yani buradaki yazı kod assert işlemine takıldığında ekranda görüntülenecek olan mesajı belirtmektedir. Örneğin: def foo(a): assert a > 0, 'paramter must be positive' print(a) foo(-10) Buarada kod çalıştırıldığında aşağıdaki gibi bir exception mesajı verilecektir: AssertionError: paramter must be positive #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Yukarıda da belirttiğimiz gibi assert deyimi programın debug versiyonunda devreye giren ancak release versiyonunda devreye girmeyen kontroller oluşturmaktadır. Eğer programcı her zaman kontrolün çalıştırılmasını istiyorsa bunu assert deyimi ile değil manuel biçimde if deyimleriyle yapmalıdır. Ayrıca Python'da assert deyimlerinden kurtulmak için -O seçeneğinin yanı sıra PYTHONOPTIMIZE isimli bir çevre değişkeni de kullanılabilmektedir. Bu çevre değişkenine herhangi bir yazı kaşı getirilirse artık Python yorumlayıcıları sanki kodu -O seçeneği ile çalıştırmışız gibi davranmaktadır. Örneğin Windows sistemlerinde bu işlemi şöyle yapabiliriz: C:\Python>set PYTHONOPTIMIZE=optimize C:\Python>python sample.py UNIX/Linux ve macOS sistemlerinde de aynı işlem şöyle yapılabilir: $ export PYTHONOPTIMIZE=optimize $ python sample.py Bu çevre değişkeni açılan terminale ilişkindir. Dolayısıyla terminal kapatıldığında bu etki de ortadan kaldırılacaktır. Ancak terminal kapatılmadan etkinin ortadan kaldırılması için bu çevre değişkenine atanan değerin yok edilmesi gerekir. Bu işlem Windows'ta şöyle yapılmaktadır: set PYTHONOPTIMIZE= Aynı işlem UNIX/Linux ve macOS sistemlerinde de şöyle yapılmaktadır: PYTHONOPTIMIZE= #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Python'da "tür açıklamaları (type annotation)" ve assert deyiminin dışında test süreci için en kapsamlı araç "unittest" modülü içerisindeki birim testine (unittesting) yönelik hazırlanmış mekanizmadır. Burada birim testlerinin Python'da nasıl yapıldığını açıklayacağız. Birim testleri son 20 yıldır yaygın kullanılan test mekanizmalarındadır. Buradaki "birim (unit)" kodu oluşturan küçük parçaları anlatmaktadır. Bu küçük parçalar da genellikle fonksiyonlar ve metotlar biçiminde karşımıza çıkmaktadır. Birim testlerinde programcı çeşitli "test durumları (test cases)" oluşturarak kendi yazdığı kodu kendisi kodla test etmektedir. Ancak bu test işlemi geliştirme işlemini sekteye uğratmayacak biçimde özel birim testi mekanizmalarıyla yapılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Birim testleri için Python'ın standart kütüphanesinde "unittest" isimli bir modül bulundurulmuştur. Programcı test işlemleri için "test durumları (test cases)" oluşturur. Test durumları unittest.TestCase isimli bir sınıftan türetilen sınıflar biçiminde oluşturulmalıdır. Örneğin: class MyTestCase(unittest.TestCase): pass Sonra yapılacak testler bu sınıfın metotları biçiminde oluşturulur. Ancak sınıfın tüm metotları test metodu olmak zorunda değildir. Test metotları "test" önekiyle başlatılarak isimlendirilmelidir. Örneğin: class MyTestCase(unittest.TestCase): def test_max(self): pass def test_sort(self): pass Burada başı "test" öneki ile başlayan test fonksiyonları programcı tarafından serbest bir biçimde yazılır. Eğer bu kodlar uzun sürerse programcı sınıfa başı "test" ile başlamayan yardımcı metotlar yerleştirilebilir. Test metotlarını da bu yardımcı metotları çağırarak yazabilir. Bizim unittest.TestCase sınıfından türeterek oluşturduğumuz sınıftaki başı "test" ile başlayan metotlara "test" ya da "test metotları" denilmektedir. Bir TestCase içerisinde istenildiği kadar test bulundurulabilir. unittest modülü içerisindeki isimlendirmeler "deve notasyonu (camel casting)" kullanılarak yapılmıştır. Bir testin doğruluğu için aşağıdaki assert metotları bulundurulmuştur. Bu metotlar TestCase sınıfının metotlarıdır. Dolayısıyla test metotları içerisinde self ile çağrılmalıdır: assertEqual(a, b) assertNotEqual(a, b) assertTrue(x) assertFalse(x) assertIs(a, b) assertIsNot(a, b) assertIsNone(x) assertIsNotNone(x) assertIn(a, b) assertNotIn(a, b) assertIsInstance(a, b) assertNotIsInstance(a, b) assertAlmostEqual(a, b) assertNotAlmostEqual(a, b) assertGreater(a, b) assertGreaterEqual(a, b) assertLess(a, b) assertLessEqual(a, b) assertRegex(s, r) assertNotRegex(s, r) assertCountEqual(a, b) assertMultiLineEqual(a, b) assertSequenceEqual(a, b) assertListEqual(a, b) assertTupleEqual(a, b) assertSetEqual(a, b) assertDictEqual(a, b) ... assert metotlarının tam listesini "Python Standard Library" dokümanlarından elde edebilirisiniz. Programcı test fonksiyonlarının içerisinde testlerini yukarıdaki assert metotlarını kullanarak yapar. En sonunda test durumlarını (test cases) çalıştırmak için unittest modülündeki main fonksiyonu çalıştırır. Bu main fonksiyonu unitest.TestCase sınıflarından türetilmiş tüm test durum sınıflarını belirler ve onların "test" ismi ile başlayan metotlarını çağırır. Test metotlardaki assert çağrılarında belirtilen iddia yanlış ise bu durumda ilgili test "hatalı (fail)" ilan edilir. En sonunda main fonksiyonu bütün testlerin sonuçlarına ilişkin bir rapor çıkarmaktadır. Programcı da bu raporu inceleyerek testlerin neden başarısız olduğunu araştırır ve kodundaki hataları düzeltir. Bir test fonksiyonu içerisinde birden fazla assert metodu kullanılabilir. Bunlardan biri başarısız olursa test orada sonlandırılmaktadır. Yani test raporu tüm assert'lere bakılarak verilmez. İlk başarısız olan assert işleminde test fonksiyonu başarısız ilan edilmektedir. Birim testi yaparken test senaryolarını düzgün bir biçimde oluşturmalısınız. Tüm test senaryolarını baştan bir dokümana yazıp sonra koda dökebilirsiniz. Bir kodu test ederken sağlam çalıştığına güvendiğiniz başka fonksiynları ve sınıfları kullanabilirsiniz. Aşağıdaki örnekte programcı "mylib.py" kütüphanesini geliştirirken aynı zamanda onu test emek istemektedir. mylib.py dosyasında bir sequence dizilimin en büyük elemanını bulan getmax isimli fonksiyon ve onu in-place biçimde sıraya dizen bsort isimli fonksiyon bulunmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ # mylib.py def getmax(sequence): if len(sequence) == 0: return None maxval = sequence[0] for i in range(1, len(sequence)): if sequence[i] > maxval: maxval = sequence[i] return maxval def bsort(sequence): for i in range(len(sequence) - 1): for k in range(len(sequence) - i - 1) : if sequence[k] > sequence[k + 1]: sequence[k], sequence[k + 1] = sequence[k + 1], sequence[k] # test.py import mylib import unittest import random class MyTestCase(unittest.TestCase): def test_getmax(self): self.assertEqual(mylib.getmax([3, 5, 7, 2, 9]), 9) self.assertEqual(mylib.getmax([10, 45, 23, -4, 56]), 56) self.assertEqual(mylib.getmax([1, 2, 3, 4, 5, 6, 7, 8]), 8) self.assertEqual(mylib.getmax([-1, -2, -3, -4, -5]), -1) def test_getmax_none(self): self.assertIsNone(mylib.getmax([])) def test_random(self): for i in range(100): a = [random.randint(0, 100000) for i in range(1000)] self.assertEqual(max(a), mylib.getmax(a)) def test_sort(self): a = [5, 4, 3, 2, 1] mylib.bsort(a) self.assertEqual(a, [1, 2, 3, 4, 5]) a = [1, 1, 1, 1, 1, 1] mylib.bsort(a) self.assertEqual(a, [1, 1, 1, 1, 1, 1]) a = [-5, -3, -6, -1] mylib.bsort(a) self.assertEqual(a, [-6, -5, -3, -1]) for i in range(100): a = [random.randint(0, 100000) for i in range(1000)] b = a.copy() mylib.bsort(a) b.sort() self.assertEqual(a, b) a = [] mylib.bsort(a) self.assertEqual(a, []) unittest.main() #------------------------------------------------------------------------------------------------------------------------------------ 81. Ders 03/11/2023 – Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ unittest modülünün main fonksiyonuna biz tüm test case'leri değil istediklerimizi de parametre olarak verebiliriz. Fonksiyon default durumda tüm TestCase sınıflarının tüm testlerini çalıştırmaktadır. Bunun için main fonksiyonunun defaultTest parametresine TestCase sınıfının ismi girilebilir. Örneğin: unittest.main(defaultTest='MyTestCase') Bireden fazla test case'leri çalıştırabilmek için ise defaultTest parametresi TestCase sınıflarının isimlerini içeren dolaşılabilir bir nesne biçiminde girilmelidir. Örneğin: unittest.main(defaultTest=['SortTestCase', 'MaxTestCase']) Benzer biçimde main fonksiyonu türettiğimiz TestCase sınıfının belli bir sınıfının belli bir test fonksiyonunu da çalıştırabilir. Bunun için yazısal biçimde test sınıfı ve nokta ile test metodu belirtilmelidir. Örneğin: unittest.main(defaultTest='MyTestCase.test_max') Benzer biçimde yine birden fazla metot da dolaşılabilir bir nesne ile belirtilebilmektedir. Örneğin: unittest.main(defaultTest=['MaxTestCase.test_getmax', 'SortTestCase.test_sort']) #------------------------------------------------------------------------------------------------------------------------------------ import mylib import unittest import random class MaxTestCase(unittest.TestCase): def test_getmax(self): self.assertEqual(mylib.getmax([3, 5, 7, 2, 9]), 9) self.assertEqual(mylib.getmax([10, 45, 23, -4, 56]), 56) self.assertEqual(mylib.getmax([1, 2, 3, 4, 5, 6, 7, 8]), 8) self.assertEqual(mylib.getmax([-1, -2, -3, -4, -5]), -1) def test_getmax_none(self): self.assertIsNone(mylib.getmax([])) def test_random(self): for i in range(100): a = [random.randint(0, 100000) for i in range(1000)] self.assertEqual(max(a), mylib.getmax(a)) class SortTestCase(unittest.TestCase) : def test_sort(self): a = [5, 4, 3, 2, 1] mylib.bsort(a) self.assertEqual(a, [1, 2, 3, 4, 5]) a = [1, 1, 1, 1, 1, 1] mylib.bsort(a) self.assertEqual(a, [1, 1, 1, 1, 1, 1]) a = [-5, -3, -6, -1] mylib.bsort(a) self.assertEqual(a, [-6, -5, -3, -1]) for i in range(100): a = [random.randint(0, 100000) for i in range(1000)] b = a.copy() mylib.bsort(a) b.sort() self.assertEqual(a, b) a = [] mylib.bsort(a) self.assertEqual(a, []) unittest.main(defaultTest=['MaxTestCase.test_getmax', 'SortTestCase.test_sort']) #------------------------------------------------------------------------------------------------------------------------------------ unittest modülündeki main fonksiyonu her test metodu için ilgili TestCase sınıfınından ayrı bir nesne yaratmaktadır. Bazen test metotlarından önce birtakım hazırlık işlemlerinin yapılması gerekebilmektedir. Bunun için her test metodu çağrılmadan önce otomatik olarak TestCase sınıfının setUp isimli metodu çağrılmaktadır. Programcı da bu setUp metodunda test öncesinde yapılması gereken bazı şeyleri yapabilir. Programcı her test fonksiyonundan önce değil de toplamda bir kez hazırlık işlemi yapacaksa bu durumda setUpClass metodunu kullanmalıdır. setUpClas metodu bir sınıf metodudur. Eğer programcı sınıfın __init__ metodu iiçerisinde birtakım hazırlık işlemlerini yapmak istiyorsa bu durumda __init__ metodu için *args ve **kwargs parametrelerini girmeli ve taban sınıfın __init__ metounu bu parametrelerle *'lı bir biçimde çağırmaıdır. Örneğin: class MaxTestCase(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # .... setUp metodu her test fonksiyonu çalıştırılmadan önce çalıştırılan metottur. setUp metodu test fonksiyonu için bazı önişlemlerin yapılmasını sağlamak amacıyla bulundurulmuştur. İşte her test fonksiyonu çalıştırıldıktan sonra tearDown isimli metot da çalıştırılmaktadır. Böylece programcı eğer setUp metodunda birtakım tahsisatlar yapmışsa tearDown metodunda bunları geri alabilir. Aşağıdaki programı çalıştırınız ve kaç kere __init__ metotlarının ve setUp metotlarının çağrıldığına dikkat ediniz. #------------------------------------------------------------------------------------------------------------------------------------ def mymax(iterable): it = iter(iterable) # iterable.__iter__() try: mval = next(it) except: raise ValueError('maxval() arg is an empty sequence') for x in it: if x > mval: mval = x return mval def mysort(a): for i in range(len(a) - 1): for k in range(len(a) - i - 1): if a[k] > a[k + 1]: a[k], a[k + 1] = a[k + 1], a[k] import unittest import random import copy class MyTestCase(unittest.TestCase): def __init__(self, *args): super().__init__(*args) print(args) self.count = 1000000 def setUp(self): random.seed(12345) def test_max(self): for _ in range(self.count): a = random.sample(range(1000), 10) self.assertEqual(max(a), mymax(a)) def test_sort(self): for _ in range(self.count): a = random.sample(range(1000), 10) b = copy.copy(a) a.sort() mysort(b) self.assertEqual(a, b) unittest.main() #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ def mymax(iterable): it = iter(iterable) # iterable.__iter__() try: mval = next(it) except: raise ValueError('maxval() arg is an empty sequence') for x in it: if x > mval: mval = x return mval def mysort(a): for i in range(len(a) - 1): for k in range(len(a) - i - 1): if a[k] > a[k + 1]: a[k], a[k + 1] = a[k + 1], a[k] import unittest import random import copy import csv class MyTestCase(unittest.TestCase): def __init__(self, *args): super().__init__(*args) self.count = 1000000 def setUp(self): random.seed(12345) self.f = open('test.csv') def tearDown(self): self.f.close() def test_max(self): for _ in range(self.count): a = random.sample(range(1000), 10) self.assertEqual(max(a), mymax(a)) a = [[int(c) for c in line] for line in csv.reader(self.f)] self.assertEqual(max(a), mymax(a)) def test_sort(self): for _ in range(self.count): a = random.sample(range(1000), 10) b = copy.copy(a) a.sort() mysort(b) self.assertEqual(a, b) a = [[int(c) for c in line] for line in csv.reader(self.f)] self.assertEqual(max(a), mymax(a)) if __name__ == '__main__': unittest.main() #------------------------------------------------------------------------------------------------------------------------------------ Aslında projelerde test kodlarının gerçek kodlarla aynı dosyada bulundurulması iyi bir teknik değildir. Genellikle programcı test kodlarını asıl kodlardan ayırır. Örneğin projemzin ismi project olmak üzere biz projemizin tüm kodlarını bir dizinde toplayabiliriz. Bu dizinde Src ve Test isimli iki dizin oluşturabiliriz. Asıl kodlarımızı src dizininin içerisinde test kodlarını ise test dizininin içerisinde bulundurabiliriz: Project Src Test Tabii burada Test dizininin içerisindeki test modüllerinin src dizinindeki test edilecek modüllere erişebilmesi gerekir. Maalesef bunun pratik bir yolu yoktur. En normal yöntem sys.path listesine buradaki Src dizinini eklemek olabilir. Aşağıdaki örnekte Test dizini içeriside test.py ve test.csv dosyaları bulundurulmuştur. Src dizinin içerisinde de mymax ve mysort fonksiyonlarının bulunduğu bir util.py dosyası vardır: Project Src util.py Test test.py test.csv #------------------------------------------------------------------------------------------------------------------------------------ # Proejct/Src/util.py def mymax(iterable): it = iter(iterable) # iterable.__iter__() try: mval = next(it) except: raise ValueError('maxval() arg is an empty sequence') for x in it: if x > mval: mval = x return mval def mysort(a): for i in range(len(a) - 1): for k in range(len(a) - i - 1): if a[k] > a[k + 1]: a[k], a[k + 1] = a[k + 1], a[k] # Project/Test/test.py import sys sys.path.append('../src') import util import unittest import random import copy import csv class MyTestCase(unittest.TestCase): def __init__(self, *args): super().__init__(*args) self.count = 1000000 def setUp(self): random.seed(12345) self.f = open('test.csv') def tearDown(self): self.f.close() def test_max(self): for _ in range(self.count): a = random.sample(range(1000), 10) self.assertEqual(max(a), util.mymax(a)) a = [[int(c) for c in line] for line in csv.reader(self.f)] self.assertEqual(max(a), util.mymax(a)) def test_sort(self): for _ in range(self.count): a = random.sample(range(1000), 10) b = copy.copy(a) a.sort() util.mysort(b) self.assertEqual(a, b) a = [[int(c) for c in line] for line in csv.reader(self.f)] self.assertEqual(max(a), util.mymax(a)) unittest.main() #------------------------------------------------------------------------------------------------------------------------------------ Python'ın standart kütüphanesindeki modüller (yani .py dosyaları) bilindiği gibi birtakım faydalı fonksiyonları ve sınıfları bulundurmaktadır. Ancak bu modüllerin bazıları aynı zamanda bir program gibi de çalıştırılabilmektedir. Yani bu modüllerin bazıları hem bir kütüphane gibi kullanılmakta hem de bir program gibi çalıştırılmaktadır. Tabii bu modüllerdeki programlar bu modülleri yazanlar tarafından aşağıdaki gibi bir kontrolle çalıştırılmıştır: # söz konusu modül ... ... ... if __name__ == '__main__: .... Mademki bazı kütüphane modülleri aynı zamanda birer program gibi çalıştırılabilmektedir. Pekiyi onları program gibi nasıl çalıştırabiliriz? Öncelikle çalıştırma için modüle ilişkin .py dosyalarının nerede olduğunu bilmemiz gerekir. Kurulum programı Python'ı kurarken bunları bazı dizinlerin içerisine çekmektedir. Her ne kadar bunların yerlerini biz bulabilirsek de buradaki programları bu yöntemle çalıştırmak zahmetlidir. Mademki Python yorumlayıcısı zaten bunların yerlerini bilmektedir. O halde bizim Python yorumlayıcısına bunu bildirmemiz yeterli olacaktır. Bu işlem de Python yorumlayıcısının komut satırında -m seçeneği ile yapılmaktadır. Örneğin: python -m [argüman_listesi] Burada -m ve modül isminden sonraki komut satırı argümanları doğrudan bu modül programına komut satırı argümanı olarak aktarılır. Özetle eğer biz Python'ın standart kütüphanesindeki bir modülde bulunan programı -m seçeneği ile çalıştırırız. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ unittest modülü de hem bir kütüphane hem de bir program gibi davranmaktadır. unitest modülüne yerleştirilmiş olan program aslında unittest.main fonksiyonunu çağırmaktadır. Bu durumda biz istersek unit test işlemlerini komut satırından da yapabiliriz. Aşağıdaki örnekte test.py içerisinden unittest.main çağrısı kaldırılmıştır. Artık test kodu aşağıdaki gibi komut satırından da çalıştırılabilir: python -m unittest test Alında çalıştırma sırasında modüldeki belli bir test case ya da test case içerisindeki belli bir test metodu da belirtilebilir. Örneğin: python -m unittest test.MyTestCase python -m unittest test.MyTestCase.test_max #------------------------------------------------------------------------------------------------------------------------------------ import sys sys.path.append('../src') import util import unittest import random import copy import csv class MyTestCase(unittest.TestCase): def __init__(self, *args): super().__init__(*args) self.count = 1000000 def setUp(self): random.seed(12345) self.f = open('test.csv') def tearDown(self): self.f.close() def test_max(self): for _ in range(self.count): a = random.sample(range(1000), 10) self.assertEqual(max(a), util.mymax(a)) a = [[int(c) for c in line] for line in csv.reader(self.f)] self.assertEqual(max(a), util.mymax(a)) def test_sort(self): for _ in range(self.count): a = random.sample(range(1000), 10) b = copy.copy(a) a.sort() util.mysort(b) self.assertEqual(a, b) a = [[int(c) for c in line] for line in csv.reader(self.f)] self.assertEqual(max(a), util.mymax(a)) #------------------------------------------------------------------------------------------------------------------------------------ Üçüncü parti kütüphaneler ve paketler ve programlar başka kütüphanelere ve paketlere bağımlı olabilirler. Çünkü programcılar başka kütüphanelerden faydalanıp kendi kütüphanelerini ve programlarını yazmaktadır. Üstelik programcılar kendi kütüphanelerini ya da programlarını yazarken başka kütüphanelerin belli versiyonlarını kullanmış olabilirler. Kütüphanelerin yeni versiyonları çıktıkça bunlar eskiye doğru uyumu oratadan kaldırabilmektedir. Yani bazen bir kütüphanenin yeni versiyonu eski versiyonu gibi kullanılamamaktadır. Bu durumda biz üçüncü parti bir kütüphaneyi ya da paketi yüklediğimizde o paket kendi kullandığı kütüphanelerin son versiyonunu yükleyecektir. Bu da paketin çalışmasını olumsuz yönde etkileyebilecektir. Örneğin bir makine öğrenmesi programı Tensorflow isimli kütüphanenin 1'li versiyonu kullanıalrak yazılmış olabilirken diğer bir program 2'li TensorFlow kütüphanesinin 2'li versiyonları kullanılarak yazılmış olabilir. Biz aynı makineye bu iki versiyonu birlikte yükleyemeyiz. Değişik kütüphanelerin ve programların değişik kütüphane versiyonlarını kullanması önemli bir sıkıntıdır. Pekiyi bu sıkıntıyı giderebilmek için ne yapabiliriz? İşte bunun için Python dünyasında "sanal ortam (virtual envirionment)" denilen bir kavram oluşturulmuştur. Sanal ortam (virtual environment) adeta makinemizdeki ayrı bir python kurulumu gibi etki oluşturmaktadır. Her sanal ortam ayrı bir Python kurulumu temsil ettiği için birinde pip programı ile yüklenen paket ona özgü olmaktadır. Her sanal ortam birbirlerinden izole edilmiştir. Örneğin biz bir samal ortama Python yorumlayıcısının 3.5 sürümünü, TensorFlow kütüphanesinin 1.4 sürümünü yükleyebiliriz. Diğer bir sanal ortama ise Python yorumlayıcısının 3.10 sürümünü, TensorFlow kütüphanesinin ise 2.1 sürümünü yükleyebiliriz. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir sanal ortam yaratmak için standart kütüphanedeki venv modülü kullanılmaktadır. Bu modül bir program gibi de çalıştırılabilmektedir. Sanal ortam yaratmanın tipik biçimi şöyledir: python -m venv Sanal ortam oluşturulduğunda bizim vermiş olduğumuz isimle bir dizin yaratılır. Yaratılan dizinin içerisinde "include" ve "lib" dizinleri bulunur. Ayrıca Windows sistemlerinde "Scripts" isimli bir dizin UNIX/Linux ve macOS sistemlerinde "bin" isimli bir dizin daha vardır. "Scripts" ya da "bin" dizinlerinin içerisinde "activate" ve "deactivate" isimli iki shell script bulunmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Sanal ortamı Windows'ta aktive etmek "Scripts" dizinine geçilip "activate" scriptinin çalıştırılması gerekmektedir. Bunun için cd komutunu kullanabilirsiniz. Dizine geçtikten sonra doğrudan komut satırından "activate" yazıp ENTER tuşuna basabilirsiniz. Örneğin: C:\Users\kaanaslan\Study\Python-App>python -m venv myenv C:\Users\kaanaslan\Study\Python-App>cd myenv C:\Users\kaanaslan\Study\Python-App\myenv>dir Volume in drive C has no label. Volume Serial Number is 8E44-2FDB Directory of C:\Users\kaanaslan\Study\Python-App\myenv 09.09.2022 20:27 . 09.09.2022 20:27 .. 09.09.2022 20:27 Include 09.09.2022 20:27 Lib 09.09.2022 20:27 92 pyvenv.cfg 09.09.2022 20:27 Scripts 1 File(s) 92 bytes 5 Dir(s) 165.163.900.928 bytes free C:\Users\kaanaslan\Study\Python-App\myenv>cd Scripts C:\Users\kaanaslan\Study\Python-App\myenv\Scripts>activate (myenv) C:\Users\kaanaslan\Study\Python-App\myenv\Scripts> Sanal ortamın aktive edildiği prompt'taki en soldaki parantezden belli olmaktadır. Bu parantezin içerisinde sanal ortamın isminin bulunduğuna dikkat ediniz. Aslında bu dizin ile isim farklı da olabilmektedir. Bunun için sanal ortam yaratılırken --prompt argümanı kullanılmalıdır. Örneğin: python -m venv myenv --prompt csd Windows'ta sanal ortamı deactivate etmek için herhangi bir yerde "deactivate" yazılıp ENTER tuşuna basılır. Yani "deactivacate" komutu için "Scripts" dizininde olmak gerekmemektedir. Örneğin: (myenv) C:\Users\kaanaslan\Study\Python-App\myenv\Scripts>deactivate #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ UNIX/Linux ve macOS sistemlerinde sanal ortamı aktive etmek için "bin" dizinine geçilip "activate" scripti aşağıdaki gibi çalıştırılmalıdır: source activate Örneğin: $ python3 -m venv myenv $ cd myenv $ ls bin include lib pyvenv.cfg $ cd bin $ source activate Bu sistemlerde de sanal ortamı deactivate etmek için herhangi bir yerde "deactive" yazıp ENTER tuşuna basabiliriz. Örneğin: $ deactivate #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir sanal ortam oluşturup onu aktive ettiğimizde biz artık bağımısız bir Python kurulumunun içinde gibi oluruz. Dolayısıyla sanal ortamımız aktive edildiğinde artık pip programı o sanal ortama install işlemi yapar. Bundan diğer sanal ortamlar ve ana kurulum etkilenmez. Sanal ortamı silmek için tek yapılacak şey o dizini silmektir. Örneğin: c:\Users\kaanaslan\Study\Python-App\myenv\Scripts>activate (myenv) c:\Users\kaanaslan\Study\Python-App\myenv\Scripts>pip install numpy Collecting numpy Downloading numpy-1.23.3-cp39-cp39-win_amd64.whl (14.7 MB) |████████████████████████████████| 14.7 MB 6.4 MB/s Installing collected packages: numpy Successfully installed numpy-1.23.3 WARNING: You are using pip version 21.2.3; however, version 22.2.2 is available. You should consider upgrading via the 'C:\Users\kaanaslan\Study\Python-App\myenv\Scripts\python.exe -m pip install --upgrade pip' command. Bu örnekte gördüğünüz gibi biz NumPy'ı aslında ana kuruluma değil "myenv" isimli sanal ortama kurduk. pip programı indirdiği paketleri lib dizini içerisindeki "site-packages" dizinine kopyalamaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Anaconda dağıtımında sanal ortamlar GUI arayüzü ile oluşturulabilmektedir. Anaconda Navigator'da "Environments" sekmesine gelindiğinde O anda yaratılmış olan sanal ortamlar görüntülenmektedir. Burada biz fare ile tıklayarak bir sanal ortamı aktive edebiliriz. Aşağıdaki kısımda yeni bir sanal ortamı yaratıp silmek için düğmeler bulunmaktadır. Anaconda dağıtımı yükdiğinde "base(root)" isimli default bir sanal ortam otomatik olarak oluşturulmaktadır. Her sanal ortama Spyder IDE'sinin yeniden kurulması gerekir. PyCharm da benzer bir biçimde çalışmaktadır. Biz PyCharm'da daha önce oluşturmuş olduğumuz bir sanal ortamı proje olarak açabiliriz. PyCharm'da bir proje yaratılırken her zmana bir sanal ortam da yaratılmaktadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir Python kurulumunda (bu bir sanal ortam da olabilir) kurulmuş bütün paketlerin versiyon listesisini elde etmek için şu komut uygulanmalıdır: pip freeze Burada install edilmiş olan paketler ekrana yazdırılır. Onun bir dosyaya yazdırılmasını istiyorsanız ">" karakteriyle "io yönlendirmesi" uygulamalısınız: pip freeze > plist.txt Artık biz yukarıdaki komutla o andaki python kurulumunun (bu bir sanal ortam da olabilir) install etmiş olduğu tüm paketleri versiyon numaralarıyla birlikte "plist.txt" isminde bir dosyaya yerleştirmiş olduk. Artık pip programı ile bu dosyadan hareketle tün bu paketleri başka bir kuruluma (bu başka kurulum bir sanal ortam da olabilir) -r seçeneği ile install edebiliriz. -r seçeneğinin yanına oluşturmuş olduğumuz dosyanın yol ifadesi getirilmelidir. Örneğin: pip install -r plist.txt #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir programı bağımsız çalıştırılabilir (executable) bir program haline getirebilmek için çeşitli üçüncü parti araçlar kullanılabilmektedir. Bunlardan biri "pyinstaller" denilen araçtır. Bunun indirilip kurulması şöyle yapılabilir: pip install PyInstaller Bu biçimde kurulum yapıldığında pyinstaller Python'ın standard kütüphanelerinin bulunduğu dizine kurulmaktadır. Dolayısıyla pyinstaller -m seçeneği ile çalıştırılmalıdır. Aslında pyinstaller programına başka alternatifler de vardır. Örneğin: py2exe PyOxidizer autopy2exe cx_freeze Ancak biz burada en yaygın kullanılan araç olan pyinstaller üzerinde duracağız. Artık bir Python programından "çalıştırılabilir (executable)" bir dosya elde edilebilir. Tek yapılacak şey şudur: python -m PyInstaller sample.py --onefile Eğer burada bir sorun çıkarsa --noupx seöeneğini de ekleyebilirisniz: python -m PyInstaller sample.py --onefile --noupx Burada sample.py dosyası çalıştırılmak istenen programı belirtmektedir. Tabii bir program çok fazla dosyadan oluşabilir. Yani bu dosyalar import edilerek kullanılmış olabilir. Biz pyinstaller programına akışın başlatılacağı ana dosyayı veririz. pyinstaller bu dosyayı inceler. Bu dosyadakilerden hareketle python yorumlayıcısı dahil olmak üzere her şeyi tek bir çalıştırılabilir dosyanın içerisine yerleştirir. Artık yalnızca bu dosya taşınsa bile program çalıştırılabilir. Yukarıdaki işlemden sonra bir "build" bir de "dist" dizinleri oluşturulacaktır. Çalıştırılabilen program "dist" dizinin içerisine çekilir. Eğer pyinstaller çalıştırılırken --onefile seçeneği girilmezse bu durumda dist dizinin içerisinde tek bir dosya değil bir grup dosya yaratılır. Tabii bu dizin kopyalanırsa program çalıştırılabilir. Ancak burada tek bir dosya söz konusu değildir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ 81. Ders 10/11/2023 - Cuma #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ XML (Extensible Markup Language) dokümanları kodlamak, iletmek ve yeniden oluşturmak için kullanılan genel bir doküman formatıdır. XML belli bir konuya yönelik değildir. Herhangi bir konudaki verilerin kodlanması için genel kuralları içerir. Böylece biz örneğin biyomedikal verilerini, veritabanı kayıtlarını, bir IDE'nin ayarlarını vs. XML kullanarak kodlayabiliriz. XML genel bir kodlama formatıdır. Dolayısıyla spesifik bir alana yönelik değildir. XML yalnızca kodlamaya ilişkin kurallara sahiptir. Kodlanan içeriğin ne anlama geldiğine yönelik kurallara sahip değildir. Yani birtakım verileri XML kullanarak text formatında saklayabilirisniz. Ancak bunların ne anlam ifade ettiğini siz bilirsiniz. Örneğin bir IDE proje ayarlarını XML olarak bir dosyaya kaydediyor olabilir. Ancak neyi nasıl kaydettiğini biz XML dosyasına bakarak anlayamayız. Onun semantiğine ilişkin dokümanları incelememeiz gerekir. XML WWW Konsorsiyumu tarafından geliştirilmiştir. ECMA tarafından da standardize edilmiştir. Orijinal dokümanlara aşağıdaki bağlantıdan erişebilirniz: https://www.w3.org/TR/REC-xml/ XML dokümanı elemanlaran oluşur. Bir eleman "başlangıç tag'ı (starting tag)" ve "bitiş tag'i (ending tag)" ile belirtilir. Başlangıç ve bitiş tag'larının arasında "içerik (content)" bulunabilmektedir. Örneğin: Ali Serçe İçerik kısmında boşluklar bırakılırsa bu boşluklar da içeriğe dahil olmaktadır. Örneğin: üzüm üzüme baka baka kararır Burada içerik " üzüm üzüme baka baka kararır " biçimindedir. Tag'lar açısal parantezler içerisinde yazılır. Bitiş tag'ında / karakteri ve başlangıç tag'ı boşuksuz biçimde bulundurulur. İçerik herhangi bir biçimde oluşturulabilir. Tag isimleri tamamen dokümanı oluşturanın istediği gibi belirleyebileceği isimlerdir. Başlangıç tag'ında istenirse "öznitelikler (attributes)" de bulundurulabilir. Öznitelikler isim="değer" biçiminde belirtilir. '=' karakterinin iki yanında boşluklar bırakılabilir. Değer ise her zaman iki tırnak içerisine alınmak zorundadır. Özellikler arasında istenildiği kadar SPACE, TAB ve ENTER karakteri bırakılabilir. Ancak ',' kullanılamaz. Örneğin: Murat Atılgan İlkokulu Eğer eleman içeriğe sahip değilse bitiş tag'ı yazılmadan başlangıç tag'ının sonunda "/>" karakterleri ile eleman yazımı bitirilebilir. Örneğin: Yukarıdaki eleman aşağıdaki ile eşdeğerdir: Başlangıç tag'ında "<" ile tag ismi bitişik yazılmalıdır. Ancak bundan sonra başlangıç tag'ını bitiren ">" karakteri öncesinde boşluklar olabilmektedir. Örneğin: Bu geçerli bir başlangıç tag'ıdır. Bitiş tag'ında "/" ve isim bitişik olmak zorundadır. Ancak bitiş tag'ını bitiren ">" karakterinden önce yine boşluklar olabilir. Örneğin: Bu da geçerli bir bitiş tag'ıdır. Tabii normalde uygulamacı bu gereksiz boşlukları kullanmaz. Örneğin: content Özellik belirtirken '=' karakterinin iki yanında boşluk bırakılabilir. Örneğin: Bu yazım geçerlidir. Tabii normalde böyle bir şeyi yapmanız için gerekçeniz yoktur. XML formatı hem insanlar tarafından text editörlerle hem de programlar tarafından otomatik biçimde oluşturulabilmektedir. XML dosyaları genellikle prgramlar tarafından okunup parse edilerek kullanılmaktadır. Bir XML dosyasının başında kesinlikle XML versiyonunu belirten aşağıdaki tag bulunmak zorudadır: Şu anda XML'in son versiyonu (hala) 1.0'dır. Her ne kadar XML'in henüz başka bir versiyonu olmasa da bu satırın dokümanın başında bulunması ileriye doğru uyumu korumak için gereklidir. XML dokümanlarınızı "doğrulamak (validate etmek)" için çeşitli araçlar vardır. Google'da "xml validation tools" yazdığınızda pek çok online araçla karşılaşacaksınız. Bir XML dökümanında bir tane kök eleman eleman bulunur. Bir elemanın içerik kısmında başka elemanlar bulunabilmektedir. Böylece doküman hiyerarşik veriler için kullanılabilir. Örneğin: Ali Serçe Kaan Aslan NEcati Ergin Burada kök eleman elemanıdır. Bu eleman elemanlarını içermektedir. Burada elemanları aslında elemanının içerik kısmındadır. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ XML dokümanlarını parse etmek için genel olarak üç mimari kullanılmaktadır: 1) DOM Parser Mimarisi 2) SAX Parser Mimarisi 3) ElementTree Mimarisi Hepsinin belli avantajları ve dezavantajları olmakla birlikte en çok tercih edilen ElemenTree parser'larıdır. Biz de burada bu parser'ı tanıtacağız. Python'ın standart kütüphanesi bu üç parser mimarisini de desteklemektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ ElementTree parser'ı default durumda tüm XML dosyasını başından sonuna okur ve ondan sonra işleme sokar. Ancak bu modül bloke olmadan parça parça işlem yapabilme yeteneğine de sahiptir. Bu parser Python'ın standart kütüphanesinde xml.etree modülünde bulunmaktadır. ElemenTree praser'ını kullanmak için önce ElemenTree sınfını from import ile import edebiliriz: from xml.etree import ElementTree Parse işlemi bu sınıfın static parse isimli metoduyla yapılmaktadır. Bu parse metodu dosyan ın yol ifadesini alabilir ya da open fonksiyonuyla açılmış olan dosya nesnesini paramatre olarak alabilmektedir. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') Ya da örneğin: from xml.etree import ElementTree with open('sample.xml') as f: tree = ElementTree.parse(f) static parse metodu bize ElemenTree sınıfı türünden bir nesne verir. Biz de artık bu sınıfın metotlarını kullanarak işlemlerimizi yaparız. parse metodunun bize verdiği ElementTree nesnesi tüm ağacı temsil etmektedir. XML dokümanlarında başlangıç tag'ı, içerik ve bitiş tag'ının oluşturduğu topluluğa "eleman (element)" denilmektedir. ElementTree mimarisinde tüm ağaç ElementTree sınıfı ile temsil edilirken elemanlar Element sınıfıyla temsil edilmektedir. XML dokümanlarında bir elemanın diğer elemanları içerebildiğine dikkat ediniz. Örneğin: 1 2008 141100 4 2011 59900 68 2011 13600 Burada "data" elemanı "country" elemanlarını "country" elemanları da "rank", "year", "gdppc", "neighbor" elemanlarını içermektedir. Biz bu XML dosyasını "sample.xml" ismiyle kaydedip örneklerde kullanacağız. parse işleminden sonra ilk yapılacak şey kök elemanı elde etmektir. Yukarıdaki örnekte kök eleman "data" isimli elemandır. Bunun için ElementTree nesnesi ile sınıfın getroot metodu çağrılır. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() Burada root değişkeni artık Elemant türündendir. Element nesnesinin üç önemli özniteliği vardır: tag, attrib ve text. tag özniteliği string olarak tag'ın ismini, text özniteliği içerik yazısını ve attrib özniteliği de tag'a ilişkin özellikleri bir sözlük biçiminde vermektedir. Sözlüğün anahtarları özelliklerin isimlerinden değerleri de özelliklere '=' ile atanan yazılardan oluşmaktadır. Örneğin: Ümraniye Bu elemanın "tag" özniteliği bize "student" yazısını, text özniteliği "1234" yazısını ve attrib özniteliği ise {'name': 'Ali Serçe', 'no': '1234'} sözlük nesnesini vermektedir. #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ Bir Element nesnesi aynı zamanda dolaşılabilir bir nesnedir. Biz Element nesnesini dolaşırsak yukarıdan aşağıya doğru onun bütün doğrudan alt elemanlarını elde ederiz. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() for element in root: print(element.tag, element.attrib) Buradan şu çıktı elde edilmiştir: country {'name': 'Liechtenstein'} country {'name': 'Singapore'} country {'name': 'Panama'} Element sınıfının iter isimli metodu bize dolaşılabilir bir nesne verir. Ancak iter metodunun verdiği nesneyi dolaştığımızda biz yalnızca doğrudan alt elemanları değil yukarıdan aşağıya doğru kendisi de dahil tüm alt elemanları elde ederiz. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() for element in root.iter(): print(element.tag, element.attrib) Buradan şöyle bir çıktı elde edilmiştir: data {} country {'name': 'Liechtenstein'} rank {} year {} gdppc {} neighbor {'name': 'Austria', 'direction': 'E'} neighbor {'name': 'Switzerland', 'direction': 'W'} country {'name': 'Singapore'} rank {} year {} gdppc {} neighbor {'name': 'Malaysia', 'direction': 'N'} country {'name': 'Panama'} rank {} year {} gdppc {} neighbor {'name': 'Costa Rica', 'direction': 'W'} neighbor {'name': 'Colombia', 'direction': 'E'} Pekiyi ağaç üzerinde spesifik elemanları nasıl elde edebiliriz? İlk akla gelen yöntem her elemanı elde ederken aynı zamanda tag özelliğine bakmak olabilir. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() for element in root.iter(): if element.tag == 'year': print(element.tag, '=>', element.text) Buradan şöyle bir sonuç elde edilmiştir: year => 2008 year => 2011 year => 2011 Aslında bunu yapmak için Element sınıfında findall isimli bir metot da bulundurulmuştur. Ancak findall metodu doğrudan alt elemanları aramaktadır. findall bize elemanları bir liste olarak verir. Örneğin: result = root.findall('year') Burada findall hiçbir eleman bulamayacaktır. Çünkü kök eleman olan "data" yalnızca "country" elemanlarına sahiptir. findall XPATH özelliğini desteklemektedir. XPATH XML ağacını sanki dizin ağacı gibi ifade etmek için kullanılan bir gösterim biçimidir. XPATH ifadesine "./" ile başlamak en yaygın durumdur. Buradaki "./" o andaki eleman anlamına gelir. Örneğin "./country/year" XPATH ifadesi "country" elemanının altındaki "year" elemanlarını bulur. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() for element in root.findall('./country/year'): print(element.tag, '=>', element.text) XPATH'te ".//" ifadesi altındaki tüm elemanlar anlamına gelmektedir. Örneğin ".//year" ilgili elemanın altındaki tüm elemanlar içerisinde "year" tag'larını bize verir. findall bize tüm elemanları bulur, onu bir listeye doldurur ve listeyi bize verir. Halbuki bu işlemin iteratör yoluyla yapılması kimi zaman daha az maliyetli olabilmektedir. İşte findall metodunun iteratörlü versiyonu iterfind ismindedir. iterfind bize dolaşılabilir bir nesne verir. Dolayısıyla biz elemanı döngü içerisinde tek tek elde ederiz. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() for element in root.iterfind('./country/year'): print(element.tag, '=>', element.text) Element sınıfının find isimli metodu yalnızca koşula uygun ilk elemanı bize verir. Örneğin: from xml.etree import ElementTree tree = ElementTree.parse('sample.xml') root = tree.getroot() element = root.find('.//year') print(element.tag, '=>', element.text) Şöyle bir sonuç elde edilecektir: year => 2008 #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------