Python App
Python App
Kurs Notları
Kaan ASLAN
Bu kurs notları Kaan ASLAN tarafından yazılmıştır. Kaynak belirtilmek koşuluyla her türlü alıntı yapılabilir.
Bu bölümde Python'ın standart kütüphanesindeki çeşitli modüller içerisinde bulunan çeşitli temel fonksiyonlar ve
sınıflar ele alınacaktır. Bu temel fonksiyonlar ve sınıflar başka konularda da çeşitli biçimlerde kullanılmaktadır.
Bu bölümde Python Standart Kütüphanesindeki tarih ve zaman işlemlerini yapan fonksiyonlar ve sınıflar ele
alınacaktır.
time Modülü
time modülü C Programlama Dilindeki prototipleri <time.h> içerisinde bulunan standart tarih zaman işlemlerini
yapmaktadır. C programcıları bu modüldeki fonksiyonlara oldukça aşina olacaklardır. Bu modülü kullanmadan önce
import etmeyi unutmayınız.
[Link] Fonksiyonu
time fonksiyonu 01/01/1970'den geçen saniye sayısını float olarak verir. Örneğin:
>>> [Link]()
1532699789.392054
Örneğin:
import time
start = [Link]()
for i in range(100000000):
pass
end = [Link]()
print(end - start)
[Link] Fonksiyonu
ctime fonksiyonu time fonksiyonundan elde edilen saniye sayısını parametre olarak alır ve tarih zaman bilgisini bize
str türünden yazısal biçimde verir. Örneğin:
1
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> t = [Link]()
>>> [Link](time)
>>> [Link](t)
'Fri Jul 27 17:01:56 2018'
>>> [Link]([Link]())
'Fri Jul 27 17:06:31 2018'
ctime fonksiyon parametresiz çağrılırsa o andaki tarih zaman bilgisi elde edilir. Örneğin:
>>> [Link]()
'Sun Aug 5 10:24:38 2018'
[Link] Fonksiyonu
localtime isimli fonksiyon time fonksiyonundan elde edilen değeri parametre olarak alır ve bize o değerin tarih zaman
karşılığını struct_time isimli bir sınıf nesnesi olarak verir. Örneğin:
>>> t = [Link]()
>>> st = localtime(t)
>>> st
time.struct_time(tm_year=2018, tm_mon=7, tm_mday=27, tm_hour=17, tm_min=30, tm_sec=11,
tm_wday=4, tm_yday=208, tm_isdst=0)
struct_time isimli sınıfın tm_xxx biçiminde isimlendirilmiş örnek öznitelikleri (instance attribute) vardır. Bu
öznitelikler ilgili tarih ve zamanın bileşenlerini bize verir. Örneğin:
import time
st = [Link]([Link]())
print('{0:02d}/{1:02d}/{2:04d}'.format(st.tm_mday, st.tm_mon, st.tm_year))
localtime parametresiz olarak kullanılırsa o andaki tarih zaman bilgisini bize verir. struct_time yapısının örnek
özniteliklerini (instance attributes) tek tek kullanan aşağıdaki gibi bir fonksiyon yazabiliriz:
import time
def dispStructTime(st):
print('tm_year:', st.tm_year)
print('tm_mon:', st.tm_mon)
print('tm_mday:', st.tm_mday)
print('tm_hour:', st.tm_hour)
print('tm_min:', st.tm_min)
print('tm_sec:', st.tm_sec)
print('tm_wday:', st.tm_wday)
print('tm_yday:', st.tm_yday)
print('tm_year:', st.tm_year)
print('tm_isdst:', st.tm_isdst)
print("Hafanın günü:", ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi',
'Pazar'][st.tm_wday])
dispStructTime([Link]())
time modülü içerisindeki sleep isimli fonksiyon saniye cinsinden bekleme yaratmak için kullanılmaktadır.
Fonksiyonun parametrik yapısı şöyledir:
2
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
sleep(seconds)
import time
for i in range(10):
print(i)
[Link](0.5)
datetime Modülü
datetime modülünün içerisinde tarih ve zamanı tutmak için kullanılan date, time, timedelte, datetime gibi sınıflar ve
bazı yardımcı fonksiyonlar vardır.
[Link] Sınıfı
Nasıl [Link] sınıfı zaman bilgisini tutmak için bulundurulmuşsa [Link] sınıfı da tarih bilgisini tutmak
için bulundurulmuştur. Bir [Link] nesnesi sınıfın başlangıç metodu yoluyla aşağıdaki gibi yaratılabilir:
Yine tarihin bileşenlerini biz sınıfın day, month ve year örnek öznitelikleriyle elde edebiliriz. Örneğin:
import datetime
d = [Link](2017, 5, 23)
print('{:02d}/{:02d}/{:04d}'.format([Link], [Link], [Link]))
date sınıfının today isimli sınıf metodu (class method) bize o anki tarihi date sınıf nesnesi olarak verir. Örneğin:
>>> d = [Link]()
>>> print(d)
2018-08-05
date sınıfının ctime örnek metoduyla tarih bilgisini yazısal olarak elde edebiliriz. Örneğin:
>>> [Link]().ctime()
'Fri Jul 27 00:00:00 2018'
Elimizde bir date nesnesi varsa date sınıfının replace metoduyla onun bazı bileşenlerini değiştirip yeni bir date
nesnesi elde edebiliriz. Örneğin:
>>> d1 = [Link]()
>>> d2 = [Link](year = 1999)
>>> print(d1)
2018-07-27
>>> print(d2)
1999-07-27
date sınıfının karşılaştırma operatör metotları olduğu için iki date nesnesini karşılaştırma operatörleriyle işleme
sokabiliriz. Örneğin:
3
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import datetime
if d1 > d2:
print('d1 > d2')
elif d1 < d2:
print('d1 < d2')
elif d1 == d2:
print('d1 == d2')
date sınıfının weekday isimli metodu ilgili tarihe ilişkin günün haftanın kaçıncı günü olduğunu vermektedir. (Haftanın
ilk günü Pazartesidir ve 0 değerindedir.) Örneğin:
>>> d = [Link]()
>>> ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'][[Link]()]
'Pazartesi'
Numpy
Sınıfın isoweekday isimli metodu weekday metodu ile aynı şeyş yapmaktadır. Ancak bu metot haftanın ilk gününü
Pazar kabul eder.Örneğin:
>>> d = [Link]()
>>> ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'][[Link]()]
'Pazartesi'
Sınıfın isocalender isimli metodu (yıl, yılın haftası, haftanın günü) biçiminde üç elemanlı bir demete geri döner.
Örneğin:
>>> [Link]().isocalendar()
(2020, 38, 1)
Sınıfın isoformat isimli metodu sınıfın tuttuğu tarihi ISO 8601 formatında (yani 'YYYY-MM-DD' formatında) bir yazı
olarak verir. Sınıfın __str__ metodu da bu metodun geri dönüş değeriyle geri dönmektedir. Örneğin:
>>> [Link]().isocalendar()
(2020, 38, 1)
>>> [Link]().isoformat()
'2020-09-14'
>>> print([Link]())
2020-09-14
Bazen yazısal bir tarih bilgisini [Link] türüne dönüştürmek isteyebiliriz. Bunun için date sınıfın fromisoformat
isimli sınıf metodu kullanılmaktadır. Örneğin:
import datetime
[Link] Sınıfı
datetime modülünün içerisindeki time sınıfı bir zaman bilgisini saat, dakika, saniye ve mikrosaniye biçiminde bizden
alarak tutar. Örneğin:
4
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
<class '[Link]'>
time sınıfının hour, minute, second ve microseconds örnek öznitelikleriyle nesnede tutulan zaman bilgisininin
bileşenlerini elde edebiliriz. Örneğin:
Örneğin:
import datetime
[Link] sınıfınının da karşılaştırma opereatör metotları ile iki time nesnesi karşılaştırılabilir. Örneğin:
import datetime
if t > k:
print('t > k')
elif t < k:
print('t < k')
else:
print('t == k')
[Link] Sınıfı
datetime modülünün timedelta sınıfı zaman aralığını turmak için düşünülmüştür. Sınıfın __init__ metodunun
parametrik yapısı şöyledir:
Örneğin:
Nesne yaratılırken verilen bu zaman değerlerini geri alabilmek için yalnızca üç örnek özniteliği kullanılmaktadır: days,
seconds ve microseconds. Örneğin:
5
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
1:32:33.500000
İki timedelta nesnesi de toplanıp çıkartılabilir. Ürün timedelta türünden olacaktır. Örneğin:
import datetime
date nesnesi ile timedelta nesnesi + ve - operatörleriyle işleme sokulabilir. Bu durumda ürün olarak date elde edilir.
Örneğin:
import datetime
today = [Link]()
td = [Link](days=4)
result = today - td
print(result)
result = today + td
print(result)
Ancak bir time nesnesi ile bir timedelta nesnesi de + ve - operatörleriyle işleme sokulamaz.
timedelta nesnesi bir tamsayıyla ya da bir float sayıyla çarpılıp bölünebilir. Sonuç yine timedelta nesnesi olarak elde
edilecektir. İki timedelta nesnesi karşılaştırma operatörleriyle karşılaştırma işlemine sokulabilir.
[Link] Sınıfı
datetime modülünün datetime sınıfı hem tarih hem de zaman bilgisini tutar. datetime sınıfının başlangıç metodu
şöyledir:
Örneğin:
Bir datetime nesnesini datetime sınıfının combine isimli sınıf metoduyla date ve time nesnesi vererek de
oluşturabiliriz. Örneğin:
>>> t = [Link](1, 3, 7)
>>> d = [Link](2009, 12, 26)
>>> dt = [Link](d, t)
>>> print(dt)
2009-12-26 01:03:07
Yine datetime sınıfının year, month, day, hour, minute, second gibi örnek öznitelikleri nesnenin içerisindeki tarih ve
zamanın bileşenlerini bize verir.
6
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
datetime sınıfının now isimli sınıf metodu bize o anki tarih ve zamanı datetime nesnesi olarak vermektedir. Örneğin:
>>> [Link]()
[Link](2020, 9, 19, 0, 13, 40, 985585)
Benzer biçimde sınıfın utcnow isimli sınıf metodu ise o anki tarih ve zamanı UTC olarak verir. Örneğin:
>>> [Link]()
[Link](2020, 9, 18, 21, 13, 57, 787950)
İki datetime nesnesi birbirinden çıkarılabilir. Sonuç timedelta türünden olur. Bir datetime nesnesiyle bir timedelta
nesnesi de toplanıp çıkarılabilir. Bu durumda sonuç timedelta türündendir. Örneğin:
Anahtar Notlar: Python'da import edilmiş olan modüllerin kaynak kodlarını görmek mümkündür. Bunun için PyCharm IDE'sinde ilgili ismin
üzerine gelinip bağlam menüsünden "Go To/Declaration" seçilebilir. Eğer bir IDE'de çalışmıyorsak bu işlem inspect modülüyle de
yapılabilmektedir. inspect modülünün getsource isimli sınıf metodu bize ilgili öğenin kaynak kodlarını görüntülemektedir. Örneğin:
import inspect
import datetime
print([Link](datetime))
Ancak buuilt-in fonksiyonlar ve sınıfların kaynak kodları görüntülenememektedir. Çünkü bunlar yorumlayıcı içerisie gömülmüş durumdadır.
calendar Modülü
calendar isimli modül bazı temel takvimsel işlemleri yapan fonksiyonlara ve sınıflara sahiptir. Modülün isleap isimli
fonksiyonu ilgili yılın artık olup olmadığını bize verir. Örneğin:
calendar modülünün TextCalendar isimli sınıfı takvim işlemleri için kullanılmaktadır. Sınıfın başlangıç metodu
haftanın hangi günden başlayacağını belirtir (0 = Monday, 1 = Tuesday, 2 = Wednasday, ...). Günler için Calendar
sınıfında tanımlanmış sembolik sabitler vardır. TextCalendar sınıfının prmonth isimli metodu ay takvimini ekrana
bastırır. Örneğin:
import calendar
cal = [Link]()
[Link](2018, 8)
August 2018
Mo Tu We Th Fr Sa Su
7
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
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
TextCalendar sınıfının pryear isimli örnek metodu yılın tüm aylarının takvimini ekrana (stdout dosyasına) basar.
Sınıfın formatmonth metodu ay takvimini ekrana bastırmak yerine bir str nesnesi olarak verir. Parametrik yapısı
şöyledir:
TextCalendar sınıfının formatyear isimli örnek metodu da yılın takvimini ekrana basmak yerine onu bir string olarak
verir. Metodun parametrik yapısı şöyledir:
Düzenli ifadeleri "yazısal kalıpların ifade edilmesinde kullanılan küçük bir dil" olarak tanımlayabiliriz. Gerçekten de
özellikle yazılarda belli kalıpların aranması sürecinde düzenli ifadelerden sıkça faydalanılmaktadır. Örneğin bir text
editörde “dd/mm/yyyy” kalıbına uyan tarihleri ya da xxxxx@[Link] kalıbına uyan e-posta adreslerini bulmak
isteyebiliriz. Bu kalıpların editörlerdeki klasik metin arama özellikleriyle bulunamayacağına dikkat ediniz. İşte düzenli
ifadeler böyle kalıpların ifade edilmesini sağlayan kurallar topluluğundan oluşmaktadır. Gelişmiş pek çok kelime
işlemci düzenli ifadeler yoluyla arama işlemi yapabilmektedir. Düzenli ifadeler üzerinde işlem yapan araçların
kullandıkları kodlara “düzenli ifade motorları (regular exprtession engines)” denilmektedir. Malesef düzenli ifadelerin
kurallarına ilişkin bir standart yoktur. Bu nedenle düzenli ifade motorlarının da tamamen aynı kurallara sahip
olduklarını söyleyemeyiz. Ancak pek çok motor büyük ölçüde birbirlerine benzemektedir.
Düzenli ifadelerde iki tür karakter kümesi vardır: Normal karakterler ve meta karakterler. Normal karakterler kalıpta karakter
olarak bulunması gereken öğelerdir. Yani kalıptaki normal bir karakter başka bir şeyi değil kendisini temsil eder. Meta karakterler
ise kalıpta kendisini temsil etmeyen, özel anlama gelen karakterlerdir. Örneğin '+' bir meta karakterdir. '+' karakterinin düzenli
ifadelerde özel başka bir anlamı vardır. Bu karakter onun solundaki karakterden “bir tane ya da daha fazla bulunma” durumunu
belirtir. Örneğin “ab+c” kalıbı aşağıdaki yazılarla uyuşabilir:
abc
abbbbc
abbbbbbbbc
abbbbbbbbbbbbc
İşte '+' gibi değişik anlamlara gelen pek çok meta karakter bulunmaktadır. Zaten düzenli ifade dilinin öğrenilmesi büyük ölçüde
bu meta karakterlerin öğrenilmesi sürecidir.
Meta Anlamı
Karakterler
. (nokta) ‘\n’ dışındaki herhangi bir karakter
? Solundaki karakterden 0 tane ya da 1 tane
* Solundaki karakterden 0 tane ya da çok tane
+ Solundaki karakterden 1 tane ya da çok tane
{n} Burada n bir sayıdır. Solundaki karakterden tam olarak n tane anlamına helir.
{n,} Burada n bir sayıdır. Solundaki karakterden tam olarak en az n tane anlamına helir.
{n,m} Burada n ve m birer sayıdır. Solundaki karakterden en az n tane en fazla m tane anlamına gelir.
[ karakterler] Köşeli parantez içerisindeki karakterlerden herhangi birisi anlamına gelir.
[x-y] x ve y aralığındaki herhangi bir karakter
8
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[^ ] Köşeli parantez içerisindeki karakterlerden olmayan herhangi bir karakter
\w Herhangi bir alfanümerik karakter
\W Herhangi bir alfanümerik olmayan karakter
\s Herhangi bir boşluk karakteri
\S Herhangi bir boşluk olmayan karakter
\d Herhangi bir sayısal karakter
\D Herhangi bir sayısal olmayan karakter
$ Satırın sonunun belli karakterlerle sonlanması durumu (Örneğin “kaan$”)
^ Satırın başı belli karakterlerle sonlanması durumu (Örneğin ^kaan”)
(...) Gruplama amacıyla kullanılır. Böylece bunun sağındaki metakarakterler bu grup için anlam kazanır.
| Veya anlamına gelmektedir. Örneğin "ali|veli" yazı içerisindeki "ali" veya "veli" ile uyşur.
Düzenli ifadelerde kullanılan tüm meta karakterlerin bunlarla sınırlı olmadığını belirtelim. Diğer meta karakterler için
Python'ın standard kütüphanesindeki dokğmantasyondan faydalanabilirsiniz:
[Link]
Parantezlerin gruplama amacıyla kullanıldığında dikkat ediniz. Örneğin ([a-z]_){3} kalıbında {3} 'a' ile 'z' arasındaki
karakterlerden biri ile ‘_’ karakterinin birleşimlerinde üç tane olacağı anlamına gelmektedir (örneğin “x_y_z_” gibi).
Kalıp içerisindeki meta karakterlerin normal karakterlerle karışmaması için Python da dahil olmak üzere pek çok dil ve
kütüphanede ters bölüleme yöntemi kullanılmaktadır. Örneğin + bir meta karakterdir. Solundaki karakterden bir ya
da birden çok eşleşmeyi sağlar. Ancak biz bu meta karakteri ters bölü ile \+ biçiminde yazarsak bu gerçekten +
karakteri anlamına gelir. Şimdi bu meta karakterlerin anlamlarına ilişkin bazı örnekler verelim:
[+-]?[0-9]+\.?[0-9]* Gerçek sayı kalıplarıyla uyuşur. Örneğin “123”, “123.45”, “-1” gibi. (Ancak “.12” ya
da “.12” gibi kalıplarla uyuşmaz)
([0-9]{1,3}\.){3}[0-9]{1,3} Bölümleri “.” ile ayrılmış IP numaralarıyla uyuşur. Örneğin “[Link]” gibi.
Burada parantezler gruplama amacıyla kullanılmıştır. Dolayısıyla kalıbın [0-9]{1,3}
kısmı 0’dan 9’a kadar karakterlerden 1 ya da 2 ya da 3 tane olacağını belirtir.
kalıbın ([0-9]{1,3}\.){3} kısmı ise üç basamağa kadar sayı ve noktalaran toplamda
üç tane bulunacağını belişrtmektedir.
^\w* Satırların başındaki sözcüklerle uyuşur
Python'da düzenli ifadeler "re" isimli modülle gerçekleştirilmektedir. Bu modüldeki çeşitli fonksiyonlar düzenli
ifadelerle ilgili işlemler yaparlar.
re modülündeki findall fonksiyonu bütün uyuşan kalıpları bir liste olarak verir. Örneğin
import re
split fonksiyonu yazıyı belli bir düzenli ifade kalıbından parçalara ayırmaktadır. Bu fonksiyonu str sınıfının split
metodunun daha ileri bir biçimi olarak düşünebilirsiniz:
9
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import re
Örneğin:
import re
text = 'ali23423423veli2456564selami23487243678ayşe000012fatma'
pattern = r'\d+'
s = [Link](pattern, text)
print(s)
re modülündeki sub isimli fonksiyon yazı içersinde bulunan kalıpları başka bir yazıyla yer değiştirir. Tabii orijinal yazı
değiştirilmez yeni bir yazı verilmektedir. Fonksiyonun parametrik yapısı şöyledir:
Örneğin:
import re
text = 'ali23423423veli2456564selami23487243678ayşe000012fatma'
pattern = r'\d+'
Fonksiyondaki count parametresi baştan itibaren bulunan kaç kalıbın değiştirileceğini belirtir. Default durumda (yani
count=0) durumunda bulunan tüm kalıplar yer değiştirilmektedir.
re modülündeki search fonksiyonu bir yazı içerisinde düzenli ifadelerle arama yapmaktadır. Arama başarılı olursa
bulunan yere ilişkin bir match nesnesi elde edilmektedir. Fonksiyonun parametrik yapısı şöyledir:
Eğer arama sonucunda bir şey bulunamazsa fonksiyon None değeri ile geri döner. Örneğin:
import re
match nesnesinin start metodu kalıbın bulunduğu offset değerini end fonksiyonu da kalıbın bittiği offset değerinin bir
fazlasını vermektedir. Örneğin:
10
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import re
else:
print('bulunmadı')
span metodu kalıbın başlangıç ve bitiş offset'inin bir fazlasını bir demet biçiminde verir.
re modülünün match fonksiyonu da search fonksiyonu gibidir. Ancak kalıbı her zaman yazının başında arar. fullmatch
fonksiyonu ise yazının tamamen kalıptan oluşup oluşmadığına bakmaktadır. Hem match hem de fullmatch
fonksiyonları başarı durumunda match nesnesine başarısızlık durumunda None değerine geri dönmektedir.
re modülünün finditer fonksiyonu yazı içerisindeki kalıpları bulan dolaşılabilir bir nesne verir. Nesne her dolaşıldıkça
match nesneleri elde edilmektedir. Örneğin:
import re
Aramada (yani kalıpta) parantezler kullanılırsa bunlara "grup" denilmektedir. match nesnesi içerisinde gruplar group
isimli metotla elde edilirler. 0'ıncı grup her zaman eşleşen yazının tamamanı veririm. Sonraki her grup eşleşen yazının
parantez içerisindeki kısımlarını vermektedir. Örneğin:
import re
text = 'bugün hava çok güzel. 12/10/2009. Evet çok güzel 10/07/2009'
pattern = r'(\d\d)/(\d\d)/(\d\d\d\d)'
Match sınıfının __getitim__ metodu da yazılmıştır. group metodu yerine ilgili gruba köşeli parantez operatörleriyle
de erişebiliriz. Örneğin:
import re
text = 'bugün hava çok güzel. 12/10/2009. Evet çok güzel 10/07/2009'
pattern = r'(\d\d)/(\d\d)/(\d\d\d\d)'
İçeriğine hızlı bir biçimde erişilmek ve onları güncellemek için düzenlenmiş (organize edilmiş) bilgilerden oluşan
dosyalara veritabanı denilmektedir. Şüphesiz veritabanları işletim sistemlerinin sunduğu dosya sistemi ile ele alınıp
kontrol edilmektedir. Ancak veritabanları bilginin hızlı elde edilmesi için daha yüksek seviyeli algoritmik bir
organizasyon içermektedir. Ticari uygulamaların çok büyük çoğunluğu veritabanı kullanmaktadır. Bu nedenle
11
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
veritabanı işlemleri bunların performanslarını belirleyecek en önemli etkenlerden biri durumundadır. Tabii
veritabanları yalnızca ticari uygulamakrda değil aslında her türlü uygulamalarda karşımıza çıkabilmektedir.
1) Programcı algoritmik alt yapıya sahipse veritabanı işlemini yapan fonksiyonları, sınıfları ve metotları kendine uygun
bir biçimde gerçekleştirebilir.
2) Programcı firmalar ya da kurumlar tarafından oluşturulmuş olan veritabanı kütüphanelerini kullanabilir. (Örneğin
tarihsel açıdan bakıldığında DBVista, Btreve, CTree gibi pek çok veritabanı kütüphanesi kullanılmıştır.)
3) Programcı veritabanı işlemlerini yapmak için oluşturulmuş ismine "Veritabanı Yönetim Sistemi (Database
Management System)" denilen özel yazılımları kullanabilir. Bugün veritabanı işlemleri ağırlıklı olarak VTYS'ler
kullanılarak yapılmaktadır.
VTYS'ler veritabanı işlemlerini yapmak için geliştirilmiş özel yazılımlardır. Tipik olarak VTYS yazılımlarının özellikleri
şunlardır:
- VTYS'lerde aşağı seviyeli dosya işlemleriyle kullanıcının ilişkisi kesilmiştir. Kullanıcılar VTYS'lerle çalışırken yüksek
seviyeli soyutlamalar kullanırlar. Örneğin bilgilerin hangi dosyalarda nasıl tutulduğuyla kullanıcılar ilgilenmezler.
- VTYS'ler client-server mimariye uygun olarak tasarlanırlar. Yani onlara birden fazla kişi aynı anda erişip işlem
yaptırabilir.
- VTYS'ler belli bir güvenlik mekanizmasına sahiptir. Yani bunlardaki veritabanlarına erişmek için "user name",
"password" gibi bilgilere sahip olmak gerekir.
- VTYS'ler bilgilerin bozulmasına karşı dirençli biçimde tasarlanmışlardır. Örneğin elektrik kesilmesi gibi bir durumda
sistem kendini onarabilmektedir.
- VTYS'ler bize ilave bazı araçlar da sunarlar. Örneğin backup-restore gibi utility'lere sahiplerdir.
- VTYS'ler işleri kolay yapmak için "yönetim konsollarına" da sahiptirler. Yani bunlar üzerinde komut satırından ya da
görsel olarak işlemler yapılabilmektedir.
- VTYS'ler kullanıcıdan istekleri yüksek seviyeli deklaratif diller yoluyla almaktadır. Örneğin SQL bu amaçla kullanılan
bir dildir. Biz VTYS'nin veritabanına kayıt eklemesi için bir SQL komutunu veririz. VTYS o SQL komutunu inceler ve
bizim istediğimiz işlemi arka planda C/C++ ile yazılmış olan kodları çalıştırarak yapar. SQL yalnızca bizim VTYS'ye
istekte bulunmamız için kullanılmaktadır. Yoksa VTYS aşağı seviyeli disk işlemlerini C/C++'ta yazılmış motor kısmıyla
yapar.
- DB2 (IBM)
- Oracle (Oracle)
- Sql Server (Microsoft’un. Paralı fakat bedavası da var)
- MySql (Open Source, fakat Oracle tarafından yönetiliyor)
- PostgreSql (Open Source)
- H2 (Open Source)
- SqLite (Open Source, Embedded)
- Access (Jet Engine) (Microsoft, Embedded)
MySql'in Kurulumu
MySql'i kurmak için tek yapılacak şey server programı indirip yüklemektir. Kurulum basittir. Birtakım sorular default
değerlerle geçilebilir. Ancak kurulum sırasında bizden bir root parolası istenecektir. Bu parola yetkili olarak VTYS'ye
bağlanmak için gerekir. Server programın yanı sıra bir yönetim ekranı elde etmek için ayrıca "MySql Workbench"
programı da kurulabilir. “MySql Workbench” eskiden “MySql GUI Tools” isimli paketteki programların
birleştirilmesiyle oluşturulmuştur. Bu eski versiyonlar da hala kullanılmaktadır. “MySql GUI Tools” paketinin de
kurulmasını tavsiye ederiz.
SqlServer paralı bir üründür. Fakat bunun da "Express Edition" isminde bedava bir sürümü vardır. Bu sürüm
Microsoft'un sayfasından indirilip kurulabilir. Tıpkı MySql'de olduğu gibi Sql Server'da da bir yönetim programı da
vardır. Buna "Sql Server Management Studio" denilmektedir. Bunun da indirilip kurulması tavsiye edilir.
SqLite'ın Kurulumu
SqLite zaten tek bir DLL'den oluşmaktadır. Dolayısıyla kurulumu diye bir durum söz konusu değildir. Ancak yönetim
konsolu olarak pek çok alternatif vardır. Bu yönetim konsolları SQLite’ın işlemlerini yapan DLL’i de zaten
indirmektedir. DSQLite için yönetim konsollarından biri "FireFox Add On olarak çalışmaktadır. FireFoz üzerinden bu
hemen yüklenebilir. Diğer iki seçenek “SQLite Studio” ve “SqLite Management Studio” programlarıdır. Bunlar
bedavadır ve doğrudan yüklenebilir.
Çok fazla tablodan oluşan veritabanlarında tabloların tasarımı önemli olmaktadır. Tasarımda çeşitli ilkeler vardır.
Bunların en önemlisi "veri tekrarının ortadan kadırılmasıdır". Yani bir veri bir tabloda varsa başka bir tabloda
olmamalıdır. Buna "normalizasyon" da denilmektedir.
13
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
SQL Veri Türleri
SQL ISO tarafından standardize edilmiş bir dildir. Ancak VTYS'ler bu standarları desteklemekle birlikte kendilerine
özgü eklentilere ve komutlara da sahip olabilmektedir. Bu nedenle örneğin MySql'deki SQL ile SqlServer'daki SQL
arasında ayrıntılarda farklılıklar olabilir. SQL veri türleri tablo sütunlarını oluştururken o sütunlardaki bilginin
formatını belirlemekte kullanılmaktadır. Standart SQL veri türlerinin önemli olanları şunlardır:
INTEGER: Tamsayısal bilgileri tutan bir türdür. İstenirse kaç digitlik sayıların tutulacağı da belirtilebilir.
DATETIME: Hem tarih hem zaman bilgisini saklamak için kullanılan türdür.
TINYTEXT: Yazısal bilgileri tutmak için kullanılan türdür. (Tipik olarak 256 byte'a kadar)
TEXT: Yazısal bilgileri tutmak için kullanılan türdür. (Tipik olarak 64K'ya kadar)
TINYBLOB: Binary bilgileri tutmak için kullanılan türdür. (Tipik olarak 256 byte'a kadar)
BLOB: Binary bilgileri tutmak için kullanılan türdür. (Tipik olarak 64K'ya kadar)
LONGBLOB: Binary bilgileri tutmak için kullanılan türdür. (Tipik olarak 4GB'ye byte'a kadar)
Yukarıdaki türler pek çok VTYS'de vardır. Bunların dışında başka standart SQL veri türleri de bulunmaktadır. Ayrıca
yukarıda belirtildiği gibi her VTYS'de o VTYS'YE özgü diğerlerinde olmayan türler bulunuyor olabilir.
Bu bölümde temel bazı SQL komutları yüzeysel olarak ele alınacaktır. SQL kolay bir dildir. Şüphesiz ne kadar iyi SQL
bilinirse o kadar iyidir. Ancak biz kursumuzda temel SQL bilgisiyle işlerimizi yürüteceğiz. SQL büyük harf duyarlılığı
olan bir dil değildir. Ancak geleneksel olarak anahtar sözcükler büyük harflerle yazılırlar. Veritabanı isimleri, tablo
isimleri, alan isimleri vs. küçük harflerle isimlendirilmektedir. SQL komutları normal olarak ‘;’ atomu ile
sonlandırılmaktadır. Ancak pek çok VTYS komutlar ‘;’ ile sonlandırılmasa bile komutları kabul etmektedir.
14
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Anahtar Notlar: Bazı işlemler SQL komutlarıyla değil VTYS'ler için yazılmış yönetim programlarıyla görsel olarak da
yapılabilir. Aslında bu yönetim programları arka planda yine SQL komutlarını kullanarak işlemleri yaparlar. Fakat çoğu
zaman bazı işlemler için (örneğin veritabanı yaratmaki tablo oluşturmak vs.) yönetim ekranlarını kullanmak daha
pratiktir.
CREATE DATABASE Komutu: Bu komut veritabanı yaratmak için kullanılır. Komutun genel biçimi şöyledir:
Örneğin:
USE Komutu: Belli bir veritabanı üzerinde işlemler yapmak için öncelikle onun seçilmesi gerekir. Bu işlem USE
komutuyla yapılır. Komutun genel biçimi şöyledir:
USE <isim>;
Örneğin:
USE student;
SHOW DATABASES Komutu: Bu komut VTYS'de yaratılmış olarak bulunan veritabanlarını gösterir. Komutun genel
biçimi şöyledir:
SHOW DATABASES;
CREATE TABLE Komutu: Bu komut veritabanı için bir tablo yaratmak amacıyla kullanılır. Komutun genel biçimi
şöyledir:
Örneğin:
Bazen yanlışlıkla (ya da kasten) aynı isimli bir tablo yaratılmak istenebilir. Bu durumda yaratım sırasında VTYS hata
bildirecektir. İşte eğer tablo yalnızca yoksa yaratılmak isteniyorsa CREATE TABLE IF NOT EXISTS komutu kullanılır.
DROP TABLE Komutu: Bu komut tabloyu silmek için kullanılır. Genel biçimi şöyledir:
INSERT INTO Komutu: Bu komut bir tabloya bir satır eklemek için kullanılır. Komutun genel biçimi şöyledir:
INSERT INTO <tablo ismi> (sütun1, sütun2, sütun3,...) VALUES (değer1, değer2, değer3,...);
Örneğin:
WHERE Cümleciği: Pek çok komut bir WHERE kısmı içermektedir. WHERE cümleciği koşul belirtmek için kullanılır.
Koşullar karşılaştırma operatörleriyle oluşturulur. Mantıksal operatörlerle birleştirilebilir. Örneğin:
15
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
WHERE yas > 20 AND dogum_yeri = 'Eskişehir';
LIKE operatörü joker karakterleri kullanılarak belli bir kalıba uyan yazı koşulu oluşturur. Örneğin:
Burada adi_soyadi 'a' ile başlayanlar koşulu verilmiştir. % karakteri "geri kalanı herhangi biçimde olabilir" anlamına
gelir. Örneğin:
Burada içinde 'an' geçenler koşulu belirtilmiştir. LIKE operatörü default durumda ASCII karakterler için büyük harf
küçük harf duyarlılığı olmayan (case insensitive) koşul oluşturmaktadır. Yani örneğin:
koşulu adı soyadı 'a' ya da 'A' ile başlayanlar anlamına gelir. Ancak örneğin:
koşulunda yalnızca 'ş' ile başlayan isimler elde edilir, 'Ş' ile başlayanlar elde edilmez.
WHERE koşulunda '=' operatörü her zaman büyük harf küçük harf duyarlılığı ile karşılaştırma yapmaktadır. Örneğin:
DELETE FROM Komutu: Bu komut bir tablodan satır silmek için kullanılır. Genel biçimi şöyledir:
Örneğin:
Burada öğrenci tablosundan adi_soyadi 'Kaan Aslan' ve numarası 12345 olan kayıt silinecektir. Bu komut kullanılırken
dikkat etmek gerekir. Çünkü koşulu sağlayan ne kadar kayıt varsa hepsi tek hamlede silinmektedir.
UPDATE Komutu: Update komutu belli kayıtların alan bilgilerini değiştirmek amacıyla kullanılır. Örneğin ismi "Kağan"
olan bir kaydı "Kaan" olarak değiştirmek isteyebiliriz. Ya da bir müşterinin bakiyesini değiştirmek isteyebiliriz.
Komutun genel biçimi çöyledir:
16
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
UPDATE <tablo ismi> SET alan1 = değer1, alan2 = değer2, ... WHERE <koşul>;
Örneğin:
UPDATE student_info SET student_name = 'Kaan Kaplan' WHERE student_name = 'Kaan Aslan';
SELECT Komutu: Veritabanında belirli koşulları sağlayan kayıtlar SELECT komutuyla elde edilir. SELECT geniş bir
komuttur. Genel biçimi oldukça karmaşıktır. Burada SELECT komutunun tipik kullanımlarını ele alacağız.
Örneğin:
Burada ismi K ile başlayan tüm kayıtların yalnızca isimleri elde edilmiştir. Birden fazla sütun aralarına ',' konularak
beirtilir. Örneğin:
Burada okul ismi içerisinde "Eskişehir" geçen okulların isimleri ve adresleri elde edilmiştir.
Sütun listesi yerine '* ' karakteri kullanılırsa "tüm sütunlar" kastedilmiş olur. Örneğin:
Eğer SELECT komutunda WHERE cümleciği yoksa tüm kayıtlar elde edilir. Örneğin:
SELECT komutuna ORDER BY cümleciği eklenebilir. ORDER BY anahtar sözcüklerini sütun listesi izler. Böylece ilgili
kayıtlar burada belirtilen alanlara göre sıraya dizilir. Default dizilim küçükten büyüğe biçimdedir. ORDER BY
cümleciğini ASC ya da DESC izleyebilir. Örneğin:
SELECT student_name, school_id FROM student_info WHERE student_name LIKE 'K%' ORDER BY
student_name ASC, school_id DESC;
Burada sıralama öğrenci ismine göre artan sırada yapılmaktadır. Ancak aynı isimli öğrenciler varsa bunlar da kendi
aralarında okul id'lerine göre büyükten küçüğe elde edilecektir.
LIMIT cümleceği belli sayıda kaydın elde edilmesi için kullanılmaktadır. Örneğin:
SELECT student_name, school_id FROM student_info WHERE student_name LIKE 'K%' ORDER BY
student_name ASC, school_id DESC LIMIT 10;
Tabii bazı sorgular yalnızca SQL standardındaki komutlarla elde edilemeyebilirler. Örneğin ismi 5 karakter olan
öğrencilerin listesini almak isteyelim.
17
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
WHERE student_name = 5
gibi bir koiul geçersizdir. İşte her VTYS'nin birtakım özel fonksiyonları vardır. Bu fonksiyonlar sütun isimlerini
parametre olarak alıp birtakım değerler verirler. Örneğin:
MySql'de LENGTH fonksiyonu sütundaki yazıların karakter uzunluğunu vermektedir. İlgili VTYS'deki içsel
fonksiyonların neler olduğunu dokümanlardan öğrenebilirsiniz. Aşağıda SQLite'te kullanılan bazı özel fonksiyonları
görüyorsunuz:
abs
coalesce
ifnull
last_insert_rowid
length
lower
ltrim
max
min
printf
round
rtrim
substr
trim
upper
Veritabanı tabloları yaratılırken belli bir sütun PRIMARY KEY olarak belirlenebilir. Genellikle PRIMARY KEY sütunları
tamsayısal sütunlar olmaktadır. PRIMARY KEY kayıtlar arasında tek (unique) elemanlara sahip olmak zaorundadır.
Yani aynı değere sahip b PRIMARY KEY sütununa ilişkin birden fazla kayıt bulunamaz. Bu da bazı işlemleri çeşitli
bakımlardan kolaylaştırmaktadır. Eğer bir sütun tablo yaratılırken AUTOINCREMENT olarak belirlenmişse insert işlemi
sırasında bu sütuna değer atama zorunluluğu yktur ve bu durumda VTYS bu sütuna önceki son değerin bir sonraki
değerini otomatik olarak atar. İleride de görüleceği gibi yabancı anahtar (foreign key) sütunları bazen
AUTOINCREMENT yapılmaktadır.
İlişkisel veritabanlarında bire-cok (one-to-many) ilişki durumları farklı tablolar oluşturarak çözümlenmektedir.
Örneğin elimizde bir Ülkelere ilişkin bilgileri tutan bir "country" tablosu olsun. Biz bu ülkelerde konuşulan dilleri de
tutmak isteyelim. Bir ülkede tek bir dil konuşulmayabilir. Eğer biz "country" tablosuna bir "language" alanı eklersek
oraya yalnızca tek dil yazabiliriz. Bu tür durumlarda çoklu ilişki için ayrı bir tablo oluşturulur. İki tablo arasında geçişe
izin veren sütunlara ise "yabancı anahtar (foreign key)" denilmektedir. Örneğin:
country Tablosu
country_name country_continent
ABD Amerika
Belçika Avrupa
Fransa Avrupa
language Tablosu
country_name country_language
ABD İngilizce
ABD İspanyolca
18
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Belçika İngilizce
Belçika Almanca
Belçika Fransızca
Fransa Fransızca
Burada belli bir ülkede konuşulan dilleri bulmak için language tablosu kullanılacaktır. Örneğin:
Örneğin öğrenci veritabanında student_info tablosu öğrenci bilgisini tutuyor olsun (öğrencini ismi, doğum tarihi,
numarası vs.). Bir öğrencinin hangi okulda olduğu bu tabloda yer alabilir. Ancak o okulun da birtakım ayrıntıları varsa
okullar için de school_info gibi ayrı bir tablo kullanılmalıdır. Bu durumda her okula bir id numarası (ya da isim)
verilebilir. student_info tablosunda okulun yalnızca id'si ya da ismi bulunur. Öğrencinin okulu hakkında detaylı bilgi
school_info tablosundan alınır. Burada okul id'si (ya da ismi) "yabancı anahtar (foreign key)" durumundadır. Örneğin:
student_info Tablosu
school_info Tablosu
Birden fazla tablodan yabancı anahtarlar yoluyla bilgi toplama işlemine JOIN işlemi denir. JOIN işleminin birden fazla
biçimi olsa da (LEFT OUTER JOIN, RIGHT OUTER JOIN gibi) en çok kullanılan JOIN işlemi INNER JOIN işlemidir. INNER
JOIN sentaksı şöyledir:
SELECT <alan listesi> FROM <tablo ismi> INNER JOIN <diğer tablo ismi> ON <koşul>
JOIN işleminde tabloların sütun isimleri aynıysa bunları ayırrmak için bu isimleri tablo isimleriyle '.' operatörü ile
birleştirilir. Örneğin:
Burada öğrencilerin isimleriyle onların okudukları okulun isimleri elde edilmek istenmiştir. Yani hangi öğrencinin
hangi okula gittiği bilgisi elde edilmektedir. Örneğin:
INNER JOIN işlemi alternatif bir sentaksla da yapılabilmektedir. Bu sentaksta FROM kısmında birden fazla tablo ismi
belirtilir ve WHERE kısmında bağlantı koşılu belirtilmektedir. Bu sentaksa "implicit join syntax" denilmektedir.
Örneğin yukarıdaki join işleminin eşdeğeri şöyle de yazılabilir.
19
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
SELECT country.country_name, language.country_language FROM country, language WHERE
country.country_name = language.country_name
Join işlemleri kartezyen çarpım oluşturup bu kartezyen çarpımdan eleman seçmek esasına dayanmaktadır. Örneğin
student_info ve school_info tablolarının kartezyen çarpımı şu görünümdedir:
student_info.student_id|student_info.student_name|student_info.school_info|school_info.school_i
d|school_info.school_name|school_info.school_city
Burada student_info tablosoundaki tüm satırlarla school_info tablosundaki tüm satırlar tek tek birleştirilerek bize
verilecektir. Yani bize verilecek kayıt sayısı student_info ile school_info tablolarındaki kayıt sayısının çarpımı kadardır.
Şimdi sorguya WHERE cümleciğini ekleyelim:
Burada öğrencilerin isimleri, gittiklerin okulun isimleri ve o okulun hangi şehirde olduğu bilgisi satır satır elde edilmek
istenmiştir. Bu üç bilgi de üç ayrı tabloda bulunmaktadır. Bu komutun WHERE cümleceğini inceleyiniz. Aynı işlem
INNER JOIN sentaksıyla şöyle de yapılabilirdi:
Burada biz yalnızca en çok kullanılan INNER JOIN isimli join işlemini ele aldık. Diğer join işlemlerini ilgili
dokümanlardan inceleyebilirsiniz. Diğer join işlemleri de yine önce join işlemine sokulan tabloların kartezyen çarpımı
ile elde edilen sonuç üzerinde işlemler yapılarak gerçekleştirilmektedir.
1) Önce connect isimli fonksiyon çağrılarak VTYS ile bağlantı sağlanır. connect fonksiyonunun parametrik yapısı
şöyledir:
Fonksiyon en basit olarak sqlite dosyasının yol ifadesini parametre olarak alır. Geri dönüş değeri olarak da bir
connection nesnesi verir. Bu fonksiyonun geri döndürdüğü nesne Connection isimli bir sınıf türündendir. Default
durumda eğer ilgili veritabanı dosyası yoksa boş olarak yaratılmaktadır. Örneğin:
conn = [Link]('[Link]')
2) Connection nesnesinden Connection sınıfının cursor isimli örnek metodu çağrılarak bir cursor nesnesi elde edilir.
Cursor nesnesi de Cursor isimli bir sınıf türündendir. Örneğin:
cur = [Link]()
3) Cursor nesnesi ile Cursor sınıfının execute isimli örnek metodu çağrılarak SQL cümlesi VTYS'ye gönderilir. Bu
metodun parametresi gönderilecek SQL cümlesinin metnini almaktadır. Örneğin:
Kayıtlar üzerinde değişilik yapılan SQL cümlelerinin veritabanına yansıtılması için connection nesnesi ile commit
metodunun çağrılması gerekir. Örneğin:
[Link]()
4) İşlemler bitince connection nesnesi Connection sınıfının close isimli örnek metodu çağrılarak kapatılmalıdır.
Örneğin:
[Link]()
import sqlite3
conn = [Link]('[Link]')
cur = [Link]()
[Link]("CREATE TABLE IF NOT EXISTS student(student_no INTEGER PRIMARY KEY, student_name
TEXT)")
[Link]("INSERT INTO student VALUES(1634, 'Kaan Aslan')")
[Link]()
[Link]()
Veritabanı ile ilgili herhangi bir hatalı işlem exception'a yol açmaktadır. Örneğin execute metodunda biz SQL sentaks
hatası yaparsak execute bir exception fırlatır. Bu durumda kodumuzun exception kontrolü içerisinde çalıştırılması
21
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
uygun olur. sqlite3 modülündeki metotlar birkaç tür ile raise işlemi yapabilmektedir. Ancak bu exception türlerinin
hepsi [Link] sınıfından türetilmiştir. Bu durumda biz bu sınıfı kullanarak tüm sqlite3 exception'larını
yakalayabiliriz. Örneğin:
import sqlite3
conn = None
try:
conn = [Link]('[Link]')
cur = [Link]()
[Link]("CREATE TABLE IF NOT EXISTS student(student_no INTEGER PRIMARY KEY,
student_name TEXT)")
[Link]("INSERT INTO student VALUES(1634, 'Kaan Aslan')")
[Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Connection sınıfı bağlam yönetim protokolünü desteklediği için aynı işlemleri şöyle deyapabilirdik:
import sqlite3
conn = None
try:
with [Link]('[Link]') as conn:
cur = [Link]()
[Link]("CREATE TABLE IF NOT EXISTS student(student_no INTEGER PRIMARY KEY,
student_name TEXT)")
[Link]("INSERT INTO student VALUES(1345, Kaan Aslan')")
[Link]()
except [Link] as e:
print('Error', e)
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
[Link]("UPDATE student SET student_no = 1789 WHERE student_no = 1634")
[Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Bazen işleme sokulacak bilgiler statik olarak değil dinamik olarak elde edilmiş olabilir. Bu durumda ilgili SQL
cümlesinin yazısal biçimde oluşturulması gerekir. Tabii ileride ele alınacağı gibi parametrik kullanım da tercih
edilebilir. Aşağıdaki örnekte klavyeden ismi ve numarası alınan öğrenci veritabanına eklenmiştir:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
22
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
no = int(input('Eklenecek öğrencinin numarasını giriniz:'))
name = input('Eklenecek öğrencinin adını soyadını giriniz:')
Şimdi de SELECT cümlesi ile kayıtların nasıl elde edileceği üzerinde duralım. SELECT cümlesi cursor nesnesinin execute
fonksiyonu ile uygulandıktan sonra Cursor sınıfının fetchall örnek metodu ile tüm kayıtlar bir demet listesi olarak elde
edilebilmektedir. Demet listesinin elemanı olan demetler select edilen sütun bilgilerinden oluşmaktadır. Örneğin biz
student_info veritabanında öğrenci bilgilerini çekelim:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
[(245, 'Sacit Bulut'), (467, 'Necati Ergin'), (1345, 'Ali Serçe'), (1789, 'Kaan Aslan')]
Tabii biz bu listeyi for döngüsü ile dolaşıp uygun elemanları ugun formatta yazdırabiliriz. Örneğin:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
23
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
No Adı Soyadı
Aslında cursor nesnesinin kendisi de dolaşılabilir (iterable) bir nesnedir. Dolayıyla biz fetchall yapmadan doğrudan
cursor nesnesini de for döngüsü ile dolaşabiliriz. Örneğin:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Cursor sınıfının fetchone isimli örnek metodu her defasında yalnızca sıradaki kaydı bize demet olarak verir. Kayıt
listesinin sonuna geelindiğinde fetchone None değerine geri dönmektedir. Örneğin:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Diğer bir fetch metodu da fetchmany isimli metottur. Bu metot en fazla parametreyle girdiğimiz miktarda kaydı bize
bir demet listesi olarak verir. Yine kayıt kalmadığında bu metot da None değerine geri dönmektederi. Örneğin:
import sqlite3
conn = None
try:
conn = [Link]('SampleDatabases/[Link]')
cur = [Link]()
24
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]("SELECT * FROM student")
print(f"{'No':<10}{'Adı Soyadı'}\n")
while True:
result = [Link](5)
if not result:
break
for no, name in result:
print(f'{no:<10}{name}')
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Pekiyi fetchall, fetchone ve fetchmany metotları arasındaki farklılıklar nelerdir? VTYS'lerden sorgulama yapıldığında
kütüphaneler VTYS'den kayıtları parça parça çekebilmektedir. İşte fetchall ile tek hamlede bütün kaydın çekilmesi
bazı durumlarda hem zaman kaybına hem de büyük miktarda bellek kullanımına yol açabilmektedir. Bu nedenle kayıt
sayısı çok yüksekse onların fetchone ile teker teker ya da fetchmany ile parça parça elde edilmesi uygun
olabilmektedir.
execute metodunda diğer farmework'lerde olduğu gibi yer tutucular kullanılabilmektedir. Yer tutucu olarak ? ya da
"isim:" kullanılabilir. Eğer yer tutucu kullanılmışsa buna karşılık execute metodunda SQL cümlesinden sonraki
argümanda bu yer tutucularla eşleşen bir demet girmek gerekir. Örneğin:
Burada ilk ? no ile ikinci ? ise name ile eşleşmiştir. Yani adeta bu soru işaretlerinin yerini no ve name nesnelerinin
içerisindeki değerler almış gibi bir etki oluşmaktadır. Örneğin:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
Yer tutucu olarak '?' yerine ":isim" de kullanılabilir. Bu durumda bu isimlerin ve ona karşı gelen değerlerin bir sözlük
nesnesi ile execute metoduna verilmesi gerekir. Sözlüğün anahtarı ":isim" yer tutucusundaki "isim", değeri de onun
yerine yerleştirilecek değeri belirtir. Örneğin:
Burada :no yer tutucusu yerine sözlükteki 'no' anahtarının değeri :name yer tutucusu yerine ise sözlükteki 'name'
anahtarının değeri yerleştirilecektir. Örneğin:
25
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
Biz şimdiye kadar önce connection nesnesinden cursor nesnesini elde ettik ve bu cursor nesnesi ile execute işlemi
uyguladık. Aslında doğrudan connection sınıfının da bir execute örnek metodu vardır. Bu metot zaten kendi
içerisinde cursor nesnesini elde edip onunla execute işlemi yapar. Connection sınıfının bu bahsettiğimiz execute
metodu bize cursor nesnesini vermektedir. Yani biz işlemlere bu executre metodunun yarattığı cursor nesnesi ile
devam edebiliriz. Örneğin:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]("SELECT * FROM student")
print(f"{'No':<10}{'Adı Soyadı'}\n")
for no, name in cur:
print(f'{no:<10}{name}')
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Şimdi de veritabanı üzerinde çeşitli işlemler yapan küçük bir program yazalım:
import sqlite3
def get_option():
print('1) Kayıt Ekle')
print('2) Kayıt Listele')
print('3) Kayıt sil')
print('4) Çık')
while True:
try:
result = int(input('\nSeçiminiz:'))
if result >= 1 and result <= 4:
break
print('Giriş geçersiz!')
except:
print('Giriş geçersiz!')
return result
def add_record(cur):
26
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
while True:
try:
no = int(input('No:'))
break
except:
print('Numara geçersiz! Yeniden Numara giriniz:')
def list_record(cur):
cond = input('Koşul cümlesini giriniz:')
sql = 'SELECT student_no, student_name FROM student '
if cond != '':
sql += 'WHERE {}'.format(cond)
try:
[Link](sql)
print('\n-------------------------')
print(f"{'No':<10}{'Adı Soyadı'}\n")
for no, name in cur:
print(f'{no:<10}{name}')
print('--------------------------\n')
except:
print('Listeleme işlemi başarısız!')
def delete_record(cur):
cond = input('Koşul cümlesini giriniz:')
sql = 'DELETE FROM student WHERE {}'.format(cond)
print(sql)
try:
[Link](sql)
[Link]()
if [Link] > 0:
print('{} kayıt silindi\n'.format([Link]))
else:
print('Herhangi bir kayır silinemedi!\n')
except:
print('Kayıt silinemedi!..\n')
def main():
try:
conn = [Link]('[Link]')
cur = [Link]()
while True:
option = get_option()
if option == 4:
break
{1: add_record, 2: list_record, 3: delete_record}[option](cur)
finally:
[Link]()
main()
27
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Son eklenen kayda ilişkin (yani INSERT INTO komutuyla son eklenen autoincrement alana ilişkin) autoincrement alan
değeri Cursor sınıfının lastrowid örnek özniteliği ile elde edilebilir. SqLite'ta PRIMARY KEY sütunu default olarak zaten
"autoincrement" durumdadır.
Daha önce de belirttiğimiz gibi her ne kadar SQLite SQL standartlarını destekliyorsa da SQLite'ın aslında standart SQL
türlerinden daha az sütun türü vardır. Yani bazı türler SQLite'ta aslında başka bir tür olarak ifade edilmektedir.
Örneğin SQLite'ta VARCHAR alan aslında TEXT alan olarak içsel biçimde ifade edilmektedir. SQLite'taki içsel sütun
türleri ile bunun Python karşılıkları şöyledir:
Insert, update işlemlerinden sonra yapılan değişikliklerin veritabanına yansıtılması için commit işleminin yapılması
gerektiğini daha önce belirtmiştik. Pekiyi bu commit işleminin anlamı nedir? Commit işlemi veritabanlarında
transaction oluşturmak için kullanılmaktadır. Transaction terimi veritabanlarında bir grup işlemin kesintisiz tek bir
işlemmiş gibi işletilmesi anlamına gelir. Eğer transcaction kavramı olmasaydı yani yapılan değişiklikler bir bütün
olarak değil parça parça yapılsaydı veritabanının tutarlılığı ve bütünlüğü bozulabilirdi. Örneğin bir programın iki ayrı
tabloda birbirleriyle ilişkili iki kaydı güncellemek istediğini düşünelim. Birinci tablo için UPDATE sql cümlesi ile
güncelleme işlemi yapıldığında daha program ikinci tabloda güncelleme yapamadan başka bir program daha hızlı
davranıp bu iki kaydı güncelleyebilir. Bu durumda ilk program diğer kaydı güncellediğinde iki tablodaki kayıt arasında
tutarsızlık oluşabilir. Bunu engellemenin tek yolu bu iki işlemin başka bir program araya girmeden sanki tek bir işlem
gibi "atomik" bir biçimde yapılmasını sağlamaktır. İşte commit işleminden önce uygulanan SQL cümleleri gerçek
anlamda veritabanına yansıtılmamaktadır. Commit işlemiyle en son commit'ten bu yana yapılan işlemler bir bütün
olarak atomik bir biçimde veritabanına yansıtılmaktadır. Örneğin:
[Link]('UPDATE .....')
[Link]('UPDATE .....')
[Link]()
Bazen bir grup işlem yapılırken aradaki bir işlemde sorun çıkabilir. Bu durumda her ne kadar işlemler henüz commit
ile tablolara yansıtılmamış olsa da bunların geçersiz hale getirilmeleri gerekmektedir. İşte bu geçersiz hale getirme
işlemine veritabanlarında "rollback" denilmektedir. Rollback işlemi Python'da Connection sınıfının rollback
metoduyla yapılmaktadır. Örneğin:
try:
...
[Link]('....')
[Link]('.....')
[Link]('....')
...
except [Link]:
[Link]()
Biz şimdiye kadar SQL komutlarını Cursor sınıfının execute metodula yaptık. Cursor sınıfının execute metodunda biz
yalnızca tek bir SQL cümlesini uygulayabiliriz. Halbuki Cursor sınıfının executemany isimli bir metodu daha vardır. İşte
executemany birden fazla cümlenin uygulanmasına olanak sağlayan bir metottur. Ancak executemany'de bu işlem
aralarına ';' konulmuş ayrı SQL cümleleri ile değil parametrik olarak yapılmaktadır. Yani executemany'de yine tek bir
SQL cümlesi bulundurulur. Ancak bu cümle "?" ya da ":isim" biçiminde yer tutucularla oluşturulmuş olmalıdır.
executemany metodunun ikinci parametresi tipik olarak bir demet listesi olur (aslında biribirini içeren iki dolaşılabilir
28
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
nesne de kullanılabilmektedir). executemany bu listedeki demetleri yer tutucularla eşleştirerek birden fazla kez
verilen SQL cümlesini çalıştırır. Örneğin:
[Link]("INSERT INTO student VALUES(?, ?)", [(251, 'Birsen Saban'), (252, 'Vicdan
Özer')])
Burada iki kez INSERT INTO cümlesi farklı değerlerle çalıştırılacaktır. executemany metodu zaten elimizde toplu halde
bir bilgi varsa tercih edilmektedir. Örneğin bir csv dosyasının içeriğini okuyup tek hamlede onu executemany ile
veritabanına ekleyebiliriz:
result = readcsv('[Link]')
[Link]("INSERT INTO student VALUES(?, ?)", result)
executemany metodunda yer tutucu olarak ":isim" tekniği de kullanılabilir. Bu durumda ikinci parametre tipik olarak
bir sözlük dizisi olmalıdır. Örneğin:
executemany yukarıda gördüğünüz gibi tek bir SQL cümlesinin birden fazla veri grubu için yinelemeli bir biçimde
çalıştırılmasını sağlıyordu. Fakat bazen gerçekten birden fazla farklı SQL cümlesinin çalıştırılmasını isteyebiliriz. İşte
bunun için executescript metodu kullanılmaktadır. Bu metotta yer tutucular kullanımaz. Birden fazla SQL cümleleri
aralarına ';' getirilerek yazılır. Örneğin:
import sqlite3
conn = None
try:
conn= [Link]('[Link]')
cur = [Link]()
[Link]("""
INSERT INTO student VALUES(623, 'Sami Ercan');
INSERT INTO student VALUES(854, 'İhsan Derman');
INSERT INTO student VALUES(778, 'Necmiye Artar');
""")
[Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Bazen yabancı anahtar eşliğinde birden fazla tabloya kayıt eklemek gerekebilir. Tipik olarak bunun için INSERT INTO
komutunda aynı zamanda sorgulama da yapılmaktadır. Örneğin student veri tabanında "student" ve "school" isimli
iki tablo olsun. "student" tablosu öğrencilerin bilgilerini, school tablosu ise okulların bilgilerini tutuyor olsun. İki tablo
arasındaki bağlantnın school_id isimli yabancı anahtarla yapıldığını varsayalım:
29
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bu tür durumlarda veri girişi yapılırken bunların ayrı ayrı yapılması (ayrı menülerden ya da ayrı pencerelerden) uygun
olur. Yani veriyi giren kişi önce okulları oluşturup sonra öğrencileri kaydedebilir. Böylece öğrenci kaydedilirken okullar
oluşturulmuş durumda olur. Tabii bu iki işlem birlikte de yapılabilir. Örneğin:
import sqlite3
conn = None
try:
conn = [Link]('[Link]')
cur = [Link]()
[Link]("""
CREATE TABLE IF NOT EXISTS student(student_no INTEGER PRIMARY KEY, student_name
VARCHAR(64), school_id INTEGER);
CREATE TABLE IF NOT EXISTS school(school_id INTEGER PRIMARY KEY AUTOINCREMENT,
school_name VARCHAR(64));
""")
[Link]()
[Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Burada ikinci INSERT işleminin VALUES kısmında SELECT işleminin yapıldığına dikkat ediniz. Böyelce örnekte önce
okul bilgisi school tablosuna insert edilmiş daha sonra da buradaki school_id değeri kullanılarak öğrenci bilgileri
student tablosuna insert edilmiştir. Ancak bu örnekte şöyle bir kusur vardır: Burada aynı okul birden fazla kez school
tablosuna insert edilebilmektedir. Pekiyi bunu nasıl engelleyebiliriz? İlk akla gelen yöntem önce bir SELECT
sorgulaması yapıp okulun daha önce school tablosuna insert edilip edilmediğine bakmak, eğer okulbu tabloya insert
edilmemişse gerçekten insert işlemi yapmaktır. Buradaki işlem biraz uzun olduğu için VTYS'ler değişik biçimlerde bu
işlemi içselleştirmişlerdir. Bazı VTYS'lerde SQL INSERT INTO komutuna WHERE ... NOT EXITS gibi koşullar
getirilebilmektedir. Ancak Sqlite bunun yerine UNIQUE sütun kavramını kullanmaktadır. Sqlite'ta bir sütun UNIQUE
yapılırsa eğer INSERT INTO yerine INSERT OR IGNORE TO kullanılırsa zaten ilgili kayıt daha önce varsa yeniden insert
işlemi yapılmamaktadır. Yukarıdaki kodu Sqlite için şöyle revize edebiliriz:
30
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import sqlite3
conn = None
try:
conn = [Link]('[Link]')
cur = [Link]()
[Link]("""
CREATE TABLE IF NOT EXISTS student(student_no INTEGER PRIMARY KEY, student_name
VARCHAR(64), school_id INTEGER);
CREATE TABLE IF NOT EXISTS school(school_id INTEGER PRIMARY KEY AUTOINCREMENT,
school_name VARCHAR(64), UNIQUE(school_name));
""")
[Link]()
[Link]()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Sqlite'ta "bellek veritabanı (memory database)" diye isimlendirilen bir veritabanı çeşidi de vardır. Bellek veritabanları
veritabanı ismi için ":memory:" kullanılarak yaratılırlar. Bunlar tamamen ana bellekte oluşturulmaktadır. Dolayısıyla
da program sonlandığında bu bilgiler (eğer başka bir yere yazılmamışsa) yok edilirler. Bellek veritabanları size ilk
bakışta "gereksiz" gibi gelebilir. Ancak uzun süreli çalışan bazı programlar verileri de zaten saklama niyetleri yoksa bu
özelliği kullanabilmektedir. Şüphesiz bellek veritabanları disk tabanlı gerçel veritabanlarına göre çok daha hızlı
çalışmaktadır.
Sqlite'ta tarih ve zaman için özel bir veri türü bulundurulmamıştır. Ama Sqlite SQL standartlarını desteklemek için
DATE, TIME ve DATETIME gibi sütun türlerini kabul etmektedir. Sqlite'ta tarih ve zaman bilgileri aslında arka planda
tamamen TEXT, INTEGER ya da REAL türleriyle turulmaktadır. Başka bir deyişle biz aslında bir tarih bilgisini "yyyy-aa-
gg" biçiminde bir yazı gibi bir sütunda saklayabiliriz. Sonra o sütundan bilgileri alarak yeniden onu tarih bilgisine
dönüştürebiliriz. Ya da örneğin tarih ve zaman bilgisini time modülündeki 01/01/1970'ten geçen saniye sayısı olarak
REAL bir alanda tutabiliriz. Örneğin:
import sqlite3
conn = None
try:
conn = [Link](':memory:')
cur = [Link]()
[Link]("CREATE TABLE student(student_no INTEGER, student_name VARCHAR(64),
student_bdate DATE)")
31
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
for no, name, bdate in cur:
print(no, name, bdate, sep=', ')
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
Bu örnekte tarih bilgisi veritabanına tamamen yazısal biçimde girilmiştir. Daha önceden de belirttiğimiz gibi aslında
Sqlite'ın DATE, TIME ve DATETIME biçiminde özel türleri yoktur. Bunlar TEXT türüne karşılık gelmektedir. Tabii biz
yazısal olarak sakladığımız tarihi yeniden datetime müdülündeki date sınıfının fromisoformat metodu ile
[Link] türüne dönüştürebiliriz. Örneğin:
...
[Link]("SELECT * FROM student")
import datetime
Eğer execute ya da executemany metotlarında yer tutucu kullanıyorsak bu duurmda DATE, TIME, DATETIME alanlar
için [Link], datetimetime ve [Link] nesnelerini girebiliriz. Örneğin:
import sqlite3
import datetime
conn = None
try:
conn = [Link](':memory:')
cur = [Link]()
[Link]("CREATE TABLE student(student_no INTEGER, student_name VARCHAR(64),
student_bdate DATE)")
while True:
no = int(input('Öğrenci numarasını giriniz:'))
if not no:
break
name = input('Öğrencinin adını ve soyadını giriniz:')
bdate = [Link](input('Öğrencinin doğu tarihini ISO formatında
giriniz:'))
[Link]("INSERT INTO student VALUES(?, ?, ?)", (no, name, bdate))
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
32
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bazen programcı bu ratih ve zaman bilgileri üzerinde sorgulama da yapmak isteyebilir. Bu durumda Sqlite'ın tarih ve
zaman fonksiyonlarından faydalanılmalıdır. Örneğin 01/01/2010'dan sonra doğmuş kişileri SELECT cümlesiyle şöyle
elde edebiliriz:
Şimdi de Connection sınıfının iki ilginç örnek özniteliği hakkında bilgi verelim. Sınıfn text_factory isimli örnek
özniteliğine tipik olarak bir fonksiyon nesnesinin adresi atanır. Sınıf SELECT ile yapılan sorgulamadan elde edilen
yazısal sütunlar için bu fonksiyonu çağırıp bu fonksiyondan geri döndürülen değeri bize vermektedir. Böylece ilgili
yazısal sütunlar (TEXT sütunlar) istenirse bu fonksiyon tarafından dönüştürülebilirler. text_factory örnek özniteliğine
girilecek fonksiyonun tek parametresi olması gerekir. Bu parametre ilgili sütundaki yazıdır. Kütüphane bu fonksiyona
yazıyı str nesnesi olarak değil bytes nesnesi olarak fonksiyıona geçirmektedir. Örneğin:
import sqlite3
def rev_converter(text):
text = str(text, 'UTF-8')
return text[::-1]
conn = None
try:
conn = [Link]('[Link]')
cur = [Link]()
[Link]("CREATE TABLE IF NOT EXISTS student(student_no INTEGER, student_name TEXT)")
conn.text_factory = rev_converter
while True:
no = int(input('Öğrencinin numarasını giriniz:'))
if not no:
break
name = input('Öğrencinin adını giriniz:')
[Link]("INSERT INTO student VALUES(?, ?)", (no, name))
[Link]()
finally:
if conn:
[Link]()
Burada rev_converter fonksiyonu yazıyı ters çevirmiştir. Fonksiyona geçirilen yazının bytes türünden olduğuna ve
onu bizim str türüne dönüştürdüğümüze dikkat ediniz. Tabii text_factory örnek özniteliğine biz fonksiyonu lambda
ifadesi olarak da girebilirdik:
Aslında bu örnek özniteliğine biz çağrılabilir (callable) herhangi bir nesne girebiliriz. Örneğin bu örnek özniteliğine
__call__ metodu bulunan bir sınıf nesnesi girilebilir:
class Reverser:
def __call__(self, s):
return str(s, 'UTF-8')[::-1]
...
conn.text_factory = Reverser()
conn = None
try:
conn = [Link]('[Link]')
cur = [Link]()
while True:
no = int(input('Öğrencinin numarasını giriniz:'))
if not no:
break
name= input('Öğrencinin adını ve soyadını giriniz:')
path = input('Öğrenci fotoğrafına ilişkin dosyanın yol ifadesini giriniz:')
[Link]()
import PIL
import io
import IPython
bio = [Link](photo)
image = photo_data = [Link](bio)
[Link]((200, 300))
[Link](image)
print()
except [Link] as e:
print('Error', e)
finally:
if conn:
[Link]()
MySQL oldukça yaygın kullanılan bir VTYS'dir. Açık kaynak kodlu ve ticari uygulamaların büyük çoğunluğu bunu
kullanmaktadır. MySQL client-server çalışan gerçek bir VTYS'dir. Yukarıda da belirtildiği gibi SQLite Python'ın belli bir
sürümünden sonra standart kütüphaneye eklenmiştir. Ancak MySQL ile işlemler yapan modüller Python'ın standart
kütüphanesinde bulunmamaktadır.
Yerel makinemize Windows'ta MySql kurmak oldukça kolaydır. Kurulum sırasında bizden root kullanıcısı için bir
parola istenecektir. Server kurulumunun yanı sıra "MySql Workbench" denilen GUI aracının ve bunun eski biçimi olan
"MySql GUI Tools" araçlarının kurulması tavsiye edilir. Windows için tüm kurulumların Inter adresleri şöyledir:
Pyton MySql Connector komut satırında "pip" denilen Python yardımcı programıyla Internet'ten yüklenebilir. pip
yazılımların yüklü olduğu çeşitli "repository"lere bakarak oradan yüklemeyi yapmaktadır. pip programı Python'ın
içerisinden -m seçeneğiyle de çalıştırılabilmektedir. MySql Connector yüklemesi şöyle yapılabilir:
Insall edilecek paket isminin "mysql-connector-python" olduğuna dikkat ediniz. Yanlışlıkla "mysql-connector"
paketini install etmediğinize emin olunuz.
pip kullanırken dikkat edilmesi gereken en önemli nokta şudur: Sistemde birden fazla Python sürümü yüklü
olabilmektedir. Her Python sürümünün ayrı bir pip paket yöneticisi vardır. Biz hangi Python versiyonuna paket
yüklemesini yapacaksak o versiyona ilişkin pip ya da python programını çalıştırmamaız gerekir. Eğer biz PyCharm
IDE'sini kullanıyorsak Settings/Project/Project Interpreter menüsünden PyCharm'ın o proje için kullandığı
yorumlayıcıyı görebiliriz:
Tabii yükleme işlemi PyCharm'da aslında doğrudan yukarıdaki menüdeki + sembolünden de yapılabilir. pip ve diğer
araçların ayrıntılı kullanımları ayrı bir başlık halinde ele alınacaktır.
Python'da MySql kullanımı aslında SQLite kullanımı ile aynıdır. Tabii bazı detaylar vardır. MySQL'de connect
fonksiyonu ile VTYS'ye bağlanırken SQLite'ta olduğu gibi dosya ismi belirtilmez. MySQL client-server çalışan gerçek bir
VTYS olduğu için "host name", user-name", "password" bilgileri de bağlantı için gerekmektedir. Kullanım hakkında
genel bir tutorial W3C sayfasında aşağıdaki adreste bulunmaktadır:
[Link]
connect işlemi sırasında conenct fonksiyonuna host, user, passwd ve daatabase parametreleri girilmelidir. Yine
bağlantının kesilmesi Connectin close metoduyla yapılır:
conn = None
try:
conn = [Link](host='localhost', user='root', passwd=csd1993, database='student')
print('Ok')
except [Link] as e:
35
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print(e)
finally:
if conn:
[Link]()
Mysql işlemlerinde pek çok farklı türden exception oluşabilmektedir. Ancak bu exception sınıflarının hepsi
[Link] sınıfından türetilmiştir. Dolayısıyla MySql'de oluşabilecek tüm exception'lar
[Link] sınıfı ile yakalanabilir.
Bağlantıdan soınraki temel çalışma biçimi Sqlite ile aynıdır. Yani yine bir cursor nesnesi elde edilir ve onun üzerinde
execute işlemi uygulanır. Ancak detaylarda farklılık vardır. Örneğin:
conn = None
try:
conn = [Link](host='localhost', user='root', passwd=csd1993, database='student')
cur = [Link]()
[Link]("SELECT * FROM student")
for no, name in cur:
print(no, name)
except [Link] as e:
print(e)
finally:
if conn:
[Link]()
Mysql'in Sqlite'ta göre çok daha fazla özellikleri olduğunu anımsatmak istiyoruz. MySql'de standart SQL komutlarının
yanı sıra MySql'e özgü pek çok komut da kullanılabilmektedir.
Python'ın [Link] modülünde execute işlemindeki yer tutucular %s ile belirtilmektedir. Örneğin:
conn = None
try:
conn = [Link](host='localhost', user='root', passwd='csd1993', database='student')
cur = [Link]()
while True:
no = int(input('No:'))
if no == 0:
break
name = input('Adı Soyadı:')
[Link]("INSERT INTO student VALUES(%s, %s)", (no, name))
[Link]()
[Link]("SELECT * FROM student")
for no, name in cur:
print(no, name)
except [Link] as e:
print(e)
finally:
if conn:
[Link]()
Örneğin:
conn = None
try:
conn = [Link](host='localhost', user='root', passwd='csd1993')
36
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
cur = [Link]()
[Link]("CREATE DATABASE IF NOT EXISTS school")
[Link]("USE school")
[Link]("CREATE TABLE IF NOT EXISTS school(school_id INTEGER PRIMARY KEY
AUTO_INCREMENT, school_name VARCHAR(45))")
while True:
name = input('Okul İsmi:')
if name == '':
break
[Link]("INSERT INTO school(school_name) VALUES(%s)", (name,))
[Link]()
[Link]("SELECT * FROM school")
for no, name in cur:
print(no, name)
except [Link] as e:
print(e)
finally:
if conn:
[Link]()
Python'da Microsoft firmasının "Sql Server" isimli VTYS'si de benzer biçimde kullanılmaktadır. Bunun için en çok
tercihj edilen connector paketi pyodbc isimli pakettir. Paketin kurulumu şöyle yapılabilir:
MAC OS X sistemlerinde ve Linux sistemlerinde ayrıca ODBC sürücüsünün (ODBC driver) de kurulması gerekmektedir.
ODBC sürücüsünün MAC OS X sistemlerindeki kurulumu için Microsoft'un aşağıdaki dokümanına başvurunuz:
[Link]
macos?view=sql-server-ver15
Kurulum tamamlandıktan sonra VTYS'ye bağlanak için "bağlantı yazısının (connection string)" uygun bir biçimde
oluşturulması gerekir. pyodbc modülü connect fonksiyonunda tek bir bağlantı yazısı yerine isimli argümanları da
kabul etmektedir. Örneğin:
import pyodbc
pyodbc de çeşitli sorunlarda pek çok farklı türden exception nesnesi fırlatabilmektedir. Ancak bu exception
nesnelerinin hepsi [Link] sııfından türetilmiştir. Yani tüm exception'lar buy sınıfla yakalanabilirler. pyodbc
paketinde de işlemler diğerindeki gibi yapılmaktadır. Örneğin:
import pyodbc
conn = None
try:
conn = [Link](driver='/usr/local/lib/[Link]',
server='[Link]', database='AVS', uid='xxxxx', password='yyyyy')
cur = [Link]()
[Link]("SELECT * FROM posts WHERE post_id > 4000")
for t in cur:
print(t)
except [Link] as e:
print(e)
37
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
finally:
if conn:
[Link]()
DBM anahtar değer tutan bir veritabanıdır. Biz DBM veritabanını sözlüklerin diskte oluşturulmuş ve kalıcı yapılmış bir
biçimi gibi düşünebiliriz. DBM veritabanı ilk kez 1979 yılında Ken Thompson tarafından geliştirilmiştir. Daha sonra bu
veritabanının pek çok farklı versiyonu oluşturulmuştur. Örneğin ilk kez BSD'lerde kullanılan Berkeley DB veritabanı
DBM benzeri bir veritabanıdır. Benzer biçimde GNU projesinde de bu veritabanı ayrıca gerçekleştirilmiştir. Ayrıca
Oracle'ın mülkiyetinde olan ndbm veritabanı da tamamen benzer bir işlev sunmaktadır.
Python'daki DBM veritabanı aslında bir arayüz gibidir. Yani yukarıda sözü edilen bazı veritabanlarını (sistemde hangisi
varsa) aynı arayüzle kullanabilmektedir.
1) Önce veritabanı dbm modülündeki open fonksiyonuyla açılır. open fonksiyonunun parametrik yapısı şöyledir:
Fonksiyonun birinci parametresi veritabanı dosyasının ismini belirtir. Bazı sistemler burada belirtilen isme ilişkin iki
dosya oluştururken bazıları tek dosya oluşturmaktadır. İkinci parametre veritabnını açış modunu belirtir. Bu mod
aşağıdakilerden biri olabilir:
[Link] fonskiyonunun son parametresi UNIX/Linux sistemlerindeki dosyanın erişim haklarını belirtmektedir. Bu
parametre default olarak geçilebilir. Fonksiyon başarı durumunda bize işlem yapmak için kullanacağımız bir DBM
nesnesi verir. Örneğin:
2) Veritabanı yaratıldıktan ya da açıldıktan sonra artık anahtar-değer çitleriyle işlem yapabiliriz. Örneğin:
import dbm
d = [Link]('testdbm', 'c')
print(type(d))
d['ali'] = '100'
38
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
d['veli'] = '200'
d['selami'] = '300'
d['ayşe'] = '400'
d['fatma'] = '500'
print("d['ayşe'] = {}".format(d['ayşe']))
print("d['fatma'] = {}".format(d['fatma']))
Görüldüğü gibi veritabanına yeni bir anahtar-değer çifti eklemek için tek yapılacak şey sözlüklerde olduğu gibi köşeli
parantez operatörüyle atama yapmaktır. Değer yine köşeli parantez operatörüyle anahtar verilerek elde edilir.
3)[Link] fonksiyonunun geri döndürdüğü dbm nesnesinin keys isimli örnek metodu bize veritabanındaki tüm
anahtarları vermektedir. Örneğin:
import dbm
d = [Link]('testdbm', 'c')
print(type(d))
Ancak values biçiminde bir metot yoktur. keys metodunun dolaşılabilir bir nesne geri verdiğine dikkat ediniz.
Örneğin:
import dbm
d = [Link]('testdbm', 'c')
print(type(d))
keys = list([Link]())
print(keys)
dbm veritabanı içsel olarak anahtar ve değeri her zaman bytes nesnesi biçiminde tutup bize vermektedir. Ancak
arayüz anahtar ve değer olarak bytes nesnesinin yanı sıra bizden str de kabul edebilmektedir. Bu durumda bizden
alınan string default encode metodu ile UTF-8 formatına dönüştürülerek bytes nesnesi oluşturulur. dbm köşeli
parantez ile anahtarı verdiğimizde değeri bize her zaman bytes nesnesi olarak verir. Örneğin:
>>> d['ayşe']
b'400'
>>>
Örneğin:
Eğer biz veritabanında anahtar ve değer olarak yazı tutacaksak UTF-8 encoding'i ile bytes nesnesini yeniden yazıya
dönüştürmek zorunda kalabiliriz. Örneğin:
>>> d['ağrı'].decode()
'dağ'
DBM veritabanında yine del ve in operatörleri sözlüklerde olduğu gibi çalışmaktadır. Örneğin:
>>> 'veli' in d
True
Örneğin:
>>> len(d)
6
DBM veritabanında herhangi bir uygunsuz durumla karşılaşıldığında [Link] isimli bir sınıfla raise işlemi
yapılmıştır. Tabii biz exception'ları parametresiz except bloğu ile ya da taban sınıf olan Exception parametreli
exception bloğu ile yakalayabiliriz. Örneğin:
import dbm
try:
d = [Link]('testdbm', 'c')
v = d['xxx']
print(v)
except:
print('anahtar bulunamadı!')
Ancak bu veritanında bazı hatalar [Link] ile değil standart KeyError, TypeError gibi exception sınıflarıyla da ifade
edilmektedir. Örneğin bir anahtar bulunamadığında KeyError, anahtara atanan değer str ya da bytes olmadığında
TypeError ile raise işlemi yapılmaktadır.
dbm sınıfının tıpkı sözlüklerde olduğu gibi bir get metodu da vardır. Bu metotta ilgili anahtar bulunamazsa exception
oluşmaz, ikinci argümanla girilen değer geri döndürülür. İkinci hiç girilmeyebilir. Bu duurmda None değeri geri
döndürülmektedir. Örneğin:
import dbm
db = None
try:
db = [Link]('[Link]')
result = [Link]('xxxxx', 'Not found')
print(result)
except Exception as e:
40
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print(e)
finally:
if db:
[Link]()
Yukarıda da belirtildiği gibi asında dbm bir arayüzdür. Bu arayüz farklı DBM gerçekleştirimlerini kullanabilmektedir.
dbm arayüzü sırasıyla şu gerçekleştirimlerin sistemde yüklü olup olmadığına bakarak ilk bulduğu gerçekleştirimi
kullanmaktadır:
[Link]
[Link]
[Link]
Her DBM gerçekleştiriminin dosya formatı farklı olduğu için veritabanı [Link] fonksiyonuyla açılırken bu uyuma
da bakılmaktadı[Link]ğer söz konusu dosyanın formatına uygun bir dbm gerçekleştirimi yüklüyse yukarıdaki listede
arka sırada olsa bile o gerçekleştirim kullanılmaktadır. Bir veritabanı dosyasının hangi gerçekleştirimle oluşturulduğu
ayrıca istenirse hiç open uygulanmadan [Link] fonksiyonu ile sorgulanabilir. Örneğin:
>>> [Link]('testdbm')
'[Link]'
Biz Python kursunda dosyaları yaratıp, açıp temel okuma yazma işlemlerini yapmıştık. Burada ise dosya sistemi
üzerinde bazı önemli işlemlerin nasıl yapıldığını göreceğiz.
Python standart kütüphanesindeki os modülü içerisinde dosya sistemine ilişkin işlemler yapan önemli sınıflar ve
fonksiyonlar bulunmaktadır.
- chdir
- getcwd
- remove
- truncate
- stat
- rename
- listdir
- scandir
- walk
- mkdir
- makedirs
- is_dir
- is_file
def remove_tree(dirpath):
for dirname, dirs, files in [Link](dirpath, topdown=False):
for file in files:
[Link](dirname + '\\' + file)
41
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link](dirpath)
remove_tree(r'F:\Dropbox\Kurslar\Python-App\Src\ali')
Bir programı komut satırında çalıştırırken program isminden sonra yazılan yazılara "komut satırı argümanları
(command line arguments) denilmektedir. Tabii Python'da biz programı Python yorumlayıcısı ile çalıştırmaktayız.
Ancak UNIX/Linux sistemlerinde script dosyalarının doğrudan çalıştırılabildiğini anımsayınız. Örneğin:
Burada "ali veli selami" yazısı programın komut satırı argümanlarıdır. Python'da programın komut satırı argümanları
sys modülündeki argv isimli değişkenle elde edilebilmektedir. Modüldeki argv değişkeni bir str listesidir. Listenin her
elemanı bir komut satırı argümanını tutmaktadır. Listenin ilk elemanı her zaman çalıştırılan programın yol ifadesini
belirtir. Örneğin:
# [Link]
import sys
if __name__ == '__main__':
for arg in [Link]:
print(arg)
Bilindiği gibi eğer boşluk karakterleriyle ayrılmış olan yazıların tek bir argüman olarak aktarılması isteniyorsa çift
tırnak içerisine alınmalıdır. Örneğin:
Anımsanacağı gibi UNIX/Linux sistemlerinde komut satırında bu amaçla çift tırnak yerine tek tırnak da
bullanılabilmektedir. (Tabii bu sistemlerde çift tırnakla tek tırnak arasında bazı küçük farklılıklar vardır.)
Komut satırı argümanlarının [Link] dizisi içerisinde string biçiminde bulunduğuna dikkat ediniz. Aşağıda komut
satırı argümanlarıyla girilen değerlerin toplamını ekrana yazdıran program görüyorsunuz:
# [Link]
import sys
if __name__ == '__main__':
total = 0
for s in [Link][1:]:
total += float(s)
print(total)
42
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Programı komut satırında şöyle çalıştırabiliriz:
Örneğin:
import sys
if __name__ == '__main__':
if len([Link]) == 2:
with open([Link][1]) as f:
for line in f:
print(line, end='')
else:
print('wrong number of arguments')
Burada program komut satırı argümanıyla aldığı yol ifadesine ilişkin dosyanın içeriğini ekrana yazdırmaktadır.
PyCharm IDE'sinde programı çalıştırdığımızda aslında programı IDE'nin kendisi python yorumlayıcısını kullanarak
çalıştırmaktadır. İşte biz PyCharm IDE'sinde komut satırı argümanlarını Run/Edit Configuration/Parameters kısmında
belirtebiliriz. Örneğin:
Benzer biçimde Anaconda Spyder IDE'sinde de komut satırı argümanları Run/Configuration per file/Command line
options edit alanında girilebilir:
43
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Python Programlarının Sonlandırılması
Bir Python programı akış kaynak dosyanın sonuna geldiğinde otomatik olarak sonlandırılmaktadır. Ancak sonlandırma
işlemi istenildiği zaman sys modülündeki exit fonksiyonuyla da yapılabilir. Örneğin:
import sys
for i in range(val):
print(i)
exit fonksiyonunun parametresi ilgili programın (prosesin) exit kodunu belirtmektedir. Programların exit kodları
genellikle programın başarılı bir biçimde sonlandırılıp sonlandırılmadığını belirtirler ve 0 değeri başarılı sonlanmalar
için sıfır dışı değerler başarısız sonlanmalar için kullanılmaktadır. exit fonkiyonuna hiç argüman girilmeyebilir. Bu
durumda sanki 0 değeri girilmiş gibi işlem yapılmaktadır.
UNIX/Linux sistemlerinde ve Mac OS X sistemlerinde komut satırından son çalıştırılan programların exit kodlarını
şöyle görüntüleyebilirsiniz:
echo $?
echo %errorlevel%
Bir programda eğer hiç [Link] kullanmamışsak programın akışı kaynak kodun sonuna geldiğinde program sonlanır.
Bu tür sonlanmalarda exit kodu olarak 0 elde edilmektedir.
44
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bir nesne tüm elemanlarıyla bir dosyada ya da başka bir ortamda saklanabilir. Buna nesnenin "seri hale getirilmesi
(serialization)" denilmektedir. Daha sonra seri hale getirilmiş bu nesneler geri alınarak kullanılabilmektedir. Buna da
"seri hale getirilmiş nesnelerin geri alınması (object deserialization)" denilmektedir. Genel olarak nesnelerin seri hale
getirilmesi denildiğinde saklama ve geri alma işlemlerinin her ikisi de kastedilir. Nesnelerin seri hale getirilmesi
(object serialization) yalnızca Python'da değil pek çok framework ve kütüphanede bulunan bir özelliktir.
Python'da nesnelerin seri hale getirilmesi pickle modülündeki fonksiyonlar ve sınıflarla yapılmaktadır. Seri hale
getirme ve geri alma işlemi kabaca şöyle yapılmaktadır:
1) Nesne pickle modülünün dump isimli fonksiyonu ile seri hale getirilebilir. Aslında dump içerisinde Pickler isimli
sınıfı kullanmaktadır. Bu sınıf daha sonra ele alınacaktır. [Link] fonksiyonunun parametrik yapısı şöyledir:
Fonskiyonun bitinci parametresi seri hale getirilecek nesneyi belirtir. ikinci parametre built-in open fonksiyonundan
elde eidlen dosya nesnesini belirtmektedir. Biz istersek seri hale getirme işlemi için önceden belirlenmiş bir protokol
kullanabiliriz. Bu konu ileride ele alınacaktır. Default protokol "binary" olduğu için dosyanın da binary modda açılması
gerekebilmektedir. Örneğin:
import pickle
try:
with open('[Link]', 'wb') as f:
[Link](a, f)
except Exception as e:
print(e)
2) Nesneyi geri almak için [Link] fonksiyonu kullanılmaktadır. Bu fonksiyonun parametrik yapısı şöyledir:
Fonksiyonun birinci parametresi ilgili dosyaya ilişkin dosya nesnesini alır. Diğer parametreler default değerlerle
geçilebilir. Fonksiyon bize ilgili nesneyi yaratıp onu (yani adresini) vermektedir. Tabii orijinal nesne hangi türdense
load bize o türden bir nesne verecektir. Şüphesiz load dosyadan (ya da ilgili kaynaktan) okumayı dosya göstericisinin
gösterdiği yerden itibaren yapar. Yani nesneyi geri almadan önce dosya göstericisinin tam uygun konumda olması
gerekir. Örneğin:
import pickle
try:
with open('[Link]', 'rb') as f:
b = [Link](f)
print(b)
except Exception as e:
print(e)
Aslında pickle modülündeki global dump fonksiyonu kendi içerisinde Pickler isimli bir sınıf nesnesi yaratıp o sınıf
sınıfın dump metodunu çağırmaktadır. Yani biz dump işlemini şöyle de yapabilirdik:
import pickle
try:
with open('[Link]', 'wb') as f:
pickler = [Link](f)
45
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link](a)
except Exception as e:
print(e)
Burada:
pickler = [Link](file)
[Link](a)
işleminin eşdeğerinin:
[Link](a, file)
biçiminde olduğuna dikkat ediniz. Benzer biçimde load işlemi de aslında arka planda pickle modülünün Unpickler
isimli sınıfı ile yapılmaktadır. Yani biz load işlemini global load fonksiyonuyla değil aşağıdaki gibi de yapabilirdik:
import pickle
try:
with open('[Link]', 'rb') as f:
unpickler = [Link](f)
a = [Link]()
print(a)
except Exception as e:
print(e)
Burada:
unpickler = [Link](file)
a = [Link]()
işleminin eşdeğerinin,
a = [Link](file)
Aslında biz kendi sınıflarımızı da -belirli koşulların sağlanması durumunda- seri hale getirip geri alabiliriz. Örneğin:
import pickle
class Person:
def __init__(self, name, no):
[Link] = name
[Link] = no
def __str__(self):
return '{}, {}'.format([Link], [Link])
try:
with open('[Link]', 'wb') as f:
person = Person('Kaan Aslan', 123)
[Link](person, f)
except Exception as e:
print(e)
import pickle
46
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
class Person:
def __init__(self, name, no):
[Link] = name
[Link] = no
def __str__(self):
return '{}, {}'.format([Link], [Link])
try:
with open('[Link]', 'rb') as f:
person = [Link](f)
print(person)
except Exception as e:
print(e)
Nesneleri seri hale getirip dosya yazdıktan sonra bunları geri alırken dosya göstericisinin tam o noktada olması
gerekir. Örneğin biz üç nesneyi bu biçimde seri hale getirmiş olalım:
Biz bu nesneleri aynı sırada alabiliriz. Ancak bunlardan herhangi birini almak istiyorsak dosya göstericisini o noktaya
getirmemiz gerekir. Örneğin:
import pickle
a = [1, 2, 3, 4, 5]
b = ['Ali', 'Veli', 'Selami']
c = {'name': 'Kaan Aslan', 'no': 123}
try:
with open('[Link]', 'w+b') as f:
[Link](a, file)
[Link](b, file)
[Link](c, file)
[Link](0, 0)
x = [Link](file)
y = [Link](file)
z = [Link](file)
print(x, y, z)
except Exception as e:
print(e)
Tabii biz yazarken dosya offset'ini kaydedersek tam o noktaya dosya göstericisini konumlandırarak geri alma işlemini
yapabiliriz. Örneğin aşağıda ikinci nesnenin başlangıç offset'i kaydedilip sonra yalnızca bu ikinci nesne alınmıştır.
import pickle
a = [1, 2, 3, 4, 5]
b = ['Ali', 'Veli', 'Selami']
c = {'name': 'Kaan Aslan', 'no': 123}
try:
with open('[Link]', 'w+b') as f:
[Link](a, f)
[Link](b, f)
47
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
pos = [Link]()
[Link](c, f)
[Link](pos, 0)
x = [Link](f)
print(x)
except Exception as e:
print(e)
pickle modülündeki dumps (sonundaki s'ye dikkat ediniz) fonksiyonu seri hale getirme işlemini bir dosyaya yapmaz.
Seri hale getirilmiş byte'ları bize bytes nesnesi olarak verir. Örneğin:
import pickle
a = [1, 2, 3, 4, 5]
try:
b = [Link](a)
print(b)
except Exception as e:
print(e)
b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04K\x05e.'
Benzer biçimde loads fonksiyonu da bizden seri hale getirilmiş byte nesnesini alarak onu açar. Örneğin:
import pickle
a = [1, 2, 3, 4, 5]
try:
b = [Link](a)
print(b)
x = [Link](b)
print(x)
except Exception as e:
print(e)
dump ve dumps fonksiyonları ayrıca bir de protocol parametresi alabilmektedir. Bu protocol parametresi seri hale
getirme işleminin kodlama biçimini belirtmektedir. Örneğin prtocol 0, kodlamanın ASCII karakterleriyle yapılacağı
anlamına gelmektedir. Bu kodlama biçiminde seri hale getirilmiş olan nesne nispeten okunabilir durumdadır.
Örneğin:
import pickle
a = [1, 2, 3, 4, 5]
try:
with open('[Link]', 'wb') as file:
b = [Link](a, file, protocol=0)
except Exception as erre
print(e)
load sırasında protocol numarası belirtilmez. Çünkü zaten protokol numarası seri hale getirilen dosyaya
yazılmaktadır. Örneğin:
import pickle
try:
with open('[Link]', 'rb') as f:
b = [Link](f)
48
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print(b)
[Link]()
except Exception as e:
print(e)
Shelve Kullanımı
Shelve modülü aslında dbm ile pickle modülünün birleştirilmiş bir hali gibidir. Anımsanacağı üzere dbm nesneleriyle
biz anahtar ve değeri str ya da bytes olan nesneleri dosyaya kaydedip alabiliyorduk. Shelve tamamen dbm gibi
çalışmaktadır. Shelve nesnelerinin dbm nesnelerinden en önemli farkı değer olarak string ya da bytes zorunluluğunun
olmamasıdır. Yani shelve arka planda değeri önce pickle modülü ile seri hale getirip dbm ile yazar. Benzer biçimde
geri alım sırasında da değeri önce bytes biiminde dbm kullanarak geri almakta ve onu pickle modülü ile yeniden
orijinal türe dönüştürmektedir. Örneğin:
import shelve
try:
sh = [Link]('testshelve')
sh['ali'] = 123
sh['Veli'] = 456
sh['Selami'] = 623
[Link]()
except Exception as e:
print(e)
Görüldüğü gibi kullanım tamamen DBM'e benzemektedir. Ancak değer olarak biz artık herhangi bir türü (kendi
sınıflarımız da dahil olmak üzere) kullanabiliriz. shelve sınıfı da "kaynak yönetim protokolünü (resource management
protocol)" desteklediği için with deyimiyle kullanabilir. with çıkışında shelve dosyaları otomatik olarak kapatılacaktır.
Örneğin:
import shelve
try:
with [Link]('[Link]') as sh:
sh['ali'] = 123
sh['Veli'] = 456
sh['Selami'] = 623
shelve modülündeki open fonksiyonunun ikinci parametresi dbm ile aynıdır. Burada default açış modu yine 'c' (yani
dosya yoksa yarat varsa olanı aç anlamında) biçimindedir.
shelve nesnesi de len fonksiyonuna işlemine sokulabilir. Yine del operatörü ile belli elemanı silebiliriz. in operatörü ile
de belli bir anahtar bulunup bulunmadığını test edebiliriz. Örneğin:
import shelve
try:
with [Link]('testshelve', 'w') as sh:
for key in [Link]():
print('{} => {}'.format(key, sh[key]))
49
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print('Toplam eleman sayısı: {}'.format(len(sh)))
print('Anahtar Var' if 'Veli' in sh else 'Anahtar Yok')
del sh['Veli']
print('Anahtar Var' if 'Veli' in sh else 'Anahtar Yok')
except Exception as e:
print(e)
Yukarıda da belirtildiği gibi aslında shelve kendi içerisinde dbm ve pickle kullanılarak yazılmıştır. Biz de kendi shelve
sınıfımızı aşağıdaki gibi yazabiliriz:
import dbm
import pickle
class myshelve:
def __init__(self, db):
[Link] = db
@staticmethod
def open(*args):
return myshelve([Link](*args))
def __len__(self):
return [Link].__len__()
ms = [Link]('testshelve', 'c')
ms['ali'] = 123
ms['veli'] = 256
ms = [Link]('testshelve', 'c')
result = ms['ali']
print(result)
result = ms['veli']
print(result)
print(len(ms))
Python'da GUI uygulamaları için pek çok framework kullanılabilmektedir. Python'ın kendisi "tkinter" denilen
kütüphaneyi desteklemektedir. Yani Python'ın standart kütüphanesinde "tkinter" denilen GUI framework kullanıma
hazır halde zaten bulunmaktadır. ("tkinter" "ti key intır" ya da "tikintır" biçiminde okunabilmektedir.) "Tkinter"
aslında TCL denilen dilde ağırlıklı kullanılan "TK" isimli kütüphanenin Python'dan kullanılan biçimidir. Python'da
"tkinter" kütüphanesinin en önemli alternatifi PyQt'dir. PyQt aslında bir Qt isimli C++ GUI framewrork'ünün
Python'dan kullanılan biçimidir. Ancak Python'da kullanılabilmektedir. Yetenek olarak TK kütüphanesinden (yani
"ttkinter"dan) oldukça iyidir. Tabii Python'da GUI işlemler için başka seçenekler de vardır. Örneğin wxWidget, Gtk+
diğer önemli seçeneklerdendir.
Mesaj tabanlı programlama modelinde klavye ve fare gibi aygıtlarda oluşan girdileri programcı kendisi almaya
çalışmaz. Fare gibi, klavye gibi girdi aygıtlarını işletim sisteminin (ya da GUI alt sistemin) kendisi izler. Oluşan girdi
50
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
olayı hangi pencereye ilişkinse işletim sistemi ya da GUI alt sistem, bu girdi olayını “mesaj” adı altında bir yapıya
dönüştürerek o pencerenin ilişkin olduğu (yani o pencereyi yaratan) programın “mesaj kuyruğu (message queue)”
denilen bir kuyuk sistemine yerleştirir. Mesaj kuyruğu içerisinde mesajların bulunduğu FIFO prensibiyle çalışan bir
kuyruk veri yapısıdır. Sistemin daha iyi anlaşılması için süreci maddeler halinde özetlemek istiyoruz:
1. Her programın (ya da thread’in) “mesaj kuyruğu” denilen bir kuyruk veri yapısı vardır. Mesaj kuyruğu mesajlardan
oluşmaktadır.
2. İşletim sistemi ya da GUI alt sistem gerçekleşen girdi olaylarını “mesaj (message)” adı altında bir yapı formatına
dönüşürmekte ve bunu pencerenin ilişkin olduğu programın (ya da thread’in) mesaj kuyruğuna eklemektedir.
3. Mesajlar ilgili olayı betimleyen ve ona ilişkin bazı bilgileri barındıran yapı (structure) nesneleridir. Örneğin
Windows’ta mesajlar MSG isimli bir yapıyla temsil edilmişleridir. Bu yapının elemanlarında mesajın ne mesajı olduğu
(yani neden gönderildiği) ve olaya ilişkin bazı bilgiler bulunur.
Görüldüğü gibi GUI programlama modelinde girdileri programcı elde etmeye çalışmamaktadır. Girdileri bizzat işletim
sisteminin kendisi ya da GUI alt sistemi elde edip programcıya mesaj adı altında iletmektedir.
GUI programlama modelinde işletim sisteminin (ya da GUI alt sistemin) oluşan mesajı ilgili programın (ya da
thread’in) mesaj kuyruğuna eklemenin dışında başka bir sorumluluğu yoktur. Mesajların kuyruktan alınarak işlenmesi
ilgili programın sorumluluğundadır. Böylece GUI programcısının mesaj kuyruğuna bakarak sıradaki mesajı alması ve
ne olmuşsa ona uygun işlemleri yapması gerekir. Bu modelde programcı kodunu şöyle düzenler: Bir döngü içerisinde
sıradaki mesajı kuyruktan al, onun neden gönderildiğini belirle, uygun işlemleri yap, kuyrukta mesaj yoksa da blokede
bekle”. İşte GUI programlarındaki mesaj kuyruğundan mesajı alıp işleyen döngüye mesaj döngüsü (message loop)
denilmektedir.
Bir GUI programının işleyişini tipik akışı aşağıdaki gibi bir kodla temsil edebiliriz:
Bu temsili koddan da görüldüğü gibi tipik bir GUI programında programcı bir döngü içerisinde mesaj kuyruğundan
sıradaki mesajı alır ve onu işler. Mesajın işlenmesi ise “ne olmuş ve ben buna karşı ne yapmalıyım?” biçiminde
oluşturulmuş olan kodlarla yapılmaktadır.
Pekiyi bir GUI programı nasıl sonlanmaktadır? İşte pencerenin sağındaki (bazı sistemlerde solundaki) X simgesine
kullanıcı tıkladığında işletim sistemi ya da GUI alt sistem bunu da bir mesaj olarak o pencerenin ilişkin olduğu
prosesin (ya da thread’in) mesaj kuyruğuna bırakır. Programcı da kuyruktan bu mesajı alarak mesaj döngüsünden
çıkar ve program sonlanır.
GUI ortamımız ister .NET, ister Java, ister MFC olsun, isterse PyQt olsun, işletim sisteminin ya da GUI alt sistemin
çalışması hep burada ele alındığı gibidir. Yani örneğin biz .NET'te ya da Java'da işlemlerin sanki başka biçimlerde
51
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
yapıldığını sanabiliriz. Aslında işlemler bu ortamlar tarafından aşağı seviyede yine burada anlatıldığı gibi
yapılmaktadır. Bu ortamlar (frameworks) ya da kütüphaneler çeşitli yükleri üzerimizden alarak bize daha rahat bir
çalışma modeli sunarlar. Ayrıca şunu da belirtmek istiyoruz: GUI programlama modeli özellikle nesne yönelimli
programlama modeline çok uygun düşmektedir. Bu nedenle bu konuda kullanılan kütüphanelerin büyük bölümü
sınıflar biçiminde nesne yönelimli diller için oluşturulmuş durumdadır.
Şimdi GUI programlama modelindeki mesaj kavramını biraz daha açalım. Yukarıda da belirttiğimiz gibi bu modelde
programcıyı ilgilendiren çeşitli olaylara “mesaj” denilmektedir. Örneğin klavyeden bir tuşa basılması, pencere
üzerinde fare ile tıklanması, pencere içerisinde farenin hareket ettirilmesi gibi olaylar hep birer mesaj
oluşturmaktadır. İşletim sistemleri ya da GUI alt sistemler mesajları birbirinden ayırmak için onlara birer numara
karşılık getirirler. Örneğin Windows’ta mesaj numaraları WM_XXX biçiminde sembolik sabitlerle kodlanmıştır.
Programcılar da konuşurken ya da kod yazarken mesaj numaralarını değil, bu sembolik sabitleri kullanırlar. (Örneğin
WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_KEYDOWN gibi) Mesajların numaraları yalnızca gerçekleşen olayın
türünü belirtmektedir. Oysa bazı olaylarda gerçekleşen olaya ilişkin bazı bilgiler de söz konusudur. İşte bir mesaja
ilişkin o mesaja özgü bazı parametrik bilgiler de işletim sistemi ya da GUI alt sistem tarafından mesajın bir parçası
olarak mesajın içerisine kodlanmaktadır. Örneğin Windows’ta biz klavyeden bir tuşa bastığımızda Windows
WM_KEYDOWN isimli mesajı programın mesaj kuyruğuna bırakır. Bu mesajı kuyruktan alan programcı mesaj
numarasına bakarak klavyenin bir tuşuna basılmış olduğunu anlar. Fakat hangi tuşa basılmıştır? İşte Windows basılan
tuşun bilgisini de ayrıca bu mesajın içerisine kodlamaktadır. Örneğin WM_LBUTTONDOWN mesajını Windows farenin
sol tuşuna tıklandığında kuyruğa bırakır. Ancak ayrıca basım koordinatını da mesaja ekler. Yani bir mesaj oluştuğunda
yalnızca o mesajın hangi tür bir olay yüzünden oluştuğu bilgisini değil aynı zamanda o olayla ilgili bazı bilgileri de
kuyruktaki mesajın içerisinden alabilmekteyiz.
GUI programlama modelinde bir mesaj oluştuğunda o mesajın bir an evvel işlenmesi ve akışın çok bekletilmemesi
gerekir. Aksi takdirde programcı kuyruktaki diğer mesajları işleyemez bu da “program donmuş etkisi” yaratmaktadır.
Eğer bir mesaj alındığında uzun süren bir işlem yapılmak isteniyorsa bir thread oluşturulup o işi o thread’e devretmek
ve böylece mesaj döngüsünün işlemesini sağlamak gerekir.
GUI programlama modellerinde genel olarak mesaj kavramı pencere kavramıyla ilişkilendirilmiştir. Yani bir pencere
yaratılmadıktan sonra bir mesajın oluşma durumu da yoktur. Bu nedenle mesaj döngüsüne girmeden önce
programcının en az bir pencere (tipik olarak programın ana penceresi) yaratmış olması gerekir.
Windows gibi bazı sistemlerde thread’lerle ilişkilendirilmiştir. Bu sistemlerde prosesin tek bir mesaj kuyruğu yoktur.
Her thread’in ayrı bir mesaj kuyruğu vardır. Bu durumda işletim sistemi ya da GUI alt sistem bir pencereye ilişkin bir
işlem gerçekleştiğinde o pencerenin hangi prosesin hangi thread’i tarafından yaratılmış olduğunu belirler ve mesajı o
thread’in mesaj kuyruğuna bırakır. Böylece biz bir thread oluşturup o thread’te de bir pencere yaratmışsak artık
bizim de o thread’te o pencerenin mesajlarını işlemek için mesaj döngüsü oluşturmamız gerekir. Tabii eğer
thread’imizde biz hiçbir pencere oluşturmamışsak böyle bir mesaj döngüsünü oluşturmamıza da gerek yoktur.
(Örneğin Microsoft eğer bir thread bir pencere yaratmışsa böyle thread’lere “GUI thread’ler” yaratmamışsa "worker
thread’ler" demektedir).
GUI tabanlı sistemlerde ekranda bağımsız olarak kontrol edilebilen dikdörtgensel bölgelere pencere (window)
denilmektedir. Konumlarına ve işlevselliklerine göre pencereler birkaç gruba ayrılmaktadır. Doğrudan masaüstüne
açılan pencerelere “ana pencereler (top level windows)” denilmektedir. Pek çok GUI alt sisteminde görünür durumda
olan ana pencereler bir çubukta gösterilmektedir (örneğin Windows’taki task bar). Her ne kadar zorunlu değilse de
ana pencerelerin genellikle bir çerçevesi (frame), bir başlık kısmı (caption) ve başlık kısmının üzerinde bazı simgeleri
bulunur.
Pencerenin sınır çizgilerinin ve başlığının altında kalan alana “çalışma alanı (client area)” denilmektedir. Çalışma alanı
bizim aktif çizim yapabileceğimiz, başka alt pencereleri yerleştirebileceğimiz (yani bizim için ayrılmış) olan alandır.
Tabii bir pencerenin toplam genişliği ve yüksekliği çalışma alanının genişliği ve yüksekliği ile aynı olmak zorunda
değildir. Eğer pencerenin başlığı ve sınır çizgileri yoksa bu durumda pencerenin kendi genişliği ve yüksekliği çalışma
alanının genişliği ve yüksekliği ile aynı olur.
52
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bir pencerenin içerisinde görüntülenen, onun dışına çıkamayan pencerlere “alt pencereler (child windows)”
denilmektedir. Alt pencerelerde genellikle başlık kısmının ve sınır çizgilerinin bulunması istenmez. (Ancak tabii alt
pencereler de istenirse başlık kısmına ve sınır sizgilerine sahip olabilirler.) Alt pencerelerin alt pencereleri de söz
konusu olabilir. Her alt pencerenin bir üst penceresi (parent window) vardır. Aynı üst pencereye sahip olan
pencerelere kardeş pencereler (sibling windows) denilmektedir.
Ayrıca bir de ismine “sahiplenilmiş (owned)” pencere denilen bir pencere türü daha vardır. Tipik olarak diyalog
pencereleri bu türdendir. Sahiplenilmiş pencereler hem bir çeşit alt pencere gibi hem de ana pencere gibi davranırlar.
Bunlar her zaman üst pencerelerinin yukarısında görüntülenir. Bunların üst pencereleri minimize edildiğinde bunlar
da görünmez olurlar.
Daha önceden de belirttiğimiz gibi Python GUI işlemleri için Tk isimli kütüphaneti standart kütüphanesine dahil
etmiştir. Tk kütüphanesi 1991 yılında John Ousterhout tarafından Tcl ("tikıl" diye okunuyor) isimli bir script dilinden
kullanılmak üzere tasarlanmıştır. Bu nedenle bu kütüphane genellikle Tcl isimli script diliyle birlikte Tcl/Tk biçiminde
ifade edilir. Her ne kadar Tk ilk başlarda Tcl için geliştirilmiş olsa da zamanla pek çok dilden kullanılabilen "cross
platform" bir özellik kazanmıştır.
Tk Python'ın standart kütüphanesi içerisinde olduğu için herhangi bir kurulum gerektirmemektedir. Yani Python'ın
yüklü olduğu yerlerde zaten bu kütüphanede kullanıma hazır halde bulunmaktadır.
Ekrana boş bir pencere çıkartan iskelet bir Tk programı şöyle yazılabilir:
import tkinter as tk
53
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
root = [Link]()
[Link]()
Buradaki örnek görüntülerin Mac OS X sistemlerinden alınma olduğunu belirtmek istiyoruz. Şimdi iskelet programı
satır satır açıklayalım. Programın ilk satırının şöyle olduğunu görüyorsunuz:
import tkinter as tk
Burada tkinter modülünü tk ismiyle import etmiş olduk. Böylelikle modüldeki tüm isimleri tk ismiyle niteliklendirerek
kullanabilecrğiz. Bazı programcılar from import deyimi ile modüldeki tüm isimleri global isim alanına taşıyarak
doğrudan kullanırlar:
Ancak biz bu bölümdeki örneklerde bunu yapmayacağız. tkinter modülündeki tüm isimleri tk ismiyle niteliklendirerek
kullanacağız. İskelet programımızın ikinci satırı şöyledir:
root = [Link]()
Tk sınıfı programın ana penceresini temsil etmektedir. Yani Tk sınıfı türünden bir nesne yaratmakla biz bir ana
pencere yaratmış oluruz. Nihayet iskelet programımızın son satırı da şöyledir:
[Link]()
Tk sınıfının mainloop metodu programı mesaj döngüsüne sokar. Artık program yalamını bu fonksiyon içerisindeki bir
döngüde geçirecektir. mainloop mesaj kuyruğuna gelen mesajlartı alarak onları bizim isteğimiz doğrultusunda işler.
Ana pencere kapatıldığında mainllop metodunun çalışması bitecek ve program da sonlanacaktır. mainloop çağrısının
altına yazdığınız şeylerin ancak ana pencere kapatıldığında çalışacağına dikkat ediniz. Örneğin:
import tkinter as tk
root = [Link]()
[Link]()
print('program sonlanıyor...')
GUI uygulamalarındaki düğmeler, edit alanları, listeleme kutuları gibi GUI elemanları işlenmiş birer alt penceredir.
Tkinter dünyasında (ve birtakım başka kütüphanelerde) bu alt pencerelere "widget" denilmektedir. "Widget" sözcüğü
"window gadget" sözcüklerinden kısaltılarak uydurulmuştur. Tipik bir GUI uygulamasında programcı öncelikle
54
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
kullanıcı arayüzünü oluşturan GUI elemanları pencerelerin içerisine yerleştirir sonra da bu elemanlarla kullanıcı
etkileşime girdiğinde yapılacak işlemleri tanımlar. Tkinter'da pek çok GUI eleman vardır. Bu elemanların hepsi
[Link] isimli bir sınıftan türetilmiş sınıflarla temsil edilmişlerdir. Kütüphanedeki önemli GUI elemanlar
şunlardır:
Label
Button
Checkbutton
RadioButton
Entry
Listbox
Combobox
Menu
Canvas
Scale
Scrollbar
...
Tkinter'da bir GUI elemanın (widget) yaratılması için yapılacak tek şey bu GUI eleman sınıfı türünden bir nesne
yaratamaktır. GUI elemana ilişkin sınıfın __init__ metodunun birinci parametresinde üst pencerenin belirtilmesi
gerekir. Örneğin W bir GUI eleman sınıfını temsil etmek üzere yaratım şöyle yapılmaktadır:
tk.W(parent)
Burada parent argümanı GUI elemanın içerisinde olacak üst pencere nesnesini (örneğin root) belirtmektedir.
Her GUI elemanın birtakım özellikleri vardır. Örneğin "zemin rengi", "yazısı", "yazısının rengi", "yazısının fontu" gibi.
Bu özelliklere biz "konfigürasyon seçenekleri (configuration options)" diyeceğiz. Nesnenin konfigürasyon seçenekleri
üç biçimde oluşturulabilmektedir:
Burada Label nesnesinin üst penceresi root penceresidir. Nesnenin "text", "bg" ve "fg" isimli konfigürasyon
seçenekleri nesne yaratılırken girilmiştir. GUI elemanlara ilişkin sınıfların __init__ metotlarının ** parametreleri
vardır. Bu konfigürasyon parametreleri yaratımda bu ** parametrelerle eşleşmektedir.
label = [Link](root)
[Link](text='Test', bg='yellow', fg='blue')
Burada konfigürasyon seçenekleri nesne yaratılırken değil nesne yaratıldıktan sonra config metıdunda belirtilmiştir.
3) Nesnenin __setitem__ fonksiyonunda (yani sözlük nesnesinde olduğu gibi köşeli parantezlerle). Örneğin:
label = [Link](root)
label['text'] = 'Test'
label['bg'] = 'yellow'
label['fg'] = 'blue'
Burada konfigürasyon seçenekleri sözlüklerde olduğu gibi anahtar-değer biçiminde verilmiştir. Konfigürasyon
seçeneklerini belirten anahtarların str türünden olduğuna dikkat ediniz.
Şüphesiz konfigürasyon seçeneklerini bu yöntemlerin her biri ile karma biçimde değiştirebiliriz. Bir seçenek daha
sonra yeniden değiştirilirse artık yeni değeri geçerli değer olarak işleme sokulacaktır.
55
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
GUI elemanlara ilişkin konfigürasyon seçeneklerinin bazıları pek çok GUI elemanda ortaktır. Bunlara "standart
konfigürasyon seçenekleri" denilmektedir. Bazı seçenekler ise yalnızca ilgili GUI elemana özeldir. Bunlara da "widget
spesifik konfigürasyon seçenekleri" denilmektedir. Yukarıdaki örneğimizde "bg" ve "fg" standart konfigürasyon
seçeneklerine örnek verilebilir.
Bir GUI eleman yaratılmış ve konfigüre edilmiş olsa da henüz ekranda gözükmez. GUYI elemanların ekranda
gözükebilmesi için onların yerleştirilmeleri gerekir. Yerleştirme işlemi "geometri yöneticisi (geometry manager)"
denilen nesnelerle yapılmaktadır. Tkinter'da GUI elemanların yerleştirilmesi için kullanılan üç farklı geometri
yöneticisi vardır: "place" , "pack" ve "grid". "place" geometri yöneticisi GUI elemanlarının konumlandırılmasını
otomatik yapmaz. "pack" ve "grid" geometri yöneticileri konumlandırmaları otomatik olarak yapmaktadır. Bu
nedenle uygulamada programcılar ağırlıklı olarak "pack" ya da "grid" geometri yöneticilerini tercih ederler. Ancak biz
GUI elemanlarını daha iyi açıklayabilmek için notlarımızdaönce "place" geometri yöneticisini kullanacağız.
Tkinter'da "place", "pack" ve "grid" geometri yöneticileri "Place", Pack" ve "Grid" isimli sınıflarla temsil edilmişlerdir.
GUI elemanlarının konumlandırılması bu sınıfların place, pack ve grid metotlarıyla yapılmaktadır. Bütün GUI eleman
sınıflarının Widget isimli bir sınıftan türetildiğini söylemiştik. İşte GUI eleman sınıflarının türetildiği bu Widget sınıfı da
Place, Pack ve Grid sınıflarından çoklu türetilmiş durumdadır. Böylece aslında her GUI eleman sınıfı "Place", "Pack" ve
"Grid" sınıflarının özelliklerini taşıyor durumdadır. Dolayısıyla biz de konumlandırmayı herhangi bir GUI nesnesinin
place, pack ve grid metotlarıyla yapabiliriz.
Place geometri yöneticisi pek tercih edilmese de en kolay öğrenilebilen geometri yöneticisidir. Yukarıda da belirtildiği
gibi place geometri yöneticisi ikle konumlandırma GUI eleman sınıflarının Place sınıfından gelen place fonksiyonuyla
yapılmaktadır. place metodunun parametrik yapısı şöyledir:
Metodun cnf ve **kw parametreleri argümanları sözlük biçiçimde ve isimli olarak almaktadır. Bu iki paremtrede
belirtilen isimli parametreler metot tarafından birleştirilir. x ve y isimli argümanlar ilgili GUI elemanın sol üst
köşesinin bulunacağı pixel koordinatlarını belirtir. Bu koordinatlardaki x ve y değerleri üst pencere çalışma alanı
orijinlidir. Aşağıdaki örnekteki yerleşime bakınız:
import tkinter as tk
root = [Link]()
[Link]()
[Link](x=100, y=50)
56
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
place metodunun relx ve rely parametresi 0 ile 1 arasında oransal değerler almaktadır. Üst pencerenin genişiği ve
yüksekliğşi 1 olmak üzere verilen değerler bununla orantılıdır. Örneğin:
[Link](relx=0.5, rely=0.5)
relx ve rely parametreleriyle konumlandırma yapıldığında pencerenin boyutunun değiştirilmesi durumunda ilgili GUI
eleman da oranlar korunacak biçimde yeniden konumlandırılmaktadır. Halbuki x ve y ile konumlandırma yapıldığında
pencere boyutu değiştirilse bile GUI eleman her zaman o x ve y değerlerinde kalır.
place metodunun width ve height parametreleri pixel cinsinden GUI elemanın geenişliğini ve yüksekliğini
belirtmektedir. Eğer GUI elemanda genişlik ve yükseklik belirtilmezse eleman default genişliğe yüksekliğe sahip
olacak biçimde yaratılır. Genellikle bu genişlik ve yükseklik elemanın içerisindeki yazıyla ilgilidir. Örneğin:
Bazı GUI elemanların zaten width ve height isimli konfigürasyon seçenekleri de vardır. Ancak bu width ve height
konfigürasyon seçenekleri alt pencerenin genişliğini ve yüksekliğini pixel cinsinden değil karakter sayısı cinsinden
oluşturmaktadır.
place geometri yöneticisi ile konumlandırmanın en önemli zorluğu programcının arayüzdeki GUI elemanlarının
yerlerini pixel cinsinden hesaplamasıdır. Programcı bu hesabı yaptıktan sonra GUI elemanların yerleştirildiği üst
pencerenin boyutu değiştirildiğinde arayüz buna otomatik olarak uyum sağlayamamaktadır.
57
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Tkinter'daki GUI Elemanlar
Tkinter içerisinde görsel arayüz oluşturmak için pek çok GUI eleman vardır. Bu bölümde bu GUI elemanları ele
alacağız.
Tkintrer'da programın ana penceresinin Tk sınıfı ile temsil edildiğini söylemiştik. Tk sınıfının geometry isimli metodu
ana pencerenin ilk açıldığındaki boyutunu ve konumunu belirlemekte kullanılır. Eğer bu metot ile bir belirleme
yapılmadıysa ana pencerenin ilk konumu ve büyüklüğü işletim sisteminin pencere yöneticisi tarafından
belirlenmektedir. geoemetry metodunun genel biçimi şöyledir:
'widthxheight+xpos+ypos'
Buradaki width pencerenin genişliğini, height yüksekliğini, xpos ana pencerenin sol süt köşesinin x koordinatını, ypos
ise y koordinatını belirtmektedir.
Örneğin:
import tkinter as tk
root = [Link]()
[Link]('800x600+100+100')
[Link]()
Burada ana pencere 800X600 büyüklüğünde ve sol üst köşe koordinato x=100, y = 100 olacak biçimde
görüntülenecektir. İstenirse yalnızca ana pencerenin büyüklüğü belirtilip konumu hiçbeelirtilmeyebilir. Örneğin:
[Link]('800x600+100+100')
Label Kullanımı
GUI ortamlarında yazı görüntülemek için kullanılan alt pencerelere genellikle Label denilmektedir. Label alt
penceresinin zemin rengi üst pencere ile aynıdır ve pencerenin içerisinde yalnızca bir yazı bulunur. Uygulamada
pencere içerisinde çeşitli yazıların görüntülenmesi için kullanılmaktadır. Örneğin:
import tkinter as tk
root = [Link]()
xpos = 5
ypos = 5
for index, city in enumerate(['Ankara', 'İzmir', 'Adana', 'Eskişehir', 'İstanbul']):
[Link](root, text=city).place(x=xpos, y=ypos)
ypos += 20
[Link]()
58
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Derste Yapılan Örnekler
#--------------------------------------------
import tkinter as tk
import sqlite3
class GUIDatabase:
def __init__(self, master):
[Link]('370x180')
[Link](width=False, height=False)
[Link]('Database GUI Example')
[Link] = master
[Link] = [Link]('[Link]')
cur = [Link]()
[Link]("CREATE TABLE IF NOT EXISTS student(student_no INTEGER PRIMARY KEY,
student_name TEXT)")
[Link]()
self.entry_name.focus()
def record(self):
try:
name = self.entry_name.get().strip()
no = int(self.entry_no.get().strip())
cur = [Link]()
[Link]("INSERT INTO student VALUES(?, ?)", (no, name))
[Link]()
[Link]()
self.entry_name.delete(0, [Link])
self.entry_no.delete(0, [Link])
self.entry_name.focus()
except [Link] as e:
print(e)
root = [Link]()
gdb = GUIDatabase(root)
[Link]()
#--------------------------------------------
import tkinter as tk
import sqlite3
59
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
class GUIDatabase:
def __init__(self, master):
[Link] = master
[Link]('360x180')
def button_start_handler(self):
print('Started')
self.button_start.config(state=[Link])
self.button_stop.config(state=[Link])
def button_stop_handler(self):
print('Stopped')
self.button_start.config(state=[Link])
self.button_stop.config(state=[Link])
root = [Link]()
gdb = GUIDatabase(root)
[Link]()
#--------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('400x350')
[Link]('Sample Data Binding')
[Link] = master
self.listbox_cities.bind('<Double-Button-1>', self.listbox_double_click)
self.listbox_cities.activate(1)
self.listbox_cities.focus()
def button_ok_handler(self):
s = self.listbox_cities.get([Link])
print(s)
sel = self.listbox_cities.curselection()
60
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print(sel)
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link]
import [Link]
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
[Link](width=False, height=False)
[Link] = master
self.check_button_var = [Link]()
self.radio_button_var = [Link]()
self.radio_button_var.set('Arial 12')
self.menu_bar = [Link](master)
self.file_popup = [Link](tearoff=0)
self.menu_bar.add_cascade(label='File', menu=self.file_popup, font='Arial 12',
underline=0)
self.file_popup.add_command(label='Open...', command=self.file_open_handler,
font='Arial 10 bold', underline=1,
foreground='red', accelerator='Ctrl+O')
[Link]('<Control-o>', self.file_open_handler)
self.file_popup.add_command(label='Save As...', command=self.file_saveas_handler,
font='Arial 10 bold',
underline=0, accelerator='Ctrl+S')
[Link]('<Control-s>', self.file_saveas_handler)
self.file_popup.add_command(label='Close', command=self.file_close_handler, font='Arial
10', underline=2,
foreground='blue', state=[Link])
self.file_popup.add_checkbutton(label='Check Button', variable=self.check_button_var,
command=self.file_checkbutton_handler)
self.file_popup.add_separator()
self.file_popup.add_command(label='Exit', command=[Link], font='Arial 10',
underline=1,
accelerator='Ctrl+E')
self.edit_popup = [Link](tearoff=0)
self.edit_popup.add_command(label='Cut', underline=0, command=self.edit_cut_handler,
background='yellow',
accelerator='Ctrl+X')
self.edit_popup.add_command(label='Copy', underline=1, command=self.edit_copy_handler,
accelerator='Ctrl+C')
self.edit_popup.add_command(label='Paste', underline=0,
command=self.edit_paste_handler, accelerator='Ctrl+V')
self.edit_popup.add_separator()
self.edit_font_popup = [Link](tearoff=0)
self.edit_popup.add_cascade(label='Font', menu=self.edit_font_popup)
self.edit_font_popup.add_radiobutton(label='Arial 12', command=self.edit_font_handler,
61
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
value='Arial 12',
variable=self.radio_button_var)
self.edit_font_popup.add_radiobutton(label='Consolas 12',
command=self.edit_font_handler, value='Consolas 12',
variable=self.radio_button_var)
self.edit_font_popup.add_radiobutton(label='Verdana 12',
command=self.edit_font_handler, value='Verdana 12',
variable=self.radio_button_var)
[Link] = [Link](master)
[Link](x=0, y=0, width=800, height=64)
img = [Link](file='[Link]')
self.toolbar_button_open = [Link]([Link], command=self.file_open_handler,
image=img, padx=0, pady=0)
self.toolbar_button_open.image = img
self.toolbar_button_open.place(x=0, y=0, width=64, height=64)
img = [Link](file='[Link]')
self.toolbar_button_saveas = [Link]([Link], command=self.file_saveas_handler,
image=img, padx=0, pady=0)
self.toolbar_button_saveas.image = img
self.toolbar_button_saveas.place(x=64, y=0, width=64, height=64)
img = [Link](file='[Link]')
self.toolbar_button_close = [Link]([Link], command=self.file_close_handler,
image=img, padx=0, pady=0, state=[Link])
self.toolbar_button_close.image = img
self.toolbar_button_close.place(x=128, y=0, width=64, height=64)
img = [Link](file='[Link]')
self.toolbar_button_exit = [Link]([Link], command=[Link], image=img,
padx=0, pady=0)
self.toolbar_button_exit.image = img
self.toolbar_button_exit.place(x=192, y=0, width=64, height=64)
img = [Link](file='[Link]')
self.toolbar_button_cut = [Link]([Link], command=self.edit_cut_handler,
image=img, padx=0, pady=0)
self.toolbar_button_cut.image = img
self.toolbar_button_cut.place(x=300, y=0, width=64, height=64)
[Link]('<Control-x>', self.edit_cut_handler)
img = [Link](file='[Link]')
self.toolbar_button_copy = [Link]([Link], command=self.edit_copy_handler,
image=img, padx=0, pady=0)
self.toolbar_button_copy.image = img
self.toolbar_button_copy.place(x=364, y=0, width=64, height=64)
[Link]('<Control-c>', self.edit_copy_handler)
img = [Link](file='[Link]')
self.toolbar_button_paste = [Link]([Link], command=self.edit_paste_handler,
image=img, padx=0, pady=0)
self.toolbar_button_paste.image = img
self.toolbar_button_paste.place(x=428, y=0, width=64, height=64)
62
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]('<Control-v>', self.edit_paste_handler)
[Link]('<Button-3>', self.text_mouse_right_press_handler)
[Link]('<Button-3>', self.toolbar_mouse_right_press_handler)
except Exception as e:
[Link](title='Error', message=str(e))
def file_close_handler(self):
[Link]('1.0', 'end')
self.file_popup.entryconfig(0, state=[Link])
self.file_popup.entryconfig(2, state=[Link])
self.toolbar_button_open.config(state=[Link])
self.toolbar_button_close.config(state=[Link])
63
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def edit_font_handler(self):
[Link]['font'] = self.radio_button_var.get()
def file_checkbutton_handler(self):
print('Checked' if self.check_button_var.get() else 'Unchecked')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link]
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
[Link](width=False, height=False)
[Link] = master
[Link]('<Button-3>', self.button_down_handler)
[Link]('<Return>', self.enter_handler)
[Link]('<B1-Motion>', self.mouse_motion_handler)
[Link]('<KeyPress>', self.key_press_handler)
[Link]('<KeyRelease>', self.key_release_handler)
root = [Link]()
gdb = GUI(root)
[Link]()
64
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
# --------------------------------------------
import tkinter as tk
import [Link]
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
[Link](width=False, height=False)
[Link] = master
[Link]('<Button-1>', self.button_down_handler)
[Link]('<B1-Motion>', self.button_motion_handler)
[Link](x=newx, y=newy)
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
from PIL import Image
from PIL import ImageTk
import os
class GUI:
def __init__(self, master):
[Link]('800x700')
[Link]('Sample Messagebox')
[Link] = master
self.back_img = [Link](r'CardImages\blue_back.png')
self.back_img = self.back_img.resize((71, 96))
self.back_img = [Link](self.back_img)
ypos = 0
xpos = 0
for index, file in enumerate([Link]('CardImages')):
button = [Link](master, text='', relief=[Link], image=self.back_img)
[Link](x=xpos, y=ypos, width=71, height=96)
if index % 10 == 9:
ypos += 96
xpos = 0
else:
xpos += 71
[Link](x=newx, y=newy)
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
[Link]('<Configure>', self.resize_handler)
root = [Link]()
gdb = GUI(root)
[Link]()
67
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link]
import [Link]
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
[Link] = master
[Link] = [Link](master)
[Link](side='top', expand=True, fill='x')
img = [Link](file='[Link]')
self.toolbar_button_open = [Link]([Link], command=self.file_open_handler,
image=img, padx=0, pady=0)
self.toolbar_button_open.image = img
self.toolbar_button_open.pack(side='left')
img = [Link](file='[Link]')
self.toolbar_button_saveas = [Link]([Link], command=self.file_saveas_handler,
image=img, padx=0,
pady=0)
self.toolbar_button_saveas.image = img
self.toolbar_button_saveas.pack(side='left')
img = [Link](file='[Link]')
self.toolbar_button_close = [Link]([Link], command=self.file_close_handler,
69
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
image=img, padx=0, pady=0,
state=[Link])
self.toolbar_button_close.image = img
self.toolbar_button_close.pack(side='left')
img = [Link](file='[Link]')
self.toolbar_button_exit = [Link]([Link], command=[Link], image=img,
padx=0, pady=0)
self.toolbar_button_exit.image = img
self.toolbar_button_exit.pack(side='left')
img = [Link](file='[Link]')
self.toolbar_button_cut = [Link]([Link], command=self.edit_cut_handler,
image=img, padx=0, pady=0)
self.toolbar_button_cut.image = img
self.toolbar_button_cut.place(x=300, y=0, width=64, height=64)
except Exception as e:
[Link](title='Error', message=str(e))
def file_close_handler(self):
[Link]('1.0', 'end')
self.toolbar_button_open.config(state=[Link])
self.toolbar_button_close.config(state=[Link])
70
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link]
import [Link]
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
self.frame1 = [Link](master)
self.label_name = [Link](self.frame1, text='Adı Soyadı', width=9, justify='left',
anchor='e')
self.label_name.pack(side='left', padx=(0, 5))
self.frame2 = [Link](master)
self.label_no = [Link](self.frame2, text='No', width=9, anchor='e')
self.label_no.pack(side='left', padx=(0, 5))
self.frame3 = [Link](master)
self.button_cancel.pack(side='right')
self.button_ok.pack(side='right', padx=(0, 5))
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
from PIL import Image
from PIL import ImageTk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
self.frame4 = [Link](master)
self.frame3 = [Link](self.frame4)
self.frame1 = [Link](self.frame3)
self.label_name = [Link](self.frame1, text='Adı Soyadı', width=9, anchor='e',
font='Calibri 12')
self.label_name.pack(side='left')
71
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
self.entry_name = [Link](self.frame1, width=25, font='Calibri 12')
self.entry_name.pack(side='left')
self.frame2 = [Link](self.frame3)
self.label_no = [Link](self.frame2, text='No', width=9, anchor='e', font='Calibri
12')
self.label_no.pack(side='left')
[Link](side='top', anchor='w')
[Link](side='left')
img = [Link]('[Link]')
img = [Link]((60, 50))
img = [Link](img)
self.frame5 = [Link](master)
self.button_cancel.pack(side='right')
self.button_ok.pack(side='right', padx=(0, 7))
[Link](width=False, height=False)
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Calculator')
[Link](width=False, height=False)
self.frame_main = [Link](master)
self.frame_main.pack(side='top', expand=True, fill='both', padx=10, pady=10)
[Link] = [Link]()
[Link] = [Link](self.frame_main, justify='right', font='calibri 12', width=25,
textvariable=[Link], state='disabled', disabledbackground='white',
disabledforeground='black')
[Link](side='top', fill='x', pady=(0, 15))
self.frame1 = [Link](self.frame_main)
72
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
self.button_backspace['command'] = lambda: self.button_handler(self.button_backspace)
self.button_backspace.pack(side='left', padx=(0, 10))
[Link](side='top', anchor='w')
self.frame2 = [Link](self.frame_main)
self.frame3 = [Link](self.frame_main)
self.frame4 = [Link](self.frame_main)
73
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
self.button_2 = [Link](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.frame5 = [Link](self.frame_main)
self.lastkey_op = False
self.prev_val = 0
self.last_op = None
74
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
result = self.prev_val / float([Link]())
self.lastkey_op = True
self.last_op = button['text']
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
75
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link](row=1, column=1, columnspan=2, sticky='we')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
from PIL import Image
from PIL import ImageTk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
[Link](width=True, height=True)
img = [Link]('[Link]')
img = [Link]((45, 45))
img = [Link](img)
self.label_image.image=img
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
[Link](0, weight=1)
[Link](1, weight=1)
[Link](2, weight=1)
[Link](0, weight=1)
[Link](1, weight=1)
[Link](2, weight=1)
root = [Link]()
gdb = GUI(root)
[Link]()
#----------------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('Sample Messagebox')
[Link](width=True, height=False)
self.entry_name = [Link](master)
self.entry_name.grid(row=0, column=1, sticky='we', padx=(0, 10), pady=(10, 0))
self.entry_no = [Link](master)
self.entry_no.grid(row=1, column=1, sticky='we', padx=(0, 10), pady=(5, 10))
[Link](1, weight=1)
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
77
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
from PIL import Image
from PIL import ImageTk
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
img = [Link]('abbey_road.jpg')
img = [Link](img)
[Link](fill='both', expand=True)
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
from PIL import Image
from PIL import ImageTk
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
self.IMAGE_INIT_X = 300
self.IMAGE_INIT_Y = 200
78
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
img = [Link]('abbey_road.jpg')
img = [Link](img)
self.image_width = [Link]()
self.image_height = [Link]()
[Link]('<Button-1>', self.mouse_click_handler)
[Link]('<B1-Motion>', self.mouse_motion_handler)
[Link]('<ButtonRelease-1>', self.mouse_release_handler)
[Link](fill='both', expand=True)
[Link] += deltax
[Link] += deltay
[Link] = event.x
[Link] = event.y
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import datetime
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
xpos = 100
79
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
ypos = 20
for i in range(20):
[Link].create_text(xpos, ypos, text=str(i), font='Arial 12 bold', fill='blue')
ypos += 20
t = [Link]()
[Link].create_text(300, 300, text=f'{[Link]}:{[Link]}:{[Link]}',
fill='magenta', font='Calibri 30 bold')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link] as ttk
import [Link]
class GUI:
def __init__(self, master):
[Link]('900x800')
[Link]('Sample Messagebox')
self.combobox_tv = [Link]()
self.frame_keyboard_setting = [Link]([Link])
self.frame_mouse_setting = [Link]([Link])
[Link](self.frame_keyboard_setting, text='Keyboard')
[Link](self.frame_mouse_setting, text='Mouse')
def button_ok_handler(self):
print([Link]())
print(self.combobox_tv.get())
print([Link]())
def button_progress_handler(self):
if [Link]['value'] == [Link]['maximum']:
[Link](title='Warning', message='it reached maximum value')
[Link]['value'] = [Link]['value'] + 1
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link] as ttk
class GUI:
def __init__(self, master):
[Link]('900x800')
[Link]('Sample Messagebox')
81
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link] = [Link](master, width=400, height=400)
self.frame_students = [Link]([Link])
self.frame_teachers = [Link]([Link])
[Link](self.frame_students, text='Students')
[Link](self.frame_teachers, text='Teachers')
[Link]('<<NotebookTabChanged>>', self.tab_changed_handler)
def button_ok_handler(self):
#[Link](self.frame_students)
[Link](1)
#[Link](0, text='xxxxx')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link] as ttk
class GUI:
def __init__(self, master):
[Link]('900x800')
[Link]('Sample Messagebox')
[Link] = {'Deri Mantarı': 'Deri mantarı tehlikeli olmayan kronik bir hastalıktır.',
'Bakteri Enfeksiyonları': 'Yüksek ateş görülür, antibiyotikle tedavi
edilir.'}
[Link] = [Link](master)
[Link] = [Link]([Link], height=30)
[Link] = [Link]([Link])
[Link]('<<TreeviewSelect>>', self.treeview_select_handler)
[Link](side='left', expand=True, fill='both')
[Link](side='left', expand=True, fill='both')
82
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link](side='top', expand=True, fill='both')
def button_ok_handler(self):
pass
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link] as ttk
import os
import [Link]
import datetime
ROOT_PATH = 'F:\\'
class GUI:
def __init__(self, master):
[Link]('900x800')
[Link]('Sample Messagebox')
[Link] = [Link](master)
[Link] = [Link]([Link], height=30, selectmode='browse',
columns=('Directory', 'Modification Date'))
[Link]('Directory', text='Directory')
[Link]('Modification Date', text='Modification Date')
[Link]('<<TreeviewSelect>>', self.treeview_select_handler)
[Link]('<<TreeviewOpen>>', self.treeview_open_handler)
83
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
for entry1 in it1:
try:
if entry1.is_dir():
id = [Link]('', 'end', tags=([Link], False),
text=[Link]([Link]), values=[[Link]([Link]),
[Link]([Link])])
with [Link]([Link]) as it2:
for entry2 in it2:
if entry2.is_dir():
[Link](id, 'end', tags=([Link], False),
text=[Link]([Link]), values=[[Link]([Link]),
[Link]([Link])])
except:
pass
def button_ok_handler(self):
pass
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
import [Link] as ttk
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Sample Messagebox')
84
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
style = [Link]()
[Link]('[Link]', foreground='red')
[Link]('[Link]', foreground='blue')
root = [Link]()
gdb = GUI(root)
[Link]()
# --------------------------------------------
import tkinter as tk
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Tk Sınıfından Ana pencere')
[Link] = master
def button_create_handler(self):
[Link] = [Link]([Link])
[Link](f'TopLevel Sınıfından Ana Pencere: {[Link]}')
[Link]('800x600')
[Link] += 1
root = [Link]()
gdb = GUI(root)
# --------------------------------------------
import tkinter as tk
from PIL import Image
from PIL import ImageTk
class GUI:
def __init__(self, master):
[Link]('800x600')
[Link]('Tk Sınıfından Ana pencere')
[Link] = master
[Link] = [Link](master)
[Link](side='top', fill='both', expand=True)
self.button_create = [Link](master, text='Create Modal Dialog',
command=self.button_create_handler)
self.button_create.pack()
def button_create_handler(self):
85
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link] = MyDialog([Link])
class MyDialog([Link]):
def __init__(self, master):
super().__init__(master)
[Link]('+400+400')
[Link](width=False, height=False)
[Link](master)
self.grab_set()
[Link](width=True, height=True)
img = [Link]('[Link]')
img = [Link]((45, 45))
img = [Link](img)
self.label_image.image = img
def button_ok_handler(self):
[Link]()
def button_cancel_handler(self):
[Link]()
root = [Link]()
gdb = GUI(root)
[Link]()
PyQt Python'ın standart kütüphanesi içerisinde olmadığı için bunun önce pip yoluyla kurulması gerekmektedir.
Kurulum komut satırından şöyle yapılabilir:
86
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Ya da PyCharm IDE'sinde File/Settings/Project/Project Interpreter/de kurulum yapılabilir:
PyQt projesi [Link] sitesinde barındırılmaktadır. PyQt'nin genel dokümantasyonuna aşağıdaki adresten u
ulaşabilirsiniz:
[Link]
[Link]
Ancak buradaki sınıf dokümantasyonunda sınıfların metotları tek tek açıklanmamaktadır. Bu nedenle ayrıntılı
açıklama için Qt'nin aşağıdaki orijinal dokümantasyonuna başvurabilirsiniz:
[Link]
Ekrana boş bir pencere çıkartan basit PyQt programı şöyle yazılabilir:
#[Link]
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
İskelet programda [Link] modülünün içerisindeki tüm isimlerin dışarıya aktarıldığına dikkat edniz. Bu
sayede biz PyQt sınıflarını hiç niteliklendirme yapmadan doğrudan kullanabilmekteyiz. PyQt5 bir paket (package)
biçiminde organize edilmiştir. PyQt5 paketinin içerisinde çeşitli modüller vardır. En önemli modüllerden biri
QtWidgets isimli modüldür. PyQt kütüphanesinde bütün sınıflar Q hatfiyle başlatılarak isimlendirilmiştir. Ancak
87
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
programcı kendi sınıflarını Q harfi olmadna isimlendirmelidir. QAyrıca PyQt'de sınıfların metotları "deve notasyonu
(camel casting)" isimlendirilmiştir.
Bir PyQt programında uygulamanın tamamı QApplication isimli bir sınıfla temsil edilmektedir. QApplication sınıfı
bizden komut satırı argümanlarını parametre olarak almaktadır. QApplication nesnesi yaratıldıktan sonra sıra
programın ana penceresinin yaratılmasına gelmiştir. Ana pencere QWidget sınıfı ile oluşturulabilir. Ancak biz
QWidget nesnesine eklemeler yapacağımız için ana pencerenin bu QWidtget sınıfından türetilmiş olan bir sınıfla
oluşturulması daha uygundur. Yani aslında iskelet program aşağıdaki gibi de oluşturulabilirdi:
#[Link]
import sys
from [Link] import *
app = QApplication([Link])
mainWindow = QWidget()
[Link]()
[Link]()
Ancak bu durumda biz QWidget üzerinde eklemeler yapamazdık. İşte tipik olarak PyQt uygulamalarında ana pencere
doğrudan QWidget sınıfı ile değil QWidget sınıfından türetilmiş bir sınıf ile oluşturulmaktadır. İskelet programda
MainWindow isimli sınıf QWiget sınıfından türetilmiştir. MainWindow sınıfının __init__ metodunda taban sınıf olan
QWidget sınıfının __init__ metodunun çağrıldığına dikkat ediniz. Tabii aslında biz MainWindow sınıfının __init__
metodunu boş bırakmayacağız. Eğer boş bırakacak olsaydık doğrudan sınıfı pass anahtar sözcüğüyle de kapatabilidik.
İskelet programda ana pencere nesnesi yaratıldıktan sonra QWidget sınıfından geelen show isimli metot ile bu
pencere görünür yapılmıştır. Bir pencerenin yaratılmasıyla görünür yapılması farklı adımlarla gerçekleştirilmektedir.
İskelet programda QApplication sınıfının exec isimli metodu mesaj döngüsü (message loop) oluşturmak için
kullanılmaktadır. Ana pencere kapatıldığında bu fonksiyon sonlanmış olur. Program yaşamaını QApplication sınıfının
exec metodunda geçirir.
PyQt framework'ünde mesaj döngüsü QApplication sınıfının exec metoduyla oluşturulmaktadır. Bu metot bir döngü
içerisinde mesaj kuyruğundaki sıradaki mesajı alır ve programcının belirlediği fonksiyonları ya da metotları çağırır.
Programcı da GUI programını "şu düğmeye tıklandıysa şu metot çağrılsın" biçiminde organize eder. Bu çağırma
işlemini tamamen PyQt framework'ü arka planda organize etmektedir.
Programların GUI arayüzlerinde kullanılan düğmeler, edit alanları, listeleme kutuları, menüler vs. hep aslında birer alt
penceredir. Örneğin:
88
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Aslında yukarıdaki ana pencerede gördüğünüz düğme gibi, seçenek kutusu gibi, radyo düğmesi gibi görsel öğeler içi
boş pencereler üzerinde çizim işlemleri yapılarak oluşturulmuşlardır. Biz de sıfırdan içi boş bir pencereden hareketle
kendi görsel öğelerimizi oluşturabiliriz. Ancak Qt gibi ortamlarda çeşitli görsel elemanlar alt pencere biçiminde zaten
önceden oluşturulmuş durumdadır. Bu görsel öğeler birer sınıfla temsil edilmiştir. Böylece programcı hangi görsel
öğeyi kullanacaksa o sınıf türünden bir nesne yaratır, sonra o nesnenin üye fonksiyonlarını çağırarak nesnenin tam
olarak istediği biçimde gözükmesini sağlar. GUI ortamlarında kullanıcı arayüzleri hep böyle oluşturulmaktadır. Ancak
bir noktaya dikkat çekmek istiyoruz: Her ne kadar Qt’de -diğer ortamlarda olduğu gibi- pek çok görsel öğe alt pencere
biçiminde hazır bulundurulmuş olsa da bunlar programcının isteklerini tam olarak karşılamıyor olabilir. Bu durumda
programcı arzu ettiği görsel öğeleri sıfırdan içi boş alt pencerelerden hareketle oluşturmak ya da başkaları tarafından
oluşturulmuş olanları satın alıp kullanmak isteyebilir. Neyse ki gereksinimlerin büyük çoğunluğu mevcut alt pencere
sınıflarıyla karşılanabilmektedir.
PyQt'de görsel arayüzü oluşturan alt pencere sınıflarının bütün ortak özellikleri QWidget isimli bir sınıfta toplanmıştır.
Tüm pencere sınıfları QWidget sınıfından türetilmiş durumdadır. Yani QWidget sınıfı tüm pencerelerin ortak
özelliklerini barındıran en temel sınıftır. İskelet PyQt programında da ana pencerenin QWidget sınıfyla yaratıldığına
dikkat ediniz. Diğer tüm görsel öğeler çeşitli biçimlerde çeşitli sınıflardan türetilmiş durumdadır. Örneğin:
GUI sistemlerde ekranda görüntülenecek en küçük birime pixel (picture element) denilmektedir. Her türlü görüntü
(yazılar, resimler, şekiller vs.) aslında pixellerin bir araya gelmesiyle oluşturulmaktadır. Her pixel 16 milyon renkten
bir tanesiyle renklendirilebilmektedir. Ekran bir pixel matrisi olarak düşünülebilir. Örneğin 1200X800 çözünürlük
demekle aslında yatayda 1200, düşeyde 800 pixel'in olduğu bir matris anlaşılmaktadır. İşte PyQt'de bir öğenin yerini
belirlemek için pixel koordinat sistemi kullanılmaktadır. Alt pencereler için orijin noktası o alt pencerenin üst
penceresinin çalışma alanının sol-üst köşesidir. Üst pencereler için ise orijin noktası masaüstünün sol üst köşesidir.
89
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
PyQt'de Alt Pencerelerin Oluşturulması
Alt pencereler ilgili alt pencere sınıfı türünden nesne yaratılarak oluşturulabilirler. Alt pencereler oluşturulurken
bunların başlangıç metotlarında (__init__ metotlarında) yaratılacak olan alt pencerenin üst penceresi belirtilir. Genel
olarak pek çokm alt pencere sınıfının başlangıç metotlarının birinci parametresi başlık yazısı, ikinci parametresi üst
pencere nesnesidir. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link] = QPushButton('Ok', self)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Burada QPushButton isimli alt pencere yaratılmış ve onun referansı sınıfın pushButtonOk isimli örnek özniteliğinde
saklanmıştır.
Başlangıç metodunun birinci parametresinin düğme üzerinde ıkacak yazıyı, ikinci parametresinin ise üst pencere
nesnesini belirttiğine dikkat ediniz. Buradaki self QMainWindow nesnesini temsil emektedir.
Pencerelerin Konumlandırılması
Bir pencere (QWidget) yaratıldığında başlangıç konumu (0, 0) biçimindedir. Programcının yarattığı pencereleri
konumlandırması gerekebilir. Aslında konumlandırma birtakım "layout" nesneleriyle otomatik de yapılabilmektedir.
Bu konu ileride ele alınacaktır. Pencerelerin konumlandırılması taban sınıf olan QWidget sınıfının metotları ile
yapılmaktadır.
QWidget sınıfının pos isimli metodu pencerenin sol-üst köşesinin koordinatını bize QPoint türünden bir nesne olarak
verir. QPoint bir noktayı temsil eden genel bir sınıftır. Aslında doğrudan QWidget sınıfının x ve y isimli metotları bize
ayrı ayrı pencerennin konumunu QPoint olarka değil int türden vermektedir.
QWidget sınıfının move isimli metodu pencereyi konumlandırmak için kullanılır. move bizden pencerenin sol-üst
köşesinin konumunu x ve y olarak almaktadır. Örneğin:
90
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
QWidget sınıfının rect isimli örnek metodu bize pencerenin konumunu QRect isimli bir sınıf türünden vermektedir.
Ancak bu metodun verdiği konum yine kendi çalışma alanı orijinlidir. Dolayısıyla bu metottan elde edeceğimiz x ve y
değerleri her zaman (0, 0) olur. QRect dikdörtgensel bir bölgeyi temsil eden genel bir sınıftır.
size metodu bize pencerenin genişlik ve yüksekliğini QSize isimli bir sınıf türünden vermektedir. Pencerenin genişlik
ve yüksekliğini resize metodu ile değiştirebiliriz. Örneğin:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link] = QPushButton('Ok', self)
[Link](100, 100);
[Link](200, 200)
QWidget sınıfının geometry isimli metodu bize oencerenin konumunu QRect olarak verir. Ancak bunun pos
metodundan farkı orijin olarak üst pencereyi almasıdır. QWidget sınıfının setGeometry metoduyla da biz tek hamlede
hem konumlandırma hem de boyutlandırma yapabiliriz. Bu metot bizden sırasuyla x, y, width ve height değerlerini
istemektedir. Örneğin:
Pencerenin genişliğini QWidget sınıfının width örnek metodu ile, yüksekliğini de height örnek metodu ile doğrudan
alabiliriz.
Tabii ana pencere de QWidget sınıfın türetildiğine göre biz yukarıdaki metotları ana pencere için de kullanabiliriz.
Ancak ana pencerede geometry, size, width ve height gibi metotlar pencere başlığını ve sınır çizgilerini dahil
etmezler. Biz ana pencere için pencere başlığı ve sınır çizgileri dahil olmak üzere konum almak istiyorsak
frameGeometry metodunu kullanmalıyız. Örneğin:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
print([Link]())
print([Link]())
Burada ana pencerenin konumu hem geometry metodu ile hem de frameGeometry metodu ile elde edilmiştir. Her
iki metot da ana pencereler için x = 0, ve y = 0 değeri verirler. Ancak genişlik ve yükseklik geometry metodunda sınır
çizgiler ve başlık kısım dahil olmayacak biçimde (yani yalnızca çalışma alanı dahil olacak biçimde) frameGeomtry
metodunda bunlar da dahil olacak biçimde verilir.
Ana pencereler için en büyük ve en küçük genişlik ve yükseklik değerleri QWidget sınıfının setMaximumWidth,
setMinimumWidth, setMaximumHeight, setMinimumHeight metotlarıyla ayarlanabilmektedir. Bu metotlardaki
değerler yine çalışma alanı ile ilgilidir. Yani pencerenin başlık kısmı ve sınır çizgileri bu değerlerde dikkate
alınmamaktadır.
Konumlandırmayla ilgili akla takılan bazı sorular ve onların yanıtları aşağıda verilmiştir:
91
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Soru: pos, x ve y ve mov fonksiyonlarındaki sol üst köşe koordinatları neresidir ve orijini nereye göredir?
Cevap: Bu fonksiyonlardaki sol üst köşe her zaman pencerenin tamamının sol üst köşesidir. Buradaki sol üst köşe
koordinatları alt pencere için üst pencerenin çalışma alanının sol üst köşesi orijinli, ana pencereler için masa üstünün
sol üst köşesi orijinlidir.
Soru: Mademki rect bize her zaman sol üst köşe koordinatı sıfır olan bir QRect veriyor, bunun uygulamada bir anlamı
olabilir mi? Yani rect ile elde edilen bilgi ile size ile elde edilen bilgi aynı olmuyor mu?
Cevap: Evet teorik olarak böyle ancak bazen çalışma alanı konumunun QRect olarak ve çalışma alanı orijinli alınması
gerekebilmektedir.
Tüm pencerelerin bir yazısı vardır. Örneğin QPushButton penceresinin yazısı düğme üzerindeki yazıdır. QTextBox
penceresinin yazısı edit alanının içerisindeki yazıdır. Ana pencerenin yazısı pencere başlığındaki (caption) yazıdır.
92
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
PyQt'de ana pencerenin üzerinde başlık yazısı QWidget sınıfının windowTitle isimli metodu ile alınabilir,
setWindowTitle metodu ile set edilebilir. Örneğin:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
Herhangi bir alt pencerenin yazısı ise QWidget sınıfından gelen text metodula alınıp setText metoduyla set edilebilir.
GUI uygulamalarında mesajları konsol ekranına yazmak iyi bir teknik değildir. Bu ancak debug amaçlı uygulanabilecek
bir tekniktir. Programcı mesajları doğrudan bir diyalog penceresi içerisinde kullanıcıya gösterir. İşte Qt'de bu diyalog
penceresine MessageBox denilmektedir. Messagebox PyQt'de QMessageBox sınıfıyla temsil edilmiştir. MessageBox
oluşturmak için programcı önce message box penceresini yaratır. Sonra pencerenin başlık yazısını setWindowTitle
metodu ile, içeride görüntülenecek yazıyı da setText metodu ile oluşturur. Sonra da sınıfın exec metodunu çağırır.
Örneğin:
msg = QMessageBox(self)
[Link]('Error')
[Link]('File not found!')
[Link]()
Bu biçimde çıkartılacak mesaj penceresinde default olarak Ok düğmesi vardır. Ancak biz QMEssageBox sınıfının
setStandardButtons metodu ile birkaç düğme arasından bir takım seçebiliriz.
msg = QMessageBox(self)
[Link]('Error')
[Link]('File not found!')
[Link]([Link]|[Link]|[Link])
[Link]()
Tabii bu tür durumlarda bizim mesaj penceresini hangi düğmeyle kapattığımız bilmemiz gerekir. exec metodunun
geri dönüş değeri hangi düğmeyle mesaj penceresinin kapatıldığını belirtmektedir. Örneğin:
msg = QMessageBox(self)
[Link]('Error')
[Link]('File not found!')
[Link]([Link]|[Link]|[Link])
result = [Link]()
if result == [Link]:
print('abort')
elif result == [Link]:
print('retry')
else:
print('ignore')
Mesaj pencerelerine birkjaç simge görüntüsünden biri yerleştirilebilir. Bunun QMessageBox sınıfının setIcon metodu
kullanılmaktadır. Örneğin:
msg = QMessageBox(self)
[Link]('Error')
[Link]('File not found!')
[Link]([Link]|[Link]|[Link])
[Link]([Link])
[Link]()
Aslında biz doğrudan pencereyi QMessageBox sınıfının question, warning, information, critical isimli static
metotlarıyla da oluşturabiliriz. Bu metotlar sırasıyla bizden üst pencere referansını, balık yazsını ve pencere
içerisindeki yazıyı almaktadır. Örneğin:
Qt'de gerçekleşen olaylara sinyal (signal), bu olaylar sonucunda çağrılacak metotlara da slot (slot) denilmektedir. Biz
sinyallere slot bağlayarak belli bir olay gerçekleştiğinde bir metodumuzun çağrılması sağlayabiliriz. Sinyallere yalnızca
slot'lar değil, sinyaller de bağlanabilmektedir. Örneğin biz A sinyaline B sinyalini de bağlayabiliriz. Bu durumda A
sinyali oluştuğunda (Qt terminolojisinde "emit" edildiğinde deniyor) bu durum B sinyalinin oluşmasına yol
açmaktadır. Bir sinyale tek bir slot ya da sinyal bağlanmak zorunda değildir. İstenildiği kadar çok sinyal ve slot
bağlanabilir.
Sınıfların sinyallerinin neler olduğu Qt dokümanlarda belirtilmektedir. Türemiş sınıf taban sınıfın sinyallerini de
içermektedir. Örneğin QPushButton sınıfının tüm sinyal kümesi QPushButton,i QAbstractButton ve QWidget
sınıflarının sinyallerinden oluşmaktadır.
Örneğin biz bir düğmeye basıldığında bir kodumuzu çalıştırmak isteyelim. İşte yapmamız gereken şey QPushButton
sınıfının clicked isimli sinyaline bir slot bağlamaktadır. Sinyal sınıflarının connect isimli metotlarıyla bu bağlantı
yapılabilmektedir. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
def buttonClickedHandler(self):
[Link](self, 'Message', 'Ok')
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
94
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bu örnekte sınıfın örnek clicked elemanı bir sinyaldir. Bu sinyale buttonClickHandler isimli slot bağlanmıştır. Böylece
düğmeye tıkladığımızda bu metot çağrılır. Sinyaller pyqtBoundSignal isimli bir sınıf türündendir. conenct metodu da
aslında bu sınııfn bir örnek metodudur. Tabii aslında slot global bir fonksiyon da olabilmektedir. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
def buttonClickedHandler():
[Link](None, 'Message', 'Ok')
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Tabii istersek slot yerine doğrudan bir lambda ifadesi de kullanabiliriz. Örneğin:
Penecelerin Kapatılması
Bir pencere QWidget sınıfından gelen close metoduyla kapatılabilir. Söz konusu pencere ana pencereyse close ana
pencerenin kapanmasına ve dolayısıyla da mesaj döngüsünden (yani exec metodundan) çıkılmasına yol çamaktadır.
O halde bizim GUI programı sonlandırmak için tek yapacağımız şey ana pencereyi [Link]() metoduyla kapatmaktır.
Bu bölümde Qt'deki temel alt pencere sınıfları ele alınacaktır. GUI uygulamalar bu sınıflar türündne nesneler
yaratılarak gerçekleştirilmektedir.
QPushButton Sınıfı
Pushbutton en çok kullanılan standart GUI elemanıdır. Belli bir olayı başlatmak ya da bitirmek için kullanılır.
QPushButton sınıfı QAbstractButton sınıfından türetilmiştir. Aslında QPushButton sınıfının QCheckBox ve
QRadioButton sınıfları ile ortak elemanları vardır. Bu ortakl elemanlar QAbstractButton sınıfında toplanmıştır.
QAbstractButton sınıfı da QWidget sınıfından türetilmiş durumdadır.
95
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
QPushButton nesnesi bir düğme ve üzerinde bir yazıyla karakterize edilmiştir. Bu düğmeye tıklanıp el farden
çakildiğinde sınıfın QAbstractButton sınıfından gelen clicked isimli sinyali tetiklenir.
QCheckBox Sınıfı
Bu sınıf bir kutu ve onun yanında bir yazıdan oluşan bir alt pencereyi temsil etmektedir. Bu pencereye tıklandığında
kutu çarpılanır, bir daha tıklandığında çarpısı kaldırılır. Programcı check box nesnesinden onun çarpılı olup olmadığı
sonucunu elde etmek ister. Kontrolün çarpılı olup olmadığı QCheckBox sınıfının QAbstractButton sınıfından gelen
isChecked metodu ile elde edilebilir. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
def buttonClickHandler(self):
[Link](self, 'Message', 'checked' if [Link]() else
'unchecked')
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
QCheckBox sınıfının QAbstractButton sınıfından gelen setChecked metodu ile biz seçenek kutularını programlama
yoluyla da çarpılayabiliriz ya da çarpısını kaldırabiliriz. Örneğin:
QCheckBox sınıfının setTristate metodu True olarak girilirse üç konumlu seçenek kutusu oluşturulmuş olur. Üç
konumlu seçenek kutularında kontrolün konumu checkState metodu ile alınmaktadır. Benzer biçimde set etme de
setCheckState metodu ile yapılmaktadır. Bu metotlar şu üç değeri almaktadır:
96
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
0 Unchecked
1 PartiallyChecked
2 Checked
Örneğin:
def buttonClickHandler(self):
[Link](self, 'Message', ['Unchecked', 'Indeterminate',
'Checked'][[Link]()])
Radyo Düğmeleri bir grup düğmenin yalnızca birinin seçilebildiği bir durum oluşturmak için kullanılır. Aynı pencerenin
alt pencerelerindeki radyo düğmeleri bir grup oluşturmaktadır. Bir dayo düğmesine tıklandığında daha önce
çarpılanmış olan düğme yerine artık tıklanan çarpılanır. Tabii radyo düğmelerinin bir tane yaratılması anlamsızdır.
Radyo düğmeleri PyQt'de QRadioButton sınıfıyla temsil edilmiştir. Radyo düğmelerindne programcı nihai olarak
hangi düğmenin seçilmiş olduğu bilgisini alır. Bunun maalesef çok pratik bir yolu yoktur. Programcı tek tek bu
düğmelere bakarak isChecked metodu ile kontrol yapar. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
def buttonClickHandler(self):
if [Link]():
result = 'A Checked'
elif [Link]():
result = 'B Checked'
elif [Link]():
97
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
result = 'C Checked'
elif [Link]():
result = 'D Checked'
elif [Link]():
result = 'E Checked'
else:
result = 'Nothing Checked'
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Hangi radyo düğmesinin seçildiğini başka yöntemlerle de anlayabiliriz. Örneğin radyo düğmelerini bir listeye
yerleştirip bir döngü içerisinde hangisinin seçili olduğuna bakılabilir:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link] = []
rb = QRadioButton('A', self)
[Link](150, 10)
[Link](rb)
rb = QRadioButton('B', self)
[Link](150, 30)
[Link](rb)
rb = QRadioButton('C', self)
[Link](150, 50)
[Link](rb)
rb = QRadioButton('D', self)
[Link](150, 70)
[Link](rb)
rb = QRadioButton('E', self)
[Link](150, 90)
[Link](rb)
def buttonClickHandler(self):
for rb in [Link]:
if [Link]():
break
else:
rb = None
main()
Bir pencerenin tüm radyo düğmeleri aynı grubu oluşturmaktadır. Pekiyi aynı pencerede birden fazla grup
oluşturabilit miyiz? İşte bu durumda radyo düğmelerini ana pencerenin altındaki başka pencerelere yerleştirmek
gerekir. Bunun tipik olarak QGroupBox pencereler bulundurulmuştur. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](640, 300)
self.groupBox1 = QGroupBox(self)
[Link](10, 50, 200, 80)
self.groupBox2 = QGroupBox(self)
[Link](270, 50, 200, 80)
def buttonClickHandler(self):
pass
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
99
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Programın ekran görüntüsü şöyledir:
QLabel Kullanımı
İsmine QLabel denilen amacı yalnızca alt pencere içerisinde yazı göstermek olan basit bir kontrol vardır. Bu kontrol
bizden bir yazıyı alır, transparan bir zemine bu yazıyı yazar. Dolayısıyla GUI ekranlarda sabit yazılar bu sınıfla
oluşturulmaktadır. Sınıfın QWidget sınıfından gelen text ve setText metotları ilgili yazıyı alıp set etmek için
kullanılmaktadır. Örneğin:
import sys
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](300, 200)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
QLabel içeriisndeki yazıyı istediğimiz bir fontla da yazdırabiliriz. Bunun için bir QFont nesnesi oluşturup QLable
sınıfının setFont metodu çağrılmalıdır. Örneğin:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](300, 200)
Aslında mevcut fontu font metodu ile alıp yalnızca onun boyutunu da değiştirebiliriz. Örneğin:
import sys
from [Link] import *
from [Link] import *
100
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](300, 200)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Kullanıcıdan tek satırlı bilgi almak için QLineEdit isimli sınıf kullanılmaktadır. Bir QLineEdit nesnesi diğer GUI öğeler
gibi sınıfın başlangıç metodula yaratılır. Edit alınındaki yazı yine QWidget sınıfından gelen text metodula elde
edilmektedir. Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](300, 150)
[Link] = QLineEdit(self)
[Link](10, 24)
[Link](200, 20)
[Link] = QLineEdit(self)
[Link](10, 64)
[Link](200, 20)
def buttonOkClickedHandler(self):
msg = [Link]() + ', ' + [Link]()
[Link](self, 'Message', msg)
101
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def buttonQuitClickedHandler(self):
[Link]()
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
QLineEdit sınıfının setMaxLength isimli metodu edit alanındaki karakter sayısını kısıtlamak için kullanılmaktadır.
Sınıfın setAlignment metodu ile biz hizalama sağlayabiliriz. Hizalama değerleri şunlardır:
[Link]
[Link]
[Link]
[Link]
Örneğin:
[Link]([Link])
Çok satırlı edit aalanları adeta notepad benzeri bir editör gibidir. PyQt'de bu edit alanları QTextEdit sınıfıyla temseil
edilmiştir. Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 350)
[Link] = QTextEdit(self)
[Link](10, 10, 580, 330)
[Link](QFont('Arial', 14))
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Yine çok satırlı edit alanından tüm yazı text metodu ile alınıp setText metodu ile geri set edilebilir. Örneğin bir
dosyanın içeriğini QTextEdit nesnesine yerleştirebiliriz:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QWidget):
102
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 350)
[Link] = QTextEdit(self)
[Link](10, 10, 580, 330)
[Link](QFont('Consolas', 14))
try:
f = open('[Link]')
s = [Link]()
[Link](s)
except:
[Link](self, 'Error', 'file error!')
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
QListWidget Kullanımı
Bu kontrole pek çok framework'te "listbox" da denilmektedir. QListWidget ismi üzerinde bir grup öğeyi tutan bir liste
oluşturur ve kullanıcının bu listeden seçim yapabilmesini sağlar. Kontrolün kullanımı şöyledir:
[Link] = QListWidget(self)
[Link](10, 10)
[Link](200, 300)
2) Sıra öğelerin listeye eklenmesine gelmiştir. Aslında QListWidget içerisine QListWidgetItem nesneleri eklenir. tipik
olarak önce QListWidgetItem nesnesi yaratıo sonra QListWidget sınıfının addItem metodu ile ekleme yapılabilir.
Ancak doğrudan string alan bir addItem metodu da vardır. Bu metot kendi içerisinde QListWidget nesneesi yaratıp
eklemeyi yapmaktadır. Örneğin:
[Link]('Adana')
[Link]('Adıyaman')
[Link]('Eskişehir')
Aslında aynı işlem sınıfın addItemsisimli metoduyla da tek hamlede yapılabilmektedir. AddItems dolaşılabilir bir
string'lerden oluşan bir nesneyi parametre olarak almaktadır. Örneğin:
3) QListWidget'ta seçili olan elemanlar sınıfın selectedItems metodu ile alınabilir. Ancak bu metot bize seçili olan tüm
nesneleri bir QListWidgetItem olarak vermektedir. Eğer tek seçim yapılmışsa programcı bu listenin ilk elemanını
alabilir. Örneğin:
def buttonOkClickedHandler(self):
[Link](self, 'Selected Items', [Link]()[0].text())
103
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Tabii listede hiçbir eleman seçili de olmayabilir. Bu durmda selectedItems bize boş bir liste verir. İşte programcının
bunu kontrol etmesi gerekebilmektedir. Örneğin:
def buttonOkClickedHandler(self):
if len([Link]()) != 0:
[Link](self, 'Selected Items', [Link]()[0].text())
Aslında seçilen aktif elemanın indeksicurrentRow metoduyla alınabilir. Zaten hiçbir eleman seçilmemişse bu metot
bize -1 vermektedir. Biz QListWidget nesnesi oluşturulduğunda sınıfın setCurrentRow isimli metoduyla beli bir
eleman seçili duruma da geitirilebilmektedir.
Listeleme kutularında bir eleman üzerine çift tıklama çok karşılaşılan bir eylemdir. Bir eleman üzerine çift
tıklandığında itemDoubleClicked isimli sinyal tetiklenmektedir. Bu sinyal için çağrılacak slotun QListWidgetItemn
tüüründen seçilen elemanı belirten bir parametresi vardır. Örneğin:
[Link]([Link])
...
def onListItemDoubleClicked(self, item):
[Link](self, 'Selected Item', [Link]())
Seçili eleman değiştiğinde de currentRowChanged isimli sinyal tetiklenmektedir. Bu sinyalin parametresi int türden
seçilen elemanın indeksini belirtmektedir. Böylece programcı eleman değiştiğinde yeni eleman hakkında bilgileri
gösterebilmektedir. Örneğin QListWidget içerisinde kişilerin isimleri olabilir. Kullanıcı bir kişiyi seçtiğinde onun
fotoğrafı otomatik olarak gesterilmek istenebilir. Seçili eleman değiştiğinde tetiklenen diğer bir sinyal de
currentItemChanged isimli sinyaldir. Bu sinyalin eski seçilen eleman ve yeni seçilen eleman olmak üzere
QListWidgetItem türünden iki parametresi vardır. Örneğin:
[Link]([Link])
...
def onCurrentItemChanged(self, old, new):
print([Link]())
QListWidget içerisindeki nesnelerin hepsini silmek için clear isimli metot, liste içerisindeki elemanların sayısını almak
için ise count isimli metot kullanılır.
resize Sinyali
Bir pencerenin boyutu değiştirildiğinde resizeEvent isimli metot çağrılmaktadır. Böylece programcı pencere
boyutunun değiştiğini otomatik olarak anlayabilir. Bu işlemin sinyal-slot mekanizmasıyla değil resize isimli metodun
yazılmasıyla yapıldığına dikkat ediniz. Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link](700, 500)
def main():
app = QApplication([Link])
104
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
mainWindow = MainWindow()
[Link]()
[Link]()
main()
PyQt'de resimler yaz çizim yoluyla ya da QLbale yoluyla görüntülenebilmektedir. Maalesef PyQt'de diğer
framework'lerde olduğu gibi "picturebox" benzeri bir kontol yoktur.
Bir QLabel nesnesi yazının yanı sıra aslında zemininde bir resim de gösterebilmektedir. Bunun tek yapılacak şey sınııfn
setPixmap metoduyla resim dosyasını belirtmektir. Bu metot bir QPixmap nesnesini parametre olarak alır. QPixmap
nesnesi de resmin yolk ifadesi verilerek oluşturulabilmektedir. QPixmap temel pek çok dosya formatını
desteklemektedir.
QLabel nesnesinin zeminine setPixmap metodu ile bir resim yerleştirildiğinde label otomatik olarak resim boyutuna
getirilir. Aşağıdaki örnekte önce QPixmap ile resim diskten yüklenerek kullanıma hazır hale getirilmiştir. Sonra ana
pencere tam bu resmin boyutuna ayarlanmıştır. Böylece ana resmin içerisinde bütün resm görüntülenmektedir.
Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link]('Generic Window')
pixmap = QPixmap('[Link]')
[Link]([Link]())
[Link] = QLabel(self)
[Link](pixmap)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
105
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Tabii resmin boyutlandırılarak görüntülenmesi de önemlidir. Maalesef QLabel bu kadar esnek değildir. Bu tür
durumlarda resmi çizim metotlarıyla çizmek çok daha fazla olanaklar sunmaktadır.
QComboBox Kullanımı
Combox kontrolü listbox (yani Qt'teki QListWidget) kontrolünün farklı bir biçimidir. Bir liste içerisindeki tek bir
elemanı seçmek kullanılır. Combobox'ın list alanı kontrole tıklandığında açılmaktadır . Combobox kullanımı listbox'a
oldukça benzerdir:
[Link] = QComboBox(self)
[Link](10, 10)
[Link](200, 30)
[Link]('Ankara')
[Link]('İzmir')
[Link]('Adana')
[Link]('Eskişehir')
[Link]('Kütahya')
[Link]('Antalya')
3) Seçilen elemanın kendisi current isimli metotla, onun yazısı da currentText isimli metotla elde edilebilir. Örneğin:
[Link]([Link])
...
def buttonOkClickedHandler(self):
print([Link]())
Combox'ın başlığındaki yazı kullanıcı tarafından değiştirilebilsin isteniyorsa setEditable metodu True ile çağrılmalıdır.
Combox'ta yeni bir eleman seçildiğinde currentIndexChanged isimli sinyal tetiklenir. Böylece biz yeni bir eleman
seçildiğinde otomatik bazı işlemleri yapabiliriz.
Menülerin Oluşturulması
106
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Menüler bir GUI programının en önemli elemanlarındadır. Menüler Windows gibi bazı işletim sistemlerinde ve
pencere yöneticilerinde pencereye özgüdür ve pencere içerisinde görüntülenir. Mac OS X gibi bazılarında ise
masaüstünde tek bir menü bulunmaktadır. Aktif uygulama değiştikçe bu menü o uygulamanın menüsü olur.
Menülerdeki ana çubuğa menü çubuğu (menu bar) denilmektedir. Menü çubuğuna bağlı olan menü pencerelerine
ise popup denir. Popup pencereler üzerinde menü elemanları vardır:
Menülerin ve araç çubuklarının QWidget penceresinde bulundurulması mümkün olsa da oldukça zahmetlidir. Bunun
için ana pencere görevi yapacak QMainWindow sınıfı düşünülmüştür. Yani menü ve araç çubuğu içeren
uygulamaların ana pencereleri için QWidget sınıfı değil, QMainWindow sınıfı kullanılmalıdır.
Menü çubuğu Qt’de QMenuBar sınıfıyla, Popup penceleri ise QMenu sınıfıyla tyemsil edilmektedir. Menü elemanları
ise QAction sınıfıyla temsil edilmiştir. QAction nesneleri yalnızca menü elemanları olarak değil aynı zamanda araç
çubuğu (toolbar) elemanları olarak da kullanılmaktadır. Menu elemanı seçildiğinde ya da araç çubuğu elemanına
tıklandığında emit edilecek sinyaller QAction sınıfının içerisindedir.
QMainWindow nesnesi yaratıldığında zaten bir QMenuBar nesnesi de yaratılmaktadır. Bu nesneneye biz
QMainWindow sınıfının menuBar metodu ile erişlebiliriz. Yani bizim ayrıca QMenuBar nesnesi yaratmamıza gerek
yoktur.
Bir Popup QMenu sınıfı ile yaratılır ve QMenuBar sınıfının addMenu metouyla menü çubuğuna eklenir. Örneğin:
filePopup = QMenu('&File)
[Link]().addMenu(filePopup)
Ya da popup eklemek için doğrudan QMenuBar sınıfının string parametreli AddMenu metodu da kullanılabilir:
filePopup = [Link]().addMenu('&File)
Burada aslında addMenu kendisi bir QMenu nesnesi yaratıp yine onu eklemektedir. Ancak PyQt5'te muhtemel bir
bug yüzünden QMenu nesnesinin kendisi addMenu ile eklendiğinde sorun oluşmaktadır. Bu nedenle PyQt5'te
eklemeyi string parametreli addMenu metoduyla aşağıdaki gibi yapınız:
filePopup = [Link]().addMenu('&File)
Bu biçimde addMenu kendi içerisinde yarattığı QMenu nesnesini bize geri dönüş değeri olarak vermektedir.
Popup pencereler menü çubuğuna eklendikten sonra sıra QAction nesnelerinin (yani menü elemanlarının) popup
pencerelere eklenmesine gelmiştir. Bunun için QMenu sınıfının addAction metotları kullanılır. addAction metotları
tek parametreli ya da iki parametreli biçimde kullanılabilmektedir:
107
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Tek parametreli kullanımda verilen yazı menü elemanında görüntülenmektedir. İki parametreli kullanımda hem yazı
hem de simge metoda verilmektedir. addAAction metotları geri dönüş değeri olarak yaratlan QAction nesnesini
vermektedir. Simgiler QIcon sınıfıyla temsil edilmişlerdir. Simgeler tipik olarak 16x16 ya da 32x32 .ico, .png ya da
.bmp dosya formatlarına ilişkin olabilir. Bir QIcon nesnesi dosyanın yol ifadesi belirtilerek QIcon('[Link]')
biçiminde yaratılabilir. Örneğin:
import sys
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 400)
filePopup = [Link]().addMenu('&File')
openAction = [Link]('&Open')
closeAction = [Link]('&Close')
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Şimdi de menü elemanlarının (yani QAction nesnelerine) aynı zamanda bir simge de ekleyelim. Bunun için öncelikle
Open ve Close elemanları için 16x16'lık birer simge (icon) bulmamız gerekir. Bedava simge bulunduran pek çok site
vardır. Biz kursumuzda bunun için [Link] sitesinden faydalanacağız. Simgeleri .png formatında indirmek
daha uygundur. Örneğin:
filePopup = [Link]().addMenu('&File')
openAction = [Link](QIcon('Icons/[Link]'), '&Open')
closeAction = [Link](QIcon('Icons/[Link]'), '&Close')
Aslında önce QAction nesneleri yaratıp sonra QMenu sınıfının addAction metotlarına bunları da verebiliriz. Ancak
PyQt5 yine burada böcekler içermektedir. Bu nedenle PyQt5'te QAction nesnelerini ayrı yaratıp eklemek yerine
doğrudan addAction metodu ile yaratınız.
Şüphesiz menü eklemenin en önemli amacı menü elemanı seçildiğinde birşeyler yapmaktır. İşte QAction sınıfının
triggered isimli sinyalleri menü elemanı seçildiğinde bir metodun çağrılmasını sağlamktadır. Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 400)
filePopup = [Link]().addMenu('&File')
108
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]([Link])
[Link]([Link])
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
İç içe popup menüler olabilir. Bu durumda QMenu elemanına addAction değil addMenu uygulamak gerekir. Örneğin:
filePopup = [Link]().addMenu('&File')
editPopupAction = [Link]('&Edit')
copyAction = [Link](QIcon('Icons/[Link]'),'Copy')
pasteAction = [Link](QIcon ('Icons/[Link]'), 'Paste')
QAction nesnelerine birer kısa yol tuşu da atayabiliriz. Bunun için QAction sınıfının setShortcut isimli metodu
kullanılmaktadır. Bu metot bizden QKeySequence türünden bir değer alır. Bu sınıfın standart bazı kısayol tuş
elemanları vardır. Örneğin:
filePopup = [Link]().addMenu('&File')
109
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
editPopupAction = [Link]().addMenu('&Edit')
copyAction = [Link](QIcon('Icons/[Link]'),'Copy')
[Link]([Link])
Biz kısa yol tuşu olarak istediğimiz bir tuş kombinasyonunu da atayabiliriz. Bunun için QKeySequence nesnesini tuş
kombinasyonunu belirten yazıyla yaratmalıyız. Örneğin:
[Link]('Ctrl+k')
Bazen bir eylemde menü elemanlarının konumlarının değiştirilmesi gerekebilir. Bu durumda menü elemanlarının
sınıfın veri elemanı yapılması uygun olur. Örneğin:
[Link] = [Link]().addMenu('&Fruit')
[Link] = [Link]('&Banana')
[Link](True)
[Link] = [Link]('&Apple')
[Link](True)
QAction sınıfının setCheckable metodu menü elemanı her seçildiğinde onu "checked" ya da "unchecked" duruma
getirmek kullanılır. Bu metot True parametresiyle çağrılırse (default False durumdadır) bu otomatik
checked/unchecked sağlanmış olmaktadır. Eğer QAction nesnesi "checkable" durumdaysa biz onu yine setChecked
metodu ile "checked" ya da "unchecked" duruma getirebiliriz.
Bir QAction nesnesi "enabled" ya da "disabled" durumda olabilir. Default durum "enabled" biçimdedir. "Disabled"
durumda nesne seçilemez. Bazen programlarda o anda kullanılması anlamlı olmayan menü ya da araç çubuğu
elemanları "disabled" yapılır. Örneğin File opoup menüsünde "open" ve "close" isimli iki menü elemanı olsun. Tek
dökümanlı bir uygulamada doküman henüz "open" yapılmadan "close" yapılmayacağına göre baştan "open"
menüsünün "enabled", "close" menüsünün "disabled" olması uygundur. Ancak kullanıcı "open" yaptıktan sonra artık
tam ters durum oluşmalıdır. Bu işlemin kodu şöyle oluşturulabilir:
110
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
filePopup = [Link]().addMenu('&File')
def onOpenTriggered(self):
[Link](False)
[Link](True)
def onCloseTriggered(self):
[Link](True)
[Link](False)
Araç çubuklarını kullanabilmek için yine ana pencerenin QMainWindow sınıfından türetilmesi gerekmektedir.
QMainWindow sınıfının addToolBar isimli metodu ile ana pencereye bir araç çubuğu eklenebilir. Bu metot bize
QToolBar sınıfı türünden eklenen araç çubuğu nesnesini verir. addToolBar metodu bizden araç çubuğunu simgeleyen
bir isim istemektedir.
Araç çubukları aslında genellikle (ama her zaman değil) menü elemanları için kısa yol amaçlı kullanılmaktadır. Araç
çubuklarına da PyQt'de QAction nesneleri eklenmektedir. Yani başka bir deyişle QToolBar sınıfının da bir addAction
metodu vardır. Genellikle programcı hem menülere hem de araç çubuklarına aynı action nesnesini verir.
Menü ve araç çubuklarında (Yani QMenu ve QToolBar sınıflarında) addSeparator isimli metotlar bir ayırma çizgisi
oluşturmaktadır. Böylece mantıksal gruplar birbirlerinden ayrılabilmektedir. Örneğin:
toolBar = [Link]('MyToolBar')
[Link](openAction)
[Link](closeAction)
[Link]()
[Link](copyAction)
[Link](pasteAction)
QAction sınıfının setToolTip metodu fare menü ya da araç çubuğu elemanın üzerinde bekletildiğinde çıkartılacak
yazıyı belirlemekte kullanılabilmektedir. Örneğin:
111
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]('Dosyayı açar')
QSlider Kullanımı
Slider yaygın biçimde kullanılan bir UI elemanıdır. Bir slider yürütece sahiptir. Kullanıcı yürüteci belli bir pozisyona
çekerek bazı ayarlamaları yapar. Özellikle volüm kontrolü, renk kontrolü gibi, ekran parlaklığı gibi derecesi olan
büyüklükler slider ile temsil edilme eğilimindedir. PyQt'de slider QSlider isimli bir sınıfla temsil edilmiştir. Bir slider
oriyansyon belirtilerek (default Vertical biçimdedir) aşağıdaki gibi yaratılır:
Default durumda minium = 0, maksimum = 99 biçimdedir. Ancak biz sınıfın setMinimum ve setMaximum metotlarıyla
bu değeri değiştrebiliriz. Slider'ın konumu value metodu ile elde edilebilir ve programlama yoluyla setValue metodu
ile set edilebilir. Sınıfın setTickInterval metodu ile tick çizgilerinin aralığı belirlenebilir. Ancak tick'lerin çıkması için
bizim sınıfın setTickPositin isimli property'si ile bunların pozisyonlarını belirlememiz gerekir.
QSlider sınıfının valueChanged isimli sinyali pozisyon parametresi almaktadır. Yürüteç konum değiştirdiğinde arka
planda bu sinyal tetiklenmektedir.
Örneğin:
import sys
from [Link] import *
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 200)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Qt5 ile birlikte pencerelerdeki renk değiştirme gibi görsel öğeler için CSS benzeri bir modele geçilmiştir. Ancak eski
biçimdeki renk değiştirmeler desteklenmeye devam etmektedir.
QWidget sınıfındaki setPalette metodu renk değiştirmek için genel bir metottur. Bu metot bizden bir QPalette
nesnesi alır. Bir QPalette nesnesi içerisinde pencerenin değişik öğelerinin renkleri belirtilebilmektedir. Bu öğelere role
denilmektedir. QPalette sınıfının setColor isimli metodu ile biz bir rol için bir renk set edebiliriz. İstenirse yine
QWidget sınıfının palette metodu ile aktif palet alınabilmektedir. Renkler QColor isimli sınıfla temsil edilmiştir. Bir
rengi oluştururken onun RGB bileşenlerini [0,255] aralığında belirtmemiz gerekir. Bu durumda örneğin pencerenin
zemin rengini şöyle değiştirebiliriz:
palette = QPalette()
[Link]([Link], QColor(255, 255, 0))
[Link](palette)
QColor sınıfında bazı renkler önceden tanımlanmış olarak static eleman biçiminde bulundurulmaktadır. Bu renkler
için RGB belirtmek yerine [Link] sınıfındaki elemanları kullanabiliriz. Örneğin:
palette = QPalette()
[Link]([Link], [Link])
[Link](palette)
Bazı ana renkler doğrudan Qt modülün içerisinde de bulundurulmuştur. Yani biz örneğin [Link], [Link], [Link]
biçiminde de bazı renkleri QColor olarak belirtebilmekteyiz.
Diyalog Pencereleri
113
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Diyalog pencereleri "sahiplenilmiş (owned)" tarzda pencerelerdir. Bunlar hem ana pencere (top level windows) hem
de alt pencere (child window) gibi davranırlar. Kendi üzet pencerelerinin sınırları şışına çıkabilirler ancak her zaman
üst pencerelerinin görsel bakımdan üzerinde görüntülenirler. Diyalog pencereleri Modal ve Modeless olmak üzere
ikiye ayrılmaktadır. Modal bir pencere açıldığında arka plandaki pencereyle etkileşim ortadan kalkar. Ta ki modal
pencere kapatılana kadar. MessageBox gibi OpenFile gibi pek çok diyalog penceresi modal pencerelerdir. Örneğin:
Modeless diyalog pencerelerinde ise arka plan etkişelmiş devam etmektedir. Örneğin "Find and Replace" tarzı
pencelerler modeless pencerelerdir. Örneğin:
class MyModalDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
[Link]('My Modal Dialog')
[Link](300, 200)
2) Modal diyalog penceresi açılacağı zaman bu sınıf türünden bir nese yaratılıp QDialog sınıfndan gelen exec
fonksiyonu çağrılır. Örneğin:
114
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def onModalTriggered(self):
myModalDialog = MyModalDialog(self)
[Link]()
Modal pencere yaratılırken onun üst penceresinin verildiğine dikkat ediniz. Yaratımn sırasındaki self ana pencereyi
temsil etmektedir. Ayrıca QDialog sınıfından sınıf türettiğimizde taban sınıfın __init__ metodunu çağırmayı
unutmamalıyız.
3) Dialog pencerelerinin içerisinde genellikle etkileşim için GUI elemanlar bulundurulur. Örneğin en azıncan modal
dialog pencerelerinde bir Ok ve Cancel tuşları bulundurulmaktadır.
4) Diayalog pencerelerini kapatmak için close metodu değil QDialog sınıfından gelen done isimli metot çağrılmalıdır.
Bu metot hangi nedenle çıkıldığını belirten bir parametre almaktadır.
import sys
from [Link] import *
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 400)
dialogPopup = [Link]().addMenu('Dialog')
modalAction = [Link]('Modal')
[Link]([Link])
[Link]('Ctrl+d');
def onModalTriggered(self):
myModalDialog = MyModalDialog(self)
result = [Link]()
if result == [Link]:
[Link](self, 'Message', 'Adı Soyadı: {}, No:
{}'.format([Link](), [Link]()))
class MyModalDialog(QDialog):
115
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def __init__(self, parent):
super().__init__(parent)
[Link]('My Modal Dialog')
[Link](370, 150)
[Link] = QLineEdit(self)
[Link](10, 25)
[Link](250, 20)
[Link] = QLineEdit(self)
[Link](10, 75)
[Link](250, 20)
[Link] = QLabel(self)
[Link](290, 20 , 64, 64)
[Link](QPixmap('Icons/[Link]'))
def onButtonOk(self):
[Link]([Link])
def onButtonCancel(self):
[Link]([Link])
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
1) Modal diyalog pencerelerinde olduğu gibi modeless diyalog pencerelerinde de öncelikle QDialog sınıfından bir sınıf
türetilir.
2) Modeless diyalog pencerelerinin açılması QDialog sınıfının exec metodu ile değil show metodu ile yapılmalıaıd.r
3) Modeless diyalog pencerelerinden çıkış done metoduyla değiş doğrudan close metoduyla yapılmalıdır.
Modal diyalog penceresi örneğimize modeless diyalog penceresi örneğini şöyle ekleyebiliriz:
import sys
from [Link] import *
from [Link] import *
116
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link]('Generic Window')
[Link](600, 400)
dialogPopup = [Link]().addMenu('Dialog')
modalAction = [Link]('Modal')
[Link]([Link])
[Link]('Ctrl+d');
modelessAction = [Link]('Modeless')
[Link]([Link])
def onModalTriggered(self):
myModalDialog = MyModalDialog(self)
result = [Link]()
if result == [Link]:
[Link](self, 'Message', 'Adı Soyadı: {}, No:
{}'.format([Link](), [Link]()))
def onModelessTriggered(self):
myModelessDaialog = MyModelessDialog(self)
[Link]()
class MyModalDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
[Link]('My Modal Dialog')
[Link](370, 150)
[Link] = QLineEdit(self)
[Link](10, 25)
[Link](250, 20)
[Link] = QLineEdit(self)
[Link](10, 75)
[Link](250, 20)
[Link] = QLabel(self)
[Link](290, 20 , 64, 64)
[Link](QPixmap('Icons/[Link]'))
def onButtonOk(self):
117
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]([Link])
def onButtonCancel(self):
[Link]([Link])
class MyModelessDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
[Link]('My Modal Dialog')
[Link](360, 240)
palette = [Link]().palette()
color = [Link]([Link])
[Link]([Link]())
[Link]([Link]())
[Link]([Link]())
def onButtonQuit(self):
[Link]()
def onValueChanged(self):
redValue = [Link]()
blueValue = [Link]()
greenValue = [Link]()
118
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
palette = QPalette(QColor(redValue, greenValue, blueValue))
[Link]().setPalette(palette)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Burada modeless pencere içerisinde üç slider bulunmaktadır. Bu slider'lar hareket ettirildiğinde ana pencerenin
zemin rengi değiştirilmektedir.
PyQt'de standart bazı diyalog pencereleri için QDialog sınıfından türetilmiş sınıflar tasarlanmıştır.
QFileDialog sınıfı "dosya açma"ve "dosya saklama" amacıyla dosya seçmek için kullanılan standart bir diyalog
penceresidir. Bu diyalog penceresi şöyle kullanılır:
1) QFileDialog sınıfı türünden bir nesne yaratılır. Bu pencere yaratılırken QFileDialog sınıfının __init__ metodunda
istenirse pencere başlığında çıkartılacak yazı da belirlenebilir. Örneğin:
2) Sonra exec fonksiyonu ile diyalop penceresi açılır. exec fonksiyonun geri dönüş değeri yine [Link] ya da
QDialogRejected biçimindedir.
3) Seçilen dosyanın yol ifadesi QFileDialog sınıfının selectedFiles isimli metoduyla alınabilir. Bu metot bize dosyaların
yol ifadelerinden oluşan str türünden bir liste vermektedir. Bu durumda seçilen dosyanın yol ifadesi şöyle
yazdırılabilir:
4) QFileDialog sınıfının setNameFilters isimli metodu bizden bir string listesi ister. Bu listenin her elemanı
"Gösterilecek yazı (filtreleme ifadesi)" biçiminde olmalıdır. Örneğin:
119
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
QFileDialog sınııfnın setFileMode isimli metodu ile biz diyalog penceresinde nelerin bulundurulacağını belirleyebiliriz.
Örneğin bu metodu [Link] argümanıyla çağırırsak bu durumda bu dialog penceresinden yalnızca
biz dizin seçebiliriz.
QFileDialog sınıfının daha pek çok özelliği vardır. Örneğin filtreleme yazısının hangisinin ilk açılışta aktif olacağı, dosya
seçerken dosya zaten varsa (save amaçlı olduğunda) uyarı yazısının çıkıp çıkmayacağı, çoklu seçim yapılıp
yapılmayacağı gibi. Sınıfın ayrıntılı kullanımı için ilgili dokümanlara başvurabilirsiniz.
Aslında QFileDialog sınıfında getOpenFileName ve getOpenFileNames isimli statik iki metot bulundurulmuştur. Bu
metot zaten kendi içerisinde QFileDialog nesnesini yaratıp exec yapmaktadır:
getOpenFileName metodu tek dosya seçmek için getOpenFileNames metodu birden fazla dosya seçmek için
kullanılmaktadır. Fonksiyonların birinci parametreleri üst pencere nesnesini, ikinci parametreleri pencere başlık
yazısını, üçüncü parametreleri başlangıçta diyalog penceresinin hangi dizinde açılacağını, dördüncü parametreleri
filtreleme yazısını belirtmektedir. Filtre yazısı tek bir string biçiminde oluşturulmaktadır. Birden fazla seçenek varsa
aralarına ";;" karakterleri yerleştirilir. getOpenFileName metodu bir demete geri dönmektedir. Demetin ilk elemanı
seçilmiş olan dosyanın yol ifadesini, ikinci elemanı ise onun hangi filtreleme yazısı ile seçildiğini belirtir. Örneğin:
Eğer diyalop penceresi cancel tuşuyla kapatılmışsa metodun geri dönüş değerine ilişkin demetin elemanları boş string
içermektedir. Bu kontrolün yapılması uygun olur. Örneğin:
120
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
getOpenFileNames isimli metot ise birden fazla dosya seçimine izin vermektedir. Bu metodun geri dönüş değeri yine
bir demettir. Demetin birinci elemanı seçilen dosyalara ilişkin bir string listesi ikinci elemanı ise seçimin hangi filtre
elemanı ile yapıldığını belirten string nesnesidir.
Ayrıca QFileDialog sınıfının bir de getSaveFileName isimli static metodu vardır. Bu metot da "save etme" için dosya
seçimine izin vermektedir:
QTableWidget Kullanımı
QTableWidget çok sütunlu bir liste kontrolüdür. Belli türden olguların çeşitli özelliklerini görüntülemek için
kullanılmaktadır. (Örneğin bir insanın Adı Soyadı, Doğum Yeri, Doğum Tarigi gibi. ) QTableWidget şöyle kullanılır:
[Link] = QTableWidget(self)
[Link](10, 10, 580, 230)
2) Daha sonra tablodaki sütun sayısı ve satır sayısı QTableWidget sınıfının setColumnCount ve setRowCount
metotlarıyla belirlenir. Örneğin:
[Link] = QTableWidget(self)
[Link](10, 10, 580, 230)
[Link](3)
[Link](5)
3) Artık sıra tepedeki başlık satırının hücrelerini uygun yazılarla set etmeye gelmiştir. Bunun için sınıfın
setHorizontalHeaderLabels isimli metodu kullanılır. Bu metot parametre olarak stringler'den oluşan bir liste
almaktadır. Örneğin:
[Link] = QTableWidget(self)
[Link](10, 10, 580, 230)
[Link](3)
[Link](5)
[Link](['Adı Soyadı', 'Mesleği', 'Numarası'])
4) QTableWidget nesnesindeki her hücre QTableWidgetItem isimli bir sınıfla temsil edilmiştir. Hücreleri set etmek
için her bir hücre için QTableWidgetItem nesnesi yaratıp bu nesneyi QTableWidget sınıfının setItem metoduyla set
etmemiz gerekir. setItem bizden set edilecek hücrenin satır ve sütun numarasını ve bir de QTableWidgetItem
121
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
nesnesini ister. QTableWidgetItem sınıfının setText metodu hücre içerisindeki yazıyı set etmek için, setForeground
metodu bu yazının rengini set etmek için, setBackground metodu ise zemin rengini set etmek için kullanılmaktadır.
Örneğin:
[Link] = QTableWidget(self)
[Link](10, 10, 580, 230)
[Link](3)
[Link](5)
[Link](['Adı Soyadı', 'Mesleği', 'Numarası'])
item1 = QTableWidgetItem()
[Link]('Ali Serçe')
[Link](0, 0, item1)
item2 = QTableWidgetItem()
[Link]('Bilgisayar Mühendisi')
[Link](0, 1, item2)
item3 = QTableWidgetItem()
[Link]('1234')
[Link](0, 2, item3)
Aslında QTableWidgetItem sınıfının str parametreli başlangıç metodunda hücre görüntülenecek yazı verilebilir.
Örneğin:
Tabii uygulamada hücreleri tek tek set etmek yerine bilgileri bir yerden alıp bir döngü içerisinde set etmek daha
uygundur. Örneğin:
personInfo = [('Ali Serçe', 'Bilgisayar Mühendisi', '1234'), ('Ahmet İnce', 'İşçi', '3567'),
('Sacit Bulut', 'Muhasebeci', '4786')]
for row in range(len(personInfo)):
item = QTableWidgetItem()
[Link](personInfo[row][0])
[Link](row, 0, item)
item = QTableWidgetItem()
[Link](personInfo[row][1])
[Link](row, 1, item)
item = QTableWidgetItem()
[Link](personInfo[row][2])
[Link](row, 2, item)
personInfo = [('Ali Serçe', 'Bilgisayar Mühendisi', '1234'), ('Ahmet İnce', 'İşçi', '3567'),
('Sacit Bulut', 'Muhasebeci', '4786')]
for row in range(len(personInfo)):
for col in range(len(personInfo[row])):
item = QTableWidgetItem()
[Link](personInfo[row][col])
[Link](row, col, item)
Aslında programcı işin başında tüm satırları yaratmak zorunda değildir. QTableWidget sınıfının insertRow isimli
metodu yeni bir satır insert etmektedir. Yani yukarıdaki örnek şöyle de olabilirdi:
personInfo = [('Ali Serçe', 'Bilgisayar Mühendisi', '1234'), ('Ahmet İnce', 'İşçi', '3567'),
('Sacit Bulut', 'Muhasebeci', '4786')]
122
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
for row in range(len(personInfo)):
[Link](row)
for col in range(len(personInfo[row])):
item = QTableWidgetItem()
[Link](personInfo[row][col])
[Link](row, col, item)
Aslında QTableWidget nesnesinin başlık kısımları da QTableWidgetItem türünden nesneler almaktadır. Biz yukarıdaki
örneklerde başlık kısımlarındak yazıları tek hamlede setHorizontalHeaderLabels metodu ile aşağıdaki gibi set ettik:
Aslında bu işlem arka planda QTableWidgetItem nesneleri yaratılarak yapılmaktadır. Biz de başlıkları böyle set etmek
yerine daha ayrıntılı biçimde QTableWidgetItem nesnelerini oluşturarak da set edebiliriz. QTableWidgetItem
nesneleri QTableWidget sınıfının setHorizontalHeaderItem metoduyla set edilebilmektedir.
header1 = QTableWidgetItem('Mesleği')
[Link]([Link])
[Link](QFont('Arial', 12))
[Link]([Link])
[Link](1, header1)
header2 = QTableWidgetItem('No'")
[Link]([Link])
[Link](QFont('Arial', 12))
[Link]([Link])
[Link](2, header2)
QTableWidget sınıfının pek çok sinyali vardır. Örneğin aktif hücre değiştiğinde currentCellChanged sinyali
oluşmaktadır. Bir hücrede kullanıcı değişiklik yaptığında currentItemChanged sinyali tetiklenir. Diğer sinyaller için
PyQt dokiümanlarından bilgi alabilirsiniz.
QTableWidget sınıfının currentItem isimli metodu bize o anda aktif olan hücreye ilişkin QTableWidgetItem nesnesini
vermektedir.
GUI elemanlarının kod yoluyla oluşturulması biraz zaman alıcı olmaktadır. Özellikle elemanların uygun konumlara
yerleştirilmesi biraz zahmetlidir. İşte bunun için QtDesigner denilen bir araç kullanılmaktadır. QtDesigner QtCreator
IDE'sinin bir parçeası durumuna getirilmiştir. Ancak bağımsız olarak da yüklenebilmektedir.
QtDesigner isimli araç bağımsız bir program olarak da yüklenebilmektedir. Ancak normal olarak artık bu araç
QtCreator denilen IDE'nin bir parçası olarak bulunmaktadır. QtDesigner aşağıda siteden bağımsız bir program olarak
indirilebilir:
[Link]
Ancak eğer aynı zamand abir C/C++ programcısı iseniz QtDesigner aracı için QtCreator IDE'sini yüklemizi tavsiye deriz.
Yukarıda da belirtildiği gibi zaten QtCreator IDE'sinin içerisinde QtDesigner hazır olarak bulunmaktadır. Ayrıca
QtDesigner Python dünyasından da pip programıyla indirilip kurulabilmektedir.
123
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
1) Önce görsel olarak designer'da tasarım yapılır ve bu tasarım .ui uzantılı bir XML dosyası olarak diskte saklanır.
2) Elde edilen .ui dosyası pyuic5 (python user interfacse compiler) denilen program tarafından bir python modülüne
dönüştürülür. (Bu programın PyQt4'teki ismi pyuic4 biçimindedir.) pyuic5 programı şöyle kullanılmaktadır:
pyuic5 -o <hedef python dosyasının yol ifadesi> <ui dosyasının yol ifadesi>
Örneğin:
Bu işlemin sonucunda biz designer'da oluşturduğumuz görsel öğeleri oluşturan bir python müdlü elde etmiş oluruz.
Yani başka bir deyişle buradaki [Link] dosyası içerisinde aslında designer'da oluşturduğumuz görsel öğeleri
oluşturan Python kodları vardır. Bu modülün içerisinde Ui_XXX biçiminde bir sınıf vardır. Buradaki XXX designer'daki
ana formın "object name" property'sinden gelmektedir.
3) Şimdi programcı ui dosyasından elde ettiği Python modülünü kendi programında import etmelidir:
import sys
from [Link] import *
import mainwindow
Bundan sonra programcı bir pencere sınıfı oluşturup (QWidget'tan ya da QMainWindow'dan türetilebilir) __init__
metodunda bu modüldeki görseli kendi ana penceresine gömmelidir. Bu işlem iki satırda yapılır. Önce modüldeki
Ui_XXX sınıfı türünden bir nesne yaratılır. Sonra da bu nesne yoluyla bu Ui_XXX sınıfının setupUi isimli metodu
çağrılır. Bu metot görsel öğeleri parametresiyle aldığı Widget nesnesinin içerisine yerleştirecektir. Bu nedenle
parametrede için self argümanının geçilmesi uygun olur. Örneğin:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
uiForm = mainwindow.Ui_Form()
[Link](self)
import sys
from [Link] import *
import mainwindow
class MainWindow(QWidget):
def __init__(self):
super().__init__()
uiForm = mainwindow.Ui_Form()
[Link](self)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Tabii eğer Ui_XXX nesnesi birtakım metotlardan da kullanıcaksa bunu da sınıfın veri elemanı yapmak uygun olur.
Örneğin:
124
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import sys
from [Link] import *
import mainwindow
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link] = mainwindow.Ui_MyWindow()
[Link](self)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
4) Program içerisinde designer'da oluşturduğumuz görsel öğeleri oarada verdiğimiz "object name" isimleriyle
kullanabiliriz. Başka bir deyişle pyuic5 programı Ui_XXX sınıfının örnek veri elemanı olarak bu görsel öeğlere ilişkin
nesneleri oluşturmuş durumdadır. Biz de bunu programımızda kullanabiliriz. Örneğin designer'da listWidgetNames
ismi verilmiş QListWidget nesnesine şöyle elemanlar ekleyebiliriz. Örneğin:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link] = mainwindow.Ui_MyWindow()
[Link](self)
5) GUI mesajları yine ilgili nesneler üzerinde connect işlemi yapılarak işlenebilir. Örneğin:
import sys
from [Link] import *
import mainwindow
class MainWindow(QWidget):
def __init__(self):
super().__init__()
[Link] = mainwindow.Ui_MyWindow()
[Link](self)
def onButtonOk(self):
[Link](self, 'Message', 'Checked' if
[Link]() else 'Unchecked')
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
125
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Örneğin şimdi de menülü bir programı designer'da oluşturalım:
Artık desginer'da formu yaratırken pencere cinsi olarak MainWindow seçilmelidir. Daha sonra yine save edilen .ui
dosyası pyuic5 programı ile işleme sokulur. Sonra da programdan import edilerek kullanılır.
Tabii programımızda başka ana pencereler ya da dialog pencereleri varsa bunlar için de ayru .ui dosyaları oluşturulur.
Bunlar da aynı biçimde kullanıma sokulmaktadır. Örneğin menüden bir eleman seçildiğinde bir diyalog penceresinin
açılmasını isteyelim. Burada diyalog penceresi yine designer'da oluşturulabilir ve aşağıdaki kodda kullanılabilir:
import sys
from [Link] import *
import mainwindow
import modaldialog
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link] = mainwindow.Ui_MainWindow()
[Link](self)
[Link]([Link])
def onModalDialog(self):
myDialog = ModalDialog()
[Link]()
class ModalDialog(QDialog):
def __init__(self):
super().__init__()
[Link] = modaldialog.Ui_Dialog()
[Link](self)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
126
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Renk Seçme Dialog Penceresinin Kullanımı
Renk seçmek için kullanılan standart diyalog penceresi PyQt'de QColorDialog sınıfıyla temsil edilmiştir. Bu sınıfın
kullanımı şöyledir:
1) QColorDialog sınıfı türünden bir nesne yaratılır ve sınıfın exec metodu çağrılır. Yine exec metodunun geri dönüş
değeri [Link] ya da [Link] biçimindedir. Örneğin:
def onColorTriggered(self):
cd = QColorDialog()
if [Link]() == [Link]:
pass
2) Dialog penceresinden seçilen renk QColor olarak sınıfın selectedColor metoduyla alınabilir. Örneğin seçilen renk ile
pencerenin zemini boyamak isteyelim:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link](600, 350)
dialogPopup = [Link]().addMenu('Dialog')
modalAction = [Link]('Color...')
[Link]([Link])
[Link]('Ctrl+c');
def onColorTriggered(self):
cd = QColorDialog()
if [Link]() == [Link]:
palette = QPalette()
[Link]([Link], [Link]())
[Link](palette)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
3) Sınıfın setCurrentColor isimli metodu şle biz diyalog penceresi açıldığında görüntülenecek default rengi de
belirleyebiliriz.
İşletim sistemlerindeki pek çok GUI alt sisteminde pencere içerisindeki görüntüler bu alt sistem tarafından otomaatik
olarak tutulup geri basılmamaktadır. Bu sistemlerde pencere içerisindeki görüntünün oluşturulması programcıya
bırakılmıştır. Programcı pencere içerisine bir çizim yaptığında o pencerenin üzerine bir pencere getirilip tekrar
açıldığında o çizim kaybolur. İşte işletim sisteminin GUI alt sistemi bunu tutarak geri basmamaktadır. Bunun yerine
pencere içerisindeki görüntünün bozulduğunu anlatan bir mesajı uygulamanın (thread'in) mesaj kuyruğuna bırakır.
Bu mesaj pencere içerisindeki görüntünün bozulduğu ve yeniden çizilmesi gerektiği anlamına gelmektedir. Dolayısıyla
bu sistemlerde çizimler bu mesaj geldiğinde yapılmalıdır. (Bazı işletim sistemlerinin GUI alt sisteemleri pencere
127
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
içerisindeki çizimleri kendisi tutup basabilmektedir. Ancak Qt’nin “cross platform” özelliği ortak bir bölene göre
tasarlanmıştır. Qt’de pencere içerisindeki çizim bozulduğunda framework tarafından QWidget sınıfının paintEvent
isimli fonksiyonu çağrılır. O halde çizimler bu fonksiyon içerisinde yapılmalıdır. Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link](600, 400)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Çizimin paintEvet isimli metotta yapılması gerektiğini belirttik. Pekiyi çizim nasıl yapılacaktır? İşte çizim yapmak için
QPainter isimli bir sınıf kullanılır. QPainter sınıfı türünden bir nesne hangi pencere içerisine çizim yapılacağını anlatan
bir QWidget parametresiyle yaratılır. Örneğin:
İşte çizim metotları aslında QPainter sınıfının örnek metotları biçiminde bulunmaktadır. Örneğin:
Çizimlerin şekilleri kalem (pen) ile, iç boyamaları ise fırça (brush) ile yapılmaktadır. QPanter nesnesi yaratıldığında
defauşlt bir kalem ve fırça da nesneye iliştirilmiş durumdadır. Ancak daha sonra biz farklı kalemler ve fırçalar
yaratarak painter nesnesinin onu kullanmasını sağlayabiliriz.
Kalemler QPen isimli sınıfla temsil edilmiştir. Bir QPen nesnesi sınıfın aşağıdaki __init__ metoduyla yaratılabilir:
Kelmin tipik üç özelliği stili, rengi ve kalınlığıdır. Eğer __init__ tek argümanla çağrılırsa buradaki değer kalemin stilini
belirtir. Kalem stilleri şunlardan oluşmaktadır:
128
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bir kalem yaratıldıktan sona QPainter nesnesinin o kalemi kullanabilmesi için set işleminin yapılması gerekir. Örneğin:
pen = QPen([Link])
[Link](pen)
Bir kalemi yaratırken renk, kalınlık ve stil birlikte verilebilir. Renk QBrush nesnesi biçiminde yani bir fırça gibi
verilmelidir. (Kalem yeteri kadar kalın olduğunda fırça etkisi yaratmaktadır.) Örneğin:
Aslında setPen metodu yalnızca renk verilerek de çağrılabilir. Bu durumda1verilen renkte, kalınlıklı, düz çizgili bir
kalem kullanılır.
129
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Fırça nesneleri QBrush sınıfıyla temsil edilmiştir. Bir QBrush nesnesi sınıfın aşağıdaki __init__ metotlarıyla
yaratılabilir:
Bu metotlar fırçanın biçimini, rengini ve fırçayı oluşturan resmi bizden argüna olarak almaktadır. Fırça biçimleri
aşağıda verilmiştir:
Temel olarak QPainter sınıfında drawXXX ve fillXXX biçiminde (yani ismi draw ve fill ile başlayan) bir grup çizim
metodu vardır. drawXX metotları hem çizimin dışını kalem nesneiyle yaparlar hem de içini fırça nesnesiyle boyarlar,
halbuki fillXXX metotları ise yalnızca kapılı şekillerin içinin boyanması için kullanılmaktadır. QPainter nesnesi
yaratıldığında default kalem 1 kalınlıklı, siyah, düz çizgili kalemdir. Default fırça ise transparan (yani boş) bir fırçadır.
Burada bu metotların önemlileri ele alınacaktır.
- drawEllipse metodu elips çizmek için kullanılır. drawEllipse metodu temel olarak bizden bir dikdörtgen ister ve o
dikdörtgenin iç teğet elipsini çizer. Dikdörtgen kareye yaklaştıkça elips de çembere yaklaşır. drawEllipse elipsin içini
de set edilen fırçayla boyamaktadır. Örneğin:
[Link](QBrush([Link]))
[Link](10, 10, 100, 100)
130
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
- drawLine metodu iki nokta belirtilerek bir çizgi çizmek için kullanılır. Örneğin:
[Link](QPen(QBrush([Link]), 4, [Link]))
[Link](100, 50, 300, 200);
- drawPolyLine metodu bir grup noktayı alıp bu noktaları birleştirerek çizgiler çizer. Metot bizden birden fazla QPoint
nesnesi istemektedir. Örneğin:
Aslında metodun parametresi *'lıdır. Yani bu durumda bizim girdiğimiz argümanlar bir demet biçimine
dönüştürülerek aktarılır. Tabii biz istersek argümanı *'layarak aynı etkiyi oluşturabiliriz. Örneğin:
[Link](QPen(QBrush([Link]), 4, [Link]))
xorg, yorg = 350, 150
points = []
[Link](*points)
1) range fonksiyonu gerçek sayısal artırım yapamamaktadır. Bu nedenle bunun yerine "list comprehension"
kullanılmıştır.
2) Kartezyen sistemde y ekseni yukarıya doğru artar. Ancak ekran koordinat sisteminde aşağıya doğru artmaktadır.
3) sinüs fonksiyonundan elde edilen değerin pixel'e dönüştürülmesi için değer 50 ile çarpılmıştır. Başka bir deyişle
gerçek kartezyen sistmedeki 1 ekran koordinat sisteminde 50 pixel'e karşılık gelmektedir.
- drawPolygone metodu tıpkı drawPolyLine gibidir. Ancak son noktayla ilk noktayı birleştirir. Örneğin:
132
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Tabii biz istersek QPainter nesnesine bir fırça da set ederek çizimin içinin boyanmasını sağlayabiliriz. Örneğin:
[Link](QBrush([Link]))
points = [QPoint(100, 100), QPoint(120, 210), QPoint(150, 50), QPoint(300, 50)]
[Link](*points)
[Link](QPen(QBrush([Link]), 4, [Link]))
[Link](QBrush([Link]))
- drawPixmap metodu bir resmi çizmek için kullanılmaktadır. Pixmap sınıfı resmi temsil eder. drawPixmap metodu da
resmi çizer. Örneğin:
pixmap = QPixmap("[Link]")
[Link](10, 10, [Link](), [Link](), pixmap)
drawPixmap metodu bizden çizilecek yerin sol üst köşesini ve yapılacak çizimin genişlik ve yükseliğini tabii bir de
resmin kendisini istemektedir. Bu metot orijinal resmi bizim belirttiğimiz gen,işlik ve yüksekliğe ölçeklendirerek çizer.
Yukarıdaki örnekte resim orijinal büyüklüğünde çizilmiştir.
133
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
BdrawPixmap metodu ile istenirse resmin belli bir kısmı ölçeklendirilerek de çizilebilir. Örneğin:
pixmap = QPixmap("[Link]")
[Link](10, 10, 600, 600, pixmap, 200, 200, 400, 400)
Pencere üzerinde fare tıklandığında ya da pencere üzerinde fare hareket ettirildiğinde PyQt Ortamı bizim çeşitli
metotlarımızı çağırarak bize bildirimde bulunmaktadır. Böylece programcı fare ile ilgili işlemler yapabilmektedir. Bu
metotlar şunlardır:
mousePressEvent
mouseReleaseEvent
mouseMoveEvent
mouseDoubleClickEvent
134
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Metotların QMouseEvent isimli sınıf türünden bir parametreleri vardır. Bu parametre bize ilgili fare olayı hakkında
detayları vermektedir. (Örneğin farenin hangi tuşuna basık durumdadır, O anda fare hangi konumdaır gibi). Biz
PyQt'de yukarıdaki metotları pencere sınıfının içerisinde yazarak bu olayları yakalayabiliriz. Çünkü PyQt fare ilgili
hareketler yapıldığında ilgili sınıfın yukarıdaki metotlarını çağırmaktadır. İlgili pencere içerisinde farenin herhangi bir
tuşuna tıklandığında mousePressEvent metodu, el fareden çekildiğinde ise mouseRelease metodu çağrılmaktadır.
Farenin herhangi bir tuşuna basılıp fare hareket ettirildiğinde bir dizi mouseMoveEvent çağrımları yapılır. Ancak bu
çağrımların her pixel için yapılacağı garanti değildir. Farenin sürüklenmesi sırasında hangi yoğunlukta
nouseMoveEvent metodunun çağrılacağı sistemin genel durumuna bağlı olmaktadır. Nihayet pencere içerisinde fare
ile çift tıklandığında mouseDoubleClickEvent metodu çağrılmaktadır. Biz bu metotların parametrelerinden farenin o
andaki konum bilgisini ve farenin hangi tuşa basılmış olduğu bilgisini elde edebiliriz. Örneğin:
import sys
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
[Link](700, 500)
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Bazen fare mesajlarında çizimler yapmak isteyebiliriz. Çizimleri fare mesajlarında yaptığımızda bunların paintEvent
metodunda saklanıp yeniden yapılması gerekir. İşte genellikle bunun yerine tüm çizimlerin paintEvent metodunda
yapılması ancak fare mesajlarında yalnızca çizilecek bilgilerin güncellenmesi yoluna gidilmektedir. paintEvent
metodunu biz manuel olarak kendimiz çağırmamalıyız. (Neden bizim bu paintEvent metodunu çağırmamamız
gerektiğinin bazı ayrıntıları vardır. Ancak burada ele alınmayacaktır.)
Yazboz tahtası uygulamasında biz elimizi fareye tıklayıp kaldırana kadarki çizimleri QPolygon isimli PyQt'nin liste
benzeri bir sınıfında saklayacağız. Sonra bu QPolygon nesnelerini de ayrı bir listede biriktireceğiz. Farenin her bir
hareketinde paintEvent çizimlerinin yeniden yapılması için update metodunu uygulayacağız. Aşağıda böyle bir yazboz
tahtası uygulaması verilmiştir. Uygulamada painter nesnesine "antialiasing" özelliği verildiğine dikkat ediniz.
"Antialiasing" çizim sırasında komşu piksellerin uygun renklerle boyanmasını sağlayan algoritmik tekniğe verilen bir
isimdir. Böylece çözünürlüğü gerçek kartezyen sisteme göre çok daha düşük olan ekran koordinat sisteminde
çizimlerin "kırıklı" görülmesinin bir derece üstesinden gelinebilmektedir.
import sys
from [Link] import *
from [Link] import *
from [Link] import *
class MainWindow(QMainWindow):
135
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def __init__(self):
super().__init__()
[Link](700, 500)
[Link] = None
[Link] = []
[Link] = False
def main():
app = QApplication([Link])
mainWindow = MainWindow()
[Link]()
[Link]()
main()
Thread'ler bir programın işletim sistemi tarafından bağımsız çizelgelenen akışlarıdır. Bir program tek bir akışa sahip
olmak zorunda değildir. Şimdiye kadar bizim programlarımızda tek bir akış vardı. Oysa programlarda birden fazla akış
aynı zamanda işletilebilmktedir.
İşletim sistemleri terminolojisinde çalışmakta olan programlara "process" denilmektedir. Thread proseslerin akışlarını
belirtir. Proses kavramı akışın dışında çalışmakta olan programın tüm özelliklerini betimlemektedir. Örneğin onun
yetki derecesini, açmış olduğu dosyaları çalışma dizinini vs.
İşletim sistemleri proseslerin thread'lerini zaman paylaşımlı olarak çalıştırmaktadır. Yani tipik olarak işletim sistemi
bir prosesin bir thread'ini CPU ya da çeekirdeğe atar. O thread belli süre çalıştırılır. Sonra işletim sistemi o thread'i
durdurur. CPU ya da çekirdeğe diğer thread'i atar. Böyle böyle aslında programlar zaman paylaşımlı biçimde
çalıştırılmaktadı. CPU'lar ya da çekirdekler aynı anda birden fazla programı çalıştıramazlar. "Biraz ondan, biraz
bundan" biçiminde zaman paylaşımlı bir çalışma uygulanmaktadır. Bir thread'in CPU ya da çekirdeğe atandığında
parçalı çalışma süresine "quanta süresi" ya da "quantum" denilmektedir. İşletim sistemlerinin kullandığı tipik quanta
süreleri 20 ms., 60 ms gibi değerlerdir. Quanta sürelerinin çok yüksek olması interaktiviteyi azaltmaktadır. Quanta
süresinin çok kısa olması ise "birim zamanda yapılan iş miktarının (througput)" azalmasına yol açar. Çünkü thread'ler
arası geçişin (buna "context switch" de denilmektedir) belli bir zaman maliyeti vardır. Böylece bir akışın belli bir
noktadan belli bir noktaya gelmesi için gereken mutlak zaman sistemin yüküne göre değişebilmektedir.
136
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Pekiyi sistemimizde birden fazla CPU ya da çekirdeğin (core) olması durumunda ne olacaktır? Aslında bu durumda
prensipte değişen hiçbir şey yoktur. Yine zaman paylaşımlı bir çalışma uygulanır. Bu durumda her CPU ya da
çekirdeğin ayrı bir çizelge kuyruğu olacaktır. Tabii thread'ler birden fazla CPU ya da çekirdeğin kuyruğuna
atanacaklarından toplamda çalışma süreleri hızlanacaktır. Ancak yine zaman paylaşımlı bir çalışma söz konusudur.
Bizim programımızın farklı thread'leri farklı CPU ya da çekirdeklere atandığında onlar aynı anda çalışıyor olabilirler.
Ancak bir thread aynı anda farklı CPU ya da çekirdeklerde çalışamamaktadır.
Thread sözcüğü etimolojik olarak "iplik" sözcüğünden gelmektedir. Akışlar ipliklere benzetilerek bu sözcük
uydurulmuştur. Thread'ler bir prosesin bağımsız çizelgelenen akışlarını belirtir. Proses çalışmakta olan programın
tamamını kavramsal olarak anlatmaktadır. Thread ise yalnızca bir akış belirtir. Dolayısıyla thread'ler proses
kavramının içerisinde yer alır. Thread'lerin ilk ciddi denemeleri 80'li yıllarda yapılmıştır. Fakat 90'lı yıllarda işletim
sistemlerine gerçek anlamda sokulmuştur. Örneğin DOS'ta thread yoktu. Windows 3.1 sistemleri de thread'li
sistemler değildir. Microsoft'un ilk thread'li sistemi Windows NT (1993) ve sonra Windows 95 (1995)'tir. Linux'un ilk
versiynlarında thread'ler yoktu. 2.0'dan itibaren thread'li çalışma Linux sistemlerine sokulmuştur.
Çok thread'li işletim sistemlerinde proses çalışmaya bir thread'le başlar. Yani proses yaratıldığında bir thread de
yaratılmış durumdadır. Buna prosesin ana thread'i (main thread) denir. Diğer thread'ler işletim sisteminin sistem
fonksiyonlarıyla (yani Windows'ta API fonksiyonlarıyla, UNIX/Linux sistemlerinde POSIX fonksiyonlarıyla) yaratılırlar.
Biz Python'da thread'ler için Python'ın standart kütüphanesindeki sınıfları ve metotları kullanacağız. Ancak bunlar
aslında işletim sisteminin sistem fonksiyonlarıyla thread'leri oluşturulmaktadır.
1) Thread'ler arka plan olayları izlemek için iyi bir araç oluşturmaktadır. Örneğin hem bir işi yaparken hem de ekranın
sağ üst köşesine saati basmak isteyelim. Saati ne zaman basacağız. Her işlemin arasında saati basmamız gerekir.
Pekiyi bu durumda klavye ya da disk işlemleri yapıldığında ne olacak? Ya da hem bir işi yaparken hem de arka planda
dışsal bir olayı (örneğin seri portu, ya da bir termometreyi) izleyecek olalım. Eskiden bu tür işlemleri yapmak için tüm
programın organizasyonunu değiştirmek gerekiyordu. Yani bu tür işlemler çok zor yapılabiliyordu. Halbuki thread'li
sistemlerde bir thread yaratıp bu arka plan olayı bu thread' devredebiliriz. Böylece diğer thread’ler kendi işlemini
yapabilir. Artık bu thread’ler bloke olsa bile arka plan olaylar izlenmeye devam edecektir.
2) Thread'ler bir programı hızlandırmak için kullanılabilir. Yani biz programımızda çok thread kullanırsak toplamda
daha fazla CPU zamanı çekeriz.
137
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
3) Thread'ler blokeli IO işlemlerinde yoğun olarak kullanılmaktadır. Yani bir IO işlemi başlattığımızda (örneğin boru ya
da soket gibi) belli bir süre bloke oluruz. Bu durumda gerekli olan başka şeyleri yapamayız. İşte IO işlemleri
thread'lere yaptırılırsa blokeden yalnızca o thread etkilenir.
4) Thread'ler paralel programlama için mecburen kullanılmaktadır. Paralel programları bir işi parçalara ayırarak onu
aynı anda birden fazla işlemci ya da çekirdeğe atayarak gerçekleştirme sürecine denilmektedir.
5) Thread'ler GUI programlama modelinde bazen mecburen kullanılmak zorundadır. Örneğin bir mesaj geldiğinde bir
işi uzatırsak kuyrukta sıradaki mesajları işleyemeyiz. İşte uzun sürebilecek işlemler thread'lere havale edilebilir.
Thread'lerin Yaratılması
Python'da thread'ler iki biçimde yaratılabilmektedir. Aslında iki yaratım biçimi birbirinin aynısıdır fakat kod
orgaznizasyonu bakımından faklılıkları vardır. threading modülündeki Thread isimli sınıf thread'i temsil etmektedir.
Bu sınıf türünden bir nesne yaratılıp sınıfın start isimli örnek metodu çağrılırsa thread akışı çalışmaya başlar. Thread
sınıfının başlangıç metodunun parametrik yapısı şöyledir:
Metodun birinci parametresi geleceğe yönelik saklı tutulmuştur. Bu parametre None olarak (zaten default değerinin
None olduğuna dikkat ediniz) girilmelidir. target parametresi thread akışının başlatılacağı fonksiyonu ya da metodu
belirtmektedir. Thread'lere birer isim verilebilir. name parametresi thread'e verilecek ismi belirtir. args ve kwargs
parametreleri thread metoduna geçirilecek argümanları belirtmektedir. args parametresi bir demet biçiminde
oluşturulmalıdır. kwargs parametresi ise bir sözlük biçiminde olmalıdır. daemon parametresi thread'in arka plan mı
ön plan mı olduğunu belirtir. Örneğin:
import threading
import time
def main():
thread = [Link](target=threadProc)
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
[Link](1)
def threadProc():
for i in range(10):
print('Other thread: {}'.format(i))
[Link](1)
main()
Burada hem ana thread hem de yarattığımız thread 0'dan 9'a kadar sayıları birer saniye aralıklarla ekrana
yazdırmaktadır. Ekrana yazdırma sırasında thread'ler bir arada çalıştırkları için bir iç içe geçme oluşabilir. Bunu
önemsemeyiniz. time modülündeki sellep isimli fonksiyon parametresiyle belirtilen saniye kadar thread akışını
bekletir. sleep fonksiyonun parametresi float türdendir. Dolayısıyla 0.1 saniye gibi değerler de girilebilmektedir.
Thread fonksiyonu çalışmaya başladığında ona parametre aktarılabilmektedir. Bunun için Thread sınıfının başlangıç
metodundaki args parametresi kullanılır. Bu parametrenin bir demet olması gerekmektedir. Bu demetteki elemanlar
thread fonksiyonuna argüman olarak geçirilmektedir. Örneğin:
import threading
import time
def main():
thread = [Link](target=threadProc, args=('Other Thread',))
138
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
[Link](1)
def threadProc(arg):
for i in range(10):
print('{}: {}'.format(arg, i))
[Link](1)
main()
args argümanında tek bir eleman girilecekse parantezin içerisinde son ',' atomunu unutmayınız.
Şüphesiz thread fonksiyonu bir sınıfın örnek metodu da olabilir. Örnek metotlarının ilgili sınıf türünden referansla
ifade edildiğini anımsayınız. Örneğin:
import threading
import time
class Sample:
def __init__(self):
pass
def threadProc(self, arg):
for i in range(10):
print('{}: {}'.format(arg, i))
[Link](1)
def main():
s = Sample()
thread = [Link](target=[Link], args=('Other Thread',))
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
[Link](1)
main()
Yani thread'i temsil eden fonksiyonun global bir fonksiyon olması gerekmemektedir. Bir sınıfın örnek metodu, static
metodu ya da sınıf metodu olabilir.
Birden fazla thread aynı thread fonksiyonundan çalışmaya başlayabilir. Örneğin biz bir döngü içerisinde birden fazla
thread yaratabiliriz:
import threading
import time
threads = []
def main():
for i in range(10):
thread = [Link](target=threadProc, args=('Thread No {}'.format(i + 1),))
[Link](thread)
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
[Link](1)
def threadProc(arg):
for i in range(10):
139
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print('{}: {}'.format(arg, i))
[Link](1)
main()
Bu örnekte 10 farklı thread akış threadProc isimli fonksiyondan başlayacak biçimde yaratılmıştır. Thread nesnelerinin
global bir dizide saklandığına dikkat ediniz.
Thrad'lerin yaratılmasının ikinci yolu [Link] sınıfından türetme yapmaktır. Aslında Threda sınıfının start
metodu kendi içerisinde Thread sınıfının run metodunu çağırır. Biz Thread sınıfından türetme yapıp bu run metodunu
override edersek bizim run metodumuz çalıştırılacaktır. İşte bu run metodu thread akışının başladığı metot olabilir.
Normal olarak Thread sınıfının run metodu (yani default run metodu) sınıfın başlangıç metodunda target
parametresiyle belirtilen fonksiyonu çağırmaktadır.
Örneğin:
import threading
import time
class MyThread([Link]):
def __init__(self, arg):
super(MyThread, self).__init__()
[Link] = arg
def run(self):
for i in range(10):
print('{}: {}'.format([Link], i))
[Link](1)
def main():
mt = MyThread('Other thread')
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
[Link](1)
main()
Burada MyThread isimli sınıf Thread isimli sınıftan türetilmiştir. Dolayısıyla Threda sınıfıyla yapabileceğimiz her şeyi
MyThread sınıfıyla da yapabiliriz. Örneğin MyThread sınıfı türündne bir nesne yaratıp start metodunu çağırabiliriz.
MyThread nesnesiyle start metodu çağrıldığında bu metot kendi içerisinde run metodunu çağırmaktadır. run
metodunu da override ettiğimizden dolayı artık MyThread sınıfındaki run metodu çalıştırılacaktır. Böylece bizim
thread'imizin akışı MyThread sınıfının run metodundan başlamış gibi olacakatır.
140
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Türetme yöntemiyle thread yaratmanın bir avantajı thread'in bir sınıfla temsil edilmesidir. Bu da nesne yönelimli
teknik için daha uygun olabilmektedir. Ancak kursumuzdaki uygulamalarda diğer yöntemi daha fazla kullanılacağız.
Thread'lerin Sonlanması
Bir thread akışının sonlanmasının en normal yolu thread'in başlangıç fonksiyonunun doğal biçimde sonlanmasıdır.
Örneklerimizde target parametresiyle belirtitğimiz fonksiyon ya da run metodu bittiğinde thread'imiz de
sonlanacaktır. Maalesef Python'ın threading modülünde bir thread'in kendi kendini sonlandıran (örneğin exit gibi) bir
metot bulundurulmamıştır. Oysa hem işletim sistemlerinde hem de diğer pek çok ortam ortamda (framework) bu işi
yapacak fonksiyonlar bulunmaktadır. Ancak Python'da bir thread'de bir exception oluşursa bu exception yalnızca o
thread'i sonlandırmaktadır. Thread'lerin herhangi bir noktada sonlandırılması bu yöntemle yapılabilir. Örneğin:
import threading
import time
def main():
thread = [Link](target=threadProc, args=('Other Thread',))
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
[Link](1)
def threadProc(arg):
try:
for i in range(10):
print('{}: {}'.format(arg, i))
foo(i)
[Link](1)
except:
pass
def foo(i):
if i == 5:
raise Exception
main()
Python'da da tıpkı .NETe olduğu gibi thread'ler daemon (şeytan anlamına gelmekteir) thread ve normal thread olmak
üzere ikiye ayrılmaktadır. Default durumda thread daemon değildir. Thread'i daemon yapmak için thread sınıfının
daemon isimli elemanına True değerin yerleştirilmesi gerekir. Örneğin:
[Link] = True
Thread yaratılırken de Thread sınıfının başlangıç metodunda daemon parametresi True geçilebilir. Örneğin:
Daemon thread ile normal threda arasındaki tek fark şudur: Sistemdeki son daemon olmayan thread sonlandığında
bütün daemon thread'ler otomatik sonlandırılıp program sonlandırılmaktadır. Ana thraea normal bir thread'tir. Biz
ana thread'i sonlandırırsak başka bir normal thread sonlanmaz. Son normal thread sonlandığında tüm daemnon
thread'ler sonlanmaktadır. Aşağıdaki örnekte ana thread içerisinde bir normal threda yaratılmıştır. Sonra ana thread
sonlandırılmıştır. Fakat yaratılmış olan normal thread çalışmaya devam edecektir:
import threading
import time
def main():
thread = [Link](target=threadProc, args=('Other Thread',))
141
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
if i == 5:
break
[Link](1)
def threadProc(arg):
for i in range(10):
print('{}: {}'.format(arg, i))
[Link](1)
main()
Şimdi ana yarattığımız thread'i daemon thread yapalım. Artık ana thread sonlandığında daemon thread'lerin hepsi
sonlandırılacaktır:
import threading
import time
def main():
thread = [Link](target=threadProc, args=('Other Thread',))
[Link] = True
[Link]()
for i in range(10):
print('Main thread: {}'.format(i))
if i == 5:
break
[Link](1)
def threadProc(arg):
for i in range(10):
print('{}: {}'.format(arg, i))
[Link](1)
main()
Bu örnekte ana thread'in sayacı 5'e geldiğinde ana thread sonlandırılmaktadır. Artık yarattığımız thread çalışmaya
devam etmeyecektir. Örneğin uygulamalarda yalnızca ana thread normal yapılıp diğerleri daemon yapılırsa ana
thread sonlandığında diğer tüm thread'ler sonlandırılacaktır.
Her durumda sistemdeki son normal thread'in sonlandığında programın sonlanacağına dikkat ediniz.
Bazen bir thread yaratılır ve diğer bir thread o thread sonlana kadar belli bir noktada beklemek isteyebilir. Çoık
thread'li uygulamalarda bu tür durumlarla sık karşılaşılmaktadır. Bunun için thread sınıfının join isimli metodu
kullanılır. Örneğin:
import threading
import time
def main():
thread = [Link](target=threadProc, args=('Other Thread',))
[Link]()
[Link]()
print('ok')
def threadProc(arg):
142
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
for i in range(10):
print('{}: {}'.format(arg, i))
[Link](1)
main()
İstenirse join metoduna bir zaman aşımı değeri verilebilir. Bu durumda join eğer thread hala sonlanmamışsa en fazla
o kadar bekler.
threading modülündeki global current_thread metodu bize kendi thread'imizin (yani bu metodu çağıran thread'in)
thread nesnesini verir. Thread sınıfının name özeniteliği ise thread'e verdiğimiz ismi temsil etmektedir. Örneğin:
import threading
import time
def main():
thread = [Link](target=threadProc, name='Other Thread')
[Link]()
[Link]()
print('ok')
def threadProc():
for i in range(10):
print('{}: {}'.format(threading.current_thread().name, i))
[Link](1)
main()
Therad sınıfının is_alive isimli metodu thread sonlanmışsa False, sonlanmamışsa True değerine geri dönmektedir.
Örneğin:
Metotların parametre değişkenleri ve yerel değişkenleri "stack" denilen bir bölümde yaratılmaktadır. Thread'lerin de
her sistemde olduğu gibi Python'da da stack'leri birbirlerinden ayrılmıştır. Bu nedenle iki thread akışı aynı fonksiyon
ya da metot üzerinde ilerlerken o fonksiyon ya da metodun yerel değişkenlerininfarklı kopyalarını kullanıyor
durumdadırlar. Yani bir thread fonksiyondaki yerel değişkeni değiştirdiğinde diğer thread onu değişmiş olarak
görmez. Thread'in yerel değişkenlerinin her thread için ayrı bir kopyası vardır. Halbuki global değişkenlerin toplamda
tek bir kopyası vardır. Yani thread'lerden biri bir global değişkenin değerini değiştirirse diğeri onu değişmiş görür.
CPython yorumlayıcısı maalesef GIL (Global Interpreter Lock) denilen ana bir kilide sahiptir. Bu nednele bu dağıtımda
aynı anda prosesin iki thread'i çalışamamaktadır. Örneğin sistemimizde 4 çekirdek olsun. Bizim CPython dağıtımında
yarattığımız thread'ler farklı çekirdeklere atanmış olabilirler. Ancak ne olursa olsun bunlar aynı anda çalışamazlar. Bu
nedenle bu dağıtımdaki default thread kütüphanesi ile "paralel programlama" yapılamamaktadır. Tabii aslında bu
durum Python dilinden değil gerçekleştirimden kaynaklanmaktadır. Örneğin JPython ve IronPython
gerçekleştirimlerinde bu kısıt yoktur. CPython'daki bu kısıt yorumlayıcının genel performansını artırmak için
konulmuştur.
Bloke Kavramı
Bir thread çalışırken dışsal bir olayı başlattığında (örneğin disk işlemi, klavye okuması, soket okuması vs. gibi) işletim
sistemi thread'i CPU zamanı harcanmasın diye geçici olarak çizelge dışına çıkartır ve olayı kendisi arka planda kesme
143
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
(interrupt) tekniğiyle izler. Bu sırada sanki thread hiç çizelgede değilmiş gibi bekletilir. İşlem bittiğinde işletim sistemi
yeniden thread'i çizelgeye yerleştirir. Sonuçta thread yine olay bitene kadar beklemiş olur fakat boşuna CPU zamanı
harcanmamıştır. İşte bir thread'in bir işlem bitene kadar ya da gerçekleşene kadar çizelge dışına çıkartılarak
bekletilmesine thread'in bloke olması (blocking) denilmektedir. Örneğin programlama dillerindeki sleep gibi
fonksiyonlar da aslında meşgul bir döngüde sürekli bekleme yapmazlar. Bunlar da bloke yol açarak threadi
bekletirler. Böylece bir sistemde yüzlerce thread olabilir fakat bunların çoğu belli bir olayı bekler durumdadır. Yani
çok az thread aktif olarak belli bir anda CPU'yu kullanma eğilimindedir.
Aslında thread'in bir quanta süresinin tamamını harcaması çok nadirdir. Örneğin thread'in quanta süresi 20 ms.
olsun. Pek çok thread daha birkaç milisaniye içerisinde bir IO olayına girer ve uzun süre bekler.
Burada Running thread'in o anda CPU'ya atanmış olduğunu gösteriyor. Thread quanta süresini normal olarak
doldurduğunda çizelgede bekletilir. Bu durum şekilde "Ready" ile temsil edilmiştir. Thread çalışırken bloke olursa
çizelgeden çıkarılır. Bu durum da şekilde "Waiting" ile belirtilmiştir.
Bir proses bloke olduğunda işletim sistemi onu ismine "wait kuyruğu (wait queue)" denilen bir kuyrukta bekletir.
Sonra olay gerçekleşince oradan onu alarak yeniden çizelge kuyruğuna koyar. Genellikle işletim sistemleri her olay
için ayrı birer wait kuruğu oluşturmaktadır.
Pekiyi bir thread ne kadar zaman CPU harcayıp ne kadar zaman wait kuyruğunda bekler? Tabii bu thread'inden
thread'ine değişir. Genel olarak çok CPU kullanan fakat az IO yapan thread'lere "CPU yoğun (CPU bound)" thread'ler,
çok IO yapıp az CPU kullanan thread'lere de "IO yoğun (IO bound)" thread'ler denilmektedir. Genellikle thread'ler IO
yoğun olma eğilimindedir. Örneğin matematiksel hesaplamalar yapan bir thread CPU yoğun, veritabanı işlemi yapan
bir thread IO yoğundur. O halde bir sistemde yüzlerce thread olsa da aslında bunların çoğu uykuda (yani wait
kuyruğunda bekliyor) durumdadır.
144
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bir thread'in CPU kullanım oranından bahsedilebilir. Pekiyi bu nasıl hesaplanmaktadır? Değişik hesaplama yöntemleri
söz konusu olabilir. Bir thread'e verilen quanta süresinin o thread'in ortalama ne kadarını kullandığı iyi bir ölçüt
olabilir. Örneğin thread'in toplam CPU’da harcağı zaman ile wait kuyruklarında harcadığı zamanın toplamı ile de bir
oran belirlenebilir.
Thread Senkronizasyonu
Thread'ler konusunun en önemli bölümünü thread senkronizasyonu oluşturmaktadır. Thread'ler bir arada çalışırken
birbirlerini beklemek zorunda kalabilirler.
Başından sonuna kadar tek bir akış tarafından çalıştırılması gereken kod parçalarına kritik kod (critical section)
denilmektedir. Pek çok durumda thread'ler ortak bir kaynak üzerinde bir arada çalışma yapıyor olabililer. Bu ortak
kaynak bir veri yapısı olabileceği gibi dış dünyadaki donanımsal bir aygıt da olabilir. İşte bir thread ortak bir kaynak
üzerinde ilerlerken o sırada thread'ler arası geçiş olursa o kaynak kararsız bir durumda kalır. Diğer bir thread kaynağı
kullanmak istediğinde sorun çıkar. Örneğin iki thread'iğn aynı global değişkeni artırdığını düşünelim. Artırma yapan
thread tam artırmanın ortasında kesilirse ve diğer thread artırma yapmaya çalışırsa bu işlem umulduğu gibi
gerçekleşmeyebilir. Bu tür ortak kaynak kullanan thread'lerin işin tamamı bitene kadar birbirlerini beklemesi
gerekmektedir. Örneğin:
import threading
x = 0
def main():
thread1 = [Link](target=threadProc1, args=('Thread-1', ))
thread2 = [Link](target=threadProc2, args=('Thread-1',))
[Link]()
[Link]()
[Link]()
[Link]()
def threadProc1(args):
global x
for i in range(10_000_000):
x += 1
def threadProc2(args):
global x
for i in range(10_000_000):
x += 1
main()
Burada bizim işlemi tek bir += operatörüyle yapmış olmamız bunun atomik olacağı anlamına gelmez. Makine
komutları atomiktir. Yani bir makine komutu çalıştırılırken zaten thread’ler arası geçiş oluşamaz. Ancak iki makine
komutu arasında thread’ler arası geçiş olabilir. İşte Python yorumlayıcısı x += 1 ifadesini tek bir makine komutuyla
yapmak zorunda değildir. Örneğin yorumlayıcı bu işlemi aşağıdaki gibi üç makine komutuyla yapabilirler:
MOV reg, x
145
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
INC reg
MOV x, reg
Gerçekten de yukarıdaki program çalışırsa muhtemelen yitmi milyon değeri görülmeyecektir. İşte bu örnekteki
artırım işlemi kritik bir kodu temsil eder. Bu artırım başından sonuna kadar tek bir thread akışı tarafından
yapılmalıdır.
Krtitik kodlar manuel olarak oluşturulamazlar. Örneğin aşağıdaki gibi bir kodla krtitik kod oluşturamayız:
Bu kodun iki sorunu vardır: Birincisi bekleyen thread meşgul bir döngüde (busy loop) bekleme yapar. İkincisi burada
ok ile gösterilen noktada thread'ler arası geçiş oluşursa birden fazla thread kritik koda girebilir.
Pekiyi bu işlem güvenli bir biçimde nasıl yapılabilir? İşte bu işlem ismine "senkronizasyon nesneleri denilen" bir grup
nesneyle yapılmaktadır. Python'da bu amaçla Lock nesneleri (mutex nesneleri) kullanılmaktadır.
1) Lock nesnesi global düzeyde yaratılır. Ya da yerel düzeyde yaratılıp thread fonksiyonlarına parametre yoluyla
aktarılır. Lock nesneleri threading modülündeki Lock isimli sınıfla temsil edilmektedir:
def main():
lock = [Link]()
thread1 = [Link](target=threadProc1, args=(lock, ))
thread2 = [Link](target=threadProc2, args=(lock, ))
[Link]()
[Link]()
[Link]()
[Link]()
2) Kritik kod Lock sınıfının aquire ve release metotları arasına alınır. aquire kilidi elde etmek için (yani kilitlemek için)
release ise serbest bırakmak için (yani açmak için) kullanılmaktadır.
146
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Thread'lerden biri aquire metodu ile kilidi aldığı zaman artık diğer thread'ler kilidi alan thread release metodu ile
kilidi bırakana kadar acquire metodunda blokede bekler. Tabii bekleme meşgul bir döngüde değil çizelge dışına
çıkılarak yapılmaktadır. Böylece kritik kod başından sonuna kadar tek bir akış tarafından çalıştırılmış olur.
import threading
x = 0
def main():
lock = [Link]()
thread1 = [Link](target=threadProc1, args=(lock, ))
thread2 = [Link](target=threadProc2, args=(lock, ))
[Link]()
[Link]()
[Link]()
[Link]()
def threadProc1(lock):
global x
for i in range(10_000_000):
[Link]()
x += 1
[Link]()
def threadProc2(lock):
global x
for i in range(10_000_000):
[Link]()
x += 1
[Link]()
main()
Ayrıca lock deyimi "kaynak yönetim protokolünü (resource management protocol)" desteklemektedir. Yani biz lock
nesnelerini with ile kullanabiliriz. Bu durumda with deyimine girişte nesne otomatik olarak kilitlenip, çıkıldığında
açılmaktadır. Yukarıdaki kodu biz daha sade biçimde şöyle de yazabilirdik:
def threadProc1(lock):
global x
for i in range(10_000_000):
with lock:
x += 1
def threadProc2(lock):
global x
for i in range(10_000_000):
with lock:
x += 1
Anımsanacağı gibi iki thread aynı anda ekrana birşeyler yazmak istediğinde yazım biçimsel olarak bozulabiliyordu. İşte
Lock nesneleri ile biz yazımın bozulmamasını sağlayabiliriz. Örneğin:
147
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import threading
import time
def main():
lock = [Link]()
thread1 = [Link](target=threadProc1, args=('thread-1', lock, ))
thread2 = [Link](target=threadProc2, args=('thread-2', lock, ))
[Link]()
[Link]()
[Link]()
[Link]()
main()
import threading
import time
import random
def main():
lock = [Link]()
thread1 = [Link](target=threadProc1, args=('thread-1', lock, ))
thread2 = [Link](target=threadProc2, args=('thread-2', lock, ))
[Link]()
[Link]()
[Link]()
[Link]()
main()
Pekiyi birdne fazla thread acquire metodunda kilidin açılmasını beklerken kilit açıldığında hangi bekleyen thread kilidi
alır? Genel olarak işletim sistemleri bu konuda herhangi bir garanti vermemktedir. (Yani kilide ilk gelen thread'in kilidi
alacağı yönünde bir garanti yoktur.)
Lock sınıfının acquire isimli metodu biraz daha genişkullanıma sahiptir. Metodun parametrik yapısı şöyledir:
acquire(blocking=True, timeout=-1)
blocking parametresi default olarak True biçimdedir. Eğer bu parametre False yapılırsa acquire kilidi alamadığı
durumda blokeye yol açmaz. Bu durumda metot False değerine geri döner. Örneğin:
Eğer kilit başka bir thread tarafından alınmamışsa blokesiz modda acquire True ile geri dönmektedir. acquire
metodunun timeout parametresi zaman aşımını belirtir. Thread en fazla burada belirtilen saniye kadar blokede
kalmaktadır. Halbuki default durumda thread kilit açılana kadar blokede kalır. acquire metodu eğer zaman aşımından
dolayı sonlanmışsa False değerine, kilit açık olduğundan dolayı sonlanmışsa True değerine geri döner.
Lock nesnelerini hangi thread kilitlemişse o thread açmak zorundadır. Yani başka bir thread eğer kilidi almamışsa
relese metodu ile kilidi açamaz.
Semaphore Nesneleri
Semaphore'lar sayaçlı senkronizasyon nesneleridir. Bir kritik koda en fazla n tane akışın girmesini sağlamak için
kullanılırlar. Semaphore'lar sayesinde üretici-tüketici problemi gibi algoritmik problemler çözülebilmektedir.
Semaphore'lar özellikle n tane kaynağın bölüştürülmesi gerektiği durumlarda tercih edilmektedir. Örneğin elimizde
üç tane kaynak olsun biz bu kaynakları n tane thread'in olduğu bir ortamda paylaştırmak isteyelim. İlk gelen üç
thread bu kaynakları elde edecektir. Ancak daha sonra gelenler kaynakları elden eden thread'lerin en az biri kaynağı
bırakana kadar blokede bekleyecektir.
149
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Semaphore kullanımı şöyledir:
1) Semaphore nesnesi bir sayaç sayısı verilerek yaratılır. Bu sayı paylaştırılacak kaynağı temsil etmektedir. Örneğin:
sem = [Link](3)
Semaphore sınıfının acquire metodu eğer semaphore sayacı sıfırdan büyükse geçişe izin verir. Ancak sayaç otomatik
olarak bir eksiltilir. Eğer semaphore sayacı 0 ise acquire metodu blokeye yol açarak thread'i bekletir. Ta ki sayaç
sıfırdan büyük olana kadar. release metodu semaphore sayacını bir artırmaktadır. Böylece artık kritik koda girmek
için bekleyen bir thread girişi yapabilir. Ancak kritik koda girmek için birden fazl thread bekliyorsa hangi thread'in
kritik koda gireceği hakkında bir garanti verilmemektedir.
Eğer bir semaphore yaratılırken semaphore sayacı 1 ise böyle semaphore'lara "binary semaphore'lar" denilmektedir.
Binary semaphore'lar Lock (yani mutex) nesnelerine benzemektedir. Ancak semaphore nesnelerine başka bir thread
tarafından release uygulanabilmektedir. Bu bakımdan binary semaphore'lar yine de Lock (mutex) nesnelerindne
ayrılmaktadır. Semaphore'lar en fazla üretici-tüketici tarzı problemlerin çözümü için kullanılmaktadır. Burada önce
üretici-tüketici problemini ele alacağız. Aslında Python'da zaten üretici-tüketici problemi queue isimli sınıfla
geröekleştirilmiştir. Yani programcının aşağıda anlatılacağı biçimde bu problemi semaphore'larla çözmesine gerek
yoktur.
import threading
import time
import random
shared = 0
def main():
thread_producer = [Link](target=producer)
thread_consumer = [Link](target=consumer)
thread_producer.start()
thread_consumer.start()
thread_producer.join()
thread_consumer.join()
def producer():
150
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
global shared
i = 0
while True:
[Link]([Link]())
shared = i
i += 1
if i == 100:
break
def consumer():
while True:
val = shared
[Link]([Link]())
print(val, end=' ', flush=True)
if val == 99:
break
print()
main()
0 0 0 1 1 1 3 4 4 5 6 7 11 12 12 14 14 15 16 17 18 18 19 21 22 24 24 25 25 26 27 27 28 30 30 31
32 32 36 36 37 38 38 40 41 42 43 43...
Üretici-Tüketici problemi tipik olarak semaphore nesneleriyle çözülmektedir. Çözüm için iki semaphore alınır. Üretici
ve tüketici birbirlerini bu semaphore'larla beklerler. Yani üretici henüz tüketici değeri almadna yeni bir değer
yerleştirmez. Tüketici de üretici yeni bir değer yerleştirmeden eski değeri yeniden almaz.
Burada iki semaphore oluşturulmuştur. Üretici semaphore'un başlangıç değeri 1, tüketici semaphore'un 0'dır. Üretici
tüketicinin semaphore syacını, tüketici de üreticinin semaphore sayacını 1 artırır. Böylece birbirlerini kurtarırlar.
Python kodu şöyle oluşturulabilir:
import threading
import time
import random
semprod = [Link](1)
semcons = [Link](0)
shared = 0
def main():
thread_producer = [Link](target=producer)
151
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
thread_consumer = [Link](target=consumer)
thread_producer.start()
thread_consumer.start()
thread_producer.join()
thread_consumer.join()
def producer():
global shared
i = 0
while True:
[Link]([Link]())
[Link]()
shared = i
[Link]()
i += 1
if i == 100:
break
def consumer():
while True:
[Link]()
val = shared
[Link]()
[Link]([Link]())
print(val, end=' ', flush=True)
if val == 99:
break
print()
main()
Pekiyi üretici-tüketici probleminde neden tek bir thread hem üretim hem de tüketimi kendisi yapmıyor da bunun için
iki farklı thread kullanılıyor? Bunun nedeni hız kazancı sağlamaktır. Bilgiyi tek bir thread'in alıp işlediğini düşünelim:
Üretici-Tüketici probleminde paylaşılan alan bir kuyruk sistemi olabilir. Bu durumda üretici yalnızca kuyruk dolu
olunca tüketici de yalnızca kuyruk boş olunca diğerini bekler Toplamda bekleme süresi çok daha azılır. Python'da
bunu uygulamak için üreticinin semaphore sayacı kuruk uzunluğuna tüketicinin ise sıfıra kurulur. Aşağıdaki
uygulamada kuyruk sistemini bir listeyle döngüsel olarak oluşturacağız.
152
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import threading
import time
import random
shared = [0] * 10
semprod = [Link](len(shared))
semcons = [Link](0)
head = 0
tail = 0
def main():
thread_producer = [Link](target=producer)
thread_consumer = [Link](target=consumer)
thread_producer.start()
thread_consumer.start()
thread_producer.join()
thread_consumer.join()
def producer():
global shared, head
i = 0
while True:
[Link]([Link]())
[Link]()
shared[head] = i
head += 1
head %= len(shared)
[Link]()
i += 1
if i == 100:
break
def consumer():
global tail
while True:
[Link]()
val = shared[tail]
tail += 1
tail %= len(shared)
[Link]()
[Link]([Link]())
print(val, end=' ', flush=True)
if val == 99:
break
print()
main()
Üretici tüketici probleminin birden fazla üretici ve birden falz tüketici olduğu genel biçimleri de vardır. Temelde
bunların gerçekleştirilmesi benzer biçimdedir. Kursumuzda bu gerçekleştirimler üzerinde durulmayacaktır.
153
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Python'da çok üreticili ve çok tüketicili üretici-tüketici probleminin kuyruklu versiyonu hazır biçimde oluşturulmuştur.
Yani programcının aslında yukarıdaki gibi bir kuyruk sistemi oluşturmasına bir gerek yoktur. queue modülndeki
Queue isimli sınıf zaten kendi içerisinde semaphore kontrolü ile üretici-tüketici problemini gerçekleştirmektedir.
Bir Queue nesnesi kuyruk uzunluğu verilerek yaratılır. Sonra put metodu ile kuyruğa yerleştirme yapılır, get metodu
ile kuyruktan bilgi alınır. Queue sınıfı FIFO kuyruk oluşturmaktadır. Bu sınıfın farklı birkaç değişik biçimi de vardır. Bu
durumda yukarıdaki üretici-tüketici problemini aşağıdaki gibi queue nesnesiyle gerçekleştirebiliriz:
import threading
import time
import random
import queue
q = [Link](10)
def main():
thread_producer = [Link](target=producer)
thread_consumer = [Link](target=consumer)
thread_producer.start()
thread_consumer.start()
thread_producer.join()
thread_consumer.join()
def producer():
i = 0
while True:
[Link]([Link]())
[Link](i)
i += 1
if i == 100:
break
def consumer():
while True:
val = [Link]()
[Link]([Link]())
print(val, end=' ', flush=True)
if val == 99:
break
print()
main()
Queue sınıfının emty isimli metodu kuyruk boşsa True değerine, doluysa False değerine geri dönmektedir. İsterse
programcı get ve put metotlarına zaman aşımı verebilmektedir. Bu metotların parametrik yapısı şöyledir:
Eğer get metodu metotlar zaman aşımından dolayı sonlanmışlarsa [Link] isimli exception'ı put metodu ise
[Link] isimli exception'ı oluşmaktadır. Queue sınıfının ayrıca blokesiz biçimde işlem yapan get_nowait ve
put_nowait metotları da vardır. (Aslında bu metotlar yerine get ve put metotlarında block=False da geçilebilir.)
get_nowait metodu kuyruk boşsa blokede beklemez bunun yerine [Link] isimli exception'ı oluşturmaktadır.
Benzer biçimde put_nowait metodu da eğer kuyruk doluysa bekleme yapmayıp doğrudan [Link] exception
oluşturmaktadır.
154
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
queue modülündeki PriorityQueue senkronize üretici-tüketici öncelik kuyruğu oluşturmaktadır. Bu kuyrukta
elemanlar kuyruğua eklenirken onlara bir özelik derecesi de verilmektedir. Böylece kuyruktan eleman alınırken alan
thread önceliği en yüksek olan (sayısal olarak en düşük olan) elemanı önce alır. Eğer eşit öncelikli birden fazla eleman
kuyrukta varsa FIFO sırası işletilmektedir.
Bir prosesten diğerine belli miktarda byte gönderip alma sürecine "Proseslerarası Haberleşme" denilmektedir.
Örneğin biz bir Python programından diğerine bilgi gönderip diğerinin bu bilgiyi kullanmasını sağlayabiliriz.
Proseslerarası haberleşme kabaca ikiye ayrılır:
Proseslerarası haberleşme yöntemlerine pek çok durumda gereksinim duyulmaktadır. Örneğin bir proses dış
dünyadan elde ettiği verileri başka proseslere iletmek isteyebilir. Ya da bir proses başka bir proses tarafından
yönetilmek istenebilir. Aynı makinenin prosesleri arasındaki haberleşmelerde kullanılan tipik teknikler şunlardır:
- Borular (Pipes)
- Paylaşılan Bellek Alanları (Shared Memory)
- Mesaj Kuyrukları (Message Queue)
Farklı makineler birbirlerine ağ içerisinde bağlanmış olabilir. Biz bir makinede çalışan bir programın ağa bağlı başka
bir makinedeki prosese bilgi göndermesini ve almasını isteyebiliriz. Böyle bir haberleşmede artık işletim sisteminin
dışında başka birtakım aktörler de devreye girecektir. Örneğin kablolama sisteminden kullanılan hub'a kadar bazı
donanım birimleri işin içine karışmaktadır. Üstelik bu tür haberleşmelerde işletim sistemleri bile birbirlerinden farklı
olabilmektedir. İşte hetorejen böyle ortamlarda haberleşmenin sağlıklı yürütülmesi için önceden belirlenmiş birtakım
kuralların bulunması gerekir. (Örneğin kablo standartları ve konnektörler nelerdir? Network kartının özellikleri nasıl
olacaktır? Bilgiler nasıl paketlere ayrılıp gönderilecektir? Makinalar nasıl birbirlerinden ayrılacaktır vs. gibi...) İşte tüm
bu belirlemelere protokol denilmektedir. Bugün en yaygın kullanılan protokol Internet'in de kullandığı IP (Internet
Protocol) protokol ailesidir. Kursumuzda IP protokol ailesi ile haberleşme ele alınacaktır.
Tıpkı fonksiyonların birbirlerini çağırarak daha yüksek seviyeli işlemleri yapar hale gelmesi gibi protokoller de üst üste
yığılarak ayrı ayrı oluşturulmaktadır. Her üst protokol aşağının zaten hazır olduğu fikriyle yalnızca kendi
gereksinimlerini tanımlamaktadır. Böyle katmanlı tasarımın pek çok faydası vardır. Örneğin bu sayede üst seviye
protokoller detay barındırmazlar ve aşağı düzeydeki protokollerin değişmesinden fazlaca etkilenmezler. İşte farklı
makşnelerin haberleşmesi için bu biçimde oluşturulmuş pek çok protokol ailesi vardır. Örneğin AppleTalk, NETBIOS
vs. gibi...
Network altında bilgisayar haberleşmesi için protokol katmanlarının nasıl oluşturulması gerektiğine yönelik IEEE
ismine OSI (Open System Interconnection) denilen bir doküman yayınlamıştır. Buna OSI model denilmektedir. OSI
model bir protol ailesi değildir. Protokol ailesi oluşturacaklar için bir kılavuz niteliğindedir. OSI'nin toplam 7 katmanı
vardır:
155
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
OSI'nin en aşağı katmanına “Fiziksel Katman (Physical Layer)” deilmektedir. Fiziksel katmanda iletişimin yapılacağı
ortam tanımlanmaktadır. Örneğin kullanılacak kablolar, konnektörler, gerilim seviyeleri gibi. Bunun üzerinde “Veri
Bağlantı Katmanı (Data Link Layer)” bulunmaktadır. Bu katmanda network kartlarına ilişkin belirlemeler, fiziksel
adresleme belirlemeleri vs. bulunmaktadır. Örneğin Ethernet kartlarının protokolü olan Ethernet Protokolü bir Veri
Bağlantı Katmanı Protokolüdür. Network katmanı (Network Layer) mantıksal adreslemenin tanımlandığı, bilginin nasıl
paketlere ayrılıp gönderileceğinin tanımlandığı en önemlş katmanlardan biridir. Örneğin IP protokol ailesinin IP
Protokolü (Internet Protocol) OSI'ye göre Network katmanına ilişkindir. Network katmanında ayrıca
“internetworking” için rotalama belirlemeleri de bulunmaktadır. Network üzerinde “İleti Katmanı (Transport Layer)”
bulunmaktadır. Burada paketlerin numaralandırılması, mantıksal port adreslerinin tanımlanması, hata durumunda
bunun telafi edilmesi gibi belirlemeler bulundurulmaktadır. Örneğin IP protokol ailesindeki TCP ve UDP protokolleri
İleti Katmanına ilişkin protokollerdir. “Otgurum Katmanı (Session Layer)” pek çok ailede bulunmamaktadır. Burada
haberleşme için gereken oturum açmaya yönelik belirlemeler bulunur. Örneğin izinler, kimlik doğulama gibi. Bunun
yukarısında da “Sunum Katmanı (Presentation Layer)” bulunur. Sunum katmanında gönderilip alnına bilgilerin
sıkıştırılmasına, açılmasına, şifrelenmesine vs. yönelik belirlemeler bulunmaktadır. IP protokol ailesi Sunum
Katmanına da sahip değildir. Nihayet en tepede “Uygulama Katmanı (Application Layer)” bulunmaktadır. Bu katman
artık belli bir amacı gerçekleştirmek için oluşturlan yazılımların kullanacağı belirlemeleri içerir. Örneğin eposta için
kullanılan POP3, dosya transferi için kullanılan FTP birer Uygulama Katmanı Protolüdür.
Bilgisayarları birbirlerine bağlamak ilk kez 60'yıllarda insanların aklına gelmiştir. Soğuk savaş yıllarında Amerika
Savunma Bakanlığına bağlı olan DARPA (Defense Advanced Research Project Agency) kurumu birkaç üniversite ile
birlikte 1969 yılında ARPANET isimli bir proje başlattı. ARPANET ilk kez 1969 yılında uzak mesafeden dört
üniversitenin birbirlerine bağlanmasıyla hayata geçirilmiş oldu. ARPANET'te daha sonra bazı devlet kurumları ve
üniversiteeler katılmaya başlamıştır. 70'lı yılların sonlarına doğru ARPANET Amerika'da gelişmeye başlamıştır. 1983
yılında ARPANET NCP (Network Control Protocol) protokolünü bırakarak IP ailesine ailesine geçmiştir. Ve arık ağ
Internet ismiyle yayılmaya devam etmiştir. Internet 80 yıllarda Avrupa'ya ve Türkiye'ye de geldi. Ancak tabi kişsel
bilgisayarlar daha yeniydi ve Internet'e ancak Üniversitelerden ve bazı devlet kurumlarından, özel sektörden
bağlanılabiliyordu. 1990-91 yıllarında HTTP protokü tasarlandı ve ilk Web sayfaları oluşturulmaya başlandı. 90'lı
yılların ortalarına doğru tüm dünyada kişisel bilgisayarlarla servis sağlayıcılar sayesinde Internet'e girmek mümkün
hale gelmiştir. Daha sonraları modern modem/router'larla yüksek hızlı evden erişimler sağlanmıştır.
Internet ismi “internetworking” sözcüğünden gelmektedir. Internetworking “yerel ağların birbirlerine router isimli
cihazlarla bağlanmalarıyla oluşturulmaktadır. Internetworking temel bir terimdir ve IP protokol ailesinin ismi buradan
gelmektedir. Bugün Internet denildiğinde herkesin bağlandığı ARPANET'ten evrimleşen dev ağ aklıma gelir. (Internet
yazarken I'yı büyük yazarsak bu ağ anlaşılır.) Şüphesiz mevcut protokoller sayesinde herkes kendi internetini
kurabilir. Örneğin biz de birkaç arkadaşınızla ayrı bir Internet dünyası oluşturabiliriz. Hatta bazı ülkelerin bu biçimde
kendilerine özgü Internet'leri vardır.
156
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
IP Protokol Ailesi
IP açık bir protokol ailesidir. Burada açık demekle hiçbir şirketin malının olmadığık bağımsız konsorsiyumlar
tarafından yönetildiği anlamına gelmektedir. Ayrıca dokümanlar herkes tarafından paylaşılmakta ve isteyen kişiler
önerilerde bulunabilmektedir.
IP protokolü Vint Cerf ve Bob Kahn tarafından 1974 yılında önce TCP sonra IP biçiminde tasarlanmıştır. Sonra aileye
diğer üyeler katılmıştır. İlk ciddi gerçekleştirimi BSD sistemlerinde yapılmıştır. 1983 yılında ARPANET'in IP ailesine
geçmesiyle popülaritesi çok artmıştır.
IP protokol ailesi aslında geniş bir ailedir. Ailede pek çok yardımcı protokol vardır. Yukarıdaki şekil yalnızca
kursumuzda söz konusu edilen konuları kapsayacak biçimde oluşturulmuştur.
Ailenin en önemli taban protokolü IP (Internetworking Protocol) protokolüdür. Zaten aileye ismini bu protokol
vermiştir. IP protokolü paket anahtarlamalı (packet switching) bir protokoldür. Yani bilgiler paket denilen öbeklere
ayrılarak gönderilip alınır. IP protokolğnde adresleme artık fiziksel değil mantıksaldır. IP protokol ailesinde ağa bağlı
her birime “host” denilmektedir. IP protokolünde her host'un ismine IP adresi denilen mantıksal bir adresi vardır.
Mantıksal adres bunun donanımsal olarak belirlenmediği yazılımsal olarak atandığı anlamına gelmektedir. Fakat
örneğin Ethernet protokolünün kullandığı MAC adresi fiziksel bir adrestir. Fiziksel adres bunun donanımsal olarak
kartın üzerine çakılı olduığu ya da donanımın kendisinin bunu tespit edip işlem yaptığı adres demektir. Dolayısıyla
mantıksal adresler dinamiktir, fiziksel adresler statiktir. Mantıksal adresler biz ağa dahil olduğumuzda bize
atanmaktadır. Tabi biz de istediğimiz adresin atanması konusunda ısrarcı olabiliriz.
IP protokolünün de versiyonları vardır. Şu anda hala ağırlıklı kullanılan versiyon IPV4'tür. Ancak IPV6 yavaş yavaş
daha yaygın kullanılır hale gelmiştir. IPV4te IP adresleri 4 byte uzunluktadır. Ancak IPV6'da IP adresleri 16 byte'tır. 4
byte'lık IP adresleri şu an için artık çok yetersiz kalmaktadır.
Bugün bilgisayarlarımızda fiziksel ve data link katmanı olarak Ethernet ve Wireless Protokolleri kullanılmaktadır.
Ethernet protokolü ethernet kartına gereksinim duyar. Bu kart fiziksel olarak bilgileri bilgisayarımızdan dışarı
gönderip almakta kullanılır. Ethernet protokolü de paket anahtarlamalı bir protokoldür. Yani bilgiler paket paket
gönderilip alınır. Paket anahtarlama hattın etkin kullanımını sağlar. Biz Ethernet kartlarını bir hub'la biribirine
bağlayarak yerel bir ağ (local area network) oluşturabiliriz. Bugün evlerimizdeki ağ da yerel bir ağdır. Yerel ağları
birbirlerine bağlamak için “router” denilen aygıtlar kullanılır. Ethernet kartı (yani network kartı) aynı ağdaki bir
bilgisayardan diğerine paket haberleşmesi için kullanılmaktadır. Ancak router farklı ağlar arasında paket haberleşmesi
için kullanılır. Bugün evlerimizdeki ADSL modemler aynı zamanda birer router görevindedir.
157
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bizim evimizdeki yerel ağ Internet isimli dev ağa router aracılığıyla tek bir host gibi bağlanmaktadır. Dolayısıyla bizim
Internet için dışarıdan kullanılacak tek bir IP adresimiz vardır (Tabi tek bir router ve hattımızın bulunduğunu
varsayıyoruz). Bizim evimizdeki yerel ağ ayrı bir IP ağıdır. Yani ayrı bir dünyadır. Biz istersek hiç Internet'te çıkmadan
kendi yerel ağımızda tüm Internet uygulamalarını (Yani IP protokol uygulamalarını) çalıştırabiliriz. Buna genellikle
“Intranet” denilmektedir. O halde bizim evimizdeki bir bilgisayarın bir yerel IP adresi vardır bir de router'ımızın
Internet'ten görülen bir IP adresi vardır. Router dış dünyadan gelen paketleri yerel ağda uygun bilgisayara
dağıtmaktadır. Yerel ağdaki paketleri de dış dünyaya ilişkinse dış dünyaya yollamaktadır. Biz yerel ağımızdaki bir
host'tan diğerine bilgi gönderirken router devreye girmez.
Ip protokolünde gönerilen bir paketin başında “IP header” isimli bir başlık kısmı vardır. Burada pakete ilişkin
metadata bilgileri bulunur. Örneğin paket hangi IP adresine gönderilmektedir? Checksum bilgisi nedir? Hangi IP
versiyonu kullanılmaktadır? vs. Aslında tabi (böylek olmak zorunda değil ama) bilgiler neticede ethernet kartı ile
gönderilip alındığı için IP paketi aslında Ethernet protokolünün ethernet paketinin data bölümünde kodlanır.
Ethernet protokolünün de ayrı bir header bölümü vardır. Örneğin:
Ethernet protokolü IEEE 802.3 numaralı standardıyla belirlenmiştir. Wireless protokolü de aynı ailedendir. O da IEEE
802.11 numaralı standarttır.
Ip protokü ile birden fazla paketten oluşan bilgi gönderilebilir mi? Evet fakat bunun için paketlere numara vererek
bizim de adeta ayrı bir protokol oluşrumamız gerekir. Zaten TCP protokolü buna benzer bir protokoldür.
TCP protokolü güvenli (reliable) bir protokoldür. Burada güvenlik demek alış verişin yolda bozulmasının teleafi
edilmesi ve paketlerin düzgün aktarılması anlamına gelir. Çünkü TCP'de bir akış kontrolü (flow control) vardır.
Gönderen tarafla alan taraf karşılıklı konuşarak hatalı giden paketlerin telafisini sağlayabilmektedir. TCP stream
tabanlı bir protokoldür. Stream tabanlı demekle byte byte okumaya kaldığı yerden devam edebilmek anlaşılır. TCP ile
biz daha büyük bilgileri gönderip alabiliriz. TCP bu durumda bu bilgiyi IP paketlerine böler. Onlara numara verir ve
onların karşı tarafa güvenli ulaşmasını denetler. Karşı taraf gelen bilgiyi sanki borudan okuma yapıyormuş gibi byte
byte elde edebilir.
UDP (User Datagram Protocol) güvenli olmayan paket tabanlı (datagram) bir haberleşme sunar. Yani UDP'de bilgiler
IP'deki gibi bağımsız paketler halinde gönderilip alınır. UDP'de bir paket ya alınır ya alınmaz. Byte byte okuma
mümkün değildir. Paketin alındığına dair bir geri bildirim yapılmaz. Tabi bu özelliğinden dolayı UDP daha hızlıdır. UDP
özellikle periyodik data gönderimlerinde, televizyon yayını gibi işlemlerde tercih edilmektedir.
TCP bağlantılı (connection oriented) bir protokoldür, UDP bağlantısızdır (connectionless). Bağlantılı protokol demek
iki taraf haberleşmeden önce birbirlerine bağlanıp karşılıklı konuşma için birbirlerini tanımaları demektir. TCP tipik
olarak client-server tarzda bir çalışmayı akla getirmektedir. Client-Server haberleşmede bir taraf client bir taraf server
olur. Client taraf server tarafa bağlanır, haberleşme bundan sonra yapılır.
158
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
TCP UDP
Bağlantılı Bağlantısız
Stream Tabanlı Datagram Tabanlı
Güvenilir Güvenilir Değil
Yavaş Hızlı
TCP ve UDP'de işin içine port numarası kavramı da girmektedir. Port numarası aynı host'taki uygulamaları
birbirlerinden ayırmak için düşünülmüştür. Adeta şirketlerdeki içsel (internal) telefon numaralarına benzetilebilir.
TCP ve UDP protokollerinde bilgi göndermek için yalnızca gönderilecek host'un IP'sinin bilinmesi yeterli değildir. Aynı
zamanda oradaki uygulamanının hangi port ile ilgilendiğinin de bilinmesi gerekir. Genellikle gösterimde ip adresi ve
port numarası aralarına ':' karakteri getirilerek “ip:port” biçiminde belirtilmektedir. IPV4'te toplam 65536 port
numarası vardır (yani port numarası için iki byte yer ayrılır). IPV6'da ise port numaraları 4 byte uzunluğundadır.
IPV4'te ilk 1024 port numarası Internet'in kendi uygulama protokelleri için ayrılmıştır. Bunlara “well known” portlar
da denilmektedir. Örneğin FTP 21, SSH 22, Telnet 23, HTTP 80 numaralı portları kullanmaktadır. Biz kendi
uygulamalarımız için port numarası belirleyeceksek ilk 1024 portu kullanmamalıyız.
IP protokol ailesinde her host'un bir IP adresinin olduğunu belirtmiştir. IP adreslerinin akılda tutulması zordur. Bu
nedenle IP ailesinde IP adresleri host isimleriyle eşleştirilmiştir. Protokol host isimleriyle değil IP adresleriyle çalışır.
Ancak IP ailesinde host isimlerinin hangi IP adreslerine karşılık geldiği ismine "DNS" denilen özel server'larda
tutulmaktadır. Bu serverlardan sorgulama "DNS Protokolü" ile yapılmaktadır. Host isimleri IP adresleri bire bir
eşlenmiş değildir. Bir host ismine birden fazla IP adresi karşılık gelebileceği gibi, bir IP adresine de birden fazla host
ismi karşılık gelebilmektedir.
Yukarıda da belirtildiği gibi TCP tipik olarak client-server bir çalışmayı akla getirmektedir. Client-Server modelde
ismine client ve server denilen iki ayrı program vardır. Asıl işi server program yapar. Client yalnızca istekte bulunur.
Server işi yapar sonuçları client'a gönderir. Bir server birden fazla client'a hizmet verebilmektedir. >
Client-Server modelde önce client server'a bağlanır. Haberleşme ondan sonra başlar. Client-Server uygulamalar her
ne kadar TCP'yi çağrıştırıyorsa da aslında bu bir haberleşme mimarisidir. Yani aslında client-server çalışma için IP
ailesinin kullanılması gerekmez. Bu çalışma örneğin aynı makinadaki prosesler arasında borularla mesaj kuyruklarıyla
da sağlanabilir.
1) Server programın çalıştığı makine güçlü olabilir. Biz de onun gücünden yararlanmak istiyor olabiliriz. Örneğin uzun
zaman alan bir işlemi el terminalinden yapmak yerine el terminalini client olarak kullanıp asıl işi server'a yaptırmak
uygun olabilir.
159
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
2) Server program kaynak paylaşımı sağlayabilir. Örneğin yazıcı telk bir bilgisayara bağlıdır. Başka bilgisayardaki print
programları client gibi çalışarak yazıcının bağlı makinadaki server programa isteği iletir. Server da print işlemini client
için yapar. Ya da örneğin server'a bir veritabanı bağlıdır. Client ondan istekte bulunur. Örneğin banka ATM'lerinde
veritabanı ATM makinasının içerisinde değildir. ATM'deki program client program gibi davranmaktadır.
3) Server program client'lar arasında işbirliği sağlayabilir. Onlar arasındaki iletişime aracılık edebilir. Örneğin bir char
programında client'lar birbirini tanımamaktadır. Herkes yalnızca server'ı tanır. Her client server'a bağlanır. Server
client arasında haberleşmeye aracılık eder. Ağ üzerinde çalışan oyun programları bu biçimde bir server'ın işbirliği ile
gerçekleştirilmektedir.
4) Client-Server çalışma dağıtık uygulamalarda da karşımıza çıkabilmektedir. Yani bir işin belirli parçalarını başka
bilgisayarlarda yapıp sonra onu birleştirmek isteyebiliriz.
Socket Kavramı
Farklı bilgisayarlar arasında proseslerarası haberleşmede kullanılcaka protokollerin işletim sistemleri tarafından
destekleniyor olması gerekmektedir. Bugün işletim sistemleri bazı yaygın protokolleri destekler durumdadır.
Windows gibi, MAC OS X gibi, Linux gibi işletim sistemleri IP protokol ailesini uzun süredir desteklemektedir. İşletim
sistemlerinde bir protokol ailesi kullanılarak uygulama programlarının yazılabilmesi için bir kütüphanenin de
bulunuyor olması gerekir. İşte bu kütüphaneye "soket kütüphanesi" denilmektedir. Windows, MAC OS X ve Linux gibi
sistemler soket kütüphanesini biribirine çok benzer biçimde desteklemektedir. Bu soket kütüphanesi aslında C
Programlama Dilinden kullanım için oluşturulmuştur. Ancak pek çok bu C kütüphanelerini kendine özgü biçimde
kullanarak benzer kütüphaneleri oluşturmuştur. Python da aslında arka planda işletim sistemlerinin sunduğu C'de
yazılmış olan soket kütüphanesini kullanmaktadır.
Soket kütüphanesi yalnızca IP ailesi için oluşturulmuş bir kütüphane değildir. Soket fonksiyonları pek çok protokol
ailesinin ortak fonksiyonlarıdır. Yani biz IP ailesinde de çalışsak, Apple Talk ailesinde de çalışsak yine aynı soket
fonksiyonlarını kullanırız.
Python'da soket kütüphanesi standart kütüphanedeki "socket" modülüyle gerçekleştirilmiştir. Dolayısıyla soket
arayüzünü kullanabilmek için bizim "socket" modülünü import etmemiz gerekir.
TCP/IP uygulamları için bir tane server programın bir tane de client programın yazılması gerekmektedir. Bu modelde
client program server'ın IP adresini ve port numarasını belirterek server programa bağlanır. Sonra karşılıklı veri alış
verişi yapılır. Biz de burada önce server sonra da client programın yazımını göreceğiz.
160
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Pyton'da soket işlemlerinde fonksiyonlar ve metotlar birtakım hatalar karşısında [Link] isimli bir türle raise
işlemi yapmaktadır. Programcının try-except bloklarıyla bu tür hataları ele alması uygun olur.
1) Server programın öncelikle bir soket yaratması gerekmektedir. Bu işlem soket isimli fonksiyonla yapılır.
Fonksiyonun parametrik yapısı şöyledir:
Fonskiyonun family parametresi kullanılacak protokol ailesini belirtmektedir. Bu parametre default biçimde
socket.AF_INET değerini almıştır. AF_INET "IPV4" protokolü anlamına gelmektedir. IPV6 için AF_INET6 kullanılmalıdır.
type parametresi soketin stream tabanlı mı, yoksa datagram tabanlı mı olduğunu belirtir. TCP uygulamaları için bu
parametresinin sock.SOCK_STREAM biçiminde girilmesi gerekmektedir. proto parametresi spesifik protokolü belirtir.
Ancak IP protokol ailesinde zaten ikinci parametre socket.SOCK_STREAM geçildiğinde bu spesifik transport protokolü
TCP, sock.SOCK_DGRAM geçildiğinde UDP anlaşılmaktadır. fileno parametresi UNIX türevi sistemlerde anlamlıdır. Bu
durumda bir TCP soket yaratma şöyle yapılabilir:
import socket
Tabii aslında verilen arümanlar parametre değişkenlerinin default değerleridir. Bu durumda aynı işlem şöyle de
yapılabilir:
import socket
serverSock = [Link]()
[Link] fonksiyonundan elde edilen ürün [Link] isimli bir sınıf türündendir. Artık diğer işlemler bu
sınıfın metotlarıyla yapılacaktır.
161
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
2) Soket yaratıldıktan sonra onun bağlanması (bind edilmesi) gerekmektedir. Bağlama işlemi [Link] sınıfının
bind isimli metouyle yapılır. bind işlemi sırasında "hangi network kartından gelen bağlantı taleplerinin" ve "hangi port
için" gelen bağlantı taleplerinin dikkate alınacağı belirtilir. Yani bizim bind işleminde "şu network kartından ve port
için gelen bağlantı isteklerini kabul et" belirmesini yapmış oluruz. [Link] fonksiyonunun parametrik yapısı
şöyledir:
bind(address)
bind fonksiyonu (host, port numarsı) biçiminde bir demeti parametre olarak almaktadır. Buradaki host IP adresini
temsil etmektedir. IP adresleri bir yazı biçiminde noktalı formda verilebilir. Örneğin:
'[Link]'
'[Link]'
gibi. Biz host olarak doğrudan host'un adını da girebiliriz. Bu durumda DNS işlemi zaten ilgili fonksiyon tarafında
yapılmaktadır. Server bind işlemini yaparken belli bir network kartının IP adresini verebilir. Bu duurmda yalnızca o
karttan gelen bağlantı istekleri kabul edilecektir. Ya da server tüm kartlardan gelen bağlantı isteklerini kabul edebilir.
Bunun için ip adresi boş string geçilebilir ya da '[Link]' biçiminde loopback adres geçilebilir. Örneğin:
import socket
PORT = 5050
try:
serverSock = [Link](family=socket.AF_INET, type=socket.SOCK_STREAM)
[Link](('', PORT))
except [Link] as msg:
print('Socket error:{}'.format(msg))
else:
print('ok')
3) Artık sıra listen işlemine gelmiştir. [Link] sınıfının listen metodu soketi aktif dinleme konumuna sokar.
listen metodu blokeye yol açmamaktadır. Ancak bu metottan sonra işletim sistemi artık gelen bağlantı isteklerini
bunu bekleyen programımıza iletecektir. listen metodunun parametrik yapısı şöyledir:
listen([backlog])
Metodun parametresi dinleme kuruğunun uzunluğunu almaktadır. İşletim sistemi gelen bağlantı isteklerini sokete
ilişkin bir dinleme kuyruğuna yerleştirir. Eğer bu kuyruk tamamen dolarsa yeni bağlantı istekleri reddedilecektir. Tabii
server her bağlantıyı kabul ettiğinde kuyrukta yer açılır.
Örneğin:
import socket
PORT = 5050
162
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
try:
serverSock = [Link](family=socket.AF_INET, type=socket.SOCK_STREAM)
[Link](('', PORT))
[Link](8)
except [Link] as msg:
print('Socket error:{}'.format(msg))
else:
print('ok')
4) Artık server program gelen bağlantı isteklerini [Link] sınıfının accept metoduyla kabul edecektir. Tabii
bunun için client'ın bağlantı talebinde bulunuyor olması gerekir. Default blokeli modda server accept metodunda bir
bağlantı olulşana kadar blokede bekler. Tabii server eğer accept uygulamadan önce kuruklanmış bir bağlantı isteği
varsa accept hiç bloke olmadan doğrudan bağlantıyı sağlar. accept metodu parametresizdir. accept metodunun geri
dönüş değeri iki elemanlı bir demet nesnesidir. Demetin ilk elemanı client ile konuşmakta kullanılacak soketi, ,kinci
elemanı ise bağlanılan client'a ilişkin adresi belirtir. Server listen ve accept işlemi için bir soket kullanmaktadır. Bu
örneklerimizdeki serverSocket isimli sokettir. Ancak accept başarılı olduğunda bu metot bize yeni bir soket geri
döndürür. Artık server o client'la o soketi kullanarak konuşur. server accept metodunu birden fazla kez çağırırsa
birden fazla client ile bağlantı kurar. Her bağlantı kurduğu client ile o accept'in geri dönüş değeri ile verilen soketle
konuşur. Örneğin:
import socket
PORT = 5050
try:
serverSock = [Link](family=socket.AF_INET, type=socket.SOCK_STREAM)
[Link](('', PORT))
[Link](8)
5) Artık client ile bağlantı sağlandıktan sonra sıra okuma ve yazmaya gelmiştir. Bunun için [Link] sınıfının send
ve recv metotları kullanılır. Ancak bu metotlaar client program anlatıldıktan sonra ayrı bir başlıkta ele alınacaktır.
6) İşimiz bittikten sonra soketi shutdown edip kapatırız. Soketi kapatmak için close metodu kullanılmaktadır. Örneğin:
import socket
PORT = 5050
try:
serverSock = [Link](family=socket.AF_INET, type=socket.SOCK_STREAM)
[Link](('', PORT))
[Link](8)
163
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
print('Ok')
1) Client program da önce socket modülünün soket fonksiyonuyla aynı biçimde soket nesnesini yaratır. Örneğin:
import socket
PORT = 5050
try:
clientSock = [Link](family=socket.AF_INET, type=socket.SOCK_STREAM)
except [Link] as msg:
print('Socket error:{}'.format(msg))
else:
print('Ok')
2) Client program belli bir kaynak porttan bağlanmak isteyebilir. Bunun client programın da bind işlemi yapması
gerekir. Ancak client ptogram bind yapmazsa işletim sistemi tarafından ona boş bir kaynak port atanmaktadır.
164
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bağlantı oluştuğunda atarafların bir kaynak port numarası ve kaynak IP adresi, bir de hedef port numarası ve hedef IP
adresi vardır. Bağlantı client tarafın hedef IP adresini ve hedef port numarasını belirterek kurulur. Ancak bu işlemi
client yaparken kendi IP adresi ve kendi kaynak port numarası vardır. İşte biz bazen server'a belli bir kaynak port
numarası ile bağlanmak isteyebiliriz. (Bazı server'lar bizim belli bir kaynak portu kullanmamızı isteyebilmektedir.) Bu
işlem client'ın bind işlemi yapmasıyla sağlanır. Ancak client bind işlemi yapmazsa otomatik olarak ona bir kaynak port
numarası atanmaktadır.
3) Artık client [Link] sınıfının connect metoduyla server'a bağlanır. client connect metodunu yine bir demet
içerisinde (host, port no) biçiminde argüman oluşturarak çağırır. Tabii hedef makinenin host ismi ya da IP adresi
verilebilmektedir. Ancak port numarasının server'ın dinlediği port numarasıyla aynı olması gerekir. Yukarıda da
belirtildiği gibi geenel olarak TCP protokolünde server'a farklı bir kaynak porttan bağlanılabilir. connect metodu
çağrıldığında belli bir zaman aşımı (timeout) miktarı kadar client bekler. Eğer server bu süre zarfında accept işlemi
yapmazsa bağlantı başarısız olur. Örneğin:
import socket
PORT = 5050
try:
clientSock = [Link](family=socket.AF_INET, type=socket.SOCK_STREAM)
[Link](('[Link]', PORT))
print('connected')
[Link]()
except [Link] as msg:
print('Socket error:{}'.format(msg))
#[Link]
import socket
PORT = 5050
try:
serverSock = [Link]()
[Link](('', PORT))
[Link](8)
#[Link]
import socket
PORT = 5050
try:
clientSock = [Link]()
[Link](('[Link]', PORT))
print('connected')
165
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link]()
except [Link] as msg:
print('Socket error:{}'.format(msg))
Bağlantı kurulduktan sonra artık iki taraf karşılık biçimde (full duplex) birbirlerine bilgi gönderip alabilirler. Bunun için
[Link] sınıfının send ve recv metotları kullanılmaktadır. send metodunun parametrik yapısı şöyledir:
[Link](bytes[, flags])
Metodun birinci parametresi gönderilecek bytes türünden nesneyi ikinci parametresi gönderim bayraklarını
belirtmektedir. İkinci parametre girilmeyebilir. send metodu tüm bilgi network tamponuna bırakılana kadar blokede
kalmaktadır. Bilgi network tamponuna bırakıldıktan sonra işletim sistemi tarafından TCP paketi haline dönüştürülüp
gönderilmektedir. Ancak send metodu geri döndüğün de bilgi henüz yerel bilgisayardan dışarı çıkmamış olabilir. send
metodu gönderilmek istenen byte sayısı ile geri dönmektedir. secv metodunun parametrik yapısı da şöyledir:
[Link](bufsize[, flags])
Metodun birinci parametresi kaç byte okunacağı bilgisidir. İkinci parametre okuma bayrağını belirtir. Bu bayrak
girilmeyebilir. Metot geri dönüş değeri olarak bytes nesnesi verir. recv metodu blokeli modda (yani default durumda)
henüz sokete hiçbir bilgi gelmemişse blokede bekler. Ancak sokette en az 1 byte bilgi varsa parametresiyle belirtilen
miktarda byte'ın okunması için beklemez. Okuyabildiği kadar byte'ı okur, okuyabildiği byte sayısı ile geri döner. Eğer
recv sırasında karşı taraf soketi close ile kapatmışsa bu durum recv boş bir byte dizisi verir. Boş bir byte dizisinin
mantıksal olarak False biçimde değerlendirildiğini anımsayınız. recv sırasında karşı taraf soketi kapatmadan bağlantı
koparsa bu durum exception oluşmaktadır.
Bağlantıdan sonra server'ın client'a yazı gönderip client'ın bu yazıyı ekrana bastığı bir soket uygulaması şöyle olabilir:
#[Link]
import socket
PORT = 5050
try:
with [Link]() as serverSock:
[Link](('', PORT))
[Link](8)
#[Link]
import socket
PORT = 5050
try:
166
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
with [Link]() as clientSock:
[Link](('[Link]', PORT))
print('connected')
while True:
b = [Link](1000)
if not b:
break
s = [Link]('UTF-8')
if s == 'quit':
break
print(s)
except [Link] as msg:
print('Socket error:{}'.format(msg))
Yukarıda da belirtildiği gibi client program bind işlemi yaparak kendi kaynak port numarasını belirleyebilir. Eğer böyle
bir belirleme yapılmamışsa kaynak port olarak herhangi boş bir port seçilir. Örneğin:
#[Link]
import socket
DEST_PORT = 5050
SOURCE_PORT = 6890
try:
with [Link]() as clientSock:
[Link](('', SOURCE_PORT))
[Link](('[Link]', DEST_PORT))
print('connected')
while True:
b = [Link](1000)
s = [Link]('UTF-8')
if s == 'quit':
break
print(s)
except [Link] as msg:
print('Socket error:{}'.format(msg))
Çok client'lı server uygulamalarında her client'la bağlantı kurabilmek için server programın yeniden accept
uygulaması gerekir. Yani her accept işlemi yeni bir client ile bağlantı sağlamaktadır. Bu durumda server'ın bir döngü
içerisinde accept uygulaması uygun olur. Ancak aynı anda birden fazla client ile server nasıl konuşacaktır? Bu tür
durumlarda server bir client ile konuşurken diğer client'larla aynı anda konuşamayacağından sorunlar oluşur. İşte
bunun çeşitli modeller kullanılabilmektedir.
Thread modelinde server her client bağlantısı gerçekleştiğinde yeni bir thread açar. O thread'le o client konuşur.
Böylece server bir client ile konuşurken bloke olursa bu işlemden diğer client konuşmaları etkilenmez. Aşağıda örnek
bir thread modelli multi client uygulama verilmiştir. Bu uygulamada client server'a bağlanıp server'a mesajlar
göndermektedir.
#[Link]
import socket
import threading
PORT = 5050
def main():
167
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
try:
with [Link]() as serverSock:
[Link](('', PORT))
[Link](8)
while True:
clientSock, clientAddr = [Link]()
print('connected:{}'.format(clientAddr))
thread = [Link](target=threadProc, args=(clientSock, clientAddr))
[Link]()
main()
#[Link]
import socket
PORT = 5050
def main():
try:
with [Link]() as clientSock:
[Link](('[Link]', PORT))
print('connected...')
while True:
s = input('Yazı giriniz:')
b = [Link]('UTF-8')
[Link](b)
if s == 'quit':
break
[Link](socket.SHUT_RDWR)
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
Şimdi buradaki server'a gelen yazının tersini client'a gönderecek biçimde değiştirelim:
#[Link]
168
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
import socket
import threading
PORT = 5050
def main():
try:
with [Link]() as serverSock:
[Link](('', PORT))
[Link](8)
while True:
clientSock, clientAddr = [Link]()
print('connected:{}'.format(clientAddr))
thread = [Link](target=threadProc, args=(clientSock, clientAddr))
[Link]()
sr = s[::-1]
print(sr)
br = [Link]('UTF-8')
[Link](br)
main()
#[Link]
import socket
PORT = 5050
def main():
try:
with [Link]() as clientSock:
[Link](('[Link]', PORT))
print('connected...')
while True:
s = input('Yazı giriniz:')
b = [Link]('UTF-8')
[Link](b)
if s == 'quit':
169
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
break
br = [Link](1000)
if not br:
break
sr = [Link]('UTF-8')
print('message from server: {}'.format(sr))
[Link](socket.SHUT_RDWR)
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
Bir soket yaratıldıktan sonra artık onunla bir dosyaymış gibi işlem yapabiliriz. Bunun için makefile metodu
kullanılmaktadır. makefile metodunun parametrik yapısı şöyledi:
Metodun birinci parametresi open fonksiyonundaki gibidir. Ancak yalnızca 'r', 'w' ve 'b' modları kullanılabilmektedir.
Bu metot bize bir file nesnesi verir. Böylece biz sanki soketle bir dosyaymıi gibi işlem yaapabiliriz. Örneğin soketten
bir satır okumak için doğrudan dosya nesnesi ile readline metodu çağrılabilir. Oluşturulan bu dosya nesnesi tamponlu
bir biçimde çalışmaktadır. Yani aslında bu nesne soketten daha fazla bilgiyi kendi tamponuna okur ve bize daha önce
okuduğu bilgiyi verir. Benzer durum yazma için de geçerlidir.
Client server programa pek çok şey yaptırabilir. Pekiyi neeyi yaptıracağını client server'a nasıl iletecektir? İşte client
ilşe server arasında bu istekler ve sonuçlar çeşitli mesajlaşmalarla gerçekleştirilir. Mesajlaşmalar text ya da binary
düzeyde yapılabilir. Ancak text tabanlı mesajlaşmalar daha kolay gerçekleştirilirler. Bu nedenle ilk tercih edilecek
mesajlaşma formatı text formattır. Aslında Internet'in uygulama katmanındaki FTP, TELNET, SSH, HTTP, POP3, SMTP
gibi protokoller hep text tabanlı mesajlaşma yapmaktadır. Örneğin dört işlem yapan bir server düşünelim. Client
server'a yaptıraacağı işlemleri birer komut olarak yazısal biçimde oluşturur:
ADD Toplama
SUB Çıkartma
MULTIPLY Çarpma
DIVIDE Bölme
Örneğin:
RESULT: <sonuç>
Örneğin bir char programı nasıl tasarlanabilir. Chat programında client önce server'a TCP protokolü ile bağlanır. Buna
fiziksel bağlantı diyebiliriz. Bundan sonra client server'a username ve password bilgileri gönderir. Server bunu kendi
elindeki username ve password ile karşılaştırır. Eğer bir uyuşma varsa server clien'ı sisteme kabul eder. Buna
mantıksal bağlantı diyebiliriz. Şimdi client artık diğer login olmuş kullanıcıları dağıtılması için server'a bir mesaj
gönderir. Server da tüm client'lara bir mesaj olarak bunu iletir. Böyle bir programda kabaca mesajlar şöyle
düzenlenebilir:
170
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
LOGIN <user name> <password>\n
PUTMSG <message text>\n\n
LOGOUT
OK\n
NEW_MESSAGE <message text>\n\n
ERROR <error message>\n
LOGIN_CLIENT <user name>\n
LOGOUT_CLIENT <client user name>\n
Genel olarak client'ın server'a gönderdiği her mesaj için server'ın bir yanıt vermesi iyi tekniktir. Yukarıdaki örnekte
server işlem olumluysa OK mesajı olumsuzsa ERROR mesajı döndürmektedir.
Şimdi dört işlem yapan bir client-server TCP uygulaması yazalım. Client'tan server'a mesajlar şunlardır:
RESULT <sonuç>
ERROR <metin>
#[Link]
import socket
import threading
PORT = 5050
def main():
try:
with [Link]() as serverSock:
[Link](('', PORT))
[Link](8)
while True:
clientSock, clientAddr = [Link]()
print('Connected Client:{}'.format(clientAddr))
thread = [Link](target=threadProc, args=(clientSock, clientAddr))
[Link]()
171
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
def multiply_proc(fileWrite, param1, param2):
[Link]('RESULT {}\n'.format(param1 * param2))
[Link]()
fileRead = [Link]('r')
fileWrite = [Link]('w')
try:
while True:
line = [Link]()
args = [Link]()
if args[0] == 'quit':
break
proc = [Link](args[0], None)
if not proc:
[Link]('ERROR "invalid operation"\n')
[Link]()
continue
if len(args) != 3:
[Link]('ERROR "two operands must be specified"\n')
[Link]()
continue
try:
param1 = float(args[1])
param2 = float(args[2])
except:
[Link]('ERROR "invalid operand"\n')
continue
proc(fileWrite, param1, param2)
print('Message From Client {}, Command: {}'.format(clientAddr, line))
main()
#[Link]
import socket
PORT = 5050
def main():
try:
with [Link]() as clientSock:
[Link](('[Link]', PORT))
while True:
s = input('CMD>').strip()
if s == '':
continue
[Link](s + '\n')
[Link]()
if s == 'quit':
break
response = [Link]()
print(response, end='')
[Link](socket.SHUT_RDWR)
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
TCP server programın daha zahmetsiz oluşturulması için hazır bazı sınıflar da bulundurulmuştur. Bu sınıflar
socketserver isimli modülün içerisindedir. Aslında bu modülde UDP server için de sınıflar vardır. Ancak biz bu noktada
TCP server sınıfları üzerinde duracağız. Zahmetsiz bir TCP server programı socketserver modülü kullanılarak şöyle
yazılır:
1) Bu modelde aslında server işlevselliğinin önemli bölümünü BaseRequestHandler isimli sınıf yapmaktadır. Yani
listen ve accept dahil olmak üzere tüm işlemler aslında bu sınıf tarafından yapılmaktadır. Bu nedenle ilk iş olarak
BaseRequestedHandler isimli sınıftan bir sınıf türetilir.
2) BaseRequestHandler sınıfından türetilmiş olan sınıfta handle isimli metot override edilir. Bu metot server'a bir
bağlantı isteği geldiğinde taban sınıf tarafından çağrılmaktadır.
class MyTCPServer([Link]):
def handle(self):
pass
3) Server'ı kontrol etmek için bir kontrol nesnesi yaratmamız gerekir. Kontrol nesnesi olarak TCPServer sınıfı ya da
ThreadingTCPServer sınıfı kullanılabilir. Bu sınıfların dunder init metotları bizden iki parametre isterler. Birinci
parametre server'ın dinleme yapacağı network adresi ve port numarasını içeren demettir. İkinci parametre ise
BaseRequestHandler sınıfından türetilen sınıfın ismidir. (Anımsanacağı gibi Python'da sınıf isimleri o sınıf bilgilerinin
bulunduğu type türünden bir sınıf nesnesinin adresini belirtmektedir.)
4) Son olarak bizim artık server programı çalışır duruma getirmemiz gerekmektedir. Bunun için kontrol sınıfının
serve_forever isimli metodu çağrılır. Örneğin:
def main():
try:
tcpServer = [Link](('', PORT), MyTCPServer)
tcpServer.serve_forever()
print('ok')
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
173
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bu durumda server yeni bağlantı olduğunda handler metodunu çağırır. Biz de bu metotta server programın
yapacağını yazarız. Tabii aslında bu haliyle çok fazla bir kolay bize sunulmamaktadır. Zaten Python'da TCP server
programının kendisini yazmak oldukça kolaydır.
Kontrol sınıfı TCPServer sınıfıyla oluşturulursa tek bir client bağlantısı söz konusu olur. Örneğin:
#[Link]
import socket
import socketserver
PORT = 5050
class MyTCPServer([Link]):
def handle(self):
print('new client connected...')
while True:
b = [Link](1000)
if not b:
break
s = [Link]('UTF-8')
if s == 'quit':
break
print(s)
def main():
try:
tcpServer = [Link](('', PORT), MyTCPServer)
print('waiting for connection...')
tcpServer.serve_forever()
print('ok')
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
#[Link]
import socket
PORT = 5050
def main():
try:
with [Link]() as clientSock:
[Link](('[Link]', PORT))
print('connected...')
while True:
s = input('Yazı giriniz:')
b = [Link]('UTF-8')
[Link](b)
if s == "quit":
break
[Link](socket.SHUT_RDWR)
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
174
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Kontrol sınıfı olarak TCPServer kullanılmışsa şu noktalar göz önünde bulundurulmalıdır:
- Bu durumda server aynı anda tek bir client ile konuşacak durumdadır.
- Bir client bağlantısı sonlandığında server yeni bir client ile yeniden bağlanabilir.
Kontrol sınıfları (yani TCPServer veThreadingTCPServer) BaseServer isimli bir sınıftan türetilmiştir. Kontrol sınıfının
bazı faydalı metotları da vardır. Örneğin shutdown ve close metotları server'ı durdurmak için kullanılabilmektedir.
Diğer metotları Python dokümanlarından inceleyebilirsiniz.
socketserver modülüyle biz çok client'lı server uygulamaları da yazabiliriz. Bunun için kontrol sınıfı olarak
ThreadingTCPServer sınıfı kullanılır. Aslında diğer tüm işlemler aynıdır. Bu durumda her handle metodu ayrı bir
thread yaratılarak çağrılmaktadır. Böylece multiclient bağlantı yapılabilmektedir. Örneğin:
#[Link]
import socket
import socketserver
PORT = 5050
class MyTCPServer([Link]):
def handle(self):
print('new client connected...')
while True:
b = [Link](1000)
if not b:
break
s = [Link]('UTF-8')
if s == 'quit':
break
print(s)
def main():
try:
tcpServer = [Link](('', PORT), MyTCPServer)
print('waiting for connection...')
tcpServer.serve_forever()
print('ok')
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
#[Link]
import socket
PORT = 5050
def main():
try:
with [Link]() as clientSock:
[Link](('[Link]', PORT))
print('connected...')
while True:
s = input('Yazı giriniz:')
b = [Link]('UTF-8')
[Link](b)
175
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
if s == "quit":
break
[Link](socket.SHUT_RDWR)
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
Biz yukarıdaki örneklerde taban sınıf olarak BaseRequestHandler sınıfını kullandık. Ayrıca taban sınıf olarak
StreamRequestHandler sınıfı da kullanılabilir. Bu durumda sınıfın rfile ve wfile isimli elemnaları birer dosya nesnesi
durumundadır. Yani başka bir deyişle StreamReuestHandler sınıfı BaseRequestHandler sınıfının makefile yapılmış hali
gibidir. Örneğin:
#[Link]
import socket
import socketserver
PORT = 5050
class MyTCPServer([Link]):
def handle(self):
print('new client connected...')
while True:
b = [Link]()
if not b:
break
s = [Link]('UTF-8')
if s == 'quit':
break
print(s)
def main():
try:
tcpServer = [Link](('', PORT), MyTCPServer)
print('waiting for connection...')
tcpServer.serve_forever()
print('ok')
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
Burada tek farklılık artık request elemanı terine rfile elemanının kullanılmasıdır.
UDP Haberleşmesi
Anımsanacağı gibi UDP bağlantısız datagram tabanlı bir haberleşme sunmaktadır. Client-Server terimleri UDP için
çok anlamlı olmasa da bu protokolde yine istekte bulunulan tarafa server, istek yapana tarafa da client
denilmektedir.
176
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bu durumda örnek bir UDP server programı şöyle yazılabilir:
import socket
PORT = 5051
def main():
try:
with [Link](socket.AF_INET, socket.SOCK_DGRAM) as sock:
[Link](('', PORT))
print('wating for data...')
while True:
b, addr = [Link](1024)
s = [Link]('UTF-8')
if s == 'quit':
break
print('{}: {}'.format(addr, s))
main()
Burada recvfrom metodu iki elemanlı bir demete geri dönmektedir. Demetin birinci elemanı UDP paketini oluşturan
bytes türünden bilgiyi ikinci elemanı ise bilgiyi gönderen client'ın IP adresini ve port numarasını belritmektedir.
UDP Client program yukarıda da belirtildiği gibi bir connect işlemi yapmaz. Doğrudan server'ın IP adresini ve port
numarasını belirterek sendto metoduyla gönderme yapar:
177
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Örnek bir client program da şöyle yazılır:
#[Link]
import socket
PORT = 5051
def main():
try:
with [Link](socket.AF_INET, socket.SOCK_DGRAM) as clientSock:
while True:
s = input('Yazı giriniz:')
b = [Link]('UTF-8')
[Link](b, ('[Link]', PORT))
if s == 'quit':
break
except [Link] as msg:
print('Socket error:{}'.format(msg))
main()
Python'ın standart kütüphanesinde matematiksel istatistiksel işlemleri yapan modüller vardır. Ancak bunların
yeterliliği şüphelidir. Bu nedenle her ne kadar standart kütüphanenin bir parçası olmasa da birtakım kütüphaneler
çok yaygın olarak kullanılmaktadır. Bunların en önemlileri Numpy, SciPy ("saypay" biçiminde okunuyor) ve Pandas
isimli kütüphanelerdir. NumPy en taban kütüphanedir. SciPy Numpy kullanılarak gerçekleştirilmiştir. Benzer biçimde
Pandas da NumPy kütüphanesinin üzerine kurulmuştur. Biz kursumuzun bu bölümünde bu kütüphanelerin
kullanımları üzerinde duracağız. Bu kütüphanelerin hepsi C Programlama Dilinde yazılmıştır. Dolayısıyla çok hızlı olma
iddiasındadır.
NumPy standart kütüphaneye dahil olmadığı için ayrıca kurulması gerekmektedir. Kurulum yine pip kullanılarak basit
biçimde aşağıdaki gibi yapılabilir:
178
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
pip install numpy
PyCharm IDE'sinde projede seçilen "Interpter"a dikkat ediniz. PyCharm "Virtual Environment" adı altında her proje
için default olarak bir Python yorumlayıcısını da bulundurmaktadır. Yine paketler proje özgü olarak bu ortamda
saklanmaktadır. Bu nedenle PyCharm'da ya bu "Project Interpeeter"ı değiştirilmeli ya da mevcut "Project
Interpreter"ınapaket yeniden yüklenmelidir. Örneğin:
Biz Python programlarında numpy kütüphanesini import ederken çoğu kez kolay yazım sağlamak için numpy ismini
aşağıdaki biçimde np olarak kullanacağız:
import numpy as np
NumPy Dokümantasyonu
[Link]
Python'ın temel veri yapıları (listeler, demetler, kümeler, sözlükler) matematiksel ve istatistiksel veri analizi için çok
uygun değildir. Genellikle R, Matlab gibi dillerde olduğu gibi matematiksel ve istatistiksel veri analizinde vektörel
işlemler çok kolaylıklar sağlamaktadır. Oysa Python'ın veri yapılarıyla vektörel işlemler yapılamaz. İşte NumPy'da
ismine "ndarray" denilen vektörel işlemler yapabilen yeni bir veri yapısı oluşturulmuş ve işlemlerde hep bu veri yapısı
kullanılmıştır. Python'ın built-in list veri yapısıyla NumPy'ın ndarray veri yapısı arasındaki farkı basit bir örnekle şöyle
açıklayabiliriz:
>>> a = [1, 2, 3, 4, 5]
>>> b = [10, 20, 30, 40, 50]
>>> c = a + b
>>> c
[1, 2, 3, 4, 5, 10, 20, 30, 40, 50]
>>> import numpy
>>> x = [Link]([1, 2, 3, 4, 5])
>>> y = [Link]([10, 20, 30, 40, 50])
179
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> z = x + y
>>> z
array([11, 22, 33, 44, 55])
Burada önce Python'ın built-in list nesesi ile iki list toplanmıştır. Bildiğiniz gibi biz iki list nesnesini topladığımızda yeni
bir list nesnesi yaratılır. Bu list nesnesi iki nesnenin elemanlarının birleşiminde oluşmaktadır. Oysa NumPy
kütüphanesinin ndarray denilen sınıfı söz konusu olduğunda bu sınıf türünden iki nesneyi topladığımızda karşılıklı
elemanlar toplanmaktadır. İşte bu nedenlerden dolayı NumPy'ın en önemli temel veri yapısı ndarray denilen sınıftır.
Biz önce bu ndarray sınıfını inceleyeceğiz. ndarray sınıfı Numpy kullanıcıları tarafından "numpy array" ya da kısaca
"array" biçiminde de nitelendirilmektedir.
Biz de kursumuzda pek çok programcının yaptığı gibi numpy paketini aşağıdaki gibi bir import direktifi ile np ismiyle
kullanacağız:
import numpy as np
ndarray vektörel bir dizi oluşturmak için kullanılan NumPy paketine özgü temel bir veri yapısıdır. Dolayısla SciPy ve
Pandas kütüphaneleri de hep bu ndarray veri yapısını kullanmaktadır. Bu veri yapısı yukarıda da belirtildiği gibi
ndarray sınıfı ile temsil edilmektedir. Sınıfın pek çok metodu vardır. Sınıftaki operatör metotları doğal bir kullanım
sağlamaktadır.
Bir ndarray nesesini yaratmanın çeşitli yolları vardır. En temel yol array isimli fonksiyonu kullanmaktadır. array
fonkisyonu bizden liste, demet gibi dolaşılabilir bir nesne alıp ondan ndarray nesnesi yaratmaktadır. (Ancak bu
fonksiyonun her türden dolaşılabilir nesneyle kullanılamayacağını da belirtelim. Örneğin:
import numpy as np
a = [Link]([1, 2, 3, 4, 5])
print(a)
print(type(a))
[1 2 3 4 5]
<class '[Link]'>
Numpy dizilerinin köşeli parantezler ile aralarında virgül olmadan print edildiklerine dikkat ediniz. Tabii print edilen
bu yazıyı ndarray sınıfının __str__ metodu oluşturmaktadır. Tabii array fonksiyonuna biz liste yerine bir demet de
verebilirdik:
a = [Link]((1, 2, 3, 4, 5))
Bilindiği gibi Pyton'ın built-in türü olan Int sınırsız uzunlukta bir tamsayı türüdür. Halbuki nümerik uygulamalarda
işlemcinin işleyebildiği uzunlukta veriler işlemciler tarafından çok daha etkin işlemelere sokulabilmektedir. Bu
nedenle ndarray nesnesinde kullanılabilecek özel türler tanımlanmıştır. Bu türler de şüphesiz Python'da aslında bir
sınıf görünümündedir. Bu türlere "dtype" denilmektedir. Her bir ndarray nesnesinin bir dtype türü vardır. Temel
dtype türleri şunlardır:
Numpy dizilerinin her elemanı aynı dtype türünden olmak zorundadır. Bu nedenle Numpy dizilerini Python'ın list
sınıfıyla karıştırmayınız. Yukarıdaki dtype tür isimlerindeki sayılar o türün bit uzunluğuyla ilgilidir. Bir Numpy dizisinin
dtype türü sınıfın dtype isimli örnek özniteliği ile elde edilebilir. Örneğin:
import numpy as np
a = [Link]([1, 2, 3, 4, 5])
print([Link])
print(type([Link]))
int64
<class '[Link]'>
İçerisinde int değerlerin bulunduğu bir liste ya da demetten oluşturulan Numpy dizisinin default olarak dtype
özelliğinin int64 olduğuna dikkat ediniz. Eğer liste ya da demette yalnızca float ya da float ve int karışık olarak
bulunuyorsa bu durumda oluşturulacak Numpy dizisinin dtype özelliği float64 olacaktır. Örneğin:
import numpy as np
float64
float64
Eğer array fonksiyonunda kullanılan liste ya da demetin bazı elemanları string ise tüm elemanlar string'e
dönüştürülür ve Numpy dizisi yazıları tutar hale gelir. Komut satırında oluşturulan aşağıdaki örneğe dikkat ediniz:
Eğer istersek biz array fonksiyonunda dtype isimli argümanını kullanarak dtype türünü istediğimiz gibi belirleyebiliriz.
Örneğin:
181
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
array fonksiyonunda nesnenimn dtype özelliği doğrudan np modülündeki tür isimleriyle oluşturulabileceği gibi yazısal
biçimde de oluşturulabilmektedir. Örneğin:
ndarray yalnızca tek boyutlu değil çok boyutlu dizileri de temsil edebilmektedir. Biz array fonksiyona parametre
olarak liste listesi gibi dolaşılabilir bir nesne verirsek o da bize çok boyutlu bir ndarray nesnesi verir. Örneğin:
Bir ndarray nesnesinin boyutu onun ndim isimli örnek özniteliği ile elde edilebilir. Örneğin:
Aslında array fonksiyonun ndmin isimli bir argümanı da vardır. Biz listeyi tek boyutlu girsek bile bu ndim argümanı ile
boyutu ayarlayabiliriz. Örneğin:
Aslında array fonksiyonun birinci parametresine biz dolaşılabilir bir nesne vermeyebiliriz. Bu duurmda skaler bir
ndarray nesnesi yaratılır. Bu pek kullanılan bir durum değildir. Örneğin:
>>> a = [Link](10)
>>> a
array(10)
>>> print(a)
10
>>> type(a)
<class '[Link]'>
>>> [Link]
0
Bir Numpy dizisinin boyutsaş özellikleri bir demet biçiminde shape isimli örnek özniteliği ile elde edilebilir. Örneğin:
Burada a isimli Numpy dizisi 4 satır, 2 sütundan oluşmaktadır. ndim ile shape örnek özniteliklerinin farklı anlamlara
geldiğine dikkat ediniz.
Biz yukarıda ndarray nesnesi yaratmak için array fonksiyonunu kullandık. Aslında ndarray nesnesi yaratmak için başka
fonksiyonlar da bulunmaktadır.
zeros isimli fonksiyon içi sıfır dolu bir ndarray nesnesi yaratmak için kullanılmaktadır. Fonksiyonun parametrik yapısı
şöyledir:
Fonksiyonun birinci parametresi sıfırlarla dolu olacak ndarray nesnesinin boyutunu belirtir. Boyut tipik olarak bir
demetle ifade edilmektedir. Demetin her elemanı boyut uzunluklarını belirtmektedir. Bu parametre demet yerine int
biçimde de geçilebilir. Bu durumda ndarray nesnesi 1 boyutlu dizi biçiminde olacaktır. Örneğin:
>>> a = [Link](10)
>>> a
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
>>> b = [Link]((3, 2))
>>> b
array([[0., 0.],
[0., 0.],
[0., 0.]])
>>> c = [Link]((10,))
>>> c
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
Fonksiyonun dtype parametresinin default durumda float (yani yaygın platformlarda float64) olduğuna dikkat ediniz.
Tabii istersek dtype türünü değiştirebiliriz. Örneğin:
Fonksiyonun order parametresi pek çok fonksiyonda bulunmaktadır. Bu nesnenin içcel diziliminin Sütun temelli mi
('C') yoksa satır teemelli mi ('R') olduğunu belirtir. Bu konu ileride ele alınacaktır.
ones isimli fonksiyon zeros fonksiyonu gibidir. Fakat diziyi 1'lerle doldurur. Örneğin:
Örneğin:
>>> a = [Link](10)
>>> a
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
full fonksiyonu zeros ve ones gibi doldurma yapmaktadır. Ancak doldurulacak değer fonksiyonda ayrıca belirtilir.
Fonksiyonun parametrik yapısı şöyledir:
183
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[Link](shape, fill_value, dtype=None, order='C')
Örneğin:
Nesnenin dtype özelliği belirtilmezse yine default olarak float64 alınmaktadır. diag isimli fonksiyon diagonaldeki
vektörü oluşturmak için kullanılmaktadır. Parametrik yapısı şöyledir:
[Link](v, k=0)
Fonksiyonun birinci parametresi matrisi ikinci parametresi diyagonal numarası belirtir. Örneğin:
Asıl diyagonalin numarası 0'dır. Yukarıya doğru pozitif, aşağıya doğru negatif numralandırma yapılmaktadır.
arange isimli fonksiyon Python'ın standart range fonksiyonu gibidir. Parametrik yapısı şöyledir:
Örneğin:
>>> a = [Link](10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b = [Link](5, 10)
>>> b
array([5, 6, 7, 8, 9])
>>> c = [Link](10, 20, 2)
>>> c
array([10, 12, 14, 16, 18])
>>> c = [Link](20, 10, -1)
184
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> c
array([20, 19, 18, 17, 16, 15, 14, 13, 12, 11])
arange fonksiyonunda start, stp ve step değerleri float türden olabilmektedir. Örneğin:
Ancak bu biçimde kullanımlar pek yaygın değildir. Çünkü yuvarlama hatalarından dolayı dizide kaç tane elema olacağı
kestirilememektedir.
linspace isimli fonksiyon doğrusal biçimde iki aralıkta eşit aralıklı değerler üretmektedir. Fonksiyonun parametrik
yapısı şöyledir:
Fonksiyonun birinci parametresi başlangıç, ikinci parametresi bitiş, üçüncü parametresi ise kaç tane değer
üretileceğini belirtmektedir. Bitiş değeri dahildir. Örneğin:
Örneğin:
Yine fonksiyondaki default dtype türünğn float64 olduğunu belirtelim. linspace en fazla kullanılan fonksiyonlardan
biridir.
empty isimli fonksiyon ilkdeğer verilmemiş biçimde bir ndarray nesnesi oluşturur. Eğer nesneye daha sonra ilkdeğer
verilecekse ilkdeğer verme zahmeti ortadan kaldırılabilir. Büyük dizilerde bu çok az da olsa bir zaman kazancı
sağlayabilmektedir. Fonksiyonun parametrik yapısı şöyledir:
Yine fonksiyonun birinci parametresi dizinin boyutsal durumunu, ikinci parametresi dtype türüne temsil eder.
Örneğin:
Oluşturulan ndarray nesnesinin içerisindeki değerlerin çöp değerler (garbage values) olduğuna dikkat ediniz. Dizi için
bellekte neresi tahsis edilmişse orada daha önceden bulunan rastgele değerler dizi elemanlarında gözükmektedir.
Aslında mademki ndarray bir sınıftır. Onun da __init__ metodu vardır. İşte ndarray sınıfın __init__ metodu bize
empty fonksiyonundaki gibi çöp değerlerden oluşan ndarray nesnesi verir. ndarray fonksiyonun parametrik yapısı
şöyledir:
185
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
Fonksiyonun ilk iki parametresi empty fonksiyonuyla aynıdır. Diğer parametreler burada ele alınmayacaktır. Örneğin:
Ayrıca bir de ndarray nesnesi yaratan zeros_like, ones_like, full_like ve empty_like isminde like'lı fonksiyonlar vardır.
Bu like'lı fonksiyonlar bir ndarray nesnesini alıp onun boyutlarında ama içerisinde 0'lar (zeros_like), 1'ler (ones_like),
belli değerler (full_like) ve çöp değerler (empty_like) olacak biçimde ndarray yaratırlar. Örneğin:
like'lı fonksiyonların bizden dizinin boyutlarını (shape'ini) almadığına bu boyut bilgisini ona geçirdiğimiz dizilerden
aldığına dikkat ediniz. Bu like'lı fonksiyonlara geçirdiğimiz dizilerin boyut belirtmenin dışında bir amacı yoktur. Başka
bir deyişle örneğin:
>>> b = [Link]([Link])
>>> b
array([[0., 0.],
[0., 0.],
[0., 0.]])
işlemi ile:
>>> b = np.zeros_like(a)
>>> b
array([[0, 0],
[0, 0],
[0, 0]])
186
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
identity isimli fonksiyon birim matris oluşturmak için kullanılmaktadır. Fonksiyonun parametrik yapısı şöyledir:
[Link](n, dtype=None)
Fonksiyonun birinci parametresi birim kare matrisin satır (ya da sütun) sayısını belirtir. İkinci parametres yine dtype
türünü belirtmektedir. Örneğin:
>>> a = [Link](5)
>>> a
array([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
eye fonksiyonu da köşegeni 1 olan bir matris oluşturur. Ancak köşegeni numarayla belirtebiliriz. 0 değeri ana
köşegendir. + değerler yukarı, - değerler aşağı yön belirtir. Örneğin:
Aslınada burada görmedğimiz birkaç tane daha ndarray yaratan fonksiyon vardır. Bunları NumPy dokümanlarından
inceleyebilirisiniz. Son olarak burada gördüğümüz fonksiyonların bir listesini yapalım:
array
zeros
ones
diag
arange
linspace
full
empty
ndarray
zeros_like
ones_like
full_like
empty_like
identity
eye
Nasıl list, tuple, str türleri dilimleniyorsa (slicing) benzer biçimde ndarray nesnesi de indesklenebilir ve
dilimlenembiliri. Örneğin:
187
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> a[7]
80
ndarray değiştirilebilir (mutable) bir sınıftır. Dolayısıyla biz dizi elemanlarını daha sonra değiştirebiliriz. Örneğin:
İndekslemede negatif değerler yine uzunlukla toplama anlamına gelmektedir. (Yani -1'inci indeks son elemanı
belirtmektedir.) Örneğin:
Çok boyutlu ndarray nesnesinin elemanlarına erişirken köşeli parantez içerisinde birden fazla indis belirtilir. (Halbuki
built-in list sınıfında birden fazla köşeli parantezin kullanıldığını anımsayınız.) Örneğin:
Çok boyutlu ndarray nesnelerinde yine negatif indeksler kullanılabilir. Her boyutun negatif indeski yine o boyutun
uzunluğu ile toplanmaktadır.
Örneğin:
>>> a = [Link](10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a[::2]
array([0, 2, 4, 6, 8])
>>> a[::-2]
array([9, 7, 5, 3, 1])
188
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Yine biz dilimleme yoluyla elemanları değiştirebiliriz. Örneğin:
ndarray nesnesine dilimleme yoluyla atama yapılırken atanan dolaşılabilir nesnenin uzunluğunun dilimlemeden elde
edilecek uzunluktan fazla ya da eksik olmaması gerekir. (Halbuki list sınıfında bunun mümkün olduğunu anımsayınız. )
Eğer dilimlemede atanan değer dolaşılabilir bir nesne değilse ya da tek elemanlı dolaşılabilir bir nesne ise bu değer
tüm dilimlenen elemanlara atanmış kabul edilir. Örneğin:
çok boyutlu ndarray nesnesinin dilimlenmesinde her boyut için dilimleme yapılabilir. Örneğin:
189
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> a = [Link]([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> a
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> a[1:, 1:]
array([[5, 6],
[8, 9]])
Çok boyutlu ndarray dizisinin dilimlenmesinden elde edilen ürün tek boyutlu dizi olabilir:
Örneğin:
Dilimleme yoluyla elde edilen ndarray nesnesine skaler bir değer atanabilir. Bu durumda bu değer dilimlenmiş tüm
elemanlara atanmaktadır. Örneğin:
Biz bir ndarray nesnesini dilimleyerek ona bir dolaşılabilir nesne yoluyla atama da yapabiliriz. Ancak bu durumda
atanacak dolaşılabilir nesnenin eleman sayısı atamanın yapıldığı dilimin eleman sayısı ile aynı olmak zorundadır.
Örneğin:
Atama sırasında kaynak nesnenin herhangi bir dolaşılabilir nesne olabileceğine dikkat ediniz. Örneğin:
Dilimlerden Elde Edilen Alt Dizilerin Bir Görüntü (View) Belirtmesi Durumu
Bilindiği gibi listeler dilimlendiğinde aslında yeni bir liste nesnesi yaratılıp eskis nesnedeki adresler bu yeni nesneye
kopyalanıyordu. Biz buna sığ kopyalama (shallow copy) diyorduk. Bu nedenle dilimlemeden sonra değiştirilemez
(immutable) nesnelere atama yapıldığında artık bundan asıl liste etkilenmemektedir. Örneğin:
190
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Halbuki ndarray nesnesinin dilimlenmesinde elde edilen ürün listelerdeki gibi değildir. Asıl dizinin bir görüntüsüdür.
Yani biz dilimleme sonucuyla elde ettiğimiz nesnede değişiklik yaparsak her zaman asıl nesnede de değişiklik yapmış
oluruz. Örneğin:
Burada konuyu şöyle ele alabilirsiniz: Aslında biz ndarray dizisinde dilimleme yaptığımızda asıl dizinin o kısmını temsil
eden bir dizi elde ederiz. Dilimleme sonucunda elde ettiğimiz dizi farklı bir dizi değildir. Asıl dizimin ilgili kısmını temsil
eden bir dizidir. İşte buna asıl dizinin görüntüsü (view) denilmektedir. Başka bir deyişle ndarray sınıfını yazanlar
aslında dilimleme sonucunda elde edilen ndarray nesnesini asıl diziye referans eden elemanlardan oluşturmşlardır.
Biz dilimlenmiş yeni nesne üzerinde işlem yaptığımızda aslında orijinal nesne üzerinde işlem yapılmaktadır.
Dilimlenmiş nesnes yalnızsca asıl nesnenin neresinin dilimlendiği bilgisini tutmaktadır. Aynı durum çok boyutlu
ndarray dizileri için de aynı biçimde söz konusudur. Örneğin:
191
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> a
array([[-1, -1, 3],
[-1, -1, 6],
[ 7, 8, 9]])
>>> b
array([[-1, -1],
[-1, -1]])
Eğer biz dilimlemeden elde edilen ürünün bir görüntü (view) olmasını istemiyorsak global copy fonksiyonuyla ya da
ndarray sınıfının copy metoduyla bilinçli biçimde kopya çıkartmalıyız. Örneğin:
Tabii eğer copy metodunu kullanacaksak artık buna bir argüman vermeyiz. Örneğin:
Bir ndarray nesnesi bir ndarray ya da liste nesneyle indekslenebilir. Ancak bu indekslemeden bir görüntü (view)
nesnesi elde edilmektedir. Örneğin:
Burada da görüldüğü gibi indekslemedne bir görüntü nesnesi elde edilmemiştir. Bu tür indekslemeler gerçekten bir
sığı kopyalamaya yol açmaktadır. Eğer bir ndarray nesnesi Bool türden bir ndarray nesnesi ya da liste nesnesi ile
indekslenirse bu durumda indeks nesnesinde True olan elemanlar elde edilir. Örneğin:
192
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bool indekslemede indeks dizisinin uzunluğunun indekslenecek dizi uzunluğuyla aynı olması gerekir. Örneğin:
Yine Bool türden indekslemelerde elde edilen ürün bir görüntü (view) değildir.
Bir ndarray nesnesinin boyutları global reshape fonksiyonuyla ya da ndarray sınıfının reshape metoduyla
değiştirilebilir. Ancak her türlü boyut değiştirmeler görüntüsel (view) biçimde gerçekleştirilmektedir. reshape
fonksiyonunun parametrik yapısı şöyledir:
Fonksiyonun birinci parametresi diziyi, ikinci parametresi bir demet olarak boyutu (demet yerine skaler kullanılırsa
tek boyut anlaşılmaktadır), üçüncü parametresi ise içsel yerleşimi belirtmektedir. Örmeğin:
>>> a = [Link](12)
>>> b = [Link](a, (4, 3))
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> b
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
>>> b[1, 1] = -1
>>> a
array([ 0, 1, 2, 3, -1, 5, 6, 7, 8, 9, 10, 11])
>>> b
array([[ 0, 1, 2],
[ 3, -1, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
>>> a = [Link](12)
>>> b = [Link]((4, 3))
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> b
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
ndarray sınıfının flatten isimli metodu (bunun karşılığı olarak bir global fonksiyon yoktur) çok boyutlu diziyi
görüntüsel olarak tek boyuta indirger. Örneğin:
Global transpose isimli ve ndarray sınıfının transpose isimli metodu görüntüsel biçimde transpose matrisi verir.
Örneğin:
Global vstack fonksiynu bizden bir demet biçiminde satır vektörlerini (yani 1xn'lik matris) alarak iki boyutlu matris
oluşturmaktadır. Örneğin:
Sütun vektörlerinin bu biçimde oluşturulması size biraz zahmetli gelebilir. Bunu kolaylaştırmak için reshape
fonksiyonunu ya da metodunu uygulayabilirisiniz. Örneğin:
194
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[3]])
>>> y
array([[4],
[5],
[6]])
>>> z
array([[7],
[8],
[9]])
>>> a = [Link]((x, y, z))
>>> a
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
axpand_dims isimli global fonksiyon diziye yeni bir boyut katmak için kullanılabilmektedir. Örneğin:
Buradaki axis parametresi 0 için satır vektörü 1 için sütun vektörü oluşturma anlamındadır.
Global insert isimli fonksiyon ndarray nesnesine insert işlemi yapar. Ancak bu fonksiyon nesnenin kendisine değil yeni
oluşturduğu kopyaya işlemi yapmaktadır. Fonksiyonun parametrik yapısı şöyledir:
Fonksiyonun birinci parametresi inset işlemine konu olan diziyi, ikinci paarametresi insert pozisyonunu üçüncü
parametresi de insert edilecek değeri belirtir. Son parametre insert edilecek boyutu belirtmektedir. Ayrıca sınıfın
insert isimli bir metodu yoktur. insert işlemi yalnızca global insert fonksiyonuyşa yapılmaktadır. Bu metot bize yeni
insert edilmiş yeni bir dizi vermektedir. Bu dizinin default boyutu 1xn biçimindedir. Örneğin:
Fonksiyonun bir görüntü dizisi vermediğine kopyalanmış yeni bir dizi oluşturduğuna dikkat ediniz. Çok boyutlu
dizilere insert işlemi yapılırken default olarak tek boyutlu bir dizi elde edilir. Örneğin:
195
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Fakat istersek axis parametresini kulanabiliriz. Örneğin:
Görüldüğü gibi tamamaen yeni bir sütun oluşturulup insert edilecek değer o sütuna doldurulmuştur.
delete isimli global fonksiyon insert işleminin tersini yapmaktadır. Yani diziden bir değeri silerek yeni bir dizi verir.
Parametrik yapısı şöyledir:
Fonksiyonun birinci parametresi delete işlemine konu olan diziyi, ikinci parametresi delete edilecek indeksi
belirtmektedir. Üçüncü parametre belli bir satırın ya da sütunun delete edilmesi için kullanılır. Bu fonksiyonun da
ndarray metodu olarak bir karşılığı yoktur. Örneğin:
Global append isimli fonksiyon bir dizinin sonuna yeni bir eleman eklemek için kullanılmaktadır. Ancak ekleme
mevcut diziye yapılmaz yeni biri dizi yaratılıp oraya yapılır. Fonksiyon bu yeni diziyle geri dönmektedir. append
fonksiyonun da bir metot karşılığı yoktur. Örneğin:
Fonksiyonun birinci parametresi söz konusu diziyi, ikinci parametresi eklenecek değeri belirtir. Örneğin:
ndarray nesnesinin kendisi üzerinde insert, delete ve append yapılmadığına yeni bir dizi yaratılarak bu işlemlerin
yapılabildiğine dikkat ediniz. Bunun nedeni bir dizinin çeşitli görüntülerinin (view) oluşturulabilmesindendir. Çünkü
asıl dizide değişiklik yapıldığında görüntü dizisinin güncellenmesi algoritmik bir bir sorundur.
Biz şimdiye kadar hep ndarray nesnelerinin oluşturulması ve işlenmesi üzerinde durduk. Aslında ndarray nesneleri
birtakım aritmetik, mantıksal ve fonksiyonel işlemlere sokulmaktadır. Ancak bu işlemler vektörel biçimde yani dizinin
her elemanı karşılıklı olacak biçimde yapılmaktadır. Matlab gibi, R gibi dillerde de temel çalışma biçimi böyledir.
196
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Biz iki ndarray nesnesini aritmetik işlemlere soktuğumuzda dizinin karşılıklı elemanları işleme sokulur ve sonuç bir dizi
biçiminde elde edilir. Örneğin:
Yine +=, -=, *=, /= gibi operatörler asıl nesne üzerinde değişiklik yaparlar. Yani:
a += b
ile
a = a + b
listelerde olduğu gibi tamamen aynı anlama gelmemektedir. a += b işleminde değişiklik mevcut a dizisi üzerinde
yapılmaktadır. Halbuki a = a + b işleminde yeni bir dizi yaratılmakta ve a artık bu yeni diziyi göstermektedir.
Bir dizi ile bir skaler de işleme sokulabilir. Bu durumda skaler dizinin her elemanıyla işleme sokulmuş olur. Bu
işlemlerden ürün olarak yine bir dizi elde edilmektedir. Örneğin:
Örneğin eleimizde çeşitli x değerleri olsun. Biz bu x değerlerini x ** 2 - 3 işlemine sokup y değerlerini elde etmek
isteyelim. Vektörel işlemler bu gibi durumlarda çok pratiklik sağlamaktadır:
Çok boyutlu dizilerde işlem yapılarken yayılma (broadcasting) özelliği vardır. Örneğin bir matris ile onun bir satır ya
da sütunu biçiminde tek boyutlu bir dizi işleme sokulursa işlem matrisin her satırı ya da her sütunu üzerinde
yapılmaktadır. Örneğin:
Burada 3X3'lik bir matris ile 1X3'lük bir dizi işleme sokulmuştur. İşlem matrisin her satırı üzerinde yapılmıştır. Yani bir
deyişle yukarıdaki işlemin eşdeğeri şöyledir:
numpy modülündeki çeşitli fonksiyonlar bizden bir ndarray disizini alıp onun her elemanı üzerinde işlem yapıp ürün
olarak bize yine bir dizi verirler. Örneğin sin, cos, tan, arcsin, arccos, arctan fonksiyonları tribonometrik işlemleri
yapmaktadır. Örneğin:
sqrt, exp, log, log2 ve log10 fonksiyonları da klasik üstel işlemleri yapmaktadır. Örneğin:
>>> a = [Link](10)
>>> b = [Link](a)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b
array([0. , 1. , 1.41421356, 1.73205081, 2. ,
2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])
Bunların dışında daha pek çok vektörel işlem yapan numpy fonksiyonu vardır. Örneğin abs, floor, ceil, round gibi.
Bunları numpy dokümanlarından inceleyebilirsiniz.
Bazı fonksiyonlar bizden bir ndarray nesnesini alıp skaler değer vermektedir. Örneğin sum fonksiyonu ndarray
nesnesindeki değeleri toplayıp bize bunun toplamına ilişkin skaler bir değer verir. Örneğin:
Örneğin biz tek hamlede std ve var fonksiyonlarıyla bir grup sayının standart sapmasını ve varyansını elde edebiliriz:
mean fonksiyonu bir ndarray nesnesinin ortalamasını bize verir. Bu durumda örneğin biz standart sapmayı şöyle de
elde edebilirdik:
prod isimli fonksiyon bir ndarray içerisindeki tüm değerlerin çarpımını bize verir. Örneğin 10 faktöryel değerini
hesaplamak isteyelim:
min ve max fonksiyonları dizinin en küçük ve en büyük elemanlarını bize verir. Örneğin:
Tüm bu fonksiyonlar aslında çok boyutlu dizilerle de işleme sokulabilmektedir. Ancak bunlar çok boyutları dizileri
sanki tek boyutluymuş gibi ele alırlar. Yani biz bir matrisin toplamını da sum fonksiyonuyla elde edebiliriz.
all fonksiyonu bir dizideki tüm elemanlar True ise (hangi değerlerin True olarak ele alındığını biliyorsunuz) any
fonksiyonu herhangi bir değer True ise True değerine değilse False değerine geri döner. Bu fonksiyonların geri dönüş
değeri 0 ya da sıfır dışı bir değer olabilir. Örneğin:
Lineer cebir işlemleri için hem numpy modülündeki hem de [Link] modülündeki fonksiyonlar
kullanılabilmektedir. numpy modülündeki dot isimli fonksiyon matris çarpımı yapar. Yani matrisin karşılıklı
elemanlarını değil matemetikteki matris çarpımını gerçekleştiri. Örneğin aşağıdaki iki matrisi çarpmak isteyelim:
Ters matris almak için linalg modülündeki inv fonksiyonu kullanılmaktadır. Örneğin yukarıdaki matrisin tersini alacak
olalım:
3x1 + x2 - 2x3 = 3
x1 + x2 + x3 = 2
-2x1 + 2x2 - x3 = -7
200
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
İki tarafı ters matris çarparsak çözümü elde deriz. Örneğin:
Görüldüğü gibi çözüm x1 = 2, x2 = -1 ve x3 = 1 biçiminde bulunmuştur. Aslında bu işlemi tek hamlede yapan
[Link] fonksiyonu vardır. Örneğin:
Nümerik analizlerde verilerin kod içerisinde oluşturulmasıyla seyrek karşılaşılmaktadır. Aslında analiz edilecek
edilecek veriler ya dosyalardan okunmakta ya da başka bir ortamdan (örneğin bir veritabanı, web servis, soket gibi)
alınmaktadır. Tabii en yaygın karşılaşılan durum verilerin bir text dosyadan alınmasıdır. Text dosyalarda veriler ya
tablosal biçimde yani satır satır ya da düz biçimde bulunmaktadır. Tablosal biçimde veriler dosyalarda genellikle
aralarına ',' karakteri ya da TAB karakter getirilerek satır satır bulundurulmaktadır. Bilindiği gibi aralarına ',' karakteri
getirilerek tablosal biçimde oluşturulmuş olan dosyalara CSV dosyaları denilmektedir.
Pyton'da normal dosyalardan ve CSV dosyalarından okuma yapmak için çeşitli fonksiyonlar zaten bulunmaktadır.
Örneğin standart kütüphanedi csv modülünde reader isimli fonksiyon bizden bir dolaşılabilir nesneyi alır onu CSV
olarak okur. Her satırı bize bir liste olarak verir. Dosya nesnelerinin de dolaşılabilir nesneler olduğunu anımsayınız.
Örneğin:
import csv
reader fonksiyonun aldığı dolaşılabilir nesne dolaşıldığında bize CSV satırları vermesi gerekmektedir.
Ancak Python'ın standart [Link] fonksiyonu NumPy için kullanışlı değildir. Çünkü biz NumPy'da okuduğumuz
değerleri bir string listesi biçiminde değil bir ndarray nesnesi biçiminde elde etmek isteriz. Bunun için NumPy
modülünde ayrı fonksiyonlar bulundurulmuştur.
[Link] fonksiyonu CSV tarzı text dosyalarını okuyarak bize içeriğini ndarray olarak vermektedir. Fonksiyonun
parametrik yapısı şöyledir:
Default durumda fonksiyon boşluk karakterlerini ve New Line karakterlerini ayıraç olarak almaktadır. Fonksiyon bize
tablosal biçimde verileri bir matris olarak verir. Örneğin:
import numpy as np
result = [Link]('[Link]')
print(result)
10 20 30
40 50 60
Default dtype'ın float64 olduğuna dikkat ediniz. Tabii bu dtype parametresi kullanılarak değiştirilebilir. Örneğin:
import numpy as np
CSV okumaları için delimiter parametresini ',' biçiminde girmek gerekir. (Default durumda yukarıda da belirtildiği gibi
delimeter bir ya da biren fazla boşluk karakterleri biçimindedir.) Örneğin:
import numpy as np
10,20,30,40
50,60,70,80
90,100,110,120
[[ 10 20 30 40]
[ 50 60 70 80]
202
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
[ 90 100 110 120]]
Burada ',' karakterlerinin solunda ve sağında yine istenildiği kadar SPACE ve TAB karakteri bulunabilmektedir.
Örneğin biz iki [Link] ve [Link] dosyalarında bulunan iki matrisi çarpmak isteyelim. İşlemi aşağıdaki gibi yapabiliriz:
import numpy as np
a = [Link]('[Link]')
b = [Link]('[Link]')
c = [Link](a, b)
print(c)
Bazen tablosal bir dosyada yalnızca belirli sütunları elde etmek isteyebiliriz. Bunun için fonksiyonun usecols
parametresi kullanılır. Bu parametre bir demet biçiminde girilmelidir. Örneğin:
import numpy as np
[[10. 20.]
[40. 50.]
[70. 80.]]
genfromtxt fonksiyonu da loadtxt fonksiyonuyla çok benzerdir. Ancak loadtxt fonksiyonundan daha yeteneklidir.
Parametrik yapısı oldukça geniştir:
Örneğin:
import numpy as np
NumPy Paketi sayılsal değerleri üzerinde işlemler yapmaktadır. Ancak matematikte semboli değerleri üzerinde
işlemler de çok sık yapılmaktadır. Yani örneğin biz belli bir aralıkta belirli integrali numpy ve pandas kullanarak
hesaplayabiliriz. Ancak sembolik işlemleri bu paketlerle yapamayız. Sembolik işlemler için SymPy paketi
kullanılmaktadır. SymPy diğer paketlerde olduğu gibi indirilip kurulmaktadır.
[Link]
import sympy as sp
SymPy'da ifadelerin daha matematiksel bir biçimde görüntülenmesi isteniyorsa işin başında bir kez
sympy.init_printing() fonksiyonunun çağrılması gerekir.
SymPy işlemlerinde x, y, z gibi değerlere sembol denilmektedir. Bir sembol herhangi bir değeri alan bir sembolik bir
değişkendir. Semboller birkaç biçimde yaratılabilmektedir. En çok kullanılan yöntem Symbol sınıfının başlangıç
metodu olan Symbol fonksiyonunu kullanmaktır. Bu fonksiyon bizden sembolün ismini string olarak alır ve Symbl
sınıfı türünden bir nesne verir. Örneğin:
>>> x = [Link]('x')
>>> x
x
>>> type(x)
<class '[Link]'>
Bir sembol yaratılırken onun hakkında bazı bilgiler varsa o bilgiler de sembole iliştirilebilir. Sonra istenirse Symbol
sınıfının örnek öznitelikleriyle bu bilgilerin olup olmadığı elde edilebilir. Sembole iliştirilecek bilgiler şunlardır:
İlgili özellik Symbol fonksiyonunda aynı isimli bool parametre ile belirtilmektedir. Örneğin:
204
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> x.is_real
True
Örneğin:
Çok sayıda sembol hızlı bir biçimde yaparılacaksa symbols isimli fonksiyon tercih edilebilir. Bu fonksiyon bizden
sembollerin isimlerini virgüllerle ayrılmış tek bir yazı biçiminde ister. Bu yazıyı parse ederek sembolleri oluşturur ve
onları bize bir demet biçiminde verir. Örneğin:
Burada symbols fonksiyonu x, y, z ve t sembollerini oluşturup bize bunu bir demet biçimde vermiştir. Yani yukarıdaki
işlemin eşdeğeri şöyle yazılabilir:
symbols fonksiyonunda biz yine özellik belirtebiliriz. Ancak bunlar tüm semboller için geçerli olur. Örneğin:
Yalnızca Semboller değil sayıal değerler de SYmPy'da özel sınıflarla temsil edilmektedir. SymPy'ın sayısal sınıfları
Python'ın orijinal sınıflarına benzemekle birlikte sembolik işlemler için özelleştirilmiştir. Integer isimli sınıf tamsayı
değerleir tutar. Yine bu sınıfın da tuttuğu tamsayı değerlerin bir sınırı yoktur. Örneğin:
>>> x = [Link](100)
>>> type(x)
<class '[Link]'>
>>> x
100
Benzer biçimde Float sınıfı da noktalı sayısal değerleri tutmak için düşünülmüştür. Örneğin:
>>> a = [Link](12.3)
>>> type(a)
<class '[Link]'>
>>> a
12.3000000000000
[Link] sınıfı Python'ın standart Float sınıfından farklı olarak değerleri IEEE754 formatına göre tutmaz.
Dolayısıyla biz istediğimiz duyarlılıkta sayıları bu sınıfa tutturabiliriz. Tabii noktalı sayılar Python'da Float türden
olduğuna göre artık bu tür sayıları bizim sınıfa string olarak vermemiz gerekir. Örneğin:
205
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> a = [Link]('12.2345678990034455555555333')
>>> a
12.2345678990034455555555333
Bu biçimde [Link] sınıfnın sayıları yuvarlama hatası olmadan tuttuğuna dikkat ediniz. İstersek biz Float sınıfına ikinci
parametre vererek belli bir duyarlılıkta sayıyı saklamasını siteyebiliriz. Örneğin:
>>> a = [Link]('12.2345678990034455555555333')
>>> b = [Link](a, 10)
>>> a
12.2345678990034455555555333
>>> b
12.23456790
SymPy'da bazı özel değerler sembolik biçimde de kullanılabilmektedir. Örneğin pi sayısı sembolik biçimde [Link]
biçiminde, e sayısı sympy.E biçiminde kullanılabilir. Sonsuz değeri ise semblik olarak [Link] ile kullanılır.
SymPy'daki ifadeler sembollerin, sabit değerlerin operatörlerle birleşiminden elde edilmektedir. Örneğin:
>>> x = [Link]('x')
>>> y = 2 * x ** 2 - 3 * x - 5
>>> y
2*x**2 - 3*x - 5
>>> print(type(y))
<class '[Link]'>
Burada 2x2-3x-5 biçiminde bir ifade oluşturulmuştur. Bu ifadenin türüne bakıldığında ADd sisimli bir sınıf türünde
olduğu görülmektedir. Add sınıfı aslında ifadelerin toplanması ve çıkartılmasını temsil eden bir sınıftır. Örneğin:
>>> y = 2 * x
>>> y
2*x
>>> print(type(y))
<class '[Link]'>
Burada da ifadenin Mul isimli bir sınıfla temsil edildiğini görüyorsunuz. Bir ifadenin terimleri args property'si ile bir
demet biçiminde elde edilebilir. Örneğin:
>>> y = 2 * x
>>> [Link]
(2, x)
Örneğin:
>>> y = 2 * x ** 2 - 3 * x - 5
>>> [Link]
(-5, -3*x, 2*x**2)
>>> [Link][2].args
(2, x**2)
>>> [Link][2].args[1].args
(x, 2)
Bu örneklerden ne anlaşılmaktadır? SymPy'da ifadeler (expressions) bir ağaç oluşturmaktadır. İfadeler içerisindeki
operatörler ve sabitler ayrı birer sınıfla temsil edilmektedir. Bu sınıflar bir ifade ağacı oluşturmaktadır. Örneğin:
Örneğin:
SymPy'da yukarıdaki ifade sınıfları değiştirilemez (immutable) sınıflardır. Yani örneğin bir Add, Mul, Pow nesnesi
yaratıldıktan sonra artık içerik bakımından değiştirilemez.
Bir ifadeyi oluşturan Add, Pow, Mul gibi sınıfların hepsi Aslında Expr isimli bir sınıftan türetilmiştir.
207
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
İfadelerin Sadeleştirilmesi (Simplify)
>>> x = [Link]('x')
>>> e = x ** 2 + x
>>> e
x**2 + x
>>> s = [Link](e)
>>> s
x*(x + 1)
>>> s = [Link]()
>>> s
x*(x + 1)
Örneğin:
>>> e = (x ** 2 - 1) / (x + 1)
>>> [Link]()
x - 1
Örnek:
Açım sadeleştirmenin tersidir. Yani çarpansal ifadeler çarpılarak toplamsal hale getirilir. Bu işlem [Link] isimli
isimli fonksiyonla ya da ifade sınıflarının expand metoduyla yapılmaktadır. Örneğin:
>>> x = [Link]('x')
>>> e = (x + 1) * (x - 3)
208
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> [Link](e)
x**2 - 2*x - 3
expand fonksiyonun çeşitli biçimleri de vardır. Bu biçimler açım işlemini neye göre yapıulacağını belirtir. Normal
expand duruma göre bunlardan birini kullanmaktadır. expand fonksiyonlarının listyesi şöyledir:
expand_log
expand_mul,
expand_multinomial
expand_complex
expand_trig
expand_power_base
expand_power_exp
expand_func
>>> x = [Link]('x')
>>> e = [Link](2 * x)
>>> sp.expand_trig(e)
2*sin(x)*cos(x)
Örneğin:
İfadelerin çarpanlarına ayrılması için [Link] fonksiyonu ya da ifade sınıflarının factor metodu kullanılmaktadır.
Örneğin:
>>> x = [Link]('x')
>>> e = x ** 2 - 1
>>> [Link]()
(x - 1)*(x + 1)
Örneğin:
>>> x = [Link]('x')
>>> e = x ** 3 - 1
>>> [Link]()
(x - 1)*(x**2 + x + 1)
Örneğin:
>>> x = [Link]('x')
>>> e = x **3 + 6 * x ** 2 + 5 * x
>>> [Link]()
x*(x + 1)*(x + 5)
Sympy sembolik bir matematiksel işlem sunmaktadır. Ancak biz istersek belli bir ifadenin değerini elde edebiliriz.
Bunun için ifade sınıflarının subs metodu kullanılmaktadır. subs metodu isim olarak aslında "substitute (yer
değiştirme)" sözcüğünden kısaltma yapılarak uydurulmuştur. subs metodu belli bir sembolü belli bir sembol ya da
değerle yer değiştirmek için kullanılmaktadır. Örneğin:
209
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> x, y = [Link]('x, y')
>>> e = x ** 2 - 1
>>> [Link](x, y)
y**2 - 1
>>> x, y = [Link]('x')
>>> e = x ** 2 - 1
>>> [Link](x, 2)
3
Burada subs metodu ile x yerine 2 yerleştirilmiştir. Pekiyi birden fazla değişken varsa ne olacak? Bu duurmda
seçenklerden biri birden fazla subs metodu uygulamaktır. Örneğin:
Bunun diğer bir yolu subs metoduna ikili elemanlardan oluşan bir dolaşılabilir nesne vermektir. Örneğin:
Diğer bir seçenek ise subs metoduna bir sözlük nesnesi vermektir. Sözlüğün anahtarları sembol isimlerinden değerleri
yer değiştirecek değerlerden oluşmalıdır. Örneğin:
İfadelerin değerlerini elde etmenin diğer bir yolu da ifade sınıflarının evalf metodunu kullanmaktır. Örneğin:
>>> e = [Link] / 2
>>> e
pi/2
>>> [Link]()
1.57079632679490
evalf metodu subs gibi yer değiştirme yapmaz. Yalnızca sembolik birtakım değerleri gerçek değerlerine dönüştürür.
Örneğin [Link] sembolik pi değeridir. Ancak biz evalf ile bunu gerçek bir değere dönüştürebiliriz. evalf metodu
parametre alabilir. Bu durumda metodun parametresi sembolik değer açılımlarının kaç basamak olacağını belirtir.
Örneğin:
>>> e = [Link] / 2
>>> [Link](20)
1.5707963267948966192
[Link] isimli netot bizden bir sembol ve bir ifade ister. Bize bu ifadeyi normak bir Python fonksiyonu olarak
verir. Yani artık biz subs yerine normal fonksiyon çağırma ile değer elde edebiliriz. Örneğin:
210
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> x = [Link]('x')
>>> e = x ** 2 - 2 * x + 1
>>> f = [Link](x, e)
>>> f(10)
81
>>> f(2)
1
Eğer ifadede birden fazla sembol varsa lambdşy metodunda semboller bir demet biçiminde verilmelidir. Örneğin:
Bir ifadenin türevi [Link] isimli fonksiyonla elde edilir. Fonksiyonun birinci parametresi türevi alınacak ifadeyi
ikinci parametresi ise hangi sembole göre türeviv alınacağını belirtmektedir. Örneğin:
>>> x = [Link]('x')
>>> e = x ** 2
>>> [Link](e, x)
2*x
>>> x = [Link]('x')
>>> e = [Link](x)
>>> [Link](e, x)
cos(x)
>>> x = [Link]('x')
>>> f = (1 + (x + x ** 2) ** 3) ** 4
>>> [Link](f, x).subs(x, 1)
104976
>>> 2 ** 4 * 3 ** 8
104976
211
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Çok dereceli türevde diff fonksiyonuna daha fazla argüman girilir. Örneğin x ** 2 fonksiyonunun ikinci türevini alalım:
>>> x = [Link]('x')
>>> f = x ** 2
>>> [Link](f, x, x)
2
İntegral alma işlemi de benzer biçimde integrate isimli fonksiyonla yapılmaktadır. Örneğin:
>>> x = [Link]('x')
>>> f = x ** 2
>>> [Link](f)
x**3/3
Örneğin:
>>> x = [Link]('x')
>>> f = [Link](x)
>>> [Link](f)
-cos(x)
Python'da matematiksel grafikleri çizmek için çeşitli kütüphaneler bulunuyorsa da bunlardan en yaygın
kullanılanlardan biri matplotlib isimli kütüphanedir. matplotlib aslında bir paket görünümündedir. Çizim işlemleri için
bu paketin pyplot denilen modülü kullanılmaktadır. Biz de bu bölümde bu kütüphanenin temel kullanımını ele
alacağız. Tabii matplotlin kütüphanesi de arka planda numpy kütüphanesini kullanmaktadır. Yani genellikle Python
programcıları bu iki kütüphaneyi birlikte kullanırlar.
Matpplotlib standart bir kütüphane olmadığı için onun da kurulumu gerekmektedir. Kurulum pip yoluyla aşağıdaki
gibi yapılabilir:
Benzer biçimde PyCharm IDE'sinde de aynı şlem Setting menüsü kullanılarak yapılabilir.
[Link]
import numpy as np
import [Link] as plt
212
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Figure grafiğin tamamını temsil eder. Eksen ise onun içerisindeki grafin çizildiği asıl alanı belirtmektedir.
O halde bizim grafiği çizmemiz için önce figure ve sonra da en az bir eksen yaratmamız gerekir. Eksen figürün
içerisindedir. Aslında bir figür birden fazla eksen içerebilir.
Pyplot modülünde çizimler tek tek global global fonksiyonlar çağrılarak da sınıfların metotları çağrılarak da
yaılabilmektedir. Başka bir deyişle kütüphane hem prosedürel hem de nesne yönelimli teknikle kullanılabilecek
biçimde tasarlanmıştır. Biz burada dha çok metotlar kullanarak (yani nesne yönelimli biçimde) kütüphaneyi
kullanacağız.
Bir figür nesnesi yaratmak için pyplot modülündeki figure fonksiyonu kullanılır. Örneğin:
Aslında figure fonksiyonun pek çok default değer alan parametresi vardır. Dokümantasyonda figure fonksiyonu şöyle
açıklanmıştır:
[Link]
[Link](num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None,
frameon=True, FigureClass=<class '[Link]'>, clear=False, **kwargs)
Bir eksen Figure sınfının add_axes isimli metodula (tabii aslında global bir fonksion da vardır) figüre eklenmektedir.
Örneğin:
Fonksiyonun rect parametresi feksenin oransal olarak figürün neresinde görüntüleneceğini belirlemekte kullanılır. Bu
parametre dört elemanlı dolaşılabilir bir nesne biçiminde girilmelidir. Bu dört elemanın anlamı şöyledir:
213
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Bu değerler 0 ile 1 arasında oransal değerler olması gerekmektedir. Örneğin left değerinini 0.1 olması demek
eksenin tüm figüre alanının sol tarafından %10'dan itibaren başlaması demektir. Örneğin [0.1, 0.1, 0.8, 0.8]
değerleri şu anlama gelir:
Aslında figür ve ekseni yaratmak tek hamlede pyplot modülünün subPlots metoduyla da yapılmaktadır. Genellikle bu
metot tercih edilmektedir. Fonksiyonun parametrik yapısı şöyledir:
Fonksiyonb bize figüre ve eksenden oluşan bir demet vermektedir. Fonksiyonun ilk iki parametresi olan nrpws ve
ncols matrisel biçimde kaç tane eksen yaratılacağını belirtir. Örneğin:
Asıl çizim işlemi axes nesnesinin plot fonksiyonlarıyla yapılmaktadır. plot fonksiyonun parametrik yapısı şöyledir:
Fonksiyon değişken sayıda dizilim alabilmektedir. Tabii normal olarak x ve y değerleri için iki ayrı parametrenin
girilmesi gerekir. plot fonksiyonu birden fazla kez çağrılırsa aynı eksen üzerinde eklemeler yapar. Örneğin:
import numpy as np
import [Link] as plt
214
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
fig, ax = [Link]()
x = [Link](-6, 6, 200)
y = [Link](x)
[Link](x, y)
x = [Link](-6, 6, 200)
y = x ** 2 / 12
[Link](x, y)
[Link]()
plot fonksiyonun **'lı parametresine dikkat ediniz. Bu fonksiyon parametre isimlerinde olmayan çok sayıda isimli
parametreyi ** parametresiyle kabul etmektedir. Şimdi bu isimli parametrelerin bazılarını görelim:
- linestyle isimli parametre bir string almaktadır. Çizgilerin stilini belirtir. Stiller şunlardır:
linestyle description
Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
x = [Link](-5, 5, 30)
y = x ** 2;
[Link](x, y, linestyle='--')
215
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
y = x
[Link](x, y, linestyle=':')
[Link]()
Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
x = [Link](-5, 5, 100)
y = [Link](x)
[Link](x, y, linewidth=5, color='red')
[Link]()
- marker parametresi grafiği oluşturan noktalar için basılacak küçük şekillerin ne olacağını belirtir. Bunun için
[Link] dokümanlarına bakınız. Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
216
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
x = [Link](-5, 5, 50)
y = [Link](x)
[Link](x, y, linewidth=1, color='red', marker='*')
[Link]()
Grafik çizilirken x ve y eksenlerinin limit değerleri set_xlim ve set_ylim fonksiyonlarıyla ayarlanabilir. Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
ax.set_xlim(-8, 8)
ax.set_ylim(-2, 2)
x = [Link](-5, 5, 50)
y = [Link](x)
[Link](x, y, color='red')
[Link]()
Axes sınıfının set_title isimli metodu grafiğe başlık eklemek için kullanılır. Örneğin:
ax.set_title('Sinüs grafiği')
217
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Yine bu fonksiyonda da ** parametresine dikkat ediniz. Bu parametre sayesinde başlık yazısı çeşitli biçimlerde
özelliklendirilebilir. Benzer biçimde eksenlere isimler vermek de grafiklerin okunabilirliğini artırmaktadır. Bunun için
Axes sınıfının set_xlabel ve set_ylabel metotları kullanılır. Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
[Link]()
import numpy as np
import [Link] as plt
fig, ax = [Link]()
ax.set_xlabel('x')
ax.set_ylabel('y')
x = [Link](-5, 5, 50)
y = [Link](x)
[Link](x, y, color='red')
[Link]()
218
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Eksen alanını grid ile kaplayabiliriz. Bunun için Axes sınıfının grid isimli metodu kullanılmaktadır. Bu metodun
parametrik yapısı şöyledir:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
ax.set_xlabel('x')
ax.set_ylabel('y')
x = [Link](-5, 5, 50)
y = [Link](x)
[Link](x, y, color='red')
[Link]()
219
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Biz burada şimdiye kadar yalnızca plot fonksiyonunu gördük. plot fonksiyonu çizgi grafiği çizmektedir. Halbuki plot
yerine kullanılabilecek başka fonksiyonlar da vardır. Bu tür çizim metotları bazıları şunlardır:
plot
step
bar
hist
errorbar
scatter
fill_between
quiver
pie
Aslında burada belirtilmeyen daha pek çok grafik türü ve o grafiği çizen çizim metotları vardır.
fig, ax = [Link]()
[Link]()
220
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Tabii bar fonksiyonun da pek çok isimli parametresi vardır.
pie metodu pasta dilimi grafiği çizmek için kullanılmaktadır. pie metodunun parametrik yapısı şöyledir:
Metodun x parametresi pasta dilimindeki yüzdeleri belirtir. Metot tüm değerlerin toplamına orantı yaparak pasta
dilimini çizmektedir. labels parametresi pasta diliminin ne anlam ifade ettiğine ilişkin yazılardır. Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
ax.set_title('Öğrenim Durumları')
[Link]([10, 5, 2], labels=['İlkokul', 'Lise', 'Üniversite'])
[Link]()
Fonksiyonun colors parametresi ile biz istediğimiz dilime istediğimiz rengi atayabiliriz. Örneğin:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
ax.set_title('Öğrenim Durumları')
[Link]([10, 5, 2], labels=['İlkokul', 'Lise', 'Üniversite'], colors=['red', 'green', 'blue'])
[Link]()
221
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Histogram çizmek için hist isimli metot kullanılır. Bilindiği gibi histogram sıklık grafiğidir. Biz histograama bir grup veri
veririz. Histogram da bunlara ilişkin bir sıklık grafiği bize çizer. hist fonksiyonunun parametrik yapısı şöyledir:
Burada x tüm değerleri belirtmektedir. bins parametresi aralıkların kaça bölüneceği ile ilgilidir. Burada girilen değer
en büyük ve en küçük x değeri arasındaki farka bölünür ve aralıklar ona göre belirlenir. Örneğin merkezi limit
teoremini ispatlamak için bir histogram çizecek olalım. Merkezi limit teoremine göre ana kütle (population) dağılımı
ne olursa olsun o ana kütleden çekilen örneklemlerin ortalaması normal dağılmaktadır. Örneğin ana kütle 0 ile 100
arasındaki değerlerden oluşsun. Biz de bu ana kütleden 5'lik örneklemler çekelim ve bunların ortalamalarını
hesaplayalım. Örnek bir çözüm şöyle olabilir:
import numpy as np
import [Link] as plt
fig, ax = [Link]()
total = 100000
ssize = 5
222
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Aslında yukarıda da belirtildiği gibi bir figür üzerinde birden fazla eksen oluşturulabilir. Bu eksenlerin her birine farklı
grafikler yerleştirilebilir. Bunun için subplots fonksiyonunda ilk iki parametreye matrisel biçimde eksen sayılarını
girmek gerekir. Örneğin:
fig, ax = [Link](1, 3)
Burada 1X3'lük bir eksen dizisi elde edilecektir. Yani ax burada artık çok boyutlu her elemanı Axes türünden olan bir
ndarray nesnesi belirtmektedir.
fig, ax = [Link](2, 3)
[Link]()
223
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Pandas Kütüphanesinin Kullanımı
Pandas kütüphanesi adeta Python'ı R benzeri bir dil gibi kullanabilme yönünde birtakım özellikler sunmaktadır. Tabii
Pandas da aslında numpy üzerine oturtulmuş yüksek seviyeli bir kütüphanedir. Bu sayede biz sütunsal bilgiler
üzerinde kolaylıkla işlemler yapabilmekteyiz. Pek çok istatistiksel analiz böyle sütunsal bilgiler üzerinde
yürütülmektedir.
Pandas kütüphanesi de ayrıca install edilmelidir. Install işlemi aşağıdaki gibi komut satırından yapılabilir:
[Link]
Burada kütüphanenin tüm dokümantasyonu elde edilebilir. Örneklerimizde aşağıdaki iki import işlemini yapacağız:
import numpy as np
import pandas as pd
Nasıl numpy kütüphanesinin en önemli veri türü ndarray ise Pandas kütüphanesinde de en önemli veri türü Series
isimli türdür. Bir Series nesnesi Series isimli global fonksiyonla oluşturulabilir. Örneğin:
Bir Series nesnesinin iki sütun biçiminde görüntülendiğine dikkat ediniz. Sütunlardan ilki bir indeks belirtmektedir.
Diğer sütun ise gerçek verileri belirtir.
Series nesnesi içeriisndeki index ayrıca elde edilebilir. Bunun için index isimli örnek özniteliği kullanılmaktadır.
Örneğin:
>>> i = [Link]
>>> type(i)
<class '[Link]'>
>>> i
RangeIndex(start=0, stop=5, step=1)
Yine Series içeriisndeki değerler de bağımsız olarak values isimli örnek özniteliği ile elde edilebilir. Örneğin:
>>> v = [Link]
>>> type(v)
<class '[Link]'>
>>> v
array([12, 34, 23, 67, 56], dtype=int64)
Tabii biz bir Series nesnesinin içeriisndeki belli bir indekste bulunan bilgiyi elde edebiliriz. Örneğin:
224
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
>>> s[0]
12
>>> s[1]
34
>>> s[2]
23
İstenirse indeks'ler sayısal olmaktan çıkartılıp yazısal biçime de getirilebilir. Bunun için index özniteliğine str
nesnelerinden oluşan dolaşılabilir bir nesne atamak gerekir. Örneğin:
>>> s['Ali']
12
>>> s['Veli']
34
>>> s[0]
12
>>> s[1]
34
Burada hala bizim sayısal indeksleri kullanabildiğimize dikkat ediniz. Ayrıca bir seriye isimsel indeksler verilmişse biz
sanki o isimleri bir örnek özniteliğiymiş gibi de kullanabiliriz. Örneğin:
Aslında Series fonksiyonunda biz index parametresiyle de indekslemeyi aynı anda yapabiliriz. Örneğin:
Bir seriye isim de verilebilir. Bunun için sınıfın name örnek özniteliği kullanılır. Örneğin:
Bir bir seriyi indeekslerken birden fazla indeks girebiliriz. Genel olarak bireden fazla indeks dolaşılabilir bir nesneyle
ifade edilebilir. Bu durumda alt bir seri elde edilir. Ancak bu seriyle diğeri arasında bir bağlantı yoktur. Tabii yine sığ
kopyalama yapılmaktadır. Örneğin:
Örneğin:
Örneğin:
226
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Data Frame'ler
Data Frame tablosal bir veriyi ifade etmek için kullanılmaktadır. Data frame'ler sütunlardan oluşur. Her sütun farklı
türlerden olabilmektedir. Bir data frame DataFrame fonksiyonuyla (yani bu sınıfın başlangıç metoduyla) iç içe
dolaşılabilir bir nesne verilerek yaratılabilir. (Örneğin liste listesi, liste demeti, demet listesi, demet demeti gibi).
Örneğin:
Burada iki sütundan olşan tablosal bir bilgi vardır. Satırların ve sütunların indekslendiğine dikkat ediniz. Biz istersek
yine index metoduya satırdaki indekslemeyi değiştirebiliriz. Örneğin:
Tabii genellikle satır indeksini değiştirmek yerine sütunlara anlamlı isimler atamak daha çok tercih edilen bir
durumdurdur.
227
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
index ve columns özniteliklerini ayrı ayrı kullanmak yerine DafaFrame fonksiyonunda isimli parametre biçiminde de
kullanabiliriz. Örneğin:
>>> df = [Link]([['M', 28], ['F', 42], ['M', 56], ['M', 62]], index=['Ali', 'Ayşe',
'Selami', 'Fuat'], columns=['Gender', 'Grade'])
>>> df
Gender Grade
Ali M 28
Ayşe F 42
Selami M 56
Fuat M 62
Biz belli bir sütunu data frame içerisinden indeksleme yöntemiyle elde edebiliriz. İndekslemede tipik olarak sütun
isimleri kullanılmaktadır. İndeksleme sonucunda -eğer tek bir sütun belirtilmişse- bize ürün olarak bir Series nesnesi
verilecektir. Örneğin:
>>> df = [Link]([['M', 28], ['F', 42], ['M', 56], ['M', 62]], index=['Ali', 'Ayşe',
'Selami', 'Fuat'], columns=['Gender', 'Grade'])
>>> df
Gender Grade
Ali M 28
Ayşe F 42
Selami M 56
Fuat M 62
>>> s1 = df['Gender']
>>> type(s1)
<class '[Link]'>
>>> s1
Ali M
Ayşe F
Selami M
Fuat M
Name: Gender, dtype: object
>>> s2 = df['Grade']
>>> type(s2)
<class '[Link]'>
>>> s2
Ali 28
Ayşe 42
Selami 56
Fuat 62
Name: Grade, dtype: int64
Aslında sütun indekslemesi yaparken eğer sütunlara isim vermemişsek sütun isimleri yerine doğrudan onların indeks
değerlerini de belirtebiliriz. Ancak sütuna isim verilmişse artık indekslemede sütun numarası kullanılamamaktadır.
Örneğin:
İstersek bir data frame nesnesinin birden fazla sütununu da indeksleyebiliriz. Bu durumda elde edeceğimiz ürün
Series değil DataFrame nesnesi olur. Birden fazla sütunun çekilmesi için köşeli parantez içeisinde dolaşılabilir bir
nesne vermek gerekir (örneğin tipik olarak bir liste). Tabii bu dolaşılabilir nesne eğer sütunlara isim verilmişse sütun
isimlerinden, sütunlara isim verilmemişse sütun indekslerinden oluşacaktır.
228
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
Örneğin:
Bir data frame satırlara göre de indekslenebilir. Yani data frame'den belli satırlar çekilebilir. Bunun için eğer sayısal
temelde (yani index numarası ile) indeksleme yapılacaksa iloc isimli property, eğer isimsel temelde indeksleme
yapılacaksa loc isimli property kullanılmaktadır. (Eskiden ix property'si kullanılıyordu. Ancak pandas'ın ileri
sürümlerinde bu property "deprecated" yapılmıştır.) Örneğin:
Yine birden fazla satırı çektiğimizde bize o satırlar Series olarak değil DataFrame nesnesi olarak verilecektir. Örneğin:
Aslında tıpkı Series nesnesinde olduğu gibi eğer sütunlara isim verilmişse sanki o isimler bir property gibi de
kullanılabilmektedir. Örneğin:
DataFrame sınıfının info isimli metodu bize o data frame ile ilgili ayrıntılı bilgiler vermektedir. Örneğin:
Tıpkı numpy'ın ndarray nesnesinde olduğu gibi Series nesnelerinde ve DataFrame nesnelerinde de belli sütunlar
üzerinde vektörrel işlemler yapılabilmektedir. Örneğin:
Yani Series nesneleri ve DataFrame nesneleri de vektörel işlem yeteneğine sahiptir. Böylece tıpkı ndarray
nesnelerinde olduğu gibi biz bu nesnelerde de Bool türden indeksleme yoluyla belli kayıtları seçebiliriz. Örneğin:
Pandas özellikle istatistiksel amaçla çok yoğun kullanılmaktadır. İstatistiksel veriler de genellikle program içerisinde
elle girilmek yerine bir dosyadan okunurlar. İşte Pandas'ın read_csv isimli fonksiyonu CSV (comma separated vector)
biçimindeki text dosyalarını okuyarak onların içeriğini bize DataFrame nesnesi olarak verir. read_csv fonksiyonun pek
çok default parametresi vardır. Bu default parametreler CSV dosyalarındaki farklılıkları ele almak için
kullanılmaktadır. Standart CSV dosyalarında sütunlar yalnızca ',' karakteri ile ayrılmaktadır. Ancak bazı CSV
dosyalarında ',' karakterinden sonra 'SPAVE karakteri de bulunmaktadır. Örneğin aşağıdaki CSV dosyasının içeriği
("oscar_age_male.csv") aşağıdaki gibidir:
230
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN
"Index", "Year", "Age", "Name", "Movie"
1, 1928, 44, "Emil Jannings", "The Last Command, The Way of All Flesh"
2, 1929, 41, "Warner Baxter", "In Old Arizona"
3, 1930, 62, "George Arliss", "Disraeli"
4, 1931, 53, "Lionel Barrymore", "A Free Soul"
5, 1932, 47, "Wallace Beery", "The Champ"
6, 1933, 35, "Fredric March", "Dr. Jekyll and Mr. Hyde"
7, 1934, 34, "Charles Laughton", "The Private Life of Henry VIII"
....
Burada standart olmayan (yani ',' den sonra SPACE olan) CSV dosyası okunurken skipinitialspace parametresi True
geçilmelidir. Okuma işlemi şöyle yapılabilir:
import pandas as pd
df = pd.read_csv(r'D:\Dropbox\Kurslar\Python-App\Src\Sample\oscar_age_male.csv',
skipinitialspace=True)
print(df[['Age', 'Year']])
Görüldüğü gibi çok büyük dosyalar bu biçimde bir DataFrame olarak elde edilebilmektedir. Artık bundan sonra
programcı bu sütunlar üzerinde çeşitli istatistiksel işlemleri yapabilir. Örneğin Oscar ödülü alanların yaş ortalamasını
şöyle bulabiliriz:
import pandas as pd
df = pd.read_csv(r'D:\Dropbox\Kurslar\Python-App\Src\Sample\oscar_age_male.csv',
skipinitialspace=True)
print(df['Age'].mean())
231
C ve Sistem Programcıları Derneği (CSD) - Kaan ASLAN