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

Bu blogdaki popüler yayınlar

Python Programlama Diline Giriş III

Python Uygulamaları

HTML Giriş