Python Programlama Diline Giriş V
Python
Programlama Diline Giriş V
Merhaba
Arkadaşlar. Bu haftadaki konularımız.
1
Dosya Göstericisinin Konumlandırılması
2
Text ve Binary Dosyalar
3
Text Dosyalarda Karakter Kodlaması (Character Encoding)
4
Dolaşılabilir (Iterable) Sınıfların Oluşturulması
5
Üretici Fonksiyonlar (Generators)
6
Üretici Fonksiyonlara Neden Gereksinim Duyulmaktadır?
7
Üretici İfadeler (Generator Expessions)
8
Lambda İfadeleri (Lambda Expression)
9
Bazı Önemli Built-In Fonksiyonlar
10
filter fonksiyonunu
11
eval Fonksiyonu
12
exec Fonksiyonu
13
Python'da Paketler (Packages)
14
Dokümantasyon Yazıları (Documentation Strings)
Dosya Göstericisinin Konumlandırılması
Dosyanın herhangi bir yerinden okuma ya da yazma yapmak için
öncelikle dosya göstericisi uygun offsete konumlandırılmalıdır. Bunun için seek
metodu kullanılır. Metodun birinci parametresi konumlandırma offset'ini ikinci parametresi
konumlandırma orijinini belirtir. İkinci parametre default değer almıştır. Eğer
girilmezse konumlandırma baştan itibaren yapılır. İkinci parametre için üç
senek söz konusudur:
Konumlandırma Anlamı 0 Konumlandırma dosyanın başından itibaren yapılır. Bu durumda
konumlandırma offset'i 0 ya da pozitif olmak zorundadır. 1 Konumlandırma o andaki dosya göstericisinin konumuna göre
yapılır. Bu durumda birinci parametre negatif, pozitif ya da sıfır olabilir.
Negatif geri, pozitif ileri anlamına gelmektedir. 2 Konumlandırma EOF'tan itibaren yapılır. Bu durumda birinci parametre negatif ya da sıfır girilir.
Dosya göstericisinin baştan itibaren konumu tell metoduyla elde
edilebilir. Örneğin biz dosya göstericisini önce EOF'a konumlandırıp sonra tell
değerini elde edersek dosyanın uzunluğunu elde etmiş oluruz.
Text ve Binary Dosyalar
Dosyaların uzantıları aslında insanlar için düşünülmüştür.
Dosyaların içerisinde ne olursa olsun dosyalar birer byte yığını olarak
düşünülebilirler. İçerisinde yalnızca yazılar bulunan ve bu niyetle
oluşturulmuş olan dosyalara halk arasında "text dosyalar"
denilmektedir. İçerisinde formatlı yazı bulunan ancak bu formatlama bilgisinin
de yazısal olarak kodlandığı dosyalara "rich text dosyalar"
denilmektedir. İçrisinde bağlantı bilgisinin (hyper link) dosyalara ise "hyper
text" dosyalar denir.
Örneğin HTML dosyaları
"hyper text" dosyalardır. Bunların dışında içerisinde saf yazı bulunmayan
dosyalara ise "binary dosyalar" denilmeketdir. Örneğin
".exe" uzantılı çalıştırılabilir dosyalar, ".jpg" uzantılı
resim dosyaları binary dosyalardır.
Dosyalar da içerikleri ne olursa olsun "text modda" ya
da "binary modda" açılabilirler. Tabii text dosyaların text modda
açılması binary dosyaların binary modda açılması normal olan durumdur. open
fonksiyonunda default açış modu text moddur. Binary modda açım için açış moduna
"b" harfi eklenir. Örneğin:
with open('test.txt', 'r+b') as f:
pass
Dosya binary modda açıldığında read ve write metotları artık str
türüyle değil bytes türüyle çalışmaktadır. Yani biz read ile okuma yaptığımızda
read bize bir string değil bytes nesnesi verir. write ile yazma yaparken de
benzer biçimde write parametre olarak bizden bytes nesnesi ister. Örneğin:
Önceki konularda da gördüğümüz gibi biz yazıları bytes nesnesine
dönüştürebiliriz ve buradan elde ettiğimiz değerleri de dosyaya yazabiliriz.
Örneğin:
Text Dosyalarda Karakter Kodlaması (Character Encoding)
Anımsanacağı gibi içerisinde yalnızca yazısal bilgilerin olduğu
dosyalara text dosyalar denilmektedir. Bilindiği gibi bilgisayar belleğinde ya
da dosyaların içerisinde aslında her şey sayısal düzeyde ikilik sistemde
tutulmaktadır. Yani aslında yazı diye bir şey yoktur. Bir yazı (ya da string)
bir sayı dizisidir.
Örneğin bir dosyanın içerisindeki "ankara" yazısı
aslında birtakım sayıların yan yana getirilmesiyle kodlanmaktadır. İşte hangi
sayıların hangi karakterleri temsil ettiğine yönelik çeşitli tablolar
oluşturulmuştur. Bu tabloların ilki ASCII tablosudur. Karakter tabloları bir
karakterin kaç byte yer kapladığına göre üç kısma ayrılmaktadır:
1) Tek
byte'lık karakter tabloları
2) İki
byte'lık karakter tabloları
3) Değişken
uzunluğa sahip karakter tabloları
Tek byte'lık karakter tablolarında her karakter tek byte ile
temsil edilmektedir. Örneğin ASCII tablosu, EBCDIC tablosu böyle tek byte'lık
karakter tablolarıdır. Tek byte'lık karakter tabloları ile en fazla 256 farklı
karakter temsil edilebilir.
Bu da bazı doğal diller için sorun oluşturmaktadır. İki byte'lık
karakter tablolarının en yaygın olanı UNICODE tablodur. UNICODE tabloda tüm
ulusların karakterleri ve değişik pek çok karakterler aynı tabloya
yerleştirilmiştir. Bazı karakter tablolarında ise her karakter eşit yer
kaplamaz. Bazı karakterler 1 byte ile bazıları 2 byte ile bazıları daha çok
byte ile kodlanmışlardır. Örneğin UTF-8 denilen kodlama aslında UNICODE
tablonun sıkıştırılmış bir biçimidir. UTF-8 bu nedenden dolayı çok
kullanılmaktadır.
Aslında ASCII tablosunun orijinali bir byte değil 7 bitti.
Dolayısıyla orijinal ASCII tablosu 128 karakter içeriyordu. Ancak sonraları
tablo 8 bite tamamlanmıştır. Yani tabloya 128 karakter daha eklenmiştir. Ancak
bu eklemeler farklı uluslar tarafından farklı biçimde yapılmıştır. İşte ASCII
tablosunun bu farklı varyasyonlarına "code page" denilmektedir. ASCII
Latin-1 code page'i tipik Avrupa ülkerinin kullandığı code
page'tir. Bu code page'te Türkçe karakterler yoktur. Türkçe karakterlerin
bulunduğu farklı code page'ler tanımlanmıştır. Örneğin Windows 1254, ISO
8859-9, OEM Türkçe code page'leri Türkçe karakterleri barındırmaktadır.
Dolaşılabilir (Iterable) Sınıfların Oluşturulması
Anımsanacağı gibi Python'da bir sınıf nesnesinin for döngüsüyle
dolaşılabilmesi ya da birtakım fonksiyonlara parametre yoluyla geçirilebilmesi
için onun dolaşılabilir (iterable) olması gerekiyordu. Örneğin list, set, dict,
tuple, str, range sınıfları dolaşılabilir sınıflardır. İşte biz de istersek
kendi sınıfımızı dolaşılabilir yapabiliriz. Tabii dolaşılabilir sınıfların bir
biçimde bir "collection" sınıf gibi davranması beklenir. Yani bu
sınıflar dolaşıldıkça bize birtakım değerler vermelidir.
Yani dolaşım için aslında bizim iki nesneye gereksinimimiz vardır:
Dolaşılabilir bir neneye ve dolaşım nesnesine. Tabii dolaşılabilir sınıfla
dolaşım sınıfı aynı da olabilir. Bu da dolaşılabilir nesneyle dolaşım
nesnesinin aynı olabileceği anlamına gelir. Örneğin:
Üretici Fonksiyonlar (Generators)
Üretici fonksiyon (generator) kavramı pek az dilde vardır. C, C++,
Java, C# gibi statik tür sistemine sahip dillerde yoktur. Üretici fonksiyonlar
normal birer fonksiyon gibi tanımlanırlar. Fonksiyonun içerisinde en az bir
yield deyimi kullanılmışsa bu fonksiyon üretici bir fonksiyon olur. yield
deyiminin genel biçimi şöyledir:
yield
<ifade>
Üretici bir fonksiyon aynı zamanda dolaşılabilir bir nesne
oluşturmaktadır. Üretici fonksiyon çağrıldığında geri dönüş değeri olarak bir
nesne elde edilir. Bu nesne "generator" isimli dolaşılabilir bir
sınıf türündendir. Yani üretici fonksiyonun çağrılmasından elde edilen nesnenin
__iter__ metodu vardır.
Bu __iter__ metodu da bize
dolaşım (iterator) nesnesini verir. Dolaşım nesnesine ilişkin sınıfın __next__
metotları da ilerlemeyi sağlamaktadır. Üretici fonksiyonun verdiği nesne
dolaşılırken her __next__ çağrısında akış fonksiyonda ilk yield deyimi görülene
kadar ilerler ve yield deyiminde geçici süre durdurulur. Böylece her __next__
çağrısı sonraki yield deyimine kadar çalışmayı sağlamaktadır. __next__ metodu
yield anahtar sözcüğünün yanındaki ifadenin değerine geri döner.
Üretici fonksiyon bittiğinde (bu bitiş akışın fonksiyonu
bitirmesiyle de olabilir, return deyimiyle de olabilir) bu durum StopIteration exception'ının
oluşmasına yol açmaktadır. Örneğin:
Üretici fonksiyonun çalışmasını for döngüsü temelinde özet olarak
ele alalım: Bir üretici fonksiyon for döngüsüyle dolaşılışırken bu döngünün her
yinelenmesinde yield deyiminde belirtilen değer döngü değişkenine atanmaktadır.
For döngüsünün her yinelenmesinde fonksiyon kalınan yerden sonraki yield
deyimine kadar çalışmaya devam ettirilir. yield deyimindeki değer döngü
değişkenine atanarak o noktada geçici süre durdurulur ve döngüsünün
içerisindeki deyimler çalıştırılır. En sonunda fonksiyon bittiğinde döngü de
biter.
Üretici Fonksiyonlara Neden Gereksinim Duyulmaktadır?
Üretici fonksiyon mekanizması fonksiyonu durdurup bir sonuç elde
edip onun kalınan yerden çalışmasına devam etmesini sağlayan bir mekanizmadır.
Dolayısıyla elde edilen değerler bir hamlede değil adım adım elde edilmektedir.
Bu da bellek kullanımı bakımından ve organizasyon bakımından faydalar
sağlamaktadır.
Örneğin os.walk fonksiyonu tüm ağacı dolaşıp ağacı bize bir liste
olarak verseydi bunun ne dezavantajı olurdu? Birincisi bu kadar dosyanın yol ifadelerinin
yerleştirileceği liste çok büyük olurdu. (Belki de programcı bunun içerisinde
bir dosyayı aramaktadır.
Bu dosyayı bulunca işleme devam etmek istemeyebilir. Bu basit
işlem için bile bütün ağacın bir listeye çekilmesi verimsiz bir yöntemdir.)
İkincisi dizin ağacının dolaşılması uzun bir zaman alırdı. Bu işlem
istenilidiği zaman da kesilemezdi. Halbuki üretici fonksiyonlar sayesinde hem
önceden bir bellek tahsisatının yapılmasına gerek kalmamaktadır hem de bu işlem
parça parça yapılabilmektedir. Üstelik de işlem folaşım sırasında istenildiği
zaman kesilebilmektedir.
Pekiyi C, C++, Java ve C# gibi derleyici temelli dillerde üretici
fonksiyonlar dile dahil edilemez miydi? Derleyici temelli dillerde üretici
fonksiyonların derleyici tarafından gerçekleştirilmesi oldukça zordur.
Python'ın bir yorumlayıcı biçiminde çalışması bu gerçekleştirimin yapılmasını
kolaylaştırmaktadır. Diğer dillerde daha çok bu tür işlemler "geri çağrılan
fonksiyonlar (callback functions)" yoluyla yapılmaktadır.
Şüphesiz üretici fonksiyonlar ile yapılan bazı işlemler
dolaşılabilir sınıflar yoluyla da yapılabilmektedir. Örneğin range işlemini
yapan mekanizma hep dolaşılabilir bir sınıf yoluyla hem de üretici fonksiyon
yoluyla oluşturulabilir. Şüphesiz üretici fonksiyon yazmak daha pratilktir.
Ancak dolaşılabilir sınıflar neticde sınıf oldukları için soyutlamaya daha elverişlidir.
Dolaşılabilir sınıfların akışı durdurup yeniden kalınan yerden çalıştırmadığına
dikkat ediniz. Dolaşılabilir sınıflarda durum bilgisi sınıfın örnek
özniteliklerinde saklanmaktadır.
Halbuki üretici fonksiyonlarda durum bilgisi doğrudan fonksiyonun
yerel değişkenlerinde saklanır. Örneğin üretici fonksiyonlar bir iç fonksiyon
durumunda olabilir ve dış fonksiyonun yerel değişkenlerini kullanabilir.
Dolaşılabilir sınıflar genel olarak üretici fonksiyonlardan daha hızlı çalışma
eğilimindedir. Yorumlayıcının üretici fonksiyonu durdurup yeniden çalıştırması
daha fazla bilgisayar zamanına yol açmaktadır.
Üretici İfadeler (Generator Expessions)
İçlemler konsusunda liste içlemlerinin, küme ve sözlük içlemlerinin
nasıl oluşturulduğunu görmüştük. O bölümde demetler için bir içlem
mekanizmasının olmadığına dikkat etmişsinizdir. Aslında sentaksı demet içlemi
gibi olan fakat içlem değil üretici fonksiyonlar biçiminde işlev gören bir
ifade türü bulunmaktadır. Bunlara "üretici ifadeler (generetaor
expressions)" denilmektedir.
Üretici ifadeler tamamen bir demet içlemi gibi kullanılmaktadır.
Ancak elde edilen ürün bir demet değil üretici bir fonksiyondur. Üretici
ifadelerin genel biçimi şöyledir:
(<içlem sentaksı>)
Üretici
ifadelerden bir üretici nesnesi (generator) elde edilmektedir. Bu üretici
nesnesi dolaşılabilir bir nesnedir. Bu nesne dolaşıldığında içlem sentaksındaki
sıradaki ifadenin değeri elde edilmektedir.
Örneğin:
Görüldüğü gibi burada bir demet içlemi değil üretici bir ifade
oluşturulmuştur. Üretici ifadelerden elde edilen üretici dolaşıldığında her
dolaşımda önce for döngüsü çalıştrılır, varsa if koşulu dikkate alınır ve
soldaki ifadenin değeri hesaplanır. Dolaşımdan bu değer elde edilmektedir.
Yukarıdaki kodun işlevsel eşdeğeri aşağıdaki gibidir:
Örneğin:
Lambda İfadeleri (Lambda Expression)
Bazen bazı küçük fonksiyonların yazılarak hemen işleme sokulması
gerekebilmektedir. Bu tür durumlarda Python'da önce def deyimi ile fonksiyonu
başka yerde oluşturup kullanmak gerekir. İşte lambda ifadeleri bir fonksiyonu
bir ifadenin bir parçası olarak hem tanımlayıp hem de kullanma kolaylığı
sağlamaktadır.
Gerçekten de fonksiyonların
birer ifade gibi hemen tanımlanıp işleme sokulması artık pek çok dile
sokulmuştur. Örneğin C++ 2011 versiyonuyla "lambda ifadelerine" sahip
olmuştur. Benzer biçimde bu mekanizma Java ve C# gibi dillere de sokulmuş
durumdadır.
Lambda
ifadelerinin genel biçimi şöyledir:
lambda [parametre listesi] : <ifade>
Lambda ifadeleri taamamen bir fonksiyon gibidir. Bunların normal
fonksiyonlardan tek farkı bir deyim gibi değil bir ifade gibi işleme
sokulmasıdır. Anımsanacağı gibi def anahtar sözcüğüyle bir fonksiyon
bildirildiğinde fonksiyonun ismi zaten bu bildirimde yer almaktadır. Halbuki
lambda ifadelerinde fonksiyonun bir ismi yoktur. Tabii lambda ifadesi bir
değişkene de atanabilir. Bu durumda bunun def ile tanımlanan bir fonksiyondan
farkı kalmaz. Örneğin:
lambda ifadelerinde return anahtar sözcüğü kullanılmaz. Zaten ':'
atomunun yanındaki ifade return edilecek ifadeyi belirtir. Yukarıdaki örnekte x
ve y fonksiyonunun parametreleri, x + y ise geri dönüş değeridir. Bunun eşdeğri
şöyledir:
def foo(x, y):
return x + y
result = foo(10, 20)
print(type(foo))
print(result)
Bazı Önemli Built-In Fonksiyonlar
Bu bölümde
şimdiye kadar görülmemiş olan bazı önemli built-in fonksiyonlar ele alalım.
filter
fonksiyonunu: Bu fonksiyon bir fonksiyon ve
bir de dolaşılabilir nesneyi parametre olarak alır. Dolaşılabilir nesnenin her
elemanını fonksiyona sokar. Fonksiyondan True ile geri döndürülen elemanları
biriktirerek dolaşılabilir bir nesne biçiminde geri döndürülür. Örneğin:
eval
Fonksiyonu: eval Fonksiyonu bizden str
türünden bir parametre ister. Bizden aldığı yazıyı bir Python ifadesi olarak
yorumlar ve bunun sonucunu geri dönüş değeri biçiminde verir. Örneğin:
exec Fonksiyonu: Bu fonksiyon
bizden bir Python kodunu alır ve onu çalıştırır. eval fonksiyonundan farkı tek
bir ifadeyi değil bir kod bloğunu çalıştırabilmesidir. Örneğin:
Python'da Paketler (Packages)
Bir grup modülü (yani Python dosyasını) barındıran dizinlere
Python'da paket (package) denilmektedir. Modüller (yani Python dosyaları)
dosyalara benzetilirse paketler de dizinlere benzetilebilir. Bir paket kendi
içerisinde başka paketleri ve modülleri içerebilmektedir. Paketler de yine
import deyimi ile yüklenirler. Onlar da birer modül statüsündedir.
Dokümantasyon Yazıları (Documentation Strings)
Python dünyasına adım atanların önemlş bir bölümü modüllerin,
fonksiyonların ve sınıfların yetersiz biçimde dokümanta edildiğini
düşünmektedir. Maalesef dokümantasyon zaafı bu programlama dilinin neredeyse
bir özelliği haline gelmiştir.
Python'da en önemli dokümantasyon unsurlarından biri
dokyonümantasyon yazılarıdır. Dokümantasyon yazıları modül, fonksiyon, metot
gibi öğelerden hemen sonra yerleştirilen bir string ile oluşturulmaktadır.
Örneğin:
def square(a):
return a
* a
Yorumlar
Yorum Gönder