diff --git a/.DS_Store b/.DS_Store index 5008ddf..79809da 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/C++-OzetNotlar-Ornekler.txt b/C++-OzetNotlar-Ornekler.txt index 9df5ab0..e96b921 100644 --- a/C++-OzetNotlar-Ornekler.txt +++ b/C++-OzetNotlar-Ornekler.txt @@ -1333,7 +1333,6 @@ int main() value)" ve "rvalue" biçiminde iki üst kategori de eklenmiştir. glvalue kategorisi lvalue ve xvalue kategorilerinin birleşiminden oluşmaktadır. rvalue kategorisi ise prvalue ve xvalue kategorilerinin birleşiminden oluşmaktdır. Bu durum aşağıdaki şekil ile özetlenebilir: - glvalue rvalue glvalue rvalue / \ / \ / \ / \ @@ -4274,7 +4273,36 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 33) C++'a C++11 ile birlikte decltype isimli bir "tür belirleyicisi (type specifier)" eklenmiştir. decltype belirleyicisi parantez içerisinde bir ifade ile + 33) C'de önek ++, önek --, =, işlemli atama operatörleri, koşul operatörü sağ taraf değeri üretmektedir. Ancak C++'ta başından beri bu operatörler sol taraf + değeri üretmektedir. Örneğin aşağıdaki ifade C'de geçersiz olduğu halde C++'ta geçerlidir: + + (a = 10) = 20; // C'de geçersiz C++'ta geçerli + + C'de a = 10 işleminden elde edilen ürün bir sağ rataf değeri olduğu için ona bir değer atanamaz. Ancak C++'ta atama operatöründen elde edilen ürün atanan + nesnenin kendisidir. Dolayısıyla yukarıdaki örnek C++'ta geçerlidir. Bu durumda a'ya önce 10 atanacak sonra 20 atanacaktır. Benzer biçimde aşağıdaki ifade de + C'de geçersiz olduğu halde C++'ta geçerlidir: + + ++a = 10; // C'de geçersiz C++'ta geçerli + + Ancak C++'ta da sonek ++ ve -- operatörleri sol taraf değeri üretmemektedir. Örneğin: + + a++ = 10; // C'de de C++'ta da geçersiz! + + Benzer biçimde aşağıda ifade de C'de geçersiz olduğu halde C++'ta geçerlidir: + + foo() ? a : b = 10; // C'de geçersiz C++'ta geçerli + + Normalde koşul operatörünün operand'ları farklı türlerdense işlem öncesi otomatik tür dönüştürmesi yoluyla aynı türe dönüştürülmektedir. Ancak operatörün + ikinci ve üçüncü operand'ları nesne belirtiyorsa bunların aynı türdne olması gerekmemktedir. Dolayısıyla yularıdaki işlemin eşdeğeri şöyledir: + + if (foo()) + a = 10; + else + b = 10; +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 34) C++'a C++11 ile birlikte decltype isimli bir "tür belirleyicisi (type specifier)" eklenmiştir. decltype belirleyicisi parantez içerisinde bir ifade ile kullanılmaktadır. Genel biçimi şöyledir: decltype() @@ -4467,7 +4495,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 34) C++11 ile birlikta C++'a NULL adres sabitini temsil eden nullptr anahtar sözcüğü eklenmiştir. Anımsanacağı gibi C'de NULL adres sabiti iki biçimde + 35) C++11 ile birlikta C++'a NULL adres sabitini temsil eden nullptr anahtar sözcüğü eklenmiştir. Anımsanacağı gibi C'de NULL adres sabiti iki biçimde belirtiliyordu: 1) 0 değerini veren tamsayı türlerine ilişkin sabit ifadeleri @@ -4533,7 +4561,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 35) typedef anahtar sözcüğü bir bildirime eklenebilir ve bildirimdeki değişkeni o değişkenin türüne ilişkin tür ismi haline getirir. C++11 ile birlikte bazı + 36) typedef anahtar sözcüğü bir bildirime eklenebilir ve bildirimdeki değişkeni o değişkenin türüne ilişkin tür ismi haline getirir. C++11 ile birlikte bazı nedenlerden dolayı alternatif bir typedef mekanizması da dile eklenmiştir. Buna "type alias" da denilmektedir. Bu alternatif typedef mekanizmasının genel biçimi şöyledir: @@ -4620,7 +4648,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 36) C++'ta parametre değişkenleri default değer alabilmektedir. Fonksiyon çağrılırken default değer alan parametre değişkenleri için argüman girilmeyebilir. + 37) C++'ta parametre değişkenleri default değer alabilmektedir. Fonksiyon çağrılırken default değer alan parametre değişkenleri için argüman girilmeyebilir. Bu durumda sanki argüman olarak o default değerlerin girilmiş olduğu kabul edilir. Eğer default değer alan parametre değişkenleri için argüman girilmişse bu durumda bu default değerler dikkate alınmaz. Örneğin: @@ -4928,7 +4956,7 @@ int main() } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 37) Anımsanacağı gibi C'de statik ömürlü (yani global ve statik yerel) nesnelere verilen ilkdeğerlerin sabit ifadesi olması zorunludur. Örneğin aşağıdaki gibi + 38) Anımsanacağı gibi C'de statik ömürlü (yani global ve statik yerel) nesnelere verilen ilkdeğerlerin sabit ifadesi olması zorunludur. Örneğin aşağıdaki gibi bir global değişken tanımalması C'de geçerli değildir: int square(int a) @@ -5024,7 +5052,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 38) C'de fonksiyon tanımlamada parametre değişkenlerine isim verilmesi zorunludur. Ancak C++'ta böyle bir zorunluluk yoktur. Örneğin: + 39) C'de fonksiyon tanımlamada parametre değişkenlerine isim verilmesi zorunludur. Ancak C++'ta böyle bir zorunluluk yoktur. Örneğin: void foo(int) // C'de geçersiz, C++'ta geçerli { @@ -5071,7 +5099,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 39) C++'ta parametrik yapıları farklı olmak koşuluyla aynı faaliyet alanında aynı isimli birden fazla fonksiyon bulunabilir. Bu duruma İngilizce "function + 40) C++'ta parametrik yapıları farklı olmak koşuluyla aynı faaliyet alanında aynı isimli birden fazla fonksiyon bulunabilir. Bu duruma İngilizce "function overloading" denilmektedir. Halbuki C'de hiçbir durumda aynı isimli birden fazla fonksiyon bulunamamaktadır. Parametrik yapıların farklı olması demek parametrelerin türce veya sayıca farklı olması demektir. Parametre değişkenlerinin isimlerinin ve fonksiyonların geri dönüş değerlerinin türlerinin bu bağlamda bir önemi yoktur. Önemli olan parametre değişkenlerinin türlerinin ya da sayılarının farklı olmasıdır. Örneğin aşağıdaki foo fonksiyonları C++'ta birlikte bulunabilir: @@ -6021,7 +6049,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 40) C++'ta global "isim kirliliğini (name pollution)" engellemek için "isim alanları (namespaces)" denilen C'de olmayan bir özellik bulunmaktadır. Farklı firma + 41) C++'ta global "isim kirliliğini (name pollution)" engellemek için "isim alanları (namespaces)" denilen C'de olmayan bir özellik bulunmaktadır. Farklı firma ya da kurumların kütüphanelerinin bir arada kullanıldığı projelerde "isim çakışmaları (name collisions)" oluşabilmektedir. Örneğin A firmasının kütüphanesi ile B firmasının kütüphanesini birlikte kullanırken bu iki firma tesadüfen bir yapı ya da sınıfa aynı isimleri vermiş olabilirler. Bu önemli bir problemdir. Biz her iki firmanın kütüphanelerini kullanmak için onların başlık dosyalarını include ettiğimizde isim çalışması nedeniyle error'ler oluşacaktır. İşte isim @@ -6191,7 +6219,6 @@ int main() } } } - --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #include @@ -6974,7 +7001,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 41) Gereksiz niteliklendirmeyi elimine edebilmek için "using namespace direktifi" denilen bir direktiften faydalanılmaktadır. using namespace direktifinin + 42) Gereksiz niteliklendirmeyi elimine edebilmek için "using namespace direktifi" denilen bir direktiften faydalanılmaktadır. using namespace direktifinin genel biçimi şöyledir: using namespace ; @@ -7530,7 +7557,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 42) using namespace direktifinin dışında C++'ta ayrıca bir de "using bildirimi" denilen bir bildirim de vardır. using bildirimi bir isim alanındaki ismi + 43) using namespace direktifinin dışında C++'ta ayrıca bir de "using bildirimi" denilen bir bildirim de vardır. using bildirimi bir isim alanındaki ismi bir faaliyet alanına sokmak için kullanılmaktadır. Bu nedenle bu bir direktif değil bildirimdir. Çünkü bildirimlerde yeni bir isim bir faaliyet alanına katılmaktadır. using bildiriminin genel biçimi şöyledir: @@ -7634,7 +7661,7 @@ int main() } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 43) İsim alanlarına ilişkin diğer bir bildirim de "namespace alias" denilen bildirimdir. Bu bildirim bir isim alanı ismini daha kolay kullanmak için düşünülmüştür. + 44) İsim alanlarına ilişkin diğer bir bildirim de "namespace alias" denilen bildirimdir. Bu bildirim bir isim alanı ismini daha kolay kullanmak için düşünülmüştür. Örneğin isim alanı ismi uzun olabilir. Biz bunu kısa bir biçimde kullanmak isteyebiliriz. Ya da örneğin iç bir isim alanınını tek bir isim iel kullanmak isteyebiliriz. bildirimin genel biçimi şöyledir: @@ -7662,7 +7689,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 44) C++11 ile birlikte diğer bazı dillerde çeşitli biçimlerde bulunan "öznitelikler (attributes)" konusu dile eklenmiştir. Öznitelikler derleyici için + 45) C++11 ile birlikte diğer bazı dillerde çeşitli biçimlerde bulunan "öznitelikler (attributes)" konusu dile eklenmiştir. Öznitelikler derleyici için kullanılan direktiflerdir. Özniteliklerin temel amacı derleyicinin daha iyi kod üretmesini sağlamak, uyarı mekanizması üzerinde etkili olmak ve derleyicinin bazı davranışlarını değiştirmektir. @@ -8000,7 +8027,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 45) C++'ın çeşitli versyonlarında deyimlerde de bazı eklemeler yapılmıştır. + 46) C++'ın çeşitli versyonlarında deyimlerde de bazı eklemeler yapılmıştır. C++17 ile birlikte if deyimine isteğe bağlı bir "init kısmı" eklenmiştir. Bu init kısmı bir ifade içerir. İfadeden sonra ';' atomunun bulunması gerekir. Yukarıda da belirttiğimiz gibi bu "init" kısmı deyidme bulundurulmak zorunda değildir. Dolayısıyla deyim eski biçimiyle uyumludur. Örneğin: @@ -8048,7 +8075,7 @@ int main() C++20 ile birlikte Aralık tabanlı for döngülerine de "init" kısmının eklendiğini aralık tabanlı for döngülerini anlattığımız kısımda belirtmiştik. /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 46) C++17 ile birlikte "constexpr if" C++23 ile de "consteval if" deyimi biçiminde yeni iki if deyimi varyasyonu eklenmiştir. constexpr if deyiminin genel biçimi + 47) C++17 ile birlikte "constexpr if" C++23 ile de "consteval if" deyimi biçiminde yeni iki if deyimi varyasyonu eklenmiştir. constexpr if deyiminin genel biçimi şöyledir: if constexpr ([init;] koşul) @@ -8135,7 +8162,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 47) C++'ta ilk standartlardan beri "anonim birlik (anonymous union)" oluşturma özelliği bulunmaktadır. Anonim birlik oluşturabilmek için birliğe bir isim + 48) C++'ta ilk standartlardan beri "anonim birlik (anonymous union)" oluşturma özelliği bulunmaktadır. Anonim birlik oluşturabilmek için birliğe bir isim verilmemesi ve birlik bildiriminde aynı zamanda nesne tanımlamanın yapılmamış olamsı gerekmektedir. Örneğin: union { @@ -8205,7 +8232,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 48) C++11 ile birlikte dile static_assert isimli bir bildirim (decalartion) eklenmiştir. static_assert bildirimi derleme zamanında assert kontrolü yapmaktadır. + 49) C++11 ile birlikte dile static_assert isimli bir bildirim (decalartion) eklenmiştir. static_assert bildirimi derleme zamanında assert kontrolü yapmaktadır. Bilindiği gibi standart assert fonksiyonu programın çalışma zamanı sırasında işleme sokulmkatadır. Halbuki static_assert bildirimi derleme aşamsında işleme sokulur. Derleme aşamasında #if önişlemci komutuyla da bazı kontroller yapılıp #error önişlemci komutuyla derleme işlemi sonlandırılabilmektedir. Ancak önişlemci aşamasında yapılacek kontroller oldukça sınırlıdır. assert bildiriminin genel biçimi şöyedidr: @@ -8235,7 +8262,7 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - 49) C99 ile birlikte C'ye "designated initializer" denilen ilkdeğer verme biçimi eklenmişti. Bu sayede artık C99 ve sonrasında yapıların ve dizilerin + 50) C99 ile birlikte C'ye "designated initializer" denilen ilkdeğer verme biçimi eklenmişti. Bu sayede artık C99 ve sonrasında yapıların ve dizilerin elemanlarına sırasıyla ilkdeğer verme zorunluğu kaldırılmış oldu. C'de sentaks aşağıdaki gibidir: struct SAMPLE { @@ -26131,10 +26158,10 @@ int main() --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Çokbiçimli (polymophic) mekanizma şöyledir: Bir sınıf türünden gösterici ya da referans ile o sınıfın bir üye fonksiyonu çağrılmış olsun. Bu üye fonksiyon - nitelikli isim araması kuralına göre gösterici ya da referansın statik türüne ilişkin sınıfta aranır (tabii o sınıfta bulunamazsa taban sınıflara da bakılır). - Sonra bulunan fonksiyonun sanal olup olmadığına bakılır. Eğer bulunan fonksiyon sanal değilse bulunan fonksiyon çağrılır. Eğer bulunan fonksiyon sanal ise - o fonksiyonun çağrılmasında kullanılan gösterici ya da referansın dinamik türüne ilişkin sınıfın override edilmiş sanal fonksiyon çağrılır. Eğer gösterici + Çokbiçimli (polymophic) mekanizma şöyledir: Bir sınıf türünden gösterici ya da referans ile bir üye fonksiyon çağrılmış olsun. Bu üye fonksiyon nitelikli + isim araması kuralına göre gösterici ya da referansın statik türüne ilişkin sınıfta aranır (tabii o sınıfta bulunamazsa taban sınıflara da bakılır). Sonra + bulunan fonksiyonun sanal olup olmadığına bakılır. Eğer bulunan fonksiyon sanal değilse bulunan fonksiyon çağrılır. Eğer bulunan fonksiyon sanal ise o + fonksiyonun çağrılmasında kullanılan gösterici ya da referansın dinamik türüne ilişkin sınıfın override edilmiş sanal fonksiyon çağrılır. Eğer gösterici ya da referansın dinamik türüne ilişkin sınıfta ilgili sanal fonksiyon override edilmemişse yukarıya doğru o sanal fonksiyonun override edilmiş olduğu ilk taban sınıfın sanal fonksiyonu çağrılır. Örneğin: @@ -26240,7 +26267,8 @@ int main() C A'daki foo sanal fonksiyonu B'de override edilmiştir ancak C'de override edilmemiştir. Bu durumda A sınıfı türünden bir gösterici ya da referansın dinamik - türü C olsa bile çağrılan foo fonksiyonu B sınıfının foo fonksiyonu olacaktır. + türü C olsa bile çağrılan foo fonksiyonu B sınıfının foo fonksiyonu olacaktır. Başka bir deyişle eğer fonksiyon sanal ise o fonksiyonun son ovveride edilmiş + biçimi (final overrider) çağrılmaktadır. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #include @@ -26290,7 +26318,7 @@ int main() /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bir fonksiyonun sanallığı en tepedekiş taban sınıfta başlatılmak zorunda değildir. Örneğin: + Bir fonksiyonun sanallığı en tepedeki taban sınıfta başlatılmak zorunda değildir. Örneğin: A void bar(); B virtual void bar(); @@ -26436,90 +26464,162 @@ int main() B b; C c; - test(&a); - test(&b); - test(&c); + test(&a); // A::foo çağrılır + test(&b); // B::foo çağrılır + test(&c); // C::foo çağrılır return 0; } -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nesne yönelimli teknikte ideal olarak kod üzerinde değişiklikler yapılmaz. Her zaman ekleme yöntemi uygulanır. Daha önceki kod sağlam çalışıyorsa ve ekleme işleminden - sonra problem oluşmuşsa problem eklenen kısımla ilgilidir. Bu durumda eklenen kısmın test edilmesi yeterlidir. Halbuki biz kodda bir değişiklik yaparsak - tüm kodun yeniden test edilmesi gerekir. Çünkü yaptığımız değişiklik başka yerleri bozuyor olabilir. - İşte çokbiçimlilik türden bağımsız kod parçalarının ekleme yöntemiyle oluşturulması için kullanılan bir tekniktir. NYPT'de değişebilecek öğelere doğrudan değil - çokbiçimli olarak erişilir. Örneğin top ile oynanan bir oyun programında top değişebilir bir öğe olsun. Bu durumda biz top kavramını bir sınıfla temsil ederiz. - Oyunda "patlak topu", "zıplayan topu", "normal topu" bu top sınıfından türetme yaparak oluştururuz. Oyunda top genel bir kavram olarak Top sınıfıyla temsil edilir. - Ancak topun çeşiti eylemleri sanal fonksiyonlarla ifade edilir. Böylece bu top değiştiğinde artık kodda değişiklik yapılması gerekmez. +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 71. Ders 20/05/2024 - Pazartesi +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Bir eylem alt türlerde farklı asıl işlevi aynı olmak üzere farklı biçimlerde yapılıyorsa o eyleme "çokbiçimli eylem" diyebiliriz. Örneğin bir topun vurulduğunda + gitmesi çokbiçimli bir eylemdir. Her top gider ancak kendine göre değişik bir biçimde gider. Örneğin patlak top, hafif top ağır top vurulduğunda aynı biçimde + gitmemektedir. Örneğin Tetris oyununda bir şeklin düşmesi çokbiçimli bir eylemdir. Her şekil düşer ancak kendine göre düşer. Örneğin karesel şeklin düşmesi + sırasında yapılan çizimlerle çubuksal şeklin düşmesi sırasında yapılan çizimler farklıdır. Örneğin maaş hesaplaması da çokbiçimli bir eylemdir. Her çalışanın + maaşı vardır ancak bunlar değişik biçimde hesaplanmaktadır. Örneğin satranç taşlarının hamle yapılırken girmesi de çokbiçimli bir eylemdir. Her taş gider + ancak kendine göre farklı bir biçimde gider. Örneğin bir veri yapısına eleman insert edilmesi de çokbiçimli bir eylemdir. Her veri yapısına eleman insert + edilebilir ancak bu insert işlemi her veri yapısında farklı biçimde yapılmaktadır. Bağlı listeye eleman insert ederken yapılan işlemlerle dinamik diziye + eleman insert edilirken yapılan işlemler farklıdır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nesne yönelimli teknikte ideal durumda kod üzerinde değişiklikler yapılmaz. Geliştirme faaliyeti her zaman ekleme yöntemi yapılarak gerçekleştirilir. Böylece + daha önceki kod sağlam çalışıyorsa ve ekleme işleminden sonra problem oluşmuşsa problem eklenen kısımla ilgilidir. Bu durumda eklenen kısmın test edilmesi + yeterlidir. Halbuki biz kodda bir değişiklik yaparsaktüm kodun yeniden test edilmesi gerekir. Çünkü yaptığımız değişiklik başka yerleri bozuyor olabilir. + Bir yeri düzeltirken başka yeri bozulması durumlarıyla hem yazılımda hem diğer alanlarda sıkça karşılaşılmaktadır. NYPT'de mevcut kod iyi bir biçimde test edilir. + Yeni eklemeler yapıldığında kodun tamamı değil yalnızca eklenen kısmın testi yapılır. Halbuki klasik şelale modelinde (waterfall model) kodda bir değişiklik + yapıldığında tüm testler yinelenmektedir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + İşte çokbiçimlilik aslında ekleme yoluyla geliştirme süreci için kullanılan önemli bir tekniktir. Çokbiçimlilik sayesinde kod parçaları taban sınıfa dayalı + bir biçimde genel yazılır. Böylece bir ekleme yapıldığında genel yazılmış kodlar üzerinde test işlemleri yapılmaz. Örneğin top ile oynanan bir oyun olsun. + Oyundaki top kavramı Top isimli bir sınıfla temsil ediliyor olsun. Topun gitmesi çokbiçimli bir eylemdir. Top sınıfında git isimli sanal bir fonksiyon + bulundurulur. Değişik toplar bu Top sınıfından türetilir ve bu git fonksiyonu bu sınıflarda override edilir. Böylece kod taban Top sınıfına dayalı olarak + genel bir biçimde oluşturulur. Örneğin: + + Top + + NormalTop PatlakTop ZiplayanTop + + + Top *top; + + top = top_sec("NormalTop"); + + top->git(); + ... + top->git(); + ... + top->git(); + ... + + Burada top_sec fonksiyonu Top sınıfından türetilmiş bir sınıf türünden nesneyi dinamik olarak tahsis edip onun adresiyle geri dönmektedir. Böylece git fonksiyonu + çağrıldığında top_sec fonksiyonu hangi çeşit topu verdiyse aslında o top gidecektir. Bu oyuna yeni bir top çeşiti ekleyecek olsak kodun bu kısımlarında bir + değişiklik yapmamız gerekmez. Tek yapacağımız şey yeni Top sınıfından yeni bir top sınıfı türetip sanal fonksiyonları o sınıfta override etmektir. Örneğin: + + Top + + NormalTop PatlakTop ZiplayanTop HafifTop + + Tabii top_sec fonksiyonuna da eklemelerin yapılması gerekir. Pekiyi burada yeni top eklenirken top_al fonksiyonunda değişiklik yapılması yine kodda bozulma riski + oluşturmaz mı? Aslında bu fonksiyonda değişiklik yapmadan da top_sec fonksiyonunun bizim eklediğimiz hafif topu vermesi sağlanabilir. Buna NYPT'de "fabrika + kalıbı (factory pattern)" denilmektedir. + + Tipik olarak NYPT'de proje içerisinde değişebilecek öğeler belirlenir. Bunlara doğrudan değil taban sınıf yoluyla çokbiçimli mekanizmayla erişilir. Bu biçimdeki + erişime "arayüz (interface) yoluyla" erişim de denilmektedir. + + Aşağıda bu örneğin basit bir simülasyonu yapılmıştır. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #include +#include #include using namespace std; class Top { public: - virtual void git() {} + virtual void git(); //... }; -class NormalTop : public Top { +class NormalTop : public Top { public: - void git(); + void git() override; //... }; class PatlakTop : public Top { public: - void git(); + void git() override; //... }; -class ZiplayanTop: public Top { +class ZiplayanTop : public Top { public: - void git(); + void git() override; +}; + +class Oyun { +public: + void baslat(); + //... +private: + Top *top_sec(); //... }; +void Top::git() +{ + cout << "Top gidiyor" << endl; +} + void NormalTop::git() { - cout << "Normal top gidiyor" << endl; + cout << "NormalTop gidiyor" << endl; } void PatlakTop::git() { - cout << "Patlak top gidiyor" << endl; + cout << "PatlakTop gidiyor" << endl; } void ZiplayanTop::git() { - cout << "Zıplayantop gidiyor" << endl; + cout << "PatlakTop gidiyor" << endl; } -Top *top_sec(string isim) +Top *Oyun::top_sec() { Top *top; + string topismi; - if (isim == "normal") + cout << "Top ismi: "; + cin >> topismi; + + if (topismi == "NormalTop") top = new NormalTop(); - else if (isim == "patlak") + else if (topismi == "PatlakTop") top = new PatlakTop(); - else if (isim == "ziplayan") + else if (topismi == "ZiplayanTop") top = new ZiplayanTop(); else - top = new Top(); + throw runtime_error("gecersiz top"); return top; } -int main() +void Oyun::baslat() { Top *top; - top = top_sec("patlak"); - //... + top = top_sec(); top->git(); //... @@ -26527,93 +26627,120 @@ int main() //... top->git(); //... +} + + +int main() +{ + Oyun oyun; + + oyun.baslat(); return 0; } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- Yukarıdaki programda oyuna yeni bir top cinsi ekleyecek olalım. Tek yapacağımız şey aslında bu yeni top sınıfını Top sınıfından türetmek ve sanal fonksiyonları - bu sınıfta override etmektir. + bu sınıfta override etmektir. Tabii top_sec fonksiyonunda bu yeni topun da verilmesini sağlamalıyız. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #include +#include #include using namespace std; class Top { public: - virtual void git() {} + virtual void git(); //... }; -class NormalTop : public Top { +class NormalTop : public Top { public: - void git(); + void git() override; //... }; class PatlakTop : public Top { public: - void git(); + void git() override; //... }; -class ZiplayanTop: public Top { +class ZiplayanTop : public Top { public: - void git(); - //... + void git() override; }; class HafifTop : public Top { - void git(); +public: + void git() override; //... }; + +class Oyun { +public: + void baslat(); + //... +private: + Top *top_sec(); + //... +}; + +void Top::git() +{ + cout << "Top gidiyor" << endl; +} + void NormalTop::git() { - cout << "Normal top gidiyor" << endl; + cout << "NormalTop gidiyor" << endl; } void PatlakTop::git() { - cout << "Patlak top gidiyor" << endl; + cout << "PatlakTop gidiyor" << endl; } void ZiplayanTop::git() { - cout << "Zıplayan top gidiyor" << endl; + cout << "PatlakTop gidiyor" << endl; } void HafifTop::git() { - cout << "Hafif top gidiyor" << endl; + cout << "HafifTop gidiyor" << endl; } -Top *top_sec(string isim) +Top *Oyun::top_sec() { Top *top; + string topismi; - if (isim == "normal") + cout << "Top ismi: "; + cin >> topismi; + + if (topismi == "NormalTop") top = new NormalTop(); - else if (isim == "patlak") + else if (topismi == "PatlakTop") top = new PatlakTop(); - else if (isim == "ziplayan") + else if (topismi == "ZiplayanTop") top = new ZiplayanTop(); - else if (isim == "hafif") + else if (topismi == "HafifTop") top = new HafifTop(); else - top = new Top(); + throw runtime_error("gecersiz top"); return top; } -int main() +void Oyun::baslat() { Top *top; - top = top_sec("hafif"); - //... + top = top_sec(); top->git(); //... @@ -26621,56 +26748,51 @@ int main() //... top->git(); //... +} + + +int main() +{ + Oyun oyun; + + oyun.baslat(); return 0; } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Taban sınıftaki birssanal fonksiyon bazen programcılar tarafından "override" edildi sanılmakta ancak programcı bu işlem sırasında parametre - türlerini yanlış yazdığı için aslında override işlemi yapılamamaktadır. İşte bu tür hataları engellemek için C++11 ile birlikte C++'a override anahtar sözcüğü - de eklenmiştir. Fonksiyonun parametre parantezinden sonra "override" anahtar sözcüğü yazılırsa bu durumda eğer taban sınıfta override edilecek bir sanal fonksiyon yoksa - derleme aşamasında error oluşmaktadır. Örneğin: + Çok karşılaşılan bir kalıp türemiş sınıflara ilişkin nesne adreslerinin taban sınıfa ilişkin br gösterici dizisinde tutulması ve bu dizi elemanlarıyla + taban sınıfın sanal fonksiyonlarının çağrılmasıdır. Örneğin aşağıdaki gibi bir türetme şeması söz konusu olsun: - class A { - public: - virtual void foo(long a); - //... - }; + Shape - class B : public A { - public: - void foo(int a) override; /* derleme sırasına error oluşacak */ - //... - }; + RectangleShape EllipseShape LineShape - Yine C++11 ile birlikte zaten Java ve C# gibi dillerde olan "final override" kavramı da C++'a eklenmiştir. Eğer fonksiyonun parametre parantezinden sonra "final" - anahtar sözcüğü getirilirse bu durumda o sanal fonksiyon artık daha fazla override edilemez. Edilmeye çalışılırsa derleme zamanı sırasında error oluşur. Örneğim: + Shape sınıfının draw isimli sanal bir fonksiyonunun olduğunu ve bu fonksiyonun türemiş sınıflarda override edildiğini varsayalım. Biz farklı türemiş sınıf + nesnelerinin adreslerini taban sınıf türünden bir dizi de tutabiliriz. Örneğin: - class A { - public: - virtual void foo(long a); - //... - }; + vector shapes; - class B : public A { - public: - void foo(long a) override final; // foo artık daha fazla override edilemez! - //... - }; + Burada shapes isimli vector nesnesinin elemanları Shape * türündendir. Yani aslında bu shapes nesnesi dinamik farklı olan heterojen nesneleri tutabilmektedir. + Şimdi bu vektöre eklemeler yapalım: - class C : public B { - public: - void foo(long a); // compile time error! - //... - }; + shapes.push_back(new RectangleShape()); + shapes.push_back(new EllipseShape()); + shapes.push_back(new LineShape()); + shapes.push_back(new RectangleShape()); + shapes.push_back(new EllipseShape()); + shapes.push_back(new EllipseShape()); ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + Artık bu vektörün her bir elemanı aslında tür bakımından farklı olabilen ancak Shape sınfından türetilmiş olan nesneleri göstermektedir. Şimdi bu shapes + vektörünün her elemanı için draw sanal fonksiyonunu çağıralım: -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Çokbiçimli uygulamalarda çoğu kez taban sınıf aslında türden bağımsız işlem yapmak için bulundurulmaktadır. Aslında o taban sınıf türünden bir nesne yaratılmamaktadır. - İşte bu tür durumlarda taban sınıftaki sanal fonksiyonların boşuna gövdeye sahip olması gerekmez. Eğer taban sınıftaki sanal fonksiyonun gövdeye sahip olması istenmiyorsa - fonksiyonun parametre parantezinden sonra "= 0" sentaksı kullanılır. Yalnızca sanal fonksiyonlarda bu sentaks kullanılabilmektedir. Bu tür fonksiyonlara C++'ta - "saf sanal fonksiyonlar (pure virtual functions)" denilmektedir. En az bir saf sanal fonksiyona sahip olan sınıfa da "soyut sınıf (abstract class)" denir. + for (Shape *shape : shapes) + shape->draw(); + + Vektörün ilgili elemanı hangi sınıf türündense onun draw fonksiyonu çağrılacaktır. Yukarıdaki for döngüsünde farklı türler sanki aynı türmüş gibi işleme + sokulmultur. + + Aşağıda bu örneğe ilişkin kod verilmiştir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #include @@ -26678,45 +26800,1624 @@ int main() using namespace std; -class A { +class Shape { public: - virtual void foo() = 0; // pure virtual + virtual void draw(); //... }; -class B : public A { +class RectangleShape : public Shape { public: - void foo() - { - cout << "B::foo" << endl; - } + void draw() override; + //... }; -class C : public A { +class EllipseShape : public Shape { public: - void foo() - { - cout << "C::foo" << endl; - } + void draw() override; + //... }; -class D : public A { +class LineShape : public Shape { public: - void foo() - { - cout << "D::foo" << endl; - } + void draw() override; + //... }; +void Shape::draw() +{} + +void RectangleShape::draw() +{ + cout << "RectangleShape::draw" << endl; +} + +void EllipseShape::draw() +{ + cout << "EllipseShape::draw" << endl; +} + +void LineShape::draw() +{ + cout << "LineShape::draw" << endl; +} + int main() { - vector v{new B(), new C(), new D(), new C()}; + vector shapes; - for (A *pa : v) - pa->foo(); + shapes.push_back(new RectangleShape()); + shapes.push_back(new EllipseShape()); + shapes.push_back(new LineShape()); + shapes.push_back(new RectangleShape()); + shapes.push_back(new EllipseShape()); + shapes.push_back(new EllipseShape()); + + for (Shape *shape : shapes) + shape->draw(); + + //... + + for (Shape *shape : shapes) + delete shape; return 0; } +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + PowerPoint benzeri bir program yazacak olalım. Programda dikdörtgen, elips, doğru gibi farklı çizim öğeleri fareyle çizilip üzerine tıklandığında seçilebilsin. + GUI kütüphaneleri fare ile tıklandığında bize tıklanan yerin koordinatı vermektedir. Bu uygulamada her şekil Shape isimli sınıftan türetilmiş sınıflarla temsil + edilebilir. Örneğin: + + Shape + + RectangleShape EllipseShape LineShape + + Bir şekil çizildiğinde şeklin koordinatları ilgili sınıfın veri elemanlarında saklanabilir. Böylece tıpkı yukarıdaki örnekte olduğu gibi çizilen her şekil + Shape sınıfı türünden adresleri tutan bir vektörde biriktirilebilir: + + vector shapes; + + Bir noktanın bir şeklin içinde olmasının tespit edilmesi çokbiçimli bir eylemdir. Çünkü örneğin noktanın dikdört içerisinde olduğunun tespiti ile doğru üzerinde + olduğunun tespiti farklı biçimlerde yapılmaktadır. Noktanın ilgili şeklin içerisinde olup olmadığını tespit eden fonksiyon Shape sınıfının sanal bir fonksiyonu + olabilir. Bu fonksiyon türemiş sınıflarda override edilebilir. Örneğin: + + class Shape { + public: + virtual bool isinside(int x, int y); + //... + }; + + Bu durumda fare ile tıklandığında tıklanan yerin çizilmiş bir şeklin içinde olup olmadığı aşağıdaki gibi tespit edilebilir: + + for (Shape *shape : shapes) + if (shape->isinside(x, y)) { + //... + break; + } + + Öte yandan bir şekle tıklandığında şeklin küçük yuvarlakçıklarla görsel biçimde seçilmesi de çokbiçimli bir eylemdir. Çünkü seçim sırasında bu yuvarlakçıklar + o şekle göre çizilmektedir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 72. Ders 22/05/2024 - Çarşamba +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Tetris oyununda dörtlü şekiller aşağıya doğru düşmekte bunlar sola sağa hareket ettirilebilme ve döndürülebilmektedir. Şekillerin aşağıya düşmesi, sola, sağa + hareket etmesi, döndürülmesi çokbiçimli eylemlerdir. Yani tüm Tetris şekillerinde bu hareketler vardır ancak bu hareketler şekle göre farklı çizimlerle + gerçekleştirilmektedir. İşte böyle bir oyunun şekillerle ilgili bu kısmını çokbiçimli mekanizmayı kullanarak yazabiliriz. Tüm şekilleri tepedeki Shape + isimli bir sınıftan türetebiliriz. Şekillerin ortak özelliklerini bu Shape sınıfında toplayabiliriz. Şeklin hareketleri Shape sınıfında tanımlanmış sanal + fonksiyonlarla sağlanabilir. Bu sanal fonksiyonlar bu şekil sınıflarında override edilebilir. Türetme şeması şöyle olabilir: + + Shape + + SquareShape BarShape ZShape TShape LShape + + Shape sınıfının sanal fonksiyonları şöyle olabilir: + + class Shape { + public: + virtual void move_down(); + virtual void move_left(); + virtual void move_right(); + virtual void rotate(); + //... + }; + + Bu fonksiyonlar tüm türemiş sınıflarda override edlebilir. Oyunun kendisi de Tetris isimli bir sınıfla temsil edilebilir. Örneğin: + + class Tetris { + public: + Tetris(); + void run(); + //... + private: + Shape *get_random_shape(); + //... + }; + + Oyun aşağıdaki gibi çalıştırılabilir: + + int main() + { + Tetris tetris; + + tetris.run(); + + return 0; + } + + Tetris sınıfının run üye fonksiyonuna dikkaty ediniz: + + void Tetris::run() + { + Shape *shape; + int ch; + + for (;;) { + shape = get_random_shape(); + for (int i = 0; i < 20; ++i) { + shape->move_down(); + Sleep(300); + if (_kbhit()) { + ch = _getch(); + if (ch == 'q') { + delete shape; + goto EXIT; + } + if (ch == 224) { + ch = _getch(); + switch (ch) { + case Rotate: + shape->rotate(); + break; + case Right: + shape->move_right(); + break; + case Down: + goto NEXT; + case Left: + shape->move_left(); + break; + } + } + } + } + NEXT: + delete shape; + } + EXIT: + ; + } + + Burada get_random_shape fonksiyonu bize rasgele bir şekeil nesnenin adresini vermektedir. Yani bu fonksiyonun geri dönüş değeri statik türü Shape olan + dinamik türü yukarıdaki şekil sınıflarına ilişkin olan bir nesne adresidir. Dolyısıyla rastgele alınan şekil nesneleri üzerinde ilgili sanal fonksiyonlar + çağrıldığında o şekil sınıflarının sanal fonksiyonları çağrılacaktır. Buradaki tuş kontrolü Microsft C derleyicilerinin _kbhit ve _getch fonksiyonları + kullanılarak gerçekleştirilmiştir. Tuşa basınca programın beklememesi için "tuşa basılmışsa klavyeden okuma yapma" yoluna gidilmektedir. _getch fonksiyonunda + eğer basılan tuş özel bir tuşsa (yani ASCII karakterlerinin dışında bir tuş ise _getch birinci çağırmada 224 özel değerini ikinci çağrışta ise özel tuşun + "scan code" değerini vermektedir.) + + Aşağıda basit bir Tetris simülasonu yapan örnek kodlar verilmiştir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +// shape.hpp + +#ifndef SHAPE_HPP_ +#define SHAPE_HPP_ + +class Shape { +public: + virtual void move_down(); + virtual void move_left(); + virtual void move_right(); + virtual void rotate(); + //... +}; + + +#endif + +// shape.cpp + +#include "Shape.hpp" + +void Shape::move_down() +{} + +void Shape::move_left() +{} + +void Shape::move_right() +{} + +void Shape::rotate() +{} + +// barshape.hpp + +#ifndef BARSHAPE_HPP_ +#define BARSHAPE_HPP_ + +#include "shape.hpp" + +class BarShape : public Shape { +public: + void move_down() override; + void move_left() override; + void move_right() override; + void rotate() override; + //... +}; + +#endif + + +// barshape.cpp + +#include +#include "barshape.hpp" + +using namespace std; + +void BarShape::move_down() +{ + cout << "BarShape::move_down" << endl; +} + +void BarShape::move_left() +{ + cout << "==> BarShape::move_left <==" << endl; +} + +void BarShape::move_right() +{ + cout << "==> BarShape::move_right <==" << endl; +} + +void BarShape::rotate() +{ + cout << "==> BarShape::rotate <==" << endl; +} + +// zshape.hpp + +#ifndef ZSHAPE_HPP_ +#define ZSHAPE_HPP_ + +#include "shape.hpp" + +class ZShape : public Shape { +public: + void move_down() override; + void move_left() override; + void move_right() override; + void rotate() override; + //... +}; + +#endif + +// zshape.cpp + +#include +#include "zshape.hpp" + +using namespace std; + +void ZShape::move_down() +{ + cout << "ZShape::move_down" << endl; +} + +void ZShape::move_left() +{ + cout << "==> ZShape::move_left <==" << endl; +} + +void ZShape::move_right() +{ + cout << "==> ZShape::move_right <==" << endl; +} + +void ZShape::rotate() +{ + cout << "==> ZShape::rotate <==" << endl; +} + +// tshape.hpp + +#ifndef TSHAPE_HPP_ +#define TSHAPE_HPP_ + +#include "shape.hpp" + +class TShape : public Shape { +public: + void move_down() override; + void move_left() override; + void move_right() override; + void rotate() override; + //... +}; + +#endif + +// tshape.cpp + +#include +#include "tshape.hpp" + +using namespace std; + +void TShape::move_down() +{ + cout << "TShape::move_down" << endl; +} + +void TShape::move_left() +{ + cout << "===> TShape::move_left <==" << endl; +} + +void TShape::move_right() +{ + cout << "==> TShape::move_right <==" << endl; +} + +void TShape::rotate() +{ + cout << "==> TShape::rotate <==" << endl; +} + +// lshape.hpp + +#ifndef LSHAPE_HPP_ +#define LSHAPE_HPP_ + +#include "shape.hpp" + +class LShape : public Shape { +public: + void move_down() override; + void move_left() override; + void move_right() override; + void rotate() override; + //... +}; + +#endif + +// lshape.cpp + +#include +#include "lshape.hpp" + +using namespace std; + +void LShape::move_down() +{ + cout << "LShape::move_down" << endl; +} + +void LShape::move_left() +{ + cout << "==> LShape::move_left <==" << endl; +} + +void LShape::move_right() +{ + cout << "==> LShape::move_right <==" << endl; +} + +void LShape::rotate() +{ + cout << "==> LShape::rotate <==" << endl; +} + +// tetris.hpp + +#ifndef TETRIS_HPP_ +#define TETRIS_HPP_ + +#include "shape.hpp" + +class Tetris { +public: + Tetris(); + void run(); + //... +private: + Shape *get_random_shape(); +private: + const static int NSHAPES = 5; + enum Keys {Rotate = 72, Right = 77, Down = 80, Left = 75}; +}; + +#endif + + +// tetris.cpp + +#include +#include +#include +#include "tetris.hpp" +#include +#include +#include "squareshape.hpp" +#include "barshape.hpp" +#include "zshape.hpp" +#include "tshape.hpp" +#include "lshape.hpp" + +using namespace std; + +Tetris::Tetris() +{ + srand(time(nullptr)); +} + +void Tetris::run() +{ + Shape *shape; + int ch; + + for (;;) { + shape = get_random_shape(); + for (int i = 0; i < 20; ++i) { + shape->move_down(); + Sleep(300); + if (_kbhit()) { + ch = _getch(); + if (ch == 'q') { + delete shape; + goto EXIT; + } + if (ch == 224) { + ch = _getch(); + switch (ch) { + case Rotate: + shape->rotate(); + break; + case Right: + shape->move_right(); + break; + case Down: + goto NEXT; + case Left: + shape->move_left(); + break; + } + } + } + } + NEXT: + delete shape; + } +EXIT: + ; +} + +Shape *Tetris::get_random_shape() +{ + Shape *shape; + + switch (rand() % NSHAPES) { + case 0: + shape = new SquareShape(); + break; + case 1: + shape = new BarShape(); + break; + case 2: + shape = new ZShape(); + break; + case 3: + shape = new TShape(); + break; + case 4: + shape = new LShape(); + break; + } + + return shape; +} + +// app.cpp + +#include +#include "tetris.hpp" + +using namespace std; + +int main() +{ + Tetris tetris; + + tetris.run(); + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Daha önce bir satranç tahtası uygulaması yapmıştık. Bu uygulamada satranç tahtasını Board isimli sınıfla, tahta üzerindeki kareleri Square isimli sınıfla + ve taşları da Figure isimli sınıfla temsil etmiştik. Aslında her taş türü ortak özelliklere sahip olsa da birbirinden farklıdır. Satrançta bir taşın gitmesi + çokbiçimli bir eylemdir. Yani "her taş gider ama kendisine göre değiş bir biçimde gider". Uygulamayı çokbiçimli mekanizmayı kullanacak hale getirebilmek + için önce Figure sınıfından çeşitli taş sınıflarını türetmemiz gerekir. Örneğin: + + Figure + + Pawn Knight Bishop Rook Queen King + + Yine uygulamamızda Square sınıfı Figure sınıfı türünden bir gösterici tutacaktır. Ancak bu göstericinin dinamik türü Figure değil türemiş sınıf türlerine + ilişkin olacaktır. Bir taş hareket ettirildiğinde taşın geçerli olarak gidebileceği kareler Figure sınıfının get_valid_moves isimli bir sanal fonksiyonuyla + elde edilebilir. Böylece biz bir karedeki taşın türünü bilmeden o taşın kendi hareketlerine göre tahtanın o anki konumununda nerelere gidebileceğini elde + edebiliriz. GUI uygulamalarında genellikle bir satranç taşı geçersiz bir kareye bırakıldığında taş eski yerine geri döndürülmektedir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 73. Ders 27/05/2024 - Pazartesi +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Büyük projeleerde çok miktarda sınıf ve dolayısıyla çok miktarda kaynak dosya söz konusu olabilmektedir. Bir sınıf başka bir sınıfı kullaıyorsa kullanılan + sınıfın bildiriminin derleyici tarafından daha önce görülmesi gerekir. Ancak bazen bir sınıf bir sınıfı kullanırken diğeri o sınıfı kullanıyor olabilmektedir. + Örneğin: + + class A { + //... + B *m_pb; // dikkat! henüz B sınıfı görülmedi! error oluşacak + }; + + class B { + //... + A *m_pa; + }; + + Burada A sınıfı B sınıfını B sınıfı da A sınıfını kullanmaktadır. Fakat bu kodun derlenmesinde hata oluşacaktır. Çünkü derleyici A sınıfını gördüğünde + henüz B sınıfını görmemiştir. Sınıfları yer değiştirsek aynı problem yine oluşacaktır. Halbuki bu tür durumlarla sık karşılaşılmaktadır. Tabii yukarıdaki + örnekte elemanların gösterici ya da referans olmaması durumunda zaten mantıksal bir problem vardır ve bu problemin de çözümü olamaz. Örneğin: + + class A { + //... + B m_b; // dikkat! henüz B sınıfı görülmedi! error oluşacak + }; + + class B { + //... + A m_a; + }; + + Burada A ve B sınıfılarının sizoef değeri belli olmadığı için zaten anlamsız bir durum söz konusudr. + + Pekiyi bir sınıf bir sınıf türünden diğeri de o sınıf türünden gösterici ya da referans veri elemanına sahip ise buradaki bildirim problemi nasıl çözülmektedir? + Aslında bu problem C++'a özgü değildir. Yapılar söz konusu olduğunda aynı problem C'de karşımıza çıkmakradır. İşte bu problem "eksik bildirim (incomplete + declaration)" kullanılarak çözülmektedir. Bir sınıf türünden bir referans ya da gösterici sınıf bildiriminin tamam mı görülmeden yalnızca onun isimsel bildirimi + yapılarak tanımlanabilmektedir. Örneğin: + + class A; + + A *pa; // geçerli + + Tabii bu biçimde bildirilmiş olan göstericilerin ve referansların kullanılması için bildirimin "tamamlanması (complete hale getirilmesi)" gerekmektedir. + Başka bir deyişle derleyicinin artık bu sınıf bildiriminin tamımamını görmesi gerekir. Bu durumda yukarıdaki bildirim aşağıdaki gibi düzeltilebilir: + + class B; + + class A { + //... + B *m_pb; // dikkat! henüz B sınıfı görülmedi! error oluşacak + }; + + class B { + //... + A *m_pa; + }; + + Burada anahtar nokta bir sınıf türünden gösterici ya da referans tanımlamak için derleyicinin o sınıfın bildirimini görmek zorunda olmadığıdır. Çünkü + göstericiler ve referanslar için ayrılacak alanın onların türüyle bir ilgisi yoktur. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Büyük projelerde önemli bir problem de başlık dosyalarının başka başlık dosylarını include etmesidir. Örneğin biz Sample isimli bir sınıf yazacak olalım. + Bu sınıf için "sample.hpp" ve "sample.cpp" dosyalarını oluşturmalıyız. Ancak Sample sınıfı A, B, C, D sınıfları türünden veri elemanlarına sahip ise + onlara ilişkin başlık dosyaları mecburen "sample.hpp" içerisinde include edilecektir: + + // sample.hpp + + #ifndef SAMPLE_HPP_ + #define SAMPLE_HPP_ + + #include "a.hpp" + #include "b.hpp" + #include "c.hpp" + #include "d.hpp" + + class Sample { + //... + private: + A m_a; + B m_b; + C m_c; + D m_d; + }; + + #endif + + Burada Sample sınıfını kullanacak olanlar "sample.hpp" dosyasını include etmelidir. Ancak bu dosya da diğer başlık dosyalarını include ettiği için + derleme süresi göreli biçimde uzayacaktır. Proje içerisinde Sample sınıfını kullanan her kaynak dosyada aynı durum söz konusu olacaktır. Küçük projelerde + bu türlü durumlar önemli sorun oluşturmamaktadır. Ancak büyük projelerde yukarıda problem derleme zamanının ciddi biçiminde uzamasına yol açabilmektedir. + Pekiti bu problem nasıl bertaraf edilebilir? İşte bunun tek çözümü Sample sınıfının A, B, C ve D sınıfı türünden veri elemanları yerine bu sınıflar türünden + gösterici veri elemanlarına sahip olmasıdır. Örneğin: + + // sample.hpp + + #ifndef SAMPLE_HPP_ + #define SAMPLE_HPP_ + + class A; + class B; + class C; + class D; + + class Sample { + //... + private: + A *m_pa; + B *m_pb; + C *m_pc; + D *m_pd; + }; + + #endif + + Artık Sample sınıfını kullanmak isteyen kişiler "sample.hpp" dosyasını include ettikleri zaman diğer include işlemleri için zaman harcamak zorunda + kalmayacaklardır. Tabii bu yöntemin de en önemli dezavantajı bu gösterici veri elemanları için elemana sahip sınıfın yapıcı fonksiyonunda (burada Sample) + dinamik tahsisat yapıp bunların yıkıcı fonksiyonlarında bu tahsisatı serbest bırakmaktadır. + + Örneğin Qt isiml,i C++ GUI framework'ünde çalışma yukarıda esasa dayandırılmıştır. Yani "içerme ilişkisi (composition)" nesnnein kendisi ile değil gösterici + kullanarak sağlanmaktadır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + C++'ta yukarıdaki temanın daha sistematik hale getirilmiş bir biçimine "pimple (pointer implementation) idiom" denilmektedir. Pimple idiom'un ana amacı + yukarıdaki gibi sınıfın başka sınıf türünden private veri elemanları yerine o sınıflar türünden gösterici veri elemanları kullanarak derleme zamanını + kısaltmaktır. Pimple idiom bunun biraz daha sistematik hale getirilmiş biçimidir. + + Pimple idiom genel olarak şöyle kullanılmaktadır: + + 1) Sınıfın private elemanları başka bir sınıf içerisine alınır. Tabii sınıf dış dünyadan gizlenmelidir. Bu nedenle bu sınıf asıl sınıfın private bölümünde + iç sınıf olarak bildirilir. Biz henüz iç sınıfları görmedik. İç bir sınıfın (yani başka bir sınıfın içerisinde bildirilmiş sınıfın) hiçbir ayrıcalığı + yoktur. Başka bir deyişle iç sınıf ile dış sınıf arasında bir "data içermesi" söz konusu değildir. İç sınıfın dış sınıfa dış sınfın da iç sınıfa özel bir + erişimi yoktur. + + 2) Sınıfın private veri elemanları için asıl sınıfın private bölümünde bir gösterici veri elemanı tutulur. Tabii bu gösterici eleman için "eksik bildirim + (incomplete declartion)" uygulanır. + + 3) private veri elemanlarına ilişkin sınıf başlık dosyasında değil "cpp" dosyasında bildirilir. + + 4) private veri elemanlarına ilişkin sınıf nesnesi asıl sınıfın yapıcı fonksiyonunda new operatörü ile tahsis edilir. delete operatörü ile bu nesne yok edilir. + + Şimdi bu idiom için bir örnek verelim. Örneğimizde Sample sınıfı vector sınıfı türünden ve string sınıfı türünden veri elemanlarına sahip olsun. Eğer pimple + idion kullanılmazsa Sample sınıfının bildirimi aşağıdaki olacaktır: + + // sample.hpp + + #ifndef SAMPLE_HPP_ + #define SAMPLE_HPP_ + + #include + #include + + class Sample { + //... + private: + std::vector m_v; + std::string m_s; + }; + + #endif + + Buradaki problem "sample.hpp" dosyasını include eden kişinin ve dosyalarını da include etmiş olmasıdır. Şimdi pimple idiom uygulayalım: + + // sample.hpp + + #ifndef SAMPLE_HPP_ + #define SAMPLE_HPP_ + + class Sample { + public: + Sample(); + ~Sample(); + //... + private: + struct Impl; + + Impl *m_impl; + }; + + #endif + + Görüldüğü gibi Sample sınıfının private elemanları başka bir sınıfın içerisine alınmış ve "içerme ilişkisi (composition)" gösterici yoluyla oluşturulmuştur. + Impl sınıfının Sample sınıfı içerisinde bildirildiğine dikkat ediniz. "sample.cpp" dosyası da şöyle olacaktır: + + #include + #include + #include + #include "sample.hpp" + + using namespace std; + + class Sample::Impl { + vector m_v; + string m_s; + //... + }; + + Sample::Sample() + { + m_impl = new Impl(); + //... + } + + Sample::~Sample() + { + delete m_impl; + } + + Yukarıda da belirttiğimiz gibi pimpl idiom kullanmanın derleme zamanını kısaltması yanında bazı dezavantajları da vardır. Bunlar şöyle sıralanabilir: + + - Daha karmaşık ve zahmetli bir organizasyon oluşturması + - Dinamik tahsisata gereksinim duyulması (dinamik tahsisatlar belli bir zaman almaktadır) + - Tüm private veri elemanlarına gösterici yoluyla erişilmektedir. Bir elemana doğrudan erişmekle gösterici erişme arasında nano düzeyde farklılık vardır. + + Sonuç olarak büyük bir proje söz konusu değilse pimple idiom yerine normal yöntem tercih edilebilir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 74. Ders 29/05/2024 - Çarşamba +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Şimdi de daha önce yapmış olduğumuz satranç tahtası uygulmasında çokbiçimli mekanizmayı kullanalım. Anımsayacağınız gibi biz daha öce satranç taşlarının + hepsini Figure sınıfyla temsil etmiştik. Bir taşın ne taşı olduğunu Figure sınıfının içerisinde tutmuştuk. Şimdi her farklı taş için Figure sınıfından sınıflar + türetelim: + + Figure + + Pawn Knight Bishop Rook Queen King + + Tabii taşların ortak birtakım özellikleri yine taban sınıf olan Figure sınıfında bulundurulmalıdır. Örneğin taşın rengi ve taşın bulunduğu kare taban + Figure sınıfında saklanabilir. + + Bu tasarımda Square sınıfı yine Figure türünden bir gösterici veri elemanına saip olacaktır. Ancak bu gösterici veri elemanının dinamik türü (yani gösterdiği + yerdeki nesne) Pawn, Knight, Bishop, Rook, Queen ya da King olacaktır. Buradaki Figure sınıfı aslında "taş kavramını temsil eden" bir sınıftır. Dolayısıyla + mekanizma sayesinde bir karenin (Square nesnesinin) içerisindeki taşın türünü bilmeden çokbiçimli işlemler yapılabilir. Figure sınıfının aşağıdaki gibi sanal + fonksiyonları söz konusu olabilir: + + #ifndef FIGURE_HPP_ + #define FIGURE_HPP_ + + #include + #include "chess.hpp" + + class Board; + + class Figure { + public: + Figure(Color color, const Pos &pos); + Color color() const { return m_color; } + const Pos &pos() { return m_pos; } + + virtual void disp() const; + virtual char fsym() const; + virtual std::vector valid_moves(Board &board); + protected: + static const char *ms_colors[2]; + private: + Color m_color; + Pos m_pos; + }; + + #endif + + Her taş sınıfının bir disp üye fonksiyonu vardır. Bu disp üye fonksiyonu her taş sınıfı için o taşa özgü bir yazdırma yapmaktadır. Her taşın bir tahtada + görüntülenecek bir sembolü vardır. Ancak bu sembol her taş için farklıdır. Sınıfın fsym üye fonksiyonu taş sınıfına ilişkin bu sembolü vermektedir. + Tahtanın belli bir konumunda her taşın gidebileceği yerler o taşa özgü biçimde belirlenmektedir. valid_moves fonksiyonu tahtanın konumunu alarak ilgi + taşın gidebileceği kareleri bir vector nesnesi biçiminde vermektedir. Taşın gidebileceği karelerin elde edilmesi işlemi çokbiçimli bir eylemdir. + + Aşağıda satranç tahtası uygulaması yukarıda açıkladığımız haliyle verilmiştir. Ancak valid_moves fonksiyonlarının içi yazılmamıştır. Bu fonksiyonların + içini yazmaya çalışabilirsiniz. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +// chess.hpp + +#ifndef CHESS_HPP_ +#define CHESS_HPP_ + +enum class Color { Black, White }; +enum class FigureType { King, Queen, Rook, Bishop, Knight, Pawn }; + +struct Pos { + Pos(char col, char row) : m_col(col), m_row(row) + {} + char m_col; + char m_row; +}; + +#endif + +// board.hpp + +#ifndef BOARD_HPP_ +#define BOARD_HPP_ + +#include +#include "square.hpp" + +class Board { +public: + Board(); + Square &at(char col, char row); + void move(const std::string &m); + void disp() const; +private: + Square m_squares[8][8]; +}; + +#endif + +#include +#include +#include "board.hpp" +#include "pawn.hpp" +#include "knight.hpp" +#include "bishop.hpp" +#include "rook.hpp" +#include "queen.hpp" +#include "king.hpp" + +using namespace std; + +Board::Board() +{ + for (int row = 0; row < 8; ++row) + for (int col = 0; col < 8; ++col) + m_squares[row][col].color((row + col) % 2 ? Color::White : Color::Black); + + for (int i = 0; i < 8; ++i) { + m_squares[1][i].figure(new Pawn(Color::White, Pos(i + 'A', '2'))); + m_squares[6][i].figure(new Pawn(Color::Black, Pos(i + 'A', '2'))); + } + + m_squares[0][0].figure(new Rook(Color::White, Pos('A', '1'))); + m_squares[7][0].figure(new Rook(Color::Black, Pos('H', '1'))); + + m_squares[0][7].figure(new Rook(Color::White, Pos('A', '8'))); + m_squares[7][7].figure(new Rook(Color::Black, Pos('H', '8'))); + + m_squares[0][1].figure(new Knight(Color::White, Pos('B', '1'))); + m_squares[7][1].figure(new Knight(Color::Black, Pos('G', '1'))); + + m_squares[0][6].figure(new Knight(Color::White, Pos('B', '8'))); + m_squares[7][6].figure(new Knight(Color::Black, Pos('G', '8'))); + + m_squares[0][2].figure(new Bishop(Color::White, Pos('C', '1'))); + m_squares[7][2].figure(new Bishop(Color::Black, Pos('F', '1'))); + + m_squares[0][5].figure(new Bishop(Color::White, Pos('C', '8'))); + m_squares[7][5].figure(new Bishop(Color::Black, Pos('F', '8'))); + + m_squares[0][3].figure(new Queen(Color::White, Pos('D', '1'))); + m_squares[7][3].figure(new Queen(Color::Black, Pos('D', '8'))); + + m_squares[0][4].figure(new King(Color::White, Pos('E', '1'))); + m_squares[7][4].figure(new King(Color::Black, Pos('E', '8'))); +} + +Square &Board::at(char col, char row) +{ + return m_squares[tolower(row) - '1'][tolower(col) - 'a']; +} + +void Board::move(const std::string &m) +{ + string::size_type pos = m.find('-'); + if (pos == string::npos) + throw invalid_argument("invalid move format"); + + Square &source = at(m[0], m[1]); + Square &target = at(m[3], m[4]); + + target.put(source.take()); +} + +void Board::disp() const +{ + const char *fg_black = "\x1b[34m"; + const char *fg_white = "\x1b[33m"; + const char *bg_black = "\x1b[40m"; + const char *bg_white = "\x1b[47m"; + const char *reset = "\x1b[0m"; + + for (int row = 7; row >= 0; --row) { + for (int col = 0; col < 8; ++col) { + cout << (m_squares[row][col].color() == Color::Black ? bg_black : bg_white); + cout << ' '; + auto figure = m_squares[row][col].figure(); + if (figure != nullptr) { + cout << (figure->color() == Color::Black ? fg_black : fg_white); + cout << m_squares[row][col].figure()->fsym(); + } + else + cout << ' '; + cout << ' '; + } + cout << reset << endl; + } + cout << reset << endl; +} + +// square.hpp + +#ifndef SQUARE_HPP_ +#define SQUARE_HPP_ + +#include "chess.hpp" + +class Figure; + +class Square { +public: + Square(); + + Color color() const { return m_color; } + void color(Color color){ m_color = color; } + void figure(Figure *figure) { m_figure = figure; } + Figure *figure() const { return m_figure; } + Figure *take(); + void put(Figure *figure); + +private: + Color m_color; + Figure *m_figure; +}; + +#endif + +// square.cpp + +#include +#include "square.hpp" + +using namespace std; + +Square::Square() : m_figure(nullptr) +{} + +Figure *Square::take() +{ + Figure *figure; + + figure = m_figure; + m_figure = nullptr; + + return figure; +} + +void Square::put(Figure *figure) +{ + if (m_figure != nullptr) + delete m_figure; + + m_figure = figure; +} + +// figure.hpp + +#ifndef FIGURE_HPP_ +#define FIGURE_HPP_ + +#include +#include "chess.hpp" + +class Board; + +class Figure { +public: + Figure(Color color, const Pos &pos); + Color color() const { return m_color; } + const Pos &pos() { return m_pos; } + virtual void disp() const; + virtual char fsym() const; + virtual std::vector valid_moves(Board &board); +protected: + static const char *ms_colors[2]; +private: + Color m_color; + Pos m_pos; +}; + +#endif + +// figure.cpp + +#include +#include "figure.hpp" + +using namespace std; + +const char *Figure::ms_colors[2] = {"Siyah", "Beyaz"}; + +Figure::Figure(Color color, const Pos &pos) : m_color(color), m_pos(pos) +{} + +void Figure::disp() const +{} + +char Figure::fsym() const +{ + return '?'; +} + +vector Figure::valid_moves(Board &board) +{ + return vector(); +} + +// pawn.hpp + +#ifndef PAWN_HPP_ +#define PAWN_HPP_ + +#include "figure.hpp" + +class Pawn : public Figure { +public: + Pawn(Color color, const Pos &pos) : Figure(color, pos) + {} + void disp() const override; + char fsym() const override; + std::vector valid_moves(Board &board) override; +}; + +#endif + +// pawn.cpp + +#include +#include +#include "pawn.hpp" +#include "board.hpp" + +using namespace std; + +void Pawn::disp() const +{ + cout << ms_colors[static_cast(color())] << " Piyon" << endl; +} + +char Pawn::fsym() const +{ + return 'p'; +} + +vector Pawn::valid_moves(Board &board) +{ + // İçi doldurulacak + + return vector(); +} + +// knight.hpp + +#ifndef KNIGHT_HPP_ +#define KNIGHT_HPP_ + +#include "figure.hpp" + +class Knight : public Figure { +public: + Knight(Color color, const Pos &pos) : Figure(color, pos) + {} + void disp() const override; + char fsym() const override; + std::vector valid_moves(Board &board) override; +}; + +#endif + +// knight.cpp + +#include +#include "knight.hpp" + +using namespace std; + +void Knight::disp() const +{ + cout << ms_colors[static_cast(color())] << " At" << endl; +} + +char Knight::fsym() const +{ + return 'a'; +} + +vector Knight::valid_moves(Board &board) +{ + // İçi doldurulacak + + return vector(); +} + +// bishop.hpp + +#ifndef BISHOP_HPP_ +#define BISHOP_HPP_ + +#include "figure.hpp" + +class Bishop : public Figure { +public: + Bishop(Color color, const Pos &pos) : Figure(color, pos) + {} + void disp() const override; + char fsym() const override; + std::vector valid_moves(Board &board) override; +}; + +#endif + +// bishop.cpp + +#include +#include "bishop.hpp" + +using namespace std; + +void Bishop::disp() const +{ + cout << ms_colors[static_cast(color())] << " Fil" << endl; +} + +char Bishop::fsym() const +{ + return 'f'; +} + +vector Bishop::valid_moves(Board &board) +{ + // İçi doldurulacak + + return vector(); +} + +// rook.hpp + +#ifndef ROOK_HPP_ +#define ROOK_HPP_ + +#include "figure.hpp" + +class Rook : public Figure { +public: + Rook(Color color, const Pos &pos) : Figure(color, pos) + {} + void disp() const override; + char fsym() const override; + std::vector valid_moves(Board &board) override; +}; + +#endif + +// rook.cpp + +#include +#include "rook.hpp" + +using namespace std; + +void Rook::disp() const +{ + cout << ms_colors[static_cast(color())] << " Kale" << endl; +} + +char Rook::fsym() const +{ + return 'k'; +} + +vector Rook::valid_moves(Board &board) +{ + // İçi doldurulacak + + return vector(); +} + +// queen.hpp + +#ifndef QUEEN_HPP_ +#define QUEEN_HPP_ + +#include "figure.hpp" + +class Queen : public Figure { +public: + Queen(Color color, const Pos &pos) : Figure(color, pos) + {} + void disp() const override; + char fsym() const override; + std::vector valid_moves(Board &board) override; +}; + +#endif + +// queen.cpp + +#include +#include "queen.hpp" + +using namespace std; + +void Queen::disp() const +{ + cout << ms_colors[static_cast(color())] << " Vezir" << endl; +} + +char Queen::fsym() const +{ + return 'v'; +} + +vector Queen::valid_moves(Board &board) +{ + // İçi doldurulacak + + return vector(); +} + +// king.hpp + +#ifndef KING_HPP_ +#define KING_HPP_ + +#include "figure.hpp" + +class King : public Figure { +public: + King(Color color, const Pos &pos) : Figure(color, pos) + {} + void disp() const override; + char fsym() const override; + std::vector valid_moves(Board &board) override; +}; + +#endif + +// king.cpp + +#include +#include "king.hpp" + +using namespace std; + +void King::disp() const +{ + cout << ms_colors[static_cast(color())] << " Sah" << endl; +} + +char King::fsym() const +{ + return 's'; +} + +vector King::valid_moves(Board &board) +{ + // İçi doldurulacak + + return vector(); +} + +// app.cpp + +#include +#include "board.hpp" + +using namespace std; + +int main() +{ + + Board board; + + board.disp(); + + board.move("e2-e4"); + board.disp(); + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 75. Ders 03/06/2024 - Pazartesi +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Çokbiçimli mekanizmaya diğer bir örnek veri yapıları konusu üzerinde verilebilir. Bazı veri yapılarında bazı işlemler çokbiçimlidir. Yani bu işlemler bu + veri yapılarında bulunur ancak bu işlemler her veri yapısında o veri yapısına özgü bir biçimde gerçekleştirilmektedir. Örneğin pek çok veri yapısında + "sona eleman ekleme" biçiminde bir işlem vardır. Ancak dinamik dizilerde sona eklenmesiyle bağlı listelerde sona eleman eklenmesi farklı biçimlerde yapılmaktadır. + İşte türden bağımsız bir biçimde pek çok veri yapısı için çalışacak bir sınıf yazmak istediğimizde çokbiçimlilikten faydalanabiliriz. Örneğin: + + List + + LinkedList ArrayList ... + + Elemanlar arasında öncelik sonralık ilişkisi olan veri yapılarına "liste tarzı veri yapıları" denilmektedir. Bağlı listeler (linkedList), dinamik büyütülen + diziler (ArrayList), liste tarzı veri yapısıdır. Bu liste tarzı veri yapılarında bazı işlemler ortak ancak çokbiçimli olarak bulunurlar. Örneğin "sona + eleman ekleme" bağlı listelerde de dinamik büyütülen dizilerde de söz konusudur. araya eleman ekleme de benzer biçimde bu veri yapılarında söz konusudur. + Ancak bazı veri yapılarında bu işlemler söz konusu olmayabilir. İşte List sınıfında bu işlemler için sanal fonksiyonlar bulundurulup bunlar türemiş sınıflarda + override edilirse türden bağımsız veri yapıları ile işlemler yapılabilir. Örneğin: + + class List { + public: + virtual size_t add(int val); + virtual void insert(int pos, int val); + virtual void remove(int pos); + virtual void clear(); + //... + }; + + class Sample { + public: + Sample(List *list) : m_list(list) + {} + void do_something(); + //... + private: + List *m_list; + }; + + void Sample::do_something() + { + //... + m_list->add(val); + //... + m_list->insert(pos, val); + //... + m_list->remove(pos); + //... + }; + //... + + LinkledList ll; + Sample s(&ll); + + s.do_something(); + + Burada Sample sınıfı veri yapısı olarak bağlı liste kullanmaktadır. Ancak biz sınıfa dokunmadan Sample sınıfının dinamki büyütülen dizi kullanmasını da + sağlayabiliriz. Örneğin: + + ArrayList al; + Sample s(&al); + + s.do_something(); +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + O halde farklı kavramların ortak birtakım eylemsel özellikleri varsa bu eylemsel özellikler bir taban sınıfta toplanıp sanal fonksiyonlarla temsil + edilebilirler. Böylece biz de ortak eylemsel özellikleri kullanan türden bağımzıs kod parçaları oluşturabiliriz. Daha önceden de belirttiğimiz gibi proje + içerisinde değişebilecek öğeler varsa kodumuzun bu değişimden etkilenmemesi için çokbiçimli mekanizmayı kullanabiliriz. Örneğin bir Parser sınıfı yazmak + isteyelim. Parser sınıfı bir kaynaktan karakterleri okuyarak işlemini ypıyor olsun. Burada kaynak çeşitli biçimlerde olabilir. Örneğin karakterler bir dosyadan + bir soketten, bir string'ten (yani char türden bir diziden) alınıyor olabilir. Burada kaynak değişebilmektedir. Bu değişimden Parser sınıfımızın etkilenmemesi + için kaynağı bir taban sınıf ile temsil edebiliriz ve çokbiçimli mekanizmadan faydalanabiliriz. Örneğin: + + Source + + FileSource MemorySource SocketSource + + Burarada Source sınıfının şöyle bildirildiğini varsayalım: + + class Source { + public: + virtual char read_char(); + //... + }; + + Bu sanal fonksiyonun türemiş sınıflarda override edildiğini varsayalım. Bu durumda Parser sınıfımızı kaynaktan bağımsız bir biçimde şöyle oluşturabiliriz: + + class Parser { + public: + Parser(Source *source) : m_source(source) + {} + void do_parse(); + //... + private: + Source *m_source + }; + + void Parser::do_parse() + { + char ch; + + //... + ch = m_source->read_char(); + //... + ch = m_source->read_char(); + //... + ch = m_source->read_char(); + } + + Şimdi Parser sınıfımızın kaynak olarak dosya kullanacağını varsayalım: + + FileSource fs("test.txt"); + Parser parser(&fs); + + parser.do_parse(); +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Örneğin C# Programlama Dilinde .NET sınıf kütüphanesinde bulunan Stream isimli sınıf "dosya gibi işlem gören" genel bir kavramı temsil etmektedir. + Stream sınıfının Read, Write, Seek gibi çeşitli sanal fonksiyonları vardır. Bu sanal fonksiyonlara sahip olan türemiş sınıflarda override edilmiştir. Bötylece biz + Stream sınıfı türünden bir referans parametreli fonksiyona aslında Stream sınıfından türetilen herhangi bir sınıf nesnesini parametere olarak geçirebiliriz. + Read, Write ve Seek eylemleri çok biçimli eylemlerdir. Örneğin "dosyadan da okuma söz konusur, soketten de okuma söz konusudur, bellekteki bir diziden de + okuma söz konusudur." +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Çokbiçimli uygulamalarda çoğu kez taban sınıf aslında türden bağımsız işlem yapmak için bulundurulmaktadır. Yani aslında o taban sınıf türünden bir nesne + yaratılmamaktadır. İşte bu tür durumlarda taban sınıftaki sanal fonksiyonların boşuna gövdeye sahip olması gerekmez. Eğer taban sınıftaki sanal fonksiyonun + gövdeye sahip olması istenmiyorsa fonksiyonun parametre parantezinden sonra "= 0" sentaksı kullanılır. Yalnızca sanal fonksiyonlarda bu sentaks kullanılabilmektedir. Bu tür fonksiyonlara C++'ta + "= 0" sentaksı ile bildirilmiş olan sanal fonksiyonlara "saf sanal fonksiyonlar (pure virtual functions)" denilmektedir. Örneğin: + + class A { + public: + virtual void foo() = 0; + //... + }; + + Burada foo fonksiyonu "saf sanal (pure virtual)" bir fonksiyondur. + + En az bir safsanal fonksiyona sahip olan sınıfa "soyut sınıf (abstract class)" denilmektedir. Yukaırdaki örnekte A bir soyut sınıftır. + + Soyut sınıflar normal üye fonksiyonlara, yapıcı ve yıkıcı fonksiyonlara , veri elemanlarına sahip olabilirler. + + Soyut sınıflar türünden nesneler yaratılamaz. Çünkü bu durumda gövdesi olmayan bir fonksiyonun çağrılması gibi bir durumla karşılaşılabilir. Örneğin yukarıdaki + A sınıfı türünden bir nesne yaratılamaz: + + A a; // geçersiz! soyut sınıflar türünden nesneler yaratılmaz + + Yukarıdaki tanımlama geçersizdir. Eğer bu tanımlama geçerli olsaydı bu durumda aşağıdaki bir çağrıda ne olacaktı? + + a.foo(); // olmayan bir fonksiyon çağrılamaz! + + Soyut sınıflar türünden nesne yaratımı new operatörüyle de yapılamamaktadır. Örneğin: + + A *pa = new A(); // geçersiz! soyut bir sınıf türünden dinamik nesne de yaratılamaz + + Pekiyi soyut sınıflar türünden nesneler yaratılamıyorsa soyut sınıflar ne işe yaramaktadır? İşte soyut sınıflar türünden nesneler yaratılamaz ancak göstericiler + ve referanslar tanımlanabilir. Örneğin: + + A *pa; // geçerli, soyut sınıflar türünden göstericiler tanımlanabilir + + Bir soyut sınıftan bir sınıf türetilip soyut sınıftaki bütün saf sanal fonksiyonlar override edilirse bu durumda türemiş sınıf soyut olmaz, somut (concrete) + hale gelir. Örneğin: + + class A { + public: + virtual void foo() = 0; + //... + }; + + class B : public A { + public: + void foo() override; + //... + }; + + B b; // geçerli, B soyut değil somut + + b.foo(); // geçerli, B::foo çağrılacak + + A *pa; // geçerli, A sınıfı türünden gösterici tanımlanabilir + + pa = &b; // geçerli + + pa->foo(); // geçerli, B::foo çağrılır + + Burada artık olmayan bir fonksiyonun çağrılması gibi potansiyel bir durum oluşmamaktadır. + + Soyut bir sınıftan türetilmiş olan bir sınıfta soyut sınıfın tüm saf sanal fonksiyonları override edilmemişse türemiş sınıf da soyut olur. Bu durumda türemiş + sınıf türünden de nesneler yaratılamaz. Örneğin: + + class A { + public: + virtual void foo() = 0; + virtual void bar() = 0; + //... + }; + + class B : public A { + public: + void foo() override; + //... + }; + + Burada B sınıfında A sınıfının yalnızca foo fonksiyonu override edilmiştir, bar fonksiyonu override edilmemiştir. Bu duurumda B sınıfı da soyut bir sınıftır. + B sınıfı türünden de nesneler yaratılamaz. Örneğişn: + + B b; // geçersiz! B sınıfı da soyut bir sınıf + + Pekiyi neden burada B sınıfı da soyut bir sınıftır? Çünkü artık B türünden bir nesne ile olmayan bir fonksiyonun çağrılması gibi potansiyel bir durum yine + oluşmuştur. Örneğin: + + B b; + + b.foo(); // B::foo var + b.bar(); // B sınıfında da A sınıfında da bar fonksiyonu yok! + + A soyut bir sınıf olsun. A sınıfından türetilen B sınıfında A sınıfının bazı saf sanal fonksiyonları override edilmiş olsun, bazıları ise override edilmemiş + olsun. Bu durumda B sınıfı da soyut bir sınıftır. Şimdi B sınıfından C sınıfını türetmiş olalım. C sınıfında da B sınıfının override etmediği A sınıfındaki + klan saf sanal fonksiyonları override etmiş olalım. Bu durumda C sınıfı somut bir sınıftır. Yani C ınıfı türünden nesneler yaratabiliriz. Örneğin: + + class A { + public: + virtual void foo() = 0; + virtual void bar() = 0; + //... + }; + + class B : public A { + public: + void foo() override; + //... + } + + class C : public B { + public: + void bar() override; + //... + }; + + + Burada A ve B sınıfları soyuttur. Ancak C sınıfı soyut değildir. + + Bir sınıfın soyut olup olmadığını anlamanın kolay bir yolu vardır. Eğer o sınıf türünden bir nesne yaratıldığında "olmayan bir fonksiyonun çağrılması gibi" + potansiyel bir durum oluşuyorsa o sınıf soyuttur, oluşmuyorsa o sınıf somuttur. Yukarıdaki örnekte C sınıfı türünden bir nesne yaratmış olalım: + + C c; + + Bu c nesnesi ile foo ve bar fonksiyonlarını çağırdığımızda bunlar gerçekten var mıdır? + + c.foo(); // B::foo çağrılır + c.bar(); // C::bar çağrılır + + O halde C sınıfı soyut değil somuttur. + + Saf sanal fonksiyonlar gövdeye sahip olmak zorunda değildir. ancak istenirse onlar için gövde de bulundurulabilir. Fakaty saf sanal fonksiyonlar için + gövde bulundurulması o sınıfları soyut olmaktan çıkarmamaktadır. Örneğin: + + class A { + public: + virtual void foo() = 0; + //... + }; + + void A::foo() + { + //... + } + + Bu tanımala tamamen geçerlidir. Ancak A sınıfı hala soyut bir sınıftır. Örneğin: + + A a; // geçersiz! A soyut bir sınıf + + Burada A sınıfı türünden bir nesne yaratılamayacağına göre A sınıfından türetilmiş olan somut sınıfların foo fonksiyonunu override etmiş olması gerektiğine + göre A sınıfının foo saf sanal fonksiyonunun gövdesinin olmasının ne anlamı vardır? İşte türemiş sınıf üye fonksiyonları bu durumda sanallağı ortadan kaldırarak + taban sınıfın saf sanal fonksiyonunu çağırabilmektedir. Bu konu izleyen paragraflarda ele alınmaktadır. + +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- Soyut sınıflar türünden nesneler yaratılmaz. Ancak göstericiler ve referanslar tanımlanabilir. Örneğin A bir soyut sınıf olsun B de bu soyut sınıftan türetilmiş @@ -26736,36 +28437,76 @@ int main() pa->foo(); // B::foo çağrılır r.foo(); // B::foo çağrılır + O halde örneğin daha önce yapmış olduğumuz Tetris örneğindeki düşen şekilleri temsil eden Shape sınıfı soyut bir sınıf olabilir: + + class Shape { + public: + virtual void move_down() = 0; + virtual void move_left() = 0; + virtual void move_right() = 0; + virtual void rotate() = 0; + //... + }; + + Artık biz zaten Shape sınıfının bu üye fonksiyonları için gövde bulundurmak zorunda değiliz. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bir soyut sınıftan türetme yapıldığında türemiş sınıfın taban soyut sınıftaki tüm saf sanal fonksiyonları override etmesi beklenir. Eğer türemiş sınıf taban - sınıftaki tüm saf sanal fonksiyonları override etmezse bu durumda tüemiş sınıf da soyut olur, türemiş sınıf türünden de nesneler yaratılmaz. Örneğin: + Pekiyi biz bir sanal fonksiyonu saf sanal yapmalı mıyız? Eğer bir sanal fonksiyonu saf sanal yaparsak bu durumda sınıfımız soyut olur. Başkalrı da bu sınıfı + doğrudan kullanamaz. Başkalarının bu sınıftan faydalanabilmesi için ondan türetme yapıp bu sanal fonkisyonu override etmeleri gerekir. Aksi takdirde türemiş + sınıf da soyut olacaktır. Ancak sanal fonksiyon saf yapılmazsa bu durumda o fonksiyonun default bir gerçekleştirimi olduğu için türemiş sınıflar bu fonksiyonu + override etmek zorunda kalmayacaklardır. + Bazen soyut sınıflar bir "kontrat" oluşlturmak için de kullanılabilmektedir. Örneğin biz bir kişinin bir sınıfı için foo, bar ve tar fonksiyonlarını muttlaka + yazmasını istiyorsak bu durumda soyut bir sınıfta bu fonksiyonları saf sanal olarak bulundurabiliriz ve o kişiden bu sınıfı taban sınıf olarak kullanmasını + isteyebiliriz. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 76. Ders 05/06/2024 - Çarşamba +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Çokbiçimli mekanizamanın devreye girdiği ilginç bir durum vardır. A sınıfından B sınıfının türetildiğini düşünelim. A sınıfındaki sanal olmayan foo + fonksiyonunun bar fonksiyonunu çağırdığını varsayalım. Eğer burada bu foo fonksiyonu B sınıfı türünden bir nesneyle çağrılırsa çağrılan bar fonksiyonu A + sınfının değil B sınıfının bar fonksiyonu olacaktır. Örneğin: + class A { public: - //... - virtual void foo() = 0; - virtual void bar() = 0; + void foo(); + virtual void bar(); //... }; - Burada A soyut (abstract) bir sınıftır. A sınıfı türünden nesneler yaratamayız. Şimdi A sınıfından B sınıfını türetelim: - class B : public A { public: - //... - void foo() override // yalnızca foo override edilmiş olsun - { - //... - } + void bar() override; //... }; - Burada B sınıfında yalnızca foo override edildiği için B sınıfı da soyuttur ve B sınıfı türünden de nesneler yaratamayız. + void A::foo() + { + bar(); + } - A soyut sınıfındaki saf sanal fonksiyonların bir bölümü ondan türetilmiş B sınıfında override edilmiş olsun. Bu duurmda B de soyut bir sınıftır. Ancak geri - kalan saf sanal fonksiyonlar B'den türetilmiş C override edilirse C artık soyut olmaz. + void A::bar() + { + cout << "A::bar" << endl; + } + + void B::bar() + { + cout << "B::bar" << endl; + } + + //... + + B b; + + b.foo(); + + Burada foo fonksiyonuna geçirilen this göstericisinin statik türü A, dinamik türü B'dir. foo fonksiyonunda bar fonksiyonun çağrılması aslında this->bar() + biçiminde yapılmaktadır. bar fonksiyonu sanal olduğuna ve thsi göstericisinin dinamik türü B olduğuna göre B sınıfının bar fonksiyonu çağrılacaktır. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #include @@ -26774,1670 +28515,2798 @@ using namespace std; class A { public: - virtual void foo() = 0; - virtual void bar() = 0; + void foo(); + virtual void bar(); //... }; class B : public A { public: - void foo() override - { - //... - } + void bar() override; //... }; -class C : public B { -public: - void bar() override - { - //... - } -}; +void A::foo() +{ + bar(); +} + +void A::bar() +{ + cout << "A::bar" << endl; +} + +void B::bar() +{ + cout << "B::bar" << endl; +} int main() { - A *pa = new C(); // geçerli, C soyut değil somut' - //... + B b; - delete pa; + b.foo(); return 0; } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Saf sanal fonksiyonların kullanılmasına yönelik Tetris örneğinin basit biimi aşağıdaki gibi olabilir. Bu örnekte taban Shape sınıfı "şekil kavramını" - temsil etmektedir. Bu Shape sınıfından sınıflar türetilmiş ve sanal fonksiyonlar override edilmiştir. Shape sınıfı türünden hiçbir nesne yaratılmayacağı için - Shape sınıfı soyut bir sınıf yapılmıştır. + Aşağıda bir komut satırı uygulamasının kaba kısmını yapan CommandPrompt soyut bir sınıf örneği verilmiştir. Komut satırı sınıfın run üye fonksiyonuyla + oluşturulmaktadır. run üye fonksiyonu stdin dosyasından komutu alıp onu boşluklardan parse ederek sınıfın protected bölümündeki m_params isimli bir vector'ün + içerisine yerleştirmektedir. Bu işlemi yaptıktan sonra run execute isimli saf sanal fonksiyonu çağırmaktadır. Bu saf sanal fonksiyon türemiş sınıfta override + edilerek komut satırının isteğe bağlı ayrıntıları fonksiyonda gerçekleştirileblecektir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#include -#include -#include -#include -#include +// commandprompt.hpp -using namespace std; +#ifndef COMMANDPROMPT_HPP_ +#define COMMANDPROMPT_HPP_ -class Shape { +#include +#include + +class CommandPrompt { public: - virtual void move_down() = 0; - virtual void move_left() = 0; - virtual void move_right() = 0; - virtual void rotate() = 0; - -private: - //... -}; - -class BarShape : public Shape { -public: - void move_down() override; - void move_left() override; - void move_right() override; - void rotate() override; -}; - -class TShape : public Shape { -public: - void move_down() override; - void move_left() override; - void move_right() override; - void rotate() override; -}; - -class ZShape : public Shape { -public: - void move_down() override; - void move_left() override; - void move_right() override; - void rotate() override; -}; - -class SquareShape : public Shape { -public: - void move_down() override; - void move_left() override; - void move_right() override; - void rotate() override; -}; - -class LShape : public Shape { -public: - void move_down() override; - void move_left() override; - void move_right() override; - void rotate() override; -}; - -class Tetris { -public: - Tetris(); + CommandPrompt() : m_prompt("CSD") + {} + CommandPrompt(const std::string &prompt) : m_prompt(prompt) + {} void run(); +protected: + virtual bool execute() = 0; + + std::vector m_params; private: - Shape *get_random_shape(); // static olabilir ancak henüz görmedik - //... -}; - -void BarShape::move_down() -{ - cout << "Barshape move down" << endl; -} - -void BarShape::move_left() -{ - cout << "Barshape move left" << endl; -} - -void BarShape::move_right() -{ - cout << "Barshape move right" << endl; -} - -void BarShape::rotate() -{ - cout << "Barshape rotate" << endl; -} - -void TShape::move_down() -{ - cout << "TShape move down" << endl; -} - -void TShape::move_left() -{ - cout << "TShape move left" << endl; -} - -void TShape::move_right() -{ - cout << "TShape move right" << endl; -} - -void TShape::rotate() -{ - cout << "TShape rotate" << endl; -} - -void ZShape::move_down() -{ - cout << "ZShape move down" << endl; -} - -void ZShape::move_left() -{ - cout << "ZShape move left" << endl; -} - -void ZShape::move_right() -{ - cout << "ZShape move right" << endl; -} - -void ZShape::rotate() -{ - cout << "ZShape rotate" << endl; -} - -void SquareShape::move_down() -{ - cout << "SquareShape move down" << endl; -} - -void SquareShape::move_left() -{ - cout << "SquareShape move left" << endl; -} - -void SquareShape::move_right() -{ - cout << "SquareShape move right" << endl; -} - -void SquareShape::rotate() -{ - cout << "SquareShape rotate" << endl; -} - -void LShape::move_down() -{ - cout << "LShape move down" << endl; -} - -void LShape::move_left() -{ - cout << "LShape move left" << endl; -} - -void LShape::move_right() -{ - cout << "LShape move right" << endl; -} - -void LShape::rotate() -{ - cout << "LShape rotate" << endl; -} - -Tetris::Tetris() -{ - srand(time(NULL)); -} - -void Tetris::run() -{ - Shape *pshape; - int ch; - - for (;;) { - pshape = get_random_shape(); - for (int i = 0; i < 25; ++i) { - pshape->move_down(); - Sleep(500); - if (_kbhit()) { - ch = _getch(); - switch (ch) { - case 'a': // move left - pshape->move_left(); - break; - case 'd': // move right - pshape->move_right(); - break; - case 's': // rotate - pshape->rotate(); - break; - case 'q': - goto EXIT; - } - } - - } - } -EXIT: - ; -} - -Shape *Tetris::get_random_shape() -{ - Shape *pshape; - - switch (rand() % 5) { - case 0: - pshape = new TShape(); - break; - case 1: - pshape = new ZShape(); - break; - case 2: - pshape = new SquareShape(); - break; - case 3: - pshape = new LShape(); - break; - case 4: - pshape = new BarShape(); - break; - } - - return pshape; -} - -int main() -{ - Tetris tetris; - - tetris.run(); - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Aslında sınıf (class) ve üye fonksiyon (member function), veri elemanı (data member) gibi kavramlar yapay kavramlardır. İşlemcilerimiz prosedürel teknikte - kodladığımız programları çalıştırmaya uygundur. Nesne yönelimli teknik yapay bir kavramdır. Gerçek makinenin çalışması C'deki gibidir, C++'taki gibi değildir. - Bu nedenle C++'ta bir sınıf aslında derlendiğinde C gibi kodlar üretilmektedir. Üye fonksiyonların sınıfın veri elemanlarına doğrudan erişmesi de makinede - mümkün değildir. Dolayısıyla sınıf faaliyet alanı da yapay bir kavramdır. Üye fonksiyonların sınıfın veri elemanlarına doğrudan erişmesi aslında derleyicilerin - üye fonksiyonlara gizlice geçirdikleri bir parametre yoluyla sağlanmaktadır. Derleyiciler üye fonksiyonun çağrılmasında kullanılan nesnenin adresini üye fonksiyona - gizlice (genellikle birinci parametre olarak) geçirirler ve veri elemanına erişmeyi bu gizli parametre yoluyla yaparlar. Bu gizlice geçirilen parametreye - this göstericisi denilmektedir. Aşağıdaki gibi bir sınıf söz konusu olsun: - - - class Sample { - public: - void set_val(int val); - void disp(); - private: - int m_val; - }; - - void Sample::set_val(int val) - { - m_val = val; - } - - void Sample::disp() - { - cout << m_val << endl; - } - - int main() - { - Sample s; - - s.set_val(10); - s.disp(); - - return 0; - } - - Aslında bu kod derlendiğinde makine kodları incelendiği zaman bu kodların tamamen C gibi ve aşağıdakine benzer olduğu görülür: - - #include - - using namespace std; - - struct Sample { - int m_val; - }; - - void Sample_set_val(struct Sample *this, int val) - { - this->m_val = val; - } - - void Sample_disp(struct Sample *this) - { - printf("%d\n", this->m_val); - } - - int main() - { - struct Sample s; - - Sample_set_val(&s, 10); - Sample_disp(&s); - - return 0; - } - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - this göstericisi üye fonksiyonlara gizlice geçiriliyor olsa da üye fonksiyon içerisinde açıkça kullanılabilir. this hangi sınıfın üye fonksiyonu içerisinde kullanılıyorsa - o sınıf türünden bir gösterici belirtmektedir. Bir üye fonksiyon içerisinde sınıfın m_val gibi bir veri elamanına doğrudan m_val ismiyle erişmekle this->m_val - biçiminde erişmek arasında hiçbir performans farklılığı yoktur. Zaten programcı bu elemana m_val biçiminde eriştiğinde aslında derleyici ürettiği kodda buna - this->m_val gibi erişmektedir. - - foo gibi bir üye fonksiyon içerisinde bar üye fonksiyonun aşağıdaki gibi çağrıldığını düşünelim: - - void Sample::foo() - { - //... - bar(); - //... - } - - Aslında bu çağrı tamamen this->bar() gibi yapılmaktadır. Yani bar fonksiyonuna geçirelecek this göstericisi aslında foo fonksiyonun this göstericisidir. - - void Sample::foo() - { - //... - this->bar(); // bar(); ile eşdeğer - //... - } - - const bir üye fonksiyon aslında ona gizlice geçirilen this göstericisinin const olduğu (gösterdiği yer const olduğu) bir fonksiyondur. - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bazen bir üye fonksiyonun geri dönüş değeri kendi sınıfı türünden bir sol taraf değeri referansı olur. Fonksion da *this ifadesiyle geri döner. Bu durumda *this - üye fonksiyonun çağrıldığı nesnedir. Dolayısıyla fonksiyon da aslında bize bu nesneyi geri döndürür. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class Sample { -public: - Sample &foo(); - void bar(); - //... -}; - -Sample &Sample::foo() -{ - cout << "Sample::foo" << endl; - - return *this; -} - -void Sample::bar() -{ - cout << "Sample::bar" << endl; -} - -int main() -{ - Sample s; - - s.foo().bar(); // s.foo(); s.bar(); ile eşdeğer etki - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Aslında bir sınıfın üye fonksiyonları ve veri elemanları static olabilir ya da olmayabilir. Biz şimdiye kadar hep static olmayan (nonstatic) üye fonksiyonlarını - ve veri elemanlarını kullandık. Gerçekten de static üye fonksiyonlar ve veri elemanları seyrek kullanıldığı için "üye fonksiyon" denildiğinde özellikle static'lik - belirtilmediyse static olmayan üye fonksiyon anlaşılmalıdır. Benzer biçimde "veri elemanı" denildiğinde de özellikle static'lik belirtilmediyse static olmayan - veri elemanı anlaşılmalıdır. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bazı global fonksiyonlar belli bir konuyla ilgili iş yapıyor olabilirler. Bu durumda o global fonskiyonların o konuya ilişkin sınıf ile ilişkilendirilmeleri - iyi bir tekniktir. Örneğin bir yılın artık yıl olup olmadığını belirleyen isleap isimli bir fonksiyon mantıksal ilgi nedeniyle Date sınıfın static bir üye fonksiyonu - yapılmalıdır. static üye fonksiyonlar sınıfın veri elemanlarını kullanamazlar. Eğer sınıfın veri elemanlarını kullanmayan fonksiyonlar sınıfın static olmayan - bir üye fonksiyonu olarak yazılırsa bu durumda gereksiz biçimde onun bir nesne ile çağrılma zorunluluğu ortaya çıkar. Halbuki bu fonksiyonlar sınıfın veri elemanlarını zaten kullanmamaktadır. - İşte bir üye fonksiyon static yapılırsa artık o üye fonksiyon bir nesne olmadan sınıf ismi ve :: operatörüyle (tabii public bölümdeyse) çağrılabilmektedir. Bir üye fonksyonu static yapabilmek için - sınıf bildirimi içerisindeki prototipin önüne "static" anahtar sözcüğü getirilir. static anahtar sözcüğü tanımlamada kullanılamaz. Örneğin: - - class Sample { - public: - void foo(); - static void bar(); - //... - }; - - void Sample::foo() - { - //... - } - - void Sample::bar() - { - //... - } - - int main() - { - Sample::foo(); // nesne olmadan sınıf ismiyle çağırabiliriz - - return 0; - } - - static üye fonksiyonlar nesne ile çağrılmadığı için onlara this parametresi geçirilmez. Dolayısıyla da static üye fonksiyonlar sınıfın static olmayan veri elemanlarını - ve static olmayan üye fonksiyonlarını kullanamazlar. - - Aslında static üye fonksiyonlar bir nesne, gösterici ya da referans yoluyla da çağrılabilirler. Bu çağrımada derleyici static üye fonksiyonun çağrıldığı - nesnenin türüne bakar ve üye fonksiyonun o sınıf ismi ile çağrıldığını kabul eder. Tabii bu durumda yine this göstericisi fonksiyona geçirilmemektedir. Örneğin: - - Sample::foo(); - - Sample s; - s.foo(); // tamamen Sample::foo() ile eşdeğer - - Sınıfın static üye fonksiyonları const üye fonksiyonlar yapılamaz. Zaten static üye fonksiyonlar sınııfn static olmayan veri elemanlarına erişemedikleri için - bunların const yapılmasının bir anlamı da yoktur. - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -// date.hpp - -#ifndef DATE_HPP_ -#define DATE_HPP_ - -class Date { -public: - Date() = default; - Date(int day, int month, int year); - void disp() const; - - int day() const { return m_day; } - void set_day(int day) { m_day = day; } - - int month() const { return m_month; } - void set_month(int month) { m_month = month; } - - int year() const { return m_year; } - void set_year(int year) { m_year = year; } - - static bool isleap(int year); - static Date today(); -private: - int m_day; - int m_month; - int m_year; + std::string m_prompt; + const static int MAX_CMD_LINE = 4096; }; #endif -// date.cpp +// commandprompt.cpp #include -#include -#include "date.hpp" +#include +#include "commandprompt.hpp" using namespace std; -Date::Date(int day, int month, int year) +void CommandPrompt::run() { - m_day = day; - m_month = month; - m_year = year; -} - -void Date::disp() const -{ - cout << m_day << '/' << m_month << '/' << m_year << endl; -} - -bool Date::isleap(int year) -{ - return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; -} - -Date Date::today() -{ - Date result; - - time_t t = time(NULL); - tm *pt = localtime(&t); - - result.m_day = pt->tm_mday; - result.m_month = pt->tm_mon + 1; - result.m_year = pt->tm_year + 1900; - - return result; + string cmdline; + char buf[MAX_CMD_LINE]; + char *str; + + for (;;) { + cout << m_prompt << '>'; + cin.getline(buf, 1024); + + m_params.clear(); + for (str = strtok(buf, " \t"); str != nullptr; str = strtok(nullptr, " \t")) + m_params.push_back(str); + + if (!execute()) + break; + } } // app.cpp #include -#include "date.hpp" +#include "commandprompt.hpp" using namespace std; +class MyCommandPrompt : public CommandPrompt { +public: + MyCommandPrompt(const string &prompt) : CommandPrompt(prompt) + {} +protected: + bool execute() override; +}; + +bool MyCommandPrompt::execute() +{ + if (m_params[0] == "exit") + return false; + + return true; +} + int main() { - Date date; + MyCommandPrompt mcp("CSD"); - cout << (Date::isleap(2000) ? "Artik" : "Artik degil") << endl; - date = Date::today(); - - date.disp(); + mcp.run(); return 0; } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Sınıfların static veri elamanlarının toplamda tek bir kopyası bulunur. static veri elemanları sınıf türünden nesnelerin içerisinde yer kaplamazlar. Yani - nesnelerin bir parçası değillerdir. O sınıf türünden nesne yaratılsa da yaratılmasa da her zaman bu tek kopya yaratılmış bir biçimde bulunur. Sınıf içerisinde - bu static veri elemanı kullanıldığında her zaman o tek olan kopya kullanılıyor durumdadır. Aslında sınıfın veri veri elemanaları pekala global bir nesne olabilecek - durumdadır. Ancak programcı onu global yapmak yerine sınıf ile mantıksal ilişkisi nedeniyle sınıfın içerisine yerleştirmiştir ve static yapmıştır. + Yıkıcı fonksiyonlar da sanal (virtual) olarak bildirilebilirler. Bular da türemiş sınıflarda override edilebilirler. Ancak yapıcı fonksiyonların sanal olması + söz konusu değildir. Örneğin: - Sınıfların static veri elemanları ismiyle de kullanılabilmektedir. Çünkü onlar aslında hiçbir nesnenin parçası değildir. Veri elemanını static yapmak için sınıf - bildiriminde static anahtar sözcüğü kullanılır. Ancak static veri elemanlarının sınıfın dışında bir tanımlamasının yapılması gerekir. Bu tanımlama sınıf ismi - ve çözünürlük operatörü ile yapılır. Tanımlama sırasında static anahtar sözcüğü kullanılmaz ancak ilkdeğer verilebilir. Örneğin: - - class Sample { - public: + class A { //... - int m_a; - int m_b; - static int m_c; + virtual ~A(); + }; + + class B : public A { + //... + ~B(); // override işlemi }; - int Sample::m_c; + Her ne kadar taban ve türemiş sınıftaki yıkıcı fonksiyonların isimleri farklı gibiyse de istisna olarak burada override işlemi yapılmamkatdır. + + Pekiyi yıkıcı fonksiyonların sanal yapılmasının amacı nedir? B sınıfının A sınıfından türetildiğini düşünelim. A sınıfında bazı sanal + fonksiyonlar B sınıfında override edilmiş olsun. Aşağıdaki duruma dikkat ediniz: - int main() - { - Sample::m_c = 10; + A *pa = new B(); - cout << Sample::m_c << endl; + Burada new B() işlemi ile türemiş sınıf türünden dinamik bir nesne yaratılmış ve onun adresi taban sınıf türünden bir gsöetericiye yerleştirilmiştir. new B() + işleminde B sınıfının yapıcı fonksiyonu çağrılacaktır. Tabii B sınıfının yapıcı fonksiyonu da A sınıfının yapıcı fonksiyonunu çalıştıracaktır. Şimbi biz + bu nesneyi delete operatörü ile serbest bırakmak isteyelim: - return 0; - } ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#include + delete pa; -using namespace std; + Burada delete operatörünün operand'ı taban sınıf türünden olduğu için delete operatörü A sınıfının yıkıcı fonksiyonunu çağıracaktır. Halbuki bir dengenin + sağlanması için B sınıfının yıkıcı fonksiyonun çağrılması gerekmektedir. İşte sanal yıkıcı fonksiyonlar bu işe yaramktadır. delete operatörü eğer operand'ı + olan sınıftaki yıkıcı fonksiyon sanal ise operand'ı olan göstericinin dinamik türüne ilişkin sınıfın override edilmiş sanal fonksiyonu çağırmaktadır. Örneğin: + + class A { + public: + A(); + virtual ~A(); + //... + }; + + class B : public A { + public: + B(); + ~B(); + //.... + }; -struct Sample { //... - int m_a; - int m_b; - static int m_c; -}; + A *pa = new B(); + delete pa; // B'nin yıkıcı fonksiyonu çağrılır -int Sample::m_c; + Burada delete pa ifadesinde pa göstericisinin dinamik türü B olduğu için artık B sınıfın yıkıcı fonksiyonu çağrılacaktır. Tabii bu yıkıcı fonksiyon da + A sınıfının yıkıcı fonksiyonunu çağıracaktır. + + override anahtar sözcüğü yıkıcı fonksiyonlarla da kullanılabilir. Örneğin: -int main() -{ - Sample::m_c = 10; + class A { + public: + A(); + virtual ~A(); + //... + }; - cout << Sample::m_c << endl; + class B : public A { + public: + B(); + ~B() override; // geçerli + //.... + }; - return 0; -} + Ancak taban sınıfın yıkıcı fonksiyonunun sanal olup olmadığının bilinmesine gerek duyulmadan kod oluşturmak için yıkıcı fonksiyonlarda bu override anahtar + sözcüğünü kullanmayabilirsiniz. -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Sınıfın static veri elemanlarının da private bölüme konularak onlara public getter/setter fonksiyonlar ile erişilmesi veri gizlemesi (data hidening) bakımından - tavsiye edilmektedir. Tabii bu durumda getter/setter fonksiyonların static üye fonksiyon olması anlamlıdır. static üye fonksiyonlar sınıfın diğer static üye - fonksiyonlarını doğrudan çağırabilir ve sınıfın static veri elemanlarını doğrudan kullanabilir. Ancak static olmayan elemanlarını kullanamaz. + Daha önce yapmış olduğumuz Tetris örneğindeki nesne yaratma işlemine dikkat ediniz: - Sınıfın static veri elemanlarına yine o sınıf türünden nesne, gösterici ya da referans yoluyla erişebiliriz. Bu durumda erişimde kullanılan nesne aslında yalnızca - sınıfın türünü belirtmektedir. Bu biçimde erişimler yanlış anlaşılmaya yol açabilmektedir. Bu nedenle static veri elemanlarına sınıf ismiyle erişmeye gayret etmelisiniz. + Shape *shape; + //... + + shape = get_random_shape(); + //... + + delete shape; + + Burada get_random_shape fonksiyonu bize Shape sınıfından türetilmiş olan dinamik bir nesnenin adresini vermektedir. Ancak biz bu nesnenin dinamik türünü + bilmemekteyiz. Bu gösterici ile delete işlemi yaptığımızda dinamik türe ilişkin sınıfın yıkıcı fonksiyonunun çalışabilmesi için Shape sınıfındaki yıkıcı + fonksiyonun sanal olması gerekir. Tabii bizim basit örneğimizde bu sınıflarda yıkıcı fonksiyonlar yoktu. Bu durumda derleyici onları içi boş bir biçimde + bizim için yazmıştı. Bu nedenle bu basit örneğimizde bunedenle yıkıcı fonksiyonun sanal yapılmaması bir soruna yol açmamaktadır. Ancak böylesi bir durumda + yıkıcı fonksiyonların sanal olması gerekmektedir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#include - -using namespace std; - -class Sample { -public: - Sample(int a = 0, int b = 0) : m_a(a), m_b(b) - { - ++m_count; - } - static int count() { return m_count; } - static void set_count(int count) { m_count = count; } - -private: - int m_a; - int m_b; - static int m_count; -}; - -int Sample::m_count; - -int main() -{ - Sample s; - Sample k; - Sample z; - - cout << Sample::count() << endl; - - return 0; -} - /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Biz sınıfın üye fonksiyonlarına ve veri elemanlarına "sınıfın elemanları" diyelim. static olmayan üye fonksiyonlarına ve static olmayan veri elemanlarına da "static olmayan elemanları" - diyelim. Bu durumda static üye fonksiyonlar sınıfın yalnızca static elemanlarını kullanabilirler. Ancak static olmayan üye fonksiyonlar sınıfın hem static olmayan - elemanlarını hem de static olan elemanlarını kullanabilirler. + C++'ta Türemiş sınıf türünden new işlemi yapılıp bu alanın taban sınıf türünden bir gösterici ile delete edilmesi durumunda taban sınıftaki yıkıcı fonksiyon + sanal değilse tanımsız davranış olarak değerlendirilmektedir. Bazı durumlarda (basit Tetris örneğimizde olduğu gibi) bu tanımsız davranış bir soruna yol + yol açmayabilir. Ancak pek çok durumda da ciddi sorunlara yol açabilmektedir. Örneğin: + + class A { + public: + A(); + virtual ~A(); + //... + }; + + class B : public A { + public: + B(const string &s) : m_s(s) + {} + ~B(); + private: + string m_s; + }; + //... + + A *pa = new B("ankara"); + //... + delete pa; + + Burada B sınıfı yerine A sınıfının yıkıcı fonksiyonunun çağrılması B sınıfının m_s string veri elemanı için yıkıcı fonksiyonun çağrılamamasına yol açacaktır. + Bu da en azından bir bellek sızıntısı oluşturacaktır. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -// date.hpp +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Pekiyi sınıfımız için yıkıcı fonksiyonu ne zaman sanal yapmalyız? Kabaca "eğer sınıfımızda en az bir sanal fonksiyon varsa" sınıfımızdaki yıkıcı fonksiyonun + da sanal yapılması uygun bir tekniktir. Çünkü bu sınıfı kullanacak kişiler yukarıdaki gibi çokbiçimli işlemleri yapabilirler. Bizim de başkaları tarafından + yazılmış olan sınıflardaki yıkıcı fonksiyonların sanal olup olmadığına dikkat etmemiz gerekebilir. Eğer taban sınıfı yazanlar yıkıcı fonksiyonu sanal yapmamışlarsa + biz türemiş sınıf türünden dinamik biçimde tahsis ettiğimiz nesneleri yine türemiş sınıf türünden adreslerle serbest bırakmalıyız. Tabii bunun için dinamik + tahsis etmiş olduğumuz nesnenin türünü bilmemiz gerekir. Sınıfımız için yıkıcı fonksiyonu hiç yazmazsak derleyicinin yazdığı yıkıcı fonksiyonun sanal olmadığına + dikkat ediniz. Ancak sanal yıkıcı fonksiyon defaulted hale de getirilebilmektedir. Örneğin: -#ifndef DATE_HPP_ -#define DATE_HPP_ - -class Date { -public: - Date() = default; - Date(int day, int month, int year); - void disp() const; - - int day() const { return m_day; } - void set_day(int day) { m_day = day; } - - int month() const { return m_month; } - void set_month(int month) { m_month = month; } - - int year() const { return m_year; } - void set_year(int year) { m_year = year; } - - const char *get_day_name() const; - - static bool isleap(int year); - static Date today(); - -private: - static long get_total_days(int day, int month, int year); - static const char *get_day_name(int day, int month, int year); -private: - int m_day; - int m_month; - int m_year; - - static int ms_montab[12]; - static const char *ms_monnames[7]; -}; - -#endif - -// date.cpp - -#include -#include -#include "date.hpp" - -using namespace std; - -int Date::ms_montab[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -const char *Date::ms_monnames[7] = {"Pazar", "Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi"}; - -Date::Date(int day, int month, int year) -{ - m_day = day; - m_month = month; - m_year = year; -} - -void Date::disp() const -{ - cout << m_day << '/' << m_month << '/' << m_year << '-' << get_day_name() << endl; -} - -const char *Date::get_day_name() const -{ - return get_day_name(m_day, m_month, m_year); -} - -bool Date::isleap(int year) -{ - return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; -} - -Date Date::today() -{ - Date result; - - time_t t = time(NULL); - tm *pt = localtime(&t); - - result.m_day = pt->tm_mday; - result.m_month = pt->tm_mon + 1; - result.m_year = pt->tm_year + 1900; - - return result; -} - -long Date::get_total_days(int day, int month, int year) -{ - long total = 0; - - for (int i = 1900; i < year; ++i) - total += isleap(i) ? 366 : 365; - - ms_montab[1] = isleap(year) ? 29 : 28; - for (int i = 0; i < month - 1; ++i) - total += ms_montab[i]; - - total += day; - - return total; -} - -const char *Date::get_day_name(int day, int month, int year) -{ - auto tdays = get_total_days(day, month, year); - - return ms_monnames[tdays % 7]; -} - -#include -#include "date.hpp" - -using namespace std; - -int main() -{ - Date d(23, 4, 1920); - - d.disp(); - - cout << d.get_day_name() << endl; - - return 0; -} + A { + //; + virtual ~A() = dafult; + }; +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- Global bir fonksiyon ya da başka bir sınıfın üye fonksiyonu bir sınıfın arkadaş fonksiyonu yapılabilir. Bu durumda arkadaş yapılan fonksiyon özel bir - erişim ayrıcalığına sahip olur. Arkadaş fonksiyonlar içeriisinde arkadaş olunan sınıf türünden bir nesne, gösterici ya da referans yoluyla o sınıfın tüm - bölümlerine erişebiliriz. friend bildirimi sınıfın herhangi bir bölümünde yapılabilir. Hangi bölümünde yapıldığının bir önemi yoktur. + erişim ayrıcalığına sahip olur. Arkadaş fonksiyonlar içeriisinde (parametre değişkenleri de dahil olmak üzere) arkadaş olunan sınıf türünden bir nesne, + gösterici ya da referans yoluyla o sınıfın tüm bölümlerine erişebiliriz. friend bildirimi sınıfın herhangi bir bölümünde yapılabilir. Hangi bölümünde + yapıldığının bir önemi yoktur. - Global bir fonksiyon friend yapıldığında friend bildirimi için o global fonksiyonun prototipinin ya da tanımlamasının görülmüş olması gerekmez. Ancak - friend bildirimi dışarısı için bir prototip bildirimi olarak kullanılamaz. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class Sample { -public: - Sample() = default; - Sample(int a) : m_a(a) - {} - friend void foo(); -private: - int m_a; -}; - -void foo() -{ - Sample s; - - s.m_a = 10; // geçerli çünkü foo friend - - cout << s.m_a << endl; // geçerli çünkü foo friend -} - -int main() -{ - foo(); - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Arkadaş sınıf bir sınıfın tüm üye fonksiyonlarının arkadaş olduğu anlamına gelmektedir. Bir sınıfı arkadaş yapabilmek için "friend class" bildirimi gerekir. - Örneğin: - - class Mample { - public: - void foo(); - void bar(); - //... - }; + Global bir fonksiyon friend yapıldığında friend bildirimi için o global fonksiyonun prototipinin ya da tanımlamasının daha önceden görülmüş olması gerekmez. + Ancak friend bildirimi dışarısı için bir prototip bildirimi olarak kullanılamaz. Örneğin: class Sample { //... - friend class Mample; + friend void foo(); + private: + int m_a; + int m_b; }; + //... - Burada Mample sınıfı Sample sınıfının arkadaş sınıfıdır. Yani Mample içerisindeki foo ve bar üye fonksiyonlarında Sample türünden bir nesne, gösterici ya da referenas - yoluyla Sample sınıfının her bölümüne erişilebilir. Arkadaş bildiriminde arkadaş olarak bildirilen sınıf daha sonra bildirilebilir. Örneğin: - - class Sample { - //... - friend class Mample; - }; - - class Mample { - public: - void foo(); - void bar(); - //... - }; - - Tabii bunun için friend anahtar sözüğünün yanında class anahtar sçzcüğünün bulundurulması gerekir. Eğer arkadaş sınıf daha yukaıda bildiriliyorsa bu durumda - class anahtar sözcüğünün bulundurulması gerekmez. Örneğin: - - class Mample { - public: - void foo(); - void bar(); - //... - }; - - class Sample { - //... - friend Mample; // geçerli - }; ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class Sample { -public: - Sample() = default; - Sample(int a) : m_a(a) - {} - friend class Mample; -private: - int m_a; -}; - -class Mample { -public: void foo() { Sample s; - s.m_a = 10; // geçerli, Mample sınıfı Sample sınıfıın arkadaşı + s.m_a = 10; // geçerli foo fonksiyonun Sample sınıfına bir erişim yarıcalığı var, Sample sınıfının her bölümüne erişebilir. + s.m_b = 20; // geçerli foo fonksiyonun Sample sınıfına bir erişim yarıcalığı var, Sample sınıfının her bölümüne erişebilir. + //... } - void bar() + Burada Smaple sınıfı içerisindeki foo fonksiyonu henüz prototipi ya da tanımalaması görülmeden friedn yapılmıştır. foo fonksiyonunun friend yapılması onun + Sample sınıfının her bölümüne nesne, gösterici ya da referans yoluyla erişebilmesini sağlamaktadır. + + Sınıf hangi isim alanında bildirilmişse friend fonksiyon da o isim alanında aranmaktadır. Örneğin: + + namespace CSD + { + + class Sample { + //... + friend void foo(); + private: + int m_a; + int m_b; + }; + //... + } + + void foo() { Sample s; - s.m_a = 10; // geçerli, Mample sınıfı Sample sınıfıın arkadaşı + s.m_a = 10; // geçersiz! friend olan foo bu foo değil! + s.m_b = 20; // geçersiz! friend olan foo bu foo değil //... } -}; -int main() -{ - //... + Burada Sample sınıfında friend yapılan foo CSD isim alanındaki foo fonksiyonudur. Çünkü Sample sınıfı CSD isim alanında bildirilmiştir. - return 0; -} + Tabii başka bir isim alanındaki (global isim alanı da dahil olmak üzere) bir fonksiyon da friend fonksiyon yapılabilir. Ancak bu durumda fonksiyonun bildiriminin + ya da tanımlamasının daha önce görülmüş olması gerekir. Örneğin: -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Örneğin bir bağlı liste bir sınıfla temsil edilmiş olsun. Onun düğümleri de bir sınıfla temsil edilmiş olabilir. Bu durumda bağlı listenin bu düğümlerin - private elemanlarına erişmesi gerekebilmektedir. İşte Node sınıfında bağlı sınıfını arkadaş sınıf yapabiliriz. + namespace Mample + { + //... + } + + namespace CSD + { + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend void ::foo(); // geçersiz! bu noktaya kadar global isim alanında foo fonksiyonun görülmesi gerekirdi + friend void Mample::foo(); // geçersiz! bu noktaya kadar Mample isim alanında foo fonksiyonun görülmesi gerekirdi + private: + int m_a; + int m_b; + }; + } + + Buradaki friend fonksiyonlar nitelikli bir biçimde bildirilmiş olduğu için friend bildirimine kadar onların bildiriminin görülmesi gerekmektedir. + + Ancak friend bildirimi dışarısı için bir prototip bildirimi yerine geçmemektedir. Örneğin: + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend void foo(); + private: + int m_a; + int m_b; + }; + + int main() + { + foo(); // geçersiz! foo fonksiyonunun prototipi ya da tanımlaması görülmedi + + return 0; + } + + void foo() + { + Sample s(10, 20); + + cout << s.m_a << ", " << s.m_b << endl; + + } + + Burada main içerisinde foo fonksiyonunun çağrılması geçersizdir. Çünkü henüz derleyici foo fonksiyonunun prototiğini görmemiştir. Ancak main fonksiyonunun + yukarısana foo için bir prototip yerleştirilirse sorun giderilecektir. + + friend bir fonksiyonun tanımlaması sınıf içerisinde yapılabilir. Bu fonksiyon sınıfın içinde bulunduğu isim alanınındaki global bir fonksiyon olarak + değerlendirilmektedir. Ancak yine bu tanımalama da dışarısı için etkili olmamaktadır. Örneğin: + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + + friend void foo() // foo global isim alanında inline bir fonksiyon olarak tanımlanmaktadır + { + Sample s(10, 20); + + cout << s.m_a << ", " << s.m_b << endl; + } + private: + int m_a; + int m_b; + }; + + int main() + { + foo(); // geçersiz! foo fonksiyonunun prototipi ya da tanımlaması görülmedi + + return 0; + } + + Burada Sample sınıfının içerisinde tanımlanan foo aslında global isim alanındaki bir foo fonksiyonudur. Ancak yine de main içerisinde foo fonksiyonun kullanılması + için bir prototip bildiriminin yaılmış olması gerekmektedir. Yukarıdaki örnekte main fonksiyonunun yukarısına foo fonksiyonunuın prototiği yerleştirilirse + sorun ortadan kalkacaktır. + + Tanımlaması sınıf bildiriminin içerisine yapılmış olan friend fonksiyonlar normal üte fonksiyonlarda olduğu gibi inline kabul edilmektedir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -// linkedlistint.hpp +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Başka bir sınıfın üye fonksiyonu da arkadaş fonksiyon olabilir. Atbii bu duurmda niteliklendirmenin yapılması gerekir. Örneğin: -#ifndef LINKEDLISTINT_HPP -#define LINKEDLISTINT_HPP + class Mample + { + public: + void foo(); + }; + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend void Mample::foo(); // void Mample::foo() fonksiyonu arkadaş fonksiyon + + private: + int m_a; + int m_b; + }; + + void Mample::foo() + { + Sample s(10, 20); + + cout << s.m_a << ", " << s.m_b << endl; // geçerli + } + + int main() + { + Mample m; + + m.foo(); + + return 0; + } + + Burada Sample sınıfı içerisinde Mample sınıfının foo fonksiyonu arkadaş fonksiyon yapılmıştır. Sınıf ismi belirtilirken niteliklendirme yapıldığı için + ilgili sınıfın üye fonksiyon bildiriminin dah ayukarıda görülmesi gerekmektedir. Bu durumda eksik bildirim (incomplete declarations) uygulanamaz. Örnepşn: + + class Mample; + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend void Mample::foo(); // geçersiz! + + private: + int m_a; + int m_b; + }; + +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Bir sınıfın tamamı da arkadaş sınıf yapılabilir. Arkadaş sınıf demek kabaca o sınıfın tüm üye fonksiyonlarının arkadaş fonksiyon olması demektir. Örneğin. + + class Mample { + public: + void foo(); + void bar(); + //... + }; + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend Mample; + private: + int m_a; + int m_b; + }; + + Burada Mample sınıfı Sample sınıfının arkadaş sınıfıdır. Yani Mample sınıfının foo ve bar üye fonksiyonları Sample sınıfının her bölümüne erişebilir. + + Arkadaş sınıf bildiriminde arkadaş yapılan sınıf yukarıda eksik (incomplete) biçimde bildirilebilir. Sınıfın asıl bildirimi daha sonra da yapılabilir. + Örneğin: + + class Mample; + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend Mample; + private: + int m_a; + int m_b; + }; + + Mample *pm; // geçerli eksik bildirim ile gösterici ya da referans tanımlanabilir + + class Mample { + public: + void foo(); + void bar(); + //... + }; + + Burada Mample sınıfı için yukarıda eksik bildirim yapılmıştır. Sınıf daha sonra bildirilebilir. Aslında yukarıya eksik bildirim yapmak yerine friend anahtar + sözcüğünün yanına class anahtar sözcüğü de eklenebilirdi. Örneğin: + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend class Mample; + private: + int m_a; + int m_b; + }; + + class Mample { + public: + void foo(); + void bar(); + //... + }; + + Tabii bu durumda Mample sınıfının Sample sınıfı içerisinde arkadaş olarak bildirilmiş olması dışarısı için bir eksik bildirim (incomplete declaration) + oluşturmamaktadır. Örneğin. + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend class Mample; + private: + int m_a; + int m_b; + }; + + Mample *pm; // geçersiz! Smaple sınıfındaki bildirim burada etkili olmamaktadır + + class Mample { + public: + void foo(); + void bar(); + //... + }; + + Bir sınıfın başka bir sınıfın arkadaşı olmasının onun bütün fonksiyonlarının o sınıfın arkadaşı olması gibi bir anlama geldiğini söylemiştik. Ancak arkadaş + olunan sınıfa erişim ayrıcalığı yalnızca üye fonksiyonlar için değil sınıf bildirimi için de söz konusudur. Örneğin: + + class Sample { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + //... + friend class Mample; + private: + enum Color { Red, Green, Blue}; + + int m_a; + int m_b; + }; + + class Mample { + public: + void foo() + {} + void bar() + {} + + Sample::Color m_color; // geçerli + }; + + Burada Mample sınıfının bildirimindeki Sample::Color türü Sample sınıfının private bölümünde bildirilmiştir. Eğer Mample arkadaş sınıf olmasaydı bu türü + kullanamazdı. Yani arkadaşlık aslında sınıfın gövdesini de kapsamaktadır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Pekiyi bir sınıfın arkadaş fonksiyonu o sınıfın taban sınıfının da her bölümüne erişebilir mi? Taban sınıfın public ve protected bölümleri türetme biçimine + göre türemiş sınıfın public, protected ve private bölümleri gibi darandığına göre arkadaş fonksiyon arkadaş olunan sınıfın taban sınıflarının public ve + protected bölümlerine erişebilir. Ancak taban sınııfn private bölümü hiçbir zaman türemiş sınıf tarafından kalıtım yoluyla alınmamaktadır (inherit edilmemektedir). + Bu nedenle arkadaş fonksiyon taban sınıfın private bölümüne erişemez. Örneğin: + + class Base { + public: + int m_x; + protected: + int m_y; + private: + int m_z; + }; + + class Sample : public Base { + public: + Sample(int a, int b) : m_a(a), m_b(b) + {} + friend void foo(); + private: + int m_a; + int m_b; + }; + + void foo() + { + Sample s(10, 20); + + cout << s.m_a << ", " << s.m_b << endl; // geçerli + cout << s.m_x << endl; // geçerli + cout << s.m_y << endl; // geçerli + cout << s.m_z << endl; // geçersiz! + } + + Bu örnekte taban sınıfın public nölümü türemiş sınıfın public bölümüymüş gibi, taban sınıfın protected bölümü türemiş sınıfın protected bölümüymüş gibi + işlem görmektedir. Taban sınıfın private bölümü ise tamamen erişime kapalıdır. Arkadaş foo fonksiyonu Sample sınıfının her bölümüne erişebildiğine göre + Base sınıfının public ve protected bölümlerine de erişebilir. Tabii burada türetme biçimi protected ya da private olsaydı da bir şey değişmeyecekti. (Ancak + bir dizi türetme yapıldığında durum farklılaşabilecektir.) + + Türemiş bir sınıf arkadaş sınıf yapıldığında bu durum onun taban sınıflarının arkadaş yapıldığı anlamına gelmemektedir. Benzer biçimde taban sınıf arakadaş + yapıldığında bu durum türemiş sınıfların arkadaş yapıldığı anlamına gelmemektedir. (Bir benzetmeyle durumu şöyle açıklayabiliriz. Biz birisine vekalet + verdiğimizde onun babası bu vekalate sahipmiş gibi davranamaz. Benzer biçimde babaya vekalet verdiğimizde onun çocuğu da vekalete sahipmiş gibi davranamaz.) +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 78. Ders 12/06/2024 - Çarşamba +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Pekiyi C++'ta arkdaşlık gerçekten gerekli midir? Arkadaş fonksiyonların ve sınıfların arkadaş olunan sınıfın private bölümüne erişimi kapsülleme ve "veri + elemanlarının gizlenmesi" prensiplerine aykırıdır. Örneğin sınıfın private elemanları değiştirilirse yalnızca sınıfın üye fonksiyonlarının içini değil arkadaş + fonksiyonların içini de yeniden düzenlemek gerekir. İşte arkadaşlık mutlak anlamda gerekli değildir. Örneğin Java ve C# gibi dillerde böyle bir özellik yoktur. + Ancak bazı durumlarda arkadaşlık yazım kolaylığı ve pratiklik sunmaktadır. Bu özelliğin kısıtlı biçimde gerektiğinde kullanılması tavsiye edilebilir. + + Örneğin bir bağlı liste sınıfı yazacak olalım. Bu sınıfta bağlı listenin düğümleri başka bir sınıfla temsil ediliyor olsun: + + class Node { + //... + }; + + class LinkedList { + //... + }; + + + Burada LinkedList sınıfının üye fonksiyonları Node sınıfını kullanmaktadır. Bu durumda LinkedList sınıfı Node sınıfının arkadaş sınıfı yapılırsa + LinkedList sınıfının üye fonksiyonları Node sınıfının her bölümüne erişebilir. Örneğin: + + class Node { + public: + Node(int val) : m_val(val) + {} + friend class LinkedList; + private: + int m_val; + Node *m_next; + Node *m_prev; + }; + + class LinkedList { + public: + LinkedList() : m_head(nullptr), m_tail(nullptr), m_count(0) + {} + ~LinkedList(); + void add(int val); + void walk() const; + //... + private: + Node *m_head; + Node *m_tail; + std::size_t m_count; + }; + + Burada Node sınıfı dış dünyadan gizlenmemiştir. Ancak LinkedList sınıfına bir erişim ayrıcalığı verilmiştir. Tabii alternatif olarak Node sınıfı LinkedList + sınfının private bölümünde bulundurulabilirdi ve Node sinıfının tüm elemanları public de yapılabilirdi. + + Aşağıda örneğin daha somut bir biçimi verilmiştir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +// linkedlist.hpp + +#ifndef LINKEDLIST_HPP_ +#define LINKEDLIST_HPP_ #include -class Node { -public: - Node() = default; - Node(int val) : m_val(val), m_next(nullptr) - {} - friend class LinkedListInt; -private: - Node *m_next; - int m_val; -}; +namespace CSD +{ + class Node { + public: + Node(int val) : m_val(val) + {} + friend class LinkedList; + private: + int m_val; + Node *m_next; + Node *m_prev; + }; -class LinkedListInt { -public: - LinkedListInt() : m_head(nullptr), m_tail(nullptr), m_size(0) - {} - ~LinkedListInt(); - Node *add_tail(int val); - void disp() const; - std::size_t size() const { return m_size; } -private: - Node *m_head; - Node *m_tail; - std::size_t m_size; -}; + class LinkedList { + public: + LinkedList() : m_head(nullptr), m_tail(nullptr), m_count(0) + {} + ~LinkedList(); + void add(int val); + void walk() const; + std::size_t count() { return m_count; } + //... + private: + Node *m_head; + Node *m_tail; + std::size_t m_count; + }; +} #endif -// linkedlistint.cpp +// linkedlist.cpp #include -#include "linkedlistInt.hpp" +#include "linkedlist.hpp" using namespace std; -Node *LinkedListInt::add_tail(int val) +namespace CSD { - Node *new_node = new Node(val); + void LinkedList::add(int val) + { + Node *new_node = new Node(val); - if (m_head != nullptr) - m_tail->m_next = new_node; - else - m_head = new_node; + if (m_tail != nullptr) + m_tail->m_next = new_node; + else + m_head = new_node; - m_tail = new_node; + new_node->m_prev = m_tail; + new_node->m_next = nullptr; + m_tail = new_node; - ++m_size; + ++m_count; + } - return new_node; -} + void LinkedList::walk() const + { + Node *node; -void LinkedListInt::disp() const -{ - for (Node *node = m_head; node != nullptr; node = node->m_next) - cout << node->m_val << " "; - cout << endl << m_size << " element listed" << endl; -} + for (node = m_head; node != nullptr; node = node->m_next) + cout << node->m_val << ' '; + cout << endl; + } -LinkedListInt::~LinkedListInt() -{ - Node *node, *temp; + LinkedList::~LinkedList() + { + Node *node, *temp_node; - node = m_head; - while (node != nullptr) { - temp = node->m_next; - delete node; - node = temp; + node = m_head; + + while (node != nullptr) { + temp_node = node->m_next; + delete node; + node = temp_node; + } } } // app.cpp #include -#include "LinkedListInt.hpp" +#include "linkedlist.hpp" using namespace std; +using namespace CSD; int main() { - LinkedListInt lli; + LinkedList ll; for (int i = 0; i < 100; ++i) - lli.add_tail(i); + ll.add(i); - lli.disp(); + ll.walk(); return 0; } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Operatör fonksiyonları (operator ovrloading) C++'ın yanı sıra C#, Swift, Python gibi dillerde de olan bir özelliktir. Operatör fonksiyonları sayesinde sınıf nesneleri - sanki temel türlerden nesnelermiş gibi +, -, * gibi operatörlerle işleme sokulabilmektedir. Operatör fonksiyonları aslında dile ilave bir işlevsel katmaz. - Yalnızca okunabilirlilk sağlamaktadır. + Arkadaş fonksiyonlar özellikle operatör fonksiyonları konusunda sıkça kullanılmaktadır. İzleyen paragraflarda operatör fonksiyonları konusu ele alınmaktadır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - Operatör fonksiyonları bir sınıfın static olmayan üye fonksiyonları biçiminde yazılabilir ya da global bir fonksiyon biçiminde yazılabilir. Operatör fonksiyonlarının - genel biçimi şöyledir: +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Operatör fonksiyonları (operator overloading) C++'ın yanı sıra C#, Swift, Python gibi dillerde de olan bir özelliktir. Operatör fonksiyonları sayesinde sınıf + nesneleri sanki temel türlerden nesnelermiş gibi +, -, * gibi operatörlerle işleme sokulabilmektedir. Operatör fonksiyonları aslında dile ilave bir işlevsel + katmaz. Yalnızca okunabilirlik sağlamaktadır. Örneğin Java'da opretaor fonksiyonları (metotları) yoktur. Sınıflarla ilgili işlemler normal fonksiyon çağırma + sentaksıyla yapılmaktadır. - operator ([parametre bildirimi]) - { + C++'ta operatör fonksiyonları oldukça ayrıntılı bir konudur. Pek çok nesne yönelimli programlama dilince bu özellik C++'a kıyasla daha basit ve ayrıntısız + biçimde tasarlanmıştır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Operatör fonksiyonlarının ne işe yaradığını basit bir örnekle açıklayalım. Bir Complex sayı sınıfı yazmak istediğimizi düşünelim. Aynı zamanda bu sınıf iki + Complex sayı üzerinde toplama, çıkartma, çarpma, bölme gibi işlemleri yapabiliyor olsun. Pekiyi bu işlemleri bu sınıfa nasıl yaptırabiliriz? Şüphesiz en doğal + yol sınıfa bu işlemleri yapabilecek üye fonksiyonlar yerleştirmektir. Yerleştirilen bu üye fonksiyonlar statik yapılabilir ya da yapılmayabilir. Örneğin: + + class Complex { + public: + Complex() = default; + Complex(double real, double imag = 0) : m_real(real), m_imag(imag) + {} + void disp() const; + Complex add(const Complex &z) const; //... + private: + double m_real; + double m_imag; + }; + + Buradaki static olmayan add üye fonksiyonu iki Complex sayıyı toplayıp sonucu bir Complex nesnesi biçiminde vermektedir. Bu fonksiyon aşağıdaki gibi kullanılabilir: + + Complex x{3, 2}, y{4, 3}, result; + + result = x.add(y); + + Burada x nesnesiin adresi add üye fonksiyonuna this göstericisi olarak aktarılacaktır. y nesnesi de z referansına yine adres yoluyla aktarılacaktır. Yani add + içerisinde doğrudan kullanılan m_real ve m_imag aslında x nesnesinin elemanları, z ile kullanılan m_real ve m_imag ise y nesnesinin elemanlarıdır. Üye + fonksiyon şöyle yazılabilir: + + Complex Complex::add(const Complex &z) const + { + Complex result; + + result.m_real = m_real + z.m_real; + result.m_imag = m_imag + z.m_imag; + + return result; } - Operatör fonksiyonları tamamen normal bir fonksiyon gibidir. Bunların tek farkı isimlerinin operator anahtar sözcüğü ve operatör sembolünden oluşmasıdır. + Burada bir sorun yoktur. Ancak yukarıdaki işlem aşağıdaki gibi ifade edilseydi daha sade ve daha doğal bir görünüm oluşurdu: - Operatör fonksiyonlarının geri dönüş değerleri herhangi bir biçimde olabilir. Ancak operatör fonksiyonlarının parametre sayıları üzerinde kısıt vardır. Şöyle ki: + result = x + y; - - Operatör fonksiyonu iki operandlı bir operatöre ilişkinse onu üye operatör fonksiyon olarak yazarken fonksiyon tek parametreye, global operatör fonksiyonu olarak yazarken - fonksiyon iki parametreye sahip olmak zorundadır. + İşte operatör fonksiyonları operatör işlemlerinin bu biçimde doğal olarak yazılmasını sağlamaktadır. Yukarıda da belirttiğimiz biz işlemi daha doğal olarak + x + y biçiminde yazsak da aslında izleyen paragraflarda göreceğiniz gibi arka planda operatör fonksiyonu denilen bir fonksiyon çağrılmaktadır. Operatör + fonksiyonları yalnızca okunabilirlik ve algı bakımından bir fayda sağlamaktadır. - - Operatör fonksiyonu tek operandlı bir operatöre ilişkinse onu üye operatör fonksiyon olarak yazarken fonksiyon sıfır parametreye, global operatör fonksiyonu olarak yazarken - fonksiyon bir parametreye sahip olmak zorundadır. + Pekiyi biz yukarıdaki örnekte bir Complex sayı ile bir double sayıyı toplamak istesek ne yapabiliriz? C++'ta farklı farklı parametrik yapılara ilişkin + aynı isimli fonksiyonlar bulnabileceğine göre add fonksiyonunu aşağıdaki gibi overload edebiliriz: + + class Complex { + public: + Complex() = default; + Complex(double real, double imag = 0) : m_real(real), m_imag(imag) + {} + void disp() const; + Complex add(const Complex &z) const; + Complex add(double real) const; + private: + double m_real; + double m_imag; + }; + + Şimdi artık aşağıdaki gibi bir işlemi de yapabiliriz: + + Complex x{3, 2}, y{4, 3}, result; + + result = x.add(3); + + Tabii burada add fonksiyonlarını static bir fonksiyon olarak da yazılabilirdik: + + class Complex { + public: + Complex() = default; + Complex(double real, double imag = 0) : m_real(real), m_imag(imag) + {} + void disp() const; + static Complex add(const Complex &x, const Complex &y); + static Complex add(const Complex &x, double real); + private: + double m_real; + double m_imag; + }; + + Tabii bu durumda static iye fonksiyonlar sınıf ismi belirtilerek çağrılmalıdır: + + Complex x{3, 2}, y{4, 3}, result; + + result = Complex::add(x, y); + + result.disp(); + + Buradaki static fonksiyonların yazımına dikkat ediniz: + + Complex Complex::add(const Complex &x, const Complex &y) + { + Complex result; + + result.m_real = x.m_real + y.m_real; + result.m_imag = x.m_imag+ y.m_imag; + + return result; + } + + Complex Complex::add(const Complex &x, double real) + { + Complex result; + + result.m_real = x.m_real + real; + result.m_imag = x.m_imag; + + return result; + } + + static fonksiyonlar sınıfın üye fonksiyonları olduğu için onların içerisinde o sınıf türünden nesne, gösterici ya da referans yoluyla sınıfın her bölümüne + erişebildiğini anımsayınız. + + Pekiyi şimdi de toplama işini yapan add fonksiyonunu static değil global bir fonksiyon olarak yazmayı deneyelim. Global fonksiyonların sınıfın public bölümü + dışındaki elemanlarına erişemediğini anımsayınız. Bu durumda sınıfın m_real ve m_imag veri elemanları için ya getter fonksiyonlar yazılmalı ya da bu + fonksiyonlar arkadaş fonksiyon yapılmalıdır. Örneğin. + + class Complex { + public: + Complex() = default; + Complex(double real, double imag = 0) : m_real(real), m_imag(imag) + {} + void disp() const; + friend Complex add(const Complex &x, const Complex &y); + friend Complex add(const Complex &x, double real); + + private: + double m_real; + double m_imag; + } + //... + + Complex add(const Complex &x, const Complex &y) + { + Complex result; + + result.m_real = x.m_real + y.m_real; + result.m_imag = x.m_imag+ y.m_imag; + + return result; + } + + Complex add(const Complex &x, double real) + { + Complex result; + + result.m_real = x.m_real + real; + result.m_imag = x.m_imag; + + return result; + } --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Operatör fonksiyonları olmasaydı biz sınıflar üzerindeki operatörsel işlemleri normal fonksiyonlara yaptırırdık. Örmeğin aynı sınıf üründen iki sınıf nesnesini - toplamak için sınıf içerisine add isimli bir static olmayan üye fonksiyon ya da static bir üye fonksiyon yerleştirebilirdik. Gerçekten de örneğin Java'da operatör - fonksiyonları olmadığı için böyle işlemler üye fonksiyonlarıyla (metotlarla) yapılmaktadır. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class Number { -public: - Number() = default; - Number(int val) : m_val(val) - {} - void disp() const; - Number add(const Number &a) const; - static Number add(const Number &x, const Number &y); - - int val() const { return m_val; } - void set_val(int val) { m_val = val; } + C++'ta operatör fonksiyonları iki biçimde tanımlanabilmektedir: -private: - int m_val; -}; + 1) Üye fonksiyon biçiminde (bunlara "üye operatör fonksiyonları" da denilmektedir) + 2) Global fonksiyonlar biçiminde (bunlara "global operatör fonksiyonları" da denilmektedir) -void Number::disp() const -{ - cout << m_val << endl; -} + Global operatör fonksiyonları herhangi bir isim alanında bulunabilir. Normal isim arama kuralları operatör fonksiyonları için de geçerlidir. C++'ta operatör + fonksiyonları sınıfın static üye fonksiyonları biçiminde olamaz. -Number Number::add(const Number &x) const -{ - Number result; - - result.m_val = m_val + x.m_val; - - return result; -} - -Number Number::add(const Number &x, const Number &y) -{ - Number result; - - result.m_val = x.m_val + y.m_val; - - return result; -} - -int main() -{ - Number a(10), b(20), c; - - c = a.add(b); - c.disp(); - - c = Number::add(a, b); - c.disp(); - - return 0; -} + Biz kursumuzda önce üye fonksiyonları üzerinde ilerleyeceğiz sonra global operatör fonksiyonlarını da ele almaya başlayacağız. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - İşte operatör fonksiyonları aslında yukarıdaki gibi normal fonksiyonlardır. Onların tek farklılıkları isimlerinin operator anahtar sözcüğü ve operatör sembolünden - oluşmasıdır. Tabii operatör fonksiyonları aslında isimle de çağrılabilmektedir. + Operatör fonksiyonlarının aslında diğer fonksiyonlardan önemli bir farkı yoktur. Ancak bunların isimleri özel biçimde belirtilmektedir. Operatör fonksiyonlarının + isimleri "operator" anahtar sözcüğü ile operatör sembolünden oluşmaktadır. operator anahtar sözcüğü ile operator sembolü bitişik yazılabilir ancak prgramcılar + genellikle operator anahtar sözcüğü ile operatör sembolü arasında bir boşluk (SPACE) bulundurmaktadır. Örneğin: + + operator + + operator > + operator ++ + operator ! + ..... + + Operatör fonksiyonlarının paramerte sayılarında da bir kısıt vardır. Şöyle ki: + + 1) Eğer operatör fonksiyonu iki operand'lı bir operatöre ilişkinse bu fonksiyon üye operatör fonksiyonu olarak yazılırken bir parametreye global operatör + fonksiyonu olarak yazılırken iki parametreye sahip olmak zorundadır. + + 2) Eğer operatör fonksiyonu tek operand'lı bir operatöre ilişkinse bu fonksiyon üye operatör fonksiyonu olarak yazılırken sıfır parametreye global operatör + fonksiyonu olarak yazılırken bir parametreye sahip olmak zorundadır. + + Örneğin / operatörü için operatör fonksiyonu yazmak isteyelim. Eğer biz bu operatör fonksiyonunu üye operatör fonksiyonu olarak yazacaksak fonksiyonun bir + parametreye global operatör fonksiyonu olarak yazacaksak iki parametreye sahip olması gerekir. Ya da örneğin ! operatör için operatör fonksiyonu + yazmak isteyelim. Eğer operatör fonksiyonunu üye fonksiyonu olarak yazcaksak fonksiyonun sıfır parametreye global operatör fonksiyonu olarak yazacaksak + bir parametreye sahip olması gerekmektedir. + + Operatör fonksiyonlarının geri dönüş değerlerinin türleri üzerinde özel bir kısıt yoktur. Ancak izleyen paragraflarda da görüleceği üzere bunların geri dönüş + değerleri işlevleriyle uyumlu olmalıdır. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#include +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Şimdi daha önce yapmış olduğumuz Complex sınıfı örneğindeki add fonksiyonlarını üye operatör fonksiyonlarıyla yer değiştirelim: -using namespace std; + class Complex { + public: + Complex() = default; + Complex(double real, double imag = 0) : m_real(real), m_imag(imag) + {} + void disp() const; -class Number { -public: - Number() = default; - Number(int val) : m_val(val) - {} - void disp() const; - Number operator +(const Number &a) const; + Complex operator +(const Complex &z) const; + Complex operator +(double real) const; + private: + double m_real; + double m_imag; + }; + //... - int val() const { return m_val; } - void set_val(int val) { m_val = val; } - -private: - int m_val; -}; + Complex Complex::operator +(const Complex &z) const + { + Complex result; -void Number::disp() const -{ - cout << m_val << endl; -} + result.m_real = m_real + z.m_real; + result.m_imag = m_imag + z.m_imag; -Number Number::operator +(const Number &x) const -{ - Number result; + return result; + } - result.m_val = m_val + x.m_val; + Complex Complex::operator +(double real) const + { + Complex result; - return result; -} + result.m_real = m_real + real; + result.m_imag = m_imag; -int main() -{ - Number a(10), b(20), c; + return result; + } - c = a + b; // a.operator +(b) - c.disp(); + Aslında bu örnek ile konuya girişte yaptığımız örnek arasındaki tek farklılık fonksiyonların isimlerindedir. Ancak operatör fonksiyonları bizim artık + bu toplama işlemini doğal bir gösterimle yapmamıza olanak sağlamaktadır. Örneğin: - c = a.operator +(b); - c.disp(); + Complex x{3, 2}, y{4, 3}, result; - return 0; -} + result = x + y; // result = x.operator +(y) + result.disp(); + + result = x + 3; // result = x.operator +(3) + result.disp(); + + Burada result = x + y ifadesinin eşdeğeri aslında result = x.operator +(y) biçimindedir. Benzer biçimde result = x + 3 ifadesinin eşdeğeri de + result = x.operator +(3) biçimindedir. Biz bu işlemleri hem operator senktasıyla hem de fonksiyon çağırma sentaksıyla da yapabiliriz. Yani aşağıdaki koda + yukarıdakiyle eşdeğerdir ve geçerlidir: + + Complex x{3, 2}, y{4, 3}, result; + + result = x.operator +(y) + result.disp(); + + result = x.operator +(3) + result.disp(); + + Tabii operatör fonksiyonlarından amaçladığımız şey aslında operatörlerle doğal bir gösterim oluşturmaktır. Dolayısıyla bizim özel bir gerekçemiz yoksa (bazen + olabilir) operatör sentaksını tercih etmemiz gerekir. + + Önceki örnekteki operatör fonksiyonlarını şimdi global operatör fonksiyonu olarak yazalım: + + class Complex { + public: + Complex() = default; + Complex(double real, double imag = 0) : m_real(real), m_imag(imag) + {} + void disp() const; + + friend Complex operator +(const Complex &r, const Complex &y); + friend Complex operator +(const Complex &r, double real); + + private: + double m_real; + double m_imag; + }; + //... + + void Complex::disp() const + { + cout << m_real << '+' << m_imag << 'i' << endl; + } + + Complex operator +(const Complex &x, const Complex &y) + { + Complex result; + + result.m_real = x.m_real + y.m_real; + result.m_imag = x.m_imag + y.m_imag; + + return result; + } + + Complex operator +(const Complex &x, double real) + { + Complex result; + + result.m_real = x.m_real + real; + result.m_imag = x.m_imag; + + return result; + } + + Kullanımda değişen bir şey omayacaktır: + + Complex x{3, 2}, y{4, 3}, result; + + result = x + y; // result = operator +(x, y) + result.disp(); + + result = x + 3; // result = operator +(3) + result.disp(); + + Yine biz operatör sentaksı yerine fonskiyon çağırma semtaksını da kullanabilirdi: + + Complex x{3, 2}, y{4, 3}, result; + + result = operator +(x, y) + result.disp(); + + result = operator +(3) + result.disp(); + + Operatör fonksiyonlarının operatör sentaksıyla da fonksiyon çağırma sentaksıyla da kullanılabildiğine dikkat ediniz. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- C++ derleyicisi bir operatörle karşılaştığında önce operand'ların türlerine bakar. Eğer operand'lar temel türlere ilişkinse işlem öncesi otomatik tür - dönüştürmesi yoluyla işlemi yapar. Eğer operand'lardan en az bir tanesi bir sınıf türündense bu işlemi yapabilmek için sınıf içerisinde ve global düzeyde - operatör fonksiyonu araştırır. Operatör sembolü @ ile temsil ediliyor olsun: + dönüştürmesi yoluyla işlemi yapar. Yani işlemler C'de olduğu gibi yütürülür. Ancak operand'lardan en az bir tanesi bir sınıf türündense derleyici bu işlemi + yapabilecek üye operatör fonksiyonları ve global operatör fonksiyonları araştırır. Örneğin a @ b biçiminde (burada @ herhangi bir operatörü temsil ediyor + olsun) bir operatör işleminin yapıldığını düşünelim. a değişkeni bir sınıf türünden olsun. O halde bu işlem A sınıfındaki "operator @" isimli üye fonksiyon + tarafından da global düzeydeki "operator @" isimli bir fonksiyon tarafından da yapılabilir. Yani bu işlemin iki fonksiyon çağrı eşdeğeri olabilir: - 1) a @ b gibi bir işlem için derleyici a operand'ının ilişkin olduğu sınıfta a.operator @(b) çağrısına uygun bir @ operatör fonksiyonu araştırır. Aynı zamanda - derleyici bu işlemi yapabilecek global düzeyde operator @(a, b) çağrısına uygun bir global operatör fonksiyonu araştırmaktadır. Sınıfın operator @ ve global operator @ - fonksiyonları aday fonksiyonlar olarak seçilir ve "over resolution" işlemine sokulur. + a.opetor @(b) + operator @(a, b) - 2) @a ya da a@ gibi unary bir operatör için derleyici yine sınıfın içerisinde ve global düzeyde operator @ fonksiyonları araştırmaktadır. Sınıfın içerisinde - a.operator @() çağrısına uygun ya da global düzeyde operator @(a) çağrısına uygun global operatör fonksiyonlarını aday fonksiyon olarak belirler ve bunları - "overload resolution" işlemine sokar. + İşte derleyici a @ b işleminde a ifadesinin ilişkin olduğu sınıfta (burada A sınıfı) ve global düzeyde "operator @" fonksiyonlarını araştırıp bunların en + uygun olanını bulmaya çalışmaktadır. - Yukarıdaki örnekte: + Daha teknik bir açıklamayı şöye yapabiliriz: - c = a + b; + 1) a @ b gibi bir işlem için derleyici a operand'ının ilişkin olduğu sınıfta "operator @" isimli üye opreatör fonksiyonlarını ve global düzeyde operator @ + isimli global fonksiyonları aday fonksiyonlar olarak belirler. (Yani üye operatör fonksiyonları için çağrının a.operator @(b) biçiminde, global operatör + fonksiyonları için ise çağrının operator(a, b) biçiminde yapılmış olduğunu varsayar.) Aday fonksiyonları overload resolution işlemine sokarak en uygun + fonksiyonu bulmaya çalışır. Eğer en uygun fonksiyon bulunamazsa ya da birden fazla olacak biçimde bulunursa bu durum error oluşturacaktır. Burada üye + fonksiyonları ile global fonksiyonlarının farklı parametre sayısına sahip olduğu halde birlikte overload resolution işlemine sokulması size tuhaf gelebilir. + Ancak standartlara göre bu süreçte üye fonksiyonlar onların çağrılmasında kullanılanılan nesnenin adresinin this göstericisi ile eşleştirildiği kabul + edilerek global fonksiyonlar gibi overload resolution işlemine sokulmaktadır. Örneğin: - Burada derleyici bu toplama işlemini yapabilecek Number sınıfında operator + isimli üye fonksiyonu ile global düzeyde operator + isimli global fonksiyonları - araştıracaktır. Dolayısıyla derleyici için bu işlem aşağıdakiyle eşdeğer olacaktır: + a.operator @(b) - c = a.operator +(b); + işlemi overload resolution sırasında sanki aşağıdaki ile eşdeğer kabul edilmektedir: - C++'ta ".", "::", "?:" ve ".*" operatörlerine ilişkin operatör fonksiyonları zaten hiç yazılamamaktadır. - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Operatör fonksiyonları kombine edilebilir. Ancak bu duurmda operatör fonksiyonlarının çağrılma sırası operatör nceliklerine göre yapılmaktadır. Operatör - önceliklerini değiştirmenin bir yolu yoktur. Örneğin: - - result = a + b * c; - - gibi bir işlemde önce b ile c operator * fonksiyonu ile işleme sokulur. Buradan elde edilecek değer a ile toplanır. Yani bu işlemin eşdeğeri şöyledir: - - result = a.operator +(b.operator *(c)); + operator @(&a, b) + Bu fonksiyonun da birinci parametresinin this göstericisi olduğu kabul edilmektedir. + + 2) @a ya da a@ gibi bir işlem için derleyici a operand'ının ilişkin olduğu sınıfta operator @ ve global operator @ üye fonksiyonlarını aday fonksiyon olarak + belirler. (Yani üye operatör fonksiyonları için çağrının a.operator @(), global operatör fonksiyonları için de çağrının operator @(a) biçiminde yapılmış + olduğunu varsayar.) Yine derleyici bu aday operatör fonksiyonlarını yukarıda belirttiğimiz gibi overload resolution işlemine sokar. Eğer en uygun fonksiyonu + bulamazsa ya da birden fazla olarak bulursa bu durum error oluşturur. + Örneğin: - result = a + b + c; + Complex x{3, 2}, y{4, 3}, result; - Bu işlemin de eşdeğeri şöyledir: + result = x + y; - result = a.operator +(b).operator +(c); + Burada ifade derleyici tarafından adeta iki biçimde yazılmış gibi ele alınmaktadır: ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + result = x.operator +(y) + result = operator +(x, y) -#include + Bu çağrılara uygun Complex sınıfındaki operator + üye fonksiyonları ve global operator + fonksiyonları arasında en uygun fonksiyon belirlenmeye çalışılacaktır. -using namespace std; - -class Number { -public: - Number() = default; - Number(int val) : m_val(val) - {} - void disp() const; - Number operator +(const Number &a) const; - Number operator *(const Number &x) const; - - int val() const { return m_val; } - void set_val(int val) { m_val = val; } - -private: - int m_val; -}; - -void Number::disp() const -{ - cout << m_val << endl; -} - -Number Number::operator +(const Number &x) const -{ - Number result; - - result.m_val = m_val + x.m_val; - - return result; -} - -Number Number::operator *(const Number &x) const -{ - Number result; - - result.m_val = m_val * x.m_val; - - return result; -} - -int main() -{ - Number a(1), b(2), c(3), result; - - result = a + b * c; // result = a.operator +(b.operator *(c)); - result.disp(); - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Operatör fonksiyonları anlamlı ve herkes tarafındna tahmin edilebilecek bir işleve sahip olacaksa bulundurulmalıdır. Bazı operatörler varsa onunla ilişkili - bazı operatörlerin de sınıfta bulundurulması uygun olur. Örneğin sınıfta '+' operatör fonksiyonu varsa '-' operatör fonksiyonu da sınıfta bulundurulmalıdır. - '*' operatör fonksiyonu varsa '/' operatör fonksiyonu da sınıfta bulundurulmalıdır. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class Number { -public: - Number() = default; - Number(int val) : m_val(val) - {} - void disp() const; - Number operator +(const Number &a) const; - Number operator -(const Number &a) const; - Number operator *(const Number &x) const; - Number operator /(const Number &x) const; - - int val() const { return m_val; } - void set_val(int val) { m_val = val; } - -private: - int m_val; -}; - -void Number::disp() const -{ - cout << m_val << endl; -} - -Number Number::operator +(const Number &x) const -{ - Number result; - - result.m_val = m_val + x.m_val; - - return result; -} - -Number Number::operator -(const Number &x) const -{ - Number result; - - result.m_val = m_val - x.m_val; - - return result; -} - -Number Number::operator *(const Number &x) const -{ - Number result; - - result.m_val = m_val * x.m_val; - - return result; -} - -Number Number::operator /(const Number &x) const -{ - Number result; - - result.m_val = m_val / x.m_val; - - return result; -} - -int main() -{ - Number a(1), b(2), c(3), result; - - result = a + a + a; - result.disp(); - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Operatör fonksiyonları da "overload" edilebilirler. Yani farklı parametrik yapılara sahip aynı isimli birden fazla operatör fonksiyonu bir arada bulunabilir. - Örneğin iki Number nesnesini toplayan + operatör fonksiyonunun yanı sıra bir Number nesnesi ile bir int değeri toplayan bir + operatör fonksiyonu bir arada - bulunabilir. Ancak bazı operatörlerin değişme özelliğinin sağlanması da gerekir. Örneğin bir Number nesnesi ile bir int değeri toplayabiliyorsak, bir int değerle - bir Number nesnesini de toplayabilmemiz gerekir. Ancak bir int değerle birt Number nesnesini toplayabilen üye operatör fonksiyonu yazmak mümkün değldir. Örneğin: - - int a = 10; - Number b(20); - Number result; - - result = a + 10; // result = a.operator +(10); - result = 10 + a; // result = 10.operator +(a); Dikkat! int bir sınıf değil! mümkün değil - - İşte bu tür durumlarda global operatör fonksiyonları ile bu işlemler yapılabilmektedir. C++'ta birkaç operatörün operatör fonksiyonları üye operatör fonksiyonu - biçiminde yazılmak zorundadır. Ancak pek çok operatör üye operatör fonksiyonu biçiminde ve global operatör fonksiyonu biçiminde yazılabilmektedir. Bu tür durumlarda - programcılar genellikle üye operatör fonksiyonu olarak yazabildiklerini üye operatör fonksiyonu olarak, yazamadıklarını da global operatör fonksiyonu olarak - yazmalıdırlar. - - Operatör fonksiyonunu global operatör fonksiyonu olarak yazdığımızda artık sınıfın private elemanlarına erişemeyeceğimize dikkat ediniz. Bu durumda mecburen - sınıfın getter/setter fonksiyonlarındna faydalanırız. Aslında bu tür fonksiyonların "arkadaş (friend)" yapılması çok karşılaşılan bir durumdur. - - Tabii aslında bu tür global operatör fonksiyonları bir "sarma (wrapper)" fonksiyon olarak da yazılabilmektedir. Örneğin: - - inline Number operator +(int val, const Number &x) - { - return x + val; - } - - C++'ta dört operatöre ilişkin operatör fonksiyonlarının üye operatör fonksiyonu olarak yazılması zorunlu tutulmuştur. Bunlar "=", "(...)", "[]" ve "->" operatör - fonksiyonlarıdır. Diğer operatörlerin operatör fonksiyonları üye operatör fonksiyonu ya da global operatör fonksiyonu biçiminde yazılabilirse de programcı - mümkün olduğu kadar üye operatör fonksiyonu biçiminde bunları yazmaya çalışmalıdır. - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class Number { -public: - Number() = default; - Number(int val) : m_val(val) - {} - void disp() const; - Number operator +(const Number &a) const; - Number operator -(const Number &a) const; - Number operator *(const Number &x) const; - Number operator /(const Number &x) const; - - Number operator +(int val) const; - Number operator -(int val) const; - Number operator *(int val) const; - Number operator /(int val) const; - - int val() const { return m_val; } - void set_val(int val) { m_val = val; } - -private: - int m_val; -}; - -void Number::disp() const -{ - cout << m_val << endl; -} - -Number Number::operator +(const Number &x) const -{ - Number result; - - result.m_val = m_val + x.m_val; - - return result; -} - -Number Number::operator -(const Number &x) const -{ - Number result; - - result.m_val = m_val - x.m_val; - - return result; -} - -Number Number::operator *(const Number &x) const -{ - Number result; - - result.m_val = m_val * x.m_val; - - return result; -} - -Number Number::operator /(const Number &x) const -{ - Number result; - - result.m_val = m_val / x.m_val; - - return result; -} - -Number Number::operator +(int val) const -{ - Number result; - - result.m_val = m_val + val; - - return result; -} - -Number Number::operator -(int val) const -{ - Number result; - - result.m_val = m_val - val; - - return result; -} - -Number Number::operator *(int val) const -{ - Number result; - - result.m_val = m_val * val; - - return result; -} - -Number Number::operator /(int val) const -{ - Number result; - - result.m_val = m_val / val; - - return result; -} - -inline Number operator +(int val, const Number &x) -{ - Number result; - - result.set_val(val + x.val()); - - return result; // return x + val; -} - -Number operator *(int val, const Number &x) -{ - Number result; - - result.set_val(val * x.val()); - - return result; -} - -int main() -{ - Number a(1), result; - - result = a + 2; - result.disp(); - - result = 2 + a; - result.disp(); - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Karşılaştırma operatör fonksiyonlarının geri dönüş değerleri herhangi bir türden olabilir ancak bunların bool türden olması en uygun durumdur. - Karşılaştırma operatör fonksiyonlarının mümkünse hepsi yazılmalıdır. Yani örneğin eğer biz < operatör fonksiyonunu yazmış isek > opertör fonksiyonunu da - yazmalıyız. == operatör fonksiyonunu yazmış isek != operatör fonksiyonunun da yazılması uygun olur. Yani mümkünse <, >, <=, >=, ==, != operatör fonksiyonlarının - hepsinin yazılması tavsiye edilmektedir. Tabii bazı sınıflar için örneğin == ve != operatörleri anlamlı iken <, >, <=, >= operatör fonksiyonlaırnın bir anlamı olmayabilir. + Buradan çıkan bir sonuç şudur: Bir operatör işlemini aynı kalitede yapabilecek hem üye operatör fonksiyonu hem de global operatör fonksiyonu varsa bu operatörün + kullanımı (bulunması değil) error oluşturacaktır. Bu nedenle programcının bir operatör işlemini ya üye operatör fonksiyonuna ya da global operatör fonksiyonuna + yaptırması gerekir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - ++ ve -- operatörlerine ilişkin operatör fonksiyonları da yazılabilir. Ancak bu operatörler hem önek (prefix) hem de sonek (postfix) kullanılabildiğinden dolayı - bunların yazımları için bazı noktalaa dikkat edilmesi gerekir. Birinci nokta önek ++ operatörünün C++'ta nesnenin kendisine ilişkin sol taraf değeri üretmesidir. - Bu özelliğin sağlanabilmesi için bu operatör fonksiyonu yazılırken fonksiyonun geri dönüş değerinin türü aynı sınıf türünden bir referans olmalı ve fonksiyon - *this ifadesi ile geri dönmelidir. C++'ta bu operatörlerin sonek versiyonları nesne belirtmez bu nedenle bu operatörlerin seonek versiyonları yazılırken geri dönüş değerinin - türü kendi sınıfı türünden const bir nesne (referans değil) olmalıdır. Sonek versiyonların nesnenin değerini önce yerel bir nesnede saklayıp artırım ya da eksiltimi nesne üzerinde yapması - ve sonra da bu yerel nesneyle geri dönmesi uygun olur. Bu operatörlere ilişkin operatör fonksiyonlarının yazılması sırasında bunların önek ve sonek biçimleri - ayrı ayrı yazılır. Sonek biçiminde aslında programcının kullanmayacağı "dummy" bir int parametre bulundurulur. Örneğin: + C++'ta pek çok operatöre ilişkin operatör fonksiyonları yazılabilmektedir. Ancak istisna olarak aşağıdaki operatörlere ilişkin operatör fonksiyonları + yazılamamaktadır: + + . + :: + ?: + .* +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 79. Ders 24/06/2024 - Pazartesi +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Operatör fonksiyonları yoluyla operatör öncelikleri değiştirilememektedir. Örneğin: + + Complex x, y, z, result; + //... + + result = x + y * z; + + Burada temel türler içim operatör öncelikleri nasılsa operatör fonksiyonları da o önceliklere göre çağrılacaktır. Yani yukarıdaki işlemin üye operatör + fonksiyonu eşdeğeri şöyledir: + + result = x.operator +(y.operator *(z)); + + Tabii aslında atama işlemi de bir operatör fonksiyonuyla yapılmaktadır. O halde yukarıdaki işlemin gerçek eşdeğeri aşağıdaki gibidir: + + result.operator = (x.operator +(y.operator *(z))) + + Tabii aynı işlemler global operatör fonksiyonlarıyla da yapılabilirdi: + + result = operator +(x, operator * (y, z)); + + Tabii operatörler arasındaki "soldan sağalık ya da sağdan solalık (associativity)" durumu da değiştirilemez. Örneğin: + + Complex x, y, z, result; + //.. + + result = x + y + z; + + Burada önce x + y toplamı yapılacak sonra bu toplam z ile toplanacaktır. Yani bunun üye operatör fonksiyonu eşdeğeri şöyledir: + + result = x.operator +(y).operator +(z); + + Tabii bu tür işlemler yapılırken ara işlemlerde geçici nesneler yaratılmaktadır. Dolayısıyla bu geçici nesneler için sınıfın yıkıcı fonksiyonları da + çalıştırılacaktır. Daha önceden de çeşitli defalar belirttiğimiz gibi C++'ta her zaman yapıcı fonksiyonlarla yıkıcı fonksiyonlar ters sırada çağrılmaktadır. + Dolayısıyla alt işlemler sırasında yaratılan geçici nesneler için de yıkıcı fonksiyonlar ters sırada çağrılır. Örneğin: + + result = x + y + z; + + İşleminde iki geçici nesne yaratılmaktadır. Birincisi x + y işleminin sonucunda yaratılan geçici nesnedir. İkincisi de bunun z ile toplanması sonucunda + yaratılan geçici nesnedir. C++'ta bir ifade de bir geçici nesne yaratılmış ise o nesneye ilişkin yıkıcı fonksiyonlar tüm ifade (burada atama işlemi de dahildir) + bittiğinde çağrılır. Tabii burada yıkıcı fonksiyonların çağrılması yapıcı fonksiyonlara göre ters sırada gerçekleşecekir. Örneğin: + + result = x + y + z; + + Burada üç işlem (operatör) söz konusudur: + + İ1: x + y ------> geçici nesne + İ2: İ1 (geçici nesne) + z ------> geçici nesne + İ3: result = İ2 + + Önce İ2'deki geçici nesne için sonra İ1'deki geçici nesne için yıkıcı fonksiyonlar çağrılacaktır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + C++'ta aşağıdaki operatörlere ilişkin operatör fonksiyonları global operatör fonksiyonu biçiminde yazılamamaktadır: + + = + () + [] + -> + + Bu operatörlere ilişkin operatör fonksiyonlarının üye operatör fonksiyonları biçiminde yazılması gerekmektedir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Operatör fonksiyonları anlamlı ve herkes tarafından tahmin edilebilecek bir işleve sahip olacaksa bulundurulmalıdır. Operatör fonksiyonlarına o operatörün + sembolü ile mantıksal biçimde ilişkili olmayan işlemlerin yaptırılması kötü bir tekniktir. Örneğin tarih bilgileri üzerinde işlemler yapan Date sınıfı için + çıkartma operatör fonksiyonunun yazılması anlamlı olabilir. Genellikle beklenti iki tarihin çıkartılmasından aradaki gün farkının elde edilmesi biçimindedir. + Ancak iki tarihin toplanmasının ya da çarpılmasının bir anlamı yoktur. Ancak Date sınıfı için >, <, >=, <=, == ve != operatör fonksiyonlarının bulundurulması + anlamlıdır. + + Programcının eğer anlamlıysa o gruptaki tüm operatörleri yazması iyi bir tekniktir. Örneğin bir sınıf için + operatör fonksiyonunu yazılıyorsa eğer anlamlıysa + -, * ve / operatör fonksiyonlarını da yazılmalıdır. Benzer biçimde sınıf için == operatör fonksiyonu yazılıyorsa != operatör fonksiyonu da ve eğer anlamlıysa + >, < , >=, <= operatör fonksiyonları da yazılmalıdır. Ya da örneğin sınıf için ++ operatör fonksiyonu yazılıyorsa eğer anlamlıysa -- opreatör fonksiyonu da + yazılmalıdır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Operatör fonksiyonlarının geri dönüş değerleri üzerinde herhangi bir kısıt yoktur. Örneğin anlamsız olsa da + operatör fonksiyonun geri dönüş değeri void + olabilir. Global operatör fonksiyonlarının en az bir parametresinin bir sınıf türünden olması gerekmektedir. Yani örneğin biz iki int değeri toplayan bir + operatör fonksiyonu yazamayız. Ancak bir sınıf nesnesi ile bir int değeri toplayan bir operatör fonksiyonu yazabiliriz. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Yukarıda belirttiğimiz birkaç istisna operatör dışında operatör fonksiyonarının çoğu hem üye operatör fonksiyonu olarak hem de global operatör fonksiyonu + olarak yazılabilmektedir. Pekiyi biz hangisini tercih etmeliyiz? Pek çokları gibi biz de şunu tavsiye edeceğiz: "Eğer operatör fonksiyonunu üye operatör + fonksiyonu olarak yazabiliyorsanız üye operatör fonksiyonu olarak yazın. Üye operatör fonksiyonu olarak yazamıyorsanız mecburen onu global operatör fonksiyonu" + olarak yazacaksınız. Örneğin z değişkeni Complex türünden olsun. Biz de bir Complex sayı ile bir double sayıyı toplayabilecek bir operatör fonksiyonu yazmak + isteyelim. Bunu üye operatör fonksiyonu olarak yazabiliriz: class Complex { + public: //... - Complex &operator ++(); // önek - const Complex operator ++(int); // sonek - Complex &operator --(); // önek - const Complex operator --(int); // sonek + Complex operator +(double real) const; }; - Aşağıdak Complex sayı sınıfında bu operatörlerin kullanılmasına ilişkin bir örnek verilmiştir. + Böylece biz z + 10 gibi bir işlemi bu operatör fonksiyonun yaptırabiliriz. Ancak 10 + z gibi bir işlem üye operatör fonksiyonuna yaptırılamaz. Bu durumda + mecburen bu işlemin global operatör fonksiyonu yoluyla yaptırılması gerekir. Örneğin: + + Complex operator +(double real, const Complex &z); + + Global operatör fonksiyonlarının normal global fonksiyonlar gibi ele alındığına dikkat ediniz. Bu nedenle eğer bu fonksiyonların ilgili sınıfın sınıfın + private veri elemanlarına erişmesi isteniyorsa arkadaş yapılması gerekebilmektedir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -// complex.hpp +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Operatör fonksiyonu yazarken "değişme özelliğinin" sağlanması iyi bir tekniktir. Yani x + y işlemi için operatör fonksiyonu yazılıyorsa y + z işlemi için de + eğer gerekiyorsa operatör fonksiyonu bulundurulmalıdır. Çünkü kişilerin beklentisi +, * gibi operatörlerde değişme özelliğinin sağlanmasıdır. Örneğin x + değişkeni bir sınıf türünden olsun. Eğer x + 10 gibi bir işlemi yapacak bir operatör fonksiyonu yazılıyorsa 10 + x işlemini yapacak bir opetaör fonksiyonu + da yazılmalıdır. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#ifndef COMPLEX_HPP_ -#define COMPLEX_HPP_ +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Biz artık operatör fonksiyonu yazılabilecek tüm operatörlerin operatör fonksiyonlarının nasıl yazılabileceği üzerinde tek tek duracağız. Operatör + fonksiyonlarını tanıtırken çeşitli sınıflarla örnek vereceğiz. Örneklerimizden biri rasyonal sayılar üzerinde işlem yapan Rational isimli bir sınıf olacaktır. + Bilindiği gibi matematikte iki tamsayının bölümü biçiminde ifade edilebilen sayılara rasyonel sayılar denilmektedir. Tüm rasyonel sayıların devirli + ondalık açılımları vardır. Rational sınıfı aldığı rasyonel sayıyı en sade biçimde tutmaya çalışmaktadır. Sadeleştirme için pay ve payda "ortak bölenlerinin + en büyüğü (greatest common divisor)" ile bölünmektedir. İki sayının ortak bölenlerinin en büyüğünü bulmak için en etkili algoritma "Öklit algoritması" + denilen algoritmadır. Sınıftaki temel üye fonksiyonlar şunlardır: -class Complex { -public: - Complex() = default; - Complex(double real, double imag = 0); - Complex operator +(const Complex &r) const; - Complex operator +(double real) const; - Complex operator -(const Complex &r) const; - Complex operator *(const Complex &r) const; + namespace CSD + { + class Rational { + public: + Rational(int a, int b = 1); + void disp() const; + //... + private: + void simplify(); + static int gcd(int a, int b); + private: + int m_a; + int m_b; + }; + } - Complex &operator ++(); // prefix - const Complex operator ++(int); // postfix +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - Complex &operator --(); // prefix - const Complex operator --(int); // postfix - void disp() const; -private: - double m_real; - double m_imag; -}; +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 80. Ders 26/06/2024 - Çarşamba +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + +, -, * ve / operatörlerine ilişkin operatör fonksiyonları yazılırken genel olarak fonksiyonlarların geri dönüş değerlerinin ilgili sınıf türünden olması + anlamlıdır. Örneğin iki rosyaonel sayıyı toplayan + operatör fonksiyonun geri dönüş değeri de Rasyonel bir sayı olmalıdır. Böylece operatörler aynı ifadede + kombine edilerek kullanılabilirler. Bu operatörlere ilişkin operatör fonksiyonları üye operatör fonksiyonu olarak ve global operatör fonksiyonu olarak + yazılabilir. Ancak yukarıda da belirttiğimiz gibi biz mümkün olduğunca üye operatör fonksiyonlarını tercih etmeliyiz. Ayrıca bu fonksiyonlar eğer anlamlıysa + farklı türlere çalışabilecek biçimde overload da edilebilirler. + + Örneğin iki rasyonel sayıyı toplayan + operatör fonksiyonu Rational sınıfının üye fonksiyonu olarak şöyle yazılabilr: + + Rational Rational::operator +(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b + m_b * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Benzer biçimde Rational sınıfına -, *, / operatör fonksiyonları da eklenebilir. Bu durumda Rational sınıfı aşağıaki duruma gelecekt: + + class Rational { + public: + Rational(int a = 0, int b = 1); + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + Pekiyi bir rasyonel sayı ile bir tamsayının toplanması anlamlı mıdır? Evet anlamlıdır. Zaten tamsayılar aslında paydası 1 olan rasyonel sayılardır. Bir + Ratioanal nesnesi ile bir int nesne aslında yukarıdaki operatör fonksiyonları varsa işleme sokulabilecektir. Örneğin: + + Rational x{1, 2}, result; + + result = x + 1; // aslında geçerli + + Biz sınfımız için int parametreli bir + operatör fonksiyun yazmamaış olsak bile x + 1 işlemi aslında geçerlidir. Bu konu ileride ayrı bir başlık halinde + ele alınacaktır. Yukarıdaki işlemin eşdeğeri şöyledir: + + result = x.operator +(1); + + + operatör fonksiyonun parametresinin const Rational & türünden olduğuna dikkat ediniz. İşte aslında aşağıdaki ildeğer verme işlemi de geçerlidir: + + const Rational &r = 1; + + C++ standartlarına göre burada 1 değeri Rational nesnesine dönüştürüelecek sonra atama işlemi yapılacaktır. Başka bir deyişle aslında bu ifade aşağıdaki ile + eşdeğerdir: + + const Rational &r = Rational(1); + + Biz bu konuyu henüz ele almadık. İzleyen paragraflarda "sınıflar ile ilgili tür dönüştürmeleri" konusu içerisinde bu durumu açıklayacağız. Ancak yine de + Rational nesnesi ile int değerler arasında doğrudan işlem yapan operatör fonksiyonlarının sınıfa eklenmesi bu işlemlerin daha hızlı yapılmasına yol açacaktır. + Bu durumda sınıfa aşağıdaki üye operatör fonksiyonları da eklenebilir: + + class Rational { + public: + Rational(int a = 0, int b = 1); + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + + Rational operator +(int a) const; + Rational operator -(int a) const; + Rational operator *(int a) const; + Rational operator /(int a) const; + + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + Ancak r bir Rational nesnesi a da int bir değer olmak üzere biz örneğin r + a gibi bir işlemi yeni eklediğimiz + operatör fonksiyonuyla yapabiliriz. Ancak + bunun tersi olan a + r işlemini yukarıdaki opreatör fonksiyonlarıyla yapamayız. Bu tür işlemleri ancak global operatör fonksiyonlarıyla yapılabileceğini + anımsayınız. O halde bizim aşağıdaki global operatör fonksiyonlarını da yazmamız gerekir: + + Rational operator +(int a, const Rational &r); + Rational operator -(int a, const Rational &r); + Rational operator *(int a, const Rational &r); + Rational operator /(int a, const Rational &r); + + Bu global fonksiyonların sınıfın arkadaş yapılması gerekmektedir. Ayrıca burada bir noktaya dikkatinizi çekmek istiyoruz: Aslında yukarıdaki + ve * global + operatör fonksiyonları üye operatör fonksiyonları kullanılarak yazılabilir. Tabii böyle bir satırlık fonksiyonların inline yapılması uygun olur. inline + fonksiyonların başlık dosyalarına bulundurulması gerektiğini anımsayınız: + + inline Rational operator +(int a, const Rational &r) + { + return r + a; + } + + inline Rational operator *(int a, const Rational &r) + { + return r * a; + } + + Bu eklemeler yapıldıktan sonra başlık dosyasındaki bildirimler şu hale gelecektir: + + class Rational { + public: + Rational(int a = 0, int b = 1); + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + + Rational operator +(int a) const; + Rational operator -(int a) const; + Rational operator *(int a) const; + Rational operator /(int a) const; + + friend Rational operator -(int a, const Rational &r); + friend Rational operator /(int a, const Rational &r); + + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + inline Rational operator +(int a, const Rational &r) + { + return r + a; + } + + inline Rational operator *(int a, const Rational &r) + { + return r * a; + } + + Yukarıdaki örnek fonksiyonların yazımı aşağıda bütünsel olarak verilmiştir: +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* rational.hpp */ + +#ifndef RATIONAL_HPP_ +#define RATIONAL_HPP_ + +namespace CSD +{ + class Rational { + public: + Rational(int a = 0, int b = 1); + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + + Rational operator +(int a) const; + Rational operator -(int a) const; + Rational operator *(int a) const; + Rational operator /(int a) const; + + friend Rational operator -(int a, const Rational &r); + friend Rational operator /(int a, const Rational &r); + + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + inline Rational operator +(int a, const Rational &r) + { + return r + a; + } + + inline Rational operator *(int a, const Rational &r) + { + return r * a; + } +} #endif -// complex.cpp +/* rational.cpp */ #include -#include "complex.hpp" +#include +#include +#include "rational.hpp" + +namespace CSD +{ + using namespace std; + + Rational::Rational(int a, int b) + { + + if (b < 0) { + m_a = -a; + m_b = -b; + } + else { + m_a = a; + m_b = b; + } + + if (b == 0) + throw invalid_argument("denominator shall not be zero"); + + simplify(); + } + + Rational Rational::operator +(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b + m_b * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Rational Rational::operator -(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b - m_b * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Rational Rational::operator *(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Rational Rational::operator /(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b; + result.m_b = m_b * r.m_a; + + result.simplify(); + + return result; + } + + Rational Rational::operator +(int a) const + { + Rational result; + + result.m_a = a * m_b + m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator -(int a) const + { + Rational result; + + result.m_a = -a * m_b + m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator *(int a) const + { + Rational result; + + result.m_a = a * m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator /(int a) const + { + Rational result; + + result.m_a = m_a; + result.m_b = a * m_b; + + return result; + } + + void Rational::disp() const + { + cout << m_a; + if (m_b != 1 && m_a != 0) + cout << '/' << m_b; + cout << endl; + } + + void Rational::simplify() + { + int gcd_val = gcd(abs(m_a), abs(m_b)); + + m_a /= gcd_val; + m_b /= gcd_val; + } + + int Rational::gcd(int a, int b) + { + int temp; + + while (b != 0) { + temp = b; + b = a % b; + a = temp; + } + + return a; + } + + Rational operator -(int a, const Rational &r) + { + Rational result; + + result.m_a = a * r.m_b - r.m_a; + result.m_b = r.m_b; + + return result; + } + + Rational operator /(int a, const Rational &r) + { + Rational result; + + result.m_a = a * r.m_b; + result.m_b = r.m_a; + + return result; + } +} + +/* app.cpp */ + +#include +#include "rational.hpp" using namespace std; +using namespace CSD; -Complex::Complex(double real, double imag) +int main() { - m_real = real; - m_imag = imag; + Rational x{1, 5}, y{2, 3}, z{1, 6}, result; + + result = x + y * z - 1; + + result.disp(); + + return 0; } -void Complex::disp() const +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + İşaret + ve işaret - operatörlerinin tek operand'lı operatör olduğunu anımsayınız. Bu operatörler operand'ları üzerinde bir değişilik yapmamakta diğer + opereatörlerde olduğu gibi sonuca ilişkin yeni nesne üretmektedir. Yani örneğin r değişkeninin Rational sınıfı türünden olduğunu kabul edelim. Bu durumda + -r işlemi ile r nesnesi değişmeyecektir, r'nin negatifine ilişkin yeni bir nesne operatör fonksiyonunun geri dönüş değeir olarak verilecektir. Aynı durum + işaret + operatörü için de benzerdir. Bu operatör fonksiyonları üye operaör fonksiyonu olarak ya da global operatör fonksiyonu olarak yazılabilir. Yukarıda + da belirttiğimiz gibi bu tür durumlarda biz operatör fonksiyonunun üye fonksiyonu olarak yazılmasını tavsiye ediyoruz. İşaret + operatörünün nesnenin aynı + değeri ile geri döndüğünü anımsayınız. Bu durumda bu operatör fonksiyonu *this ile geri döndürülebilir ve fonksiyonun geri dönüş değeri aynı sınıf türünden + const bir referans olabilir. Örneğin Ration sınıfında işaret + operatörü üye fonksiyon olarak şöyle yazılabilir: + + const Rational &Rational::operator +() + { + return *this; + } + + Böylece +r gibi bir ifade kullanıldığında bunun r'den bir farkı olmayacaktır. İşaret + ve işaret - operatörleri nesne belirtmediği için bu fonksiyonun , + geri dönüş değeri const & yapılmıştır. Böylece aşağıdaki gibi bir ifade geçerli olmayacaktır: + + Rational x{1, 2}, y; + + +y = x; // geçersiz! +y const & belirttiği için Rational sınıfınn kopya atama operatör fonksiyonu çağrılamayacaktır + + + İşaret - operatör fonksiyonu mantıksal olarak operand'ının negatif değeri ile geri dönmelidir. Tabii geri dönüş değeri olarak nesnenin kendisini değil + negatif bir kopyasını vermelidir. Örneğin Rational sınıfı için - operatör fonksiyonu şöyle yazılabilir: + + Rational Rational::operator -() const + { + Rational result; + + result.m_a = -m_a; + result.m_b = m_b; + + return result; + } +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 81. Ders 01/07/2024 - Pazartesi +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + ++ ve -- operatörlerine ilşkin operatör fonksiyonlarının yazımı biraz ilginçtir. Öncelikle bu operatörler hakkında şu anımsatmayı yapalım: + + 1) Bu operatörlerin önek kullanımları nesnenin kendisini artırmakta ya da eksiltmektedir ve nesnenin kendisine ilişkin sol taraf değeri üretmektedir. + Bu operatörler için yazılacak operatör fonksiyonunun bu semantiği sağlaması gerekir. + + 2) Bu operatörlerin sonek kullanımları nesnenin kendisi artırmakta ya da eksiltmektedir ancak sonraki işleme nensnenin artmamış ve eksiltilmemiş hali + sokulmaktadır. Bu operatörlerin son ek kullanımları bir sol taraf değeri belirtmemektedir. Bu operatörler için yazılacak operatör fonksiyonunun bu semantiği + sağlaması gerekir. + + Yukarıdaki semantik tek bir fonksiyon tarafından karşılanamayacağı için bu operatörlerin önek ve sonek biçimleri ayrı iki operatör fonksiyonu olarak + yazılmaktadır. Ancak bunların birbirlerine karışmaması için sonek versiyonlar "dummy bir int parametre" almaktadır. + + Pekiyi bu fonksiyonların önek ve sonek biçimlerinin parametrik yapıları nasıl olmalıdır? İşte bu oparatör fonksiyonları eğer üye fonksiyonu olarak yazılacaksa + bunların sıfır parametresi olmalıdır. Ancak geri dönüş değerlerinin aynı sınıf türünden referans olması ve fonksiyonun artırım işleminden sonra *this ile + geri dönmesi uygun olacaktır. Örneğin sınıfın ismi T olmak üzere bu operatörlerin önek biçimleri şöyle oluşturulmaldır: + + class T { + public: + // + T &operator ++(); // önek ++ + T &operator --(); // önek -- + //... + }; + + T &T::operator ++() // önek ++ + { + + + return *this; + } + + T &T::operator --() // önek -- + { + + + return *this; + } + + Tabii bu operatör fonksiyonları global olarak yazılacaksa bir parametre sahip olacaklardır. Burada bu operatör fonksiyonlrı çağrıldığında artırılmış + nesnenin kendisinin elde edildiğine dikkat ediniz. + + Eğer bu operatörlerin sonek kullanımına ilişkin operatör fonksiyonu üye operatör fonksiyonu olarak yazılacaksa dummy bir int parametre belirtilmelidir. + Bu int parametre hiç kullanılmayacağı için ona isim verilmesine de gerek yoktur. Sonek ++ ve -- operatörlerine sonek semantiği verebilmek için artırım + ya da eksiltimin nesne üzerinde yapılması ancak fonksiyonun nesnenin artırılmamış ya da eksiltilmemiş bir kopyası ile geri döndürülmesi gerekmektedir. + Bu durumda fonksiyonun geri dönüş değerinin kendi sınıfı türünden olması (kendi sınıf türünden referans değil) atamayı engellemek için const yapılması + uygun olmaktadır. Örneğin: + + Örneğin: + + class T { + public: + // + const T operator ++(int); // sonek ++ + const T operator --(int); // sonek -- + //... + }; + + const T T::operator ++(int) + { + T temp = *this; + + + + return temp; + } + + const T T::operator --(int) + { + T temp = *this; + + + + return temp; + } + + Burada fonksiyonların nesnelerin artırılmamış ya da eksiltilmemiş biçimlerinin kopyalarına geri döndüğüne ancak nesneler üzerinde artırma ve eksiltme + işlemini yaptığına dikkat ediniz. Fonksiyonların geri dönüş değerlerinin const olması sonek kullanımların atama gibi işlemlere sokulamamasına yol açmaktadır. + Böylece bu fonksiyonlar nesne belirten bir ifade gibi kullanılamatacaklardır. + + Örneğin Rational sayı sınıfı için ++ ve -- operatör fonksiyonları üye fonksiyon olarak aşağıdaki gibi yazılabilir: + + class Rational { + public: + //... + Rational &operator ++(); // prefix ++ + Rational &operator --(); // prefix -- + const Rational operator ++(int); // postfix -- + const Rational operator --(int); // postfix - + + void disp() const; + //... + private: + int m_a; + int m_b; + }; + + Rational &Rational::operator ++() // prefix ++ + { + m_a = m_a + m_b; + + return *this; + } + + Rational &Rational::operator --() // prefix -- + { + m_a = m_a - m_b; + + return *this; + } + + const Rational Rational::operator ++(int) // postfix ++ + { + Rational temp = *this; + + m_a = m_a + m_b; + + return temp; + } + + const Rational Rational::operator --(int) // postfix -- + { + Rational temp = *this; + + m_a = m_a - m_b; + + return temp; + } + + Pekiyi derleyici ++ ve -- operatörleriyle karşılaştığında operand'lar bir sınıf türündense ne yapmaktadır? Örneğin x T sınıfı türünden bir nesne olsun ve + biz onu ifade içerisinde ++x biçiminde kullanmış olalım. İşte derleyici bu durumda Sanki bu işlemi x.operator++() ve operator ++(x) biçiminde ele almaktadır. + Bu durumda sınıftaki ++ operatör fonksiyonları ve global ++ operatör fonksiyonları aday fonksiyonlar olarak seçilecektir. Ancak sonek ++ operatör fonksiyonlarının + dummpy int parametreleri olduğu için bunlar asla uygun fonksiyon olarak seçilemeyecektir. Şimdi biz bu x nesnesini x++ biçiminde kullanmış olalım. Bu durumda + bu işlemin eşdeğeri x.operator ++(0) ya da operator ++(x, 0) biçimindedir. Buradaki argümanın 0 olmasının bir önemi yoktur. İşte derleyici bu durumda sınıftaki + ++ operatör fonksiyonlarını ve global ++ operatör fonksiyonlarını aday fonksiyon olarak belirler. Ancak artık önek ++ fonksiyonları uygun fonksiyon olarak + seçilemeyecektir. Standartlarda sonek ++ ve -- operatörlerinin operand'ları birer sınıf türündense derleyicinin dummy int parametre için 0 değerini argüman + yaptığı belirtilmiştir. Yani x++ gibi bir işlemin derleyici için eşdeğeri x.operator ++(0) ya da operator ++(x, 0) biçimindedir. Tabii biz sonek ++ vee + operatörlerini fonksiyon çağırma sentaksıyla kullanırsakbu argümana istediğimiz değeri verebiliriz. Tabii bu dummy parametrenin isimlendirilerek kullanılmasının + bir anlamı yoktur. Örneğin c.operator ++(10) gibi bir çağrım da geçerlidir. + + Aşağıda rasyonel sayı işlemlerini yapan Rational sınıfı görmüş olduğumuz operatör fonksiyonları eklenerek verilmiştir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +// rational.hpp + +#ifndef RATIONAL_HPP_ +#define RATIONAL_HPP_ + +namespace CSD { - cout << m_real << '+' << m_imag << 'i' << endl; + class Rational { + public: + Rational(int a = 0, int b = 1); + + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + + Rational operator +(int a) const; + Rational operator -(int a) const; + Rational operator *(int a) const; + Rational operator /(int a) const; + + friend Rational operator -(int a, const Rational &r); + friend Rational operator /(int a, const Rational &r); + + const Rational &operator +() const; + Rational operator -() const; + + Rational &operator ++(); // prefix ++ + Rational &operator --(); // prefix -- + const Rational operator ++(int); // postfix -- + const Rational operator --(int); // postfix - + + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + inline Rational operator +(int a, const Rational &r) + { + return r + a; + } + + inline Rational operator *(int a, const Rational &r) + { + return r * a; + } } -Complex Complex::operator +(const Complex &r) const +#endif + +// rational.cpp + +#include +#include +#include +#include "rational.hpp" + +namespace CSD { - Complex result; + using namespace std; - result.m_real = m_real + r.m_real; - result.m_imag = m_imag + r.m_imag; + Rational::Rational(int a, int b) + { - return result; -} + if (b < 0) { + m_a = -a; + m_b = -b; + } + else { + m_a = a; + m_b = b; + } + + if (b == 0) + throw invalid_argument("denominator shall not be zero"); -Complex Complex::operator +(double real) const -{ - Complex result; + simplify(); + } - result.m_real = m_real + real; - result.m_imag = m_imag; + Rational Rational::operator +(const Rational &r) const + { + Rational result; - return result; -} + result.m_a = m_a * r.m_b + m_b * r.m_a; + result.m_b = m_b * r.m_b; -Complex Complex::operator -(const Complex &r) const -{ - Complex result; + result.simplify(); - result.m_real = m_real - r.m_real; - result.m_imag = m_imag - r.m_imag; + return result; + } - return result; -} + Rational Rational::operator -(const Rational &r) const + { + Rational result; -Complex Complex::operator *(const Complex &r) const -{ - Complex result; + result.m_a = m_a * r.m_b - m_b * r.m_a; + result.m_b = m_b * r.m_b; - result.m_real = m_real * r.m_real - m_imag * r.m_imag; - result.m_imag = m_real * r.m_imag + m_imag * r.m_real; + result.simplify(); - return result; -} + return result; + } -Complex &Complex::operator ++() -{ - ++m_real; + Rational Rational::operator *(const Rational &r) const + { + Rational result; - return *this; -} + result.m_a = m_a * r.m_a; + result.m_b = m_b * r.m_b; -const Complex Complex::operator ++(int) -{ - Complex z = *this; + result.simplify(); - ++m_real; + return result; + } - return z; -} + Rational Rational::operator /(const Rational &r) const + { + Rational result; -Complex &Complex::operator --() -{ - --m_real; + result.m_a = m_a * r.m_b; + result.m_b = m_b * r.m_a; - return *this; -} + result.simplify(); -const Complex Complex::operator --(int) -{ - Complex z = *this; + return result; + } - --m_real; + Rational Rational::operator +(int a) const + { + Rational result; - return z; + result.m_a = a * m_b + m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator -(int a) const + { + Rational result; + + result.m_a = -a * m_b + m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator *(int a) const + { + Rational result; + + result.m_a = a * m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator /(int a) const + { + Rational result; + + result.m_a = m_a; + result.m_b = a * m_b; + + return result; + } + + void Rational::disp() const + { + cout << m_a; + if (m_b != 1 && m_a != 0) + cout << '/' << m_b; + cout << endl; + } + + void Rational::simplify() + { + int gcd_val = gcd(abs(m_a), abs(m_b)); + + m_a /= gcd_val; + m_b /= gcd_val; + } + + int Rational::gcd(int a, int b) + { + int temp; + + while (b != 0) { + temp = b; + b = a % b; + a = temp; + } + + return a; + } + + Rational operator -(int a, const Rational &r) + { + Rational result; + + result.m_a = a * r.m_b - r.m_a; + result.m_b = r.m_b; + + return result; + } + + Rational operator /(int a, const Rational &r) + { + Rational result; + + result.m_a = a * r.m_b; + result.m_b = r.m_a; + + return result; + } + + const Rational &Rational::operator +() const + { + return *this; + } + + Rational Rational::operator -() const + { + Rational result; + + result.m_a = -m_a; + result.m_b = m_b; + + return result; + } + + Rational &Rational::operator ++() // prefix ++ + { + m_a = m_a + m_b; + + return *this; + } + + Rational &Rational::operator --() // prefix -- + { + m_a = m_a - m_b; + + return *this; + } + + const Rational Rational::operator ++(int a) // postfix ++ + { + Rational temp = *this; + + cout << a << endl; + m_a = m_a + m_b; + + return temp; + } + + const Rational Rational::operator --(int) // postfix -- + { + Rational temp = *this; + + m_a = m_a - m_b; + + return temp; + } } // app.cpp #include -#include "complex.hpp" +#include "rational.hpp" using namespace std; +using namespace CSD; int main() { - Complex z{3, 2}, k{1, 2}; + Rational x{1, 2}, y{1, 3}, result; - z.disp(); - - auto result = z-- + k; - - z.disp(); - result.disp(); + result = x++ - y; + + result.disp(); // 1/6 + x.disp(); // 3/2 + return 0; } /*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bir sınıf nesnesini [] operatörüyle kullanabilmek için o sınıfta bir [] operatör fonksiyonunun bulunuyor olması gerekir. a bir sınıf nesnesi olmak - üzere a[n] işleminin eşdeğeri a.operator[](n) biçimindedir. Gerçekten de standrat kütüphanedeki vector gibi string gibi sınıflar bu operatör fonksiyonuna sahiptir. + Karşılaştırma operatörleri iki operand'lı operatörler olduğu için bunlar üye fonksiyon olarak yazılacaksa bir parametreye global operatör fonksiyonu + olarak yazılacaksa iki parametreye sahip olmak zorundadır. Bu operatör fonksiyonlarının geri dönüş değerlerinin bool türden olması en normakl durumdur. + Operatör fonksiyonları konusunun girişinde de belirttiğimiz gibi eüer mümkünse tüm karşılaştırma operatörleri için operatör fonksiyonlarının yazılması + iyi bir tekniktir. Eğer nmümkün değilse karşıt operatörlerin yazılmasına gayret edilmelidir. Yani örneğin iki olduğu arasında büyüklük küçüklük ilişkisi + olmayabilir ancak eşitlik ilişkisi olabilir. Bu durumda == operatör fonksiyonuyazılacaksa != operatör fonksiyonu da yazılmalıdır. > operatör fonksiyonu + yazılacaksa < operatör fonksiyonun yazılması da anlamlıdır. Tabii bir operatör fonksiyonun tezatı aslında diğeri kullanılarak da yazılabilir. Örneğin + == operatör fonksiyonu yazılmışsa !(a == b) işlemi zaten a != b anlamına gelmektedir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Aslında iki karşılaştırma operatörü yazılırsa diğer karşılaştırma operatörleri bu iki operatör kullanılarak yazılabilir. Bu iki operatörden biri < ya da > + diğeri ise == ya da != operatörü olabilir. Örneğin < ve == operatörlerine ilişkin operatör fonksiyonlarının yazılmış olduğunu kabul edelim. Şimdi biz a ve + b nesneleriyle bu operatörleri kullanarak 6 karşılaştırma işlemini şöyle yapabiliriz: + + 1) a < b İşlemi: Zaten bu operatör fonksiyonu yazılmıştır. + + 2) a == b İşlemi: Zateb bu operatör fonksiyonu yazılmıştır + + 3) a > b İşlemi: Bu işlemin eşdeğeri b < a olduğuna göre ve < operatör fonksiyonu yazılmış olduğuna göre bu operatör fonksiyonu < operatör fonksiyonu + çağrılarak yazılabilir. Örneğin: + + bool opeartor >(a, b) + { + return b < a; + } + + 4) a <= b İşlemi: Bu işlem "küçüktür ya da eşittir" biçiminde ifade edilebilir. Yani bu operatör fonksiyonu şöyle yazılabilir: + + bool opeartor <=(a, b) + { + return a < b || a == b; + } + + 5) a >= b işlemi: Bu işlem de "büyüktür ya da eşittir" biçiminde ifade edilebilir. Yani bu operatör fonksiyonu şöyle yazılabilir: + + bool opeartor >=(a, b) + { + return b < a || a == b; + } + + 6) a != b İşlemi: Bu işlem "eşit değildir" biçiminde ifade edilebilir. Bu operatör fonksiyonu şöyle yazılabilir: + + bool operator !=(a, b) + { + return !(a == b); + } +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + 82. Ders 08/07/2024 - Pazartesi +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + C++ standartlarında özellikle standart kütüphanede matematikteki "sıralama ilişkisine (ordering relation)" göndermeler yapılmıştır. Yani bazı olgular bu + sıralama ilişkisine ilişkin terminoloji kullanılarak açıklanmıştır. Biz de burada sıralama ilişkisine ilişkin iki önemli kavramı açıklayacağız. Bunlardan + biri İngilizce "total ordering" denilen ilişkidir. Buna Türkçe "tam sıralama ilişkisi" diyebiliriz. Diğeri ise "partial ordering" denilen ilişkidir. Buna da + Türkçe "kısmi sıralama ilişkisi diyebiliriz. + + Eğer bir kümenin elemanları arasında tam sıralama ilişkisi varsa herhangi iki eleman a ve b olmak üzere aşağıdaki üç durumdan biri söz konusudur: + + 1) a < b + 2) b < a + 3) a == b + + Yani biz o kümenin herhangi iki elemanını aldığımızda ya biri diğerinden küçüktür ya da bunlar eşittir. Tam sıralama ilşkisi tipik olarak <= operatörü ile + betimlenmektedir. Örneğin tamsayılar kümesi tam sıralama ilişkisine sahiptir. Yani herhangi iki eleman aldığımızda ya biri diğerinden küçüktür ya da bunlar + eşittir. Tam sıralama ilişkisi kümenin elemanlarının sıraya dizilebilmesi anlamına gelmektedir. Eğer bir kümede tam sıralama ilişkisi varsa biz o küme + üzerinde işlem yapan bir sınıf yazmak istersek o sınıf için yukarıda da belirttiğimiz gibi yalnızca < ve == operatör fonksiyonlarını yazabiliriz. Diğer + karşılaştırma operatörleri bu operatör fonksiyonları kullanılarak tanımlanabilmektedir. + + Kısmi sıralama ilişkisi tam sıralama ilişkisine benzemektedir. Ancak kısmi sırlama ilişkisinde kümenin tüm elemanları arasında küçüklük ya da eşitlik + ilişkisi olmak zorunda değildir. Yalnızca bazı elemanları arasında bu ilişkisi varsa buna kısmi sıralama ilişkisi denilmektedir. Bu durumda kısmi sıralama + ilişkisinde kümenin herhangi iki elemanı a ve b olmak üzere şu dört durumdan biri söz konusudur: + + 1) a < b + 2) b < a + 3) a == b + 4) a ve b karşılaştırılmaz + + Örneğin IEEE gerçek sayılar kümesi kısmi sıralama ilişkisine sahiptir. Çünkü kümedeki NaN gibi bir gerçek sayı belirtmeyen bit kombinasyonları vardır. + Bu NaN sayıları diğer gerçek sayılarla karşılaştırılamaz. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + C++'ın standart kütüphanesinde bulunan bazı fonksiyonlar ve sınıflar bir biçimsde tam sıralama ilişkisini kullanmaktadır. Örneğin sort isimli fonksiyon + şablonu herhangi türden bir diziyi sıraya dizmektedir. Ancak bu sıraya dizme işlemini < operatörünü kullanarak yapmaktadırç. Dolayısıyla sort fonksiyonunun + bizim sınıf nesnelerinden oluşan dizimizi sıraya dizmesini istiyorsak sınıfımızda < operatör fonksiyonunu bulundurmalıyız. Diğer karşılaştırma operatör + fonksiyonlarını bulundurmak zorunda değiliz. Biz henüz şablon (template) işlemlerini görmedik. Ancak burada sort fonksiyonun nasıl kullanıldığını açıklamak + istiyoruz. C'de bir diziyi parametre olarak almak isteyen fonksiyonlar genellikle dizinin başlangıç adresini ve uzunluğunu parametre olarak almaktadır. + Halbuki C++'ın fonksiyon şablonları dizinin başlangıcına ve bitişine ilişkin adresleri (yani ilk elemanın ve son elemandan sonraki olmayan elemanın adreslerini) + almaktadır. Örneğin a dizisi 10 eleman uzunluğunda olsun. Biz bu a dizisini sort fonksiyonuyla şöyle sıraya dizeriz: + + sort(a, a + 10); + + Aşağıda sort fonksiyonun kullanımına örnekler verilmiştir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +#include +#include +#include + +using namespace std; + +class Number { +public: + Number(int val) : m_val(val) + {} + int val() const { return m_val; } + bool operator <(const Number &r) const + { + return m_val < r.m_val; + } +private: + int m_val; +}; + +int main() +{ + Number numbers[] = {Number(12), Number(5), Number(7), Number(56), Number(45)}; + int a[] = {4, 78, 2, 12, 5}; + string names[] = {string("selami"), string("ali"), string("veli"), string("ayse"), string("fatma")}; + + sort(numbers, numbers + 5); + + for (int i = 0; i < 5; ++i) + cout << numbers[i].val() << " "; + cout << '\n'; + + sort(a, a + 5); + + for (int i = 0; i < 5; ++i) + cout << a[i] << " "; + cout << '\n'; + + sort(names, names + 5); + + for (int i = 0; i < 5; ++i) + cout << names[i] << " "; + cout << '\n'; + + return 0; +} + + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Şimdi de rasyonel sayılar üzerinde işlemler yapan Rational sınıfımıza karşılaştırma operatör fonksiyonlarını ekleyelim. Bu durumda sınıf bildirimi şu hale + gelecektir: + + class Rational { + public: + Rational(int a = 0, int b = 1); + + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + + Rational operator +(int a) const; + Rational operator -(int a) const; + Rational operator *(int a) const; + Rational operator /(int a) const; + + friend Rational operator -(int a, const Rational &r); + friend Rational operator /(int a, const Rational &r); + + const Rational &operator +() const; + Rational operator -() const; + + Rational &operator ++(); // prefix ++ + Rational &operator --(); // prefix -- + const Rational operator ++(int); // postfix -- + const Rational operator --(int); // postfix - + + bool operator <(const Rational &r) const; + bool operator >(const Rational &r) const; + bool operator <=(const Rational &r) const; + bool operator >=(const Rational &r) const; + bool operator ==(const Rational &r) const; + bool operator !=(const Rational &r) const; + + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + inline Rational operator +(int a, const Rational &r) + { + return r + a; + } + + inline Rational operator *(int a, const Rational &r) + { + return r * a; + } + + Bu karşılaştırma operatör fonksiyonlarının Rational sınıfı için yazılması aslında oldukça kolaydır. Biz rasyonel sayıların payını paydasına bölüp elde + edilen noktalı sayıları karşılaştırabiliriz. Her ne kadar iki gerçek sayının tam eşitlik karşılatırması yuvarlama hatalarından dolayı sorunluysa da + bizim sınıfımızda rasyonel sayılar üzerinde sadeleştirmeler yaptığımız için böylesi bir sorun oluşmayacaktır. Örneğin < operatör fonksiyonu üye fonksiyon + olarak şöyle yazabiliriz: + + bool Rational::operator <(const Rational &r) const + { + return static_cast(m_a) / m_b < static_cast(r.m_a) / r.m_b; + } + + Aşağıda sınıfın tüm hali verlmiştir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +// rational.hpp + +#ifndef RATIONAL_HPP_ +#define RATIONAL_HPP_ + +namespace CSD +{ + class Rational { + public: + Rational(int a = 0, int b = 1); + + Rational operator +(const Rational &r) const; + Rational operator -(const Rational &r) const; + Rational operator *(const Rational &r) const; + Rational operator /(const Rational &r) const; + + Rational operator +(int a) const; + Rational operator -(int a) const; + Rational operator *(int a) const; + Rational operator /(int a) const; + + friend Rational operator -(int a, const Rational &r); + friend Rational operator /(int a, const Rational &r); + + const Rational &operator +() const; + Rational operator -() const; + + Rational &operator ++(); // prefix ++ + Rational &operator --(); // prefix -- + const Rational operator ++(int); // postfix -- + const Rational operator --(int); // postfix - + + bool operator <(const Rational &r) const; + bool operator >(const Rational &r) const; + bool operator <=(const Rational &r) const; + bool operator >=(const Rational &r) const; + bool operator ==(const Rational &r) const; + bool operator !=(const Rational &r) const; + + void disp() const; + //... + private: + static int gcd(int a, int b); + void simplify(); + private: + int m_a; + int m_b; + }; + + inline Rational operator +(int a, const Rational &r) + { + return r + a; + } + + inline Rational operator *(int a, const Rational &r) + { + return r * a; + } +} + +#endif + +// rational.cpp + +#include +#include +#include +#include "rational.hpp" + +namespace CSD +{ + using namespace std; + + Rational::Rational(int a, int b) + { + + if (b < 0) { + m_a = -a; + m_b = -b; + } + else { + m_a = a; + m_b = b; + } + + if (b == 0) + throw invalid_argument("denominator shall not be zero"); + + simplify(); + } + + Rational Rational::operator +(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b + m_b * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Rational Rational::operator -(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b - m_b * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Rational Rational::operator *(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_a; + result.m_b = m_b * r.m_b; + + result.simplify(); + + return result; + } + + Rational Rational::operator /(const Rational &r) const + { + Rational result; + + result.m_a = m_a * r.m_b; + result.m_b = m_b * r.m_a; + + result.simplify(); + + return result; + } + + Rational Rational::operator +(int a) const + { + Rational result; + + result.m_a = a * m_b + m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator -(int a) const + { + Rational result; + + result.m_a = -a * m_b + m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator *(int a) const + { + Rational result; + + result.m_a = a * m_a; + result.m_b = m_b; + + return result; + } + + Rational Rational::operator /(int a) const + { + Rational result; + + result.m_a = m_a; + result.m_b = a * m_b; + + return result; + } + + void Rational::disp() const + { + cout << m_a; + if (m_b != 1 && m_a != 0) + cout << '/' << m_b; + cout << endl; + } + + void Rational::simplify() + { + int gcd_val = gcd(abs(m_a), abs(m_b)); + + m_a /= gcd_val; + m_b /= gcd_val; + } + + int Rational::gcd(int a, int b) + { + int temp; + + while (b != 0) { + temp = b; + b = a % b; + a = temp; + } + + return a; + } + + Rational operator -(int a, const Rational &r) + { + Rational result; + + result.m_a = a * r.m_b - r.m_a; + result.m_b = r.m_b; + + return result; + } + + Rational operator /(int a, const Rational &r) + { + Rational result; + + result.m_a = a * r.m_b; + result.m_b = r.m_a; + + return result; + } + + const Rational &Rational::operator +() const + { + return *this; + } + + Rational Rational::operator -() const + { + Rational result; + + result.m_a = -m_a; + result.m_b = m_b; + + return result; + } + + Rational &Rational::operator ++() // prefix ++ + { + m_a = m_a + m_b; + + return *this; + } + + Rational &Rational::operator --() // prefix -- + { + m_a = m_a - m_b; + + return *this; + } + + const Rational Rational::operator ++(int a) // postfix ++ + { + Rational temp = *this; + + cout << a << endl; + m_a = m_a + m_b; + + return temp; + } + + const Rational Rational::operator --(int) // postfix -- + { + Rational temp = *this; + + m_a = m_a - m_b; + + return temp; + } + + bool Rational::operator <(const Rational &r) const + { + return static_cast(m_a) / m_b < static_cast(r.m_a) / r.m_b; + } + + bool Rational::operator >(const Rational &r) const + { + return static_cast(m_a) / m_b > static_cast(r.m_a) / r.m_b; + } + + bool Rational::operator <=(const Rational &r) const + { + return static_cast(m_a) / m_b <= static_cast(r.m_a) / r.m_b; + } + + bool Rational::operator >=(const Rational &r) const + { + return static_cast(m_a) / m_b >= static_cast(r.m_a) / r.m_b; + } + + bool Rational::operator ==(const Rational &r) const + { + return static_cast(m_a) / m_b == static_cast(r.m_a) / r.m_b; + } + + bool Rational::operator !=(const Rational &r) const + { + return static_cast(m_a) / m_b != static_cast(r.m_a) / r.m_b; + } +} + +// app.cpp + +#include +#include +#include "rational.hpp" + +using namespace std; +using namespace CSD; + +int main() +{ + Rational x{1, 3}, y{1, 2}; + + if (x > y) + cout << "x > y" << endl; + else if (x < y) + cout << "x < y" << endl; + else if (x == y) + cout << "x < y" << endl; + + Rational rnumbers[] = {Rational(1, 3), Rational(3, 4), Rational(4, 3), Rational(2, 3), Rational(3, 7)}; + + sort(rnumbers, rnumbers + 5); + + for (auto &r : rnumbers) + r.disp(); + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Bir sınıf nesnesini [] operatörüyle kullanabilmek için o sınıfta bir [] operatör fonksiyonunun bulunuyor olması gerekir. a bir sınıf nesnesi olmak üzere + a[n] işleminin eşdeğeri a.operator[](n) biçimindedir. Gerçekten de standart kütüphanedeki vector gibi string gibi sınıflar bu operatör fonksiyonuna sahiptir. [] opeartör fonksiyonu üye operatör fonksiyonu olarak yazılmak zorundadır. - [] operatörüne ilişkin operatör fonksiyonları yazılırken fonksiyonun bir referansa geri dönmesi gerekir. Çünkü köşeli parantez - ifadesine aynı zamanda atama da yapılabilmektedir. const nesnelerle köşeli parantez operatörünün kullanılabilmesi için bu operatör fonksiyonun - const bir biçimi de bulundurulmalıdır. Tabii bu biçimin const bir referansa geri dönmesi uygun olur. + [] operatör fonksiyonun bir paramtresi olmak zorundadır. Buı operatör fonksiyonlarının geri dönüş değerleri herhangi bir türdne olabilirse de en uygun durum + bu fonksiyonların geri dönüş değerlerinin bir referans olmasıdır. Böylelikle köşeli parantezli ifadeye atama yapılabilir. Örneğin: + + result = a[n] + 10; + + ifadesinin eşdeğeri şöyledir: + + result = a.operator [](n) + 10; + + Örneğin: + + a[n] = 10; + + ifadesinin eşdeğeri de şöyledir: + + a.operator [](n) = 10; + + İşte bir fonksiyonun geri dönüş değerine bir atama yapabilmek için fonksiyonun geri dönüş değerinin bir referans belirtmesi gerekir. Şimdi a nesnesinin + const bir sınıf nesnesi olduğunu düşünelim. Bu durumda const nesneyle const olmayan üye fonksiyon çağrılamayacağı için [] operatör fonksiyonunun const bir + biçiminin de bulunması gerekir. Tabii const nesnelerin [] operatöryle kullanımları sonucunda elde edilen nesneye atama yapılmasının engellenmesi için bu + const biçimin geri dönüş değerinin de const referans olması uygun olur. Özetle eğer T sınıfı için [] operatör fonksiyonu yazılmak isteniyorsa bu fonksiyonların + parametrik yapılarınınm aşağıdaki gibi olması uygundur. + + T &operator [](K index); + const T &operator[] (K index) const; + + Burada T türü [] ile elde edilen nesnenin türünü temsil etmektedir. K türü ise [] içerisinde kullanılan ifadenin türünü temsil etmektedir. C'de ve C++'ta + köşeli parantezin bir operatör olduğunu dolayısıyla onun içerisindeki ifadeinin de tamsayı türlerine ilişkin olması gerektediğini anımsayınız. Ancak C++'ta + eğer [] operatörünün operandı bir sınıf türünden nesne ise bu durumda köşeli parantezin içerisindeki ifadenin tamsayı türündne olması gerekmez. Örneğin a + bir sınıf türünden nesne belirtiyor olsun: + + result = a["ankara"]; + + Burada a bir adres belirtiyorsa bu ifade geçersizdir. Ancak C++ta a bir sınıf türünden nesne belirtiyorsa bu ifadenin eşdeğeri şöyledir: + + result = a.operator []("ankara"); + + O halde böylesi bir kullanım C++'ta mümkün olabilir. + + Tabii bir sınıfta [] operatörüne ilişkin operatör fonksiyonun bulunması için sınıfın "bir indekse dayalı bir değeri elde edecek bir organizasyona sahip olması" + gerekir. Yani sınıfımız için bu [] kullanımı anlamlı ise bu operatör fonksiyonunu yazmalıyız. Aşağıdaki örnekte Date sınıfının day, month, year bileşenleri [] operatör fonksiyonu ile elde edilmektedir. --------------------------------------------------------------------------------------------------------------------------------------------------------------*/ @@ -28506,6 +31375,746 @@ int main() return 0; } +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Şimdi de int türden bir diziyi temsil eden IntArray isimli bir sııf için [] operatör fonksiyonunu yazalım. Anımsayacağınız gibi zaten C++'ın standart + kütüphanesinde C++11 ile eklenen array isimli bir sınıf şablonu bulunmaktadır. Aşağıda bu IntArray sınıfının gerçekleştirimini veriyoruz. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +// intarray.hpp + +#ifndef INTARRAY_HPP_ +#define INTARRAY_HPP_ + +#include +#include + +namespace CSD +{ + class IntArray { + public: + IntArray(); + IntArray(size_t size); + IntArray(std::initializer_list il); + IntArray(const IntArray &ia); + IntArray(IntArray &&ia); + ~IntArray(); + + IntArray &operator =(const IntArray &ia); + IntArray &operator =(IntArray &&ia); + + int &operator[](size_t index); + const int &operator[](size_t index) const; + + + void disp() const; + + private: + int *m_array; + std::size_t m_size; + }; +} + +#endif + +// intarry.cpp + +#include +#include +#include "intarray.hpp" + +using namespace std; + +namespace CSD +{ + IntArray::IntArray() + { + m_array = nullptr; + m_size = 0; + } + + IntArray::IntArray(size_t size) + { + m_array = new int[size]; + m_size = size; + } + + IntArray::IntArray(initializer_list il) + { + m_size = il.size(); + m_array = new int[m_size]; + + for (size_t i = 0; int val : il) + m_array[i++] = val; + } + + IntArray::IntArray(const IntArray &ia) + { + m_array = new int[ia.m_size]; + for (size_t i = 0; i < ia.m_size; ++i) + m_array[i] = ia.m_array[i]; + m_size = ia.m_size; + } + + IntArray::IntArray(IntArray &&ia) + { + m_array = ia.m_array; + m_size = ia.m_size; + ia.m_array = nullptr; + } + + IntArray::~IntArray() + { + delete[] m_array; + } + + void IntArray::disp() const + { + for (size_t i = 0; i < m_size; ++i) + cout << m_array[i] << " "; + cout << endl; + } + + IntArray &IntArray::operator =(const IntArray &ia) + { + if (this == &ia) + return *this; + + delete[] m_array; + + m_array = new int[ia.m_size]; + for (size_t i = 0; i < ia.m_size; ++i) + m_array[i] = ia.m_array[i]; + m_size = ia.m_size; + + return *this; + } + + IntArray &IntArray::operator =(IntArray &&ia) + { + if (this == &ia) + return *this; + + swap(m_array, ia.m_array); + m_size = ia.m_size; + + return *this; + } + + int &IntArray::operator[](size_t index) + { + if (index >= m_size) + throw invalid_argument("index aout of range"); + + return m_array[index]; + } + + const int &IntArray::operator[](size_t index) const + { + if (index >= m_size) + throw invalid_argument("index aout of range"); + + return m_array[index]; + } +} + +// app.cpp + +#include +#include "intarray.hpp" + +using namespace std; +using namespace CSD; + +IntArray foo() +{ + IntArray ia = {1, 2, 3, 4, 5}; + + //... + + return ia; +} + +int main() +{ + IntArray ia; + const IntArray cia = {10, 20, 30, 40}; + + ia = foo(); + + ia.disp(); + + ia[4] = 500; + + ia.disp(); + + cia.disp(); + + cout << cia[2] << endl; + + // cia[2] = 10; ---> error! + + return 0; +} + + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + C++'ta bir sınıf nesnesi sanki bir fonksiyonmuş gibi fonksiyon çağırma operatörüyle kullanılabilir. Bunun için sınıfın fonksiyon çağırma operatör fonksiyonun + yazılmış olması gerekir. a bir sınıf türünden nesne belirtmek üzere: + + a(...) + + ifadesinin eşdeğeri: + + a.operator()(...) + + biçimindedir. Burada operator () operatör fonksiyonun ismini belirtmektedir. Buradaki parantezlerin içi boş olmak zorundadır. Diğer (...) ise gerçekten fonksiyon + çağırma operatörünü belirtmektedir. + + Bir sınıf nesnesinin bir fonksiyon gibi kullanılmasına " fonksiyon nesneleri (function object)" ya da İngilizce kısaca "functor" denilmektedir. C++'ın standart + kütüphanesinde bir fonksiyon isteyen şablon sınıflar bu biçimde functor nesnelerini de kabul ederler. Sınıflar durumsal bilgileri tutabildikleri için callback + mekanizmasında fonksiyonlara gör avantajlara sahip olmaktadır. Fonksiyon çağırma operatör fonksiyonu üye operatör fonksiyonu olarak yazılmak zorundadır. + + Fonksiyon çağırma operatör fonksiyonları herhangi bir parametrik yapıya ve geri dönüş değerine sahip olabilirler. Aşağıdaki örnekte Pow isimli sınıf nesne + yaratılırken yapıcı fonksiyonla üst değerini parametre olarak almaktadır. Sonra da fonksiyon çağırma operatör fonksiyonu parametre yoluyla aldığı değerin + burada belirtilen üssüne geri dönmektedir. Örneğin: + + class Pow { + public: + Pow(double pow) : m_pow(pow) + {} + double operator ()(double base) + { + return pow(base, m_pow); + } + private: + double m_pow; + }; + + Kullanım çyle olabilir: + + Pow pow{2}; + double result; + + result = pow(3); // result = pow.operator ()(3) + cout << result << endl; // 9 +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +#include +#include + +using namespace std; + +class Pow { +public: + Pow(double pow) : m_pow(pow) + {} + double operator ()(double base) + { + return pow(base, m_pow); + } +private: + double m_pow; +}; + +int main() +{ + Pow pow{2}; + double result; + + result = pow(3); // result = pow.operator ()(3) + cout << result << endl; // 9 + + result = pow(4); // result = pow.operator ()(3) + cout << result << endl; // 16 + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Örneğin C++'ın standart kütüphanesi içerisindeki for_each fonksiyonu dolaşılabilir bir dizilimi (bu terimi özennikle kullandık) paramatere olarak alır ve + dizilimin her elemanı ile bu fonksiyonu çağırır. for_eack bir fonksi,yon şablonu biçiminde yazılmıştır. Biz bu fonksiyona normal bir fonksiyonu parametre + olarak geçirebileceğimiz gibi fonksiyon çağırma operatör fonksiyonu yazılmış olan bir fonksiyon nesnesini de parametre olarak geçirebiliriz. Daha önceki + örnekte de belirttiğimiz gibi bu tür standart fonksiyonlara parametre olarak bir dizi geçirilecekse dizi onun ilk elemanının ve son elemanından sonraki + elemanın adresi yoluyla geçirilmelidir. Örneğin: + + void foo(int val); + + int a[] = {1, 2, 3, 4, 5}; + + for_each(a, a + 5, foo); + + Burada dizinin her elemanı için foo fonksiyonu çağrılacaktır. Dizi elemanları bu fonksiyona argüman olarak geçirilecektir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +#include +#include +#include + +using namespace std; + +class Pow { +public: + Pow(double pow) : m_pow(pow) + {} + void operator ()(double base) + { + cout << pow(base, m_pow) << endl; + } +private: + double m_pow; +}; + +void foo(int val) +{ + cout << val << endl; +} + +int main() +{ + int a[] = {1, 2, 3, 4, 5}; + Pow pow{2}; + + for_each(a, a + 5, foo); + cout << "----------------" << endl; + for_each(a, a + 5, pow); + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Fonksiyon nesnelerinin kullanımına diğer bir örnek de copy_if isimli standart C++ fonksiyonuna dayalı olarak verilebilir. copy_if fonksiyonu bir dizilim + içerisindeki değerleri başka bir dizilem kopyalamak için kullanılmaktadır. Ancak kopyalamada tüm elemanlar değil bizim istediğimiz koşulu sağlayan elemanlar + kopyalanmaktadır. Fonksiyon bizden bir fonksiyon ister, kaynak dizilimdeki elemanları bu fonksiyona argüman yaparak fonksiyonu çağırır. Eğer fonksiyonm true + ile geri dönerse o elemanı kopyalamaya dahil eder, false ile geri dönerse o elemanı kopyalamaya dahil etmez. Fonksion hedef diziye son kopyalanan elemanın + hedef dizideki adresinden sonraki adresi geri döndürmektedir. Fonksiyonun parametreleri şöyledir. + + copy_if(source_beg, source_end, target_beg, function) + + Örneğin: + + int a[10] = {4, 5, 6, 12, 45, 67, 21, 45, 78, 23}; + int b[10]; + int *end; + + end = copy_if(a, a + 10, b, foo); + + Burada a dizisinin tüm elemanları sırasıyla foo fonksiyonuna sokulacak eğer foo fonksiyonu true ile geri dönerse o eleman b'ye kopyalanacaktır. Fonksiyon b'de + kopyalanan son elemandan sonraki adresle geri dönmektedir. C++'ın standart kütüphanesinde bool değere geri dönen bu biçimde callback fonksiyonlara "predicate" + denilmektedir. Bu predicate fonksiyon tek parametreye sahipse "unary predicate", iki parametreye sahipse "binary predicate" denilmektedir. + +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +#include +#include +#include + +using namespace std; + +class UnaryPredicate { +public: + UnaryPredicate(int val) : m_val(val) + {} + bool operator ()(int a) + { + return a > m_val; + } +private: + int m_val; + +}; + +int main() +{ + int a[10] = {4, 5, 6, 12, 45, 67, 21, 45, 78, 23}; + int b[10]; + int *end; + + end = copy_if(a, a + 10, b, UnaryPredicate(30)); + + for (int *pi = b; pi < end; ++pi) + cout << *pi << " "; + cout << endl; + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Bir sınıf türünden nesne ile * gösterici operatörünü (indirection operator) kullanırsak bu durumda sınıfın operator * fonksiyonu çağrılmaktadır. Başka + bir deyişle a bir sınıf türünden nesne olmak üzere *a işlemi ile a.operator *() çağrısı eşdeğerdir. * gösterici operatörü tek operand'lı (unary) bir operatör + olduğu için bu operatör fonksiyonu üye operatör fonksiyonu olarak yazılacaksa operatör fonksiyonunun sıfır parametresi, global operatör fonksiyonu olarak + yazılacaksa operatör fonksiyonunun bir parametresi olmak zorundadır. + + Pekiyi * gösterici operatörüne ilişkin operatör fonksiyonu ne ile geri dönmelidir? Bu operatör bir nesne ürettiğini göre fonksiyonun geri dönüş değeri de + bir referans türünden olmalıdır. Çünkü bir fonksiyon çağrısının nesne belirtmesi için fonksiyonun geri dönüş değerinin referans olması gerekir. + + * gösterici operatörüne ilişkin operatör fonksiyonlarının yazılmasının amacı "bir sınıfın gösterici gibi davranmasını" sağlamaktır. Böyle sınıflara C++'ta + "akıllı göstericler (smart pointers)" denilmektedir. Tabii sınıfın aynı zamanda "gösterdiği yer const olan bir const göstericiyi" temsil edebilmesi için + sınıfa const bir * operatör fonksiyonun da eklenmesi gerekir. const * operatör fonksiyonunun geri dönüş değerinin const referans olması gerekir. Örneğin: + + class IntPointer { + public: + IntPointer(int *pi) : m_pi(pi) + {} + ~IntPointer() + { + delete m_pi; + } + int &operator *() + { + return *m_pi; + } + const int &operator *() const + { + return *m_pi; + } + private: + int *m_pi; + }; + + Burada IntPoiner sınıfının yapcı fonksiyonu bir nesnenin adresini almış ve o adresi sınıfın gösterici veri elemanında saklamıştır. Sonra da * operatörü + uygulandığında bu operatmr fonksiyonu bu göstericinin gösteridği yerdeki nesneyi geri döndürmüştür. Sınıfı aşağıdaki gibi kullanabiliriz: + + IntPointer ip(new int(10)); + + cout << *ip << endl; // cout << ip.operator *() << endl; + *ip = 20; // ip.operator *() = 20 + cout << *ip << endl; // cout << ip.operator *() << endl; + +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Akıllı göstericiler oluşturmak için gereken diğer bir operatör fonksiyonu da -> operatör fonksiyonudur. Her ne kadar -> operatörü iki operand'lı bir operatörse + de sanki tek operand'lı bir operatörmüş gibi yazılmaktadır. -> operatörüne ilişkin operatör fonksiyonun üye operatör fonksiyonu oalrak yazılması zorunlu + tutulmuştur. -> operatör fonksiyonunun sıfır parametresi olmak zorundadır. a bir sınıf türünden nesne belirtmek üzere a->b ifadesiin eşdeğeri şöyledir: + + a.operator ->()->b + + Burada bir noktaya dikkatini çekmek istiyoruz: a->b işleminde a.operator ->() ifdadesi yalnızca a-> işlemine karşı gelmektedir. O halde a->b ifadesinin eşdeğeri + a.operator ->()->b biçiminde olacaktır. Bu durumda a->b ifadesinin geçerli olabilmesi için -> operatör fonksiyonunun bir sınıf nesnesinin adresiyle geri + dönmesi o geri döndürülen sınıfın da b isimli bir elemanın olması gerekmektedir. + + Burada yine * operatöründeki gibi const bir göstericinin takilt edilmesi gerekiyorsa sınıfa const bir -> operatör fonksiyonu eklenmelidir. Tabii artık bu + fonksiyonun geri döndürdüğü sınıf türünden adresin const olması gerekir. Örneğin: + + struct Sample { + Sample(int x, int y, int z) + { + this->x = x; + this->y = y; + this->z = z; + } + + int x, y, z; + }; + + class SamplePointer { + public: + SamplePointer(Sample *ps) : m_ps(ps) + {} + ~SamplePointer() + { + delete m_ps; + } + Sample &operator *() + { + return *m_ps; + } + const Sample &operator *() const + { + return *m_ps; + } + Sample *operator ->() + { + return m_ps; + } + const Sample *operator ->() const + { + return m_ps; + } + private: + Sample *m_ps; + }; + + Burada SamplePointer sınıfı Sample sınıfı türünden bir göstericiyi temsil etmektedir. Dolayısıyla sınıfa * operatör fonksiyonunun yanı sıra -> operatör + fonksiyonu da eklenmiştir. Sınıfın kullanımına şöyle bir örnek verilebilir: + + SamplePointer sp{new Sample(10, 20, 30)}; + + cout << sp->x << ", " << sp->y << ", " << sp->z << endl; + + Bu önekte sp->x gibi bir ifadenin sp.operator ->()->x ile eşdeğer olduğuna dikkat ediniz. Tabii sınıfın -> operatör fonksiyonu ancak sınııfn bir sınıf + türünden göstericiyi taklit etmesi durumunda bulundurulabilir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +#include + +using namespace std; + +struct Sample { + Sample(int x, int y, int z) + { + this->x = x; + this->y = y; + this->z = z; + } + + int x, y, z; +}; + +class SamplePointer { +public: + SamplePointer(Sample *ps) : m_ps(ps) + {} + ~SamplePointer() + { + delete m_ps; + } + Sample &operator *() + { + return *m_ps; + } + const Sample &operator *() const + { + return *m_ps; + } + Sample *operator ->() + { + return m_ps; + } + const Sample *operator ->() const + { + return m_ps; + } +private: + Sample *m_ps; +}; + +int main() +{ + SamplePointer sp{new Sample(10, 20, 30)}; + const SamplePointer csp{new Sample(100, 200, 300)}; + + cout << (*sp).x << ", " << (*sp).y << ", " << (*sp).z << endl; + cout << sp->x << ", " << sp->y << ", " << sp->z << endl; + + sp->x = 40; // sp.operator ->()->x = 40 + sp->y = 50; // sp.operator ->()->y = 50 + sp->z = 60; // sp.operator ->()->z = 50 + + cout << sp->x << ", " << sp->y << ", " << sp->z << endl; + + cout << (*csp).x << ", " << (*csp).y << ", " << (*csp).z << endl; + + csp->x = 1000; // error + + return 0; +} + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + Pekiyi bir sınıfın bir göstericiyi taklit etmesinin nasıl bir faydası olabilir? Böyle bir durumun en bariz faydası "dinamik tahsisatlar için otomatik + boşaltımın" sağlanabilmesidir. Yani bu sayede programcı dinamik olarak tahsis ettiği nesnenin adersini bir akıllı gösterici sınıfına verir. delete işlemi de + sınıf nesnesi faaliyet alanını bitirdiğinde çağrılacak yıkıcı fonksiyon ile otomatik yapılır. Örneğin: + + //... + { + IntPointer ip(new int(10)); + + cout << *ip << endl; + + *ip = 20; + + cout << *ip << endl; + } + //.... + + Burada new ile tahsisatı rpogramcı yapmıştır. Ancak delete ile geri bırakma otomatik biçimde nesne faaliyet alanından çıktığında yapılacaktır. Burada size + bir delete işlemi için bu kadar zahmete katlanmak saçma gelebilir. Ancak başka birtakım bağlamlarda bu otomatik boşaltım oldukça fayda sağlamaktadır. Örneğin + birtakım dinamik tahsisatlar yapıldıktan sonra bir exception oluşursa bellek sızıntısı bu yolla engellenebilmektedir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + + + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + C++11 ile birlikte şablon tabanlı unique_ptr ismiyle genel bir smart pointer sınıfı standart kütüphaneye eklenmiştir. unique_ptr sınıfı (ismi üzerinde) + bir nesneyi gösteren tek bir smart pointer nesnesinin olmasını hedefleyen bir sınıftır. uniqe_ptr bir göstericiyi taklit eder. Ancak o göstericinin gösterdiği + yeri gösteren başka bir uniqe_ptr nesnesinin bulunamayacağını da garanti etmektedir. Başka bir deyişle hiçbir zaman iki unique_ptr nesnesi aslında aynı dinamik + alanı gösteremez. + + uniqe_ptr nesnesi dinamik tahsis edilmiş bir nesnenin adresiyle yaratılmalıdır. Örneğin: + + unique_ptr pi(new int); + unique_ptr ps(new string("ankara")); + + unique_ptr sınıfının yapıcı fonksiyonu "explicit" olduğu için (explicit yapıcı fonksiyonlar ileride ele elınacaktır) biz nesneyi '=' ile llkdeğer vererek yaratamayız. + Örneğin: + + unique_ptr pi = new int; // error! + unique_ptr pi(new int); // geçerli + unique_ptr pi{new int}; // geçerli + + unique_ptr sınıfının * ve -> operatörleri overload edilmiştir. Örneğin: + + unique_ptr ps(new string("ankara")); + string result; + + cout << *ps << endl; + + result = ps->substr(0, 3); + cout << result; + + Sınıfın yıkıcı fonksiyonu tabii dinamik alanı delete etmektedir. Sınıfın kopya yapıcı fonksiyonu ve kopya operatör fonksiyonu "deleted" biçimdedir. Yani aşağıdaki + gibi biz unique_ptr nesnesinin kopyasını çıkartamayız: + + unique_ptr p1(new int); // geçerli + unique_ptr p2(p1); // error! + + p2 = p1; // error! + + Sınıfın release isimli üye fonksiyonu sınıfın tuttuğu adresini geri dönüş değeri olarak verir. Ancak sınıfın içerisindeki göstericiye null adres atar. Yani release + işleminden sonra artık sınıf o dinamik nesneyi göstermez hale gelr. Örneğin: + + #include + #include + + using namespace std; + + int main() + { + unique_ptr us(new string("ankara")); + string *s; + + s = us.release(); // us artık dinamik nesneyi göstermiyor, s artık dinamik nesneyi gösteriyor + + cout << *s << endl; + + delete s; // delete etmek bizim sorumluluğumuzda + + return 0; + } + + sınıfın reset üye fonksiyonu bizden yeni bir nesneyi parametre olarak alır. Eskisini delete eder. Artık nesne yeni dinamik nesneyi gösterir duruma gelir. Örneğin: + + #include + #include + + using namespace std; + + int main() + { + unique_ptr us(new string("ankara")); + + us.reset(new string("izmir")); + + cout << *us << endl; + + return 0; + } + + reset fonksiyonuu default argümanla çağırırsak nesnin içerisindeki göstericiye null adres atanır. Tabii yine nesne daha önce gösterdiği dinamik nesneyi delete + edecektir. Örneğin: + + unique_ptr us(new string("ankara")); + + us.reset(); // artık us bir nesneyi göstermiyor, eski alan delete edildi + + Nesneyi transfer etmek için release ile reset beraber kullanılmalıdır. Örneğin: + + #include + #include + + using namespace std; + + int main() + { + unique_ptr us1(new string("ankara")); + unique_ptr us2; + + us2.reset(us1.release()); // transfer etmenin normal yöntemi + + cout << *us2 << endl; + + return 0; + } + + Sınıfın get üye fonksiyonu sınıfın tuttuğu nesne adresini bize verir. get fonksiyonu sahipliği release gibi bırakmamaktadır. Dolayısıyla get kullanırken dikkat ediniz. + Örneğin: + + #include + #include + + using namespace std; + + int main() + { + unique_ptr us(new string("ankara")); + string *s; + + s = us.get(); // us nesnenin adresini tutmaya devam ediyor + + cout << *us << endl; + + return 0; + } + + unique_ptr sınıfının kopya yapıcı fonksiyonu yoktur ancak taşıma yapıcı fonksiyonu vardır. Benzer biçimde sınıfın kopya atamam operatör fonksiyonu yoktur, + ancak taşıma atama operatör fonksiyonu vardır. Örneğin: + + #include + #include + + using namespace std; + + unique_ptr foo() + { + unique_ptr us(new string("ankara")); + + return us; // geçerli, çünkü sınıfın taşıma yapıcı fonksiyonu vardır + } + + int main() + { + unique_ptr us(foo()); + + cout << *us << endl; // ankara + + us = unique_ptr(new string("izmir")); // geçerli, sınıfın taşıma atama operatör fonksiyonu var + + cout << *us << endl; // izmir + + return 0; + } + +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------------------------- + uniqe_ptr sınıfının "deleter" denilen ikinci bir şablon parametresi daha vardır. Deleter nesneyi yok ederken çağrılacak fonksiyonu belirtir. Bu şablon parametresi + default durumda delete operatörü ile silme yapan bir sınıfı argüman olarak almıştır. Programcı isterse silme işlemini kendi belirlediği bir fonksiyonla ya + da bir sınıfla yapabilir. Tabii böyle bir sınıf yazılırken sınıfın fonksiyon çağırma operatör fonksiyonunun yazılmış olması gerekir. Bu konu izleyen bölümde + ele alınacaktır. Aşağıda deleter kullanımına ilişkin bir örnek verilmiştir. +--------------------------------------------------------------------------------------------------------------------------------------------------------------*/ + +#include +#include + +using namespace std; + +void foo(int *pi) +{ + cout << "foo called" << endl; + + delete pi; +} + +int main() +{ + unique_ptr a(new int, foo); + + *a = 10; + + cout << *a << endl; + + return 0; +} + /*------------------------------------------------------------------------------------------------------------------------------------------------------------- En önemli operatör fonksiyonlarından biri de "atama operatör" fonksiyonudur. Bir atama işleminde sol taraftaki nesne bir sınıf türündense bu duurmda atama işlemi sınıfın "atama operatör fonksiyonu" denilen bir fonksiyon çağrılarak yapılır. Yani a bir sınıf nesnesi olmak üzere: @@ -29703,392 +33312,6 @@ int main() return 0; } -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - C++'ta bir sınıf nesnesi sanki bir fonksiyonmuş gibi fonksiyon çağırma operatörüyle kullanılabilir. Bunun için sınıfın fonksiyon çağırma operatör fonksiyonun - yazılmış olması gerekir. a bir sınıf türünden nesne olmak üzere: - - a(...) - - ifadesinin eşdeğeri: - - a.operator()(...) - - biçimindedir. Bir sınıf nesnesinin bir fonksiyon gibi kullanılmasına İngilizce "function object" ya da kısaca "functor" denilmektedir. C++'ın standart kütüphanesinde - bir fonksiyon isteyen şablon sınıflar bu biçimde functor nesnelerini de kabul ederler. Sınıflar durumsal bilgileri tutabildikleri için yetenekli fonksiyonların yazılmasına - izin vermektedir. Fonksiyon çağırma operatör fonksiyonu üye operatör fonksiyonu olarak yazılmak zorundadır. - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -// app.cpp - -#include -#include - -using namespace std; - -class Sample { -public: - Sample(const char *str) : m_str(str) - {} - void operator()(int a, int b) const; -private: - string m_str; -}; - -void Sample::operator()(int a, int b) const -{ - cout << m_str << ": " << a << ", " << b << endl; -} - -int main() -{ - Sample s("test"); - - s(10, 20); // s.operator()(10, 20) - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - C++'ta bir gösterici gibi davranan sınıflara "akıllı gösterici (smart pointer)" sınıfları denilmektedir. C++11 ile birlikte C++'a unique_ptr gibi, shared_ptr - gibi smart pointer sınıfları eklenmiştir. Bir sınıfın bir gösterici gibi davranmasının bazı faydaları söz konusu olabilir. Ancak en önemli fayda otomatik boşaltımın - sağlanmasıdır. Programcı smart pointer sınıfı için bir nesneyi dinamik olarak tahsis eder. Ancak bunun delete edilmesi sınıfın yıkıcı fonksiyonu yoluyla otomatik - yapılır. Tabii standart kütüphanedeki unique_ptr gibi shared_ptr gibi sınıflar şablon olarak yazılmıştır. - - * operatörü bir sol taraf değeri ürettiğine göre bu operatöre ilişkin operatör fonksiyonunun bir referansa geri dönmesi uygun olur. - - Aşağıda int türünden bir göstericiyi taklit eden bir smart pointer sınıfı örneği verilmiştir. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include - -using namespace std; - -class IntPtr { -public: - IntPtr(int *pi) : m_pi(pi) - {} - ~IntPtr() - { - delete m_pi; - } - int &operator *() - { - return *m_pi; - } - const int &operator *() const - { - return *m_pi; - } - int *release() - { - auto temp = m_pi; - m_pi = nullptr; - - return temp; - } - void reset(int *pi) - { - delete m_pi; - - m_pi = pi; - } -private: - int *m_pi; -}; - - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - -> operatör fonksiyonu bir sınıf türünden gçstriciyi taklit eden sınıflar için kullanılmaktadır. -> operatör fonksiyonu üye fonksiyon biçiminde yazılmak zorundadır. - -> operatör fonksiyonu bir sol taraf değeri ile değil bizzat sınıf türünden bir adresle geri dönmelidir. Çünkü s bir smart pointer nesnesi olmak üzere: - - s->a - - gibi bir işlemin C++ operatör fonksiyonu eşdeğeri aslında şöyledir: - - s.operator->()->a - - Aşağıda string sınıfı türünden göstericiyi temsil eden örnek bir smart pointer sınıfı verilmiştir. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include -#include - -using namespace std; - -class StringPtr { -public: - StringPtr (string *ps) : m_ps(ps) - {} - ~StringPtr() - { - delete m_ps; - } - string &operator *() const - { - return *m_ps; - } - string *operator ->() const - { - return m_ps; - } -private: - string *m_ps; -}; - -int main() -{ - StringPtr ss(new string("ankara")); - string s; - - cout << *ss << endl; - - s = (*ss).substr(0, 3); - cout << s << endl; - - s = ss->substr(0, 3); // s = ss.operator->()->substr(0, 3) - cout << s << endl; - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - C++11 ile birlikte şablon tabanlı unique_ptr ismiyle genel bir smart pointer sınıfı standart kütüphaneye eklenmiştir. unique_ptr sınıfı (ismi üzerinde) - bir nesneyi gösteren tek bir smart pointer nesnesinin olmasını hedefleyen bir sınıftır. uniqe_ptr bir göstericiyi taklit eder. Ancak o göstericinin gösterdiği - yeri gösteren başka bir uniqe_ptr nesnesinin bulunamayacağını da garanti etmektedir. Başka bir deyişle hiçbir zaman iki unique_ptr nesnesi aslında aynı dinamik alanı gösteremez. - - uniqe_ptr nesnesi dinamik tahsis edilmiş bir nesnenin adresiyle yaratılmalıdır. Örneğin: - - unique_ptr pi(new int); - unique_ptr ps(new string("ankara")); - - unique_ptr sınıfının yapıcı fonksiyonu "explicit" olduğu için (explicit yapıcı fonksiyonlar ileride ele elınacaktır) biz nesneyi '=' ile llkdeğer vererek yaratamayız. - Örneğin: - - unique_ptr pi = new int; // error! - unique_ptr pi(new int); // geçerli - unique_ptr pi{new int}; // geçerli - - unique_ptr sınıfının * ve -> operatörleri overload edilmiştir. Örneğin: - - unique_ptr ps(new string("ankara")); - string result; - - cout << *ps << endl; - - result = ps->substr(0, 3); - cout << result; - - Sınıfın yıkıcı fonksiyonu tabii dinamik alanı delete etmektedir. Sınıfın kopya yapıcı fonksiyonu ve kopya operatör fonksiyonu "deleted" biçimdedir. Yani aşağıdaki - gibi biz unique_ptr nesnesinin kopyasını çıkartamayız: - - unique_ptr p1(new int); // geçerli - unique_ptr p2(p1); // error! - - p2 = p1; // error! - - Sınıfın release isimli üye fonksiyonu sınıfın tuttuğu adresini geri dönüş değeri olarak verir. Ancak sınıfın içerisindeki göstericiye null adres atar. Yani release - işleminden sonra artık sınıf o dinamik nesneyi göstermez hale gelr. Örneğin: - - #include - #include - - using namespace std; - - int main() - { - unique_ptr us(new string("ankara")); - string *s; - - s = us.release(); // us artık dinamik nesneyi göstermiyor, s artık dinamik nesneyi gösteriyor - - cout << *s << endl; - - delete s; // delete etmek bizim sorumluluğumuzda - - return 0; - } - - sınıfın reset üye fonksiyonu bizden yeni bir nesneyi parametre olarak alır. Eskisini delete eder. Artık nesne yeni dinamik nesneyi gösterir duruma gelir. Örneğin: - - #include - #include - - using namespace std; - - int main() - { - unique_ptr us(new string("ankara")); - - us.reset(new string("izmir")); - - cout << *us << endl; - - return 0; - } - - reset fonksiyonuu default argümanla çağırırsak nesnin içerisindeki göstericiye null adres atanır. Tabii yine nesne daha önce gösterdiği dinamik nesneyi delete - edecektir. Örneğin: - - unique_ptr us(new string("ankara")); - - us.reset(); // artık us bir nesneyi göstermiyor, eski alan delete edildi - - Nesneyi transfer etmek için release ile reset beraber kullanılmalıdır. Örneğin: - - #include - #include - - using namespace std; - - int main() - { - unique_ptr us1(new string("ankara")); - unique_ptr us2; - - us2.reset(us1.release()); // transfer etmenin normal yöntemi - - cout << *us2 << endl; - - return 0; - } - - Sınıfın get üye fonksiyonu sınıfın tuttuğu nesne adresini bize verir. get fonksiyonu sahipliği release gibi bırakmamaktadır. Dolayısıyla get kullanırken dikkat ediniz. - Örneğin: - - #include - #include - - using namespace std; - - int main() - { - unique_ptr us(new string("ankara")); - string *s; - - s = us.get(); // us nesnenin adresini tutmaya devam ediyor - - cout << *us << endl; - - return 0; - } - - unique_ptr sınıfının kopya yapıcı fonksiyonu yoktur ancak taşıma yapıcı fonksiyonu vardır. Benzer biçimde sınıfın kopya atamam operatör fonksiyonu yoktur, - ancak taşıma atama operatör fonksiyonu vardır. Örneğin: - - #include - #include - - using namespace std; - - unique_ptr foo() - { - unique_ptr us(new string("ankara")); - - return us; // geçerli, çünkü sınıfın taşıma yapıcı fonksiyonu vardır - } - - int main() - { - unique_ptr us(foo()); - - cout << *us << endl; // ankara - - us = unique_ptr(new string("izmir")); // geçerli, sınıfın taşıma atama operatör fonksiyonu var - - cout << *us << endl; // izmir - - return 0; - } - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - uniqe_ptr sınıfının "deleter" denilen ikinci bir şablon parametresi daha vardır. Deleter nesneyi yok ederken çağrılacak fonksiyonu belirtir. Bu şablon parametresi - default durumda delete operatörü ile silme yapan bir sınıfı argüman olarak almıştır. Programcı isterse silme işlemini kendi belirlediği bir fonksiyonla ya - da bir sınıfla yapabilir. Tabii böyle bir sınıf yazılırken sınıfın fonksiyon çağırma operatör fonksiyonunun yazılmış olması gerekir. Bu konu izleyen bölümde - ele alınacaktır. Aşağıda deleter kullanımına ilişkin bir örnek verilmiştir. ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include -#include - -using namespace std; - -void foo(int *pi) -{ - cout << "foo called" << endl; - - delete pi; -} - -int main() -{ - unique_ptr a(new int, foo); - - *a = 10; - - cout << *a << endl; - - return 0; -} - -/*------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bir sınıf türünden nesne sanki bir fonksiyonmuş gibi fonksiyon çağırma operatörleriyle çağrılma işlemine sokulabilir. Örneğin: - - Sample s; - - s(...); - - Bu durumda sınıfın "fonksiyon çağırma operatör fonksiyonu" denilen operatör fonksiyonu çalıştırılır. Fonksiyon çağırma operatör fonksiyonun geri dönüş değeri - ve parametreleri herhangi türden olabilir. Bu durumda örneğin: - - s(....) - - işleminin eşdeğeri: - - s.operator()(....) - - biçimindedir. - - Bu biçimdeki bir sınıf nesnesinin sanki bir fonksiyon gibi kullanılmasına "fonksiyon nesneleri (function object)" ya da kısaca "functor" denilmektedir. - Fonksiyon nesneleri gerçekte bir fonksiyon değil sınıf olduğu için çağrılar arasında durumsal bilgileri tutabilmektedir. - ---------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -#include -#include - -using namespace std; - -class Sample { -public: - Sample(const char *msg) : m_msg(msg) - {} - - int operator()(int a, int b); -private: - string m_msg; -}; - -int Sample::operator()(int a, int b) -{ - cout << m_msg << endl; - - return a + b; -} - -int main() -{ - Sample s("this is a test"); - int result; - - result = s(10, 20); // result s.opereator()(10, 20); - cout << result << endl; - - return 0; -} - /*------------------------------------------------------------------------------------------------------------------------------------------------------------- Fonksiyon nesneleri C++ standart kütüphanesinde algoritmalar tarafından kullanılabilmektedir. Böylece bu algoritmalar durumsal bilgiyi tutabilir duruma gelirler. Örneğin for_each fonksiyonu bir dizilimin her elemanı için bir fonksiyonun çağrılmasını sağlar. Dolayısıyla bir for_each fonksiyonuna çağrılam diff --git a/Python-OzetNotlar-Ornekler.txt b/Python-OzetNotlar-Ornekler.txt index e44987a..106f91a 100644 --- a/Python-OzetNotlar-Ornekler.txt +++ b/Python-OzetNotlar-Ornekler.txt @@ -1,28701 +1,29715 @@ -/*-------------------------------------------------------------------------------------------------------------------------- - - C ve Sistem Programcıları Derneği - - Python Programlama Dili - - Sınıfta Yapılan Örnekler ve Özet Notlar - - Eğitmen: Kaan ASLAN - - Bu notlar Kaan ASLAN tarafından oluşturulmuştur. Kaynak belirtmek koşulu ile her türlü alıntı yapılabilir. - - (Notları okurken editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.) - - ----------------------------------------------------------------------------------------------------------------------------*/ - -#------------------------------------------------------------------------------------------------------------------------ - REPL (Read and Evalutate and Print Loop) Ortamı için yaygın üç seçenek kullanılmaktadır: - - 1) CPython dağıtımındaki IDLE - 2) Spyder IDE'sindeki IPython konsolu - 3) CPython yorumlayıcısı -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da "Merhaba Dünya" yazısını ekrana basan program aşağıdaki yazılabilir: - - print('Merhaba Dünya') - - Bir Python programını komut satırından şöyle çalıştırabiliriz: - - python - - Örneğin: - - python sample.py - - Eğer python program isminden sonra çalıştrırılacak dosyanın ismi belirtilme>> a = 3928239847982347923749823749827349872398472983749823748982934234 ->>> type(a) - - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki kodu bir program olarak çalıştırınız. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10287364872364872348762348762 - -print(type(a)) # - -#------------------------------------------------------------------------------------------------------------------------ - Python'da noktalı sayılar "float" isimli türle temsil edilmiştir. float türü 8 byte uzunlukta kayan noktalı (IEEE 754 - long real format) bir formata sahiptir. Python'daki float türü C, C++, Java ve C# gibi dillerdeki double türü ile aynıdır. - Noktalı sayıların tutuluş formatından kaynaklanan ve ismine "yuvarlama hatası (rounding error)" denilen bir olgu vardır. - Yuvarlama hatası bir noktalı sayının tam olarak ifade edilemeyip ona yakın bir sayının ifade edilmesiyle oluşan bir hatadır. - Yuvarlama hataları sayıyı ilk kez depolarken de oluşabilir. Aritmetik işlemler sonucunda da oluşabilir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> f = 123.456789 ->>> f -123.456789 ->>> type(f) - - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yuvarlama hataları her noktalı sayıda ortaya çıkmaz bazı noktalı sayılarda ortaya çıkar. Yuvarlama hataları sayı ilk kez depolanırken - ortaya çıkabildiği gibi, bir işlem sonucunda da ortaya çıkabilmektedir. Bugün işlemcilerin kullandığı IEEE 754 numaralı fırmatta - yuvarlama hatalarını ortadan kaldırmanın bir yolu yoktur. Eğer yuvarlama hataları istenmiyorsa bu durumda özel başka türler tercih edilebilir. - Örneğin Pyton Standart Kütüphanesindeki "decimal" türü yapay bir tür olsa da yuvarlama hatalarına olanak vermemktedir. Programlama dillerinde - yuvarlama hataları yüzünden iki noktalı sayının tam eşitliğinin karşılaştırılması uygun kabul edilmemektedir. Çünkü iki noktalı - sayı yuvarlama hatalarından dolayı biribirine eşit olacakken olmayabilir. - - Aşağıda yuvarlama hatasına bir örnek verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 0.3 ->>> b = 0.1 ->>> c = a - b ->>> c -0.19999999999999998 - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişkene True ya da False anahtar sözcükleri atanırsa o değişken "bool" türünden olur. bool türü True ve False - biçiminde yalnızca iki değer tutabilmektedir. Programcılar "doğru-yanlış", "olumsu-olumsuz", "evet-hayır" gibi iki - değer içeren olguları genellikle bool türü ile temsil ederler. Tabii yalnızca Pyton'da değil diğer programlama dillerinin - çoğunda aslında bool türden bir değişken 0 ya da 1 gibi bir sayı tutmaktadır. Yani biz bir değişkene True değerini atasak bile - aslında bu değişken içsel olarak muhtemelen bu değeri 1 sayısı biçiminde tutmaktadır. Diğer programlama dillerinden geçenlerin - dikkat etmesi gereken bir nokta Python'da True ve False anahtar sözcüklerinin ilk harfinin büyük harf olmasıdır. Halbuki pek çok dilde - bu anahtar sözcükler küçük harflerle oluşturulmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - ->>> b = True ->>> b -True ->>> type(b) - ->>> b = False ->>> b -False ->>> type(b) - - -#------------------------------------------------------------------------------------------------------------------------ - Python'da yazıları tutan türe "str" denilmektedir. Bu türün ismi str olsa da biz bu türe "string" de diyeceğiz. String - oluşturmak için tek tırnak ya da çift tırnak kullanılır. Bu bağlamda tek tırnakla çift tırnak arasında Python'da hiçbir - farklılık yoktur. Bazı programlama dillerinde tek tırnak ile çift tırnak tamamen farklı anlamlara gelmektedir. Bu dillerden - geçen kişiler Python'da tek tırmak ile çift tırnak arasında bir fark olmadığına dikkat etmelidir. - - Biz kursumuzda ağırlıklı olarak tek tırnak kullanımını tercih edeceğiz. Pek çok programlama dilinde tek bir karakteri - tutmakta kullanılan "char" isimli bir tür de bulunmaktadır. Ancak Python'da böyle bir tür yoktur. Tek karakterli string'ler bu gereksinimi - karşılamaktadır. Örneğin C, C++, Java ve C# dillerinde tek bir karakter tek tırnakla bireden fazla karakterden oluşan yazılar da çift - tırnakla ifade edilmektedir. Oysa Python'da karakter ve string biçiminde ikşi ayrı tür yoktur. Karakterler terk karakterden olulan string'lerle - ifade edilmektedir. Dolayısıyla Python'da tek tırnakla çift tırnak arasında da bir farklılık kalmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - ->>> s = 'Ankara' ->>> s -'Ankara' ->>> type(s) - ->>> k = "Ankara" ->>> k -'Ankara' ->>> type(k) - - -#------------------------------------------------------------------------------------------------------------------------ - Python'da "complex" isimli bir tür de vardır. Bir complex nesne oluşturmak için 'j' harfi kullanılır. Ancak j harfinden önce - ona bitişik olan bir sayı getirilmesi zorunludur. Bu sayı genel olarak float olabilir. Complex sayılara '+' ya da '-' operatörü ile bir gerçek kısım - da ekleyebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 1 + 2j ->>> b = 3j ->>> c = a + b ->>> a -(1+2j) ->>> b -3j ->>> c -(1+5j) ->>> type(a) - ->>> type(b) - ->>> type(c) - - -#------------------------------------------------------------------------------------------------------------------------ - i sayısını ifade etmek için yalnızca j kullanılamaz. Çünkü yalnızca j bir değişken anlamına gelmektedir. Dolayısıyla - i sayısı 1j biçiminde ifade edilmelidir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 1j ->>> a -1j ->>> type(a) - - -#------------------------------------------------------------------------------------------------------------------------ - Python'da None anahtar sözcüğü ile temsil edilen özel bir değer vardır. Bu None değeri "NoneType" isimli bir türdendir. - None değeri "yokluk", "boşluk", "hiçlik", "öylesinelik" ve başarısızlık gibi durumları ifade etmek için kullanılmaktadır. - Biz bir değişkenin içerisine None değerini atadığımızda artık o değişken NoneType türünden olur. (None sşzcüğündeki ilk 'N'nin - büyük harf olduğuna dikkat ediniz.) Komut satırında içerisinde None bulunan bir değişkenin ismini yazdığımızda ekrana bir şey - basılmaz. Ancak print fonksiyonuyla bu değişken yazdırılmak istendiğinde "None" yazısı görüntülenmektedir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = None ->>> a ->>> print(a) -None ->>> type(a) - - -#------------------------------------------------------------------------------------------------------------------------ - Statik tür sistemine sahip programlama dillerinde bir değişken kullanılmadan önce derleyiciye tanıtılmak zorundadır. - Bu tanıtma eylemine "bildirim (declaration)" denilmektedir. Fakat Python gibi dinamik tür sistemine sahip programlama dillerinde - bildirim diye bir işlem yoktur. Zaten bu tür dillerde bildirim işleminin bir anlamı da olamaz. Python'da bir değişken ona - ilk kez değer atandığında yaratılmaktadır. (Halbuki statik tür sistemine sahip programlama dillerinde değişkenler bildirim sırasında yaratılırlar.) - - Henüz yaratılmamış olan bir değişkeni ifadelerde kullanamayız. Tabii onu yaratma amacıyla atama operatörünün solunda kullanabiliriz. - Bir değişken yaratılırken ona bir sabit, bir değişken genel olarak bir ifade atanabilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 12.3 -b = a + 1 # geçerli a yaratılmış, b de yaratılıyor -c = x + 2 # x yaratılmamış error oluşur - - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da ifade ettiğimiz gibi Python'da bir değişken ona ilk kez değer atandığında yaratılır ve değişkenin türü de - ona atanan değerin türünden olur. Eninde sonunda değişkenlere bir biçimde sabitlerin atanması gerekmektedir. O halde - sabitlerin de türleri vardır. Şimdi sabit türleri üzerinde duracağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Programlarda doğrudan yazdığımız sayılara ya da yazılara "sabit (literals)" denilmektedir. Sabitler işin başında değişkenleri yaratmak için - kullanılırlar. Sabitlerin türleri vardır. Sabitler bir değişkene atandığında değişken de o sabitin türünden olur. - - 1) Sayı nokta içermiyorsa böyle sayılar int türden sabit belirtir. int türden sabitler 0x ya da 0X ile başlatılarak 16'lık sistemde de - yazılabilirler. int bir sabit 0o ya da 0O önekiyle başlatılarak yazılırsa 8'lik sistemde (octal system) yazıldığı - kabul edilmektedir. Nihayet int bir sabit 0b ya da 0B ile başlatılarak 2'lik sistemde (binary system) yazılabilir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 123 ->>> a -123 ->>> type(a) - ->>> a = 0o123 ->>> a -83 ->>> type(a) - ->>> a = 0x123 ->>> a -291 ->>> type(a) - ->>> a = 0b101010 ->>> a -42 ->>> type(a) - - -#------------------------------------------------------------------------------------------------------------------------ - Python'da int türden sabitler yazılırken sayıların aralarına alt tire karakteri konulabilir. Bu kural uzun tamsayılarda - okunabilirliği artırmak amacıyla dile sokulmuştur. Ancak iki tane alt tire yan yana bulundurulamaz ve sayının başında ve sonunda sonunda - alt tire bulundurulamaz. Alt tire 10'luk, 16'lık, 8'lik ve 2'lik sistemlerdeki yazımlarda da kullanılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 1_000_000_000 ->>> a -1000000000 ->>> a = 0x1234_5678 ->>> a -305419896 ->>> a = 0B1010_0101 ->>> a -165 ->>> a = 1_0_0_0 ->>> a -1000 - -#------------------------------------------------------------------------------------------------------------------------ - 2) Bir sayı nokta içeriyorsa sabit float türdendir. Noktanın soluna ya da sağına bir şey yazılmanışsa 0 yazılmış olduğu - varsayılır. float sabitlerde de alt tire benzer amaçla kullanılabilmektedir. float sabitler tıpkı diğer programlama dillerinde - olduğu gibi üstel biçimde de belirtilebilmektedir. Bunun için önce nokta içeren ya da içermeyen bir sayısal kısım sonra 'e' ya da 'E' - harfi sonra da on üzeri anlamına gelen kuvvet değeri bulundurulur. Örneğin 1.2e-3 değeri 1.2 x 10^-3 anlamına gelmektedir. Tabii - üstel biçimdeki sayıda hiç nokta olmasa bile (1e3 örneğindeki gibi) sabit float kabul edilir. float sabitlerde de basamaklamak için - alt tire kullanılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 2.3 ->>> type(a) - ->>> b = .28 ->>> type(b) - ->>> c = 12. ->>> type(c) - ->>> d = 10_3.23_45_57 ->>> type(d) - ->>> e = 1e40 ->>> type(e) - ->>> f = -1.2e-2 ->>> type(f) - ->>> g = 1.23_45e2_3 ->>> type(g) - - -#------------------------------------------------------------------------------------------------------------------------ - Bir int sabiti pratik bir biçimde float sabit yapmak için sonuna nokta konulması yeterlidir. Örneğin: - - >>> a = 123 - >>> type(a) - - >>> a = 123. - >>> type(a) - - - C, C++, Java ve C# gibi dillerde sabitlerin sonlarına bazı sonekler getirilebilmektedir. Python'da böyle bir sonek getirme - durumu yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 3) bool türden iki sabit vardır: True ve False. Python'da bool türü aritmetik işlemlere sokulabilmektedir. Bu durumda True - 1 olarak False 0 olarak işleme girmektedir. İki bool değeri kendi aralarında işleme sokarsak sonuç int türden elde edilmektedir. - Python'daki bool sabitlerin ilk harflerinin büyük harf olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = True ->>> type(a) - ->>> b = False ->>> type(b) - ->>> c = a * 3 ->>> c -3 ->>> type(c) - ->>> d = True + True ->>> d -2 ->>> type(d) - - -#------------------------------------------------------------------------------------------------------------------------ - 4) Python'da bir yazı tek tırnak ya da çift tırnak arasına alınırsa bu ifade str türünden sabit belirtir. Bu bağlamda - tek tırnakla çift tırnak arasında hiçbir farklılık yoktur. Python'ın 2'li versiyonları stringleri ASCII karakterleri biçiminde - saklıyordu. Ancak 3'lü versiyonlarla birlikte Python'da tüm string'ler UNICODE tablo esas alınarak tutulmaya başlandı. UNICODE - tabloda her karakter 2 byte ile temsil edilmektedir. Böylece Python stringlerinin içerisinde biz dünyanın bütün karakterlerini - yerleştirebiliriz. Boş bir string oluşturmak da geçerlidir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 'ankara' ->>> a -'ankara' ->>> type(a) - ->>> b = 'ağrı dağı' ->>> b -'ağrı dağı' ->>> type(b) - ->>> c = "burdur" ->>> c -'burdur' ->>> type(c) - ->>> d = '' ->>> d -'' ->>> type(d) - - -#------------------------------------------------------------------------------------------------------------------------ - UNICODE tablonun ilk 128 karakteri standart ASCII tablosu ile aynıdır. İkinci 128'lik karakteri ASCII Latin-1 (ISO 8859-1) - code page'i ile aynıdır. ASCII ve UNICODE tablonun ilk 32 karakteri görüntülenemeyen (nonprintable) kontrol karakterlerinden oluşmaktadır. - Bu karakterleri yazdırmaya çalıştığımızda ekranda bir şey görmeyiz. Ancak bir eylem oluşur. Bu özel karakterlerin bazıları ters bölü - tekniği ile kullanıma sunulmuştur. Ters bölü karakterlerinin listesi şöyledir: - - \a Alert (beep sesi çıkar) - \b Backspace (imleç backspace tuluna basılmış gibi bir geri gider) - \f Form feed (bu karakter yazıcıya gönderilirse bir sayfa atar) - \n New Line (imleç aşağı satırın başına geçirilir) - \r Carriage Return (imleç bulunulan satırın aşağısına geçirilir) - \t (Horizontal) Tab (imleç yatay olarak ilk tab stop'a atlar) - \v Vertical Tab (İmleç düşey olarak bir tab atlar) - - Bu ters bölü karakterleri stringlerin içerisinde tek bir karakter olarak değerlendirilmektedir. Örneğin: - - s = 'Ali\nVeli' - print(s) - - Burada şöyle bir görüntü oluşacaktır: - - Ali - Veli - - Buradaki \n karakteri tek bir karakter olarak ele alınmaktadır. - - Python'da tek tırnak içerisindeki tek tırnak, çift tırnak içerisindeki çift tırnak soruna yol açmaktadır. Bu nedenle tek tırnak - içerisinde tek tırnak karakteri \' biçiminde yazılır. Benzer biçimde çift tırnak içerisindeki çift tırnak karakteri de \" biçiminde yazılmaktadır. - Ancak tek tırnak içerisindeki çift tırnak, çift tırnak içerisindeki tek tırnak bir soruna yol açmamaktadır. Örneğin: - - s = "Türkiye'nin başkenti Ankara'dır" - print(s) - - k = 'Bu konuyu "vurgulamak" istiyorum' - print(k) - -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = 'ankara' ->>> a -'ankara' ->>> type(a) - ->>> b = 'ağrı dağı' ->>> b -'ağrı dağı' ->>> type(b) - ->>> c = "burdur" ->>> c -'burdur' ->>> type(c) - ->>> d = '' ->>> d -'' ->>> type(d) - ->>> e = 'Türkiye\'nin başkenti Anraka\'dır' ->>> print(e) -Türkiye'nin başkenti Anraka'dır ->>> f = "Türkiye'nin başkenti Ankara'dır" ->>> print(f) -Türkiye'nin başkenti Ankara'dır ->>> g = "Bu konuyu \"vurgulamak\" istiyorum" ->>> print(g) -Bu konuyu "vurgulamak" istiyorum ->>> h = 'Bu konuyu "vurgulamak" istiyorum' ->>> print(h) -Bu konuyu "vurgulamak" istiyorum - -#------------------------------------------------------------------------------------------------------------------------ - String içerisindeki ters bölü karakterinin kendisini ters bölü biçiminde yazarsak o karakter yanındaki karakterle birlikte - tek bir karakter olarak ele alınabilir. Örneğin: - - path = 'C:\temp\a.dat' - - Burada \t karakteri TAB karakter ve \a karakteri de ALERT karakteri olarak ele alınacaktır. Bu karakterlerin gerçekten ters - bölü karakteri olarak ele alınabilmesi için \\ biçiminde yazılması gerekir. Örneğin: - - path = 'C:\\temp\\a.dat' - - C, C++, Java ve C# gibi dillerde ters bölü karakterinin yanındaki karakter ters bölü karakterlerinden biri değilse bu durum - sentaks bakımından geçerli kabul edilmemektedir. Halbuki Python'da ters bölü karakterinin yanındaki karakter yukarıda listesini verdiğimiz - ters bölü karakterlerinden biri değilse artık orada ters bölü karakteri gerçekten ters bölü karakteri olarak ele alınmaktadır. Örneğin: - - path = "C:\day\month"; - - Bu string C'de geçersizdir. Ancak Python'da geçerlidir. Çünkü \d ve \m biçiminde bir ters bölü karakteri yoktur. Dolayısıyla \d karakterleri - gerçekten ters bölü karakteri ve d karakteri olarak ele alınmaktadır. Fakat programcının yine de ters bölü karakterinin kendisini - iki ters bölü karakteri ile yazması tavsiye edilmektedir. Örneğin: - - path = "C:\\day\\month"; - - Komut satırında str türünden bir değişkeni print fonksiyonu ile yazdırdığımızda gerçekten ters bölü karakterleri kendi işlevleri ile - işlem görmektedir. Ancak str türünden değişkenin ismini yazıp ENTER tuşuna bastığımızda bu durumda ilgili yazı programcıların kolay anlaması - için ters bölülü biçimde görüntülenmektedir. Örneğin: - - >>> s = 'ali\nveli' - >>> print(s) - ali - veli - >>> s - 'ali\nveli' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir string'in önüne (yani soluna) onunla yapışık 'r' ya da 'R' karakteri getirilirse (Buradaki r "regular" sözcüğünden geliyor) bu string'ler içerisindeki - ters bölü karakterleri gerçekten ters bölü karakterleri anlamına gelmektedir. Tabii içinde hiç ters bölü olmayan string'lerin başına r getirmenin - bir anlamı yoktur. Ancak yasak da değildir. -#------------------------------------------------------------------------------------------------------------------------ - ->>> path = 'C:\temp\a.dat' ->>> print(path) -C: emp.dat ->>> path = 'C:\\temp\\a.dat' ->>> print(path) -C:\temp\a.dat ->>> path = r'C:\temp\a.dat' ->>> print(path) -C:\temp\a.dat ->>> path = R'C:\temp\a.dat' ->>> print(path) -C:\temp\a.dat ->>> s = r'ali, veli, selami' # başına r getirmenin bir anlamı yok ->>> print(s) -ali, veli, selami - -#------------------------------------------------------------------------------------------------------------------------ - Python'da tek tırnak ve çift tırnak içerisindeki yazılar tek satıra (aynı satıra) yazılmak zorundadır. Örneğin: - - s = 'ali veli - selami' - - Bu yazım geçersizdir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da string'ler tek tırnağın ve çift tırnağın yanı sıra üç tek tırnak ve üç çift tırnakla da belirtilebilirler. (İki tek tırnak - ya da iki çift tırnak biçiminde bir belirtme yoktur.) -#------------------------------------------------------------------------------------------------------------------------ - -s = '''This is a test''' -print(s) - -s = """This is a test""" -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Üç tek tırnak ile üç çift tırnak arasında bir farklılık yoktur. Üçlü tırnakların tekli tırnaklardan iki farlılığı vardır: - - 1) Normalde tek tırnak içerisindeki yazılar tek bir satırda yazılmak zorundadır. Ancak üç tırnaklı yazılar farklı satırlara bölünerek yazılabilir. - 2) Üç tek tırnak içerisinde tek tırnak, üç çift tırnak içerisinde çift tırnak bir soruna yol açmaz. - - String'in tek tırnak ya da üç tırnak ile ifade edilmesi arasında ters bölü karakter sabitleri bakımından bir farklılık yoktur. - Yine üç tırnaklı string'lerin başına 'r' ya da 'R' öneki getirilebilir. Bu durumda yine buradaki ters bölü karakterleri gerçekten - ters bölü karakteri olarak değerlendirilir. -#------------------------------------------------------------------------------------------------------------------------ - -s = '''Bugün hava -çok güzel''' -print(s) - -s = '''Türkiye'nin başkenti Ankara'dır''' -print(s) - -s = '''c:\temp\a.dat''' -print(s) - -s = r'''c:\temp\a.dat''' -print(s) - -s = """"Türkiye'nin başkenti Ankara'dır. "Ankara" büyük bir şehirdir""" -print(s) - -s = '''Türkiye'nin başkenti Ankara'dır. "Ankara" büyük bir şehirdir''' -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Üç tek tırnağın ya da üç çift tırnağın içerisindeki tek tırnak ya da çift tırnak yazının başında olabilir. Ancak istisna olarak - sonunda olamaz. Çünkü Python'da ardışıl en uzun karakterlerden atom yapılmaktadır. Örneğin: - - s = """"ankara"""" - - Burada iki tırnak içerisinde "ankara" yazılmak istenmiştir. Bunu Python yorumlayıcısı şöyle ayrıştıracaktır ayıracaktır: - - s - = - """ - "ankara - """ - " (burada tek kalıyor) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'un 2'li versiyonlarında bir byte'lık ASCII tablosunu kullanılıyordu. Bu versiyonlarda UNICODE string'ler için - string'lere yapışık 'u' ya da 'U' önekleri kullanılıyordu. Ancak Python'ın stringleri zaten üçlü versiınlarla birlikte - default UNICODE haline getirilmiştir. Bugün hala bu 'u' ya da 'U' öneki geçerli olsa da artık bunun bir işlevi kalmamıştır. -#------------------------------------------------------------------------------------------------------------------------ - -s = u'Ağrı Dağı çok yüksek' # artık u önekine hiç gerek yok -print(s) - -s = 'Ağrı Dağı çok yüksek' -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Tek tırnak ya da üç tırnağın önüne onunla yapışık 'b' ya da 'B' öneki getirilirse elde edilen nesne "bytes" denilen bir - türden olur. Yani artık bu nesne bir string değildir. Bu konu ileride ele alınacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -s = b'Bu bir denemedir' -print(s) -print(type(s)) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da (tıpkı C Programlama Dilinde olduğu gibi) aralarında hiç bir atom olmayan yan yana iki string otomatik olarak - birleştirilmektedir. Tabii bu iki string'in aynı satır üzerinde olması gerekir. Örneğin: - - s = 'ali' 'veli' - - ile, - - s = 'aliveli' - - aynı anlamdadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 5) None anahtar sözcüğü NoneType türünden sabit belirtmektedir. Zaten bu türden tek sabit None anahtar sözcüğüdür. None - değeri REPL ortamında ekrana bir şey basmamaktadır. None değeri print edilirse ekrana None yazısı çıkar. -#------------------------------------------------------------------------------------------------------------------------ - ->>> a = None ->>> a ->>> print(a) -None ->>> type(a) - - -#------------------------------------------------------------------------------------------------------------------------ - 6) Python'da 'j' harfi önünde yapışık bir float ya da int değerle kullanıldığında Complex türden sabit anlamına gelir. - Complex sabitler 'j' nin yanı sıra + ya da - operatörü ile bir gerçek kısma da sahip olabilirler. Yalızca j değerine - sahip karmaşık sayı oluşturmak için 1j ifadesini kullanmak gerekir. Çünü tek başına j bir değişken ismi olarak ele - alınmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -z = 1j -print(z) - -z = -2j + 5 -print(z) - -z = 0j + 2 -print(z) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da açıklama yazıları oluşturmak için (comment/remark) # kullanılmaktadır. # karakterinden satır sonuna kadarki - karakterler yorumlayıcı tarafından dikkate alınmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -pi = 3.14159 -radius = 2 - -area = pi * radius * radius # dairenin alanı - -#------------------------------------------------------------------------------------------------------------------------ - Python'da etkisiz kod oluşturmak geçersiz değildir. Örneğin: - - 10 + 20 - - Böyle bir kodun bir anlamı yoktur. Çünkü bu kod program içerisinde bir durum değişikliği (side effect) oluşturmaz. Bu - bağlamda bir değişkene atanmamış string'ler de etkisiz koddur ve geçerlidir. O halde Python'da birden fazla satır üzerinde - yorumlama yapabilek için üç tırnaklı string'ler kullanılabilir. Gerçekten de Python programcıları birden fazla satıra - yayılmış yorumlamaları üç tırnaklı string'lerle yaparlar. -#------------------------------------------------------------------------------------------------------------------------ - -""" -Merhaba Dünya -Programı -""" - -print('Hello World') - -#------------------------------------------------------------------------------------------------------------------------ - Python'da bir değişken ona ilk kez değer atandaığında yaratılmaktadır. Değişken isimlendirmesinde diğer programlama - dillerinin çoğunda söz konusu olan kurallar geçerlidir: - - - Değişken ismi sayısal karakterlerle başlatılamaz, ancak alfabetik karakterlerle başlatılıp sayısal karakterlerle - devam ettirilebilir. - - Değişken isimleri anahtar sözcüklerden oluşturulamaz. - - Değişken isimleri boşluk karakterlerini içeremez. - - Değişken isimleri operatör karakterlerini içermez. - - Python "büyük harf-küçük harf duyarlılığı olan (case sensitive)" bir programlama dilidir. Yani değişken isimlerindeki - büyük harflerle küçük harfler birbirinden farklıdır. C, C++, Java ve C# dilleri de böyledir. Ancak Pascal, Basic gibi - dillerde büyük harf küçük harf duyarlılığı yoktur (case insensitive). - - Python 3'lü versiyonlarla birlikte UNICODE karakter tablosunu kullanmaya başlamıştır. Dolayısıyla değişken isimlendirmede - UNICODE tablodaki bütün dillerin alfabetik karakterleri kullanılabilmektedir. Örneğin aşağıdaki gibi bir değişken ismi - geçerlidir: - - ağrı_dağının_yüksekliği = 5137 - - - Python Language Reference içerisinde değişkenlerin maksimum uzunluğu konusunda bir şey söylenmemiştir. Bu durum - değişkenlerin yeterince uzun olabileceği ancak bu limitin yorumlayıcıyı yazanların isteğine bırakılmış olduğu anlamına - gelir. - - Birden fazla sözcükten oluşan değişkenlerin harflendirilmesinde üç yöntem sıklıkla kullanılmaktadır: - - 1) Klasik C tarzı harflendirme (yılan (snake) notasyonu da denilmektedir): Burada sözcüklerin arasına alt tire getirilir. - Örneğin: - - number_of_students - loop_count - - 2) Deve Notasyonu (Camel Casting): Burada ilk sözcüğün tamamı küçük harflerle yazılır. Sonraki her sözcüğün yalnızca ilk - harfi büyük harfle yazılır. Örneğin: - - numberOfStudents - loopCount - - 3) Pascal Notasyonu (Pascal Casting): Pascal dilinde tercih edilen notasyon olduğu için bu isim verilmiştir. Burada her - sözcüğün ilk harfi büyük yazılır. Örneğin: - - NumberOfStudents - LoopCount - - Her ne kadar Python büyük harf-küçük harf duyarlılığı olan bir dil ise de Python'da küçük harf yoğun bir harflendirme - tercih edilmektedir. - - Biz kursumuzda en yaygın harflandirme biçimi olan klasik C tarzı harflendirmeyi (yani sözcüklerin arasına alt tire - getirme yöntemini) kullanacağzı. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - CPU'ya elektriksel olarak doğrudan bağlı belleklere "ana bellek (main memory)" ya da "birincil bellek (primary memory") - ya da halk arasında "RAM" denilmektedir. RAM'deki her byte'a ilk byte 0 olmak üzere artan sırada bir sayı karşılık düşürülmüştür. - Bu sayıya "ilgili byte'ın adresi" denilmektedir. Bellekte erişilebilen bölgere "nesne (object)" denilmektedir. Nesneler bir byte - ya da daha uzun olabilirler. 1 byte'tan daha uzun nesnelerin adresleri onların en küçük adresiyle ifade edilir. - - Programlama dillerindeki değişkenler birer nesne belirtmektedir. Yani onların da adresleri vardır. c = a + b gibi bir işlemde - a, b, c aslında RAM'de bulunur. CPU a ile b'yi kendi içine çeker. İşlem CPU tarafından elektrik devrelerle yapılmaktadır. - Sonuç yeniden RAM'e aktarılmaktadır. CPU nesnelerin isimleriyle değil adresleriyle çalışmaktadır. - - Bir nesnenin içerisinde tipik olarak bir değer tutulur. Ancak bazı nesnelerin içerisinde başka nesnelerin adresleri vardır. - İşte başka nesnelerin adreslerini tutan nesnelere C Programlama Dİlinde "pointer", Java ve C# gibi dillerde ise "reference" - denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da değişkenler her zaman adres tutarlar. Bir değişkene bir değer atandığında aslında yorumlayıcı değişkene o değerin bulunduğu - yeri adresini atamaktadır. Örneğin: - - a = 123 - - Burada yorumlayıcı RAM'de bir int nesne yaratır. Onun içerisine 123 değerini yerleştirir. Onun adresini a'ya atar. Birazdan biz şöyle yapmış olalım: - - a = 3.14 - - Şimdi derleyici RAM'de bir float nesne oluşturur. Onun içerisine 3.14 değerini yerleştirir. Bu kez onun adresini a'ya atar. - Python'da tüm atamalar adres atamasıdır. Biz Python'da "değişken" terimini adres tutan nesneler için kullanıyoruz. Ancak nesne terimini - değişkenlerin gösterdiği yer için kullanıyoruz. Aslında C, C++ gibi programlama dillerinin terminolojisinde hem değişkenler hem de - onların gösterdikleri yerdeki değerler birer nesnedir. Ancak Python'da biz değişken demekle içerisinde adres olan isimli varlıkları kastedeceğiz. - Değişkenlerin gösterdiği yerlere nesne diyeceğiz. - - Dinamik tür sistemine sahip programlama dillerinde genel olarak tür bilgisi değişkenin içerisinde değil nesnenin içerisinde turulmaktadır. - Yani değişken o anda hangi nesneyi gösteriyorsa o türden olmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişkenin içerisindeki adres id isimli built-in bir fonksiyonla elde edilebilir. (Aslında Python referans kitabına göre - id fonksiyonu nesne ile ilgili tek (unique) bir değer vermelidir. Anack tipik olarak bu değer o nesnenin adresidir.) - - Aşağıdaki örnekte değişkene farklı değerler atandığında onun içerisindeki adresin değiştiğini göreceksiniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = 123 -print(id(a)) - -a = 'this is a test' -print(id(a)) - -a = 12.3 -print(id(a)) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da tüm atamalar aslında adres atamasıdır. Ancak bir değişken atama dışında kullanıldığında o değişkenin içindeki adresteki nesne kullanılır. - Örneğin: - - a = 10 - b = 20 - c = a + b - - Burada aslında a'nın ve b'nin içerisinde birer adres vardır. Ancak a + b işleminde bu adresler değil bu adreslerin gösterdiği yerdeki int nesnelerin değerleri - toplanır. Toplama sonucunda yeni int nesne yaratılır. c'ye yeni yaratılmış olan int nesnenin adresi atanır. Örneğin: - - >>> a = 10 - >>> b = 20 - >>> c = a + b - >>> id(a) - 140711499592776 - >>> id(b) - 140711499593096 - >>> id(c) - 140711499593416 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da tür bilgisi değişkenin içerisinde saklanmaz. Nensenin içerisinde saklanır. Örneğin: - - a = 123 - - Burada a değişkeni içerisinde 123 olan int bir nesnenin adresini tutulmaktadır. Bu int nesnenin içerisinde yalnızca 123 değeri yoktur. - Bu nesnenin tür bilgisi de vardır. Ancak biz kursumunda çizimlerde nesnenin içerisindeki tür bilgisi alanını göstermeyeceğiz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da tüm atamalar adres atamalarıdır. Bir değişkeni bir değişkene atadığımızda biz aslında o değişkenin içindeki adresi diğer değişkene atamış oluruz. - Örneğin: - - a = 10 - b = a - - Burada b = a işlemi ile aslında biz a değişkeninin içerisindeki adresi b değişkenine atamış olduk. Yani burada hem a hem de b - aynı nesneyi göstermektedir. Örneğin: - - >>> a = 100 - >>> b = a - >>> id(a) - 140711499595656 - >>> id(b) - 140711499595656 - - Python'da bir değişken bir değişkene atandığında aslında o değişkenin içerisindeki adres diğer değişkene atanmaktadır. Atama sonrasında - her iki değişken de aynı nesneyi gösteriyor durumda olur. Örneğin: - - a = b - - Burada artık b de a da aynı nesneyi gösterecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da türler (types), "değiştirilebilir (mutable)" ve "değiştirilemez (immutable)" olmak üzere ikiye ayrılmaktadır. - Değiştirilebilir türler türünden bir nesne yaratıldığında o nesnenin içerisindeki değer herhangi bir zaman değiştirilebilir. - Değiştirilemez türler türünden bir nesne yaratıldığında ise o nesnenin içerisindeki değerler nesne yaratılırken onun içerisine yerleştirilir. - Bir daha da değiştirilemez. Python'ın 6 temel türü de kategorik olarak "değiştirilemez" türlerdir. Daha açık bir biçimde belirtirsek: - - int türü değiştirilemezdir - float türü değiştirilemezdir - str türü değiştirilemezdir - bool türü değiştirilemezdir - complex türü değiştirilemezdir - NoneType türü değiştirilemezdir - - Programcının değiştirilemez türden bir nesne yaratılırken belli bir değerle yaratıldığını bir daha da o değerin asla değişmeyeceğini - biliyor olması gerekir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - int, float, str, bool, complex, NoneType türlerinin "değiştirilemez" olmasının ilginç bazı sonuçları söz konusudur. Örneğin: - - >>> a = 10 - >>> id(a) - 140711499592776 - >>> a = 20 - >>> id(a) - 140711499593096 - - Burada a değişkeni int türdendir. int nesne de kategorik olarak "değiştirilemez" bir türdür. O halde a'ya ikinci kez - değer atandığı durumda a = 20 işlemi için önceki nesnenin içerisindeki 10 değiştirilemeyeceğinden dolayı içerisinde - 20 olan farklı bir int nesne yaratılacak ve a artık o int nesneyi gösterecektir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -print(id(a)) - -a = 20 -print(id(a)) - -#------------------------------------------------------------------------------------------------------------------------ - Bu durumda örneğin iki değişken aynı int nesneyi gösteriyor durumda olsun. Bunlardan birine atama yapıldığında diğerinin değeri - değişmez. Örneğin: - - a = 10 - b = a - - Burada a ve b aslında aynı nesneyi göstermektedir. Şimdi şu işlemi yapmış olalım: - - a = 20 - - Burada int türü değiştirilemez olduğu için yeni bir int nesne yaratılacak onun içerisine 20 yerleştirilecek ve a da artık bu - yeni nesneyi gösterecektir. Halbuki b içerisinde 10 olan eski nesneyi göstermektedir. Yani buradaki semantik - C, C++, Java ve C# gibi dillerdekiyle sonuç itibariyle aynı olacaktır. - -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = a -a = 20 - -print(a) # 20 -print(b) # 10 - -#------------------------------------------------------------------------------------------------------------------------ - Temel türlerin değiştirlemez olması Python'a geçen pek çok kişi tarafından yadırganmaktadır. Çünkü böylesi bir tasarım - bazı basit işlemlerde bile yavaşlığa yol açabilmektedir. Örneğin bir döngü içerisinde sürekli bir değişkeni 1 artırdığımızı - düşünelim. Döngünün her yinelenmesinde yeni bir nesne yaratılacak belki de eski nesne çöp toplayıcı tarafından silinecektir. - Bunun yavaşlığa yol açacağı muhakkaktır. Her ne kadar döngüleri bilmiyor olsak da aşağıda böyle bir örnek veriyoruz: - - a = 0 - - for i in range(10): - print(a, id(a)) - a = a + 1 - - Burada a değişkeninin id'sinin sürekli değiştirğini göreceksiniz: - - 0 2172851716304 - 1 2172851716336 - 2 2172851716368 - 3 2172851716400 - 4 2172851716432 - 5 2172851716464 - 6 2172851716496 - 7 2172851716528 - 8 2172851716560 - 9 2172851716592 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'ın geniş bir standart kütüphanesi vardır. Bu standart kütüphanede neredeyse pek çok konuya ilişkin fonksiyonlar - ve sınıflar hazır bir biçimde bulunmaktadır. - - Standrat kütüphanedeki fonksiyonların ve sınıfların önemli bir bölümü çeşitli modüllerin içerisine yerleştirilmiştir. - Bu fonksiyonları ve sınıfları kullanmadan önce o modülün "import edilmesi" gerekmektedir. Örneğin karekök almakta kullanılan - sqrt fonksiyonu "math" isimli bir modülün içerisindedir. O halde sqrt kullanılmadan önce bu modülün aşağıdaki gibi import edilmesi gerekir: - - import math - - Modülün içerisindeki fonksiyonlar ve sınıflar modül ismi ve '.' operatörüyle niteliklendirilerek kullanılırlar. Örneğin: - - a = math.sqrt(10) - - gibi. Burada biz sqrt fonksiyonunu doğurdan değil math.sqrt biçiminde kullandık. Ancak önce math modülünü import ettik. - import etmenin ne anlama geldiğini ileride ayrı bir başlık altında ele alacağız. - - Python'ın standart kütüphanesi python.org sitesinde "Python Standcart Library" ismiyle dokmante edilmiştir. Bu dokümana - aşağıdaki bağlantıdan erişebilirsiniz: - - https://docs.python.org/3/library/index.html - - O halde Python için iki önemli doküman vardır: - - 1) Python dilinin resmi anlatımını yapan doküman: Python Language Reference - - https://docs.python.org/3/reference/index.html - - 2) Python'ın standart kütüphanesini açıklayan doküman: Python Standard Library - - https://docs.python.org/3/library/index.html - - Tüm Python dokümanları da aşağıdaki bağlantıda bulunmaktadır: - - https://docs.python.org/3/ -#------------------------------------------------------------------------------------------------------------------------ - -import math - -result = math.sqrt(10) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Standart kütüphane içerisindeki bazı fonksiyonları ve sınıfları hiç import işlemi yapmadan doğrudan kullanabilmekteyiz. - İşte bu fonksiyonlara ve sınıflara "built-in" fonksiyonlar ve sınıflar denilmektedir. Örneğin print ve id fonksiyonları built-in - fonksiyonlardır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da ekrana (yani stdout dosyasına) yazan tek bir print fonksiyonu vardır. Benzer biçimde klavyeden (yani stdin dosyasından) - okuyan da tek bir input isimli fonksiyon vardır. input fonksiyonu yazı parametresi alır. Önce yazıyı ekrana basar. Sonra giriş ister. - Kullanıcı bir yazı girerek ENTER tuşuna basar. input fonksiyonu da girilmiş olan yazıyı bize str nesnesi oalrak verir. - input parantezleri boş bırakılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -s = input('Bir yazı giriniz:') -print(type(s)) -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii Python'da her türlü atama "adres ataması" anlamına geldiğine göre aslında biz input fonksiyonunun geri dönüş değerini - bir değişkene atadığımızda string nesnesinin adresini o değişkene atamış oluru. Örneğin: - - s = input('Bir yazı giriniz:') - - Burada s değişkenine bir str nesnesinin adresi atanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - input fonksiyonu her zaman okunan yazıyı string olarak verir. Eğer biz int ya da float ya da diğer türlerden okuma yapmak istiyorsak bu yazıyı - int türüne, float türüne ya da diğer türlere dönüştürmeliyiz. Bu dönüştürma aşağıdaki gibi yapılmaktadır: - - a = int(input('Bir sayı giriniz:)) - - Burada input fonksiyonundan alınan yazı int fonksiyonuna sokulmuştur. Buradaki int fonksiyonu yazıyı int türüne dönüştürmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) - -print(a * a) - -b = float(input('Bir değer giriniz:')) -print(b * b) - -#------------------------------------------------------------------------------------------------------------------------ - Programın çalışma zamanı sırasında ortaya çıkan ciddi hatalara "exception" denilmektedir. Bir exception oluştuğunda - programcı oluşan bu exception'ı yakalayıp programın çalışmasını devam ettirebilir. Ancak programcı exception'ı yakalamazsa - program çöker. Exception işlemleri ileride ayrıntılarıyla ele alınacaktır. Örneğin biz klavyeden int bir değer okumak isterken - klavyeden girdiğimiz karakterler uygun değilse exception oluşur. Oluşan exception'ların bir türü vardır. Bu tür genellikle - XXXError biçiminde isimlendirilmiştir. Örneğin ValueError, TypeError gibi. - - Aşağıdaki örnekte bir sayı yerine bir isim girerek exception oluşturunuz. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) - -print(a * a) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da altprogramlara "fonksiyon (function)" denilmektedir. Küçük olmayan bir programın tek parça halinde yazılması iyi bir - teknik değildir. Programlar prosedürel teknikte mantıksal bakımdan parçalara ayrılır. Parçalar fonksiyonlar biçimin de yazılır. - Sonra da bu fonksiyonlar çağrılarak program çalıştırılır. Bir işin parçalara bölünüp parçalardan bütünün oluşturulması yalnızca - programlamada değil pek çok mühendislik faaliyette uygulanan bir tekniktir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyon ya bir sınıfın içerisindedir ya da sınıfın içerisinde değildir. Python dünyasında sınıfın içerisindeki fonksiyonlara - "metot (method)" denilmektedir. Yani Python dünyasında "fonksiyon (function)" sınıfın içerisinde olmayan yordamlar için "metot (method)" - ise sınıfın içerisinde bulunan yordamlar için kullanılmaktadır. - - Bir fonksiyon çağrıldığında onun içerisindeki kod çalıştırılır. Fonksiyonun çalışması bitince akış kalınan yerden devam eder. Fonksiyon - çağırma işleminin genel biçimi şöyledir: - - ([argüman listesi]) - - Görüldüğü gibi fonksiyon isminden sonra (...) bulunmakta ve onların içlerine de "argümanlar" yazılmaktadır. Argümanlar herhangi birer ifade - olabilir. Eğer argümanlar birden fazla ise ',' atomu ile ayrılmaktadır. Örneğin: - - print(a) - - Burada a bir argümandır. Örneğin: - - print(a, b, c) - - Burada birden fazla argüman kullanılmıştır. Bir arüman ifadelerden oluşabilir. Örneğin: - - print(a + b, c + d) - - Burada iki argüman vardır. Argümanlardan biri a + b diğeri ise c + d biçimindedir. - - Bir fonksiyon bir modülün içerisindeyse o fonksiyonu çağırmak için modülün ismi de kullanılmaktadır. Örneğin: - - math.sqrt(10) - - gibi. - - Ancak bir metodun çağrılması için o metodun içinde bulunduğu sınıf türünden bir değişkenin bulunuyor olması gerekir. Metot çağırma işleminin - genel biçimi de şöyledir: - - .([argüman listesi]) - - Bir modül içerisindeki fonksiyonu çağırma biçimiyle bir metodu çağırma biçimi birbirne benzemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Son 20 yıldır programalama anlatan dokümanlarda öylesine uydurulmuş fonksiyon isimleri için "foo", "bar", "tar" gibi - isimler kullanılmaktadır. Biz de kursumuzda çeşitli örneklerde bu isimleri kullanacağız. Bu isimlerin hiçbir özel anlamı - yoktur. Öylesine uydurulmuş isimlerdir. Örneğin: - - foo() - bar() - tar() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da is operatörü iki değişkenin aynı nesneyi gösterip göstermediğini anlamak için kullanılmaktadır. Bu operatör bool bir değer - üretmektedir. Eğer bu operatör True değer üretirse iki değişken aynı nesneyi gösteriyor durumdadır (yani onların içerisinde aynı adres vardır). False - değer üretirse bu durum iki değişkenin aynı nesneyi göstermediği anlamına gelir. Bu durumda a is b ile id(a) == id(b) aynı anlamdadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -result = a is b -print(result) # False - -a = 100 -b = a - -result = a is b -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - is not operatörü de is operatörünün tersi işlemi yapmaktadır. Yani a is not b işlemi eğer a'nın içerisindeki adres b'nin içerisindeki - adresten farklıysa True değerini, aynısya False değerini üretmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -result = a is not b -print(result) # True - -a = 100 -b = a - -result = a is not b -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - Python'da None değerini içeren tek bir nesne vardır. Dolayısıyla biz farklı değişkenlere None değerini atadığımızda - aslında bu değişkenlere aynı adresi atamış oluruz. Örneğin: - - a = None - b = None - - Burada a is b her zaman True değerini verecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Derleyiciler ve yorumlayıcılar kodu daha hızlı çalışacak ve/veya daha az yer kaplayacak biçimde yeniden düzenleyebilmektedir. - Buna "kod optimizasyonu" denilmektedir. Optimizasyon kodun anlamı değişmeyecek biçimde özenle yapılmaktadır. Örneğin - Python'da farklı değikenlere aynı sabitleri atadığımızda temel türler değiştirilebilir olmadığı için aslında yorumlayıcı - bu sabitler için tek bir yer ayırıp değişkenlere aynı nesnenin adresini atayabilmektedir. Örneğin: - - >>> a = 10 - >>> b = 10 - >>> c = 8 + 2 - >>> a is b - True - >>> a is c - True - >>> b is c - True - >>> id(a) - 140732013605960 - >>> id(b) - 140732013605960 - >>> id(c) - 140732013605960 - - Kod optimizasyonları tamamen çalışmakta olduğunuz derleyici ya da yorumlayıcıya hatta bazen onların versiyonlarına bağlı - biçimde yapılmaktadır. Bir optimizasyonu bir Python yorumlayıcısı yaparken diğeri yapmayabilir. Önemli olan böyle bir - kodu yeniden düzenleme hakkının yorumlayıcıya verilmiş olmasıdır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 12345 -b = 12345 - -result = a is b -print(result) - -s = 'ali' -k = 'ali' - -result = s is k -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Bir işleme yol açan işlem sonucunda bir değerin üretilmesini sağlayan atomlara operatör (operator) denilmektedir. Operatörler - genellikle üç biçimde sınıflandırılmaktadır: - - 1) İşlevlerine Göre - 2) Operand Sayılarına Göre - 3) Operatörün Konumuna Göre - - İşlevlerine göre sınıflandırma operatörün hangi konu ile ile ilgili işlem yaptığı ile ilgilidir. Tipik sınıflar şunlardır: - - - Artitmetik Operatörler (Aritmetic Op.) - - Karşışaltırma Operatörleri (Comparision Op.) - - Mantıksal Operatörler (Logical Op.) - - Bit Operatörleri (Bitwise Op.) - - Özel Amaçlı Operatörler (Special Pupose Op.) - - Operatörün işleme soktuğu atomlara operand denilmektedir. Operatörler "tek operandlı (unary)", "iki operandlı (binary)" ve "üç operandlı (ternary)" olabilirler. - Örneğin / operatörü "iki opendlı (binary)" bir operatördür. Çünkü a / b biçiminde iki değeri işleme sokmaktadır. Ancak örneğin "not" operatörü - tek operandlı (unary) bir operatördür. not a biçiminde tek bir değeri işleme sokmaktadır. Programlama dillerinde üç operandlı operatörler - seyrek bir biçimde bulunurlar. Python'da üç operandlı tek bir operatör vardır. - - Operatör operandlarının önüne getirilerek kullanılıyorsa bunlara "önek (prefix)", sonuna getirilerek kullanılıyorsa bunlara "sonek (postfix)" - ve arasına getirilerek kullanılıyorsa bunlara da "araek (infix)" operatörler denilmektedir. Örneğin / operatörü araek bir operatördür. Ancak not - operatörü önek bir operatördür. - - Bir operatörü ele alırken bu üç dınıflandırmada da nereye düştüğünün belirtilmesi gerekir. Örneğin "/ operatörü iki openadlı araek (binary infix) - aritmetik operatördür". Ya da örneğin "not operatörü tek operandlı önek (unary prefix)" bir mantıksal operatördür" gibi. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir ifadede birden fazla operatör varsa bu operatörler belli bir sırada yapılır. Buna "operatörler arasındaki öncelik ilişkisi - (operator precedency)" denilmektedir. Örneğin: - - a = b + c * d - - Burada üç operatör vardır. İşlemler şu sırada yapılacaktır: - - İ1: c * d - İ2: b + İ1 - İ3: a = İ2 - - Çünkü * operatörü + operatörüne göre daha önceliklidir. Öncelik sırasını değiştirmek için parantezlerden faydalanılmaktadır. Örneğin: - - a = (b + c) * d - - İ1: (b + c) - İ2 = İ1 * d - İ3: a = İ2 - - Python'da pek çok operatör bulunduğu için programcının hangi operatörün hangi operatörden daha öncelikli olduğunu bilmesi gerekmektedir. - - Operatörler arasındaki öncelik ilişkisi "operatörlerin öncelik tablosu" denilen bir tabloyla betimlenmektedir. Bu tablo satırlardan oluşur. - Her satırın yanında "soldan-sağa" ya da "sağdan sola" ibaresi vardır. Tabloda üst satırdaki operatörler alt satırdaki operatörlerden - daha yüksek önceliklidir. Aynı satırda bulunan operatörler "ifade içerisindeki (tablodaki değil) konumlarına göre soldan sağa ya da sağdan sola öncelikli" - yapılmaktadır. Aşağıda öncelik stablosunun yalın bir biçimi görünmektedir: - - () soldan sağa - * / soldan sağa - + - soldan sağa - = sağdan sola - - Bu durumda örneğin: - - a = b - c * d + e - - İ1: c * d - İ2: b - İ1 - İ3: İ2 + e - İ4: a = İ3 - - Öncelik tablosunda bir satırdaki operatörlerin sırasının hiçbir önemi yoktur. Çünkü "soldan sağa ya da sağdan sola ibaresi - ifadedeki sırayı belirtmektedir, tablodaki sırayı belirtmemektedir." -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - *, /, + ve - operatörleri iki operand'lı araek aritmetik operatörlerdir. Bunlar temel dört işlemi yaparlar. Öncelik tablosounda - * ve / operatörleri + ve - operatörlerinden daha yüksek önceliklidir. - - () soldan sağa - * / soldan sağa - + - soldan sağa - = sağdan sola - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da / operatörünün her iki operandı int olsa bile bu operatör float bir değer üretmektedir. Bu anlamda / operatörünün - davranışı C, C++, Java ve C#'takinden farklıdır. -#------------------------------------------------------------------------------------------------------------------------ - -result = 10 / 4 - -print(result, type(result)) # 2.5, - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da noktadan sonraki kısmı atan // biçiminde ayrı bir operatör de vardır. Bu operatöre "floordiv" operatörü denilmektedir. - Bu operatörde elde edilen sonuç pozitifse sayının noktadan sonraki kısmı atılır. Tam kısmı elde edilir. Operandların - her ikisi de int ise sonuç int türden olur. Operand'ların en az biri float türdense sonuç float olur. -#------------------------------------------------------------------------------------------------------------------------ - -result = 10 // 4 -print(result, type(result)) # 2 - -result = 10. // 4 -print(result, type(result)) # 2.0 - -#------------------------------------------------------------------------------------------------------------------------ - // operatörü sonuç üzerinde "floor" işlemi uygulamaktadır. floor işlemi bir noktalı sayının kendisinden küçük ya da - ona eşit olan en yakın tamsayıya dönüştürülmesine denilmektedir. Bu davranış C, C++, Java ve C#'takinden farklıdır. - Örneği Python'da 10 // 4 bize 2 verir. Çünkü 2.5 değerinin floor işlemine sokulmasından 2 değeri elde edilmektedir. - Ancak -10 // 4 bize -3 verir. Çünkü -2.5 değerinin floor işlemine sokulmasından -3 elde edilmektedir. C, C++, Java - ve C#'taki davranışa "truncation toward zero" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -result = -10 // 4 -print(result, type(result)) # -3 - -#------------------------------------------------------------------------------------------------------------------------ - - // operatörü öncelik tablosunda * ve / ile soldan sağa öncelikli satırda bulunmaktadır. - - () soldan sağa - * / // soldan sağa - + - soldan sağa - = sağdan sola - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - % operatörü iki operandlı araek bir aritmetik operatördür. Bu operatör soldaki operandın sağdaki operanda bölümünden - elde edilen kalan değerini üretir. Operatörün her iki operandı da int türündense sonuç int türünden elde edilir. - Operandlardan en az biri float ise sonuç float türden olur. % operatörü de öncelik tablosunda * , / ve // operatörü - ile aynı satırda bulunmaktadır: - - () soldan sağa - * / // % soldan sağa - + - soldan sağa - = sağdan sola - -#------------------------------------------------------------------------------------------------------------------------ - -result = 20 % 3 -print(result, type(result)) # 2 - -result = 20.5 % 3 -print(result) # 2.5 - -#------------------------------------------------------------------------------------------------------------------------ - a sayısını b ye böldüğümüzde bölüm c kalan d ise aslında a = c * b + d ilişkisi söz konusudur. Burada c = a // b olduğuna - göre d = a - a // b * b olur. Bu nedenle Python'da negatif bir sayının pozitif bir sayıya bölümünden elde edilen kalan - pozitif olmaktadır. Yani örneğin -10 % 4 işleminin sonucu 2'dir. Çünkü -10 // 4 işleminin sonucu "floordiv" nedeniyle -3'tür. - -3 * 4 = -12'dir. -12'ye 2 toplarsak -10 elde ederiz. -10 % 4 işleminin -10 - -10 // 4 * 4 ile eşdeğer olduğuna dikkat - ediniz. (C, C++, Java ve C# gibi dillerde tamsayılı bölme "floordiv" biçiminde yapılmadığı için o dillerde -10 % 4 - işlemi -10 - -10 / 4 * 4 işlemi ile eşdeğerdir. Bu dillerde -10 / 4 işleminin sonucu -2 olduğu için dolayısıyla da - -10 % 4 işlemi de -2'ye eşit olmaktadır.) -#------------------------------------------------------------------------------------------------------------------------ - -result = -10 % 4 -print(result, type(result)) # 2 - -result = 10 % -4 -print(result, type(result)) # -2 - -#------------------------------------------------------------------------------------------------------------------------ - İşaret + ve işaret - operatörleri tek operandlı önek (unary prefix) aritmetik operatörlerdir. İşaret - operatörü operandının - negatif değerini üretir. İşaret + operatörü operandı ile aynı değeri üretir. Bu iki operatör de öncelik tablosunda - diğer aritmetik operatörlerden yüksek öncelikli biçimde "sağdan sola" grupta bulunmaktadır: - - () soldan sağa - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - = sağdan sola - - Örneğin: - - a = ----1 - - Burada - operatörlerinin hepsi işaret - operatörüdür. İşlemler sağdan sola şöyle yapılır: - - İ1: -1 => -1 - İ2: -İ1 => 1 - İ3: -İ2 => -1 - İ4: -İ3 => 1 - İ5: a = İ4 => 1 - - Örneğin: - - a = 3----1 - - Hu ifadenin anlamlı olabilmei için ilk - operatörünün çıkartma operatörü diğer - operatörlerinin işaret - operatörü olması - gerekir. İşlemler şöyle yapılacaktır: - - İ1: -1 => -1 - İ2: -İ1 => 1 - İ3: -İ2 => -1 - İ4: 3 - İ2 => 4 - İ5: a = İ4 => 4 - -#------------------------------------------------------------------------------------------------------------------------ - -a = 3----1 -print(a) # 4 - -#------------------------------------------------------------------------------------------------------------------------ - Python'da üs almak için ** operatörü bulundurulmuştur. ** iki operandlı araek bir operatördür. Bu operatör soldaki - operandının sağdaki kuvvetini elde eder. Sayının 0.5'inci kuvvetinin karekök anlamına geldiğini anımsayınız. Sayının - negatif kuvvetlerinin de geçerli olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -result = 3 ** 3 -print(result) # 27 - -result = 3 ** 1.7 -print(result) # 6.473007839923779 - -result = 3 ** 0.5 -print(result) # 1.7320508075688772 - -result = 2 ** -2 -print(result) # 0.25 - -#------------------------------------------------------------------------------------------------------------------------ - ** operatörü öncelik tablosunda işaret + ve işaret - operatörlerinden daha yüksek öncelikli sağdan sola bir grupta bulunmaktadır. - Bu operatörün öncelik tablosundaki yeri matematiksel alana uygun olsa da programlama dillerine göre biraz yadırganmaktadır: - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - = sağdan sola - -#------------------------------------------------------------------------------------------------------------------------ - -result = -3 ** 2 -print(result) # -9 - -result = (-3) ** 2 -print(result) # 9 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - ** operatörünün sağdan sola öncelikli grupta olduğuna dikkat ediniz. Yani ifade içerisinde birden fazla ** operatörü - varsa önce sağdaki yapılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -result = 2 ** 3 ** 2 -print(result) # 512 - -result = (2 ** 3) ** 2 -print(result) # 64 - -#------------------------------------------------------------------------------------------------------------------------ - Python'da 6 karşılaştırma operatörü vardır. Karşılaştırma operatörlerinin hepsi iki operandlı araek (binary infix) - operatörlerdir: - - < > <= >= == != - - "Eşit mi" karşılaştırmasının == operatörü ile "eşit değil mi" karşılaştırmasının ise != operatörü ile yapıldığına - dikkat ediniz. - - Karşılaştırma operatörleri artimetik operatörlerden daha düşük önceliklidir. Bu operatörler bool türden değer üretirler. -#------------------------------------------------------------------------------------------------------------------------ - -result = 3 > 2 -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - bool türünden değerler karşılaştırma operatörleri de dahil olmak üzere diğer türlerle işleme sokulursa önce int türüne - dönüştürülürler sonra karşılaştırma işlemine girerler. bool değerler int türüne dünüştürülürken True 1 olarak, False - ise 0 olarak dönüştürülmektedir. Örneğin: - - result = True > 0 - - Bu işlemde operandlardan biri bool diğeri int türdendir. Bu durumda True int türüne 1 olarak dönüştürülecek ve sonuç - True olarak elde edilecektir. Benzer biçimde iki bool türü de kendi aralarında karşılaştırma işlemine sokulursa önce - operand'lar int türüne dönüştürülür, sonra karşılaştırma yapılır. Örneğin: - - result = True > False - - Burada aslında 1 > 0 gibi bir karşılaştırma yapılmıştır. Dolayısıyla bu işlem True sonucunu verecektir. -#------------------------------------------------------------------------------------------------------------------------ - -result = True > 0 -print(result) # True - -result = True > False -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - Python'da karşılaştırma operatörleri kombine edildiğinde "and" etkisi oluşur. Örneğin a < b < c işlemi geçerlidir ve - bu işlem a < b and b < c anlamına gelmektedir. Ya da örneğin a == b != c geçerlidir. Bu işlem a == b and b != c anlamına - gelmektedir. Örneğin: - - a == b == c - - işlemi a == b and b == c anlamına gelmektedir. - - Tabii ikiden fazla karşılaştırma operatörü de bu biçimde kombine edilebilir. Örneğin a > b < c > d işleminin eşdeğeri - a > b and b < c and c > b biçimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -val = int(input('Bir değer giriniz:')) - -result = 10 < val < 20 # eşdeğeri: 10 < val and val < 20 -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Karşılaştırma operatörleri öncelik tablosunda aritmetik operatörlerdne düşük öncelikli gruplardadır. - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - = sağdan sola - - Yani örneğin: - - result = a + b > c + d - - gibi bir işlemde a + b ile c + d karşılaştırılmaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - Python'da üç mantıksal operatör vardır: and, or ve not operatörleri. and ve or operatörleri iki operandlı araek - (binary infix), not operatörü ise tek operandlı önek (unary prefix) operatörlerdir. - - Python'da mantıksal operatörlerin operandları herhangi bir türden olabilir. Halbuki Java ve C# gibi bazı dillerde mantıksal - operatörlerin operandları bool türden olmak zorundadır. Yani örneğin Python'da aşağıdaki gibi bir işlem geçerlidir: - - result = 3 and -3.5 - - Python'da mantıksal operatörler karşılaştırma operatörlerinden daha düşük önceliklidir. Ancak mantıksal not operatörü - pek çok programlama dilinde yüksek bir önceliğe sahipken Python'da o dillere göre düşük bir önceliğe sahiptir: - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - not sağdan sola - and soldan sağa - or soldan sağa - = sağdan sola - - Genellikle mantıksal operatörler karşılaştırma operatörlerinin çıktıları üzerinde işlem yapmak için kullanılırlar. - Örneğin: - - result = a > b and c > d - - Burada a > b ve c > d koşılunun aynı anda sağlanıp sağlanmadığına bakılmaktadır. - - and ve or operatörleri şöyle çalışmaktadır: Bu operatörlerin diğer programlama dillerinde olduğu gibi "kısa devre (short circuit)" - özellikleri vardır. Bu operatörlerin önce her zaman sol tarafındaki ifade yapılır. Sağ tarafında ne kadar yüksek öncelikli - operatör olursa olsun önce yalnızca sol tarafları yapılır. Bu operatörler operandlarını True/False olarak yorumlarlar. - int ve float operandlarda sıfır değeri False, sıfır dışı herhangi bir değer True anlamına gelmektedir. and operatörünün sol - tarafındaki ifade False ise bu operatörün sağ tarafındaki ifade hiç yapılmaz, operatör sol tarafındaki değeri üretir. and - operatörünün sol tarafındaki ifade True ise bu kez sağ tarafındaki ifade yapılır. Operatör sağ tarafındaki ifadenin değerini - üretir. or operatörü de benzerdir. Bu operatörün de önce sol tarafındaki ifade yapılır. Bu ifade True ise sağ tarafındaki - ifade hiç yapılmaz operatör sol tarafındaki değeri üretir, eğer bu ifade False ise bu kez sağ tarafındaki ifade yapılır. - Operatör sağ tarafındaki değeri üretir. Bu operatörlerin sol tarfındaki ya da sağ tarafındaki değeri ürettiğine dikkat ediniz. - Örneğin: - - result = -3 and 6.7 - print(result) # 6.7 - - Örneğin: - - result = 0 and 6.7 - print(result) # 0 - - Örneğin: - - result = 3 + 2 or False - print(result) # 5 - - Örneğin: - - result = 3 > 2 and 5 < 0 - print(result) # False - - Örneğin: - - result = 1.2 and 0 - print(result) # 0 - - Örneğin: - - result = 10 and 20 - print(result) # 20 - -#------------------------------------------------------------------------------------------------------------------------ - and ve or operatörleri aynı ifadede kullanıldığında her zaman sol taraftaki operatörün sol tarafı önce yapılmaktadır. - Örneğin: - - ifade1 and ifade2 or ifade3 - - Burada önce ifade1 yapılır eğer ifade1 True ise ifade2 yapılır ve and operatmründen ifade2 elde edilir. ifade2 True - ise ifade3 yapılmaz sonuç olarak ifade2 elde edilir. Örneğin: - - ifade1 or ifade2 and ifade3 - - Burada ifade aslında aşağıdaki sonucu vermelidir: - - ifade1 or (ifade2 and ifade3) - - Yani bu ifade aslında ifade1 ile (ifade and ifade3)'ün or'lanması anlamına gelmektedir. and ve or operatörlerinin her zaman sol tarafı - önce yapılacağına göre burada önce ifade1 yapılır. Eğer ifade1 True ise doğurdan ifade1'in değeri elde edilir. Eğer ifade1 - False ise bu kez ifade2 yapılır. ifade2 False ise ifade3 hiç yapılmaz, ifade2'nin sonucu elde edilir. Eğer ifade1 False ancak - ifade2 True ise ifade3'ün sonucu elde edilir. Özetle and ve or operatörleri kombine edildiğinde her zaman en soldaki operatörün en solu - önce yapılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -result = 10 and 0 or 5 -print(result) # 5 - -result = 0 and -5 or 5 -print(result) # 5 - -result = 100 or -5 and 5 -print(result) # 100 - -result = 0 or 0 and 5 -print(result) # 0 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - not operatörü tek operandlı önek (unary prefix) mantıksal operatördür. Bu operatör True değerini False, False değerini True yapar. - Operatörün ürettiği değer her zaman bool türdendir. Bu operatörün de operandları int ya da float türünden olabilir. Sıfır dışı değerler - (nonzero değerler) True olarak sıfır değeri False olarak ele alınır. -#------------------------------------------------------------------------------------------------------------------------ - -result = not 23.4 -print(result) # False - -result = not False -print(result) # True - -result = not 0 -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - not operatörü öncelik tablosunda and ve or operatörlerinden daha yüksek önceliklidir ve kandi içerisinde sağdan sola - grupta bulunmaktadır. - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - not sağdan sola - and soldan sağa - or soldan sağa - = sağdan sola - -#------------------------------------------------------------------------------------------------------------------------ - -result = not not not not True -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - Python'da mantıksal not operatörü C, C++, Java ve C# gibi dillerle kıyaslandığında düşük önceliklidir. Özellikle bu dillerden geçen - kişiler Python'da not operatörünün aritmetik ve karşılaştırma operatörlerinden daha düşük öncelikli olduğuna dikkat etmelidir. Örneğin: - - result = not a + b - - işleminde ya da: - - result = not a < b - - işleminde önce toplama ve karşılaştırma oeratörleri yapılıp sonra not operetörü yapılır. Halbuki C, C++ gibi dillerde önce not işlemi - yapılmaktadır: - - result = !a < b; - -#------------------------------------------------------------------------------------------------------------------------ - Python'da boş olmayan bir string mantıksal olarak True, boş bir string False biçiminde ele alınmaktadır. Başka bir deyişle - dolu string'ler bool türüne True olarak boş string'ler False olarak dönüştürülmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -result = 'ankara' or 12.3 -print(result) # 'ankara' - -result = 'istanbul' and 'ankara' -print(result) # 'ankara' - -result = not '' -print(result) # True - -result = not '0' -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - Python'da None değeri mantıksal olarak ele alınacağı zaman her zaman False biçimde ele alınır. Bşka bir deyişle - None değeri bool türüne her zaman False olarak dönüştürülür. -#------------------------------------------------------------------------------------------------------------------------ - -result = not None -print(result) # True - -result = None or 'ankara' -print(result) # 'ankara' - -#------------------------------------------------------------------------------------------------------------------------ - Ayrıca henüz görmemiş olsak da önemli türler bool türüne şöyle dönüştürülmektedir: - - - Dolu bir liste True olarak, boş liste False olarak. - - Dolu bir demet True olarak, boş bir demet False olarak. - - Dolu bir sözlük True olarak, boş bir sözlük False olarak. - - Dolu bir küme True olarak, boş bir küme False olarak. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da atama işlemi aslında bir operatör değil bir deyim statüsündedir. Ancak biz geleneksel olarak burada atama işlemi için - "operatör" terimini kullanacağız. Bir operatör olarak atama operatörü iki operandlı araek bir operatördür. Anımsanacağı gibi Python'da - tüm atama işlemleri aslında adres ataması anlamına gelmektedir. Örneğin: - - a = b - - Burada b'nin içerisindeki değer a'ya atanmamaktadır. b'nin içerisindeki adres a'ya atanmaktadır. Çünkü bütün değişkenler adres tutmaktadır. - Bu işlemin sonucunda a is b ifadesi True değerini verecektir. - Örneğin: - - a = 10 - - Burada a'nın içerisine 10 atanmamaktadır. 10 bir int nesneye yerleştirilip a'ya o int nesnenin adresi atanmaktadır. Atama operatörü öncelik - tablosunun en düşük düzeyinde ve sağdan sola öncelikli gruptadır. - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - not sağdan sola - and soldan sağa - or soldan sağa - = sağdan sola - - Örneğin: - - a = b = 10 - - İ1: b = 10 - İ2: a = İ1 - - Yani burada 10 değeri bir nesneye yerleştirilecek, o nesnenin adresi hem b'ye hem de a'ya atanacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da atama işlemi aslında bir operatör olarak değil deyim olarak değerlendirilmektedir. Dolayısıyla atama işleminden bir değer elde edilmez. - Örneğin biz C, C++, Java ve C# gibi dillerde atama operatörünü parantez içerisine alıp oradan elde edilen değeri diğer operatörlerle - işleme sokabiliriz. Ancak Python'da bunu yapamayız. -#------------------------------------------------------------------------------------------------------------------------ - -a = (b = 10) + 20 # C, C++, Java ve C#'ta geçerli, Python'da geçersiz! - -#------------------------------------------------------------------------------------------------------------------------ - Atama işleminden Python'da bir değer elde edilememesi yüzünden diğer dillerde yapılan bazı faydalı işlemler Python'da - atama işlemiyle yapılamamaktadır. Örneğin: - - while (a = int(input())) != 0: # Python'da geçersiz, ama benzeri diğer dillerde geçerli - pass - -#------------------------------------------------------------------------------------------------------------------------ - Python'a 3.8 versiyonu ile birlikte (2019 Ekim) "Walrus operatörü" diye isimlendirilen değer üreten bir atama operatörü de eklenmiştir. - Bu operatör sayesinde yukarıda belirttiğimiz diğer dillerde yapamadığımız işlemleri yapabiliriz. Walrus operatörü := sembolü ile - temsil edilmektedir. İki operandlı araek bir operatördür. -#------------------------------------------------------------------------------------------------------------------------ - -a = (b := 10) + 20 -print(a, b) # 30 10 - -#------------------------------------------------------------------------------------------------------------------------ - Python'da gereksiz bir biçimde Walrus operatörünün kullanılması konusunda programcının cesaretini kırmak için gereksiz - Walrus operatörünün kullanılması error oluşturmaktadır. Örneğin: - - a := 10 - - Burada Walrus operatörüne hiç gerek yoktur. Bu nedenle bu kullanım error oluşturur. Ancak örneğin: - - a = (b := 10) + 20 - - Burada Walrus operatörü yerine atama operatörünü kullanamayız. Buradaki Walrus operatörü doğru kullanılmıştır ve error oluşturmayacaktır. - Örneğin: - - a := b := 10 - - Burada Walrus operatörü gereksiz kullanılmıştır. Error oluşacaktır. Çünkü bu işlem atama operatörleriyle de yaılabilmektedir. Walrus operatörü - yukarıda geçersiz durumlarda paranteze alınırsa geçerli hale gelmektedir. Örneğin: - - (a := 10) - - Bu işlem geçerlidir. Parantezlerin "elde edilen değerin kullanılşması anlamına geldiğine dikkat ediniz.). Örneğin: - - a := 10 # error! - (a := 10) #geçerli - - Örneğin: - - b := (a := 10) # error! - - Fakat örneğin: - - b = (a := 10) # geçerli - (b := (a := 10)) # geçerli - - Örneğin: - - print(a := 10) # geçerli parantez zaten vardır - - Buı kod aşağıdaki ile eşdeğerdir: - - a = 10 - print(a) - - Özetle atama operatörü ile yapabileceğimiz bir işlemi Walrus operatörü ile yapmaya çalışırsak bu durum error oluşturur. Ancak error oluşturmasın istiyorsak - paranteze almalıyız. Ancak atama operatörü ile yapamadığımız bir şeyi Walrus operatörüyle yapabiliyorsak bu durum error oluşturmaz. Örneğin: - - print(a := 10) - - Biz bu işlemi atama operatör ile yapamazdık. O halde burada Walrus operatörünün kullanılması geçerlidir. Ancak örneğin: - - a := 10 # error! - - Biz bu işlemi atama operatörü ile yapabilirdik. Burada paranteze alınmadan Walrus operatörünün kullanılması error oluşturur. - - if, while gibi deyimlerde zaten atama operatörü kullanılamadığı için Walrus operatörünün paranteze alınması gerekmez. Bu tür deyimlerde - atanan değer doğrudan işleme sokulmaktadır. Örneğin : - - while s := input(): - pass - - Biz burada Walrus operatörü yerine atama operatörünü kullanamayız. Bu örnekte Walrus operatörünün en dıştan ayrıca paranteze alınması gerekmemektedir. - - Walrus opeeratörü öncelik tablosunda atama opeeatör ile aynı gruptadır: - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - not sağdan sola - and soldan sağa - or soldan sağa - = := sağdan sola - - Bu nedenle aşağıdaki gibi bir kodda parantez gereklidir: - - while (a := int(input('Bir değer giriniz:'))) != 0: - print(a * a) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da ++ ve -- biçiminde operatörler yoktur. ++ ve -- operatörleri C, C++, Java ve C# gibi dillerde vardır ve çok kullanılmaktadır. - Bu dillerde ++ operatörü değişkenin içerisindeki değeri 1 artıtmak için, -- operatörü değişken içerisindeki değeri 1 eksiltmek için - kullanılmaktadır. Python'da değişkenin içerisindeki değeri 1 artırmak ve 1 eksiltmek için sonraki paragrafta açıklayacak olduğumuz - += ve -= gibi işlemli atama operatörleri (augmented assignment operators) ile yapılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da +=, -=, *=, /=, //=, %= gibi bir grup işlemli atama operatörü (augmented assignment statments) vardır. Bu operatörler iki operandlı araek - operatörlerdir. op bir operatör belirtmek üzere: - - a op= b - - işlemi aşağıdakiyle eşdeğerdir: - - a = a op b - - Bu durumda a += 2 işlemi a = a + 2 ile, a *= 10 işlemi a = a * 10 ile eşdeğerdir. and, or ve not operatörlerinin işlemli biçimi yoktur. - Python'da bir değişkeni 1 artırmak için en pratik yöntem += operatörünü kullanmaktadır. Örneğin: - - a += 1 - - Benzer biçimde bir değişkeni 1 eksiltmek için en pratik yok -= operatörünü kullanmaktır: - - a -= 1 - - Tabii işlemli atama operatörleri temel türlerle kullanıldığında bu temel türler "değiştirilemez (immutable)" olduğu - için yine yeni nesnelerin yaratılmasına yol açacaktır. Örneğin: - - >>> a = 10 - >>> id(a) - 140712253256776 - >>> a += 1 - >>> a - 11 - >>> id(a) - 140712253256808 - - Biz kursumuzda atama işlemi için kullanılan = sembolünü ve işlemli atamalar için kullanılan op= sembollerini birer "operatör" - olarak ele alıyoruz. Halbuki bu semboller aslında "Python Language Reference" içerisinde operatör değil "deyim (statement)" - olarak ele alınmaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - İşlemli atama operatörleri atama operatörleriyle sağdan sola aynı öncelik grubunda bulunmaktadır: - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - not sağdan sola - and soldan sağa - or soldan sağa - = := , +=, -=, *=, ... sağdan sola - -#------------------------------------------------------------------------------------------------------------------------ - *=, /= ve //= operatörlerinin atama operatörü önceliğinde olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 - -a *= 2 + 1 - -print(a) # 30 - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi print fonksiyonu çıktıları imlecin bulunduğu yerden itibaresn ekrana (stdout dosyasına) yazdırıp imleci - aşağıda satırın başına geçirmektedir. print fonksiyonu değişken sayıda argüman alabilmektedir. Yani biz print sonksiyonu - ile birden fazla ifadeyi yazdırabiliriz. Bu durumda print fonksiyonu her argümanın değerini yazdırdıktan sonra bir - SPACE boşluk bırakır. print fonksiyonu tüm işlemini bitirince '\n' karakterini ekrana (stdout dosyasına) yazdırmaktadır. - Yani print işleminden sonra imleç aşağı satırın başına geçmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -print('ali', 'veli', 'selami') # ali veli selami - -a = 10; b = 20; c = 30 -print(a, b, c) # 10 20 30 - -#------------------------------------------------------------------------------------------------------------------------ - print fonksiyonuyla birden fazla argümanı yazdırırken her argümandan sonra print araya bir SPACE karakteri bırakmaktadır. - Ancak programcı isterse "sep" isimli parametreyle boşluk yerine argümanlar arasında istenilen yazının bastırılmasını sağlayabilir. - sep için girilen yazının tek karakter olması gerekmemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 30 - -print(a, b, c, sep='***') # 10***20***30 -print('Ok') - -#------------------------------------------------------------------------------------------------------------------------ - sep aparemtresinin hiç girilmemesiyle bunun sep=' ' biçiminde girilmesi arasında bir fark olmadığına dikkat ediniz. - Yani sep parametresinin default değerini ' ' olarak düşünebilirsiniz. - - Aşağıdaki örnekte her argümandan sonra print ", " yazısını ekrana (stdout dosyasına) bastırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 30 - -print(a, b, c, sep=', ') # 10, 20, 30 -print('Ok') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte her argüman yazdırıldıktan sonra imleç aşağıda staırın başına geçirilecek ve dolayısıyla argümanlar - satır satır alt alta yazdırılmış olacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 30 - -print(a, b, c, sep='\n') - -#------------------------------------------------------------------------------------------------------------------------ - print fonksiyonunun "end" isimli parametresi tüm yazdırma işlemi bittiğinde ekrana basılacak yazıyı belirtmektedir. Default durumda - bu yazı '\n' biçimindedir. Yani imleç aşağı satırın başına geçirilir. Ancak biz end parametresi yoluyla print fonksiyonunun işlemini bitirdikten - sonra istediğimiz bir yazıyı ekrana bastırmasını isteyebiliriz. Örneğin: - - print(10, 20, 30, sep='*', end='---') - print('ankara) - - Burada ekranda şunları göreceğiz: - - 10*20*30---ankara -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 30 - -print(a, b, c, sep='xxx', end='yyy') -print('Ok') - -# # 10xxx20xxx30yyyOk - -#------------------------------------------------------------------------------------------------------------------------ - Bilindiği gibi bir değişkenin içerisinde belli bir türden nesnenin adresi olabilir. Örneğin: - - a = 12.3 - - Burada a değişkeninin içerisinde float bir nesnenin adresi vardır. Yani biz a'yı kullandığımızda float bir değeri işleme sokmuş oluruz. - İşte bir nesnenin içerisindeki değeri değiştirerek başka bir türden bir nesne biçiminde elde etme işlemine programlama dillerinde - "tür dönüştürmesi (type conversion / type cast)" denilmektedir. Örneğin biz yukarıdaki a değişkeninin gösterdiği nesnenin içerisindeki değeri - int bir nesne biçiminde elde etmek isteyebiliriz. - - Tür dönüştürme işleminin genel biçimi şöyledir: - - () - - Örneğin: - - a = 13.2 - - b = int(a) - - Tür dönüştürme işlemiyle yeni bir nesne yaratılmaktadır. Her tür dönüştürme işlemi yeni bir nesnenin yaratılmasına yol açmaktadır. Yani tür - dönüştürmesi mevcut nesnesinin türünü değiştirmemektedir. Mevcut nesnenin değerindne hareketle arzu edilen türden yeni bir nesnenin yaratılmasını - sağlamaktadır. Örneğin: - - a = 13.2 - b = int(a) - - Burada a'nın türü değiştirilkmemiştir. Yeni bir int nesne yaratılmış ve onun adresi b'ye atanmıştır. - - Aslında T bir tür belirtmek üzere T türünden bir nesnenin yaratılması T(...) biçiminde bir ifadeyle yapılmaktadır. - Bu konu ileride "sınıflar" konusunda ayrıntılarıyla ele alınacaktır. - -#------------------------------------------------------------------------------------------------------------------------ - float bir değer int türüne dönüştürüldüğünde noktadan sonraki kısmı atılmış olan bir int değer elde edilir. Burada yuvarlama - yapılmadığına float değer negatif de olsa pozitif de olsa noktadan sonraki kısmın atıldığına dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = 13.99 - -print(id(a)) - -b = int(a) - -print(id(b)) - -print(a) # 13.99 -print(b) # 13 - -a = -13.99 - -b = int(a) -print(b) # -13 - -#------------------------------------------------------------------------------------------------------------------------ - bool bir değer int türüne dönüştürülürse eğer değer True ise 1, False ise 0 elde edilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = True - -b = int(a) - -print(b) # 1 - -a = False - -b = int(a) - -print(b) # 0 - -#------------------------------------------------------------------------------------------------------------------------ - Bir string'i int türüne dönüştürürken string içerisindeki yazı int türü için anlamlı sayısal karakterlerden oluşuyorsa - yeni bir int nesne yaratılır ve yazı int bir nesne biçiminde ifade edilir. Ancak yazının içerisindeki karakterler int - türü için anlamlı değilse bu durumda exception (ValueError) oluşur. Daha önceden de belirttiğimiz gibi bir exception oluştuğunda eğer - exception programcı tarafından ele alınmadıysa program çökmektedir. String'in başındaki boşluk karakterlerine "leading space", string'in - sonundaki boşluk karakterlerine "trailing space" denilmektedir. Dönüştürme sırasında yazının başındaki ve sonundaki boşluk karakterleri - dikkate alınmamaktadır. Örneğin: - - >>> a = '123' - >>> b = int(a) - >>> b - 123 - >>> a = '12.3' - >>> b = int(a) - Traceback (most recent call last): - File "", line 1, in - ValueError: invalid literal for int() with base 10: '12.3' - >>> a = ' -123 ' - >>> b = int(a) - >>> b - -123 -#------------------------------------------------------------------------------------------------------------------------ - -a = '123' - -b = int(a) - -print(b) # 123 - -a = 'ali' - -b = int(a) # exception oluşur! - -print('unreachable code') - -#------------------------------------------------------------------------------------------------------------------------ - Bir string'i int türüne dönüştürürken string içerisindeki yazının kaçlık sistemde bir sayı belirttiğini de int fonksiyonunun - ikinci parametresiyle ifade edebiliriz. Örneğin: - - a = '100' - - b = int(a) - - Default durumda a içerisindeki yazının 10'luk sistemde bir sayı belirttiği varsayılmaktadır. Fakat örneğin: - - a = '100' - - b = int(a, 16) - - Burada artık biz '100' yazısında belirtilen sayının 16'lık sistemde bir sayı olması gerektiğini belirtiyoruz. Örneğin: - - a = '100' - - b = int(a, 2) - - Burada a yazısının belirttiği sayının 2'lik sistemde yorumlanması gerektiği belirtilmektedir. 16'lık, 8'lik ya da 16'lık - sistemden yazı dönüştürürken yazının başında taban belirten önekler bulunabilir. Örneğin: - - >>> s = '0x1234' - >>> a = int(s, 16) - >>> a - 4660 - >>> s = '0o1234' - >>> a = int(s, 8) - >>> a - 668 - >>> s = '0b10101' - >>> a = int(s, 2) - >>> a - 21 - -#------------------------------------------------------------------------------------------------------------------------ - -a = '0x10' - -b = int(a, 16) -print(b) # 16 - - -a = '0b1010' - -b = int(a, 2) -print(b) # 10 - -a = '0x10' -b = int(a) # exception oluşur! - -print(b) - -#------------------------------------------------------------------------------------------------------------------------ - Complex türünden int türüne dönüştürme geçerli kabul edilmemektedir. Böyle bir dönüşüm yapılmak istendiğinde exception (TypeError) - oluşmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - None değerinin int türüne dönüştürülmesi de Python'da geçerli kabul edilmemektedir. Bu dönüştürme de exception'a (TypeError) yol açmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - int bir ifadenin int türüne dönüştürülmesi geçerlidir ancak anlamsızdır. Bu durumda dönüştürüleek ifade ile aynı değer - elde edilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - O halde int türüne dönüştürme özetle şöyle yürütülmektedir: - - 1) float bir değer int türüne dönüştürüldüğünde noktandan sonraki kısım atılırç - - 2) str türü int türüne dönüştürüldüğünde eğer yaqzının içerisindeki karakterler int türü için geçerliyse dönüştürme yapılır - değilse exception (ValueError) oluşur. - - 3) bool bir değer int türüne dönüştürüldüğünde False için 0, True için 1 elde edilmektedir. - - 4) complex türü int türüne dnüştürülemez. Dönüştürülmeye çalışılırsa exception (TypeError) oluşur. - - 5) None değeri (NoneType türü) int türüne dönüştürülemez. Dümüştürülmek istenirse exception (TypeError) olulur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - int bir değer float türüne dönüştürülürken eğer int değer float formatıyla (IEEE 754 long real format) tam olarak ifade - edilebiliyorsa .0 biçiminde float türüne kayıp olmadan dönüştürülür. Ancak Python'da int değerlerin bir sınırının olmadığını anımsayınız. - Bu durumda çok büyük int değerler float türüne dönüştürülemeyecektir. İşte bu tür durumlarda exception (OverrlowError) oluşmaktadır. - Bazı int değerler mantis kayıplarıyla basamaksal bir kayıp olmadan float türe dönüştürülebilmektedir. Bu durumda bir exception - oluşmaz. Sayı float türüyle tam olarak ifade edilemese bile mantis kaybıyla ona en yakın büyük ya da ona en yakın küüçük - sayı elde edilmektedir. Ancak basamaksal kayıplar exception'a (OverflowError) yol açmaktadır. Örneğin: - - >>> a = 12345678901234567890123456789 - >>> b = float(a) - >>> b - 1.2345678901234568e+28 - >>> int(b) - 12345678901234568227576610816 - - >>> a = 1817236817263817263871263871263871263871623876123876128736187263871263817263871263871623812638716238716238761238 - 761283761827361872368712638172638712638712638712638172638126387126387123618723618273618273618723681723618236182361827361 - 283612836128361872361872361823681726381726387126387126381723687126381726387126387126381723618276387126387126387123 - >>> b = float(a) - Traceback (most recent call last): - File "", line 1, in - OverflowError: int too large to convert to float -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir string float türüne dönüştürülebilir. Bu durumda string float ile ifade edilebiliyorsa (yani string'i oluşturan karakterler - float sayı için anlamlıysa) dönüştürme yapılır. String float türüyle ifade edilemiyorsa exception (ValueError) oluşur. - float fonksiyonu her zaman tek parametrelidir. flaot türü ile ifade edilemeyecek çok büyük büyük ya da küçük yazısal değerlerin - float türüne dönüştürülmesi sırasında ne olacağı konusunda "Python Standard Library Reference" açık bir şey söylememiştir. - Mevcut CPythob gerçekleştirimi bu durumda +inf ya da -inf değerlerini üretmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = ' 123.45 ' - -b = float(a) -print(b) - -a = '123.23ali' - -b = float(a) # exception oluşur -print(b) - -#------------------------------------------------------------------------------------------------------------------------ - bool türünden bir ifade float türüne dönüştürüldüğünde eğer bool değer True ise 1.0 biçiminde false ise 0.0 biçiminde - float bir değer elde edilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Complex bir ifade float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur. Benzer biçimde None değeri de - flaot türüne döüştürülememektedir. Dönüştürülmek istenirse yine exception (TypeError) oluşmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - O halde float türüne dönüştürme özetle şöyle yürütülmektedir: - - 1) int bir değer float türüne bilgi kaybı olmadan, mantissel kayıp ile dönüştürülebilir. Ancak basamaksal bir kayıp - oluşacaksa dönüştürme exception (OverflowError) ile sonuçlanır. - - 2) str türü float türüne dönüştürülebilir. Tabii bunun için yazının karakterlerinin float türü için anlamlı olması gerekir. - Aksi takdirde dönüştürme exception (ValueError) ile sonuçlanır. - - 3) bool türü float türüne dönüştürülürse True için 1.0, False için 0.0 değeri elde edilir. - - 4) Complex türü float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur. - - 5) None değeri (NoneType türü) türü float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir int değer bool türüne dönüştürülürken eğer değer sıfır dışı bir değerse True olarak 0 değeri ise False olarak dönüştürülür. - Benzer biçimde float bir değer de bool türüne aynı biçimde dönüştürülmektedir. (Sıfır dışı değer demekle 0'ın dışında pozitif ya da - negatif herhangi bir değer kastedilmektedir.) None değer bool türüne her zaman False olarak dönüştürülmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 123801928309123 - -b = bool(a) -print(b) # True - -a = -12.34 - -b = bool(a) -print(b) # True - -a = None - -b = bool(a) -print(b) # False - -#------------------------------------------------------------------------------------------------------------------------ - Bir string bool türüne dönüştürülürken string'in içerisindeki yazıya bakılmaz. String'in boş mu dolu mu olduğuna bakılır. - Dolu bir string bool türüne True olarak, boş bir string False olarak dönüştürülmektedir. Örneğin aşağıdaki dönüştürme - True değerini verecektir: - - s = 'False' - b = bool(s) - print(b) # True - -#------------------------------------------------------------------------------------------------------------------------ - -a = 'ankara' - -b = bool(a) -print(b) # True - -a = 'False' - -b = bool(a) -print(b) # True - -a = '' - -b = bool(a) -print(b) # False - -#------------------------------------------------------------------------------------------------------------------------ - Ayrıca boş listeler, demetler, sözlükler, kümeler bool türüne False biçiminde, dolu listeler, demetler, sözlükler ve kümeler de True biçiminde - dönüştürülürler. Listeler, demetler, sözlükler, kümeler konusu izleyen bölümlerde ele alınmaktadırç -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir int değer ya da bir float değer complex türüne dönüştürülebilir. Bu durumda sanal kısmı 0 olan bir complex sayı elde edilir. - bool bir değer complex türüne dönüştürülürse gerçek kısmı 1 ya da 0 olan sanal kısmı 0 olan bir complex sayı elde edilmektedir. - Dönüştürülecek tür ne olursa olsun complex türünün gerçek ve sanal kısımları float türdendir. Örneğin: - - >>> z = 3j+1 - >>> z.real - 1.0 - >>> z.imag - 3.0 - >>> a = 10 - >>> z = complex(a) - >>> z - (10+0j) - >>> z.real - 10.0 - >>> z.imag - 0.0 - - complex sayılar print edilirken her gerçek ve sanal kısımları tamsayı ise güzel bir görünüt oluşturmak için nokta kullanılmamaktadır. - Ancak aslında her durumda complex türünün gerçek ve sanal kısımları float bir sayı biçimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 - -b = complex(a) -print(b) # (10+0j) - -a = 10.5 - -b = complex(a) -print(b) # (10.5+0j) - -a = True - -b = complex(a) -print(b) # (1+0j) - -#------------------------------------------------------------------------------------------------------------------------ - Bir string de complex türüne dönüştürülebilir. 'a+bj' biçimindeki bir yazının başında ve sonunda boşluk karakterleri bulunabilir. - Ancak arasında bulunamaz. Ayrıca yazı 'aj+b' biçiminde de belirtilemektedir. Örneğin: - - >>> s = '2+3j' - >>> z = complex(s) - >>> print(z) - (2+3j) - >>> s = '10' - >>> z = complex(s) - >>> print(z) - (10+0j) - >>> s = '3j-2' - >>> z = complex(s) - Traceback (most recent call last): - File "", line 1, in - ValueError: complex() arg is a malformed string - -#------------------------------------------------------------------------------------------------------------------------ - -a = '3+2j' - -b = complex(a) -print(b) # (3+2j) - -a = '10' - -b = complex(a) -print(b) # (10+0j) - -a = 10.5 - -b = complex(a) -print(b) # (10.5+0j) - -#------------------------------------------------------------------------------------------------------------------------ - Temel türlerin hepsi str türüne dönüştürülebilir. Yani örneğin int bir değer, float bir değer, bool bir değer, complex bir değer, - None değeri str türüne dönüştürülebilmektedir. Dönüştürme sonucunda o değere ilişkin bir yazı elde edilmektedir. Örneğin: - - >>> a = 10 - >>> s = str(a) - >>> s - '10' - >>> a = 12.34 - >>> s = str(a) - >>> s - '12.34' - >>> a = True - >>> s = str(a) - >>> s - 'True' - >>> a = None - >>> s = str(a) - >>> s - 'None' - >>> a = 3j+2 - >>> s = str(a) - >>> s - '(2+3j)' -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da klavyeden (stdin dosyasından) okuma yapan input isimli tek bir fonksiyon vardır. input fonksiyonun da str türünden - bir değer verdiğini anımsayınız. Bu durumda biz kalvyeden int, float gibi okumalar yapmak için aslında string'i bu türlere dönüştürmekteyiz. - Örneğin: - - a = int(input('Bir sayı giriniz:')) - - print(a * a) - - Ya da örneğin: - - a = float(input('Bir sayı giriniz:')) - - print(a * a) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Programlama dillerinde farklı türler ile işlem yapıldığında nasıl bir sonucun elde edileceği programcı tarafından biliniyor - olması gerekir. C, C++, Java, C# ve Python gibi dillerde farklı türlerle işlem yapılabilmektedir. Ancak Swift gibi bazı dillerde - farklı türlerle işlemler yapılamamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da iki int değeri aritmetik işleme sokarsak sonuç int türden çıkar. Benzer biçimde iki float değeri aritmetik işleme sokarsak - sonuc float türden çıkar. Ancak int bir değer ile float bir değeri aritmetik işleme sokarsak sonuç float türünden çıkacaktır. - Tabii özel bir durum olarak / operatörünün her zaman float değer ürettiğine dikkat ediniz. Yani biz iki int değeri / operatörüyle - işleme sokarsak sonuç int türden değil float türden çıkacaktır. Mantıksal operatörlerin de her zaman bool türden değer ürettiğine dikkat - ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 3.14 -c = a + b -print(c, type(c)) # 13.14 - -#------------------------------------------------------------------------------------------------------------------------ - bool türü ile int türü işleme sokulursa sonuç int türünden, bool türü ile float türü işleme sokulursa sonuç float türünden, - bool türü ile complex türü işleme sokulursa sonuç complex türünden elde edilmektedir. Bu tür işlemlerde bool değer True ise - 1 olarak, False ise 0 olarak işleme sokulmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = True -c = a + b -print(c, type(c)) # 11 - -a = 12.34 -c = a + b -print(c, type(c)) # 13.34 - -a = 3j+2 -c = a + b -print(c, type(c)) # (3+3j) - -#------------------------------------------------------------------------------------------------------------------------ - iki bool değer kendi aralarında işleme sokulduğunda sonuç int türünden çıkmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = True -b = False -c = a + b - -print(c, type(c)) # 1 - -#------------------------------------------------------------------------------------------------------------------------ - int bir değerle float bir değer işleme sokulduğunda sonuç float çıkacaktır ancak sonuç üzerinde bir kayıp oluşabilir. - Eğer oluşan kayıp basamaksal değilse bu bir hata olarak değerlendirilmez. Gerçek değer en yakın ondan büyük olan ya da küçük olan değer - elde edilir. Ancak basamaksal bir kayıp söz konusu olursa exception (OverflowError) oluşmaktadır. Örneğin: - - >>> a = 1987239812739817239817239871293871928371987239817239817239871293871298371982739812379812739182739812739 - 817239817239871239871293871298371982379182739817239817239871298371928379182739817239817298379827349827349872349 - 87239847293847928374982734982734982734987239847293847928374982374982374982374982374982374982371234234234234 - >>> b = a * 2.0 - Traceback (most recent call last): - File "", line 1, in - OverflowError: int too large to convert to float -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - complex türü ile int türü, float türü ve bool türü aritmetik işleme sokulursa sonuç complex türünden elde edilmektedir. Örneğin: - - >>> a = 3j+2 - >>> b = 1 - >>> c = a + b - >>> c - (3+3j) - >>> b = 1.2 - >>> c = a + b - >>> c - (3.2+3j) - >>> b = True - >>> c = a + b - >>> c - (3+3j) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da iki str türündne değişken toplama işlemine sokulduğunda işlem sonucunda yine bir str nesnesi elde edilir. - Elde edilen string iki string'in birleşiminden oluşur. Java, C# gibi pek çok dilde de bu özellik benzer biçimde vardır. - Örneğin: - - >>> s = 'ankara' - >>> k = 'istanbul' - >>> result = s + k - >>> result - 'ankaraistanbul' - - Ancak str türünden bir değişkenle diğer türden bir değer toplanamaz. Oysa Java ve C# gibi bazı dillerde bu durum mümkündür. - O dillerde bu toplama işlemi yapılırken string olmayan tür otomatik olarak string türüne dönüştürülüp sonuç string türünden elde - edilmektedir. Ancak yukarıda da belirttiğimiz gibi Python'da bu durum geçerli değildir. Özellikle diğer dillerden geçen kişilerin - bu duruma dikkat etmesi gerekir. Örneğin: - - >>> s = 'Ankara' - >>> a = 6 - >>> b = s + a - Traceback (most recent call last): - File "", line 1, in - TypeError: can only concatenate str (not "int") to str - - Tabii programcı böyle bir şeyi yapmak isterse diğer operandı kendisi str türüne dönüştürmelidir. Örneğin: - - >>> s = 'Ankara' - >>> a = 6 - >>> b = s + str(a) - >>> b - 'Ankara6' - - String'lerle ilgili diğer özellikler izleyen bölümlerde ele alınmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aralarında fiziksel ya da mantıksal ilişki olan bir grup nesneden oluşan topluluğa "veri yapısı (data structure)" denilmektedir. - Veri yapısı çoğul bir anlam ifade etmektedir. Python'da da built-in çeşitli veri yapıları vardır. Bunlar "listeler", "demetler", - "sözlükler", "kümeler" ve "stringler"dir. Python'daki bu built-in bu veri yapıları doğrudan dilin sentaksıyla desteklenmiş durumdadır. - Bu da programlamayı oldukça kolaylaştırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir (iterable) nesne demek bu nesne dolaşıldığında birtakım değerlerin elde edilmesi demektir. Yani dolaşılabilir nesneler - üzerinde dolaşım (iteration) işlemi yapılabilir. Dolaşım işlemi sırasında biz dolaşılabilir nesneden birtakım değerler elde ederiz. - Dolaşılabilir nesneler genellikle kendi içlerinde birtakım nesneleri tutarlar. Başka nesneleri tutan nesnelere Java, C# gibi fillerde - "collection", C++'da ise "container" denilmektedir. Örneğin str türü dolaşılabilir (iterable) bir türdür. Bu durumda str nesnesi de dolaşılabilir - bir nesnedir. Bir string nesnesi dolaşıldığında tek tek stirng'in karakterleri elde edilmektedir. (Tabii string'in karakterleri de - aslında birer string belirtmektedir.) Özetle: - - - Bir nesne dolaşılabilir ise o nesne dolaşıldığında bize nesneler vermektedir. - - Dolaşılabilir nesnelerin dolaşıldığında bize hangi nesneleri vereceği o dolaşılabilir nesne öğrenilirken öğrenilmelidir. - Örneğin string nesneleri dolaşıldığında yazının karakterleri elde edilmektedir. - - Dolaşım (iterator) nesneleri de aynı zamanda dolaşılabilir nesnelerdir. Dolaşılabilir nesnelerle dolaşım nesneleri arasında küçük - bir fark vardır. Bir dolaşılabilir nesne dolaşıldıktan sonra yeniden dolaşılabilir. Biz ondan yine aynı nesneleri elde ederiz. - Ancak bir dolaşım (iiterator) nesnesi bir kez dolaşıldıktan sonra artık biter. Biz onu bir daha dolaşmak istesek bir şey elde edemeyiz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listeler Python'da en çok kullanılan veri yapısıdır. Listeler diğer programlama dillerindeki "dizilere (arrays)" - benzetilebilir. Bir liste yaratmanın çeşitli yolları vardır. Liste yaratmak için en çok kullanılan yöntem köşeli parantez sentaksıdır. - Köşeli parantezler içerisine ',' atomu ile ayrlmış değerler girilirse bir liste nesnesi yaratılmaktadır. Örneğin: - - a = [10, 'ali', 2.3, True, 20] - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] -print(a) # [10, 20, 30, 40, 50] -print(type(a)) # - -#------------------------------------------------------------------------------------------------------------------------ - Liste elemanları aynı türden olmak zorunda değildir. (Halbuki C, C++, Java ve C# gibi bazı dillerde diziler aynı türden - elemanlara sahip olmak zorundadır.) -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 3.14, 'ankara', False] -print(a) # [1, 3.14, 'ankara', False] - -#------------------------------------------------------------------------------------------------------------------------ - Listenin belli bir elemanına [] operatörü ile erişebiliriz. Yani listelerin elemanları bu sayede sanki bağımsız nesnelermiş gibi - kullanılabilmektedir. Listenin ilk elemanı 0'ıncı indekslş elemanıdır. Bu durumda n elemanlı bir listenin son elemanı (n - 1)'nci - indekste olacaktır. Köşeli parantez içerisine bir ifade yerleştirilebilir. Örneğin a bir liste belirtmek üzere a[2], a[i + 2], - a[i + k - 1] gibi kullanımlar geçerlidir. Bu duurmda önce köşeli parantezin içerisindeki ifadenin sayısal değeri hesaplanır. - Sonra o değerdeki indekse erişilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -val = a[2] + 100 -print(val) # 130 - -#------------------------------------------------------------------------------------------------------------------------ - Köşeli parantez içerisindeki ifade int türden olma zorundadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -i = 1 - -val = a[i + 1] # köşeli parantez içerisindeki ifade int türden geçerli -print(val) - -i = 1.0 -val = a[i] # exception! köşeli parantez içerisindeki ifade int türden olmak zorunda - -i = 1 - -val = a[i + 2.] #exception! köşeli parantez içerisindeki ifade int türden olmak zorunda - -#------------------------------------------------------------------------------------------------------------------------ - Bir listenin olmayan bir elemanına erişmeye çalışırsak IndexError isimli bir exception oluşur. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -val = a[100] # IndexError oluşur - -#------------------------------------------------------------------------------------------------------------------------ - Listeler "değiştirilebilir (mutable)" türlerdir. Bir türün değiştirilebilir olması demek o türdne bir nesnenin içeriğinde - değişiklik yapılabilmesi demektir. Yani list türü değiştirilebilir olduğu iiçin biz bir listenin elemanlarını atama yoluyla değiştebiliriz. - Örneğin: - - a = [10, 20, 30, 40, 50] - print(a[0]) # 10 - a[0] = 'ali' - print(a[0]) # ali - print(a) # ['ali', 20, 30, 40, 50] - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -print(a) # [10, 20, 30, 40, 50] - -a[2] = 'ali' - -print(a) # [10, 20, 'ali', 40, 50] - -#------------------------------------------------------------------------------------------------------------------------ - Bir listedeki eleman sayısı built-in len isimli fonksiyonla elde edilebilir. len fonksiyonu bize listenin eleman sayısını - int bir değer olarak vermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -n = len(a) -print(n) # 5 - -#------------------------------------------------------------------------------------------------------------------------ - Boş liste de söz konusu olabilir. Boş bir liste 0 eleman uzunluğundadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [] - -n = len(a) -print(n) # 0 - -#------------------------------------------------------------------------------------------------------------------------ - Daha önceden de belirttiğimiz gibi T bir tür belirtmek üzere T(...) hem T türüne dönüştürme anlamına gelir hem de T türünden - nesne yaratma anlamına gelir. Örneğin int() ifadesinden içinde 0 olan bir int nesne, float() ifadesinden içinde 0 olan bir float nesne elde ederiz. - bool() ifadesi bize False bir nesne verir. İşte list() ifadesi de içi boş bir listeyi bize vermektedir. Yani aşağıdaki iki ifade eşdeğerdir: - - a = list() - a = [] - - Örneğin: - - >>> a = int() - >>> a - 0 - >>> b = float() - >>> b - 0.0 - >>> c = bool() - >>> c - False - >>> d = str() - >>> d - '' - >>> e = complex() - >>> e - 0j - >>> f = list() - >>> f - [] - -#------------------------------------------------------------------------------------------------------------------------ - -a = list() - -n = len(a) -print(n) # 0 - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfının tür fonksiyonu olan list fonksiyonuna biz dolaşılabilir bir nesne verebiliriz. Bu durumda bu nesne dolaşılır. - Dolaşım sonucunda elde edilen değerlerden liste oluşturulur. Örneğin str nesneleri dolaşılabilir nesnelerdir. Yani str sınıfı dolaşılabilir - bir sınıftır. Bir str nesnesi dolaşıldığında tek tek string'in karakterleri elde edilmektedir. O halde bir list fonksiyonuna - bir string nesnesi verirsek list o string'i dolaşıp o string'in karakterlerini elde edecek ve o karakterlerden liste oluşturacaktır. Örneğin: - - s = 'ankara' - a = list(s) - - Burada s string'i dolaşıldığında sırasıyla 'a', 'n', 'k', 'a', 'r', 'a' str nesneleri elde edilmektedir. İşte bu nesnelerden - liste oluşturulmuştur. - - int türü, float türü, bool türü, complex türü, NonType türü "dolaşılabilir (iterable)" türler değildir. - Bu nedenle biz list fonksiyonuna argüman olarak bu türden değerler veremeyiz. Eğer bunu yaparsak exception (TypeError) oluşur. - Örneğin: - - >>> s = 'ankara' - >>> a = list(s) - >>> a - ['a', 'n', 'k', 'a', 'r', 'a'] - >>> k = 123 - >>> a = list(k) - Traceback (most recent call last): - File "", line 1, in - TypeError: 'int' object is not iterable -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -a = list(s) -print(a) # ['a', 'n', 'k', 'a', 'r', 'a'] - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfı da dolaşılabilir bir sınıftır. Yani list nesneleri de dolaşılabilir nesnelerdir. Bir list nesnesi dolaşıldığında - tek tek liste içerisindeki değerler elde edilmektedir. Örneğin: - - a = [1, 2, 3, 4, 5] - b = list(a) - - Burada a listesi dolaşıldığında sırasıyla 1, 2, 3, 4, 5 değerleri elde edilecektir. İşte bu değerlerden yeni bir list nesnesi - oluşturulmuştur. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] -b = list(a) - -print(a, id(a)) -print(b, id(b)) - -#------------------------------------------------------------------------------------------------------------------------ - Bir list nesnesi elemanların kendilerini değil adreslerini tutmaktadır. Örneğin: - - a = [10, 'ali', 12.3] - - aslında burada bu listenin ilk elemanı (yani a[0] elemanı) içerisinde 10 değeri bulunan nesnenin adresini tutar. Diğer elemanlar da - bu bimimdedir. Örneğin: - - >>> a = [10, 'ali', 12.3] - >>> id(a[1]) - 1667401777200 - >>> val = a[1] - >>> id(val) - 1667401777200 - >>> print(a[1]) - ali - >>> print(val) - ali - - Biz listenin bir elemanını değiştirdiğimizde aslında yeni bir nesne yaratıp o nesnenin adresini o elemana yerleştiririz. Örneğin: - - >>> a = [10, 'ali', 12.3] - >>> id(a[0]) - 1667362351696 - >>> a[0] = 11 - >>> id(a[0]) - 1667362351728 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listelerin değiştirilebilir olması demek liste elemanlarına başka adreslerin atanabilmesi demektir. Örneğin: - - a = [1, 2, 3] - - Burada biz listenin 1'inci indisli elemanını değiştirelim: - - a[1] = 100 - - Burada biz içerisinde 2 değeri olan int nesneyi değiştirmedik. Listenin 1'inci indisli elemanındaki adresi değiştirdik. - Yani artık listenin 1'inci indisli elemanı içerisinde 100 olan başka bir int nesneyi göstermektedir. Örneğin: - - >>> a = [1, 2, 3] - >>> id(a[1]) - 1667362351440 - >>> a[1] = 100 - >>> id(a[1]) - 1667362543056 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir listenin bir elemanı başka bir liste olabilir. Örneğin: - - a = [10, [20, 30, 40], 50] - - Burada bu listenin 1'inci indisli elemanı başka bir list nesnesini göstermektedir. Bir listenin içerisindeki listenin elemanlarına - ikinci bir köşeli parantez ile erişebiliriz. Yani yukarıdaki örnekte a[1]'in türü list biçimindedir. O halde örneğin - a[1][2] gibi bir ifade ile biz 40 değerine erişiriz. Tabii aslında listenin 1'inci indisli elemanı diğer listenin adresini tutmaktadır. Örneğin: - - >>> a = [10, [20, 30, 40], 50] - >>> a[1][2] - 40 - >>> a[1][0] - 20 - -#------------------------------------------------------------------------------------------------------------------------ - Python'da matrissel bir veri yapısı listenin içerisinde liste ile oluşturulur. Bunun başka bir yolu yoktur. Örneğin biz - 3x3'lük bir matris oluşturmak isteyelim: - - a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - - Tabii matrisel bir listenin elemanları aynı uzunlukta listelerden oluşmak zorunda değildir. Örneğin: - - a = [[1, 2, 3, 4], [5, 6, 7], [8, 9]] -#------------------------------------------------------------------------------------------------------------------------ - -a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -print(a) # [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -val = a[2][1] -print(val) # 8 - -val = a[1][2] -print(val) # 6 - -#------------------------------------------------------------------------------------------------------------------------ - liste elemanlarına erişirken köşeli parantez içerisindeki değer negatif olabilir. Eğer köşeli parantez içerisindeki indeks belirten - ifade negatif ise bu özel bir anlam ifade etmektedir. Bu durumda efektif indeks (yani gerçek indeks) bu negatif değerle listenin - uzunluğu toplanarak elde edilir ve erişim bu efektif indekse yapılır. a[i] gibi bir erişimde i'nin negatif bir değerde olduğunu varsayalım. - Bu durumda a[i] ile a[i + len(a)] tamamen eşdeğerdir. Örneğin: - - a = [10, 20, 30, 40, 50] - - Burada a[-1] ile aslında a[-1 + len(a)] aynı anlamdadır. -1 + len(a) burada 4 değerini verir. O zaman a[-1] ifadesi aslında - listenin son elemanını belirtmektedir. O halde a[-2] ifadesi listenin sondan bir önceki elemanını belirtecektir. Yani negatif indeksler - sondan başa doğru listeyi indekslemektedir. n elemanlı bir listenin son negatif indeksinin -n olduğuna ancak son pozitif indeksinin - n - 1 olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -print(a[-1]) # 50 -print(a[-2]) # 40 -print(a[-3]) # 30 -print(a[-4]) # 20 -print(a[-5]) # 10 - -#------------------------------------------------------------------------------------------------------------------------ - Köşeli parantez içerisindeki negatif değer büyük olursa bu durumda efektif indeks gerçekten negatif olur. Yani bu durum dizide - olmayan bir elemana erişmek znlamına gelir. Böylesi durumlarda exception (IndexError) oluşmaktadır. Örneğin: - - a = [10, 20, 30, 40, 50] - - Burada köşeli parantez içerisine yazabileceğimiz mutlak değerce en büyük negatif değer -5'tir. Bundan daha küçük negatif değerler - exception oluşmasına (IndexError) yol açacaktır. Yani biz büyük negatif değerler vererek dizinin başından daha önceki yerlere erişemeyiz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -print(a[-10]) # exception oluşur! efektif indeks (yani gerçek indeks) -5 - -#------------------------------------------------------------------------------------------------------------------------ - Listenin elemanı liste olduğu durumda da negatif indeksler benzer biçimde kullanılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -val = a[-2][1] # 5 - -print(val) - -val = a[-2][-1] # 6 -print(val) - -#------------------------------------------------------------------------------------------------------------------------ - Listelerin negatif indekslenmesi onun son elemanlarına erişme işlemini kolaylaştırmaktadır. Yine bazı algoritmalarda - bu durum daha okunabilir ve kısa yazımlar sağlamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir liste içinde bir grup elemanı bir liste olarak elde edebiliriz. Bunun için "dilimleme (slicing)" denilen bir sentaks - kullanılmaktadır. Dilimleme sentaksının genel biçimi şöyledir: - - a[start:stop] - a[start:stop:step] - - Görüldüğü gibi dilimlemede step kısmı hiç belirtilmeyebilir. Dilimleme ile listenin start indeksli elemanı dahil olacak biçimde ancak - stop indeksli elemanı dahil olmayacak biçimde elemanları yine bir liste olarak elde ederiz. start, stop ve step int türden ifadeler olmak zorundadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[2:7] -print(b) # [30, 40, 50, 60, 70] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimleme sırasında start ya da stop indeks liste uzunluğundan büyük olursa liste uzunluğuna çekilir. Yani bu durum bir - exception oluşturmaz. Dilimleme sırasında start ile stop aynı değerdeyse ya da start stop değerinden büyükse boş liste elde - edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[3:4] -print(b) # [40] - -b = a[3:3] -print(b) # [] - -b = a[5:2] -print(b) # start stop ile aynı olursa ya da stop'tan büyük olursa boş liste elde edilir - -b = a[2:10] -print(b) # [30, 40, 50, 60, 70, 80, 90, 100] - -b = a[2:20] -print(b) # [30, 40, 50, 60, 70, 80, 90, 100] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimlemede start ve stop ifadelerinde negatif indeksler kullanılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[5:-2] -print(b) # [60, 70, 80] - -b = a[-5:-1] -print(b) # [60, 70, 80, 90] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimlemede start ve stop ifadelerinde negatif indeks girildikten sonra efektif indeks hesaplandığında (yani bu değer len(a) ile toplandığında) - eğer negatif çıkıyorsa 0 olarak işleme girer. Bu durumda bir exeption oluşmaz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[-20:3] # efektif indekx -10 ama 0 olarak ele alınacak -print(b) # [10, 20, 30] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimlemede start belirtilmezse start değerinin 0 olarak belirtildiği girildiği gelir, stop belirtilmezse stop değerinin listenin uzunlu biçiminde - girildiği anlamına gelir. Örneğin a[:n] ifadesi a[0:n] ile eşdeğerdir. a[n:] ifadesi ise a[n:len(a)] ile eşdeğerdir. Başka bir deyişle dilimlemde start - belirtilmezse "baştan itibaren", stop belirtilmezse "geri kalan hepsi" anlamına gelmektedir. Tabii start da stop da belirtilmeyebilir. - Bu durumda listenin tüm elemanlardna liste oluşturulur. Yani a[:] ifadesi a[0:len(a)] eşdeğerdir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[:7] # eğer start belirtilmezse 0 belirtilmiş gibi işlem yapılır. -print(b) # [10, 20, 30, 40, 50, 60, 70] - -b = a[2:] # stop değeri yazılmazsa listenin uzunluğu yazılmış gibi kabul edilir (yani geri kalan hepsi) -print(b) # [30, 40, 50, 60, 70, 80, 90, 100] - -b = a[:] # start ve stop yazılmazsa listenin hepsi elde edilir -print(b) # [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimlemede step atlama miktarını belirtir. step hiç belirtilmezse sanki 1 olarak belirtilmiş gibi kabul edilir. - Tabii elde edilecek değerlere hiçbir zaman stop dahil olmaz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[2:9:2] -print(b) # [30, 50, 70, 90] - -b = a[:8:3] -print(b) # [10, 40, 70] - -#------------------------------------------------------------------------------------------------------------------------ - step değeri negatif de olabilir. Ancak bu durumda ilerleme yönü ters olur. İlerleme ters olduğu için start indeksinin, - stop indeksinden daha büyük olması gerekir. Yine start indeksi dahil stop indeksi dahil değildir. step negatif ise - bu durumda start indeksin boş bırakılması "listenin uzunluğı - 1" anlamına stop indeksin boş bırakılması efektif -1 anlamına (yani ilk - eleman dahil olacak biçimde) gelmektedir. Örneğin a[:2:-1] ifadesinde ilerleme yönü terstir. start yazılmamıştır. Bu durumde sanki start - yerine len(a) - 1 yazılmış gibi işlem yapılır. Başka bir deyişle ifadenin eşdeğeri a[len(a) - 1:2:-1] biçimindedir. Örneğin a[4::-1] - ifadesinde ilerleme yönü yine terstir. stop belirtilmemiştir. Bu durumda sanki efektif -1 gibi baka bir deyişle atop inswka "-len(a) - 1" - gibi ele alınmaktadır. İfadenin eşdeğeri a[4:-len(a)-1:-1] biçimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[8:2:-1] # 8 dahil 2 dahil değil ters yönde hareket -print(b) # [90, 80, 70, 60, 50, 40] - -b = a[:2:-1] -print(b) # [100, 90, 80, 70, 60, 50, 40] - -b = a[4::-1] # 4'üncü indeksten başa kadar hepsi -print(b) # [50, 40, 30, 20, 10] - -#------------------------------------------------------------------------------------------------------------------------ - Bir listeyi tersyüz etmek için a[::-1] kalıbı kullanılmaktdır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -b = a[::-1] # listeyi ters olarak elde ederiz -print(b) # [100, 90, 80, 70, 60, 50, 40, 30, 20, 10] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimleme yoluyla liste elemanları güncellenebilir. Eğer step belirtilmezsa bu işlemin genel sentaksı şöyledir: - - a[start:stop] = - - Bu durumda önce dilimlenen elemanlar silinir, sonra onların yerine dolaşılabilir nesnedeki elemanlar start indeksten itibaren insert edilir. - Burada silinen eleman sayısı ile dolaşılabilir nesnedeki eleman sayısının aynı olması gerekmemektedir. (Burada atama operatörü yerine - Walrus operatörünü kullanamayız.) -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -a[2:6] = [1, 2, 3, 4, 5, 6, 7] -print(a) # [10, 20, 1, 2, 3, 4, 5, 6, 7, 70, 80, 90, 100] - -#------------------------------------------------------------------------------------------------------------------------ - String'lerin de dolaşılabilir nesneler olduğunu anımsayınız. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -a[2:6] = 'ankara' -print(a) # [10, 20, 'a', 'n', 'k', 'a', 'r', 'a', 70, 80, 90, 100] - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir nesnede hiç eleman yoksa bu durum bir silme anlamına gelecektir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -a[2:6] = [] -print(a) # [10, 20, 70, 80, 90, 100] - -#------------------------------------------------------------------------------------------------------------------------ - Dilimleme yoluyla liste elemanları güncellenirken eğer step belirtilirse bu durumda ona atanan dolaşılabilir - nesnenin eleman sayısı dilimleme yoluyla silinecek eleman sayısı ile aynı olmak zorundadır. (Python standard kütüphane dokümanlarında - step değerinin 1 olması durumunda da atanan dolaşılabilir nesnenin eleman sayısının dilimlemedne elde edilen eleman sayısı ile aynı olması gerektiği - dolaylı olarak belirtilmektedir. Ancak CPython gerçekleştirimi step değeri 1 ise sanki step değeri hiç belirtilmemiş gibi işlem yapmaktadır.) -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - -a[2:6:2] = [1, 2, 3] # exception oluşur! dilimlemeden 2 eleman elde edildi, ancak dolaşılabilir nesnede 3 eleman var - -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Dilimleme yoluyla atama yapılırken dilimleme sonucunda hiçbir eleman seçilmiyorsa bu durum start indeks'ten itibaren insert işlemi - anlamına gelmektedir. Örneğin: - - >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a[3:3] = 'ali' - >>> a - [10, 20, 30, 'a', 'l', 'i', 40, 50, 60, 70, 80, 90, 100] - >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a[4:3] = 'ali' - >>> a - [10, 20, 30, 40, 'a', 'l', 'i', 50, 60, 70, 80, 90, 100] -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listenin belli bir elemanına atama yapmak ile dilimleme yoluyla atama yapmak arasındaki farklılığa dikkat ediniz: - - >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a[0] = [1, 2, 3, 4, 5] - >>> a - [[1, 2, 3, 4, 5], 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a[0:1] = [1, 2, 3, 4, 5] - >>> a - [1, 2, 3, 4, 5, 20, 30, 40, 50, 60, 70, 80, 90, 100] - - Biz dilimleme yapmadan bir dolaşılabilir nesneyi belli bir indekse atadığımızda listenin o indeksinde dolaşılabilir nesne olur. - Halbuki dilimleme yoluyla atama yapıldığında dolaşılabilir nesnenin elemanları dilimlenen elemanların yerlerine insert edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dilimleme yoluyla güncelleme yapılırken step değeri negatif ise yine dolaşılabilir nesnedeki eleman sayısı dilimlenen - eleman sayısı kadar olmak zorundadır. Ancak elemanlar ters yönde insert edilecektir. Örneğin: - - >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a[7:2:-1] = [100, 200, 300, 400, 500] - >>> a - [10, 20, 30, 500, 400, 300, 200, 100, 90, 100] - >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] - >>> a[7:2:-2] = [100, 200, 300] - >>> a - [10, 20, 30, 300, 50, 200, 70, 100, 90, 100] -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listeyi dilimlediğimizde biz yeni bir list nesnesi oluştururuz. Ancak bu yeni oluşturulan list nesnesi aslında dilimlenen list nesnesindeki - dilimlenen elemanların adreslerinden oluşmaktadır. Yani dilimleme işlemi "sığ kopyalama (shallow copy)" biçiminde yapılmaktadır. - Sığ kopyalama demek yerni oluşturulan listenin elemanlarının dilimlene listenin dilimlenen elemanlarıyla aynı nesneleri göstermesi demektir. - Sığ kopyaalama sırasında dilimlenen listedeki adresler yeni oluşturulan listeye kopyalanmaktadır. Böylece dilimlenen listenin ilgili elemanlarıyla - yeni oluşturulan listenin elemanları aynı nesneleri gösterir hale gelmektedir. Aşağıdaki örneği inceleyiniz: - - >>> a = [10, 20, 30, 40, 50] - >>> b = a[2:4] - >>> id(a) - 1620015142592 - >>> id(b) - 1620015144768 - >>> id(a[2]) - 1619975761104 - >>> id(b[0]) - 1619975761104 - >>> id(a[3]) - 1619975761424 - >>> id(b[1]) - 1619975761424 - - Burada a[2] elemanın gösterdiği nesne ile b[0] elemanının gösterdiği nesnenin, a[3] elemanının gösteridği nesne ile b[1] elemanının - gösteridiği nesnenin aynı nesneler olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listeler değiştirilebilir (mutable) nesneler olduğuna göre listenin elemanı bir liste ise dilimlemede yeni oluşturulan listeye - bu eleman olan listenin adresi kopyalanacaktır. Bu durumda bu eleman olan listede yapılacak değişiklik her iki listede de - görünür olacaktır. Aşağıdaki örneğe dikkat ediniz: - - >>> a = [10, [20, 30, 40], 50] - >>> b = a[0:2] - >>> b[1][0] = 100 - >>> a - [10, [100, 30, 40], 50] - >>> b - [10, [100, 30, 40]] - >>> a[1][1] = 200 - >>> a - [10, [100, 200, 40], 50] - >>> b - [10, [100, 200, 40]] - - Bu örnekte id(a[1]) ile id(b[1])'in aynı olduğuna dikkat ediniz: - - >>> b = a[0:2] - >>> id(a[1]) - 2644868980608 - >>> id(b[1]) - 2644868980608 - >>> a[1] is b[1] - True -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi bir sınıf içerisindeki fonksiyonlara Python'da "metot (method)" denilmekteydi. Bir metodun aynı sınıf türünden - bir değişkenle "." operatörü kullanılarak çağrılması gerektiğini anımsayınız. Örneğin: - - a.foo() - - Burada a hangi sınıf türündense foo da o sınıfın bir metodudur. Metotlar belli bir nesne üzerinde işlem yapan fonksiyonlardır. Dolayısıyla - a.foo() ifadesinde foo metodu a nesnesi üzerinde (a değişkeninin gösterdiği nesne üzerinde) işlem yapmaktadır. Örneğin: - - x = [1, 2, 3, 4, 5] - - Burada x list sınıfı türünden bir değişkendir. foo list sınıfının bir metodu olsun: - - x.foo(...) - - Burada foo bu x nesnesi üzerinde onun elemanları ile ilgili bir işlem yapacaktır. eğer fonksiyonlar bir sınıf içerisinde değilse - belli bir nesne üzerinde işlem yapmak yerine genel işlemleri yapmak üzere yazılmış olurlar. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfının append isimli metodu list nesnesinin sonuna yeni bir eleman ekler. Örneğin: - - a = [1, 2, 3, 4, 5] # [1, 2, 3, 4, 5] - - print(a) - a.append('ankara') # [1, 2, 3, 4, 5, 'ankara'] - print(a) - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] -print(a) # [10, 20, 30] - -a.append(100) -print(a) # [10, 20, 30, 100] - -a.append('ali') -print(a) # [10, 20, 30, 100, 'ali'] - -a.append(12.4) -print(a) # [10, 20, 30, 100, 'ali', 12.4] - -#------------------------------------------------------------------------------------------------------------------------ - append her zaman tek bir nesnenin eklenmesine yol açmaktadır. Yani append ile biz listeye liste eklemek istesek listenin içerisindekiler - eklenmeyecektir. Listenin kendisi tek bir eleman olarak eklenecektir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] -print(a) # [10, 20, 30] - -a.append([100, 200, 300]) -print(a) # [10, 20, 30, [100, 200, 300]] - -#------------------------------------------------------------------------------------------------------------------------ - Bir listenin sonuna birden fazla eleman extend metoduyla eklenmektedir. extend metodu bizden dolaşılabilir bir nesne alır. - O nesneyi dolaşarak elde ettiği tüm nesneleri listeye ekler. Listelerin ve string'lerin dolaşılabilir nesneler olduğunu anımsayınız. - Eğer extend metoduna dolaşılabilir olmayan bir argüman gireresek exception (TypeError) oluşacaktır. extend metodu tek bir argüman almaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] -print(a) # [10, 20, 30] - -a.extend([1, 2, 3, 4, 5]) -print(a) # [10, 20, 30, 1, 2, 3, 4, 5] - -#------------------------------------------------------------------------------------------------------------------------ - str dolaşılabilir bir sınıf olduğuna göre bir string'in karakterlerini extend metodu ile listeye ekleyebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] -print(a) # [10, 20, 30] - -a.extend('ankara') -print(a) # [10, 20, 30, 'a', 'n', 'k', 'a', 'r', 'a'] - -#------------------------------------------------------------------------------------------------------------------------ - list sınfının index isimli metodu parametresiyle aldığı nesneyi listede arar. Eğer bulursa ilk bulduğu yerin indeks numarasıyla - geri döner. Eğer bulamazsa exception (ValueError) oluşmaktadır. (Dolayısıyla bu metot genellikle zaten var olduğunu bildiğimiz bir elemanın yerini - aramak için kullanılmaktadır.) Bu metot bize int türünden index belirten bir değer vermektedir. Örneğin: - - >>> a = [10, 2, 7, 8, 19, 41] - >>> result = a.index(19) - >>> result - 4 - >>> result = a.index(30) - Traceback (most recent call last): - File "", line 1, in - ValueError: 30 is not in list - - İleride ayrı bir konuda ele alınacak olsa da şimdiden bir noktayı belirtmek istiyoruz: Python'da farklı sınıflar türünden (int, float ve bool - haricinde) değişkenler == ve != operatörleriyle karşılaştırıldığında == işlemi her zaman False, != işlemi her zaman True vermektedir. Örneğin: - - >>> 'ali' == 19 - False - - DOlayısıyla index metoduyla arama yapılırken liste içerisinde farklı türlerden nesneler varsa bunlar asla aradığımız değere - eşit olamayacağı için geçilmektedir. index metodunun her eleman için == karşılaştırması yaptığını düşünebilirsiniz. Örneğin: - - >>> a = [10, 5, 'ali', 43, 21] - >>> result = a.index(43) - >>> result - 3 - >>> result = a.index('ali') - >>> result - 2 - - Eğer değer liste içerisinde birden fazla yerde varsa list metodu değerin listede ilk bulunduğu indeksi vermektedir. Örneğin: - - >>> a = [1, 4, 7, 8, 4, 7] - >>> result = a.index(4) - >>> result - 1 - - index metodu asında iki argüman ve üç argüman da alabilir. Eğer metodu biz iki argümanlı kullanırsak birinci argüman aranacak - değeri, ikinci argüman ise aramanın başlatılacağı indeksi belirtir. Örneğin: - - >>> a = [10, 4, 5, 8, 9, 4, 8] - >>> result = a.index(4, 2) - >>> result - 5 - - Burada 4 değeri aranmıştır. Ancak arama baştan itibaren değil 2'inci indeksten itibaren başlatılmıştır. Eğer index üç argümanla - kullanılırsa üçüncü argüman aramanın bitirileceği indeksi belirtir. Ancak bu indeks aramaya dahil değildir. Örneğin: - - >>> a = [10, 4, 5, 8, 9, 4, 8] - >>> result = a.index(4, 2, 5) - Traceback (most recent call last): - File "", line 1, in - ValueError: 4 is not in list - - Burada 4 değeri aranmak istenmiştir. Ancak arama 2'inci indeksten başlatılıp 5'inci indekse kadar devam ettirilmiştir. 5'indeks - aramaya dahil olmadığı için exception oluşmuştur. Burada aramanın başlatılacağı ve bitirileceği indeks negatif indeks olarak belirtilebilir. - -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 3, 5, 'ali', 'veli', 7, 'veli'] - -result = a.index('veli') -print(result) # 4 - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfının count isimli metodu bizden bir değer alır. Liste içerisinde o değerden kaç tane olduğunu bize verir. Örneğin: - - >>> a = [1, 3, 7, 4, 3, 8, 3, 4, 3] - >>> result = a.count(3) - >>> result - 4 - >>> result = a.count(10) - >>> result - 0 - >>> result = a.count('ali') - >>> result - 0 -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 5, 7, 7, 4, 7, 9, 7] - -result = a.count(7) -print(result) # 4 - -result = a.count(5) -print(result) # 1 - -result = a.count(70) -print(result) # 0 - -#------------------------------------------------------------------------------------------------------------------------ - Bir listeden tek bir elemanı silmek için pop isimli metot kullanılabilir. Bu metot argümansız kullanılırsa son eleman silinir. - Argümanlı kullanılırsa belirtilen indeksteki eleman silinir. Yani metot argümanlı ya da argümansız kullanılabilmektedir. - Argümanlı kullanılacaksa argüman index belirtmelidir. pop silinen elemanın kendisini de bize geri dönüş değeri olarak vermektedir. - pop metoduna verilen index sınır dışındaysa ya da liste boşsa exception (IndexError) oluşmaktadır. index metodu negatif indeksleri - kabul etmektedir. Bu durumda negatif değerler yine list uzunluğu ile toplanıp efektif indeks elde edilmektedir. Yani örneğin a.pop(-2) - işlemi son elemandan bir önceki elenı siler. Örneğin: - - >>> a = [10, 20, 30, 40, 50] - >>> result = a.pop() - >>> result - 50 - >>> a - [10, 20, 30, 40] - >>> result = a.pop(2) - >>> result - 30 - >>> a - [10, 20, 40] - >>> result = a.pop(-2) - >>> result - 20 - >>> a - [10, 40] - >>> a.pop(10) - Traceback (most recent call last): - File "", line 1, in - IndexError: pop index out of range - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] -print(a) # [10, 20, 30, 40, 50] - -a.pop() - -print(a) # [10, 20, 30, 40] - -a = [10, 20, 30, 40, 50] -a.pop(2) - -print(a) # [10, 20, 40, 50] - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfının remove metodu da silme yapar. Ancak silinecek eleman pop metodunda olduğu gibi indeks numarasıyla değil - bizzat değeriyle belirtilmektedir. remove parametresiyle belirtilen değer, liste içerisinde arar. Eğer bulursa yalnızca ilk - bulduğunu siler, eğer bulamazsa exception (ValueError) oluşur. remove metodu bize herhangi bir geri dönüş değeri vermez. - Örneğin: - - >>> a = [3, 7, 9, 'ali', 3] - >>> a.remove('ali') - >>> a - [3, 7, 9, 3] - >>> a.remove(3) - >>> a - [7, 9, 3] - >>> a.remove('veli') - Traceback (most recent call last): - File "", line 1, in - ValueError: list.remove(x): x not in list - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 40] -print(a) # [10, 20, 30, 40, 50, 40] - -a.remove(40) -print(a) # [10, 20, 30, 50, 40] - -#------------------------------------------------------------------------------------------------------------------------ - clear isimli metot listenin tüm elemanlarını siler. Yani liste 0 elemanlı boş bir liste haline gelir. clear metodunun - parametresi yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] -print(a) # [10, 20, 30, 40, 50] - -a.clear() -print(a) # [] - -#------------------------------------------------------------------------------------------------------------------------ - reverse metodu listeyi ters yüz eder. Bu metot bize bir geri dönüş değeri vermez. Ters yüz etme işlemi nesnenin üzerinde (in place) - yapılmaktadır. a bir liste belirtmek üzere a[::-1] işlemi de listeyi ters yüz eder. Ancak bu işlem ters yüz edilmiş yeni bir liste - vermektedir. Bu işlem sonucunda a listesinde bir değişiklik olmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] -print(a) # [10, 20, 30, 40, 50] - -b = a[::-1] # [10, 20, 30, 40, 50] -print(a) -print(b) # [50, 40, 30, 20, 10] - -a.reverse() -print(a) # [50, 40, 30, 20, 10] - -#------------------------------------------------------------------------------------------------------------------------ - Bir veri yapısının (list, dict vs.) bir metodu işlemi veri yapısının üzerinde yapıyorsa buna "in place" işlem denilmektedir. - Tabii her metot işlemini in place yapmayabilir. Metot işlem yapılmış yeni bir veri yapısını bize verebilir. Bu "in place" bir işlem değildir. - Örneğin a.reverse() ile biz "in place" bir işlem yapmış olduk. Ancak a[::-1] işlemi "in place" bir işlem değildir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfının sort isimli metodu liste elemanlarını sıraya dizmektedir. Default durum küçükten byüğe sıraya dizmedir. - sort bize bir değer vermez. Bizzat elemanları sıraya dizer (in place işlem). sort metodu "stable" sort yapmaktadır. - Stable sort aynı elemanların sort edilmiş listede orijinal listedeki sırasına göre yan yana sıraya dizilmesi anlamına gelmektedir. - Örneğin: - - >>> a = [4, 17, 2, 21, 8, -4] - >>> a.sort() - >>> a - [-4, 2, 4, 8, 17, 21] -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 6, 2, 8, 16, -4, 9, 22] - -a.sort() -print(a) # [-4, 2, 3, 6, 8, 9, 16, 22] - -#------------------------------------------------------------------------------------------------------------------------ - sort metodu sıraya dizme işleminde listenin elemanları arasında < karşılaştırması yapmaktadır. Eğer listenin herhangi iki - elemanı arasında < karşılaştırması yapılamazsa bu durumda exception (TypeError) oluşur. Örneğin listenin bir elemanı str bir elemanı int - türden olsun. Bu iki eleman < operatöryüyle karşılaştırılamaz. Bu durumda sort işlemi exception'a yol açacaktır. Tabii - string'ler kendi aralarında < operatörü ile karşılaştırılabilmektedir: - - >>> a = ['veli', 'ali', 'sacit', 'ayşe', 'talat'] - >>> a.sort() - >>> a - ['ali', 'ayşe', 'sacit', 'talat', 'veli'] -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 6, 'ali', 8, 16.2, -4, True, 'veli'] - -a.sort() # exception oluşur! - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi string'ler kendi aralarında karşılaştırılabilmektedir. Dolayısıyla string'lerden - oluşmuş bir liste sort edilebilr. -#------------------------------------------------------------------------------------------------------------------------ - -a = ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] -print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] - -a.sort() -print(a) # ['adana', 'izmir', 'kayseri', 'samsun', 'sivas'] - -#------------------------------------------------------------------------------------------------------------------------ - sort metodu default durumda listeyi küçükten büyüğe (ascending) sıraya dizmektedir. Listeyi büyükten küçüğe (descending) - sıraya dizmek için reverse=True isimli parametresinin kullanılması gerekir. (Yani a.sort() ile a.sort(reverse=False) aynı - anlamdadır.) -#------------------------------------------------------------------------------------------------------------------------ - -a = ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] -print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] - -a.sort(reverse=True) # büyükten küçüğe -print(a) # ['sivas', 'samsun', 'kayseri', 'izmir', 'adana'] - -a.sort() -print(a) # ['adana', 'izmir', 'kayseri', 'samsun', 'sivas'] - -#------------------------------------------------------------------------------------------------------------------------ - list sınıfının reverse metodunun yanı sıra ayrıca reversed isimli bir built-in bir fonksiyon (metot değil) da vardır. - Bu reversed fonksiyonu bizden dolaşılabilir bir nesne alır. Onun ters yüz edilmiş halini bize dolaşılabilir (iterable) - bir nesne biçiminde verir. reversed fonksiyonu "in place" bir işlem yapmaz. reversed fonksiyonu bize bir liste değil dolaşılabilir - bir nesne vermektedir. Yani biz ters yüz edilmiş değerleri o dolaşılabilir nesneyi dolaşarak elde edebiliriz. list fonksiyonunun - dolaşılabilir nesneyi dolaşarak bir liste yaptığını anımsayınız. Örneğin: - - >>> a = [1, 2, 3, 4, 5] - >>> result = reversed(a) - >>> type(result) - - >>> b = list(result) - >>> b - [5, 4, 3, 2, 1] - >>> a - [1, 2, 3, 4, 5] - - reversed fonksiyonu argüman olarak yalnızca liste almaz. Aslında dolaşılabilir herhangi bir nesneyi argüman alabilmektedir. - Örneğin: - - >>> s = 'ankara' - >>> result = reversed(s) - >>> type(result) - - >>> b = list(result) - >>> b - ['a', 'r', 'a', 'k', 'n', 'a'] - -#------------------------------------------------------------------------------------------------------------------------ - -a = ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] -print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] - -b = reversed(a) -c = list(b) - -print(c) # ['sivas', 'kayseri', 'samsun', 'adana', 'izmir'] -print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] - -#------------------------------------------------------------------------------------------------------------------------ - Aslında biz reversed fonksiyonunu her "dolaşılabilir" nesneyle kullanamayız. reversed fonksiyonu "tersten dolaşılabilir (reverse iterable)" - nesneler ile kullanılabilmektedir. Bu bağlamda listeler, stringler aynı zamanda tersten dolaşılabilir biçimdedir. Bu nedenle biz - reverse fonksiyonunu listeler ve string'lerle kullanabilmekteyiz. -#------------------------------------------------------------------------------------------------------------------------ - -s = list(reversed('ankara')) -print(s) # ['a', 'r', 'a', 'k', 'n', 'a'] - -#------------------------------------------------------------------------------------------------------------------------ - sort işlemi için sorted isimli global built-in bir fonksiyon da bulunmaktadır. Bu fonksiyon bize her zaman sort edilmiş yeni bir liste vermektedir. - Fonksiyon dolaşılabilir herhangi bir nesneyi parametre olarak alabilmektedir. Örneğin: - - >>> a = [5, 4, 3, 2, 1] - >>> b = sorted(a) - >>> a - [5, 4, 3, 2, 1] - >>> b - [1, 2, 3, 4, 5] - >>> a = sorted('ankara') - >>> a - ['a', 'a', 'a', 'k', 'n', 'r'] -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 6, 1, 34, 51, 23, 10] -print(a) # [3, 6, 1, 34, 51, 23, 10] - -b = sorted(a) -print(b) # [1, 3, 6, 10, 23, 34, 51] - -a = 'ankara' -b = sorted(a) -print(b) # ['a', 'a', 'a', 'k', 'n', 'r'] - -#------------------------------------------------------------------------------------------------------------------------ - Python'da pek çok veri yapısı ile kullanılabilen "in" isimli iki operandlı araek özel amaçlı bir operatör bulunmaktadır. - in operatörünün sol tarafındaki operand olup olmadığı kontrol edilecek değeri belirtir. Sağ tarafındaki operand ise arama - yapılacak nesneyi belirtir. Bu operatör eğer sol tarafındaki operand ile belirtilen değer sağ tarafındaki operand ile belirtilen - veri yapısında var ise True değer, yok ise False üretmektedir. Yani in operatörü bool bir değer üretmektedir. Örneğin: - - >>> a = [10, 4, 7, 'ali', 5] - >>> 5 in a - True - >>> 8 in a - False - >>> s = 'istanbul' - >>> 'i' in s - True - >>> 'k' in s - False -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 6, 1, 34, 51, 23, 10] - -result = 34 in a # 34 değeri a içinde var mı? -print(result) # True - -result = 'ali' in a # 'ali' değeri a içinde var mı? -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - Biz bir liste içerisinde başka bir liste var mı diye in operatörü ile bakabiliriz. in operatörü == karşıalştırması yapmaktadır. - İki listenin bu biçimde karşılaştırılması ileride ele alınacaktır. Örneğin: - - >>> a = [1, 2, [3, 4], 5, 6] - >>> [3, 4] in a - True - >>> [4, 3] in a - -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 6, 1, [34, 51], 23, 10] - -result = 34 in a -print(result) # False - -result = [34, 51] in a -print(result) # True - -result = [51, 34] in a -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - not in operatörü ise bir elemanın bir listede olmadığını sorgulamak için kullanılmaktadır. Yani in operatörünün tersini yapmaktadır. - Örneğin: - - >>> a = [1, 2, 3, 4, 5] - >>> 5 in a - True - >>> 5 not in a - False -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -result = 30 not in a -print(result) # False - -result = 15 not in a -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - Veri yapılarından eleman silmek için genel amaçlı "del" isiminde bir deyim de kullanılmaktadır. - del deyimi yalnızca listelerde değil "değiştirilebilir (mutable)" başka nesnelerde de kullanılabilmektedir. del deyimi - listelerle kullanılırken listenin belli bir elemanı köşeli parantezler ile belirtilir. Örneğin del a[3] gibi. Örneğin: - - >>> a = [10, 20, 30, 40, 50] - >>> del a[2] - >>> a - [10, 20, 40, 50] - - Bu örnekte biz aynı işlemi pop metoduyla da yapabilirdik. Yine biz del deyimi ile listenin olmayan bir elemnanını silmek istersek - del deyimi exception (IndexError) oluşturur. Örneğin: - - >>> a = [10, 20, 30, 40, 50] - >>> del a[30] - Traceback (most recent call last): - File "", line 1, in - IndexError: list assignment index out of range -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] -print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] - -del a[0] -print(a) # [3, 'veli', 45, 73, 'ali', 21, 16, 7] - -#------------------------------------------------------------------------------------------------------------------------ - del deyimi ile listelerde silme yapılırken silinecek elemanlar dilimleme ile de belirtilebilmektedir. Bu durumda önce dilimlenen - elemanlar belirlenir sonra hepsi tek hamlede silinir. Örneğin: - - >>> a = [10, 20, 30, 40, 50, 60, 70] - >>> del a[1:6:2] - >>> a - [10, 30, 50, 70] - -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] -print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] - -del a[::2] -print(a) # [3, 45, 'ali', 16] - -#------------------------------------------------------------------------------------------------------------------------ - del deyimi ile ',' atomuyla ayrılmış birden fazla silme işlemi yapılabilir. Bu durumda silme ayrı ayrı soldan sağa biçimde yapılmaktadır. - Örneğin: - - >>> a = [10, 20, 30, 40, 50, 60, 70] - >>> del a[2], a[3] - >>> a - [10, 20, 40, 60, 70] - - Burada önce 2'inci indeksli eleman silinmiştir. Bu durumda 3'üncü indeksli eleman artık başlangıçtaki 4'üncü indeksli eleman - durumuna gelmiştir. Halbuki dilimleme yoluyla eleman silerken önce elemanlar belirlenmekte sonra hepsi tek hamlede silinmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] -b = [4, 8, 2] -print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] - - -del a[3], b[0] -print(a) # ['ali', 3, 'veli', 73, 'ali', 21, 16, 7] -print(b) # [8, 2] - -#------------------------------------------------------------------------------------------------------------------------ - Aynı liste üzerinde silme yapılırken ilk silmeden sonra elemanların indeks numaralarının değişeceğine dikkat ediniz. - Yani: - - del a[i], a[k] - - işlemi aşağıdaki ile eşdeğerdir: - - del a[i] - del a[k] -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] - -print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] -del a[0], a[1] -print(a) # [3, 45, 73, 'ali', 21, 16, 7] - -#------------------------------------------------------------------------------------------------------------------------ - Python'da iki liste nesnesi + operatörü ile toplanabilir. Ancak çıkartılamaz, çarpılamaz ve bölünemez. İki liste toplandığında - bu işlemden yeni bir liste edilmektedir. Bu yeni listenin elemanları + operatörünün solundaki liste elemanlarına sağındaki liste elemanlarının - eklenmesiyle oluşturulmuş bir liste olur. a + b işleminde önce len(a) + len(b) kadar uzunlukta yeni bir liste yaratılır. Bu listeye - önce a listesindeki adresler, sonra b listesindeki adresler eklenir. Böylece c listesi aslında a listesindeki adreslerden ve - b listesindeki adreslerden oluşuyor durumda olur. Bu da bir çeşit sığ kopyalama anlamına gelmekteir. - - Aşağıdaki örnekte aslında id(b[2]) ile id(c[5]) aynı değerleri vermektedir. Çünkü bunlar aslında aynı nesneyi gösterirler. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 'ali', 20] -b = [30, 'veli', 40] - -c = a + b -print(c) # [10, 'ali', 20, 30, 'veli', 40] - -print(id(b[2])) -print(id(c[5])) # b[2] ile c[5] aynı nesnesyi gösteriyorlar - -#------------------------------------------------------------------------------------------------------------------------ - İki list nesnesi toplanırken oluşturulan liste toplanan iki list nesnesinin tuttukları adreslerden oluştuğuna göre list nesnesinin - bir elemanu "değiştirilebilir (mutable)" bir nesne ise orada yapılan değişiklik dolaylı biçimde toplama işlemiyle elde edilen nesnede de - gözükecektir. Aşağıdaki örneği izleyiniz: - - >>> a = [1, 2, [3, 4]] - >>> b = [10, 20] - >>> c = a + b - >>> c - [1, 2, [3, 4], 10, 20] - >>> a[2][0] = 100 - >>> c - [1, 2, [100, 4], 10, 20] - - Aşağıda da benzer bir örnek verilmiştir. Burada b listesinin 1'inci indisli elemanı başka bir listenin adresini tutmaktadır. - Toplama sonucunda c listesinin 4'üncü indisli elemanı da aynı listeyi gösteriyor durumda olur. Listeler değiştirilebilir (mutable) - olduğuna göre biz hem b hem de c tarafından gösterilen bu listeyi her iki liste yoluyla değiştirebiliriz. Örneğin: -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] -b = [40, [50, 60], 70] - -c = a + b -print(c) # [10, 20, 30, 40, [50, 60], 70] - -c[4][0] = 100 -print(b) # [40, [100, 60], 70] - -#------------------------------------------------------------------------------------------------------------------------ - İki listeyi toplarken yeni oluşturulan liste her zaman iki listenin uzunlukları toplamı kadar uzunluğa sahip olur. -#------------------------------------------------------------------------------------------------------------------------ - -a = [[10, 20, 30]] -b = [[40, 50, 60]] - -c = a + b - -print(c) # [[10, 20, 30], [40, 50, 60]] - -#------------------------------------------------------------------------------------------------------------------------ - İki listenin toplanması durumunda listelerden biri ya da her ikisi boş liste olsa bile yine de toplama işlemi sonucunda - yeni bir liste yaratılmaktadır. Tabii genel olarak bir programda "gözlemlenebilir bir yan etki (observable side effects)" oluşmadıktan - sonra derleyiciler ve yorumlayıcılar kodu daha hızlı çalışacak biçimde ya da daha az yer kaplayacak biçimde yeniden düzenleyebilirler. - Yani aşağıdaki örnekte aslında yorumlayıcı bu toplama işlemini hiç yapmayabilir. Çünkü yorumlayıcının bu toplama işlemini yapıp - yapmadığı program içerisinde anlaşılamamaktadır. Başka bir deyişle yorumlayıcı bu toplama işlemini yapsa da yapmasa da biz - bunun yapılıp yapılmadığını gözlemleyemeyiz: - - c = [10, 20, 30] + [] - - print(c) - - Benzer biçimde aşağıdaki örnekte de yorumlayıcı önce iki listeyi oluşturup sonra onları toplamak yerine doğrudan - toplama ilişkin listeyi oluşturabilir. Yorumlayıcı bunu yaptiığında bzim kodumuz bundan hiçbir biçimde etkilenmeyecektir: - - a = [1, 2, 3] + [4, 5] - - Burada belki de yorumlayıcı doğurdan [1, 2, 3, 4, 5] elemanlarına sahip bir liste oluşturup bu listenin adresini a'ya - atamıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi a += b işlemi a = a + b anlamına gelmektedir. Ancak listelerde bu eşdeğerlik söz konusu değildir. a ve b birer liste - olmak üzere a += b aslında b listesinin lemanlarının mevcut a listesinin sonuna eklenmesi anlamına gelmektedir. Dolayısıyla a += b aslında - listelerde adeta a.extend(b) gibi bir etki yaratmaktadır. Örneğin: - - >>> a = [1, 2, 3] - >>> b = [4, 5] - >>> id(a) - 1570379741824 - >>> a = a + b - >>> a - [1, 2, 3, 4, 5] - >>> id(a) - 1570379772992 - >>> a = [1, 2, 3] - >>> b = [3, 4] - >>> id(a) - 1570379782464 - >>> a += b - >>> a - [1, 2, 3, 3, 4] - >>> id(a) - 1570379782464 - - Burada listeler için a = a + b işlemi ile a += b işleminin aynı olmadığını görmekteyiz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] -b = [40, 50] - -print(id(a)) -a = a + b -print(a) -print(id(a)) # a'nın id'si değişiyor, çünkü a artık yeni bir listeyi gösteriyor - -a = [10, 20, 30] -print(id(a)) - -a += b -print(a) -print(id(a)) # a'nın id'si değişmiyor. Çünkü ekleme a'ya yapılıyor - -#------------------------------------------------------------------------------------------------------------------------ - Python dokümanlarına göre (Python Library Reference) a list türünden olmak üzere a.extend(b) ile a += b tamamen eşdeğerdir. - extend metodunda metodun parametresinin herhangi bir dolaşılabilir nesne olabileceğini belirtmiştik. Aynı durum a += b operatöründe de - geçerlidir. Bu işlemde a list türündense b aslında dolaşılabilir herhangi bir nesne belirtebilir. Örneğin: - - >>> a = [1, 2, 3] - >>> a.extend('ali') - >>> a - [1, 2, 3, 'a', 'l', 'i'] - >>> a = [1, 2, 3] - >>> a += 'ali' - >>> a - [1, 2, 3, 'a', 'l', 'i'] - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir liste int bir değerle çarpılabilir. (İki liste çarpılamaz, bölünemez ve çıkartılamaz. Ayrıca bir liste int bir toplanamaz ve bölünemez.) - Bu işleme Python'da "yineleme (repitition)" denilmektedir. a bir liste, n de int bir değer belirtmek üzere a * n ya da n * a tamamen - n defa a yı kendisiyle toplamaya eşdeğerdir. Örneğin a * 3 tamamen a + a + a anlamına gelmektedir. Yani a içerisindeki değerler üç kere yinelenmektedir. - Listeler toplanırken oluşturulan yeni listeye operand olarak kullanılan listelerdekş adreslerin kopyalandığını anımsayınız. Örneğin: - - >>> a = [1, 2, 3] - >>> b = a * 2 - >>> a - [1, 2, 3] - >>> b - [1, 2, 3, 1, 2, 3] - >>> id(a[0]) - 1570340235568 - >>> id(b[0]) - 1570340235568 - >>> id(b[3]) - 1570340235568 - >>> b = a + a - >>> b - [1, 2, 3, 1, 2, 3] - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] - -b = a * 3 # eşdeğeri a + a + a -print(b) # [10, 20, 30, 10, 20, 30, 10, 20, 30] - -b = 3 * a -print(b) # [10, 20, 30, 10, 20, 30, 10, 20, 30] - -b = [0] * 10 -print(b) # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - -#------------------------------------------------------------------------------------------------------------------------ - a * 3 gibi bir ifade a + a + a anlamına geldiğine göre aslında burada yeni yaratılacak listenin elemanları tekrarlı bir biçimde - a listesinin elemanlarındaki adresleri tutacaktır. Yani yineleme işlemi aslında yinelen listenin (örneğimzde a) adreslerinin yinelenmesiyle - oluşturulmaktadır. Sonuçta bir "sığ kopyalama (shallow copy)" durumu oluşur. Yani yineleme liste elemanlarının gösterdiği nesnelerin kopyasını oluşturmamaktadır. Örneğin: - - a = [10, 20] - b = a * 3 - - gibi bir işlemde b listesinin elemanları üç kere a listesinin elemanlarındaki adreslerden oluşmaktadır. Örneğin: - - >>> a = [10, 20] - >>> b = a * 3 - >>> id(a[0]) - 1672940513872 - >>> id(a[1]) - 1672940514192 - >>> id(b[0]) - 1672940513872 - >>> id(b[1]) - 1672940514192 - >>> id(b[2]) - 1672940513872 - >>> id(b[3]) - 1672940514192 - >>> id(b[4]) - 1672940513872 - >>> id(b[5]) - 1672940514192 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yineleme eişlemi bazı durumlarda çok pratik olanaklar sağlamaktadır. Örneğin biz her elemanı 0 olan 100 elemanlık bir listeyi - kolay bir biçimde yineleme ile oluşturabiliriz: - - >>> a = [0] * 100 - - Burada a 100 elemanlı bir listedir ve bu listenin her elemanında 0 vardır. Tabii aslında a listesinin her elemanı içerisinde - 0 olan aynı int nesnenin adresini tutuyor durumdadır: - - >>> id(a[0]) - 1570340235536 - >>> id(a[1]) - 1570340235536 - >>> id(a[99]) - 1570340235536 - - Bu durum bir sorun oluşturmaz. Nasıl olsa int "değiştirilemez (immutable)" bir türdür. Biz listeninin bir elemanına değer - atadığımızda diğer elemanların değeri değişmeyecektir. Örneğin: - - >>> a[0] = 100 - >>> id(a[0]) - 1570340427216 - >>> id(a[1]) - 1570340235536 - >>> id(a[2]) - 1570340235536 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yineleme işleminde çarpılan değer 0 ya da negatif bir değerse yineleme işleminden boş bir liste elde edilmektedir. Örneğin: - - >>> a = [1, 2, 3] - >>> a * 0 - [] - >>> a * -3 - [] -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz Python'da ne zaman bir köşeli parantez açsak yeni bir liste yaratılmaktadır. Örneğin: - - a = [[1, 2], [1, 2], [1, 2]] - - Burada a listesinin elemanları farklı listeleri göstermektedir: - - >>> a = [[1, 2], [1, 2], [1, 2]] - >>> id(a[0]) - 1570379740736 - >>> id(a[1]) - 1570379741760 - >>> id(a[2]) - 1570379739776 - - Ancak aşağıdaki gibi bir listenin her elemanının aynı listeyi göstermesini de saplayabiliriz: - - a = [1, 2] - b = [a, a, a] - - Burada b listesinin her elemanı a'yı göstermektedir: - - >>> a = [1, 2] - >>> b = [a, a, a] - >>> id(b[0]) - 1570379735488 - >>> id(b[1]) - 1570379735488 - >>> id(b[2]) - 1570379735488 - - O halde burada biz a listesi üzerinde değişilik yaparsak bu durumda b'nin elemanları aynı a listesini gösterdiğine göre sanki - onlar üzerinde de eğişiklik yapılmış gibi bir etki oluşacaktır. Örneğin: - - >>> a[1] = 100 - >>> b - [[1, 100], [1, 100], [1, 100]] - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki gibi boş bir listemiz olsun: - - a = [] - - Şimdi biz aşağıdaki gibi bir liste oluşturalım: - - b = [a, a, a] - - Burada b'nin ilk üç elemanına aslında a listesinin adresi yerleştirilmiştir. Dolayısıyla aslında b listesinin elemanlarının hepsi - aynı a listesini göstermektedir. Şimdi aşağıdaki gibi bir işlem uygulayalım: - - b[0].append(100) - - biz aslında bu 100 değerini a listesine eklemiş olduk o zaman b'yi yazdırdığımızda şöyle bir görüntü ile karşılaşırız: - - [[100], [100], [100]] - - Yani biz bir listenin elemanına bir değer yerleştirdiğimzde aslında o değeri liste elemanına yerleştirmeyiz. O elemanın belirttiği nesnenin - adresini liste elemanına yerleştirmiş oluruz. - - >>> a = [] - >>> id(a) - 1672976339968 - >>> b = [a, a, a] - >>> id(b[0]) - 1672976339968 - >>> id(b[1]) - 1672976339968 - >>> id(b[2]) - 1672976339968 - >>> b[0].append(100) - >>> a - [100] - >>> b - [[100], [100], [100]] - >>> a.append(200) - >>> b - [[100, 200], [100, 200], [100, 200]] - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki koda dikkat ediniz: - - a = [[], [], []] - - Burada her köşeli parantez yeni bir listenin yaratılmasına yol açtığı için a'nın elemanları farklı boş listeleri gösterecektir. - Ancak örneğin: - - a = [[]] * 3 - - Burada yinelemeden dolayı oluşturulan a listesinin elemanları aynı boş listeyi gösterecektir. Bunu ispatlayalım: - - >>> a = [[], [], []] - >>> id(a[0]) - 1570379711424 - >>> id(a[1]) - 1570379736832 - >>> id(a[2]) - 1570379738880 - >>> a = [[]] * 3 - >>> id(a[0]) - 1570379700800 - >>> id(a[1]) - 1570379700800 - >>> id(a[2]) - 1570379700800 - -#------------------------------------------------------------------------------------------------------------------------ - -a = [[]] -b = a * 3 -print(b) - -b[0].append(100) -print(b) # [[100], [100], [100]] - -b[1].append(200) -print(b) # [[100, 200], [100, 200], [100, 200]] - -#------------------------------------------------------------------------------------------------------------------------ - * operatörü soldan sağa öncelikli olduğuna göre a bir liste olmak üzere aşağıdaki ifade de geçerlidir: - - b = a * 2 * 3 - - Burada önce a * 2 işlemi yapılacak buradan bir liste elde edilecek sonra elde edilen bu liste 3 ile çarpılacaktır. Yani sonuçta - a'nın elemanlarından 6 kez oluşturulmuş olacaktır. Örneğin: - - >>> a = [1, 2, 3] - >>> b = a * 2 * 3 - >>> b - [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] - - Dolayısıyla Python'da çarpmanın değişme özelliği ve birleşme özelliği muhafaza edilmiştir. Örneğin bu işlem aşağıdakiyl eşdeğerdir: - - b = a * (2 * 3) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listelerde a = a * n ile a *= n işlemi de aynı anlama gelmemektedir. a = a * n işleminde a * n ile yeni bir liste yaratılır, - a artık bu yeni listeyi gösterir. Halbuki a *= n işleminde (n - 1) tane a, a'nın sonuna eklenmektedir. Örneğin: - - >>> a = [1, 2, 3] - >>> id(a) - 1570379775488 - >>> a = a * 2 - >>> a - [1, 2, 3, 1, 2, 3] - >>> id(a) - 1570379885120 - >>> a = [1, 2, 3] - >>> id(a) - 1570379931200 - >>> a *= 2 - >>> id(a) - 1570379931200 - >>> a - [1, 2, 3, 1, 2, 3] -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] - -print(a, id(a)) -a = a * 3 -print(a, id(a)) # a'nın adresi değişiyor, a artık a * 3 ile yaratılan listeyi gösteriyor - -a = [10, 20, 30] - -print(a, id(a)) -a *= 3 -print(a, id(a)) # a'nın adresi değişmiyor, ekleme a'nın sonuna yapılıyor - -#------------------------------------------------------------------------------------------------------------------------ - Demetler (tuples) listelere benzeyen önemli diğer bir veri yapısıdır. Bir demet normal parantezler kullanılarak yaratılabilir. - Örneğin: - - t = (10, 20, 30) - - Demetler tuple isimli sınıfla temsil edilmektedir. ("tuple" sözcüğü "tyupıl" gibi de "tapl" gibi de okunabilmektedir.) -#------------------------------------------------------------------------------------------------------------------------ - -t = (10, 20, 30) -print(t) # (10, 20, 30) - -print(type(t)) # - -#------------------------------------------------------------------------------------------------------------------------ - Bir demet tuple sınıfının tür fonksiyonu olan tuple fonksiyonu ile de yaratılabilir. tuple fonksiyonuna argüman girilmezse - boş bir demet yaratılır. Eğer tuple fonksiyonuna dolaşılabilir bir nesne argüman olarak verilirse tuple fonksiyonu bu nesneyi dolaşarak - onun elemanlarından demet oluşturmaktadır. Örneğin: - - >>> t = tuple() - >>> t - () - >>> a = [1, 2, 3, 4, 5] - >>> t = tuple(a) - >>> t - (1, 2, 3, 4, 5) - >>> t = tuple('ankara') - >>> t - ('a', 'n', 'k', 'a', 'r', 'a') - -#------------------------------------------------------------------------------------------------------------------------ - -t = tuple() -print(t) # () - -t = tuple('ankara') -print(t) # ('a', 'n', 'k', 'a', 'r', 'a') - -a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -t = tuple(a) -print(t) # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - -#------------------------------------------------------------------------------------------------------------------------ - Demetler listelere pek çok bakımdan benzemektedir. Örneğin: - - - Demetlerin elemanlarına da [] operatörüyle erişilir. - - Demetlerde de elemana erişirken negatif indeksler listelerdeki gibi anlam taşır. - - Demetlerde de tamamen listelerde olduğu gibi dilimleme yapılabilir. Tabii dilimleme işleminden demet elde edilir. - - Demetler de dolaşılabilir (iterable) nesnelerdir. -#------------------------------------------------------------------------------------------------------------------------ - -t = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100) - -val = t[3] -print(val) # 40 - -val = t[-3] -print(val) # 80 - -result = t[1:6] -print(result) # (20, 30, 40, 50, 60) - -result = t[1:6:2] -print(result) # (20, 40, 60) - -result = t[::-1] -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Demetler dolaşılabilir nesnelerdir. Demetler dolaşıldığında sırasıyla demetin elemanları elde edilir. Örneğin: - - >>> t = (1, 2, 3, 4, 5) - >>> a = list(t) - >>> a - [1, 2, 3, 4, 5] -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Demetlerde de eleman uzunluğu built-in len fonksiyonuyla elde edilebilir. -#------------------------------------------------------------------------------------------------------------------------ - -t = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100) - -n = len(t) -print(n) # 10 - -#------------------------------------------------------------------------------------------------------------------------ - Boş bir demet içi boş bir parantezle oluşturulmaktadır. Örneğin: - - >>> t = () - >>> type(t) - - >>> len(t) - 0 - - Burada yorumlayıcı bu parantezleri öncelik parantezi olarak değerlendirmez. Ancak tek elemanlı demetleri oluştururken dikkat etmek gerekir. - Çünkü (10) gibi bir sentaks demet belirtmez. Buradaki parantezler öncelik parantezi anlamına gelir. Tek elemanlı demetleri oluştururken - parantez içerisine ekstra bir ',' eklemek gerekir. Örneğin: - - t = (10, ) - - Aslında listelerde ve demetlerde son elemandan sonra boş virgül bırakılabilmektedir: - - a = [10, 20, 30, ] # geçerli - print(len(a)) # 3 - - Tabii buradaki son ',' atomunun hiçbir işlevi yoktur. Ancak sentaks bakımından bu son ',' geçerlidir. Yani buradaki son ',' - boş bir eleman oluşturmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -t = (10) -print(t, type(t)) # 10 - -t = (10, ) -print(t, type(t)) # (10,) - -#------------------------------------------------------------------------------------------------------------------------ - Demetler de tamamen listelerde oluduğu gibi heterojen yani farklı türlerden elemanlara sahip olabilir. Örneğin: - - >>> t = (10, 'ali', True, 20.5) - >>> t - (10, 'ali', True, 20.5) -#------------------------------------------------------------------------------------------------------------------------ - -t = ('ali', 12, None, 'velli', 3.14, True) -print(t) # ('ali', 12, None, 'velli', 3.14, True) - -#------------------------------------------------------------------------------------------------------------------------ - Bir listenin elemanı bir demet, bir demetin elemanı da bir liste olabilir. Örneğin: - - a = [10, 'ali', ('veli', 20), True] - t = (10, 'veli', [20, 30], 40) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Demet elemanları da tıpkı listelerde olduğu gibi adreslerden oluşmaktadır. Yani bir demet elemanların kendisini değil - onların adreslerini tutmaktadır. Örneğin: - - t = ('ali', 12) - - Burada t demetinin ilk elemanı içerisinde 'ali' yazısının bulunduğu str nesnesinin adresini, ikinci elemanı içerisinde 12 değerinin - bulunduğu int nesnesinin adresini turmaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listelerle demetler arasındaki en önemli farklılı (aslında tek farklılık, diğer farklılıklar bu farklılığın bir sonucu) - listelerin "değiştirilebilir (mutable)", demetlerin ise "değiştirilemez (immuatable)" olmasıdır. Bir demet yaratıldıktan - sonra artık onun elemanları bir daha değiştirilemez. Örneğin: - - t = (10, 20, 30) - t[0] = 100 # geçersiz! exception oluşur - - Burada demetin bir elemanı değiştirilmeye çalışılmıştır. Demet nesnesi "değiştirilebilir (mutable)" değildir. Dolayısıyla bir demet - yaratıldıktan sonra biz onun herhangi bir elemanını değiştiremeyiz. (Yani onun herhangi bir elemanına başka bir nesnenin adresini - atayamayız.) - - tuple sınıfının da çeşitli metotları vardır. Ancak list sınıfında bulunan elemanlar üzerinde değişiklik yapan metotlara tuple - sınıfı sahip değildir. Bir demet nesnesi bir kez yaratılır. Sonra onun üzerinde değişiklik yapılamaz. Ona eleman da eklenemez, ondan - eleman da silinemez. - - Bu nedenle demetlerde listelerde bulunan aşağıdaki işlemler ve metotlar yoktur: - - - Sona eleman ekleme işlemi (append metodu) demetlerde yoktur. - - Sona dolaşılabilir nesnenin elemanlarını ekleme işlemi (extend) demetlerde yoktur. - - Belli bir elemanın silinmesi işlemi (pop ve remove) demetlerde yoktur. - - Tüm elemanların silinmesi işlemi (clear) demetlerde yoktur. - - Araya eleman ekleme işlemi (insert) demetlerde yoktur. - - In-place Sıraya dizme işlemi (sort metodu) demetlerde yoktur. - - In-place ters yüz etme işlemi (reverse) demetlerde yoktur. - - Demetlerde dilimleme yoluyla atama da yapılamamaktadır. - - Ancak demetlerde eleman değişikliği yapmayan index ve count metotları bulunmaktadır. Örneğin: - - >>> t = (10, 'ali', 20, 'veli', 10) - >>> t.index(20) - 2 - >>> t.count(10) - 2 - -#------------------------------------------------------------------------------------------------------------------------ - -t = (10, 20, 'ali', 20, 40, 'veli') - -result = t.index('veli') -print(result) # 5 - -result = t.count(20) -print(result) # 2 - -#------------------------------------------------------------------------------------------------------------------------ - Tabii demetlerin elemanları da aslında nesnelerin adreslerini tutmaktadır. Bu konuda listelerle bir farklı yoktur. - - >>> t = (10, 'ali', 20) - >>> id(t) - 1672976258432 - >>> id(t[0]) - 1672940513872 - >>> id(t[1]) - 1672976339248 - >>> id(t[2]) - 1672940514192 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir grup bilgiden elde edilen onu temsil eden kısa bir bilgiye hash denilmektedir. Bir grup bilgiden bir hash - değerinin elde edilmesi çeşitli amaçlarla kullanılabilmektedir. Örneğin hash değerleri bazı veri yapıları için - anahtar olarak kullanılırlar. Yine hash değerleri bozulmayı tespit etmek için de bazı bilgi güvenliği ve şifreleme gibi - bazı aalanlarda kullanılmaktadır. - - Python'da bazı türler "hash'lenebilir (hashable)" iken bazı türler "hash'lenemez (unshable)" biçimdedir. Genel olarak değiştirilebilir türler - hash'lenebilir değildir. Bu nedenle listeler elemanları ne olursa olsun hash'lenebilir değildir. Öte yandan demetler değiştirilemez - türler olduğu için hash'lenebilir türlerdir. Ancak her demet nesnesi de hash'lenebilir değildir. Bir demetin hash'lenebilir olması - için onun bütün elemanlarının hash'lenebilir olması gerekir. int, float, str, bool, complex, None türleri hash'lenebilirdir. Bu durumda - örneğin bir demetin bir elemanı bir liste ise o demet hash'lenebilir olmaz. Ancak listeler hiçbir durumda "hash'lenebilir" değildir. - - Bir nesnenin içerisindeki değerin hash değeri hash isimli built-in fonksiyonla elde edilebilir. Örneğin: - - >>> a = 'ali' - >>> hash(a) - 3186741938407899844 - >>> t = (1, 2, 3, 4) - >>> hash(t) - 590899387183067792 - - Aşağıdaki demet hash'lenebilir değildir: - - t = (1, 2, [3, 4], 5) - - Çünkü bu demet bir liste elemanına sahiptir. Bu liste de hash'lenebilir değildir: - - >>> t = (1, 2, [3, 4], 5) - >>> hash(t) - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'list' - - Aşağıdaki demet hash'lenebilirdir: - - >>> t = (1, 2, (3, 4), 5) - >>> hash(t) - -2963430713186267524 - - Aşağıdaki demet de hash'lenebilir değildir: - - >>> t = (1, 2, (3, 4, [5, 6]), 7) - >>> hash(t) - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'list' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Demetin bir elemanı bir liste olsun: - - t = (1, [2, 3], 4) - - Biz şimdi bu listenin elemanlarını değiştirebiliriz: - - t[1][1] = 100 - - Burada bu demeti yazdırdığımızda şöyle bir sonuç göreceğiz: - - (1, [2, 100], 4) - - Aslında bu durum demetin değiştirildiği anlamına gelmemektedir. Çünkü bu işlemle biz aslında demetin elemanlarında değişiklik - yapmış olmuyoruz. Yani demetin elemanları yine aynı adresleri tutmaktadır. Biz burada değişikliği demetin elemanı olan liste üzerinde - yapmış oluyoruz. Bu durum yanlış yorumlanmamalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -t = (1, [2, 3], 4) -print(t) # (1, [2, 3], 4) - -t[1][1] = 100 -print(t) # (1, [2, 100], 4) - -#------------------------------------------------------------------------------------------------------------------------ - 15. Ders - 30/05/2021-Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Demetler oluşturulurken bazı zorunlu durumlar dışında parantezler kullanılmayabilir. Örneğin: - - t = (1, 2, 3) - - ile, - - t = 1, 2, 3 - - aynı anlamdadır. Örneğin: - - t = 10, - - ile, - - t = (10, ) - - aynı anlamdadır. Demet oluştururken pek çok durumda parantez gerekmediğine göre biz de örneklerimizde bazen parantezleri - kullanacağız bazen de kullanmayacağız. - -#------------------------------------------------------------------------------------------------------------------------ - -t = 10, 20, 30 - -print(t) - -t = 10, [20, 30, 40], 50 -print(t) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii bazı durumlarda demeti yaratırken parantezleri mecburen kullanırız. Örneğin listenin ya da demetin bir elemanı demet olacaksa - mecburen demet parantezleri kullanılmak zorundadır. - - a = [10, (20, 30, 40), 50] - print(a) - - t = 10, (30, 40, 50), 50 - print(t) - - Örneğin bir fonksiyona argüman olarak bir demet göndermek istiyorsak yine mecburen demet parantezini kullanmak zorundayız: - - print(10, 20) - print((10, 20)) - - Boş bir demet oluştururken boş parantezlerin kullanılması ya da tuple sınıfının tür fonksiyonun kullanılması gerekir. Örneğin: - - t = () - print(t) - - t = tuple() - print(t) - - Yani aşağıdaki gibi bir sentaks geçerli değildir: - - t = , - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tek elemanlı bir demeti parantezsiz de oluşturabiliriz. Tabii bu durumda değerden sonra bir ',' atomu gerekir. Örneğin: - - t = 10, - print(t) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İki demet + operatörü ile toplanabilir. Toplama işleminden yeni bir demet nesnesi elde edilir. Bu yeni demet nesnesi - iki demet nesnesinin elemanlarının uç uca eklenmesinden oluşmaktadır. Yine tıpkı listelerde olduğu gibi yeni yaratılan - demete bu demetlerin içerisindeki adresler kopyalanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = (10, 20, 30) -b = (40, 50, 60) - -c = a + b -print(c) - -print(id(c[3]), id(b[0])) # aynı adresler - -#------------------------------------------------------------------------------------------------------------------------ - Listelerde a = a + b ile a += b farklı anlamlara geliyordu. Ancak demetlerde bu iki ifade tamamen aynı anlama gelmektedir. - Çünkü demetlerde sona ekleme biçiminde bir işlem yoktur. Örneğin: - - >>> a = 1, 2, 3 - >>> id(a) - 1486505308992 - >>> b = 4, 5, 6 - >>> a += b - >>> id(a) - 1486504943872 - >>> a - (1, 2, 3, 4, 5, 6) -#------------------------------------------------------------------------------------------------------------------------ - -a = 10, 20, 30 -b = 40, 50 - -print(a, id(a)) -a = a + b -print(a, id(a)) - -a = 10, 20, 30 -print(a, id(a)) -a += b -print(a, id(a)) # id farklı çıkacak, çünkü demetlerde sona ekleme mümkün değil - -#------------------------------------------------------------------------------------------------------------------------ - Demetlerde de yineleme (repitition) işlemi yapılabilmektedir. Yani bir demet int bir değerle çarpılabilir. Bu durumda yine bir - demet nesnesi yaratılır. Bu demet nesnesi çarpılan demetin elemanlarının yinelenmesinden oluşur. Tabii iki demeti çarpamayız, - çıkartamayız, bölemeyiz. Yine yineleme işleminde çarpılan değer 0 ya da negatif bir değerse boş bir demet elde edilmektedir. - Operand'lar yine çarpma işlemimde yer değiştirebilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = (10, 20, 30) - -b = 3 * a # a * 3 de yazılabilirdi - -print(b) - -#------------------------------------------------------------------------------------------------------------------------ - Listelerde a = a * n ile a *= n farklı anlamlara geliyordu. Ancak demetlerde sona ekleme işlemi olmadığı için bu iki ifade - de tamamen aynı anlama gelmektedir. Örneğin: - - >>> a = 1, 2, 3 - >>> id(a) - 1486505308992 - >>> a *= 2 - >>> id(a) - 1486504943872 - >>> a - (1, 2, 3, 1, 2, 3) -#------------------------------------------------------------------------------------------------------------------------ - -a = 10, 20, 30 - -print(a, id(a)) -a = a * 3 -print(a, id(a)) - -a = 10, 20, 30 -print(a, id(a)) -a *= 3 -print(a, id(a)) # id farklı çıkacak, çünkü demetlerde sona ekleme mümkün değil - -#------------------------------------------------------------------------------------------------------------------------ - Demetlerle listeler kullanım bakımından birbirine benzemektedir. Pekiyi ne zaman listeleri ne zaman demetleri tercih - etmeliyiz? - - 1) Eğer veri yapısı üzerinde onu yarattıktan sonra ekleme, silme gibi değişikler yapılacaksa mecburen listeleri kullanırız. - Çünkü demetlerde yaratıldıktan sonra değişiklik yapılamamaktadır. - - 2) Liste türünden nesneler demet türünden nesnelere göre daha fazla yer kaplama eğilimindedir. Çünkü onların değiştirilebilir, - büyütülebilir ve küçültülebilir olmaları nedeniyle nesne içerisinde bu işlemler için bazı bilgilerin bulundurulması gerekmektedir. - Bu nedenle eğer veri yapısı içerisindeki elemanlar üzerinde bir değişiklik yapmayacaksak, veri yapısına eleman ekleyip ondan bir şeyler - silmeyeceksek demetleri tercih etmeliyiz. - - 3) Demetler değiştirilebilir olmadığı için eğer onların elemanları hash'lenebilir (hashable) ise hashable nesnelerdir. Bu da onların - sözlük gibi, küme gibi veri yapılarında kullanılabilmesi anlamına gelir. Listeler hiçbir durumda hashable değildir. - - Her ne kadar veri yapısı üzerinde değişlik yapılmayacaksa liste yerine demetlerin kullanılması daha uygunsa da Python programcıları - yine de bu tür durumlarda demet yerine liste kullanabilmektedir. Çünkü pek çok uygulamada demetlerle listelerin kapladıkları yerin - farklı büyüklekte olmasının önemi yoktur. Bazen programcılar "değişikliğin yapılmasının istenmediğini vurgulamak" için de demet kullanabilmektedir. - Örneğin ileride de göreceğimiz gibi bir fonksiyonun birden fazla değeri çağıran fonksiyona iletmesi gerektiği durumda listeler yerine - demetler hem etkinlik bakımından hem de okunabilirlik bakımından daha uygun bir seçenektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir bir nesnenin elemanları "açım (unpacking)" denilen bir sentaksla değişkenlere atanabilmektedir. Açım (unpacking) - işlemi Ruby' Python gibi dillerde eskiden beri vardı. Daha sonraları C# gibi, Swift gibi dillere ve hatta C++'a bile bu özellik - eklenmiştir. Açım işlemi eskiden yalnızca demetler üzerinde yapılabiliyordu. Ancak daha sonra dolaşılabilir her nesne açılabilir - yapıldı. Dolayısıyla açım sentaksı da genelleştirildi. Açım için aşağıdaki üç sentaks tamamen aynı anlama gelmektedir: - - 1) (x, y, z, ...) = - 2) x, y, z, ... = - 3) [x, y, z ...] = - - Açım sırasında = operatörünün sağ tarafında "dolaşılabilir (iterable)" bir nesne bulunmak zorundadır. Açım sırasında bu - nesne dolaşılır, elde edilen değerler sol taraftaki değişkenlere sırasıyla atanır. Örneğin: - - t = 10, 20, 30 - - x, y, z = t - - bu işlem tamamen aşağıdaki eşdeğerdir: - - x = t[0] - y = t[1] - z = t[2] - - Örneğin: - - [x, y, z] = 'ali' - - bu işlem tamamen aşağıdaki ile eşdeğerdir: - - x = 'a' - y = 'l' - z = 'i' - - Açım işleminde sol taraftaki sentaksın bir önemi yoktur. Üç sentaks biçimi de tamamen birbirleriyle eşdeğerdir. -#------------------------------------------------------------------------------------------------------------------------ - -t = (10, 20, 30) - -(x, y, z) = t - -print(x, y, z) # 10 20 30 - -x, y, z = t - -print(x, y, z) # 10 20 30 - -[x, y, z] = t - -print(x, y, z) # 10 20 30 - -x, y, z = 'van' -print(x, y, z) # v a n - -#------------------------------------------------------------------------------------------------------------------------ - Açım (unpacking) yapılırken = operatörünün sağındaki dolaşılabilir nesnenin eleman sayısının açımdaki değişken sayısı - ile tamamen aynı olması gerekir. Aksi takdirde exception oluşmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -t = (10, 20, 30) - -x, y = t # geçersiz! exception oluşur - -x, y, z, k = t # geçersiz! exception oluşur - -#------------------------------------------------------------------------------------------------------------------------ - Açılacak dolaşılabilir nesnedeki elemanlardan bazıları liste, demet gibi başka bir dolaşılabilir nesne olabilir. Örneğin: - - >>> a, b, c = [10, [20, 30], 40] - >>> a - 10 - >>> b - [20, 30] - >>> c - 40 - >>> a, b, c = 100, 'ankara', 200 - >>> a - 100 - >>> b - 'ankara' - >>> c - 200 - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, (20, 30), 40] - -x, y, z = a - -print(x, y, z) # 10 (20, 30) 40 - -#------------------------------------------------------------------------------------------------------------------------ - Açım işlemi özyinelemeli (recursive) bir biçimde de yapılabilir. Yani açılan bir eleman dolaşılabilir bir nesne ise o da açılabilir. - Tabii bu durumda ek parantezler kullanılmalıdır. Ancak bu parantezlerin demet parantezi ya da liste parantezi olması arasında - bir farklılık yoktur. Örneğin: - - >>> a, (b, c), d = [10, [20, 30], 40] - >>> a - 10 - >>> b - 20 - >>> c - 30 - >>> d - 40 - >>> a, [b, c], d = [10, [20, 30], 40] - >>> a - 10 - >>> b - 20 - >>> c - 30 - >>> d - 40 - - Örneğin: - - >>> a, (b, (c, d, e), f), g = [10, [20, (30, 40, 50), 60], 70] - >>> a, b, c, d, e, f, g - (10, 20, 30, 40, 50, 60, 70) - - Tabii özyinelemenin sonuna kadar devam ettirilmesi gerekmez. Örneğin: - - >>> a, b, c = [10, [20, (30, 40, 50), 60], 70] - >>> a, (b, c, d), e = [10, [20, (30, 40, 50), 60], 70] - >>> print(a, b, c, d, e) - 10 20 (30, 40, 50) 60 70 - >>> a, (b, (c, d, e), f), g = [10, [20, (30, 40, 50), 60], 70] - >>> print(a, b, c, d, e, f, g) - 10 20 30 40 50 60 70 - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] - -x, y, z = a - -print(x, y, z) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii açım işlemi aslında bir çeşit atama işlemidir. Yani açım sonucunda değişkenlere aslında dolaşılabilir nesnenin elemanlarının - adresi yerleştirilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30] - -x, y, z = a - -print(id(a[0]), id(a[1]), id(a[2])) -print(id(x), id(y), id(z)) # id'ler aynı - -#------------------------------------------------------------------------------------------------------------------------ - Python'da iki değişkenin değerinin yer değiştirilmesi açım (unpacking) işlemi ile kolay bir biçimde yapılabilmektedir. - Örneğin: - - >>> a = 10 - >>> b = 20 - >>> a, b = b, a - >>> print(a, b) - 20 10 - -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -print(a, b) # 10 20 - -a, b = b, a - -print(a, b) # 20 10 - -#------------------------------------------------------------------------------------------------------------------------ - range isimli fonksiyon bize range isimli dolaşılabilir bir sınıf nesnesi vermektedir. range fonksiyonunun bize verdiği - dolaşılabilir nesne dolaşıldığında start değerinden başlayarak stop değerine kadar (start dahil stop dahil değil) - step miktarı artırımlarla int değerler elde edilmektedir. range fonksiyonu tek argümanlı, iki argümanlı ya da üç argümanlı - kullanılabilmektedir: - - range(stop) - range(start, stop) - range(start, stop, step) - - Tek argümanlı kullanımda dolaşım sırasında 0'dan argümanla belirtilen değere kadar int değerler elde edilir. - Örneğin: - - >>> r = range(10) - >>> a = list(r) - >>> a - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - Yani tek argümanlı kullanımsa aslında start değeri 0, step değeri 1 gibiymiş gibi işlem uygulanmaktadır. Bu durumda tek - argümanlı kullanımda biz aslında argüman olarak stop değerini vermiş oluruz. Dolaşım sırasında start değeri her zaman dolaşıma - dahildir ancak stop değeri dahil değildir. -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10) # start = 0, stop = 10, step = 1 - -a = list(r) -print(a) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -#------------------------------------------------------------------------------------------------------------------------ - range fonksiyonunu iki argümanla çağırırsak birinci argüman start değerini ikinci argüman stop değerini belirtir. - (start dahil stop dahil değildir.) Örneğin: - - >>> a = list(range(10, 20)) - >>> a - [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20) # start = 10, stop = 20, step = 1 - -a = list(r) -print(a) # [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] - -#------------------------------------------------------------------------------------------------------------------------ - range fonksiyonu üç argümanla kullanıldığında birinci argüman start değerini, ikinci argüman stop değerini ve üçüncü - argüman da step değerini belirtir. Örneğin: - - >>> a = list(range(10, 20, 3)) - >>> a - [10, 13, 16, 19] - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20, 2) # start = 10, stop = 20, step = 2 - -a = list(r) -print(a) # [10, 12, 14, 16, 18] - -#------------------------------------------------------------------------------------------------------------------------ - range fonksiyonunun üç parametresi de int türden olmak zorundadır. Böylece range nesnesi dolaşıldığında nesne her zaman bize - int değerler verir. Bu durum range fonksiyonu ile noktalı artırımlar yapamayacağımız anlamına da gelmektedir. Örneğin: - - r = range(0, 1, 0.1) - - Böyle range nesnesi yaratmak istersek step değeri float olduğu için exception oluşacaktır. Yuvarlama hataları ile kararsız - durumların oluşmaması noktalı artırımlara, float türden start ve stop değerlerine izin verilmemiştir. Örneğin eğer noktalı - artırımlara izin verilseydi şöyle sorunlar ortaya çıkabilirdi: - - a = list(range(a, b, 0.1)) # bu durum geçersizdir, biz burada "geçerli olsaydı" durumunu değerlendiriyoruz - - Burada a değerinden başlanarak b değerine kadar 0.1 artırımlarla değerler elde edilmek istenmiştir. Elde edilen değerlere - b değeri dahil olmayacaktır. İşte eğer bir yuvarlama hatası olursa ve son değer b'ye çok yakın ama yuvarlama hatasından dolayı - b'den küçük olursa bu da dolaşılma dahil edilecektir. Bu durum da farklı uzunluklarda listelerin elde edilmesine yol açabilecektir. - - Fakat örneğin yaygın kullanılan "NumPy" isimli kütüphanede arange isimli noktalı artırım yapan bir dolaşılabilir sınıf vardır. - NumPy kütüphanesi "Python Uygulamaları" kursunda ayrıntılı biçimde ele alınmaktadır. Örneğin: - - >>> import numpy - >>> a = numpy.arange(0, 1, 0.1) - >>> a - array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]) - - NumPy sayısal analiz, istatistik, matematik ve makine öğrenmesinde çok yaygın kullanılan üçüncü parti bir kütüphanedir. - Python'ın standart kütüphanesi içerisinde değildir. Aşağıdaki gibi install edilebilir: - - pip install numpy - - Anaconda dağıtımı default olarak NumPy kütüphanesini barındırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - range fonksiyonunda step değeri negatif verilebilir. Bu durumda dolaşım sırasında büyükten küçüğe değerler elde edilir. - Tabii eğer step değeri negatif ise start değerinin stop değerinden daha büyük olması gerekir. Yine stop değeri dahil değildir. - Örneğin: - - >>> a = list(range(10, 0, -1)) - >>> a - [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] - >>> a = list(range(10, -1, -1)) - >>> a - [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 0, -1) # start = 10, stop = 0, step = -1 - -a = list(r) -print(a) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] - -r = range(10, -1, -1) # start = 10, stop = -1, step = -1 - -a = list(r) -print(a) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - -#------------------------------------------------------------------------------------------------------------------------ - 16. Ders - 01/06/2022-Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii range fonksiyonunun step parametresi 0 olamaz. Eğer bu parametre 0 girilirse exception (ValueError) oluşacaktır. - Örneğin: - - >>> r = range(0, 10, 0) - Traceback (most recent call last): - File "", line 1, in - ValueError: range() arg 3 must not be zero - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - range sınıfı Python Standart Kütüphanesinde bir "sequence container" olarak ele alınmıştır. "Sequence container" terimi Python'da - elemanlar arasında öncelik sonralık ilişkisi olan ve elemanlarına [...] operatörü ile erişilen veri yapılarına betimlemektedir. - - range sınıfı bir "sequence container" belirttiği için listeler ve demetler üzerinde yapılabilen bazı işlemler range nesneleri - üzerinde de yapılabilmektedir. Örneğin bir range nesnesi dilimlenebilir. Bu işlemden yeni bir range nesnesi elde edilir: - - >>> r = range(100) - >>> k = r[30:60:3] - >>> k - range(30, 60, 3) - - Burada biz r range nesnesini dilimleyerek ondan başka bir range nesnesi elde ettik. range nesnelerinin dilimlenmesine seyrek - bir biçimde gereksinim duyulmaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(100) - -k = r[30:60:3] -a = list(k) -print(a) # [30, 33, 36, 39, 42, 45, 48, 51, 54, 57] - -#------------------------------------------------------------------------------------------------------------------------ - range nesnesinin belli bir elemanına [...] operatör ile erişebiliriz. Ancak range nesnesinin elemanları değiştirilememektedir. - Örneğin: - - >>> r = range(100) - >>> r[23] - 23 - >>> r[23] = 100 - Traceback (most recent call last): - File "", line 1, in - TypeError: 'range' object does not support item assignment - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20) - -val = r[3] -print(val) # 13 - -#------------------------------------------------------------------------------------------------------------------------ - Listelerde ve demetlerde kullandığımız in ve not in operatörleri range nesnelerinde de kullanılabilmektedir. Örneğin: - - >>> r = range(30, 60, 3) - >>> 39 in r - True - >>> 40 in r - False - >>> 41 not in r - True - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20) - -result = 15 in r -print(result) # True - -result = 25 in r -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - Teknik olarak range sınıfı "değiştirilemez (immutable)" bir sınıftır. Dolayısıyla bu sınıfta append, extend, remove gibi - metotlar bulunmaz. Ancak index ve count metotları sınıfta bulunmaktadır. Örneğin: - - >>> r = range(100) - >>> r.count(67) - 1 - >>> r = range(30, 60, 3) - >>> r.index(39) - 3 - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20) - -result = r.index(15) -print(result) # 5 - -result = r.count(12) -print(result) # 1 - -#------------------------------------------------------------------------------------------------------------------------ - range nesnelerinin dolaşıldıkları takdirde kaç eleman verecekleri yine built-in len fonksiyonuyla elde edilebilir. - Örneğin: - - >>> r = range(30, 60, 3) - >>> len(r) - 10 - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20) - -result = len(r) -print(result) # 10 - -#------------------------------------------------------------------------------------------------------------------------ - range nesnesinde belirtilen start, stop ve step değerleri sınıfın "start", "stop" ve "step" örnek öznitelikleri (instance attibutes) - ile elde edilebilmektedir. Örneğin: - - >>> r = range(30, 60, 3) - >>> r.start - 30 - >>> r.stop - 60 - >>> r.step - 3 - -#------------------------------------------------------------------------------------------------------------------------ - -r = range(10, 20, 3) - -print(r.start, r.stop, r.step) # 10 20 3 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi "dolaşılabilir (iterable)" nesne her dolaşıldığında yeniden aynı değerleri verebilen nesnelerdi. - Oysa "dolaşım (iterator)" nesneleri bir kez dolaşıldığında biten, yani ikinci kez dolaşıldığında bittiği için artık - bir değer vermeyen nesnelerdir. Teknik olarak her dolaşım nesnesi dolaşılabilir bir nesne gibidir. Ancak dolaşım bitince - artık bir daha dolaşılsa da değer vermez. Özetle bir dolaşılabilir nesne dolaşıldığında bitiyorsa o nesneye özel olarak - "dolaşım (iterator)" nesnesi denilmektedir. - - range nesnei "dolaşılabilir bir nesnedir, dolaşım nesnesi değildir" . Dolayısıyla aynı range nesnesini her dolaştığımızda - aynı elemanlarını yeniden elde ederiz. Örneğin: - - >>> r = range(10) - >>> a = list(r) - >>> a - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> b = list(r) - >>> b - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir değeri doğrudan fonksiyona argüman olarak vermekle önce onu bir değişkene atayıp değişkeni argüman olarak vermek arasında - işlevsel bir farklılk yoktur. Örneğin: - - r = range(10) - a = list(r) - - Biz bu işlemi kısaca şöyle de yapabilirdik: - - a = list(range(10)) - - Bu durumda yine bir range nesnesi yaratılacak ve o nesne (yani onun adresi) list fonksiyonuna geçirilecektir. Örneğin: - - a = list((1, 2, 3, 5)) - - Burada da önce bir demet nesnesi yaratılacak o demet nesnesi list fonksiyonuna geçirilecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Matematikte farklı (distinct) elemanların oluşturduğu topluluğa küme (set) denilmektedir. Python'da da bu matematiksel anlamı - destekleyecek biçimde bir küme veri yapısı bulunmaktadır. - - Bir küme küme parantezleri içerisinde eleman listesi girilerek oluşturulmaktadır. Örneğin: - - >>> s = {'ali', 'ankara', 100, 3.4} - >>> s - {'ali', 'ankara', 3.4, 100} - - Kümeler set isimli sınıfla temsil edilmiştir. Kğme türünden bir değişkene type fonksiyonunu uyguladığımızda onun set - isimli bir sınıf türünden olduğunu görürüz. Örneğin: - - >>> type(s) - - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, True, 30.2} - -print(s, type(s)) # {True, 'ali', 20, 10, 30.2} - -#------------------------------------------------------------------------------------------------------------------------ - Kümelerde elemanlar arasında öncelik sonralık ilişkisi yoktur. Yani kümelerde bir sıra yoktur. (Matematikteki kümelerde de - bir sıra kavramı yktur). Dolayısıyla biz kümenin elemanlarına [...] operatörü ile erişemeyiz. Küme elemanlarını [...] operatörü ile - dilimleyemeyiz. Bir küme print fonksiyonu ile yazdırıldığında onun elemanlarının hangi sırada yazdırıldığının bir önemi yoktur. - Örneğin: - - >>> s = {'ali', 'ankara', 100, 3.4} - >>> print(s) - {'ali', 'ankara', 3.4, 100} - >>> s - {'ali', 'ankara', 3.4, 100} - >>> s[1] - Traceback (most recent call last): - File "", line 1, in - TypeError: 'set' object is not subscriptable - - Küme elemanlarında bir sıra olmadığı için küme elemanlarını yazdırdığımızdaki sıranın da bir önemi yoktur. Bir kümeyi - yazdırırken elemanların bizim girdiğimiz sırada yazdırılması gerekmemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Kümelerde [...] operatörü kullanılmadığı için kümeler üzerinde dilimleme (slicing) işlemleri de yapılamamaktadır. Örneğin: - - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> s[2:5] - Traceback (most recent call last): - File "", line 1, in - TypeError: 'set' object is not subscriptable -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Kümelerin eleman sayısı built-in len fonksiyonuyla elde edilebilmektedir. Örneğin: - - >>> s = {'ali', 'ankara', 'veli', 123} - >>> len(s) - 4 -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, True, 30.2} - -result = len(s) -print(result) # 5 - -#------------------------------------------------------------------------------------------------------------------------ - Bir kümeye zaten onda var olan bir elemanı eklemeye çalışmak exception oluşturmaz. Eleman ekleneceği zaman elaman zaten varsa - eklenmez. Dolayısıyla aşağıdaki küme oluşturma işlemi tamamen geçerlidir: - - a = {10, 20, 10, 30, 20} - - Burada 10 elemanı ve 20 elemanı olduğu halde yine küme parantezlerinin içerisinde bulunmaktadır. Bu durum bir exception oluşturmaz. - Örneğin: - - >>> s = {1, 2, 1, 1, 3, 4, 1, 5, 2, 3} - >>> print(s) - {1, 2, 3, 4, 5} - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, 'ali', 10} - -print(s) # {10, 'ali', 20} - -#------------------------------------------------------------------------------------------------------------------------ - Boş bir küme söz konusu olabilir. Ancak boş bir küme {} biçiminde oluşturulmaz. Buradaki {} ifadesi boş bir küme değil - boş bir sözlük (dicitonary) oluşturmaktadır. Sözlükler konusu izleyen bölümde ele alınacaktır. - - Sözlüklerle kğmeler benzer sentakslarla oluşturulmaktadır. Ancak sözlüklere kümelerden daha fazla gereksinim duyulmaktadır. - Bu nedenle boş küme parantezinin boş küme değil boş sözlük belirtmesinin daha anlamlı olduğu düşünülmüştür. Örneğin: - - >>> d = {} - >>> type(d) - - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Kümeler set isimli sınıfla temsil edilmektedir. Boş bir küme set fonksiyonunun argümansız çağrılmasıyla oluşturulabilmektedir. - Örneğin: - - >>> s = set() - >>> type(s) - - >>> s - set() - >>> len(s) - 0 - -#------------------------------------------------------------------------------------------------------------------------ - -a = {} - -print(a, type(a)) # {} - -s = set() -print(s, type(s)) # set() - -#------------------------------------------------------------------------------------------------------------------------ - set fonksiyonu da tıpkı list ve tuple fonksiyonlarında olduğu gibi argüman olarak bizden dolaşılabilir bir nesne alıp - o nesneyi dolaşarak o değerlerden bir küme oluşturabilmektedir. Örneğin: - - >>> a = [1, 2, 1, 4, 8, 2, 5, 8, 1] - >>> s = set(a) - >>> s - {1, 2, 4, 5, 8} - - Örneğin: - - >>> s = set('ankara') - >>> s - {'n', 'a', 'k', 'r'} - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 'ali', 'veli', 20] -s = set(a) - -print(s) # {10, 'ali', 20, 'veli'} - -s = set(range(10)) -print(s) # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - -s = set('ankara') -print(s) # {'r', 'k', 'a', 'n'} - -#------------------------------------------------------------------------------------------------------------------------ - Kümeler yinelenenn elemanları atmak için iyi bir araç olabilmektedir. - - Aşağıdaki örnekte klavyeden girilen bir yazıdaki farklı karakterlerin sayısı yazdırılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -s = input('Bir yazı giriniz:') - -result = len(set(s)) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Kümeler de "dolaşılabilir (iterable)" nesnelerdir. Bir kümeyi dolaştığımızda onun elemanlarını elde ederiz. Ancak elemanların - hangi sırada elde edileceği hakkında bir belirleme yapılmamıştır. Yani biz kümeyi dolaştığımızda onun tüm elemanlarını elde ederiz - ancak sırası konusunda bir garanti verilmemektedir. Örneğin: - - >>> s = {'ali', 'ankara', 12, 5, 'istanbul'} - >>> a = list(s) - >>> a - ['istanbul', 'ali', 5, 'ankara', 12] - -#------------------------------------------------------------------------------------------------------------------------ - -s = {'ankara', 10, 20.5, False} -a = list(s) - -print(a) # listedeki sıra herhangi bir biçimde olabilir - -#------------------------------------------------------------------------------------------------------------------------ - Bir elemanın küme içerisinde olup olmadığının belirlenmesi için yine "in" ve "not in" operatörleri kullanılmaktadır. - Genel olarak kümeler gibi veri yapıları arka planda "hash tabloları (hash tables)" ya da "ikili ağaçlarla (binary trees)" denilen - algoritmik veri yapılarıyla gerçekleştirilmektedir. Örneğin CPython yorumlayıcısında kümeler algoritmik olarak "hash tabloları" - ile gerçekleştirilmiştir. Bu veri yapıları özellikle algoritmik aramalarda en çok tercih edilen veri yapılarındandır. - Bu nedenle kümelerde "var mı yok mu" testi listeler ve demetlere göre çok daha hızlı yapılabilmektedir. Çok sayıda elemandan - oluşan toplulukta in ve not in operatörleriyle "var mı yok mu" testi yapacaksak listeleri ve demetleri değil kümeleri tercih - etmeliyiz. Az sayıda eleman için böyle bir hız farkı anlamlı değildir. Listeler ve demetlerde "var mı yok mu" testi ancak - "sıralı arama (sequential search)" denilen yöntemle yapılabilmektedir. Bunun ise zaman maliyeti yüksektir. -#------------------------------------------------------------------------------------------------------------------------ - -s = {'ankara', 10, 20.5, False} - -result = 10 in s -print(result) # True - -result = 10 not in s -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - set nesnesindne hash tablosu oluşturabilmek için set elemanlarının "hashlenebilir (hashable)" olması gerekmektedir. - Temel türlerin hash'lenabilir olduğunu, demetlerin hash'lenebilir olduğunu ancak listelerin hash'lenebilir olmadığını - anımsayınız. Bu durumda bir liste bir kümenin elemanı yapılamamaktadır. Kümelerin kendisi de hash'lenebilir değildir. - Bu nedenle kümenin bir elemanı bir küme de olamaz. Eğer biz bir kümeye eleman olarak bir liste ya da küme verirsek bu durumda - exception (TypeError) oluşur. Örneğin: - - >>> s = {1, (2, 3), 'ankara', 12.3} - >>> s - {(2, 3), 1, 'ankara', 12.3} - >>> k = {'ankara', [1, 2, 3], 'izmir'} - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'list' - >>> m = {'ali', 12, {'veli', 'selami'}} - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'set' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi bir liste (ve küme) hiçbir zaman hash'lenebilir değildir. Ancak demetler "eğer onların elemanları - hash'lenebilir ise" hash'lenebilir. olmaktadır. Yani örneğin bir demetin elemanı bir liste ise artık demet hash'lenebilir - olmaz: - - >>> s = {1, (2, 3, [4, 5]), 6} - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'list' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da bool değerlerden hash değeri elde edilirken True için 1, False için 0 değeri elde edilmektedir. Örneğin: - - >>> hash(b) - 1 - >>> b = False - >>> hash(b) - 0 - - Dolayısıyla bool bir değer bir küme elemanı yapılırken bu hash değeri kullanılacağı için dikkat etmek gerekir. Örneğin: - - >>> s = {'ali', True} - >>> s - {True, 'ali'} - >>> s = {1, 'ali', True} - >>> s - {1, 'ali'} - - Aynı durum float ve int nesneler için de benzer biçimde söz konusudur: - - >>> s = {1.0, 'ali', 1} - >>> s - {1.0, 'ali'} - - Aslında hash tablolarında aynı hash değerine ilişkin elemanlar tabloda bir arada bulunabilmektedir. Yukarıdaki problem aslında - hash değeri ile karşılaştırma sırasında ortaya çıkmaktadır. Çünkü aşağıdaki karşılaştırmalar True değerini vermektedir: - - >>> 1 == True - True - >>> 1.0 == 1 - True - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Kümeler değiştirilebilir türlerdir. Yani biz bir set nesnesi içerisindeki değeri kümeden çıkartabiliriz. Yeni bir değeri kümeye - ekleyebiliriz. - - set sınıfının add isimli metodu tek bir elemanı kümeye eklemek için kullanılmaktadır. Örneğin : - - >>> s = {'ali', 'veli', 10, 20} - >>> s.add('istanbul') - >>> s - {'istanbul', 'ali', 10, 20, 'veli'} - - Anımsanacağı gibi listelerde tek bir elemanın eklenmesi append isimli metotla yapılıyordu. "append" sözcüğü sona ekleme - anlamında listeler için daha uygun olabilir. Kümelerde "sona ekleme" diye bir kavram olmadığı için "append" ismi yerine - "add" ismi tercih edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 20, 30, 40, 50} - -s.add(60) -print(s) # {50, 20, 40, 10, 60, 30} - -s.add((100, 200, 300)) - -print(s) # {50, 20, (100, 200, 300), 40, 10, 60, 30} - -#------------------------------------------------------------------------------------------------------------------------ - Bir grup elemanı kümeye tek hamlede eklemek için update metodu kullanılmaktadır. (bu metodu mantıksal olarak listelerdeki extend - metoduna benzetebiliriz. Tabii extend sona ekleme yapmaktadır. Kümelerede sıra olmadığına göre update metodu sona eklemez.) - update metodu "dolaşılabilir bir nesneyi parametre olarak alır. Nesneyi dolaşarak elde edilen değerleri kümeye ekler. - Örneğin: - - >>> s = {'ali', 10, 'veli', 'istanbul'} - >>> s.update([30, 'izmir', 'selami']) - >>> s - {'istanbul', 'selami', 'ali', 10, 'izmir', 'veli', 30} - >>> s.update('sakarya') - >>> s - {'istanbul', 's', 'selami', 'ali', 'r', 'y', 10, 'a', 'izmir', 'k', 'veli', 30} - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 20, 30, 40, 50} - -print(s) # {50, 20, 40, 10, 30} - -s.update((100, 200, 300)) - -print(s) # {50, 20, 100, 40, 10, 300, 30, 200} - -s.update('ankara') - -print(s) # {200, 10, 'a', 'k', 20, 30, 'n', 100, 'r', 40, 300, 50} - -#------------------------------------------------------------------------------------------------------------------------ - Tabii daha önceden de belirttiğimiz gibi zaten var olan bir elemanın yeniden kümeye eklenmeye çalışılması bir exception'a yol - açmayacaktır. -#------------------------------------------------------------------------------------------------------------------------ - ->>> s = {10, 20, 30} ->>> s.add(20) ->>> s -{10, 20, 30} - -#------------------------------------------------------------------------------------------------------------------------ - Kümedeki bir elemanı silmek için remove metodu kullanılmaktadır. remove ile silinecek eleman listede yoksa exception (KeyError) - oluşmaktadır. Örneğin: - - >>> s = {'ali', 10, 'veli', 20} - >>> s.remove(10) - >>> s - {'veli', 20, 'ali'} - >>> s.remove('sacit') - Traceback (most recent call last): - File "", line 1, in - KeyError: 'sacit' -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Kümelerden eleman silmek için discard isimli bir metot da bulunmaktadır. discard metodu silinmek istenen eleman yoksa - exception oluşturmaz. Sadece silme işlemini yapmaz. remove metodundan tek farkı budur. Örneğin: - - >>> s = {10, 'ali', 20, 'veli'} - >>> s.discard('veli') - >>> s - {'ali', 10, 20} - >>> s.discard('can') - >>> s - {'ali', 10, 20} - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - set sınıfında eleman silme ile ilgili pop isimli bir metot da vardır. Ancak set sınıfının pop isimli metodu parametresizdir. - Bu metot her çağrıldığında kümeden gelişigüzel bir elemanı siler ve sildiği elemanı bize geri dönüş değeri olarak verir. - Ancak küme boşsa exception (KeyError) oluşmaktadır. Örneğin: - - >>> s = {'ali', 10, 'selami', 5} - >>> s.pop() - 10 - >>> s.pop() - 'selami' - >>> s - {5, 'ali'} - >>> s.pop() - 5 - >>> s - {'ali'} - >>> s.pop() - 'ali' - >>> s - set() - >>> s.pop() - Traceback (most recent call last): - File "", line 1, in - KeyError: 'pop from an empty set' - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, 'veli'} - -val = s.pop() -print(val) - -val = s.pop() -print(val) - -val = s.pop() -print(val) - -val = s.pop() -print(val) - -print(s) # set() - -#------------------------------------------------------------------------------------------------------------------------ - Kümelerde eleman silme işlemi listelere göre çok daha hızlı yapılmaktadır. Listelerde bir eleman silinmesi sırasında listenin - elemanlarının kaydırılması gerekmektedir. Halbuki kümelerde böyle bir kaydırma yapılmadan çok hızlı bir biçimde eleman - silinebilmektedir. Hash tablolarında eleman silme işlemi çok hızlı yapılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - set sınıfının clear metodu kümedeki tüm elemanları silmek için kullanılmaktadır. Metot parametreye sahip değildir. Örneğin: - - >>> s = set(range(10)) - >>> s - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - >>> s.clear() - >>> s - set() - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, 'veli'} - -print(s) # {10, 'ali', 20, 'veli'} - -s.clear() - -print(s) # set() - -#------------------------------------------------------------------------------------------------------------------------ - set sınıfının copy isimli metodu kümenin bir kopyasını oluşturmak için kullanılmaktadır. Tabii buradaki kopyalama da aslında - yine "sığ kopyalama (shallow copy)" biçiminde yapılmaktadır. Yani aslında küme elemanları da asıl nesnelerin adreslerini tutmaktadır. - Dolayısıyla kopyalamada aslında adresler kopyalanmaktadır. Asıl nesneler kopyalanmamaktadır. Örneğin: - - >>> s = set(range(10)) - >>> s - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - >>> s.clear() - >>> s - set() - >>> s = set(range(10)) - >>> s - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - >>> k = s.copy() - >>> k - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - >>> id(s) - 2567818069376 - >>> id(k) - 2567817420576 - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, 'veli'} -k = s.copy() - -print(s) # {10, 'ali', 20, 'veli'} -print(k) # {10, 'ali', 20, 'veli'} - -s.add(100) - -print(s) # {100, 'ali', 10, 20, 'veli'} -print(k) # {10, 'ali', 20, 'veli'} - -#------------------------------------------------------------------------------------------------------------------------ - Biz bir set değişkenini başka bir değişkene atarsak iki farklı değişken aslında aynı nesneyi gösteriyor durumda olur. - Bu durum bir kopyalama anlamına gelmemektedir. Örneğin: - - >>> s = set(range(10)) - >>> s - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - >>> k = s - >>> k - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - >>> id(s) - 2567818070048 - >>> id(k) - 2567818070048 - >>> s.add(100) - >>> s - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100} - >>> k - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100} - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, 'ali', 20, 'veli'} -k = s - -print(s, id(s)) # {'ali', 10, 20, 'veli'} 2594897135872 -print(k, id(k)) # {'ali', 10, 20, 'veli'} 2594897135872 - -s.add(100) - -print(s) # {100, 'ali', 10, 20, 'veli'} -print(k) # {100, 'ali', 10, 20, 'veli'} - -#------------------------------------------------------------------------------------------------------------------------ - 17. Ders - 06/06/2022-Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İki kümenin ortak elemanlarının bulunması işlemine "kesişim (intersection)" denilmektedir. Kesişim işlemi set sınıfının intersection - metoduyla ya da & operatörüyle yapılabilmektedir. a ve b iki küme olmak üzere: - - c = a.intersection(b) - - işlemi ile - - c = a & b - - işlemi aynı sonucu verecektir. Arada şöyle bir farklılık vardır: & operatöründe sağ taraftaki operand yalnızca set ya da ileride görecek olduğumuz - frozenset türünden olmak zorundadır. Ancak intersection metodunun parametresi herhangi bir dolaşılabilir nesne olabilir. - Örneğin: - - >>> s = {'ali', 10, 'veli', 'eskişehir'} - >>> k = {10, 'eskişehir', 'adana', 20} - >>> result = s.intersection(k) - >>> result - {10, 'eskişehir'} - >>> result = s.intersection(['ali', 'adana', 'eskişehir']) - >>> result - {'ali', 'eskişehir'} - >>> result = s & k - >>> result - {10, 'eskişehir'} - -#------------------------------------------------------------------------------------------------------------------------ - -a = {10, 'ali', 20, 'veli', 'selami', 30} -b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} - -c = a & b -print(c) # {'veli', 20, 30} - -c = a.intersection(b) -print(c) # {'veli', 20, 30} - -x = [10, 'ali', 100, 200, 'sibel'] - -c = a.intersection(x) # parametre dolaşılabilir nesne olabilir -print(c) # {10, 'ali'} - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte klavyeden (stdin dosyasından) iki sözcük okunmuştur. Bu iki sözcüğün ortak karakterleri ekrana yazdırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -word1 = input('Bir sözcük giriniz:') -word2 = input('Bir sözcük daha giriniz:') - -s = set(word1) - -result = s.intersection(word2) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Aslında intersection metodunda biz birden fazla dolaşılabilir nesnesyi de agüman olarak verebiliriz. Bu durumda sol - taraftaki nesneyle argüman olarak verilmiş dolaşılabilir nesnelerin kesişimleri bulunmaktadır. Örneğin: - - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> k = ['ali', 'fatma', 'hüseyin', 'ayşe'] - >>> m = ['ayşe', 'ali', 'fatma', 'can'] - >>> result = s.intersection(k, m) - >>> result - {'ali', 'ayşe', 'fatma'} - - Biz aslında s.intersection(k, m) işlemiyle s, k ve m'nin kesişimlerini bulmuş olduk. Yani yapmaya çalıştığımız şey - s & k & m ile aynıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İki kümenin birleşimi '|' operatörü ile ya da set sınıfının union metodu ile elde edilebilmektedir. Burada da yine '|' - operatörünün sağ tarafındaki operand set ya da frozenset türünden olabilir. Ancak union metodunun parametresi herhangi bir dolaşılabilir - nesne olabilir. Yine union metoduna birden fazla dolaşılabilir nesneyi argüman olarak verebiliriz. Örneğin: - - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> k = {'ali', 'can', 'veli', 'hüseyin', 'sibel'} - >>> result = s.union(k) - >>> result - {'veli', 'fatma', 'sibel', 'can', 'ayşe', 'ali', 'hüseyin', 'selami'} - >>> result = s | k - >>> result - {'veli', 'fatma', 'sibel', 'can', 'ayşe', 'ali', 'hüseyin', 'selami'} - >>> result = s.union(['kaan', 'ali', 'sacit']) - >>> result - {'veli', 'kaan', 'ayşe', 'fatma', 'ali', 'selami', 'sacit'} - >>> result = s.union(['kaan', 'ali', 'sacit'], ['umut', 'şükran']) - >>> result - {'veli', 'şükran', 'fatma', 'kaan', 'ali', 'sacit', 'umut', 'ayşe', 'selami'} - -#------------------------------------------------------------------------------------------------------------------------ - -a = {10, 'ali', 20, 'veli', 'selami', 30} -b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} - -c = a | b -print(c) # {'ayşe', 'veli', 40, 10, 'selami', 20, 'fatma', 30, 'ali'} - -c = a.union(b) -print(c) # {'ayşe', 'veli', 40, 10, 'selami', 20, 'fatma', 30, 'ali'} - -x = [10, 'ali', 100, 200, 'sibel'] - -c = a.union(x) # parametre dolaşılabilir nesne olabilir -print(c) # {200, 10, 20, 'sibel', 'ali', 30, 'veli', 100, 'selami'} - -c = a.union('ankara') -print(c) # {'k', 10, 20, 'a', 'ali', 30, 'n', 'veli', 'r', 'selami'} - -#------------------------------------------------------------------------------------------------------------------------ - İki kümenin farkı '-' operatörü ile ya da difference metodu ile elde edilebilir. Yine diğerlerinde olduğu gibi '-' operatörünün - sağ tarafındaki operand set ya da frozenset türünden olmak zorundadır. Ancak difference metodunun parametresi herhangi bir dolaşılabilir - nesne türünden olabilir. Örneğin: - - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> s - {'veli', 'ayşe', 'fatma', 'ali', 'selami'} - >>> k = {'ali', 'sacit', 'fatma', 'hüseyin', 'bora'} - >>> k - {'fatma', 'bora', 'ali', 'hüseyin', 'sacit'} - >>> result = s - k - >>> s - {'veli', 'ayşe', 'fatma', 'ali', 'selami'} - >>> result = s.difference(k) - >>> result - {'veli', 'ayşe', 'selami'} - - Yine difference metodunda birdne fazla dolaşılabilir nesne argüman olarak verilebilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = {10, 'ali', 20, 'veli', 'selami', 30} -b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} - -c = a - b -print(c) # {'selami', 10, 'ali'} - -c = a.difference(b) -print(c) # {'selami', 10, 'ali'} - -x = [10, 'ali', 'selami'] -c = a.difference(x) - -print(c) # {'veli', 20, 30} - -#------------------------------------------------------------------------------------------------------------------------ - İki kümenin ortak olmayan elemanlarının elde edilmesine "exor" işlemi denilmektedir. Exor işlemi '^' operatörü ile ya da - symmetric_diffrence isimli metotla yapılmaktadır. Yine '^' operatörünün sağ tafındaki operand set ya da frozenset türünden - olabilir. Ancak symmetric_difference metodunun parametresi herhangi bir dolaşılabilir nesne türünden olabilmektedir. - Örneğin: - - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> s - {'veli', 'ayşe', 'fatma', 'ali', 'selami'} - >>> k = {'hüseyin', 'ali', 'sacit', 'selami'} - >>> k - {'ali', 'hüseyin', 'selami', 'sacit'} - >>> result = s ^ k - >>> result - {'veli', 'fatma', 'sacit', 'ayşe', 'hüseyin'} - >>> result = s.symmetric_difference(['ali', 'jale', 'mahmut']) - >>> result - {'veli', 'fatma', 'jale', 'mahmut', 'ayşe', 'selami'} - -symmetric_difference metodunda argüman olarak tek bir dolaşılabilir nesne verilebilmektedir. - -#------------------------------------------------------------------------------------------------------------------------ - -a = {10, 'ali', 20, 'veli', 'selami', 30} -b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} - -c = a ^ b -print(c) # {'ayşe', 40, 10, 'selami', 'fatma', 'ali'} - -c = a.symmetric_difference(b) -print(c) # {'ayşe', 40, 10, 'selami', 'fatma', 'ali'} - -x = 'ali', 10, 20, 'ayşe' - -c = a.symmetric_difference(x) -print(c) # {'ayşe', 'veli', 'selami', 30} - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda görmüş olduğumuz temel küme işlemlerinin update'li versyonları da vardır. Bunlar aşağıda listelenmiştir: - - a &= b ya da a.intersection_update(b) - a |= b ya da a.update(b) - a -= b ya da a.diffrence_update(b) - a ^= b ya da a.symmetric_difference_update(b) - - Burada yine operatör versiyonlarının sağ tarafındaki operand set ya da frozenset türünden olmak zorundadır. Metot versiyonlarının parametreleri ise - herhangi bir dolaşılabilir nesne olabilmektedir. Yine metotlu versiyonlarda symmmetric_difference_update dışındaki metotlara birden fazla - dolaşılabilir nesne argüman olarak verilebilmektedir. - - Yukarıdaki işlemlerde sonuç başka bir nesne olarak elde edilmemektedir. Sonuç soldaki nesne değiştirilerek elde edilmektedir. - Yani bu işlemler sonucunda yeni bir nesne yaratılmamaktadır. Örneğin: - - >>> a = {10, 'ali', 20, 'veli', 'selami', 30} - >>> b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} - >>> id(a) - 4373858784 - >>> id(b) - 4373859008 - >>> a &= b - >>> a - {'veli', 20, 30} - >>> id(a) - 4373858784 - - Burada aslında a |= b işlemi ile a.update(b) işlemi aynıdır. Bu nedenle union_update isimli bir metot yoktur. Onun yerine - update isimli metot vardır. Örneğin: - - c = a.intersection(b) - - işleminde a ve b değişmemektedir. Bu işlemden yeni bir set nesnesi elde edilmektedir. Halbuki örneğin: - - a.intersection_update(b) - - Burada a nesnesi artık a ve b'nin kesişimlerinden oluşan bir kğme haline gelmektedir. Bu işlemden yeni bir - küme elde edilmemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir a kümesinin tüm elemanları b kümesinde varsa "a kümesi b kümesinin bir alt kümesidir (subset)". Alt küme işleminin tersine - "üst küme (superset)" denilmektedir. Öz alt küme (proper subset) kendisi dahil olmayan alt kümedir. Her küme kendisinin - bir alt kümesidir ancak öz alt kümesi değildir. Benzer biçimde her küme kendisinin bir üst kümesidir ancak öz üst kümesi değildir. - - Bir kümenin diğer kümenin alt kümesi olup olmadığı "<=" operatörü ile ya da issubset operatörü ile belirlenir. Yine "<=" - operatöründe sağ taraftaki operand set ya da frozenset türünden olabilir. Ancak issubset metodunda parametre herhangi bir dolaşılabilir - sınıf türünden olabilir. Benzer biçimde üst küme kontrolü de ">=" ya da issuperset metoduyla yapılabilmektedir. Benzer biçimde >= operatörünün - sol tarafındaki ve sağ tarafındaki nesneler set ya da frozenset türünden olmak zorundadır. Ancak issuperset metodunun parametresi herhangi - bir dolaşılabilir nesne olabilir. - - Öz alt küme için bir metot yoktur. Bu işlem '<' operatörü ile yapılır. Öz üst küme kontrolü için de bir metot yoktur. Bu işlem '>' operatörü ile yapılır. - Örneğin: - - {'veli', 'ayşe', 'fatma', 'ali', 'selami'} - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> s - {'veli', 'ayşe', 'fatma', 'ali', 'selami'} - >>> k = {'ali', 'veli', 'selami'} - >>> k - {'ali', 'veli', 'selami'} - >>> result = k < s - >>> result - True - >>> result = s < s - >>> result - False - >>> result = s <= s - >>> result - True - -#------------------------------------------------------------------------------------------------------------------------ - -a = {10, 20, 30, 40, 50} -b = {10, 40} - -result = b < a -print(result) # True - -result = a < a -print(result) # False - -result = a <= a -print(result) # True - -result = a > b -print(result) # True - -result = a.issubset(range(10, 100, 10)) -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - İki kümenin hiçbir ortak elemanı yoksa bu iki kümeye "ayrık kümeler (disjoint sets)" denilmektedir. Ayrıklık kontrolü - set sınıfının isdisjoint metodu ile yapılmaktadır. Bu metodun parametresi herhangi bir dolaşılabilir nesne olabilir. - Disjoint işleminin bir operatör karşılığı yoktur. Tabii işlem s & k == set() biçiminde de yapılabilir. Örneğin: - - >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} - >>> k = {'hasan', 'hüseyin', 'kazım'} - >>> s.isdisjoint(k) - True - -#------------------------------------------------------------------------------------------------------------------------ - -a = {10, 20, 30, 40, 50} -b = {100, 200} - -result = a.isdisjoint(b) -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - Python'da "seyrek kullanılan" set sınıfının değiştirilemez (immutable) biçimi olan "frozenset" isimli bir sınıf da vardır. - set sınıfı ile frozenset sınıfı arasındaki farklılıklar şunlardır: - - 1) frozenset sınıfı değiştirilebilir olmadığı için onun add gibi, update gibi, intersection_update gibi, remove gibi - metotları yoktur. Ancak değiştirme işlemi yapmayan metotları set sınıf ile aynıdır. - - 2) frozenset sınıfında &=, |=, ^= -= gibi operatörler soldaki nesne üzerinde işlem yapmazlar. Yeni nesne yaratırlar. Yani örneğin, - a |= b işlemi tamamen a = a | b işlemi ile eşdeğerdir. - - 3) frozenset elemanları eğer hash'lenebilir ise frozenset nesnesi de hash'lenebilir durumdadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir frozenset nesnesi küme parantezleriyle yaratılamaz. Ancak frozenset sınıfının tür fonksiyonu olan frozenset fonksiyonu - ile yaratılabilir. Bu fonksiyon herhangi bir dolaşılabilir nesneyi parametre olarak alabilmektedir. Örneğin: - - >>> fs = frozenset(['ali', 'veli', 'selami', 'ayşe', 'fatma']) - >>> fs - frozenset({'veli', 'fatma', 'ayşe', 'ali', 'selami'}) -#------------------------------------------------------------------------------------------------------------------------ - -fs = frozenset([1, 'ali', 'selami', 2]) -print(fs) - -print(len(fs)) - -#------------------------------------------------------------------------------------------------------------------------ - set ile frozenset nesneleri '|', '&', '-', '^' işlemlerine sokulabilmektedir. Bu durumda işlemin sonucu soldaki operandın - türüne bağlıdır. Eğer soldaki operand set türündense sonuç set türünden, frozenset türündense sonuç frozenset türünden - olur. Tabii bunların metot karşılıklarında her zaman sonuç metodun çağrıldığı nesnenin türünden olmaktadır. Başka bir deyişle - yukarıdaki operatör işlemlerinde sol taraftaki ya da sağ taraftaki operand set ya da frozenset olabilmektedir. Ancak işlemin sonucu - her zaman sol taraftaki operand türünden olur. Örneğin (sembolik biçimde yazıyoruz): - - set & frozenset => set - frozenset & set => frozenset - frozenset & frozenset => frozenset - - Örneğin: - - >>> fs = frozenset(['ali', 'veli', 'selami', 'ayşe', 'fatma']) - >>> fs - frozenset({'veli', 'fatma', 'ayşe', 'ali', 'selami'}) - >>> s = {'ali', 'sacit', 'veli'} - >>> s - {'ali', 'veli', 'sacit'} - >>> result = fs & s - >>> result - frozenset({'ali', 'veli'}) - >>> result = s & fs - >>> result - {'ali', 'veli'} - -#------------------------------------------------------------------------------------------------------------------------ - -fs = frozenset([1, 'ali', 'selami', 2]) -s = {1, 'sibel', 10, 'veli'} - -result = fs & s -print(result, type(result)) # frozenset({1}) - -result = s & fs -print(result, type(result)) # {1} - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirtildiği gibi frozenset sınıfında &=, |=, ^=, -= gibi işlemlerde bir update yapılmamaktadır. Örneğin: - - >>> fs = frozenset(['ali', 'veli', 'selami']) - >>> fs - frozenset({'ali', 'selami', 'veli'}) - >>> id(fs) - 1525529415904 - >>> s = {'ali', 'ayşe', 'veli'} - >>> s - {'ali', 'veli', 'ayşe'} - >>> fs |= s - >>> fs - frozenset({'ayşe', 'ali', 'selami', 'veli'}) - >>> id(fs) - 1525529416800 - >>> id(s) - 1525529417024 - >>> s |= fs - >>> s - {'ayşe', 'ali', 'selami', 'veli'} - >>> id(s) - 1525529417024 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Kümenin bir elemanı küme olamaz. Çünkü kümler hash'lenebilir değildir. Ancak kümenin bir elemanı frozenset olabilir. - Çünkü frozenset hash'lenebilir biçimdedir. Örneğin: - - >>> s = {1, 2, {3, 4}, 5} - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'set' - >>> s = {1, 2, frozenset({3, 4}), 5} - >>> s - {frozenset({3, 4}), 1, 2, 5} - - Anımsanacağı gibi bir demetin elemanı bir liste olabiliyordu. Ancak bir frozenset'in elemanlarının hash'lenebilir - olması gerekmektedir. Örneğin: - - >>> fs = frozenset([[1, 2, 3], 4, 5]) - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'list' - -#------------------------------------------------------------------------------------------------------------------------ - -s = {10, frozenset((20, 30)), 40} - -print(s) # {40, 10, frozenset({20, 30})} - -#------------------------------------------------------------------------------------------------------------------------ - Sözlükler (dictionaries) "anahtar-değer" çiftlerini tutan, anahtar verildiğinde değerin hızlı bir biçimde bulunmasını sağlayan - veri yapılarıdır. Sözlükler hızlı aramayı mümkün hale getirmek için özel algoritmik yöntemlerden faydalanmaktadır. Programlama - dillerinde sözlükler "hash tabloları (hash tables)", "dengelenmiş ikili ağaçlar (balanced binary tree)", "sıralı diziler (sorted arrays)" - biçiminde gerçekleştirilebilmektedir. CPython gerçekleştiriminde sözlükler "hash tabloları" yoluyla oluşturulmuştur. - - Sözlükler yine küme parantezleri kullanılarak yaratılırlar. Ancak küme parantezlerinin içerisi "anahtar: değer" çiftleerinden - oluşturulmaktadır. Sözlük yaratma işleminin genel biçimi şöyledir: - - { anahtar: değer, anahtar: değer, anahtar: değer, ...} - - Burada sözlklerin küme partantezleri içerisinde anahtardan sonra ':' ile değer belirtilerek yaratıldığını görüyorsunuz. - Örneğin: - - >>> d = {'ankara': 6, 'istanbul': 34, 'eskişehir': 26, 'izmir': 35, 'denizli': 20} - >>> d - {'ankara': 6, 'istanbul': 34, 'eskişehir': 26, 'izmir': 35, 'denizli': 20} - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 100, 'veli': 200, 'selami': 300, 'ayşe': 1000, 'fatma': 400} - -print(d) # {'ali': 100, 'veli': 200, 'selami': 300, 'ayşe': 1000, 'fatma': 400} -print(type(d)) # - -#------------------------------------------------------------------------------------------------------------------------ - Sözlükler "dict" isimli sınıfla temsil edilmiştir. Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> type(d) - - - Bir sözlüğü print fonksiyonu ile yazdırdığımızda sözlüğün bütün elemanları anahtar-değer çiftleri biçiminde yazdırılmaktadır. - Örneğin: - - >>> print(d) - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerde anahtarların hash'lenebilir olması gerekmektedir. Ancak değerler için böyle bir koşul yoktur. Dolayısıyla anahtarlar - int, float, str gibi türlerden olabilir. Ancak liste ve küme olamaz. Demetlerin eğer elemanları hash'lenebilir ise hash'lenebilir - olduğunu anımsayınız. Benzer biçimde frozenset nesneleri de hash'lenebilir durumdadır. Örneğin: - - >>> d = {'eskişehir': ['mihalıççık', 'sivrihisar', 'seyitgazi'], 'istanbul': ['şişli', 'pendik', 'ataşehir'], - 'izmir': ['konak', 'gaziemir', 'karşıyaka']} - - Burada d sözlüğünün anahtarları str türündendir. str türü de hash'lenebilir bir türdür. Ancak d sözlüğünün değerleri birer listedir. - Değerlerin hash'lenebilir olması gerekmez. Yani sözlüklerin değerleri herhangi bir türden olabilir. - - Aşağıdaki örnekte sözlüğün anahtarının bir liste olamadığına dikkat ediniz. - - >>> d = {[10, 20]: 100} - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'list' - - Görüldüğü gibi biz sözüğün anahtarını hash'able olmayan bir türden yapmak istersek xception (TypeError) oluşmaktadır. - - Demetler hash'able olabildiği için sözlük anahtarı yapılabilmektedir. Örneğin: - - >>> d = {(10, 20): 100} - >>> d - {(10, 20): 100} - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerde anahtarlar ve değerler herhangi bir türden olabilirler (anahtarlar hash'lenebilir olmak zorundadır). Anahtarların ve - değerlerin aslında aynı sözlük içerisinde aynı türden olması gerekmemektedir. Örneğin: - - d = {'ali': 100, 200: 'veli', 20: 30.2} - - Bu sözlük geçerlidir. Ancak uygulamada anahtarların ve değerlerin tutarlı biçimde aynı türlerden olması fayda sağlamaktadır. - Bazen bir anahtar verildiğinde birden fazla değerin elde edilmesi istenebilir. Bu durumda değer bir liste ya da demet gibi - veri yapısı olabilir. Örneğin: - - d = {'eskişehir': ['alpu', 'mihalıççık', 'seyitgazi'], 'ankara': ['keçiören', 'polatlı', 'çankaya'], - 'izmir': ['konak', 'buca', 'karşıyaka']} - - Burada anahtarlar şehir isimlerinden değerler de onların ilçelerini belirten listelerden oluşmaktadır. Programcı bir şehrin - ismini verdiğinde onun ilçelerini hızlı bir biçimde elde etmek istemiş olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'eskişehir': ['alpu', 'mihalıççık', 'seyitgazi'], 'ankara': ['keçiören', 'polatlı', 'çankaya'], - 'izmir': ['konak', 'buca', 'karşıyaka']} - -print(d) - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerin kendisi de hash'lenebilir değildir. Dolayısıyla bir sözlük başka bir sözlüğe anahtar yapılamaz. Ancak bir szölüğün - değeri bir sözlük olabilir. Örneğin: - - >>> d = {'içanadolu': {'eskişehir': 26, 'konya': 42, 'ankara': 6}, 'ege': {'izmir': 35, 'aydın': 9}, 'marmara': {'istanbul': 34, 'kocaeli': 41}} - >>> d - {'içanadolu': {'eskişehir': 26, 'konya': 42, 'ankara': 6}, 'ege': {'izmir': 35, 'aydın': 9}, 'marmara': {'istanbul': 34, 'kocaeli': 41}} - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listelerin, demetlerin, kümelerin ve sözlklerin elemanları sabit yerine değişken de olabilir. Python'da bu bağlamda - sabitle değişken arasında bir farklılık yoktur. Biz bir sabit oluşturduğumuzda zaten o sabit için önce bir nesne yaratılmakta sonra o nesnenin - adresi kullanılmaktadır. Bunun doğrudan yapılmasıyla dolaylı yapılması arasında bir farklılık yoktur. Örneğin: - - a = [10, 20, 30] - - Böyle bir liste nesnesi yaratıldığında listenin elemanları 10, 20 v3 30 değerlerinin tutulduğu int türden nesnelerin adreslerini - tutmaktadır. Bu işlemin aşağıdakinden bir farkı yktur: - - x = 10 - y = 20 - z = 30 - a = [x, y, z] - - Burada da liste elemanlarında yine 10, 20, 30 değerlerinin bulunduğu int türden nesnelerin adresleri tutulmaktadır. - - x = 26 - d = {x: 'eskişehir'} - - Biz her ne kadar atama işlemini bir operatör gibi ele almış olsak da aslında Python'da atama işlemi bir operatör değil - deyim statüsündedir. Dolayısıyla aşağıdaki bir durum geçerli değildir: - - >>> a = [x = 10 + 20, 20] - File "", line 1 - a = [x = 10 + 20, 20] - ^^^^^^^^^^^ - SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? - - Tabii walrus gerçekten atama işlemi yapıp değer üreten bir operatördür. Dolayısıyla aşağıdaki gibi bir durum geçerlidir: - - >>> a = [x := 10, 20] - >>> a - [10, 20] - >>> x - 10 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirtildiği gibi sözlükler dict isimli sınıfla temsil edilmiştir. dict sınıfının tür fonksiyonu olan dict fonksiyonu - ile de sözlük yaratabiliriz. Örneğin boş bir sözlük dict() biçiminde yaratılabilir: - - >>> d = dict() - >>> d - {} - - Boş küme parantezleri de boş sözlük yaratmak için kullanılabilmektedir. Örneğin: - - >>> d = {} - >>> d - {} - - Boş küme parantezlerinin boş bir küme yaratmadığına boş bir sözlük yarattığına dikkat ediniz. Boş bir kme yaratmak için - mecburen set() çağrısı kullanılmaktadır: - - >>> s = set() - >>> s - set() - -#------------------------------------------------------------------------------------------------------------------------ - -d = dict() -print(d) # {} - -d = {} -print(d) # {} - -#------------------------------------------------------------------------------------------------------------------------ - dict fonksiyonuna biz iki elemanlı dolaşılabilir nesnelerden oluşan dolaşılabilir bir nesneyi argüman olarak geçirirsek, - dict fonksiyonu bu nesneyi dolaşır. Her dolaşımda iki elemanlı bir dolaşılabilir nesne elde eder. O iki elemanlı dolaşılabilir - nesnenin ilk elemanı anahtar ikinci elemanı değer olacak biçimde bir sözlük nesnesi oluşturur. Örneğin: - - >>> a = [('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)] - >>> a - [('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)] - >>> d = dict(a) - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - - Burada a nesnesini dolaştığımızda biz iki elemanlı dolaşılabilir nesneler elde edilmektedir. İşte dict fonksiyonu bunlardan - sözlük yapmaktadır. Bu örnekte listenin elemanları birer demettir. Ancak tabii bunun tersi de söz konusu olabilirdi. Örneğin: - - >>> a = (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]) - >>> a - (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]) - >>> d = dict(a) - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - - Ya da örneğin: - - >>> a = (('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)) - >>> d = dict(a) - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - - Örneğin: - - >>> a = (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50], 'ak', 'tk', 'xy') - >>> d = dict(a) - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'a': 'k', 't': 'k', 'x': 'y'} - - Dolaşılabilir nesnenin elemanlarının iki elemandan daha fazla eleman içeren dolaşılabilir nesne olması durumu geçerli değildir. - Bu durumda exception (Valueerror) oluşmaktadır. Örneğin: - - >>> d = dict([('ali', 10), ('veli', 20, 30)]) - Traceback (most recent call last): - File "", line 1, in - ValueError: dictionary update sequence element #1 has length 3; 2 is required - -#------------------------------------------------------------------------------------------------------------------------ - -a = [('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)] - -d = dict(a) -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - - -a = [['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]] - -d = dict(a) -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -a = (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]) - -d = dict(a) -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -s = ['ak', 'sa', 'mk', 're'] - -d = dict(s) -print(d) - -#------------------------------------------------------------------------------------------------------------------------ - 18. Ders - 08/06/2022-Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - dict fonksiyonunda "değişken=değer" biçiminde argümanlar girdiğimizde dict bize o değişkenlerin string halini anahtar, - '=' operatörünün sağındakini de değer yaparak bir sözlük nesnesi oluşturur. Örneğin: - - >>> d = dict(x=10, y=20, z=30) - >>> d - {'x': 10, 'y': 20, 'z': 30} - >>> d = dict(eskişehir=26, istanbul=34, izmir=35) - >>> d - {'eskişehir': 26, 'istanbul': 34, 'izmir': 35} - - Bu biçimde sözlük nesnesi oluşturma işlemi seyrek olarak kullanılmaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - -d = dict(ali=10, veli=20, selami=30, ayşe=40, fatma=50) - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -#------------------------------------------------------------------------------------------------------------------------ - Burada değişken ismi yerine başka bir şey getirilemez. Örneğin aşağıdaki geçerli değildir: - - d = dict('ali'= 10, 'veli'=20, 'selami'=30) # geçersiz! - d = dict(10='ali', 20='veli', 30='selami') # geçersiz! - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sözlükte anahtarı verip değeri elde etmek için [...] operatörü kullanılır. Köşeli parantezin içerisine anahtar yazılır. - Operatör de bize o anahtarın değerini verir. [...] operatöründe verdiğimiz anahtar sözlükte yoksa exception (KeyError) oluşur. - Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> val = d['ayşe'] - >>> val - 40" - >>> val = d['fatma'] - >>> val - 50 - >>> val = d['sacit'] - Traceback (most recent call last): - File "", line 1, in - KeyError: 'sacit' - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -print(d) - -val = d['veli'] -print(val) # 20 - -val = d['fatma'] -print(val) # 50 - -#------------------------------------------------------------------------------------------------------------------------ - Tabii bir sözlükte anahtar ve değerlerin türleri hep aynı olmak zorunda değildir. Örneğin: - - >>> d = {'ali': 10, 20: 'veli', 1.5: 'selami'} - >>> d - {'ali': 10, 20: 'veli', 1.5: 'selami'} - >>> d['ali'] - 10 - >>> d[20] - 'veli' - >>> d[1.5] - 'selami' - -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 'veli': 20, 30: False} - -print(d) - -val = d['veli'] -print(val) # 20 - -val = d[10] -print(val) # ali - -#------------------------------------------------------------------------------------------------------------------------ - dict sınıfının get isimli metodu da anahtar verildiğinde değerin alınması için kullanılır. Ancak get anahtar sözlükte yoksa - exception oluşturmaz. İkinci parametresiyle girdiğimiz değere geri döner. Bu ikinci parametre için argüman girmeyebiliriz. Bu durumda - get anahtar bulunamazsa None değeri ile geri dönmektedir. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> val = d.get('ayşe') - >>> val - 40 - >>> val = d.get('sacit') - >>> print(val) - None - >>> val = d.get('ayşe', 'bulunamadı') - >>> val - 40 - >>> val = d.get('sacit', 'bulunamadı') - >>> val - 'bulunamadı' - >>> val = d.get('sacit', 0) - >>> val - 0 - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -print(d) - -val = d.get('selami', 'anahtar bulunamadı') -print(val) # 30 - -val = d.get('sacit', 'anahtar bulunamadı') -print(val) # Not found - -val = d.get('sacit') -print(val) # None - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerde "in" ve "not in" operatörleri belli bir anahtarın sözlükte olup olmadığını anlamak için kullanılabilir. - Sözlüklerde de tıpkı kümelerde olduğu gibi "in" ve "not in" operatörleri çok hızlı çalışmaktadır. Bu operatörler anahtarın - varlığını sorgulamaktadır, değerlerin değil. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> 'ayşe' in d - True - >>> 20 in d - False - >>> 20 not in d - True - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -print(d) - -result = 'selami' in d -print(result) # True - -result = 'fehmi' not in d -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - Built-in len fonksiyonuna biz bir sözlük nesnesini verirsek fonksiyon bize sözlükte kaç tane anahtar-değer çifti olduğunu verir. - Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> len(d) - 5 - >>> d = {} - >>> len(d) - 0 - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -result = len(d) -print(result) # 5 - -d = {} - -result = len(d) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Sözlükler de dolaşılabilir (iterable) nesnelerdir. Bir sözlük nesnesi dolaşıldığında yalnızca "anahtarlar" elde edilir. - Ancak bu anahtarların elde edilmesi Python 3.6'ya kadar herhangi bir sırada olabiliyordu. Ancak Python 3.6 ve sonrasında - artık sözlük nesneleri dolaşıldığında anahtarlar eklenme sırasıyla elde edilir hale getirildi. Yani mevcut Python süsümlerinde - bir sözlük nesnesi dolaşıldığında her zaman anahtarlar sözlükteki sıraya göre elde edilmektedir. Bu durumun Python 3.6'da - garanti edilmediğine dikkat ediniz. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> a = list(d) - File "", line 1 - a = list(d) - IndentationError: unexpected indent - >>> a = list(d) - >>> a - ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -a = list(d) -print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -#------------------------------------------------------------------------------------------------------------------------ - Sözlükler değiştirilebilir türlerdir. Biz sözlüğe yeni bir anahtar-değer çifti ekleyebiliriz, sözlükten bir anahtar-değer çiftini - silebiliriz. Mevcut bir anahtarın değerini değiştirebiliriz. - - Bir sözlükte anahtarlar "tektir (unique)". Ancak daha önce var olan bir anahtara ilişkin anahtar-değer çiftini sözlüğe eklemek - istersek bu durum herhangi bir soruna yol açmaz. Artık daha önceki anahtarın değeri değiştirilmiş olur. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ali': 40, 'selami': 50} - >>> d - {'ali': 40, 'veli': 20, 'selami': 50} - - Tabii sözlükteki bir anahtarın değiştirilmesi biçiminde bir işlem yoktur. Zaten bu işlem bir anahtar-değer çiftinin silinip yeni bir anahtar-değer - çiftinin eklenmesi ile aynı anlamdadır. - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'ali': 100} - -print(d) # {'ali': 100, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüğe yeni bir anahtar değer çifti eklemenin en basit yolu köşeli parantezli atama yapmaktır. Yani: - - d[key] = value - - ataması sözlüğe yeni bir anahtar-değer çifti eklemektedir. Ancak burada eğer anahtar zaten varsa anahtarın değeri değiştirilir. - Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d['sacit'] = 60 - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 60} - >>> d['selami'] = 100 - >>> d - {'ali': 10, 'veli': 20, 'selami': 100, 'ayşe': 40, 'fatma': 50, 'sacit': 60} - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -d['sacit'] = 60 - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 60} - -d['veli'] = 100 -print(d) # {'ali': 10, 'veli': 100, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 60} - -#------------------------------------------------------------------------------------------------------------------------ - Bir sözlüğe tek hamlede birden fazla anahtar-değer çifti eklemek için update metodu kullanılmaktadır. update metodunun parametresinin - dolaşılabilir bir nesne olması gerekir. Bu durumda bu nesne dolaşıldıkça iki elemanlı dolaşılabilir nesneler elde edilmelidir. - update metodu da onların birinci elemanlarını anahtar, ikinci elemanlarını değer yaparak bunları sözlüğe ekler. Yani update metodunun - parametresi dict fonksiyonundaki gibi olmalıdır. Tabii zaten anahtar sözlükte bulunuyorsa onun yine değeri değiştirilecektir. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d.update([('sacit', 100), ('mehmet', 200), ('sibel', 300)]) - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 100, 'mehmet': 200, 'sibel': 300} - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -d.update([('sibel', 60), ('kazım', 70), ('hasan', 90)]) - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sibel': 60, 'kazım': 70, 'hasan': 90} - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi bir sözlük nesnesi dolaşıldığında anahtarlar her zaman Python'ın 3.6 ve sonrasında sözlüğe - eklenme sırası dikkate alınarak elde edilmektedir. Yani biz bir sözlüğe köşeli paramtezlerle ya da update metodu ile ekleme - yaptığımızda bu eklenenlerin anahtarları sözlük dolaşıldığında bizim eklediğimiz sırada elde edilecektir. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d['ahmet'] = 100 - >>> d.update([('sacit', 200), ('mehmet', 300), ('sibel', 400)]) - >>> a = list(d) - >>> a - ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'ahmet', 'sacit', 'mehmet', 'sibel'] -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sözlükten bir anahtar-değer çiftini silmek için pop isimli metot kullanılır. pop metodu bizden anahtarı alır. Anahtar-değer - çiftini sözlükten siler. Ancak silinen değeri bize geri dönüş değeri olarak verir. pop metodu anahtarı bulamazsa ve tek argüman ile - çağrılmışsa exception (KeyError) oluşturmaktadır. Ancak pop iki argümanla da çağrılabilir. Bu durumda anahtar bulunamzsa - ikinci argümandaki değer geri döndürülmektedir. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> val = d.pop('selami') - >>> val - 30 - >>> d - {'ali': 10, 'veli': 20, 'ayşe': 40, 'fatma': 50} - >>> val = d.pop('sibel') - Traceback (most recent call last): - File "", line 1, in - KeyError: 'sibel' - >>> val = d.pop('sibel', 'anahtar yok') - >>> val - 'anahtar yok' - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -value = d.pop('ayşe') -print(value) # 40 - -print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'fatma': 50} - -value = d.pop('sacit', 'anahtar yok') -print(value) # anahtar yok - -#------------------------------------------------------------------------------------------------------------------------ - Bir sözlüğün tüm anahtarlarını keys isimli metotla, tüm değerlerini values isimli metotla elde edebiliriz. Bu metotlar bize - dolaşılabilir nesne verir. O nesne dolaşıldığında anahtarlar ve değerler elde edilecektir. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> d - {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - >>> a = list(d.keys()) - >>> a - ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - >>> b = list(d.values()) - >>> b - [10, 20, 30, 40, 50] - - Yine keys metodu Python 3.6 ve sonrasında bize anahtarları onların listeye eklenme sırasına göre vermektedir. Benzer biçimde - yine Python 3.6 ve sonrasında values metodu da bize değerleri anahtarların eklenme sırasına göre vermektedir. Ancak Python'un - önceki sürümleri bunu garanti etmemektedir. - - Burada keys metodunun aslında çok da gerekmediği ancak values metodunun gerektiği gibi bir sonuç çıkartabilirsiniz. Çünkü zaten - biz sözlük nesnesini dolaştığımızda onunanahtarlarını elde edebiliyorduk. -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -result = d.keys() -a = list(result) -print(a) # [10, 20, 30, 40, 50] - -result = d.values() -a = list(result) -print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -#------------------------------------------------------------------------------------------------------------------------ - Python'da çeşitli konularda karşımıza "view" nesnesi kavramı çıkabilmektedir. View nesnesi ana bir nesnenin bir bölümünü - ya da tamamını temsil eden bir nesnedir. Ancak ana nesne üzerinde değişiklik yapıldığında bu view nesnesi bu değişikliği görür. - Bazı view nesneleri read only, bazıları read/write olabilmektedir. Eğer bir view nesnesi read/write biçimdeyse o view nesnesi - üzerinde değişiklilkler yapıldığında bundan ana nesne etkilenecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - keys ve values metotlarının bize verdiği dolaşılabilir nesneler "view" nesneleridir. Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> result = d.keys() - >>> a = list(result) - >>> a - [10, 20, 30, 40, 50] - >>> d[60] = 'sacit' - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'sacit'} - >>> a = list(result) - >>> a - [10, 20, 30, 40, 50, 60] - - Burada biz keys() metodunu çağırarak view nesnesini elde ettik. Daha sonra sözlüğe eleman ekledik. Sonra bu view nesnesini - dolaştığımızda elemanın ekli olduğunu gördük. -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -keys = d.keys() -a = list(keys) -print(a) # [10, 20, 30, 40, 50] - -values = d.values() -a = list(values) -print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -d[60] = 'nurettin' - -a = list(keys) -print(a) # [10, 20, 30, 40, 50, 60] - -a = list(values) -print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'nurettin'] - -#------------------------------------------------------------------------------------------------------------------------ - dict sınıfının items isimli metodu dolaşılabilir bir view nesnesi verir. Bu nesne dolaşıldığında anahtar-değer çiftleri - iki elemanlı demetler biçiminde elde edilmektedir. Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> items = d.items() - >>> items - dict_items([(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')]) - >>> a = list(items) - >>> a - [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] - >>> t = tuple(items) - >>> t - ((10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')) - >>> s = set(items) - >>> s - {(30, 'selami'), (40, 'ayşe'), (20, 'veli'), (50, 'fatma'), (10, 'ali')} - >>> d[60] = 'sacit' - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'sacit'} - >>> a = list(items) - >>> a - [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma'), (60, 'sacit')] - - Yine Python 3.6'ya kadar items metodundan elde edilen dolaşılabilir nesne dolaşıldığında elemanların hangi sırada elde - edileceğinin bir garantisi yoktu. Ancak Python 3.6 ile birlikte artık dolaşımdan elde edilen değerler onların sözlüğe eklenme - sırasına göre olmaktdır. -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -result = d.items() - -a = list(result) -print(a) # [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] - -d[60] = 'sacit' - -a = list(result) -print(a) # [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma'), (60, 'sacit')] - -#------------------------------------------------------------------------------------------------------------------------ - dict sınıfının clear isimli metodu sözlük içerisindeki anahtar-değer çiftlerinin hepsini siler. Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> id(d) - 2500835703104 - >>> d.clear() - >>> d - {} - >>> id(d) - 2500835703104 - -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -print(d, id(d)) # {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} 2449660834816 - -d.clear() - -print(d, id(d)) # {} 2449660834816 - -#------------------------------------------------------------------------------------------------------------------------ - dict sınıfının copy isimli metodu sözlüğün bir kopyasını bize verir. Tabii kopya çıkartma işlemi yine "sığ kopyalama (shallow copy)" - biçiminde yapılmaktadır. Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> id(d) - 2500835703104 - >>> d.clear() - >>> d - {} - >>> id(d) - 2500835703104 - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> id(d) - 2500835738496 - >>> k = d.copy() - >>> k - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> id(k) - 2500835703104 - >>> id(d[10]) - 2500835729264 - >>> id(k[10]) - 2500835729264 - - Burada copy metodu ile çıkartılan kopya farklı bir sçzlük nesnesidir. Ancak iki sözlük nesnesindeki değerlerin (ve anahtarlarınd da) - aynı olduğu görülmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -k = d.copy() - -print(d, id(d)) -print(k, id(k)) - -#------------------------------------------------------------------------------------------------------------------------ - Daha önce sözlükten anahtarı verip değer elde etmenin iki yolunu görmüştük: [...] operatörü ve get metodu. Aslında bu iş için - üçüncü bir metot daha vardır. O da setdefault isimli metottur. setdefault metodu bize her zaman anahtarın değerini verir. Eğer anahtarı bulamazsa - setdefault o anahtarı bizim verdiğimiz değerler sözlüğe ekler eklediği anahtarın değerini bize verir. Yani setdefault anahtarı bulnazsa - ikinci parametresiyle belirtilen değerle anahtarı eklemekteve bu değeri bize vermektedir. Metotta ikinci parametre için argüman - girilmeyebilir. Eğer ikinci argüman girilmezse ve setdefault anahtarı bulamazsa anahtar için değer olarak sözlüğe None eklemektedir. - setdefault anahtarı bulursa ikinci argümanı hiç kullanmaz. Bu ikinci argüman "eğer anahtar bulamazsa" kullanılmaktadır. - Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> val = d.setdefault(20, 'kaya') - >>> val - 'veli' - >>> val = d.setdefault(60, 'kaya') - >>> val - 'kaya' - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'kaya'} - >>> val = d.setdefault(30) - >>> val - 'selami' - >>> val = d.setdefault(70) - >>> print(val) - None - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'kaya', 70: None} - -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -val = d.setdefault(20) -print(val) # veli - -result = d.setdefault(60, 'sacit') -print(result) # sacit - -print(d) # {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'sacit'} - -#------------------------------------------------------------------------------------------------------------------------ - Biz daha önce sözlükten pop metodu ile anahtar-değer çiftini silmiştik. Ancak del deyimi ile de sözlükten bir anahtar-değer - çiftini silebiliriz. Bunu sağlamak için del deyiminde anahtar yine köşeli parantez içerisinde verilir. Örneğin: - - del d[10] - - Burada del deyimi d sözlüğündeki 10 anahtarını ve o anahtara karşı gelen değeri sözlükten silecektir. Ancak anahtar sözlükte yoksa - del deyimi exception (KeyError) oluşturmaktadır. Örneğin: - - >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - >>> del d[40] - >>> d - {10: 'ali', 20: 'veli', 30: 'selami', 50: 'fatma'} - >>> del d[100] - Traceback (most recent call last): - File "", line 1, in - KeyError: 100 - -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -del d[40] - -print(d) # {10: 'ali', 20: 'veli', 30: 'selami', 50: 'fatma'} - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi string'ler Python'da str isimli bir sınıfla temsil edilmektedir. Yani biz string'leri tek tırnak, iki tırnak, - üç tek tırnak ya da üç çift tırnak ile yarattığımızda aslında str sınıfı türünden bir nesne yaratmış olmaktayız. Yine biz str - sınıfının "değiştirilemez (immutable)" bir sınıf olduğunu görmüştük. Biz string üzerinde onu yarattıktan sonra bir değişiklik - yapamamaktayız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tıpkı listeler gibi, demetler gibi string'ler de Python'da "sequence type" grubundadır. Yani adreta string'ler onları oluşturan - larakterden oluşan bir dizilim gibi düşünülebilir. - - String'lerin de karakterlerine tıpkı listelerde ve demetlerde olduğu gibi [...] operatörü ile erişebiliriz. Örneğin: - - >>> s = 'ankara' - >>> result = s[2] - >>> result - 'k' - >>> type(result) - - - Tabii Python'da Java, C# gibi bazı dillerde olduğu gibi tek bir karakteri temsil eden bir tür yoktur. Dolayısıyla biz Python'da - bir string'in belli bir indeksteki karakterini yine bir string olarak elde ederiz. - - String'ler değiştirilemez nesneler olduğu için biz bir string'in karakterlerini değiştiremeyiz. Örneğin: - - >>> s = 'ankara' - >>> c = s[2] - >>> c - 'k' - >>> s[2] = 'x' - Traceback (most recent call last): - File "", line 1, in - TypeError: 'str' object does not support item assignment - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir stirng'in karakter uzunluğu built-in len fonksiyonu ile elde edilebilir. Örneğin: - - >>> s = 'ankara' - >>> result = len(s) - >>> result - 6 - >>> k = '' - >>> result = len(k) - >>> result - 0 -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -result = len(s) -print(result) # 6 - -#------------------------------------------------------------------------------------------------------------------------ - Elemana erişimde yine negatif indeksler liste ve demetlerle aynı biçimde kullanılabilmektedir. Örneğin: - - >>> s = 'ankara' - >>> s[-1] - 'a' - >>> s[-2] - 'r' - - String'lerde dilimleme de tamamen listelerde ve demetler olduğu gibi yapılabilmektedir. Tabii bir string dilimlendiğinde - dilimleme işlemi sonucunda yine bir string elde edilmektedir. Örneğin: - - >>> s = 'ankara' - >>> s - 'ankara' - >>> result = s[2:6] - >>> result - 'kara' - >>> result = s[1:3] - >>> result - 'nk' - >>> result = s[2:-2] - >>> result - 'ka' - - Örneğin bir string'i ters çevirmek için yine aynı s[::-1] işlemini uygulayabiliriz: - - >>> s = 'ankara' - >>> k = s[::-1] - >>> s - 'ankara' - >>> k - 'arakna' - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -k = s[2:4] -print(k) # ka - -k = s[::-1] -print(k) # arakna - -k = s[-1] -print(k) # a - -#------------------------------------------------------------------------------------------------------------------------ - İki string '+' operatörü ile toplanabilir. Bu durumda yeni bir string nesnesi yaratılır. Bu yeni string nesnesi iki string'in - birleştirilmesinden oluşur. Örneğin: - - >>> s = 'ankara' - >>> k = 'izmir' - >>> result = s + k - >>> result - 'ankaraizmir' - - Java, C# gibi bazı dillerde bir string ile başka bir türden nesne toplanabilmektedir. Bu durumda o dillerde string olmayan tür - otomatik olarak string türüne dönüştürülüp string toplamı yapılmaktadır. Ancak Python'da bir özellik yoktur. Yani biz Python'da - örneğin bir string ile int bir değeri toplayamayız. (Halbuki bu işlem Java ve C# gibi bazı dillerde yapılabilmektedir.) - Bu tür durumlarda bizim açıkta diğer operand'ı str türüne dönüştürmemiz gerekir. Örneğin: - - >>> a = 10 - >>> s = 'a = ' + a - Traceback (most recent call last): - File "", line 1, in - TypeError: can only concatenate str (not "int") to str - >>> s = 'a = ' + str(a) - >>> s - 'a = 10' - -#------------------------------------------------------------------------------------------------------------------------ - -a = 'ankara' -b = 'izmir' - -c = a + b -print(c) # ankaraizmir - -#------------------------------------------------------------------------------------------------------------------------ - iki string toplandığında yeni bir string elde edilmektedir. Bu yeni string iki string'in uç uca eklenmesinden elde edilen yazıdan - oluşur. Ancak s += k gibi bir işlem mevcut s yazısının sonuna ekleme yapma anlamına gelmez. Çünkü string'ler değiştirilemez (immutable) - nesnelerdir. Dolayısıyla s += k tamamen s = s + k anlamına gelir. Anımsanacağı gibi bu durum değiştirilemez olan demetlerde de - böyleydi. Ancak listeler değiştirilebilir olduğu için orada += işlemi sona ekleme anlamına geliyordu. Örneğin: - - >>> s = 'ali' - >>> k = 'veli' - >>> id(s) - 1986218231280 - >>> id(k) - 1986218230640 - >>> s += k - >>> s - 'aliveli' - >>> id(s) - 1986218223088 - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' -k = 'izmir' - -result = s + k -print(result) # ankaraizmir - -print(id(s)) -s += k # s = s + k -print(id(s)) -print(s) # ankaraizmir - -#------------------------------------------------------------------------------------------------------------------------ - Bir string tıpkı listelerde ve demetler olduğu gibi "yineleme (repitition)" işlemine sokulabilir. Bu durumda string n defa - kendisiyle toplanmaktadır. Yine yineleme işleminde "çarpılan değer" 0 ya da negatif bir değerse boş string elde edilmektedir. - Örneğin: - - >>> s = '*' * 20 - >>> s - '********************' - >>> s = 'ali' * 5 - >>> s - 'alialialialiali' - >>> s = 'veli' * 0 - >>> s - '' - -#------------------------------------------------------------------------------------------------------------------------ - -s = '*' * 10 - -print(s) # ********** - -s = 'ali' * 3 -print(s) # alialiali - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte klavyeden bir yazı girilmektedir. Sonra o yazı üstte ve altta tireler olacak biçimde afiş gibi ekrana - yazdırılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -s = input('Bir yazı giriniz:') - -print('-' * len(s)) -print(s) -print('-' * len(s)) - -#------------------------------------------------------------------------------------------------------------------------ - Şimdi de str sınıfının metotları üzerinde duracağız. Ancak bu konuda bir noktaya dikkat çekmek istiyoruz. str sınıfının - "değiştirilemez" olduğunu yukarıda belirtmiştik. Bu durumda str sınıfının sanki yazıyı değiştirecekmiş gibi işlemler yapan - metotları aslında mevcut yazıyı değiştirmezler. Bize yeni bir yazı verirler. Zaten yaratılmış bir string nesnesi üzerinde - değişiklik yapmanın bir yolu yoktur. Biz aşağıda metotların sanki mevcut yazı üzerinde değişiklik yapıyormuş gibi bir - anlatım tarzı uygulayacağız. Aslında burada söylemek istediğimiz şey metodun değiştirilmiş yeni yazı ile geri döndüğüdür. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının capitalize isimli metodu parametresizdir. Bu metot bize aynı yazıdan yalnızca ilk harfi büyük olan yeni bir - yazı oluşturup onu vermektedir. Örneğin: - - >>> s = 'list sınıfı' - >>> k = s.capitalize() - >>> k - 'List sınıfı' - >>> s - 'list sınıfı' - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'bugün hava çok güzel' - -k = s.capitalize() -print(k) # Bugün hava çok güzel -print(s) # bugün hava çok güzelçok güzel - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının title isimli metodu yazının tüm sözcüklerinin ilk harfini büyük harf yapar (yani büyük harf yapılmış yazıyla - geri döner.) Bu metot da parametresizdir. Örneğin: - - >>> s = 'list sınıfı' - >>> k = s.title() - >>> k - 'List Sınıfı' - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'bugün hava çok güzel' - -k = s.title() -print(k) # Bugün Hava Çok Güzel - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 19. Ders - 13/06/2022-Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının center isimli metodu bir yazıyı belli bir genişlikte ortalar. Metodun birinci parametresi geri döndürülecek yazının - uzunluğunu belirtir. Yani yazı bu uzunluktaki yazının içerisinde ortalanacaktır. Eğer metot tek argümanla çağrılırsa yazının iki tarafındaki - boş alan boşluk karakteriyle doldurulur. Ancak metot iki argümanla da çağrılabilir. Bu durumda ikinci argüman tek karakterli bir string - olarak girilmek zorundadır. Bu ikinci argümanda belirtilen karakter iki tarafın doldurulacağı karakteri belirtir. Ancak eğer birinci - parametre ile belirtilen uzunluktan yazının uzunluğu çıkarıldığında tek sayı kalıyorsa bu durumda fazlalığın ne tarafa verileceği - konusunda "Python Standard Library Reference" içerisinde bir şey söylenmemiştir. Bu durum herhangi bir garantinin verilmediği - anlamına gelmektedir. Örneğin: - - >>> s = 'ankara' - >>> k = s.center(20) - >>> print(':' + k + ':') - : ankara : - >>> s = 'ali' - >>> k = s.center(7) - >>> print(':' + k + ':') - : ali : - >>> k = s.center(6) - >>> print(':' + k + ':') - : ali : - >>> k = s.center(20, '.') - >>> print(':' + k + ':') - :........ali.........: - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -k = s.center(20); -print(':' + k + ':') - -k = s.center(20, 'x'); -print(':' + k + ':') - -k = s.center(11, 'x'); -print(':' + k + ':') - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının find metodu bir yazı içerisinde bir yazıyı aramak ve yerini bulmak için kullanılmaktadır. Metot tek argümanla - çağrılırsa arama baştan sona kadar yapılmaktadır. Eğer yazı bulunursa find metodu bize yazının "ilk bulunduğu yerin" asıl yazıdaki - indeks numarasıyla geri döner. Eğer yazı bulunamazsa find -1 değeri ile geri dönmektdir. Örneğin: - - >>> s = 'ankara' - >>> result = s.find('k') - >>> result - 2 - >>> result = s.find('ar') - >>> result - 3 - >>> result = s.find('a') - >>> result - 0 - >>> result = s.find('x') - >>> result - -1 - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'bugün hava çok güzel, evet evet hava çok güzel' - -index = s.find('hava') -print(index) # 6 - -index = s.find('yarın') -print(index) # -1 - -#------------------------------------------------------------------------------------------------------------------------ - find metodu iki argümanlı da çağrılabilir. Bu durumda arama ikinci argümanla belirtilen indeksten başlatılır. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'bugün hava çok güzel, evet evet hava çok güzel' - -index = s.find('hava', 10) -print(index) # 32 - -#------------------------------------------------------------------------------------------------------------------------ - find metodu üç argümanlı da çağrılabilir. Bu durumda üçüncü argüman aramanın bitirileceği indeksi belirtir. Ancak bu indeks - aramaya dahil değildir. (Yani sanki önce dilimleme yapılıp bu dilimlemenin içerisinde find uygulanıyor gibi düşünebilirsiniz.) -#------------------------------------------------------------------------------------------------------------------------ - -s = 'bugün hava çok güzel, evet evet hava çok güzel' - -index = s.find('z', 10, 20) -print(index) # 17 - -#------------------------------------------------------------------------------------------------------------------------ - rfind isimli metot tamamen find metodu metodu gibidir. Ancak son bulunan yerin indeks numarasını verir. Başka bir deyişle - aramayı sondan başa doğru yapar. Yine metot iki ve üç argümanlı çağrılabilir. Argümanlar yine yazının başından itibaren indeks belirtir. - Yani bu duurmda ikinci ve üçüncü argümanlar sanki aranacak kısmın başını ve sonunu belirtiyor gibidir. (Yani sanki önce dilimleme - yapılıp bu dilimlemenin içerisinde rfind uygulanıyor gibi düşünebilirsiniz.) Örneğin: - - >>> s = 'adıyaman' - >>> pos = s.rfind('a', 1, 6) - >>> pos - 4 - >>> pos = s.rfind('a', 1) - >>> pos - 6 - >>> pos = s.rfind('a') - >>> pos - 6 - - Aşağıdaki örnekte Windows'tai bir yol ifadesinin hedefindeki dosya ismi elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -path = r'c:\windows\system\test.dll' - -index = path.rfind('\\') -fname = path[index + 1:] - -print(fname) # test.dll - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki programı girişi klavyeden isteyecek biçimde de değiştirebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -path = input('Bir yol ifadesi giriniz:') - -index = path.rfind('\\') -fname = path[index + 1:] - -print(fname) - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının find metodu ile aynı işlemleri yapan index isimli bir metodu, rfind metodu ile ile aynı işlemleri yapan - rindex isimli bir metodu da vardır. Yine index ve rindex de tek argümanla, iki argümanla ya da üç argümanla çağrılabilmektedir. - Bu bakımdan bunların find metotlarından bir farkı yoktur. index ve rindex metotlarının find ve rfind metotlarından tek farkları - başarısızlık durumunda exception index ve rindex metotlarının -1 ile geri dönmek yerine exception (ValueEroor) oluşturmasıdır. - Halbuki anımsanacağı üzere find ve rfind başarısızlık durumunda -1 değerine geri dönüyordu. Örneğin: - - >>> s = 'ankara' - >>> pos = s.index('a') - >>> pos - 0 - >>> pos = s.rindex('a') - >>> pos - 5 - >>> pos = s.rindex('x') - Traceback (most recent call last): - File "", line 1, in - ValueError: substring not found - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'bugün hava çok güzel' - -result = s.index('çok') -print(result) # 11 - -result = s.rindex('a') -k = s[result:] -print(k) # a çok güzel - -result = s.index('izmir') # exception oluşur -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - count metodu belli bir karakterin ya da yazının asıl yazı içerisinde kaç tane olduğu bilgisini bize verir. Örneğin: - - >>> s = 'ankara' - >>> result = s.count('a') - >>> result - 3 - >>> s = 'bugün hava çok güzel, evet hava çok güzel' - >>> result = s.count('hava') - >>> result - 2 - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'istanbul\'da iş buldum' - -result = s.count('bul') -print(result) # 2 - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının isxxx isimli bir grup metodu vardır. Bu metotlar yazının tüm karakterlerinin belirtilen koşulu sağlayıp sağlamadığına - bakmaktadır. Örneğin isalpha metodu yazının tüm karakterlerinin alfabetik karakter olup olmadığına bakar. Python 3'lü versüyonlardan - sonra tamamen UNICODE sisteme geçmiştir. Yani bu metotlar UNICODE tablodaki bütün dillerin harflerini bu testte dikkate alırlar. - Önemli isxxx metotları şunlardır: - - isalpha (alfabetik mi?) - isupper (büyük harf mi?) - islower (küçük harf mi?) - isspace (boşuk karakterlerinden mi?) - isalnum (alfanümerik karakter mi?) - ... - - Örneğin: - - >>> s = 'ankara' - >>> s.islower() - True - >>> s.isupper() - False - >>> s = '1234' - >>> s.isdigit() - True - >>> s = 'ali Ankara' - >>> s.islower() - False - >>> s = '1abc' - >>> s.isidentifier() - False - >>> s = 'abc1' - >>> s.isidentifier() - True - >>> s = ' \t \n ' - >>> s.isspace() - True - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ağrı-04' -k = 'AĞRIDağı' -m = '1293456789' -r = ' ' - -result = s.islower() -print(result) # True - -result = k.isupper() -print(result) # False - -result = m.isdigit() -print(result) # True - -result = k.isalpha() -print(result) # True - -result = r.isspace() -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının join isimli metodu argüman olarak dolaşılabilir bir nesne alır. Ancak bu dolaşılabilir nesne dolaşıldıkça - string'ler elde edilmelidir. join metodu bu dolaşılabilir nesnenin elemanlarını aralarına metodun çağrılmasında belirtilen yazıyı - ayraç yaparak bir yazı biçiminde birleştirir ve bize böyle bir yazı verir. Örneğin: - - >>> s = ', ' - >>> result = s.join(['ali', 'veli', 'selami']) - >>> result - 'ali, veli, selami' - >>> result = ', '.join(['ali', 'veli', 'selami']) - >>> result - 'ali, veli, selami' - >>> result = '\n'.join(['adana', 'adıyaman', 'afyon']) - >>> result - 'adana\nadıyaman\nafyon' - >>> print(result) - adana - adıyaman - afyon - >>> result = ''.join(['ali', 'veli', 'selami']) - >>> result - 'aliveliselami' - >>> result = '-'.join('ankara') - >>> result - 'a-n-k-a-r-a' - -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] -s = 'xxx' - -k = s.join(names) -print(k) # alixxxvelixxxselamixxxayşexxxfatma - -s = ', ' -k = s.join(names) -print(k) # ali, veli, selami, ayşe, fatma - -k = ' '.join(names) -print(k) # ali veli selami ayşe fatma - -k = ''.join(names) -print(k) # aliveliselamiayşefatma - -k = ' '.join('ankara') -print(k) # a n k a r a - -k = '\n'.join(names) -print(k) - -""" -ali -veli -selami -ayşe -fatma -""" - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının split metodu adeta join metodunun tersi gibidir. split yazının ayrıştırılacağı yazıyı parametre olarak alır. - Bu parametre yazıyı ayraç kabul ederek yazıyı parçalara ayırır ve bu parçalardan bir string listesi yapar bize o string listesini - verir. split parametresiz kullanılırsa tüm boşluk karakterlerini ayıraç kabul eder. Yani bir yazıyı tüm boşluklardan ayrıştırmak - için split parametresiz kullanılabilir. Örneğin: - - >>> s = 'ali, veli, selami, ayşe, fatma' - >>> result = s.split(', ') - >>> result - ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - >>> result = s.split(',') - >>> result - ['ali', ' veli', ' selami', ' ayşe', ' fatma'] - >>> result = s.split(' ') - >>> result - ['ali,', 'veli,', 'selami,', 'ayşe,', 'fatma'] - >>> result = s.split('xxx') - >>> result - ['ali, veli, selami, ayşe, fatma'] - >>> s = 'ali,,,veli,,,selami' - >>> result = s.split(',') - >>> result - ['ali', '', '', 'veli', '', '', 'selami'] - >>> s = 'ali veli \n\n\t \t selami \t ' - >>> result = s.split() - >>> result - ['ali', 'veli', 'selami'] - >>> - - join ile split metotlarının adresta ters işlemler yaptıklarına dikkat ediniz: - - >>> names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - >>> result = ', '.join(names).split(', ') - >>> result - ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ali, veli, selami, ayşe, fatma' - -a = s.split(',') -print(a) # ['ali', ' veli', ' selami', ' ayşe', ' fatma'] - -a = s.split(', ') -print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -a = s.split(' ') -print(a) # ['ali,', 'veli,', 'selami,', 'ayşe,', 'fatma'] - -s = 'ali,,,veli,,,selami' -a = s.split(',') -print(a) # ['ali', '', '', 'veli', '', '', 'selami'] - -s = 'ali veli selami' -a = s.split(' ') -print(a) # ['ali', '', '', 'veli', '', '', 'selami'] - -s = 'ali veli \t\t\t selami' -a = s.split() -print(a) # ['ali', 'veli', 'selami'] - -s = 'ali, veli, selami' -a = s.split('xxx') -print(a) # ['ali, veli, selami'] - -s = 'ali, veli, selami, ayşe, fatma' -a = s.split(', ') -k = ', '.join(a) -print(k) # ali, veli, selami, ayşe, fatma - -d = '13/06/2022' -a = d.split('/') -print(a) # ['13', '06', '2022'] - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının strip isimli metodu argümansız çağrılırsa metot yazının başındaki ve sonundaki boşluk karakterlerini atar. - Bu işlev pek çok programama dilinde "trim" isimli fonksiyonlarla yapılmaktadır. strip fonksiyonu parametreli kullanılırsa - strip edilecek karakteri de belirlememize olanak sağlar. Burada argüman birden fazla karakter olarak girilirse tüm bu karakterler - bireysel olarak strip karakterleri olarak ele alınmaktadır. Örneğin: - - >>> s = ' Ali Serçe ' - >>> k = s.strip() - >>> print(':' + k + ':') - :Ali Serçe: - >>> s = '........Ali Serçe.......' - >>> k = s.strip('.') - >>> print(':' + k + ':') - :Ali Serçe: - >>> s = '.,.,.,.,.,Ali Serçe.,.,.,.,.' - >>> k = s.strip('.,') - >>> print(':' + k + ':') - :Ali Serçe: - >>> k = s.strip(',.') - >>> print(':' + k + ':') - :Ali Serçe: - -#------------------------------------------------------------------------------------------------------------------------ - -s = ' ankara ' - -k = s.strip() -print(':' + k + ':') # :ankara: - -s = 'xxxyyyyyyyyxxxankaraxxxxxxxxxxxxxyyyyyy' -k = s.strip('xy') -print(':' + k + ':') # :ankara: - -s = ' ankara izmir ' -k = s.strip() -print(':' + k + ':') # :ankara izmir: - -#------------------------------------------------------------------------------------------------------------------------ - strip metodunun lstrip ve rstrip isimli benzerleri vardır. lstrip yalnızca sol taraftaki strip karakterlerini atar, rstrip - ise yalnızca sağ taraftaki strip karakterlerini atar. (strip her iki taraftaki strip karakterlerini atmaktadır).Örneğin: - - >>> s = ' Ali Serçe ' - >>> k = s.lstrip() - >>> print(':' + k + ':') - :Ali Serçe : - >>> k = s.rstrip() - >>> print(':' + k + ':') - : Ali Serçe: - >>> s = '......,,,,,,,Ali Serçe,,,,,.....' - >>> k = s.lstrip('.,') - >>> print(':' + k + ':') - :Ali Serçe,,,,,.....: - >>> k = s.rstrip('.,') - >>> print(':' + k + ':') - :......,,,,,,,Ali Serçe: - -#------------------------------------------------------------------------------------------------------------------------ - -s = ' bugün hava çok güzel ' - -result = s.lstrip() -print(':' + result + ':') # :bugün hava çok güzel : - -result = s.rstrip() -print(':' + result + ':') # : bugün hava çok güzel: - -#------------------------------------------------------------------------------------------------------------------------ - 20. Ders - 15/06/2022-Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının partition isimli metodu yazı içinde aranacak bir yazıyı parametre olarak alır. Yazıyı bulursa üçlü bir - demete geri döner. Demetin ilk elemanı yazıda bulunan yerin sol tarafındaki yazıdan, sonraki elemanı bulunan yazının kendisinden ve - sonraki elemanı da bulunan yazının sağ tarafındaki yazıdan oluşacaktır. Örneğin: - - >>> s = 'aliveliselami' - >>> s - 'aliveliselami' - >>> result = s.partition('veli') - >>> result - ('ali', 'veli', 'selami') - >>> s = 'ali veli selami' - >>> s = ' ali veli selami ' - >>> result = s.partition('veli') - >>> result - (' ali ', 'veli', ' selami ') - >>> s = 'aliveliselami' - >>> result = s.partition('selami') - >>> result - ('aliveli', 'selami', '') - >>> result = s.partition('ali') - >>> result - ('', 'ali', 'veliselami') - >>> result = s.partition('xxx') - >>> result - ('aliveliselami', '', '') - - Burada eğer yazı asıl yazının başında bulunursa demetin ilk elemanının boş string olduğuna, sonunda bulunursa demetin son elemanın boş string - olduğuna dikkat ediniz. Eğer Yazı asıl yazıda bulunamazsa demetin ilk elemanı asıl yazıdan ikinci ve üçüncü elemanları boş - string'ten oluşacaktır. - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankaraizmiristanbul' - -t = s.partition('izmir') -print(t) # ('ankara', 'izmir', 'istanbul') - -left, center, right = s.partition('izmir') -print(left) # ankara -print(center) # izmir -print(right) # istanbul - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirtitğimiz gibi eğer partition parametresiyle belirtilen yazıyı bulamazsa bu durumda yine üçlü demete - geri döner. Demetin birinci elemanı tüm yazıdan, ikinci ve üçüncü elemanları boş string'lerden oluşur. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankaraizmiristanbul' - -t = s.partition('xxxx') -print(t) # ('ankaraizmiristanbul', '', '') - -t = s.partition('ankara') -print(t) # ('', 'ankara', 'izmiristanbul') - -t = s.partition('istanbul') -print(t) # ('ankaraizmir', 'istanbul', '') - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının replace isimli metodu bir yazı içerisinde belli bir yazıyı başka yazıyla yer değiştirir. Metodun iki parametresi vardır. - Birinci parametre aranacak yazıyı, ikinci parametre yer değiştirilecek yazıyı belirtir. Örneğin: - - >>> s = 'ali top at, ali ip atla' - >>> k = s.replace('ali', 'veli') - >>> k - 'veli top at, veli ip atla' - >>> s - 'ali top at, ali ip atla' - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'istanbul istanbul güzel istanbul' - -k = s.replace('istanbul', 'ankara') -print(k) # ankara ankara güzel ankara - -#------------------------------------------------------------------------------------------------------------------------ - replace metodunun isteğe bağlı bir üçüncü parametresi de vardır. Bu parametre girilecekse int bir sayı olarak girilmelidir. - Bu durumda yalnızca replace burada belirtilen sayıda değiştirme yapar. Örneğin: - - >>> s = 'istanbul istanbul güzel istanbul' - >>> k = s.replace('istanbul', 'ankara', 2) - >>> k - 'ankara ankara güzel istanbul' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının startswith isimli metodu yazının parametresiyle belirtilen yazı ile başlayıp başlamadığını belirlemek için - kullanılmaktadır. Örneğin: - - >>> s = '- bu bir denemedir' - >>> result = s.startswith('-') - >>> result - True - >>> result = s.startswith('- ') - >>> result - True - >>> result = s.startswith('- xxx') - >>> result - False - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -result = s.startswith('an') -print(result) # True - -result = s.startswith('anka') -print(result) # True - -result = s.startswith('anki') -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının endswith isimli metodu yazının parametresiyle belirtilen yazı ile bitip bitmediğini belirlemek için kullanılmaktadır. - Örneğin: - - >>> s = 'bu bir denemedir...' - >>> result = s.endswith('...') - >>> result - True - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -result = s.endswith('ra') -print(result) # True - -result = s.endswith('kara') -print(result) # True - -result = s.endswith('an') -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - iki string toplandığında yeni bir string elde edilmektedir. Bu yeni string iki string'in uç uca eklenmesinden elde edilen yazıdan - oluşur. Ancak s += k gibi bir işlem mevcut s yazısının sonuna ekleme yapma anlamına gelmez. Çünkü string'ler değiştirilemez (immutable) - nesnelerdir. Dolayısıyla s += k tamamen s = s + k anlamına gelir. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' -k = 'izmir' - -result = s + k -print(result) # ankaraizmir - -print(id(s)) -s += k # s = s + k -print(id(s)) -print(s) # ankaraizmir - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının upper metodu yazıdaki küçük harfleri büyük harflere lower metodu da büük harfleri küçük harflere dönüştürür. - Ancak upper ve lower büyük ya da küçük harf olamayan karakterleri olduğu gibi bırakır. Örneğin: - - >>> s = 'AnKaRa-06' - >>> k = s.upper() - >>> k - 'ANKARA-06' - >>> k = s.lower() - >>> k - 'ankara-06' - - Türkçe'deki küçük harf 'i'nin UNICODE büyük harf karşılığı 'I' biçimindedir. Benzer biçimde büyük harf 'I' karakterinin de - küçük harf karşılığı 'i' biçimindedir. Bu da bazen Türkçe yazıla riçin istediğimiz sonucun elde edilmesini engeller. Örneğin: - - >>> s = 'iznik gölü' - >>> k = s.upper() - >>> k - 'IZNIK GÖLÜ' - - Bu problem şöyle çözülebilir: - - >>> s = 'iznik gölü' - >>> k = s.replace('i', 'İ').upper() - >>> k - 'İZNİK GÖLÜ' - -#------------------------------------------------------------------------------------------------------------------------ - -s = 'AğRı dAğI-04' - -k = s.upper() -print(k) # AĞRI DAĞI-04 - -k = s.lower() -print(k) # ağrı daği-04 - -#------------------------------------------------------------------------------------------------------------------------ - İki string >, >=, <, <=, == ve != operatörleriyle karşılaştırılabilir. Karşılaştırma leksikografik olarak yapılmaktadır. - Leksikografik karşılaştırma sözlükteki sıraya göre karşılaştırmadır. Yani iki yazıda karşılıklı karakterler aynı olduğu sürece - ilerlenir. İlk aynı olmayan karakterlerin UNICODE sıra numaralarına bakılır. Hangi karakterin UNICODE sıra numarası büyükse - o yazı diğerinden büyüktür. Tabii daha çok iki string'in eşit olması ya da eşit olmaması biçiminde karşılaştırmalar yapılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -password = 'maviay' -s = input('Enter password:') - -result = s == password -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Karşılaştırmanın Türkçe'ye göre değil UNICODE tabloya göre yapıldığına dikkat ediniz. Örneğin: - - >>> s = 'aysel' - >>> k = 'ayçe' - >>> result = s > k - >>> result - False - - Eğer karşılaştımr Türkçe karakterlere göre yapılsaydı "aysel" yazısı "ayçe" yazısından büyük olurdu. Ancak UNICODE - tabloda 'ç' karakteri 's' karakterinden daha ileride bulunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'aysel' -k = 'ayçe' - -result = s > k -print(result) # False 's'nin UNICODE numarası 'ç'nin UNICODE numarasından küçük - -#------------------------------------------------------------------------------------------------------------------------ - Tabii bir yazı diğer yazının ilk bölümü ile aynı ise bu durumda uzun olan yazı daha büyük olur. Yani örneğin 'aliye' yazısı - 'ali' yazısından daha büyüktür. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ali' -k = 'aliye' - -result = k > s -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - UNICODE tabloda önce büyük harfler sonra küçük harfler gelmektedir. Zaten UNICODE toblonun ilk 128 karakteri standart ASCII - tablosu ile aynıdır. Sonraki 128 karakteri ASCII Latin-1 Code Page'i ile aynıdır. Büyük harflerin tabloda önce gelmesi ASCII - tablosundan kaynaklanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ali' -k = 'Ali' - -result = s > k -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir karakterin UNOCODE tablodaki sıra numarası ord isimli built-in fonksiyonla elde edilebilmektedir. Örneğin: - - >>> result = ord('A') - >>> result - 65 - >>> result = ord('a') - >>> result - 97 - >>> result = ord('0') - >>> result - 48 - >>> result = ord('5') - ord('0') - >>> result - 5 - - ord fonksiyonuna biz tek karakterli bir string'i argüman olarak verebiliriz. Aksi takdirde exception (TypeError) oluşur. Örneğin: - - >>> result = ord('ali') - Traceback (most recent call last): - File "", line 1, in - TypeError: ord() expected a character, but string of length 3 found - - (Fonkisyonun birden fazla karakter için TypeError ile exception oluşturması ve exception mesajı biraz uygunsuz olmuştur. Zira Python'da - char diye bir tür yoktur. Dolayısıyla fonksiyonun TypeError yerine ValueError exception'ı oluşturması daha uygun gözükmektedir.) - - (C, C++, Java ve C# gibi dillerde bir karakteri tek tırnak içerisine aldığımızda zaten bu ifade o karakterin ilgili tablodaki - sıra numarasını belirtmektedir. Bu dillerde tek karakterden oluşan yazılar "char" isimli bir türdendir. Bu char türü de zaten - bu dillerde aritmektik işlemlere sokulabilmektedir. Dolayısıyla bu dillerde ord gibi bir fonksiyona gereksinim duyulmamaktadır. ) -#------------------------------------------------------------------------------------------------------------------------ - -s = input('Bir karakter giriniz:') -result = ord(s) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - ord fonksiyonunun yaptığı şeyin terci chr fonksiyonuyla yapılabilmektedir. chr fonksiyonu bizden int bir değeri parametre - olarak alır. Onun UNICODE tablodaki karakter karşılığını tek elemanlı bir string olarak verir. -#------------------------------------------------------------------------------------------------------------------------ - -n = int(input('Bir karakter numarası giriniz:')) -result = chr(n) -print(result) - -k = ord(result) -print(k) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki gibi üç değşiken olsun: - - a = 10 - b = 20 - c = 30 - - Burada a, b, c'nin değerlerini bilmediğimizi varsayalım. Ve aşağıdaki gibi bir yazıyı ekrana basmak isteyelim: - - a = 10, b = 20, c = 30 - - Bunu şimdiye kadarki bilgilerimizle ancak string'leri toplayıp yapabiliriz: - - a = 10 - b = 20 - c = 30 - - - s = 'a = ' + str(a) + ', b = ' + str(b) + ', c = ' + str(c) - print(s) - - Ya da örneğin: - - print('a = ' + str(a) + ', b = ' + str(b) + ', c = ' + str(c)) - - Bu tür yazımlara "formatlı yazım" denilmektedir. Formatlı azım işlemleri çok sık karşımıza çıkmaktadır. Ancak görüldüğü - gibi string toplamlarıyla formatlı yazıların oluşturulması oldukça zordur. Formatlı yazım için Python'da zaman içerisinde - üç değişik yöntem standart kütüphaneye ve dile dahil edilmiştir: - - 1) % operatör metodu yoluyla formatlı yazım - 2) str sınıfının format metoduyla formatlı yazım - 3) string enterpolasyonu yoluyla formatlı yazım - - String enterpolasyonu Python'a çok sonralı 3.6 versiyonuyla girmiştir. String enterpolasyonu diğer iki yönteme göre hem daha pratik hem de - daha hızlı bir formatlama sunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - str sınıfının format isimli metodu istenildiği kadar çok argüman alabilmektedir. Bu metot yazı içerisindeki {n} kalıbını yer tutucu - olarak kabul eder ve bu {n} yer tutucusu yerine format metodunun n'inci argümanın değerini yerleştirir. format metodunun ilk argümanı 0'ıncı - argümanıdır. Örneğin: - - >>> a = 10; b = 20; c = 30 - >>> s = 'a = {0}, b = {1}, c = {2}'.format(a, b, c) - >>> print(s) - a = 10, b = 20, c = 30 - - Burada {0} a ile, {1} b ile ve {2} c ile eşleştirilmiştir. Genellikle programcılar bu format metodunu doğrudan print - fonksiyonun içerisine yerleştirirler. Örneğin: - - >>> print('a = {0}, b = {1}, c = {2}'.format(a, b, c)) - a = 10, b = 20, c = 30 - - Format sentaksındaki sayıların peşi sıra gelme gibi bir zorunluluğu yoktur. Aörneğin: - - >>> print('a = {2}, b = {1}, c = {0}'.format(a, b, c)) - a = 30, b = 20, c = 10 - - Örneğin: - - >>> a = 10; b = 20; c = 30 - >>> print('{0}{1}{2}'.format(a, b, c)) - 102030 - - Uygunsuz durumlarda exception oluşmaktadır. Örneğin yer tutucu içerisindeki sayı argüman sayısından büyükse exception oluşur: - - >> print('a = {10}, b = {1}, c = {2}'.format(a, b, c)) - Traceback (most recent call last): - File "", line 1, in - IndexError: Replacement index 10 out of range for positional args tuple - - Ancak format metodundaki argümanlar string'te kullanılmamışsa bu durum bir soruna yol açmamaktadır. Örneğin: - - >>> a = 10; b = 20; c = 30 - >>> print('{0}'.format(a, b, c)) - 10 - -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 30 - -s = 'a = {0}, b = {1}, c = {2}' -k = s.format(a, b, c) -print(k) # a = 10, b = 20, c = 30 - -s = '{2} {0} {1}' -k = s.format(a, b, c) -print(k) # 30 10 20 - -print('a = {0}, b = {1}, c = {2}'.format(a, b, c)) # a = 10, b = 20, c = 30 - -#------------------------------------------------------------------------------------------------------------------------ - Aynı numaralı yer tutucu yazı içerisinde birden fazla kez kullanılabilir. Örneğin: - - >>> a = 10 - >>> b = 20 - >>> print('{0}, {1}, {0}'.format(a, b)) - 10, 20, 10 -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 30 - -print('{2} {2} {1} {0} {1}'.format(a, b, c)) # 30 30 20 10 20 - -#------------------------------------------------------------------------------------------------------------------------ - format metodundaki argüman türleri herhangi bir türden olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -city = 'Eskişehir' -plate = 26 -region = 'İç Anadolu' - -print('{0}-{1}-{2}'.format(city, plate, region)) # Eskişehir-26-İç Anadolu - -#------------------------------------------------------------------------------------------------------------------------ - Yer turucularda küme parantezinin içi boş bırakılabilir. Bu durumda her boş küme parantezi argümanlarla sırasıyla eşleştirilmektedir. - Ancak yazıdaki bir yer tutucu numaralı diğeri numarasız olamaz. Ya tüm yer tutucular numaralı olmalı ya da hiçbiri numaralı olmamalıdır. - Örneğin: - - >>> a = 10 - >>> b = 20 - >>> c = 30 - >>> print('a = {}, b = {}, c = {}'.format(a, b, c)) - a = 10, b = 20, c = 30 - >>> print('a = {}, b = {}, c = {}'.format(a, b, c)) - - Numarısız yer tutucu kullanımı genellikle tercih edilmektedir. Ancak tabii biçimde aynı argüman birden fazla kez kullanılamamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 -c = 20 - -print('a = {}, b = {}, c = {}'.format(a, b, c)) # a = 10, b = 20, c = 20 - -#------------------------------------------------------------------------------------------------------------------------ - fromat metodunda aslında daha ayrıntılı belirlemeler yapılabilmektedir. Biz şimdilik format metodunun bu ayrıntıları üzerinde - durmayacağız. Bunun için Internet'te çeşitli kaynaklara ya da Python Library Reference içerisindeki aşağıdaki kısma göz gezdirebilirsiniz: - - https://docs.python.org/3/reference/lexical_analysis.html#f-strings - - Ayrıntılı formatlama işlemi küme parantezi içerisinde ':' sentaksı ile yapılmaktadır. Örneğin: - - day = 8 - month = 5 - year = 2007 - - print('{0:02d}/{1:02d}/{2:04d}'.format(day, month, year)) # 08/05/2007 - - Örneğin biz değerleri değişik sayı sistemlerinde yazdırabiliriz: - - x = 100 - - print('{:X}'.format(x)) # 64 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 21. Ders - 20/06/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'a 3.6 ile birlikte "string enterpolasyonu" denilen bir özellik de eklenmiştir. Aslında string enterpolasyonları - bazı programlama dillerinde uzun süredir bulunmaktaydı. Artık yavaş yavaş pek çok programlama diline bu özellik sokulmuştur. - - String enterpolasyonu bir string'e ona yapışık bir 'f' ya da 'F' harfi önek getirilerek oluşturulmaktadır. String enterpolasyonunda - yapılanlar string sınıfının format metoduna benzerdir. Ancak string enterpolasyonunda küme parantezleri içerisinde bir - ifade bulunmak zorundadır. Yorumlayıcı akış sırasında string enterpolasyonu ile karşılaştığında bizzat kendisi bu ifadenin - değerini o anda hesaplayarak yer tutucu yerine yerleştirir. Örneğin: - - a = 10 - b = 20 - - print(f'a = {a}, b = {b}') # yorumlayıcı buradaki string'i 'a = 10, b = 20' haline dönüştürür. - - str sınıfının format metodu ismi üzerinde bir metottur. Yani bu metot yoluyla formatlama yapılacağı zaman formatlama - program çalışırken metot tarafından yapılmaktadır. Oysa string enterpolasyonları doğrudan yorumlayıcı tarafından program - çalıştırılırken işleme sokulur. Bu nedenle string enterpolasyonları hem daha kolay bir yazım sunmakta hem de göreli olarak daha hızlı - sonuç vermektedir. Dolayısıyla artık Python programcılları bu tarz formatlamalar için her zaman string enterpolasyonlarını tercih etmelidir. - Örneğin: - - >>> a = 10; b = 20; c = 30 - >>> s = f'a = {a}, b = {b * b}, c = {c}' - >>> s - 'a = 10, b = 400, c = 30' - >>> print(s) - a = 10, b = 400, c = 30 - -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -print(f'a = {a}, b = {b}') # a = 10, b = 20 - -#------------------------------------------------------------------------------------------------------------------------ - Tabii string enterpolasyonlarında küme parantezlerinin içerisinde aslında herhangi bir ifade olabilir. Örneğin: - - import math - - x = 10 - print(f"karekök {x} = {math.sqrt(x)}") # karekök 10 = 3.1622776601683795 -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -print(f"a'nın karesi' = {a * a}, b'nin karesi = {b * b}") # a'nın karesi' = 100, b'nin karesi = 400 -print(f"a'nın karekökü ' = {math.sqrt(a)}, b'nin karekökü = {math.sqrt(b)}") # a'nın karekökü ' = 3.1622776601683795, b'nin karekökü = 4.47213595499958 - -#------------------------------------------------------------------------------------------------------------------------ - string enterpolasyonunda küme parantezlerinin içerisnnde gerçek tırnaklar kullanılacaksa asıl string'in tırnağının bu tırnaklarla - karışması engellenmelidir. Örneğin: - - >>> s = f'{', '.join(a)}' - File "", line 1 - s = f'{', '.join(a)}' - ^ - SyntaxError: f-string: expecting '}' - - Burada f'{', '.join(a)}' biçimindeki string enterpolasyonu geçerli değildir. Çünkü küme parantezlerinin içerisinde de - tek tırnak karakteri kullanılmıştır. Tabii biz bu tür durumlarda string'in tırnaklarını çft tırnak ya da üç tırnak yaparak - sorunu çözebiliriz. Örneğin: - - >>> s = f"{', '.join(a)}" - >>> s - 'ali, veli, selami, ayşe, fatma' - - String enterpolasyonunda küme parantezleri içerisinde ters karakteri kullanılamamaktadır. Yani aşağıdaki gibi bir - string enterpolasyonu geçerli değildir: - - s = f'{\', \'.join(a)}' - -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -print(f"{', '.join(a)}") # ali, veli, selami, ayşe, fatma - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirtitğimiz gibi artık (3.6 ve sonrasında) string enterpolasyonu str sınıfının format metoduna göre tercih - edilmelidir. Ancak yine de seyrek bazı durumlarda str sınıfının format metodu daha kolay bir kullanım sunabilmektedir. Örneğin - string içerisinde aynı ifadenin değerinin birden fazla kez kullanılması durumunda string enterpolasyonunda küme parantezleri - içerisinde bu ifadenin tekrar tekrar yazılması gerekir. Halbuki str sınıfının format metodunda bu işlem daha az tuşa basılarka - yapılabilir. Örneğin: - - x = 10 - - print(f'{x * x}, {x * x}, {x * x}, {x * x}, {x * x}') - - print('{0}, {0}, {0}, {0}, {0}'.format(x * x)) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - String enterpolasyonundaki format kuralları str sınıfının format metodunndaki gibidir. Örneğin: - - day = 8 - month = 7 - year = 2009 - - print(f'{day:02d}/{month:02d}/{year:04d}') - -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -print(f'{a:<20}{b:>20}') # 10 20 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'ın 2'li versiyonlarında string formatlama string sınıfının % operatör metodu ile yapılıyordu. O zamanlar C Programlama - Dili bütün programlama dillerini etkisi altına almıştı ve bu biçimdeki formatlama aslında C Programlama Dilindeki "printf" gibi, - "scanf" fonksiyonlardan esinlenerek oluşturulmuştu. Bu çeşit formatlamada yer tutucular C'nin printf fonksiyonunda olduğu gibi - % karakterleriyle oluşturulmaktadır. Örneğin %d "int türünü 10'luk sistemde yazdır", %f "float türünü 10'luk sistemde yazdır anlamına gelmektedir. - Bu yontemde string içerisindeki % karakterleri % operatörünün sağındaki demetin elemanlarıyla eşleştirilmektedir. - Bu formatlama biçiminde yine printf fonksiyonundaki formatlama biçimleri ("%8.3f" gibi) burada da kullanılabilmektedir. Ancak artık bu yöntem Python'da - eski bir yöntem olarak değerlendirilmektedir. Yeni programlarda artık bu tarzda formatlama tercih edilmemektedir. Örneğin: - - >>> x = 10 - >>> y = 20 - >>> print('x = %d, y = %d' % (x, y)) - x = 10, y = 20 - - Bu biçimdeki formatlamada % operatörünün solunda string'in sağında ise bir demetin bulunduğuna dikkat ediniz. Örneğin: - - >>> import math - >>> x = 0.5 - >>> print('sin(%.1f) = %.2f' % (x, math.sin(x))) - sin(0.5) = 0.48 - - Bu formatlama biçiminde % operatörünün sağında ya tek bir ifade bulundurulur ya da bir demet biçiminde birden fazla ifade bulundurulur. - Yani başka bir deyişle tek bir değeri formatlamak için demet kullanmaya gerek yoktur. Ancak birden fazla değeri formatlamak için demet kullanmak - gerekir. Demet yerine liste ya da başka bir dolaşılabilir nesne kullanamyız. Örneğin: - - >>> a = 10 - >>> print('a = %d' % a) - a = 10 - - Burada string içerisinde tek bir yer tutucu olduğu için % operatörünün sağında demet yerine doğrudan bir ifade kullanılmıştır. - -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -b = 20 - -s = 'a = %d, b = %d' % (a, b) -print(s) # a = 10, b = 20 - -s = 'a = %x, b = %x' % (a, b) -print(s) # a = a, b = 14 - -c = 12.3456 - -print('%-10.3f' % c) # 12.346 - -#------------------------------------------------------------------------------------------------------------------------ - Daha önce de belirttiğimiz gibi Python'da genel bir silme semantiği için del isimli bir deyim bulundurulmuştur. - del deyiminin genel biçimi şöyledi: - - del - del a[ifade], ... - - del deyimi değişkenleri silebilmektedir. Burada silmek demekle sanki o değişken hiç yaratılmamış gibi bir durum oluşturma - kastedilmektedir. Yani bir değişkeni del deyimi ile sildikten sonra o değişkeni kulanırsak bu durum exception'a yol açar. Örneğin: - - >>> a = 10 - >>> print(a) - 10 - >>> del a - >>> print(a) - Traceback (most recent call last): - File "", line 1, in - NameError: name 'a' is not defined - - Tabii del deyiminde ',' atomu ile tek hamlede birden fazla değişkeni silebiliriz. Örneğin: - - >>> a = 10; b = 20 - >>> print(a, b) - 10 20 - >>> del a, b - >>> print(a, b) - Traceback (most recent call last): - File "", line 1, in - NameError: name 'a' is not defined - - Anımsanacağı gibi Python'da "değişken (variable)" kavramı ile "nesne (object)" kavramı farklı anlamlara gelmektedir. Python'da adres tutan yani bir nesneyi - gösteren isimlere değişken denmektedir. Değişkenin gösterdiği yere nesne denilmektedir. del deyimi değişkenleri siler. Nesnelerin silinmesi yorumlayıcı - tarafından "çöp toplama (gerbage collection)" mekanizması yoluyla otomatik olarak silinmektedir. Bir nesneyi gösteren hiçbir değişken kalmadıysa - Python'ın çöp toplama mekanizması devreye girer ve o nesne silinir. Örneğin: - - s = 'ankara' - s = 'izmir' - - Burada s değişkeni önce "ankara" yazısının bulunduğu nesneyi gösterirken sonra "istanbul" yazısının bulunduğu nesneyi gösterir hale gelmiştir. - İşte Python'ın çöp toplayıcı mekanizması devreye girip "ankara" yazısının bulunduğu nesneyi otomatik olarak silecektir. Her ne kadar del deyimi - nesneleri silmiyorsa da nesnelerin silinmesi için bir zemin de oluşturabilmektedir. Örneğin: - - s = 'ankara' - del s - - Burada del deyimi ile s değişkeni silindiği için artık "ankara" yazısına ilişkin nesneyi gösteren bir değişken kalmayacaktır. Dolayısıyla del deyimi - dolaylı da olsa bu nesnenin silinmesine de önayak olacaktır. - - del deyimi ile köşeli parantez ile erişebildiğimiz veri yapılarının elemanları da silinebilmektedir. Örneğin: - - >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100] - >>> del a[3] - >>> a - [1, 2, 3, 5, 6, 7, 8, 9, 0] - >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100] - >>> del a[2:5] - >>> a - [1, 2, 6, 7, 8, 9, 0] - - Tabii del ile biz ancak değiştirilebilir veri yapılarında silme yapabiliriz. Örneğin bir demet değiştirilemez olduğuna göre - demetin bir elemanını del deyimi ile silemeyiz. - - del deyimi ile bu biçimde silme yapılırken silme işleminin soldan sağa yürütüldüğüne dikkat ediniz. Örneğin: - - del a[2], a[5] - - Burada önce listenin 2'inci indeksli elemanı silinecektir. Bu silinme işleminden sonra 5'inci indeskli eleman bu silinmeden - sonra oluşan listenin 5'inci indeksli elemanı olacaktır. Yani: - - del a[2], a[5] - - işlemi ile aşağıdaki işlem eşdeğerdir: - - del a[2] - del a[5] - - Örneğin: - - >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - >>> del a[2], a[5] - >>> a - [1, 2, 4, 5, 6, 8, 9, 10] - - del deyimi ile dilimleme yapılarak da birden fazla eleman silinebilmektedir. Örneğin: - - >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100] - >>> del a[2:6] - >>> a - [1, 2, 7, 8, 9, 100] - - del deyimi ile sözlüklerden de eleman silinebilir. Çünkü sözlük elemanlarına da aslında köşeli parantez sentaksı ile erişilebilmektedir. - Tabii bu durumda sözlüğün yalnızca anahtarı ya da değeri değil anahtar-değer çifti silinir. Örneğin: - - >>> d = {'ali': 10, 'veli': 20, 'selami': 30} - >>> del d['veli'], d['selami'] - >>> d - {'ali': 10} -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da farklı türler her zaman == ve != operatöryle karşılaştırılabilirler. int, float ve bool türlerinin dışındaki - farklı türler == ve != operatörleriyle karşılaştırıldığında == operatörü ile karşılaştırma her zaman False, != operatör - ile karşılaştırma her zaman True değerini verir. Örneğin: - - >>> a = [1, 2, 3] - >>> b = (1, 2, 3) - >>> a == b - False - >>> a != b - True - >>> a == 10 - False - >>> b != 10 - True - - Burada a list türünden b de tuple türündendir. Dolayısıyla a == b hiçbir zaman eşit olamayacağı için False değerini vermiştir. - Benzer biçimde biz bir list ya da demet ile örneğin int, float gibi türleri == ve != operatörleriyle karşılaştırabiliriz. Bu durumda - == yine her zaman False değerini, != ise True değerini verir. - - Tabii daha önceden de belirttiğimiz gibi int, float ve bool türleri kendi aralarında tüm karşılaştırma operatörleriyle karşılaştırılabilir. - Bu durumda sayıların değerleri karşılaştırılmaktadır. Örneğin: - - >>> a = 10 - >>> b = 10.0 - >>> a == b - True - >>> a = 1.0 - >>> b = True - >>> a == b - True - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20] -b = (10, 20) - -result = a == b -print(result) # False - -result = a != b -print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - Ancak int, float ve bool türlerinin dışındaki türlerin >, <, >= ve <= operatörleriyle karşılaştırılması geçersiz bir durumdur ve exception'a yol açar. - Örneğin: - - >>> a = [10, 20] - >>> b = (10, 20) - >>> a > b - Traceback (most recent call last): - File "", line 1, in - TypeError: '>' not supported between instances of 'list' and 'tuple' - - Özetle int, float ve bool dışındaki farrklı türleri == ve != operatörleriyle karşılaştırabiliriz ancak >, <, >=, <= - operatörleriyle karşılaştıramayız. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi int, float ve bool türleri farklı tür olsalar da birbirleriyle karşılaştırılabilmektedir. Örneğin: - - >>> a = 10 - >>> b = 3.7 - >>> a > b - True - >>> c = True - >>> a > c - True - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İki listeyi ve iki demeti kendi aralarında tüm karşılaştırma operatörleriyle karşılaştırabiliriz. Bu durumda karşılaştırma - leksikografik biçimde yapılır. Yani karşılıklı elemanlar eşit olduğu sürece ilerlenir. İlk eşit olmayan elemanların durumlarına - bakılarak karar verilir. Örneğin: - - >>> a = [1, 2, 3, 4, 5] - >>> b = [1, 2, 3, 5, 1] - >>> a > b - False - >>> a < b - True - >>> a = [1, 2, 3, 4, 5] - >>> b = [1, 2, 3, 4, 5] - >>> a == b - True - >>> a = [1, 2, 3, 4, 5] - >>> b = [1, 2, 3, 4, 5, 6] - >>> a == b - False - >>> a > b - False - >>> a < b - True -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] -b = [10, 20, 40, 3, 5] - -result = a > b -print(result) # False - -result = b > a -print(result) # True - -result = a == b -print(result) # False - -#------------------------------------------------------------------------------------------------------------------------ - Tabii listeler ve demetler heterojen türlere sahip olabildiğine göre karşılıklı elemanların karşılaştırılabilir olması - gerekmektedir. Eğer karşılıklı elemanlar karşılaştırılabilir değilse exception oluşur. Örneğin: - - >>> a = [10, 'ali', 20] - >>> b = [10, 20, 30] - >>> a > b - Traceback (most recent call last): - File "", line 1, in - TypeError: '>' not supported between instances of 'str' and 'int' - - Aşağıdaki gibi bir durumda exception oluşmadığına dikkat ediniz: - - >>> a = [10, 'ali', 20] - >>> b = [20, 30, 40] - >>> a > b - False - - Çünkü burada str ile int türleri karşılaştırılmadan zaten karşılaştırmanın sonucu tespit edilebilmiştir. - - Tabii listenin elemanları liste ya da demet, demetin elemanları da liste ya da demet olabilir. Bu durumda karşılaştırma - özyinelemei biçimde yapılır. Örneğin: - - >>> a = [1, [2, 3, 4], 5] - >>> b = [1, [2, 3, 5], 2] - >>> a > b - False - >>> a < b - True - - Örneğin - - >>> a = [1, [2, 3, 4], 5] - >>> b = [1, (2, 3, 4), 2] - >>> a == b - False - >>> a > b - Traceback (most recent call last): - File "", line 1, in - TypeError: '>' not supported between instances of 'list' and 'tuple' - - İki sözlük nesnesi kendi aralarında yalızca == ve != operatörleriyle karşılaştırılabilir. Bu durumda iki sözlüğün - anahatar değer çiftlerinin bire bir aynı olmasına bakılmaktadır (yani yalnızca anahtarlara bakılmamaktadır). Örneğin: - - >>> d1 = {'ali': 10, 'veli': 20, 'selami': 30} - >>> d2 = {'ali': 10, 'veli': 20, 'selami': 50} - >>> d1 == d2 - False - >>> d1 > d2 - Traceback (most recent call last): - File "", line 1, in - TypeError: '>' not supported between instances of 'dict' and 'dict' - - İki sözlük nesneesinin >, <, >= ve <= operatörleriyle karşılaştırılmasının geçersiz olduğuna dikkat ediniz. - - Her ne kadar Python'da 3.7 ve sonrasında sözlük elemanlarında dolaşım sırasında bir sıra söz konusu olsa da == ve != - operatörlerinde bu biçimde lexikografik bir karşılaştırma yapılmamaktadır. Yani eşitlik koşulu için karşılıklı anahtar-değer - çiftlerinin eşitliğine değil tüm anahtar-değer çiftlerinin eşitliğine bakılmaktadır. Örneğin: - - >>> d = {'ali': 30, 'veli': 30, 'selami': 40} - >>> k = {'ali': 30, 'selami': 40, 'veli': 30} - >>> d == k - True - - İki küme == ve != operatörleriyle karşılaştırılabilir. Bu durumda iki kümenin elemanlarının tamamen aynı olup olmadığına - bakılmaktadır. Örneğin: - - >>> s = {'ali', 100, 'veli', 120} - >>> k = {100, 120, 'veli', 'ali'} - >>> s == k - True - >>> s != k - False - - Kümelerde >, >=, <, <= operatörlerinin küme işlemi yaptığını anımsayınız. (Kümelerde '<' öz alt küme, '>' öz üst küme, '<=' alt küme ve - '>=' üst küme işlemlerini yapmaktadır.) Örneğin: - - >>> a = {10, 'ali', 'veli'} - >>> b = {10, 'veli'} - >>> a > b - True - >>> b < a - True - >>> c = set() - >>> c < a - True - -#------------------------------------------------------------------------------------------------------------------------ - 22. Ders 22/06/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir satırın başından itibaren ilk boşluk olmayan karaktere kadarki SPACE sayısına "girinti düzeyi (indent level)" denilmektedir. - Bir satırın girinti düzeyi eğer satırın başında hep SPACE karakteri varsa SPACE karakterlerinin toplamı olarak hesaplanır. - Ancak satırın başına SPACE ve TAB karakterleri varsa hesap şöyle yapılmaktadır: Her TAB karakteri görüldüğünde bu TAB karakterlerinin - o zamana kadarki SPACE sayısını 8'in katlarına tamamlamak için n tane SPACE anlamına geldiği kabul edilir. Bu biçimdeki SPACE'lerin sayısına - bakılır. Örneğin: - - SPACE SPACE SPACE - - Bu satırın girinti düzeyi 3'tür. Örneğin: - - SPACE TAB SPACE - - Bu satırın girinti düzeyi 9'dur. Çünkü: - - SPACE (1) TAB (7) SPACE - - Örneğin: - - SPACE TAB TAB SPACE - - Bu satırın girinti düzeyi 17'dir. Çünkü: - - SPACE (1) TAB (7) TAB (8) SPACE (1) - - Örneğin aşağıdaki iki satırın girinti düzeyleri editörde bu iki satır alt alta gözükmüyor olsa bile Python yorumlayıcısına göre aynıdır: - - SPACE SPACE SPACE SPACE SPACE SPACE SPACE SPACE SPACE - SPACE TAB SPACE - - Buradaki hesabın editörün tab ayarıyla ilgili olmadığına dikkat ediniz. Yukarıdaki iki satır editörde alt alta gözükmüyor olsa bile - aynı girinti düzeyine sahiptir. - - Daha önceden de belirttiğimiz gibi Python editörlerinin hemen hepsi zaten TAB yerine n tane SPACE karakterini kaynak koda yazmaktadır. - Bu durumda yorumlayıcı zaten artık TAB karakterlerini görmez yalnızca SPACE karakterlerini görür. Girinti düzeyi hesabı oldukça kolay olur. - Tabii Python editörünüzün TAB yerine SPACE basması yönünde bir zounluluk yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python programlarında girinti düzeyi 0 ile başlamak zorundadır. Yani Python programları en soldaki sütuna dayalı bir biçimde yazılır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir programlama dilinde çalıştırma birimlerine "deyim (statement)" denilmektedir. Yani "imperative dillerde" programın çalışması - deyimlerin çalıştırılmasıyla sağlanmaktadır. Python yorumlayıcısı yukarıdan aşağıya doğru deyimleri tek tek sırasıyla çalıştırır. - C, C++, Java ve C# gibi pek çok dilde program main ya da Main isimli özel bir fonksiyondan çalışmaya başlamaktadır. Ancak Python gibi - bazı dillerde program kaynak kodun tepesinden çalışmaya başlar. - - Her programlama dilinde deyimlerin sınıflandırılması o dile özgü bir biçimde yapılır. Python'da deyimler iki gruba - ayrılmaktadır: - - 1) Basit Deyimler (Simple Statements) - 2) Bileşik Deyimler (Compound Statements) - - Python'da basit deyimler tek parçadan oluşan deyimlerdir. Bu deyimler tek satır üzerine yazılabilmektedir. Ancak bileşik deyimler - birden fazla parçadan oluşan ve tek satır üzerinde yazılamayan ya da yazılmak zorunda olmayan deyimlerdir. Basit deyimlerin en önemli - özellikleri birden fazla basit deyimin aynı satıra aralarına ';' atomu getirilerek yazılabilmesidir. - Python'da basit deyimlerin de bileşik deyimlerin de çeşitli biçimler vardır. Örneğin if gibi for gibi deyimler Python'da - bileşik deyimler grubundandır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'daki en yalın basit deyim "ifadesel deyimdir (expression statment)". Bir ifade programın parçası halinde - bulundurulduğunda bu artık deyim olur. Bu tür deyimlere ifadesel deyim denir. Örneğin: - - print(z) - input('Bir yazı giriniz:') - - Bu iki deyim ifadesel deyimdir. - - Biz bir ifadeyi bir satıra yazdığımızda artık o ifadenin artık bir deyim heline geldiğine dikkat ediniz. Örneğin: - - print(a + b) - - Tabii ifadeler başka deyimlerin parçalarını da oluşturabilmektedir. - - Python'da atama işlemi aslında bir operatör değil bir deyim statüsündedir. Buna "atama deyimi (assignment statement)" denilmektedir. Örneğin: - - x = 10 - y = 20 - z = x + y - print(z) - - Burada ilk üç deyim atama deyimidir. Son deyim ifadesel deyimdir. Ancak bunların hepsi kategorik olarak basit deyim statüsündedir. - - Biz basit deyimleri istersek tek satır üzerinde onların aralarına ';' getirerek de yazabiliriz: - - x = 10; y = 20; z = x + y; print(z) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Programalama dillerinde bileşik deyimlerin parçalarını oluşturan deyimlerin nasıl yazılması gerektiğine yönelik çeşitli biçimler - bulunmaktadır. Örneğin C, C++, Java ve C# gibi diller bloklama tekniğini kullanırlar. Bu dillerde bileşik deyimin parçalarını oluşturan - deyimler bloklanarak belirlenir. Bloklama bu dillerde küme parantezleri ile yapılmaktadır. Örneğin: - - if (koşul) { - ifade1; - ifade2; - ifade3; - } - ifade4; - - Ancak Python'da bloklama için girinti düzeyi tekniği kullanılmaktadır. Bir bileşik deyimin içindeki deyimlerin neler olduğu o deyimlerin - girinti düzeylerine bakılarak belirlenir. Örneğin: - - if koşul: - ifade1 - ifade2 - ifade3 - ifade4 - - Burada ifade1, ifade2 ve ifade3 aynı girinti düzeyine sahip olduğu için bileşik deyimin parçalarını oluşturmaktadır. - - Bir bileşik deyimin içindeki deyimlerin aynı girinti düzeyine sahip olması gerekir. Örneğin: - - if koşul: - ifade1; - ifade2 - ifade3 - ifade4 - - Bu yazım geçersizdir. Tabii yukarıda açıkladığımız girinti düzeyi kuralına göre editörde alt alta gözükmediği halde iki satır - aslında aynı girinti düzeyine sahip olabilir. Ancak yukarıda da belirttiğimiz gibi Python editörünüz eğer TAB yerine belli miktar - SPACE basıyorsa zaten o koddaki bileşik deyimin parçaları hep aynı hizada görüntülenecektir. - - Python'da bileşik deyimler genel olarak bir anahtar sözcükle başlatılır, sonra bunu bir ya da birden fazla ifade izler sonra da bir ':' - atomu bulundurulur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da bileşik deyimin anahtar sözcüğü ile aynı satıra yazılan birden fazla basit deyime ya da farklı satırlara - aynı girinti düzeyiyle yazılan birden fazla deyime "suit" denilmektedir. Pek çok deyim bir suit içermek durumundadır. - Örneğin: - - while ifade: ifade1; ifade2; ifade3 - - Burada ifade1, ifade2 ve ifade3 bir suit belirtmektedir. Örneğin: - - while ifade: - ifade1 - ifade2 - ifade3 - - Burada da ifade1, ifade2 ve ifade3 bir suite belirtir. Örneğin: - - while ifade: ifade1 - ifade2 - ifade3 - - Burada ifade1, ifade2 ve ifade3 bir suit belirtmemektedir. Çünkü suit ya deyimin anahtar sözcüğü ile aynı satıra yazılmış - birden fazla deyimi belirtir ya da farklı satırlara aynı girinti düzeyiyle yazılmış birden fazla deyimi belirtir. Örneğin: - - while ifade: - ifade1 - ifade2; ifade3 - - Bu bir suite belirtmektedir. Görüldüğü gigi farklı satırlara yazılmıi deyimlerdeki satırlarda birden fazla basit deyim - olabilmektedir. Örneğin: - - while ifade: - ifade1 - ifade2 - ifade3 - - Burada ifade1, ifade2 ve ifade3 bir suit belirtmez. Çünkü aynı girinti düzeyine sahip değildir. - - Suit'i oluşturan "farklı satırlardaki aynı girinti düzeyine sahip deyimler" arasında boş satırlar olabilir. Bu suit - kuralını bozmaz. Örneğin: - - while ifade: - ifade1 - ifade2 - - ifade3 - ifade4 - - Buradaki suit yazımı geçerlidir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - if deyimi bir ifadenin doğru ya da yanlış olması durumuna göre farklı işlemlerini yapılmasını sağlayan en temel bileşik deyimdir. - Genel biçimi şöyledir: - - if : - [else: ] - - Buradan da görüldüğü gibi if deyiminin doğruysa ve yanlışsa kısmında ayrı suite'ler vardır. if deyimin else kısmı hiç olmayabilir. - Aşağıdaki if deyimi yazım bakımındna geçerlidir: - - if ifade: - ifade1 - ifade2 - else: - ifade3 - ifade4 - - else anahtar sözcüğü ile if anahtar sözüğünün aynı girinti düzeyine saip olması gerekmektedir. Aşağıdaki if deyimi de geçerlidir: - - if ifade: ifade1; ifade - else: - ifade3 - ifade4 - - Aşağıdaki if deyimi de geçerlidir: - - if ifade: - ifade1; ifade2 - else: - ifade3 - ifade4 - - Yukarıda da belirttiğimiz gibi Python'da genel olarak bileşik deyimlerde deyimin kontrol kısmının sonunda ':' atomu bulunmaktadır. - Bu atom ifadeye yapışık olmak zorunda değildir. Ancak aynı satırda bulunmak zorundadır. Örneğin: - - if ifade : - ifade1; ifade2 - else : ifade3; ifade4 - - Bu if deyimi geçerlidir. Tabii bu yazım iyi bir görüntüye sahip değildir. - - if deyiminde kısmındaki deyimin doğruysa kısmındaki suit'in girinti düzeyiyle yanlışsa kısmındaki suit'in girinti düzeyinin - aynı olması gerekmez. Örneğin: - - if ifade: - ifade1 - ifade2 - else: - ifade3 - ifade4 - - Bu yazım geçerlidir. Tabii böyle bir yazımda okunabilirlik bozuk olacaktır. Bu nedenle Python programcıları her ne kadar - zorunlu olmasa da if deyiminin doğruysa ve yanlışsa kısımlarındaki suit'leri aynı girinti düzeyine sahip olacak biçimde yazarlar. - Örneğin: - - if ifade: - ifade1 - ifade2 - else: - ifade3 - ifade4 - - Suit "aynı satır üzerine yazılmış birden fazla basit deyim ya da farklı staırlara yazılmış olan aynı girinti düzeyine sahip - birden fazla deyim" anlamına geldiğine göre aşağıdaki if sentaksı geçersizdir: - - if ifade: ifade1 - ifade2 - else: ifade3; ifade4 - - Çünkü if deyiminin doğurysa ksmındaki deyimler suite oluşturmamaktadır. Örneğin: - - if ifade: - ifade1 - ifade2 - else: ifade3 - - Bu yazım da yanlıştır. Çünkü if deyimin doğruysa kısmındaki ifadeler suit belirtmemektedir. Örneğin: - - if ifade: ifade1; ifade2 - else: ifade3; ifade4 - ifade5 - - Bu yazım doğrudur. Burada ifade5 if deyiminin else kısmında değildir. Yani buradaki ifade5 if deyimi ile aynı girinti düzeyine - sahip olduğu için artık if deyimi içerisinde değildir. Örneğin: - - if ifade: - ifade1: - ifade2: - else: - ifade3 - ifade4 - ifade5 - - Bu yazım geçersizdir. Çünkü burada ifade5 if dışında değildir. ifade5'in if dışında olabilmesi için if ile aynı hizada yazılması gerekirdi. - Öte yandan ifade5 else kısmındaki suite yazımına da aykırıdır. - - Örneğin: - - if ifade: ifade1 else: ifade2 - - Bu if deyimi geçersizdir. else anahtar sözcüğü kesinlikle if ile aynı girinti düzeyinde yazılmak zorundadır. - - if deyimi şöyle çalışmaktadır: Önce yorumlayıcı if anahtar sözcüğünün yanındaki ifadenin türüne bakar. Eğer bu ifade bool türden değilse onu - bool türüne dönüştürür. Bu dönüşümden sonra eğer bu ifade True ise yaşnızca if deyiminin doğruysa kısmındaki suite çalıştırılır eğer bu ifade False - ise yalnızca if deyiminin yanlışsa kısmındaki suit çalıştırılır. Böylece if deyiminin çalışması biter. Program if deyiminden sonraki deyiminden - çalışmaya devam eder. Örneğin: - - x = 10 - if x > 0: - print('ankara') - print('izmir') - else: - print('adana') - print('eskişehir') - - Burada toplam üç deyim vardır. İlk deyim atama deyimidir ve basit bir deyimdir. İkinci deyim if deyimidir. Üçüncü deyim 'eskişehir'i ekrana yazdıran - basit deyimdir. x > 0 ifadesi zaten bool türdendir. Bu ifade True ise 'ankara' ve 'izmir' yazıları False ise 'adana' yazısı ekrana çıkacaktır. - 'eskişehir' yazısını basan basit deyim if içerisinde değildir. Çünkü bu deyim if ile aynı girinti düzeyinde yazılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -x = int(input('Bir syaı giriniz:')) - -if x % 2 == 0: - print('çift') -else: - print('tek') - -print('program sonlanıyor') - -#------------------------------------------------------------------------------------------------------------------------ - Boş bir listenin, demetin ve strin'in bool türüne False olarak, dolu bir listenin, string'in ve demetin True olarak - dönüştürüldüğünü anımsayınız. Örneğin: - - a = [] - if a: - ifade1 - ifade2 - else: - ifade - ifade4 - - Bu if deyimi yanlışsa kısmındna sapacaktır. Benzer biçimde sıfırdan farklı int ve float değerlerin bool türüne True olarak, - 0 değerinin False olarak dönüştürüldüğünü de anımsayınız. Örneğin: - - a = int(input('Bir değer giriniz:)) - if a: - print('girilen değer sıfır değil') - else: - print('girilen değer sıfır') -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıda ikinci derece bir denklemin köklerini bulan program verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -a = float(input('a:')) -b = float(input('b:')) -c = float(input('c:')) - -delta = b ** 2 - 4 * a * c -if delta < 0: - print('kök yok') -else: - x1 = (-b + math.sqrt(delta)) / (2 * a) - x2 = (-b - math.sqrt(delta)) / (2 * a) - -print(f'x1 = {x1}, x2 = {x2}') - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki genel biçimden de görüldüğü gibi if deyiminin else kısmı olmayabilir. if deyiminin doğruysa kısmındaki - suite'ten sonra else anahtar sözcüğü gelmezse derleyici bunun "else kısmı olmayan bir if" olduğunu kabul eder. Örneğin: - - if ifade: - ifade1 - ifade2 - ifade3 - - Burada toplamda iki deyim vardır. if deyimi ve ifade3'ten oluşan basit deyim. Burada if deyimin else kısmı bulunmamaktadır. - ifade3 if deyimin dışında olan başka bir basit deyimdir. Örneğin: - - a = int(input('Bir sayı giriniz:')) - - if a > 0: - print('Pozitif') - print('Program sonlanıyor') - - Burada if deyiminin else kısmı bulundurulmamıştır. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir sayı giriniz:')) - -if a > 0: - print('Pozitif') -print('Program sonlanıyor') - -#------------------------------------------------------------------------------------------------------------------------ - C, C++, Java ve C# gibi dillerde if anahtar sözcüğündne sonra if ifadeesinin parantezler içerisinde olması zorunludur. - Örneğin: - - if (a > 0) - ifade1; - else - ifade2; - - Pekiyi nedne Python'da böyle bir zorunluluk yoktur? Bu dillerde bu zorunluluğun olmasının asıl nedeni if ifadesi ile - if'in doğruysa kısmının ayrıştırılmasını sağlanmak istenmesidir. Örneğin: - - if a > 10 b = 20; - - O dillerde burada hangi ifadenin if deyiminin kontrol ifadesi olduğu hangi ifadenin if deyiminin doğruysa kısmını oluşturduğu - anlaşılamamaktadır. Halbuki: - - if (a > 10) b = 20; - - Artık her şey çok açıktır. Python'da zaten if deyiminin kontrol ifadesinden sonra ':' atomu gelmek zorunda olduğu için - bu ayrıştırma parantezlere gereksinim duyulmadan da yapılabilmektedir. Örneğin: - - if a > b: b = 20 - - Swift gibi Kotlin gibi yeni bazı dillerde küme parantezleri dozunlu turulduğu için o dillerde de kontrol ifadesinin - paranteze alınması gerekmemiştir. Örneğin: - - if a > 10 { - b = 10; - } - - Tabii Python'da biz istersek if deyiminin kontrol ifadesinde de parantezleri kullanabiliriz. Ne de olsa parantezler - her ifadede kullanılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - if deyiminin doğruysa kısmında başka bir if deyimi olabilir. Örneğin: - - ifade1 - if ifade2: - ifade3 - if ifade4: - ifade5 - ifade6 - else: - ifade7 - ifade8 - else: - ifade9 - ifade10 - - Burada toplamda dışarıdan bakıldığında üç deyim vardır. Dıştaki if deyiminin doğruysa kısmında başka bir if deyimid vardır. - - C/C++, Java ve C# gibi dillerde hizalamanın bir önemi olmadığı için programcılar "dangling else" denilen bir durumda bazen hata yapabilmektedir. - "Dangling else" iki if için tek bir else bulunması durumudur. Ancak Python'da girinti düzeylerine bakılarak else'in aslında hangi if deyiminin - else kısmı olduğu zaten anlaşılmaktadır. Örrneğin: - - if ifade1: - if ifade2: - ifade3 - ifade4 - else: - ifade5 - - Buradaki else dıştaki if'in else kısmıdır. Fakat örneğin: - - if ifade1: - if ifade2: - ifade3 - ifade4 - else: - ifade5 - - Buradaki else artık içteki if'in else kısmıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte üç sayının en büyüğü bulunmuştur. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Sayı giriniz:')) -b = int(input('Sayı giriniz:')) -c = int(input('Sayı giriniz:')) - -if a > b: - if a > c: - print(a) - else: - print(c) -else: - if b > c: - print(b) - else: - print(c) - -#------------------------------------------------------------------------------------------------------------------------ - Bir grup koşuldan bir tanesi doğru iken diğerlerinin doğru olma olasılığı yoksa bu koşullara "ayrık (discrete) koşullar" - denilmektedir. Örneğin: - - a > 0 - a < 0 - - Bu iki koşul ayrıktır. Örneğin: - - a > 0 - a < 0 - a == 0 - - Bu üç koşul da ayrıktır. Örneğin: - - a > 0 - a > 5 - - Bu koşullar ayrık değildir. Örneğin: - - a == 1 - a == 2 - a == 3 - - Buradaki üç koşul da ayrıktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Programalamda ayrık koşulların ayrı if'lerle ifade edilmesi kötü bir tekniktir. Örneğin: - - if a > 0: - ifade1 - if a < 0: - ifade2 - if a == 0: - ifade3 - - Burada a > 0 durumunda gereksiz bir biçimde diğer iki karşılaştırma da yapılacaktır. Bu karşılaştırmalar önemsiz olsa da bir bilgisayar zamanının - harcanmasına yol açmaktadır. Örneğin: - - if a == 1: - ifade1 - if a == 2: - iafde2 - if a == 3: - ifade3 - - Bu da kötü bir tekniktir. Ayrık koşulların else-if biçiminde organize edilmesi iyi bir tekniktir. Örneğin: - - if a == 1: - ifade1 - else: - if a == 2: - ifade2 - else: - if a == 3: - ifade3 -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) - -if a == 1: - print('bir') -else: - if a == 2: - print('iki') - else: - if a == 3: - print('üç') - else: - if a == 4: - print('dört') - else: - if a == 5: - print('beş') - else: - print('hiçbiri') - -print('program sonlanıyor') - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örnekte görüldüğü gibi else-if merdivenleri kaydırmalı bir biçimde yazılması gerektiği için görüntüyü bozmaktadır. - Bu görüntünün bozulmaması ve bu tür else-if merdivenlerinin daha kolay yazılması için Python'da if deyimin bir parçası olarak - elif kısmı da bulundurulmuştur. elif (else if'ten kısaltma) tamamen else if anlamına gelmektedir. Ancak yazımda if ile aynı - girinti düzeyine sahip olmak zorundadır. elif bir ifadeyle birlikte bulundurulur. elif kısımlarından sonra son bir else kısmı da - bulundurulabilmektedir. Örneğin: - - if a == 1: - print('bir') - elif a == 2: - print('iki) - elif a == 3: - print('üç') - elif a == 4: - print('dört') - elif a == 5: - print('beş') - else: - print('hiçbiri') - -#------------------------------------------------------------------------------------------------------------------------ -a = int(input('Bir değer giriniz:')) - -if a == 1: - print('bir') -elif a == 2: - print('iki') -elif a == 3: - print('üç') -elif a == 4: - print('dört') -elif a == 5: - print('beş') -else: - print('program sonlanıyor') - -#------------------------------------------------------------------------------------------------------------------------ - Bir program parçasının yinelemeli olarak çalıştırılmasını sağlayan deyimlere döngü deyimleri denilmektedir. Python - döngü deyimleri bakımından minimalist biçimde tasarlanmıştır. Python'da iki döngü deyimi vardır: - - 1) while Döngüleri - 2) for Döngüleri - - C/C++, Java ve C# gibi dillerde while döngüleri kendi aralarında "kontrolün başta yapıldığı while döngüleri" ve "kontrolün - sonda yapıldığı while döngüleri (do-while)" olmak üzere ikiye ayrılmaktadır. Ancak Python'da kontrolün sonda yapıldığı - while döngüleri yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 23. Ders - 27/06/2022-Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - while döngüsünün genel biçimi şöyledir: - - while : - - while anahtar sözcüğünden sonra bir ifade ve sonra da ':' atomu bulunmak zorundadır. Bu ':' atomundan sonra da bir "suite" - bulunmalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - while döngüsü şöyle çalışır: Yorumlayıcı önce while anahtar sözcüğünün sağındaki ifadenin türüne bakar. Eğer bu ifade - bool türden değilse onu bool türüne dönüştürür. Sonra ifadenin değerine bakar. Eğer ifade True ise suit'i oluşturan deyimleri - çalıştırır ve başa döner. Eğer ifade False ise döngü deyiminin çalışması sonlandırılır. Program döngü deyiminden sonraki deyimle - çalışmaya devam eder. Yani while döngüleri "bir ifade doğru olduğu sürece yinelenen" döngülerdir. -#------------------------------------------------------------------------------------------------------------------------ - -i = 0 -while i < 10: - print(i) - i += 1 - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte bir listenin elemanları while döngüsü ile yazdırılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -i = 0 -while i < len(a): - print(a[i]) - i += 1 - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte de bir listenin elemanları while döngüsü ile sondan başa doğru yazdırılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -i = len(a) - 1 -while i >= 0: - print(a[i], end=' ') - i -= 1 - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte 1'den klavyeden girilen sayıya kadar olan tamsayıların toplamı yazdırılmıştır. (Tabii aslında bu toplam - (n * (n + 1)) / 2 biçimindedir.) -#------------------------------------------------------------------------------------------------------------------------ - -n = int(input('Bir sayı giriniz:')) - -i = 1 -total = 0 - -while i <= n: - total += i - i += 1 - -print(total) - -#------------------------------------------------------------------------------------------------------------------------ - while ifadesi bool türünden değilse bool türüne dönüştürülmektedir. Boş bir string'in ya da listenin bool türüne False olarak, - dolu bir string'in ya da listenin True olarak dönüştürüldüğünü anımsayınız. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -while s: - print(s) - s = s[:-1] - -#------------------------------------------------------------------------------------------------------------------------ - int ya da float bir değerin bool türüne sıfır dışı ise True olarak sıfır ise False olarak dönüştürüldüğünü anımsayınız. - Dolaysıyla aşağıdkai örnekte i değeri 0'a geldiğinde döngüden çıkılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -i = 10 -while i: - print(i) - i -= 1 - -#------------------------------------------------------------------------------------------------------------------------ - Python'da atama operatörünün değer üretmediğini bunun için dile Walrus operatörünün eklendiğini anımsayınız. Aşağıdaki - while döngüsünde girilen sayının karesi ekrana yazdırılmaktadır. Ancak 0 girildiğinde döngü sonlandırılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -while (n := int(input('Bir değer giriniz:'))) != 0: - print(n * n) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki kalıp çokça kullanılmaktadır. Pekiyi Walrus operatörü olmasaydı (yani eski Python sürümlerinde çalışyor olsaydık) - aynı şeyi nasıl yapabilirdik? Aşağıdaki gibi bir çözüm akla gelebilir: -#------------------------------------------------------------------------------------------------------------------------ - -val = int(input('Bir değer giriniz:')) - -while val != 0: - print(val * val) - val = int(input('Bir değer giriniz:')) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki çözümde rahatsız edici bir nokta vardır. input satırı kodda takrarlanmaktadır. Bu tekrarın aşağıdaki gibi - engellenmesi de aklınıza gelebilir: - - val = 1 - while val != 0: - val = int(input('Bir değer giriniz:')) - print(val * val) - - Ancak kodun bu biçimde organize edilmesi yine okunabilirliği bozmaktadır. Koda bakan kişi ne yapılmak istendiğini - hemen anlayamayacaktır. O halde Walrus'lu kalıp gerçekten iyi işlev görmektedir: - - while (val := int(input('Bir değer giriniz:'))) != 0: - print(val * val) - - Aslında zaten int değerler 0'dan farklıysa bool türüne True olarak dönüştürüldüğüne göre ifadedeki karşılaştırma - kısmını da atabiliriz: - - while val := int(input('Bir değer giriniz:')): - print(val * val) - - Fakat bu yazım biçimi yerine açıkça karşılaştırma yapmak kodu daha anlaşılabilir hale getirecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İç içe döngüler söz konusu olabilir. Yani bir suit içerisinde başka bir döngü de olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -n = int(input('Bir sayı giriniz:')) - -i = 1 -while i <= n: - k = 0 - while k < i: - print('*', end='') - k += 1 - print() - i += 1 - -#------------------------------------------------------------------------------------------------------------------------ - Tabii yukarıdaki örneği aslında "yineleme (repitition)" ile çok daha kolay aşağıdaki gibi yapabilirdik. -#------------------------------------------------------------------------------------------------------------------------ - -n = int(input('Bir sayı giriniz:')) - -i = 1 -while i <= n: - print('*' * i) - i += 1 - -#------------------------------------------------------------------------------------------------------------------------ - Bilindiği gibi her sayı asal sayıların çarpımı biçiminde yazılabilir. Buna sayının "asal çarpanları (prime factors)" - denilmektedir. Örneğin 100'ün asal çarpanları 2 * 2 * 5 * 5 biçimindedir. Bir sayının asal çarpanlarını bulmak - "düz mantıkla (brute force)" oldukça kolaydır. Sayı 1 olmadığı sürece döngüye sokulur. Sayının bölüneceği sayı bir değişkende - tutulur ve başlangıçta bu değişkenin içerisinde 2 vardır. Sayı bu sayıya bölündüğü sürece bölünerek ilerlenir. Sayı bu sayıya bölünmezse - sonra sayı ile devam edilir. Aşağıda bu algoritma uygulanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -n = int(input('Bir sayı giriniz:')) - -divider = 2 -while n != 1: - if n % divider == 0: - print(divider, end=' ') - n //= divider - else: - divider += 1 -print() - - -#------------------------------------------------------------------------------------------------------------------------ - Bir problemi kesin çözüme götüren adımlar topluluğuna "algoritma (algorithm)" denilmektedir. Tabii söz konusu problemi - çözebilecek birdenfazla alternatif algoritmalar söz konusu olabilir. Bu durumda bunların kıyaslanması gerekebilmektedir. - Algoritmaları kıyaslamak için en çok kullanılan iki temel ölçüt "hız" ve "kaynak kullanımı"dır. Ancak default ölüçüt - olarak her zaman "hız" kullanılmaktadır. Alternatif algoritmaların hızlarını karşılaştırmak da o kadar olmayabilir. - Çünkü algoritmalar listeler gibi birtakım veri yapıları üzerinde işlemler yapıyor olabilir. Onların çalışma hızı o - veri yapısının dağılımına göre değişebilir. Örneğin falanca sort algoritması filance biçimdeki dizilerde daha hızlı çalışırken - başka dizilerde daha yavaş çalışıyor olabilir. Algoritmaların kıyaslanması sürecine genel olarak "algoritma analizi" - denilmektedir. - - Bazen algortimaların kesin sonucu bulması mevcut bilgisayarlarla seneler sürüyor olabilir. Bu tür durumlarda kesin sonucu bulmak - yerine "nispeten tatimin edici" bir sonucun bulunması da arzu edilebilmektedir. Genellikle kesin çözümü bulmayan - bu tür adımlar topluluğuna "sezgisel yöntemler (heuristics)" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da sonsuz döngü oluşturmak için while anahtar sözcüğünün yanındaki ifadeyi True yapabiliriz. Örneğin: - - while True: - ... - - Burada koşul her zaman sağlanacağına göre bu döngü de her zaman yinelenecektir. Tabii bir döngünün sürekli dönmesi çoğu kez - arzu edilen bir durum değildir. Şüphesiz sonsuz döngü için while yanındaki ifadeyi sıfır dışı herhangi bir sayı biçiminde de - yazabiliri. Örneğin: - - while 1: - .... - - Ancak sonsuz döngü oluşturmak için while anahtar sözcüğünün yanındaki ifadeyi açıkça True yapmak daha anlaşılabilir - bir durum oluşturur. Bir Python programı sonsuz döngüye girmişse komut o programı biz komut satırından çalıştırmışsak - Ctrl+C tuşları ile programı zorla durdurabiliriz. IDE'lerde durdurmak için fare durdurma simgesine tıklamak gerekir. - Aşağıdaki programı çalıştırıp zorla sonlandırmayı deneyiniz. -#------------------------------------------------------------------------------------------------------------------------ - -i = 0 -while True: - print(i) - i += 1 - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belrttiğimiz gibi Python'da C/C++, Java ve C# gibi dillerde bulunan "kontrolün sonda yapıldığı while - döngüleri (do-while döngüleri)" yoktur. Bazı algortimik problemlerde kontrolün sonra yapıldığı while döngüleri - işlemleri kolaylaştırabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - for döngülerinin genel biçimi şöyledir: - - for in : - - Python'da for döngüleri C/C++, Java ve C#'taki belli bir miktar yinelemeye yol açan tarzda for döngüleri değildir. Python'daki - for döngüleri diğer bazı dillerdeki "foreach" döngüleri gibidir. Yani Python'da for döngüleri aslında dolaşılabilir nesneleri - dolaşan bir döngüdür. - - for döngüleri şöyle çalışır: Döngünün her yinelenmesinde dolaşılabilir nesnenin sıradaki elemanı döngü değişkenine - atanır. (Tabii aslında onun adresi döngü değişkenine atanır.) Sonra suit çalıştırılır. Böylece elemanlar sırasıyla tek tek - for döngüsündeki değişkene atanmış olur. Dolaşılabilir nesne bittiğinde dolaşım da biter. - Örneğin: - - a = [10, 20, 30, 40, 50] - for x in a: - print(x) - - Burada döngünün her yinelenmesinde x'e listenin elemanları (yani o elemanların adresleri) atanacaktır. Liste elemanları - bittiğinde döngü bitecektir. Tabii aslında döngü değişkenine atama bir adres atamasıdır. Yani yukarıdaki örnekte aslında - x' sırasıyla 10, 20, 30, 40, 50 nesnelerinin adresleri atanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -for x in a: - print(x, end=' ') - -print() - -i = 0 -while i < len(a): - print(id(a[i])) - i += 1 - -print() - -for x in a: - print(id(x)) - -#------------------------------------------------------------------------------------------------------------------------ - Biz for döngüleri ile dolaşılabilir olan her nesneyi dolaşabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -s = 'ankara' - -for c in s: - print(c) - -#------------------------------------------------------------------------------------------------------------------------ - Bir sözlük dolaşıldığında sözlüğün anahtarlarının elde edildiğini anımsayınız. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 58, 'veli': 87, 'selami': 59, 'ayşe': 98, 'fatma': 81} - -for x in d: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir olmayan nesnelere for döngüüsyle dolaşılamazlar. Örneğin: - - a = 10 - - for x in a: - print(x) - - Burada in anahtar sözcüğünün yanındaki ifade int türdendir. int türü de dolaşılabilir bir tür değildir. Dolayısıyla burada - bir error oluşaacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz Python'da C/C++, Java ve C# gibi dillerdeki for döngülerini range fonksiyonu ile emüle edebiliriz. Örneğin: - - for i in range(n): - ... - - Bu for döngüsü C'deki aşağıdaki for döngüsüne benzemektedir: - - for (i = 0; i < n; ++i) { - ... - } -#------------------------------------------------------------------------------------------------------------------------ - -for i in range(10): - print(i, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Bir listeyi dolaşmak isteyelim. for döngüsü ile iki alternatif yöntem kullanılabilir. Birincisi indeks yoluyla dolaşma - olabilir. Örneğin: - - a = [10, 20, 30, 40, 50] - - for i in range(len(a)): - print(a[i], end=' ') - -İkincisi liste dolaşılabilir bir nesne olduğuna göre onu doğrudna dolaşmak olabilir. Örneğin: - - for x in a: - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -for i in range(len(a)): - print(a[i], end=' ') - -print() - -for x in a: - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - for döngüsünde biz döngü değişkenini değiştirmekle dolaşılabilir nesneyi değiştirmiş olmayız. Örneğin bir listenin - elemanlarını onların kareleriyle değiştirmek isteyelim. Bunu şöyle yapamayız: - - for x in a: - x = x ** 2 - - Çünkü biz burada x'i değiştiriyoruz, a'da bir değişiklik yapmıyoruz. Bu tür durumlarda indeks yoluyla listeye erişmek gerekir. Örneğin: - - for i in range(len(a)): - a[i] = a[i] ** 2 - - Burada gerçekten liste elemenaı değiştirilmiştir. - -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -for x in a: - x = x ** 2 - -print(a) - -for i in range(len(a)): - a[i] = a[i] ** 2 - -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii dolaşılabilir bir nesne de dolaşılabilir nesnelerden oluşuyor olabilir. Örneğin bir demet listesi söz konusu olabilir. - Bu durumda biz listeyi dolaştığımızda demetleri elde ederiz: - - a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] - - for t in a: - print(t) - - Burada her dolaşımda elde edilen demetler de iç bir for döngüsüyle dolaşılabilir. Örneğin: - - for t in a: - for k in t: - print(k, end=' ') - print() - -#------------------------------------------------------------------------------------------------------------------------ - -a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] - -for t in a: - print(t) - -print() - -for t in a: - for k in t: - print(k, end=' ') - print() - -#------------------------------------------------------------------------------------------------------------------------ - Eğer for döngüsü ile dolaşımda dolaştıkça elde edilen nesneler de dolaşılabilir ise biz "açım (unpacking)" işlemini for - dönüsünün içerisinde yapabiliriz. Örneğin: - - a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] - - for t in a: - x, y = t - ... - - Bu işlemi for içerisinde de tek hamlede de yapabiliriz: - - for x, y in a: - ... - - Tabii bu açım işleminde köşeli parantezler ya da normal parantezler de kullanılabilir. Ancak gereksizdir. Örneğin: - - for [x, y] in a: - print(x, y) - - for (x, y) in a: - print(x, y) - -#------------------------------------------------------------------------------------------------------------------------ - -a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] - -for t in a: - x, y = t - print(x, y) - -print() - -for x, y in a: - print(x, y) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da _ aslında normal bir değişken ismi olarak kullanılabilmektedir. Ancak genel olarak programcılare _ ismini - "ben bu değişkenle ilgilenmiyorumi yalnızca bu değişkeni yer turucu olarak kullanıyorum" anlamında kullanmaktadır. Örneğin biz - 1000 kere dönen bir döngüde bir şeyler yapacak olalım. Ancak döngü değişkenini hiç döngü içerisinde kullanmayacak olalım. Bu durumu - vurgulamak için döngü değişkeni için _ ismini kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -for _ in range(1000): - pass - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerin de dolaşılabilir olduğunu, sözlükleri dolaştıkça anahtarların elde edildiğini anımsayınız. Tabii elimizde - bir anahtar varsa biz onun değerini de eld edebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -for key in d: - print(key, d[key]) - -#------------------------------------------------------------------------------------------------------------------------ - dict sınıfının items isimli metodunuın bize dolaşılabilir bir nesne verdiğini, o nesne dolaşıldığında da anahtar değer - çiftlerindne oluşan demetlerin elde edildiğini anımsayınız. O zaman biz sözlükteki anahtar değer çiftlerini şöyle de elde edebiliriz: - - for key, value in d: - print(keyi value) -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -for t in d.items(): - print(t) - -print() - -for key, value in d.items(): - print(key, value) - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir bir nesneyi tersten dolaşabilir miyiz? Bunun yanıtı "o nesneye ilişkin sınıfı yazan kişi buna izin verdiyse - dolaşabiliriz" biçimindedir. Tabii str gibi, list gibi, tuple gibi nesneler ters indekslemeyle ya da ters çeviren dilimleme - ile terten dolaşılabilirler. Bu türlere daha önceden de belirttiğimiz gibi "sequence type" denilmektedir. Pekiyi ya - dolaşılabilir nesne bir "sequence type" değilse? - - Aşağıda bir listenin tersten dolaşımına bir örnek görüyorsunuz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -for i in range(len(a) - 1, -1, -1): - print(a[i], end=' ') - -print() - -for x in a[::-1]: - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Tersten dolaşmanın daha genel bir biçimi daha önce görmüş olduğumuz "reversed" fonksiyonunu kullanmaktadır. Anımsanacağı gibi - reversed fonksiyonu bize tersten dolaşılabilir bir nesne vermekteydi. O zaman tersten dolaşımı aşağıdaki gibi de yapabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -for x in reversed(a): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Tabii biz her dolaşılabilir nesneyi reversed fonksiyonuna sokamayız. Bir dolaşılabilir nesnenin tersten dolaşılabilirliği o sınıfı - yazanlar tarafından sağlanmaktadır. Örneğin kümeler dolaşılabilir nesnelerdir. Kümeler dolaşıldığında elemanların hangi sırada elde - edileceğinin bir garantisi yoktur. Ancak kümeler tersten dolaşılabilir değildir. Daha önceden de belirtildiği gibi Python'da sonradan - 3.7 versiyonu ile birlikte sözlükler dolaşılırken elemanların onların ekleme sırasına göre dolaşılması garanti edilmiştir. - Bu eklemeden sonra artık sözlükler de reversed fonksiyonu ile tersten dolaşılabilir hale gelmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 5: 'selami'} - -d[100] = 'sacit' -d[300] = 'fehmi' - -for x in reversed(d): - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - 24. Ders - 29-06/2022-Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - break deyimi yalnızca döngüler içerisinde kullanılabilir. Genel biçimi şöyledir: - - break - - Programın akışı break anahtar sözcüğünü gördüğünde döngü deyimi sonlandırılır ve akış döngüden sonraki ilk deyim ile devam eder. - Tabii genellikle break bir koşul ile birlikte kullanılır. Örneğin: - - while True: - ... - if koşul: - break - ... - -#------------------------------------------------------------------------------------------------------------------------ - -while True: - val = float(input('Bir sayı giriniz:')) - if val == 0: - break - print(val * val) - -#------------------------------------------------------------------------------------------------------------------------ - İç içe döngülerde break yalnızca kendi döngüsünü sonlandırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -for i in range(10): - for k in range(10): - print(f'({i},{k})') - s = input() - if s == 'quit': - break - if s == 'quit': - break - -#------------------------------------------------------------------------------------------------------------------------ - Programın akışı continue anahtar sözcüğünü gördüğünde döngü başa sararak yeni bir yinelemeye geçilir. break deyimi - döngünün kendisini sonlandırırken continue deyimi döngü içerisindeki suite'i sonlandırmaktadır. Yani akış continue - deyimini gördüğünde sanki suit bitmiş de yeni bir yineleme yapılıyormuş gibi bir etki oluşur. Ancak continue - deyimine break deyiminden çok daha seyrek gereksinim duyulmaktadır. - - Aşağıdaki örnekte çift sayılarda programın akışı continue deyimini gördüğü için döngü başa sarmaktadır. Dolayısıyla ekrana - yalnızca tek sayılar basılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -for i in range(10): - if i % 2 == 0: - continue - print(i) - -#------------------------------------------------------------------------------------------------------------------------ - continue deyimi genellikle döngü içerisindeki uzun if bloklarını elimine etmek için kullanılmaktadır. Örneğin: - - while True: - a = int(input()): - if a % 7 == 0: - ... - ... - ... - ... - ... - - Bu döngü continue ile daha analaşılabilir bir hale getirilebilir: - - while True: - a = int(input()): - if a % 7 != 0: - continue - ... - ... - ... - ... - ... - - Aşağıdaki örnekte bir komut satırı (ya da REPL) ortamı yaratılmak istenmiştir. Programda bir prompt eşliğinde bir komut - girilmesi istenmektedir. Eğer komut önceden belirlenen komutlardan bir tanesi ise ilgili işlem yapılmakta, değilse ekrana - "invalid command!" yazısı çıkartılmaktadır. Eğer kişi yalnızca boşluklar girerek ENTER tuşuna basarsa bu durumda - continue ile akış başa sardırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -while True: - print('CSD>', end='') - cmd = input().strip() - if cmd == '': - continue - if cmd == 'quit': - break - if cmd == 'copy': - print('copy command executes...') - elif cmd == 'rename': - print('rename command executes...') - elif cmd == 'dir': - print('dir command executes...') - else: - print('invalid command!') - -#------------------------------------------------------------------------------------------------------------------------ - Bir sayının basamak sayısını bulmak için bir döngü içerisinde sayı sürekli 10'a bölünebilir ve bu işlemin kaç kere yapıldığı - hesaplanabilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir sayı giriniz:')) - -count = 0 -while a: - count += 1 - a //= 10 - -print(count) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii aslında sayının basamak sayısı hiç döngü kullanmadan 10 tabanına göre logaritmasının 1 fazlası olarak elde edilebilir. - 10 tabanına göre logaritma hesabı yapan standart math modülünde log10 isimli bir fonksiyon vardır. Bu fonksiyon sonucu bize - float olarak verir. float sayıyı noktadan kurtarmak için int dönüştürmesi yapılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -a = int(input('Bir sayı giriniz:')) -result = int(math.log10(a)) + 1 -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Alternatif bir çözüm de int türden sayıyı önce yazıya yani str türüne dönüştürmek sonra uzun karakter uzunluğuna bakmak - olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -val = int(input('Bir sayı giriniz:')) - -result = len(str(val)) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da pass deyimi "boş deyim" anlamına gelmektedir. Normalde if gibi while gibi for gibi deyimlerde bir suite bulundurmak - zorunludur. Ancak bazen biz içi boş bir suit oluşturmak isteyebiliriz. İşte o zaman pass deyimi kullanılır. Örneğin: - - for _ in range(10000000): - pass - - Burada programcı for döngüsünün içerisinde bir şey yapmak istememiştir. Burada programcının belki de amacı akışı bir süre - meşgul bir döngüde bekletmektir. C, C++, Java ve C# gibi dillerde ';' boş deyim anlamına gelir. Ancak Python'da böyle - bir kullanım yoktur.. -#------------------------------------------------------------------------------------------------------------------------ - -for i in range(10): - print(i) - for k in range(10000000): - pass - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin biz kullanıcıyı pozitif bir değer girme konusunda zorlamak istebiliriz. Bunu bir while döngüsü içerisinde Walrus - operatörü kullanarak sağlayabiliriz. Ancak bu durumda gerçekten de döngünün içerisinde yapacak bir şey kalmamaktadır. - O halde biz de pass deyimi ile sentaksın gereksinim duyduğu deyimi suite yerine yerleştirebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -while (val := int(input('Pozitif bir sayı giriniz:'))) <= 0: - pass - -print(val ** 0.5) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örneklerde kişiler sanki pass deyimi gereksizmiş gibi bir izlenime kapılabilmektedir. continue bir deyim olduğuna - göre ve sonraki yinelemeye geçmeyi sağladığına göre yukarıdaki örneklerde pass yerine continue yerleştirsek aynı durumu - oluşturabiliriz. Örneğin: - - for _ in range(10000000): - pass - - ile aşağıdaki deyim arasında işleyiş bakımından gerçekten bir fark yoktur: - - for _ in range(10000000): - continue - - Ancak continue deyimi pass deyiminin yerini tutamamaktadır. Çğnkü continue deyimi yalnız döngülerde kullanılabilmektedir. - Halbuki pass deyimi normal bir deyimdir. Her yerde kullanılabilir. Örneğin ilerleyen zamanlarda fonksiyonlar konusunu - göreceğiz. Fonksiyonlar da suit içermektedir. Dolayısıyla örneğin içi boş bir fonksiyon yazmak için continue deyimini kullanamayız. - Fakat pass deyimini kullanabiliriz: - - def foo(): - pass -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii aslında pass deyimi suite içerisinde kullanılmak zorunda değildir. Herhangi bir yerde kullanılabilir. pass deyimi - hiçbir işleme yol açmaz. Şüphesiz herhangi bir yerde gereksiz pass kullanımı da kötü bir tekniktir. Örneğin: - - x = 10 - pass # gerek yok, kötü teknik! - print(x) - pass # gerek yok, kötü teknik! - print(x * x) - -#------------------------------------------------------------------------------------------------------------------------ - pass deyimi ileride görecek olduğumuz fonksiyonlar, sınıflar gibi deyimlerde de boş suit oluşturmak amacıyla da - kullanılabilmektedir: - - def foo(): # içi boş fonksiyon - pass - - class Sample: # içi boş sınıf - pass -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 25. Ders 04/07/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da diğer bazı dillerdeki gibi bir switch deyimi yoktu. Ancak Python'un 3.10 versiyonuyla birlikte dile "match" - adı altında switch benzeri bir deyim eklenmiştir. Kursun yapıldığı sırada Python'un son versiyonu 3.11.4'tür. Dolayısıyla - aşağıda açıklanacak olan match deyiminin çalıştırılması için yorumlayıcınızın versiyonuna dikkat ediniz. Eğer Anaconda dağıtımında - çalışıyorsanız. "Envrionment" sekmesinden yeni bir "Virtual Envirionment" yaratıp Python'un en güncel versiyonunu yükleyebilirsiniz. - - Python'daki match deyimi diğer bazı dillerdeki switch deyimininden daha ayrıntılı ve daha yeteneklidir. match deyimi - "Structural Pattern Matching" başlığı altında PEP-634 dokmanında açıklanmıştır. PEP-636 dokğmanı da eğtici (tutorial) biçimde - hazırlanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - match deyiminin genel biçimi şöyledir: - - match : - case : - - case : - - case : - - .... - - match deyimi şöyle çalışmaktadır: Yorumlayıcı Önce match anahtar sözcüğünün yanındaki ifadeyi inceler. Sonra case - bölümlerini sırasıyla gözden geçirir. match ifadesi ile case bölümlerinde kalıp uyuşursa ilk uyuşan case bölümündeki - suite'i çalıştırır. - - Diğer bazı dillerde olduğu gibi Python'da case bölümleri break gibi bir deyimle sonlandırılmanaktadır. match deyiminde - zaten bir case kalıbı ile uyuşum sağlandığında yalnızca o case bölümündeki deyimler çalıştırılmaktadır. match deyiminde - diğer bazı dillerde olduğu gibi "aşağıya düşme (fall through)" mekazizması yoktur. Bir case bölümü uyuşumu sağlarsa yalnızca - o case bölümü çalıştırılır. Sonra match deyimi biter ve programın akışı sonraki deyim ile devam eder. - - Python'ın match deyiminde case bölümlerinde birden fazla uyuşum söz konusu olabilir. Bu durumda yukarıdan aşağıya doğru ilk uyuşan case bölümü - çalıştırılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) - -match a: - case 1: - print('bir') - case 2: - print('iki') - case 3: - print('üç') - case 4: - print('dört') - case 5: - print('beş') - -#------------------------------------------------------------------------------------------------------------------------ - Diğer bazı dillerde switch deyiminin eğer hiçbir case bölümü uyuşum sağlamazsa çalıştırılan bir "default" bölümü vardır. - Python'ın match deyiminde "default" bölüm yoktur. Ancak onunla aynı anlama gelen _ kalıbı vardır. Bu kalıp eğer yukarıdaki - kalıpların hiçbiri uyum sağlamazsa uyum sağlar. _ kalıbı case bölümlerinin sonuna yerleştirilmek zorundadır. (C, C++, - Java ve C# gibi dillerde default bölümün sonda olması zorunlu değildir.) -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) - -match a: - case 1: - print('bir') - case 2: - print('iki') - case 3: - print('üç') - case 4: - print('dört') - case 5: - print('beş') - case _: - print('hiçbiri') - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda belirttiğimiz gibi Python'a eklenen match deyimi oldukça detaylı ve yetenekli bir deyimdir. case bölümlerindeki - kalıplar değişik biçimde oluşturulabilmektedir. Yukarıdaki örneklerde biz case anahtar sözcüğünün yanına birer sabit yazdık. - (Buraya diğer dillerdeki gibi sabit ifadeleri yazamayız. Tek bir sabit yazmak zorundayız). Bu tür kalıplara "sabit kalıpları - (iteral patterns)" denilmektedir. Sabit kalıpları tek bir sabittten oluşur. Bu sabitler string de olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -s = input('Bir şehir giriniz:') - -match s: - case 'ankara': - print('06') - case 'eskişehir': - print('26') - case 'kocaeli': - print('41') - case 'adana': - print('01') - case 'izmir': - print('35') - case _: - print('hiçbiri') - -#------------------------------------------------------------------------------------------------------------------------ - Bir case bölümünde "veya" biçiminde birden fazla kalıp "|" atomu ile oluşturulabilmektedir. Bu kalıba "veya kalıbı (or pattern)" - denilmektedir. Örneğin: - - case 1 | 2 | 3: - pass - - Burada bu kalıplardan herhangi biri uyuşum sağlarsa ilgili case bölümü çalıştırılır. -#------------------------------------------------------------------------------------------------------------------------ - -while True: - cmd = input('CSD>').strip() - if cmd =='': - continue - match cmd: - case 'copy': - print('copy executes') - case 'rename': - print('rename executes') - case 'del' | 'erase' | 'remove': - print('delete executes...') - case 'quit' | 'exit': - break - case _: - print(f'invalid command: {cmd}') - -#------------------------------------------------------------------------------------------------------------------------ - Veya kalıbında veya kalıbını parantez içerisine alarak case bölümüne bir "as" cümleceği ekleyebiliriz. as cümleceğini - bir değişken izler bu değişken hangi veya kalıbı uyuşum sağladıysa onun değerini barındırır. Örneğin: - - case ('del' | 'erase' | 'remove') as as_cmd: - print(f'{as_cmd} executes...') - - Burada del, erase ya da remove komutlarından hangisi yazılmışsa as_cmd onu belirtecektir. Aşağıdaki örnekte veya kalıbında - as cümleceği kullanılmıştır. Bu örnekte zaten match ifadesi uyuşum sağlayan komutu içerdiğine göre as cümleceği gereksizmiş - düşünülebilir. Ancak as cümleceği diğer kalıplarda da kullanılabilmektedir. Bu tür durumlarda faydalı durumlara yol açabilmektedir. - - Eğer ilgili case bölümü uyuşum sağlamazsa as anahtar sözcüğünün yanındaki değişken hiç yaratılmamış olacaktır. Bu - nedenle buradaki as değişkenini match deyimi dışında kullanırken dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -while True: - cmd = input('CSD>').strip() - if cmd =='': - continue - match cmd: - case 'copy': - print('copy executes') - case 'rename': - print('rename executes') - case ('del' | 'erase' | 'remove') as as_cmd: - print(f'{as_cmd} executes...') - case 'quit' | 'exit': - break - case _: - print(f'invalid command: {cmd}') - -#------------------------------------------------------------------------------------------------------------------------ - case bölümlerinde kullanılabilen diğer önemli bir kalıp da "dizilim (sequence)" kalıbıdır. Eğer match anahtar sözcüğünün - yanındaki ifade bir "dizilim türünden (sequence type)" ise (burada string dizilim türünden kabul edilmemektedir) bu durumda - case bölümleri dizilim kalıbına sahip olabilir. Standartlarda ve PEP 634'te dizilim türlerinin neler olduğu açıklanmıştır. - Ancak biz kursta dizilim türü için geldiğimiz noktaya kadar görmüş olduğumuz "list" ve "tuple" türlerini kullanabiliriz. - O halde özetle bizim bir dizilim kalıbını kullanabilmemiz için match yanındaki ifadenin bir liste ya da demet olması gerekir. - Bu durumda case yanındaki ifade de bir liste, demet olabilir. match anahtar sözcüğünün yanındaki ifadenin liste ya da demet - olması ve case anahtar sözcüğünün yanındaki ifadenin liste demet olması tamamen aynı etkiyi yaratmaktadır. Bu durumda dizilim - kalıbında case anahtar sözcüğünün yanındaki ifade aşağıdaki biçimlerden birine ilişkin olur: - - case [val1, val2, val3, ..., valn]: - case (val1, val2, val3, ..., valn): - case val1, val2, val3, ..., valn: - - Bu biçimlerin hepsi birbirleriyle eşdeğerdir. Aralarında hiçbir farklılık yoktur. Burada uyuşum için dizilimdeki - elemanların sırasıyla val1, val2, val3, ..., valn ile aynı olması gerekmektedir. - - Aşağıdaki örnek yukarıdaki örneğin dizilim kalıplı biçimidir. Ancak aslında dizilim kalıbının kullanılma nedeni aşağıdaki - örnekle örtüşmemektedir. Dizilim kalıplarında as cümleceği matsch yanındaki ifade hangi türden dizilim olursa olsun her zaman bir listedir. -#------------------------------------------------------------------------------------------------------------------------ - -while True: - cmd = input('CSD>').split() - if len(cmd) == 0: - continue - match cmd: - case 'copy', : - print('copy executes') - case ['rename']: - print('rename executes') - case (['del'] | ['erase'] | ['remove']) as as_cmd: - print(f'{as_cmd[0]} executes...') - case ['quit'] | ['exit']: - break - case _: - print(f'invalid command: {cmd[0]}') - -#------------------------------------------------------------------------------------------------------------------------ - Dizilim kalıbında dizilimin elemanları olarak sabit yerine değişken isimleri de bulundurulabilir. Bu durumda uyuşum her - zaman sağlanır ve dizilimin ilgili elemanı o değişkene atanır. Örneğin: - - case ['del', path]: - pass - - Buradaki dizilim kalıbı şu anlama gelmektedir: "Dizilimin birinci elemanı 'del' yazısı ikinci elemanı herhangi bir şey olabilir. - Ancak bu herhangi bir şey her ne ise path değişkenine atanacaktır." Dolayısıyla bu kalıp aşağıdaki gibi dizilimlerle eşleşebilir: - - ['del', 'a.txt'] - ('del', 'b.txt') - ['del', 123] - ... - - Burada biz hem del eşleşmesini sağlayıyoruz hem de del komutunun yanındaki yazıyı elde etmiş oluyoruz. Tabii yukarıdaki kalıp aşağıdaki gibi - bir dizilimle uyuşmaz: - - ['del', 'a.txt', 'b.txt'] - - Çünkü kalıptaki path tek bir eleman anlamına gelmektedir. Yukarıdaki kalıp aşağıdakiyle de eşleşmez: - - ['del'] -#------------------------------------------------------------------------------------------------------------------------ - -while True: - cmd = input('CSD>').split() - if len(cmd) == 0: - continue - match cmd: - case 'copy', source_path, dest_path: - print(f'copy {source_path} {dest_path} executes') - case 'rename', source_path, dest_path: - print(f'rename {source_path} {dest_path} executes') - case (['del', path] | ['erase', path] | ['remove', path]) as as_cmd: - print(f'{as_cmd[0]} {path} executes') - case ['quit'] | ['exit']: - break - case _: - print(f'invalid command: {cmd[0]}') - -#------------------------------------------------------------------------------------------------------------------------ - 26. Ders 04/07/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dizilim kalıbında dizilimin bir elemanı *'lı bir isimden oluşabilir. Ancak dizilimde tek bir *'lı eleman olabilir. - *'lı eleman sıfır ya da daha fazla elemanla uyuşum sağlar. *'lı elemanda *'ın yanındaki değişken her zaman list türünden - olmaktadır. Bu değişken uyuşum sağlayan elemanları barındıran bir list nesnesi biçimindedir. Örneğin: - - a = [10, 20, 30, 40, 50] - - match a: - case 10, 20, *others: - print(others) # [30, 40, 50] - #.... - - Burada birinci case uyuşum sağlayacaktır. Dizilimin 30, 40, 50 elemanları bir liste olarak others değişkenine atanacaktır. - Yukarıda da belirttiğimiz gibi burada dizilim ne olursa olsun *'lı eleman her zaman liste olur. case anahtar sözcüğünün yanında - birden fazla *'lı eleman içeren dizilim kullanılamaz. Ancak *'lı eleman tipik olarak sonda bulunuyor olsa da aslında sonda bulunmak - zorunda değildir. Örneğin: - - a = [10, 20, 30, 40, 50] - - match a: - case 10, *others, 50: - print(others) # [20, 30, 40] - - Dizilim kalıbında uyuşumun sağlanması için her zaman dizilimin tüm elemanlarının case içerisinde eşleştirilmiş olması gerekmektedir. Örneğin - aşağıdaki case uyuşum sağlamaz: - - a = [10, 20, 30, 40, 50] - - match a: - case 10, *others, 40: - print(others) - - Ancak aşağıdaki case uyuşum sağlar: - - a = [10, 20, 30, 40, 50] - - match a: - case 10, *others, 40, 50: - print(others) # [20, 30] - - Dizilim kalıbında case bölümünde birtakım değişken isimleri yazılabilir. Bu durumda bu değişken isimleri her zaman uyuşum sağlar ve - uyuşum sağlandığında bu değişken isimlerine dizilimin ilgili elemanları atanmış olur. Örneğin: - - a = [10, 20, 30, 40, 50] - - match a: - case 10, x, y, 40, 50: - print(x, y) # 20 30 - - Burada x ve y her zaman uyuşum sağlayacaktır ve x'ya 20, y'ye de 30 atanacaktır. - - a = [10, 20, 30, 40, 50] - - match a: - case 10, x, y, 40, 50: - print(x, y) # 20 30 - - Eğer buradaki değişkenin önemi yoksa bu durumda genellikle _ tercih edilir. Normalde Python'da _ aslında geçerli bir değişken ismidir. - Ancak match deyiminde _ bir değişken olarak değil "wildcard pattern" denilen, "her zaman uyuşum sağlama anlamında kullanılmaktadır. Örneğin: - - a = [10, 20, 30, 40, 50] - - match a: - case 10, _, _, 40, 50: - print('matched') # Artık _ değişkenini burada kullanamayız, özel anlamı var - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - case anahtar sözcüğünün yanındaki ifade bir değişken olursa buna "capture pattern" denilmektedir. Bu durumda bu değişkenin - içerisindeki değere bakılmaz. Uyuşumun her zaman sağlandığı kabul edilir ve match ifadesi bu değişkene atanır. Tabii - "capture pattern" her şeye uyum sağladığı için case bölümlerinin sonuna yerleştirilmelidir. Eğer "capture pattern" case - bölümlerinin sonuna yerleştirilmezse sentaks error oluşur. Capture pattern tipik olarak '_' ile kullanılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) -match a: - case 10: - print('on') - case 20: - print('yirmi') - case x: # capture pattern - print(f'x = {x}') # a neyse o yazdırılır - -#------------------------------------------------------------------------------------------------------------------------ - Diğer bir case kalıbı da "sözlük kalıbı (mapping pattern)" denilen kalıptır. Bu kalığta match yanındaki ifade bir sözlüktür - Bu durumda case anahtar sözcüğünün yanında da bir sözlüğün bulunması gerekir. Tabii sözlük doğrudan küme parantezleriyle - yazılmalıdır. Bu durumda case bölümüne yazılan sözlük ifadesinde anahtar-değer çiftleri uyuşursa case uyuşumu sağlanmış - kabul edilir. Dizilim kalıbına benzemeyen biçimde sözlük kalıbında match ifadesindeki sözlüğün her elemanının case bölümündeki - sözlükle uyuşması gerekmemektedir. Önemli olan case bölümündeki sözlüğün yazılan elemanlarının uyuşumudur. Uyuşum hem - anahtar hem değer ile yapılır. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -match d: - case {'ali': 10, 'veli': 60}: - print('match etmeyecek') - case {'selami': 30, 'ayşe': 40}: - print('match edecek') # match edecek - -#------------------------------------------------------------------------------------------------------------------------ - Sözlük kalıbında değerde _ kullanılırsa değerin her zaman uyuştuğu kabul edilir. Örneğin: - - case {'ali': 10, 'veli': _}: - pass - - Burada anahtar 'veli' ancak değer ne olursa olsun uyuşum sağlanmaktadır. _ karakteri yalnızca sözlüğün değerlerinde - kullanılabilmektedir. Anahtarlarında kullanılamamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -match d: - case {'ali': 10, 'veli': _}: - print('match edecek') # match edecek - -#------------------------------------------------------------------------------------------------------------------------ - Aslında kalıptaki sözlüğün değer kısmında bir değişken ismi de getirilebilir. Bu durumda anahtar uygunsa değer her zaman - uyuşma sağlar ve değer de aynı zamanda buradaki değişkene yerleştirilir. Örneğin: - - case {'ali': 10, 'veli': x}: - print(x) - - Burada 'veli' anahtarı uyuşursa bunun değeri de uyuşmuş kabul edilir. Dolayısıyla değeri x değişkenine yerleştirilmektedir. - Ancak anahtar için bir değişken ismi getirilememektedir. - -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -match d: - case {'ali': 10, 'veli': x}: - print(x) # 20 - -#------------------------------------------------------------------------------------------------------------------------ - Sözlük kalıbında sözlük elemanı olarak **'lı bir değişken kullanılabilir. Bu **'lı değişkene geri kalan tüm sözlük elemanlarına - uyuşum sağlayan bir sözlük yerleştirilir. Ancak **'lı değişkenin bir tane olması ve sözlüğün sonunda bulunması zorunludur. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -match d: - case {'selami': 30, 'fatma': 50, **others}: - print(others) # {'ali': 10, 'veli': 20, 'ayşe': 40} - -#------------------------------------------------------------------------------------------------------------------------ - case ifadelerinde *'lı değişkeninin liste ve demetler için kullanıldığına, **'lı değişkenin sözlükler için kullanıldığına - dikkat ediniz. Aslında *'lı ve **'lı değişkenler zaten Python'ın ileride göreceğimiz başka konularında da karşımıza çıkacaktır. - match deyimi dile eklandiğinde zaten var olan *'lı ve **'lı sentakslar burada da kullanılmaya başlanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında match deyiminde "sınıf kalıbı (class paterrn)" da kullanılabilmektedir. Ancak henüz sınıf konusunu ele almadığımızdan dolayı - bu kalıp üzerinde burada durmayacağız. Aşağıda basit bir örnek verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - -s = Sample(10, 20) -match s: - case Sample(a = 10, b = 20): - print('match edecek') # match edecek - -#------------------------------------------------------------------------------------------------------------------------ - 27. Ders 18/07/2022-Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - match deyiminde case bölümlerinde "koruma (guard)" da oluşturulabilmektedir. case bölümlerinde if anahtar söcüğüyle - ayrı bir koşul belirtilebilir. Bu durumda bu case bölümünün uyuşum sağlaması için o if koşulunun da sağlanması gerekir. Guard - oluşturmanın genel biçimi şöyledir: - - case if : - ... - - Örneğin: - - match a: - case 10 if x > 0: - pass - - Burada case bölümünün uyuşum sağlaması için a'nın 10 olmasının yanı sıra aynı zamanda x'in de 0'dan büyük olması - gerekmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir sayı giriniz:')) -x = -2 - -match a: - case 10 if x > 0: - print('Ok') - case _: - print('cannot match...') - -#------------------------------------------------------------------------------------------------------------------------ - case bölümündeki koruma kısmı bazı durumlarda pratik kullanımlara yol açabilmektedir. Örneğin uyuşum dışında ek başka - koşulların kontrolü bu sayede yapılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -cmds = input('Komut giriniz:').split() - -match cmds: - case ['delete', *args] if len(cmds) == 2: - print('delete command') - case _: - print('cannot match...') - -#------------------------------------------------------------------------------------------------------------------------ - Koşul operatörü (conditional operator) üç operandlı (ternary) bir operatördür. Operatör if deyimine benzemekle birlikte bir değer üretir. - Bu bağlamda ifadelerin içerisinde kullanılabilir. C, C++, Java ve C# gibi dillerde bu operatörün benzeri ?: biçiminde bulunmaktadır. - Koşul operatörü -ismi üzerinde- bir deyim değildir. Bir operatördür. Yani bir değer üretmektedir. Genel biçimi şöyledir: - - if else - - Koşul operatörü şöyle çalışmaktadır: if anahtar sözcüğünün yanındaki ifade bool türünden değilse bool türüne dönüştürülür. - Eğer bu ifade True ise yalnızca ifade1 yapılır ve operatör bu değeri üretir. Eğer bu ifade False ise yalnızca ifade3 yapılır ve operatör - bu değeri üretir. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir sayı giriniz:')) - -b = 100 if a % 2 == 0 else 200 - -print(b) - -#------------------------------------------------------------------------------------------------------------------------ - Şüphesiz koşul operatörü ile yapılan şeyler aslında if deyimiyle de yapılabilir. Ancak koşul operatörü bazı durumlarda - okunabilirliği artırmakta ve pratik bazı kullanımlara olanak sağlamaktadır. Örneğin: - - b = 100 if a % 2 == 0 else 200 - - Biz bu işlemi if deyimi ile aşağıdaki gibi de yapabilirdik: - - if a % 2 == 0: - b = 100 - else: - b = 200 - -#------------------------------------------------------------------------------------------------------------------------ - Koşul operatörünü if deyimi gibi kullanmak kötü bir tekniktir. Örneğin: - - a = int(input('Bir sayı giriniz:')) - - print('çift') if a % 2 == 0 else print('tek') # kötü teknik! - - Koşul operatörünün ürettiği değerin bir biçimde aynı ifadede kullanılması gerekir. Örneğin bir karşılaştırma sonucunda - bir değişkene değer atama işlminde koşul operatörünü kullanabiliriz: - - days = 366 if isleap(year) else 365 # doğru kullanım - - Koşul operatörü özellikle üç durumda tercih edilmelidir: - - 1) Bir karşılaştıma sonucunda elde edilen değerin bir değişkene atandığı durumlarda. Örneğin: - - result = 100 if val % 2 == 0 else 200 - - Aynı işlemi if deyimiyle şöyle de yapabilirdik: - - if val % 2 == 0: - result = 100 - else: - result = 200 - - 2) Fonksiyon ya da metot çağrımlarında argüman olarak koşul operatörü bazen yazımı kısaltmak için kullanılabilmektedir. Örneğin: - - val = int(input('Bir sayı giriniz:')) - - print('çift' if val % 2 == 0 else 'tek') - - Bu örnekte val değişkeninin çift ya tek olmasına göre print fonksiyonuna 'çift' ya da 'tek' yazısı argüman olarak - gönderilmiştir. - - Aslında burada yapılmak istenen aşağıdakiyle tamamen eşdeğerdir: - - if val % 2 == 0: - print('çift') - else: - print('tek') - - 3) return ifadelerinde de koşul operatörü bazı durumlarda tercih edilmektedir. Örneğin: - - return 100 if a % 2 == 0 else 200 - - Bu işlemin eşdeğeri şöyledir: - - if a % 2 == 0: - return 100 - else: - return 200 - - return işlemi fonksiyonların anlatıldığı izleyen bölümlerde ele alınmaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - Koşul operatör olmasaydı biz if deyimini kullanarak yine yapmak istediğimiz şeyleri yapardık. Koşul operatörü bazı - ifadelerin daha kompakt yazılabilmesine olanak sağlamaktadır. Örneğin 0'dan 100'e kadar sayıları aşağıdaki gibi - beşer beşer yazdırmak isteyelim: - - 0 1 2 3 4 - 5 6 7 8 9 - ... - - İlk aklımıza gelen şöyle bir döngü olacaktır: - - for i in range(100): - if i % 5 == 4: - print(i, end='\n') - else: - print(i, end=' ') - - Oysa koşul operatörü kullanarak bu kod parçasını daha kompakt bir biçimde yazabiliriz: - - for i in range(100): - print(i, end='\n' if i % 5 == 4 else ' ') - - Burada i'nin durumuna göre print fonksiyonunun end parametresi '\n' olarak ya da ' ' olarak girilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -for i in range(100): - print(i, end='\n' if i % 5 == 4 else ' ') - -#------------------------------------------------------------------------------------------------------------------------ - Koşul operatörü öncelik tablosunda tablonun en aşağısında hemen atama işlemlerinin yukarısında bulunmaktadır. Yani - koşul operatörü düşük öncelikli bir operatördür. - - () soldan sağa - ** sağdan sola - + - sağdan sola - * / // % soldan sağa - + - soldan sağa - < > <= >= == != soldan sağa - not sağdan sola - and soldan sağa - or soldan sağa - if else sağdan sola - = := , +=, -=, *=, ... sağdan sola - - Aşağıdaki örnekte + operatörü koşul operatörnün dışında değildir. Onun bir parçasını oluşturmaktadır: - - result = 100 if val % 2 == 0 else 200 + 300 - - Burada val çift ise result değişkenine 100 değeri tek ise 200 + 300 değeri atanacaktır. Eğer buradaki + operatörü - koşul operatöründen ayrıştırılmak isteniyorsa parantezler kullanılmalıdır. Örneğin: - - result = (100 if val % 2 == 0 else 200) + 300 - - Artık buradaki + operatörü ayrı bir operatördür. Yani val çiftse de tekse de elde edilen değere 300 toplanmaktadır. - Birtakım operatörlerin koşul operatörünün operand'ları olmaktan çıkartmak için parantezlerin kullanılması gerekmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin iki değerden büyük olanını bir değişkene atamak istediğimizde koşul operatörünü tercih edebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) -b = int(input('Bir değer giriniz:')) - -result = a if a > b else b -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii yukarıdaki örnekte aslında biz büyük olan değeri doğrudan da print ile yazdırabilirdik. -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) -b = int(input('Bir değer giriniz:')) - -print(a if a > b else b) - -#------------------------------------------------------------------------------------------------------------------------ - Koşul operatörü de iç içe kullanılabilir. İç içe kullanımda parantez kullanmak zorunlu değildir. Ancak parantezsiz - ifadeyi yazmak operatör öncelikleri dikkate alındığında zordur. Bu nedenle koşul operatörünü iç içe kullanırken - parantezleri kullanınız. Örneğin: - - a = int(input('Bir değer giriniz:')) - b = int(input('Bir değer giriniz:')) - c = int(input('Bir değer giriniz:')) - - result = (a if a > c else c) if a > b else (b if b > c else c) - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - -a = int(input('Bir değer giriniz:')) -b = int(input('Bir değer giriniz:')) -c = int(input('Bir değer giriniz:')) - -result = (a if a > c else c) if a > b else (b if b > c else c) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfta şöyle bir soruldu: Aşağıdaki kodda neden ben matrisin tek bir elemanını değiştirdiğim halde matrisin tüm - satırlarının elemanı değişmiş oluyor? - - a = [[0] * 5] - b = a * 5 - print(b) - b[0][0] = 100 - print(b) - - Buradan öyle bir çıktı elde edilmiştir: - - [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - [[100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0]] - - Bunun nedeni "yineleme (repitition)" işleminde aslında adres kopyalaması yapılmasıdır. Burada yineleme yapıldığında - artık b listesinin her elemanı aslında a listesini göstermektedir. Dolayısıyla a listesinde bir değişiklik yapıldığında - b'bnin her elemanı a'yı gösterdiği için bu değişiklik sanki b'nin her elemanında yapılmış gibi bir duurm oluşmaktadır. - Burada eğer istenen şey b'nin farklı listeleri göstermesi ise bu durum bir öngü ile sağlanmalıdır: - - b = [] - - for _ in range(5): - a = [0] * 5 - b.append(a) - - print(b) - - b[0][0] = 10 - print(b) - - Tabii aslında tytukarıdkai işlem "liste içlemiyle (list comprehension)" tek satırda da yapılabilmektedir. Liste içlemleri - konusu ileride ele alınmaktadır: - - b = [[0] * 5 for _ in range(5)] - - print(b) - - b[0][0] = 100 - print(b) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Şimdiye kadar biz hep var olan fonksiyonları çağırdık. Şimdi kendimiz fonksiyonlar yazacağız. Bir fonksiyonun yazılmasına - "fonksiyonun tanımlanması (function definition)" da denilmektedir. Python'da fonksiyon tanımlamanın genel biçimi şöyledir: - - def ([parametre listesi]): - - Buradaki def bir anahtar sözcüktür, mutlaka bulundurulması gerekir. Fonksiyon ismi isimlendirme kurallarına uygun herhangi bir - isim olabilir. Fonksiyonlar parametre değişkenlerine sahip olabilirler ya da olmayabilirler. Her fonksiyon bir "suit" - içermek içermelidir. Örneğin: - - def foo(): - print('foo') - - Burada fonksiyonun ismi "foo" biçimindedir. Fonksiyonun parametresi yoktur. Örneğin: - - def bar(a, b): - print(a, b) - - Burada fonksiyonun ismi "bar" biçimindedir. a ve b bu fonksiyonun parametre değişkenleridir. Burada parametre - değişkenleri için tür belirtilmediğine yalnızca onların isimlerinin yazıldığına dikkat ediniz. Dinamik tür sistemine - sahip programlama dillerinde zaten bildirim yoktur. - - Biz aşağdaki örneklerimizde fonksiyon ismi uydurmak istediğimizde genellikle "foo", "bar", "tar", "zar" gibi - isimleri kullanacağız. Bu isimlerin hiçbir özel anlamı yoktur. Bunlar öylesine uydurulmuş isimlerdir. - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - -def bar(): print('bar'); print('yes bar') - -foo() -bar() - -#------------------------------------------------------------------------------------------------------------------------ - Aslında Python'da fonksiyon tanımlama işlemi bir deyim statüsündedir. Yorumlayıcı bir fonksiyonun tanımlanadığını gördüğünde - önce bir "fonksiyon nesnesi (function object)" yaratır. Sonra o fonksiyon nesnesinin içerisine fonksiyonun kodlarını (yani suit deyimlerini) - yerleştirir. Fonksiyon nesnesinin adresini de fonksiyon ismine atar. Böylece aslında fonksiyon isimleri sıradan birer değişkendir. - Fonksiyon isimleri fonksiyon nesnelerini göstermektedir. Bir fonksiyonu (...) operatörü ile çağırdığımızda aslında biz - bu operatörün operand'ı olan değişkenin içerisindeki adreste bulunan fonksiyon nesnesinin içerisindeki kodları çalıştırmış oluruz. - Örneğin: - - def foo(): - print('foo') - - Bu tanımlamayı yorumlayıcı gördüğünde önce bir fonksiyon nesnesi oluşturup foo'nun kodlarını (yani suit deyimlerini) o nesnenin - içerisine yerleştirir. O nesnenin adresini de foo değişkenine atar. Artık foo değişkeni fonksiyon nesnesini göstermektedir. - Biz fonksiyonu aşağıdaki gibi çağırmış olalım: - - foo() - - Aslında burada yapılan şey foo değişkenin içerisindeki adreste bulunan fonksiyon nesnesinin kodlarının çalıştırılmasıdır. Örneğin: - - >>> def foo(): - ... print('foo') - ... - >>> id(foo) - 4312805088 - >>> type(foo) - - >>> foo() - foo - - Mademki fonksiyon isimleri aslında sıradan birer değişkendir ve her atama aslında birer adres atamasıdır. O halde - biz bir fonksiyon ismini başka bir değişkene atayabiliriz. Bu durumda aynı fonksiyonu o değişkenle de çağrabiliriz. - Örneğin: - - >>> def foo(): print('foo') - ... - >>> bar = foo - >>> foo() - foo - >>> bar() - foo - >>> id(foo) - 4312805232 - >>> id(bar) - 4312805232 - - Fonksiyonların da aslında böyle normal değişkenler gibi olmasına ve atanmasına yazılım dünyasında "fonksiyonların da birinci - sınıf vatandaş (functions are first class citizens)" olması denilmektedir. Python'da fonksiyonlar da birinci sınıf vatandaştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte f değişkeninin içerisindeki fonksiyon nesnesinin adresi g değişkenine atanmıştır. Artık f ve g aynı - fonksiyon nesnesini gösterir duruma gelmiştir. Dolayısıyla artık f() ve g() aslında aynı fonksiyonun çağrılmasına yol - açmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -def f(): - print('test function') - -g = f - -f() -g() - -#------------------------------------------------------------------------------------------------------------------------ - 28.Ders 20/7/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyon çağrıldığında onu çağıran fonksiyona iletilen değere "geri dönüş değeri (return value)" denilmektedir. - Fonksiyonların geri dönüş değerleri return deyimiyle oluşturulmaktadır. return deyiminin genel biçimi şöyledir: - - return [ifade] - - Programın akışı return deyimini gördüğünde fonksiyon sonlanır ve geri dönüş değeri return anahtar sözcüğünün yanındaki ifade - olacak biçimde oluşturulur. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - return 100 - -a = foo() -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonun geri dönüş değeri kullanılmak zorunda değildir. Yani isteğe bağlı (optional) bir bilgidir. Biz fonksiyonu - çağırıp geri dönüş değerini hiç kullanmayabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - return 100 - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - C, C++, Java ve C# gibi statik tür sistemine sahip dillerde fonksiyonların geri dönüş değerlerinin türleri vardır. - Fonksiyonlar aynı türden değerlerle geri dönebilirler. Ancak Python dinamik tür sistemine sahip bir programlama dili olduğu için - Python'da bir fonksiyon duruma göre farklı türlerle geri dönebilir. Python'da fonksiyonun geri dönüş değerinin türü diye bir - kavram yoktur. return deyiminde return anahtar sözcüğünün yanındaki ifade hangi türdense fonksiyon o türden bir değerle geri - dönecektir. - - Aşağıdaki örnekte klavyeden girilen değer pozitif bir değerse fonksiyon onun karesiyle geri dönmektedir. Eğer klavyeden - girilen değer pozitif değilse fonksiyon "error" yazısıyla geri dönecektir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = int(input('Bir değer giriniz:')) - if a > 0: - return a * a - - return 'error' - -result = foo() -print(result, type(result)) - -#------------------------------------------------------------------------------------------------------------------------ - Akış return deyimini görmeden fonksiyonu bitirirse geri dönüş değeri olarak None değeri elde edilmektedir. Örneğin: - - def foo(): - print('foo') - - val = foo() - - Burada geri dönüş değeri olarak None değeri elde edilecektir. - - Tabii fonksiyonun bazı akışları return deyimini görürken bazı akışları görmeyebilir. Örneğin: - - import math - - def mysqrt(val): - if val >= 0: - return math.sqrt(val) - - Burada eğer val parametresi 0 ya da 0'dna büyükse fonksiyon onun karekökü ile eğer 0'dan küçükse None değeri ile - geri dönecektir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - -result = foo() -print(result) # None - -#------------------------------------------------------------------------------------------------------------------------ - return anahtar sözcüğünün yanına bir ifade yazılmayabilir. Bu durumda fonksiyonun sonlandırılması istenmiştir ancak bir geri - dönüş değeri belirtilmemiştir. Bu durumda da geri dönüş değeri olarak None elde edilmektedir. Yani başka bir deyişle: - - return - - kullanımı ile aşağıdaki kullanım eşdeğerdir: - - return None - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - return - -result = foo() -print(result) # None - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyonun tek bir geri dönüş değeri vardır. Yani fonksiyon tek bir değerle geri dönebilir. Eğer fonksiyonun birden fazla - değer iletmesi isteniyorsa geri dönüş değeri demet, liste gibi bileşik bir nesne yapılmalıdır. Demetler bu bağlamda tercih edilmelidir. - Örneğin: - - def foo(): - print('foo') - return 10, 20 - - Burada foo fonksiyonu bir demetle geri dönmektedir. Yani biz fonksiyonu çağırdığımızda bir demet elde ederiz. Tabii - bu demet doğrudan açılabilir. Örneğin: - - a, b = foo() - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - return 100, 200 - -a, b = foo() -print(a, b) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıda ikinci derece denklemin kökleri ile geri dönen getroots isimli bir fonksiyon örneği verilmiştir. Fonksiyon - eğer kök yoksa None değerine, eğer kök varsa ikili bir demete geri dönmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def getroots(): - a = float(input('a:')) - b = float(input('b:')) - c = float(input('a:')) - - delta = b ** 2 - 4 * a * c - if delta < 0: - return None - - x1 = (-b + math.sqrt(delta)) / (2 * a) - x2 = (-b - math.sqrt(delta)) / (2 * a) - - return x1, x2 - -result = getroots() - -if result: - x1, x2 = result - print(f'x1 = {x1}, x2 = {x2}') -else: - print('no real root!') - -#------------------------------------------------------------------------------------------------------------------------ - Tabii fonksiyon bir listeyle, bir kümeyle ya da bir sözlükle de geri dönebilir. Aşağıdaki örnekte 0'dan klavyedne girilen - değere kadar değerlerin kareleri bir listede toplanmış ve fonksiyon bu listeyle geri dönmüştür. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - val = int(input('Bir sayı giriniz:')) - a = [] - for i in range(val): - a.append(i * i) - - return a - -a = foo() -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonların dış dünyadan aldıkları değerlere "parametre" denilmektedir. Fonksiyonlar parametrelere sahip olabilirler. - Bu durumda parametrelerin isimleri parametre parantezinin içerisinde ',' atomu ile ayrılarak belirtilmektedir. Örneğin: - - def foo(a, b): - pass - - Burada a ve b foo fonksiyonunun parametreleridir. Parametre terimi yerine bazen "parametre değişkeni" terimi de kullanılabilmektedir. - - Diğer statik tür sistemine sahip programlama dillerinde olduğu gibi bir parametre bildirimi Python'da yoktur. Pyton'da parametre parantezinin - içerisine yalnızca parametre değişkenlerinin isimlerinin yazıldığına dikkat ediniz. Örneğin: - - def foo(a, b, c): - print(a, b, c) - - Fonksiyonun parametre değişkenlerine "parametre (parameter)" fonksiyon çağrılırken girilen ifadelere ise "argüman (argument)" - denilmektedir. n tane parametreye sahip olan bir fonksiyon n tane argümanla çağrılmalıdır. Fonksiyon çağrılırken önce argümanların - değerleri hesaplanır. Sonra argümanlardan parametre değişkenlerine karşılıklı bir atama yapılır. Sonra akış fonksiyona aktarılır. - Yani parametreli bir fonksiyonun çağrılması parametre değişkenlerine gizli bir atama işleminin yapılması anlamına gelmektedir. - Python'da her türlü atamanın bir adres ataması olduğuna dikkat ediniz. Dolayısıyla aslında buradaki atama sırasında aslında argüman olan - nesnelerin adresleri atanmaktadır. Örneğin: - - def foo(a, b): - pass - - foo(10 + 20, 30 + 40) - - Burada önce 10 ile 20 toplanıp değeri 30 olan bir int nesne oluşturulur. 30 ile 40 toplanıp değeri değeri 70 olan bir - int nesne yaratılır. Sonra bu int nesnelerin adresleri sırasıyla a ve b değişkenlerine atanır. Örneğin: - - def foo(a): - pass - - x = 10 - foo(x) - - Burada önce içerisinde 10 değeir olan int türden bir nesne yaratılır. Bu nesnenin adresi x'e atanır. Sonra foo fonksiyonu - çağrılınca x'in içerisindeki adres parametre değişkeni olan a'ya atanacaktır. Yani aşağıdaki örnekte x'in id değeri ile - a'nın id değeri (yani adresi) aynı olacaktır: - - def foo(a): # a = x - print(a, id(a)) - - x = 10 - print(x, id(x)) - - foo(x) - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c): - print(a, b, c) - -foo(10 + 20, 'ali', 12.3) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte argümanlara ilişkin nesnenin adresinin aslında parametre değişkenine yerleştirildiğini göreceksiniz. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a): - print(a, id(a)) - -x = 10 -print(id(x)) -foo(x) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii aynı durum return işlemi için de geçerlidir. Yani return işleminde de aslında çağıran fonksiyona iletilen return ifadesindeki - nesnenin adresidir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - print(id(a)) - return a - -result = foo() -print(id(result)) - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonun parametre değişkenleri yalnızca o fonksiyonun içerisinde kullanılabilir. Dışarıdaki aynı isimli başka bir - değişken ile karışmaz. Örneğin: - - def foo(a): - pass - - a = 10 - foo(a) - - Buradaki a ile fonksiyonun parametre değişkeni olan a tamamne farklı iki değişkendir. - - İki fonksiyonun parametre değişkenlerinin aynı isimde olması da bir soruna yol açmamaktadır. Örneğin: - - def foo(a): - pass - - def bar(a): - pass - - Burada hem foo fonksiyonun hem de bar fonksiyonunun parametre değişkenin ismi a'dır. Bunlar birbirleriyle karışmaz. - Çünkü foo'nun parametre değişkeni olan a yalnızca foo içerisinde, bar'ın parametre değişkeni olan a ise yalnızca bar'ın - içerisinde kullanılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonların parametre değişkenleri onlar hangi tüden argümanla çağrılmışsa o türden olurlar. Yani onların belli bir - türleri yoktur. Daha önce de belirttiğimiz gibi statik tür sistemine sahip C, C++, Java, C# gibi dillerde parametre - değişkenlerinin belli bir türü vardır. Bu tür hiç değişmez. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a): - print(a, type(a)) - -foo(10) -foo(1.2) -foo('ali') -foo(True) -foo([1, 2, 3]) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi biz neden fonksiyon yazmak isteriz? İşte fonksiyonlar oluşturmanın gerekçeleri şunlardır: - - 1) Fonksiyonlar "yeniden kullanılabilirliği (reusability)" sağlarlar. Yani belli bir amacı gerçekleştiren fonksiyon başka projelerde - yeniden yazılmadan çağrılarak kullanılabilmektedir. Biz bir işi yapan bir fonksiyon yazdığımızda onu değişikm projelerde kullanabiliriz. - - 2) Fonksiyonlar kod tekrarını engellemektedir. Bir işlem programın çeşitli yerlerinde yineleniyor olabilir. Eğer fonksiyonlar olmasaydı - o işlemlerin tekrar tekrar yapılması gerekirdi. Bu da kodun büyümesine ve karmaşıklığa yol açardı. Halbuki fonksiyonlar kod tekrararını - engellemektedir. Biz bir işi yapan kodu bir fonksiyon olarak yazarsak onu programın çeşitli yerlerinde çağırarak kod tekrarını - engellemiş oluruz. - - 3) Karmaşık bir problem parçalarına ayrılarak daha kolay çözülebilir. Parçalarına ayırma işlemi genellikle fonksiyonlar - yoluyla yapılmaktadır. Yani işin belli kısımlarını yapan fonksiyonlar tanımlanır sonra karmaşık iş o fonksiyonların - belli sırada çağrılmasıyla gerçekleştirilir. İlk bakışta karmaşık gibi gelen pek çok olgu aslında parçalara ayrıştırıldığında - çok daha yönetilebilir bir hale gelmektedir. - - 4) Fonksiyonların isimleri vardır. Dolayısıyla yapılmak istenen şey fonksiyon çağrılarıyla daha iyi ifade edilebilmektedir. - - İşte programlamada bir işlemin fonksiyonlara ayrılarak fonksiyonların birbirlerine çağırması biçiminde gerçekleştirilmesine - "prosedürel programlama modeli (procedural paradigm)" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonlar listelerin, demetlerin sözlüklerin elemanları olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - -def bar(): - print('bar') - -def tar(): - print('tar') - -a = [foo, bar, tar] - -for i in range(len(a)): - a[i]() - -for f in a: - f() - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyon nesneleri hash'lenebilir olmadığı için sözlüklerde anahtar yapılamaz. Ancak fonksiyonlar sözlüklerde - değer biçiminde bulunabilir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - -def bar(): - print('bar') - -def tar(): - print('tar') - -d = {'ali': foo, 'veli': bar, 'selami': tar} - -d['ali']() -d['veli']() -d['selami']() - -for key in d: - d[key]() - -#------------------------------------------------------------------------------------------------------------------------ - Tabii aşağıdaki iki listede yapılan şeyler tamamen farklıdır: - - a = [foo, bar, tar] - b = [foo(), bar(), tar()] - - a listesinde listenin her elemanı bir fonksiyon nesnesini tutmaktadır. Ancak b listesinde listenin elemanları bu fonksiyonların - geri dönüş değerlerinden oluşmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - return 10 - -def bar(): - print('bar') - return 20 - -def tar(): - print('tar') - return 30 - -a = [foo(), bar(), tar()] - -for x in a: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte ikinci derece bir denklemin köklerini buluna bir fonksiyon yazılmıştır. Fonksiyon eğer kök yokse None - değerine geri döner, kök varsa x1 ve x2 köklerini bir demet olarak geri döndürmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def get_roots(a, b, c): - delta = b ** 2 - 4 * a * c - if delta < 0: - return None - - x1 = (-b + math.sqrt(delta)) / (2 * a) - x2 = (-b - math.sqrt(delta)) / (2 * a) - - return x1, x2 - -a = float(input('a:')) -b = float(input('b:')) -c = float(input('c:')) - -result = get_roots(a, b, c) -if result != None: - x1, x2 = result - print(f'x1 = {x1}, x2 = {x2}') -else: - print('Kök yok!') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte banner fonksiyonu tireler arasında parametresiyle aldığı yazıyı ekrana basmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -def banner(s): - print('-' * len(s)) - print(s) - print('-' * len(s)) - -banner('ankara') -banner('izmir') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte bir sayının asal olup olmadığını test eden isprime isimli bir fonksiyon yazılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -def isprime(val): - for i in range(2, val - 1): - if val % i == 0: - return False - - return True - -val = int(input('Bir sayı giriniz:')) - -print('Asal' if isprime(val) else 'Asal değil') - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki fonksiyonu kullnarak 100'e kadar tüm asal sayıları aşağıdaki gibi yazdırabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -def isprime(a): - for i in range(2, a): - if a % i == 0: - return False - - return True - -for i in range(2, 100): - if isprime(i): - print(i, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Aslında asallık testi daha hızlı bir biçimde aşağıdaki gibi yapılabilir. Aşağıdaki programda iki önemli matematiksel - özellikten faydalanılmıştır: - - 1) Sayı çift ise ama 2'ye eşit değilse asal değildir. Başka çift sayıların kontrol edilmesine gerek yoktur. - 2) Asal olmayan her sayının kareköküne kadar bir asal çarpanı vardır. (Öklit teoremi). Dolayısıyla sayının kareköküne - kadar kontrol yapılması yueterlidir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def isprime(a): - if a % 2 == 0: - return a == 2 - - for i in range(3, int(math.sqrt(a)) + 1, 2): - if a % i == 0: - return False - - return True - -for i in range(2, 100): - if isprime(i): - print(i, end=' ') - - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıda parametresiyle girilen değerin faktöriyeline geri dönen fonksiyon örneği verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -def factorial(n): - total = 1 - for i in range(2, n + 1): - total *= i - - return total - -result = factorial(5) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Aslında math modülü içerisinde factorial fonksiyonu hazır olarak bulunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - ->>> import math ->>> math.factorial(5) -120 - -#------------------------------------------------------------------------------------------------------------------------ - Python dilinin tasarımı ve sürdürümü ve CPython gerçekleştiriminin yazımı Python Sooftware Foundation (python.org) - tarafından yapılmaktadır. PSP içerisinde çeşitli alt gruplar vardır. Dile ilişkin sentaks ve semantik yenilikler ve - standart kütüphaneye yapılacak eklemeler "PEP (Python Enhancement Proposals)" denilen dokümanlarla yürütülmektedir. - Bir kişi taslak biçiminde bir PEP dokğmanı hazırlar. Önerisini oraya belli bir formatta yazar. Sonra bu öneri tartışılıp - sonuca bağlanır. Eğer kabul edilirse resmi bir biçimde PEP dokümanı olarak yayınlanır. Dolayısıyla PEP dokümanları - Python diline ilişkin pek çok "gerekçeyi (rationale)" de barındırmaktadır. PEP dokğmanlarına numaralar verilmiştir. - Dolayısıyla bir dil özelliğinin geniş bir açıklamasını elde etmek istiyorsanız bu PEP dokümanlarına başvurmalısınız. - Python'ın tüm PEP dokğmanlarına aşağıdaki bağlantıdan erişebilirsiniz: - - https://peps.python.org/ -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python kodlarını yazarken peşi sıra gelen atomlar arasında nasıl boşluklar bulundurulacağı, kodun güzel gözükmesi için - nasıl hizalamalar yapılacağı, değişken isimlendirmelerde hangi kuralların izleneceği gibi konuları kapsayan çeşitli yazım - biçimleri oluşturulmuştur. Yazım biçimleri (style guides) proje grupğları arasında ve kurumlar arasında farklılaşmaktadır. - Örneğin Python Software Foundation tarafından PEP 8 dokümanı ile açıklanan yazım biçimine pek çok Python programcısı uymamaktadır. - Google firmasının da Python için önerdiği kendine özgü bir yazım sitili de bulunmaktadır. Biz kurusumuda CSD'ye özgü bir - yazım sitilini kullanmaktayız. Ancak dahil olduğunuz proje grubunun yazım biçimine kendinizi uydurmanız gerekebilir. - Aşağıda iki yazım biçiminin bağlantısını veriyoruz: - - https://peps.python.org/pep-0008/ - https://google.github.io/styleguide/pyguide.html -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 29. Ders 25/07/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da diğer bazı dillerde olduğu gibi default argüman kullanımı vardır. Yani parametre değişkenleri Python'da default - değer alabilmektedir. Bir parametre değişkeni default değer almışsa çağrı sırasında o parametre değişkeni için argüman girilmezse - sanki o parametre için o default değer girilmiş gibi işlem yapılır. Örneğin: - - def foo(a, b, c = 10): - print(100, 200) - - Biz burada foo fonksiyonunu çağırırken c için default değer girmezsek sanki c için 10 değerini girmiş gibi oluruz. Örneğin: - - foo(100, 200) - - Bu çağrı aşağıdakiyle tamamen eşdeğerdir: - - foo(100, 200, 10) - - Ancak default değer almış olan parametre için argüman girilirse artık o default değerin bir önemi kalmaz. Örneğin: - - foo(100, 200, 300) - - Burada artık c için 300 değeri girilmiştir. Dolayısıyla c parametre değişkenine verilen default değerin bir önemi kalmamıştır. - Yani parametre değişkenine verilen default değer "eğer o parametre değişkeni için argüman girilmezse" devreye girmektedir. - Default değer almamış olan parametre değişkenleri için argüman girilmek zorundadır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c = 10): - print(f'a = {a}, b = {b}, c = {c}') - -foo(100, 200) # foo(100, 200, 10) -foo(100, 200, 300) # foo(100, 200, 300) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii fonksiyonun birden fazla parametresi default değer alabilir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c = 10, d = 20): - print(f'a = {a}, b = {b}, c = {c}') - -foo(100, 200) # foo(100, 200, 10, 20) -foo(100, 200, 300) # foo(100, 200, 300, 20) -foo(100, 200, 300, 400) # foo(100, 200, 300, 400) - -def bar(a = 10, b = 20): - print(f'a = {a}, b = {b}') - -bar() # bar(10, 20) -bar(100) # bar(100, 20) -bar(100, 200) # bar(100, 200) - -#------------------------------------------------------------------------------------------------------------------------ - Parametrelere verilen default değerlerin çok kullanılan değerler olması gerekir. Yoksa öylesine değerleri default değer olarak - vermek kötü bir tekniktir. Fonksiyonu inceleyen kişiler default değerlerin yaygın değerler olduğunu varsaymaktadır. - Örneğin farklı türden bir değeri int türüne dönüştürmek için kullandığımız int fonksiyonun aslında ikinci bir parametresi vardır. - Bu parametre birinci parametredeki yazının kaçlık sistemde yazılmış olduğunu belirten base parametresidir. Biz günlük yaşamımızda - 10'luk sistemi kullandığımıza göre bu parametrenin default değerinin 10 olması oldukça makuldür. Böylece kişiler fonksiyonu - çağırırken boşuna bu ikinci parametre için her defasında 10 değrini girmezler. (int fonksiyonun bu ikinci parametresi ancak birinci parametre - bir yazı ise anlamlıdır. Eğer birinci parametre bir yazı değilse ikinci parametre için argüman girmek Exception'a (TypeError) - yol açmaktadır.) Default argümanlar bu örnekte olduğu gibi fonksiyonu çağıracak kişilere kolaylık sağlamaktadır. Hatta bazen - bir fonksiyonun çok parametresi olabilir. Ancak bunların önemli bir bölümü default değer almış olabilir. Meşgul bir programcı - da yalnızca default değer almamış parametrelerin anlamlarını öğrenip onlar için argüman girebilir. -#------------------------------------------------------------------------------------------------------------------------ - -s = '123' - -val = int(s) # int(s, 10) -print(val) # 123 - -val = int(s, 16) -print(val) # 291 - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonda default değer alan parametre değişkenlerinin parametre listesinin sonunda toplanmış olması gerekir. Başka bir - deyişle "eğer bir parametre değişkeni default değer almışsa onun sağındakilerin hepsinin default değer almış olması" gerekir. - Örneğin: - - def foo(a, b = 10, c): - pass - - Böyle bir fonksiyon tanımlaması geçerli değildir. Programcının fonksiyonu şöyle tanımlaması gerekirdi: - - def foo(a, c, b = 10): - pass - -#------------------------------------------------------------------------------------------------------------------------ - -def banner(s, ch='-'): - print(ch * len(s)) - print(s) - print(ch * len(s)) - -banner('ankara') -banner('ankara', '*') - -#------------------------------------------------------------------------------------------------------------------------ - Python'da fonksiyon parametrelerine verilen default değerler fonksiyonun bir deyim olarak çalıştırılması sırasında yalnızca bir kez - işleme sokulmaktadır. Bu davranış C++, Java ve C# gibi dillerdeki davranıştan farklıdır. Örneğin: - - def foo(a = [1, 2, 3]): - print(a, id(a)) - - Burada [1, 2, 3] elemanlarına sahip olan liste nesnesi yalnızca bir kez yaratılacaktır. Yani fonksiyon her çağrıldığında yaratılmayacaktır. - Bunu basit bir biçimde şöyle test edebilirsiniz: - - foo() - foo() - foo() - - Buradan elde edilen örnek bir çıktı şöyledir: - - [1, 2, 3] 1560817328576 - [1, 2, 3] 1560817328576 - [1, 2, 3] 1560817328576 - - Benzer biçimde örneğin: - - def foo(): - print('foo') - - return 10 - - def bar(a = foo()): - pass - - Burada da foo fonksiyonu bar deyimi çalıştırılırken yalnızca bir kez çağrılacaktır. bar fonksiyonu her çağrıldığında çağrılmayacaktır. - Örneğin: - - bar() - bar() - bar() - - Bu tür durumlarda default argüman olarak "değiştirilebilir (mutable)" nesne kullanırken dikkat etmek gerekir. Örneğin: - - def foo(a = []): - a.append(10) - return a - - x = foo() # dikkat x aslında a parametre değişkeni ile aynı nesneyi gösteriyor - x.append(20) - foo() - - print(x) # [10, 20, 10] - - Burada foo fonksiyonu default argüman olarak yaratılmış olan list nesnesinin adresiyle geri dönmüştür. Dolayısıyla x değişkeninde - artık default argüman olarak yaratılmış olan nesnenin adresi bulunacaktır. Yani kodda x.append(20) aslında aynı nesneye - ekleme yapmaktadır. Daha sonra çağrılan foo'daki a parametre değişkeni yeniden boş bir list nesnesini göstermeyecektir. - Zaten bir kez yaratılmış olan list nesnesini gösterecektir. - #------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da C++, Java ve C# gibi dillerdeki "function/method overloading" özelliği yoktur. Dolayısıyla bu tür bir etki - default argüman alan parametre değişkenleriyle sağlanmaktadır. Örneğin biz range fonksiyonunu (aslında bu bir sınıftır) - tek parametreyle, iki parametreyle ve üç parametreyle kullanabiliyorduk. Aslında üç ayrı range fonksiyonu yoktur. Tek bir range - fonksiyonu vardır. range fonksiyonu aşağıdaki gibi bir mantıkla yazılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -def disp_range(start, stop=None, step = 1): - if stop == None: - stop = start - start = 0 - - for i in range(start, stop, step): - print(i, end = ' ') - print() - -disp_range(10) -disp_range(10, 20) -disp_range(10, 20, 2) - -#------------------------------------------------------------------------------------------------------------------------ - Daha önceden de belirttiğimiz gibi Python'da bir fonksiyonu ikinci kez tanımlamak aslında aynı değişkene ikinci kez - atama yapmak gibi bir etki oluşturmaktadır. Örneğin: - - def foo(a): - pass - - def foo(a, b): - pass - - C++, Java ve C# gibi dillerde aynı isimli farklı parametrik yapılara sahip fonksiyonlar bir arada bulunabilmektedir. - Oysa Python'da böyle bir durum yoktur. Bu yukarıdaki kod parçasında foo fonksiyonunu çağırırsak artık ikinci - fonksiyonu çağırmış oluruz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bazı programlama dillerinde parametre listesinde bulunan bir değişken daha sonra default argüman olarak kullanılabilmektedir. - Python'da böyle bir kullanım geçerli değildir. Örneğin: - - def foo(a, b = a): - pass - - Burada biz b parametre değişkenine a'yı default argüman olarak veremeyiz. Bu durum bir sentaks hatası oluşturacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python gibi dinamik tür sistemine sahip programlama dillerinde fonksiyonların parametre değişkenlerinin belli bir türü - olmadığı için fonksiyonlar yanlış türden argümanlarla çağrılırsa programın çalışma zamanı sırasında exception oluşarak program - çökebilir ya da program çökmeden yanlış bir biçimde çalışabilir. Genel olarak programlamada bir kodun hiç çalışmaması yanlış - çalışmasına tercih edilmektedir. (Yani kodun hatalı çalışmasındansa exception oluşarak hiç çalışmaması daha tercih edilir - bir durumdur.) Örneğin daha önceden yazmış olduğumuz banner fonksiyonunun normal olarak bir string ile çağrılması gerekir. - Ancak biz bu fonksiyonu bir string ile çağırmazsak programımız exception ile çökecektir. İşte Python'da fonksiyon çağrılarında - argümanların uygun bir biçimde girilmesi programcının sorumluluğundadır. Benzer biçimde bu banner örneğinde fonksiyonun - ikinci parametresinin bir karakter uzunluğunda bir string olarak girilmesi gerekmektedir. Aksi takdirde ya exception - oluşacak ya da program yanlış çalışacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -def banner(s, ch='-'): - print(ch * len(s)) - print(s) - print(ch * len(s)) - -banner('ankara') -banner('ankara', 10) # program çökmeyecek ama istenildiği gibi çalışmayacaktır -banner(10) # burada exception oluşacak, çünkü len fonksiyonu int türüne uygulanamaz! - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyon yazarken programcı yine de fonksiyon parametreleri üzerinde programın çalışma zamanı sırasında tür kontrolü - uygulayabilir. Bunun için isinstance isimli built-in fonksiyon kullanılmaktadır. isinstance fonksiyonunun birinci parametresi - bir ifade, ikinci parametresi ise bir tür ismi olarak girilir. Fonksiyon birinci parametresiyle belirtilen ifadenin ikinci - parametresiyle belirtilen türden (ya da o türden türemiş bir sınıf türünden) olup olmadığına ilişkin bool bir değer geri - döndürmektedir. Örneğin: - - if isinstance(s, str): - pass - - Burada s değişkeninin str türünden olup olmadığına bakılmaktadır. Fonksiyonun ikinci parametresi türlerden oluşan bir demet biçiminde de girilebilir. - Bu durum "veya" anlamına gelmektedir. Örneğin: - - if isinstance(a, (int, float, complex)): - pass - - Burada a değişkeni int, float ya da complex türündense isinstance True değerine geri dönecektir. - - Python programcıları eğer fonksiyonun parametreleri üzerinde tür kontrolü yapıp onların uygun türlerdne olmadığını görürlerse - genellikle exception oluşturup fonksiyonun çalışmasını engellerler. Exception raise isimli bir deyimle oluşturulmaktadır. Bu - konu ileride ayrıntılarıyla ele alınmaktadır. Örneğin: - - def disp_square(a): - if not isinstance(a, (int, float)): - raise TypeError('argument must be either int or float') - print(a * a) - - Burada disp_square fonksiyonuna programcının int ya da float türden bir argüman geçmesi istenmektedir. Eğer programcı - int ya da float türden argüman geçmezse exception oluşturulmuştur. - - Ancak Python programcıları fonksiyon yazarken çoğu kez fonksiyon içerisinde isinstance ile parametreler üzerinde tür kontrolü yapmazlar. - Bu durumda eğer fonksiyon uygunsuz türlerle çağrılırsa muhtemelen bir biçimde bir exception oluşacaktır. Fonksiyonun doğru türlerle - çağrılması onu çağıran kişinin sorumlulupundadır. Yani eğer fonksiyon doğru türlerle çağrılmazsa sonucuna onu çağıran programcı - katlanacaktır. - - Aşağıdaki örnekte banner fonksiyonunda isinstance fonksiyonu ile tür kontrolü uygulanmıştır. Burada eğer programcı banner - fonksiyonunu çağırırken birinci pamatreye str türünden bir argüman geçmezse ya da ikinci parametreye tek karakterli bir - str türünden argüman geçmezse exception oluşturulmuştur. -#------------------------------------------------------------------------------------------------------------------------ - -def banner(s, ch='-'): - if not isinstance(s, str): - raise TypeError('first argument must be string') - if not isinstance(ch, str) or len(ch) != 1: - raise TypeError('second argument must be one charater string') - print(ch * len(s)) - print(s) - print(ch * len(s)) - -banner('ankara', 'ali') - -#------------------------------------------------------------------------------------------------------------------------ - Bazen programcılar fonksiyon içerisinde parametre türlerini kontrol edip o türlere uygun bir çalışma sunabilirler. - Aşağıdaki örnekte banner fonksiyonun birinci parametresi int ya da float ise önce o yazıya dönüştürülmüş sonra banner - işlemi yapılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -def banner(s, ch='-'): - if isinstance(s, (int, float)): - s = str(s) - print(ch * len(s)) - print(s) - print(ch * len(s)) - -banner('ankara') -banner(10) -banner(12.4) - -#------------------------------------------------------------------------------------------------------------------------ - Python 3.10 ile birlikte birden fazla türün veya işlemine "|" operatörü ile sokulması sağlanmıştır. Örneğin: - - if isinstance(a, (int , float)): - pass - - Bu işlem Python 3.10 ve sonrasında aşağıdaki gibi de yapılabilmektedir: - - if isinstance(a, int|float): - pass - - Buradaki int|float ifadesi "int türü ya da float türü" anlamına gelmektedir. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyon çağrılırken arümanlara isim verilerek o argümanların hangi parametreler için girildiği belirtilebilmektedir. - Bu tür argümanlara Python referans kitabında "keyword arguments" denilmektedir. Ancak biz kurusumuzda bunlara "isimli argümanlar" - diyeceğiz. İsimli argümanlar "parametre_ismi=ifade" sentaksıyla kullanılmaktadır. Örneğin: - - def foo(a, b, c): - pass - - foo(c=100, a=200, b=300) - - Normal argümanlara (yani isimli olmayan argümanlara) Python referans kitabında "positional arguments" denilmektedir. - Biz kurumuzda bunları vurgulamak için "pozisyonel argümanlar" diyeceğiz. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c): - print(f'a = {a}, b = {b}, c = {c}') - -foo(10, 20, 30) # a = 10, b = 20, c = 30 -foo(c=100, a=200, b=300) # a = 200, b = 300, c = 100 - -#------------------------------------------------------------------------------------------------------------------------ - Pozisyonel argümanlarla isimli argümanları fonksiyon çağrısında bir arada kullanabiliriz. Ancak çağrıda isimli argümanların - sonda toplaşmış olması gerekir. Başka bir deyişle bir argüman isimli olarak girilmişse onun sağındakilerin hepsinin isimli olarak - girilmiş olması gerekmektedir. Örneğin: - - def foo(a, b, c): - pass - - foo(100, c=300, b=200) # geçerli - foo(100, c=300, 200) # error! -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c): - print(f'a = {a}, b = {b}, c = {c}') - -foo(100, c=200, b=300) # a = 100, b = 300, c = 200 - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyon çağrısında default argümanlar da dikkate alınarak tüm parametre değişkenlerine bir ve yalnızca bir kez - değer verilmiş olmalıdır. Eğer bir parametre değişkenine hiç değer verilmemişse ya da birden fazla kez değer verilmişse bu durum - error ile sonuçlanır. Örneğin: - - def foo(a, b, c): - pass - - foo(10, 20) # error! c değer almamış - foo(10, a=20, c=30) # error! a iki kez değer almış, b değer almamış - foo(100, 200, b=300, c=400) # error! b iki kez değer almış. - - Örneğin: - - def bar(a, b, c = 10, d = 20): - pass - - bar(10, 20) # geçerli, tüm parametreler bir ve yalnızca bir kez değer almış - bar(b=100, a=200, d=300) # geçerli, tüm parametreler bir ve yalnızca bir kez değer almış - bar(100, 200, 300, c=400, d=500) # error! c iki kez değer almış - bar(100, 200, 300) # geçerli, c iki kez değer almamış, c'nin değerini biz vermişiz -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi isimli argümanlara neden gereksinim duyulmaktadır. Aslında isimli argümanlara iki nedenden dolayı gereksinim duyulmaktadır: - - 1) Çok sayıda default değer alan parametre değişkenlerinin bulunduğu fonksiyonda default değer almış bir parametre değişkenine - istenilen bir değeri pratik bir biçimde geçirebilmek için. Örneğin: - - def foo(a, b, c=10, d=20, e=30, f=60, g=70): - pass - - Bu fonksiyonda biz en azından a ve b parametreleri için argüman girmek zorundayız. Ancak f parametresi için verilmiş default değerin - dışında bir değer girmek istersek isimli argümanlar bize kolaylık sağlamaktadır: - - foo(100, 200, f=300) - - Eğer isimli argümanlar olmasaydı biz f'ye kadarki parametre değişkenleri için o default değerleri argüman olarak belirtmek - zorunda kalırdık. Örneğin: - - foo(100, 200, 10, 20, 30, 100) - - 2) İsimli argümanlar okunabilirliği artırmak için de kullanılabilmektedir. Örneğin: - - val = int(s, base=16) - - Burada aslında isimli argüman kullanmaya gerek yoktur. Ancak programcı buradaki 16'nın taban belirttiğini vurgulamak için - isimli argüman kullanımını tercih etmiş olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz kursumuzda atama işleminde '=' atomunun iki tarafında birer boşluk karakteri bırakıyoruz. Ancak Python programcıları - genel olarak (ancak hepsi değil) isimli argüman belirtirken '=' atomunun iki yanına boşluk karakteri girmemektedir. Biz de - kursumuzda bu yazım biçimini tercih edeceğiz. Default argümanlarda bazı programcılar boşluk bırakırken bazıları bırakmamaktadır. - Biz kursumuzda default argümanlarda '=' atomunun iki tarafına boşluk bırakacağız. Python yazım stilini anlatan "PEP 8" - dokümanlarında her iki durumda da '=' atomunun iki yanında boşluk nırakılmamaktadır. Bu durum "Google Python Style Guide" - dokğmanında da aynı biçimdedir. Örneğin: - - def disp(a, base=10): - pass - - disp(100, base=16) - - Ancak biz parametre değişkenlerine default argüman verirken '=' atomunun iki yanına boşluk bırakacağız. Örneğin: - - def disp(a, base = 10): - pass - - disp(100, base=16) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonun parametre listesinde parametre değişkeni yerine '*' kullanılırsa '*'ın sağındaki tüm parametreler çağrım sırasında - isimli argümanlarla çağrılmak zorundadır. Böylece fonksiyonu yazan kişi okunabilirliği artırmak amacıyla çağıran kişiyi isimli argüman - kullanmaya zorlayabilmektedir. Örneğin: - - def foo(a, b, *, c, d): - pass - - Parametredeki "*" gerçek bir parametre değildir. Yani yukarıdaki foo fonksiyonunun 4 parametresi vardır. Buradaki "*" - bunun sağındaki parametreler için çağrım sırasında isimli argüman girilmesinin zorunlu olduğunu belirtmektedir. Örneğin: - - foo(100, 200, 300, 400) # error! c ve d isimli kullanılmak zorunda - foo(100, 200, c=300, d=400) # geçerli - foo(100, 200, d=400, c=300) # geçerli - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonun parametre listesinde bir parametre yalnızca '/' biçiminde girilmişse bu durum "onun solundaki tüm parametreler - argümanların pozisyonel olarak girilmesi gerektiği" anlamına gelmektedir. Örneğin: - - def foo(a, b, /, c = 100, d = 200): - pass - - Burada a ve b parametreleri isimli girilemez. Pozisyonel girilmek zorundadır. Yine parametre listesindeki '/' karakteri - gerçek bir parametre değildir. Dolayısıyla yukarıdaki fonksiyonun 4 parametresi vardır. Bu '/' parametresi bunun solundaki - parametreler için çağrım sırasında isimli argüman girilemeyeceği anlamına gelmektedir. Örneğin: - - foo(10, 20, 30, 40) # geçerli - bar(a=10, b=20) # error! a ve b pozisyonel girilmek zorundadır. - - Örneğin: - - def disp_pixel(x, y, /): - pass - - disp_pixel(10, 20) # geçerli - disp_pixel(10, y=20) # error! - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, /, c, d): - print(a, b, c, d) - -foo(10, 20, c=30, d=40) # 10 20 30 40 - -#------------------------------------------------------------------------------------------------------------------------ - * ile / parametre belirleyicileri aynı anda kullanılabilir. Ancak bu durumda önce / sonra * parametre belirleyicilerinin - gelmesi gerekir. Örneğin: - - def foo(a, b, /, c, *, d, e): - print(a, b, c, d, e) - - Yukarıdaki örnekte / parametresinin solu pozisyonel biçimde, * parametresinin sağı isimli biçimde kullanılmak zorundadır. - Ancak / ile * arasındaki parametreler için isimli ya da pozisyonel argüman girilebilir. Yani bu örnekte a ve b parametrelerinin - pozisyonel argümanlarla, d ve e parametrelerinin isimli argümanlarla kullanılması zorunludur. Ancak c parametresi pozisyonel - ya da isili kullanılabilir. Örneğin: - - foo(10, 20, 30, d=40, e=50) # geçerli - foo(10, 20, c=30, d=40, e=50) # geçerli - foo(10, 20, 30, 40, e=50) # error! d isimli kullanılmak zorunda - - Pekiyi programcıyı pozisyonel argüman girmeye zorlamanın bir anlamı olabilir mi? Aslında her ne kadar isimli argüman girmek okunabilirliği - artırıyorsa da bazen tam tersi olabilmektedir. Örneğin sayının sinüsünü alan fonksiyonu düşünelim. Bu fonksiyonun parametresinin isminin - bir önemi var mıdır? Burada isim belirtilirse kodu inceleyen kişide tam tersine bir tereddüt oluşur. Örneğin: - - def sin(x, /): - pass -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, /, c, *, d, e): - print(a, b, c, d, e) - -foo(10, 20, 30, d=40, e=50) # geçerli1 -foo(10, 20, c=30, d=40, e=50) # geçerli - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonların *'lı parametreleri olabilir. Ancak fonksiyonun parametre listesinde yalnızca bir parametre değişkeni - önüne * getirilerek belirtilebilir. Örneğin: - - def foo(a, b, *c): - pass - - Bunlara *'lı parametre diyeceğiz. *'lı parametreler en fazla bir tane olabilir. Örneğin: - - def foo(a, b, *c, *d): # error! - pass - - *'lı parametre genellikle parametre listesinin sonunda olsa da böyle bir zorunluluk yoktur. Örneğin: - - def foo(a, *b, c): # geçerli - pass - - *'lı parametre sıfır tane ya da daha fazla argümanla eşleşir. Yorumlayıcı çağırma ifadesindeki argümanları bir demete yerleştirip - demeti *'lı parametreye geçirmektedir. Yani *'lı parametre her zaman bir demet türündendir. Örneğin: - - def foo(a, b, *c): - pass - - foo(10, 20, 30, 40, 50) - - Burada 10 a parametresi ile, 20 b parametresi ile 30, 40 ve 50 bir demet haline getirilerek c parametresi ile eşleştirilecektir. - Örneğin: - - foo(10, 20) - - Burada yine 10 değeri a parametre değişkenine, 20 değeri b parametre değişkenine atanacaktır. c paramatre değişkenine - ise boş bir demet geçirelecektir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, *c): - print(f'a = {a}, b = {b}, c = {c}') - -foo(10, 20, 30, 40, 50, 60) # a = 10, b = 20, c = (30, 40, 50, 60) -foo(10, 20, 30) # a = 10, b = 20, c = (30,) -foo(10, 20) # a = 10, b = 20, c = () - -#------------------------------------------------------------------------------------------------------------------------ - 30. Ders 27/07/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - *'lı parametre parametre listesinin sonunda olmak zorunda değildir. Ancak her parametre değişkeninin bir ve yalnızca - bir kez değer almış olması gerekir. Örneğin: - - def foo(a, *b, c): - pass - - foo(10, 20, 30) # error! c değer almamış - - Burada 20 ve 30 c için girilmiş gibi olmaktadır. Dolayısıyla c paramere değişkeni değer almamış durumdadır. Bu tür durumlarda - *'lı parametrenin sağındaki parametre değişkenlerine mecburen isimli bir biçimde argüman girilmelidir. Örneğin: - - foo(10, 20, 30, 40, c=50) # geçerli - - Burada 10 argümanı a ile, (20, 30, 40) argümanları b ile eşleşmektedir. 50 argümanı b *'lı parametresinin bir parçası değildir. - Çünkü açıkça isimli bir biçimde belirtilmiştir. Dolayısıyla 50 c argümanı ile eşleşecektir. Burdada her parametre değişkeni daha önce - de belirttiğimöiz gibi en az bir kez ve en fazla bir kez değer almış durumdadır. Örneğin: - - def foo(a, *b, c = 100): - pass - - foo(10, 20, 30) # geçerli, c de değer almış - - Gördüğünüz gibi *'lı parametre parametre listesinin sonunda değilse *'lı parametrenin sağındaki parametrelerin ya default - değer almış olması gerekir ya da argüman listesinde isimli bir biçimde kullanılmış olması gerekir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, *b, c, d): - print(f'a = {a}, b = {b}, c = {c}, d = {d}') - -foo(10, 20, 30, c=100, d=200) # geçerli, a = 10, b = (20, 30), c = 100, d = 200 - -#------------------------------------------------------------------------------------------------------------------------ - *'lı parametrenin her zaman demet belirttiğine dikkat ediniz. Demetler dolaşılabilir nesneler olduğuna göre biz - *'lı parametreyi for döngüsi içerisinde dolaşılbiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -def add(*args): - total = 0 - for x in args: - total += x - - return total - -result = add() -print(result) # 0 - -result = add(10) -print(result) # 10 - -result = add(10, 20, 30) -print(result) # 60 - -#------------------------------------------------------------------------------------------------------------------------ - Aslında print fonksiyonu da *'lı parametre almaktadır. Biz de print fonksiyonunu myprint ismiyle yazabiliriz. Tabii - burada ekrana yazdırma için yine orijinal print fonksiyonunu kullanacağız. Aşağıdaki örnek yalnızca print fonksiyonunun nasıl - yazılmış olabileceğini göstermek amacıyla verilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -def myprint(*objects, sep=' ', end='\n'): - i = 0 - while i < len(objects): - if i != 0: - print(end=sep) - print(objects[i], end='') - i += 1 - print(end=end) - -myprint(10, 20, 30) -myprint(10, 20, 30, sep='*') -myprint(10, 20, 30, sep='*', end='/') - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki myprint fonksiyonunu for döngüsüyle de yapabilirdik. Ancak for döngüsü bu tür durumlarda daha zahmetli - bir çözüm sunabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -def myprint(*objects, sep=' ', end='\n'): - if len(objects): - print(objects[0], end='') - for x in objects[1:]: - print(end=sep) - print(x, end='') - print(end=end) - -myprint(10, 20, 30) -myprint(10, 20, 30, sep='*') -myprint(10, 20, 30, sep='*', end='/') - -#------------------------------------------------------------------------------------------------------------------------ - print fonksiyonuna hiç argüman girilmemişse fonksiyon nasıl çalışacaktır? Örneğin: - - print() - - Burada fonksiyonun *'lı parametresine boş bir demet aktarılacaktır. Dolayısıyla fonksiyon bir şey yazdırmyacaktır. - Fonksiyon bir şey yazdırmayacağına göre sep parametresi de kullanılmayacaktır. Çünkü sep parametresi print fonksiyonu - birden fazla argümanı yazdırırsa onların arasına yerleştirilecek karakteri belirtmektedir. print fonksiyonu işini bitirince - end parametresiyle belirtilen yazıyı ekrana (stdout dosyasına) bastıracağına göre boş print yalnızca imlecin aşağı satırın - başına geçmesine yol açacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii biz *'lı parametreler için listeler, demetler, kümeler ve sözlükler de girebiliriz. Örneğin: - - def foo(*a): - pass - - Burada biz fonksiyonu şöyle çağırmış olalım: - - foo(10, 20, 30) - - 10, 20 ve 30 değerleri bir demet haline getirilerek fonksiyonun a parametre değişkenine geçirilecektir. Şimdi fonksiyonu şöyle - çağırmış olalım: - - foo([10, 20, 30]) - - Burada a parametre değişkenine tek elemanlı bir demet aktarılacaktır. Demetin tek elemanı da liste olacaktır. Örneğin: - - foo((10, 20, 30)) - - Burada a parametre değişkenine yine tek elemanlı bir demet aktarılacaktır. Demetin tek elemanı da (10, 20, 30) demeti - olacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(*a): - for x in a: - print(x, end=' ') - print() - -foo(10) # 10 -foo(10, 20, 30) # 10 20 30 -foo([10, 20, 30]) # [10, 20, 30] -foo((10, 20, 30)) # (10, 20, 30) - -#------------------------------------------------------------------------------------------------------------------------ - Python'ın standart kütüphanesindeki min ve max isimli built-in fonksiyonlar bir dolaşılabilir nesneyi alıp onun en küçük - ve en büyük elemanlarına geri dönmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [34, 56, 21, 76, 18] - -result = max(a) -print(result) # 76 - -result = min(a) -print(result) # 21 - -#------------------------------------------------------------------------------------------------------------------------ - Aslında min ve max fonksiyonlarına birden fazla argüman da girilebilmektedir. Bu durumda bu argümanların en büyük ve - en küçük değerleri elde edilir. -#------------------------------------------------------------------------------------------------------------------------ - -result = max(89, 79, 34) -print(result) # 89 - -result = min(4, 67, 1, 68) -print(result) # 1 - -#------------------------------------------------------------------------------------------------------------------------ - Görüldüğü gibi eğer biz min ve max fonksiyonlarına tek eleman girersek o argümanın dolaşılabilir olması gerekir. - Ancak biz bu fonksiyonlara birden fazla argüman girersek bu fonksiyonlar o argümanların en küçük ve en büyük değerlerini bulular. - Pekiyi min ve max fonksiyonları nasıl yazılmış olabilir? Bu fonksiyonlar birden fazla argümanı kabul ettiğine göre *'lı bir - parametreye sahip olmalıdır. O zaman bu *'lı parametreye aktarılan argüman bir tane ise biz onu dolaşılabilir bir nesne kabul edip - onun en küçük ya da en büyük elemanını bulmaya çalışırız. Eğer bu *'lı parametreye birden fazla argüman girilmişse bu - durumda biz bu *'lı parametreye aktarılan demetin en küçük ya da en büyük elemanını bulmaya çalışırırz. Dolaşılabilir bir nesnenin - en küçük ya da en büyük elemanını bulmak için ilk eleman en küçük ya da en büyük kabul edilip bir değişken de saklanır. Sonraki elemanlar - bu değişkendekiyle karşılaştırılıp değişkenin değeri güncellenir. Biz ileride dolaşılabilir nesnelerin elemanlarını for döngüsü olmadan - da ilerletebileceğiz. Örneğin next isimli built-in fonksiyon bunun için kullanılmaktadır. Ancak biz henüz dolaşılabilir nesnelerin - ayrıntılarını bilmiyoruz. Bu nedenle gerçekleştirimi aşağıdaki gibi yapıyoruz. - #------------------------------------------------------------------------------------------------------------------------ - -def mymax(*args): - iterable = args[0] if len(args) == 1 else args - - maxval = None - for x in iterable: - if maxval == None or x > maxval: - maxval = x - - return maxval - -a = [1, 6, 3, 2, 5] - -result = mymax(a) -print(result) # 6 - -result = mymax(4, 16, 2, 3) -print(result) # 16 - - -def mymin(*args): - iterable = args[0] if len(args) == 1 else args - - minval = None - for x in iterable: - if minval == None or x < minval: - minval = x - - return minval - -a = [1, 6, 3, 2, 5] - -result = mymin(a) -print(result) # 1 - -result = mymin(4, 16, 2, 3) -print(result) # 2 - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonların *'lı parametreleri için fonksiyon çağrılırken isimli argüman girilememektedir. Örneğin: - - def foo(a, b, *c): - pass - - foo(10, 20, c=30) # error! - foo(10, 20, c=(30, 40, 5)) # error! - - Fonksiyonun *'lı parametreleri default argüman alamaz. Örneğin: - - def foo(a, b, *c = (10, 20, 30)): # error! *'lı parametrelere default değer veremeyiz! - pass - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 31. Ders 01/08/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonların *'lı parametrelerinin yanı sıra bir de **'lı parametreleri bulunabilmektedir. Bir fonksiyonun **'lı parametresi - eğer bulundurulacaksa parametre listesinin sonunda bulundurulmak zorundadır. (Halbuki *'lı parametre listesinin herhangi bir yerinde - bulundurulabilmektedir.) Fonksiyonun tek bir **'lı parametresi olabilir. Geleneksel olarak pek çok programcı fonksiyonun *'lı parametresine - args ismini **'lı parametresine ise kwargs ya da kargs ismini vermektedir. - - Bir fonksiyon aslında olmayan parametrelere ilişkin isimli argümanlarla çağrılırsa, yorumlayıcı bu isimli argümanları bir sözlükte - toplayarak sözlüğü fonksiyonun **'lı parametresine geçirmektedir. Olmayan parametrelere ilişkin isimli argümanların argüman isimleri - sözlüğün anahtarları, onlara '=' ile verilen değerler de o anahtarlara karşı gelen değerleri oluşturmaktadır. Buradaki anahtarlar - her zaman str türündendir. Örneğin: - - def foo(a, *args, **kwargs): - print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs)) - - Biz bu fonksiyonu şöyle çağırmış olalım: - - foo(10, 20, 30, 40, xx='ali', yy=100) - - Görüldüğü gibi aslında fonksiyonun xx ve yy isimli parametre değişkenleri yoktur. Yorumlayıcı önce {'xx': 'ali', 'yy': 100} - biçiminde bir sözlük nesnesi oluşturur sonra bu sözlük nesnesini **'lı parametreye aktarır. Örneğin: - - foo(10, 20, 30, 40) - - Burada fonksiyon olmayan bir parametre ismiyle çağrılmamıştır. Bu durumda **'lı parametreye boş sözlük geçirilecektir. - Tıpkı *'lı parametrelerde olduğu gibi fonksiyonun **'lı parametreleri de default değer alamaz ve bunlara isimli olarak - argüman geçirilemez. - - Mademki *'lı ve **'lı parametreler için isimli argüman girilememektedir. Bu durumda eğer bu argümanlar isimli kullanılırsa - ve fonksiyonun da **'lı bir parametresi varsa bu argümanlar "olmayan parametre değişkenleri gibi" değerlendirilecek ve bir - sözlük biçiminde **'lı parametreye aktarılacaktır. Örneğin: - - def foo(a, *args, **kwargs): - print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs)) - - foo(10, args=20, kwargs=30) - - Buradan şöyle bir çıktı elde edilecektir: - - a = 10, args = (), kwargs = {'args': 20, 'kwargs': 30} - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, *args, **kwargs): - print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs)) - -foo(10, 20, 30, 40, xx='ali', yy=100) # a = 10, args = (20, 30, 40), kwargs = {'xx': 'ali', 'yy': 100} -foo(10, 20, 30, 40) # a = 10, args = (20, 30, 40), kwargs = {} -foo(a=10) # a = 10, args = (), kwargs = {} -foo(10, args=(100, 200)) # a = 10, args = (), kwargs = {'args': (100, 200)} -foo(10, 20, 30, kwargs={}) # a = 10, args = (20, 30), kwargs = {'kwargs': {}} - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi fonksiyonların **'lı parametreleri neden kullanılmaktadır? Bunun iki nedeni vardır: Birincisi bir fonksiyon çok fazla - parametreye sahipse parametre sayısını azaltmak için **'lı parametre kullanılabilmektedir. Örneğin bir fonksiyon için 50 tane parametre - söz konusu olsun. Bu 50 parametrenin parametre listesinde belirtilmesi okunabilirliği bozar. Üstelik bu parametreler başka fonksiyonlarda da - söz konusu ise hem programcının bunları yazması zorlaşır hem de okunabilirlik düşer. İşte bu durumda **'lı parametreler - kolaylık sağlamaktadır. Şimdi foo fonksiyonun a, b, c, d, e, f, g, h, i, j, k, l, m parametrelerinin olduğunu düşünelim. Bu parametrelerin - ilk ikisi dışındakiler default değer alıyor olsun. Foksiyonu aşağıdaki gibi tanımlamış olalım: - - def foo(a, b, **kwargs): - pass - - Burada fonksiyonu çağıran onu aşağıdaki gibi çağırabilir: - - foo(10, 20, c=100, f=200, k=300, j=400) - foo(10, 20, m=200) - - Görüldüğü gibi **kwargs parametresi bu çağrımlara izin vermektedir. Tabii bu durumda aşağıdaki gibi bir çağrım da geçerli olacaktır: - - foo(10, 20, r=20) - - Halbuki fonksiyonda r biçiminde bir parametre yoktur. O halde bu tür fonksiyonları yazan kişiler aslında her isimli argümanı kabul - etmemektedir. Yalnızca daha önce belirlemiş oldukları isimli argümanları kabul etmektedir. Bu nedenle bu tür fonksiyonlarda - genellikle işin başında bir parametre kontrolü yapılmaktadır. Örneğin: - - def foo(a, b, **kwargs): # c, d, e, f, g, h, i, j, k, l, m - legal_args = ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'] - - for key in kwargs: - if key not in legal_args: - print(f'invalid argument: {key}={kwargs[key]}') - return - - print('Ok') - - foo(10, 20, c=10, f=20, i=30, k=40) # Ok - foo(10, 20, c=10, r=30) # invalid argument: r=30 - - Burada programcı işin başında isimli argümanlar için geçerlilik kontrolü yapmıştır. Yani fonksiyonun **'lı parametreye sahip - olması onun istenildiği gibi olmayan bir isimli argümanla çağrılabileceği anlamına gelmemektedir. - - Tabii aslında isimli argümanlar için default değerler de söz konusu olabilmektedir. Örneğin c=100, d=200, e=300, f=400, - g=500, h=600, i=700, j=800, k=900, l=1000, m=1100, default değerleri söz konusu olsun. Yani fonksiyonu çağıran bu değerleri - girmezse buradaki değerler kullanılacak olsun. Bu mekanizmayı basit bir biçimde şöyle sağlayabiliriz: - - def foo(a, b, **kwargs): - legal_args = ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'] - - for key in kwargs: - if key not in legal_args: - print(f'invalid argument: {key}={kwargs[key]}') - return - - c = kwargs.get('c', 100) - d = kwargs.get('d', 200) - e = kwargs.get('e', 300) - f = kwargs.get('f', 400) - g = kwargs.get('g', 500) - h = kwargs.get('h', 600) - i = kwargs.get('i', 700) - j = kwargs.get('j', 800) - k = kwargs.get('k', 900) - l = kwargs.get('l', 1000) - m = kwargs.get('m', 1100) - - print(f'c = {c}, d = {d}, e = {e}, f = {f}, g = {g}, h = {h}, i = {i}, j = {j}, k = {k}, l = {l}, m = {m}') - - - foo(10, 20, c=10, f=20, i=30, k=40) # c = 10, d = 200, e = 300, f = 20, g = 500, h = 600, i = 30, j = 800, k = 40, l = 1000, m = 1100 - foo(10, 20) # c = 100, d = 200, e = 300, f = 400, g = 500, h = 600, i = 700, j = 800, k = 900, l = 1000, m = 1100 - - Parametre saysı çok fazlaysa yukarıdaki yöntem biraz zahmetli olmaya başlayabilir. Bu durumda isimli argümanlar default değerleriyle - bir sözlükte toplanabilir. Sonra bu sözlükteki değerler bir döngüyle girilen isimli argümanlar için güncellenebilir: - - def foo(a, b, **kwargs): - legal_args = {'c': 100, 'd': 200, 'e': 300, 'f': 400, 'g': 500, 'h': 600, 'i': 700, 'j': 800, 'k': 900, 'l': 1000, 'm': 1100} - - for key in kwargs: - if key not in legal_args: - print(f'invalid argument: {key}={kwargs[key]}') - return - - for key, value in kwargs.items(): - legal_args[key] = value - - for key, value in legal_args.items(): - print(f'parameter: {key}, value: {value}') - - foo(10, 20, c=10, f=20, i=30, k=40) - print() - foo(10, 20) - - Tabii yukarıdaki örnekte ilk döngü ile ikinci döngü de duruma göre aşağıdaki gibi birleştirilebilir: - - def foo(a, b, **kwargs): - legal_args = {'c': 100, 'd': 200, 'e': 300, 'f': 400, 'g': 500, 'h': 600, 'i': 700, 'j': 800, 'k': 900, 'l': 1000, 'm': 1100} - - for key, value in kwargs.items(): - if key not in legal_args: - print(f'invalid argument: {key}={kwargs[key]}') - return - else: - legal_args[key] = value - - - for key, value in legal_args.items(): - print(f'parameter: {key}, value: {value}') - - foo(10, 20, c=10, f=20, i=30, k=40) - print() - foo(10, 20) - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, **kwargs): - legal_args = {'c': 100, 'd': 200, 'e': 300, 'f': 400, 'g': 500, 'h': 600, 'i': 700, 'j': 800, 'k': 900, 'l': 1000, 'm': 1100} - - for key, value in kwargs.items(): - if key not in legal_args: - print(f'invalid argument: {key}={kwargs[key]}') - return - else: - legal_args[key] = value - - - for key, value in legal_args.items(): - print(f'parameter: {key}, value: {value}') - -foo(10, 20, c=10, f=20, i=30, k=40) -print() -foo(10, 20) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi biz bir fonksiyonda ** parametresi gördüğümüzde ne düşünmeliyiz? İşte böyle bir fonksiyon karşısında bizim şu çıkarımı - yapmamız gerekir: "Bu fonksiyon pek çok parametreye sahip olabilir. Bu parametreleri fonksiyonu yazan tek tek belirtmek istememiş. - Ben geçerli parametreleri fonksiyonu çağırırken isim=değer biçiminde isimli argüman olarak geçebilirim. Benim geçebileceğim - isimli argümanların neler olduğunu anlamak için dokümantasyona bakmalıyım". - - **'lı parametreye örnek olarak matplotlib kütüphanesindeki plot, title ve text gibi fonksiyonlar verilebilir. plot fonksiyonu - aynı uzunlukta x ve y dolaşılabilir nesnelerini alıp onların karşılıklı elemanlarının belirttiği noktaları çizgiyle birleştirmektedir. - Ancak plot grafiğinin çok sayıda özelliği vardır. Bu özelliklerin tek tek parametrelerle ifade edilmesi çok zordur. Bu nedenle plot fonksiyonu - **'lı bir parametreyle tüm parametreleri temsil etmektedir. Benzer biçimde grafiğe başlık atmakta kullanılan title fonksiyonunda ve - yazı yazmakta kullanılan text fonksiyonunda da aynı durum geçerlidir. -#------------------------------------------------------------------------------------------------------------------------ - -import math -import matplotlib.pyplot as plt - -xpoints = [] -ypoints = [] - -x = -6. -while x <= 6: - xpoints.append(x) - ypoints.append(math.sin(x)) - x += 0.01 - -print(xpoints) - -plt.title('Sinüs Grafiği', fontsize=18, color='blue', pad=20, fontweight='bold') -plt.plot(xpoints, ypoints, color='red', linewidth=6, linestyle='--', alpha=0.5) -plt.text(2, -0.50, 'Test', fontsize=16, color='green', fontweight='bold') - -plt.show() - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonun *'lı parametresinden sonraki parametreler eğer default değer almamışsa zaten isimli argüman biçiminde kullanılabileceğine göre - şöyle bir ince kural dile eklenmiştir: Bir parametre değişkeni default değer almışsa *'lı parametreye kadar onun sağındakilerin hepsinin - default değer alması gerekir. *'lı parametreden sonraki parametreler karışık sırada default değer alan ve almayan biçiminde bulunabilir. Örneğin: - - def foo(a, b = 10, c = 20, d): # error! - pass - - def foo(a, b = 10, c = 20, *args, d): # geçerli - pass - - def foo(a, b = 10, c = 20, *args, d, e = 30, f): # geçerli - pass - -#------------------------------------------------------------------------------------------------------------------------ - **'lı parametre kullanımının ikinci nedeni "forwarding (iletme)" yapmak içindir. Buna ilişkin örnek izleyen örneklerde verilecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonları çağırırken argümanların önüne '*' atomu getirilebilir. Bu tür argümanlara "*'lı argümanlar" diyeceğiz. - *'lı argümanlar argüman listesinde birden fazla bulunabilirler. Örneğin: - - foo(a, b, *c, d, *e, f) - - *'lı argümanların "dolaşılabilir (iterable)" nesne belirtmesi gerekir. Yani *'ın yanındaki nesne bir liste olabilir, - demet olabilir, sözlük olabilir, range nesnesi olabilir ya da başka dolaşılabilir nesneler olabilir. Ancak öneğin int ve float olamaz. - Çünkü int ve float dolaşılabilir değildir. - - Bir argümanın önüne * getirildiği zaman derleyici bu dolaşılabilir nesneyi dolaşır ve dolaşımdan elde ettiği değerleri sanki programcı tek tek - girmiş gibi fonksiyona yollar. Yani örneğin: - - x = [10, 20, 30] - foo(*x) - - çağrısı tamamen aşağıdakiyle eşdeğerdir: - - foo(10, 20, 30) - - Örneğin: - - foo(*'ali') - - çağrısı da aşağıdaki eşdeğerdir: - - foo('a', 'l', 'i') - - Tabii buradan da görüldüğü gibi *'ın yanındaki dolaşılabilir nesnenin eleman sayısının fonksiyon ile uyumlu olması gerekmektedir. Örneğin: - - def foo(a, b, c): - pass - - foo(*'ali') # geçerli - foo(*'ankara') # error! fonksiyondaki parametreler yetersiz! - - Ancak örneğin: - - def foo(a, b, *args): - pass - - x = [10, 20, 30, 40, 50] - foo(100, *x) - - Bu çağrı geçerlidir. Çünkü eşdeğeri şöyledir: - - foo(100, 10, 20, 30, 40, 50) - - Burada 100 değeri a parametresine, 10 değeri b parametresine ve diğerleri de args parametresine demet olarak aktarılacaktır. Örneğin: - - def foo(*args): - pass - - Aşağıdaki çağrısı da geçerlidir: - - foo(*'ankara') -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c, d, e): - print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}') - -t = 10, 20, 30 - -foo(1, 2, *t) # a = 1, b = 2, c = 10, d = 20, e = 30 - - -a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -b = {100, 200, 300} -print(*a, 10, *b) # 1 2 3 4 5 6 7 8 9 10 10 200 100 300 - - -print(*range(10, 20), sep=', ') # 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 - - -def bar(a, b, *c): - print(f'a = {a}, b = {b}, c = {c}') - -x = [10, 20, 30, 40, 50, 60] - -bar(1, *x) # a = 1, b = 10, c = (20, 30, 40, 50, 60) - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin bir liste içerisindeki değerleri aralarına ', ' koyarak yazdırmak isteyelim. Tabii böyle bir yazımın sonunda ',' - karakteri olmamlıdır. Muhtemel çözüm şöyle olabilir: - - a = [10, 20, 30, 40, 50] - - for i in range(len(a)): - if i != 0: - print(end=', ') - print(a[i], end='') - - print() - - Aslında *'lı argüman sayesinde bu işlem tek bir print ile yapılabilmektedir: - - print(*a, sep=', ') - -#------------------------------------------------------------------------------------------------------------------------ - *'lı argüman sarma fonksiyon yazarken "iletme (forwarding)" amaçlı da kullanılabilmektedir. Örneğin biz myprint isimli - fonksiyon yazmak isteyelim. Ancak fonksiyonumuz aldığı parametreleri print fonksiyonuna yollasın (forward etsin): - - def myprint(*args): - print(*args) - - myprint(10, 20, 30, 40, 50) - - Ancak buradaki iletmede bir kusur vardır. Ya sep ve end parametrelerini kullanmak istersek? - - myprint(10, 20, 30, 40, 50, sep=', ', end='*') - - Aşağıdaki gibi çözüm bu örnekte çalışabilise de genel bir çözüm oluşturmaz: - - def myprint(*args, sep=' ', end='\n'): - print(*args, sep=sep, end=end) - - myprint(10, 20, 30, 40, 50, sep=', ') -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerin de dolaşılabilir nesneler olduğunu unutmayınız. -#------------------------------------------------------------------------------------------------------------------------ - -d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} - -print(*d) # 10 20 30 40 50 -print(*d.values()) # ali veli selami ayşe fatma - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonların **'lı argümanları da olabilmektedir. Bu argümanlar argüman listesinde birden fazla kez bulundurulabilmektedir. - **'lı argümanlar bir sözlük nesnesi olmak zorundadır. Bu argümanlara ilişkin sözlük nesnelerinin anahtarlarının str türünden olması - gerekir. Ancak değerleri herhangi bir türden olabilir. - - Yorumlayıcı argümanda ** gördüğünde argümana ilişkin sözlük nesnesini dolaşır. Anahtarları argümanların isimleri, değerleri - de o isimli argümanlara onlara verilmiş değerler kabul ederek fonksiyonu çağırır. Örneğin: - - def foo(a, b, c): - pass - - d = {'a': 10, 'b': 'ankara', 'c': 12.3} - - Burada çağrı şöyle yapılmış olsun: - - foo(**d) - - Bu çağrının tamamen eşdeğeri şudur: - - foo(a=10, b='ankara', c=12.3) - - Aşağıdaki çağrıya dikkat ediniz: - - d = {'a': 10, 'b': 20} - - foo(**d, 100) # error! - - Bu çağrı geçersizdir. Çünkü bunun eşdeğeri şöyledir: - - foo(a=10, b=20, 100) - - İsimli argümanlardan sonra isimsiz argümanlar gelemez. Örneğin: - - def foo(a, b, c): - pass - - foo(100, *{'b': 10, 'c': 20}) - - Bu çağrı geçerlidir. Çünkü eşdeğeri şöyledir: - - foo(100, b=10, c=20) - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a, b, c, d, e, f): - print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}, f = {f}') - -d = {'c': 100, 'd': 200, 'e': 300} - -foo(10, 20, **d, f=400) # foo(10, 20, c=100, d=200, e=300, f=400) - -#------------------------------------------------------------------------------------------------------------------------ - **'lı argümanlar sarma fonksiyonlarda "iletme (forwarding)" işlemlerinde kullanılabilmektedir. Örneğin: - - def myprint(*args, **kwargs): - print(*args, **kwargs) # perfect forwarding - - myprint(10, 20, 30, sep=', ', end='*') - - Burada myprint fonksiyonuna geçirilen tüm argümanlar print fonksiyonuna mükemmel biçimde iletilmektedir. Çünkü örnek - çağrıdaki sep ve end isimli argümanları myprint fonksiyonunun kwargs parametresine sözlük nesnesi olarak geçirilecektir. - myprint fonksiyonu da bunu print fonksiyonuna aynı biçimde iletmiştir. - - "forwarding" bir bir fonksiyonun aldığı parametreleri başka bir fonksiyona argüman olarak iletmesi durumuna denilmektedir. - Yukarıdaki örnekte myrint fonksiyonu kendisi hangi argümanlarla çağrılmışsa print fonksiyonunu da o argümanlarla çağırmıştır. - - Aşağıdaki örnekte matplotlib kütüphanesindeki plot fonksiyonu myplot isimli bir fonksiyon tarafından sarmalanmış ve - parametreler plot fonksiyonuna ilketilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -import math -import matplotlib.pyplot as plt - -xpoints = [] -ypoints = [] - -x = -6. -while x <= 6: - xpoints.append(x) - ypoints.append(math.sin(x)) - x += 0.01 - -def myplot(*args, **kwargs): - plt.plot(*args, **kwargs) - -plt.title('Sinüs Grafiği', fontsize=18, color='blue', pad=20, fontweight='bold') -myplot(xpoints, ypoints, color='red', linewidth=6, linestyle='--', alpha=0.5) -plt.text(2, -0.50, 'Test', fontsize=16, color='green', fontweight='bold') - -plt.show() - -#------------------------------------------------------------------------------------------------------------------------ - 32. Ders 03/08/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir listenin ya da demetin elemanı da *'lı bir dolaşılabilir nesne olabilir. Bu durumda bu dolaşılabilir nesne dolaşılır. - Sanki onların değerleri liste ya da demete eleman yapılmış gibi olur. Örneğin: - - a = 1, 2, 3, 4, 5 - b = [10, 20, *a, 30, 40] - - Tabii listeleri ve demetleri oluştururken istediğimiz kadar çok *'lı eleman kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, *range(5), 30, 40] -print(a) # [10, 20, 0, 1, 2, 3, 4, 30, 40] - -t = 10, 20, *'ali', 30, 40 -print(t) # (10, 20, 'a', 'l', 'i', 30, 40) - -#------------------------------------------------------------------------------------------------------------------------ - Benzer biçimde bir sözlük oluşturulurken sözlüğün elemanları da **'lı nesneler olabilir. Bu duurmda **'ın sağındaki sözlüğün - elemanları o sözlüğün elemanlarına eklenmiş olur. Örneğin: - - d = {'süleyman': 100, 'sacit': 200} - k = {'ali': 10, 'veli': 20, 'selami': 30, **d, 'ayşe': 40, 'fatma': 50} - - Tabii burada artık **'ın sağındaki sözlüğün anahtarlarının birer string olması gerekmez. Biz bu biçimde sözlük oluştururken - istediğimiz kadar çok **'lı eleman kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'süleyman': 100, 'sacit': 200} -k = {'ali': 10, 'veli': 20, 'selami': 30, **d, 'ayşe': 40, 'fatma': 50} - -print(k) # {'ali': 10, 'veli': 20, 'selami': 30, 'süleyman': 100, 'sacit': 200, 'ayşe': 40, 'fatma': 50} - -#------------------------------------------------------------------------------------------------------------------------ - Bir çağrı sırasında argümanların girilişi ile ilgili nihai kural şöyledir: - - 1) İsimli argümanlar (keyword arguments) her zaman isimsiz (pozisyonel) argümanların sağında bulunmak zorundadır. İsimli bir argümanın sağında - isimsiz bir argüman bulunamaz.**'lı argümanlar isimli argüman olarak kabul edilmektedir. - - 2) Ancak isimli argümanın sağında bir ya da birden fazla karışık sırada *'lı ve **'lı argüman bulunabilir. - - 3) Her zaman *'lı argümanlar **'lı argümanların solunda bulunmak zorundadır. - - 4) Argüman parametre eşleşmesinde önce yalnızca isimsiz argümanlar ve *'lı argümanlar dikkate alınır. isimli ve **'lı argümanlar - nerede bulunuyor olursa olsun bu aşamada dikkate alınmazlar. İsimsiz argümanlar ve *'lı argümanlar ilk n tane parametre değişkeni ile - sırasıyla eşleştirilir. Sonra isimli argümanlar eşleştirilir. Eğer toplamda bir parametre için birden fazla değer eşleştirilmişse - ya da bir parametre için hiçbir değer eşleştirilmemişse bu durum error oluşturmaktadır. Örneğin aşağıdaki gibi bir fonksiyon olsun: - - def foo(a, b, c, d, e, f): - print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}, f = {f}') - - Aşağıdaki gibi bir çağrı geçerlidir: - - t = (10, 20) - foo(1, 2, f=10, *t, e=30) - - Burada önce isimsiz ve *'lı argümanlar eşleştirilir. 1 --> a ile, 2 --> b ile, 10 --> c ile ve 20 --> d ile eşleştirilecektir. - Sonra isimli argümanlar da eşleştirilir. Burada toplamda her parametreye yalnızca bir kez ve en fazla bir kez eşleştirme y - apılmıştır. Örneğin: - - foo(1, 2, f=10, *t, *t) - - Burada f'ye iki kez eşleştirme yapıldığı için çağrı geçersizdir. Örneğin: - - foo(1, 2, f=100, **d, c=200) - - Bu çağrı da yukarıda belirtilen kurallara tamamen uygundur. Bu nedenle geçerlidir. Örneğin: - - t = (10, 20) - - foo(1, 2, f=100, *t) - - Burada argüman kuralları uygulnamıştır. Argüman parametre eşleştirmesinde önce isimş ve *'lı argümanlar dikkate alınacaktır. - Bu durumda 1 --> a ile, 2 --> b ile, 10 --> c ile, 20 -->d ile ve f --> 100 ile eşleştirilecektir. Ancak e parametre değişkeni - değer almadığı için bu çağrı error ile sonuçlanacaktır. Örneğin: - - t = (10, 20) - d = {'f': 100, 'e': 200} - - foo(1, 2, *t, **d) - - Burada da yukarıda belirtitğimiz tüm kurallara uyulmuştur. Argüman parametre eşleştirmeleri de uygundur. O halde çağrı geçerlidir. - Örneğin: - - t = (10, 20) - d = {'f': 100, 'e': 200} - - foo(1, 2, **d, *t) - - Burada **'lı argümanın *'lı argümanın sağında olması gerekirdi. Bu nedenle bu durum error oluşturacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Açağıdaki örnekte add isimli fonksiyon *'lı paramtresiyle aldığı değerlerin toplamına geri dönmektedir. Ancak bu değerler eğer - bir demet ya da liste ise onlar da bu toplama katılmışlardır. -#------------------------------------------------------------------------------------------------------------------------ - -def add(*args): - total = 0 - for x in args: - if isinstance(x, (tuple, list)): - for y in x: - total += y - else: - total += x - - return total - -result = add(1, (2, 3), (4, 5)) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örnekte biz add fonksiyonunu aşağıdaki gibi çağıramayız: - - result = add(1, (2, (3, 4)), (4, 5)) - print(result) - - Bu tür durumlarda "özyineleme (recursion)" uygulamak gerekir. Özyineleme bir olgunun kendisine benzer bir olguyu barındırması - anlamına gelmektedir. Özyineleme programalamada tipik olarak kendi kendi çağıran fonksiyonlar yoluyla sağlanır. Biz burada - özyineleme üzerinde durmayacağız. Ancak bu özyinelemeli aşağıdaki gibi sağlanabilmktedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Listenin ve demetin elemanlarının *'lı olabileceğini belirtmiştik. Aşağıdaki programı bu bağlamda inceleyiniz. -#------------------------------------------------------------------------------------------------------------------------ - -def add(*args): - total = 0 - for x in args: - total += x - - return total - -result = add(1, 2, 3, 4, 5, *(7, *(8, 9)), *[9, 2, *(3, 5, 6)]) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - 33. Ders 08/08/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Büyük bir projenin tek bir kaynak dosya biçiminde organize edilmesi iyi bir teknik değildir. Bunun tipik nedenlerini - şöyle açıklayabiliriz: - - - Program tek bir kaynak dosyada yazılırsa dosya çok büyük olabilir. Bu da dosyanın edit edilmesini zorlaştırır. - - Programda bir değişiklik yapıldığında yeniden yorumlama işlemi gerekir. - - Programın bir proje ekibi tarafından geliştirileceği durumda tek kaynak dosya bunun için uygun değildir. Kişilerin farklı - kaynak dosyalar üzerinde aynı zaman diliminde çalışması gerekir. - - Python'da ".py" uzantılı kaynak dosyalara "modül (module)" de denilmektedir. Örneğin biz içerisinde birtakım faydalı fonksiyonlar - olan "utility.py" biçiminde bir dosya oluşturmuş olalım. Bu dosyaya aynı zamanda modül de denilmektedir. Eğer biz - Python programcısı olarak bir modül içerisindeki fonksiyonları ve global değişkenleri kendi programımızdan kullanmak istersek - önce o modülü "import etmemiz" gerekir. Import işlemi "bir modülün (yani Python kaynak dosyasının)" kullanıma hazır getirilmesi - işlemidir. Inport işlemi import deyimi ile yapılmaktadır. import deyiminin genel biçimi şöyledir: - - import [as ] [, [as ], ...] - - Burada dosya ismi uzantı içermemelidir. Örneğin biz utility.py dosyasının içerisindekileri kullanmak isteyelim. import işlemini - şöyle yapabiliriz: - - import utility - - import işlemi yapılırken as anahtar sözcüğü ile modüle bir takma isim verilebilir. Örneğin: - - import utility as util - - Aslında Python'ın standart kütüphanesindeki öğeler de .py dosyalarının içerisindedir. Onları kullanmak için bizim o modülleri - import etmemiz gerekir. Örneğin: - - import math - import statistics - - Biren fazla modülün import edilmesi tek bir import deyimi ile de modül isimlerinin arasına ',' atomu getirilerek de yapılabilir. - Örneğin: - - import math, statistics - - Tabii birden fazla modülü tek bir import deyimi ile import ederken herbir modüle as cümleciği ile takma isimler de verebiliriz. - Örneğin: - - import math as mt, statistics as st - - Bir modülü import ettikten sonra o modülün içerisindeki değişkenler modül ismiyle ya da as ile belirttiğimiz isimle "." operatörü ile - niteliklendirilerek edilerek kullanılmak zorundadır. Örneğin: - - import math - - result = math.sqrt(10) - - import deyimindeki as anahtar sözcüğü niteliklendirmede belirtilecek modül ismini değiştirmek amacıyla kullanılmaktadır. - Örneğin: - - import math as mt - - result = mt.sqrt(10) - - as ile takma isim verme genellikle uzun isimleri kısaltmak için as kullanılmaktadır. Örneğin: - - import numpy as np - - result = np.sqrt(10) - - Tabii as ile isim değiştirildiğinde modül ismi artık kullanılamaz. Örneğin: - - import math as mt - - result = mt.sqrt(10) # geçerli - result = math.sqrt(10) # error! math ismi yerine mt isminin kullanılması gerekir. - - Daha önceden belirttiğimiz gibi Python'da yorumlayıcının içine gömülmüş olan yani herhangi bir modül içerisinde olmayan - dolayısıyla da kullanmak için import işlemi gerekmeyen bir grup fonksiyona "built-in" fonksiyon denilmektedir. print, input, - max, min, type, id gibi fonksiyonlar built-in fonksiyonlardır. - - Örneğin biz sample.py dosyası içerisinde utility.py dosyasındaki foo fonksiyonunu çağırmak isteyelim. Bunun için önce - dosyayı import etmemiz gerekir: - - import utility - - utility.foo() - -#------------------------------------------------------------------------------------------------------------------------ - -# utility.py - -def add(*args): - total = 0 - for x in args: - total += x - - return total - -def multiply(*args): - total = 1 - for x in args: - total *= x - - return total - -pi = 3.14156265 - -# sample.py - -import utility as util - -result = util.add(1, 2, 3, 4, 5) -print(result) - -result = util.multiply(1, 2, 3, 4, 5) -print(result) - -print(util.pi) - -#------------------------------------------------------------------------------------------------------------------------ - import deyimi kaynak dosyanın herhangi bir yerinde bulunabilir. Bazı programcılar bütün import dosyalarını programın - tepesinde import ederler. Bazıları gerektiği zaman gerektiği yerde import ederler. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir modül import edildiğinde o modülün içerisindeki tüm kodlar (yani deyimler) çalıştırılmaktadır. Yani import etme - aslında import edilen dosyanın aynı zamanda çalıştırılması anlamına da gelmektedir. Bu nedenle import edilen dosyada - örneğin print gibi komutlar varsa bunlar da işletilecektir. - - Aşağıdaki programda sample.py çalıştırıldığında import işlemi neticesinde ekranda print fonksiyonun yazdırdığı yazılar da - görüntülenecektir. sample.py programının çalıştırılması sonucunda ekranda şu yazıları görmelisiniz: - - sample.py - one - two - three - 15 - 120 - 3.14156265 -#------------------------------------------------------------------------------------------------------------------------ - -# sample.py - -print('sample.py') - -import utility as util - -result = util.add(1, 2, 3, 4, 5) -print(result) - -result = util.multiply(1, 2, 3, 4, 5) -print(result) - -print(util.pi) - -# utility.py - -print('one') - -def add(*args): - total = 0 - for x in args: - total += x - - return total - -print('two') - -def multiply(*args): - total = 1 - for x in args: - total *= x - - return total - -pi = 3.14156265 - -print('three') - -#------------------------------------------------------------------------------------------------------------------------ - Bir modül import edildiğinde yorumlayıcı modülün içindeki kodları çalıştırır. Sonra "module" isimli bir sınıf türünden bir - nesne yaratır. Modülün içerisindeki değişkenleri bu nesneye yerleştirir besbebin adresini de import deyiminde belirtilen - değişkenin içerisine yerleştirir. Yani modül isimleri aslında module türünden nesnelerin adreslerini tutmaktadır. Örneğin: - - >>> import math - >>> type(math) - - >>> math.sqrt(10) - 3.1622776601683795 - >>> x = math - >>> x.sqrt(10) - 3.1622776601683795 - >>> type(x) - - - Burada biz modeule türünden bir değişkeni başka bir değişkene atadık. Artık iki değişken de aynı module nesnesini - gösterdiğine göre fonksiyon çağrılırken hangi ismin kullanılacağının bir önemi kalmamıştır. - - Tabii import komutunda as cümleciği ile module nesnesinin adresinin atanacağı değişkenin ismini değiştirebiliriz. - Örneğin: - - import math as m - - Burada module nesnesinin adresi m değişkenine atanmaktadır. Bu import işleminde math isimli bir değişken yaratılmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir modül birden fazla kez import edilebilir. (Bu durum geçerli olsa da anlamlı değildir.) Bu durumda modülün kodları - yalnızca modül ilk kez import edildiğinde çalıştırılır. Daha sonraki import işlemlerinde modülün içerisinde kodlar - çalıştırılmaz. Bunu siz de deneyebilirsiniz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi bir modül yanlışlıkla kendisini import ederse ne olur? Örneğin: - - # sample.py - - import sample - - print('sample') - - Biz bu sample.py" dosyasını çalıştırırsak ne olur? Burada dosya çalıştırıldığında import deyiminden dolayı "sample.py" - dosyası çalıştırılacaktır. Ancak o dosya çalıştırılırken yeniden import görülse bile bu ikinci import işlemi olacağından - dosyanın içi yeniden çalıştırılmayacaktır. import işlemindne dolayı ekrana "sample" yazısı basılacaktır. import deyiminin - çalışması bittikten sonra yine print deyimi deyimi çalıştırılacağına göre yine ekrana "sample" yazısı çıkacaktır. Yani - şöyle bir çıktı elde edilecektir: - - sample - sample - - Programcıların yanlışlıkla kaynak dosyalarına standart bir modülün ismini vermeleri sık karşılaşılan bir hatadır. Örneğin: - - # math.py - - import math - - result = math.sqrt(10) - print(result) - - Burada programcı standart modül olan math modülü yerine kendi dosyasını import etmiş olabilir. Bu durumda math.sqrt çağrımı - error oluşturacaktır. Tabii bu durum aslında standart modüllerin sys.path listesinde aranması sırasına da bağlıdır (bunu izleyen - paragraflarda ele alacağız). Siz kaynak dosyalarınıza Python'ın standart modüllerinin ismini vermemelisiniz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Modül dosyası yerel düzeyde de import edilebilir. Bu durumda bu import ismine yalnızca o fonksiyonda erişebiliriz. - (Yerel değişkenler konusu izleyen bölümlerde ele alınmaktadır.) Aslında aynı dosya nasıl import edilmiş olursa olsun - her zaman tek bir modül nesnesi yaratılmaktadır. Örneğin: - - def foo(): - import util - - print(id(util)) # 1581194445344 - - foo() - - import util - - print(id(util)) # 1581194445344 - - del util - - import util - - print(id(util)) # 1581194445344 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi bir modül import edildiğinde ilgili modül dosyası (yani .py dosyası) yorumlayıcı tarafından hangi dizinlerde - aranmaktadır? İşte Python yorumlayıcısı modülleri sys.path isimli bir listede belirtilen dizinlerde sırasıyla aramaktadır. - sys modülü Python standart kütüphanesinin içerisindeki bir modüldür. path değişkeni de bu modülün içerisindeki global - bir değişkendir. Örneğin: - - >>> sys.path - ['', 'f:\\', 'C:\\Users\\CSD\\anaconda3\\python39.zip', 'C:\\Users\\CSD\\anaconda3\\DLLs', 'C:\\Users\\CSD\\anaconda3\\lib', - 'C:\\Users\\CSD\\anaconda3', 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages', 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages\\win32', - 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages\\Pythonwin'] - - Burada boş string yani '' ifadesi "çalışma dizini (current working directory)" anlamına gelmektedir. Genellikle çalışma dizini - bizim Python programını çalıştırdığımız dizindir. Ancak çalışma dizini program çalışırken de istenildiği gibi değiştirilebilir. - Default durumda çalışma dizini eğer programı komut satırından çalıştırıyorsak içinde bulunduğumuz dizindir. PyCharm IDE'si - her zaman çalışma dizinini proje dizini olarak ayarlamaktadır. Spyder IDE'si ise çalışma dizinini o anda çalıştırılan Python - programının içinde bulunduğu dizin olarak ayarlamaktadır. - - sys.path listesi program her çalıştırıldığında yeniden oluşturulmaktadır. Programcı kendi programı çalışırken bu listeye - ekleme yapabilir ya da bu listeden bazı elemanları (yani dizinleri) silebilir. Ancak bu işlem kalıcı olmaz. Program yeniden - çalıştırıldığında bu listede yine önceki çalıştırmadaki dizinler bulunacaktır. - - Aslında Python standart kütüphane dokümanlarına göre sys.path listesinin ilk elemanı boş string ya da python yorumlayıcısını - çalıştıran script dosyasının bulunduğu dizin olmak zorundadır. Ancak Python yorumlayıcıları ve birtakım IDE'ler burada belirtilen - bu kurala uymayabilmektedir. - - Pekiyi biz sys.path listesine kalıcı bir biçimde nasıl dizin ekleyebiliriz? İşte bunu yapabilmek için PYTHONPATH isimli bir - "çevre değişkeni (envirionment variable)" eklemek gerekir. Çevre değişkenleri konusu kursumuzun kapsamı dışındadır ve Derneğimizde - "Sistem Programlama ve İleri C Uygulamaları" kursunda ele alınmaktadır. Biz burada yalnızca bu çevre değişkeninin nasıl oluşturulacağını - göreceğiz. - - Windows'ta çevre değişkeni oluşturmak için "Denetim Masası/Sistem/Gelişmiş Sistem Ayarları/Ortam Değişkenleri diyalog - penceresi kullanılmaktadır. Eğer birden fazla dizin girilecekse dizinler arasında ';' bulundurulmalıdır. UNIX/Linux - ya da macOS sistemlerinde aynı işlem bash kullanıcıları için şöyle yapılmaktadır: bash kabuğu login olunduğunda - "interaktive login shell" için ~/.bah_profile dosyasını, "interaktif non-login shell" için ~/.bashrc dosyasını çalıştırmaktadır. - Bu dosyaların içerisine şu satır eklenmelidir: - - export PYTHONPATH=/istenilen/dizinin/yol/ifadesi - - UNIX/Linux ve macOS sistemlerinde PYTHONPATH çevre değişkenine birdne fazla dizin eklemek için dizinler arasıbnda ':' - karakteri bulunmalıdır. - - Tabii sys.path listesi yalnızca PYTHONPATH çevre değişkenindeki öğeleri içermemektedir. Yorumlayıcının kendisi de default olarak - bu listeye bazı dizinler eklemektedir. Yorumlayıcı tipik olarak standart Python kütüphanesinin install edildiği dizinleri de - bu listeye eklemektedir. Genel olarak PYTHONPATH ile belirtilen dizinler yorumlayıcının kendi dizinlerinden önce eklenmektedir. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 34. Ders 17/08/2022 - Carsamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir modül import edildiğinde CPython gerçekleştirimi import edilen modülü "arakoda" dünüştürerek __pycache__ isimli bir - dizinde .pyc uzantılı bir dosya içerisinde saklamaktadır. Arakodlar bir sonraki import işleminde yorumlayıcı tarafından - çok daha hızlı bir biçimde işleme sokulmaktadır. Genel olarak CPython gerçekleştirimi arakod dosyasını modül dosyası - neredeyse o modül dosyasının bulunduğu dizin içerisindeki __pycache__ dizinine yerleştirmektedir. Bu durumda örneğin - biz math modülünü import ettiğimizde math.py dosyası hangi dizindeyse arakdo dosyası da o dizinin içerisindeki __pycache__ - dizininde oluşturulacaktır. Tabii biz bu __pycache__ dizininin silersek ilk import işleminde bu dizin yeniden yaratılacaktır. - - Pekiyi ya biz import işleminden sonra modül dosyasının kaynak kodu üzerinde değişiklik yaparsak ne olacaktır? İşte CPython - yorumlayıcısı modül dosyasında değişiklik yapıldığında artık __pycache__ dizinindeki arakodu kullanmadan önce modül dosyasını - yeniden arakoda dönüştürmektedir. Böylece __pycache__ dizini içerisinde içerisinde her zaman modülün en güncel halinin - arakodu bulunmaktadır. (CPython yorumlayıcısı .py dosyasının tarih zaman bilgisiyle __pycache__ dizini içerisindeki arakod - dosyasının tarih zaman bilgisini karşılaştırarak arakod dosya oluşturulduktan sonra .py dosyasında bir değişiklik yapılıp - yapılmadığını anlayabilmektedir.) - - Ancak Python dünyasında standart bir arakod sistemi yoktur. Yani belli bir Python yorumlayıcısı import işleminde modülü - hiç arakoda dönüştürmeyebilir. Kaldı ki farklı Python yorumlayıcılarında farklı arakod sistemleri de kullanılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir modüldeki belli değişkenleri modül ismiyle niteliklendirmeden doğrudan kullanabilmek için from import deyimi - kullanılmaktadır. from import deyiminin genel biçimi şöyledir: - - from import [as - - import ya da from import deyimi global düzeyde kullanılırsa global bir değişkenin yerel düzeyde kullanılırsa yerel - bir değişkenin yaratılmasına yol açmaktadır. Örneğin: - - def foo(): - import math - - result = math.sqrt(10) - print(result) - - def bar(): - result = math.sqrt(10) # error! çünkü math ismi foo'da yerel - print(result) - - foo() - bar() - - Tabii farklı fonksiyonlarda aynı modül yerel olarak import edilmiş olsa bile toplamda modülün içerisindeki kodlar yine yalnızca - bir kez çalıştırılmaktadır. Örneğin: - - def foo(): - import utility - - util.foo() - - def bar(): - import utility - - result = utility.add(1, 2, 3, 4, 5) - print(result) - - foo() - bar() - - Burada utility içerisindkei kodlar toplamda bir kez çalıştırılacaktır. - - Biz global değişkenleri henüz görmedik. İzleyen paragraflarda bu konu üzerinde duracağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - from import deyiminin özel bir biçimi de yıldızlı biçimdir. Örneğin: - - from math import * - - Bu biçimde modüldeki tüm isimler import edilir. Yani o isimlerin hepsini biz doğrudan kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -from math import * - -result = sqrt(10) -print(result) - -result = pow(10, 2) -print(result) - -result = sin(0.5) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da başında ve sonunda iki alt tire olan bazı özel isimler çeşitli nedenlerle kullanılabilmektedir. Örneğin: - - __init__ - __new__ - __main__ - __add__ - - Bu isimlerin kolay okunması için "dunder" ya da "dunderscore" sözcükleri uydurulmuştur. Yani örneğin "dunder foo" - demekle biz "__foo__" demiş olmaktayız. Ya da örneğin "dunder init" demekle biz "__init__" demiş olmaktayız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - import ya da from import deyiminde modülün tüm kodlarının çalıştırılması bazen istenmeyebilir. Örneğin birisi "utility.py" isimli bir - program yazmış olabilir. Bu program çeşitli şeyleri yapıyor olabilir. Ancak bu u"tility.py" içerisinde foo ve bar isimli fonksiyonlar - başka kişilerin de kullanabileceği faydalı fonksiyonlar olabilir: - - # utility.py - - def foo(): - print('foo') - - def bar(): - print('bar') - - print('çeşitli kodlar') - print('çeşitli kodlar') - print('çeşitli kodlar') - - Burada biz bu foo ve bar fonksiyonlarını kullanmak için modülü import etsek modülün kodları çalışacak ve aslında bizim - istemediğimiz şeyler de yapılacaktır. Bu durum nasıl engellenebilir? - - İşte Python'da __name__ isimli özel bir değişken vardır. Bu değişken str türündendir. Eğer modül bağımısız bir program gibi - çalıştırılırsa __name__ değişkeni '__main__' yazısını içerir. Eğer modül import edilerek çalıştırılırsa __name__ değişkeni - modül ismini içerir. Böylece biz yazdığımız programdaki fonksiyonların ve değişkenlerin import edilerek kullanılmasını istiyorsak - va aynı zamanda da onu bağımsız bir program gibi de çalıştırmak istiyorsak o programı aşağıdaki gibi bir uygulayarak yazmalıyız: - - if __name__ == '__main__': - pass - - Örneğin utility.py isimli bir program yazacak olalım. Programımız da 2'den 1000'e kadar asal sayıları ekrana yazdırıyor olsun. - Ancak bu modülde isprime isimli asallık testi yapan fonksiyonu da başkalarının kullanmasına imkan tanıyacak biçimde yazmış olalım. - Şimdi bu utility modülünü import eden kişi 2'den 1000'e kadar asal sayıları yazdırmak istemeyecektir. Yalnızca isprime fonksiyonunu - kullanmak isteyecektir. O zaman "utility.py" dosyası şöyle düzenlenmelidir: - - #utility.py - - import math - - def isprime(val): - if val % 2 == 0: - return val == 2 - - sqrt_val = math.sqrt(val) - for i in range(3, int(sqrt_val) + 1): - if val % i == 0: - return False - - return True - - if __name__ == '__main__': - for i in range(2, 1000): - if isprime(i): - print(i, end=' ') - - Kullanımı da şöyle olabilir: - - # sample.py - - import utility - - result = utility.isprime(101) - print('prime' if result else 'not prime') - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Rastgele sayı üretme yazılımda çeşitli amaçlarla kullanılabilmektedir. Örneğin bir oyun programı rastgele sayılar üreterek - birtakım nesnelerin rasgele hareket etmesini sağlayabilir. Benzer biçimde bir simülayon programı rasgele sayılarla gerçek - bir ortamı simüle etmeye çalışabilir. Rastgele sayı üretimi kriptolojide de kullanılmaktadır. - - Ratsgelelik (rassallık) felsefi açılımları da olan bir konudur. Doğada rastgele sayılar elde edebilmek için rastgele olaylardan - faydalanılmaktadır. Ancak bilgisyayarlar tamamen deterministik biçimde çalışırlar. Yani bilgisayar devrelerinde doğadaki - gibi rassal deney oluşturmak mümkün olmamaktadır. Bilgisayarlar restgele sayıları tamamen sayısal işlemlerle elde ederler. - Böyle elde edilmiş rastgele sayılara "sahte rasgele sayılar (pseudo random numbers)" denilmektedir. Sahte rassal sayı - üretiminde bir "tohum değerden (seed)" başlanır. Sonra bir dizi rasrgele sayı elde edilir. Tohum değer aynı olursa aynı - dizimler elde edilmektedir. - - Python'da rastgele (rassal) sayılar standart random modülündeki fonksiyonlarla elde üretilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - rondom modülündeki random iisimli fonksiyon parametresizdir. Her çağrıldığında [0,1) aralığında rastegele float bir - sayı üretir. eğer bir alalıkta rastgele sayı üretilirken her sayının elde edilme olsaılığı diğerleri ile aynıysa böyle - rassal sayılara "düzgün dağılmış rassal sayılar" denilmektedir. random modülündeki random fonksiyonu istatistiksel - terminolojide "düzgün dağılmış rastgele sayı" üretmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -for _ in range(10): - result = random.random() - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Doğadaki pek çok olgu ismine "normal dağılım" ya da "Gauss dağılımı" denilen dağılıma uymaktadır. Bu dağılımda değerler - ortalama etrafında toplanma eğilimindedir. Oratalamadan uzaklaştıkça o değerlerin görülme sıklığı yani olasılıkları azalır. - - random modülündeki gauss fonksiyonu normal dağılıma uygun rastgele sayılar üretmek için kullanılmaktadır. Fonksiyonun ilk - parametresi ortalamayı, ikinci parametresi standart sapmayı belirtir: - - random.gauss(mu=0.0, sigma=1.0) - - Fonksiyonda ortalamanın default değerinin 0, standart sapmanın default değerinin 1 olduğunu görüyorsunuz. Bu biçimdeki - normal dağılıma istatistikte "standart normal dağılım" da denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -for i in range(10): - result = random.gauss(100, 15) - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - random modülündeki randint isimli fonksiyon [a, b] aralığında rastgele bir tamsayı (int türden sayı) üretir. Yani randint - fonksiyonu yalnızca tamsayı değerlerinden oluşan "kesikli düzgün (discrete uniform) dağılama" uygun rastgele sayılar üretmektedir. - Fonksiyonda aralığın her iki değerinin de üretime dahil olduğuna dikkat edşniz. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -for i in range(10): - result = random.randint(10, 20) - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Biz randint fonksiyonu ile yazı-tura denemesi yapabiliriz. Yazı tura işlemini belli miktarda yapıp oranlara bakarsak - gitgide sonucun 0.5'e yakınsadığını görürüz. Buna istatistikte "büyük sayılar yasası (law of large numbers)" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -def head_tail(n): - head = 0 - tail = 0 - - for _ in range(n): - result = random.randint(0, 1) - if result == 0: - head += 1 - else: - tail += 1 - - return head / n, tail / n - -head, tail = head_tail(10) -print(head, tail) - -head, tail = head_tail(1000) -print(head, tail) - -head, tail = head_tail(1000000) -print(head, tail) - -head, tail = head_tail(100000000) -print(head, tail) - -#------------------------------------------------------------------------------------------------------------------------ - random modülündeki choice isimli fonksiyon bir "sequence container'ı" yani [...] ile indekslenebilen bir nesneyi parametre - olarak alır. O nesnedeki rastgele bir elemanı geri dönüş değeri olarak verir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -result = random.choice(a) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - random modülündeki sample isimli fonksiyon indekslenebilir (yani [] ile kullanılabilen) bir nesneyi ve bir de eleman - sayısını parametre olarak alır. O nesneden o miktarda elemanı rastgele seçer. (Buna istatistikte "rassal örnekleme" - (random sampling)" denilmektedir. Fonksiyon bize rastgele değerlerden oluşan bir liste vermektedir. Fonksiyonun geri - döndürdüğü listede aynı elemanlardan bulunmayacağına dikkat ediniz. sample fonksiyonunun geri döndürdüğü listedeki eleman - sıralamsı rastgeledir. sample fonksiyonunun parametrik yapısı şöyledir: - - random.sample(population, k, *, counts=None) - -#------------------------------------------------------------------------------------------------------------------------ - -import random - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'sacit', 'süleyman'] - -result = random.sample(a, 3) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Sayısal lotoda rastgele bir colon oynamak için bu fonksiyondan faydalanabiliriz. Örneğin: - - column = random.sample(range(1, 50), 6) - - Burada fonksiyon bize 1 ile 50 arasında (50 dahil değil) 6 değerden oluşan bir liste verecektir. Bu 6 değerden hiçbiri - diğeri ile aynı olmayacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -column = random.sample(range(1, 50), 6) -print(column) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi sayısal lotoda bir kolon oynayan kodu randint fonksiyonunu kullanarak yazabilir miydik? İlk akla gelecek yöntem - gemellikle aşağıdaki gibi olmaktadır: - - import random - - column = [] - for i in range(6): - while True: - val = random.randint(1, 49) - if val not in column: - column.append(val) - break - - print(column) - - Buradaki yöntem iyi bir yöntem değildir. Burada "rastgele üretine değer daha önceden lsitede varsa yeniden değer üretilmiştir. - Böylece listede aynı değerden birden fazla kez olması engellenmiştir. Tabii aynı işlemi küme kullanarak da yapabilirdik: - - import random - - s = set() - - while len(s) != 6: - val = random.randint(1, 49) - s.add(val) - - column = list(s) - print(column) - - Aklımıza şöyle bir çözümde gelebilir: Biz sayıları numbers ismindw listede toplayalım. Sonra listeden choice fonksiyonuyla - rastgele bir sayı seçelim. Sonra o sayıyı column listesine ekleyip, numbers listesinden silelim: - - import random - - column = [] - numbers = list(range(1, 50)) - for i in range(6): - val = random.choice(numbers) - column.append(val) - numbers.remove(val) - - print(column) - - Bu yöntem de aslında etkin değildir. Çünkü numbers listesinden remove metoduyla silme yapılırken aslında listede içsel - olarak bir kaydırma yapılmaktadır. Bu da her ne kadar biz görmüyor olsak da bir zaman kaybı oluşturacaktır. Bu tür durumlarda - bir kaydırmayı engellemek için mantıksal olarka liste küçültme yöntemi uygulanmaktadır. Aşağıdaki kodda numbers listesinden - çekilen rastgele eleman listesine eklenmiştir. Ancak o eleman elemana son eleman atanarak liste küçültülmüştür. - - import random - - numbers = list(range(1, 50)) - column = [] - - for i in range(6): - val = random.randint(1, 50 - i) - column.append(numbers[val]) - numbers[val] = numbers[49 - i - 1] - - print(column) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - sample fonksiyonundaki counts parametresi birinci parametredeki nesnenin eleman sayısı kadar olmalıdır. Bu parametre - aslında ağırlıklandırmak için kullanılmaktadır. Yani örneğin: - - result = random.sample(['ali', 'veli', 'selami'], 5, counts=[3, 2, 1]) çağrısı aşağıdakiyle eşdeğerdir: - - result = random.sample(['ali', 'ali', 'ali', 'veli', 'veli', 'selami'], 5) - - counts parametresine belli bir değer girildiğinde artık sample fonksiyonun geri döndürdüğü liste aynı elemanlardan oluşabilmektedir. - - counts parametresinin isimli kullanılmak zorunda olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -a = ['ali', 'veli', 'selami'] - -result = random.sample(a, 4, counts=[10, 5, 2]) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - random modülündeki choices fonksiyonu sample fonksiyonuna benzemektedir. sample fonksiyonu iadesiz (without replacement) - çekim yaparken choices fonksiyonu iadeli (with replacement) çekim yapmaktadır. Fonksiyonun parametrik yapısı şöyledir: - - random.choices(population, weights=None, *, cum_weights=None, k=1) - - isimli kullanılmak zorunda olan k parametresi kaç elemanlık çekim yapılacağını belirtmektedir. Buradaki weights parametresi - sample fonksiyonundaki counts parametresi gibidir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -result = random.choices(names, k=3) -print(result) - -result = random.sample(names, k=7) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - random modülündeki randrange fonksiyonu parametrik kullanım bakımından range fonksiyonuna benzemektedir. Ancak rastgele - tamsayı üretmektedir. Örneğin: - - result = random.randrange(0, 10, 2) - - Burada aslında biz 0, 2, 4, 8 sayıları arasında rastgele bir sayı üretmiş oluruz. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -a = ['ali', 'veli', 'selami'] - -for i in range(10): - result = random.randrange(0, 10, 2) - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - random modülündeki shuffle isimli fonksiyon karıştırma işlemini yapar. Fonksiyonun parametresinin bir liste olması gerekir. - Karıştırma "in-place" biçimde yapılmaktadır. (Örneğin demetler değiştirilebilir olmadıkları için shuffle fonksiyonu ile - karıştırılamazlar.) -#------------------------------------------------------------------------------------------------------------------------ - -import random - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -print(a) -random.shuffle(a) -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - 35. Ders 22/08/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında karıştırma algoritması çok kolaydır. Genellikle izlenen yol dizinin tüm elemanlarını sırasıyla rastgele elemanla - yer değiştirme yöntemidir. Aşağıda bu yöntem uygulanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -def myshuffle(a): - for i in range(len(a)): - k = random.randrange(len(a)) - a[k], a[i] = a[i], a[k] - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] -myshuffle(names) -print(names) - -#------------------------------------------------------------------------------------------------------------------------ - Bir demeti karıştırmak istersek bunu nasıl yapabiliriz? Tabii yöntemlerden biri "önce demet elemanlarından bir liste - elde etmek, sonra listeyi karıştırmak, sonra da yeniden demet oluşturmak" olabilir: - - import random - - names = ('ali', 'veli', 'selami', 'ayşe', 'fatma') - - result = list(names) - random.shuffle(result) - result = tuple(result) - - print(result) - - - Aslında sample fonksiyonu da kendi içerisinde rastgele bir dizilim vermektedir. sample fonksiyonunun in-place işlem - yapmadığını anımsayınız. Bu durumda sample fonksiyonunda uzunluk parametresini nesnenin uzunluğu kadar girersek bir - karıştırma işlemi yapmış oluruz: - - import random - - names = ('ali', 'veli', 'selami', 'ayşe', 'fatma') - - result = tuple(random.sample(names, len(names))) - - print(result) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte bir oyun kartı destesi oluşturulmuş sonra da bu deste dört oyuncuya dağıtılmıştır. Ancak oyunculara dağıtılan - kartlar sıraya da dizilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -card_vals = {'As': 14, 'Papaz': 13, 'Kız': 12, 'Vale': 11, '10': 10, '9': 9, '8': 8, '7': 7, '6': 6, '5': 5, '4': 4, '3': 3, '2': 2} -card_types = {'Kupa': 3, 'Maça': 2, 'Karo': 1, 'Sinek': 0} - -def build_deck(): - deck = [] - - for cval in card_vals: - for ctype in card_types: - deck.append((cval, ctype)) - - return deck - -def distribute(deck): - random.shuffle(deck) - - players = [[], [], [], []] - - for i in range(52): - players[i % 4].append(deck[i]) - - for player in players: - player.sort(key=keyfunc, reverse=True) - - return players - -def keyfunc(t): - cval, ctype = t - return card_vals[cval] * 4 + card_types[ctype] - -def disp_players(players): - for player in players: - print(player) - print('-------') - -def main(): - deck = build_deck() - players = distribute(deck) - disp_players(players) - -main() - -#------------------------------------------------------------------------------------------------------------------------ - Rastgele sayı üreterek belli duyarlılıkta pi sayısını elde edebiliriz. Bunun için bir birim çamberdeki dörtte birlik - daire dilimini dikkate alırız. Bu dörtte birlik daire dilimi aslında 1x1'lik bir karenin içerisindedir. Biz de [0, 1] - aralığında iki rastgele sayı üreterek bu kare içerisinde rastgele noktalar elde ederiz. Kare içerisinde elde ettiğimiz - noktaların bazıları aynı zamanda bu daire diliminin de içerisinde olacaktır. Toplam nokta sayısı n tane olsun. Bu n tane - noktanın k tanesi aynı zamanda daire diliminin de içerisinde olsun. Buradaki 1x1'lik karenin daire dilimine oranı b değerinin - k değerine oranına eşit olacaktır: - - n / k = 1 / (pi / 4) - - Burada içler dışlar çarpımı ile pi değeri çekilirse şu sonuç bulunur: - - pi = 4 * k / n - - Tabii buradaki n değerini n kadar artırırsak pi'ye o kadar yaklaşırız. - - Aşağıda bu örnek verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -import random -import math - -def getpi(n): - k = 0 - for _ in range(n): - x = random.random() - y = random.random() - distance = math.sqrt(x ** 2 + y ** 2) - if distance < 1: - k += 1 - - pi = 4 * k / n - - return pi - -pi = getpi(1000) -print(pi) - -pi = getpi(10000) -print(pi) - -pi = getpi(100000) -print(pi) - -pi = getpi(1000000) -print(pi) - -pi = getpi(10000000) -print(pi) - -#------------------------------------------------------------------------------------------------------------------------ - enumerate isimli built-in fonksiyon bizden dolaşılabilir bir nesne alır, bize dolaşılabilir bir nesne verir. enumerate - fonksiyonun verdiği dolaşılabilir nesne dolaşıldığında iki elemanlı demetler elde edilecektir. Öyle ki bu demetlerin - ilk elemanları 0'dan başlayan indeks numarasından ikinci elemanları da bizim verdiğimiz dolaşılabilir nesnedeki elemnalardan - oluşur. Örneğin: - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - - for t in enumerate(names) - print(t) - - Buradan sırasıyla (0, 'ali'), (1, 'veli'), (2, selami), (3, 'ayşe'), (4, 'fatma') biçiminde demetler elde edilecektir. - Tabii biz genellikle unpack yaparak demet elemanlarını elde ederiz: - - for index, x in enumerate(names) - print(index, x) - -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -iterator = enumerate(names) -for t in iterator: - print(t) - -iterator = enumerate(names) -for index, x in iterator: - print(index, x) - -for index, x in enumerate(names): - print(index, x) - -#------------------------------------------------------------------------------------------------------------------------ - enumerate fonksiyonu bir dolaşılabilir nesneyi for döngüsü ile dolaşırken hem elemanların index numaralarını hem de - elemanların değerlerini elde etmek için kullanılmaktadır. enumerate fonksiyonunun ikinci bir parametresi de vardır. Bu ikinci - parametreye default olarak 0 değeri verilmiştir. Bu ikinci parametre indeksin nereden başlatılacağını belirtir. Örneğin: - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - - for t in enumerate(names, 10): - print(t) - - Burada artık şu demetler elde edilecektir: (10, 'ali'), (11, 'veli'), (12, selami), (13, 'ayşe'), (14, 'fatma') -#------------------------------------------------------------------------------------------------------------------------ - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -for t in enumerate(names, 10): - print(t) - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişkenin kullanılabildiği program aralığına "faaliyet alanı (scope)" denilmektedir. Python'da değişkenler faaliyet alanları - bakımından üç gruba ayrılmaktadır: - - 1) Yerel Değişkenler (Local Variables) - 2) Global Değişkenler (Global Variables) - 3) Sınıf Değişkenleri (Class Variables) - - Biz sınıf değişkenlerini "sınıflar" konusunda ele alacağız. - - Python'da "değişken (variable)" ile "nesne (object)" kavramları farklı anlamlara gelmektedir. Değişkenler isimli olan varlıklardır. - Değişkenler adres tutarlar. Değişkenlerin içerisindeki adresteki varlıklara "nesne (object)" denilmektedir. Örneğin: - - a = 10 - b = a - - Burada a ve b iki ayrı değişkendir. Ancak aynı nesnenin adresini tutmaktadır. Yani bu kod parçasında iki değişken ancak tek bir nesne vardır. - - Bir değişkene bir fonksiyon içerisinde ilk kez bir değer atandığında yeni bir değişken yaratılır. Fonksiyon içerisinde yaratılan değişkenlere - "yerel değişkenler (local variables)" denilmektedir. Yerel değişkenler yaratıldıkları noktadan yaratıldıkları fonksiyonun sonuna - kadarki bölgede faaliyet gösterirler. Başka yerden kullanılamazlar. Örneğin: - - def foo(): - x = 10; # x bir yerel değişken - print(x) # geçerli, x buarada faaliyet gösteriyor - - print(x) # error! x burada kullanılamaz, burada faaliye göstermiyor. - - Farklı fonksiyonlarda aynı isimli yerel değişkenler farklı değişkenlerdir. Bunlar birbirlerine karışmazlar. Örneğin: - - def foo(): - x = 10 - print(x) - - def bar(): - x = 20 - print(x) - - Burada foo fonksiyonundaki x ile bar fonksiyonundaki x farklı değişkenlerdir. - - Fonksiyonların parametre değişkenleri de yerel değişken gibi faaliyet alanına sahiptir. Yani yalnızca o fonksiyonda kullanılabnilirler. - Farklı fonksiyonların aynı isimli parametre değişkenleri farklı değişkenlerdir. Örneğin: - - def foo(x): # bu x foo'ya özgü bir x - print(x) - - def bar(x): # bu x bar'a özgü bir x - print(x) - - Bir değişken fonksiyonların dışında yaratılmışsa böyle değişkenlere global değişkenler denilmektedir. Global değişkenler - yaratıldıkları yerden dosyanın sonuna kadar her yerde (fonksiyonların içerisinde de) kullanılabilmektedir. Örneğin: - - a = 10 - - def foo(): - print(a) # global olan a - - def bar(): - print(a) # global olan a - - foo() - bar() - - Tabii global fonksiyonların isimleri de birer gobal değişkendir. - - Bir fonksiyon içerisinde global değişkenle aynı isimli bir değişkene atama yapılırsa bu durum global değişkene atama yapıldığı - anlamına gelmez. Aynı isimli yeni bir yerel değişken yaratılır. Atama ona yapılmış olur. Örneğin: - - x = 10 - - def foo(): - x = 20 # x yeni bir yerel değişken, global olan değil - print(x) # buradaki x yerel olan x - - foo() - print(x) # buradaki x global x, zaten yerel x burada faaliyet göstermiyor, 10 çıkacak - - Bazen bir fonksiyonun global bir değişkeni değiştirmesi istenebilir. Bu durumda yorumlayıcıya bunun belirtilmesi - gerekir. Bu işlem global bildirimi ile yapılmaktadır. global bildiriminin genel biçimi şöyledir: - - global - - Örneğin: - - global x - global y, z, k - - Örneğin: - - a = 10 - - def foo(): - global a - a = 20 # global olan a - print(x) # global olan a - - foo() - print(a) # 20 - - Burada artık foo'nun içeisindeki a global olan a'dır. Dolayısıyla foo'nun içerisinde global olan a'ya 20 atanmıştır. global - bildirimi fonksiyon içerisinde bir global değişkenin değerini değiştirmek için kullanılır. Yoksa global değişkenin değeri - değiştirilmeyecekse zaten global değişkenler doğrudan fonksiyonlar içerisinde kullanılabilmektedir. Bunun için gobal bildiriminin - yapılmasına gerek yoktur. Örneğin: - - a = 10 - - def foo(): - global a # burada global bildirimine gerek yok - - print(a) # global olan a - - foo() - - Bir fonksiyon içerisinde önce bir global değişken kullanılmışsa artık aynı isimli bir yerel değişken yaratılamaz. Eğer yaratılmak - istenirse bu durum error oluşturur. Örneğin: - - a = 10 - - def foo(): - print(a) - a = 20 # geçersiz! çünkü daha önce aynı isimli global değişken fonksiyonda kullanılmış - print(a) - - foo() - - Fonksiyon ieçrisinde önce bir global değişken kullanılıp sonra aynı değişkene ilişkin global bildirimi de yapamayız. Örneğin: - - a = 10 - - def foo(): - print(a) # global olan a - global a # error önce bir global değişken kullanılıp sonra o değişkene ilişkin global bildirimi yapılamaz! - a = 20 - print(a) - - foo() - - C/C++, Java, C# gibi dillerde fonksiyonlar ve metotlar içerisinde ayrıca bloklarla yeni faaliyet alanları oluşturulabilmektedir. - Python'da böyle bir durum söz konusu değildir. Python'da biz for döngüsü, if deyimi vs. içinde bir değişken yarattığımızda - o değişkeni bu deyimlerin dışında da kullanabiliriz. Yani Python'da fonksiyonun içerisindeki bloklar ayrı bir faaliyet alanı - belirtmemektedir. Örneğin: - - def foo(): - for i in range(10): - print(i) - x = 10 - - print(x) # geçerli - print(i) # geçerli - -#------------------------------------------------------------------------------------------------------------------------ - global bildiriminde henüz ilgili global değişken yaratılmamış olabilir. Bu durum geçerlidir. Eğer global bildirimi görüldüğünde - global değişken yaratılmadıysa yine de bu bildirim global bir değişkeni belirtir. Yani bu sayede global değişken fonksiyonun - içerisinde yaratılabilir. Örneğin: - - def foo(): - global a - - a = 10 # a global değişikeni yaratılıyor, çünkü henüz yaratılmamış - - foo() - print(a) - - Tabii aslında global bildirimi global değişkenin yaratılacağı anlamına geşmez. Yalnızca kullanılacak değişkenin - global olduğu anlamına gelir. Örneğin: - - def foo(): - global a - - print(a) # error! henüz global nesne yaratılmadı - - foo() - print(a) - - Buradaki problem foo çağrıldığında henüz a global değişkeninin yaratılmamışi olmasıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 36. Ders 24/08/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişken (nesneyi kastetmiyoruz) programın belli bir aşamasında yaratılır, bir süre faaliyet gösterdikten sonra yok edilir. - Değişkenin bellekte kaldığı zaman aralığına "ömür (duration)" denilmektedir. - - - Python'da bir yerel değişken fonksiyon çağrıldıktan sonra akış o değişkene ilk kez değer atandığı noktaya geldiğinde yaratılır, - akış o fonksiyon bittiğinde değişken otomatik olarak yok edilir. Yani yerel değişkenler fonksiyon çağrılmadan bellekte yer kaplamazlar. - Fonksiyon bittiğinde de yok edilmiş olurlar. Zaten bir yerel değişkenin fonksiyon dışında kullanılamamasının temel nedeni de budur. Örneğin: - - def foo(): - x = 10 - # ... - - print(x) - - Burada x henüz fonksiyon çağrılmadığına göre ya da çağrılıp sonlandığına göre aslında yaşamamaktadır. Bu nedenle biz x'i - fonksiyonun dışında kullanamayız. - - - Fonksiyonların parametre değşkenleri de fonksiyon çağrıldığında yaratılır, fonksiyon sonlandığında otomatik olarak yok edilir. - Bir fonksiyon çağrıldığında önce parametre değişkenleri yaratılır, sonra argümanlardan parametre değişkenlerine atama yapılır, ondan sonra - akış fonksiyona aktarılır. Örneğin: - - def foo(a, b): - pass - - foo(10, 20) - - Burada fonksiyon çağrıldığında önce a ve b yaratılır. Sonra a = 10, b = 20 atamaları yapılır. Sonra da programın akışı - fonksiyona aktarılır. Fonksiyon sonlandığında da a ve b yok edilir. - - - Bir global değişken o değişkene ilk kez değer atandığında yaratılır, program sonuna kadar yaşamaya devam eder. Bu nedenle - global değişkenler her yerden yani fonksiyonlardan da kullanılabilmektedir. Örneğin: - - x = 10 - - def foo(): - print(x) - - print(x) - foo() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişkenin ne zaman yaratılıp ne zaman yok edildiğini yukarıda açıkladık. Pekiyi değişkenlerin gösterdikleri nesneler - ne zaman yaratılıp yok edilmektedir. Örneğin: - - x = 10 - x = 20 - - Burada x değişkeni yaratılırken aynı zamanda int bir nesne de yaratılır. Daha sonra int türü "değiştirilemez (immutable)" olduğu için - yeni bir int nesnesi yaatılıp x değişkeni artık o yeni yaratılan x nesnesini gösterecektir. Pekiyi eski nesneye ne olacaktır? İşte Python'da - "çöp toplayıcı (garbage collector)" denilen mekanizma bir nesneyi hiçbir değişken göstermiyorsa artık o nesneyi arka planda bellekten silmektedir. - Çöp toplama tamamen yorumlayıcı sistem tarafından yönetilir. Dolayısıyla programcı bu konuda bir şey yapmaz. Örneğin: - - def foo(a): # bu noktada int nesnesyi x ve a gösteriyor - pass # fonksiyon bittiğinde a yok edilecek, böylece int nsneyi yalnızca x gösterir durumda olacaktır. - - def bar(): - x = 10 # bu noktada int nesneyi yalnızca x gösteriyor - foo(x) - print(x) # bu noktada yine int nesnesi yalnızca x gösteriyor - - bar() - - Buradaki kod parçasında önce bar fonksiyonu çağrılmıştır. bar fonksiyonunun içerisinde yerel x değişkeni yaratılmıştır. Bu - yerel x değişkeni içerisinde 10 değeri olan bir int nesneyi göstermektedir. Sonra foo fonksiyonu çağrılmış ve bu x değişkeninin - içerisindeki adres foo fonksiyonunun a parametre değişkenine atanmıştır. Artık içerisinde 10 olan int nesnesi iki değişken - gösteriyor durumdadır. foo bitince a parametre değişkeniş yok edilecektir. Akış bar fonksiyonuna geri döndüğünde artık yine - nesneyi yalnızca x değişkeni gösteriyor durumdadır. Nihayet bar da bitince artık x de yok edilecek ve nesneyi hiçbir değişken - göstermiyor durumda olacaktır. İşte bu durumda yorumlayıcının çöp toplayıcı mekanizması devreye girecek ve çöp haline gelmiş - içerisinde 10 olan nesneyi silecektir. - - Aşağıdaki örnekte döngünün her ynelenemesinde yeni bir int nesne yaratılır, ancak öncekiler çöp toplayıcı tarafından yok edilir. - - i = 0 - for _ in range(100): - print(id(i)) # her defasınde değişik bir adres yazıdırılacaktır - i += 1 - - Python standart dokümanlarında çöp toplayıcının kullandığı yöntem ve algoritma açıklanmamıştır. Bu nedenle çöp tıoplayıcılar - arasında işleyiş bakımından farklılıklar olabilmektedir. CPython gerçekleştirimi "referans sayacı (reference counting)" temelinde - bir çöp toplama mekanizması kullanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da çokça kullanılan bir "built-in" fonksiyon da map isimli fonksiyondur. Bu fonksiyon bizden bir fonksiyonu - ve dolaşılabilir bir nesneyi parametre olarak olarak alır. Bize bir dolaşım (şiterator) nesnesi verir. map fonksiyonun - verdiği dolaşım nesnesi dolaşıldığında bizim verdiğimiz dolaşılabilir nesnenin elemanları verdiğimiz fonksiyona argüman - yapılıp fonksiyonun geri dönüş değerleri elde edilecektir. Bizim map fonksiyonuna verdiğimiz fonksiyonun bir parametresi - olmak zorundadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -def square(x): - return x * x - -iterable = map(square, a) - -x = list(iterable) -print(x) - -for x in map(square, a): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte isimlerin karakter uzunlukları elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -for x in map(len, names): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte yazı içerisindeki sayılar toplanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -s = '1 2 3 4 5' - -total = 0 -for x in map(int, s.split()): - total += x - -print(total) - -#------------------------------------------------------------------------------------------------------------------------ - Built-in sum fonksiyonu dolaşılabilir bir nesne alıp onun tüm elemanlarının toplamanına geri dönmektedir. O halde yukarıdaki - örneği daha kompakt bir biçimde aşağıdaki gibi oluşturabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -s = '1 2 3 4 5' - -total = sum(map(int, s.split())) -print(total) - -#------------------------------------------------------------------------------------------------------------------------ - Built-in max ve min fonksiyonları bizden dolaşılabilir bir nesne alıp onların en büyük ve en küçük elemanlarını verir. - Aşağıdaki örnekte en uzun ismin karakter uzunluğu elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -result = max(map(len, names)) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii map fonksiyonunun birinci parametresi bir metot da olabilir. Ancak metotlar tek başına kullanılamazlar. Metotlar - ancak ilişkin oldukları sınıf türünden bir değişkenle '.' operatörü ile kullanılabilirler. Bu durumda map fonksiyonunun - birinci parametresibe metot vereceksek metodu isimle değil . biçiminde vermeliyiz. Örneğin: - - d = {'ali': 1, 'veli': 2, 'selami': 3, 'ayşe': 4, 'fatma': 5} - - names = ['ali', 'selami', 'ayşe', 'güray', 'fatma', 'veli', 'can'] - - for x in map(d.get, names): - print(x, end=' ') - - Burada names listesi içerisindeki her bir isim d.get metoduna sokulacak ve oradan elde edilen değerler dolaşım sırasında - elde edilecektir. Örneğin: - - s = 'ankara' - - for x in map(s.count, s): - print(x, end=' ') - - Burada yazının her karakterinin yazı içerisinde kaç tane bulunduğu elde edilmek istenmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 1, 'veli': 2, 'selami': 3, 'ayşe': 4, 'fatma': 5} - -names = ['ali', 'selami', 'ayşe', 'güray', 'fatma', 'veli', 'can'] - -for x in map(d.get, names): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Aslında map fonksiyonu birden fazla dolaşılabilir nesne alabilmektedir. Bu durumda bu dolaşılabilir nesnenin karşılıklı elemanları - birinci parametresiyle verilen fonksiyona parametre olarak aktarılır. Yani map fonksiyonuna biz kaç tane dolaşılabilir nesne verirsek - birinci parametre ile geçirdiğimiz fonksiyonun o kadar parametresi olmak zorundadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] -b = [10, 20, 30, 40, 50] -c = [100, 200, 300, 400, 500] - -def foo(a, b, c): - return a + b + c - -iterable = map(foo, a, b, c) -for x in iterable: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - map fonksiyonuna birden fazla dolaşılabilir nesne geçirdiğimizde bunların eleman sayısı aynı olmak zorunda değildir. - Bunlardan herhangi birinde sona gelindiğinde tüm dolaşım sonlandırılır. Aşağıdaki örnekte yalnızca iki dolaşım - yapılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] -b = [10, 20] -c = [100, 200, 300, 400, 500] - -def foo(a, b, c): - return a + b + c - -iterable = map(foo, a, b, c) -for x in iterable: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii burada map fonksiyonuna geçirdiğimiz fonksiyonun da parametresi *'lı olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] -s = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'cumhur'] -w = [72.4, 69.5, 84.2, 51.6, 56.2] - -def foo(*x): - return x - -for x in map(foo, a, s, w): - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da iç içe (nested) fonksiyon tanımlamaları yapılabilmektedir. Örneğin: - - def foo(): - ... - def bar(): - ... - ... - - Tabii iç fonksiyonun içerisinde de başka bir fonksiyon tanımlanabilir. Bu tür durumlarda iç fonksiyon ismi yerel bir değişken olmaktadır. - Dolayısıyla iç fonksiyon ancak dıştaki fonksiyonun içerisinden çağrılabilir. - - Aşağıdaki örnekte biz bar fonksiyonunu ancak foo fonksiyonun içerisinde ve bar tanımlandıktan sonra çağırabiliriz. - bar fonksiyonunu dışarıdan çağıramayız: - - def foo(): - print('foo') - def bar(): - print('bar') - bar() - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - def bar(): - print('bar') - - bar() - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - Eğer bir fonksiyon genel değil de yalnızca başka bir fonksiyonun yazımı için oluşturuluyorsa bu fonksiyonu asıl fonksiyonun - iç fonksiyonu olarak tanımlamak iyi bir tekniktir. Çünkü dışarıdaki fonksiyonlar herkesin ilgisini çeker. Halbuki iç - fonksiyonlar dışarıdan kullanılamayacağından dolayı zaten kişilerin ilgisini çekmez. Dolayısıyla onların kafalarını karıştırmaz. - Örneğin 2'den parametresiyle belirtilen sayıya kadar asal sayıları yazdıran bir fonksiyon yazmak isteyelim. Bu fonksiyon - sayının asal olup olmadığını test eden isprime gibi bir fonksiyonu kullanıyor olsun. Bu isprime fonksiyonu dışarıyı - ilgilendiren bir fonksiyon değilse bir iç fonksiyon olarak yazılabilir. Bir fonksiyon yalnızca başka bir fonksiyonun yazımında - kullanılmak için oluşturuluyor ise onu iç fonksiyon yapmak iyi bir tekniktir. Tabii iç bir fonksiyonu dış fonksiyon içerisinde - kullanmadıktan sonra onu tanımlamanın da bir anlamı yokturç -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def print_primes(n): - def isprime(val): - if val % 2 == 0: - return val == 2 - for i in range(3, int(math.sqrt(val)) + 1, 2): - if val % i == 0: - return False - return True - - for x in range(2, n + 1): - if isprime(x): - print(x, end=' ') - -print_primes(100) - -#------------------------------------------------------------------------------------------------------------------------ - 37. Ders 31/08/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İç bir fonksiyon dış fonksiyonun o ana kadar yaratılmış olan yerel değişkenlerini kullanabilir. Ancak dış fonksiyon iç fonksiyonun - yerel değişkenlerini kullanamaz. Örneğin: - - def foo(): - a = 10 - def bar(): - print(a) # 10 - b = 20 - print(b) # 20 - bar() - - foo() - - Burada bar fonksiyonu foo fonksiyonun a yerel değişkenini kullanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - def bar(): - print(a) # 10 - b = 20 - print(b) # 20 - bar() - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - Tabii Python'da isim araması fonksiyon çağılıp akış ilgili noktaya geldiğinde yapıldığına göre dış fonksiyonun yerel - değişkeni iç fonksiyondan sonra da oluşturulmuş olsa eğer çağrılma sırasında bu yerel değişken yaratılmışsa iç - fonksiyon içerisinden kullanılabilir. Örneğin: - - def foo(): - a = 10 - def bar(): - print(a) # 10 - print(b) # 20 - b = 20 - bar() - - Burada bar fonksiyonu çağrıldığında foo fonksiyonunun b yerel değişkeni de yaratılmış durumda olacaktır. Dolyısıyla - bar çağrımında bir sorun oluşmayacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - def bar(): - print(a) # 10 - print(b) # 20 - b = 20 - bar() - -#------------------------------------------------------------------------------------------------------------------------ - İç bir fonksiyon içerisinde dış fonksiyonun yerel değişkeni ile aynı isimli bir değişkene atama yaptığımızda biz dış - fonksiyounun yerel değişkenini değiştirmiş olmayız. İç fonksiyonda yeni bir yerel değişken yaratmış oluruz. Örneğin: - - def foo(): - a = 10 - def bar(): - a = 20 # bar'a ilişkin yeni bir yerel a oluşturulmaktadır - print(a) # 20 - bar() - print(a) # foo'nun yerel a'sı, 10 - - Burada foo fonksiyonu çağrıldığında bar içerisindeki a değişkenine atama yapıldığında üst fonksiyon olan foo fonksiyonunun - yerel a değişkenine atama yapılmamaktadır. bar içerisinde yeni bir yerel a değişkeni yaratılmış olmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - - def foo(): - a = 10 - def bar(): - a = 20 # bar'a ilişkin yeni bir yerel a oluşturulmaktadır - print(a) # 20 - bar() - print(a) # foo'nun yerel a'sı, 10 - - foo() - -#------------------------------------------------------------------------------------------------------------------------ - İç fonksiyonun dış fonksiyonun yerel değişkenini değiştirebilmesi için nonlocal bildiriminin yapılması gerekir. nonlocal - bildiriminin genel biçimi şöyledir: - - nonlocal ; - - Örneğin: - - def foo(): - a = 10 - def bar(): - nonlocal a - a = 20 # buradaki a foo'nun a'sı - print(a) # foo'nun a'sı, 20 - bar() - print(a) # foo'nun a'sı, 20 - - foo() - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - def bar(): - nonlocal a - - a = 20 # buradaki a foo'nun a'sı - print(a) # foo'nun a'sı, 20 - bar() - print(a) # foo'nun a'sı, 20 - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - Tabii iç fonksiyonun da iç fonksiyonu olabilir. Bu durumda nonlocal bildirimi benzer biçimde etki gösterir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - def bar(): - nonlocal a - def tar(): - nonlocal a - - a = 20 # foo'nun a'sı - print(a) # foo'nun a'sı yazdırılıyor, 20 - tar() - print(a) # foo'nun a'sı yazdırılıyor, 20 - bar() - print(a) # foo'nun a'sı yazdırılıyor, 20 - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - nonlocal bildirimi ile belirtilen ismin aranması içten dışa doğru yapılmaktadır. Dolayısıyla nonlocal bildirimi için bildirilen - değişkenin hemen dış fonksiyonda bulunyor olması gerekmez. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - def bar(): - def tar(): - nonlocal a # bar'da a olmadığı için foo'nun a'sı - - a = 20 - tar() - bar() - print(a) # foo'nun a'sı yazdırılıyor, 20 - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi global bildirimi fonksiyon içerisinde henüz global değişken yaratılmamış olsa bile yapılabiliyordu. Ancak - nonlocal bildirimi için durum böyle değildir. nonlocal bildirimi ile bildirilen değişken dıştaki herhangi bir fonksiyonda - bulunamazsa bu durum error oluşturur. Örneğin: - - def foo(): - def bar(): - nonlocal a # error! dış fonksiyonda a yok! - - a = 10 - bar() - - foo() - - nonlocal bildiriminde bildirilen değişken dış fonksiyonların yerel değişkeni olarak aranır. Bu aramda global değişkenlere - bakılmaz. Örneğin: - - a = 10 - def foo(): - def bar(): - nonlocal a # error! dış fonksiyonda yerel a yok! global bir a'nın olması önemli değil - - a = 10 - bar() - foo() - - Dış fonksiyonda aynı isimli değişken global bildirimi ile bildirilmiş olsa bile nonlocal bildirimi bu değişkeni görmez. - Çünkü global bildirimi de hiçbir zaman yerel bir değişkene ilişkin değildir. - - a = 10 - def foo(): - global a - def bar(): - nonlocal a # error! dış fonksiyonda yerel a yok! global bir a'nın olması önemli değil - - a = 10 - bar() - - foo() - - Benzer biçimde dış fonksiyonda bir global bildirimi yapılmışsa daha iç bir fonksiyonda yaratılan aynı isimli değişken - o iç fonksiyonun değişkeni olur. Örneğin: - - a = 10 - - def foo(): - global a - - a = 20 # global a değiştiriliyor - def bar(): - a = 30 # yeni bir yerel a yaratılıyor - bar() - - foo() - - print(a) # global a yazdırılıyor - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii her modülün global değişkenleri o modüle özgüdür. Yani her modül kendi içerisinde farklı bir isim alanı (name space) oluşturmaktadır. - Bu nedenle farklı modüllerdeki aynı isimli global değişkenler (fonksiyonlar da birer global değişken gibidir) birbirlerine karışmazlar. - Aşağıdaki sörnekte "sampl.py" modülünde, "a.py" modülünde ve "b.py" modülünde aynı isimli x global değişkeni oluşturulmuştur. "a.py" ve - "b.py" modülü "sample.py" modülünden import edilerek bu global değişkenler kullanılmıştır. Bu örnekten de görüleceği gibi "a.py" modülündeki - x değişkeni ile "b.py" modülündeki x değişkeni ve "sample.py" modülündeki x değişkeni aynı isimli olmalarına karşın farklı değişkenlerdir. - Ayrıca örnekte "a.py" modlünde ve "b.py" modülünde foo isminde iki ayrı fonksiyon da bulundurulmuştur. Bu fonksiyonlar kendi modüllerindeki - x global değişkenlerini kullanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -# sample.py - -import a -import b - -x = 10 - -print(x) # 10 -print(a.x) # 20 -print(b.x) # 30 - -a.foo() # 20 -b.foo() # 30 - -x = 100 -a.x = 200 -b.x = 300 - -print(x) # 100 -print(a.x) # 200 -print(b.x) # 300 - -a.foo() # 200 -b.foo() # 300 - -# a.py - -x = 20 - -def foo(): - print(x) - -# b.py - -x = 30 - -def foo(): - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - from import deyiminin eşdeğerini anımsayınız. Örneğin: - - from a import x - - Bu işlemin tamamen eşdeğeri şöyledir: - - import a - x = util.x - del a - - Bu durumda: - - from a import x - - deyimi ile biz x'i kullandığımızda a'daki x'i kullanmış olmayız. Çünkü int değiştirilmez bir tür olduğu için x = a.x atamasında - x artık başka bir int değişkeni belirtmektedir. Dolayısıyla: - - from a import x - - x = 100 - - print(x) # x bu modüldeki x, a modülündeki x değil - - import a - - print(a.x) # a modülündeki x - - Tabii from import deyimi ile import ettiğimiz değişken değiştitilebilir bir nesneye ilişkin ise bu durumda gerçekten o nesne - üzerinde yapılan değişiklik o modüldeki nesneyi etkileyecektir. Örneğin: - - # a.py - - x = [1, 2, 3, 4, 5] - - # sample.py - - from a import x - - print(x) # [1, 2, 3, 4, 5] - - - x[0] = 100 - - import a - - print(a.x) # [100, 2, 3, 4, 5] - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir modül yerel düzeyde import edildiğinde her ne kadar modül değişkenini yalnızca o fonksiyonda kullanabiliyor olsak da - bu modülü başka bir yerde import ettiğimizde aynı modülü kullanmış oluruz. Çünkü program içerisinde yerel de olsa global da - olsa aslında modül bir kez import edilmekte diğer import işlemleri gerçek anlamda yapılmamaktadır. - - Aşağıdaki örnekte "sample.py" modülündeki foo fonksiyonu içerisinde "a.py" modülü import edilip oradaki global değişken - değiştirilmiştir. Fonksiyon sonlandıktan sonra yeniden aynı modül global düzeyde import edilmiştir. Bu değişiklik - görülecektir. Çünkü modül yerel düzeyde import edilmiş olsa bile modül nesnesi bir kez oluşturulup saklanmaktadır ve modül - ikinci kez import edildiğinde saklanan modül nesnesi yeniden verilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -# sample.py - -def foo(): - import a - - a.x = 20 - -foo() - -import a - -print(a.x) # 20 - -# a.py - -x = 10 - -#------------------------------------------------------------------------------------------------------------------------ - Python'da globals isimli built-in fonksiyon o andaki tüm global değişkenleri bir sözlük nesnesi biçiminde bize verir. - Sözlüğün anahtarları global değişkenlerin isimlerinden değerleri ise onların değerlerinden oluşur. globals fonksiyonu ile - global değişkenleri elde ettiğinizde sizin yaratmadığınız başka değişkenleri de görürseniz şaşırmayınız. Örneğin biz - daha önce __name__ isimli global değişkenin yorumlayıcı tarafından oluşturulduğunu görmüştük. Benzer biçimde bütün - built-in değişkenler __builtins__ isimli bir global sözlük nesnesi içerisinde bulunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -name = 'ali' - -def foo(): - pass - -g = globals() -print(g) -print(list(g)) # ['__name__', '__file__', '__nonzero__', '__builtins__', 'a', 'name', 'foo', 'g'] - -#------------------------------------------------------------------------------------------------------------------------ - globals fonksiyonuyla elde etmiş olduğumuz sözlüğe eleman ekleyerek yeni global değişkenleri bu yolla oluşturabiliriz. - Yorumlayıcı da zaten bütün global değişkenleri aslında kendi içerisinde bir sözlükte tutmaktadır. Zaten globals fonksiyonu - da yorumlayıcının global değişkenleri tutmakl için kullandığı sözlüğü bize vermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 -g = globals() -g['b'] = 20 -print(b) # 20 -print(a) # 10 - -#------------------------------------------------------------------------------------------------------------------------ - globals fonksiyonuna benzeyen locals isimli bir built-in fonksiyon daha vardır. locals fonksiyonu hangi fonksiyon - içerisinde çağrılmışsa o fonksiyonun yerel değişkenlerini bir sözlük olarak vermektedir. locals fonksiyonu global - düzeyde çağrılırsa tamamen globals fonksiyonu çağrılmış gibi etki göstermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = 10 - -def foo(): - b = 20 - d = locals() - print(d) # {'b': 20} - print(list(d)) # ['b'] - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - İçteki fonksiyonun dıştaki fonksiyonun yerel değişkenlerini kullanabildiğini anımsayınız. Ancak locals fonksiyonu iç - bir fonksiyonda da çağrılabilir. Bu durumda yalnızca kendi iç fonksiyonundaki yerel değişkenlerini verir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - def bar(): - b = 20 - d = locals() - print(d) # {'b': 20} - - bar() - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - locals fonksiyonu ile elde edilen sözlüğe bir ekleme yapıldığında gerçekten de sözlüğe ekleme yapılmış olur. Ancak eklenen - isim yerel değişken olarak kullanılamaz. Bu durum globals fonksiyonundaki duruma bu bakımdan benzememektedir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - a = 10 - d = locals() - print(d) # {'a': 10} - d['b'] = 20 - print(d) # {'a': 10, 'b': 20} - print(b) # error! b eklenmiş olsa da bu biçimde kullanılamaz! - -foo() - -#------------------------------------------------------------------------------------------------------------------------ - Daha önceden de belirttiğimiz gibi "fonksiyonel proıgramlama modeli (functional programming paradigm)" bir fonksiyonun - çıktısının başka bir fonksiyona girdi yapılması onun çıktısının başka bir fonksiyona girdi yapılması biçiminde formül - yazar gibi tek bir satırda pek çok şeyin yapılabildiği programlama modelidir. Artık klasik programlama dillerine de çeşitli - yoğunlukta fonksiyonel modeli destekleyebilecek özellikler eklenmektedir. İşte Python'da "içlemler (comprehensions)" konusu da - fonksiyonel programlama modelini desteklemek amacıyla dile eklenmiştir. Python'da ki içlemlere benzer öğeler bazı programlama - dillerinde değişik biçimlerde bulunabilmektedir. Ancak popüler programlama dillerinin çoğunda içlemlere benzer öğeler yoktur. - Bu bölümde Python'da içlemler konusunu ele alacağız. ("Comprehension" sözcüğü İngilizce çeşitli anlamlara gelmektedir. Matematikte - buna Türkçe "içlem" de denildiği için biz bu terimin Türkçe karşılığı olarak "içlem" sözcüğünü tercih ediyoruz.) - - İçlemler üçe ayrılmaktadır: - - 1) Liste içlemleri (list comprehensions) - 2) Küme içlemleri (set comprehensions) - 3) Sözlük içlemleri (dictionary comprehensions) - - Eğer içlemden bir liste elde ediliyorsa buna "liste içlemi", küme elde ediliyorsa buna "küme içlemi", sözlük elde - ediliyorsa buna da "sözlük içlemi" denilmektedir. Demet içlemi biçiminde bir özellik yoktur. Ancak diğer içlemlere benzer - sentaks demetlerde kullanılırsa bu tamamen farklı bir anlama gelmektedir. Buna "üretici ifadeler (generator expressions)" - denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İçlemlerin genel biçimleri birbirine çok benzemektedir. Liste içlemlerinin genel biçimi şöyledir: - - [ for in [if koşul] ] - - En çok karşılaşılan biçim köşeli, poarantez içerisinde bir ifade ve onun yanında bir for döngüsüdür. Örneğin: - - [i * i for i in range(10)] - - İçlemin çalışma mekanizması oldukça basittir. for göngüsünün her yinelenmesinden sonra for döngüsünün solundaki ifade - çalıştırılır. Bu ifadenin değeri bir listeye eklenir. Böylece for döngüsü her çalştırıldığında listeye yeni bir eleman - eklenmiş olacaktır. İçlemden de sonuç olarak bu liste elde edilmektedir. Örneğin: - - a = [i * i for i in range(10)] - print(a) - - Burada for döngüsünden sırasıyla 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 değerleri i olarak elde edilir. Her değer elde edildiğinde soldaki i * i - ifadesi çalıştırılırsa ve bu değerler bir listede biriktirilirse sonuçta [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] biçiminde - bir liste oluşturulacaktır. Aşağıdaki içleme dikkat ediniz: - - [ifade for i in iterable] - - Bunun eşdeğeri şöyledir: - - temp = [] - for i in iterable: - temp.append(ifade) - - Örneğin: - - a = [i * i for i in range(10)] - print(a) - - Bu işlemin eşdeğeri şöyledir: - - temp = [] - for i in range(10): - temp.append(i * i) - a = temp - print(a) - - Her ne kadar yukarıdaki iki ifade eşdeğer gibi gözükse de içlemler genel olarak daha hızlı olma eğilimindedir. - Çünkü içlemler genellikle daha temel düzeyde ve tek bir operasyon biçiminde yapılmaktadır. - - Tabii içlemlerin önemi aslında hızdan ziyade kompakt bir görünüm sağlamasındadır .İçlemler bir ifade (expression) - durumundadır. Dolayısıyla başka ifadelerin içerisinde kullanılabilrler. Örneğin: - - total = sum([i * i for i in range(10)]) - print(total) - - Burada 10'a kadar sayıların karelerinin toplamı bulunmuştur. Eğer içlemler olmasaydı bu ifadeyi bu kadar kompakt yazamazdık. - - Tabii aslında for döngüsünün solundaki ifadenin döngü değişkeni ile ilgili olması zorunluluk değildir. Örneğin: - - a = [100 for i in range(10)] - print(a) # [100, 100, 100, 100, 100, 100, 100, 100, 100, 100] - - Örneğin: - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - names_len = [len(name) for name in names] - print(names_len) - - Burada biz isimlerin karakter uzunluklarından oluşan bir liste elde etmiş olduk. Örneğin: - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - print(*[len(name) for name in names]) - - Burada biz tek hamlede isimlerin uzunluklarını ekrana yazdırdık. - -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -names_len = [len(name) for name in names] -print(names_len) - -print(*[len(name) for name in names]) - -#------------------------------------------------------------------------------------------------------------------------ - Liste içlemleriyle yapılmak istenen şeylerin bir bölümü map fonksiyonuyla da yapılabilir. liste içlemleri bize ürün olarak - liste vermektedir. Halbuki map fonksiyonu bize ürün olarak dolaşılabilir bir nesne verir. Tabii biz map fonksiyonunun çıktısını - list fonksiyonuna sokarak bir liste elde edebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -names_len = [len(name) for name in names] -print(names_len) - -names = list(map(len, ['ali', 'veli', 'selami', 'ayşe', 'fatma'])) -print(names) - -#------------------------------------------------------------------------------------------------------------------------ - İçlemlerde istenirse for cümlesinin sağına if anahtar sözcüğü ile bir koşul cümleceği de eklenebilir. Bu durumda for döngüsü - her işletildiğinde sağdaki koşula bakılır. Eğer koşul doğruysa soldaki ifade işletilir. Koşul yanlışsa soldaki ifade işletilmez - ve bu ifadenin sonucu listeye eklenmez. Örneğin: - - a = [i for i in range(10) if i % 2 == 0] - print(a) # [0, 2, 4, 6, 8] - - Burada koşul i çift ise sağlanmaktadır. Dolayısıyla listede çift sayılar bulunacaktır. O halde örneğin: - - [ifade for i in iterable if koşul] - - içleminin işlevsel eşdeğeri şöyledir: - - temp = [] - - for i in iterable: - if koşul: - temp.append(ifade) - - Örneğin: - - total = sum([i for i in range(10) if i % 2 == 1]) - print(total) - - Burada 10'a kadar tek sayıların toplamı bulunmaktadır. Örneğin: - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - - a = [name for name in names if 'a' in name] - print(a) - - Burada için 'a' harfi geçen isimler bir liste biçiminde elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 38. Ders 05/09/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte başı 'a' ya da 'A' harfi ile başlayan isimler liste içlemi yoluyla elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -a = [name for name in names if name[0] == 'a' or name[0] == 'A'] -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte küçük harf olan şehir isimleri büyük harfe dönüştürülerek bir liste biçiminde elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -cities = ['ankara', 'izmir', 'eskişehir', 'muğla', 'kastamonu'] - -upper_cities = [city.upper() for city in cities] -print(upper_cities) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki liste içlemi tam palindrom olan cümleleri elde etmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -sentences = ['anastas mum satsana', 'izmir', 'ey edip adanada pide ye', 'eskişehir', 'adamla çeneç almada'] - -palindromes = [sentence for sentence in sentences if sentence == sentence[::-1]] -print(palindromes) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte tam palindrom olmayan cümleler de elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -sentences = ['anastas mum satsana', 'izmir', 'ey edip adanada pide ye', 'eskişehir', 'adamla çene çalmada'] - -palindromes = [sentence for sentence in sentences if ''.join(sentence.split()) == ''.join(sentence.split())[::-1]] -print(palindromes) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte palindrom sayılar liste içlemi yoluyla elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -numbers = [12, 1221, 13431, 12345, 197262] - -palindrome_numbers = [number for number in numbers if str(number) == str(number)[::-1]] -print(palindrome_numbers ) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte (şehir_ismi, plaka_numarası) biçimindeki demet listesinde "şehir_ismi-plaka_numarası" biçiminde - bir string listesi elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -cities = [('ankara', 6), ('izmir', 35), ('eskişehir', 26), ('muğla', 48), ('kastamonu', 37)] - -result = [city + '-' + str(plate) for city, plate in cities] -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örnek aşağıdaki gibi de yapılabilirdi -#------------------------------------------------------------------------------------------------------------------------ - -cities = [('ankara', 6), ('izmir', 35), ('eskişehir', 26), ('muğla', 48), ('kastamonu', 37)] - -result = [f'{city}-{plate}' for city, plate in cities] -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin biz belli bir sayıya kadar olan asal sayıları bir liste biçiminde liste içlemi yoluyla elde edebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def isprime(val): - if val % 2 == 0: - return val == 2 - root_val = int(math.sqrt(val)) - for i in range(3, root_val + 1, 2): - if val % i == 0: - return False - return True - -result = [i for i in range(2, 1000) if isprime(i)] -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Aslında içlemlerin içerisinde birden fazla for döngüsü de olabilir. Yani aşağıdaki gibi içlemler de söz konusu olabilir: - - result = [ifade for x in iterable1 for y in iterable2 if koşul] - - İç içe for döngüleri nasıl çalışıyorsa buradaki çalışma da benzer biçimdedir. Dıştaki döngünün her bir yinelemesinde içteki - döngü baştan sona çalıştırılmaktadır. Yukarıdaki içlemin kod karşılığı şöyle ifade edilebilir: - - temp = [] - for x in iterable1: - for y in iterable2: - if koşul: - temp.append(ifade) - result = temp - - Örneğin: - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - - names_chars = [char for name in names for char in name ] - print(names_chars) -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -names_chars = [char for name in names for char in name ] -print(names_chars) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte iki kümenin kartezyen çarpımları bir demet listesi biçiminde elde edilmişir. İçlemlerde demet oluştururken - parantezler gerekmektedir. Örneğin: - - a = ['x', 'y', 'z'] - b = [1, 2, 3] - - cp = [(i, k) for i in a for k in b] - print(cp) - - Eğer burada içlemdeki ifadeyi oluşturan demeti parantezsiz biçimde aşağıdaki gibi oluşturmaya çalışsaydık error oluşurdu: - - cp = [i, k for i in a for k in b] # error! - - Çünkü Python'da bu anlamda virgül operatörü düşük önceliklidir. Örneğin: - - t = 1, 2 + 3 - - Burada (1, 5) demeti elde edilecektir. -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] -cities = ['ankara', 'izmir', 'adana', 'iskenderun', 'fatsa'] - -cartesian_product = [(name, city) for name in names for city in cities] -print(cartesian_product) - -#------------------------------------------------------------------------------------------------------------------------ - Bir içlemdeki ifade başka bir içlem olabilir. Örneğin bu yöntemle biz liste listeleri elde edebiliriz. Aşağıdaki içleme - dikkat ediniz: - - [[ifade for y in x] for x in a] - - Burada for döngüsü her işletildiğinde ifade olarak soldaki içlem yapılacaktır. Soldaki içlem de bize bir liste vereceğine göre - burada listelerden oluşan bir liste elde edilecektir. - - Aşağıdaki örnekte listelerden oluşan bir listedaki sayılar string'e dönüştürülmüştür. -#------------------------------------------------------------------------------------------------------------------------ - -a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -b = [[str(y) for y in x] for x in a] -print(b) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte liste içeisindeki listelerdeki çift elemanlar elde elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -b = [[y for y in x if y % 2 == 0] for x in a] -print(b) # [[2], [4, 6], [8]] - -#------------------------------------------------------------------------------------------------------------------------ - Amacımız her biri 5 elemandan oluşan 10 elemanlı tüm elemanları 0 olan bir liste listesi oluşturmak olsun. - Bu işlemi "repitition" ile yapamayız: - - >>> a = [[0, 0, 0, 0, 0]] * 10 - >>> a - [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - - Her ne kadar görünüşte bu işlem yapılmış gibi olsa da aslında burada bir kusur vardır. Listeler "değiştirilebilir (mutable)" - olduğu için "repitition" işlemi de kopyalama yoluyla yapıldığı için iç listenin bir elemanı değiştirildiğinde sanki tüm - listelerin ilgili elemanları değiştirilmiş gibi olur. Örneğin: - - >>> a[0][0] = 100 - >>> a - [[100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0]] - >>> id(a[0]) - 2608002477184 - >>> id(a[1]) - 2608002477184 - >>> id(a[2]) - 2608002477184 - - Pekiyi bu işlemi doğru olarak nasıl yapabiliriz? İlk akla gelecek yöntem manuel bir for döngüsü kullanmaktadır: - - >>> a = [] - >>> for _ in range(10): - ... a.append([0, 0, 0, 0, 0]) - ... - >>> a - [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - >>> a[0][0] = 100 - >>> a - [[100, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - - Ancak bu işlemin en pratik yöntemi liste içlemi kullanmaktadır. Örneğin: - - >>> a = [[0, 0, 0, 0, 0] for _ in range(10)] - >>> a - [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - >>> a[0][0] = 100 - >>> a - [[100, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - - Bir içlemde ifade kısmında [....] ile bir lits yaratımı varsa for döngüsünün her çalışmasında artık yeni bir liste nesnesi - yaratılacaktır. Tabii bu örnekte aslında ifade olan listeyi yineleme (repition) ile de oluşturabiliriz. Çünkü int nesneler - "değiştirilemez" olduğu için bir sorun oluşmayacaktır. Örneğin: - - >>> a = [[0] * 5 for _ in range(10)] - >>> a - [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - >>> a[0][0] = 100 - >>> a - [[100, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir liste içleminde for cümleciğinin "in" kısmında da içlem kullanılabilir. Örneğin: - - [ifade for x in [for y in z]] - - Burada aslında içlemle elde edilen liste dönülmektedir. - - Aşağıdaki örnekte biz yazı sözcüklere ayrılıp sözcükler ters çevrilmiştir. Ters çevrilen sözcüklerin ilk karakteri 'n' - olanlar bir liste olarak elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -text = 'bugün hava çok güzel, sen de parka gittin mi?' -a = [revword for revword in [word[::-1] for word in text.split()] if revword[0] == 'n'] -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Genel olarak içlemler bir ifade oluşturduğu için hem daha kompakt yazımlara olanak sağlamakta hem de daha hızlı olma - eğilimindedir. Bu nedenle eğer içlem kullanabiliyorsanız içlemi tercih etmelisiniz. - - Aşağıdaki örnekte bir listenin manuel yolla ve içlem yoluyla oluşturulması örneği verilmiştir. Her ikisinin de zamanı - ölçülmüştür. -#------------------------------------------------------------------------------------------------------------------------ - -import time - -start = time.time() - -a = [] - -for i in range(10000000): - a.append(str(i)) - -stop = time.time() - -print(stop - start) # 3.7108449935913086 - -start = time.time() - -a = [str(i) for i in range(10000000)] -stop = time.time() - -print(stop - start) # 2.890331983566284 - -#------------------------------------------------------------------------------------------------------------------------ - Küme içlemleri (set comprehension) aslında sentaktik biçim olarak tamamen liste içlemleri ile aynıdır. Yalnızca dıştaki - köşeli parantezler yerine küme parantezleri kullanılmaktadır. Örneğin: - - s = {ch for ch in 'ankara'} - - Bu işlemden artık bir liste değil bir küme elde edilecektir. Küme parantezlerinin içi tamamen liste içlemlerindeki gibidir. -#------------------------------------------------------------------------------------------------------------------------ - -s = {ch for ch in 'ankara'} -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte listenin iki elemanın toplamları bir küme olarak elde edilmiştir. Burada yinelenen toplamların - alınmadığına dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -s = {i + k for i in a for k in a} -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii küme içlemi kullanmak yerine liste içlemi oluşturup elde edilen liste kümeye de dönüştürülebilir. Ancak bu işlem - küme içlemine göre daha yavaş bir çalışmaya neden olmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -s = set([i + k for i in a for k in a]) -print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Sözlük içlemleri (dictionary comprehensions) tamamen küme içlemleri gibidir. Yani yine küme parantezleriyle oluşturulur. - Ancak içlemin ifade kısmının "anahtar:değer" biçiminde olması gerekir. Tabii anahtar ve değer birer ifade olabilir. - Örneğin: - - {key: value for x in iterable} - - Burada for döngüsünün her çalışmasında öszlüğe yeni bir anahtar-değer çifti eklenecektir. Örneğin: - - d = {x: str(x) for x in itearble} - -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -d = {x: str(x) for x in a} -print(d) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki işlemi biz manuel olarak bir for döngüsüyle de yapabilirdik. Ancak içlemler hem kompkt bir ifade oluşturmak da - hem de daha hızlı olma eğilimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] -d = {} - -for x in a: - d[x] = str(x) - -print(d) - -#------------------------------------------------------------------------------------------------------------------------ - Yine aslında biz bir liste içlemi oluşturup oradan sözlük elde edebiliriz. Ancak bu yöntem dolaylı ve yavaş bir yöntemdir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -d = dict([(x, str(x)) for x in a]) -print(d) - -#------------------------------------------------------------------------------------------------------------------------ - Bir sözlükteki anahtarları değer, değerleri anahtar yapan kod parçasını daha önce aşağıdaki gibi oluşturmuştuk: - - d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - - result = {} - - for key, value in d.items(): - result[value] = key - - print(result) - - Aslında bu işlem aşağıdaki gibi sözlük içlemiyle kompakt bir biçimde de yapılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -result = {value: key for key, value in d.items()} -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki işlem items metodu kullanılmadan da aşağıdaki gibi yapılabilirdi. -#------------------------------------------------------------------------------------------------------------------------ - -d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} - -result = {d[key]: key for key in d} -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - İçlemler bir nesne oluşturduğuna göre onları ara bir değişkende saklamadan doğrudan işleme sokabiliriz. Daha önce de - bunun çeşitli örneklerini yapmıştık. Örneğin: - - a = [1, 2, 3, 4, 5] - - for t in {x: str(x) for x in a}.items(): - print(t) - - Biz burada sözlük içlemi ile sözlük nesnesi elde ettikten sonra hemen nokta operatörü ile onun metodunu çağırabilmekteyiz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Daha önceden de çeşitli defalar belirttiğimiz gibi Python'da liste içlemleri, küme içlemleri ve sözlük içlemleri vardır. - Ancak demet içlemleri diye bir şey yoktur. Aslında sentaks olarak demet içlemi gibi bir sentaks vardır. Ancak bu sentaks - tamamen farklı bir anlama gelmektedir. Örneğin: - - (ifade for x in iterable) - - Bu sentaks Python'da geçerlidir ama "üretici ifade (generator expression)" anlamına gelmektedir. Üreticiler (generators) - ileride ayrı bir konu olarak ele alınacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -ge = (str(i) for i in range(100)) # dikkat! bu bir demet içlemi değildir! Üretici ifadedir! -for s in ge: - print(s) - -#------------------------------------------------------------------------------------------------------------------------ - zip fonksiyonu çok kullanılan "built-in" bir fonksiyondur. Parametrik yapısı şöyledir: - - def zip(*iterables): - pass - - Yani fonksiyon istenildiği kadar çok dolaşılabilir nesneyi parametre olarak alabilmektedir. Başka bir deyişle - zip fonksiyonu birden fazla argümanla kullanılabilir. Ancak argümanların hepsinin dolaşılabilir nesneler olması gerekir. - - zip fonksiyonu bizden dolaşılabilir nesneleri alır ve bize geri dönüş değeri olarak bir dolaşım nesnesi verir. - zip fonksiyonun geri döndürdüğü dolaşım nesnesini dolaştığımızda demetler elde ederiz. Öyle demetler elde ederiz ki - bu demetin elemanları bizim zip fonksiyonuna verdiğimiz dolaşılabilir nesnenin karşılıklı elemanlarıdır. Örneğin: - - a = ['ali', 'veli', 'selami'] - b = [10, 20, 30] - c = [5.2, 3.8, 4.6] - - z = zip(a, b, c) - - for t in z: - print(t) - - Burada z nesnesi her dolaşıldığında üç elemanlı aşağıdaki demetler elde edilecektir: - - ('ali', 10, 5.2) - ('veli', 20, 3.8) - ('selami', 30, 4.6) - -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 'veli', 'selami'] -b = [10, 20, 30] -c = [5.2, 3.8, 4.6] - -z = zip(a, b, c) - -for t in z: - print(t) - -#------------------------------------------------------------------------------------------------------------------------ - zip fonksiyonuna verdiğimiz dolaşılabilir nesnelerin uzunlukları aynı olmak zorunda değildir. En kısa nesne bittiğinde - dolaşım biter. Aşağıdaki örnekte en kısa nesne 3 eleman uzunlukta olduğuna göre dolaşım üç kere devam edecektir. -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] -b = [10, 20, 30] -c = [5.2, 3.8, 4.6, 5.8, 9.6] - -z = zip(a, b, c) - -for t in z: - print(t) - -#------------------------------------------------------------------------------------------------------------------------ - 39. Ders 07/09/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii biz zip fonksiyonunun bize verdiği dolaşılabilir nesneyi dolaşırken açım işlemi (unpacking) de yapabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] -b = [10, 20, 30, 40, 50] -c = [5.2, 3.8, 4.6, 5.8, 9.6, 3.4, 10.2] - -for x, y, z in zip(a, b, c): - print(x, y, z) - -#------------------------------------------------------------------------------------------------------------------------ - zip yapılmış bir nesneyi unzip yapabiliriz. Aslında bunun için de yine zip fonksiyonunun kendisi kullanılmaktadır. - Şöyle ki, zip fonksiyonu bize z isimli bir dolaşım nesnesi vermiş olsun. Şimdi biz bu dolaşım nenesini her dolaştığımızda - aslında demetler elde ederiz. O zaman biz zip(*z) gibi bir çağrı yaptığımız zaman bu demetleri sanki zip fonksiyonun - argümanları yapmış gibi oluruz. Buradan bize verilen dolaşım nesnesi dolaşıldığında eski değerler elde edilecektir. - #------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] -b = [10, 20, 30, 40, 50] -c = [5.2, 3.8, 4.6, 5.8, 9.6, 3.4, 10.2] - -z = zip(a, b, c) - -for t in zip(*z): - print(list(t)) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki unzip işleminin daha iyi anlaşılması için aşağıdaki örneği inceleyiniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [(10, 'ali'), (20, 'veli'), (30, 'selami')] - -result = zip(*a) -for t in result: - print(t) - -# yukarıdakinin eşdeğeri - -result = zip((10, 'ali'), (20, 'veli'), (30, 'selami')) -for t in result: - print(t) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii biz zip fonksiyonu ile unzip yaparak elde ettiğimiz demetleri de açabiliriz. Örneğin: - - a = [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] - - x, y = zip(*a) - - Burada biz zip(*a) işleminde iki elemanlı dolaşılabilir bir nesne elde etmiş olduk. Her türlü dolaşılabilir nesneyi - açabildiğimizi (unpacking) anımsayınız. O halde bu işlemden ayrıştrılmış iki ayrı demet elde edilecektir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] - -x, y = zip(*a) -print(x) # (10, 20, 30, 40, 50) -print(y) # ('ali', 'veli', 'selami', 'ayşe', 'fatma') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte önce liste enumerate fonksiyonuna sokulmuş sonra oradan elde edilen dolaşılabilir nesne zip fonksiyonuna - *'lı argüman olarak verilmiştir. Nasıl bir sonuç elde edildiğine dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -for t in zip(*enumerate(a)): - print(t) - -#------------------------------------------------------------------------------------------------------------------------ - n elemanlı kümenin k elemanlı farklı dizilimlerine permütasyon denilmektedir. n elemanlı kümenin k'lı alt kümelerine - ise kombinasyon denilmektedir. n elemanlı kümenin k'lı permütasyonlarının sayısı şöyle hesaplanmaktadır: - - P(n, k) = n! / (n - k)! - - Benzer biçimde n elemanlı kümenin k'lı kombinasyonlarının sayısı da şöyle hesaplanmaktadır: - - C(n, k) = n! / ((n - k)! * k!) - - Eskiden bu değerleri veren Python'da standart fonksiyonlar yoktu. Ancak Python 3.8 ile birlikte math modülüne perm ce comb - fonksiyonları eklenmiştir. Örneğin: - - >>> import math - >>> math.perm(6, 3) - 120 - >>> math.comb(6, 3) - 20 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - itertools modülündeki permutations isimli fonksiyon bizden dolaşılabilir bir nesneyi ve bir sayıyı parametre olarak alır - ve bize dolaşılabilir bir nesne verir. İşte permutations fonksiyonunun bize verdiği dolaşılabilir nesneyi dolaştığımızda - bizim birinci parametreyle verdiğimiz kümenin ikinci parametreyle belirtilen permütasyonlarını demetler halinde elde ederiz. - Örneğin: - - import itertools - - names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - - for t in itertools.permutations(names, 3): - print(t) - - Burada itertools.permutations fonksiyonun geri döndürdüğü nesne her dolaşıldığında permüstasyonlar birer demet biçiminde - elde edilecektir. -#------------------------------------------------------------------------------------------------------------------------ - -import itertools - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -for t in itertools.permutations(names, 3): - print(t) - -#------------------------------------------------------------------------------------------------------------------------ - Şimdi de permutations fonksiyonun bize verdiği dolaşılabilir nesneyi enumerate fonksiyonuna sokarak dolaşalım. -#------------------------------------------------------------------------------------------------------------------------ - -import itertools - -a = ['a', 'b', 'c', 'd', 'e'] - -result = itertools.permutations(a, 3) - -for index, t in enumerate(result): - print(index, '--->', t) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte yazdırma işlemi yan yana yapılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -import itertools - -a = ['a', 'b', 'c', 'd', 'e'] - -for t in itertools.permutations(a, 5): - print(*t, sep='') - -#------------------------------------------------------------------------------------------------------------------------ - itertools modülündeki combinations isimli fonksiyon permutations isimli fonksiyonla aynı parametrik yapıya sahiptir. - Ancak bu fonksiyon kombinasyonları bize vermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import itertools - -a = ['a', 'b', 'c', 'd', 'e'] - -for t in itertools.combinations(a, 3): - print(*t, sep='') - -#------------------------------------------------------------------------------------------------------------------------ - Bir grup oyuncu bir karşılaşmada birbirleriyle eşlendirilerek oynayacak olsunlar. Her oyuncu her oyuncuyla oynasın. - Oyuncuların players isimli bir dolaşılabilir nesnede bulunduğunu düşünelim. Aslında burada bylunmak istenen şey bu oyuncuların - ikili kombinasyonlarıdır. -#------------------------------------------------------------------------------------------------------------------------ - -import itertools - -players = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -for player1, player2 in itertools.combinations(players, 2): - print(player1, '<--->', player2) - -#------------------------------------------------------------------------------------------------------------------------ - Bilindiği gibi temel bellek birimi byte'tır. Bugünkü bilgisayar sistemlerinde adresleme byte esasına göre yapılmaktadır. - Bu nedenle programcılar çeşitli biçimlerde bir grup byte'ı saklamak durumunda kalabilirler. Bu yüzden programlamlama dillerinde - byte kavramını temsil eden türler bulundurulmuştur. İşte Python'da da byte kavramını temsil etmek için "bytes" isimli bir - tür bulundurulmuştur. - - Aslında byte kavramı 8 bitten oluşur. 8 bit de [0, 255] arasında bir syaı belirtir. Ancak bute kavramını int türüyle temsil etmek verimsizdir. - Çünkü int türü sınırsız uzunlukta tamsayıları temsil etmek içimn düşünülmüştür. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bugün kullandığımız dijital bilgisayarlarda RAM'deki ve diskteki tüm bilgiler byte yığnları biçimindedir. Örneğin çalıştırılabilen - bir program artık byte yığınlarından ibarettir. Tabii en küçük bellek birimi aslında bittir. Bit 1 ya da 0 olabilen 2'lik - sistemdeki basamaklara denilmektedir. 1 byte 8 bitten oluşmaktadır. - - Byte temel bellek birimi olarak kullanıldığından programala dillerinde byte kavramını temsil eden türler bulundurulmaktadır. - Python'da byte kavramı "bytes" isimli türle temsil edilmektedir. 1 byte sayısal olarak [0, 255] arasında bir tamsayı belirtmektedir. - Bir programdaki her şey (komutlar, yazılar, sayılar) hep byte yığınlarından oluşmaktadır. - - Bir bytes nesnesini pratik bir biçimde oluşturabilmek için tek tırnak, iki tırnak ya da üç tırnağa yapışık bir 'b' ya da - 'B' harfi kullanmak gerekir. Örneğin: - - >>> b = b'ankara' - >>> type(b) - - >>> b = B'izmir' - >>> type(b) - - - bytes türü her ne kadar byte kavramı için düşünülmüşse de Python'da sanki bir string gibi yaratılmaktadır. bytes türünden nesneleri - sanki birer string'miş gibi yaratmak kişilerin kafasını karıştırmaktadır. Biz bytes nesnesi oluştururken tırnak içerisindeki - karakterlerin ASCII tablosundaki sıra numaralarından bu nesnenin sayısal değerini oluşturmuş oluruz. Örneğin: - - b = b'a' - - Biz aslında burada 'a' karakterinin ASCII tablosundaki sıra numarası olan 97 sayısına ilişkin bir bytes nesnesi oluşturmuş oluruz. - Ancak bir bytes nesnesini bu biçimde oluştururken tırnak içerisindeki karakterlerin ASCII tablosunun ilk 128 karakterlerinden - oluşturulmuş olması gerekir. Örneğin: - - >>> b = b'ağrı' - File "", line 1 - b = b'ağrı' - ^ - SyntaxError: bytes can only contain ASCII literal characters. - - Byte kavramı [0, 255] arasında bir sayı belirttiğine göre biz de ancak ASCII tablosunun ilk 128 karakterini bu biçimde kullanabildiğimize göre - o zaman geri kalan byte değerlerini nasıl temsil ederiz? İşte tırnak içerisinde \x ve yanına iki hex digit (yani \xhh biçiminde) sentaksı - hh numaralı hex sayıya karşılık gelen byte'ı temsil etmektedir. İki hex digit [0, 255] arası tüm sayıları ifade edebildiği için bir byte'ın - her değerini ifade edebilmektedir. Örneğin: - - >>> b = b'\xFC\x17\x56' - >>> len(b) - 3 - >>> b - b'\xfc\x17V' - - Burada b nesnesi içerisinde toplam 3 byte vardır. Ancak yukarıdaki örnekten de görüldüğü gibi bytes nesnesi yazdırılırken - eğer byte değeri [0, 127] arasında ise yazdırmada doğrudan o sayının ASCII karakter karşılığı basılmaktadır. Başka bir deyişle - bytes nesnesi yazdırılırken yazdırılabilen karakterler karakter olarak diğerleri ise \xhh biçiminde yazdırılmaktadır. Örneğin: - - >>> b = b'\x168\x41\xFF' - >>> b - b'\x168A\xff' - >>> len(b) - 4 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir bytes nesnesini oluşturmanın diğer bir yolu bytes fonksiyonunu kullanmaktadır. Bu fonksiyonu biz argümansız kullanırsak - boş bir bytes nesnesi elde ederiz. Eğer bu fonksiyona dolaşılabilir bir nesneyi argüman olarak verirsek bu durumda - o dolaşılabilir nesnenin içerisindeki sayılardan bytes nesnesi oluşturulur. Tabii bu durumda bizim dolaşılabilir nesne içeriside - [0, 255] arası sayılar bulundurmamız gerekir. Örneğin: - - >>> a =[34, 56, 129, 227, 78] - >>> b = bytes(a) - >>> len(b) - 5 - >>> b - b'"8\x81\xe3N' - - Örneğin: - - >>> a = [34, 556, 129, 227, 78] - >>> b = bytes(a) - Traceback (most recent call last): - File "", line 1, in - ValueError: bytes must be in range(0, 256) - -#------------------------------------------------------------------------------------------------------------------------ - Bir string nesnesinden de bir bytes nesesi oluşturulabilir. Ancak bu durumda işin içine "encoding" kavramı girmektedir. - Bir yazı farklı karakter tablolarının farklı encoding'lerine göre farklı biçimlerde byte'lara dönüştürülebilmektedir. - Encoding konusu nispeten karmaşık bir konudur ve bu konu ileride daha ayrıntılı biçimde ele alınacaktır. UNICODE tablonun - "utf-8", "utf-16", "utf-32" gibi değişik encoding'leri vardır. Tabii bir string eğer mümkünse ASCII biçimine ya da ASCII - tablosunun çeşitli code page'lerine de dönüştürülebilir. - - Python'da bir string'ten bytes nesnesi oluşturabilmek için iki yol vardır. Birinci yol bytes fonksiyonu kullanmaktır. - Eğer bytes fonksiyonunun birinci parametresi string girilirse bytes fonksiyonu bu string'ten byte'lar oluşturarak onu - bytes nesnesine dönüştürmektedir. Bu durumda bytes fonksiyonuna encoding belirten ikinci bir argüman girilmesi gerekir. - Örneğin: - - >>> s = 'ağrı dağı' - >>> b = bytes(s, 'utf-8') - >>> b - b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1' - - Bir yazıyı byte'larına ayrıştırarak bytes nesnesi elde etmenin diğer bir yolu da str sınıfının encode metodunu kullanmaktır. - Bu metot da encoding parametresi almaktadır. Ancak bu encoding girilmezse default durumda 'utf-8' kullanılır. Örneğin: - - >>> s = 'ağrı dağı' - >>> s.encode('utf-8') - b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1' - >>> s.encode() - b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1' - - Encoding belirten parametrenin ismi "encoding" biçimindedir. Bazı programcılar okunabilirliği artırmak için parametreninismini - açıkça yazmaktadır. - Örneğin: - - >>> b = bytes('ankara', encoding='utf-8') - >>> b - b'ankara' - >>> s = 'ankara' - >>> b = s.encode(encoding='utf-8') - >>> b - b'ankara' - - Bazen yukarıdaki işlemin tersini yapmak da gerekebilir. Yani elimizde bir bytes nesnesi vardır. Bu nesnenin içerisinde belli bir - encoding'e göre bir yazıyı belirten byte'lar bulunmaktadır. Biz de bu bytes nesnesinden bir str nesnesi elde etmek isteyebiliriz. - Bunun için str fonksiyonu kullanılabilir. Fonksiyonun birinci parametresi bir bytes nesnesi alırsa ikinci parametrede encoding - belirtmelidir. Örneğin: - - >>> b = b'\x61\x62\x63' - >>> s = str(b, encoding='ascii') - >>> s - 'abc' - - Bir bytes nesnesinin içerisindeki byte'lardan bir string elde etmenin ikinci yolu da bytes sınıfının decode metodunu kullanmaktır. - Yine metot parametre olarak encoding bilgisini alacaktır. Encoding eblirtilmeyebilir. Bu durumda default "utf-8" anlaşılmaktadır. - Örneğin: - - >>> b = b'\x61\x62\x63' - >>> s = b.decode(encoding='ascii') - >>> s - 'abc' - - Özetle string'ten bytes'a dönüştürme için bytes fonksiyonu ve str sınıfının encode metodu kullanılırken, bytes'tan string'e - dönüştürme için str fonksiyonu ve bytes sınıfının decode metodu kullanılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 40. Ders 12/09/2022 Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - bytes nesnelerinin herhangi bir byte'ına yine [...] operatörü ile erişilir. Ancak bu operatör bize int bir değer vermektedir. - Örneğin: - - >>> b = b'ankara' - >>> val = b[0] - >>> type(val) - - >>> val - 97 - - bytes türü de "değiştirilemez (immutable)" bir türdür. Dolayısıyla biz bir bytes nesnesinin içerisindeki yazıyı değiştiremeyiz. - Bu bakımdan bytes nesneleri str nesnelerine çok benzemektedir. - - Örneğin: - - >>> b = b'ankara' - >>> b - b'ankara' - >>> b[0] = 'x' - Traceback (most recent call last): - File "", line 1, in - TypeError: 'bytes' object does not support item assignment - - bytes sınıfının str sınıfında olan pek çok metodu bulunmaktadır. Örneğin: split, strip, count, index, find gibi. - - bytes nesneleri tamamen str nesneleri gibi dilimlenebilmektedir. Tabii bytes nesnelerinin dilimlenmesinden bytes nesneleri - elde edilmektedir. Örneğin: - - >>> b = b'ankara' - >>> b - b'ankara' - >>> b[2:4] - b'ka' - >>> b[:-2] - b'anka' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - bytes sınıfının pek çok faydalı metodu da vardır. Bu metotların çoğu str sınıfının metotlarına benzemektedir. Ancak - bytes sınıfının metotları tıpkı str sınıfında olduğu gibi asıl nesne üzerinde değişiklik yapmaz bize değiştirilmiş yeni - bir bytes nesnesi verir. Örneğin: - - >>> b = b'ali veli selami' - >>> result = b.split() - >>> result - [b'ali', b'veli', b'selami'] - >>> result = b.upper() - >>> result - b'ALI VELI SELAMI' -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da built-in bytearray isimli bir tür de vardır. Aslında bytes türü ile bytearray türü birbirlerine çok benzemektedir. - Aralarındaki tek fark bytes türü "değiştirilemez (immutable)" olduğu halde bytearray türünün "değiştirilebilir (mutable)" - olmasıdır. Bir bytearray nesnesi ancak bytearray fonksiyonu ile yaratılabilir. String gibi başına önekler getirilerek yaratılmaz. - - bytearray fonksiyonunda biz parametre olarak dolaşılabilir bir nesne verirsek bu dolaşılabilir nesnenin elemanlarının [0, 255] arasında - int değerler olması gerekir. Bu durumda bu değerlerden bytearray nesnesi yaratılır. Örneğin: - - >>> ba = bytearray([1, 2, 3, 4, 5]) - >>> ba - bytearray(b'\x01\x02\x03\x04\x05') - - bytearray nesnesi benzer biçimde bytes nesnesinden de yaratılabilir. Örneğin: - - >>> ba = bytearray(b'ankara') - >>> ba - bytearray(b'ankara') - - bytearray nesnesi string nesnesinden de yaratılabilir. Bu durumda encoding belirtmek gerekir. Örneğin: - - >>> ba = bytearray('ankara', encoding='utf-16') - >>> ba - bytearray(b'\xff\xfea\x00n\x00k\x00a\x00r\x00a\x00') -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - bytearray türü değiştirilebilir olduğu için biz bir bytearray nesnesinin içerisindeki byte'ları değiştirebiliriz. Zaten bytearray - türünün bytes türünden tek farkı budur. Örneğin: - - >>> b = b'\x00\x01\x02' - >>> len(b) - 3 - >>> b - b'\x00\x01\x02' - >>> b[0] = 10 - Traceback (most recent call last): - File "", line 1, in - TypeError: 'bytes' object does not support item assignment - >>> ba = bytearray(b'\x00\x01\x02') - >>> ba - bytearray(b'\x00\x01\x02') - >>> len(ba) - 3 - >>> ba[0] = 10 - >>> ba - bytearray(b'\n\x01\x02') - - bytearray türü üzerinde de dilimler yapılabilmektedir. - - bytearray türü de bytes türü gibi str sınıfında olan pek çok metodu içermektedir: Örneğin count, index, strip, split gibi. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - bytes türü değiştirilemez olduğu için yorumlayıcının bazı optimizasyonlarına katılabilmektedir. Aynı zamanda bytes türü - yine değiştirilemez olduğu için toplamda bellekte daha az yer kaplamaktadır. Bu nedenle eğer byte dizisinin elemanlarını - değiştirmeyecekseniz bytes türünü, değiştirecekseniz bytearray türünü tercih etmelisiniz. Yine değiştirilemez türlerin - hash'lenebilir olduğunu ama değiştirilebilir türlerin hash'lenebilir olmadığını anımsayınız. Bu nedenle örneğin bytes - türü bir sözlüğe anahtar yapılabilir ancak bytearray türü yapılamaz. Örneğin: - - >>> d = {b'ali': 100} - >>> d - {b'ali': 100} - >>> d = {bytearray(b'ali'): 100} - Traceback (most recent call last): - File "", line 1, in - TypeError: unhashable type: 'bytearray' - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - bytearray sınıfının da upper gibi lower gibi strip gibi bytes ve str sınıflarında bulunan metotları vardır. Bu metotların - bazıları in-place işlem yapmazlar tıpkı str ve bytes sınıflarına olduğu gibi yeni bir nesne verirler. Ancak bytearray - sınıfının append, remove, pop gibi metotları in-place işlem yapmaktadır. Ancak append gibi, remove gibi metotlar bytearray - elemanını bizden int bir değer gibi istemektedir. Örneğin: - - >>> ba = bytearray(b'ankara') - >>> ba - bytearray(b'ankara') - >>> ba.append(97) - >>> ba - bytearray(b'ankaraa') - >>> result = ba.pop() - >>> result - 97 - >>> ba - bytearray(b'ankara') - >>> ba.remove(ord('k')) - >>> ba - bytearray(b'anara') - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da Nesne Yönelimli Programalama Modeline İlişkin Özellikler -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz şimdiye kadar hep fonksiyonları kullanarak kodlar oluşturduk. Bir programın fonksiyonlar yoluyla oluşturulmasına - "prosedürel programlama tekniği" denilmektedir. Sınıflar (classes) "Nesne Yönelimli Programlama Tekniğini (NYPT)" - uygulamak için gereken yapı taşlarıdır. NYPT'in tek cüöleyle tanımını yapmak oldukça zordur. Ancak NYPT'yi "sınıflar - kullanarak program yazma" tekniği biçiminde tanımlayabiliriz. Sınıflar (classes) NYPT'nin yapı taşlarıdır. Bu nedenle bu - tekniği öğrenebilmek için öncelikle sınıf kavramını, sınıfların oluşturulması ve kullanımını öğrenmek gerekir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıf tanımlamanın (sınıf oluşturmanın) genel biçimi şöyledir: - - class : - - Örneğin: - - class Sample: - pass - - Sınıf isimleri Python programcıları tarafından genellikle "Pascal harflendirmesiyle (Pascal casting)" yazılmaktadır. - Yani isim tek bir sözcükten oluşuyorsa sözcüğün ilk harfi büyük olur. Birden fazla sözcükten oluşuyorsa her sözcüğün - ilk harfi büyük olur. Biz de kurusumuzda sınıf isimlerini genel olarak Pascal casting biçiminde oluşturacağız. Python - standart kütüphanesinde built-in sınıflar (list gibi, tuple gibi, dict gibi) küçük harflerle genellikle C tarzı harflendirmeyle - oluşturulmuştur. Ancak standart kütüphanenin çeşitli modüllerinde C tarzı harflendirmeyle Pascal tarzı harflendirmeye - rastlanmaktadır. Yukarıda da belirtitğimiz gibi biz kursumuzda kendi oluşturacağımız sınıfları Pascal tarzı harrflendirmeyle - ifade edeceğiz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da şimdiye kadar gördüğümüz int, float, bool gibi temel türlerin hepsi birer sınıf kabul edilmektedir. Benzer biçimde - list, tuple, dict gibi türler de birer sınıftır. Kısacası Python'da her tür aslında bir sınıf belirtmektedir. Python - referans dokğmanlarında hiç import etmeden kullanabildiğimiz int, float, str, list, set, dict gibi sınıflara "built-in türler - (built-in types)" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıflar aynı zamanda birer tür de belirtmektedir. Oluşturduğumuz bir sınıf türünden nesneler yaratabiliriz. Bir sınıf türünden - nesne yaratmanın genel biçimi şöyledir: - - sınıf_ismi([argüman listesi]) - - Örneğin: - - s = Sample() - - Burada s artık Sample türünden bir nesnenin adresini tutmaktadır. - - Bir sınıf türünden nesnenin yaratılmasının adeta bir fonksiyon çağrısına benzediğine dikkat ediniz. Aslında list, tuple, str - gibi sınıflar türünden de nesneleri biz aynı sentaksla yaratabiliyorduk. Örneğin: - - s = str() - t = tuple() - a = list() -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - pass - -s = Sample() -print(type(s)) # - -#------------------------------------------------------------------------------------------------------------------------ - NYPT'de bir sınıf türünden nesneye o sınıf türünden bir "örnek (instance)" da denilmektedir. Yani "örnek (instance)" - sözcüğü sınıf türünden nesneler için kullanılmaktadır. Sınıflar elemanlara sahip olabilirler. Sınıf nesneleri (yani örnekler) - bileşik nesnelerdir. Yani parçalara sahiptir. Python'da bir sınıf nesnesinin elemanlarına yani parçalarına "öznitelik (attribute)" - denilmektedir. (Halbuki sınıf nesnelerinin parçalarına C++'ta "veri elemanı (data member)", Java ve C#'ta "alan (field)" - denilmektedir.) Eğer öznitelik bir nesnenin parçası durumunda ise buna "örnek özniteliği (instance attribute)" de denilmektedir. - Özetle sınıf nesneleri parçalardan oluşur. Nesnenin bu parçalarına "örnek özbiteliği (instance attribute)" denilmektedir -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir nesne yaratıldığında henüz nesnenin içi boştur. Örneğin: - - class Sample: - pass - - s = Sample() - - Burada s değişkeninin gösterdiği yerdeki nesnenin içi henüz boştur. Yani s'in henüz bir özniteliği yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf türünden birden fazla nesne (yani örnek) yaratabiliriz. Her yarattığımız nesne aslında farklı bir nesnedir. Örneğin: - - class Sample: - pass - - s = Sample() - k = Sample() - - Burada s değişkeni başka bir nesneyi k değişkeni de başka bir nesneyi göstermektedir. Örneğin: - - >>> s = Sample() - >>> k = Sample() - >>> id(s) - 3104147128816 - >>> id(k) - 3104147434752 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf nesnesi yaratıldığında içi boştur demiştik. İşte bir nesnenin "öznitelikleri (attributes)" aşağıdaki sentaksla - yaratılmaktadır: - - .<öznitel_ismi> = değer - - Örneğin: - - s = Sample() - s.a = 10 - s.b = 20 - s.c = 'ankara' - - Burada biz s nesnnesinin içerisinde üç tane öznitelik (örnek özniteliği) oluşturduk. Yani artık s'nin üç parçası bulunmaktadır. - Tabii Python'da her zaman değişkenler adres tutarlar. s nesnesinin içerisindeki a, b, ve c de aslında sırasıyla int, int ve str nesnelerinin - adreslerini tutmaktadır. Başka bir deyişle s nesnesinin a, b ve c parçaları içerisinde adresler vardır. Bu adreslerde sırasıyla - int bir nesne, int bir nesne ve str türünden bir nesne bulunmaktadır. - - s ----> Sample nesnesi - a ----> int nesne - b ----> int nesne - c ----> str nesnesi - - Python terminolojisine göre s Sample sınıfı türünden bir değişkendir. Bu s değişkeni bir Sample nesnesinin adresini - tutmaktadır. Bu nesne üç parçadan oluşmaktadır. Nesnenin parçalarına "örnek öznitelikleri (instance attribute)" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da "öznitelik (attribute)" sınıfa ilişkin ya da nesneye ilişkin olabilmektedir. Eğer öznitelik sınıfa ilişkinse - buna "sınıf özniteliği (class attribute)" denilmektedir. Eğer öznitelik nesneye ilişkinse buna da "örnek özniteliği (instance - attribute) denilmektedir. Biz "nesnenin özniteliği" dediğimizde "örnek özniteliği", "sınıfın özniteliği" dediğimizde - "sınıf özniteliği" anlaşılmalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - C++, Java ve C# gibi dillerde bir sınıf türünden farklı nesnelerin elemanları hep aynı isimli ve aynı türden olur. Ancak - Python'da böyle olmak zorunda değildir. Örneğin Sample türünden iki sınıf nesnesi olsun. Bunların elemanları yani öznitelikleri - farklı olabilir: - - class Sample: - pass - - s = Sample() - k = Sample() - - s.a = 10 - s.b = 20 - - k.x = 'ali' - k.y = 12.3 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf nesnesi için öznitelikler yaratıldıktan sonra bu öznitelikler istenildiği zaman .<öznitelik_ismi> - sentaksıyla kullanılabilmektedir. Nesnenin özniteliklerini "nesnenin içerisindeki değişkenler" gibi düşünebilirsiniz. - Yukarıda belirttiğimiz gibi nesnenin öznitelikleri tüm değişkenler gibi aslında birer adres turmaktadır. Asıl nesneler - o adreslerde bulunmaktadır. Örneğin: - - >>> class Sample: - ... pass - ... - >>> s = Sample() - >>> s.a = 10 - >>> s.b = 'ankara' - >>> s.a - 10 - >>> s.b - 'ankara' - >>> id(s) - 1565042138544 - >>> id(s.a) - 1565005316624 - >>> id(s.b) - 1565042285808 - -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - pass - -s = Sample() - -s.a = 10 -s.b = 'ankara' - -print(s.a) # 10 -print(s.b) # ankara - -#------------------------------------------------------------------------------------------------------------------------ - Kursumuzun ilk bölümlerinde de belirttiğimiz gibi Python'da sınıfların içerisinde fonksiyonlar olabilir. Sınıfların - içerisindeki fonksiyonlara "metot (method)" denilmektedir. Eğer bir fonksiyon sınıfların dışındaysa ona "fonksiyon (function)", - bir sınıfın içerisindeyse ona "metot (method)" denilmektedir. - - Python'da metotların en azından bir parametresi olur. Metotların ilk parametreleri özel bir anlama sahiptir. Programcılar genel olarak - metotların bu ilk parametrelerini "self" biçiminde isimlendirirler. Ancak bu ilk parametrenin "self" biçiminde isimlendirilmesi - zorunlu değildir. Örneğin: - - class Sample: - def foo(self): - pass - - def bar(self, a): - pass - - def tar(self, a, b): - pass - - def zar(): - pass - - Burada foo, bar ve tar Sample sınıfının metotlarıdır. zar ise sınıf içerisinde olmadığı için bir metot değil fonksiyondur. - - Bir metodun çağrılması için o metodun içinde bulunduğu sınıf türünden bir değişkenin olması gerekir. Metot çağırma işleminin genel - biçimi şöyledir: - - .([argüman listesi]) - - Yani biz önce ilgili sınıf türünden bir nesne yaratırız. Nesnenin adresi sınıf türünden değişkende tutulur. Sonra o değişkenle "nokta" - operatörünü kullanarak sınıfın metotlarını çağırırız. Örneğin: - - s = Sample() - - s.foo() - s.bar(10) - s.tar(10, 20) - - Metot bu biçimde çağrılırken birinci self parametresi için argüman girilmez. Metoda girilen argümanlar self parametresinden - sonraki parametrelere ilişkindir. Örneğin: - - s = Sample() - - self.foo() - - Burada foo metodunun self parametresinden başka parametresi olmadığı için bir argüman girilmemiştir. Örneğin: - - self.bar(10) - - Burada 10 değeri bar metodunun a parametre değişkeni için girilmiştir. Örneğin: - - s.tar(10, 20) - - Burada 10 değeri a parametresi için, 20 değeri ise b parametresi için girilmiştir. - - Yukarıda da belirttiğimiz gibi aslında metotların birinci parametrelerinin isminin self olması zorunlu değildir. - Python yorumlayıcısı self ismine değil birinci parametreye izleyen paragraflarda açıklanacak olan işlevi yüklemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - print('foo') - - def bar(self, a): - print(f'bar: {a}') - - def tar(self, a, b): - print(f'tar: {a}, {b}') - -def zar(): - print('zar') - -s = Sample() - -s.foo() -s.bar(10) -s.tar(10, 20) - -zar() - -#------------------------------------------------------------------------------------------------------------------------ - Aslında biz şimdiye kadar zaten metot çağrımının nasıl yapıldığını görmüştük. Örneğin list sınıfının append metodunu - list sınıfı türünden bir değişkenler çağıyorduk: - - a = [1, 2, 3, 4, 5] - a.append(100) - - Ya da örneğin dict sınıfının pop metodunu yine dict sınıfı türünden bir değişkenle çağırıyorduk: - - d = {'ali': 10, 'veli': 20, 'selami': 30} - - d.pop('veli') -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi Python'da her atama aslında bir adres atamasıdır. O halde bir sınıf türünden değişkeni başka bir değişkene - atarsak o değişken de asıl değişkenle aynı nesneyi gösteriyor olur. Örneğin: - - class Sample: - pass - - s = Sample() - s.a = 10 - s.b = 20 - - k = s - - print(k.a) # 10 - print(k.b) # 20 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Metotların en azından bir parametresi olmak zorunda olduğunu belirtmiştik. Bu parametrenin de genellikle "self" - biçiminde isimlendirildiğini söylemiştik. Pekiyi bu self parametresinin anlamı nedir? İşte metotlar ilgili sınıf türünden - değişkenlerle çağrılırlar. Her zaman self parametresine metodun çağrılmasında kullanılan değişken (yani onun içindeki adres) - atanmaktadır. Başka bir deyişle self metodun çağrılmasında kullanılan nesneyi gösteren bir değişkendir. Örneğin: - - class Sample: - def foo(self, a, b): - pass - - s = Sample() - s.foo(10, 20) - - Burada s değişkeni (yani içindeki adres) self parametresine, 10 (yani 10 nesnesinin adresi) değeri a parametre değişkenine - ve 20 değeri de (yani 20 nesnesinin adresi) b parametre değişkenine atanmaktadır. Burada self tamamen s ile aynı nesneyi - gösterir duruma gelmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi bir sınıf türünden değişken başka bir değişkene atanabilir. Bu durumda iki değişken - de aynı nesneyi gösterir. Nesneye hangi değişkenle erişildiğinin bir önemi yoktur. Örneğin: - - s = Sample() - - s.a = 10 - k = s - - Python'da bütün atamalar adres ataması olduğuna göre k = s işleminde s'in içerisindeki adres k'ya atanmıştır. Bu durumda - s ve k aynı nesneyi göstermektedir. O halde s.a ile k.a arasında hiçbir farklılık yoktur. Çünkü s.a "s değişkenin - içerisindeki adresteki nesnenin a özniteliği" anlamına gelmektedir. k.a da "k değişkenin içerisindeki adresteki, nesnenin a - özniteliği" anlamına gelir. O halde iki ifade arasında hiçbir farklılık yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - pass - -s = Sample() -s.a = 10 - -k = s - -print(s.a) -print(k.a) - -print(id(s)) # 1781626597584 -print(id(k)) # 1781626597584 - -print(id(s.a)) # 1781443422800 -print(id(k.a)) # 1781443422800 - -#------------------------------------------------------------------------------------------------------------------------ - NYPT'te aslında bir sınıf ortak data'lar üzerinde işlem yapan fonksiyonların oluşturduğu bir veri yapısıdır. Sınıfın - metotları aynı nesne üzerinde işlem yapmaktadır. Örneğin: - - s = Sample() - ... - - s.foo() - s.bar() - s.tar() - - Burada foo, bar ve tar metotlarının self parametrelerine aynı s nesnesi geçirilmiştir. O halde bu metotlar aslında s - üzerinde işlem yapma potansiyeline sahiptir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi bir metot çağrılırken her zaman metodun çağrılmasında kullanılan değişken (yani onun - içerisindeki adres) metodun birinci parametresi olan self parametresine atanmaktadır. Bu durumda self parametresi - metodun çağrılmasında kullanılan değişken ile aynı nesneyi gösterir durumda olur. Metotlar ilgili sınıf türünden bir - değişkenle çağrıldığına göre o değişkenin atanacağı metotta bir self parametresinin bulunması gerekmektedir. Aşağıdaki - örnekte self parametresi ile s aynı nesneyi gösteriyor durumdadır. Dolayısıyla nesnenin a özniteliğine self parametresiyle de - erişilebilmiştir: - - class Sample: - def foo(self): - print(self.a) # 10 - - s = Sample() - s.a = 10 - - s.foo() - - Burada s ile self aslında aynı nesneyi göstermektedir. Dolayısıyla biz self.a ifadesi ile nesnenin a örnek özniteliğine - erişebilmekteyiz. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - print(self.a) # 10 - -s = Sample() -s.a = 10 - -s.foo() - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki, işlemin tersini de yapabiliriz. Yani metodun içerisinde nesenin öznitelikleri yaratılabilir. Yaratılan bu öznitelikler - daha sonra kullanılabilir: - - class Sample: - def foo(self): - self.a = 10 - self.b = 20 - - s = Sample() - s.foo() - - print(s.a, s.b) # 10 20 - - Burada foo metodu içerisinde self.a = 10 ve self.b = 20 ile self değişkeninin gösterdiği yerdeki nesnenin a ve b öznitelikleri - oluşturulmuştur. self ile s aynı nesneyi gösterdiğine göre biz bu a ve b örnek özniteliklerine s yoluyla da erişebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - self.a = 10 - self.b = 20 - -s = Sample() -s.foo() - -print(s.a, s.b) # 10 20 - -#------------------------------------------------------------------------------------------------------------------------ - 41. Ders 14/09/2022 Carsamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Modüller konusunda de belirtmiştik, Python'da başında ve sonunda iki alt tire olan özel bazı isimler kullanılmaktadır. - Örneğin __init__, __add__ gibi. Bu isimleri kolay ifade edebilmek için __xxx__ için "dunderscore xxx" ya da "dunder xxx" - ifadeleri kullanılmaktadır. Yani örneğin biz "dunder init" demekle "__init__" ismini, "dunder add" demekle __add__ ismini - kastediyor olacağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da sınıfların __init__ isimli özel bir metotları vardır. Bu metotlara "initializer" ya da bazen "constructor" da - denilmektedir. __init__ metodu bir sınıf türünden nesne yaratıldığında otomatik olarak çağrılan bir metottur. Örneğin: - - class Sample: - def __init__(self): - pass - - s = Sample() - - Burada Sample nesnesi Sample() ifadesi ile yaratılmak istendiğinde Python yorumlayıcısı önce nesneyi yaratır. Sonra yaratılan - nesnenin adresini self parametresine geçirerek __init__ metodunu çağırır, ondan sonra s değişkenine atama yapılır. Yani önce __init__ - metodu çağrılıp sonra nesnenin adresi s değişkenine atanmaktadır. - - Aşağıdaki örnekte Sample sınıfı türünden her nesne yaratıldığında yorumlayıcı tarafından sınıfın __init__ metodu otomatik - bir biçimde çağrılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self): - print('__init__ called') - -s = Sample() -print('Ok') -k = Sample() - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda de belirtitğimiz gibi bir sınıf türünden nesne yaratıldığında önce nesne yaratılır. Sonra sınıfın __init__ - metodu çağrılır. Ondan sonra nesnenin adresi değişkene atanır. Örneğin: - - s = Sample() - - Burada __init__ çağrıldıktan sonra s'e atama yapılacaktır. __init__ metodunun self parametresi henüz (yani yeni) yaratılmış - olan nesneyi belirtmektedir. İşte programcı tipik olarak oluşturduğu nesnenin nesnenin özniteliklerini __init__ metodu - içerisinde yaratmaktadır. Böylece yaratılan her nesne aynı elemanlara yani özniteliklere sahip olur. Örneğin: - - class Sample: - def __init__(self): - self.a = 10 - self.b = 20 - - s = Sample() - k = Sample() - - print(s.a, s.b) - print(k.a, k.b) - - Burada s ve k değişkenlerinin gösteridği yereki nesnelerin öznitelikleri __init__ tarafından yaratılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self): - self.a = 10 - self.b = 20 - -s = Sample() -k = Sample() - -print(s.a, s.b) -print(k.a, k.b) - -#------------------------------------------------------------------------------------------------------------------------ - C++, Java ve C# gibi diğer nesne yönelimli programlama dillerinde de bir nesne yaratıldığında sınıfın bir metodu otomatik - olarak çağrılmaktadır. Bu dillerde bu otomatik çağrılan metoda "constructor (yapıcı fonksiyon ya da metot)" denilmektedir. - Python'daki __init__ metodu bu dilelrdeki "constructor" metotlarına benzemektedir. Ancak Python'da __init__ metoduna "constructor" - değil "initializer" denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - __init__ metodunun self dışında başka parametreleri de olabilir. Bu durumda nesne yaraılırken bu parametreler için de - argüman girilmelidir. Örneğin: - - class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - Burada nesneyi yaratırken parantezler içerisinde a ve b için iki argüman girmeliyiz. Örneğin: - - s = Sample(10, 20) - - Burada Sample(10, 10) ifadesi ile yeni yaratılan nesnenin adresi self parametresine, 10 nesnesinin adresi a parametresine, - 20 nesnesinin adresi de b parametresine aktarılır. Bu parametreler de nesnenin özniteliklerine atanmıştır. - - Python programcıları genellikle (ama her zaman değil) diğer metotlarda da kullanabilmek için __nit__ metoduna geçirilen - parametreler için nesnede örnek öznitelikleri yaratarak o örnek öznitelikrlerine yerleştirmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - -s = Sample(10, 20) -print(s.a, s.b) # 10 20 - -k = Sample(30, 40) -print(k.a, k.b) # 30 40 - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte sınıfın __init__ metodunda a ve b isimli iki örnek özniteliği yaratılmıştır. disp metodunda da bunlar - ekrana yazdırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - def disp(self): - print(self.a, self.b) - -s = Sample(10, 20) -k = Sample(30, 40) - -s.disp() # 10 20 -k.disp() # 30 40 - -#------------------------------------------------------------------------------------------------------------------------ - Tabii biz nesnenin örnek özniteliklerini başka bir metotta da yaratabiliriz. __init__ metodunun diğer metotlardan tek - farkı yorumlayıcı tarafından oromatik çağrılmasıdır. Aşağıdaki örnekte nesnenin a ve öznitelikleri __init__ metodunda değil - set metodunda yaratılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def set(self, a, b): - self.a = a - self.b = b - - def disp(self): - print(self.a, self.b) - -s = Sample() -k = Sample() - -s.set(10, 20) -k.set(30, 40) - -s.disp() -k.disp() - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi bir sınıf nedir? Aslında sınıf örnek özniteliklerinden ve metotlardan oluşan bir veri yapısıdır. Sınıflar belli - bir konuda işlemleri yapan metotlara sahiptirler. Bu metotlar da nesnenin özniteliklerini ortak bir biçimde kullanmaktadır. - NYPT'de bir konuda bir işi yapabilecek sınıflar yazılır ve program sınıflar yoluyla oluşturulur. - - Aşağıdaki örnekte Date sınıfı bir tairh bilgisini tutup onu ekrana yazdıran metoda sahiptir. -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def disp(self): - print(f'{self.day:05d}/{self.month:02d}/{self.year:02d}') - -d = Date(19, 9, 2023) # 19/06/2023 -d.disp() - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte Complex sınıfı bir karmaşık sayıyı temsil etmektedir. Complex nesnesinin tutacağı karmaşık sayının - gerçek ve sanal kısımları __init__ metodunda sınıfın örnek özniteliklerinde saklanmış disp metodunda bunlar ekrana - yazdırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real, imag): - self.real = real - self.imag = imag - - def disp(self): - print(f'{self.real}+{self.imag}i') - -z = Complex(3, 2) -z.disp() - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte de bir nokta Point isimli bir sınıfla temsil edilmiştir. Sınıfın __init__ metodunda noktanın x ve y - bileşenleri nesnenin x ve y özniteliklerine atanmıştır. disp metodu da nesne içerisindeki bu x ve y özniteliklerini - yazdırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Point: - def __init__(self, x, y): - self.x = x - self.y = y - - def disp(self): - print(f'({self.x},{self.y})') - -pt = Point(3, 2) -pt.disp() - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf bi konuyla ilgili belli işlemleri yapmak amacıyla yazılır. Tipik olarak biz sınıfbir nesnesini yaratırken - __init__ metoduna argümanlar geçeriz. __init__ metodu bunları nesnenin özniteliklerinde saklar. Diğer metotlar da - self yoluyla bunlara erişerek faydalı işlemler yaparlar. Yukarıda verdiğimiz Date, Complex, Point gibi sınıflarda - biz yalnızca birkaç bilgiyi nesnenin özniteliklerinde tutup disp metodunda onları ekrana yazdırdık. Oysa örneğin - Date gibi bir sınıfın tarihler üzerinde faydalı işlemler yapan pek çok metodu olabilir. Örneğin Date sınıfı bir tarihten - belli gün sonranın ya da öncenin bulunması gibi, iki tarih arasındaki gün farkının bulunması gibi, iki tarihin - karşılaştırılması gibi pek çok faydalı işlemi yapan metotlara sahip olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'ın standart kütüphanesindeki modüller içerisinde hem fonksiyonlar hem de sınıflar vardır. Örneğin datetime modülünün - içerisindeki date sınıfı tarih bilgisini temsil etmektedir. Biz bir date nesnesini yıl, ay, gün değerlerini vererek yaratırız. - Sonra da bu tarih bilgisi üzerinde çeşitli işlemler yaparız. Örneğin sınıfın weekday isimli metodu bizim verdiğimiz - tarihin hangi gün olduğunu bize bir sayı olarak verir (0 = Pazartesi, 1 = Salı, 2 = Çarşamba, ...). - - Biz date nesnesinin içerisindeki tarih bileşenlerini nesnenin day, month ve year özniteliklerinden istersek geri alabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -import datetime - -d = datetime.date(2022, 9, 14) - -print(d) -print(d.day, d.month, d.year) - -days = ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'] -print(days[d.weekday()]) - -#------------------------------------------------------------------------------------------------------------------------ - Şimdi karmaşık sayıyı temsil eden Complex sınıfına add ve sub metotlarını ekleyelim. -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real, imag): - self.real = real - self.imag = imag - - def disp(self): - print(f'{self.real}+{self.imag}i') - - def add(self, z): - real = self.real + z.real - imag = self.imag + z.imag - - result = Complex(real, imag) - - return result - - def sub(self, z): - real = self.real - z.real - imag = self.imag - z.imag - - result = Complex(real, imag) - - return result - -x = Complex(6, 5) -y = Complex(2, 3) - -z = x.add(y) -z.disp() - -z = x.sub(y) -z.disp() - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi aslında daha önce görmüş olduğumuz list, tuple, set, dict birer sınıftır. Zaten biz daha - önce bu sınıflar türünden nesneler yaratıp onların metotlarını çağırarak faydalı işlemler yapmıştık. Örneğin sort metodu - list sınıfının içerisindedir. Biz de bir listeyi sıraya dizmek için sort metodunu list türünden bir değişkenler çağırırız. - Örneğin: - - a = [19, 12, 8, 40, 43] - a.sort() - print(a) - -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -print(a) - -a.pop(2) -print(a) - -a.append(100) -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi bir metodun ilgili sınıf türünden bir değişkenle çağrılmasının anlamı nedir? İşte örneğin x.foo() gibi bir çağrı - foo metodunun x değişkeninin gösterdiği yerdeki nesne üzerinde (kısaca buna x nesnesi üzerinde diyebiliriz) işlem yapacağı - anlamına gelir. Yani metotlar spesifik bir nesne üzerinde işlem yaparlar. Bu nedenle bir nesne ile çağrılırlar. Yani biz - bir metodu hangi nesneyle çağırırsak o metot aslında o nesnenin elemanları yani öznitelikleri üzerinde işlem yapar. Örneğin: - - a.append(10) - - Burada append metodu a listesine eleman eklemektedir. Fakat örneğin: - - b.append(10) - - Burada append metodu b listesine eleman eklemektedir. Örneğin: - - val = d.get(10) - - Burada get metodu d isimli sözlük nesnesinin içerisindeki 10 anahtarına karşı gelen değeri bize vermektedir. Halbuki - örneğin: - - val = k.get(10) - - Burada get metodu k sözlüğünün içerisindeki 10 anahtarına karşı gelen değeri vermektedir. - - O halde metotlar "bir kavrama ilişkin bir nesne (örnek) üzerinde belli işlemleri yapan fonksiyonlardır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Daha önceden de fonksiyonlar konusunda belirttiğimiz gibi Python'da diğer bazı nesne yönelimli dillerde bulunan "function - overloading" ya da "method overloading" denilen özellik yoktur. Yani Python'da aynı isimli tek bir fonksiyon ya da metot - bulunabilir. Fakat istersek metotlarımızın parametre değişkenlerine default değerler verebiliriz. Bu sayede diğer dillerdeki - "method overloading" benzeri bir durumu oluşturmuş oluruz. Örneğin C++, Java ve C# gibi dillerde sınıfın "yapıcı fonksiyonu - (constructor)" overload edilebilmektedir. Böylece nesne farklı biçimlerde yaratılabilmektedir. Halbuki Python'da sınıf içerisinde - yalnızca bir tane __inir__ metodu bulunabilir. Farklı parametrelerle nesnenin yaratılması default argümanlarla yapılabilmektedir. - Örneğin: - - class Complex: - def __init__(self, real = 0, imag = 0): - self.real = real - self.imag = imag - - Burada biz Complex nesnesini aşağıdaki gibi farklı parametrelerle yaratabiliriz: - - x = Comple() - y = Complex(10) - z = Complex(10, 20) - -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real = 0, imag = 0): - self.real = real - self.imag = imag - - def disp(self): - print(f'{self.real}', end='') - if self.imag != 0: - print(f' + {self.imag}i') - else: - print() - -x = Complex(6, 5) -y = Complex(6) -z = Complex() - -x.disp() # 6+5i -y.disp() # 6 -z.disp() # 0 - -#------------------------------------------------------------------------------------------------------------------------ - Sınıflar "değiştirilebilir (mutable)" türlerdir. Yani bir sınıfın örnek özniteliklerini biz değiştirebiliriz. Örneğin: - - class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - s = Sample(10, 20) - - Burada s değişkeni Sample nesnesinin adresini tutmaktadır. Tabii nesnenin a ve b öznitelikleri de aslında adres tutar. - Yani nesne yaratıldığında bellekte şöyle bir durum oluşacakatır: - - s -----> Sample nesnesi - a ---> 10 - b ---> 20 - - Sınıf nesnelerinin değiştirilebilir olması demek nesnenin özniteliklerine başka nesnelerin adreslerinin atanabilmesi demektir. - Örneğin: - - s = Sample(10, 20) - s.a = 30 - - Burada artık nesnenin a özniteliği 30 değerinin adresini göstermektedir: - - s -----> Sample nesnesi - a ---> 30 - b ---> 20 - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların metotları alternatif biçimde sınıf ismi ile de çağrılabilmektedir. Ancak bu çağrı biçiminde self parametresini - açıkça argüman olarak geçirmek gerekir. Örneğin: - - class Sample: - def foo(self): - print('foo') - - def bar(self, a): - print(f'bar: {a}') - - s = Sample() - - s.foo() - s.bar(10) - - Sample.foo(s) - Sample.bar(s, 10) - - Burada örneğin s.foo() çağrısı ile Sample.foo(s) çağrısı tamamen eşdeğerdir. Ancak birinci çağrı yani s.foo() çağrısı - tercih edilmelidir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - print('foo') - - def bar(self, a): - print(f'bar: {a}') - -s = Sample() - -s.foo() -s.bar(10) - -Sample.foo(s) -Sample.bar(s, 10) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii alternatif sentaksı biz diğer built-inm sınıflarda da kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] -print(a) - -list.append(a, 10) # a.append(10) -list.append(a, 20) # a.append(20 - -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Aslında sınıflar da Python'da deyim statüsündedir. Yani yorumlayıcı bir sınıf tanımlaması ile karşılaştığında onun içerisindeki - deyimleri de çalıştırmaktadır. Örneğin: - - class Sample: - print('one') - - def foo(self): - pass - - print('two') - - def bar(self): - pass - - print('three') - - Biz bu sınıf türünden hiçbir nesne yaratmasak bile yorumlayıcı yine de sınıfın içerisndeki kodları çalıştırmaktadır. - Biz bu programı çalıştırdığımızda aşağıdaki yazıları ekranda görececeğiz: - - one - two - three - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz "bir nesnenenin özniteliği" demekle nesnenin elemanlarını kastetmekteyiz. "Nesnenin özniteliği" ile "sınıfın örnek - özniteliği" aynı anlamdadır. Nesnenin özniteliklerine İngilizce "instance attributes" denilmektedir. Ancak Pyton'da - bir de "sınıf öznitelikleri (class attribute)" denilen bir kavram vardır. Biz "sınıf özniteliği" yerine bazen viz - "sınıf değişkenleri" de diyeceğiz. Pekiyi dınıf öznitelikleri nasıl yaratılmaktadır ve nasıl kullanılmaktadır? - - Anımsanacağı gibi bir değişken tüm fonksiyonların dışında yaratılmışsa ona "global değişken" diyorduk. Bir fonksiyonun - ya da metodun içerisinde yaratılmışsa ona "yerel değişken" diyorduk. Fonksiyonların parametre parantezleri içerisindeki - değişkenlere de "parametreler" ya da "parametre değişkenleri" diyorduk. "İşte bir değişken bir sınıfın içerisinde de - yaratılabilir. Örneğin: - - x = 10 # x global bir değişken - - def foo(): - y = 20 # y yerel bir değişken - - class Sample: - z = 30 # z sınıf değişkeni (sınıfın özniteliği) - - def bar(self): - k = 40 # k yerel değişken - - s = Sample() - s.a = 10 # a örnek özniteliği - - Yukarıdaki örnekte z değişkeninin sınıf içerisinde yaratıldığını görüyorsunuz. Bu değişken sınıfın bir özniteliğidir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıfın öznitelikleri sınıfın içerisinde doğrudan ismiyle kullanılabilir. Ancak sınıfın dışından ve sınıfın metotları - içerisinden ancak sınıf ismi ve nokta operatörü ile niteliklendirilerek kullanılabilmektedir. Örneğin: - - class Sample: - x = 10 - - def foo(): - print(Sample.x) # metot içerisinden x'i doğrudan kullanamayız, sınıf ismiyle kullanmalıyız - - print(x) # doğrudan kullanılabilir - - print(Sample.x) # dışarıdan x'i doğrudan kullanamayız sınıf ismiyle kullanmalıyız - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi fonksiyon tanımlama işlemi de aslında Python'da bir deyim statüsündeydi. Yorumlayıcı bir fonksiyonun - tanımlandığını gördüğünde bir fonksiyon nesnesi yaratıyordu, fonksiyonun iç kodlarını o nesnenin içerisine yerleştiriyordu. - Sonra da bu nesnenin adresini fonksiyon ismi olan değişkene atıyordu. Örneğin: - - def foo(): - pass - - Burada aslında foo sıradan bir değişkendir. İçerisinde bir fonksiyon nesnesinin adresi vardır. Python'da (...) operatörü - "ilgili değişkenin içerisindeki adresteki fonksiyon nesnesinin içerisinde kondu çalıştır" anlamına gelmektedir. Örneğin: - - foo() - - Bu işlem aslında "foo değişkeninin içerisindeki adreste bulunan fonksiyon nesnesinin içerisindeki kodun çalıştırılacağı" - anlamına gelmektedir. Dolayısıyla anımsanacağı gibi Pythonda "fonksiyonlar birinci sınıf vatandaştır". Örneğin: - - bar = foo - - bar() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında sınıfın metotları da deyim statüsündedir. Bir fonksiyon için yapılanların aynısı metotlar için de yapılır. Bu - durumda metot isimleri aslında sınıf değişkenleri gibidir. Başka bir deyişle sınıfın özniteliği durumundadır. Örneğin: - - class Sample: - x = 10 - - def foo(self): - pass - - Burada teknik olarak x de foo da bu sınıfın birer değişkenidir. Biz sınıf değişkenlerini dışarıdan sınıf ismi ile kullanabiliyorduk. - İşte zaten metodun alternatif çağrımı bu yüzden geçerlidir. Örneğin: - - Sample.x = 20 # geçerli - - s = Sample() - Sample.foo(s) # geçerli -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - x = 10 - - def foo(self): - pass - -print(type(Sample.x)) # -print(type(Sample.foo)) # - -#------------------------------------------------------------------------------------------------------------------------ - Aslında Python'da sınıfın öznitelikleri (yani sınıf değişkenleri) dışarıdan sınıf ismiyle kullanılabildiği gibi o sınıf - türünden bir değişkenle de kullanılabilmektedir. Ancak sınıf değişkenlerinin sınıf türünden değişkenlerle kullanılması yanlış - anlaşılmalara yol açtığı için pek tavsiye edilmemektedir. Örneğin: - - class Sample: - x = 10 - - def foo(self): - pass - - s = Sample() - - s.foo() - Sample.foo(s) - - print(s.x) # geçerli ama yanlış anlaşılabilir! - print(Sample.x) # tavsiye edilen biçim - - Tabii hem nesnenin hem de sınıfın aynı isimli öznitelikleri olabilir. Bu durumda sınıf türünden değişkenlerle nokta operatörü - kullanılarak bu değişkene erişildiğinde nesnenin özniteliği anlaşılır. (Tabii nesnenin özniteliği olmasaydı sınıfın özniteliği - anlaşılacaktı.) Örneğin: - - class Sample: - def __init__(self): - self.x = 100 - - x = 10 - - def foo(self): - pass - - s = Sample() - - print(s.x) # 100 - print(Sample.x) # 10 - - Sınıf değişkenleri C++, Java ve C# gibi dillerdeki sınıfın static elemanlarına benzemektedir. Ancak o dillerde sınıfın - metotları içerisinde sınıfın static elemanları doğrudan kullanılabilmektedir. Oysa Python'da sınıf değişkenleri bir metot - içerisinde doğrudan kullanılamaz. Sınıf değişkenleri ya sınıf ismiyle ya da self parametresi yoluyla kullanılmak zorundadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfın bir metodu diğer bir metodunu yine self parametresini kullanarak çağırabilir. Örneğin: - - class Sample: - def foo(self): - print('foo') - self.bar() - - def bar(self): - print('bar') - - s = Sample() - s.foo() - - Burada sınıfın foo metodu aynı sınıfın bar metodunu çağırmaktadır. Tabii bu çağırma yine alternatif yöntemle de aşağıdaki - gibi yapılabilirdi: - - class Sample: - def foo(self): - print('foo') - Sample.bar(self) - - def bar(self): - print('bar') - -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - print('foo') - self.bar() - - def bar(self): - print('bar') - -s = Sample() -s.foo() - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf türünden bir nesnenin yaratılma ifadesi bir fonksiyon çağrısına benzemektedir. Örneğin: - - class foo: - def __init__(self): - pass - - x = foo() - - Burada aslında foo fonksiyonu çağrılmamıştır. foo sınıfı türünden bir nesne yaratılmıştır. Örneğin: - - a = list('ankara') - - Burada aslında lits fonksiyonu çağrılmamıştır. list sınıfı türünden bir nesne yaratılmıştır. Python'da bazen - anlatımı kolaylaştırmak için (...) ile kullanılabilen ifadelerin hepsine "fonksiyon" da denilebilmektedir. Yani örneğin - bir kaynakta list(...) gibi bir ifade için "list fonksiyonu" denebilmektedir. Gerçekten de Python'un Standart Kütüphanesindeki - "Built-in Funcitons" başlığı altında pek çok sınıf sanki bir fonksiyon gibi ele alınmıştır. -#------------------------------------------------------------------------------------------------------------------------ - - -#------------------------------------------------------------------------------------------------------------------------ - Python'da fonksiyonlar birinci sınıf vatandaş olduğuna göre ve metotlar da aslında birer fonksiyon olduğuna göre biz - bir metodun adresini bir değişkene nasıl atayabiliriz? Örneğin: - - class Sample: - def foo(self): - pass - - Burada foo değişkeninin adresini bar değişkenine atamak isteyelim. Bunu aşağıdaki gibi yapanayız: - - bar = foo - - Çünkü foo Sample sınıfının bir özniteliğidir (yani Sample sııfının bir sınıf değişkenidir). Mademki foo Sample sınıfının - bir değişkenidir o halde ona sınıf ismiyle erişmemiz gerekir: - - bar = Sample.foo - - Şimdi biz bar değişkeni ile çağrı yaptığımızda artık foo metodu çağrılacaktır. Tabii bu durumda self parametresi için artık - bizim argüman olarak girmemiz gerekir. Örneğin: - - s = Sample() - bar(s) - - Tabii biz foo metodunu s.bar() biçiminde çağıramayız. Çünkü s.bar() ifadesinde bar ismi önce s nesnesinin özniteliği olarak - sonra da sınıfın özniteliği olarak aranmaktadır. Başka da bir yerde aranmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - print('foo') - -bar = Sample.foo - -s = Sample() -bar(s) - -#------------------------------------------------------------------------------------------------------------------------ - Ancak bir metodun adresinin bir değişkene atanmasının alternatif bir biçimi de vardır. Bir sınıf türünden değişken ile - nokta operatörü kullanılarak bir ifade oluşturulursa bunun için yorumlayıcı "içerisinde nesneyi ve fonksiyonu tutan - (yani nesnenin ve fonksiyonun adresini tutan)" bir metot nesnesi oluşturmamktadır. Bu nesne (...) operatörü ile kullanıldığında - saklanmış olan sınıf nesnesi ile metot çağrılmaktadır. Örneğin: - - class Sample: - def foo(self): - print('foo') - - - s = Sample() - bar = s.foo - bar() - - Burada bar değişkeni bir metot nesnesini gösterir. Onun içerisinde de s ve foo birlikte bulunmaktadır. Dolayısıyla bar - çağrıldığında sanki s.foo çağrılmış gibi olmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def foo(self): - print('foo') - -s = Sample() -bar = s.foo -bar() - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte list sınıfının append metodunun adresi f değişkeninde tutulup metot çağrılmıştır. Daha sonra da - aynı işlem list nesnesi ile metot birlikte tutularak yapılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -f = list.append - -f(a, 10) -print(a) - -f = a.append -f(20) -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - 42.Ders 21/09/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların birer deyim statüsünde olduğunu daha önce belirtmiştik. Python yorumlayıcısı bir sınıf ile karşılaştığında - type isimli bir sınıf türünden nesne yaratır. Sınıfın bilgilerini bu nesnenin içerisine yerleştirir. Bu nesnenin adresini - de sınıf ismi ile belirtilen değişkene atar. Yani sınıf isimleri aslında sıradan birer değişkendir. Sınıf isimleri type - türünden bir nesnesyi gösterirler. Bu nesnenin içerisinde de sınıfın bilgileri vardır. Ancak sınıflar türünden de nesneler - yaratılabilmektedir. Sınıflar türünden yaratılan nesneler o sınıf türünden olur. Örneğin: - - class Sample: - pass - - print(type(Sample)) # - - s = Sample() - print(type(s)) # - - Burada Sample değişkeninin türü type, s değişkeninin türü ise Sample biçimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - pass - -print(type(Sample)) # - -s = Sample() -print(type(s)) # - -#------------------------------------------------------------------------------------------------------------------------ - Meta sözcüğü (Türkçeye "üst" biçiminde de çevrelimektedir) bir kavramın kendisine ilişkin kavramlar için kullanılan bir - sözcüktür. Örneğin "data" genel olarak "veri" anlamına gelmektedir. Ancak bir verinin kendisine ilişkin bilgiler içeren - verilere "meta data" denilmektedir. İşte Python'da type sınıfı gibi bir sınıfın bilgilerini tutan sınıflara da "meta sınıflar - (meta classes)" denilmektedir. type bir meta sınıftır. Aslında Python'da bir sınıfın merta sınıfı değiştirilebilmektedir. - Bu konu ileride ele alınacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz bir değişkenin türünü type isimli built-in fonksiyon ile ekrana yazdırmıştık. Örneğin: - - a = 10 - print(type(a)) - - Pekiyi type fonksiyonu aslında bize ne vermektedir? type fonksiyonu aslında bize değişkenin gösterdiği yerdeki nesneye - ilişkin sınıfın bilgilerinin bulunduğu type türünden nesneyi (yani onun adresini) vermektedir. Örneğin: - - a = 10 - t = type(a) - - Burada biz aslında "int" isimli sınıfın bilgilerinin bulunduğu type nesnesini elde etmekteyiz. Ancak onu print ile - yazdırdığımızda o nesnenin hangi sınıf türünden olduğu bilgisi yazdırılmaktadır. Örneğin: - - class Sample: - pass - - s = Sample() - t = type(s) - - Burada t aslında Sample sınıf bilgilerinin bulunduğu nesnenin adresini tutmaktadır. t'nin gösteriği nesne type sınıfı - türündendir. Ancak t print edildiğinde t'nin gösterdiği nesnenin hangi sınıfın bilgilerini tuttuğu print edilmektedir. - Başka bir deyişle burada Sample değişkeni ile t değişkeni aynı nesneyi göstermektedir: - - print(Sample is t) # True - - Mademki type fonksiyonu aslında bize değişkenin göstermiş olduğu nesnenin ilişkin olduğu sınıfın type nesnesini vermektedir, - o hhalde biz bu type nesnesini kullanarak aynı sınıf türünden nesneler yaratabiliriz. Örneğin: - - Sample Sample: - pass - - s = Sample() - t = type(s) - - k = t() - - Biz burada t() işlemi ile aslında Sample sınıfı türünden nesne yaratmış olduk. - - Pekiyi type sınıfı türünden nesnenin type'ı nedir? Yani type ismi aslında hangi sınıf türünden nesneyi göstermektedir? - İşte type ismi de aslında yine type türünden bir sınıf nesnesini göstermektedir. Dolayısıyla örneğin: - - class Sample: - pass - - t = type(Sample) # - print(t) - print(type(t)) # - -#------------------------------------------------------------------------------------------------------------------------ - Aslında biz de hiç class bildirimi yapmadan doğrudan type sınıfının __init__ metodu yoluyla bir sınıf da oluşturabiliriz. - Örneğin: - - class Sample: - pass - - Bu işlemin tamamen eşdeğeri şöyledir: - - Sample = type('Sample', (), {}) - - Aslında bir değişkenin türünü elde etmek için kullandığımız type fonksiyonu da aynı fonksiyondur. type fonksiyonun - tek parametreli özel kullanımı yeni bir type nesnesi yaratmaz bize değişkenin ilişkin olduğu sınıfın type nesnesini verir. - - Python'da meta sınıflar nispeten ileri bir konudur. Biz kursumuzda meta sınıfları uygulama konuları içerisinde ele alacağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında bir sınıf türünden nesne type sınıfının __init__ metodu yoluyla yaratılmaktadır. Şöyle ki: - - class Sample: - pass - - Burada Sample değişkeni bu sınıfın bilgilerinin tutulduğu type türünden bir nesnesinin adresini göstermektedir. Biz - Sample(...) yaptığımızda aslında type sınıfının __call__ metodu çağrılmaktadır. Sample nesnesini bu metot yaratıp - bize vermektedir. Sınıf isimleri aslında sıradan birer değişkendir. Örneğin biz bir sınıf ismini başka bir değişkene - atayabiliriz. Bu durumda ilgili sınıf türünden nesneyi bu değişkenle de yaratabiliriz. Örneğin: - - class Sample: - pass - - Mample = Sample - - s = Mample() # s = Sample() ile aynı - print(type(s)) - - Burada Mample değişkeni de Sample değişkeni de aynı type nesnesini görmektedir. Dolayısıyla Mample() ile Sample() - aynı etkiyi yaratmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Meta sınıflar (meta classes) kavramı Python'da biraz zor anlaşılan bir kavramdır. İleride bu konuya yeniden döneceğiz. - Ancak konu şöyle özetlenebilir: Biz bir sınıf tanımladığımızda aslında başka bir sınıf türünden (type sınıfı) bir nesne - yaratmış oluruz. Bu başka sıbıfa (burada type sınıfı) "meta sınıf" denilmektedir. Python'da bu default meta sınıf olan - type sınıfı değiştirilerek de nesneler yaratılabilmektedir. Bu durumda ilginç sonuçlar oluşabilmektedir. Yani Python'da - bir sınıf tanımlanması demek aslında type türünden bir sınıf nesnesi oluşturup o sınıfın bilgilerini bu nesnenin içerisine - yerleştirmek demektir. İşte biz bir sınıf tanımladığımızda type sınıfı yerine başka bir sınıf türünden de nesne yaratılmasını - sağlayabiliriz. Burada ilginç birtakım sonuçlar ortaya çıkmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - O halde bir sınıf nedir? Bir sınıf "belli bir konuda işlemler yapan metotlardan ve bu metotların ortak kullandığı verilerden - (bu veriler nesnenin öznitelikleridir) oluşan bir veri yapısıdır." Sınıflar sayesinde birbirlerinden kopuk izlenim veren - fonksiyonlar mantıksal bir bağ içerisinde bir araya getirilmiş ve algısal kolaylıklar sağlanmıştır. Prosedürel teknikte - her şey fonksiyonlarla yapılmaktadır. Elimizde sanki birbirlerinden bağımsız olan pek çok fonksiyon var gibidir. Nesne - yönelimli teknikte bir konuya ilişkin fonksiyonlar metot adı altında bir sınıfın içerisine yerleştirilir. Böylece "çok - fazla fonksiyon var" duygusu yerine "şu sınıf bunu yapar, bu sınıf şunu yapar" biçiminde algısal kolaylık sağlanmış olur. - - Tabii Python "multiparadigm" bir programlama dildir. Programcı isterse Python'ın sınıf özelliklerini hiç kullanmadan - prosedürel teknikle program yazabilir. Ya da isterse sınıflar oluşturarak programını sınıflar kullanarak da yazabilir. - Ya da her iki tekniği bir arada kullanabilir. Daha önceden de belirttiğimiz gibi Python'ın standart kütüphanesinde - hem fonksiyonlar hem de sınıflar bulunmaktadır. - - Java ve C# gibi bazı diller "pure object oriented" biçimdedir. Bu diller prosedürel tekniğin kullanılmasına izin vermemektedir. - Çünkü bu dillerde dışarıda fonksiyon yazılamamaktadır. Tüm fonksiyonlar sınıfların içerisinde bulunmak zorundadır. Bu diller - adeta nesne yönelimli tekniğin kullanılması yönünde programcıyı zorlamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında Python'da sınıflar minimalist biçimde tasarlanmıştır. Yani C++, Java ve C# gibi dillere kıyasla Python'daki sınıfların - çok detayları yoktur. Bu nedenle Pyton'daki sınıfsal özellikleri C++, Java ve C# gibi dillerle karşılaştırmamak gerekir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Built-in dir isimli fonksiyon bir sınıfın ya da bir nesnenin öznitekilklerini elde etmek için kullanılmaktadır. dir - fonksiyonu parametre olarak bir sınıof ismini (yani type türünden bir değişkeni) ya da bir sınıf türünden değişkeni - alabilmektedir. dir fonksiyonu o sınıf ismi ile ya da o sınıf türünden değişken ile kullanılabilecek bütün isimleri - string'lerden oluşan bir liste biçiminde bize vermektedir. Pytoh programcıları bir sınıfın elemanlarını hatırlamak - istediklerinde bu fonksiyonu sıkça kullanmaktadır. Örneğin: - - >>> class Sample: - ... x = 10 - ... - >>> s = Sample() - >>> s.y = 20 - >>> dir(Sample) - ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', - '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', - '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x'] - >>> dir(s) - ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', - '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', - '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y'] - - Buarada Sample sınıfının ve s nesnesinin öznitelikleri olmayan çeşitli isimler görüyorsunuz. Bu isimler aslında object - sınıfından gelmektedir. object sınıfı izleyen paragraflarda ele alınmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Ağaç, kedi, okul, öğrenci gibi sözcüklerin hepsi birer kavram belirtmektedir. Kavramlar gerçek nesneler değildir. Bir - grup nesnenin ortak özelliklerinden hareketle uydurulmuş zihinsel temsillerdir. Örneğin aslında dünyada ağaç diye bir - şey yoktur. Somut ağaçlar vardır. Ağaç bizim birbirine benzeyen bir grup nesne için uydurduğumuz bir temsildir. Ağaç - bir kavramdır ancak evimizin önündeki ağaç o kavramdan gerçek bir bir örnektir (instance). İşte NYPT'de sınıflar kavramları - belirtirler. Sınıflar türünden nesneler de gerçek nesneleri belirtirler. - - O halde bir proje nesne yönelimli olarak modellenecekse önce proje içerisindeki bütün kavramlar sınıflarla temsil edilmeli - ve sonra da bu sınıflar türünden nesneler yaratılarak örnekler oluşturulmalıdır. Nihayet bu nesneler kullanılarak da işlemler - yapılmalıdır. Örneğin bir hastane otomasyonu için gerekli kavramların bazıları şunlardır: Hastane, doktor, hasta, hastalık, - hasta odası, hemşire, ilaç. İşte bizim önce bu sınıfları yazmamız gerekir. Sonra örneğin hastanemizde 10 tane doktor varsa - Doktor sınıfından 10 tane nesne yaratırız. 5 tane hemşire için Hemşire sınıfından 5 nesne yaratırız. Programı nesnler ile - bu sınıfların metotlarını çağırarak yazarız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii bir projedeki sınıflar arasında da birtakım ilişkiler söz konusu olmaktadır. Örneğin Hastane sınıfı ile Doktor sınıfı - arasında bir ilişki vardır. Doktor ile Hasta sınıfları arasında da bir ilişki vardır. - - NYPT'de sınıflar arasındaki ilişkiler dört başlıkta ele alınmaktadır: - - 1) İçerme İlişkisi (Composition) - 2) Birleşme İlişkisi (Aggregation) - 3) Türetme İlişkisi (Inheritance) - 4) Çağrışım İlişkisi (Association) - - Tabii iki sınıf arasında hiçbir ilişki de olmayabilir. Eğer ilişki olmamasını da bir ilişki biçimi olarak ele alırsak o zaman - beşinci maddeyi "ilişki yok" biçiminde de oluşturabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf türünden bir nesne başka bir sınıf türünden bir nesnenin bir parçasını oluşturuyorsa bu iki sınıf arasında - "içerme (composition)" ilişkisi vardır. Örneğin "insan" ile "karaciğer" sınıfları arasında, "araba" ile "motor" sınıfları - arasında bu biçimde içerme ilişkisi vardır. İki sınıf arasında içerme ilişkisi olması için aşağıdaki iki özelliğin ikisinin - de sağlanması gerekir: - - 1) İçeren nesneyle içerilen nesnenin ömürleri aynı olmalıdır. - 2) İçerilen nesne tek bir nesne tarafından içerilmelidir. - - Bu durumda örneğin "hastane" sınıfı ile "doktor" sınıfı arasında içerme ilişkisi yoktur. Çünkü bunların ömürleri aynı - değildir. Ayrıca bir doktor aynı anda birden fazla hastanede de çalışabilmektedir. Ancak insan ile karaciğer arasında, - arabayla motor arasında, "saat" ile "akrep" arasında, "dünya" ile "kıtalar" arasında, "cep telefonu" ile "işlemcisi" - arasında içerme ilişkisi vardır. "Oda" ile "duvar" arasında içerme ilişkisi yoktur. Her ne kadar oda ile duvar aynı - ömre sahipse de duvar aynı zamanda yan odanın da duvarıdır. Tabii yukarıda belirttiğimiz iki özellik "tipik durumlar" - dikkate alınarak değerlendirilmelidir. İnsan öldükten sonra karaciğeri organ nakli yoluyla yaşayabilir. Yapışık - ikizler bazı organları ortak kullanıyor da olabilir. Ancak bu durumlar tipk değil istisnai durumlardır. - - UML (Unfied Modeling Language) bir projeyi nesne yönelimli olarak modellemek için kullanılan diyagramlardan oluşan bir - dildir. UML diyagralarından biri de "sınıf diyagramları (class diagrams)" denilen diyagramlardır. Burada projedeki - sınıflar ve onların arasındaki ilişkiler betimlenmektedir. UML sınıf diyagramlarında içerme ilişkisi "içeren sınıf tarafında - içi dolu bir baklavacık (diamond)" ile temsil edilmektedir. - - İçerme ilişkisine İngilizce "has a" ilişkisi de denilmektedir. İçerme ilişkisi 1'e 1 olabileceği gibi 1'e N de olabilir. - Örneğin "insan" sınıfı ile "böbrek" sınıfı arasında içerme ilişkisi vardır. Ancak bu ilişki 1'e 2'dir. Yani bir tane insan - iki böbreğe sahiptir. UML sınıf diyagramlarında bu durum da belirtilmektedir. Satranç tahtası ile tahtanın kareleri arasında - 1'e 64 olan bir içerme ilişkisi vardır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da içerme ilişkisi içeren sınıfın __init__ metodunda içerilen nesnenin yaratılarak içeren sınıfın örnek özniteliğinde - tutulması yoluyla sağlanabilir. Örneğin: - - class Araba: - def __init__(self): - self.motor = Motor() - - class Motor: - pass - - a = Araba() - - Burada Araba sınıfı türünden nesne yaratıldığında Araba sınıfının __init__ metodu çağrılacak ve Motor nesnesi de yaratılacaktır. - Böylece araba nesnesi motor nesnesine sahip olmaktadır (has a ilişkisi). Henüz görmemiş olsak da "çöp toplayıcı (garvage collector)" - mekanizma Araba nesnesini yok ettiğinde motor nesnesi de yok olacaktır. Örneğin: - - class Board: - def __init__(self): - self.squares = [[Square() for _ in range(8)] for _ in range(8)] - - class Square: - pass - - board = Board() - - Burada Board sınıfı Square sınıfını içermektedir. Ancak 1 Board nesnesi 64 tane Square nesnesini içerir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Birleşme ilişkisinde (aggregation) bir sınıf türünden nesne başka bir sınıf türünden nesneyi bünyesine katarak kullanmaktadır. - Ancak kullanılan nesne tek bir nesne tarafından kullanılmak zorunda değildir. Kullanan nesneyle kullanılan nesnenin - ömürleri de aynı olmak zorunda değildir. İşte bu durumda bu nesnelerin sınıfları arasında "birleşme" ilişkisi vardır. - Aslında çoğu zaman "içerme" ilişkisine benzeyen ancak içerme ilişkisi olmayan iişkiler "birleşme" ilişkisidir. Örneğin - "hastane" ile "doktor" sınıfları arasında, "bilgisayar" ile "fare" sınıfları arasında "oda" ile "duvar" sınıfları arasında - içerme ilişkisi yoktur, birleşme ilişkisi vardır. - - UML sınıf diyagramlarında birleşme ilişkisi "kullanan sınıf tarafında içi boş bir baklavacık (diamond)" ile temsil - edilmektedir. Yine birleşme ilişkisi 1'e 1 olabileceği gibi 1'e N olabilmektedir. Birleşme ilişkisine İngilizce "holds a" - ilişkisi de denilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da birleşme ilişkisi kullanan sınıfta kullanılan nesneyi geçici olarak tutma yoluyla sağlanabilir. Yani birleşme - ilişkisinde kullanılacak nesnenin kullanan nesneye dahil edilmesi ve çıkartılması gibi işlemler söz konusu olmaktadır. - Örneğin: - - class Doctor: - def __init__(self, name, specialty): - self.name = name - self.specialty = specialty - - class Hospital: - def __init__(self, name): - self.name = name - self.doctors = [] - - def add_doctor(self, doctor): - self.doctors.append(doctor) - - def remove_doctor(self, name): - self.doctors.remove(name) - - def another_metods(self): - print('self.doctors is using...') - - hospital1 = Hospital('Yaşam') - doctor1 = Doctor('Ali Serçe', 'Kalp Damar') - hospital1.add_doctor(doctor1) - doctor2 = Doctor('Mehmet güneş', 'Kulak Burun Boğaz') - hospital1.add_doctor(doctor2) - - hospital2 = Hospital('Gelecek') - hospital2.add_doctor(doctor1) - - Burada bir Hospital nesnesi birden fazla Doctor nesnesini kullanabildiği gibi bir Doctor nesnesi de birden fazla - Hospital nesnesi tarafından kullanılabilmektedir. Hospital nesnesinin ve Doctor nesnelerinin ömürlerinin farklı - olabildiğine dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 43. Ders 26/09/2022 Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıflar arasındaki diğer bir ilişki biçimi de "türetme" ilişkisidir. Türetme ilişkisine İngilizce "inheritance" yani - "kalıtım" da denilmektedir. Türetme mevcut bir sınıfa dokunmadan onu genişletme anlamına gelir. Burada asıl sınıfa - "taban sınıf (base class)", genişletilmiş olan sınıfa da "türemiş sınıf (derived class)" denilmektedir. Türetmede türemiş - sınıf tamamen taban sınıf gibi de işlem görür ancak fazlalıkları da vardır. UML sınıf diyagramlarında türetme ilişkisi - türemiş sınıftan taban sınıfa doğru çekilen içi boş bir okla gösterilmektedir. Biz text ekranda ok çizemedeiğimiz için - yalnızca sınıfların ismini alt alta yazacağız. Örneğin: - - A - B - - Burada B sınıfı A sınıfından türetilmiştir. Yani B sınıfı türünden bir nesne ile hem B sınıfının elemanlarını hem de A - sınıfının elemanlarını kullanabiliriz. - - Türemiş sınıftan yeniden türetme yapılabilir. Böylece bir dizi türetme söz konusu olabilir. Örneğin: - - A - B - C - - Burada C sınıfı B sınıfından, B sınıfı da A sınıfından türetilmiştir. C sınıfı türünden bir nesneyle biz hem C'nin hem - B'nin hem de A'nın elemanlarını kullanabiliriz. Bşir sınıf birden fazla sınıfın taban sınıfı durumunda olabilir. Bu gayet - normal bir durumdur. Örneğin: - - A - B C - - Burada B de C de A sınıfından türetilmiştir. B ile C arasında bir ilişki yoktur. Ancak A ikisin de taban sınıfıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Türetme işleminin en önemli faydası tekrarı engellemektir. Prosedürel teknikte tekrarın engellenmesi için tekrar eden - kodun fonksiyon biçiminde ifade edilmesi gerekir. Ancak nesne yönelimli teknikte tekrar eden metotlar ortak taban sınıflarda - bulundurularak tekrar engellenmektedir. Örneğin B sınıfında foo, bar ve tar metotları bulunuyor olsun. C sınıfında da foo, - bar ve zar metotlarının bulunduğunu kabul edelim. Burada foo ve bar ortak metotlardır ve tekrar etmektedirler. İşte biz - foo ve bar metotlarını A gibi bir taban snıfta toplayıp B'yi ve C'yi A'dan türetebiliriz. Böylece B'de yalnızca tar, - C'de de yalnızca zar bulunmuş olur. - - A (foo, bar) - B (tar) C (zar) - - Bir sınıf kütüphanesinde bir dizi türetmelerle bir türetme şeması oluşabilir. - - Bir türetme şemasında yukarıya çıktıkça genelleşme, aşağıya indikçe özelleşme olur. Yani yukarıdaki sınıflar diğer - sınıflarda olan ortak özellikleri barındırır. NYPT'de türetme ilişkisine İngilizce "is a" ilişkisi de denilmektedir. - Örneğin "işçi bir çalışandır". O zaman İşçi sınıfı Çalışan sınıfından türetilebilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıfın birden fazla sınıfa taban sınıflık yapması tamamen normal bir durumdur. Örneğin: - - A - B C - - Burada A hem B'nin hem de C'nin taban sınıfı durumundadır. Ancak bir sınıfın birden fazla taban sınıfa sahip olması durumu - özel bir durumdur. Buna NYPT'de "çoklu türetme (multiple inheritance)" denilmektedir. Doğada çoklu türetme az olmasına karşın - karşılaşılmaktadır. Örneğin: - - A B - C - - Burada C'nin iki taban sınıfı vardır. C sınıfı A ve B sınıflarından çoklu türetilmiştir. Örneğin hava taşıtlarının - ortak özellikleri bir sınıfla, deniz taşıtlarının ortak özellikleri başka bir sınıfla temsil edilmiş olsun. Hem havada - hem de denizde giden bir taşıt bu iki sınıftan çoklu türetme yapılarak oluşturulabilir. Örneğin istream sınıfı bir dosyadan - okuma yapmak için, ostream sınıfı ise bir dosyaya yazma yapmak için oluşturulmuş olsun. Dosyadan hem okuma hem de yazma - yapan bir sınıf bu iki sınıftan çoklu türetilerek oluşturulabilir. - - Bazı programlama dillerinde çoklu türetme yoktur. Örnenğin Java ve C# dillerinde bir sınıf yalnızca tek bir sınıftan - türetilebilir. Ancak C++, Python gibi bazı dillerde çoklu türetme bulunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Türetme işlemlerine bazı örnekler vermek istiyoruz: - - - Bir personel takip programında iş yerinde çalışanlar görevlerine göre sınıflarla temsil ediliyor olsun. Tüm çalışanların - ortak birtakım özellikleri vardır. Örneğin çalışan hangi görevde olursa olsun onun bir ismi, telefon numarası, sigorta bilgileri, - departmanı gibi bilgileri vardır. İşte tüm bu ortak bilgiler tepedeki Employee isimli bir taban sınıfta toplanabilir: - - Employee - - Worker SalesPerson Manager - - Executive - - Burada işçi de, satış elemanı da yönetici de bir çalışandır. Öte yandan üst düzey yönetici (Executive) de bir çeşit - yöneticidir. - - - Bir satranç programında satranç taşları farklı sınıflarla temsil edilebilir. Ancak her taşın rengi gibi üzerine oluşturduğu - kare gibi diğerleriyle ortak özellikleri de vardır. İşte biz bu ortak özellikleri Figure isimli bir sınıfta toplayabiliriz. - Taş sınıflarını da bu Figure sınıfından türetebiliriz. Örneğin: - - Figure - - Pawn Knight Bishop Rook Queen King - - - Power Point benzeri bir program yazacak olalım. Bu programda birtakım şekiller oluşturulup sonra seçilerek işlem - yapılıyor olsun. Programda çeşitli şekiller kullanılıyor olabilir. Tüm bu şekillerin ortak birtakım özellikleri vardır. - Örneğin tüm şekillerin sınır çizgi renkleri, tüm şekillerin zemin renkleri, tüm şekillerin çizgi kalınlıkları vardır. - İşte bu şekillerin ortak özellikleri Shape isimli bir sınıfta toplanmış olabilir. Biz de diğer şekil sınıflarını bu - sınıftan türeterek oluşturabiliriz. - - Shape - - RectangleShape EllipseShape LineShape - - - Pencereli programlar (GUI uygulamaları) yazmak için GUI kütüpaheneleri (ya da framework'leri) kullanılmaktadır. GUI - uygulamalarında ekrandaki bağımsız kontrol edilebilen öğelere "pencere (window)" ya da "widget" denilmektedir. Ger GUI - eleman bir penceredir. Dolayısıyla onların pencere olmasından kaynaklanan ortak özellikleri vardır. Örneğin .NET Forms - isimli GUI kütüphanesinde tüm pencerelerin ortak özellikleri Control isimli bir sınıfta topanmıştır. Bazı GUI elemanları - da birbirine benzemektedir. Yani onların da ortak birtakım özellikleri vardır. İşte GUI kütüphanelerinde sınıflar bir - türetme şeması biçiminde oluşturulmuş durumdadır. Örneğin: - - Control - .... ButtonBase ListControl ..... - Button CheckBox RadioButton ListBox ComboBox - - Görüldüğü gibi Button (push button), CheckBox ve RadioButton GUI elemanlarının ortak özellikleri ButtonBase sınıfında, - ListBox ve ComboBox sınıflarının ortak özellikleri ise ListControl sınıfında toplanmıştır. Tüm GUI elemanlarının ortak - özellikleri ise en tepedeki Control sınıfında toplanmıştır. Tabii aslında bu kütüphanede çok sayıda GUI eleman ve çok - sayıda sınıf bulunmaktadır. Biz yalnızca yukarıda bir fikir versin diye kütüphanenin küçük bir bölümünün temsilini verdik. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi bir türetme şeması ile karşılaştığımızda oradaki sınıfları nereden başlayarak öğrenmeliyiz? En makul başlangıç - yeri en tepedeki taban sınıf olabilir. Bu sınıf ne de olsa tüm türemiş sınıflardaki ortak özellikleri yansıtmaktadır. - Genelden özele giderek kütüphaneyi öğrenmek daha uygun olabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıflararası son ilişki biçimine "çağrışım ilişkisi (association)" denilmektedir. Çağrışım ilişkisinde bir sınıf nesnesi - başka bir nesneyi kullanır. Ancak bu kullanma yüzeyseldir. Bünyesine katarak bir kullanma değildir. Örneğin Taksi sınıfı - Müşteri sınıfını kullanır. Ancak Taksi ile Şoför arasındaki ilişki birleşme ilişkisiyken Taksi ile Müşteri arasındaki ilişki - çağrışım ilişkisidir. Çağrışım ilişkisi yüzeysel bir ilişkidir. Örneğin bir sınıf diğer sınıfı yalnızca bir metodunda kullanıyorsa, - bünyesine katarak kullanmıyorsa burada bir çağrışım ilişkisinden bahsedilebilir. Örneğin Hastane sınıfı reklam yapılacağı zaman - Reklam Şirketi sınıfını kullanıyor olabilir. - - Çağrışım ilişkisi UML sınıf diyagramlarında kullanan sınıftan kullanılan sınıfa çekilen ince bir ok ile temsil edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İçerme ilişkisi, birleşme ilişkisi ve çağrışım ilişkisi şimdiye kadar görmüş olduğumuz bilgilerle oluşturulabilmektedir. - Ancak türetme ilişkisi ayrı bir sentaks ile oluşturulmaktadır. Biz de şimdi Python'da türetme ilişkisinin oluşturulması - ve kullanılması üzerinde duracağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da türetme sentaksının genel biçimi şöyledir: - - class (): - pass - - Örneğin: - - class A: - pass - - class B(A): - pass - -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('A.foo') - - def bar(self): - print('A.bar') - -class B(A): - def tar(self): - print('B.tar') - -b = B() - -b.foo() -b.bar() -b.tar() - -#------------------------------------------------------------------------------------------------------------------------ - Türetme ilişkisinde türemiş sınıf taban sınıfı kullanmaktadır. Taban sınıf türemiş sınıfı kullanmamaktadır. Aşağıdaki - örnekte B ve C sınıflarının ortak elemanları A taban sınıfında toplanmış ve kod tekrarı bu sayede elimine edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('foo') - - def bar(self): - print('bar') - -class B(A): - def tar(self): - print('tar') - -class C(A): - def zar(self): - print('zar') - -b = B() -b.foo() -b.bar() -b.tar() - -c = C() -c.foo() -c.bar() -c.zar() - - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi nesnelerin öznitelikleri tipik olarak __init__ metotlarında yaratılmaktadır. Türemiş sınıf türünden - bir nesne yaratıldığında eğer türemiş sınıfın __init__ metodu yazılmışsa türemiş sınıfın __init__ metodu otomatik çağrılır. - Ancak türemiş sınıfın __init__ metodu yazılmamışsa bu durumda taban sınıfın __init__ metodu çağrılmaktadır. Türemiş sınıfın - __init__ metodunun türetmeyi yapan programcı tarafından yazılmış olduğunu varsayalım. Bu durumda türemiş sınıf türünden - bir nesne yaratıldığında türemiş sınıfın __init__ metodu çağrılacaktır. Taban sınıfın __init__ metodu çağrılmayacaktır. - Oysa taban sınıfın __init__ metodunun içerisinde taban sınıf gereken birtakım şeyler yapılmış olabilmektedir. Türemiş - sınıf türünden nesne yaratıldığında türemiş sınıfın __init__ metodunun çağrılması ancak taban sınıfın __init__ netodunun - çağrılmaması önemli sorunlara yol açabilmektedir. Örneğin: - - class A: - def __init__(self): - self.x = 10 - - def dispA(self): - print(self.x) - - class B(A): - def __init__(self): - self.y = 20 - - def dispB(self): - print(self.y) - - - a = A() - a.dispA() # sorun yok! nesnenin a özniteliği yaratılmış durumda - - b = B() - b.dispA() # dikkat! nesnenin bir a örnek özniteliği yaratılmamış! - b.dispB() - - Burada A sınıfı türünden nesne yaratıldığında A sınıfının __init__ metodu çağrılmaktadır. Bu metotta nesne içerisinde - x isimli bir özniteliğin yaratıldığını görüyorsunuz. dispA metodu da bu özniteliği kullanmaktadır. Ancak B nesnesi - yaratıldığında yalnızca B sınıfının __init__ metodu çağrılmaktadır. Ancak türemiş sınıf nesnesi taban sınıf gibi de - kullanılabildiğine göre bu B nesnesi ile A sınıfının dispA metodu çağrıldığında sorun ortaya çıkacaktır. Çünkü A sınıfının - __init__ metodu çağrılmadığı için x özniteliği nesnede yaratılmamıştır. - - Yukarıdaki sorun tam olarak neden kaynaklanmaktadır? Eğer türemiş sınıfın __init__ metodu taban sınıfın __init__ metodunu - çağırsaydı böyle bir sorun oluşmayacaktı. Çünkü bu durumda yorumlayıcı türemiş sınıfın __init__ metodunu otomatik çağıracaktı. - Türemiş sınıfın __init__ metodu da taban sınıfın __init__ metodunu çağırdığığı için sorun oluşmayacaktı. Daha önceden de - belirttiğimiz gibi Python'daki __init__ metodu C++, Java ve C# gibi dillerdeki "constructor" metotlarına karşılık gelmektyedir. - Ancak bu dillerde türemiş sınıfların "constructor" metotları taban sınıfın "constructor" metotlarını zaten otomatik çağırmaktadır. - Böylece bu dillerde yukarıdaki sorun ortaya çıkmamaktadır. Ancak Python'da böyle bir otomatik çağırma yoktur. Yani Python'da - programcı türemiş sınıfın __init__ metodu içerisinde taban sınıfın __init__metodunu kendisi açıkça çağırmalıdır. Pekiyi bu - nasıl yapılacaktır? Çağrı aşağıdaki gibi yapılamaz: - - class A: - def __init__(self): - self.a = 10 - - def dispA(self): - print(self.a) - - class B(A): - def __init__(self): - self.__init__() # dikkat metot kendi kendisini çağırıyor! - self.b = 20 - - b = B() - b.dispA() - - Burada B sınıfının __init__ metodu yine kendisini çağırmaktadır. Bir fonksiyonun ya da metodun kendisi çağırmasına - "özyineleme (recursion)" denilmektedir. Çağırmanın açıkça taban sınıf ismi belirtilerek alternatif yöntemle yapılması - gerekir: - - class A: - def __init__(self): - self.a = 10 - - def dispA(self): - print(self.a) - - class B(A): - def __init__(self): - A.__init__(self) # açıkça A'nın __init__ metodunun çağrıldığı anlaşılıyor - self.b = 20 - - b = B() - b.dispA() # geçerli, sorun yok - - Pekiyi türemiş sınıfın __init__ metodu taban sınıfın __init__ metodunu nerede çağırmalıdır? İşte en normal olan ve uygun - durum türemiş sınıfın __init__ metodunun taban sınıfın __init__ metodunu hemen türemiş sınıfın __init__ metdonun başında - çağırmasıdır. Çünkü önce nesnenin taban sınıf kısmının ilkdeğer alması normal olan durumdur. Ne de olsa türemiş sınıf - taban sınıfı kullanabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def __init__(self): - self.a = 10 - - def dispA(self): - print(self.a) - -class B(A): - def __init__(self): - A.__init__(self) - self.b = 20 - - def dispB(self): - print(self.b) - -b = B() -b.dispA() -b.dispB() - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıfın "taban sınıfları (base classes)" denildiğinde onun yukarıya doğru tüm taban sınıfları anlaşılmalıdır. Ancak - sınıfın "doğrudan taban sınıfları (direct base classes)" denildiğinde sınıfın hemen bir yukarısındaki taban sınıflar - anlaşılmalıdır. Sınıfın "dolaylı taban sınıfları (indirect base classes)" ise sınıfın doğrudan taban sınıflarının taban - sınıflarıdır. Örneğin aşağıdaki gibi bir türetme şeması olsun: - - A - B - C - D - - Burada D'nin taban sınıfları C, B ve A'dır. D'nin doğrudan taban sınıfı C'dir. D'nin dolaylı taban sınıfları A ve B'dir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da türemiş sınıfın taban sınıfn __init__ metodunu çağırması gerektiğini belirtmiştik. İşte türemiş sınıflar tüm - taban sınıfların __init__ metotlarını çağırmamalıdır. Yalnızca doğrudan taban sınıflarının __init__ metotlarını çağırmalıdır. - Zaten her sınıf kendi doğrudan taban sınıflarının __init__ metotlarını çağırınca tüm taban sınıfların __init metotları - çağrılmış olur. Örneğin: - - class A: - def __init__(self): - pass - - class B(A): - def __init__(self): - A.__init__(self) - pass - - class C(B): - def __init__(self): - B.__init__(self) - pass - - c = C() - - Burada C sınıfı türünden bir nesne yaratıldığında aslında A, B, C sırasına göre sınıfların __init__ metotları çağrılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Taban sınıfla türemiş sınıf aynı isimli metotlara sahip olabilir. Bu durumda bu metotlar hangi sınıf türünden nesneyle - çağrılmışsa o sınıfın aynı isimli metodu çağrılmış olur. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - - b = B() - b.foo() # b nesnesi B sınıfı türündne olduğu için B.foo çağrılır - - a = A() - a.foo() # A.foo çağrılır, zaten taban sınıf türemiş sınıfa erişemez! - - Eğer biz türemiş sınıf türünden bir değişkenle türemiş sınıfın değil de taban sınıfın aynı isimli metodunu çağırmak istersek - alternatif çağırma biçimini kullanmalıyız. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - - b = B() - - b.foo() # türemiş sınıftaki foo metodu çağrılır - A.foo(b) # taban sınıftaki foo metodu çağrılır - - Taban sınıftaki bir metodun türemiş sınıfta aynı isimle yeniden yazılmasına NYPT'de "taban sınıftaki metodun override edilmesi" - denilmektedir. Override terimi pek çok dilde "çokbiçimli (polymorphic)" mekanizma için kullanılmaktadır. - - Bazen programcı kasten taban sınıftaki metot ile aynı isimli metodu türemiş sınıfta yazmaktadır. Böylece türemiş sınıf - türünden bir dğeişkenle bu metot çağrıldığında türemiş sınıfın metodu çağrılmaktadır. İşte türemiş sınıfın metodu içerisinde - de programcı tana sınıfın aynı isimli metodunu çağırabilmektedir. Böylece asıl işlevselliği taban sınıftaki metot yapar. Ancak - programcı bunu türemiş sınıfta genişletimiş olur. Buna NYPT'te de "augmentation (artırma)" denilmektedir. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print("B.foo araya girerek A.foo'ya eklemeler yapıyor") - A.foo(self) - - b = B() - - b.foo() -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirtildiği gibi Python'da çoklu türetme vardır. Ancak Java ve C# gibi bazı dillerde çok türetme yapılamamaktadır. - Fakat örneğin C++'ta ve Object Pascal'da da çoklu türetme bulunmaktadır. Çoklu türetmede türemiş sınıf türünden bir değişkenle - hem türemiş sınıf metotları hem de onun tüm taban sınıf metotları çağrılabilmektedir. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B: - def bar(self): - print('B.bar') - - class C(A, B): - def tar(self): - print('C.tar') - - c = C() - - c.tar() - c.bar() - c.foo() - - Burada C sınıfı türünden bir değişkenle hem C'nin hem A'nın hem de B'nin metotları çağrılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('A.foo') - -class B: - def bar(self): - print('B.bar') - -class C(A, B): - def tar(self): - print('C.tar') - -c = C() - -c.foo() -c.bar() -c.tar() - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi türemiş sınıfın __init__ metodunun kendi doğrudan taban sınıflarının __init__ metotlarını çağırması - gerekiyordu. Çoklu türetmede türemiş sınıf bütün doğrudan taban sınıflarının __init__ metotlarını çağırmalıdır. Örneğin: - - class A: - def __init__(self): - self.x = 10 - print('A.__init__') - - def dispA(self): - print(self.x) - - class B: - def __init__(self): - self.y = 20 - print('B.__init__') - - def dispB(self): - print(self.y) - - class C(A, B): - def __init__(self): - A.__init__(self) - B.__init__(self) - self.c = 30 - print('C.__init__') - - def dispC(self): - print(self.c) - - c = C() - - c.dispA() - c.dispB() - c.dispC() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Çoklu türetmede isim araması hangi sıraya göre yapılmaktadır. Örneğin: - - A B - C - - Burada C sınıfının A ve B'den çoklu türetildiğini düşünelim. A ve B sınıflarında foo metodu olsun ancak C sınıfında foo - metodu olmasın. C sınıfı türünden bir dğeişkenle foo metodu çağrıldığında hangi foo metodu çağrılacaktır. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B: - def foo(self): - print('B.foo') - - class C(A, B): - pass - - c = C() - - c.foo() # A.foo çağrılacaktır - - Python'da genel olarak bu tür durumlarda çoklu türetmede belirtilen sıra önemli olmaktadır. Yani yorumlayıcı önce ilk - belirtilen tana sınıf kolundan yukarıya doğru ilerlemekte eğer metot bulunamazsa sonra sırasıyla diğer taban sınıf kollarından - yukarıya doğru ilerlemektedir. Yukarıdaki örnekte A.foo metodu çağrılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Ancak bazen türetme şeması oldukça karışık olabilmektedir. Özellikle baklava (dimaond) tarzı türetmeler kişilerin - kafalarını karıştırabilmektedir. Baklava tarzı türetme iki koldan ilerlenip birleşerek devam eden türetme şemalarına - denilmektedir. Örneğin: - - A - B C - D - E - - Bu tür karmaşık türetme şemaalarında metotların aranma sırasına Python'da MRO (Member Resolution Order) denilmektedir. - MRO konusu izleten paragraflarda ele alınacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki gibi bir türetme şeması olsun. Bu şemadaki bazı sınıflarda foo metodu bulunuyopr olsun: - - A (foo) B (foo) - C E (foo) - D - - Burada D sınıfı türünden bir değişkenle foo metodunun çağrıldığını düşünelim: - - d = D() - d.foo() - - Hangi sınıfın foo metodu çağrılacaktır? Daha önce de belirttiğimiz gibi kaba kural taban sınıf kollarından sırasıyla - aşağıdan yukarıya doğru arama yapılmasıdır. Yani arama D, C, A, E, B sırasına göre yapılacaktır. Başka bir deyişle D - sınıfı için MRO sırası bu biçimdedir. Örneğimizde foo metodunun ilk bulunduğu sınıf A'dır. Bu nedenle A sınıfının foo - metodu çağrılacaktır. Bu örnekte D sınıfında taban sınıflar önce E sonra C biçiminde belirtilmiş olsaydı E sınıfının - foo metodu çağrılırdı. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('A.foo') - -class B: - def foo(self): - print('B.foo') - -class C(A): - pass - -class E(B): - def foo(self): - print('E.foo') - -class D(C, E): - pass - -d = D() -d.foo() - -#------------------------------------------------------------------------------------------------------------------------ - Çoklu türetmede baklava (diamond) biçiminde türetme şemasında her sınıfın kendi doğrudan taban sınıflarının __init__ - metotlarını çağırması önemli bir soruna yol açmaktadır. Aşağıda baklava biçiminde bir türetme şeması görüyorsunuz: - - A - B C - D - - Burada her sınıfın __init__ metodu kendi doğrudan taban sınıfının __init__ metodunu çağırdığında A sınıfının __init__ - metodu iki kez çağrılmış olur. Bir sınıfın __init__ metodunun iki kez çağrılması bazen sorunlarlara yol açmayabilir - ancak bazen sorunlara yol açabilir. Bunun bir biçimde engellenmesi gerekir. C++'ta bu problem "virtual base class" - denilen bir kavramla çözülmeye çalışılmıştır. Python'da bu problemin nasıl çözlüdüğü izleyen paragraflarda ele alınmaktadır. - - Aşağıda baklava biçiminde türetme uygulanıp her sınıfın kendi doğrudan taban sınıflarını çağırması ile tepedeki - taban sınıfının __init__ metodunun iki kez çağrılmasına örnek verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def __init__(self): - print('A.__init__') - -class B(A): - def __init__(self): - A.__init__(self) - print('B.__init__') - -class C(A): - def __init__(self): - A.__init__(self) - print('C.__init__') - -class D(B, C): - def __init__(self): - B.__init__(self) - C.__init__(self) - print('D.__init__') - -d = D() - -#------------------------------------------------------------------------------------------------------------------------ - 44. Ders 28/09/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi türetme durumlarında taban sınıflarda aynı isimli metotlar varsa isim araması kabaca - aşağıdan yukarıya doğru yapılmaktadır. Ancak çoklu türetme söz konusu olduğunda taban sınıfların belirtilme sırasına - göre kollarda aşağıdan yukarıya doğru arama yapılmaktadır. Örneğin: - - A B - C - D - - Böyle bir türetme şemasında aşağıdaki gibi bir foo metodu çağrılmış olsun: - - d = D() - - d.foo() - - Burada önce D sınıfına bakılacaktır. Çünkü metot çağrılmasında kullanılan nesne D sınıfı türündendir. Pekiyi D'de foo yoksa - ne olacaktır? Bu durumda C'ye bakılacaktır? Pekiyi ya C'de yoksa ne olacaktır? Önce sol koldan yukarıya doğru sonra sağ koldan - yukarıya doğru arama yapılacaktır. Yani D sınıfı için buradaki MRO sırası D, C, A, B biçimindedir. Örneğin: - - - A - B C - D - E - - E sınıfı için buradaki MRO sırası E, D, B, A, C, object biçimindedir. (object sınıfı izleyen paragraflarda ele alınacaktır.) - - Bir sınıfın MRO sırasını elde etmek type sınıfının __mro__ örnek özniteliği kullanılmaktadır. (Sınıf isimlerinin aslında - type sınıfı türünden birer nesne belirttiğine dikkat edniz.) Örneğin yukarıdaki şemada E sınıfının MRO sırası E.__mro__ ifadesiyle - elde edilebilmektedir. type sınıfının __mro__ örnek özniteliği aramaların yapılacağı sınıflara ilişkin type nesnelerinden - oluşan bir demet vermektedir. Karmaşık türetmelerde isimlerin arama sırası için __mro__ özniteliğine başvurabilirsiniz. - - Ayrıca type sınıfının mro isimli bir örnek metodu da vardır. Aslında type nesnesi yaratılırken bu metot çağrılıp buradan - elde edilen değer __mro__ değişkenine atanmaktadır. Bu durumda örneğin biz E.__mro__ yerine E.mro() çağrısını da uygulayabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - pass - -class B(A): - pass - -class C: - pass - -class D(B, C): - pass - -class E(D): - pass - -print(E.__mro__) - -#------------------------------------------------------------------------------------------------------------------------ - Ayrıca MRO sırası inspect modülündeki getmro fonksiyonuyla da elde edilebilmektedir. Bu fonksiyona biz bir sınıf ismini - (yani tytpe nesnesini veririz). Örneğin: - - import inspect - - t = inspect.getmro(E) - print(t) - - Tabii getmro fonksiyonu şöyle yazılmıştır: - - def getmro(cls): - return cls.__mro__ - - Genel olarak MRO sırası şu biçimdedir: Eğer çoklu türetme yoksa aşağıdan yukarıya doğrudur. Çoklu türemte varsa soldan sağa taban sınıflarda - bir kol bitirildikten sonra diğerine geçilecek biçimde bir sıra izlenir. Örneğin: - - A - B D - C E - F - G - - Burada G'nin MRO sırası şöyle olacaktır: G, F, C, B, A, E, D, object (object sınıfı izleyen paragraflarda ele alınacaktır.) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi MRO sırası isim aramasında etkili olmaktadır. Bir sınıf türünden değişkenle ya da sınıf ismi - kullanılarak nokta operatörü ile bir eleman belirtilirse bu elemanın sırasıyla hangi sınıflarda aranacağına MRO sırası - denilmektedir. Tabii isim bulunursa arama devam etmez. Örneğin: - - A - B C - D - E - - e = E() - e.foo() - - Burada foo'nun yalnızca A ve C sınıflarında bulunduğunu varsayalım. Çağrılan foo hangi foo'dur? İşte bu durum E'nin MRO - sırası ile ilgilidir. E'nin MRO sırası ise şöyledir: E, D, B, A, C, object. (object sınıfı izleyen paragraflarda ele alınacaktır.) - Bu şu anlama gelmektedir: "İsim önce E'de aranır, bulamazsa D'de aranır, bulamazsa B'de aranır, bulunamazsa A'da aranır, - orada da bulunamazsa C'de aranır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Baklava biçimindeki türetme şemalarında tepedeki taban sınıf ortak olduğu için durum biraz ilginç hale gelmektedir. Örneğin: - - A - B C - D - - Normal olarak buradaki D sınıfının MRO sırasının sanki D, B, A, C, A gibi olması gerektiğini düşünebilirsiniz. Amcak MRO - sırasında her zaman bir sınıftan bir tane bulunmaktadır. Burada D sınıfının MRO sırası şöyledir: D, B, C, A, object. - -#------------------------------------------------------------------------------------------------------------------------ - -class A: - pass - -class B(A): - pass - -class C(A): - pass - -class D(B, C): - pass - -print(D.__mro__) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da "her şey bir çeşit nesne" olduğuna göre tüm sınıflar aslında doğrudan ya da dolaylı olarak object isimli bir - sınıftan türetilmiş durumdadır. Biz bir sınıfta taban sınıf belirtmezsek Python yorumlayıcısı onun object sınıftan - türetildiğini varsaymaktadır. Örneğin: - - class Sample: - pass - - Burada Sample sınıfı object sınıfından türetilmiştir. Tabii biz buna açıkça da belirtebilirdik: - - class Sample(object): - pass - - Örneğin: - - class A: - pass - - class B(A): - pass - - Burada B sınıfı A sınıfından A sınıfı da object sınıfından türetilmiştir. İşte böyle yukarıya çıktığımızda her zaman - tepede object sınıfıyla karşılaşırız. Yani "her sınıf doğrudan ya da dolaylı olarak object sınıfından türetilmiş" durumdadır. - - Pekiyi object sınıfının içerisinde neler vardır? İşte object sınıfının içerisinde her nesne için geçerli olabilecek metotlar - bulunmaktadır. - - Biz çizimlerde ve gösterimlerde kolaylık sağlamak amacıyla türetme durumlarında en tepedeki object sınıfını göstermedik - ve göstermeyeceğiz. Ancak aslında türetme şemasının en tepeseinde bu object sınıfının bulunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıfın doğrudan taban sınıfları bir demet halinde type sınıfının __bases__ örnek özniteliği ile elde edilebilir. - Yani biz bir sınıf ismi ile __bases__ özniteliğini kullanırsak o sınıfın doğrudan taban sınıflarını elde ederiz. Örneğin: - - >>> class A: - ... pass - ... - >>> class B(A): - ... pass - ... - >>> B.__bases__ - (,) - >>> class C: - ... pass - ... - >>> class D(C, B): - ... pass - ... - >>> D.__bases__ - (, ) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Daha önceden de belirttiğimiz gibi NYPT'de taban sınıftaki bir metodun türemiş sınıfta aynı isimle yeniden yazılmasına - "taban sınıftaki metodun türemiş sınıfta override edilmesi" denilmektedir. Override etme durumunda MRO sırasına göre - türemiş sınıf türünden bir değişken ile aynı isimli metot çağrıldığında türemiş sınıfın metodu çağrılır. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - - b = B() - b.foo() # B.foo çağrılır - - Bu örnekte biz b değişkeni ile A'daki foo metodunu çağırmak istersek alternatif çağrım sentaksını kullanabiliriz. Örneğin: - - A.foo(b) - - NYPT'de taban sınıftaki metodu override eden programcı o metoda birtakım eklemeler yapmak isteyebilir. Bu nedenle - bazı eklemeler sonrasında taban sınıftaki metodu da çağırmak ister. Daha önceden de bunun self parametresi ile yapılamayacağını - görmüştük: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - self.foo() # dikkat! özyineleme - - Burada B'deki foo metodu self.foo() çağrısı ile A'daki foo metodunu çağırmamaktadır. Kendi kendini çağırmaktadır. - O halde burada da alternatif çağrının kullanılması gerekir: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - A.foo(self) - - Biz bu durumu daha önce incelemiştik. Türemiş sınıfın __init__ metodunun taban sınıfın __init__ metodunu çağırmak zorunda - olduğunu anımsayınız. Örneğin: - - class A: - def __init__(self): - self.x = 10 - - class B(A): - def __init__(self): - A.__init__(self) - self.y = 20 - - b = B() - print(b.x, b.y) - - Baklava tarzı türetmelerdeki önemli problemden de bahsetmiştik: - - A - B C - D - - Burada B sınıfını yazan kişi mecburen B sınıfının __init__ metodu içerisinde A sınıfının __init__ metodunu çağıracaktır. - Aynı durum C sınıfını yazan kişi için de geçerlidir. D sınıfını yazan kişi de mecburen B ve C sınıflarının __init__ - metodunu çağıracaktır. Bu durumda A sınıfının __init__ metodu iki kere çağrılmış olur bu da programın çökmesine ya da - yanlış çalışmasına yol açabilir. Aşağıdkai örnekle bunu yendien test ediniz: - - class A: - def __init__(self): - print('A.__init__') - - class B(A): - def __init__(self): - A.__init__(self) - print('B.__init__') - - class C(A): - def __init__(self): - A.__init__(self) - print('C.__init__') - - class D(B, C) : - def __init__(self): - B.__init__(self) - C.__init__(self) - - d = D() - - Burada ekrana şunlar çıkacaktır: - - A.__init__ - B.__init__ - A.__init__ - C.__init__ - - İşte bu tür baklava biçiminde yapılan türetmelerde tepedeki taban sınıfın __init__ metodunun iki kere çağrılmasını - engellemek için super isimli bir fonksiyon bulundurulmuştur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - super fonksiyon built-in bir fonksiyondur. İki kullanım biçimi vardır: - - super(sınıf_ismi, değişken_ismi) - super() - - Yani super fonksiyonunu biz iki argümanla ya da argümansız çağırabiliriz. İki argümanla çağrım genel bir kullanım sunmaktadır. - super fonksiyonunun birinci parametresi bir sınıf ismini (yani bir sınıfın type referansını), ikinci parametresi ise bir sınıf - türünden değişkeni alır. super fonksiyonu bir "proxy" nesne geri döndürmektedir. super fonksiyonun geri dönüş değeri ile bir - metot çağrılırsa sanki ikinci argümanla belirtilmiş değişkenle o metot çağrılmış gibi bir etki oluşmaktadır Örneğin: - - d = D() - super(D, d).foo() - - Burada aslında oluşan etki d.foo() gibi bir etkidir. Ancak bu etki oluşturulurken isim araması d değişkenin ilşkin olduğu - sınıfın MRO sırasında D sınıfından sonraki sınıftan başlatılmaktadır. Örneğin aşağıdaki gibi bir türetme şeması olsun: - - A - B - C - D - - D sınıfı türünden bir nesne yaratılım: - - d = D() - - Burada d değişkeninin ilişkin olduğiu sınıfın MRO sırası şöyledir: D, B, C, A, object. Şimdi şöyle bir çağrı yapmış olalım: - - super(C, d).foo() - - Burada aslında d.foo() çağrısı yapılmış gibidir. Ancak foo metodunun aranması B sınıfından başlatılacaktır. Çünkü d - değişkeninin işişkin olduğu sınıf D sınıfıdır. D'nin MRO sırası D, C, B, A biçimindedir. Bu sırada C'den sonraki sınıf - B sınıfıdır. O halde arama B'den bşlatılacaktır. Yani eğer B'nin foo metodu varsa B'ninki yoksa A'nınki çağrılacaktır. - A'da da yoksa exception oluşacaktır. - - Aşağıdaki örnekte B sınıfında foo olmadığı için A sınıfındaki foo çağrılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('A.foo') - -class B(A): - pass - -class C(B): - def foo(self): - print('C.foo') - -class D(C): - def foo(self): - print('D.foo') - -d = D() - -super(B, d).foo() # A.foo çağrılır - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi super fonksiyonu ne işe yaramaktadır? Kabaca elimizde bir sınıf türünden bir değişken varsa biz de bu değişkenle - kendi sınıfımızın değil taban sınıfın metodunu çağırmak istiyorsak super fonksiyonundan faydalanabiliriz. Örneğin: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - - b = B() - - Şimdi biz b.foo() biçiminde bir çağrı yaparsak B sınıfının foo metodu çağrılır. Pekiyi biz B sınıfını bypass ederek A - sınıfının foo metodunu nasıl çağırmalıyız. Yöntemlerden biri şöyle olabilir: - - A.foo(b) - - Burada açıkça A sınıfının foo metodu b ile çağrılmıştır. Aynı şeyi super fonksiyonu ile de yapabilirdik: - - super(B, b).foo() - - super fonksiyonun birinci parametresi isim aramasının başlatılacağı sınıf üzerinde etkili olmaktadır. Şöyle ki: İsim araması - b değişkeninin ilişkin olduğu sınıfın MRO sırasında B sınıfından sonraki sınıflan yapılır. super fonksiyonun ikinci - parametresinin çağrılacak metotta kullanılacak nesneyi ve aynı zamanda da söz konusu MRO sırasını ilişkin olduğu Sınıfı - belirttiğine dikkat ediniz. Şimdi C sınıfı B'den, B sınıfı da A'dan türetilmiş olsun: - - A - B - C - - Bu sınıfların foo metotların olduğunu varsayalım: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - - class C(B): - def foo(self): - print('C.foo') - - c = C() - - Burada c değişkeni ile A'nın foo'su şöyle çağrılabilir: - - super(B, c).foo() - - Burada c değişkeni C sınıfı türündendir. C'nin MRO sırası C, B, A biçimindedir. Bu durumda super(B, c).foo() çağrısında C'nin - MRO sırasında B'den sonraki sınıfı A'dır. - -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('A.foo') - -class B(A): - def foo(self): - print('B.foo') - -class C(B): - def foo(self): - print('C.foo') - -c = C() -c.foo() # C.foo -super(C, c).foo() # B.foo -super(B, c).foo() # A.foo - -#------------------------------------------------------------------------------------------------------------------------ - super fonksiyonu özellikle türemiş sınıf metodunda taban sınıfın aynı isimli metodunu çağırmak için kullanılmaktadır. - Örneğin aşağıdaki gibi bir türetme şeması olsum: - - A - B - C - D - - Bu sınıfların şöyle yazıldığını varsayalım: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - super(B, self).foo() - - class C(B): - def foo(self): - print('C.foo') - super(C, self).foo() - - class D(C): - def foo(self): - print('D.foo') - super(D, self).foo() - - Burada her foo metodu bir üst sınıftaki foo metodunu çağırmaktadır. Çağrının şöyle başlatıldığını varsayalım: - - d = D() - d.foo() - - Burada D sınıfının foo metodu çağrılacaktır. D sınıfındaki foo metodunda super(D, self).foo() çağırısı yapılmıştır. - Buradaki self D sınıfı türündendir. D sınııfnın MRO sırası D, C, B, A biçimindedir. O halde D sınıfındaki self parametresi - aslında D sınıfı türündendir. D sınının MRO sırasında D sınıfından sonraki sınıf C'dir. Bu durumda sanki d nesnesi ile - C'nin foo metodu çağrılıyor gibi bir etki oluşturmuştur. Aynı durum diğer fo metotları için de benzer biçimdedir. - Akrada şunlar görülecektir: - - D.foo - C.foo - B.foo - A.foo -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def foo(self): - print('A.foo') - -class B(A): - def foo(self): - print('B.foo') - super(B, self).foo() - -class C(B): - def foo(self): - print('C.foo') - super(C, self).foo() - -class D(C): - def foo(self): - print('D.foo') - super(D, self).foo() - -d = D() -d.foo() - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi mademki aslında taban sınıf ismini belirterek taban sınıfın metodunu çağırabiliyoruz bu durumda super fonksiyonuna ne - gerek vardır? Yani yukarıdaki örneği super fonksiyonunu çağırmadan da aşağıdaki gibi yazabilirdik: - - class A: - def foo(self): - print('A.foo') - - class B(A): - def foo(self): - print('B.foo') - A.foo(self) - - class C(B): - def foo(self): - print('C.foo') - B.foo(self) - - class D(C): - def foo(self): - print('D.foo') - C.foo(self) - - d = D() - d.foo() - - İşte bu biçimde taban sınıf ismi ile çağrı yapıldığında MRO sırası dikkate alınmamaktadır. super fonksiyonu MRO sırasını - dikkate aldığı için önemli bir işleve sahiptir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi aslında super fonksiyonun en önemli işlevi baklava şeklindeki çoklu türetme durumunda - baklavanın tepesindeki taban sınıfın __init__ metodunun birden fazla kez çağrılmasını engellemek içindir. Anımsanacağı - gibi super(X, r).foo() gibi bir çağırmada foo metodunun aranması r değişkeninin ilişkin olduğu sınıfın MRO sırasında - X sınıfından sonraki sınıftan başlatılmaktadır. O halde örneğin: - - class X(Y): - def __init__(self): - super(X, self).__init__() - - Burada self'in ilişkin olduğu sınıfın MRO sırasında X'ten sonraki sınıfan başlanarak __init__ metodu aranacaktır. - - O halde baklava biçimindeki türetme şemasında "eğer her sınıfın __init__ metodunda super fonksiyonunun birinci parametresi - kendi sınıf ismi, ikinci parametresi self olacak biçimde __init__ metodu çağrılırsa bu durumda baklava biçiminde türetme - olsa bile her sınıfın __init__ metodu toplamda bir kez çağrılmış olacaktır. Örneğin: - - - - class A: - def __init__(self): - super(A, self).__init__ - print('A.__init__') - - class B(A): - def __init__(self): - super(B, self).__init__() - print('B.__init__') - - class C(A): - def __init__(self): - super(C, self).__init__() - print('C.__init__') - - class D(B, C): - def __init__(self): - super(D, self).__init__() - print('D.__init__') - - d = D() - - Burada D'nin MRO sırtası D, B, C, A biçimindedir. O halde D() biçiminde nesne yaratıldığında D'nin __init__ metodundaki self - D sınıfı türündendir: - - class D(B, C): - def __init__(self): - super(D, self).__init__() # D'nin MRO sırasında D'den sonraki sınıf B'dir, o halde B sınıfın __init__ metodu çağrılır - print('D.__init__') - - Burada super(d, self).__init__ ile self D sınıfı türünden olduğu için ve D'nin MRO sırasında D'den sonra gelen sınıf B olduğu - için B'nin__init__ metodu çağrılacaktır B'nin __init__ metodu ise şöyledir: - - class B(A): - def __init__(self): - super(B, self).__init__() # self hala D sınıfı türünden D'nin MRO sırasında göre B'den sonraki sınıf C'dir - print('B.__init__') - - Burada da self yine D sınıfı türündendir. D'nin MRO sırasına göre B'den sonraki sınıf C olduğuna göre aslında - super(B, self).__init__ çağrısı ile C sınıfının __init__ metodu çağırılır. C sınıfının __init__ metodu şöyledir: - - class C(A): - def __init__(self): - super(C, self).__init__() # self hala D türünden, D'nin MRO sırasına göre C'den sonraki sınıf A'dır - print('C.__init__') - - Burada da self hala D türündendir. D'nin MRO sırasına göre C'den sonraki sınıf A olduğu için aslında A'nın __init__ - metodu çağrılacaktır. Ekranda şunlar görülecektir: - - A.__init__ - C.__init__ - B.__init__ - D.__init__ - - Biz bir sınıf yazarken bizden çoklu türetme yapılıp yapılmayacağını bilmedeiğimize göre taban sınıf ismi ile değil - super fonksiyonu ile sıradaki sınıfın __init__ metodunu çağırmalıyız. İyi teknik __init__ metodu çağrılırken taban sınıf - isminin değil super fonksiyonunun kullanılmasıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi biz her zaman taban sınıfın __init__ metodunu taban sınıf ismiyle değil super fonksiyonuyla - çağırmalıyız. Bu durumda baklava biçiminde bir çoklu türetme olsa bile her zaman taban sınıfların __init__ metotları bir - kez çağrılmış olur. Her sınıfın __init__ metodunda taban sınıfın __init__ metodunu super fonksiyonu ile şöyle çağırmalıyız: - - super(kendi_sınıfımızın_ismi, self).__init__(...) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi türetme şemasının en yukarısında her zaman object sınıfı olduğuna göre biz object sınıfının da __init__ metodunu - çağırmalı mıyız? Örneğin: - - class Sample: - def __init__(self): - super(Sample, self).__init__() - # ... - - object sınıfın __init__ metodunun içinib boş olduğunu varsayabilirsiniz. Bu durumda programcının object sınıfının - __init__ metodunu çağırması gerekmez. Ancak çğırmasında de bir sakınca yoktur. - - Anımsacağı gibi biz bir sınıfı başka bir sınıftan türetmiş olmasak bile onun object sınıfından türüetilmiş olduğu varsayılıyordu. - Bu durumda eğer biz sınıfımız için __init__ metodunu yazmamışsak object sınıfındaki __init__ metodu çalıştırlacaktır. Örneğin: - - class Sample: - pass - - s = Sample() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz yukarıda genel olarak türemiş sınıfın __init__ metodunun taban sınıfın __init__ metodunu super fonksiyonuyla çağırması - gerektiğini belirtmiştik. Sonra da taban object sınıfı için __init__ metodunun çağrılmasına gerek olmadığını da ifade - etmiştik. Pekiyi bu durumda aşağıdaki gibi bir çoklu türetmede ne olacaktır: - - A B - C - - Burada C'nin MRO sırası C, A, B biçimindedir. Eğer A'nın object sınıfından türetilmiş olduğu fikriyle A'nın __init__ - metodunda super çağrısı yapılmazsa B'nin __init__ metodu çağrılmayacaktır. O zaman A sınıfını oluşturan kişi object - sınıfının __init__ metodunu super fonksiyonu ile çağırmalı mıdır? Örneğin: - - class A: - def __init__(self): - print('A.__init__') - - class B: - def __init__(self): - print('B.__init__') - - class C(B, A): - def __init__(self): - super(C, self).__init__() - print('C.__init__') - - c = C() - - Burada C'nin MRO sırası C, A, B olduğu için C ve A'nin __init__ metotları çağrılacaktır ancak B'ninki çağrılmayacaktır. - Genellikle object sınıfından türetme yapıldığında (default durum) programcılar super çağrısıyla __init__ metotlarını - çağırmazlar. Bu durumda da çoklu türetmede sorunlar çıkabilir. Pekiyi çözüm nedir? - - - Aslında bir sınıfı yazan kişi o sınıfın çoklu türetmede kullanılıp kullanılmayacağını belirleyip bunu dokümantasyonda - belirtmelidir. Yani sınıfı yazan kişinin sınıfın çoklu türetmede kullanılıp kullanılmayacağını baştan öngörebilmesi gerekir. - Eğer programcının sınıfı çoklu türetmeyi destekleyecekse sınıf object sınıfından türetilmiş olsa bile super çağrısıyla MRO sırasına - göre sıradaki sınıfın __init__ metodunu çağırmalıdır. Eğer sınıfı çoklu türetmeyi desteklemiyorsa bu durumda böyle bir çağrı - yapmasına gerek yoktur. Örneğin: - - class A: - def __init__(self): - super(A, self).__init__() - print('A.__init__') - - class B: - def __init__(self): - super(B, self).__init__() - print('B.__init__') - - class C(A, B): - def __init__(self): - super(C, self).__init__() - print('C.__init__') - - c = C() - - - Çoklu türetme yapacak kişi taban sınıflarının çoklu türetmeyi destekleyip desteklemediğine dikkat etmelidir. Eğer taban - sınıfları çoklu türetmeyi desteklemiyorsa taban sınıfın __init__ metodunu super çağrısıyla değil isimsel olarak yapabilir. - Tabii bu durumda baklava biçiminde türetme yapılmışsa yine sorun oluşabilecektir. Örneğin: - - class A: - def __init__(self): - print('A.__init__') - - class B: - def __init__(self): - print('B.__init__') - - class C(A, B): - def __init__(self): - A.__init__(self) - B.__init__(self) - print('C.__init__') - - c = C() - - Eğer sınıfın dokümantasyonunda özellikle çoklu türetme desteği için bir şey söylenmemişse sınıfın çoklu türetmeyi - desteklemediği sonucu çıkartılmalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 45. Ders 03/10/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Çoklu türetmede diğer bir sorun da çoklu türetilmiş sınıfın __init__ metotlarının MRO sırasına göre diğer sınıfların __init__ - metotlarına iletilmesidir. Burada genel uygulama çoklu türetmeyi destekleyen sınıfların __init__ metotlarının *args ve **kwargs - parametrelerini bulundurmaları ve bu parametreleri *'lı bir biçimde super fonksiyonunda kullanmalarıdır. Böylece aktarım - başarıyla yapılabilmektedir. - - Aşağıdaki örnekte C sınıfı A ve B'den çoklu türetilmiştir. C sınıfının __init__ metodu kendisi için değeri alıp MRO - sırasına göre sonraki sınıfın __init__ metodunu çağırmıştır. Buradaki * ve ** parametreleri ve argümanları pass etme - (forwarding) amacıyla kullanılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def __init__(self, a, *args, **kwargs): - super(A, self).__init__(*args, **kwargs) - self.a = a - print('A.__init__') - -class B: - def __init__(self, b, *args, **kwargs): - super(B, self).__init__(*args, **kwargs) - self.b = b - print('B.__init__') - -class C(A, B): - def __init__(self, a, b, c): - super(C, self).__init__(a, b) - self.c = c - -c = C(10, 20, 30) - -print(c.a, c.b, c.c) - -#------------------------------------------------------------------------------------------------------------------------ - super fonksiyonu parametresiz de kullanılabilir. Bu durumda fonksiyonun birinci parametresi içinde bulunulan sınıfın - ismi olarak, ikinci parametresi self olarak geçilmiş kabul edilir. Tabii bu kullanım yalnıca metotlar içerisinde yapılabilir. - Örneğin A sınıfının __init__ metodunda şöyle bir çağırma yapılmış olsun: - - super().__init__() - - Bu çağrı şununla eşdeğerdir: - - super(A, self).__init__() -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def __init__(self): - print('A.__init__') - -class B(A): - def __init__(self): - super().__init__() # super(B, self).__init__() - print('B.__init__') - -class C(B): - def __init__(self): - super().__init__() # super(C, self).__init__() - print('C.__init__') - -c = C() - -#------------------------------------------------------------------------------------------------------------------------ - NYPT'nin önemli bir prensibi "kapsülleme (encapsulation)" denilen prensiptir. Kapsülleme bir kavramın bir sınıfla temsil - edilmesi ve sınıfın dışarıyı ilgilendirmeyen, iç işleyişe ilişkin olan öğelerinin dış dünyadan gizlenmesi anlamına - gelmektedir. Bu gizleme hem algısal bir açıklık sağlamakta hem de yanlış kullanımları engellemektedir. Aslında kapsülleme - dış dünyada da sıklıkla karşılaştığımız bir olgudur. Örneğin bir otomobilin pek çok aksamı kaput içerisinde gizlenmiştir. - Yalnızca kullanıcıyı ilgilendiren kısımları görünür hale getirilmiştir. Bir televizyon için de aynı durum söz konusudur. - Televizyonu kullanabilmemiz için onun karmaşık yapısını bilmemize gerek yoktur. - - C++, Java ve C# gibi dillerde kapsülleme için sınıfların "public", "private", "protected" gibi bölümleri vardır. Sınıfı - yazan kişi birtakım elemanları private bölüme yerleştirire o öğelere dışarıdan erişilemez. Sınıfın veri elemanlarının - (örnek dözniteliklerinin) dış dünyadan gizlenmesine ise "veri elemanlarının gizlenmesi (data hiding)" prensibi denilmektedir. - Sınıfın veri elemanları iç işleyişe ilişkindir. Programcılar da C++, Java ve C# gibi dillerde veri elemanlarını sınıfın private - bölümine yerleştirerek dış dünyadan gizlerler. - - Ancak Python'da bu anlamda bir gizleme mekanizması yoktur. Yani C++, Java ve C# gibi dillerdeki "public", "protected", - private" gibi erişim belirten kavramlar Python'da bulunmamaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da her ne kadar C++, Java ve C# gibi dillerde bulunan "public", "private", "protcted" bölümler olmasa da bir - sınıfın bir metodunun ya da nesnenin bir özniteliğinin dış dünyadan gizlenmesi isimsel biçimde sağlanmaya çalışılmıştır. - Öyle ki eğer bir sınıfın ya da nesnenin özniteliğinin ismi '_' ile başlatılırsa bu durum diğer dillerdeki private etkisi - yaratmaktadır. Ancak bu etki yalnızca bir tavsiye oluşturmaktadır. Başı '_' ile başlayan isimler için yorumlayıcı tarafından - erişimde bir denetleme yapılmamaktadır. Başka bir deyişle biz Python'da sınıfın ya da nesnenin özniteliğinin '_' ile başladığını - gördüğümüzde "bu özniteliklere dışarıdan (sınıfın daşından) erişilmesinin istenmediği" anlamını çıkartmalıyız. Ancak yine - de biz istersek bu özniteliklere dışarıdan erişebiliriz. Burada bir zorlayıcılığın olmadığna yalnızca yazım biçimiyle sınıf kullananlara - bir tavsiyede bulunulduğuna dikkat ediniz. Örneğin: - - class Sample: - def do_someting_important(self): - # .... - self._foo() - # .... - self._bar() - # .... - self._tar() - #.... - - def _foo(self): - pass - - def _bar(self): - pass - - def _tar(self): - pass - - s = Sample() - - s.do_someting_important() - - Burada sınıfın _foo, _bar ve _tar metotlarının dışarıdan çağrılması istenmemektedir. Bu metotlar yalnızca sınıf içerisindeki - diğer metotlardan çağrılmaktadır. Tabii biz istersek gerçekten yine de onları dışarıdan kullanabiliriz. Bunun için yorumlayıcı - tarafından zorlayıcı bir denetim uygulanmamaktadır. Örneğin: - - s._foo() - - Aynı durum nesnenin öznitelikleri için de geçerlidir. Örneğin: - - import math - - class Circle: - def __init__(self, radius): - self.radius = radius - self._area = radius * radius * math.pi - - # ... - - c = Circle(10) - - print(c.radius) - - Burada sınıfın radius örnek özniteliğine dışarıdan erişebiliriz ancak _area örnek özniteliğine dışarıdan erişmemiz - istenmemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfın ve nesnenin özniteliklerinin dış dünyadan gizlenmesi için diğer bir yöntem de isimlerin başına '__' (iki alt tire) - getirmektir. Bir isim iki alt tire ile başlanarak isimlendirilmişse bu durum bu ismin "dışarıdan kullanımının daha katı bir biçimde - istenmediği" anlamına gelmektedir. İki alt tire ile verilen isimlere gerçekten dışarıdan erişilemez. Ancak _sınıfismi__ - öneki ile erişilebilir. Yani başka bir deyişle Sample sınıfının __xxx elemanına biz s.__xxx gibi bir ifadeyle erişemeyiz. - Ancak s._Sample__xxx ismiyle erişebiliriz. Bunun amacı kişilerin ilgili özniteliğe yanlışlıkla erişmelerinin engellenmesidir. - Mutlak anlamda erişmelerinin engellenmesi değildir. Tabii başı iki alt tire ile başlayan isimlere sınıf içerisinden yine iki - alt tire ile erişebiliriz. - - Örneğin: - - class Sample: - def do_someting_important(self): - # .... - self.__foo() - # .... - self.__bar() - # .... - self.__tar() - #.... - - def __foo(self): - pass - - def __bar(self): - pass - - def __tar(self): - pass - - s = Sample() - - s.do_someting_important() - s.__foo() # error! - s._Sample__foo() # geçerli, ama kötü teknik - - Nesnenin özniteliğini iki alt tire ile başlatarak isimlendirirsek yine bu özniteliklere dışarıdan erişemeyiz. Bunlara - dışarıdan erişim yine _sınıfismi__ önekiyle yapılır. Örneğin: - - import math - - class Circle: - def __init__(self, radius): - self.radius = radius - self.__area = math.pi * radius * radius - - def disp(self): - print(self.radius, self.__area) - #... - - c = Circle(1) - - print(c._Circle__area) # geçerli - print(c.__area) #error! - - Başka bir deyişle biz sınıf içerisinde bir özniteliği ya da bir metodu iki alt tire ile isimlendirirsek aslında - yorumlayıcı bu özniteliği ya da metodu _sınıfismi__ öneki ile isimlendirmektedir. Biz sınıf içerisinde iki alt tireli - isimlere iki alt tire ile erişebilmeteyiz. Ancak dışarıdan iki alt tire ile erişemeyiz. Dışarıdan ancak _sınıfismi__ - öneki ile erişebiliriz. - - Ancak iki altireli bir örnek özniteliği sınıfın içerisinde değil de dışarıda oluşturulmuşsa bu durumda erişim iki alt tireli - isimle yapılabilmektedir. Örneğin: - - class Sample: - def __init__(self): - self.__a = 10 - - s = Sample() - s.__b = 20 - print(s.__b) # dışarıdan erişilebilir - print(s.__a) # dışarıdan erişilemez! - - İsimlerin önüne tek alt tire getirilmesine "Python Language Specification" dokümanında bir atıfta bulunulmamıştır. Ancak - isimlerin başına iki alt tire getirilmesi konusu yukarıda açıklandığı gibi "Python Language Specification" dokümanında (6.2.1) - belirtilmektedir. - - Sınıfların __xxx__ biçiminde isimlendirilmiş elemanlarına "dunder" elemanlar dendiğini belirtmiştik. Bu dunder elemanlar - semantik olarak başı iki alt tire ile başlayan isimler gibi değerlendirilmemektedir. Yani iki alt tire ile başlayan - isimlerin sonunda da iki alt tire varsa bu isimlere dışarıdan aynı biçimde erişilebilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi biz dışarıdan kullanılmasını istemediğimiz isimlerin başına tek alt tire mi yoksa iki alt tire mi getirmeliyiz? - Bu durum tamamen bizim isteğimize bağlıdır. Eğer biz "dışarıdan erişme" tavsiyesini vurgulamak istiyorsak ik alt tire - kullanabiliriz. Ancak programcıların çoğu tek alt tireyi tercih etmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pek çok teorisyene göre bir dilin "nesne yönelimli" olması için dilin şu üç özelliği destekliyor olması gerekmektedir: - - - Sınıf kavramı - - Türetme kavramı - - Çokbiçimlilik (polymophism) - - Eğer bir dilde sınıflar olduğu halde çokbiçimlilik yoksa böyle dillere "nesne tabanlı (object based)" diller denilmektedir. - Başka bir deyişle dilin nesne yönelimli olabilmesi için "çokbiçimli mekanizmaya" sahip olması gerekmektedir. C++, Java, - C# gibi dillerde çokbiçimli mekanizma bulunmaktadır. Dolayısıyla bu diller "nesne yönelimli" dillerdir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - NYPT'de çokbiçimlilik (polymorphism) biyolojiden aktarılmış bir terimdir. Biyolojide çokbiçimlilik "canlıların çeşitli - doku ve organlarının temel işlevleri aynı kalmak üzere türlere göre farklılıklar göstermesi" anlamına gelmektedir. Yani - örneğin kulak pek çok canlıda vardır. Temel işlevi duymaktır. Ancak her canlı kendine göre bir kulak yapısına sahiptir. - Yani kendine göre duymaktadır. Örneğin köpekler daha tiz sesleri duyabilirler. Benzer biçimde göz pek çok canlıda vardır. - Temel işlevi görmektir. Ancak her canlıda göz o canlıya göre değişik bir evrim geçirerek farklılaşmıştır. Kartal çok - keskin görebilmektedir. Bazı hayvanlar dünyayı siyah beyaz görmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - NYPT'de bir eylem temel işlevi aynı olmak üzere sınıflar arasında farklılıklar gösteriyorsa bu eyleme "çokbiçimli" eylem - denilmektedir. Örneğin A, B, C, D, E sınıflarının disp isimli metotları olsun. Bu metotlar kendi sınıflarının birtakım - elemanlarını kendilerine özgü biçimde ekrana yazdırıyor olsun. Burada disp metodu "çokbiçimli" bir metottur. Çünkü çeşitli - sınıflarda bu metot vardır. Temel işlevi sınıf hakkında bilgileri ekrana yazdırmaktır. Ancak her sınıfın disp metodu kendine - özgüdür ve temel işlevi aynı olmasına karşın birbirlerinden farklıdır. Örneğin bir personel takip programında iş yerinde çalışan - kişilerin görevlerine göre Worker, Manager, Executive, SalesPerson gibi farklı sınıflarla temsil edildiğini düşünelim. Bu - sınıfların hepsinde kişilerin maaşlarını hesaplayan calculate_salary isimli bir metot olsun. Buradaki maaş hesaplama eylemi - çokbiçimlidir. Bu sınıfların hepsinde vardır ama her sınıfın maaş hesabı kendine özgü bir biçimde yapılmaktadır. Çokbiçimli - metotlar farklı sınıflarda aynı isimle bulunurlar. Bunların yaptıkları işler ana hatlarıyla aynıdır ancak o sınıfa özgü - farklılıklar içermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - C++, Java, C# gibi dillerde çokbiçimli mekanizma "türetme yoluyla" sağlanmaktadır. Bu dillerde tipik olarak bir taban - sınıf oluşturulur. O taban sınıftan sınıflar türetilir. Taban sınıftaki bir metot türemiş sınıfta aynı isimle yeniden - yazılır. Buna taban sınıftaki metodun türemiş sınıfta "override" edilmesi denilmektedir. Ancak Python dinamik tür sistemine - sahip olduğu için zaten doğuştan çokbiçimlidir. Python'da zaten isim araması programın çalışma zamanında yapıldığı için - çokbiçimli mekaznizma türetmeye bağlı değildir. Bu nedenle Python'da çokbiçimli mekanizmayı oluşturmak için özel bir - özel bir sentaks da yoktur. - - Çokbiçimlilik NYPT'de "türden bağımsız" kod paraçalarının (fonksiyonların metotların) oluşturulmasına olanak sağlamaktadır. - Çokbiçimlilik sayesinde birbirine benzeyen ancak farklı olan nesnelere "sanki aynı nesneymiş" muamalesi yapabilmekteyiz. - - Aşağıdaki örnekte "duy" metodu çokbiçimli bir metottur. Farklı sınıflarda aynı isimle bulunmaktadır. Ancak bu duy metodunun - o sınıflara özgü bir gerçekleştirimi vardır. Örneğimizde foo fonksiyonu bir canlıyı alır. O canlının hangi canlı olduğunu - bilmeden onun duyma eyleminden hareketle ona özgü eylemleri gerçekleştirmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Kedi: - def duy(self): - print('kedi duyuyor') - -class Köpek: - def duy(self): - print('köpek duyuyor') - -class At: - def duy(self): - print('at duyuyor') - -class Aslan: - def duy(self): - print('aslan duyuyor') - -class İnsan: - def duy(self): - print('insan duyuyor') - -def foo(canlı): - print('--------') - canlı.duy() - #.... - canlı.duy() - #... - canlı.duy() - -kedi = Kedi() -köpek = Köpek() -at = At() -aslan = Aslan() -insan = İnsan() - -foo(kedi) -foo(köpek) -foo(at) -foo(aslan) -foo(insan) - -#------------------------------------------------------------------------------------------------------------------------ - 46. Ders 05/10/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi çokbiçimlilikten en önemli amaç "türden bağımsız kod parçalarının" oluşturulmasıdır. - Çokbiçimlilik biribirine benzeyen ancak farklı olan nesnelerin sanki aynı nesneymiş gibi işleme sokulmasına olanak sağlamaktadır. - Bu sayede biz programımıza yeni öğeler eklediğimizde program üzerinde daha az değişiklik yaparız. - - Örneğin bir Tetris oyunu yazacak olalım. Bu oyunda çelşitli şekiller düşmektedir. Bu şekiller duruma göre sola, sağa - hareket ettirilmekte ve döndürülebilmektedir. Şeklin sola, sağa hareket ettrilmesi, düşmesi ve döndürülmesi "çokbiçimli - (polymoprhic)" eylemlerdir. Yani bu eylemler bu şekil sınıflarının hepsinde vardır. Ancak her şekil kendine göre bu - eylemleri yerine getirmektedir. - - Aşağıdaki örnekte böyle bir Tetris oyunu mantıksal olarak simüle edilmeye çalışılmıştır. Burada biz Microsoft Windows'a - özgü msvcrt modülünü kullandık. Bu modül standart Python kütüphanesinde olmasına karşın yalnızca Windows sistemlerinde - kullanılabilmektedir. Ayrıca maalesef bu modül Spyder konsolunda çalışmamaktadır. Bu nedenle bu örneği Windows konsoluna - geçerek çalıştırınız. msvcrt modülündeki kbhit isimli fonksiyon bekleme yapmaz. O anda klavyeden bir tuşa basılıp - basılmadığına bakar. Eğer klavyeden bir tuşa basılmışsa True değerini, basılmamışsa False değerini geri döndürür. - Böylece hiç bekleme yapmadan o anda klavyeden bir tuşa basılıp basılmadığı anlaşılabilmektedir. msvcrt modülündeki - getch fonksiyonu ise ENTER tuşuna gereksinim duymadan tuşa basılır basılmaz tuşu okur. Bu fonksiyon basılan tuşa ilişkin - bir byte nesnesi vermektedir. O halde hiç bekeleme yapmadan tuş okumak için aşağıdaki gibi bir yol izlenebilir: - - While True: - ... - if msvcrt.kbhit(): - ch = msvcrt.getch() - ... - - UNIX/Linux sistemlerinde ENTER tuşuna gereksinim duymadan tuşa basar basmaz klavye okuması yapmak için Python standart - kütüphanesinde basit bir fonksiyon bulunmamaktadır. curses kütüphanesi bu amaçla kullanılabilir. Ancak kullanımı biraz - daha zordur. Başkaları tarafındna yazılmış olan "getch" modülü pip programıyle indirilip kurulabilir. Örneğin: - - pip install getch -#------------------------------------------------------------------------------------------------------------------------ - -import random -import time -import msvcrt - -class Shape: - def __init__(self): - pass - -class BarShape(Shape): - def __init__(self): - super().__init__() - - def move_down(self): - print('BarShape moves down') - - def move_left(self): - print('<>') - - def move_right(self): - print('<>') - - def rotate(self): - print('<>') - -class SquareShape(Shape): - def __init__(self): - super().__init__() - - def move_down(self): - print('SquareShape moves down') - - def move_left(self): - print('<>') - - def move_right(self): - print('<>') - - def rotate(self): - print('<>') - -class ZShape(Shape): - def __init__(self): - super().__init__() - - def move_down(self): - print('ZShape moves down') - - def move_left(self): - print('<>') - - def move_right(self): - print('<>') - - def rotate(self): - print('<>') - -class LShape(Shape): - def __init__(self): - super().__init__() - - def move_down(self): - print('LShape moves down') - - def move_left(self): - print('<>') - - def move_right(self): - print('<>') - - def rotate(self): - print('<>') - -class TShape(Shape): - def __init__(self): - super().__init__() - - def move_down(self): - print('TShape moves down') - - def move_left(self): - print('<>') - - def move_right(self): - print('<>') - - def rotate(self): - print('<>') - - -class Tetris: - def __init__(self): - pass - - def get_random_shape(self): - shapes = [BarShape, TShape, ZShape, LShape, SquareShape] - shape_type = random.choice(shapes) - return shape_type() - - def run(self): - while True: - shape = self.get_random_shape() - for _ in range(20): - shape.move_down() - if msvcrt.kbhit(): - ch = msvcrt.getch() - if ch == b'a': - shape.move_left() - elif ch == b's': - shape.move_right() - elif ch == b'd': - shape.rotate() - elif ch == b'q': - return - time.sleep(0.5) - -tetris = Tetris() -tetris.run() - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf türünden nesneyi str türüne dönüştürebiliriz. Böyle bir dönüştürme için ilgili sınıfın __str__ isimli bir - metodunun bulunuyor olması gerekir. s bir sınıf türünden değişken olmak üzere str(s) işlemi tamamen s.__str__() ile - aynı anlamdadır. Programcı sınıfın __str__ metodunu bir str nesnesiyle geri döndürmelidir. - - Aslında print fonksiyonu yalnızca string'leri bastırmaktadır. Eğer print fonksiyonuna girdiğimiz argüman string değilse - print fonksiyonu onu str türüne dönüştürüp yazıyı ekrana basmaktadır. s string türünden olmayan bir türden olsun. Bu durumda: - - print(s) - - ile - - print(str(s)) - - ya da - - print(s.__str__()) - - tamamen aynı anlamdadır. Örneğin: - - class Point: - def __init__(self, x, y): - self.x = x - self.y = y - - def __str__(self): - return f'({self.x}, {self.y})' - - pt = Point(3, 2) - - print(pt) - print(str(pt)) - print(pt.__str__()) - - Burada biz kendi sınıf nesnemizi print fonksiyonuyla yazdırmak istediğimizde aslında print fonksiyonu onu str türüne - dönüştürüp yazdırmaktadır. str türüne dönüştürme sırasında sınıfımızın __str__ metodu çağrılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __str__(self): - return 'this is a test' - -s = Sample() - -print(s) # this is a test - -k = str(s) # k = s.__str__() -print(k) # this is a test - -#------------------------------------------------------------------------------------------------------------------------ - Tipik olarak programcı kendi sınıfı için __str__ metodunu yazarak o nesnenin tuttuğu bilgileri bu metotta bir yazıya - dönüştürüp bu yazıyla geri dönmektedir. Böylece ilgili türden bir sınıf nesnesi print ile yazdırılmak istendiğinde - ekranda o nesneyi betimleyen bir yazı görünecektir. -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __str__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - -d = Date(4, 10, 2022) -print(d) - -k = Date(11, 10, 2008) -print(k) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi ya sınıfımız için __str__ metodunu yazdırmamışsak bu durumda bu sınıf türünden nesneyi str türüne dönüştürürken - ya da print ederken nasıl bir yazı görünecektir? İşte eğer biz sınıfımız için __str__ metodunu yazmamışsak MRO sırasına - taban sınıflardan birinin __str__ metodu çağrılır. Tüm sınıflar doğrudan ya da dolaylı olarak object sınıfından türetildiğine - göre en kötü olasılıkla object sınıfının __str__ metodu çağrılacaktır. obecjt sınıfının __str__ metodu da nesnenin türünü - ve bellek adresini bir yazı biçiminde vermektedir. Örneğin: - - >>> class Sample: - ... pass - ... - >>> s = Sample() - >>> s.__str__() - '<__main__.Sample object at 0x000002C64EF290C0>' - >>> str(s) - '<__main__.Sample object at 0x000002C64EF290C0>' - >>> print(s) - <__main__.Sample object at 0x000002C64EF290C0> -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların __str__ metotlarının nasıl bir yazı geri döndüreceği konusunda bir standart yoktur. Sınıfları yazanlar nesnenin - içerisindeki bilgilere ilişkin özet bir yazı geri döndürmektedir. Bir nesnenin çok fazla özniteliği olabilir. Bu özniteliklerin - hepsinin __str__ metodunda bir yazı biçiminde geri döndürülmesi uygun olmaz. Genellikle nesneyi temsil eden en önemli - özniteliklerin yazı biçiminde geri verilmesi uygun olur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'un standart kütüphanesindeki sınıflarda genel olarak __str__ metodu zaten yazılmış durumdadır. Böylece biz - standart kütüphanedeki bir sınıf nesnesini doğrudan yazdırırsak o nesneye ilişkin özet bilgileri görüntülemiş oluruz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında biz list gibi, tuple gibi sınıf nesnelerini print ile yazdırdığımızda önce o nesneler string türüne dönüştürülüp - elde edilen yazılar yazdırılmaktadır. Tabii bu sınıflarda sınıflarda __str__ metotları bulunmaktadır. Bir listenin print - ile nasıl yazdırılabildiği hakkında fikir edinmeniz için aşağıdaki örneği veriyoruz. -#------------------------------------------------------------------------------------------------------------------------ - -class MyList: - def __init__(self, *args): - self.args = args - - def __str__(self): - s = '[' - for x in self.args: - if s != '[': - s += ', ' - s += str(x) - s += ']' - - return s - -ml = MyList(1, 2, 3, 4, 5) -print(ml) - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin bir karmaşık sayıları temsil eden bir sınıfın __str__ metodu nesnenin tuttuğu karmaşık sayıyı yazı olarak verebilir. -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real, imag): - self.real = real - self.imag = imag - - def __str__(self): - return f'{self.real}+{self.imag}i' - -z = Complex(3, 2) -print(z) # 3+2i - -#------------------------------------------------------------------------------------------------------------------------ - Programcı bir sınıf yazmışsa onun için bir __str__ metodunu yazması iyi bir tekniktir. Yukarıda da belirttiğimiz gibi - Programcı bu metotlarda genel olarak sınıfın örnek özniteliklerini yazısal olarak kendisinin istediği biçimde verir. - Python'ın standart kütüphanesindeki sınıflarda da hep bu __str__ metotları yazılmış durumdadır. Örneğin: - - >>> import datetime - >>> d = datetime.date(2023, 10, 17) - >>> d.__str__() - '2023-10-17' - >>> str(d) - '2023-10-17' - >>> print(d) - 2023-10-17 - - Burada da görüldüğü gibi datetime modülündeki date sınıfı için __str__ metodu yazılmıştır. Bu metot nesnesinin tuttuğu - tarihi bize bir yazı olarak vermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import datetime - -d = datetime.date(2022, 10, 4) -print(d) # 2022-10-04 - -s = str(d) -print(s) # 2022-10-04 - -#------------------------------------------------------------------------------------------------------------------------ - __str__ metodunun çokbiçimli (polymorphic) bir metot olduğuna dikkat ediniz. Yani her nesnenin bir betimleyici yazısı - vardır. Bu yazı ilgili nesneye göre değişiklik göstermektedir. Bu sayede biz nensnenin türünü bilmesek bile onu print - fonksiyonu ile yazdırabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - __str__ metodunun __repr__ isminde bir benzeri de vardır. __repr__ metodu da tıpkı __str__ netodunda olduğu gibi bir - string ile geri dönmelidir. Pekiyi iki metot arasında ne farklılık vardır? İşte __str__ metodu "daha çok kullanıcıya - yönelik", __repr__ metodu ise "daha çok programcıya yönelik" bir yazı verme iddiasındadır. Tabii bu iki metodun veridiği - yazılar aynı da olabilir. Örneğin Python'ın komut satırında, Spyder'ın komut satırında (IPython) bir değişkeni yazıp ENTER - tuşuna bastığımızda bu komut satırı programları değişken ile __repr__ metodunu çağırıp buradan elde edilen yazıyı bastırmaktadır. - Örneğin: - - >>> class Sample: - ... def __str__(self): - ... return '__str__' - ... - ... def __repr__(self): - ... return '__repr__' - ... - >>> s = Sample() - >>> s - __repr__ - >>> print(s) - __str__ - - Özetle: - - 1) Bir sınıf türünden değişken str türüne dönüştürüldüğünde (ya da print fonksiyonu ile yazdırıldığında) __str__ metodu - çağrılarak onun geri döndürdüğü yazı elde edilmektedir. - - 2) Bir değişkenin ismi komut satırında yazılıp ENTER tuşuna basıldığında __repr__ metodunun geri döndürdüğü yazı - görüntülenmektedir. - - Aslında programcının bu iki metodu ayrı ayrı yazması da gerekmemektedir. Çünkü: - - 1) Eğer sınıfın __repr__ metodu varsa fakat __str__ metodu yoksa str türüne dönüştürmede __repr__ metodu kullanılmaktadır. Aynı - zamanda komut satırında değişken ismi yazılıp ENTER tuşuna basıldığında da __repr__ metodu kullanılılır. Yani biz sınıf için - yalnızca __repr__ metodunu yazarsak hem str türüne dönüştürmelerde hem de komut satırında değişken ismini yazıp ENTER tuşuna - basıldığında bu metot çağrılacaktır. - - 2) Sınıfta hem __str__ hem de __repr__ metodu varsa bu durumda str türüne dönüştürmede __str__ metodu, komut satırında değişken - ismi yazılıp ENTER tuşuna basıldığında __repr__ metodu çağrılmaktadır. - - 3) Sınıfta yalnızca __str__ metodu varsa str türüne dönüştürmede __str__ metodu çağrılır. Komut satırında değişken ismi yazılıp - ENTER tuşuna basıldığında onject sınıfının __repr__ metodu çağrılır. - - Yukarıda da belirtildiği gibi object sınıfında da __repr__ metodu vardır. Bu metot da yine değişkenin ilişkin olduğu sınıfın ismini - ve nesnenin id'sini vermektedir. - - >>> class Sample: - ... def __str__(self): - ... return '__str__' - ... - >>> s = Sample() - >>> print(s) - __str__ - >>> s - <__main__.Sample object at 0x0000026954AC5850> - - O halde bu metotların yazımı konusunda ne tavsiye edilebilir? Eğer programcı tek bir metot yazacaksa __repr__ metodunu yazması daha uygun olur. - Eğer programcı bu iki yazıyı birbirinden ayırmak istiyorsa her iki metodu da yazmalıdır. Python'ın standart kütüphanesindeki sınıflarda genellikle - bu metotlar ayrı ayrı yazılmıştır. Örneğin: - - >>> import datetime - >>> d = datetime.date(2022, 10, 4) - >>> print(d) - 2022-10-04 - >>> d - datetime.date(2022, 10, 4) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Ayrıca standart kütüphanede repr isimli bir built-in fonksiyon da vardır. Bu fonksiyon aslında parametresiyle aldığı - nesne ile __repr__ metodunu çağırıp onun geri döndürdüğü yazıyla geri dönmektedir. Yani gerçekleştirimi kabaca şöyledir: - - def repr(o): - return o.__repr__() - - Bazen programcı komut satırında değil de kendi programı içerisinde de __str__ yerine __repr__ metodunu kullanmak isteyebilir. Örneğin: - - import datetime - - d = datetime.date(2022, 10, 4) - print(d) - print(repr(d)) # print(d.__repr__()) - - Örneğin bazen programcı birtakım yazılardaki özel karakterleri görebilmek için yazıyı __repr__ metodu ile yazdırmak isteyebilir: - - f = open('test.txt', encoding='utf-8') - s = f.read() - print(repr(s)) - f.close() - - Biz henüz dosya işlemlerini görmedik. Ancak burada bir dosya açılmış, içerisindeki bilgiler okunup ekrana yazdırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bazen bir sınıfın ya da nesnenin bir özniteliğinin (metotların da aslında sınıf öznitelikleri olduğunu anımsayınız) var - olup olmadığını anlamak isteyebiliriz. Örneğin Sample sınıfının bir foo metodunun olup olmadığını anlamak bilmek isteyebiliriz. - Bunun için standart kütüphanede bulunan hasattr isimli built-in fonksiyon kullanılmaktadır. hasttar fonksiyonu iki parametreye - sahiptir. Fonksiyonun birinci parametresei ilgili sınıf türünden değişkeni ikinci parametresi ise söz konusu özniteliğin - string biçiminde ismini almaktadır. Fonksiyon bool bir değere geri dönmektedir. Eğer değişken bir sınıf türündense bu - fonksiyon önce o sınıf nesnesinin içerisindeki örnek özniteliklerine bakmakta sonra o nesnenin ilişkin olduğu sınıf - içerisindeki özniteliklere bakmaktadır. Eğer birinci parametre type türündense fonksiyon ilgili sınıfın özniteliklerine - bakmaktadır. - - Örneğin: - - class Sample: - def foo(self): - pass - - s = Sample() - s.a = 10 - - result = hasattr(s, 'foo') - print(result) # True - - result = hasattr(Sample, 'foo') - print(result) # True - - result = hasattr(s, 'a') - print(result) # True - - hasttar fonksiyonu sınıfın taban sınıflarına da bakmaktadır. Örneğin: - - class Sample: - def foo(self): - pass - - class Mample(Sample): - pass - - m = Mample() - - result = hasattr(m, 'foo') - print(result) # True - - - result = hasattr(Mample, 'foo') - print(result) # True - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 47. Ders 10/10/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi Python'da değişken kavramı ile nesne kavramı farklı anlamlara geliyordu. Python'da değişkenler nesnelerin - adreslerini tutmaktadır. Biz Python'da dğeişken dediğimizde adres tutan varlıkları, nesne dediğimizde gerçek bilgileri - tutan varklıkları kastetmekteyiz. Örneğin: - - s = 'ankara' - - Burada s bir değişkendir. "ankara" yazısı str türündne bir nesnede tutulmaktadır. s'nin içerisinde o nesnenin adresi - vardır. Yine anımsanacağı gibi Python'da her türlü atama "adres ataması" anlamına geliyordu. Örneğin: - - s = 'ankara' - k = s - - Burada bir tek nesne vardır. İki değişken aynı nesneyi göstermektedir. - - Pekiyi bir değişken bir nesneyi gösterirken başka bir nesneyi göstermeye başladığında ya da del deyimi ile silindiğinde - onun gösterdiği nesneye ne olacaktır? Örneğin: - - s = 'ankara' - s = None - - Burada s değişkeni bir str nesnesini gösterirken daha sonra onu göstermez hale gelmiştir. Pekiyi o nesne ne olacaktır? - - İşte programlama dillerinde "kullanılmayan nesnelerin otomatik biçimde" silinmesini sağlayan mekanizmalara "çöp toplama - (garbage collection)" mekanizması denilmektedir. Bu işlemi yapan alt sistemlere de "çöp toplayıcı (garbage collector)" - denilmektedir. C#, Java, Swift, Kotlin, Python gibi dillerde çöp toplama mekanizması vardır. Ancak C, C++ gibi aşağı - seviyeli dillerde böyle bir mekanizma bulunmamaktadır. - - Çöp toplama mekanizması bir nesne artık kimse tarafından kullanılamaz noktaya geldiğinde o nesneyi silen bir mekanizmadır. - Örneğin: - - s = 'ankara' - s = 100 - - Burada çöp toplama mekanizması içerisinde "ankara" yazısının bulundurğu str nesnesini silebilir. Ancak örneğin: - - s = 'ankara' - k = s - s = 100 - - Burada çöp toplama mekanizması içerisinde "ankara" yazısının bulunduğu str nesnesini silmememilidir. Çünkü o - nesneyi kullanan başka bir değişken de vardır. Örneğin: - - def foo(): - s = 'ankara' - print(s) - - foo() - - Burada foo fonksiyonu çağrıldıktan sonra içerisinde "ankara" yazısının bulunduğu nesne silinebilir mi? Yerel değişkenler - fonksiyon ya da metot sonlandığında otomatik yok edilmektedir. Dolayısıyla bu örnekte fonksiyon bittiğinde söz konusu - nesneyi gösteren herhangi bir değişken kalmayacaktır. O halde bu nesne çöp toplayıcı tarafından silinebilir durumda - olacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Çöp toplama mekanizmalarının gerçekleştiriminde çeşitli yöntemler kullanılabilmektedir. Python referans kitaplarında - çöp toplama mekanizmasının gerçekleştirimi hakkında bir şey söylenmemiştir. Yani gerçekleştirim yöntemi Python yorumlayıcına - bağlı olarak değişebilmektedir. Örneğin "Iron Python" .NET ortamı için kod ürettiğinden .NET ortamının "mark and sweep" - denilen çöp toplama yöntemini kullanmaktadır. Halbuki CPython gerçekleştirimi "referans sayacı" tekniği ile çöp - toplaması yapmaktadır. Biz de kursumuzda CPython yorumlayıcısının kullandığı referans sayacı tekniğini açıklayacağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da bir nesne yaratıldığında yorumlayıcı o nesnesnin kaç değişken tarafından referans edildiğini (yani gösterildiğini) - nesnenin içerisinde tutmaktadır. Buna nesnenin "referans sayacı (reference count)" denilmektedir. Nesnenin referans sayacı - programın çalışması sırasında azalıp artabilmektedir. Nesnenin referans sayacı 0 olduğunda artık o nesneyi hiçbir değişken - göstermiyor durumda olur. - - Aşağıdaki örnekte list nesnesinin çeşitli durumlardaki referans sayacının kaç olduğu gösterilmiştir: - - def foo(x): - # RC: 3 - y = x - # RC: 4 - - a = [100, 200, 300] - # RC: 1 - b = a - # RC: 2 - foo(b) - # RC: 2 - b = None - # RC: 1 - a = None - # RC: 0 - - İşte CPython yorumlayıcısının çöp toplama mekanizmasında bir nesnenin referans sayacı 0'a düştüğünde yorumlayıcı hemen - o nesneyi silmektedir. Çünkü referans sayacı 0'a düşmüş bir nesnenin artık programcı tarafından kullanılma olanağı - kalmamıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir nesnenin referans sayacı sys modülündeki getrefcount isimli fonksiyonla elde edilebilir. Bu fonksiyon nesnenin referans - sayacını her zaman bir fazla olarak vermektedir. Çünkü bu fonksiyon referans sayacı bulunacak nesnenin adresini parametre - yoluyla aldığı için fonksiyonun içerisinde nesnenin referans sayacı bir artmış olmaktadır. Ayrıca örneğin bir değişken - bir fonksiyona parametre olarak geçirildiğinde Python yorumlayıcısı fonksiyonun parametre değişkenini de ayrıca bir sözlükte - tuttuğu için nesnenin referans sayacı bir değil daha fazla da artabilmektedir. Tabii burada aslında sayıların pek önemi yoktur. - Buradaki en önemli nokta nesnesinin referans sayacının sıfıra düşmesidir. - - Tabii Python'da aslında int, float, str, bool gibi temel türler de birer sınıf biçimindedir. Dolayısıyla örneğin int - bir nesnenin de referans sayacı vardır, float bir nesnenin de referans sayacı vardır. - - Aşağıdkai programda bir nesnenin referans sayacı çeşitli aşamalarda yazdırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -import sys - -def foo(x): - print(sys.getrefcount(x)) - y = x - print(sys.getrefcount(x)) - -a = [100, 200, 300] -print(sys.getrefcount(a)) -b = a -print(sys.getrefcount(a)) -foo(b) -print(sys.getrefcount(a)) -b = None -print(sys.getrefcount(a)) -a = None - -#------------------------------------------------------------------------------------------------------------------------ - getrefcount fonksiyonu ile nesnelerin referans sayaçlarını elde ettiğinizde bu sayılar umduğunuz gibi çıkmayabilir. - Çünkü Python yorumlayıcıları kendi içlerinde çeşitli nedenlerden dolayı nesnenin başka değişkenler tarafından da gösterilmesini - sağlayabilmektedir. Ayrıca CPython gibi yorumlayıcılar belli sabitleri işin başında bir kez yaratıp o sabitler kullanıldığında - zaten yaratılmış olan nesnelerin adreslerini kullanabilmektedir. Bu nedenle pek çok sabitin yüksek bir referans sayacı - olabilmektedir. Örneğin: - - >>> a = 0 - >>> sys.getrefcount(a) - 313 - - Buradaki yüksek değer kişiler tarafından şüpheyle karşılanabilmektedir. Ancak yukarıda da belirtitğimiz gibi CPython - yorumlayıcısı belli sabitleri işin başında yaratıp o sabitler bir değişkene atandığında zaten yaratılmış olan nesnenin - adresini o değişkene atamaktadır. Böylece çok kullanılan sabitler için her defasında ayrı ayrı nesneler yaratılmamış - olmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi CPython'da bir nesne yaratıldıktan sonra o nesnenin referans sayacı sıfıra düştüğünde - nesne artık çöp duruma gelmektedir ve "çöp toplayıcı (garbage collector)" mekanizma sayesinde nesne bellekten yok - edilmektedir. Yani Python'da programcı nesneleri yaratır ancak onların yok edilmesiyle uğraşmaz. Onların yok edilmesi - yorumlayıcı sistem tarafından otomatik yapılmaktadır. - - Yukarıda da belirttiğimiz gibi çöp toplama mekanizması Python'a özgü değildir. Java, C# gibi dillerde ve onların kullanıldığı - ortamlarda da çöp toplama mekanizması bulunmaktadır. Yine yukarıda belirttiğimiz gibi çöp toplama mekanizması için değişik - yöntemler kullanılmaktadır. Çöp toplama sisteminin gerçekleştiriminde kullanılan bu yöntemlerin birbirlerine göre avantajları - ve dezavantajları söz konusu olabilmektedir. Örneğin CPython yorumlayıcısı "referans sayacı yoluyla çöp toplama yöntemini" - kullanır. Bu yöntemde bir nesnenin referans sayacı sıfıra düşer düşmez çöp toplayıcı onu hemen silmektedir. Ancak örneğin - .NET ve Java ortamlarında "mark and sweep" yöntemi tercih edilmektedir. O ortamlarda nesnenin silinmesi hemen değil belli - bir zaman sonra sistem durdurularak yapılmaktadır. Örneğin Iron Python ve Jython yorumlayıcıları bu ortamlarda çalıştığı - için bu ortamların çöp toplama mekanizmasını kullanmaktadır. Yukarıda da belirttiğimiz gibi Python referans kitaplarında - çöp toplama yöntemi hakkında ayrıntılar verilmemiş dolayısıyla bu konu yorumlayıcıları yazanların isteğine bırakılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bazen (seyrek olarak) bir sınıfı yazan programcı sınıfın __init__ metodu içerisinde Python dünyasının dışındaki (örneğin - işletim sistemi düzeyindeki) bir kaynağı tahsis edebilir. Bu kaynak bir bellek alanı olabileceği gibi, işletim sisteminin - kontrol ettiği çeşitli nesneler de olabilmektedir. Bazen nesne yaratılırken bir dosya yaratılır, nesne bu dpsyayı kullanır. - İşte bu tür durumlarda nesnenin yaratımı sırasında __init__ metodunda yapılan tahsisatların nesne yok edilmeden önce otomatik - olarak serbest bırakılması gerekmektedir. Örneğin: - - s = Sample() - s.foo() - s.bar() - - Burada konu kolay anlaşılsın diye Sample sınıfının __init__ metodunun bir dosya yaratıp sınıfın metotlarının o dosyayı - kullandığını varsayalım. Bu nesnenin kullanımı bittiğinde bu dosyayı da silmek isteriz. Örneğin: - - s = None - - Burada çöp toplayıcı nesneyi yok edecektir. Fakat tabii yaratılmış olan bu dosyayı silmeyecektir. İşte bu tür geri alma - işlemlerinin otomatize edilmesi için __del__ isimli bir metottan faydalanılmaktadır. - - Python'ın çöp toplayıcı mekanizması nesneyi yok etmeden hemen önce o nesne için "eğer varsa" ilgili sınıfın __del__ isimli - metodunu çağırmaktadır. Böylece programcı nesne yok edilmeden önce nesne ile ilgili birtakım son işlemleri yapabilir. Tabii - programcılar genel olarak Python dünyasının içerisinde kaldıklarından dolayı bu metodu yazmaya pek gereksinim duymamaktadır. - Ancak yukarıda da belirttiğimiz gibi eğer programcı Python dünyasının dışında birtakım tahsisatları sınıfın __init__ metdounda - yapmışsa bu tahsisatların otomatik geri bırakılmasını __del__ metodunda sağlayabilir. İşletim sistemi düzeyinde tahsis edilen - kaynaklara .NET ve Java dünyasında "unmanaged" kaynaklar denilmektedir. Aslında Python'ın standart kütüphanesi zaten bu tür - unmanaged kaynakları kullanmaktadır ve onların boşaltımını ilgili sınıfların __del__ metotlarında zaten yapmaktadır. Yukarıda - da belirttiğimiz gibi programcılar genel olarak bu __del__ metotlarını yazmayı gerektirecek uygulamalar içerisinde seyrek olarak - bulunurlar. - - Program bittiğinde hala birtakım nesnelerin referans sayaçları 0'dan büyükse onlar için de son kez __del__ metotları - çağrılmaktadır - - Aşağıdaki programı çalıştırarak çıktısını dikkatle inceleyeniz. CPython yorumlaycısının tam olarak nesneyi nerede yok - ettiğini tespit etmeye çalışınız. Program çalıştırıldığında şöyle bir çıktı elde edilecektir: - - bir - işletim sistemi düzeyinde bir kaynak tahsis ediliyor - iki - tahsis edilen kaynak boşaltılıyor - üç - -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self): - print('işletim sistemi düzeyinde bir kaynak tahsis ediliyor') - - def __del__(self): - print('tahsis edilen kaynak boşaltılıyor') - -print('bir') -s = Sample() -print('iki') -s = 10 -print('üç') - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte File sisimli sınıfın __init__ metodunda içi boş bir dosya yaratılmıştır. Sınıfın write metodu bu dosyaya - bir şeyler yazmaktadır. disp metodu ise dosyanın içerisindekileri ekrana yazdırmaktadır. Burada istenilen şey bu yaratılan - dosyanın nesnenin kullanımı bittiğinde otomatik yok edilmesidir. İşte örneğimizde sınıfın __del__ metodunda bu işlem otomatize - edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -import os - -class File: - def __init__(self, path): - self.path = path - self.f = open(path, 'w+') - - def write(self, text): - self.f.seek(0, 0) - self.f.write(text) - - def disp(self): - self.f.seek(0, 0) - text = self.f.read() - print(text) - - def __del__(self): - self.f.close() - os.remove(self.path) - -file1 = File('test.txt') -file1.write('This is a test') -file1.disp() - -file2 = File("mest.txt") -file2.write('Another example') -file2.disp() - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfın __del__ metodu ile ilgili soru ve yanıtlar şunlar olabilir: - - SORU: __del__ metodu ne zaman çağrılmaktadır? - YANIT: __del__ metodu nesnenin referans sayacı 0'a düştüğünden nesene yorumlayıcının çöp toplayıcı alt sistemi tarafından - yok edilmeden az önce çağrılmaktadır. - - SORU: __del__ metodunu kim çağırmaktadır? - YANIT: Bu metodu yorumlayıcı (yani çöp toplayıcı) çağırmaktadır. - - SORU: Ben sınıfım için __del__ metodu yazmalı mıyım? - YANIT: Sınıfınız için __del__ metodunu yazmanız için bir gerekçenizin olması gerekir. Aslında __del__ metodunun yazılması - gerektiği durumlarla seyrek karşılaşılmaktadır. Bu metot özellikle sınıfın __init-_ metodunda Python dünyasının dışında - yapılan birtakım kaynak tahsisatlarının otomatik geri bırakılması için kullanılmaktadır. - - SORU: __del__ metodu yazılmamışsa yorumlayıcı ne yapar? - YANIT: __del__ metodu yorumlayıcı (çöp toplayıcı) tarafından "eğer varsa" çağrılmaktadır. Siz sınıfınız için bu metodu - yazmadıysanız yorumlayıcı (çöp toplayıcı) bunu çağırmaya çalışmayacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi CPython'daki __del__ metodunun çağrılması tamamen deterministik midir? Aslında büyük ölçüde CPython'da bu __del__ - metodunun çağrılması deterministik gibi gözükmektedir. Ancak yine de tam olarak böyle bir durum söz konusu değildir. - Örneğin: - - def foo(): - print('foo begins') - s = Sample() - print('foo ends') - - Burada s ne zaman hayatını kaybetmektedir? CPython yorumlayıcısı s'in bir daha kullanılmadığını fark edip hemen son - print'ten önce nesneyi silebilmektedir. Halbuki örneğin C++'ta kesinlikle bu durumda "destructor" fonksiyonun nerede - çağrılacağı olarak bellidir. Ayrıca bazı durumlarda nesne referanslarının gizlice başka collection nesnelerde - tutulduğu durumlarda nesnenin tam olarak ne zaman silineceği yine kestirilememektedir. Program bittiğinde referans - sayacı 0'a düşmemiş olan nesneler için de __del__ metodunun çağrılıp çağrılmayacağı Python'da standrat bir biçimde - belirlenmemiştir.Örneğin: - - class Sample: - def __del__(self): - print('Sample.__del__') - - def foo(self): - print('foo') - - s = Sample() - s.foo() - - Burada s nesnesinin referans sayacı program bittiğinde henüz 0'a düşmemiştir. Pekiyi program bitmedne önce __del__ - metodu yine de çağrılacak mıdır? İşte bu durum Python yorumlayıcıları arasında farklılıklar oluşturabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - __del__ metodu kullanılarken dikkat edilmelidir. Çünkü bu metodun kullanımında yukarıda belirttiğimiz bazı özel durumlar - programların umulduğu gibi çalışmamasına yol açabilmektedir. Bunları bir kez daha özetlemek istiyoruz: - - - __del__ metodunun hangi noktada çağrılacağı konusunda bazen belirsizlikler olabilmektedir. Dolayısıyla Python'da ve özel - olarak CPython gerçekleştiriminde çöp toplama mekanizaması tamamen deterministik değildir. - - - Bir nesnenin başka bir nesneyi göstermsi, onunda onu göstermesi durumunda çöp tolayıcının çalışması sorunlara - yol açabilmektedir. - - - Program bittiğinde referans sayacı 0'a düşmemiş olan nesneler için __del__ metodunun çağrılıması ile ilgili kesin - bir belirleme yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz bir sınıftan türetme yapıyorsak ve sınıfımız için __del__ metodunu yazmışsak bu durumda türemiş sınıf türünden nesnemiz - çöp toplayıcı tarafından yok edilirken bizim türemiş sınıfımızın __del__ metodu çağrılacaktır. Ancak eğer taban sınıfın - da __del__ metodu varsa bizim türemiş sınıfın __del__ metodu içerisinde taban sınıfın __del__ metodunu çağırmamız gerekir. - Bu çağırma işlemi tipik olarak türemiş sınıfın __del__ metodunun sonunda yapılmalıdır. Örneğin: - - class A: - def __init__(self): - print('A.__init__') - - def __del__(self): - print('A.__del__') - - class B(A): - def __init__(self): - super().__init__() - print('B.__init__') - - def __del__(self): - print('B.__del__') - super().__del__() - - b = B() - b = None - - Burada yaratılan B nesnesinin referans sayacı 0'a düştüğünde B sınıfının __del__ metodu çağrılacaktır. Bu metot da - taban sınıfın __del__ metodunu çağırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def __init__(self): - print('A.__init__') - - def __del__(self): - print('A.__del__') - -class B(A): - def __init__(self): - super().__init__() - print('B.__init__') - - def __del__(self): - print('B.__del__') - super().__del__() - -b = B() -b = None - -#------------------------------------------------------------------------------------------------------------------------ - Tabii eğer taban sınıfın bir __del__ metodu yoksa biz türemiş sınıfın __del__ metodu içerisinde taban sınıfın __del__ - metodunu çağırmamalıyız. Çünkü Python'da object sınıfınn bir __del__ metodu yoktur. O halde biz türemiş sınıf için - __del__ metodunu yazarken eğer taban sınıfta __del__ metodu varsa onu çağırmaya çalışmalıyız. Pekiyi biz taban sınıfın - __del__ metodunun olup olmadıpını nasıl anlayabiliriz? Aslında en sağlam yol dökümantasyona bakmak olabilir. Ya da - biz manuel olarak dir gibi bir fonksiyonla taban sınıfın __del__ metodunun olup olmadığını anlayabiliriz. Eğer - elimizde taban sınıfa ilişkin bir dokümantasyon yoksa biz de taban sınıfın __del__ metoduna sahip olup olmadığını - bilemeyecek durumdaysak o zaman hasattr fonksiyonuyla bu kontrolü yapabiliriz. Örneğin: - - class B(A): - def __del__(self): - ... - base = super() - if hasattr(base, '__del__'): - base.__del__() - - Aşağıdaki örnekte türemiş sınıfın __del__ metodu önce hasattr fonksiyonu ile taban sınıfın __del__ metodu var mı diye bakmıştır. Sonra eğer varsa taban sınıfın __del__ metodunu çapırmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - def __init__(self): - print('A.__init__') - - def __del__(self): - print('A.__del__') - -class B(A): - def __init__(self): - super().__init__() - print('B.__init__') - - def __del__(self): - print('B.__del__') - base = super() - if hasattr(base, '__del__'): - base.__del__() - -b = B() -del b - -#------------------------------------------------------------------------------------------------------------------------ - 48. Ders 12/10/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - "Operatör metotları" konusu nesne yönelimli dillerin çoğunda bulunmaktadır. Örneğin bu özellik C++'ta, C#'ta, Swift'te - vardır. Fkata örneğin Java'da bulunmamaktadır. Python da operatör metotlarını desteklemektedir. Operatör metotları aslında - dile ekstra bir özellik katmamaktadır. Yalnızca okunabilirlik bakımından bir avantaj sağlamaktadır. Yani başka bir deyişle - nesne yönelimli bir dilde operatör metotları olmasa da (örneğin Java'da yok) bu işlemler normal metotlarla sağlanabilir. - Ancak operatör metotları güzel bir görünüm sunmakta ve kodun daha anlaşılabilir olmasını sağlamaktadır. - - Aşağıdaki örnekte bir Complex sayı sınıfı oluşturulmuştur. Bu sınıfa iki Complex sayıyı toplayabilen ve bir Complex sayıdan - başka bir Complex sayıyı çıkartabilen add ve sub metotları eklenmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real = 0, imag = 0): - self.real = real - self.imag = imag - - def add(self, z): - real = self.real + z.real - imag = self.imag + z.imag - - return Complex(real, imag) - - def sub(self, z): - real = self.real - z.real - imag = self.imag - z.imag - - return Complex(real, imag) - - def __repr__(self): - return f'{self.real}+{self.imag}i' - -x = Complex(7, 4) -y = Complex(5, 2) - -result = x.add(y) -print(result) - -result = x.sub(y) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Operatör metotları özel dunder isimli metotlardır. Yani operatör metotlarının isimleri dilin içerisinde belirlenmiştir. - Örneğin toplama işlemini yapan operatör metodu __add__, çıkartma işlemini yapan opereatör metodu __sub__, çarpma işlemini - yapan operatör metodu __mul__ ismindedir. Programcı bu metotları yazarak Python opereatörlerinde kendi sınıfları için - bu metotların çağrılmasını sağlayabilir. - - Python yorumlayıcısı sınıf türünden bir değişkenin bir operatör ile kullanıldığını gördüğünde bu ifadeyi eşğder metot - çağrısına dönüştürmektedir. Örneğin a bir sınıf türünden olmak üzere a + b ifadesi tamamen a.__add__(b) ile eşdeğerdir. - Örneğin a > b ifadesi de a.__gt__(b) ile eşdeğerdir. Böylece aslında biz bir sınıf nesnesini operatörlerle işleme soktuğumuzda - arka planda sınıfın dunder'lı operatör metotları çağrılmaktadır. Tabii biz de a + b yerine aslında a.__add__(b) yazabilirdik. - İki ifade arasında bir farklılık yoktur. - - Pekiyi örneğin __add__ metodunun parametrik yapısı nasıl olacaktır? Mademki a + b işleminin eşdeğeri a.__add__(b) biçimindedir. - O halde __add__ metodunun iki parametresi olmalıdır. İlk parametre zorunlu self parametresidir. a + b ifadesindeki a - değeri bu self parametresine aktarılacaktır. İkinci parametre buradaki b değerini alan parametredir. Örneğin: - - def __add(self, x): - pass - - Burada a + b işleminde a değeri self parametresine b değeri ise x parametresine atanacaktır. Tabii bizim de mantıksal - olarak iki değeri toplayıp bunun sonucuyla geri dönmemiz gerekir. - - Tabii Python yorumlayıcısı operatör metotlarında o operatöre uygun bir işlem yapılıp yapılmadığını denetleyememektedir. - Dolayısıyla bu konuda asorumluluk tamamne programcıdadır. (Yani biz __add__ metodu içerisinde çıkartma işlemi de yapabiliriz. - Bu konuda bir denetim uygulanmamaktadır. Tabii operatör metodunda operatöre uygun olmayan bir işlem yapmak kötü bir - tekniktir. ) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Operatör metotları kombine edilebilmektedir. Örneğin a, b ve c bir sınıf türünden değişkenler olsun. a + b + c işleminin - eşdeğeri a.__add__(b).__add__(c) biçimindedir. Yani burada ilgili sınıfın __add__ metodu iki kere çağrılacaktır. Önce - a + b işlemi yapılacak buradan bir sınıf nesnesi elde edilecek o nesne bu kez c ile toplanacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real = 0, imag = 0): - self.real = real - self.imag = imag - - def __add__(self, z): - real = self.real + z.real - imag = self.imag + z.imag - - return Complex(real, imag) - - def __sub__(self, z): - real = self.real - z.real - imag = self.imag - z.imag - - return Complex(real, imag) - - def __repr__(self): - return f'{self.real}+{self.imag}i' - -x = Complex(7, 4) -y = Complex(5, 2) -z = Complex(1, 2) - -result = x + y + z -print(result) - -result = x.__add__(y).__add__(z) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Operatör metotları operatör önceliklerini değiştirmez. Örneğin a, b, c birer sınıf türünden olmak üzere a + b * c işleminde - yine önce b * c yapılıp bunun sonucu a ile toplanacaktır. Dolayısıyla bu işlemin çağrı karşılığı a.__add__(b.__mull__(c)) - biçimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Complex: - def __init__(self, real = 0, imag = 0): - self.real = real - self.imag = imag - - def __add__(self, z): - real = self.real + z.real - imag = self.imag + z.imag - - return Complex(real, imag) - - def __sub__(self, z): - real = self.real - z.real - imag = self.imag - z.imag - - return Complex(real, imag) - - def __mul__(self, z): - real = self.real * z.real - self.imag * z.imag - imag = self.real * z.imag + self.imag * z.real - - return Complex(real, imag) - - def __repr__(self): - return f'{self.real}+{self.imag}i' - -x = Complex(7, 4) -y = Complex(5, 2) -z = Complex(1, 2) - -result = x + y * z # x.__add__(y.__mul__(z)) -print(result) - -result = x.__add__(y.__mul__(z)) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi a + b gibi bir işlemde b'nin a ile aynı sınıf türünden olma zorunluluğu var mıdır? Hayır yoktur. Bu durumda - farklı b türleri için farklı işlemlerin yapılması gerekebilmektedir. C++, C# gibi dillerde "function/method overloading" - özelliği olduğundan her farklı tür için farklı bir operatör fonksiyonu/metodu yazılabilmektedir. Python'da bir sınıf - için aynı isimli tek bir operatör metodu bulunabileceğinden dolayı bu işlem tür kontrolleriyle sağlanabilmektedir. - Aşağıdaki örnekte bir sayıyı temsil eden bir Number sınıfı oluşturulmuştur. Sınıfın __add__, __sub__ ve __mul__ metotları - iki Number nesnesini ve bir Number nesnesi ile int ve float nesneyi toplayabilecek, çıkartabilecek ve çarpabilecek biçimde - yazılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class Number: - def __init__(self, val = 0): - self.val = val - - def __repr__(self): - return str(self.val) - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __sub__(self, number): - if isinstance(number, int|float): - result = self.val - number - elif isinstance(number, Number): - result = self.val - number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __mul__(self, number): - if isinstance(number, int|float): - result = self.val * number - elif isinstance(number, Number): - result = self.val * number.val - else: - raise TypeError('invalid type') - - return Number(result) - -x = Number(10) -y = Number(20) -z = Number(2) - -result = (x + 10) * 2 -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi Python'da iki tane bölme operatörü vardır. / operatörü her zaman float değer vermektedir. // operatörü - (floordiv) ise bölüm sonucunda noktadan sonraki kısmı atmaktadır. İşte / operatörüne ilişkin operatör metodu __truediv__ - ismiyle // operatörüne ilişkin operatör metodu ise __floordiv__ ismiyle yazılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Number: - def __init__(self, val = 0): - self.val = val - - def __repr__(self): - return str(self.val) - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __sub__(self, number): - if isinstance(number, int|float): - result = self.val - number - elif isinstance(number, Number): - result = self.val - number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __mul__(self, number): - if isinstance(number, int|float): - result = self.val * number - elif isinstance(number, Number): - result = self.val * number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __truediv__(self, number): - if isinstance(number, int|float): - result = self.val / number - elif isinstance(number, Number): - result = self.val / number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __floordiv__(self, number): - if isinstance(number, int|float): - result = self.val // number - elif isinstance(number, Number): - result = self.val // number.val - else: - raise TypeError('invalid type') - - return Number(result) - -x = Number(30) -y = Number(20) - -result = x / y # x.__truediv__(y) -print(result) - -result = x // y # x.__floordiv__(y) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python'a 3.5 versiyonuyla birlikte @ operatörü de eklenmiştir. Bu operatöre "matris çarpımı" operatörü de denilmektedir. - Ancak bu @ operatörü standart Python sınıfları tarafından kullanılmamaktadır. Fakat NumPy gibi Pandas gibi kütüphaneler - bu operatörü kullanmaktadır. Bu operatör aslında ilgili sınıfın __matmul__ isimli operatör metodunu çağırmaktadır. Başka - bir deyişle a @ b işlemi a.__matmul__(b) ile eşdeğerdir. Python'da matris işlemi yapan standart bir sınıf yoktur. Bu operatör - matris işlemleri yapan üçüncü parti kütüphanelerde (NumPy, Pandas gibi) eklenme sebebiokunabilirlik sağlamak için kullanılmaktadır. - - Aşağıda matris çarpımının sınıfın __matmul__ metodu tarafından yaptırılmasına bir örnek verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class Matrix: - def __init__(self, matrix): - self.matrix = matrix - self.rowsize = len(matrix) - self.colsize = len(matrix[0]) - - def __matmul__(self, a): - result = [[0] * a.colsize for i in range(self.rowsize)] - for i in range(self.rowsize): - for j in range(self.colsize): - total = 0 - for k in range(self.colsize): - total += self.matrix[i][k] * a.matrix[k][j] - result[i][j] = total - - return Matrix(result) - - def __repr__(self): - s = '' - for i in range(len(self.matrix)): - for k in range(len(self.matrix[i])): - if k != 0: - s += ' ' - s += str(self.matrix[i][k]) - - s += '\n' - - return s - -m = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) -k = Matrix([[1, 2, 1], [3, 2, 1], [1, 3, 4]]) - -result = m @ k # m.__matmul__(k) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Sınıf türünden değişkenleri karşılaştırma operatörleriyle karşılaştırabilmek için ilgili sınıfta karşılaştırma operatör - metotlarının yazılmış olması gerekir. Karşılaştırma operatörleri için operatör metotlarının isimleri şunlardır: - - > __gt__ - < __lt__ - >= __ge__ - <= __le__ - == __eq__ - != __ne__ - - Karşılaştırma operatörlerine ilişkin operatör metotlarının geri dönüş değerleri herhangi bir türden olabilirse de - bunların bool türüyle geri dönmesi en normal durumdur. Çünkü karşılaştırma operatörleri Python'da bool değer üretmektedir. - - Aşağıdaki örnekte Number sınıfı için karşılaştırma operatör netotları yazılmıştur -#------------------------------------------------------------------------------------------------------------------------ - -class Number: - def __init__(self, val = 0): - self.val = val - - def __repr__(self): - return str(self.val) - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __sub__(self, number): - if isinstance(number, int|float): - result = self.val - number - elif isinstance(number, Number): - result = self.val - number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __mul__(self, number): - if isinstance(number, int|float): - result = self.val * number - elif isinstance(number, Number): - result = self.val * number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __truediv__(self, number): - if isinstance(number, int|float): - result = self.val / number - elif isinstance(number, Number): - result = self.val / number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __floordiv__(self, number): - if isinstance(number, int|float): - result = self.val // number - elif isinstance(number, Number): - result = self.val // number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __gt__(self, number): - if isinstance(number, int|float): - result = self.val > number - elif isinstance(number, Number): - result = self.val > number.val - else: - raise TypeError('invalid type') - - return result - - def __lt__(self, number): - if isinstance(number, int|float): - result = self.val < number - elif isinstance(number, Number): - result = self.val < number.val - else: - raise TypeError('invalid type') - - return result - - def __ge__(self, number): - if isinstance(number, int|float): - result = self.val >= number - elif isinstance(number, Number): - result = self.val >= number.val - else: - raise TypeError('invalid type') - - return result - - def __le__(self, number): - if isinstance(number, int|float): - result = self.val <= number - elif isinstance(number, Number): - result = self.val <= number.val - else: - raise TypeError('invalid type') - - return result - - def __eq__(self, number): - if isinstance(number, int|float): - result = self.val == number - elif isinstance(number, Number): - result = self.val == number.val - else: - raise TypeError('invalid type') - - return result - - def __ne__(self, number): - if isinstance(number, int|float): - result = self.val != number - elif isinstance(number, Number): - result = self.val != number.val - else: - raise TypeError('invalid type') - - return result - -val1 = int(input('Bir değer giriniz:')) -val2 = int(input('bir değer giriniz:')) - -x = Number(val1) -y = Number(val2) - -if x > y: - print('x > y') -elif x < y: - print('x < y') -elif x == y: # kasten yapıldı - print('x == y') - -#------------------------------------------------------------------------------------------------------------------------ - Karşılaştırma operatör metotları Python standart kütüphanesindeki bazı sınıflarda bulunmaktadır. Örneğin datettime - müdlü içerisindeki date ve datetime sınıfları tarih ve zaman karşılaştırmasını yapan karşılaştırma operatörlerine sahiptir. -#------------------------------------------------------------------------------------------------------------------------ - -import datetime - -d = datetime.date(2023, 10, 31) -k = datetime.date(2022, 7, 21) - -if d > k: - print('d > k') -elif d < k: - print('d > k') -else: - print('d == k') - -#------------------------------------------------------------------------------------------------------------------------ - Bazı operatörlerin değişme özelliği vardır. Örneğin a + b ile b + a aynı sonucu vermelidir. Ya da örneğin a * b ile - b * a da aynı sonucu vermelidir. Eğer a ve b değişkenlerinin her ikisi de bizim sınıfımız türündense değişme özelliğinin - sağlanması bakımından bir sorun oluşmaz. Eğer a.__add__(b) işlemi yapılabiliyorsa b.__add__(a) da aynı sonucu verecek biçimde - doğal olarak yapılabilecektir. Ancak bu operatörlerin sağ tarafındaki operand'lar farklı türdense değişme özelliğinin - sağlanmasında sorunlar ortaya çıkabilmektedir. Örneğin a + 3 gibi bir ifadeyi biz 3 + a gibi de yazabiliriz. Ancak operatör - metotlarında soldaki operandın bizim sınıfımız türünden olması gerekmektedir. Kendi sınıfımızda a.__add__(3) işlemini yapabilecek - bir operatör metodu yazabiliriz ancak kendi sınıfımızda 3.__add__(a) işlemini yapacak bir operaratör metodunu yazamayız. Özetle - sol taraftaki operand bizim sınıfımız türünden değilse __add__ metodu ile değişme özelliğini sağlayamayız. - - İşte iki operand'lı operatör metodunun ismi __op__ olmak üzere bir bunların __rop__ biçiminde başı "r" ile başlayan (reverse - sözcüğünden geliyor) versiyonları da vardır. Python yorumlayıcısı "op" bir operatör belirtmek üzere a op b işlemini yapacak - operatör metodunu önce sol taraftaki operandın sınıfında __op__ biçiminde arar. Bulursa bu ifadeyi a.__op__(b) olarak değerlendirir. - Eğer bu sınıfta bu operatör metodu yoksa bu kez sağ taraftaki operandın sınıfında __rop__ metodunu arar. Eğer bu metodu - bulursa ifadeyi b.__rop__(a) biçiminde değerlendirir. - - Programcının hem ___op__ hem de __rop__ metotlarını ayrı ayrı yazmasına gerek yoktur. Bunlar aynı işi yapacağına göre - ve metotlar aslında sınıf değişkenleri olduğuna göre bu işlem basit bir atama ile de sağlanabilir. Örneğin: - - class Number: - def __init__(self, val = 0): - self.val = val - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __radd__ = __add__ - - def __repr__(self): - return str(self.val) - - Tabii bazen bunların ayrı ayrı yazılması da gerekebilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Number: - def __init__(self, val = 0): - self.val = val - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __radd__ = __add__ - - def __repr__(self): - return str(self.val) - -x = Number(10) - -result = x + 2 -print(result) - -result = 2 + x # x.__radd__(2) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi hangi operatörler için __rop__ metotlarını da yazmamız gerekir? Aslında bu durum programcıya kalmıştır. Programcı - yalnızca + ve * gibi değişme özelliği olan operatörlere ilişkin operatör metotlarının r'li versiyonlarını yazabilir. - Ancak öte yandan bazı durumlarda değişme özelliği olmayan operatörler için de bu r'li operatör metotlarının yazılması - gerekebilir. Örneğin a > b işleminin eşdeğeri aslında b < a biçimindedir. Benzer biçimde b < a işleminin de eşdeğeri - a > b biçimindedir. Örneğin biz a > 10 gibi bir işlemi yapabiliyorsak 10 < a gibi bir işlemi de yapabilmek isteriz. - Bu durumda karşılaştırma operatörlerinin de r'li biçimlerinin yazılması uygun olacaktır. Tabii bu kararı verecek olan - programcıdır. - - Aşağıdaki örnekte Number sınıfının operatör fonksiyonlarına r'i biçimler eklenmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class Number: - def __init__(self, val = 0): - self.val = val - - def __repr__(self): - return str(self.val) - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __radd__ = __add__ - - def __sub__(self, number): - if isinstance(number, int|float): - result = self.val - number - elif isinstance(number, Number): - result = self.val - number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __rsub__ = __sub__ - - def __mul__(self, number): - if isinstance(number, int|float): - result = self.val * number - elif isinstance(number, Number): - result = self.val * number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __rmul__ = __mul__ - - def __truediv__(self, number): - if isinstance(number, int|float): - result = self.val / number - elif isinstance(number, Number): - result = self.val / number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __rtruediv__ = __truediv__ - - def __floordiv__(self, number): - if isinstance(number, int|float): - result = self.val // number - elif isinstance(number, Number): - result = self.val // number.val - else: - raise TypeError('invalid type') - - return Number(result) - - __rfloordiv__ = __floordiv__ - - def __gt__(self, number): - if isinstance(number, int|float): - result = self.val > number - elif isinstance(number, Number): - result = self.val > number.val - else: - raise TypeError('invalid type') - - return result - - __rgt__ = __gt__ - - def __lt__(self, number): - if isinstance(number, int|float): - result = self.val < number - elif isinstance(number, Number): - result = self.val < number.val - else: - raise TypeError('invalid type') - - return result - - __rlt__ = __lt__ - - def __ge__(self, number): - if isinstance(number, int|float): - result = self.val >= number - elif isinstance(number, Number): - result = self.val >= number.val - else: - raise TypeError('invalid type') - - return result - - __rge__ = __gt__ - - def __le__(self, number): - if isinstance(number, int|float): - result = self.val <= number - elif isinstance(number, Number): - result = self.val <= number.val - else: - raise TypeError('invalid type') - - return result - - __rle__ = __le__ - - def __eq__(self, number): - if isinstance(number, int|float): - result = self.val == number - elif isinstance(number, Number): - result = self.val == number.val - else: - raise TypeError('invalid type') - - return result - - __req__ = __eq__ - - def __ne__(self, number): - if isinstance(number, int|float): - result = self.val != number - elif isinstance(number, Number): - result = self.val != number.val - else: - raise TypeError('invalid type') - - return result - - __rne__ = __ne__ - -x = Number(5) -result = 10 < x -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - op bir operatör sembolü olmak üzere her zaman a = a op b ile a op= b aynı anlamda olmayabilir. Örneğin a + b ile a += b - int, float gibi temel türlerde eşdeğerdir. Ancak örneğin listelerde bu iki ifade eşdeğer değildir. Listelerde bilindiği gibi - a += b aslnda a üzerinde eklemeye yol açmaktadır. Yani programcı op= operatörleri için farklı operatör metotları yazarak - semantik farklılık oluşturabilmektedir. İşte op= operatörleri için __iop__ biçiminde başı "i" ile başlayan operatörler - bulundurulmuştur. Yorumlayıcı a op= b ifadesini gördüğünde eğer sınıfta bu işi yapacak bir __iop__ metodu yoksa bu ifadeyi - a = a op b olarak ele alır. Ancak eğer sınıfta bu işi yapacak bir __iop__ metodu varsa ifadeyi a = a.__iop__(b) olarak ele - almaktadır. Yani özetle biz bu "işlemli atama operatörleri" için operatör metotları yazmak zorunda değiliz. Bu durumda - normal operatör metodu devreye girecektir. Ancak biz işlemli atama operatörlerinde daha farklı bir şeylerin yapılmasını - istiyorsak bu operatörlerin i'li versiyonlarını oluşturmalıyız. - - Pekiyi biz __iop__ operatör metotlarını nasıl yazmalıyız? Öncelikle bu metotların yine bu işlemin sonucu olan bir - nesneyle geri dönmesi gerekir. Çünkü a += b gibi bir işlem eğer sınıfta bunu yapabilecek bir __iadd__ metodu varsa - a = a.__iadd__(b) biçiminde ele alınmaktadır. Yani görüldüğü gibi bu i'li metotların geri dönüş değerleri yine sol - taraftaki operanda atanmaktadır. Böylece programcı isterse hiç yeni nesne yaratmadan işlemi soldaki operand üzerinde yapıp - onunla geri dönebilir. Tabii bu durumda i'li metot self ile geri dönmelidir. - - Örneğin: - - class Number: - def __init__(self, val = 0): - self.val = val - - def __repr__(self): - return str(self.val) - - def __add__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - return Number(result) - - def __iadd__(self, number): - if isinstance(number, int|float): - result = self.val + number - elif isinstance(number, Number): - result = self.val + number.val - else: - raise TypeError('invalid type') - - self.val = result - - return self - - Burada __iadd__ metodu aslında toplamı self üzerinde oluşturup yeniden self nesnesine geri dönmüştür. Örneğin: - - x = Number(10) - y = Number(20) - - print(x) # 10 - print(id(x)) # 2825741528960 - - x += y - - print(x) # 30 - print(id(x)) # 2825741528960 - - Yani özetle a += b gibi bir işlemde eğer sınıfta __iadd__ operatör metodu yoksa bu işlem a = a.__add__(b) biçiminde eğer - sınıfta __iadd__ metodu varsa bu işlem a = a.__iadd__(b) biçiminde yapılmaktadır. Bu durumda a += b işleminde zaten a sınıfı - "değiştirilebilir (mutable)" olmadıktan sonra ya da a = a + b farklı semantik uygulanmadıktan sonra __iadd__ metodunun - yazılmasına da gerek yoktur. Örneğin list sınıfında __iadd__ metodu vardır. Ancak tuple sınıfında yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 49. Ders 17/10/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - a bir sınıf türünden değişken olmak üzere a(...) biçiminde biz bu değişkeni sanki fonksiyonmuş gibi fonksiyon çağırma - operatörü ile kullanabiliriz. Ancak bunun için ilgili sınıfın __call__ isimli bir operatör metodunun bulunuyor olması - gerekir. Yani: - - a(...) - - çağrısı aslında aşağıdaki çağrı tamamne eşdeğerdir: - - a.__call__(...) - - Bu nedenle Python'da fonksiyon çağırma operatörü ile çağrılabilen nesnelere "callable" nesneler denilmektedir. Bir - fonksiyon "callable" bir nesnedir. __call__ metodu buunan bir sınıf nesnesi de "callable" bir nesnedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __call__(self): - print('this is a test') - -s = Sample() - -s() - -#------------------------------------------------------------------------------------------------------------------------ - Tabii __call__ metotları ekstra parametrelere ve geri dönüş değerlerine sahip olabilir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, text): - self.text = text - - def __call__(self, n): - return self.text * n - -s = Sample('test') - -result = s(5) # s.__call__(5) -print(result) # testtesttesttesttest - -#------------------------------------------------------------------------------------------------------------------------ - Fonkisyon yerine sanki fonksiyonmuş gibi sınıf nesnelerinin kullanılması bazı durumlarda faydalar sağlamaktadır. Sınıf - nesneleri örnek özniteliklerinde bilgi tutabildiği için çağrılar arasında aynı değerlerin kullanılması mümkün olabilmektedir. - Fonksiyon yerine sınıf nesnelerinin kullanılması "callback" mekanizmasında yoğun olarak kullanılabilmektedir. Bir foksiyon - bir işi yaparken arka planda bizim ona verdiğimiz bir fonksiyonu çağırıyor olabilir. Bu mekanizmaya "callback" mekanizaması, - burada çağrılan fonksiyona da "callback" fonksiyon denilmektedir. İşte bu tür callback fonksiyonların "callable" nesnelerle - oluşturulmasının bazı avantajları söz konusu olabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz daha önce string'lerin, listelerin, demetlerin, kümelerin, sözlüklerin len fonksiyonuna sokulabildiğini gördük. - Aslında len fonksiyonu ilgili sınıfın __len__ isimli metodunu çağırıp onun geri dönüş değeri ile geri dönmektedir. Yani: - - len(a) - - ile - - a.__len__() - - aynı anlamdadır. Bu durumda len fonksiyonu aslında şöyle yazılmıştır: - - def len(a): - return a.__len__() - - len fonksiyonunun çokbiçimli (polymorpic) oldupuna dikkat ediniz. Yani biz pek nesnenin uzunluğunu len ile elde edebiliriz. - Ancak elde ettiğimiz değer o nesneye bağlıdır. - - Biz de kendi sınıfımız türünden nesnelerin len fonksiyonuna sokulmasını istersek sınıfımızda __len__ metodunu yazmalıyız. - __len__ metodunun yalnızca self parametresi vardır. Bu self parametresi len fonksiyonun argümanını oluşturmaktadır. Örneğin: - - class Sample: - def __init__(self, *args): - self.args = args - - def __len__(self): - return len(self.args) - - s = Sample(10, 20, 30, 40) - - result = len(s) - - print(result) # 4 - -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, *args): - self.args = args - - def __len__(self): - return len(self.args) - -s = Sample(1, 2, 3, 4, 5, 10, 20) - -result = len(s) - -print(result) # 7 - -print(s.__len__()) # 7 - -#------------------------------------------------------------------------------------------------------------------------ - Kendi sınıfımız türünden bir nesneyi biz int, float, bool, str, complex türlerine dönüştürmek istediğimizde bu dönüştürme - işlemi için sınıfımızın sırasıyla __int__, __float__, __bool__, __str__ ve __complex__ metotları çağrılmaktadır. Bu metotların - yalnızca self parametreleri bulunmaktadır. Aslında biz daha önce __str__ metodunu görmüştük. Bu metot str türüne dönüştürülürken - çağrılıyordu. İşte diğer metotlar da diğer türlere dönüştürülürken çağrılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Rational: - def __init__(self, num, denom): - self.num = num - self.denom = denom - - def __str__(self): - return f'{self.num}/{self.denom}' - - def __float__(self): - return self.num / self.denom - -r = Rational(2, 3) -print(r) - -result = float(r) -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf türünden değişkeni bool türüne dönüştürürken eğer biz sınıfımızda bu dönüşüm için __bool__ bulundurmuşsak - bu metot çağrılır. Ancak biz sınıfımızda __bool__ metodu bulundurmamışsak bu durumda değişken içerisinde None değeri - yoksa dönüştürme True olarak None değeri varsa False olarak yapılır. Örneğin: - - class Number: - def __init__(self, val): - self.val = val - - x = Number(0) - result = bool(x) - - print(result) # True - - Nesnenin özniteliklerinde ne olursa olsun ilgili değişken bool türüne True olarak dönüştürülür. Ancak biz sınıf için - __bool__ metodunu yazarsak bu durumda bu metot çağrılacaktır. Örneğin: - - class Number: - def __init__(self, val): - self.val = val - - def __bool__(self): - return bool(self.val) - - x = Number(0) - result = bool(x) - - print(result) # False -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıf türünden değişken köşeli parantez operatörüyle kullanıldığında sınıfın __getitem__ metodu çağrılmaktadır. - Böylece sanki sınıf türünden değişken bir liste gibi köşeli parantez operatörleriyle kullanılabilmektedir. Sınıf türünden - değişken atama operatörünün solunda da kullanılabilir. Bu durumda da sınıfın __setitem__ metodu çağrılmaktadır. Başka bir - deyişle eğer biz değişkeni köşeli parantezli bir biçimde ancak atama operatörünün solunda kullanmıyorsak ilgili sınıfın - __getitem__ metodu, atama operatörünün solunda kullanıyorsak ilgili sınıfın __setitem__ metodu çağrılmaktadır. - - __getitem__ metodu self parametresinin yanı sıra indeks belirten bir parametre daha sahiptir. __setitem__ ise self - parametresinin yanı sıra hem indeks belirten bir parametreye hem de atanacak değeri belirten bir parametreye sahiptir. - Bu iki metodun parametik yapıları şöyledir: - - def __getitem__(self, index): - pass - - def __setitem__(self, index, value): - pass - - Kullanım sırasında köşeli parantez içerisindeki ifade metotların index parametresine aktarılmaktadır. __setitem__ metodunun - üçüncü parametresi ise atanan değeri belirtmektedir. a bir sınıf nesnesi olmak üzere: - - b = a[index] - - işleminin eşdeğeri şöyledir: - - b = a.__getitem__(index) - - Benzer biçimde: - - a[index] = value - - işleminin de eşdeğeri şöyledir: - - a.__setitem__(index, value) - - Aşağıdaki örnekte Sample sınıfı türünden nesne yaratılırken alınan argümanlar nesnenin list türünden bir özniteliğine - yerleştirilmiştir. Sonra bu elemanları sınıf için __getitem__ ve __setitem__ metotları yazılarak bu değerleirn get ve set - edilmeleri sağlanmıştır. Ayrıca sınıfa bir de __len__ metoıdunun eklendiğine dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, *args): - self.args = list(args) - - def __getitem__(self, index): - return self.args[index] - - def __setitem__(self, index, value): - self.args[index] = value - - def __len__(self): - return len(self.args) - -s = Sample(10, 20, 30, 40, 50) - -for i in range(len(s)): - print(s[i], end=' ') # print(s.__getitem__(i), end=' ') -print() - -s[0] = 100 # s.__setitem(0, 100) -s[2] = 300 # s.__setitem__(2, 300) - -for i in range(len(s)): - print(s[i], end=' ') # print(s.__getitem__(i), end=' ') -print() - -#------------------------------------------------------------------------------------------------------------------------ - Tabii köşeli parantezlerin içerisinde int türden bir değer olmak zorunda değildir. Herhangi bir türden değer olabilir. - Index parametresini programcı anlamlandırmalıdır. Örneğin: - - class Sample: - def __getitem__(self, index): - if not isinstance(index, str): - raise TypeError('invalid type') - - return index.upper() - - s = Sample() - - result = s['ankara'] - print(result) - - Burada programcı köşeli parantezler içerisinde bir string beklemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __getitem__(self, index): - if not isinstance(index, str): - raise TypeError('invalid type') - - return index.upper() - -s = Sample() - -result = s['ankara'] -print(result) -#------------------------------------------------------------------------------------------------------------------------ - Python'daki listelerde ve demetlerde iki boyutluluk demetin ya da listenin elemanı olarak demet ya da liste kullanmakla - sağlanmaktadır. Dolayısıyla bir matrisin elemanına erişmek için iki ayrı [...] operatörü kullanılmaktadır. Örneğin: - - >>> a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - >>> a[2][1] - 8 - >>> a[2] - [7, 8, 9] - - Burada matris elemanına erişimin tek köşeli parantez ile değil iki köşeli parantez ile yapıldığına dikkat ediniz. - Yani a[i, k] gibi bir kullanım geçerli değildir. Örneğin: - - >>> a[2, 1] - Traceback (most recent call last): - File "", line 1, in - TypeError: list indices must be integers or slices, not tuple - - Ancak bu kullanımın üçüncü parti bazı kütüphanelerde oludğunu da görmekteyiz. Örneğin: - - >>> import numpy as np - >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - >>> a - array([[1, 2, 3], - [4, 5, 6], - [7, 8, 9]]) - >>> a[2, 1] - 8 - >>> a[2, 1] = 100 - >>> a - array([[ 1, 2, 3], - [ 4, 5, 6], - [ 7, 100, 9]]) - - Pekiyi köşeli parantez içerisinde virgüllü kullanım nasıl sağlanmaktadır? İşte Python yorumlayıcısı köşeli parantezin - içerisinde tek bir değer varsa onu o türden bir nesne olarak __getitem__ ve __setitem__ metotlarına geçirmektedir. - Eğer köşeli parantezin içerisinde virgül ile ayrılmış birden fazla değer varsa bu kez Python yorumlayıcısı bu değerleri - bir demete yerleştirip demeti __getitem__ ve __setitem__ metotlarına geçirmektedir. Yani örneğin: - - val = s[x, y, z] - - işlemi aşağıdakiyşe eşdeğerdir: - - val = s[(x, y, z)] # val = s.__getitem__((x, y, z)) - - Benzer biçimde örneğin: - - s[x, y, z] = val - - işlemi de aşağıdakiyle eşdeğerdir: - - s[(x, y, z)] = val # s.__setitem__((x, y, z), val) - - Bu durumu aşağıdaki kodla test edebilirsiniz. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __getitem__(self, index): - return f'type: {type(index)}, value: {index}' - -s = Sample() - -result = s[1, 2, 3] -print(result) - -result = s[1] -print(result) - -result = s['ankara'] -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da a[x, y] işleminin a[(x, y)] işleminden hiçbir farkı olmadığına dikkat ediniz. Örneğin: - - >>> import numpy as np - >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - >>> a[1, 2] - 6 - >>> a[(1, 2)] - 6 - >>> t = 1, 2 - >>> a[t] - 6 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte Matrix sınıfı bir liste listesini alıp nesnenin özniteliğinde saklamaktadır. Sonra __getitem__ - netodunda köşeli parantez içerisindeki index değerinin tek bir int değerden mi yoksa iki değerden mi oluştuğu kontrol - edilmiştir. Sınıf için bir __setitem__ metodu da yazılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class Matrix: - def __init__(self, a): - self.a = a - - def __getitem__(self, index): - if isinstance(index, int): - return self.a[index] - - if isinstance(index, tuple): - if len(index) != 2: - raise ValueError('matrix must must have two dimensions') - return self.a[index[0]][index[1]] - - raise TypeError('index invalid type') - - def __setitem__(self, index, value): - if isinstance(index, tuple): - if len(index) != 2: - raise ValueError('matrix must must have two dimensions') - self.a[index[0]][index[1]] = value - else: - raise TypeError('index invalid type') - - -a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -m = Matrix(a) - -result = m[1, 2] -print(result) # 6 - -result = m[1] -print(result) # [4, 5, 6] - -m[1, 2] = 100 - -result = m[1, 2] -print(result) # 100 - -#------------------------------------------------------------------------------------------------------------------------ - Köşeli parantez operatöründe dilimleme yapabilmek için slice isminde bir sınıftan faydalanılmaktadır. slice sınıfı built-in - bir sınıftır. Bir slice nesnesi tek argümanla, iki argümanla ya da üç argümanla yaratılabilir. slice sınıfının start, stop ve - step isimli üç örnek özniteliği vardır. Eğer slice nesnesi tek argümanla yaratılmışsa start ve step None değerinde ancak stop - ise girilen argüman değerinde olur. slice nesnesi iki argümanla yaratılmışsa start birinci argümanın, stop ikinci argümanın - değerinde olur ancak step None değerinde olur. slice nesnesi üç argümanla yaratılmışsa argümanlar sırasıyla start, stop - ve step değerlerinde olur. Örneğin: - - >>> s = slice(10) - >>> print(s.start, s.stop, s.step) - None 10 None - >>> s = slice(10, 20) - >>> print(s.start, s.stop, s.step) - 10 20 None - >>> s = slice(10, 20, 2) - >>> print(s.start, s.stop, s.step) - 10 20 2 - - İşte biz köşeli parantez içerisinde dilimle yaparsak Python yorumlayıcısı bu dilimleme için __getitem__ ve __setitem__ - metotlarına slice nesnesi geçirmektedir. Yani yorumlayıcı dilimleme sentaksını gördüğünde bir slice nesnesi yaratıp onu - da __getitem__ ve __setitem__ metotlarına argüman olarak geçirmektedir. Örneğin: - - result = a[10:20:2] - - işleminin eşdeğeri şöyledir: - - result = a.__getitem__(slice(10, 20, 2)) - - result = a[10:20] - - işleminin eşdeğeri şöyledir: - - result = a.__getitem__(slice(10, 20, None)) - - Örneğin: - - result = a[10:20:] - - işleminin eşdeğeri şöyledir: - - result = a.__getitem__(slice(10, 20, None)) - - Örneğin: - - result = a[:10:2] - - işleminin eşdeğeri şöyledir: - - result = a.__getitem__(slice(None, 10, 2)) - - Örneğin: - - result = a[::] - - işleminin eşdeğeri şöyledir: - - result = a.__getitem__(slice(None, None, None)) - - Tabii aynı durum __setitem__ metodu için de geçerlidir. Örneğin: - - s[10:20:2] = 100 - - Bu işlemin eşdeğeri de şöyledir: - - s.__setitem__(slice(10, 20, 2), 100) - - Aşağıda hangi dilimleme sentaksında nasıl bir slice nesnesinin yaratılacağı bir liste halinde verilmiştir: - - s[x:y:z] ---> slice(x, y, z) - s[:y:z] ---> slice(Nobe, y, z) - s[x:y] ---> slice(x, y, None) - s[x:y:] ---> slice(x, y, None) - s[x:y] ---> slice(x, y, None) - s[x:y:z] ---> slice(x, y, z) - s[x::z] ---> slice(x, None, z) - s[::] ---> slice(None, None, None) - - Örneğin biz listelerde, demetlerde ve string'lerde dilimleme yaparken dilimleme yerine doğrudan slice nesnelerini - kullanabiliriz. Zaten yorumlayıcı dilimlemeyi gördüğünde onu slice nesnelerine dönüştürmektedir. Örneğin: - - >>> a = [10, 20, 30, 40, 50, 60] - >>> a[2:4] - [30, 40] - >>> a[slice(2, 4, 1)] - [30, 40] - - O halde biz __getitem__ ve __setitem__ metotlarını yazarken index parametresinin bir slice nesnesi olup olmadığını isinstance - fonksiyonu ile kontrol edip dilimleme mantığına uygun işlemleri yapabiliriz. Böylece sınıfımıza dilimleme detseği - vermiş oluruz. - - Aşağıdaki örnekte aslında __getitem__ metodunun index parametresi bir slice nesnesi olsa da zaten args örnek özniteliği - bir liste olduğu için o listede doğrudan kullanılabilirdi. Ancak biz bu örnekte genel olarak bu metotlar içerisinde slice - nesnelerinin fark edilip işleme sokulması hakkında ipucu veriyoruz. -#------------------------------------------------------------------------------------------------------------------------ - - -class Sample: - def __init__(self, *args): - self.args = list(args) - - def __getitem__(self, index): - if isinstance(index, int): - return self.args[index] - - if isinstance(index, slice): - return self.args[index] - - raise TypeError('index invalid type') - - """ - def __getitem__(self, index): - return self.args[index] - """ - - def __setitem__(self, index, value): - self.args[index] = value - - def __len__(self): - return len(self.args) - - def __repr__(self): - return repr(self.args) - -s = Sample(10, 20, 30, 40, 50, 60, 70, 80, 90, 100) -print(s) - -result = s[2:5] -print(result) - -result = s[:5] -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte Date sınıfına köşeli parantez desteği verilmiştir. Yani bu örnekte Date nesnesinin gün, ay, yıl bileşenlerine - sanki onlar bir diziymiş gibi erişebilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __getitem__(self, index): - if isinstance(index, int): - match index: - case 0: - return self.day - case 1: - return self.month - case 2: - return self.year - case _: - raise ValueError('invalid index') - - raise TypeError('invalid index type') - - def __setitem__(self, index, value): - if isinstance(index, int): - match index: - case 0: - self.day = value - case 1: - self.month = value - case 2: - self.year = value - case _: - raise ValueError('invalid index') - else: - raise TypeError('invalid index type') - - def __repr__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - -d = Date(2, 11, 2023) -print(d) - -result = d[0] # result = d.__getitem__(0) -print(result) - -result = d[1] -print(result) # result = d.__getitem__(1) - -result = d[2] -print(result) # result = d.__getitem__(2) - -d[1] = 8 # d.__setitem__(1, 8) - -print(d) - -#------------------------------------------------------------------------------------------------------------------------ - Python'a 3'lü versiyonlarla birlikte "... (Ellipsis)" biçiminde bir sabit de eklenmiştir. Önceleri bu sabit yalnızca - köşeli parantez içerisinde kullanılıyordu. Sonradan genelleştirildi. "..." sabiti aslında Ellipsis isimli anahtar sözcük - olan bir değişkenle de temsil edilmektedir. Başka bir deyişle: - - a = ... - - ile - - a = Ellipsis - - aynı anlamdadır. Tıpkı None sabitinde olduğu gibi Ellipsis sabiti için de toplamda tek bir nesne vardır. Yani program içerisindeki - bütün "..." sabitleri ve Ellipsis değişkeni aslında aynı nesneyi göstermektedir. Bunun için iki Ellipsis nesnesi == ve != - operatörleriyle karşılaştırılabilir ya da benzer biçimde is ve is not operatötleriyle de karşılaştırmalar yapılabilir. Örneğin: - - >>> a = ... - >>> a - Ellipsis - >>> type(a) - - >>> id(a) - 140734384027912 - >>> b = Ellipsis - >>> type(b) - - >>> id(b) - 140734384027912 - >>> a == b - True - >>> a is b - True - >>> ... is Ellipsis - True - - Eskiden Ellipsis değişkeninin türü için Python referans kitaplarında bir belirlemede bulunulmamıştı. Python 3.10 ile - birlikte Ellipsis değişkeninin types modülündeki EllipsisType isimli bir sınıf türünden olduğu kabul edilmiştir. - Bu EllipsisType sınıfının __repr__ ve __str__ metotları "Ellipsis" yazısını vermektedir. - - Pekiyi Ellipsis ne işe yaramaktadır? Aslında Python referans kitabında Ellipsis değerinin ne işe yaradığı konusunda - bir açıklamada bulunulmamıştır. Programcılar ve kütüphaneleri geliştirenler bu Ellipsis değerine kendileri işlevler - yüklemektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python çok modelli (multi-paradigm) bir programalama dilidir. Biz Python'ı istersek sınıf konusuna girmeden tamamen - prosedürel bir biçimde kullanabiliriz. NYPT özellikle büyük projeleri mantıksal bakımdan oluşturabilmek için düşünülmüştür. - Eğer kodlarımız çok uzun ve kapsamlı değilse Python'ın sınıfsal özelliklerini kullanmamıza gerek olmayabilir. Tabii daha - önceden de belirttiğimiz gibi Pythoon'ın standart kütüphanesinde hem fonksiyonlar hem de sınıflar bulunmaktadır. Temel veri - yapıları bile (list, tuple, dict gibi) birer sınıf belirttiğine göre Python programcılarının sınıf kavramını biliyor ve sınıfları - kullanabiliyor olması gerekir. - - Daha önceden de belirttiğimiz gibi NYPT'de olgular sınıflarla temsil edilmektedir. Örneğin tarih olgusu Date isimli bir - sınıfla, Soket olgusu Socket isimli bir sınıfla, çalışan olgusu Employee isimli bir sınıfla temsil edilebilir. Sınıflardaki - metotlar nesnenin özniteliklerini ortak bir biçimde kullanmaktadır. Yani öznitelikler aslında metotlar tarafından ortak - kullanılan veri elemanlarıdır. Ancak bazı fonksiyonları nesnenin özniteliklerini hiç kullanmadıkları halde konu itibari ile - bazı sınıflarla ilgili olabilmektedir. Örneğin tarih işlemlerini yapan Date isimli bir sınıfımız olsun. Bir yılın artık - olup olmadığını veren bir isleap isimli bir fonksiyonun da olduğunu düşünelim. isleap fonksiyonu parametre olarak bir - yıl bilgisini alıp onun artık olup olmadığına yönelik bool bir değer geri döndürüyor olabilir. Örneğin: - - def isleap(year): - return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 - - Buradaki isleap fonksiyonu mantıksal olarak Date sınıfının konusuyla ilgilidir. NYPT'de bu fonksiyonun Date sınıfının - içerisinde bulundurulması iyi bir tekniktir. Ancak biz bu fonksiyonu Date sınıfının içine alırsak ona bir self parametresini - de eklememiz gerekir. Örneğin: - - class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __repr__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - - def isleap(self, year): - return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 - - Burada aslında isleap nesnenin özniteliklerini kullanmadığı halde metot olabilmek için bir self parametresi almak zorunda - kalmıştır. Üstelik artık bizim bu isleap metodunu çağırabilmemiz için metodun kullanmadığı bir nesne yaratmamız gerekir. - Örneğin: - - d = Date(0, 0, 0) - - result = d.isleap(2024) - - O halde bizim şöyle bir özelliğe gereksinimimiz vardır: Bir metot hem bir sınıf içerisinde bulunsun hem dezaten kullanmayacağı - self parametresini almasın. Bu işlem aşağıdaki gibi sağlanamaz: - - class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __repr__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - - def isleap(year): - return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 - - Burada islep metodunda self parametresini silmek bize bir fayda sağlamaz. Çünkü self parametresinin ismi self olmak zorunda - değildir. Metodun ilk parametresi her zaman self parametresi olarak ele alınacaktır. İşte nesne yönelimli programlama - dillerinde bu gereknimin saplanabilmesi için "static metot" kavramı kullanılmaktadır. static metot demek self parametresi - olmayan dolayısıyla nesnenin özniteliklerini kullanmayan ancak konu bakımından sınıfla ilişkili olan metot demektir. - - Python'da bir metodu static yapabilmek için metodun @staticmethod isimli bir dekoratörle dekore edilmesi gerekmektedir. - Dekoratörler konusu izleyen bölümlerde ele alınmaktadır. Bir dekoratör bir fonksiyonu, bir metodu ya da bir sınıfı - dekore edebilir. Dekoratör kullanımının genel biçimi şöyledir: - - @dekoratör_ismi - - Dekoratörler fonksiyonların metotların ve sınıfların hemen üstünde aynı girinti düzeyine sahip biçimde bulundurulmalıdır. - Örneğin: - - @xxx - def foo(): - pass - - O halde islep metodunu aşağıdaki gibi static bir metot haline getirebiliriz: - - class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __repr__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - - @staticmethod - def isleap(year): - return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 - - Artık burada isleap metodunun year parametresi birinci parametre olmasına karşın self anlamında dğeildir. Yani metot artuk - self parametresine sahip değildir. - - Static metotlar nesnenin özniteliklerini kullanmadığına göre onların çağrılmaıs için bir nesneye gereksinim yoktur. İşte - static metotlar sınıf ismi ve metot ismi belirtilerek çağrılırlar. Örneğin: - - result = Date.isleap(2024) - -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __repr__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - - @staticmethod - def isleap(year): - return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 - -result = Date.isleap(2024) - -print('Artık yıl' if result else 'artık yıl değil') - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi biz bir metodun static olup olmaması gerektiğini nasıl anlayabiliriz? İşte eğer bir metot aslında metot olarak - değil de global bir fonksiyon olarak da yazaılabiliyorsa bu metot static bir metot olmaya adaydır. Metodun belli bir - nesnenin özniteliklerini kullanıp kullanmadığına da bakabilirsiniz. Eğer metot nensnein özniteliklerini kullanmıyorsa - static metot yapılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 50. Ders 19/10/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin Date sınıfına o anki tarih bilgini bize bir Date nesnesi olarak veren today isimli bir metot eklemek isteyelim. - Bu metot işletim sisteminden bu tarih bilgisini alıp onu bir Date nesnesi haline getirip bu Date nesnesini bie verecektir. - Söz konusu today metodu belli bir nesnenin özniteliklerini kullanmamaktadır. Bize yeni bir nesne vermektedir. O halde bu - metot static bir metot yapılabilir. Aşağıdaki örneği inceleyiniz. -#------------------------------------------------------------------------------------------------------------------------ - -import datetime - -class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - - def __repr__(self): - return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' - - @staticmethod - def isleap(year): - return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 - - @staticmethod - def today(): - dt = datetime.date.today() - return Date(dt.day, dt.month, dt.year) - -date = Date.today() -print(date) - -#------------------------------------------------------------------------------------------------------------------------ - static metotların normal olarak sınıf ismiyle çağrılması gerekir. Çünkü onlar nesnenin veri elemanlarını (özniteliklerini) - kullanmamaktadır. Ancak ek bir özellikl olarak C++, Java ve Python gibi dillerde static metotların bir nesneyle (yani - sınıf türünden bir değişkenle) çağrılabilmesine de izin verilmiştir. Ancak static metotlar bir değişkenle çağrılsa bile - bu değişken metodun birinci parametresine (self parametresine) aktarılmamaktadır. Bu değişken yanızca sınıfı belirlemektedir. - Örneğin Date sınıfının isleap metodu normalde aşağıdaki gibi çağrılmalıdır: - - result = Date.isleap(2024) - - Ancak elimizde Date sınıfı türünden bir nesne (değişken) varsa biz bu static metodu onunla da çağırabiliriz. Örneğin: - - d = Date(5, 12, 2009) - - result = d.isleap(2024) - - Aslında bu çağrı aşağıdakiyle eşdeğerdir: - - result = type(d).isleap(2024) - - Görüldüğü gibi static metotlar ilgili sınıf türünden değişkenlerle çağrılabiliyorsa da aslında bu değişkenler metot - tarafından kullanılmamaktadır. Üstelik bu çağrı biçimi yanlış anlaşılmalara da zemin hazırlamaktadır. Örneğin: - - result = d.isleap(2024) - - Böyle bir çağrıyı gören kişi isleap metodunun static metot olduğunu anlamayabilir. Bu durumda kodu yanlış anlamlandırabilir. - Halbuki örneğin: - - result = Date.isleap(2024) - - Burada metodun static bir metot olduğu anlaşılmaktadır. - - Sonuç olarak her ne kadar static metotlar ilgili sınıf türünden değişkenlerle çağrılabiliyorsa da bu iyi bir teknik - değildir. Kodu inceleyenleri yanlış yönlendirebilmektedir. C++ ve Java'da da bu özellik vardır. Ancak C# bunun yanlış - anlaşılmalara yol açabileceği nedeniyle bunu yasaklamıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların statik metotlara benzer ismine "sınıf metotları (class methods)" denilen metotları da olabilmektedir. Sınıf - metotları ile statik metotlar tamamen benzer amaçlarla kullanılırlar. Bunların kullanım gerekçeleri ve kullanım biçimleri - tamamen aynıdır. Ancak sınıf metotlarının ekstra bir parametresi bulunmaktadır. Sınıf metotlarının birinci parametreleri - metodun çağrılmasında kullanılan sınıfın type nesnesini almaktadır. Bu parametre geleneksel olarak "cls" biçiminde - isimlendirilmektedir. Sınıf metotları @classmethod isimli dekoratörle dekore edilirler. Örneğin: - - class Sample: - @staticmethod - def foo(): - pass - - @classmethod - def bar(cls): - pass - - def tar(self): - pass - - Burada foo bir static metottur. bar ise bir sınıf metodudur. tar metodu normal bir metottur. bar metodunun cls parametresi - self anlamında değildir. Bu parametreye sınıfın type nesnesi geçirilmektedir. - - Sınıf metotları da ilgili sınıf türünden bir değişkenle çağrılabilmektedir. Ancak bu çağrı yine yanlış anlaşılmalara - yol açtığı için iyi bir teknik değildir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - @classmethod - def foo(cls): - print(cls) # - print(cls is Sample) # True - -Sample.foo() - -s = Sample() -s.foo() - -#------------------------------------------------------------------------------------------------------------------------ - Static metotlar varken sınıf metotlarına neden gereksinim duyulduğunu merak edebilirsiniz. Aslında sınıf metotları static - metotlarle aynı mantığa sahip olsa da static metot yerine sınıf metodunun kullanılması gerektiği yerler olabilmektedir. - Bir static metot türemiş sınıf sınıf ismiyle de çağrılabilmektedir. Örneğin: - - class A: - @staticmethod - def foo(): - pass - - class B(A): - pass - - A.foo() # geçerli - B.foo() # geçerli - - Taban sınıfta bir sınıf metodu bulunyor olabilir. Bu sınıf metodu türemiş sınıf ismiyle çağrılırsa metodun cls parametresine - türemiş sınıfın type nesne referansı, taban sınıf ismiyle çağrılırsa taban sınıfın type nesne referansı geçirilecektir. - Böylece biz bu metodun hangi sınıf ismi ile çağrıldığını anlayıp bazı durumlarda bu bilgiden faydalanabilir. - - Aşağıda örnekte taban sınıftaki foo sınıf metodu static bar metodunu çağırmıştır. Ancak foo sınıf metodu hangi sınıfla - çağrılmışsa onun bar metodunu çağırmak istemektedir. Bu işlem static metotlarla sağlanamaz. Siz özellikle gerekmedikçe - sınıf metodu yerine static metodu tercih etmelisiniz. -#------------------------------------------------------------------------------------------------------------------------ - -class A: - @classmethod - def foo(cls): - print('A.foo') - cls.bar() - - @staticmethod - def bar(): - print('A.bar') - -class B(A): - @staticmethod - def bar(): - print('B.Bar') - -A.foo() -print('-------------') -B.foo() - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi metotlarda alternatif bir çağrı biçimi de bulunmaktadır. Örneğin: - - class Sample: - def foo(self, a, b): - pass - - Biz buaradaki normal foo metodunu normal olarak Sample türünden bir değişkenle çağrırız: - - s = Sample() - s.foo(10, 20) - - Burada s, metodun self parametresine, 10 metodun a parametresine 20 de metodun b parametresine aktarılacaktır. - Bu çağrının alternatif olarak şöyle de yapılabileceğini belirtmiştik: - - s = Sample() - - Sample.foo(s, 10, 20) - - O halde static metotları @staticmethod ile dekore etmeden bu alternatif yöntemle çağıramaz mıyız? Örneğin: - - class Sample: - def foo(a, b): - print(f'a = {a}, b = {b}') - - Sample.foo(10, 20) - - Her ne kadar bu yöntem bu haliyle uygulanabilir bir yöntem gibi görünüyorsa da bu metot Sample sınıfı türünden bir - değişkenle çağrıldığında sorun ortaya çıkacaktır. Örneğin: - - s = Sample() - s.foo(10, 20) # exception oluşur - - Çünkü foo static bir metot olmadığı için buradaki s self parametresine atanacaktır. self parametresinin ise isminin self - olması gerekmemektedir. fonksiyonun a parametresi self gibi ele alınacak ve exception oluşacaktır. Halbuki metodu staticmethod - dekoratörü ile dekore edersek her iki durumda da sorun oluşmayacaktır: - - class Sample: - @staticmethod - def foo(a, b): - print(f'a = {a}, b = {b}') - - s = Sample() - s.foo(10, 20) # sorun yok, s kullanılmayacak - - Sample.foo(10, 20) # sorun yok -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - NYPT'de "dekoratör (decorator)" denilen bir "tasarım kalıbı (design pattern)" vardır. Python'da bu tasarım kalıbı sentaks - bakımından desteklenerek bir dil öğesi haline getirilmiştir. Bu sayede bazı tasarımlar daha özlü bir biçimde yapılabilmektedir. - - Python'da dekoratörler fonksiyonlar, metotlara ve sınıflara uygulanabilmektedir. Bir dekoratör bir fonksiyona ya da metoda uygulanmışsa - bunlara "fonksiyon dekoratörleri", bir sınıfa uygulanmışsa bunlara da "sınıf dekoratörleri" denilmektedir. Bir fonksiyonu ya da sınıfı - dekora edebilmek için fonksiyon ya da sınıfın üstüne aynı girinti düzeyine sahip olacak biçimde @dekoratör_ismi biçiminde bir sentaksın - eklenmesi gerekir. Örneğin: - - @foo - def bar(): - pass - - Burada bar fonksiyonu dekore edilmiştir. Buradaki dekoratör foo'dur. Örneğin: - - @foo - class Sample: - pass - - Burada da Sample sınıfı dekore edilmiştir. Yine buradaki sekoratör foo'dur. - - Bir dekorasyon yaparken @ sembolünün sağındaki ismin "çağrılabilen (callable)" bir nesne belirtmesi gerekir. Yani bu isim - tipik olarak bir fonksiyon ismi olabilir ya da __call__ metodu bulunan bir sınıf nesnesi olabilir. Buradaki çağrılabilen - nesnenin bir parametrenin olması gerekmektedir. Eğer çağrılabilen (callable) nesne bir sınıf nesnesi ise __call_ metodunun - self dışında ekstra bir parametresinin bulunması gerekmektedir. Örneğin: - - def foo(f): - pass - - @foo - def bar(): - pass - - Burada foo dekoratördür. foo fonksiyonunun bir parametresi vardır. O halde kullanım geçerlidir. Örneğin: - - class Sample: - def __call_(self, f): - pass - - @Sample - def bar(): - pass - - Burada da kullanım geçerlidir. Çünkü Sample sınıfının __call_ metodunun self parametresi dışında ekstra bir parametresi - daha vardır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki gibi dekore edilmş bir fonksiyon olsun: - - @foo - def bar(): - pass - - Bunun tamamen eşdeğeri şöyledir. - - def bar(): - pass - bar = foo(bar) - - Yani @ atomunun yanındaki bir fonksiyondur (genel olarak calllable bir nesne). Dekore edilmiş fonksiyon bu fonksiyona - parametre yapılıp yeniden bu fonksiyon ismine atanmıştır. Bu durumda artık dekore edilmiş fonksiyonu çağıran birisi - aslında dekoratör fonksiyonun geri döndürdüğü fonksiyonu çağırmış olacaktır. Yani örneğin: - - @foo - def bar(): - pass - - Burada artık bar ismi buradaki bar fonksiyonunu değil foo fonksiyonun geri döndürüğü fonksiyonu belirtmektedir. Biz bu - dekoratör işleminden sonra bar fonksiyonunu çağırdığımızda artık bar fonksiyonunu çağırmış olmayacağız. foo fonksiyonunun - geri döndürdüğü fonksiyonu çağırmış olacağız. Örneğin: - - def foo(f): - print('foo') - return f - - @foo - def bar(): - print('bar') - - Bu işlemin eşdeğeri şöyledir: - - def foo(f): - print('foo') - return f - - def bar(): - print('bar') - - bar = foo(bar) - - Burada aslında foo fonksiyonu çağrılmış foo fonksiyonunun geri dönüş değeri yine bar değişkenine atanmıştır. bar değişkeni - yine bar fonksiyonunu belirtmektedir. Ancak ekranda "foo" yazısı gözükecektir. - - Örneğin: - - def foo(): - print('foo') - - def bar(f): - print('bar') - return foo - - @bar - def tar(): - pass - - tar() - - Bu örnekte dekoratör fonksiyonu olan bar fonksiyonu foo fonksiyonu ile geri dönmektedir. O halde burada taslında tar - fonksiyonu çağrıldığında foo fonksiyonu çağrılacaktır. Yukarıdaki kodun eşdeğeri şöyledir: - - def foo(): - print('foo') - - def bar(f): - print('bar') - return foo - - def tar(): - pass - tar = bar(tar) - - tar() - - Dekoratör kalıbında programcı dekorasyon işleminden sonra bir fonksiyonu çağırmak istediğinde aslında başka bir - fonksiyonu çağırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('foo') - -def bar(f): - print('bar') - return foo - -@bar -def tar(): - pass - -tar() - -#------------------------------------------------------------------------------------------------------------------------ - Dekoratörlerin en önemli kullanım nedeni "araya girme" işlemini gerçekleştirmektir. Yani biz bir fonksiyon çağırırken - aslında başka bir fonksiyonu çağırırız, o fonksiyon da birşeyler yaptıktan sonra asıl fonksiyonu çağırır. Böylece biz - fonksiyonu her çağırdığımızda aslında bir araya girme işlemi yapılmış olur. Bu araya girme işlemi iki biçimde - gerçekleştirilmektedir: - - 1) İç bir fonsiyon kullanılarak - 2) __call__ metodu bulunan bir sınıf kullanılarak - - Birinci yöntemde dekoratör fonksiyonu iç bir fonksiyn ile geri döner. Böylece asıl fonksiyon çağrıldığında aslında iç - fonksiyon çağrılmış olur. İç fonksiyon dış fonksiyonun yerel değişkenlerini ve parametre değişkenlerini kullanabildiği - için asıl fonksiyonu çağırabilmektedir. Örneğin: - - def foo(f): - def bar(): - print('araya giren kod') - f() - - return bar - - @foo - def tar(): - print('tar') - - tar() - print('---------') - tar() - - - Burada aslında tar fonksiyonu çağrıldığında iç fonksiyon olan bar fonksiyonu çağrılacaktır. Ancak bar fonksiyonu araya - girme işlemini yaptıktan sonra asıl fonksiyon olan tar fonksiyonunu da çağırmaktadır. Böylece bu dekorasyondan sonra - tar fonksiyonu çağrıldığında yine tar fonksiyonu çağrılacaktır. Ancak araya bir kod da girilmiş olacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(f): - def bar(): - print('araya giren kod') - f() - - return bar - -@foo -def tar(): - print('tar') - -tar() -tar() - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki araya girme işleminde bir sorun vardır. Burada dekore edilen fonksiyon (tar fonksiyonu) parametreliyse - sorun çıkacaktır. Çünkü bu fonksiyon bar içerisinde sanki parametresiz gibi çağrılmıştır. Bu sorunu engellemenin basit - yolu bar fonksiyonunun *'lı ve **'lı parametrelerinin olması ve bu parametrelerin de *'lı ve **'lı argümanlarla orijinal - fonksiyona aktarılmasıdır. Örneğin: - - def foo(f): - def bar(*args, **kwargs): - print('araya giren kod') - return f(*args, **kwargs) - - return bar - - @foo - def tar(a, b, c): - print(f'tar: {a}, {b}, {c}') - - Burada tar çağrılırken girilen argümanlar aslında bar fonksiyonuna girilmiş gibi lacaktır. Çünkü yukarıdaki dekorasyonun - eşdeğeri şöyledir: - - tar = foo(tar) - - O halde örneğin biz tar(10, 20, 30) gibi bir çağrı yaptığımızda aslında bar(10, 20, 30) gibi bir çağrı yapmış oluruz. - Bu 10, 20, 30 parametreleri bar fonksiyonunun args parametresine bir demet olarak aktarılacaktır. bar fonksiyonunun kargs - parametresi boş sözlükten oluşacaktır. bar fonksiyonu içerisinde f(*args, **kwargs) çağrımı aslında tar fonksiyonunun - 10, 20, 30 argümanlarıyla çağrımı anlamına gelecektir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(f): - def bar(*args, **kwargs): - print('araya giren kod') - f(*args, **kwargs) - - return bar - -@foo -def tar(a, b, c): - print(f'a = {a}, b = {b}, c = {c}') - -tar(10, 20, 30) - -#------------------------------------------------------------------------------------------------------------------------ - Araya girme işlemi yukarıda da ikinci maddede belirttiğimiz gibi __call__ metodu uygun bir biçimde yazılmış olan bir - sınıf yoluyla da yapılabilir. Sınıflar durumsal bilgiyi tutabildikleri için genel olarak bu yöntem tercih edilmekedir. - - @foo - def bar(a, b, c): - print(f'a = {a}, b = {b}, c = {c}') - - Burada foo'nuın bir sınıf olduğunu düşünelim. Bu durumda yukarıdaki kodun eşdeğeir şöyle olur: - - bar = foo(bar) - - Artık bar bir fonksiyon değil foo sınıfı türünden bir nesne belirtmektedir. O halde biz artık bar(...) - çağrısını yaptığımızda foo sınıfının __call__ metodu çağrılacaktır. - - class foo: - def __init__(self, f): - self.f = f - - def __call__(self, *args, **kwargs): - print('araya girilen kod') - self.f(*args, **kwargs) - - @foo - def bar(a, b, c): - print(f'a = {a}, b = {b}, c = {c}') - - bar(10, 20, 30) - - foo sınıfının __init__ metodu içerisinde alınan fonksiyonun nesnenin örnek özniteliğinde saklandığına dikkat ediniz. - Sonra bu fonksiyon __call__ metodunda çağrılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class foo: - def __init__(self, f): - self.f = f - - def __call__(self, *args, **kwargs): - print('araya girilen kod') - self.f(*args, **kwargs) - -@foo -def bar(a, b, c): - print(f'a = {a}, b = {b}, c = {c}') - -bar(10, 20, 30) -print('---------------') -bar(40, 50, 60) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi dekoratörler araya girmeyi sağlıyorsa araya girmenin na anlamı olabilir? İşte araya giren kod arka planda bizim - için bazı faydalı işlemleri yapıyor olabilir. Örneğin araya giren kod bir log oluşturabilir ya da faydalı başka şeyleri - yapıyor olabilir. Aşağıdaki örnekte counter isimli dekoratör sınıfı fonksşyon her çağrıldığında count sınıf değişkenini - (class attribute) 1 artırmaktadır. Böylece counter.count ifadesi ile biz dekore edilen fonksiyonun program çalışırken - kaç kere çağrılmış olduğunu anlayabiliriz. Örneğin: - - class counter: - count = 0 - - def __init__(self, f): - self.f = f - - def __call__(self, *args, **kwargs): - counter.count += 1 - self.f(*args, **kwargs) - - @counter - def foo(): - print('foo') - - foo() - foo() - foo() - foo() - foo() - - print(counter.count) # 5 - - Burada foo fonksiyonunu çağıracak kişinin tek yapacağı şey fonksiyonunu counter sınıfıyla dekore etmektir. -#------------------------------------------------------------------------------------------------------------------------ - -class counter: - count = 0 - - def __init__(self, f): - self.f = f - - def __call__(self, *args, **kwargs): - counter.count += 1 - self.f(*args, **kwargs) - -@counter -def bar(): - print('bar') - -bar() -bar() -bar() -bar() -bar() - -print(counter.count) # 5 - -#------------------------------------------------------------------------------------------------------------------------ - Tabii dekorasyonun illa da @ atomuyla yapılması gerekmez. Biz dekorasyonla eşdeğer olan çağrıyı kendimiz de yapabiliriz. - Özellikle bizim yazmadığımız sınıfları dekore etmek için @ sentaksı yerine açıkça çağırma sentaksı kullanılmaktadır. - Örneğin: - - import math - - class abs_decorator: - def __init__(self, f): - self.f = f - - def __call__(self, val): - val = abs(val) - return self.f(val) - - sqrt = abs_decorator(math.sqrt) - - result = sqrt(10) - print(result) - - result = sqrt(-10) - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin daha önce görmüş olduğumuz @staticmethod ve @classmethod dekoratörleri de aslında built-in birer çarılabilir (callable) - nesnelerdir. Dolayısıyla biz bunları @ sentaksını kullanmadan alternatif biçimde de kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - @staticmethod - def foo(): - print('foo') - - def bar(): - print('bar') - bar = staticmethod(bar) - -Sample.foo() -Sample.bar() - -#------------------------------------------------------------------------------------------------------------------------ - 51. Ders 24/10/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Görüldüğü gibi dekoratörler aslında arka planda birtakım işlemlerin pratik bir biçimde yapılması için araç bir oluşturmaktadır. - Örneğin bir dekoratör fonksiyonun çalışma zamanını ölçerek geri dönüş değeri biçiminde bize verebilir: - - import time - - class profiler: - def __init__(self, f): - self.f = f - - def __call__(self, *args, **kwargs): - start = time.time() - result = self.f(*args, **kwargs) - stop = time.time() - - return stop - start, result - - @profiler - def add_total(n): - total = 0 - for i in range(n): - total += i - - return total - - rtime, result = add_total(100000000) - print(result, rtime) - - Burada herhangi bir fonksiyon profiler isimli dekoratörle dekore edildiğinde artık fonksiyon bir demete geri döner. - Demetin birinci elemanı fonksiyonun çalışma zamanını, ikinci elemanı da fonksiyonun geri dönüş değerini belirtmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import time - -class profiler: - def __init__(self, f): - self.f = f - - def __call__(self, *args, **kwargs): - start = time.time() - result = self.f(*args, **kwargs) - stop = time.time() - - return stop - start, result - -@profiler -def add_total(n): - total = 0 - for i in range(n): - total += i - - return total - -rtime, result = add_total(100000000) -print(result, rtime) - -rtime, result = add_total(10000000) -print(result, rtime) - -#------------------------------------------------------------------------------------------------------------------------ - Dekoratörler fonksiyonların yanı sıra sınıf tanımlamalarında da kullanılabilmektedir. Bunlara sınıf dekoratörleri - denilmektedir. Sınıf dekoratörlerinin genel biçimi şöyledir: - - @dekoratör_ismi - class sınıf_ismi: - pass - - Sınıf dekoratörlerinde de dekoratör yine bir fonksiyon ya da sınıf olabilir. Mekanizmanın çalışma biçimi aynıdır. Yani: - - @foo - class bar: - pass - - işleminin eşdeğeri şöyledir: - - class bar: - pass - - bar = foo(bar) - - Örneğin: - - def foo(cls): - print('araya giren kod') - return cls - - @foo - class Sample: - pass - - Buradaki kodun eşdğeri şöyledir: - - def foo(cls): - print('araya giren kod') - return cls - - class Sample: - pass - - Sample = foo(Sample) - - Burada foo fonksiyonu çağrıldığında ekrana "araya giren kod" yazısı çıkacaktır. foo fonksiyonunun paramatresiyle aldığı - aynı sınıf ile geri döndüğüne dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(cls): - print('araya giren kod') - return cls - -@foo -class Sample: - pass - -#------------------------------------------------------------------------------------------------------------------------ - Sınıf dekoratörleri sayesinde örneğin biz bir sınıf türünden nesne yaratıldığında araya girip bir şeyler yapabiliriz. - Genellikle burada araya giren kodlar nesne üzerinde bazı öznitelikleri de arka planda yaratmaktadır. - - Aşağıdaki örnekte Sample sınıfı türünden bir nesne yaratıldığında bar fonksiyonu çağrılmaktadır. Sample nesnesi bar - fonksiyonu tarafından yaratılmaktadır. Yani Sample(...) çağrısı salında bar(...) çağrısı anlamına gelmektedir. Bu - bar(...) çağrısı araya girme işlemini yapmaktadır. - - def foo(cls): - - def bar(*args, **kwargs): - obj = cls(*args, **kwargs) - print('araya giren kod') - - return obj - - return bar - - @foo - class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - def __repr__(self): - return f'a = {self.a}, b = {self.b}' - - s = Sample(10, 20) - k = Sample(30, 40) - - print(s) - print(k) -#------------------------------------------------------------------------------------------------------------------------ - -def foo(cls): - - def bar(*args, **kwargs): - obj = cls(*args, **kwargs) - print('araya giren kod') - - return obj - - return bar - -@foo -class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - def __repr__(self): - return f'a = {self.a}, b = {self.b}' - -s = Sample(10, 20) -k = Sample(30, 40) - -print(s) -print(k) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii yukarıdaki örnekte dekoratör olarak iç fonksiyon kullanmak yerine dekoratör bir sınıf da kullanılabilirdi. Yukarıda - belirttiğimiz gibi genellikle dekoratör olarak sınıfların kullanılması tercih edilmektedir. Örneğin: - - class foo: - def __init__(self, cls): - self.cls = cls - - def __call__(self, *args, **kwargs): - obj = self.cls(*args, **kwargs) - print('araya giren kod') - - return obj - - @foo - class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - def __repr__(self): - return f'a = {self.a}, b = {self.b}' - - Buradaki dekoratörün eşdeğeri aşağıdaki gibidir: - - Sample = foo(Smaple) - - Dolayısıyla Sample türünden nesne yaratan kişi aslında foo sınıfının __call__ metodunu çağırmaktadır. Orada da - Sample sınıfı türünden nesne yaratılıp araya girme işlemi yapılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -class foo: - def __init__(self, cls): - self.cls = cls - - def __call__(self, *args, **kwargs): - obj = self.cls(*args, **kwargs) - print('araya giren kod') - - return obj - -@foo -class Sample: - def __init__(self, a, b): - self.a = a - self.b = b - - def __repr__(self): - return f'a = {self.a}, b = {self.b}' - -s = Sample(10, 20) -k = Sample(30, 40) - -print(s) -print(k) - -#------------------------------------------------------------------------------------------------------------------------ - Bu tür sınıf dekoratörleri bazı framework'ler tarafından bazı karmaşık işlemleri yapmak için kullanılabilmektedir. - Örneğin bir dekoratörün sınıf içerisine gizlice bir metot eklemesi, sınıf türünden nesnelerin içerisine özniteliklerin - eklenmesi sık karşılaşılan durumlardandır. Örneğin biz bir sınıfa test isimli bir metot eklemek isteyelim ve sınıf - türünden her nesneye de count isimli bir özitelik eklemek isteyelim. Bunu şöyle yapabiliriz: - - def test(self): - print('test') - - class foo: - def __init__(self, cls): - self.cls = cls - cls.test = test - - def __call__(self, *args, **kwargs): - obj = self.cls(*args, **kwargs) - print('araya giren kod') - - return obj - - @foo - class Sample: - pass - - s = Sample() - s.test() - - Burda test fonksiyonun bir parametresinin olduğuna dikkat ediniz. Okunabilirliği artırmak için biz bu parametresyi self - biçiminde isimlendirdik. Burada test fonksiyonu aslında Sample sınıfına bir metot gibi eklenmektedir. O yüzden bu - self parametresini almıştır. -#------------------------------------------------------------------------------------------------------------------------ - -def test(self): - print('test') - -class foo: - def __init__(self, cls): - self.cls = cls - cls.test = test - - def __call__(self, *args, **kwargs): - obj = self.cls(*args, **kwargs) - print('araya giren kod') - - return obj - -@foo -class Sample: - pass - -s = Sample() -s.test() - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örnekler bu tür temalarla ilk kez karşılaşanlara kavramsal olarak karmaşık ve biraz da soyut gelmektedir. - Bu konuyla ilgili olan diğer ileri bir konu da "meta sınıflar (meta classes)" konusudur. Biz bu "meta sınıf" konusunu - Python Uygulamaları kursunda göreceğiz. Bu nispeten soyut konunun kişiler tarafından anlaşılması bazı konuları gördükten - ve uygulamaları yaptıktan sonra daha kolay olacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dekoratörler parametre de alabilmektedir. Örneğin log isimli bir dekoratör olsun. Bu dekoratör bir fonksiyon çağrıldığında - çağrılma zamanını, fonksiyonun çalışma zamanını bir log dosyasına yazıyor olsun. ama onun yazacağı dosyayı biz belirleyecek - olalım. İşte bu tür durumlarda dekoratörlerin parametre alması gerekebilmektedir. Örneğin: - - @log('test.log') - def foo(): - pass - - Parametreli dekoratörler aslında çağrılabilir (callable) bir nesnenin çağrısı sonucunda elde edilen çağrılabilir nesneyi - dekoratör olarak kullanmaktadır. Başka bir deyişle örneğin: - - @foo(a, b, c) - def bar(): - pass - - Tanımlamasının eşdeğeri şöyledir: - - bar = foo(a, b, c)(bar) - - Burada aslında foo nesnesi çağrılmış bunun geri dönüş değeri yeniden çağrılmıştır. Yani parametreli dekoratörün - çağrılması sonucunda yine bir çağrılabilir nesne elde edeilmelidir. Buradaki işlemi adım adım yeniden çözümleyelim: - - 1) Burada önce foo nesnesi ile çağırma yapılmıştır: - - temp1 = foo(a, b, c) - - 2) Bu çağrının sonucunda çağrılabilir bir nesne elde edilmelidir. Bu çağrılabilir nesne fobaro argüman yapılarak çağrılmıştır: - - temp2 = temp1(bar) - - 3) Bu nesne yeniden bar değişkenine atanmıştır: - - bar = temp2 - - Dolayısıyla burada artık bar fonksiyonunu çağırdığını sanan kişi aslında foo çağrısı sonucunda elde edilen nesnenin çağrısı - sonucunda elde edilen çağrılabilir nesneyi çağırmaktadır. - - Parametreli dekoratör yine iç fonksiyonlar yoluyla ya da sınıflar yolula gerçekleştirilebilir. İç fonksiyon yoluyla - gerçekleştirim yapılacaksa iç içe üç fonksiyon kullanılmalıdır. Örneğin: - - @foo(a, b, c) - def bar(): - pass - - Buradaki foo fonksiyonu şöyle tanımlanabilir: - - def foo(x, y, z): - def wrapper1(f): - def wrapper2(*args, **kwargs): - return f(*args, **kwargs) - return wrapper2 - return wrapper1 - - Aşağıdaki örnekte parametreli foo isimli dekoratör fonksiyonu iç fonksiyonlar kullanılarak yazılmıştır. Kodu çalıştırdıktan - sonra ekranda şu yazıları göreceksiniz: - - foo called - wrapper1 - wrapper2: 10, 20, 30 - bar - wrapper2: 10, 20, 30 - bar - - Örneğimizde aşağıdaki gibi bir dekorasyon yaapılmıştır: - - @foo(10, 20, 30) - def bar(): - print('bar') - - Bu dekorasyonun tamamen eşdeğeri şöyledir: - - def bar(): - print('bar') - - bar = foo(10, 20, 30)(bar) - - Burada önce foo fonksiyonu 10, 20, 30 argümanlarıyla çağrılmıştır. Bu çağrı ekrana "foo called" yazısını basacaktır. - Bu çağrıdan wrapper1 fonksiyonu ile dönülmüştür. Dolaysıyla bu geri döndürülen fonksiyon bar argümanıyla çağrılmıştır. - Şimdi ekrana "wrapper1" yazısı da basılacaktır. Bu fonksiyon da wrapper2 fonksiyonuyla geri döndürülmüş ve nihayetinde - wrapper2 fonksiyonu bar değişkenine atanmıştır. Artık bar fonksiyonunu çağırdıını sanan kişi aslında wrapper2 fonksiyonunu - çağırmış olmaktadır. İki kez bar çağrıldığından dolayı ekrana şunlar basılacaktır: - - foo called - wrapper1 - wrapper2: 10, 20, 30 - bar - wrapper2: 10, 20, 30 - bar - -#------------------------------------------------------------------------------------------------------------------------ - -def foo(x, y, z): - print('foo called') - def wrapper1(f): - print('wrapper1') - def wrapper2(*args, **kwargs): - print(f'wrapper2: {x}, {y}, {z}') - return f(*args, **kwargs) - return wrapper2 - return wrapper1 - -@foo(10, 20, 30) -def bar(): - print('bar') - -""" -Eşdeğeri -foo = foo(10, 20, 30)(bar) -""" - -bar() -bar() - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte her ne kadar henüz dosya işlemlerini görmemiş olsak da fonksiyon çağrıldıkça bir dosyaya log bilgilerini - yazan parametreli bir dekoratör örneği verilmiştir. Örnekte henüz görmediğimiz bazı modülleri ve fonksiyonları kullandık. - Burada log bilgisi olarak fonksiyonun çağrılma zamanı ve çalışma süresi kaydedilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import datetime -import time - -def log(path): - def wrapper1(f): - def wrapper2(*args, **kwargs): - file = open(path, 'a+', encoding='utf-8') - now = datetime.datetime.now() - file.write(f'Call time: {now}' + '\n') - - start = time.time() - retval = f(*args, **kwargs) - stop = time.time() - - file.write(f'Execution time: {stop - start}' + '\n') - file.write('-----------------------------------\n') - file.close() - return retval - return wrapper2 - return wrapper1 - -@log('log.txt') -def bar(): - for i in range(100000000): - pass - -bar() -time.sleep(2) -bar() - -#------------------------------------------------------------------------------------------------------------------------ - Parametreli dekoratörler de bir sınıf biçiminde yazılabilirler. Bu durumda dekoratör bir sınıf olur. Dolayısıyla dekorasyon - işlemi bir sınıf türünden nesnenin yaratılmasına yol açacaktır. İlgili sınıf çağrılabilir (callable) bir sınıftır. Dolayısıyla - elde edilen nesneyle çağrı uygulandığında aslında sınıfın __call__ metodu çağrılır. Bu metot da bize bir metot verir. Böylece - asıl fonksiyonu çağırdığını sanan kişi aslında söz konusu sınıfının bir metodunu çağırmaktadır. Örneğin: - - @foo(10, 20, 30) - def bar(): - pass - - Bu dekorasyonun eşdeğeri şöyledir: - - def bar(): - pass - - bar = foo(10, 20, 30)(bar) - - Burada şu açıklamaları yapabiliriz: - - 1) Eğer foo bir sınıf ise foo(10, 20, 30) çağrısı ile aslında foo sınıfı türünden bir nesne yaratılacaktır. Dolayısıyla - 10, 20 ve 30 foo sınıfının __init__ metoduna parametre olarak aktarılacaktır: - - class foo: - def __init__(self, x, y, x): - self.x = X - self.y = y - self.z = z - - 2) Elde edilen foo nesnesi ile foo(10, 20, 30)(bar) çağrısı yapılmıştır. Bu durumda sınıfın __call__ metodu çağrılacaktır: - - class foo: - def __init__(self, x, y, x): - self.x = X - self.y = y - self.z = z - - def __call__(self, f): - self.f = f - return self.proc - - Görüldüğü gibi burada biz bar fonksiyonunu da nesnenin içerisinde saklayabildik. __call__ metodunun da sınıfın bir metodu - ile geri döndüüğüne dikkat ediniz. - - 3) foo(10, 20, 30)(bar) çağrısı sonucunda geri döndürülen metot yine bar değişkenine atanmıştır. Artık bar çağrıldığında - aslında sınıfın proc metodu çağrılacaktır. Böylece artık biz bar fonksiyonunu çağırdığımızda bu fonksiyon kaydedieln bütün - bilgilere sekf yoluyla erişebilecektir: - - class foo: - def __init__(self, x, y, x): - self.x = X - self.y = y - self.z = z - - def __call__(self, f): - self.f = f - return self.proc - - def proc(self, *args, **kwargs): - print(f'Ayara giren kod: {self.x}, {self.y}, {self.z}') - return self.f(*args, **kwargs) - - Örnek bir bütün olarak aşağıda verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class foo: - def __init__(self, x, y, z): - self.x = x - self.y = y - self.z = z - - def __call__(self, f): - self.f = f - return self.proc - - def proc(self, *args, **kwargs): - print(f'Ayara giren kod: {self.x}, {self.y}, {self.z}') - return self.f(*args, **kwargs) - -@foo(10, 20, 30) -def bar(): - print('bar') - -bar() -bar() - -#------------------------------------------------------------------------------------------------------------------------ - Dekoratör olarak sınıf kullanarak yukarıdaki log örneğini de aşağıdaki gibi yapabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -import datetime -import time - -class log: - def __init__(self, file): - self.file = file - - def __call__(self, f): - self.f = f - return self.proc - - def proc(self, *args, **kwargs): - now = datetime.datetime.now() - self.file.write(f'Call time: {now}' + '\n') - - start = time.time() - retval = self.f(*args, **kwargs) - stop = time.time() - - file.write(f'Execution time: {stop - start}' + '\n') - file.write('-----------------------------------\n') - - return retval - -file = open('log.txt', 'w') - -@log(file) -def bar(): - for i in range(100000000): - pass - -""" -bar = log(f)(bar) -""" - -bar() -time.sleep(2) -bar() - -file.close() - -#------------------------------------------------------------------------------------------------------------------------ - Sonuç olarak biz bir fonksiyonun dekore edildiğini gördüğümüzde şunu düşünmeliyiz: "Bu fonyksiyonu çağırdığımda aslında - ben muhtemelen başka bir fonksiyonu çağırmış olacağım. Ama o fonksiyon da benim fonksiyonumu çağıracak. Fakat bu arada - benim faydama bazı şeyler de arka planda yapılmış olacak". Benzer biçimde bir sınıf dekoratörünü gördüğümüzde de - şunu düşünmeliyiz: "Bu sınıfa benim eklediklerimden başka şeyler de ekleniyor olabilir. Ben bu sınıf türünden nesne - yarattığımda arka planda başka şeyler de yapılıyor olabilir. Örneğin yarattığım nesneye bazı öznitelikler de ekleniyor - olabilir." -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 52. Ders 26/10/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İngilizce "exception" sözcüğü "istisna" anlamına gelmektedir. Ancak bu sözcük yazılımda "programın çalışma zamanı sırasında - oluşan problemli durumları" anlatmak için kullanılmaktadır. Exception mekanizması genel olarak nesne yönelimli programlama - dillerinde bulunmaktadır. Prosedürel dillerin çoğunda bu mekanizma yoktur. Exception programın çalışma zamanına ilişkin - bir kavramdır. Etimolojik kökeni donanımsal sorunlara dayanmaktadır. - - Bir exception oluştuğunda exception'ın ele alınması (handle edilmesi) gerekir. Eğer exception ele alınmazsa program çöker. - Python'da exception'ların birer sınıf ismi vardır. Bu isimler XXXError biçimindedir (örneğin TypeError, IndexError, ValueError gibi). - Örneğin: - - >>> int('ali') - Traceback (most recent call last): - File "", line 1, in - ValueError: invalid literal for int() with base 10: 'ali' - >>> 10 + 'ali' - Traceback (most recent call last): - File "", line 1, in - TypeError: unsupported operand type(s) for +: 'int' and 'str' - >>> a = [1, 2, 3] - >>> a[10] - Traceback (most recent call last): - File "", line 1, in - IndexError: list index out of range - - Bir exception oluştuğunda programın çökmemesi için exception'ın ele alınması (handle edilmesi) gerekir. Böylece hatalı bir - durum oluştuğunda programcı akış başka bir biçimde devam ettirebilir ya da hatayı düzeltip programın devam etmesini sağlayabilir. - Programcı exception'ı yakaladığında bunlardan hiçbirini yapamıyor olsa bile programın düzgün bir biçimde birtakım geri alım - işlemlerini yaparak sonlandırabilir. - - Bir exception oluştuğunda programcının devreye girmesine "exception'ın yakalanması" da denilmektedir. Dillerin çoğunda - exception'ın ortaya çıkmasına "exception'ın fırlatılması (throwing)" denilmektedir. Python'da bu bağlamda "fırlatma (throw) - yerine "raise etme" terimi kullanılmaktadır. - - Python'da exception'ların ele alınması için "try", "except", "else", "finally" ve "raise" anahtar sözcükleri kullanılmaktadır. - try deyiminin genel biçimi şöyledir: - - try: - - try deyimi tek başına bulundurulamaz. try deyimini except blokları ya da finally bloğu izlemek zorundadır. except ve finally - bloklarının genel biçimi şöyledir: - - except [ [as ]]: - finally: - - try bloğunu bir ya da birden fazla except bloğu izleyebilir. Ya da try bloğunu hiç except bloğu olmadan finally bloğu - da izleyebilir. Eğer try bloğunu except bloğu izliyorsa finally bloğu isteğe bağlı olarak except bloklarının sonuna - yerleştirilebilir. except anahtar sözcüğünün yanında bir exception sınıf ismi bulunur. Eğer birden fazla exception - sınıf ismi bulundurulacaksa parantezler içerisinde demet sentaksıyla bu sınıfların belirtilmesi gerekir. Exception sınıf - isimlerinden sonra isteğe bağlı olarak "as" anahtar sözcüğü ve değişken ismi getirilebilir. except bloklarından sonra istenirse - bir else bloğu da bulundurulabilmektedir. Bu durumda try blokları şu biçimlerde oluşturulabilir: - - 1) try bloğundan sonra bir grup except bloğu bulunabilir. - 2) try bloğundan sonra bir grup except bloğu ve en sonunda finally bloğu bulunabilir. - 3) try bloğundan sonra except bloğu olmadan hemen finally bloğu bulunabilir. - 4) try bloğundan sonra ve except bloklarından sonra ancak finally bloğundan önce bir else bloğu da bulunabilir. - - try anahtar anahtar sözcüğünden sonra, except cümleciğinden sonra, else anahtar sözcüğünden sonra ve finnfinally anahtar - sözcüğünden sonra bunları bir "suit" izlemek zorundadır. Biz burada suit yerine "blok" terimini kullanacağız. Örneğin - "try bloğu" dediğimizde "try" anahtar sözcüğü ve bir "suit" anlaşılmaıdır. - - except blokları oluşturulurken beş seçenek söz konusudur: - - 1) except anahtar sözcüğünü bir exception sınıf ismi izleyebilir. Örneğin: - - except ValueError: - - - 2) except anahtar sözcüğünü bir exception sınıf ismi ve "as" anahtar sözcüğü ile bir değişken ismi izleyebilir. Örneğin: - - except ValueError as e: - - - 3) except anahtar sözcüğünü parantezler içerisinde (yani demet sentaksıyla) bir ya da birden fazla exception sınıf ismi - izleyebilir. Örneğin: - - except (ValueError, TypeError): - - - 4) except anahtar sözcüğünü parantezler içerisinde (yani demet sentaksıyla) bir ya da birden fazla exception sınıf ismi - izleyebilir. Bunu da as anahtar sözcüğü ile bir değişken ismi izleyebilir. Örneğin: - - except (ValueError, TypeError) as e: - - - 5) except anahtar sözcüğünü bir şey izlemeyebilir. Örneğin: - - except: - - - Yukarıda da belirttiğimiz gibi finally bloğu bulundurulacaksa her zaman en sonda bulundurulmak zorundadır. Örneğin: - - try: - pass - except IndexError: - pass - except ValueError: - pass - finally: - pass - - Aşağıdaki sentaks da geçerlidir: - - try: - pass - finally: - pass - - Tabii finally bloğu da bulunmak zorunda değildir: - - try: - pass - except IndexError: - pass - - Ancak yalnızca try bloğu bulunamaz. except bloklarını bir else bloğu da izleyebilmektedir. Örneğin: - - try: - pass - except IndexError: - pass - except ValueError: - pass - else: - pass - finally: - pass - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Exception mekanizması şöyle çalışlmaktadır: Programın akışı try bloğuna girdikten sonra bir exception kontrolü uygulanır. - Akış try bloğuna girdikten sonra herhangi bir yerde exception oluşursa akış bir goto işlemi gibi oluşan exception'ın - türüne uygun olan except bloğuna aktarılır. İlgili except bloğu çalıştırılır, diğer except blokları atlanır. Akış except - bloklarının sonundan devam eder. Eğer programın akışı try bloğuna girdikten sonra hiç exception oluşmazsa akış try - bloğunun sonuna geldiğinde except blokları atlanır ve akış except bloklarının sonundan devam eder. Yani except blokları - "exception oluşursa çalıştırılmaktadır". Exception oluşmazsa onların bir işlevi kalmaz. Bir exception oluştuğunda akış - o exception ile aynı türden except bloğuna aktarılmaktadır. except bloklarının yalnızca bir tanesinin çalıştırıldığına - dikkat ediniz. (Yani bunlar adeta match/case gibi işlem görmektedir). Exception nerede oluşursa oluşsun akış except - bloğuna aktarıldıktan sonra bir daha geri dönmez. except bloklarının sonundan devam eder. finally bloğu ve parametresiz - except bloğu daha ileride ele alınacaktır. Bir exception oluştuğunda bu exception'ın türüne uygun bir except bloğunun - bulunyor olması gerekir. Aksi takdirde yine program çökecektir. -#------------------------------------------------------------------------------------------------------------------------ - -def tar(): - print('tar başladı') - int('xxxxx') # ValueError oluşacak - print('tar bitti') - -def bar(): - print('bar başladı') - tar() - print('bar bitti') - -def foo(): - print('foo başladı') - bar() - print('foo bitti') - -try: - foo() -except ValueError: - print('ValueError yakalandı') -print('program devam ediyor') - -#------------------------------------------------------------------------------------------------------------------------ - Python'da en çok karşılaşılan altı Exception sınıfı vardır: ValueError, TypeError, IndexError, KeyError, NameError ve - AttributeError. Bir fonksiyonun ya da metodun parametresi uygun girilmemişse genel olarak ValueError oluşmaktadır. Ancak - bir fonksiyona argüman olarak yanlış bir tür verilmişse ya da bir işlemde türler uygunsuz ise TypeError oluşmaktadır. - Örneğin int('xxx') işlemi ValueError oluşturur. Çünkü burada int fonksiyonu str türünden bir argüman alabilir. Ama - aldığı argümanın içeriği hatalıdır. Fakat örneğin z bir complex nesnesi olmak üzere int(z) gibi bir işlem TypeError - oluşturur. Çünkü int fonksiyonu argüman olarak complex türünden bir nesne alamaz. Yani tür uygun ancak içerik uygun değilse - ValueError, tür uygun değilse TypeError oluşmaktadır. Liste, demet, str gibi indekslenebilir türden nesnelerde indeksin - uygun bir değer belirtmemesi durumunda (örneğin 10 elemanlı bir listede 20'inci elemana erişmek istediğimiz bir durumda) - IndexError oluşmaktadır. Sözlük tarzı bir veri yapısında köşeli parantezler içerisinde anahtar verildiğinde böyle bir anahtar - bulunamadıysa KeyError oluşmaktadır. Bir değişken kullanıldığında henüz o değişken yaratılmamışsa NameError oluşmaktadır. - Bir sınıf türünden değişken nokta operatörüyle kullanıldığında o değişken yaratılmış olabilir ama nokta operatörünün - sağındaki öznitelik yaratılmamış olabilir. Bu durumda ise AttributeError exception'ı oluşmaktadır. Yani a.b gibi bir ifadede - a yoksa NameError ancak a var fakat b yoksa AttributeError oluşmaktadır. Tüm exception sınıfları built-in sınıflardır. - Yani hiçbir şeyi import etmeden bu exception sınıflarını kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte klavyeden bir sayı okunmak istenmiştir. Kullanıcı sayı olarak yanlış bir giriş yapmışsa sayı yeniden - istenmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -while True: - try: - val = int(input('Bir sayı giriniz:')) - print(val * val) - break - except ValueError: - print('Girdiğiniz sayı geçersiz!') - -#------------------------------------------------------------------------------------------------------------------------ - Şimdiye kadar biz hep başkaları tarafından oluşturlan exception'ları yakalamaya çalıştık. Aslında biz de exception - oluşturabiliriz. - - Exception'ı oluşturan asıl deyim raise deyimidir. Exception'lar kendiliğinde oluşmaz. Exception'ı oluşturmk için raise - anahtar sözcüğünü kullanmak gerekir. raise deyiminin genel biçimi şöyledir: - - raise - - raise anahtar sözcüğünün yanında bir exception nesnesi bulunmalıdır. Bir problem ortaya çıktığında programcı bir exception - nesnesi oluşturur. Probleme ilişkin bazı bilgileri eğer gerekiyorsa o nesnenin örnek özniteliklerine yazar ve o nesneyle - raise işlemi yapar. C++ gibi bazı programlama dillerinde bu tür işlemlerde herhangi türden nesneler kullanılabilmektedir. - Ancak Java gibi, C# gibi, Python gibi dillerde exception nesneleri özel sınıflardan türetilmiş olan sınıflar türünden - olmak zorundadır. İzleyen pragraflarda bunun ayrıntıları ele alınacaktır. - - Daha önce sözünü ettiğimiz ValueError, TypeError, IndexError gibi exception sınıflarının __init__ metorları bizden - bir yazıyı parametre olarak almaktadır. Eğer exception yakalanmazsa program çökerken bu yazı da ekrana bastırılmaktadır. - Örneğin: - - ve = ValueError('değer negatif olamaz') - raise ve - - Burada ValueError türünden bir nesne ile raise işlemi yapılmıştır. Tabii bu işlem aslında tek bir satırla da yapılabilirdi: - - raise ValueError('değer negatif olamaz') - - Aşağıdaki örnekte foo fonksiyonu negatif parametreleri kabul etmemektedir. Eğer fonksiyon negatif bir değerle çağrılırsa exception - oluşturmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a): - print('foo başladı') - if a < 0: - raise ValueError('parametre negatif olamaz') - print('foo bitti') - -try: - foo(1) - print('her şey yolunda') -except ValueError: - print('hata oluştu') -print('program bitiyor') - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin parametresiyle aldığı sayının karekökünü ekrana yazdıran bir disp_sqrt isimli bir fonlsiyon yazmak isteyelim. - Bu fonksiyon negatif değerleri kabul etmemelidir. Çünkü negatif değerlerin gerçek kökleri yoktur. O halde biz fonksiyonun - parametresini kontrol edip eğer değer negatif ise ValueError ilse raise işlemi yapabiliriz. Benzer biçimde bu fonksiyonun - parametresinin float ya da int türdne olması anlamlıdır. Biz bu fonksiyon içerisinde parametrenin türünü de isinstance - fonksiyonu ile kontrol edip duruma göre TypeError türünden bir nesne ile raise işlemi yapabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -def disp_sqrt(val): - if not isinstance(val, float|int): - raise TypeError('parameter must be float or int') - if val < 0: - raise ValueError('value must not be negative') - print(val ** 0.5) - -try: - disp_sqrt(10) - disp_sqrt(100) - disp_sqrt('ali') -except ValueError: - print('fonksiyon yanlışlıkla negatif bir değerle çağrıldı') - -print('son...') - -#------------------------------------------------------------------------------------------------------------------------ - raise deyiminde yalnızca exception sınıfının ismi de yazılabilir. Bu durumda yorumlayıcı bu sınıf türünden nesneyi yaratıp - o nesne ile raise işlemi yapmaktadır. Yani örneğin: - - raise ValueError - - ile - - raise ValueError() - - tamamen aynı anlamdadır. raise anahtar sözcüğünün yanında exception sınıf ismi görürseniz şaşırmayınız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'daki ValueError gibi, TypeError gibi, IndexError gibi standart Exception sınıflarının hepsi doğrudan ya da dolaylı - olarak Exception isimli bir sınıftab türetilmiş durumdadır. Bu Exception sınıfı da BaseException isimli bir sınıftan - türetilmiştir. Yani Python'daki exception sınıf hiyerarşisi tipik olarak şöyledir: - - BaseException - Exception - ValueError TypeError AttributeError LookupError .... - KeyError IndexError - - Tüm bu built-in exception sınıflarının hepsinin __init__ metodu *args parametrelidir. Bunlar kullanıcıdan aldıkları - argümanları taban sınıfın __init__ metodu yoluyla taban sınıfa ilettirler. En tepedeki BaseException sınıfı da bu - argümanları args isimli örnek özniteliğinde saklamakltadır. Yani bu sınıfların __init__ metotları aşağıdaki gibi - yazılmış durumdadır: - - class BaseException: - def __init__(self, *args): - self.args = args - - class Exception(BaseException): - def __init__(selfs, *args): - super().__init__(*args) - - class ValueError(Exception): - def __init__(selfs, *args): - super().__init__(*args) - - Örneğin: - - >>> ve = ValueError('Parameter may not be negative', 123, 456) - >>> ve.args - ('Parameter cannot be negative', 123, 456) - - Programcılar bir exception nesnesi ile raise işlemi yaparken en azından bu exception nesnesine hatanın nedeni hakkında - bir fikir veren bir yazıyı da argüman olarak geçirirler. Örneğin: - - def foo(a): - if a < 0: - raise ValueError('parameter may not be negative') - ... - - BaseException sınıfının __str__ ve __repr__ metodu vardır. Bu metotlar eğer exception nesnesine tek argüman girilmişse o - argümanı bir yazı olarak verirler. Örneğin: - - >>> ve = ValueError('Parameter cannot be negative') - >>> str(ve) - 'Parameter cannot be negative' - >>> print(ve) - Parameter cannot be negative - - Ancak eğer exception nesnesi birden fazla argümanla yaratılmışsa bu BaseException sınıfının __str__ metodu bu argümanları - bir yazısı biçiminde (demet biçiminde değil) vermektedir. Örneğin: - - >>> ve = ValueError('Parameter cannot be negative', 123, 456) - >>> str(ve) - "('Parameter cannot be negative', 123, 456)" - >>> print(ve) - ('Parameter cannot be negative', 123, 456) - - BaseException sınıfının __repr__ metodu biraz daha aşağı seviyeli biçimde sınıf ismini de belirterek arümanlara ilişkin - yazıları vermektedir. Örneğin: - - >>> ve = ValueError('Parameter cannot be negative') - >>> repr(ve) - "ValueError('Parameter cannot be negative')" - >>> ve - ValueError('Parameter cannot be negative') - >>> print(repr(ve)) - ValueError('Parameter cannot be negative') - >>> ve = ValueError('Parameter cannot be negative', 123, 456) - >>> repr(ve) - "ValueError('Parameter cannot be negative', 123, 456)" - >>> ve - ValueError('Parameter cannot be negative', 123, 456) - >>> print(repr(ve)) - ValueError('Parameter cannot be negative', 123, 456) -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir exception except bloğu ile yakalanırken exception sınıf isminin yanı sıra as anahtar sözcüğü ve bir değişken - kullanılabilir. Örneğin: - - except ValueError as e: - pass - - Bu durumda raise ile fırlatılan exception'daki exception nesnesi (yani onun adresi) as ile belirtilen değişkene atanmaktadır. - Böylece bir exception fırlatıldığında programcı o exception yakalayarak oradan exception arümanlarına erişebilir. - Örneğin: - - def disp_sqrt(val): - if not isinstance(val, float|int): - raise TypeError('parameter must be float or int') - if val < 0: - raise ValueError('value must not be negative') - print(val ** 0.5) - - try: - disp_sqrt(-10) - except ValueError as e: - print(e) - - Bu örnekte programcı fırlatılan exception nesnesini as cümleciği ile elde etmiş ve onun argümanlarını yazdırmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi değişik türlere ilişkin exception'lar demet sentaksıyla tek bir except bloğu tarafından yakalanabiliyordu. - Örneğin: - - except (TypeError, ValueError): - pass - - Burada bu except bloğu hem TypeError hem de ValueError exception'larını yakalayabilmektedir. Pekiyi bu sentaks ile - aynı zamanda as ile bir değişken ismi belirtiresek bu durumda ne olmaktadır? Örneğin: - - except (TypeError, ValueError) as e: - pass - - İşte böylesi bir durumda hangi exception oluşrsa (örneğimizde TypeError ya da ValueError) o exception nesnesi as ile - belirtilen değişkene atanacaktır. Yani burada TypeError oluşursa e değişkeni TypeError nesnesini, ValueError oluşursa - e değişkeni ValueError nesnesini tutacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -def disp_sqrt(val): - if not isinstance(val, float|int): - raise TypeError('parameter must be float or int') - if val < 0: - raise ValueError('value must not be negative') - print(val ** 0.5) - -try: - disp_sqrt('xxx') -except (ValueError, TypeError) as e: - print(e) - -print('son...') - -#------------------------------------------------------------------------------------------------------------------------ - Programcı kendi exception sınıflarını yazabilir ve onları kullanabilir. Bir sınıf ile raise işleminin yapılabilmesi - için ve o sınıfın except anahtar sözcüğünün yanında kullanılabilmesi için BaseException sınıfından türetilmiş olması - gerekir. Ancak Python standartları programcının kendi exception sınıflarının doğrudan BaseException sınıfından değil - bu sınıftan türetilmiş olan Exception sınıfından türetilmesi gerektiğini belirtmektedir. Tabii biz istersek zaten var - olan exception sınıflarından da türetme yapabiliriz. Örneğin: - - class NegativeError(ValueError): - pass - - def disp_sqrt(val): - if val < 0: - raise NegativeError('value must not be negative') - print(val ** 0.5) - - try: - disp_sqrt(-10) - except NegativeError as e: - print(e) - - Bu örnekte biz Python Standart Kütüphanesinde olmayan NegativeError isimli bir exceptionm sınıfı yazdık. Bu sınıfımızı - ValueError sınıfından türettik. Sınıfımız için __init__ metodu yazmadık. Bu durumda taban sınıfın __init__ metodu deveye - girecektir. Exception yakalırken yine except anahtar sözcüğünün yanına kendi exception sınıf ismini yazdığımıza dikkat - ediniz. - - Pekiyi mademki hazır pek çok built-in exception sınıfı varken programcının kendi exception sınıflarını yazmasına gerek - var mı? Exception'lar bir grup olarak yakalanacaksa böyle bir çabaya gerek olabilmektedir. Bazı kütüphanelerde bazı - konulara ilişkin exception'lar standart exception sınıflarıyla mantıksal olarak ifade edilemeyebilirler. Bu tür durumlarda - prohramcılar kendi exception sınıflarını yazabilmektedir. Gerçekten de standart kütüphanenin çeşitli modüllerinde o - konuya ilişkin özel exception sınıfları bulunmaktadır. - #------------------------------------------------------------------------------------------------------------------------ - -class NegativeError(ValueError): - pass - -def disp_sqrt(val): - if val < 0: - raise NegativeError('value must not be negative') - print(val ** 0.5) - -try: - disp_sqrt(-10) -except NegativeError as e: - print(e) - -print('son...') - -#------------------------------------------------------------------------------------------------------------------------ - 53. Ders 31/10/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Türemiş sınıf türünden oluşan bir exception taban sınıf türünden bir except bloğu tarafından yakalanabilir. Başka bir - deyişle bir except bloğu yalnızca o sınıf türünden exception'ları değil aynı zamanda o sınıftan türetilmiş olan - exception'ları da yakalayabilmektedir. Örneğin biz NegativeError isimli exception sınıfının ValueError sınıfından - türetmiştik. O halde biz bu NegativeError exception'ını istersek ValueError except bloğu ile de yakalayabiliriz. - Örneğin: - - class NegativeError(ValueError): - pass - - def disp_sqrt(val): - if val < 0: - raise NegativeError('value must not be negative') - print(val ** 0.5) - - try: - disp_sqrt(-10) - except ValueError as e: - print(e) - - Burada NegativeError exception'ı NegativeError içeren except bloğu tarafından da yakalanabilir, ValueError içeren - except bloğu taraından da yakalanabilir. - - Bu sayede farklı exception'lar eğer aynı taban sınıftan türetilmişlerse taban sınıf belirtilerek tek bir except bloğu - tarafından yakalanabilmektedir. Bu özellik yalnızca Python'da değil C++, Java ve C# gibi dillerde de benzer biçimde - bulunmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class NegativeError(ValueError): - pass - -def disp_sqrt(val): - if val < 0: - raise NegativeError('value must not be negative') - print(val ** 0.5) - -try: - disp_sqrt(-10) -except ValueError as e: - print(e) - -#------------------------------------------------------------------------------------------------------------------------ - Bütün exception sınıfları taban Exception sınıfından türetildiğine göre biz aslında bütün exception'ları Exception - parametreli ya da BaseException parametreli bir except bloğu ile yakalabiliriz. Örneğin: - - try: - do_something() - except Exception as e: - print(e) - - Tabii bu biçimde farklı exception'ları tek bir except bloğu ile yakalamak pratik olsa da bazen değişik exception'lar için - değişik ele alım işlemlerinin yapılması gerekebilmektedir. Bu tür durumlarda farklı except bloklarının oluşturulması - gerekir. - - Aşağıdaki örnekte klavye kuması sırasında oluşan hatalar doğrudan Exception sınıfı ile yakalanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -try: - val = int(input('Bir sayı giriniz:')) - print(val * val) -except Exception as e: - print(e) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi exception ele alınırken hem taban sınıfa hem de türemiş sınıfa ilişkin except blokları bir arada bulunudurlursa - ne olur? Python'da exception oluştuğunda yorumlayıcı except bloklarına yukarıdan aşağıya doğru sırasıyla bakmaktadır. - Eğer bu tür durumlarda taban sınıfa ilişkin except bloğu türemiş sınıfa ilişkin except bloğunun yukarısında bulundurulursa - taban sınıfa ilişkin except bloğu türemişl sınıfa ilişkin exception'ı da yakalayacağı için anlamsız bir durum oluşur. Bu - tür durumlarda türemiş sınıfa ilişkin except bloğunun taban sınıfa except bloğunun yukarısında bulundurulması anlamlıdır. - (C++, Java ve C# gibi derleyicilerin kullanıldığı dillerde zaten taban sınıfa ilişkin catch bloklerının türemiş sınıfa ilişkin - catch bloklarının yukarısında bulundurulması derleme zamanında error oluşmasına yol açmaktadır.) Örneğin: - - try: - do_something() - except ValueError as e: - pass - except Exception as e: - pass - - Burada ValueError oluşursa bunu ValueError parametreli except bloğu yakalayacaktır. Ancak diğer tüm exception'ları Exception - parametreli except bloğu yakalayacaktır. Bu durum anlamlıdır. Bu örnekte programcı ValueError için özel bir işlem uygulamak, - diğer bütün exception'lar için aynı işlemleri uygulamak istemiş olabilir. Ancak buradaki sıra ters olsaydı kod anlamsız olurdu: - - try: - do_something() - except Exception as e: - pass - except ValueError as e: - pass - - Burada tüm exception'lar zaten Exception parametreli except bloğu tarafından yakalanacağı için ValueError parametreli except - bloğu boşuna yerleştirilmiştir. Yukarıda da belirttiğimiz gibi bu tür durumlarda türemiş sınıfa ilişkin except blokları - taban sınıfa ilişkin except bloklarının yukarısında bulundurulmalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Programın akışı birden fazla kez try bloğuna girebilir. Bu durumda bir exception oluşursa try bloklarının except blokları - içten dışa doğru (yani son girilen try bloğundan ilk girilene doğru) gözden geçirilir. Eğer exception bir try bloğunun - except bloğu tarafından yakalanırsa orada ele alınmış alur. Artık dış try bloklarına bu exception yansıtılmaz. Eğer exception - dış hiçbir try bloklerının except blokları tarafından da yakalanamazsa program çöker. - - Aşağıdaki örnekte main fonksiyonu foo, fonksiyonunu, foo fonksiyonu bar fonksiyonunu, bar fonksiyonu da tar fonksiyonunu - çağırmaktadır. - - main ---> foo ---> bar ---> tar - - Örneğimizde tar fonksiyonunda exception oluşmuştur. Programın akışı iki kez try bloğuna girmiştir. Örneğimizde oluşan - ValueError exception'ı için önce son girilen (bar'daki) try bloğunun except bloklarına bakılır. Bu exception burada - yakalanamadığı için bu kez foo fonksiyonundaki except bloklarına bakılacaktır. ValueError exception'ı foo fonksiyonunda - yakalanmıştır. Artık sanki exception oluşmamış gibi programın çalışması oradan devam edecektir. Programı çalıştırdığınızda - ekranda şu yazıları göreceksiniz: - - main begins... - foo begins... - bar begins... - tar begins... - parameter may not be negative!... - foo ends - main ends... -#------------------------------------------------------------------------------------------------------------------------ -def foo(a): - print('foo begins...') - try: - bar(a) - except ValueError as e: - print(e) - print('foo ends') - -def bar(a): - print('bar begins...') - try: - tar(a) - except TypeError as e: - print(e) - print('bar ends') - -def tar(a): - print('tar begins...') - if a < 0: - raise ValueError('parameter may not be negative!...') - print('tar begin...') - -def main(): - print('main begins...') - foo(-2) - print('main ends...') - -main() - -#------------------------------------------------------------------------------------------------------------------------ - Özel bir except bloğu da parametresiz except bloğudur. Parametresiz except bloğu tüm exception'ları yakalar. Ancak - parametresiz except blokları bulundurulacaksa tüm except bloklarının sonunda bulundurulmalıdır. Aksi takdirde "sentaks - hatası" ile programın çalıştırılması da başlatılmaz. Örneğin: - - try: - foo() - except ValueError: - pass - except TypeError: - pass - except: - pass - - Burada ValueError ve TypeError ayrı except bloklarıyla yakalanmıştır. Ancak diğer tüm exception'lar parametresiz except bloğu ile - yakalanır. Parametresiz except bloğunda bir as cümleceği bulunamaz. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def disp_sqrt(val): - if not isinstance(val, int|float): - raise TypeError('argument must be int or float!') - if val < 0: - raise ValueError('argumant must be positive or zero') - - result = math.sqrt(val) - print(result) - -try: - disp_sqrt(10) - disp_sqrt('ankara') -except ValueError as e: - print(e) -except: - print('argument error') - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi Exception parametreli except bloğu parametresi except bloğu gibi de zaten işlev görmüyor mu? Ne de olsa Python'da - tüm exception'şarın BaseError sınıfından türetilmiş olan Exception sınıfından türetildiğini görmüştük. Yani örneğin: - - try: - pass - except Exception: - pass - - ile aşağıdaki arasında işlevsel bir farklılıkm var mıdır? - - try: - pass - except: - pass - - İşte aslında parametresiz except bloğu toplamda Exception parametreli except bloğundan daha geneldir. Biz başka dillerde - yazılan kodları Python'dan belirli koşullar çerçevesinde çağırabilmekteyiz. O dillerin exception mekanizması farklıdır. - Dolayısıyla oradaki exception'lar Exception parametreli except blokları tarafından yakalanamazlar. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - try bloğunun bir de finally bloğu olabilmektedir. finally bloğu parametresiz olmak zorundadır. finally bloğu except - blokları ile birlikte ya da except blokları olmadan kullanılabilir. Her durumda finally bloğu en sonda olmak zorundadır. - Örneğin: - - try: - foo() - except ValueError: - pass - except TypeError: - pass - except: - pass - finally: - pass - - Konuya girişte try bloklarının tek başlarına bulunamadığını belirtmiştik. Geçerli blok dizilimleri şöyledir: - - - try, except blokları - - try, except blokları, finally - - try, finally - - finally bloğu exception oluşsa da oluşmasa da çalıştırılır. Yani exception oluşursa önce exception'ı yakalayan except - bloğu çalıştırılır sonra finally bloğu çalıştırılır. Eğer exception oluşmazsa yine finally bloğu çalıştırılır. - - Bu durumu aşağıdaki programla test ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def disp_sqrt(val): - if not isinstance(val, int|float): - raise TypeError('argument must be int or float!') - if val < 0: - raise ValueError('argumant must be positive or zero') - - result = math.sqrt(val) - print(result) - -try: - val = float(input('Bir sayı giriniz:')) - result = disp_sqrt(val) -except TypeError as e: - print(e) -except ValueError as e: - print(e) -finally: - print('finally') - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi mademki finally bloğu exception oluşsa da oluşmasa da çalıştırılmaktadır. O halde finally bloğu yerine oradakileri - except bloklarının sonuna taşırsak değişen ne olacaktır? Örneğin: - - try: - foo() - except: - print('exception oluştu') - finally: - print('finally bloğunda bir işlem yapılıyor') - - Bununla aşağıdaki kod arasında işlevsel ne farklılık vardır? - - try: - foo() - except: - print('exception oluştu') - print('finally bloğunda bir işlem yapılıyor') - - İşte finally bloğu her zaman çalıştırılmaktadır. Yani try bloğu nasıl sonlandırılmış olursa olsun finally bloğu çalıştırılır. - Örneğin bir döngü içerisinde bir try bloğu olabilir.Bu try bloğunun da finally bloğu olabilir. Bu döngüden ve dolayısıyla - try bloğundan break ya continue deyimleriyle çıkılmış olabilir. Bu durumda yine finally bloğu çalıştırılır. Örneğin: - - while True: - try: - val = int(input('Bir değer giriniz:')) - if val == 0: - break - print(val * val) - except: - print('giriş geçersiz!') - finally: - print('finally işlemi yapılıyor') - - print('program devam ediyor') - - Burada try bloğundan break ile çıkılmış olsa da finally bloğu çalıştırılacaktır. Halbuki kod aşağıdaki gibi olsaydı finally kodu - çalıştırılmayacaktı: - - while True: - try: - val = int(input('Bir değer giriniz:')) - if val == 0: - break - print(val * val) - except: - print('giriş geçersiz!') - print('finally işlemi yapılıyor') - - print('program devam ediyor') - - Tabii return işleminde de aynı durum söz konusudur. Örneğin: - - def foo(): - try: - val = int(input('Bir değer giriniz:')) - if val == 0: - return - print(val * val) - except: - print('giriş geçersiz!') - finally: - print('finally işlemi yapılıyor') - - foo() - - Görüldüğü gibi try bloğundan nasıl çıkılmış olursa olsun her zaman finally bloğu çalıştırılmaktadır. - - Ayrıca iç içe try bloklarının olduğu durumda iç try bloklarında exception oluştuğunda exception dış try bloğu tarafından - yakalansa bile iç try bloklarının finally blokları yine çalıştırılmaktadır. Yani try bloğundan raise ile çıkılsa bile - finally bloüu yine çalıştırılmaktadır. Örneğin: - - import math - - def disp_sqrt(val): - try: - if not isinstance(val, int|float): - raise TypeError('argüman int ya da float olmak zorunda!') - if val < 0: - raise ValueError('argüman negatif olamaz!') - - result = math.sqrt(val) - print(result) - finally: - print('finally bölümü çalıştıtılıyor...') - - def main(): - try: - val = float(input('Bir değer giriniz:')) - disp_sqrt(val) - except Exception as e: - print(e) - - main() - - Burada iç try bloğunda TypeError ya da ValueError oluştuğunda bu exception dış try bloğuğun except bloğu tarafından - yakalanacaktır. Ancak exception'ın yakalandığı yere kadar içeriden dışarıya doğru tüm try bloklarının finally blokları - yine çalıştırılacaktır. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi finally bloğunda neden gereksinim duyulmaktadır? finally bloğu "yapılmış tahisisatların garantili bir biçimde - geri bırakılması amacıyla" kullanılmaktadır. Burada "tahsisat" demekle bir kaynak kullanımından bahsediyoruz. Bazı kaynaklar - sınırlı ölçüdedir. Kaynak kullanıldıktan sonra onun geri bırakılması gerekir. Örneğin: - - def foo(): - ... - - ... - ... - ... - - ... - - Burada kaynak tahsis edildikten sonra bir exception oluşursa bu exception dış bir try bloğu tarafından ele alınıyorsa - artık bu kaynağın geri bırakılma olanağı kalmayacaktır. Halbuki bu geri bırakma işlemini otomatize etmek için finally - bloğundan faydalanılabilir: - - def foo(): - try: - ... - - ... - ... - ... - finally: - - - Burada kaynak tahsis edildikten sonra bir exception oluşsa da kaynağın geri bırakılması finally bloğu sayesinde mümkün - olmaktadır. Örneğin bir fonksiyon içerisinde bir dosya açılmış olabilir. Ancak dosya kapatılmadan exception oluşabilir. - İşte dosya finally bloğunda kapatılırsa bir sorun oluşmaz: - - def foo(): - f = None - try: - f = open('test.txt') - s = f.read() - #... - s = f.read() - #... - finally: - if f: - f.close() - - Burada programcının read metodundaki IO hatalarını dışarıda ortak bir yerde ele aldığını düşünelim. read metotlarının - birinde IO hatası olduğunda akış başka bir faaliyet alanına gidecek dolayısıyla dosyanın kapatılma imkanı kalmayacaktır. - (Gerçi dosya bu tür sınıfların __del__ metotlarında da kapatılmaktadır ancak çöp toplayıcı mekanizmanın da nasıl - gerçekleştirileceği belli değildir.) İşte bu tür durumlarda kaynakların finally bloğunda boşaltılması en uygun durumdur. - Özellikle çöp toplama mekanizmasının etkili olmadığı Python dünyasının dışındaki tahsisatların garantili boşaltılması - için finally bloğuna gereksinim duyulmaktadır. Programcı herhangi bir noktada exception oluştuğunda o zamana kadar - yapılan birtakım işlemleri geri almak istiyorsa bunu finally bloğunda yapmalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - raise deyiminde raise anahtar sözcüğünün yanına ifade getirilmeyebilir. Buna "reraise" denilmektedir. Bu biçimdeki reaise - deyimi ancak except bloklarında kullanılabilmektedir. Reraise işlemi aslında "exception'ı yakalanmamış hale getirmektedir. - Başka bir deyişle reraise işlemi aynı exception aynı parametreyle yeniden fırlatılıyor gibi bir etki oluşturmaktadır. Reraise - işlemleri tipik olarak iç içe try bloklarında exception'ın içteki try bloğunun except blokları tarafından yakalanması durumunda - aynı yakalamanın dışarıda da yapılması amacıyla kullanılmaktadır. - - Aşağıdaki örnekte main fonksiyonu foo fonksiyonunu, foo fonksiyonu da bar fonksiyonunu çağırmaktadır. Burada iç içe - iki try bloğu kullanılmıştır. try bloklarından biri main içerisinde diğeri ise foo içerisindedir. Exception foo - içerisindeki try bloğu tarafından yakalandığında reraise işlemi uygulanmıştır. Bu durumda sanki exception aynı exception - nesnesiyle yeniden fırlatılmış gibi bir etki oluşacaktır. Klavyeden negatif bir değer girdiğinizde ekranda şu yazıları - göreceksiniz: - - main başlıyor - Bir değer giriniz:-1 - foo başlıyor - bar başlıyor - exception foo'da yakalandı: argüman negatif olamaz! - exception main'de yakalandı: argüman negatif olamaz! - main bitiyor -#------------------------------------------------------------------------------------------------------------------------ - -def foo(a): - print('foo başlıyor') - try: - bar(a) - except ValueError as e: - print(f"exception foo'da yakalandı: {e}") - raise - print('foo bitiyor') - -def bar(a): - print('bar başlıyor') - if a < 0: - raise ValueError('argüman negatif olamaz!') - print('bar bitiyor') - -def main(): - print('main başlıyor') - try: - val = int(input('Bir değer giriniz:')) - foo(val) - except ValueError as e: - print(f"exception main'de yakalandı: {e}") - - print('main bitiyor') - -main() - -#------------------------------------------------------------------------------------------------------------------------ - Python'ın standart sys modülünde exc_info isimli exception mekainizması ile ilgili bir fonksiyon vardır. Bu fonksiyon - bir except bloğu içerisinde çağrılmalıdır. Bu fonksiyon bize üçlü bir demet verir. Demetin ilk elemanı oluşan exception'ın - türünü, ikinci elemanı exception nesnesini ve üçüncü elemanı da trace bilgisini belirtmektedir. Programcı isterse as cümleciği - ile elde edebileceği exception nesnesini bu fonksiyonla da elde edebilir. Örneğin parametresiz except bloklarında as cümleciği - kullanılamayacağından dolayı exception nesnesi ve türü bu yolla elde edilebilmektedir. Fonksiyon except bloğunun dışında - çağrılmasının bir anlamı yoktur. Eğer fonksiyon except bloğunun dışında çağrılırsa fonksiyonun verdiği demetin üç elemanı da - Nobe olmaktadır. - - Aşağıdaki örnekte parametresiz except bloğunda exception bilgileri sys.exc_info fonksiyonu ile elde edilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -import sys - -try: - val = int(input('Bir sayı giriniz:')) - print(val * val) -except: - print('exception oluştu') - exc_type, exc_obj, exc_trace = sys.exc_info() - print(exc_type, exc_obj, exc_trace, sep='|') - -#------------------------------------------------------------------------------------------------------------------------ - sys.exc_info fonksiyonundan elde edilen trace bilgisi bir bağlı liste biçimindedir. Bu bağlı liste bize exception'ın - oluştuğu noktaya kadarki fonksiyon akışlarını vermektedir. trace nesnelerinin tb_next öznitelikleri bir sonraki tarce - nesnesini belirtir. tb_lineno özniteliği ise exception oluşumuna ilişkin fonksiyon çağrılarının kaynak koddaki satır - numaralarını belirtmektedir. Bu bilgiler genellikle debugger gibi, REPL gibi ortamlar tarafından kullanılmaktadır. - - Aşağıdaki exception satır numaralarının ekrana yazdırılmasına yönelik bir örnek görüyorsunuz. -#------------------------------------------------------------------------------------------------------------------------ - -import sys - -def foo(): - bar() - -def bar(): - tar() - -def tar(): - raise ValueError() - -def main(): - try: - foo() - except: - _, _, trace = sys.exc_info() - while trace: - print(trace.tb_lineno) - trace = trace.tb_next - -main() - -#------------------------------------------------------------------------------------------------------------------------ - Bir exception yakalanmadığında program çökerken bu trace bilgileri de ekrana basılmaktadır. Böylece programcı buradaki - bilgileri izeleyerek çökmenin akıştaki yerini tespit edebilir. Aşağaıdaki programda oluşan exception ele alınmadığından - program çökecektir. Program çöktüğünde ekrana çıkan yazılara dikkat ediniz: - - Traceback (most recent call last): - - File ~\anaconda3\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec - exec(code, globals, locals) - - File c:\dropbox\shared\kurslar\python\src\sample.py:15 - main() - - File c:\dropbox\shared\kurslar\python\src\sample.py:13 in main - foo() - - File c:\dropbox\shared\kurslar\python\src\sample.py:4 in foo - bar() - - File c:\dropbox\shared\kurslar\python\src\sample.py:7 in bar - tar() - - File c:\dropbox\shared\kurslar\python\src\sample.py:10 in tar - raise ValueError() - - ValueError -#------------------------------------------------------------------------------------------------------------------------ - -import sys - -def foo(): - bar() - -def bar(): - tar() - -def tar(): - raise ValueError() - -def main(): - foo() - -main() - -#------------------------------------------------------------------------------------------------------------------------ - Python 3.11 ile birlikte exception mekanizmasına oldukça ayrıntı bir özellik olan "exception group" özelliği de eklenmiştir. - Biz kursumunda henüz çok yeni olduğu gerekçesiyle bu konuyu ele almayacağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 54. Ders 02/11/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İçerisinde bilgilerin bulunduğu ikincil belleklerdeki bölgelere "dosya (file)" denilmektedir. Dosya aslında işletim - sistemleri tarafından oluşturulan yüksek seviyeli bir organizasyona ilişkin bir terimdir. İşletim sistemlerinin dosya - işlemlerini yapan alt sistemlerine "dosya sistemi (file system)" denilmektedir. Dosyaların parçaları aslında disklerdeki - bloklarda bulunur. İşletim sistemi bir biçimde hangi dosyaların hangi parçalarının diskte hangi bloklarda olduğunu tutmaktadır. - Bu tutuş biçimine göre dosya sistemleri çeşitli isimlerle anılmaktadır. Örneğin NTFS, Ext-2, HFS gibi. İşletim sistemleri - uygulama programcılarına dosyaları sanki ardışıl byte topluluklarından oluşan varlıklarmış gibi göstermektedir. Dosya - işlemleri hangi dille yapılıyor olursa olsun eninde sonunda işletim sistemlerinin sistem fonksiyonları yoluyla - gerçekleştirilmektedir. Çünkü dosya işlemlerinden birincil derecede işletim sistemleri sorumludur. - - İşletim sistemlerinin çoğu diskleri sanki dizinlerden (directories) ve dosyalardan oluşan varlıklar biçiminde göstermektedir. - Bir dosya bir volümdeki dizinlerin içerisinde bulunur. Bir disk volümlerden oluşabilmektedir. Microsoft'un kullandığı dosya - sistemlerinde (NTFS, FAT gibi) her volümün ayrı bir kök dizini bulunmaktadır. Örneğin elimizde fiziksel bir disk olsun. Biz bu - fiziksel diski kendi içerisinde üç ayrı volüme ayırabiliriz. Microsoft her volüme bir sürücü ismi atamaktadır. Örneğin "C sürücüsü", - "D sürücüsü", "E sürücüsü" gibi. Böylece Microsoft dosya sistemlerinde bir dosya belli bir volümün (yani sürücünün) belli - bir dizini içerisinde bulunur. Volümdeki en dışta bulunan dizine "kök dizin (root directory)" denilmektedir. Ancak UNIX/Linux - sistemlerinde ve macOS sistemlerinde "sürücü (drive)" diye bir kavramı yoktur. Toplamda bu sistemlerde tek bir kök dizin - vardır. UNIX/Linux ve macOS sistemlerinde disk yine birden fazla volümden oluşabilir. Ancak bu volümler ayrı sürücüler - biçiminde değil tek bir kökteki dizinler biçiminde organize edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir dosyanın dizinler içerisindeki yerini belirten yazısal ifadelere "yol ifadesi (path)" denilmektedir. Yol ifadelerinde - dizin geçişlerinde Microsoft sistemlerinde "\" karakteri, UNIX/Linux ve macOS sistemlerinde ise "/" karakteri kullanılmaktadır. - Microsoft sistemleri programlama söz konusu olduğunda UNIX/Linux uyumunu korumak için dizin geçişlerinde "/" karakterini de - kabul etmektedir. - - Yol ifadeleri "mutlak (absolute)" ve "göreli (relative)" olmak üzere ikiye ayrılmaktadır. Eğer bir yol ifadesinin ilk karakteri - UNIX/Linux sistemlerinde "/", Windows sistemlerinde "\" ise böyle yol ifadelerine mutlak yol ifadeleri, değilse göreli - yol ifadeleri denilmektedir. Örneğin: - - "/home/kaan/study/test.txt" ---> Mutlak yol ifadesi - "a/b/c.txt" ---> Göreli yol ifadesi - "test.txt" --> Göreli yol ifadesi - "\a.txt" --> Mutlak yol ifadesi (Windows) - - Mutlak yol ifadeleri her zaman kök dizinden itibaren yer belirtmektedir. Göreli yol ifadeleri ise prosesin "çalışma - dizininden (current working directory)" itibaren yer belirtir. İşletim sistemleri dünyasında çalışmakta olan programlara - "prosess (process)" denilmektedir. İşletim sistemleri her proses için bir çalışma dizini (current working directory) - tutmaktadır. İşte göreli yol ifadeleri bu çalışma dizininden itibaren bir yer belirtir. Örneğin programımızın çalışma - dizini "C:\temp" olsun. Biz de "a\b\test.txt" biçiminde göreli bir yol ifadesi vermiş olalım. Bu dosya işletim sistemi - tarafından "c:\temp\a\b\test.txt" biçiminde ele alınacaktır. Eğer yol ifadesi "\a\b\c.txt" biçiminde olsaydı prosesin - çalışma dizininin bir önemi kalmayacaktı. Çünkü mutlak yol ifadeleri her zaman kök dizinden itibaren bir yer belirtmektedir. - Örneğin "test.txt" biçimindeki bir yol ifadesi göreli bir yol ifadesidir. Bu yol ifadesindeki "test.txt" dosyası prosesin - çalışma dizininde aranır. - - Windows sistemlerinde yol ifadesine sürücü ismi de dahi edilebilir. Örneğin "c:\temp\test.txt" gibi. Bu tür yol ifadelerine - Microsoft "tam yol ifadeleri (full path)" demektedir. Microsoft sistemlerinde mutlak bir yol ifadesinde sürücü belirtilmezse - bu durumda bu yol ifadesinin prosesin çalışma dizinine ilişkin sürücüdeki bir yol ifadesi olduğu sonucu çıkartılır. Örneğin - prosesimizin çalışma dizini "F:\test\study" olun. Biz "\a\b\c.txt" biçiminde mutlak bir yol ifadesi verirsek buradaki kök - F sürücüsünün köküdür. - - Windows sistemlerinde sürücü içeren göreli yol ifadeleri de söz konusu olabilmektedir. Örneğin "c:a\b\test.txt" gibi. - Bu özel bir durumdur. Bu durumda işletim sistemi proseste bazı çevre değişkenlerine bakıp göreli yol ifadesi için orijini - belirlemeye çalışır. Ancak proseste bu çevre değişkenleri yoksa (ki genellike yoktur) bu durumda bu yol ifadesi tam yol - ifadesi biçiminde yani "C:\a\b\test.txt" biçiminde ele alınır. - - Bir yol ifadesindeki ters bölüler ya da düz bölüler arasındaki bileşene "yol bileşeni (path component)" denilmektedir. - Örneğin: - - "/ali/veli/selami/test.txt" - - Burada "ali", "veli", "selami" ve "test.txt" yol bileşenleridir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Windows, UNIX/Linux ve macOS sistemlerinde yol bileşenlerinde "." ve ".." ifadeleri özel bir anlama gelmektedir. "." - ifadesi "o andaki dizin" anlamına ".." ifadesi o andaki dizinin üst dizini anlamına gelmektedir. Örneğin: - - "/a/b/c/../test.txt" - - Bu yol ifadesi aşağıdakiyle eşdeğerdir: - - "/a/b/test.txt" - - Örneğin: - - "/a/b/./c.txt" - - Bu yoli fadesi de aşağıdaki yok ifadesi ile tamamen aynıdır: - - "/a/b/c.txt" -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Prosesin çalışma dizini "os" modülündeki getcwd fonksiyonu ile bir string biçiminde elde edilebilir. Örneğin: - - import os - - cwd = os.getcwd() - print(cwd) - - Python programının çalışma dizini default durumda neresidir? Spyder IDE'si hangi program çalıştırılıyorsa o program - dosyasının bulunduğu dizini programın default çalışma dizini yapmaktadır. Zaten bu dizin aynı zamanda IDE'de sağ üst - köşede görüntülenmektedir. PyCharm IDE'si ise proje dizinini programın default çalışma dizini yapmaktadır. Eğer biz - bir Python programını komut satırından çalıştırıyorsak programın default çalışma dizini o anda çalıştırmayı yaptığımız - dizin olur. -#------------------------------------------------------------------------------------------------------------------------ - -import os - -cwd = os.getcwd() -print(cwd) - -#------------------------------------------------------------------------------------------------------------------------ - Bir program (yani proses) çalışırken onun çalışma dizini değiştirilebilmektedir. Prosesin çalışma dizinini değiştirmek - için "os" modülü içerisindeki chdir fonksiyonu kullanılır. Eğer bu fonksiyonda geçersiz bir dizin girilirse - FileNotFoundError isimli exception oluşmaktadır. Örneğin: - - import os - - os.chdir(r'c:\windows') - - Burada prosesin çalışma dizini "c:\windows" biçiminde ayarlanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -import os - -cwd = os.getcwd() -print(cwd) - -try: - os.chdir(r'C:\xxxx') -except Exception as e: - print(e) - -print('program continues...') - -cwd = os.getcwd() -print(cwd) - -#------------------------------------------------------------------------------------------------------------------------ - Bir dosya ile işlem yapmadan önce dosyanın açılması gerekir. İşlemler bitince de dosya kapatılmalıdır. Eğer dosya kapatılmazsa - program bittiğinde zaten otomatik olarak dosya işletim sistemi tarafından kapatılır. Dosyanın açılması sırasında işletim - sistemi dosya ile ilgili bazı hazırlık işlemlerini yapmaktadır. Bir dosya açılırken "açılacak dosya" yol ifadesiyle belirtilir. - Aynı zamanda açık sırasında programcı o dosya üzerinde hangi işlemleri yapacağını da belirtmektedir. Buna dosyaının "açış modu" - denilmektedir. - - Python'da dosyalar built-in open fonksiyonu ile açılmaktadır. open fonksiyonunun parametrik yapısı şöyledir: - - open(file, mode='r', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None) - - Fonksiyonun ilk iki parametresi çok önemlidir. Diğer parametrelere özel durumlarda gereksinim duyulmaktadır. Fonksiyonun - birinci parametresi açılacak dosyasının yol ifadesini, ikinci parametresi açış modunu belirtmektedir. Açım işlemi başarısız olursa - open fonksiyonu OSError sınıfınından türetilmiş çeşitli exception sınıflarıyle raise işlemi yapmaktadır. OSError sınıfı - da Exception sınıfından türetilmiştir. - - open fonksşyonunun birinci parametresi açılacak dosyanın yol ifadesini belirtir. İkinci parametre ise açış modunu belirtmektedir. - Yukarıda da belirttiğimiz gibi açış modu "dosya üzerinde yapılmak istenen eylemi" belirtmektedir. Açış modu bir yazı olarak - verilir. Açış modu olarak verilen yazıların aşağıdakilerdne biri olması gerekir: - - 'r': Bu modda ancak var olan dosyalar açılabilir. Açılan dosyadan yalnızca okuma yapılabilir. Yani bu mod "olan dosyayı yalnızca - yazma amacıyla aç" anlamına gelmektedir. Dosya yoksa FileNotFoundError exception'ı oluşur. Açış modu hiç girilmezse default - durum 'r' kabul edilmektedir. - - 'r+': Bu modda yine var olan dosya açılabilir. Ancak dosyadan hem okuma hem de dosyaya yazma yapılabilir. Dosya yoksa yine - FileNotFoundError exception'ı oluşmaktadır. Açış modlarındaki '+' karakteri "read/write" anlamına gelmektedir. - - 'w': Bu modda dosya yoksa yaratılır ve açılır, dosya varsa sıfırlanarak açılır (yani dosyanın içerisindekiler silinir). - Bu modda dosyaya yalnızca yazma yapılabilir. Olan bir dosyanın içinin sıfırlanmasına işletim sistemleri terminolojisinde - dosyanın "truncate edşlmesi" denilmektedir. - - 'w+': Bu modda yine dosya yoksa yaratılır ve açılır, dosya varsa sıfırlanarak açılır. Ancak dosyadan okuma ve dosyaya yazma - yapılabilir. - - 'a': Bu modda eğer dosya yoksa yaratılır ve açılır, dosya varsa olan dosya açılır (sıfırlanmaz). Ancak bu modda dosyaya her - yazılan hep sona eklenir. Dosyanın başka bir yerine bir şey yazmak mümkün değildir. Bu modda dosyadan okuma yapılamaz. - - 'a+': Bu modda eğer dosya yoksa yine yaratılır ve açılır, dosya varsa yine olan dosya açılır (sıfırlanmaz). Yine bu modda - dosyaya her yazılan hep sona eklenir. Dosyanın başka bir yerine bir şey yazmak mümkün değildir. Bu modda dosyanın herhangi - bir yerinden okuma yapılabilir. - - Genel olarak dosyaların gereksinimi karşılayacak en dar modda açılması tavsiye edilmektedir. Örneğin: - - - Var olan bir dosyanın bir yerindeki yazının bir kısmını değiştirmek isteyelim. Bu durumda dosyayı 'r+' modunda açmalıyız. - - - Sıfırdan bir dosyanın içerisine bir şeyler yazmak isteyelim. Bu durumda dosyayı 'w' modunda açmalıyız. Ancak dosya varsa - dosya varsa dosyanın içeriği silinecektir. Dikkat etmemiz gerekir. - - - A dosyasının içerisindekileri okuyup yeni bir B dosyasına yazmak isteyelim. Başka bir deyişle A dosyasının B isminde bir - kopyasını oluşturmak isteyelim. Bu durumda A dosyasını 'r' modunda B dosyasını 'w' modunda açmalıyız. - - Dosya başarılı bir biçimde açılmışsa open fonksiyonu bir "dosya nesnesine (file object)" geri döner. Artık programcı - işlemleri bu geri döndürülen sınıf nesnesinin metotlarıyla yapar. Örneğin: - - f = open('test.txt', 'r') - - Dosya işlemlerinde exception çok karşılaşılabilecek bir durumdur. Eğer programınızın çökmesini istemiyorsanız dosya - işlemlerini exception kontrolü içerisinde yapabilirsiniz. Örneğin: - - try: - f = open('test.txt', 'r') - ... - except OSError as e: - print(e) - ... - -#------------------------------------------------------------------------------------------------------------------------ - -try: - f = open('test.txt', 'r') - print('Ok') -except OSError as e: - print(e) - -print('continues...') - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi dosyayı açıp kullandıktan sonra artık o dosyayla ilgili işlem yapmayacaksak dosyayı - kapatmalıyız. Aslında çöp toplayıcı dosya nesnesini toplarken de __del__ metodunda zaten close işlemi uygulanmaktadır. - Ancak programcının dosyayı ilgili dosya nesnesinin close metoduyla kapatması uygun olur. close metodunun parametresi - yoktur. Örneğin: - - try: - f = open('test.txt', 'r') - ... - f.close() - except OSError as e: - print(e) - ... - - Dosya işlemi yaparken başka bir exception oluşup akış dış bir try bloğunun except bloğuna geçebiliyorsa kapatmanın - finally bloğunda yapılması uygun olur. Tabii open içerisinde exception oluşursa yine finally bloğu çalıştırılacağı için - açılmamış dosya close edilmeye çalışılmamalıdır. Bu işlem şöyle yapılabilir: - - f = None - try: - f = open('test.txt', 'r') - ... ===> bu kısımda exception oluşup akış bir yere gidecekse dosya kapatılarak gitmelidir - f.close() - except OSError as e: - print(e) - finally: - if f: ===> open içerisinde exception oluşursa dosya açılmadığı için kapatılmamalıdır - f.close() - ... - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İşletim sistemi bir dosyadaki her bir byte'a ilk byte 0 olmak üzere ardışıl bir pozisyon numarası vermektedir. Buna - "ilgili byte'ın offset'i" ya da "offset numarası" denilmektedir. Dosya işlemleri adeta kalemin ucu görevini yapan - "dosya göstericisi (file pointer)" denilen bir offset'ten itibaren yapılmaktadır. Dosya göstericisi bir offset belirtir. - Okuma ve yazma işlemleri o offset'ten itibaren yapılır. Dosya açıldığında dosya göstericisi 0'ıncı offset'tedir. Yani dosyaının - başındadır. Okuma ve yazma yapıldığında okunan ya da yazılan byte miktarı kadar dosya göstericisi otomatik ilerletilmektedir. - Örneğin: - - 0 1 2 3 4 5 6 - x x x x x x x - ^ - DG - - Burada dosya göstericisi 0'ıncı offset'te olsun. Biz dosyaya iki byte yazdığımızda artık 0 ve 1 numaralı offset'lerdeki - byte'lar güncellenecektir. Bu işlemden sonra dosya göstericisi otomatik olarak 2 artırılacak ve artık 2 numaralı offset'i - gösterir duruma gelecektir. - - 0 1 2 3 4 5 6 - y y x x x x x - ^ - DG - - Şimdi göstericisi aşağıdaki durumda olsun: - - 0 1 2 3 4 5 6 - x x x x x x x - ^ - DG - - Buradan biz 2 byte okursak 3'üncü ve 4'üncü offset'lerdeki byte'ları okuruz. Tabii dosya göstericisi bundan sonra artık 5'inci - offset'i gösterir duruma gelir: - - 0 1 2 3 4 5 6 - x x x x x x x - ^ - DG - - Şimdi dosya göstericisinin 6'ıncı offset'ti gösterdeiğini düşünelim: - - 0 1 2 3 4 5 6 - x x x x x x x - ^ - DG - - Buradan 1 byte okumak isteyelim. Artık dosya göstericisi dosyanın son byte'ındna sonraki byte'ı yani aslında dosyada - olmayan byte'ı gösteriyor durumda olur: - - 0 1 2 3 4 5 6 7 - x x x x x x x - ^ - DG (EOF durumu) - - İşte dosya göstericisinin dosyanın son byte'ından sonraki byte'ı göstermesi durumunda "EOF (End of File) durumu" denilmektedir. - EOF durumundan okuma yapamayız. Ancak EOF durumunda açış modu da uygunsa dosyaya yazma yapılabilmektedir. Bu durumda, - yazılanlar dosyaya eklenmektedir. Örneğin yukarıdaki durumda 3 byte dosyaya yazmak isteyelim. Şöyle bir durum oluşacaktır: - - 0 1 2 3 4 5 6 7 8 9 10 - x x x x x x x y y y - ^ - DG (EOF durumu) - - Dosyayı "w" modunda ya da "w+" modunda açtığımızı varsayalım. Dosya yoksa yaratılacak ve açılacak, dosya varsa sıfırlanacak - ve açılacaktır: - - 0 - ^ - DG (EOF durumu) - - Biz bu dosyaya 5 byte yazmak isteyelim. Şöyle bir durum oluşacaktır: - - 0 1 2 3 4 5 - y y y y y - ^ - DG (EOF durumu) - - Bir dosyaya ekleme yapabilmek için dosyanın ötesine yazma yapmak gerekir. Örneğin: - - 0 1 2 3 4 5 6 - x x x x x x x - ^ - DG - - Burada dosyaya 6 byte yazmak isteyelim. Şöyle bir durum oluşacaktır: - - 0 1 2 3 4 5 6 7 8 9 10 - x x x x y y y y y y - ^ - DG (EOF durumu) - - Genel olarak işletim sistemlerinde (dolayısıyla programlama dillerinde) dosya göstericisinin gösterdiği yerden itibaren - dosyanın sonuna kadar olan byte sayısından daha fazla byte okunmak istenebilir. Bu durum patolojik kabul edilmemektedir. - Dolayısıyla exception oluşturmaz. Bu tür durumlarda okunabilen kadar byte okunur. Örneğin: - - 0 1 2 3 4 5 6 - x x x x x x x - ^ - DG - - Burada dosyadan 10 byte okumak isteyelim. Bu durum exception oluşturmayacaktır. 3 byte okunacak ve okuma işlemi başarı - ile sonuçlanacaktır. Şöyle bir durum elde edilecektir: - - 0 1 2 3 4 5 6 7 - x x x x x x x - ^ - DG (EOF durumu) - - Bir dosyanın istenilen bir yerinden okuma yapmak için ya da i,stenilen bir yerine yazma yapmak için önce dosya göstericisinin - uygun biçimde konumlandırılması gerekir. Programlama dillerinde dosya göstericisini konumlandıran programlar ya da metotlar - bulunmaktadır. - - İşletim sistemleri her dosya açıldığında o açışa ilişkin ayrı bir dosya göstericisi oluşturmaktadır. Aynı dosyayı iki - kere açtığımızda iki farklı dosya nesnesi elde ederiz. Dolayısıyla iki farklı dosya göstericisi söz konusu olur. Örneğin: - - f1 = open('test.txt) - f2 = open('test.txt) - - Burada örneğin f1 nesnesi ile okuma yaptığımızda f1 nesnesine ilişkin dosya göstericisi ilerletilecektir. Bu işlemden - f2 nesnesine ilişkin dosya göstericisi etkileneyecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dosyalar "text" ve "binary" olmak üzere ikiye ayrılmaktadır. (Aslında işletim sistemi böyle bir ayrım yapmamaktadır. - Ancak bazı programlama dilleri işlemleri kolaylaştırmak için bu ayrımı yaparlar.) İçerisinde yalnızca yazıların bulunduğu - dosyalara "text" dosyalar, içerisinde yazıların dışında başka bilgilerin de bulunduğu dosyalara "binary" dosyalar denilmektedir. - Örneğin bir Python kaynak dosyası, bir notepad dosyası text dosyadır. Ancak örneğin bir jpeg dosyası, bir exe dosyası - binary dosyadır. Text dosyaların içerisinde yalnızca yazılar bulunmaktadır. Örneğin bir html dostası onu bilmeyenlere - tuhaf gelebilir. Ancak html dosyaları aslında yazı tutmaktadır ve dolayısıyla bu dosyalar text dosyalardır. - - Dosyalar Python'da text ya da binary modda açılabilmektedir. Yukarıda açıkladıpımı default açış modları text moddur. Binary modda - açış yapmak için açış modunun sonuna 'b' harfi getirilir. Örneğin: - - f = open('text.txt', 'r') # text modda açılıyor - - Fakat örneğin: - - f = open('test.jpg', 'rb') # binary modda açılıyor - - Bir dosyayı text modda açtığımızda ondan yazı okuruz, ona yazı yazarız. Ancak binary modda açtığmızda ondan byte'lar - okuyup byte'lar yazarız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dosya nesnesine ilişkin sınıfın read metodu dosya göstericisinin gösterdiği yerden n byte ya da n karakter okumak için - kullanılır. ASCII yazılarda ve ASCII karakterlerinin kullanıldığı UTF-8 yazılarda yazının her bir karakteri 1 byte'tır. - Eğer dosya text modda açılmışsa read metodu n karakter okur ve bize onu bir str nesnesi olarak verir. Eğer dosya binary - modda açılmışsa read metodu n byte okur ve bize onu bytes nesnesi olarak verir. Ukarıda da belirttiğimiz gibi read metodu - ile dosya göstericisinin gösterdiği yerden dosya sonuna kadar olan karakter ya da byte sayısından daha fazla okuma yapmak - istenirse bu durum normal karşılanmaktadır. Bu durumda okunabilen kadar karakter ya da byte okunur. Dosya göstericisi EOF - konumuna gelir. Eğer dosya göstericisi EOF durumundaysa read metodu hiçbir karakter ya da byte okuyamaz. Bu durumda dosya - text modda açılmışsa boş string, binary modda açılmışsa boş bytes nesnesi elde edilir. Eğer okuma sırasında bir IO - hatası oluşursa exception fırlatılmaktadır. - - read fonksiyonu text modda okuma yaparken default olarak "utf-8" encoding'ini kullanmaktadır. Bu encoding'te İngilizce - karakterler 1 byte, Türkçe karakterler 2 byte yer kaplamaktadır. (Diğer bazı ülkelerin karakterleri 2 byte'tan da daha - fazla yer kaplayabilmektedir.) - Örneğin "test.txt" dosyasının UTF-8 encoding'ine sahip olduğunu varsayalım ve içerisinde - "ağrıdağı" yazınının bulunduğunu düşünelim: - - >>> f = open('test.txt', 'r') - >>> s = f.read(2) - >>> s - 'ağ' - >>> s = f.read(2) - >>> s - 'rı' - >>> s = f.read(2) - >>> s - 'da' - >>> s = f.read(2) - >>> s - 'ğı' - >>> s = f.read(2) - >>> s - '' - - Burada hep yazıdan ikişer karakter okunarak okunanlar yazdırılmıştır. Eğer read metoduna okunacak miktar girilmezse - (yani read metodu argümansız çağrılırsa) bu durumda dosya göstericisinin gösterdiği yerden EOF durumuna kadar bütün - karakterler okunur. Örneğin: - - >>> f = open('test.txt', 'r') - >>> s = f.read() - >>> s - 'ağrıdağı' - >>> s = f.read() - >>> s - '' - -#------------------------------------------------------------------------------------------------------------------------ - -f = None - -try: - f = open('sample.py', 'r') - s = f.read() - print(s) -except OSError as e: - print(e) -finally: - if f: - f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Bir dosyanın içerisindekileri tek hamlede değil de bir döngü içerisinde okumak isteyelim. Yani her defasında örneğin - 1024 karakter okuya okuya dosyanın tamamını okumak isteyelim. Bunu şöyle yapabiliriz: - - whie True: - s = f.read(1024) - if s == '': - break - print(s, end='') - -#------------------------------------------------------------------------------------------------------------------------ - -SIZE = 1024 - -f = open(r'F:\Dropbox\Kurslar\Python\Doc\Python-Examples.txt', 'r') - -while True: - s = f.read(SIZE) - if s == '': - break - print(s, end='') - -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Tabii yukarıdaki kod walrus operatörüyle aşağıdaki gibi daha sade yazılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -SIZE = 1024 - -f = open(r'F:\Dropbox\Kurslar\Python\Doc\Python-Examples.txt', 'r') - -while s := f.read(SIZE): - print(s, end='') - -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - 55. Ders 07/11/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıda da belirttiğimiz gibi bir binary dosyadan read metodu ile okuma yapmak için binary dosyayı açarken açış moduna - 'b' eklememiz gerekir. Bundan sonra artık biz read işlemi yaptığımızda read metodu bize str nesnesi değil bytes nesnesi - verecektir. Tabii biz istersek text dosyaları da binary modda açabiliriz. Bu durumda read metodu bize yine bytes nesnesi - verir. Ancak binary bir dosyanın text modda açılması çoğu kez anlamsızdır. Örneğin bir PDF dosyası binary bir dosyadır. - Bizim onu binary modda açıp okumamız uygun olur. Bu durumda read metodu bize bytes nesnesi verecektir. Ancak pdf dosyasının - içerisindeki byte'ları yazı gibi yorumlayıp yazdırmaya çalışırsak anlamlı şeyler göremeyiz. Aşağıda dosya hangi modda - açılırsa read metodunun ne geri döndüreceğini bir tablo halinde veriyoruz: - - Dosya türü Açış Modu read Metodunun Geri Dönüş Değeri - - text text str - text binary bytes - binary binary bytes - binary text str ama anlamsız - - Aşağıda bir pdf dosyasından 10 byte okuma örneği verilmiştir. Tabii pdf gibi jpg gibi bmp gibi binary dosyaları okuyup - birtakım faydalı işlemleri yapabilmemiz için bizim bu dosyalara ilişkin dosya formatları hakkında bilgi sahibi olmamız - gerekir. -#------------------------------------------------------------------------------------------------------------------------ - -f = open('../doc/python.pdf', 'rb') -b = f.read(10) -print(b) -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Bir dosyaya yazma yapmak için write metodu kullanılmaktadır. write metodunun tek bir parametresi vardır. Dosya text - modda açılmışsa write metodu bir string argüman alır, dosya binary modda açılmışsa bir bytes nesnesini argüman olarak alır. - Tabii dosyaya yazma yapılabilmesi için açış modunun uygun olması gerekir. Örneğin: - - f = open('test.txt', 'w') - f.write('this is a test' - f.close() - - Dosyaya yazma yapıldığında dosya göstericisinin gösteridği offset'te zaten birtakım bilgiler varsa write onları - ezerek yazma yapmaktadır. Dosya işlemlerinde "insert etme" diye bir işlem yoktur. Dosyaya ekleme ancak sona yapılabilmektedir. - -#------------------------------------------------------------------------------------------------------------------------ - -f = open('test.txt', 'w') - -f.write('this is a test') -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Bir text dosyada aslında satır (line) kavramı yoktur. Satır kavramı dosyaya \n karakterinin yazılmasıyla sağlanmaktadır. - Örneğin: - - f.write('ali\nveli') - - Burada dosyadaki tüm karakterler aslında yan yanadır. Ancak dosya editöre çekildiğinde editör \n (LF) karakterini görünce - imleci aşağı satırın başına geçirdiği için "sanki dosya satırlardan oluşuyormuş gibi" bir görüntü elde edilmektedir. - Tersten gidersek text dosyanın içini aşağıdaki gibi görmüş olalım: - - ankara - izmir - - Aslında dosyanın içerisindeki gerçek dizilim UNIX/Linux ve macOS sistemlerinde şöyledir: - - ankara\nizmir - - Windows sistemlerinde biz dosyaya \n karakterini yazdığımızda Windows dosyaya yalnızca \n karakterini basmaz. - \r\n karakter çiftini basar. Bu çifte CR/LF çifti denilmektedir. Dolayısıyla yukarda iki satır görünümündeki dosyanın - içeriği Windows sistemlerinde şöyle olacaktır: - - ankara\r\nizmir - - Ancak UNIX/Linux ve macOS sistemlerinde biz text dosyaya \n karakterini bastığımızda dosyaya yalnızca \n karakteri - basılmaktadır. \n karakterinin byte olarak hex karşılığı 0A, \r karakteerinin byte olarak hex karşılığı ise 0D biçimindedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte binary modda açılan bir dosyaya write metodu ile 5 byte'lık bir bilgi yazdırılmıştır -#------------------------------------------------------------------------------------------------------------------------ - -f = None - -try: - f = open('test.dat', 'wb') - f.write(b'\x12\x56\xFC\x1B\x32') -except Exception as e: - print(e) -finally: - if f: - f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Dosyalar ister text olsun ister binary olsun ardışıl byte topluluklarından oluşmaktadır. Dolayısıyla bir dosyanın arasına - bir şey insert etmek ya da dosyanın arasından bir şeyler silmek otomatik yapılabilecek işlemler değildir. Bir dosyanın - sonuna eklemeancak EOF ötesine yazma yapmakla mümkün olur. Bir dosyanın belirli bölümüne bir şeyler insert etmek zor bir - işemdir. Bu tür işlemlerin başka bir dosya kullanılarak o dosya üzerinde yapılması uygun olur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Dosya nesneleri (file objects) dolaşılabilir nesnelerdir. Bir dosya nesnesi dolaşıldığında dosyadaki satırlar elde edilir. - Tabii elde edilen satırların sonunda '\n' karakterleri de vardır. (Yani satırları print fonksiyonuyla yazdırırken imleci iki kere - aşağı satıra geçirmemek için end parametresini '' biçiminde geçebilirsiniz.) Dosyanın satırları bittiğinde dolaşma da biter. - - Her ne kadar binary dosyalar da bu biçimde dolaşılabiliyorsa da binary dosyaların bu biçimde satır satır dolaşılması - genellikle anlamsızdır. -#------------------------------------------------------------------------------------------------------------------------ - -f = open('test.txt') - -for line in f: - print(line, end='') -f.close() - -f = open('test.txt') -a = list(f) -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Dosya nesnesine ilişkin sınıfın readline isimli metodu da vardır. Bu metot dosya göstericisinin gösterdiği yerden satır - sonuna kadar ('\n' karakteri de dahil olmak üzere) karakterleri okur ve o satır yazısıyla geri döner. Eğer dosya sonuna - gelinirse metot boş string'le geri dönmektedir. Bu durumda biz bir text dosyayı readline metotlarını çağırarak da satır - satır aşağıdaki gibi okuyabiliriz: - - f = open('test.txt', 'r') - - while True: - s = f.readline() - if s == '': - break - print(s, end='') - - f.close() - - Tabii bu işlem yine Walrus operatörü ile daha pratik yapılabilir: - - f = open('test.txt', 'r') - - while (s := f.readline()) != '': - print(s, end='') - - f.close() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bazen dosyanın içerisinde bir yerlerden okuma yapmak ya da içerisinde bir yerlere yazma yapk istenebilir. Bunun için - önce dosya göstericisinin istenilen yere konumlandırılması konumlnadırılması gerekir. Konumlandırma için seek metodu - kullanılmaktadır. seek metodunun iki parametresi vardır: - - seek(offset, origin) - - seek metodunun birinci parametre offset belirtir. İkinci parametresi ise konumlandırmanın nereden itibaren yapılacağını - anlatan orijin belirtmektedr. İkinci parametre 0, 1 ya da 2 değerini alır. Bu değerler için sıraıyla os modülündeki - SEEK_SET, SEEK_CUR ve SEEK_END sembolik sabitleri de kullanılabilmektedir. 0 konumlandırmanın baştan itibaren yapılacağını, - 1 konumlandırmanın o anda dosya göstericisinin gösterdiği yerden itibaren yapılacağını, 2 ise konumlandırmanın EOF - pozisyonundan itibaren yapılacağını belirtir. İkinci parametre 0 ise birinci parametre >= 0 olmak zorundadır. İkinci - parametre 1 ise birinci parametre pozitif, negatif ya da 0 olabilir. Pozitif bulunulan yerden ileriye, negatif bulunulan - yerden geriye ve 0 ise bulunulan yere konumlandırma anlamına gelir. İkinci parametre 2 ise birinci parametre <= 0 olmak - zorundadır. Bu durumda konumlandırma EOF'tan itibaren geriye yapılır. Metodun ikinci parametresi 0 default değerini almıştır. - Dosya text modda açıldığında göreli konumlandırma (yani ikinci parametre için 1 değerinin geçilmesi) yapılamamaktadır. - Ancak göreli konumlandırma bulunulan yere (yani f.seek(0, 1) biçiminde) yapılabilmektedir. (Bulunulan yere konumlandırma - okumadna yazmaya, yazamadan okumaya geçişti zorunlu olarak gerekmektedir.) Benzer biçimde text modda EOF'a göre konumlandırmada - da negatif offset kullanılamamaktadır. Ancak binary modda istenilen offset'e göre istenildiği biçimde konumlandırma - yapılabilmektedir. - - Aşağıda bazı örnekler ve anlamları verilmiştir: - - - f.seek(100, 0) ya da f.seek(100) burada konumlandırma dosyanın başından itibaren 100'üncü offset'e yapılır. - - - f.seek(-1, 1) burada konumlandırma dosya göstericisi neredeyse onun bir gerisine yapılır. Ancak text modda bu çağrı - exception oluşturacaktır. - - - f.seek(0, 2) burada konumlandırma EOF pozisyonuna yapılır. - - - f.seek(-1, 2) burada konumlandırma son byte'a yapılır. Ancak text modda bu çağrı exception oluşturacaktır. - - - f.seek(0, 1) burada konumlandırma bulunulan offset'e yapılır. Bulunulan offset'e konumlandırma tuhaf ve saçma gibi - geliyorsa da okumdan yazmaya, yazmadan okumaya geçişti yapılması gerekmektedir. - - Aşağıdaki örnekte bir text dosyanın 20'inci offset'inden itibaren 10 karakter okunmuştur. -#------------------------------------------------------------------------------------------------------------------------ - -f = open('test.txt', 'r') - -f.seek(20, 0) -s = f.read(10) -print(s) -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Dosyanın sonua bir şeyler eklenmek isteniyorsa önce dosya göstericisi f.seek(0, 2) çağrısı ile EOF poziasyonuna - konumlandırılmalıdır. Anımsanacağı gibi EOF durumunda dosyaya yazma yapıldığında bu durum dosyaya ekleme anlamına gelmektedir. - Örneğin: - - f = open('test.txt', 'r+') - - f.seek(0, 2) - f.write('ankara') - - f.close() - - Eğer dosya 'a' modunda açılırsa her yazılan dosyanın sonuna eklenir. Yani başka bir deyişle 'a' modunda yazma yapılmadan - önce zaten işletim sistemi dosya göstericisini EOF durumuna çekmektedir. 'a' modunda dosya göstericisinin konumlandırılmasının - bir anlamı yoktur. Çünkü her yazma işleminden önce zaten otomatik olarak dosya göstericisi EOF durumuna çekilmektedir. - 'a+' modunda ise her yazılan dosyanın sonuna yaılmakta ancak dosyanın herhangi bir yerinden okuma yapılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -f = open('test.txt', 'r+') - -f.seek(0, 2) -f.write('ankara') - -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Dosya nesnesine ilişkin sınıfın tell isimli metodu dosya göstericisinin baştan itibaren konumunu geri döndürmektedir. - Örneğin bir dosyanın uzunluğunu aşağıdaki gibi elde edebiliriz: - - f.seek(0, 2) - size = f.tell() - print(size) - -#------------------------------------------------------------------------------------------------------------------------ - -f = open('test.txt', 'r') - -f.seek(0, 2) - -result = f.tell() -print(result) - -f.close() - -#------------------------------------------------------------------------------------------------------------------------ - Biz dosyaları text dosyalar ve binary dosyalar olmak üzere ikiye ayırmıştık. Ancak text dosyalar da içerisindeki karakterlerin - kodlanma biçimine göre farklılıklar gösterebilmektedir. Text dosyalarda karakterler belli bir karakter tablosuna göre - kodlanmaktadır. Aslında karakterler de birer sayı gibi tutulmaktadır. Bilgisayarın belleğinde yalnızca sayılar (ikilik sistemde) - tutulmaktadır. Dolayısıyla aslında bir yazı bir sayı dizilimi gibidir. Yazının her bir elemanına programlamada - "karakter (character)" denilmektedir. Bilgisayarların ilk günlerinden bu yana çeşitli kurumlar ve şirketler tarafından - çeşitli karakter tabloları geliştirilmiştir. Bu karakter tablolarında sembollerim numaralandırmaları birbirinden farklı - olabilmektedir. - - Bir karakter tablosu oluşturulurken dört kavram kullanılmaktadır: - - - Karakter Repertuarı (Character Repertoire): Bir karakter tablosundaki karakterlerin kümesine denilmektedir. - - Sembol (Glyph): Karakter tablosundaki karakterlerin görüntüsüne yani şekline denilmektedir. - - Kod Numarası (Code Point): Karakter tablosu içerisindeki karakterlere tek tek 0'dan başlanarak numaralar verilmiştir. - Bu numaralara İngilizce "code point" denilmektedir. - - Karakter Kodlaması (Character Encoding): Karakter tablosundaki bir code point'in ikilik sisteme yani byte formatına - dönüştürülme biçimidir. - - Dünyanın ilk karakter tablosu ASCII (American Standard Code Information Interchange) denilen tablodur. ASCII tablosunun karakter - repertuarı 128 karakterden oluşmaktadır. Yani ASCII tablosu 128 karakterden oluşmaktadır ve karakterlerin kod numaraları - (code points) 0-127 arasındadır. ASCII tablosunda her kod numarası doğrudan 2'lik sisteme dönüştürülmektedir. Yani ASCII tablosuun - özel bir karakter kodlaması yoktu. - - ASCII tablosu Amerika için yani İngilizce için oluşturulmuş bir tabloydu. Bilgisayarlar yaygınlaşıp Avrupa ülkelerinde kullanılmaya - başlanınca o ülkelerdeki özel karakterler sorun yaratmaya başladı. Bu problemi ortadan kaldırmak için ASCII tablosu 256 karaktere - yükseltildi. (Orijinal ASCII tablosu hiçbir zaman kendini 256 karaktere yükseltmedi. Bu girişim değişik kurumlar tarafından ASCII - tablosuna bir ek olarak yapıldı.) Ancak ASCII tablosuna çeşitli ülkeler ve çeşitli kurumlar bir koordinasyon sağlamadan 128 karakteri - eklediler. ASCII tablosunun bu genişletilmiş biçimlerine "code page" denilmektedir. İşte zamanla ilk 128 karakteri standart ASCII - tablosundaki karakterler olan ancak sonraki 128 karakteri birbirinden farklı olan pek çok code page'ler orataya çıktı. Farklı latin - dilleri için farklı code page'lerin ortaya çıkması karışıklığı hepten artırmıştır. Üstelik aynı dil için bile farklı code page'ler - de bulunmaktadır. Örneğin Türkçe için ACII tablosunun üç farklı code page'i vardır: OEM 754, Microsoft 1254, ISO 8859-9. - ISO geç kalmış olsa da bu code page'leri zamanla standart hale getirmiştir. ISO'nun ASCII page'lerini tanımladığı standartlara - 8850 denilmektedir. 8859-1 code page'ine "Latin 1" code page'i denilmektedir. Türkçe code page ISO 8859-9 code page'idir. - - ASCII tablosunun değişik code page'leri oluşturulmuş olsa da temel bazı problemler bu yöntemle çözülememiştir. Bazı dilelrin - karakter sayıları 256'dan fazladır. Örneğin Japonca ve Çince gibi uzak doğu dillerinde 4000'e yakın "kanji" denilen karakter - bulunmaktadır. İşte bu karışıklığı engellemek için ismine UNICODE denilen bir karakter tablosu geliştirilmiştir. Eski devirlerde - bellekler çok küçük olduğu için karakterlerin bir byte ifade edilmesi anlamlıydı. Ancak zamanla bellek miktarları üstel bir - biçimde artınca artık karakterlerin bir byte alan tutulması konusu da sorgulanmaya başlanmıştır. UNICODE tablo özünde her karakterin - 2 byte yer kapladığı dolayısıyla içerisine çok fazla karakterin yerleştirildiği bir tablodur. Dünyanın bütün dillerinin karakterleri - bu tabloya yerleştirilmiştir. - - UNICODE tablonun ilk 128 code point'ine karşı gelen karakterleri standart ASCII tablosu ile aynı karakterlerdir. Sonraki - 128 code point'ine karşı gelen karakterler ise Latin-1 code page'inin (ISO 8859-1) karakterleridir. - - UNICODE tablonun değişik encoding'leri vardır. En yaygın kullanılan encoding'i UTF-8'dir. UTF-8 encoding'inde UNICODE'un ilk 128 - code point'i 1 byte ile kodlanır. Sonraki code point'leri 2 byte, 3 byte, 4 byte ile kodlanmaktadır. Bunun bir algoritması vardır. - UTF-8 kodlaması adeta UNICODE tablonun sıkıştırılmış bir kodlaması gibidir. Ayrıca standart ASCII metinler tamamen UTF-8 encoding'i - ile aynı olmaktadır. Yani UTF-8 encoding'i ASCII uyumludur. UTF8-8 encoding'inde Türkçe karakterler 2 byte yer kaplamaktadır. Örneğin: - - "ağrı dağı" - - bu yazı 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 2 = 13 byte yer kaplar. - - Bu anlatımlardan çıkan en önemli sonuç programcı bir text dosyayla çalışıyorsa onun encoding'ini bilme zorunluluğudur. Örneğin biz - UTF-8 olarak kodlanmış bir yazıyı ISO 8859-9 olarak okumak istersek tuhaf karakterler elde edebiliriz. Pekiyi bir text dosyanın - encoding'ini nasıl bilebiliriz? UNICODE text text dosların başında encoding için bir BOM (Byte of Order Mark) marker bulundurulabilmektedir. - Böylece programlar bu BOM marker'a bakarak encoding tespitini yapmaya çalışabilmektedir. Ancak BOM marker yalnızca UNICODE encoding'ler - için söz konusudur ve BOM marker UNICODE text dosyaların başında bulunmak zorunda değildir. (Yani isteğe bağlıdır.) Text editörler - genellikle BOM marker'a bakarlar eğer bu BOM marker'ı göremezlerse default bir encoding ile yazıyı yorumlarlar. Tabii en iyi durum - zaten kullanıcının bunu biliyor olması ve ona göre dosyayla ilgili işlem yapmasıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da bir dosya text modda open fonksiyonuyla açılırken open fonksiyonun encoding parametresi ile encoding - belirtilebilmektedir. Örneğin: - - f = open('test.txt', 'r', encoding='utf-8') - - Dosyalar open fonksiyonuyla açılırken open fonksiyonun kullandığı default encoding sistemden sisteme değişebilmektedir. - Bu durum işletim sistemindeki locale ayarlarına bağlıdır. Default encoding standart locale modülündeki getpreferredencoding - fonksiyonuyla ya da 3.11 ile eklenen getencoding fonksiyonu ile elde edilebilmektedir. - - encoding parametresi için encoding belirten pek çok yazı girilebilir. Örneğin 'cp1254', 'iso8859_9' gibi. Encoding parametresi - için kullanılabilecek encoding belirten yazıların listesini aşğıdaki bağlantıdan elde edebilirsiniz: - - https://docs.python.org/3.11/library/codecs.html#standard-encodings - -#------------------------------------------------------------------------------------------------------------------------ - 56. Ders 14/11/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir exception oluştuğunda exception başka bir yerde ele alınıyor olabilir. Ancak bu tür durumlarda exception'ın oluştuğu - noktadan önce bir kaynak tahsis edilmişse akış başka bir yere gitmeden o kaynağın geri bırakılması gerekmektedir. Biz bunun - için daha önce try deyiminin finally bölümünü kullanmıştır. Örneğin: - - f = None - try: - f = open(...) - ... - ... - ... - finally: - if f: - f.close() # finally bölümü her zaman çalıştırılacağıu için dosya her zaman kağatılacaktır - - İşte genel olarak with deyimi bu tür durumlarda kolay bir yazım sağlamak için kullanılmaktadır. with deyiminin genel biçimi - şöyledir: - - with [ as ]: - - - Burada ifade as anahtar sözcüğünün yanındaki değişkene atanır ve suit çalıştırılır. Örneğin: - - with open('test.txt') as f: - s = f.read() - print(s) - - Burada open fonksiyonunun geri dönüş değeri f değişkenine atanıp ilgili suit çalıştırılmaktadır. with deyiminde as kısmı - olmak zorunda değildir. Yani with deyimi şöyle de kullanılabilir: - - with : - - - Örneğin: - - f = open('test.txt') - with f: - s = f.read() - print(s) - - with benzeri deyimler diğer nesme yönelimli programlama dillerinin bazılarında da bulunmaktadır. Örneğin bu deyim C#'ta karşımıza using - deyimi biçiminde çıkmaktadır.S - - Bir ifadenin with deyimi ile kullanılabilmesi için o ifadenin türünün "bağlam yönetim protokolüne (context management protocol)" - uygun olması gerekir. Python'un temel türleri bağlam yönetim protokolüne uymamaktadır. Ancak örneğin open fonksiyonundan elde ettiğimiz - dosya nesnesine ilişkin sınıf bağlam yönetim protokolüne uymaktadır. Biz de kendi sınıflarımızın bağlam yönetim protokolüne uymasını - sağlayabiliriz. - - Bir sınıfın bağlam yönetim protokolüne uygun olması için sınıfta __enter__ ve __exit__ isimli iki metodun bulunuyor olması gerekir. - __enter__ metodunun yalnızca self parametresi vardır. Ancak __exit__ metodunun self dışında üç parametresi daha bulunur. - Bu parametreler istenildiği gibi isimlendirilebilir. Ancak programcılar genellikle bunları xc_type, exc_value, traceback biçiminde - isimlendirirler. Örneğin: - - class Sample: - def __enter__(self): - pass - def __exit__(self, xc_type, exc_value, traceback): - pass - - with Sample() as s: - pass - - with deyimi kabaca şöyle çalışmaktadır: Önce with anahtar sözcüğünün yanındaki ifade yapılır. Sonra bu nesne ile __enter__ metodu çağrılır, - __enter__ metodunun geri dönüş değeri as değişkenine atanır. Akış with deyiminden hangi yolla çıkarsa çıksın (exception dahil olmak üzere) with deyiminin yanındaki nesne ile __exit__ metodu çağrılır. - - Örneğin: - - class Sample: - def __init__(self): - print('__init__') - def __enter__(self): - print('__enter__') - def __exit__(self, xc_type, exc_value, traceback): - print('__exit__') - - with Sample() as s: - print('suite') - print('ends') - - Burada ekrana şu yazılar çıkacaktır: - - __init__ - __enter__ - suite - __exit__ - ends - - with deyimi bir sınıf nesnesi yaratılırken yapılan bazı tahsisatların otomatik bırakılmasını mümkün hale getirmek için - düşünülmüştür. Tabii bu tür kaynaklar genellikle Python dünyasının dışında tahsis edilen "unmanaged" denilen kaynaklardır. - Fakat herhangi bir kaynak bu deyimle otomatik bırakılabilir. Örneğin: - - with open('test.txt') as f: - s = f.read() - print(s) - - Burada open fonksiyonun geri döndürdüğü dosya nesnesine ilişkin sınıf "bağlam yönetim protokolünü" desteklemektedir. Bu nedenle - with deyiminden çıkılırken yorumlayıcı tarafından adeta f.__exit__(...) biçiminde sınıfın __exit__ metodu çağrılacaktır. İşte - bu metotta sınıfı yazanlar dosyayı zaten kapatmışilardır. O halde biz open fonksiyonunu bu biçimde kullanırsak with deyiminden - çıkılırken dosya otomatik kapatılacaktır. Bizim ayrıca dosyayı close ile kapatmamıza gerek kalmayacaktır. - - with deyimi try-finally gibi düşünülmelidir. with deyimi ile exception ele alınmamaktadır. Yalnızca otomatik boşaltım mekanizması - oluşturulmaktadır. Exception ele alınmak isteniyorsa ayrıca try deyimi uygulamalıdır. Örneğin: - - class Sample: - def __init__(self): - print('kaynak tahsis ediliyor') - - def __enter__(self): - print('__enter__ called') - - def __exit__(self, xc_type, exc_value, traceback): - print('kaynak boşaltılıyor') - - def foo(): - print('bir') - with Sample() as s: - print('s suit içerisinde kullanılıyor ve exception oluşuyor') - raise ValueError() - print('iki') - - def main(): - try: - foo() - except: - print('exception yakalandı') - - main() - - Yukarıdaki kod çalıştırıldığnda ekrana aşağıdaki yazılar basılacaktır: - - bir - kaynak tahsis ediliyor - __enter__ called - s suit içerisinde kullanılıyor ve exception oluşuyor - kaynak boşaltılıyor - exception yakalandı - - Programcı bağlam yönetim protokülüne uygun bir sınıf yazarken gerekli boşaltım işlemlerini __exit__ metodu içerisinde yapmalıdır. - - Aslında with deyiminin çalışması biraz ayrıntılara sahiptir. Python Language Reference dokğmanına göre: - - with EXPRESSION as TARGET: - SUITE - - deyiminin eşdeğeri şöyledir: - - manager = (EXPRESSION) - enter = type(manager).__enter__ - exit = type(manager).__exit__ - value = enter(manager) - hit_except = False - - try: - TARGET = value - SUITE - except: - hit_except = True - if not exit(manager, *sys.exc_info()): - raise - finally: - if not hit_except: - exit(manager, None, None, None) - - Yukarıdaki kod daha basit bir biçimde şöyle de ifade edilebilirdi: - - manager = (EXPRESSION) - value = manager.__enter__() - - hit_except = False - - try: - TARGET = value - SUITE - except: - hit_except = True - if not manager.__exit__(*sys.exc_info()): - raise - finally: - if not hit_except: - manager.__exit__(None, None, None) - - - Bu eşdeğerlilikten deyimin çalışması hakkında şunlar söylenebilir: - - 1) Öncelikle with anahtar sözcüğünün yanındaki ifade işletilir. - - 2) with anahtar sözcüğünün yanındaki ifadenin değeri ile sınıfın __enter__ metodu çağrılır. Bu __enter__ metodunun - geri dönüş değeri as anahtar sözcüğünün yanındaki değişkene atanmaktadır. Yani: - - with EXPRESSION as TARGET: - SUITE - - Burada aslında TARGET = EXPRESSION.__enter__() ataması yapılmaktadır. Görüldüğü gibi aslında with deyiminde with anahtar - sözcüğünün yanındaki ifade as ile belirtilen değişkene atanmamaktadır. __enter-_ metodunun geri dönüş değeri as ile belirtilen - değişkene atanmaktadır. - - O halde bizim en azından __enter__ metodu içerisinde self ile geri dönmemiz gerekir. Örneğin: - - class Sample: - def __enter__(self): - return self - ... - - Böylece gerçekten artık with anahtar sözcüğünün yanındaki ifadenin sonucu as ile beliretilen değişkene atanmış olur. - - 3) with deyimindeki suite'ten bir exception ile çıkıldığında exception'ın bilgileri ile __exit__ metodu çağrılmaktadır. - Ancak with deyiminden exception ile çıkılmadıysa bu durumda __exit__ metodu None, None, None parametreleriyle çağrılmaktadır. - - 4) Eğer with deyiminden exception ile çıkılmışsa bu durumda __exit__ metodunun geri dönüş değerine bakılmaktadır. Eğer __exit__ - metodu True ile geri dönmüşse sanki exception oluşmamış gibi akış with deyiminden sonra devam etmektedir. Yani bu durumda adeta - oluşan exception yakalnmış gibi işlem yapılmaktadır. Ancak __exit__ metodundan False ile geri dönülmüşse exception yakalanmamış - gibi davranış oluşmaktadır. Bu durumda __exit__ metodunun geri dönüş değeri önemli olmaktadır. Bir fonksiyonda return kullanmadıysanız - fonksiyonun None değeriyle geri döndüğünü anımsayınız. Nonde değeri de if deyiminde False olarak ele alınmaktadır. - - with deyimi bazı potansiyel olanakları programcı kullanabilsin diye bu biçimde biraz karışık tasarlanmıştır. Ancak programcılar - çoğunlukla bu protokülü desteklerken oldukça yalın bir kod kullanırlar. Programcılar genellikle __enter__ metodundan self ile geri - dönerler. __exit__ metodunda kaynak boşaltımını yaparlar ve __exit__ metodunda return kullanmazlar. Bu durumda __exit__ metodu - None ile geri döner ve exception oluşursa exception dışarıya verilir. - - Pekiyi __enter__ metoduna neden gereksinim duyulmuştur? İşte bazen programcı __enter__ metodunda aslında başka bir sınıf - türünden nesne vermek isteyebilir. Ancak böylesi durumlarla çok seyrek karşılaşılmaktadır. - - with anahtar sözcüğünün yanındaki ifadede exception oluşursa __exit__ metodunun çağrılmayacağına dikkat ediniz. Bu ifade - semantik olarak with deyimi ile bağlantılı değildir. Örneğin: - - with open('test.txt) as f: - pass - - Burada open fonksiyonunda bir exception oluşursa bu exception dıştaki bir try bloğu tarafından yakalanabilir. Çünkü henüz - with deyiminin içerisine girilmemiştir. - - Aşağıdaki örnekte bir dosyayı sarmalayan bir sınıf örneği verilmiş ve __exit__ metodunda dosyanın kapatılması sağlanmıştır. - -#------------------------------------------------------------------------------------------------------------------------ - -class FileWrapper: - def __init__(self, *args, **kwargs): - self.f = open(*args, **kwargs) - - def __enter__(self): - return self - - def __exit__(self, xc_type, exc_value, traceback): - self.f.close() - - def read(self, *args, **kwargs): - return self.f.read(*args, **kwargs) - - def write(self, *args, **kwargs): - return self.f.write(*args, **kwargs) - - def seek(self, *args, **kwargs): - return self.f.seek(*args, **kwargs) - -with FileWrapper('test.txt', 'r', encoding='cp1254') as fw: - s = fw.read() - print(s) - -#------------------------------------------------------------------------------------------------------------------------ - Aslında with deyiminde birden fazla ifade de kullanılabilmektedir. Yani örneğin: - - with ifade1 as değişken1, ifade2 as değşken2, ifade3 as değişken3: - - - Bu biçimdeki gibi bir with deyimi geçerlidir. Birden fazla ifadenin bulunduğu with deyimleri "iç içe" gibi ele alınmaktadır. - Yani yukarıdaki with deyiminin eşdeğeri şöyledir: - - with ifade1 as değişken1: - with ifade2 as değişken2: - with ifade3 değişken3: - - - Dolayısıla örneğin: - - with ifade1 as değişken1, ifade2 as değşken2, ifade3 as değişken3: - - - Bu biçimdeki with deyiminde ifadelere ilişkin sınıfların __enter__ metotları soldan sağa çağrılacaktır. __exit__ metotları - da ters sırada sağdan sola çağrılacaktır. Ayrıca burada baş taraftaki ifadelerde exception oluşrsa artık sol taraftaki - nesneler için __exit__ metodunun çağrılacağına dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Biz şimdiye kadar dolaşılabilir (iterable) nesneler ve dolaşım nesneleri üzerinde çalıştık. Onları for döngüsüyle - doğrudan, list gibi tuple gibi sınıflarla dolaylı olarak dolaştık. Pekiyi kendimiz nasıl dolaşılabilir bir sınıf - yazabiliriz? Bu bölümde bu konu ele alınacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Öncelikle "dolaşılabilir (iterable)" nesne ile "dolaşım (terator)" nesnesi kavramları arasındaki farkı yeniden anımsatmak - istiyoruz. Bir nesne "dolaşılabilir (iterable)" ise onu her defasında yeniden dolaşabiliriz. Örneğin list, tuple, set, - dict gibi sınıflar türünden nesneler dolaşılabilir nesnelerdir. Bir nesne "dolaşım (iterator)" nesnesi ise onu bir kez - dolatığımızda bitirmiş oluruz. İkinci kez dolaşamayız. Python'un zip gibi, map gibi, enumerate gibi metotları bize - dolaşılabilir bir nesne değil bir dolaşım nesnesi vermektedir. Hem dolaşılabilir nesne hem de dolaşım nesnesi for - döngüsü ile dolaşılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir sınıfın dolaşılabilir olması için sınıfın içerisinde __iter__ isimli bir metodun bulunuyor olması gerekir. __iter__ - metodunun self parametresi dışında başka bir parametresi yoktur. Öneğin: - - class Sample: - def __iter__(self): - pass - - Burada artık Sample dolaşılabilir (iterable) bir sınıftır. __iter__ metodunun bir "dolaşım (iterator)" nesnesiyle geri - döndürülmesi gerekmektedir. Bu durumda dolaşılabilir bir sınıf bize bir dolaşım nesnesi vermelidir. Asıl dolaşım işlemi - bu dolaşım nesnesiyle yapılmaktadır. Bu durumda iki sınıf söz konusu olmaktadır: - - 1) Dolaşılabilir (iterable) sınıf - 2) Dolaşım (iterator) sınıfı - - Yukarıda da belirttiğimiz gibi bir sınıfın dolaşılabilir olması için __iter__ metoduna sahip olması gerekir. Ancak sınıfın - dolaşım sınıfı olması için __next__ metoduna sahip olması gerekmektedir. __next__ metodunun da self dışında bir parametresi - yoktur. Dolaşılabilir sınıf ile dolaşım sınıfları farklı olabilmektedir. Ancak çoğu kez bunlar aynı sınıf da olabilirler. Bu - durumda sınıfın hem __iter__ hem de __next__ metotları bulunur. __iter__ metodu self ile geri döndürülür. Aşağıdaki - örnekte dolaşılabilir sınıf ile dolaşım sınıfı farklı sınıflar olarak oluşturulmuştur. Dolaşılabilir sınıfın __iter__ - metodunda dolaşım sınıfı türünden bir nesne ile geri dönülmüştür: - - class SampleIterator: - def __next__(self): - pass - - class SampleIterable: - def __iter__(self): - si = SampleIterator() - return si - - Burada dolaşılabilir sınıfının __iter__ metodunun dolaşım sınıfı türünden bir nesneye geri döndüğüne dikkat ediniz. __iter__ - ve __next__ metotları yalnızca self parametresine sahiptir. - - Aslında Python Language Reference dokümanlarına göre dolaşım sınıfınlarının yalnızca __next__ metoduna değil aynı zamanda - __iter__ metoduna da sahip olması gerekmektedir. Bunun nedenini izleyen paragraflarda anlayacaksınız. Genellikle dolaşım sınıflarının - __next__ metotları self ile geri döndürülür. Örneğin: - - class SampleIterator: - def __next__(self): - pass - - def __iter__(self): - return self - - class SampleIterable: - def __iter__(self): - si = SampleIterator() - return si - - Aşağıda ise dolaşılabilir sınıf ile dolaşım sınıfı aynı sınıf yapılmıştır: - - class SampleIterable: - def __iter__(self): - return self - - def __next__(self): - pass - - Burada SampleIterable sınıfı hem dolaşılabilir bir sınıftır hem de dolaşım sınıfıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki gibi bir for döngüsü olsun: - - for x in iterable: - - - Bu döngünün tam eşdeğeri aslında şöyledir: - - iterator = iterable.__iter__() - try: - while True: - x = iterator.__next__() - - except StopIteration: - pass - - Buradaki eşdeğer kodun sözel açıklaması şöyle yapılabilir. Python yorumlayıcısı önce dolaşılabilir nesne ile __iter__ - metodunu çağırır ve ondan dolaşım nesnesini elde eder. Sonra sürekli olarak dolaşım nesnesi ile __next__ metodu - çağrılmaktadır. Her __next__ çağrısı bir değer verir ve o değer döngü değişkenine yerleştirilerek suit çalıştırılır. - Bu döngüden çıkış StopIteration exception'ı ile yapılmaktadır. Başka bir deyişle programcı dolaşım sınıfının __next__ - metodu ile vereceklerini verir. Artık verecek bir şeyi kalmayınca StopIteration exception'ını fırlatır. - - Aşağıdaki örnekte Counter isimli sınıf __init__ metodunda bir stop değeri almıştır. Bu sınıf türünden nesne her - dolaşıldığında 0'dan bu stop değerine kadar tamsayılar elde edilecektir: - - class Counter: - def __init__(self, stop): - self.stop = stop - - def __iter__(self): - self.count = 0 - return self - - def __next__(self): - if self.count == self.stop: - raise StopIteration() - self.count += 1 - return self.count - 1 - - Burada her __next__ çağrıldığında nesnenin count özniteliğinin içerisindeki değerle geri dönülmüştür. Eğer bu count - değeri stop değerine ulaşmışsa dolaşımın sonlandırılması için StopIteration exception'ı raise edilmiştir. Örneğin: - - c = Counter(10) - - for x in c: - print(x) - - Bu kodun eşdeğeri şöyledir: - - c = Counter(10) - - iterator = c.__iter__() - try: - while True: - x = iterator.__next__() - print(x) - except StopIteration: - pass - - Burada count örnek özniteliğinin __iter__ metodu içerisinde sıfırlandığına dikkat ediniz. Böylece her dolaşımda __iter__ - metodu çağrılacağı için dolaşım baştan başlayacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, stop): - self.stop = stop - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == self.stop: - raise StopIteration() - self.i += 1 - return self.i - 1 - -s = Sample(5) - -for x in s: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - 57. Ders 16/11/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Şimdiye kadar gördüğümüz bütün dolaşılabilir sınıflar da aslında burada belirttiğimiz gibi yazılmıştır. Aşağıda bir - list nesnesinin for döngüsü ve eşdeğer döngü ile dolaşımına örnek veriyoruz. -#------------------------------------------------------------------------------------------------------------------------ - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -for name in names: - print(name) - -print('----------------') - -iterator = names.__iter__() -try: - while True: - name = iterator.__next__() - print(name) -except StopIteration: - pass - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte bir sınıf __init__ metodu ile aldığı parametreleri bir demet biçiminde nesnenin örnek özniteliğinde - saklamaktadır. Daha sonra her __next__ metodunda sırasuyla bunlardan brini vermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Args: - def __init__(self, *args): - self.args = args - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == len(self.args): - raise StopIteration() - self.i += 1 - return self.args[self.i] - -args = Args('ali', 'veli', 123, True) - -for x in args: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - İki değer arasında rastgele n tane değer veren dolaşılabilir bir sınıf örneğini aşağıdaki gibi yazabiliriz. Bu örnekte - sınıfın __next__ metodu random modülündeki randint fonksiyonunu kullanarak rastgele değer üretmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import random - -class RandomIterable: - def __init__(self, beg, end, n): - self.beg = beg - self.end = end - self.n = n - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == self.n: - raise StopIteration() - self.i += 1 - return random.randint(self.beg, self.end) - -a = list(RandomIterable(0, 100, 10)) -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir (iterable) sınıf ile dolaşım (iterator) sınıfı aynı sınıf olmak zorunda değildir. Ancak daha önce de - belirttiğimiz gibi "Python Language Reference" dokümanına göre dolaşım sınıfı aynı zamanda dolaşılabilir bir sınıf gibi - de davranmak zorundadır. Yani dolaşım sınıfının __next__ metodunun yanı sıra __iter__ metodu da bulunmalıdır. Tabii bu - __iter__ metodu tipik olarak self ile geri dönmelidir. - - Aşağıda dolaşılabilir sınıf ile dolaşım sınıflarının farklı sınıflar olduğu bir örnek verilmiştir. Bu örnekte SqrtIterable - sınıfı bir değer almaktadır. Sınıf nesnesi dolaşıldığında 0'dan bu değere kadar (bu değer dahil değil) sayıların karekökleri - elde edilmektedir. Örneğin: - - si = SqrtIterable(10) - - for x in si: - print(x) - - Burada 0'dan 10'a kadar sayıların karekökleri yazdırılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class SqrtIterable: - def __init__(self, n): - self.n = n - - def __iter__(self): - return SqrtIterator(self.n) - -class SqrtIterator: - def __init__(self, n): - self.n = n - self.i = 0 - - def __next__(self): - if self.i == self.n: - raise StopIteration() - self.i += 1 - - return math.sqrt(self.i - 1) - - def __iter__(self): - return self - -si = SqrtIterable(10) - -for x in si: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte orijinal built-in range sınıfının myrange isminde bir benzeri yazılmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class myrange: - def __init__(self, start, stop = None, step = 1): - if stop is None: - self.stop = start - self.start = 0 - else: - self.start = start - self.stop = stop - - self.step = step - - def __iter__(self): - self.i = self.start - return self - - def __next__(self): - if self.i >= self.stop: - raise StopIteration() - self.i += self.step - return self.i - self.step - - def __getitem__(self, index): - if isinstance(index, slice): - start = index.start - stop = index.stop - step = index.step - - if start is None: - start = 0 - elif start < 0: - start = self.__len__() + start - if stop is None: - stop = self.__len__() - elif stop < 0: - stop = self.__len__() + stop - if step is None: - step = self.step - else: - step = step * self.step - - if start < 0: - start = 0 - elif start > self.__len__(): - start = self.__len__() - - if stop < 0: - stop = 0 - elif stop > self.__len__(): - stop = self.__len__() - - start = self.start + start * self.step - stop = self.start + stop * self.step - - return myrange(start, stop, step) - - if isinstance(index, (int, bool)): - if index < 0: - index = self.__len__() + index - if index < 0: - raise IndexError('range object index out of range') - - val = self.start + index * self.step - if val >= self.stop: - raise IndexError('range object index out of range') - return val - - raise TypeError('myrange indices must be integers or slices, not float') - - def __len__(self): - return math.ceil((self.stop - self.start) / self.step) - - def __repr__(self): - return f'myrange({self.start}, {self.stop}, {self.step})' - -mr = myrange(10, 20, 2) - -for x in mr: - print(x) - -print('------------') - -for x in mr: - print(x) - -print('------------') - -print(len(mr)) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii dolaşılabilir bir nesne dolaşılırken dolaşım sırasında nesne bize tek bir değer vermek zorunda değildir. Örneğin - bir demet nesnesi de verebilir. Aşağıdaki örnekte SqrtIterable sınıfında nesne dolaşıldığında ilgili sayı ve onun karekökünden - oluşan bir demet elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class SqrtIterable: - def __init__(self, n): - self.n = n - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == self.n: - raise StopIteration() - self.i += 1 - - return self.i - 1, math.sqrt(self.i - 1) - - -si = SqrtIterable(10) - -for val, root in si: - print(val, root) - -#------------------------------------------------------------------------------------------------------------------------ - Built-in enumerate fonksiyonunu biz de basit bir biçimde aşağıdaki gibi yazabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -class myenumerate: - def __init__(self, iterable, start = 0): - self.iterator = iterable.__iter__() - self.i = start - - def __iter__(self): - return self - - def __next__(self): - self.i += 1 - return self.i - 1, self.iterator.__next__() - -a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] - -me = myenumerate(a, 10) - -for index, name in me: - print(index, name) - -for index, name in me: - print(index, name) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örnekte olduğu gibi bazen manuel bir biçimde __next__ metodunun çağrılması gerekebilir. Örneğin elimzde - dolaşılabilir bir nesne olsun. Biz bu nesneyi for döngüsü ile dolaşmak isteyelim. Ancak ilk elemanı pas geçmek isteyelim. - Ya da elimizde yine bir dolaşılabilir nesne olsun. Biz bu nesnenin elemanlarını bir list nesnesine yerleştirelim. Ancak ilk elemanı - pas geçmek isteyelim. Dolaşım nesnelerinin de dolaşılabilir nesneler gibi davrandığını anımsayınız. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50, 60] - -iterator = a.__iter__() -iterator.__next__() # ilk elemanı pas geçmek için - -for x in iterator: - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin dolaşılabilir bir nesnenin en büyük elemanını bulan bir fonksiyon yazmak isteyelim. Bilindiği bu fonksiyon aslında built-in - biçimde max ismiyle bulunmaktadır. Biz en büyük elemanı bulurken ilk elemanın en büyük olduğunu kabul edip sonraki elemanlarla - karşılaştırırız. Bu tür durumlarda manule __next__ işlemi gerekebilmektedir. Aşağıdaki örnekte böyle bir mymax fonksiyonu yazılmıştır. - (Orijinal max fonksiyonu eğer dolaşılabilir nesnede eleman yoksa ValueError ile raise işlemi yapmaktadr.) -#------------------------------------------------------------------------------------------------------------------------ - -import random - -class RandomIterable: - def __init__(self, beg, end, n): - self.beg = beg - self.end = end - self.n = n - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == self.n: - raise StopIteration() - self.i += 1 - - return random.randint(self.beg, self.end) - -def mymax(iterable): - iterator = iterable.__iter__() - - maxval = iterator.__next__() - - for x in iterator: - if x > maxval: - maxval = x - - return maxval - -ri = RandomIterable(0, 100, 10) -result = mymax(ri) - -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python standart kütüphanesinde iter ve next isimli iki built-in fonksiyon bulunmaktadır. Bu fonksiyonlar aslında parametresiyle - verilen nesne üzerinde __iter__ ve __next__ metotlarını çağırmaktadır. Başka bir deyişle iterable.__iter__() çağırısı ile - iter(iterable) çağrısı eşdeğerdir. Benzer biçimde iterator.__next__() çağrısı ile next(iterator) çağrısı da eşdeğerdir. Yani - bu fonksiyonların aşağıdaki gibi yazılmış olduğunu varsayabilirsiniz: - - def iter(i): - return .__iter__() - - def next(i): - return i.__next__() - - Aslında burada eşğderlik tam olarak böyle değildir. Bu konuda çok küçük ayrıntılar vardır. Biz bunların üzerinde - durmayacağız. -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -iterator = iter(a) # a.__iter__() - -try: - while True: - val = next(iterator) # iterator.__next__() - print(val) -except StopIteration: - pass - -#------------------------------------------------------------------------------------------------------------------------ - Dolaşılabilir nesnelerdeki dolaşım işlemi ileriye doğru yapılmaktadır. Pekiyi geriye doğru dolaşım yapılamaz mı? - İşte Python'da bunun için built-in global reversed isimli bir fonksiyon bulundurulmuştur. Biz bu fonksiyonu daha önce - yüzeysel bir biçimde görmüştük. reversed foksiyonu bizden dolaşılabilir bir nesneyi alır, bize tersten dolaşıma izin - veren yeni bir dolaşım nesnesi verir. Yani kullanımı şmöyledir: - - ri = reversed(iterable) - - Biz bu nesneyi dolaşırsak elemanları tersten elde ederiz. Örneğin: - - a = [10, 20, 30, 40, 50] - - ri = reversed(a) - - for x in ri: - print(x) -#------------------------------------------------------------------------------------------------------------------------ - -a = [10, 20, 30, 40, 50] - -ri = reversed(a) - -for x in ri: - print(x, end=' ') - -print() - -for x in ri: # bu dolaşımdan bir şey elde edilmeyecek - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Ancak her dolaşılabilir nesne reversed fonksiyonuyla tersten dolaşılamamaktadır. Nesnenin reversed fonksiyonuyla tersten - dolaşılabilmesi için o sınıfı yazanların bunu sağlamaları gerekir. İşte aslında reversed fonksiyonu ilgili dolaşılabilir - nesne üzerinde __reversed__ isimli metodu çağırmaktadır. Sınıf yazan programcı da eğer tersten dolaşıma izin verecekse bu - __reversed__ metodunu yazar ve bu metottan tersten dolaşım yapabilecek bir itertor nesnesi ile geri döner. Başka bir deyişle - aslında reversed(iterable) çağrısı ile itreable.__reversed__ çağrısı bazı ayrıntılar dışında eşdeğerdir. reversed built-in - fonksiyonunun şöyle yazılmış olduğunu varsayabilirsiniz: - - def reversed(iterable): - return iterable.__reversed__() - - Bu durumda bizim bir sınıf nesnesini reversed fonksiyonuyla kullanabilmemiz için sınıfın __reversed__ isimli bir metodunun - bulunuyor olması gerekir. - - Aşağıdaki örnekte daha önce yazmış olduğumuz SqrtIterable sınıfını tersten dolaşılabilir hale getiriyoruz. Bu örnekte - sınıfın __reversed__ metodu ReverseSqrtIterator isimli tersten dolaşımı yapan bir sınıf nesnesiyle geri döndürülmüştür. - Böylece tersten dolaşım için artık bu sınıfın __next__ metodu çağrılacaktır. Bu sınıfa dolaşımın son değeir olan n değerinin - aktarıldığına dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class SqrtIterable: - def __init__(self, n): - self.n = n - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == self.n: - raise StopIteration() - self.i += 1 - - return math.sqrt(self.i - 1) - - def __reversed__(self): - return ReverseSqrtIterator(self.n) - -class ReverseSqrtIterator: - def __init__(self, n): - self.n = n - self.i = self.n - 1 - - def __iter__(self): - return self - - def __next__(self): - if self.i < 0: - raise StopIteration() - self.i -= 1 - - return math.sqrt(self.i + 1) - -si = SqrtIterable(10) - -for x in si: - print(x) - -print('-------------') - -for x in reversed(si): - print(x) - -#------------------------------------------------------------------------------------------------------------------------ - Yukarıdaki örnekte olduğu gibi geriye doğru dolaşımı sağlamak için __reversed__ metodunda farklı bir iterator sınıfının - oluşturulması gerekir. Aynı sınıfın hem ileriye doğru hem de geriye doğru dolaşım yapabilmesi bazen mümkün olsa da karmaşık - bir tasarımı gerektirir. Bu nedenle siz de yukarıdaki örnekte olduğu gibi tersten dolaşım yapan Iterator sınıfını ayrı - bir sınıf olarak yazınız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'ın temel veri yapılarını gerçekleştiren sınıflarından hangileri tersten dolaşılabilmektedir? - - list sınıfı, tuple sınıfı ve str sınıfı tersten dolaşılabilir sınıflardır. Örneğin: - - a = [10, 20, 30, 40 , 50] - - for x in reversed(a): - print(x, end=' ') - - print() - - t = (10, 20, 30, 40 , 50) - - for x in reversed(t): - print(x, end=' ') - - print() - - s = 'ankara' - - for c in reversed(s): - print(c, end=' ') - - print() - - set sınıfının tersten dolaşımı mümkün değildir. Zaten set sınıfında dolaşım yapıldığında dolaşımın hangi sırada yapılacağı - hakkında bir belirlemede bulunulmamıştır. Hal böyleyken bu sınıf için tersten dolaşımın da zatren anlamı yoktur. - - dict sınıfı dolaşıldığında anahtarların elde edildiğini anımsayınız. Önceden de belirttiğimiz gibi dict sınıfının dolaşılmasının - hangi sırada yapılacağı Python 3.7'ye kadar belirli değildi. Dolayısıyla set sınıfıyla aynı gerekçeler yüzünden dict sınıfı da - Python 3.7'ye kadar tersten dolaşılamıyordu. Ancak Python 3.7 ve sonrasında artık dict sınıfının dolaşılması sırasında - anahtarların eklenme sırasına göre elde edileceği garanti edilmiştir. Dolayısıyla 3.7 ve sonrasında dict sınıfı da Tersten - dolaşılabilir hale getirilmiştir. - - Daha önce görmüş olduğumuz map gibi, filter gibi enumerate gibi fonksiyonların verdiği sınıf nesneleri de tersten - dolaşılabilir değildir. Yani bu nesneleri biz reversed fonksiyonuyla tersten dolaşamayız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi map isimli built-in fonksiyon bizden bir fonksiyon ve dolaşılabilir nesneyi parametre olarak alıp bize bir dolaşım - nesnesi veriyordu. Biz bu nesneyi dolaştığımızda dolaşılabilir nesnedeki elemanların bu fonksiyona argüman olarak geçilmesi sonucunda - elde edilen değerleri elde ediyorduk. Örneğin: - - def foo(x): - return x ** 2 - - a = [1, 2, 3, 4, 5] - - result = map(foo, a) - - for x in result: - print(x, end= ' ') - - Built-in map fonksiyonu aşağıdaki gibi basit bir biçimde yazılabilir. Bu örnekte biz mymap sınıfının __next__ metodunda - StopIteration uygulamadık. Çünkü zaten aldığımız dolaşılabilir nesne üzerinde __next__ işlemi yaptığımızda StopIteration - oluşacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -class mymap: - def __init__(self, f, iterable): - self.f = f - self.iterator = iter(iterable) - - def __iter__(self): - return self - - def __next__(self): - val = next(self.iterator) - return self.f(val) - -a = [3, 6, 2, 8, 9] - -def square(val): - return val * val - -for x in mymap(square, a): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Python Standart Kütüphanesinde filter isimli built-in bir fonksiyon da vardır. Bu fonksiyon tıpkı map fonksiyonunda - olduğu gibi bir fonksiyonu ve dolaşılabilir nesneyi parametre olarak alır ve bize bir dolaşım nesnesi verir. (Tabii filter - aslında bir sınıf biçiminde yazılmıştır. Ancak anlatımlarda bir fonksiyonmuş gibi ele alınmaktadır.) filter fonksiyonun - verdiği dolaşım nesnesi dolaşıldığında şunlar olmaktadır: filter fonksiyonuna verilen dolaşılabilir nesnenin elemanları - filter fonksiyonuna verilen fonksiyona tek tek sokulur, true değerini veren elemanlar dolaşım sürecinde elde edilir. - Örneğin: - - a = [34, 12, 15, 9, 26, 41] - - def foo(val): - return val % 2 == 0 - - for x in filter(foo, a): - print(x, end=' ') - - Burada a listesi içerisindeki çift elemanlar elde edilmektedir. Örneğin: - - def bar(val): - return val > 20 - - for x in filter(foo, a): - print(x, end=' ') - - Burada a listesindeki 20'den büyük elemanlar elde edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -a = [34, 12, 15, 9, 26, 41] - -def foo(val): - return val % 2 == 0 - -for x in filter(foo, a): - print(x, end=' ') - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'sibel'] - -def f(s): - return 'a' in s - -for name in filter(f, names): - print(name, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - filter sınıfını basit bir biçimde aşağıdaki gibi yazabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -class myfilter: - def __init__(self, f, iterable): - self.f = f - self.iterator = iter(iterable) - - def __iter__(self): - return self - - def __next__(self): - while True: - val = next(self.iterator) - if f(val): - return val - -names = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'sibel'] - -def f(s): - return 'a' in s - -for name in myfilter(f, names): - print(name, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi dolaşılabilir nesnelere neden gereksinim duyarız? Örneğin 0'dan n'e kadar sayıların kareköklerini elde eden ve bunu - bize bir liste olarak veren (neticede liste de dolaşılabilir bir nesnedir) fonksiyon ile aynı işlem için oluşturulan dolaşılabilir - sınıf arasında ne fark vardır? Biz bu işlemi bir içlem içeren bir fonksiyonla da aşağıdaki gibi yapabilirdik: - - def get_sqrts(n): - return [math.sqrt(i) for i in range(n)] - - a = get_sqrts(100000) - for x in a: - print(x, end=' ') - - İşte sonucu bir liste biçiminde veren fonksiyonlar sonucu baştan oluşturup bir listeye yerleştirmek zroundadırlar. Eğer bizim - amacımız bu değerler üzerinde işlem yapmaksa bütün değerlerin işin başında listeye yerleştirilmesine ve onların bellekte yer - kaplamasına gerek yoktur. Dolaşılabilir nesneler değerleri dolaşım sırasında vermektedir. Böylece değerlerin baştan liste gibi - bir nesnede depolanmasına gerek kalmamaktadır. Bu tür durumlarda değerleirn bir listeyle geri döndürülmesi aynı zamanda bu listenin - baştan oluşturulması sırasında uzun beklemelere neden olabilmektedir. Halbuki değerler talep edildiğinde verildiği zaman bu uzun - bekleme parçalara bölünmüş olacaktır. Tabii bazen değerlerin bir liste biçiminde elde edilmesi de istenebilir. Programcı bu listenin - elemanları üzerinde işlemler isteyebilir. Bu tür durumlarda ilgili dolaşılabilir nesneden liste oluşturulabilir. - - Örneğin diskimizdeki tüm dosyalar üzerinde işlemler yapmak isteyelim. Yani kökten başlayarak her dizine tek tek geçip tüm - dosyaları elde etmek isteyelim. Bu çok yorucu ve uzun zaman alan bir işlemdir. Diskimizde binlerce dosya bulunuyor olabilir. - Bunlarının hepsinin elde edilmesi ve bize bir liste olarak verilmesi oldukça zordur. Oysa biz istediğimzde bize kalınan yerden - devam edilerek dosyalar verilse bu işlem çok daha uygun olur. İşte dolaşılabilir nesneler bir işi adım adım yapmak için - tercih edilmektedir. Eğer dizin ağacını dolaşan bir fonksiyonu dolaşılabilir bir nesne yoluyla gerçekleştirmezsek hem - bizim tüm dosyaları bir listede saklamamız gerekir hem de bu işlem çok uzun zaman alabilir. Gerçekten de os modülü - içerisindeki walk fonksiyonu dolaşılabilir bir nesne vermektedir. -#------------------------------------------------------------------------------------------------------------------------ - -import os - -for root, dirs, files in os.walk('f:\\'): - print(root) - -#------------------------------------------------------------------------------------------------------------------------ - 58. Ders 21/11/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Üretici fonksiyonlara (generators) özellikle yorumlayıcılarla çalışılan dillerde karşılaşılmaktadır. Ancak derleyici - temelli bazı dillerede de özellikle son yıllarda üretici fonksiyonlar özelliği eklenmiştir. - - Python'da bir fonksiyonda en az bir tane yield isimli deyim kullanılırsa artık o fonksiyona "üretici fonksiyon (geenerator)" - denilmektedir. Örneğin: - - def foo(): - print('one') - yield 1 - print('two') - yield 2 - print('three') - yield 3 - print('ends') - - Burada foo fonksiyonu bir üretici fonksiyondur. - - yield deyiminin genel biçimi şöyledir: - - yield [ifade] - - Bir üretici fonksiyonun türü normal bir fonksiyondur. Ancak üretici fonksiyon çağrıldığında bir "üretici nesne (generator object)" - elde edilmektedir. Bu üretici nesneye Python Language Reference dokümanlarında "generator iterator" da denilmektedir. Örneğin: - - g = foo() - - Burada g bir üretici nesnedir. Yani bir üretici fonksiyon çağrıldığında fonksiyonun kodları çalıştırılmamaktadır. Bu çağrı - ifadesi ismine "üretici nesne (generator object)" denilen bir nesne vermektedir. Üretici fonksiyonlar normal bir fonksiyon - gibidir. Üretici nesneler ise "generator" isimli bir sınıf türünden nesnelerdir. -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('one') - yield 1 - print('two') - yield 2 - print('three') - yield 3 - print('ends') - -print(type(foo)) # - -g = foo() - -print(type(g)) # - -#------------------------------------------------------------------------------------------------------------------------ - Üretici nesneler birer dolaşım (iterator) nesnesidir dolayısıyla da dolaşılabilir nesnelerdir. Yani biz bir üretici nesneyi - for döngüsüyle dolaşabiliriz. İstersek next metodunu sürekli çağırarak da (__iter__ metodunu çağırmadan) da dolaşabiliriz. - Bir üretici nesne dolaşılmak istendiğinde (next yapıldığında) üretici fonksiyonun kodları çalıştırılır ve yield deyiminin - olduğu yerde çalışma geçici olarak durur. yield deyiminin yanındaki ifadenin değeri next işleminden elde edilen değer olur. - Yani biz bir üretici nesneyi dolaşırsak aslında yield deyimlerinde belirtilen ifadelerin değerlerini elde ederiz. Üretici nesneler - dolaşılırken yield deyimi fonksiyonu durdurmaktadır. Yeni bir yinelemede akış durdurulmanın yapıldığı yield deyiminden itibaren - devam eder ve sonraki yield deyimine kadar çalışır, sonraki yield deyiminde yeniden durdurulur. yield deyiminin fonksiyonu - sonlandırmadığına geçici olarak durdurduğuna dikkat ediniz. Üretici fonksiyon bittiğinde (akış üretici fonksiyonun sonuna geldiğinde - ya da üretici fonksiyonda return kullanıldığında) StopIteration oluşturulmaktadır. Yani üretici nesnenin dolaşımı üretici - fonksiyon bittiğinde sona ermektedir. Örneğin aşağıdaki gibi bir üretici fonksiyon olsun: - - def foo(): - print('one') - yield 1 - print('two') - yield 2 - print('three') - yield 3 - print('ends') - - Biz şimdi bir üretici nesne elde edelim: - - g = foo() - - Bu üretici nesne bir dolaşım nesnesi dolayısıyla da dolaşılabilir bir nesnedir. Şimdi onu bir adım dolaşalım: - - val = next(g) - print(f'next return value: {val}') - - Burada foo fonksiyonu ilk yield görülene kadar çalıştırılıp yield deyiminde durdurulacaktır ve next işleminden yield deyiminin - yanındaki değer elde edilecektir. Dolayısıyla ekranda şunlar görünecektir: - - one - next return value: 1 - - Şimdi next fonksiyonu ile bir adım daha dolaşım uygulayalım: - - val = next(g) - print(f'next return value: {val}') - - Bu durumda akış durdurulan yield deyiminden devam eder ve ilk yield deyimi görüldüğünden yeniden durdurulur. Ekranda şunlar görünecektir: - - two - next return value: 2 - - Şimdi bir adım daha dolaşalım. Ekranda şunları göreceksiniz: - - three - next return value: 3 - - Akış yield 3 deyiminde durdurulmuştur. Şimdi bir adım daha dolaşmak isteyelim. Artık fonksiyon gerçekten bitmektedir. İşte bu durumda - StopIteration oluşacaktır. - - Tabii biz üretici nesnesyi aşağıdaki gibi for dmngüsüyle de dolaşabilirdik: - - g = foo() - - for x in g: - print(f'x: {x}') - - Bu dolaşımda akış her yield deyiminde durduğunda o yield deyiminin yanındaki ifadenin değeri x'e atanacaktır. Ekranda şunları göreceksiniz: - - one - x: 1 - two - x: 2 - three - x: 3 - ends -#------------------------------------------------------------------------------------------------------------------------ - -def foo(): - print('one') - yield 1 - print('two') - yield 2 - print('three') - yield 3 - print('ends') - -g = foo() - -for x in g: - print(f'x: {x}') - -#------------------------------------------------------------------------------------------------------------------------ - Üretici fonksiyonlar sayesinde biz dolaşılabilir nesneleri çok daha kolay oluşturabiliriz. Örneğin: - - def sqrt_iterable(n): - for i in range(n): - yield math.sqrt(i) - - for x in sqrt_iterable(10): - print(x) - - Burada üretici nesne dolaşıldığında her yinelemede 0'dan itibaren n değerine kadar (n değeri dahil değil) sayıların - karekökleri elde edilmektedir. Biz bu işlemi yapan dolaşılabilir sınıfı daha önce yazmıştık. Aşağıda aynı işlemi yapan - dolaşılabilir sınıf ile üretici fonksiyon bir arada verilmiştir. İkisini karşılaştırarak yazım üretici fonksiyonların - yazım kolaylığına dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class SqrtIterable: - def __init__(self, n): - self.n = n - - def __iter__(self): - self.i = 0 - return self - - def __next__(self): - if self.i == self.n: - raise StopIteration - self.i += 1 - - return math.sqrt(self.i - 1) - -for x in SqrtIterable(10): - print(x) - -print('------------------') - -def sqrt_iterable(n): - for i in range(n): - yield math.sqrt(i) - -for x in sqrt_iterable(10): - print(x) - -print('------------------') - -a = list(sqrt_iterable(10)) -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Biz daha önce range fonksiyonunu dolaşılabilir bir sınıf olarak yazmıştık. Standart kütüphanedeki range fonksiyonu da - aslında dolaşılabilir bir sınıftır. Ancak istersek bu range işlemini yapan dolaşılabilir nesneyi bir üretici fonksiyon - kullanarak da yazabiliriz. Tabii range fonksiyonunu üretici fonksiyon olarak yazdığımızda artık üretici nesneyle [] - operatörünü kullanamayız. Aşağıda range fonksiyonunun temel işlevini yapan üretici bir fonksiyon verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -def myrange(start, stop = None, step = 1): - if stop == None: - stop = start - start = 0 - - i = start - while i < stop: - yield i - i += step - - -for x in myrange(10): - print(x, end=' ') - -print() - -for x in myrange(10, 20, 2): - print(x, end=' ') - -print() - -for x in myrange(10, 20): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - 2'den itibaren parametresiyle belirtilen sayıya kadarki asal sayıları veren üretici fonksiyon aşağıdaki gibi yazılabilir. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -def get_primes(n): - def isprime(val): - if val % 2 == 0: - return val == 2 - sqrt_val = int(math.sqrt(val)) - for i in range(3, sqrt_val + 1, 2): - if val % i == 0: - return False - return True - - if n < 2: - raise ValueError('argument must greater or equal 2') - i = 2 - for i in range(2, n): - if isprime(i): - yield i - -for x in get_primes(100): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi dolaşılabilir bir nesne elde oluşturabilmek için dolaşılabilir sınıflar mı yoksa üretici fonksiyonlar mı tercih - edilmelidir? İşte bu tercih içinde bulunulan duruma göre değişebilmektedir: - - - Dolaşılabilir sınıflar her __next__ metodunda bize yeni değeri verirler. Oysa üretici fonksiyonlar her yield işleminde - bize değer verirler. Üretici fonksiyonların durudulması ve o noktadan çalışma devam ettirilmesi daha uzun zaman almaktadır. - Oysa dolaşılabilir sınıflarda değerler __next__ çağrısı ile daha hızlı edilmektedir. Yani dolaşılabilir sınıflar üretici - fonksiyonlara göre daha hızlı olma eğilimindedir. - - - Üretici fonksiyonları yazmak çok daha kolaydır. Çünkü herhangi bir fonksiyon hemen üretici fonksiyon haline getirilebilir. - Oysa dolaşılabilir sınıfları yazmak daha zordur. Dolaşılabilir sınıflarda akış durdurulmadığı için her __next__ metodunda - durumsal bilginin saklanarak o noktadan devam ettirilmesi gerekmektedir. Bu da daha fazla çaba anlamına gelir. Üretici - fonksiyonlar çok daha pratik yazılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Daha önce bizbuilt-in enumerate fonksiyonunu dolaşılabilir bir sınıf biçiminde yazmıştık. Şimdi de bu fonksiyonu - üretici fonksiyon olarak yazalım: - - def myenumerate(iterable, start = 0): - i = start - for val in iterable: - yield i, val - i += 1 - - Aşağıda daha önce yazmış olduğumuz sınıf gerçekleştirimi ile üretici fonksiyon gerçekleştirimini birlikte veriyoruz. -#------------------------------------------------------------------------------------------------------------------------ - -class myenumerate1: - def __init__(self, iterable, start = 0): - self.iterator = iterable.__iter__() - self.i = start - - def __iter__(self): - return self - - def __next__(self): - self.i += 1 - return self.i - 1, self.iterator.__next__() - -def myenumerate2(iterable, start = 0): - i = start - for val in iterable: - yield i, val - i += 1 - -a = [10, 20, 30, 40, 50] - -for index, val in myenumerate1(a): - print(index, val) - -print('--------------------') - -for index, val in myenumerate2(a): - print(index, val) - -#------------------------------------------------------------------------------------------------------------------------ - Daha önce biz built-in map fonksiyonunu dolaşılabilir bir sınıf biçiminde yazmıştık. Gerçekten de standrat kütüphanedeki - map fonksiyonu aslında bir sınıf biçiminde yazılmıştır. Şimdi de bu map fonksiyonunu bir üretici fonksiyon biçiminde - yazalım. Bu fonksiyonun ne kadar kolay yazıldığına dikkat ediniz: - - def mymap(f, iterable): - for x in iterable: - yield f(x) - -#------------------------------------------------------------------------------------------------------------------------ - -a = [3, 6, 2, 8, 4, 9] - -def square(val): - return val * val - -def mymap(f, iterable): - for x in iterable: - yield f(x) - -for x in map(square, a): - print(x, end=' ') - -print() - -for x in mymap(square, a): - print(x, end=' ') - -#------------------------------------------------------------------------------------------------------------------------ - Üretici nesneler birer dolaşım nesnesi belirtiyordu. Dolayısıyla o nesnelerle __next__ metodunu çağırsak ya da eşdeğer - olarak next fonksiyonuna o nesneleri versek akış ilk yield deyimine kadar çalışıyor ve yield değerini bize veriyordu. - Ancak bu işlemin tersi de mümkündür. Yani yield deyimi de bir değer üretebilmektedir. Sonraki yinelemeye geçildiğinde - akış yield eyiminden devam ederken yield deyimi ürettiği değeri vermektedir. Default durumda next işlemleri sırasında - yield deyimi None değer üretir. Örneğin: - - def foo(): - print('one') - val = yield 1 - print(f'yield returns {val}') - - print('two') - val = yield 2 - print(f'yield returns {val}') - - print('three') - val = yield 3 - print(f'yield returns {val}') - - g = foo() - - try: - val = g.__next__() - print(f'{val} gets from yield') - - print('----------------') - - val = g.__next__() - print(f'{val} gets from yield') - - print('----------------') - - val = g.__next__() - print(f'{val} gets from yield') - - print('----------------') - - val = g.__next__() - print(f'{val} gets from yield') - - except StopIteration: - pass - - Örneğimizde akış yield deyiminde durdurulduktan sonra devam ettirilirken yield deyiminin ürettiği değer val değişkenine - atanmıştır. Hem yield deyiminin ürettiği değer hem de yield deyiminden elde edilen değer ekrana yazdırılmıştır. - Örnek programdan şöyle bir çıktı elde edilecektir: - - one - 1 gets from yield - ---------------- - yield returns None - two - 2 gets from yield - ---------------- - yield returns None - three - 3 gets from yield - ---------------- - yield returns None - - İşte biz üretici nesne ile send metodunu çağırırsak aslında hem akışı durduğu yerden devam ettirmiş oluruz hem de yield işleminden - istediğimiz bir değerin elde edilmesini sağlarız. Ancak send metodu yield'de durmuş akış için kullanılır. Dolayısıyla üretici - nesne üzerinde dolaşım send ile başlatılmaz. Ancak devam ettirilebilir. Yukarıdaki örneği send metodunu kullanarak aşağıdaki - gibi değiştirelim: - - def foo(): - print('one') - val = yield 1 - print(f'yield returns {val}') - - print('two') - val = yield 2 - print(f'yield returns {val}') - - print('three') - val = yield 3 - print(f'yield returns {val}') - - g = foo() - - try: - val = g.__next__() - print(f'{val} gets from yield') - - print('----------------') - - val = g.send(100) - print(f'{val} gets from yield') - - print('----------------') - - val = g.send(200) - print(f'{val} gets from yield') - - print('----------------') - - val = g.send(300) - print(f'{val} gets from yield') - - except StopIteration: - pass - - Artık ekrana şunlar çıkacaktır: - - one - 1 gets from yield - ---------------- - yield returns 100 - two - 2 gets from yield - ---------------- - yield returns 200 - three - 3 gets from yield - ---------------- - yield returns 300 - - send metodu üretici nesnenin ilişkin olduğu "generator" isimli sınıfın bir metodudur. send metodunun kullanımına dikkat ediniz: - - g.send(ifade) - - metoda verilen argüman akışın yield deyiminden devam ettirilmesi sırasında yield deyiminin üreteceği değeri belirtmektedir. - - Yukarıda da belirttiğimiz gibi her send işlemi yield'de duran akışı devam ettirmektedir. send işleminin next işleminden farkı - akış devam ettrilirken yield deyiminin bir değer üretmesini sağlamasıdır. Yani biz yield'de duran akışı devam ettirirken aynı - zamanda yield'den bir değer de elde edilmesini istiyorsak bu durumda send uygulamalıyız. - - Üretici fonksiyonu next ile kaldığı yerden devam ettirirsek yield None üretmektedir. Başka bir deyişle: - - g.__next__() ya da next(g) işlemi ile aşağıdaki işlem eşdeğerdir: - - g.send(None) - - Üetici fonksiyon nesnesinin ilişkimn olduğu sınıfta (generator sınıfı) bir __send__ metodu yoktur. Bu metot send ismindedir. - Anımsanacağı gibi next isimli built-in fonksiyon aslında __next__ metodunu çağırmaktadır. Yani şöyle yazılmıştır: - - def next(iterator): - return iterator__next__() - - Ancak built-in bir send fonksiyonu yoktur. Yani send üretici nesneyle kullanılmalıdır. - - Üretici fonksiyonlarda yield deyiminin değer üretme özelliği Python'a sonradan eklenmiştir. Bu özelliğin kullanım - alanı oldukça sınırlıdır. Bazen durmuş durumda olan üretici fonksiyonları kaldığı yerden devam ettirirken üretici fonksiyona - değer iletmek gerekebilmektedir. İşte bu tür seyrek durumlarda send metodu kullanılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Anımsanacağı gibi liste içlemleri, küme içlemleri ve sözlük içlemleri vardı ancak demet içlemleri biçiminde bir içlem - yoktu. Bunun ana nedeni demetlerin değiştirilebilir nesneler olmamasıdır. Ancak demet içlemi sentaksı Python'da - bulunmaktadır fakat başka bir anlama gelmektedir. Demet içlemi sentaksına Python'da "üretici ifadeler (generator expresssions)" - denilmektedir. Örneğin: - - a = [1, 2, 3, 4, 5] - - b = [x * x for x in a] # listte içlemi - print(b) - - g = (x * x for x in a) # üretici ifade (generator expression) - - Bir üretici ifade aslında üretici bir fonksiyonun basit bir yazım biçimidir. Üretici ifade bize aslında bir üretici nesne - vermektedir. Yani: - - g = (ifade for değişken in dolaşılabilir_nesne) - - İşleminin eşdeğeri şöyedir: - - def some_generator_name(): - for x in dolaşılabilir_nesne: - yield ifade - - g = some_generator_name() - - Üretici ifadelerin doğrudan üretici nesne verdiğine dikkat ediniz. Halbuki üretici fonksiyonlardan üretici nesne elde - edebilmek için onların çağrılması gerekmektedir. Örneğin: - - >>> g = (i * i for i in range(10)) - >>> type(g) - - >>> a = list(g) - >>> a - [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] - - Örneğin aşağıdaki gibi bir üretici ifade olsun: - - g = (i * i for i in range(10)) - - Bunun üretici fonksiyon karşılığı şöyle oluşturulabilir: - - def some_name(): - for i in range(10): - yield i * i - - g = some_name() - -#------------------------------------------------------------------------------------------------------------------------ - -a = [1, 2, 3, 4, 5] - -g = (x * x for x in a) # üretici ifade (generator expression) - -for x in g: - print(x, end=' ') -print() - -#------------------------------------------------------------------------------------------------------------------------ - 59. Ders 23/11/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi üretici ifade de aslında üretici fonksiyon belirttiğine göre arada ne fark vardır? Yani örneğin: - - g = (i * i for i in range(100)) - - gibi bir üretici ifade ile aşağdıaki arasında ne farklılık vardır? - - def some_name(): - for i in range(100): - yield i * i - - g = some_name() - - İşte üretici ifadeler "ifade (expression)" tanımına uymaktadır. Dolayısıyla başka ifadelerin içerisinde kullanılabilirler. - Örneğin: - - import statistics - - result = statistics.mean((i * i for i in range(10))) - - print(result) - - Biz burada üetici ifadeyi doğrudan fonksiyon çağırma işleminde argüman olarak kullandık. Halbuki aynı işlemi üretici - fonksiyonlarla yapmak isteseydik daha zor olacaktı: - - def some_name(): - for i in range(10): - yield i * i - - result = statistics.mean(some_name()) - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Üretici ifadeler eğer bir fonksiyon ya da metot çağrılırken argüman olarak kullanılıyorsa ve çağrım işleminde başka - da bir argüman bulundurulmuyorsa üretici ifadelerdeki dıştaki parantezler hiç kullanılmayabilir. Örneğin: - - import statistics - - result = statistics.mean((i * i for i in range(10))) - - print(result) - - Yukarıdaki çağrımda dıştaki parantezler hiç kullanılmayabilirdi: - - result = statistics.mean(i * i for i in range(10)) - - Ancak eğer çağrımda başka bir argüman da kullanılıyorsa bu durumda dıştaki parantezler ihmal edilemez. Örneğin: - - for index, val in enumerate(i * i for i in range(10), 100): # geçersiz! - print(f'{index} => {val}') - - Burada enumerate fonksiyonu iki argümanla çağrıldığı için üretici ifadedeki dış parantezler ihmal edilemez. Tabii - enumerate fonksiyonu tek argümanla çağrılsaydı dıştaki parantezler ihmal edilebilirdi: - - for index, val in enumerate(i * i for i in range(10)): # geçerli! - print(f'{index} => {val}') - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi üretici ifadeler bize ne fayda sağlamaktadır? İşte üretici ifadeler aslında dolaşılabilir nesneleri oluşturmanın - en kolay yollarından biridir. Daha önce de belirttiğimiz gibi dolaşılabilir nesneler içlemlerle de benzer sentaks ile - oluşturulabilmektedir. Ancak içlemler liste, küme ya da sözlük nesnesi yaratmaktadır. Bazı işlemlerde elemanlar bu - nesnelerin içerisinde gereksiz bir biçimde yer kaplayabilmektedir. Örneğin: - - result = statistics.mean([i * i for i in range(1000000)]) - - Burada ortalama elde edildikten sonra oluşturulan buı liste nesnesi zaten yok edilmektedir. Halbuki bunun boşuna 1000000 - elemandan oluşan bir liste yaratmış olduk. Aynı işlemi üretici ifadelerle daha etkin biçimde gerçekleştirebiliriz: - - result = statistics.mean((i * i for i in range(1000000))) - - Artık burada 1000000 eleman gereksiz bir biçimde yer kaplamamaktadır. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aşağıdaki örnekte print fonksiyonuna dolaşılabilir bir üretici ifade *'lı bir biçimde verilmiştir. Bu durumda bu üretici - ifade dolaşılıp elde edilen değerler ekrana yazdırılacaktır. -#------------------------------------------------------------------------------------------------------------------------ - -print(*(i for i in range(100) if i % 7 == 0)) - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyonun bir ifade içerisinde tanımlanarak o ifade içerisinde kullanılmasına programlama dillerinde "lambda - ifadeleri (lambda expressions)" denilmektedir. Aslında lambda ifadeleri eskiden fonksiyonel dillerde karşımıza çıkan - yapılardı. Ancak son yıllarda pek çok dile fonksiyonel özellikler katmak için lambda ifadeleri sokulmuştur. C++, C# - ve Java gibi dillerde lambda ifadeleri daha geniş bir sentaks yapısına dolayısıyla kullanım alanına sahiptir. Python'daki - lambda ifadeleri bu dillerdekilere gör oldukça kısıtlı ve minimalist bir yapıdadır. Lambda ifadelerinin genel biçimi şöyledir: - - lambda [parametre_listesi]: - - Örneğin: - - f = lambda a: a * a - result = f(10) - print(result) - - lambda ifadeleri aslında bir fonksiyon oluşturmaktadır. lambda anahtar sözcüğünün yanındaki değişken listesi fonksiyonun - parametreleridir. Burada fonksiyon tanımlamasında olduğu gibi (...) atomları kullanılmaz. İki nokta üst üste atomunun yanında - bir ifadenin olması gerekir. Bu ifade de aslında fonksiyonun geri dönüş değerini belirtir. Burada return anahtar sözcüğü - kullanılmaz. Zaten bu ifade return ifadesi anlamına gelmektedir. lambda ifadelerinden elde edilen değer bir fonksiyon nesnesinin - adresidir. Dolayısıyla biz onu bir değişkene atarsak o değişken fonksiyon nesnesini gösterecektir. Bu da tamamen fonksiyon - ile aynı etkinin yaratılması anlamına gelir. Örneğin: - - >>> f = lambda a: a * a - >>> type(f) - - >>> f(10) - 100 - >>> f(5) - 25 - - Bu durumda: - - f = lambda a: a * a - - işleminin eşdeğeri şöyledir: - - def f(a): - return a * a - - - Örneğin: - - f = lambda a, b: a + b - - Bu işlemin eşdeğeri de şöyledir: - - def f(a, b): - return a + b - - Tabii lambda ifadeleri bir deyim dğildir, ifadedir. Dolayısıyla başka ifadelerin içerisinde kullanılabilir. Örneğin: - - for x in map(lambda val: val * val, range(100)): - print(x, end=' ') - - Burada biz map fonksiyonuna bir fonksiyon oluşturup onu vermiş olduk. Bu işlemin eşdeğeri şöyledir: - - def square(val): - return val * val - - for x in map(square, range(100)): - print(x, end=' ') - - Örneğin: - - a = [12, 23, 14, 81, 64, 72, 17, 48] - - for x in filter(lambda val: val % 2 == 0, a): - print(x, end=' ') - - Burada biz listenin koşulu sağlayan elemanlarını elde etmiş olduk. - - Lambda ifadelerindeki ':' atomundan sonraki ifadede içinde bulunulan fonksiyonun yerel değişkenleri ve global değişkenler - kullanılabilir. Yani lambda ifadeleri bir fonksiyonun içerisinde kullanılmışsa adeta bir iç fonksiyon (nested funstion) gibi - işlem görmektedir. Örneğin: - - def foo(val): - for x in map(lambda a: a * val, [1, 2, 3, 4, 5]): - print(x, end=' ') - print() - - foo(5) - foo(10) - - Bu durumda yukarıdaki kodun eşdeğeri şöyledir: - - def foo(val): - def f(a): - return a * val - for x in map(f, [1, 2, 3, 4, 5]): - print(x, end=' ') - print() - - foo(5) - foo(10) - - lambda ifadelerinin parametresi olmak zorunda değildir. Ancak genellikle lambda ifadeleri parametreli olur. Örneğin: - - f = lambda: 100 - result = f() - print(result) - - lambda ifadelerinin birden fazla parametresi de olabilir. Örneğin: - - f = lambda a, b: a + b - result = f(10, 20) - print(result) # 30 - - Lambda ifadelerinde ':' atomunun sağında bir deyim olamaz. (Yani if, for return gibi deyimler kullanılamaz.) ':' atomunun - sağında yalnızca bir ifade olabilir. Tabii koşul operatörü bir operatör olduğu için lambda ifadelerinde kullanılabilir. Örneğin: - - f = lambda a, b: a if a > b else b - - result = f(10, 20) - print(result) # 20 - - Buradaki if deyim değildir bir operatör görevindedir. Örneğin: - - a = [3, 6, 1, 8, 9, 2, 4, 7] - - for x in map(lambda val: 1 if val % 2 == 0 else 0, a): - print(x, end=' ') - - Burada listenin çift elemanları için 1 değeri tek elemanları için 0 değeri elde edilmektedir. - - Lambda ifadeleri fonksiyon belirttiğine göre hiç ara değişken kullanılmadan fonksiyon çağırma operatör ile çağrılabilir. Ancak - tabii dıştan bir parantez zorunludur. Örneğin: - - result = (lambda a, b: a if a > b else b)(10, 20) - - print(result) # 20 - - Mademki aslında metotlar birer sınıf değişkenidir. O halde bir lambda ifadesi ile bir metot da oluşturabiliriz. Örneğin: - - class Sample: - def __init__(self, a): - self.a = a - - get_a = lambda self: self.a - - s = Sample(10) - - result = s.get_a() - print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python'daki lambda ifadeleri diğer dillerdeki lambda ifadeleriyle kıyaslandığında oldukça basit bir yapıdadır. Diğer - dillerin bazılarında lambda ifadelerinde deyimler kullanılabilmektedir. Python'daki lambda ifadelerinde yalnızca - ifadelerin kullanılıyor olması lambda ifadelerinin gücünü düşürebilmektedir. Ancak Python'ın sentaktik yapısı dikkate - alındığında dile ayrıntılı bir lambda ifadesiyerleştirmenin zoruluğu da anlaşılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - 60. Ders 28/11/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların eleman erişiminde kullanılan __getattr__ isimli bir metodu vardır. Bir sınıf türünden değişkenle o sınıfın - olmayan bir örnek özniteliğine ya da bir metoduna erişildiğinde yorumlayıcı tarafından sınıfın __getattr__ metodu - çağrılmaktadır. __getattr__ metodunun self parametresi dışında erişilmek istenen elemanın ismini alan bir name parametresi - (ismi name olmak zorunda değildir) de vardır. __getattr__ metodunun geri dönüş değeri erişim sonucunda elde edilen değerdir. - Örneğin: - - class Sample: - def __getattr__(self, name): - print(name) - return 0 - - s = Sample() - x = s.val - print(x) # 0 - - Burada s.val ifadesinde sınıfın olmayan bir örnek özniteliğine erişilmek istenmiştir. Bu durumda sınıfın __getattr__ metodu - çağrılacak ve name parametresine erişimde kullanılan gerçekte olmayan "val" ismi geçirilecektir. Bu erişimden elde edilen - değer __getattrr__ metodundan döndürülen değerdir. Eğer nesne ile zaten var olan bir elemana erişiyorsa bu durumda - __getattr__ çağrılmamaktadır. Örneğin: - - class Sample: - def __init__(self): - self.val = 10 - def __getattr__(self, name): - print(name) - return 0 - - s = Sample() - x = s.val - print(x) # 10 - - Burada s.val erişiminde val zaten vardır. Bu durumda __getattr__ çağrılmayacaktır. - - Normal olarak eğer sınıfta __getattr__ metodu yoksa biz sınıfın olmayan bir elemanına erişmeye çalıştığımızda AttributeError - isimli exception oluşur. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self): - self.a = 10 - - def __getattr__(self, name): - print(f'__getattr__ called: {name}') - return 0 - -s = Sample() - -print(s.x) -print(s.y) - -result = s.x + s.y -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Tabii nesne ile olmayan bir örnek özniteliğine değer atama işlemi zaten o örnek özniteliğinin yaratılmasına yol açtığı - için bu durumda __getattr__ metodu çağrılmamaktadır. Örneğin: - - class Sample: - def __init__(self): - self.a = 10 - - def __getattr__(self, name): - print(f'__getattr__ called: {name}') - return 0 - - s = Sample() - - s.x = 100 # burada __getattr__ çağrılmayacak - print(s.x) # burada da __getattr__ çağrılmayacak, çünü artık x örnek özniteliği var - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfın __getattr__ metodu "property" kavramını oluşturmak amacıyla ve başka birtakım amaçlarla kullanılabilmektedir. - Nesne yönelimli programlama dillerinin bir bölümünde var olan "property" kavramı metotların adeta birer örnek özniteliği - gibi kullanılmasını sağlayan bir mekanizmadır. Yani bu kavram sayesinde programcı nesnenin bir örnek özniteliğine eriştiğinde - aslında arka planda bir metot çağrılmaktadır. C++ ve Java gibi dillerde property kavramı yoktur. Fakat örneğin C#'ta vardır. - Python'da property'ler __getattr__ metodu yoluyla ya da property isimli dekoratör yoluyla oluşturulabilmektedir. - Örneğin Circle isimli bir sınıf yazmak isteyelim. Sınıfın area isimli bir örnek özniteliğinin olmasına gerek yoktur. - Çünkü area aslında yarıçaptan hareketle hesaplanabilmektedir. İşte biz __getattr__ yoluyla sanki area isimli örnek bir - özniteiği sınıfta varmış gibi bir durum oluşturabiliriz: - - class Circle: - def __init__(self, radius, x, y): - self.radius = radius - self.x = x - self.y = y - - def __getattr__(self, name): - if name == 'area': - return 3.14159 * self.radius ** 2 - raise AttributeError(f"Circle object has no attribute '{name}'") - - def __repr__(self): - return f'radius: {self.radius}, center: ({self.x},{self.y})' - - c = Circle(10, 3, 4) - print(c) - - print(c.area) - - Bu örnekte nesne yoluyla aslında olmayan area örnek özniteliğine erişildiğinde __getattr__ çağrılacak ve dairenin alanı - o anda hesaplanarak sanki bu area örnek özniteliğinin değeriymiş gibi verilecektir. Örneğimizde area ismi dışında olmayan - başka bir örnek özniteliği kullanılırsa AttributeError exception'ının oluşturulduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class Circle: - def __init__(self, radius, x, y): - self.radius = radius - self.x = x - self.y = y - - def __getattr__(self, name): - if name == 'area': - return 3.mat.pi * self.radius ** 2 - raise AttributeError(f"Circle object has no attribute '{name}'") - - def __repr__(self): - return f'radius: {self.radius}, center: ({self.x},{self.y})' - -c = Circle(10, 3, 4) -print(c) - -print(c.area) -print(c.xxxxxxxx) # exception oluşacak - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi property kavramına neden gereksinim duyulmaktadır? Bunu basit bir örnekle açıklayabiliriz. Biz __getattr__ metodu - ile property oluşturmadan aynı işlemi sınıfa area isimli örnek özniteliğini yerleştirerek yaptığımızı düşünelim: - - import math - - class Circle: - def __init__(self, radius, x, y): - self.radius = radius - self.x = x - self.y = y - self.area = math.pi * radius ** 2 - - def __repr__(self): - return f'radius: {self.radius}, center: ({self.x},{self.y})' - - c = Circle(10, 3, 4) - print(c) - print(c.area) - - Burada sınıfı kullanan programcı dışarıdan radius örnek özniteliğini değiştirirse area bilgisi değiştirilmeyecektir. - Örneğin: - - c = Circle(10, 3, 4) - print(c.area) - c.radius = 20 - print(c.area) - - Halbuki biz area örnek özniteliğine erişildiğinde bir metodun çağrılmasını saplarsak bu metot o andaki radius değerini - kullanacağı için sorun olmayacaktır. İşte sınıfın birbirleriyle ilişkili veri elemanları varsa onların arasındaki koordinasyon - property'lerle sağlanabilmektedir. C++ ve Java gibi dillerde property kavramı olmadığı için bu tür işlemler getter/setter - metotlarıyla yapılmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfta olmayan elemanların kullanılması teması property dışında başka yerlerde de karşımıza çıkabilmektedir. - Örneğin biz __getattr__ sayesinde sınıf nesnesi yaratırken verdiğimiz isimli argümanları sanki onlar birer örnek - özniteliğiymiş gibi kullanabiliriz. Aşağıdaki örnekte sınıfın __init__ metodunda olmayan isimli argümanlar saklanmış, - __getattr__ metodunda da bunlara erişildiğinde bunların değerleri geri döndürülmüştür. Yani adeta nesnenin öznitelikleri - __init__ metodunda verilmiş gibi olmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __init__(self, **kwargs): - self.kwargs = kwargs - - def __getattr__(self, name): - if val := self.kwargs.get(name): - return val - - raise AttributeError(f"Sample object has no attribute '{name}'") - -s = Sample(x = 10, y = 20, z = 30) - -print(s.x) -print(s.y) -print(s.z) - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfın __getattr__ metoduna benzer bir de __getattribute__ metodu vardır. __getattribute__ metodu bir sınıf türünden - değişken ile sınıfın bir elemanına erişilirken eleman olsa da olmasa da çağrılmaktadır. Halbuki __getattr__ metodunun - eleman yoksa çağrıldığını anımsayınız. Tabii nesnenin olmayan bir elemanına değer atandığında __getattribute__ metodu - çağrılmamaktadır. Çünkü olmayan elemana değer atama işlemi aslında onun yaratılmasına yol açmaktadır. Tabii yine tıpkı - __getattr__ metodunda olduğu gibi eleman olsa da olmasa da __getattribute__ metodunun geri dönüş değeri erişimden elde - edilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - def __getattribute__(self, name): - print(f'__getattr__ called: {name}') - return 0 - - def _foo(self): - print('foo') - -s = Sample() -s.x = 10 # __getattribute__ çağrılmayacak -print(s.x) # __getattribute__çağrılacak, ekrana 0 basılacak -print(s.y) # __getattribute__ çağrılacak, ekrana 0 basılacak - -#------------------------------------------------------------------------------------------------------------------------ - 61. Ders 30/11/2022 - Çarşamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfın __getattribute__ metodunda sınıfın bir elemanına erişmeye çalışırsak yeniden __getattribute__ çağrılacağı için - özyinelemeli bir durum oluşşacaktır. Programcı genellikle böyle b,r durumun oluşmasını istemez. Örneğin: - - class Sample: - def __init__(self, **kwargs): - self._x = 10 - - def __getattribute__(self, name): - if name == 'x': - return self._x - - s = Sample() - - val = s.x - print(val) # None - - Burada s.x erişiminde sınıfın __getattribute__ metodu çağrılacaktır. Bu metot çağrıldığında name parametresinde 'x' ismi - bulunacaktır. Böylece metot return self._x deyimini görecektir. Ancak self._x erişimi de yeniden __getattribute__ metodunun - çağrılmasına yol açacaktır. Ancak bu kez akış if içerisine girmeyecek ve metot None değeri ile geri dönecektir. - - __getattribute__ metodu içerisinde özyineleme yapmadan nesnenin örnek özniteliğine erişmenin tek yolu object sınıfının - __getattribute__ metodunu kullanmaktır. Eğer object sınıfının __getattribute__ metodu kullanılırsa artık ilgili sınıfın - __getattribute__ metodu çağrılmaz. Örneğin: - - class Sample: - def __init__(self, **kwargs): - self._x = 10 - - def __getattribute__(self, name): - if name == 'x': - return object.__getattribute__(self, '_x') - - s = Sample() - - val = s.x - print(val) # None - - Burada object.__getattribute(self, '_x'') çağrısı ile artık özyineleme yapmadan _x isimli örnek özniteliğinin değerine - erişilmiştir. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi sınıfın hem __getattr__ hem de __getattribute__ metotları varsa ne olur? İşte bu durumda __getattr__ hiç devreye - girmez. Olan elemana erişimde zaten __getattr__ devreye girmeyecektir. Olmayan elemana erişimde de __getattr__ devreye - girmez. Yani bu durumda __getattr__ metodunu yazmanın bir anlamı kalmaz. Örneğin: - - class Sample: - def __getattr__(self, name): - print('__getattribute__ called') - return 0 - - def __getattribute__(self, name): - print('__getattribute__ called') - return 0 - - s = Sample() - - s.x = 10 - val = s.x # __getattribute__ çağrılır - val = s.y # __getattribute__ çağrılır - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların __setattr__ metotları bir sınıf nesnesi ile nesnenin olan ya da olmayan elemanlarına değer atandığı durumda - çağrılmaktadır. Bu anlamda __setattr__ adeta __getattribute__ metodunun set yapan biçimi gibidir. Ayrıca bir __setattribute__ - metodu bulunmamaktadır. __setattr__ metodunun self parametresi dışında iki parametresi daha vardır. Bunlardan ilki atama - yapılmak istenen elemanın ismini belirtir. İkincisi ise o elemana atanmak istenen değeri belirtmektedir. Yani __setattr__ - metodunun parametrik yapısı şöyle olmalıdır: - - def __setttr__(self, nanme, value): - pass - - Örneğin: - - class Sample: - def __init__(self): - self.a = 10 # dikkat! yine __setattr__ çağrılacak - - def __setattr__(self, name, value): - print(name, value) - - s = Sample() - - s.a = 20 # dikkat! _setattr__ çağrılacak - - __setattr__ metodu çağrıldığında artık elemana atama yapılmamaktadır. Elemana atama yapılmak isteniyorsa bunu programcının - __setattr__ metodu içerisinde yapması gerekmektedir. Tabii __setattr__ içerisinde nesnenin bir örnek özniteliğine atama - yapılırsa yine "özyineleme (resursion)" oluşur. Bunun oluşması istenmiyorsa atama izleyen paragraflarda açıklayacağımız - nesnenin __dict__ elemanı yoluyla ya da object sınıfının __setattr__ metodu yoluyla yapılmalıdır. Örneğin: - - class Sample: - def __init__(self): - self._x = 0 - - def __getattr__(self, name): - if name == 'x': - return self._x - - raise AttributeError(f"Sample object has no attribute '{name}'") - - def __setattr__(self, name, value): - if name == 'x': - object.__setattr__(self, '_x', value) - else: - object.__setattr__(self, name, value) - - s = Sample() - print(s.x) - s.x = 10 - print(s.x) - - Burada aslında var olmayan bir x özniteliği var olan _x ismi ile kullanılmaya çalışılmıştır. Biz Sample türünden bir değişken - ile nesnenin x özniteliğine erişmek istediğimizde sınıfın __getattr__ metodu çağrılacak, bu metot da aslında sınıfın _x - özniteliğinin değerini verecektir. Biz x özniteliğine değer atamak istediğimizde sınıfın __setattr__ metodu çağrılacaktır. - Biz de bu metot içerisinde aslında nesnenin _x özniteliğine değer atamaktayız. Ancak bu atamanın özyinelemeyi engellemek - için object sınıfı yoluyla yapıldığına dikkat ediniz. - - Aslında __setattr__ çağrılmadan nesnenin bir özniteliğine eleman atamak için __dict__ örnek özniteliğini de kullanabiliriz. - __dict__ örnek özniteliği izleyen bölümde ele alınmaktadır. Ancak burada bir örnek vermek amacıyla bunu kullanmak istiyoruz: - - class Sample: - def __init__(self): - self._x = 0 - - def __getattr__(self, name): - if name == 'x': - return self._x - - raise AttributeError(f"Sample object has no attribute '{name}'") - - def __setattr__(self, name, value): - if name == 'x': - self.__dict__['_x'] = value - else: - self.__dict__[name] = value - - s = Sample() - print(s.x) - s.x = 10 - print(s.x) - - Sınıfın __setattr__ metodu yine bir property etkisi yaratmak için kullanılabilmektedir. Aşağıdaki örnekte Circle sınıfında - aslında area isimli bir örnek özniteliği yoktur. Ancak __getattr__ ve __setattr__ yoluyla sanki böyle bir öznitelik varmış - gibi bir etki oluşturulmuştur. Burada area özniteliğine erişildiğinde aslında o anda dairenin alanı hesaplanıp verilmektedir. - area özniteliğine atamaya yapıldığında ise dairenin yarı çapı hesaplanıp radius özniteliğine atanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -import math - -class Circle: - def __init__(self, radius, x, y): - self.radius = radius - self.x = x - self.y = y - - def __repr__(self): - return f'radius: {self.radius}, center: ({self.x},{self.y}), area: {self.area}' - - def __getattr__(self, name): - if name == 'area': - return math.pi * self.radius ** 2 - - raise AttributeError(f"Circle object has no attribute '{name}'") - - def __setattr__(self, name, value): - if name == 'area': - self.radius = math.sqrt(value / math.pi) - else: - self.__dict__[name] = value - -c = Circle(3, 10, 12) -print(c) - -c.radius = 20 -print(c) - -c.area = 10 -print(c) - -#------------------------------------------------------------------------------------------------------------------------ - Daha önce de bahsettiğimiz gibi property'ler veri elemanı gibi kullanılan metotlardır. Biz yukarıda Circle sınıfı örneğinde - Circle sınıfın area isimli bir örnek özniteliğini kullanmıştık. Ancak gerçekte böyle bir örnek özniteliği sınıfta yoktu. Aslında - böyle bir örnek özniteliğinin sınıfta olmasına gerek de yoktu. Çünkü eğer biz dairenin yarıçapını biliyorsak zaten alanını - hesaplayabiliriz. Onun için ayrıca yer kaplayan bir eleman bulundurmamıza gerek yoktur. İşte yukarıdaki örnekte Circle - sınıfında gereksiz bir area örnek özniteliğini tutmak yerine biz sanki bu örnek özniteliği varmış gibi durum oluşturduk. - - Ancak property kullanımının dşiğer bir amacı da (birinci amaçtan daha aşağı bir amaç değildir) "veri elemanlarının gizlenmesi - (data hiding)" prensibinin uygulanmasını sağlamaktır. Veri elemanlarının gizlenmesi NYPT'nin önemli prensiplerinden biridir. - Genellikle Python'da programcılar bu prensibi kullanmazlar. Ancak bazı programcılar property'ler yoluyla bu prensibi - kullanabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Veri elemanlarının gizlenmesi (data hiding) veri elemanlarının dışarıdan kullanımını engelleyip onlara kodlar yoluyla - erişimi mümkün hale getirmek anlamına gelmektedir. Python'da dışarıdan erişilmesini istemediğimiz öniteliklerin başına - '_' ve '__' ekliyorduk. Tabii bu ekleme erişimi engellemiyordu. Yalnızca dışarıya bir "erişme" biçiminde mesaj veriyordu. - Halbuki C++, Java ve C# gibi diğer dillerde dışarıdan erişimin tamamen engellenmesi için sınıflarda "private" bulundurulmuştur. - - NYPT'de sınıfın örnek öznitelikleri (veri elemanları) iç işleyişe ilişkin olma eğilimindedir. Özniteliklere dışarıdan - erişilmesini engellemenin ve onlara kodlar yoluyla erişilmesine izin verilmesinin dört temel neden vardır: - - 1) Örnek özniteliklerine dışarıdan erişilirse onlar üzerinde yapılacak değişikliklerden onları kullanan kodlar etkilenir. - Örneğin: - - class Date: - def __init__(self, day, month, year): - self.day = day - self.month = month - self.year = year - # ... - - d = Date(10, 20, 2007) - print(d.day, d.month, d.year) - - Burada sınıfın day, month ve year örnek öznitelikleri yerine programcı daha sonra tarih bilgisini bir yazıy ile tutacak biçimde - tek bir eleman kullanırsa bu sınıfı kullanan kodlar geçersiz hale gelir. Halbuki bu özniteliklere doğrudan erişimi engelleyip - onlara kodlarla erişimi mümkün hale getirirsek bu durumda biz sınıfın bu örnek özniteliklerinde değişiklik yapsak bile bu - değişiklikten onları kullanmış olan kodların etkilenmesişni engelleyebiliriz. - - 2) Sınıfın bir örnek özniteliğine programcı geçersiz değerler atayabilir. Bu da tamamen sınıfın hatalı çalışmasına yol açabilir. - Örneğin: - - d.day = 100 - - Burada aslında gün bilgisi geçersiz bir biçimde oluşturulmuştur. Muhtemelen bu durum geçiştirilirse nesnenin davranışı - bozulacaktır. Halbuki sınıfın örnek özniteliklerine bir kod ile değer atanırsa sınır kontrolü uygulanabilir. - - 3) Sınıfın birbirleriyle ilişkili örnek öznitelikleri olabilir. Yani bunlardan birine değer atandığında diğerlerinin - de değiştirilmesi gerekebilir. İşte bu tür işlemlerin kodla yapılması bu karmaşık ilişkinin arka planda doğru bir biçimde - oluşturulmasını sağlayabilmektedir. - - 4) Bazen nesnenin bir özniteliğinden değer alırken ya da ona değer yerleştirirken aynı zamanda başka bir işlemin de - yapılması gerekebilmektedir. İşte örnek özniteliklerine doğrudan değil kodla erişilirse arka planda bu işlemler - yapılabilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Propert'ler aslında yukarıda da örneklerini verdiğimiz gibi sınııfn __getattr__ ve __setattr__ metotları yoluyla - oluşturulabilmektedir. Ancak bu yöntem biraz zahmetlidir. Python'da property oluşturulmasını kolaylaştırmak için property - isminde bir dekoratör sınıf bulundurulmuştur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python'da property kullanımı aslında property isimli bir dekoratör sınıf yoluyla kolay bir biçimde yapılabilmektedir. - property sınıfı built-in bir sınıftır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: - - property(fget=None, fset=None, fdel=None, doc=None) - - Bunu sınıfsal olarak aşağıdaki gibi de gösterebiliriz: - - class property: - def __init__(self, fget = None, fset = None, fdel = None, doc = None) : - pass - - Property kullanımı şöyle yapılmaktadır: - - 1) Programcı sınıfında gizlediği örnek özniteliğinin değerini alacak ve değerini set edecek iki metot bulundurur. (Aslında - delete işlemi ve doc işlemi için de iki metot daha bulundurabilmektedir.) get metodunun yalnızca self parametresi olur ve - bu metot get edilecek değer ile geri döndürülür. set metodunun ise self dışında bir parametresi daha vardır. Bu parametre - örnek özniteliğine atanan değeri belirtmektedir. set metodunun bir geri dönüş değeri yoktur. - - 2) Programcı sonra property sınıfı türünden bir sınıf değişkeni ekler. Bu sınıf değişkeni aslında değişken yoluyla erişimde - kullanılacak örnek özniteliğini belirtmektedir. Artık bu sınıf türünden bir değişken oluşturup, o değişken ile ilgili öziteliğe - onun değerini alacak bir ifadeyle erişildiğinde get metodu, ona değer yerleştirmek istendiğinde de set metodu çalışacaktır. - Örneğin: - - class Sample: - def _get_x(self): - pass - - def _set_x(self, value): - pass - - x = property(_get_x, _set_x) - - Burada artık adeta nesnenin x isimli bir örnek özniteliği varmış gibi bir durum oluşturulmuştur. Örneğin: - - s = Sample() - - s.x = 10 # _set_x metodu çağrılır - print(s.x) # _get_x metodu çağrılacak - - Şimdi de daha önce __getattr__ ve __setattr__ kullanarak yapmış olduğumuz Circle sınıfın area property'sini bu yöntemle - gerçekleştirelim: - - import math - - class Circle: - def __init__(self, radius, x, y): - self._radius = radius - self._x = x - self._y = y - - def __repr__(self): - return f'radius: {self._radius}, center: ({self._x},{self._y}), area: {self.area}' - - def _get_area(self): - return math.pi * self._radius ** 2 - - def _set_area(self, value): - self._radius = math.sqrt(value / math.pi) - - area = property(_get_area, _set_area) - - c = Circle(3, 10, 12) - print(c) - - c.radius = 20 - print(c) - - c.area = 10 - print(c) - - Burada Circle sınıfına area isimli bir property eklenmiştir. Bu property bir örnek özniteliği gibi kullanılmaktadır. - Ancak aslında bu property kullanıldığında sınıfın _get_area ve _set_area metotları çalıştırılmaktadır. - - Tabii sınıfa property eklendiğinde artık sınıfın örnek özniteliklerine bu property yoluyla erişilecektir. Bu durumda - sınıfın proporty'si olan örnek özniteliklerinin başına '_' karakteri getirilerek isimlendirilmesi uygun olur. Benzer - biçimde property fonksiyonuna verdiğimiz getter, setter ve deleter metotları da bu biçimde isimlendirilmelidir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tabii property'lerin read/write olması gerekmez. Örneğin "read-only" property'ler söz konusu olabilmektedir. Read-only - property demek yalnızca get yapılan ama set yapılamayan property demektir. - - Aşağıdaki örnekte Date sınıfına tarih bilgisinin gün, ay ve yıl bileşenlerinin elde edildiği day, month ve year property'leri - eklenmiştir. Bu property'lerin read-only olduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self._day = day - self._month = month - self._year = year - - def _get_day(self): - return self._day - - def _get_month(self): - return self._month - - def _get_year(self): - return self._year - - day = property(_get_day) - month = property(_get_month) - year = property(_get_year) - -d = Date(9, 1, 2024) - -print(d.day, d.month, d.year) - -#------------------------------------------------------------------------------------------------------------------------ - property sınıfının __init__ metodunun üçüncü parametresi (başka bir deyişle property fonksiyonunun üçüncü parametresi) - ilgili örnek özniteliğini del deyimi ile silmeye çalıştığımızda çağrılacak metodu belirtmektedir. Örneğin: - - class Sample: - def __init__(self, a): - self._a = a - - def _get_a(self): - return self._a - - def _set_a(self, value): - self._a = value - - def _del_a(self): - print('deleting self._a') - del self._a - - a = property(_get_a, _set_a, _del_a) - - s = Sample(10) - - print(s.a) - - s.a = 20 - print(s.a) - - del s.a - - property için delete metodunun yazılmasına seyrek bir biçimde gereksinim duyulmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Aslında property'ler dekoratör sentaksıyla çok daha kolay bir biçimde oluşturulabilmektedir. Örneğin: - - class Sample: - def __init__(self, a): - self._a = a - - @property - def a(self): - return self._a - - Anımsanacağı gibi burada @property dekoratörünün eşdeğeri şöyledir: - - class Sample: - def __init__(self, a): - self._a = a - - def a(self): - return self._a - - a = property(a) - - Yani biz yine property fonksiyonuna a metodunu verip property ismi olarak a ismini oluşturmuş olmaktayız. Bu sentaks - property oluşturmak için oldukça kolaydır. Tabii burada biz yalnızca örnek özniteliğini get ettik. Bunun set edilmesini - izleyen paragrafta ele alacağız. -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self._day= day - self._month = month - self._year = year - - @property - def day(self): - return self._day - - @property - def month(self): - return self._month - - @property - def year(self): - return self._year - -d = Date(9, 1, 2024) - -print(d.day, d.month, d.year) - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi dekoratör yoluyla setter metodu nasıl yazılmaktadır? İşte setter metodu için dekoratör fonksiyonu görevini - property sınıfının setter metodu yapmaktadır. Programcı önce property dekoratörü ile getter metodunu oluşturur. Sonra da - getter ismini kullanarak property sınıfının setter metodunu dekoratör yapar. Örneğin: - - Örneğin: - - class Sample: - def __init__(self, a): - self._a = a - - @property - def a(self): - return self._a - - # a = property(a) - - @a.setter - def a(self, val): - self._a = val - - # a = a.setter(a) - - s = Sample(10) - - print(s.a) - - s.a = 20 - print(s.a) - - Burada aşağıdaki dekoratöre dikkat ediniz: - - @a.setter - def a(self, val): - self._a = val - - Bu dekoratörün eşdeğer kodu şöyledir: - - a = a.setter(a) -#------------------------------------------------------------------------------------------------------------------------ - -class Date: - def __init__(self, day, month, year): - self._day= day - self._month = month - self._year = year - - @property - def day(self): - return self._day - - @day.setter - def day(self, value): - self._day = value - - # day = day.setter(day) - - @property - def month(self): - return self._month - - @month.setter - def month(self, value): - self._month = value - - # month = month.setter(month) - - @property - def year(self): - return self._year - - @year.setter - def year(self, value): - self._year = value - - # year = year.setter(year) - -d = Date(9, 1, 2024) - -print(d.day, d.month, d.year) - -d.day = 10 -d.month = 12 -d.year = 2003 - -print(d.day, d.month, d.year) - -#------------------------------------------------------------------------------------------------------------------------ - property sınıfının kendisinin tam olarak yazılması izleyen paragraflarda ele alacağımız "descriptor" kullanımı ile mümkün - olabilmektedir. Ancak biz yukarıdaki setter işleminde property nesnesinin naısl oluşturulduğu konusunda bir fikir vermek - istiyoruz. Bunun için property sınıfının myproperty isminde eksik bir halini oluşturalım: - - class myproperty: - def __init__(self, fget = None, fset = None, fdel = None, fdoc = None): - self._fget = fget - self._fset = fset - self._fdel = fdel - self._fdoc = fdoc - - def setter(self, fset): - self._fset = fset - return self - - Tabii burada oluşturduğumuz myproperty sınıfı yalnızca setter metodunun nasıl property nesnesine eklendiğini açıklamak - için oluşturulmuştur. Yukarıda da belirttiğimiz gibi property işlevinin yerine getirilmesi için "descriptor" konusunun - kullanılması gerekmektedir. Buradaki myproperty sınıfının aşağıdaki gibi kullanıldığını varsayalım: - - class Sample: - def __init__(self, a): - self._a = a - - @myproperty - def a(self): - return self._a - - @a.setter - def a(self, value): - self._a = value - return self - - Burada yorumlayızı önce myproperty dekoratörünü görecektir. Bu dekoratörün eşdeğeri şöyledir: - - a = myproperty(a) - - Böylece aslında myproperty nesnesinin _fget özniteliğine bu a metodu yerleştirilmiş olacaktır. Yani bu metot bir getter - olarak myproperty nesnesine yerleştirilecektir. Sonra yotumlayıcı diğer dekoratörü örecektir. Onun da eşdeğeri şöyledir: - - a = a.setter(a) - - Burada aslında myproperty sınıfının setter metodu çalıştırılmaktadır. Bu metot self ile geri döndürüldüğü için a'ya yine aynı - myproperty nesnesi atanmış olacaktır. Böylece aslında yine myproperty nesnesinin içerinde getter ve setter metotları - bulunuyor olacaktır. - - Burada bir yer kafa karıştırabilir. a eğer myproperty sınıfı türündense aşağıdaki metotta a ismi değişmeyecek midir? - - @a.setter - def a(self, val): - self._a = val - - Çünkü bu kodun eşdeğerinin aşağıdaki gibi olduğunu görmüştük: - - def a(self, val): - self._a = val - - a = a.setter(a) - - Bu eşdeğerlilikten hareketle a metodunu gören yorumlayıcı a değişkenine değer atadığında a değişkeninin içinin bozulacağını - düşünebilirsiniz. İşte dekoratör sentaksında aslında dekoratöre konu olan değişken boşuna hiç oluşturulmamaktadır. Yani burada a - ismi aslında bozulmamaktadır. Başka bir deyişle aslında yukarıdaki dekoratörün eşdeğeri şöyledir: - - def some_temporary_name(self, val): - self._a = val - - a = a.setter(some_temporary_name) - - Başka bir deyişle örneğin: - - @foo - def bar(): - pass - - Burada aslında bar değişkenine iki kere değer atanmamaktadır. Yani yorumlayıcı önce fonksiyon nesnesinin adresini bar değişkenine - atayıp sonra onu dekatöre vermemektedir. Fonksiyon nesnesini yaratıp henüz bar değişkenine atamdan dekoratöre vermektedir. Dolayısıyla - aslında bar değişkenine yalnızca dekoratörün döndürdüğü değer atanmaktadır. - - deleter metodu da yine property nesnesinin deleter isimli metoduyla oluşturulmaktadır. Örneğin: - - class Sample: - def __init__(self, a): - self._a = a - - @property - def a(self): - return self._a - - # a = myproperty(a) - - @a.setter - def a(self, value): - self._a = value - - # a = a.setter(a) - - @a.deleter - def a(self): - print('a deleter') - - # a = a.deleter - - s = Sample(10) - - del s.a - - Bu dekoratörün oluşturulma biçimi de tamamen setter property'sinde olduğu gibidir. Örneğin: - - class myproperty: - def __init__(self, fget = None, fset = None, fdel = None, fdoc = None): - self._fget = fget - self._fset = fset - self._fdel = fdel - self._fdoc = fdoc - - def setter(self, fset): - self._fset = fset - return self - - def deleter(self, fdel): - self._del = fdel - return self - -#------------------------------------------------------------------------------------------------------------------------ - 62. Ders 05/12/2022 - Pazartesi -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıfların diğer bir özel metodu ise __new__ isimli metottur. Biz bir sınıf türünden nesne yarattığımızda aslında bu - nesne iki aşamada kullanıma hazır hale getirilmektedir: - - 1) Önce ilgili sınıfın __new__ isimli static static metodu çağrılır. Bu işlem sonucunda nesnenin bellekte tahsis edilmesi - sağlanır. - - 2) Nesne için bellekte yer tahsis edildikten sonra onun örnek özniteliklerinin yaratılması ve birtakım gerekli ilk işlemlerin - yapılması için sınıfın __init__ metodu çağrılır. - - Örneğin: - - s = Sample() - - Burada aslında yorumlayıcı önce Sample sınıfının __new__ isimli static metodunu çağrır, daha sonra __init__ metodunu çağırır. - __new__ metodunun amacı tahsisat yapmak, __init__ ise amacı nesneye ilkdeğerlerini vermektir. - - Aslında nesne için tahsisat object sınıfının __new__ static metodu ile yapılmaktadır. Yani programcı kendi sınıfı için __new__ - static metodunu yazmış olsa bile aslında asıl tahsisatın yine object sınıfının __new__ metoduyla yapılmasını sağlamalıdır. - O halde programcı aslında "tahsisatı yapmak için değil, yalnızca araya girmek için" bu __new__ static metodunu yazmak ister. - - Programcılar sınıfları için __new__ metodunu genellikle yazmazlar. __new_ metodunun yazılmasına seyrek olarak gereksinim - duyulmaktadır. Yukarıda da belirttiğimiz gibi __new__ metodu static bir metottur ve bu metodun bir parametresi olmak zorundadır. - Bu parametre yaratılmak istenen sınıfın bilgilerini belirten o sınıfa ilişkin type nesnesini almaktadır. __new__ metodu - tahsis edilen nesne ile geri dönmelidir. Tabii yukarıda belirttiğimiz gibi asıl tahsisat object sınıfının __new__ metodu - ile yapıldığı için programcının kendi sınıfı için yazdığı __new__ metodunun object sınıfının __new__ metodunun geri dönüş - değeri ile geri dönmesi gerekir. Bu durumda sınıfın __new__ metodu tipik olarak şöyle yazlmalıdır: - - @staticmethod - def __new__(cls): - # araya girme işlemi - return object.__new__(cls) - - Görüldüğü gibi programcı tahsisatı kendisi yapmamaktadır. Tahisat object sınıfının __new__ metodu tarafından yapılmaktadır. - Programcı yalnızca bu mekanizmada araya girmektedir. - - Pekiyi biz sınıfımız için __new__ metodunu yazmadığımızda ne olmaktadır? Bu durumda taban sınıfın yani object sınıfının __new__ - metodu çağrılacaktır. Zaten tahsisat da bu object sınıfının __new__ metodu tarafından yapılmaktadır. Ancak burada ince bir nokta - üzerinde durmak istiyoruz. Bizim __new__ metodunu yazdığımız sınıfın taban sınıfı da __new__ metodunu araya girme amaçlı - yazmış olabilir. Bu durumda bizim doğrudan object sınıfının __new__ metodunu çağırmak yerine taban sınıfın __new__ metodnu - çağırmamız daha uygun olur. Yani __new__ metodu aslında aşağıdaki gibi yazılmalıdır: - - @staticmethod - def __new__(cls): - # araya girme işlemi - return super().__new__(cls) - - Biz burada taban sınıfın __new__ metodunu çağırmış olduk. Aynı işlemi taban sınıflar da yapacağına göre yine tahsisat object - sınıfının __new__ metodu tarafından yapılmış olacaktır. Örneğin: - - class A: - def __init__(self): - print('A.__init__') - - @staticmethod - def __new__(cls): - print('A.__new__') - return super().__new__(cls) - - class B(A): - def __init__(self): - super().__init__() - print('B.__init__') - - @staticmethod - def __new__(cls): - print('B.__new__') - return super().__new__(cls) - - s = B() - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıf nesnesinin tahsis edilmesi sürecinde bazı ayrıntılar vardır. Anımsanacağı gibi bir sınıf türünden değişkenle - fonksiyon çağırma operatörü kullanıldığında sınıfın __call__ isimli metodu çağrılmaktaydı. Örneğin: - - class Sample: - def __call__(self): - print('__call__') - - s = Sample() - - s() # s.__call__() - - Yine anımsanacağı gibi bir sınıfın tanımlamasını gören yorumlayıcı önce type sınıfı türünden bir nesne yaratıp sınıfın - bilgilerini o nesneye yerleştiriyordu ve o nesnenin adresini de sınıf ismine ilişkin değişkene atıyordu. Yani sınıf isimleri - aslında type sınıfı türünden bir nesneyi gösteren değişken biçimindeydi. Örneğin: - - class Sample: - pass - - Burada Sample değişkeni type sınıfı türündendir. Bir sınıf türünden nesneyi fonksiyon çağırma operatörü ile yarattığımızı - anımsayınız: - - s = Sample(...) - - O halde aslında bir sınıf nesnesi type sınıfının __call__ metodu ile yaratılmaktadır. İşte type sınıfının __call__ metodu - aşağıdaki gibi yazılmıştır: - - class type: - def __call__(self, *args, **kwargs): # 1 - instance = self.__new__(self, *args, **kwargs) # 2 - if isinstance(instance, self): - instance.__init__(*args, **kwargs) # 3 - return instance # 4 - - Burada önemli noktaları tek tek belirtelim: - - 1) Bir sınıf türünden nesne yaratılmak istendiğinde aslında type sınıfının __call__ metodu çağrılmaktadır. Neeneyi yaratırken - kullandığımız tüm isimsiz ve isimli argümanlar bu __call__ metoduna geçirilmektedir. - - 2) type sınıfının __call__ metodunda ilgili sınıfın static __new__ metodu çağrılmaktadır. Tahsisat bu çağrı ile yapılmaktadır. - __new__ metoduna nesneyi yaratırken geçirilen argümanların da geçirildiğine dikkat ediniz. O halde aslında __new__ metodunun - nesneyi yaratırken kullanılan argümanların hepsini alabilecek biçimde yazılması gerekir. Örneğin: - - @staticmethod - def __new__(cls, *args, **kwargs): - # araya giren kod - return super().__new__(cls) - - Burada __new__ metodu nesnenin yaratılması sırasında kullanılan argümanları da almıştır. Bu argümanlara bazı uygulamalarda - gereksinim duyulabilmektedir. Asıl tahsisatı yapan object sınıfının __new__ metodunun yalnızca tahsisat yapılacak sınıfı - alan parametreye (yani yalnızca cls parametresine) sahip olduğunu da belirtmek istiyoruz. Bu nedenle türemiş sınıfın __new__ - metodu içerisinde taban sınıfın __new__ metodu çağrılırken taban sınıfın __new__ metoduna taban sınıfın __init__ metodundaki - parametrelerin aktarılması uygun olmaktadır. - - 3) İlgisi sınıfın __new__ metodunun çağrılması sonucunda tahsis edilen nesne ilgili sınıf türündense bu kez aynı arümanlarla - o sınıfın __init__ metodu çağrılmaktadır. Başka bir deyişle eğer __new__ metodundan elde edilen nesne başka bir sınıf türünden - olursa ilgili sınıfın __init__ metodu çağrılmamaktadır. - - 4) Nihayet __call__ metodu tahsis edilen nesnenin adresiyle geri dönmektedir. - - Örneğin: - - s = Sample(10, 20) - - Burada aslında type sınıfının __call__ metodu çağrılmaktadır. type sınıfının __call__ metodu Sample sınıfının __new__ metodunu - çağırır. Eğer __new__ metodunun geri dönüş değeri Sample sınıfı türündense (genellikle böyle olur) bu kez Sample sınıfının - __init__ metodu çağrılır. Nihayet tahsis edilen nesnenin adresi s değişkenine atanacaktır. - - Burada bir noktayı yeniden vurgulamak istiyoruz: Biz bir sınıf için __new__ metodunu yazdığımızda eğer bu metodu o sınıf - türüden bir nesneyle geri döndürmezsek bu durumda o sınıfın __init__ metodu çağrılmayacaktır. Aşağıdaki örnekte Sample sınıfının - __init__ metodu çağrılmayacaktır. - - class Mample: - def __init__(self): - print('Mample.__init__') - - class Sample: - @staticmethod - def __new__(cls, *args, **kwargs): - print('Sample.__new__') - instance = object.__new__(Mample) - return instance - - def __init__(self): - print('Sample.__init__') - - s = Sample(10, 20) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - type sınıfı ile object sınıfı arasında mantıksal bir ilişki yoktur. type sınıfı bir "meta sınıf"tır. Yani bir sınıfın - bilgilerini tutan sınıftır. Meta sözcüğü üst kavram olarak kullanılmaktadır. Sınıf bilgi tutar, ancak sınıfın kendi - bilgileri de bir sınıf tarafından tutulmaktadır. İşte o type sınıfıdır. Bu durumda type meta bir sınıftır. object sınıfı - ise her sınıfın taban sınıfı görevinde olan nesne yaratma işlemini yapan sınıftır. Kaldı ki type sınıfı da object - sınıfından türetilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi biz neden bir sınıf için __new__ metodunu yazmak isteriz? İşte bunun nedeni biraz ileri düzey konularla ilgilidir. - Örneğin "singleton" kalıbı __new__ metodu kullanılarak aoluşturulabilir. NYPT'de singleton kalıbı bir sınıf türünden - tek bir nesnenin yaratıldığı (yani kişiler nesne yarattığını sansalar da aslında yaratımdan hep aynı nesnenin elde edildiği) - bir kalıptır. Singleton kalıbında belli bir anda ilgili sınıf türünden toplamda tek bir nesne bulunmaktadır. Singleton - kalıbı bazı işlemlerin gerçekleştirilmesi için kullanılabilmektediri - - Aşağıda singleton kalıbına örnek verilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - @staticmethod - def __new__(cls, *args, **kwargs): - if Sample._singleton is None: - Sample._singleton = Sample._singleton = object.__new__(cls) - - return Sample._singleton - - _singleton = None - -s = Sample() -print(s, id(s)) - -k = Sample() -print(k, id(k)) - -#------------------------------------------------------------------------------------------------------------------------ - Betimleyiciler (descriptors) ya da betimleyici sınıflar (descriptor classes) Python'a sonradan eklenmiştir. property - sınıfı gibi birtakım sınıfların yazılabilmesi için böyle bir kavrama ihtiyaç duyulmuştur. Bir sınıfın betimleyici - sınıf olması için o sınıfta __get__, __set__ ya da __delete__ metotlarının en bir tanesinin bulunuyor olması gerekir. - Genellikle betimleyici sınıflarda __get__ ve __set__ metotları birlikte bulunurkar. Ancak bazı sınıflarda yalnızca - __get__ metodu bulunuyor olabilir. - - __get__ metodunun self dışında iki parametresi olmalıdır. Bu parametrelee genellikle instance ve owner biçiminde - isimlendirilmektedir. Örneğin: - - def __get__(self, instance, owner): - pass - - __set__ metodunun da self dışında iki parametresi bulunur. Bu parametreler de genellikle instance ve value biçiminde - isimlendirilmektedir. Örneğin: - - def __set__(self, instance, value): - pass - - __delete__ metodunun ise self parametresi dışında tek bir parametresi olmalıdır. Bu parametre de genellikle instance - biçiminde isimlendirilmektedir. Örneğin: - - def __delete__(self, instance): - pass - -#------------------------------------------------------------------------------------------------------------------------ - -class MyDescriptor: - def __get__(self, instance, owner): - pass - - def __set__(self, instance, value): - pass - - def __delete__(self, instance): - pass - -#------------------------------------------------------------------------------------------------------------------------ - Metimleyici sınıflar başka sınıflarda kullanılsın diye oluştrulurlar. Betimleyici sınıf türünden bir nesne yaratılır - ve ilgili sınıfın bir sınıf değişkenine atanır. Betimleyici sınıfların bu kullanım dışında başka anlamlı kullanımları - yoktur. Örneğin: - - class MyDescriptor: - def __get__(self, instance, owner): - pass - - def __set__(self, instance, value): - pass - - def __delete__(self, instance): - pass - - class Sample: - a = MyDescriptor() - - Burada a Sample sınıfının bir özniteliğidir yani bir sınıf değişkenidir. Bilindiği gibi sınıf değişkenlerine normalde - ismiyle erişilir. Ancak onlara ilgili sınıf türünden değişkenlerle de erişebiliriz. - - Bir betimleyici nesne (yani betimleyici sınıf türünden yaratılmış olan sınıf değişkeni) adeta yerleştirildiği sınıfın bir - örnek özniteliği gibi davranmaktadır. Örneğin: - - s = Sample() - - s.a = 10 - - val = s.a - print(val) - - del s.a - - Bir betimleyici sınıf elemanına sahip olan bir sınıf türünden nesne yaratıldıktan sonra bu sınıf değişkeni ile betimleyici - elemana değer almak amacıyla erişimde betimleyici sınıfın __get__ metodu, değer yerleştirmek amacıyla erişimde betimleyici - sınıfın __set__ metodu ve del operatörü ile silme amaçlı kullanımda da betimleyici sınıfın __delete__ metodu çağrılmaktadır. - Örneğin: - - s = Sample() - - Burada şöyle kod yazmış olalım: - - result = s.a - - Burada s nesnesi yoluyla sınıfın a isimli betimleyici elemanına erişilmektedir. Bu erişim değer almak amacıyla yapılmıştır. - O zaman betimleyici sınıfın __get__ metodu çağrılacakltır. __get__ metodunun self parametresi a sınıf değişkenini belirten - MyDescriptor sınıfı türünden olur. instance parametresine betimleyiciye erişmekte kullanılan nesne (yani örneğimizde s nesnesi) - geçirilecektir ve owner parametresine de Sample sınıfının type nesne referansı geçirilecektir. Genellikle bu owner parametresine - __get__ metodunda gereksinim duyulmamaktadır. Betimleyiciye erişimden elde edilen değer __get__ metodundan elde edilen değerdir. - Daha açık bir anlatımla örneğin: - - result = s.a - - Burada adeta şu işlem yapılıyor gibidir: - - result = a.__get__(s, type(s)) - - Şimdi s.a ifadesine atama yapmak isteyelim. Örneğin: - - s.a = 100 - - Burada artık betimleyici sınıfın __set__ metodu çağrılacaktır. Metodun self parametresine yine a betimleyici nesnesi - geçirilecektir. s nesnesi yine instance parametresine geçirilecektir. value paramtresine ise atanan değer olan 100 değeri - geçirilecektir. Yani yukarıdaki atama işleminin eşdeğeri şöyledir: - - a.__set__(s, 100) - - Şimdi de bu s.a ifadesini del operatörü ile silmeye çalışalım. Örneğin: - - del s.a - - Burada da betimleyici sınıfın __delete__ metodu çağrılacaktır. (Bu metodu __del__ metoduyla karıştırmayınız. __del__ - metodu çöp toplayıcı tarafından çağrılmaktadır.) __delete__ metodunun self parametresine yine a betimleyici nesnesi - geçirilir. s yine metodun instance parametresine aktarılmaktadır. Yani bu işlemin eşdeğeri şöyledir: - - a.__delete__(s) - - Aşağıdaki örnekte çıkan sonuçlara dikkat ediniz. - -#------------------------------------------------------------------------------------------------------------------------ - -class MyDescriptor: - def __get__(self, instance, owner): - print(instance, owner) - print(type(instance), type(owner)) - - return 100 - - def __set__(self, instance, value): - print(instance, value) - print(type(instance), type(value)) - - def __delete__(self, instance): - print(instance) - -class Sample: - a = MyDescriptor() - - def __repr__(self): - return 'Sample object' - -s = Sample() - -val = s.a # val = a.__get__(s, type(s)) -print(val) - -print('-' * 10) - -s.a = 200 # a.__set__(s, 200) - -print('-' * 10) - -del s.a - -#------------------------------------------------------------------------------------------------------------------------ - Pekiyi betimleyici sınıflar ve değişkenler neden kullanılmaktadır? İşte bazı işlemler ancak bu betimleyici konusu ile - yapılabilmektedir. Örneğin daha önceki konumuzda property isimli bir sınıf görmüştük ve o sınıfı dekoratör olarak da - kullanmıştık. İşte property gibi bir sınıf yazabilmek için mecburen bir betimleyici sınıfa gereksinimimiz olur. - - Aşağıdaki property kullanımına bir daha dikkat ediniz: - - class Sample: - def __init__(self, a): - self._a = a - - def get_a(self): - print('get_a called ') - return self._a - - def set_a(self, val): - print('set_a called ') - self._a = val - - def del_a(self): - print('del_a called') - - a = property(get_a, set_a, del_a) - - s = Sample(100) - - result = s.a - print(result) - - s.a = 20 - - del s.a - - Buradaki property sınıfı bir betimleyici sınıftır. Ve böyle bir betimleyici işlevi olmadan daha önceki bilgilerimizle biz property - gibi bir sınıf yazamayız. - - Aşağıda orijinalk property sınıfının tamamen bir benzeri yazılmıştır. Yazıma dikkat ediniz. Betimleyici olmadan property sınıfını yazmaya - çalışınız ve yazamadığınızı görünüz. - -#------------------------------------------------------------------------------------------------------------------------ - -cclass myproperty: - def __init__(self, fget = None, fset = None, fdel = None, fdoc = None): - self._fget = fget - self._fset = fset - self._fdel = fdel - self._fdoc = fdoc - - def setter(self, fset): - self._fset = fset - return self - - def deleter(self, fdel): - self._fdel = fdel - return self - - def __get__(self, instance, owner): - if self._fget is not None: - return self._fget(instance) - - raise AttributeError('unreadable attribute') - - def __set__(self, instance, value): - if self._fset is not None: - self._fset(instance, value) - else: - raise AttributeError("can't set attribute") - - def __delete__(self, instance): - if self._fdel is not None: - self._fdel(instance) - else: - raise AttributeError("can't delete attribute") - -class Sample: - def __init__(self, a): - self._a = a - - @myproperty # a = property(a) - def a(self): - return self._a - - @a.setter # a = a.setter(a) - def a(self, value): - self._a = value - - @a.deleter # a = a.deleter(a) - def a(self): - print('delete _a') - -s = Sample(10) - -print(s.a) # 10 - -s.a = 20 - -print(s.a) # 20 - -del s.a # delete s_a - -#------------------------------------------------------------------------------------------------------------------------ - Python install edildiğinde onun bütün standart kütüphaneleri yerel makineye çekilmektedir. Ancak programcılar başkaları - tarafından yazılmış olan yüzlerce farklı kütüphaneyi kullanabilmektedir. İşte bu üçüncü parti kütüphaneler Internet'te - ismine İngilizce "repository" denilen server'larda tutulmaktadır. Bir kütüphaneyi bu server'lardan indirip yerel makinede - kullanıma hazır hale getirmek için "pip (python installer program)" denilen bir program kullanılmaktadır. pip programı - tipik olarak komut satırından şöyle kullanılır: - - pip install kütüphane_ismi - - pip programı bu üçüncü parti kütüphaneyi indirir ve onu kullanıma hazır hale getirir. Nevcut bir paketi yerel makineden - silmek için ise pip komutu şöyle kullanılmaktadır: - - pip uninstall kütüphane_ismi - - Yerel makinede kurulmuş olan üçünücü parti kütüphaneleri görebilmek için ise pip komutu şöyle kullanılmaktadır: - - pip list kütüphane_ismi - - pip programı otomatik olarak ilgili kütüphanenin en son versiyonunu indirip kurmaktadır. Kütüphanenin belli bir versiyonu - == ile versiyon numarası belirtilerek kurulabilmektedir. Örneğin: - - pip install kütüphane_ismi == versiyon_numarası - - pip komutunun ayrıntıları vardır. Biz bu aytıntılar üzerinde burada durmayacağız. Bunun için Python dokümanlarına - başvurabilirsiniz. - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Paketler (packages), içerisinde birden fazla Python kaynak dosyası bulunan dizinlere denilmektedir. Genellikle kütüphaneler - tek bir kaynak dosya olarak yazılmazlar. Birden fazla dosya biçiminde organize edilirler. İşte bir paket, içerisinde - birden fazla kaynak dosyadan oluşan bir dizini belirtmektedir. Örneğin pip programıyla bir kütüphaneyi indirip kurmak - istediğinizi düşünelim: - - pip install paket_ismi - - pip programı Internet'te üçüncü parti kütüphanelerin bulunduğu server'lara başvurur oradan kütüphanenin içeriğini yerel - makineye indirir ve kütüphaneyi oluşturan Python dosyalarını bir dizine yerleştirir. Yerel makinedeki bu dizin Python - için birden fazla Python dosyasından oluşan bir pakettir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - - Python'da bir dizin'in bir paket olarak ele alınması için tek gereken şey o dizin'in içinde "__init__.py" isimli bir - dosyanın bulunyor olmasıdır. Paketler de tamamen modüller gibi import edilmektedir. - - Örneğin bulunduğumuz dizin'in altında mypackage isimli bir dizin yaratıp onun içerisine "__init__.py" dosyasını yerleştirelim. - Dosyanın içi boş olabilir. Biz bir paketi nasıl bir dosyayı import ediyorsak dosya gibi import edebiliriz. Örneğin: - - import mypackage - - Yine import işlemi sırasında as cümleciği de kullanılabilir. Örneğin: - - import mypackage as mp - - Bir paket import edildiğinde paketin içerisindeki "__init__.py" dosyası otomatik çalıştırılmaktadır. Örneğin mypackage - isimli paketteki "__init__.py" dosyasının içeriği şöyle olsun: - - # __init__.py - - print('__init__.py') - - Biz aşağıdaki gibi paketi import ettiğimizde bu dosyanın içindekiler çalıştırılacaktır: - - import mypackage - - Tabii paketi iki kere import edersek paket yalnızca ilk import edildiğinde "__init__.py" dosyası çalıştırılır. - - Şimdi mypackage dizini içerisine "__init__.p"y dosyasının yanı sıra "a.py" ve "b.py" dosyalarını da yerleştirelim. - Dosyaların içeriği şöyle olsun: - - # a.py - - print('this is mypackage.a module') - - def foo(): - print('a.foo) - - # b.py - - print('this is mypackage.b module') - - def bar(): - print('b.bar') - - Paketler import edildiğinde tıpkı kaynak dosyalar gibi module nesneleri oluşturulmaktadır. Paket isimleri de bu - module nesnelerini gösteren değişken durumunda olurlar. Örneğin: - - import mypackage - - print(type(mypackage)) # - - Bir paketin içerisindeki spesifik bir dosya da import edilebilir. Örneğin: - - import mypackage.a - - Bu biçimde bir paketin içerisindeki dosyayı import etmeden önce paketin import edilmesine gerek yoktur. Bu tür durumlarda - zaten paketin kendisi de otomatik olarak import edilmektedir. Yani yukarıdaki import işleminde sanki önce paketin kendisi - import edilmiş sonra da paketin içerisindeki dosya import edilmiş gibi bir etki oluşacaktır. Dolayısıyla yine __init__.py - dosyasının içeriği ve a.py dosyasının içeriği çalıştırılacaktır. Yukarıdaki gibi bir paketin içerisindeki bir module import - edildiğinde iki modül nesnesi oluşturulacaktır. Birinci module nesnesi mypackage ismine atanacak, ikinci module nesnesi ise - mypackage.a ismine atanacaktır. Örneğin: - - import mypackage.a - import mypackage.b - - Burada ilk import işleminde paketin __init__.py dosyası çalıştırılır. Ancak ikinci import işleminde artık çalıştırılmaz. - Yani burada ekrana şunlar çıkacaktır: - - __init__.py - this is mypackage.a module - this is mypackage.b module - - Bir paketteki bir dosyanın içerisindeki değişkenleri (örneğin fonksiyonları) kullanabilmek için önce o paketin içerisindeki - dosyanın import edilmesi gerekir. Sonra paket_ismi.modül_ismi.değişken_ismi biçiminde paketteki modül içerisinde bulunan - değişken kullanılabilir. Örneğin: - - import mypackage.a - import mypackage.b - - mypackage.a.foo() - mypackage.b.bar() - - Yalnızca paketi import edip dosyayı import etmeden o dosyanın içerisindeki değişkenleri kullanamayız. Örneğin: - - import mypackage - - mypackage.a.foo() # error! - mypackage.b.bar() # error! - - Burada mypackage import edildiğinde a ve b modül isimleri oluşturulmamaktadır. - - Tabii import deyimindeki as cümleciği ile paket içerisindeki modül ismini kısaltabiliriz. Örneğin: - - import mypackage.a as a - import mypackage.b as b - - a.foo() - b.bar() - - Örneğin matplotlib paketinin içerisindeki pyplot kütüphanesini programcılar genellikle aşağıdaki biçimde import - ederek kullanılar: - - import matplotlib.pyplot as plt - - plt.plot(...) - - Paketteki dosyanın içerisinde bulunan spesifik bir değişkeni form import deyimi ile de import edebiliriz. Tabii bu durumda - yine paketin __init__.py dosyası ve paket içerisindeki dosyanın içerisindeki kodlar çalıştırılacaktır. Örneğin: - - from mypackage.a import foo - from mypackage.b import bar - - foo() - bar() - - Ekrana şu yazılar çıkacaktır: - - __init__.py - this is mypackage.a module - this is mypackage.b module - foo - bar - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir paketin __init__.py dosyasında paketin içerisindeki dosyalar import edilebilir. Bu durumda biz o paketi import - ettiğimizde o dosyaları da import etmiş gibi oluruz. Örneğin mypackage dizininindeki __init__.py dosyası şöyle yazılmış - olsun: - - # __init__.py - - print('__init__.py') - - import mypackage.a - import mypackage.b - - Şimdi biz paketi import edelim: - - import mypackage - - Artık paketin içerisindeki dosyaların içerisindeki değişkenleri paket ismi ve dosya ismi belirterek kullanabiliriz. - Örneğin: - - import mypackage - - mypackage.a.foo() - mypackage.b.bar() - - Ancak __init__.py içerisinde o paketteki dosyalar import edilirken yine paket ismi kullanılmak zorundadır. Yani - import işlemi aşağıdaki gibi yapılamaz: - - # __init__. py - - print('__init__.py') - - import a # error! - import b # error! - - Örneğin numpy kütüphanesini import ettiğimizde onun __init__.py dosyasında paket içerisindeki birtakım dosyalar - zaten import edilmektedir. Biz de aşağıdaki gibi işlemler yapabilmekteyiz: - - import numpy - - a = numpy.random.randint(0, 10, 100) - print(a) - - Burada numpy bir pakettir. random ise bir dosyadır. Bu random dosyasının import işlemi paketin __init__ dosyasında - yapıldığı için biz randint fonksiyonunu numpy.random.randint biçiminde kullanabildik. - - Bazen programcı paketin __init__.py dosyasında from import deyimi ile ilgili modülün içerisideki değişkenleri paketin - içerisine taşır. Böylece artık yalnızca paket ismi ile o değişkenlere erişilebilir. Örneğin mypackage paketindeki __init__.py - dosyasının içeriği şöyle olsun: - - # mypackage.__init__.py - - print('__init__.py') - - from mypackage.a import foo - from mypackage.b import * - - Şimdi biz bu paketi import ettiğimizde buradaki from import deyimleri çalıştırılacak (tabii bu deyimler çalışırken a.py - ve b.py dosyalarının içi de çalıştırılşacaktır) ve foo ile b modülünün içerisindeki değişken isimleri sankii bu paketin - içerisindeymiş gibi bir etki olulacaktır. Şimdi paketi aşağıdaki gibi kullanalım: - - import mypackage - - mypackage.foo() - mypackage.bar() - - Artık foo ve bar değişkenlerine mypackage ismiyle eriştik. Ekranda şu yazılar görünecektir: - - __init__.py - this is mypackage.a module - this is mypackage.b module - foo - bar - - Burada dikkat edilecek nokta artık mypackage dizinin içerisindeki a dosyasının içerisinde bulunan foo fonksiyonunun Sanki - mypackage içerisindeymiş gibi kullanılabildiğidir. Bu da kullanım kolaylığı oluşturmaktadır. - - Burada kullandığımız teknik aslında çok yaygın kullanılmaktadır. Yani biz bir paketi import ettiğimizde o paketin __init__.py - dosyası içerisinde from import deyimleriyle aslında o paketin ieçrisindeki modüllerin (dosyaların) içerisindeki isimler paket - isim alanına taşınmaktadır. Örneğin aslında pandas içerisinde yüzlerce dosyanın olduğu bir pakettir. Ancak biz sanki bütün - isimler pandas isim alanındaymış gibi onları kullanırız. Örneğin: - - import pandas - - s = pandas.Series([1, 2, 3, 4, 5]) - print(s) - - Aslında Series sınıfı pandas paketinin içerisindeki bir dosyanın içerisinde bulunmaktadır. Ancak from import deyimi ile pandas - paket isim alanına taşınmıştır. - - Yukarıdaki örnekte mypackage paketinin __init__.py dosyasında import as uygulamanın kısaltma bakımından bir faydasının olmayacağına - dikkat ediniz: - - # __init__.py - - print('__init__.py') - - import mypackage.a as a - - Burada a ismi yine paket ismiyle kullanılmak zorundadır. - -#------------------------------------------------------------------------------------------------------------------------ - 63. Ders 07/12/2022 - Carsamba -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - İç içe paketler de oluşturulabilmektedir. Bir paketin içerisindeki pakete "alt paket (subpackage)" de denilmektedir. - Yani paketlerin içerisinde modüller (python dosyaları) olabileceği gibi başka paketler de olabilmektedir. - - Şimdi yukarıda oluşturmuş olduğumuz mypackage ismli paketin içerisinde util isimli bir paket daha oluşturalım. util - paketinin içerisinde de __init__.py dosyası ve bir de "c.py" dosyası olsun. Bu dosyaların içeriği de şöyle olsun: - - # mypackage/util/__init__.py - - print('util.__init__.py') - - # mypackage/util/c.py - - print('this is mypackage.util.c module') - - def tar(): - print('tar') - - Örneğimizdeki dizin yapısı şöyledir: - - mypackage - __init__.py - a.py - b.py - util - __init__.py - c.py - - Şimdi bu alt paket içeisindeki "c.py" dosyasını import edecek olalım: - - import mypackage.util.c - - Burada her ne kadar biz c modülünü import etmek istiyorsak da aslında mypackage ve util paketleri de import edilmektedir. - Yani burada önce mypackage içerisindkei __init__.py dosyası, sonra "mypackage/util" içerisindeki __init__.py dosyası nihayet - "mypackage/util içerisindeki "c.py" dosyası çalıştırılacaktır. - - Aynı durum from import deyimi için de geçerlidir: - - from mypackage.util.c import tar - - Tabii biz dış paketin __init__.py dosyasında iç paketlerdeki isimleri de from import deyimi ile dış paketin isim alanına - taşıyabiliriz. Örneğin mypackage paketinin __init__.py dosyası şöyle olsun: - - print('__init__.py') - - from mypackage.a import * - from mypackage.b import * - from mypackage.util.c import * - - Burada "a.py", "b.py" ve "c.py" dosyalarının ieçrisindeki isimlerin hepsi sanki mypackage modülünün (paketinin) içerisindeymiş - gibi kullanılabilecektir. - - Pekiyi iç paketin __init__.py dosyası içerisinde nasıl import işlemi yapabiliriz? Biz iç pakette de import işlemi yaparken - yine en dıştan itibaren import edeceğimiz dosyayı niteliklendirmeliyiz. Yani örneğin mypackage dizini içerisindeki util - dizininindeki __init__.py dosyasında aşağıdaki gibi bir import işlemi yapamayız: - - import c - - aşağıdaki gibi de bir import işlemi yapamayız: - - import util.c - - Şöyle import işlemi yapabiliriz: - - import mypackage.util.c - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Modüllerin (yani .py dosyalarının) __all__ isminde özel bir global değişkenleri vardır. Bu global değişken dolaşılabilir - bir nesne biçiminde olmalıdır. Bu dolaşılabilir nesne string elemanlarından oluşur. Ancak pratikte genellikle bu __all__ - değişkeni programcı tarafından bir liste biçiminde oluşturulmaktadır. İşte "from import" deyimi ile *'lı import yapıldığında - yalnızca modülün __all__ değişkeninde belirtilen değişkenler dışarıya import edilir. Örneğin "x.py" isminde bir Python - dosyamız olsun ve bu dosyanın içeriği aşağıdaki gibi olsun: - - # x.py - - def foo(): - print('foo') - - def bar(): - print('bar') - - def tar(): - print('tar') - - a = 10 - b = 20 - c = 30 - - Normal olarak biz bu modülü *'lı import ettiğimizde foo, bar, tar, a, b, c değişken isimlerinin hepsini kullanabiliriz. Örneğin: - - from x import * - - foo() - bar() - tar() - - print(a) - print(b) - print(c) - - Şimdi x modülüne __all__ global değişkenini aşağıdaki gibi ekleyelim: - - __all__ = ['foo', 'bar', 'a', 'b'] - - def foo(): - print('foo') - - def bar(): - print('bar') - - def tar(): - print('tar') - - a = 10 - b = 20 - c = 30 - - Şimdi biz bu modülü *'lı bir biçimde import edersek yalnızca foo, bar, a ve b'yi kullanabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python dünyası diğer dillere ve framework'lere kıyasla nispeten zayıf dokümantasyon içermektedir. Bu zayıf dokümantasyon - diğer dillerden Python'a geçenler tarafından bazen şaşkınlıkla karşılanmaktadır. Zayıf dokümantasyon adeta bu dilde doğal - karşılanan bir durum haline gelmiştir. Pek çok Python programcısı yazdığı fonksiyonların, sınıfların ve modüllerin - dokümantasyonlarını kodun içerisine gömmektedir. Bunu sağlamak için Python'da "doküman yazıları (document strings)" denilen - bir dil özelliği kullanılmaktadır. Yani Python'da kodu yazan kişi aynı zamanda temel bir dokümantasyon da oluşturmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyon için doküman yazısı fonksiyonun hemen suit'inin başında (yani ikinci satırında) bir string olarak belirtilir. - Bu string tek tırnaklı ya da üç tırnaklı olabilir. Bilindiği gibi üç tırnaklı string'ler birden fazla satır üzerine - yazılabilmektedir. Örneğin: - - def square(a): - """square fonksiyonu parametresiyle aldığı değerin karesine geri döner.""" - return a * a - - Bir fonksiyonun doküman yazısı yorumlayıcı tarafından fonksiyon nesnesinin __doc__ isimli değişkeninin içerisine - yerleştirilmektedir. Dolayısıyla biz doküman yazısını __doc__ değişkeni yoluyla elde edebiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -def square(a): - """square fonksiyonu parametresiyle aldığı değerin karesine geri döner""" - return a * a - -result = square.__doc__ -print(result) - -#------------------------------------------------------------------------------------------------------------------------ - Python'da bazen programcılar bir fonksiyon hakkında bir açıklama bulamayabilirler. Bu tür durumlarda fonksiyonun __doc__ - elemanına başvurulabilir. Örneğin: - - >>> import math - >>> math.floor.__doc__ - 'Return the floor of x as an Integral.\n\nThis is the largest integer <= x.' - - Python'da built-in help isimli fonksiyon bir değişkenin ismini alıp ona ilişkin bilgileri ekrana yazdırmaktadır. Bu yazdırma - sırasında doküman yazıları da yazdırılmaktadır. Örneğin: - - >>> help(math.floor) - Active code page: 65001 - Help on built-in function floor in module math: - - floor(x, /) - Return the floor of x as an Integral. - - This is the largest integer <= x. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sınıflar için de sınıfların metotları için de doküman yazıları oluşturulabilir. Sınıflar için doküman yazısı oluşturulurken - yine yazı sınıfı oluşturan suit'in hemen başında (yani class anahtar sözcüğünün hemen alt satırında) bulundurulmalıdır. - - Örneğin: - - class Sample: - "Bu sınıf çok önemli işlemler yapan metotlara sahiptir." - - def foo(self): - "foo metodu falanca işi yapar." - - def bar(self): - "bar metodu filanca işi yapar." - - Yine bir sınıfın doküman yazısı sınıfın __doc__ isimli sınıf değişkeni biçiminde oluşturulmaktadır. Biz bu doküman - yazılarına sınıf isimleriyle ya da ilgili sınıf türünden değişkenlerle erişebiliriz. Örneğin: - - print(Sample.__doc__) - print(Sample.foo.__doc__) - print(Sample.bar.__doc__) - - s = Sample() - - print(s.__doc__) - print(sfoo.__doc__) - - Üç tırnaklı yazıların satırın başından itibaren boşluklu bir yazı oluşturacağına dikkat ediniz. Dolayısıyla ilgili - deyimin doküman yazısını __doc__ özniteliği ile eldeettikten sonra bunu yazdırmadan önce baştaki boşlukları atabilirsiniz. - Built-in help fonksiyonu zaten bu işlemleri yaparak doküman yazılarını düzgün bir biçimde yazdırmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -class Sample: - "Bu sınıf çok önemli işlemler yapan metotlara sahiptir." - - def foo(self): - "foo metodu falanca işi yapar." - - def bar(self): - "bar metodu filanca işi yapar." - -print(Sample.__doc__) -print(Sample.foo.__doc__) -print(Sample.bar.__doc__) - -s = Sample() - -print(s.__doc__) -print(s.foo.__doc__) -print(s.bar.__doc__) - -#------------------------------------------------------------------------------------------------------------------------ - Programcı isterse tüm modüle de doküman yazısı iliştirebilir. Modülün doküman yazıları hemen modülün ilk string'i olmak - zorundadır. Öneğin: - - # x.py - - """Bu modül falanca işleri yapar """ - - def foo(): - print('foo') - - def bar(): - print('bar') - - Biz yine modülün doküman yazısını modülün __doc__ değişkeni ile elde edebiliriz. Örneğin: - - import x - - print(x.__doc__) - - Kendi modülümüzün doküman yazısını ise doğrudan __doc__ değişkeni ile yazdırabiliriz. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Doküman yazıları için herkes tarafından kabul edilen bir standart yoktur. Değişik proje grupları doküman yazılarını - kendilerine özgü bir oluşturabilmektedir. Örneğin NumPy'ın doküman yazı standardına ilişkin bağlantı aşağıda verilmiştir: - - https://numpydoc.readthedocs.io/en/latest/format.html - - Google genel stili de aşağıda bağlantıda açıklanmıştır: - - https://google.github.io/styleguide/pyguide.html - - Tabii programcı kendi proje grubu tarafından benimsenmiş olan stili kullanabilir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bu bölümde gittikçe daha fazla kullanılır hale gelmekte olan "tür açıklamaları (type annotations)" konusu üzerinde - duracağız. Tür açıklamaları konusu Python'ın 3'lü versiyonlarıyla birlikte eklenmiş olan bir özelliktir. Bu özellik - Python2ın neredeyse her sürümünde genişletile genişletile bugünkü haline gelmiştir. Dolayısıyla kursumuzda güncel - son versiondaki tür açıklamaları üzerinde duracağız. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Python dinamik tür sistemine sahip bir programlama dili olduğu için değişkenlerin, fonksiyon parametrelerinin, fonksiyonların - geri dönüş değerlerinin türleri değişebilmektedir. Dinamik tür sistemine sahip programlama dillerinde en önemli sorunlardan - biri tür kontrolünün çalışma zamanı sırasında yapılabilmesidir. Örneğin bir fonksiyon yanlış türden bir argümanla çağrıldığında - problem kod çalışırken akış o noktaya geldiğinde ortaya çıkmaktadır. Bu da kodu yazanın çok dikkatli olmasını gerektirmektedir. - İşte tür açıklamaları bir değişkenin niyet edilen türünün program çalışmadan önce üçüncü parti araçlar tarafından kontrol - edilmesini sağlamak amacıyla dile eklenmiştir. Tür açıklamaları yorumlayıcı için bir direktif ya da kontrol sağlamamaktadır. - Yalnızca insanlar ve üçüncü parti statik analiz araçları için kontrol imkanları sunmaktadır. Başka bir deyişle tür açıklamaları - tamamen yorumlayıcı tarafından görmezden gelinmektedir. - - Aşağıdaki banner fonksiyonuna dikkat ediniz: - - def banner(s, ch='-'): - print(ch * len(s)) - print(s) - print(ch * len(s)) - - Bu fonksiyonun bir string ile çağrılması gerekir. Eğer bu fonksiyon bir string ile çağrılmazsa muhtemelen bir exception - oluşacaktır. Pekiyi dalgın bir programcı bu fonksiyonu aşağıdaki gibi int bir değerle çağıramaz mı? - - banner(123) - - İşte bu durumda yukarıda da belirttiğimiz gibi kod çalışırken exception oluşacaktır (çünkü int türü len fonksiyonuna sokulamaz). - Eğer biz yukarıdaki fonksiyonu örneğin bir listeyle çağırırsak exception oluşmaz ancak fonksiyon istediğimizi de yapmaz. - Aslında bu durum exception oluşmasından da kötüdür. - - Pekiyi biz bir Python programında yukarıdaki gibi hataları nasıl tespit edebiliriz? Bu tür hatalar yazılımın iyi bir - biçimde test edilmesiyle büyük ölçüde düzeltilebilmektedir. Örneğin "birim testleri (unit testing)" bu tür işlemlerin - test edilmesi için kullanılabilir. İşte tür uyumluluğu üçüncü parti statik analiz araçları tarafından da belirli koşullar - sağlanırsa tür açıklamaları sayesinde kontrol edilebilmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tür kontrolü için kullanılan statik analiz araçlarının en yaygınları şunlardır: mypy, Pytype, Pyright, Pyre. Biz kursumuzda - mypy kullanacağız. mypy programını şöyle kurabilirsiniz: - - pip install mypy - - Bu analiz araçlarının tür kontrollerini yapabilmesi için kodun "tür açıklamaları (type annotations)" ile oluşturulmuş - olması gerekir. Bu nedennle bizim tür açıklamalarının nasıl oluşturulacağını bilmemiz gerekmektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tür açıklamalarının genel biçimi şöyledir: - - : [= ] - - Aslında tür açıklamaları daha genel olarak düşünülmüştür. Yukarıdaki genel biçimde "ifade" yerine tür bilgisi yazılırsa - (int, str, float gibi tür isimleri Python'da birer ifadedir) tür açıklaması yapılmış olur. Aslında bu açıklamalar tür - açıklaması biçiminde olmak zorunda değildir. Ancak pratikte açıklamaların (annotations) en yaygın kullanımı tür açıklamaları - biçimindedir. Örneğin: - - a: int = 123 - b: float = 12.3 - - Yukarıda da belirttiğimiz gibi ':' atomundna sonra tipik olarak bir tür ismi getirilmektedir. Ancak tasarımda daha - genel bir yol izlenmiş ve buradaki ifadenin herhangi bir türden olabilmesi sağlanmıştır. Örneğin: - - a: 'ankara' = 123 - - Bu ifade sentaks olarak geçerlidir. Ancak tür açıklaması için kullanılan 'ankara' ifadesi mypy ya da diğer araçlar - tarafından tanınmayacaktır. Yani tür açıklamaları sentaks bakımından geniş bir biçimde tasarlanmıştır. Fakat mevcut - araçlar tür açıklamaları için tür isimlerini kullanmaktadır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Global ya da yerel değişkenlere tür açıklaması yazılırken onun ilkdeğer verilerek yaratılması zorunlu değildir. Örneğin: - - x: int - - x = 10 - print(x) - - x = 2.3 - print(x) - - Burada x değişkenin int türden olduğu belirtilmiştir. Biz bir değişkeni tür açıklamasıyla aşağıdaki gibi belirtmiş olalım: - - a: int - - Burada biz bu a değişkenini yaratmış değiliz. Yani a değişkenini henüz kullanamayız. Ancak Python yorumlayıcısı bunun - bir tür açıklaması olduğunu anlar ve bunun için herhangi bir error mesajı vermez. Tabii bir değişkeni yaratmadan aşağıdaki - gibi bir kullanım geçerli değildi: - - a - - Python'da bu tür etkisiz kodların oluşturulmasının yasak olmadığını anımsayınız. Ancak buradaki sorun a'nın yaratılmamış - olmasıdır. - - Yukarıda da belirttiğimiz gibi bir program çalıştırılırken Python yorumlayıcısı tür açıklamalarını dikkate almaz. Bu tür - açıklamaları üçüncü parti programlar tarafından (örneğin mypy) dikkate alınmaktadır. Yukarıdaki "sample.py" dosyasını mypy - ile şöyle işleme sokabiliriz: - - mypy sample.py - - Spyder IDE'sindeki IPython içerisinden de şu biçimde işleme sokabiliriz: - - !mypy sample.py - - Burada mypy şöyle bir çıktı oluşturmuştur: - - C:\Study\Python>mypy sample.py - sample.py:6: error: Incompatible types in assignment (expression has type "float", variable has type "int") - Found 1 error in 1 file (checked 1 source file) - - Tabii biz tür açıklaması yaparken genellkle aynı zamanda ona değer atayarak aynı zamanda değişkeni yaratabiliriz. Örneğin: - - x: int = 10 -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tür açıklamaları IDE'lere entegre edilmiş araçlar tarafından da dikkate alınabilmektedir. Örneğin PyCharm IDE'sinde - "Code/Inspect Code" menüsü ile tür kontrolü yapılabilir. Mypy aynı zamanda PyCharm IDE'sine bir plugin olarak eklenebilmektedir. - Bunun için "File/Settings/Plugins" sekmesine gelinit. Burada "mypy" seçilerek araç IDE'ye dahil edilir. Visual Studio - Code IDE'sine de bir plugin olarak mypy eklenebilmektedir. Maalesef henüz tür kontrolü yapmak için Spyder IDE'sine entegre - edilmiş bir araç yoktur. - - mypy programı başarısız olursa sıfır dışında bir exit kodu üretmektedir. Başarı durumunda mypy programının exit kodu 0'dır. - Örneğin: - - C:\Study\Python>mypy sample.py - sample.py:3: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] - Found 1 error in 1 file (checked 1 source file) - - C:\Study\Python>echo %errorlevel% - 1 - - Ancak örneğin: - - C:\Study\Python>mypy sample.py - Success: no issues found in 1 source file - - :\Study\Python>echo %errorlevel% - 0 - - Böylece build araçları ya da programcının oluşturduğu make dosyaları önce mypy programını çalıştırıp eğer bir hata varsa - programı python yorumlayıcına hiç vermeyebilirler. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Parametre değişkenlerine tür açıklaması yapılırken de aynı sentaks kullanılmaktadır. Örneğin: - - def banner(s: str, ch: str = '-'): - print(ch * len(s)) - print(s) - print(ch * len(s)) - - banner('ankara') - banner(123) - - Bu programı mypy programına sokalım: - - C:\Study\Python>mypy sample.py - sample.py:7: error: Argument 1 to "banner" has incompatible type "int"; expected "str" - Found 1 error in 1 file (checked 1 source file) - - Görüldüğü gibi mypy fonksiyonun yanlışlıkla int bir değerle çağrıldığını tespit edip bize bildirebilmiştir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Fonksiyonun geri dönüş değeri hakkında açıklama oluşturmak için -> sembolü kullanılmaktadır. Örneğin: - - def square(a: int) -> int: - return a * a - - Burada biz fonksiyonun parametresinin int türden, geri dönüş değerinin de int türden olması gerektiğini belirtiyoruz. - Şimdi "sample.py" programının aşağıdaki gibi olduğunu kabul edelim: - - def square(a: int) -> int: - return a * a - - result = square(1.2) - print(result) - - Programı mypy'a sokalım: - - C:\Study\Python>mypy sample.py - sample.py:4: error: Argument 1 to "square" has incompatible type "float"; expected "int" - Found 1 error in 1 file (checked 1 source file) - - Şimdi program şöyle olsun: - - def square(a: int) -> int: - return a * a - - result: str - - result = square(1) - print(result) - - Buradaki hata square'in geri dönüş değerinin str türünden olması gereken bir değişkene atanmasıdır. Bu kodu aşağıdaki - gibi mypy'a sokalım: - - C:\Study\Python>mypy sample.py - sample.py:6: error: Incompatible types in assignment (expression has type "int", variable has type "str") - Found 1 error in 1 file (checked 1 source file) - - Tabii yukarıdaki örnekten de gördüğünüz gibi eğer tür açıklaması yapılmamışsa değişken yaratılırken mymy ve üçüncü parti - statik analiz araçları herhangi bir hata rapor etmemktedir. Örneğin: - - def square(a: float) -> float: - return a * a - - result = square(10.2) - print(result) - - Burada biz result değişkeni şçin bir tür açıklaması yapmadık. Ancak square fonksiyonunun geri dönüş değerinin float türden - olması gerektiğini belirttik. Ancak result değişkenine atama için mypy bir hata rapor etmeyecektir. - - mypy programı da versiyondan versiyona genişletilmektedir. Örneğin artık biz bir değişkene ilk kez değer atayıp onu - yarattığımızda mypy sanki o değişken için atanan türe ilişkin tür açıklaması yapılmış gibi işlem uygulamaktadır. Örneğin: - - a = 10 - print(a) - - a = 3.14 - print(a) - - mypy sanki burada a için int açıklaması yapılmış gibi bir işlem uygulamaktadır. Dolayısıyla a değişkenine daha sonra float - bir değer atandığında bunu hata olarak rapor etmektedir: - - sample.py:4: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] - Found 1 error in 1 file (checked 1 source file) - -#------------------------------------------------------------------------------------------------------------------------ - - #------------------------------------------------------------------------------------------------------------------------ - Biz bir değişkenin kendi sınıfımız türünden olmasını da sağlayabiliriz. Yani tür açıklamalarında kendi sınıflarımızı da - kullanabiliriz. Örneğin: - - class Sample: - pass - - def foo(a: Sample): - print(a) - - s = Sample() - - foo(s) - - Burada foo fonksiyonu Sample sınıfı türünden parametre almaktadır. Biz onu başka türden bir argümanla çağırırsak mypy hata - verecektir. Tabii türemiş sınıf taban sınıf gibi de kullanılabildiği için biz buradaki foo fonksiyonuna Sample sınıfından - türetilmiş bir sınıf türünden değişken de geçebiliriz. Örneğin: - - class Sample: - pass - - class Mample(Sample): - pass - - def foo(a: Sample): - print(a) - - m = Mample() - - foo(m) - - Burada mypy herhangi bir hata mesajı vermeyecektir. - #------------------------------------------------------------------------------------------------------------------------ - -class Sample: - pass - -class Mample(Sample): - pass - -def foo(a: Sample): - print(a) - -m = Mample() - -foo(m) - -#------------------------------------------------------------------------------------------------------------------------ - Örneğin biz bir değişkenin list türünden olması gerektiğini benzer biçimde açıklayabiliriz: - - def foo(a: list): - pass - - Burada a parametre değişkeni, elemanları herhangi bir biçimde olan bir list nesnesini alabilir. Ancak istersek listenin - elemanlarının belli bir türden olmasını da sağlayabiliriz. Bunun için list isminden sonra köşeli parantezler içerisinde - ilgili tür ismi belirtilir. Örneğin: - - a: list[int] - - Burada a int elemanlardan oluşan bir liste olmalıdır. Örneğin: - - a = [1, 2, 'ali'] - - Böyle bir atama mypy tarafından hata olarak değerlendirilecektir. Örneğin: - - def foo(a: list[int]): - print(a) - - a = [10, 20, 30, 40, 50] - foo(a) - - b = [10, 20, 'ali', 'veli'] - foo(b) - - Burada foo fonksiyonu elemanları int türden olan bir listeyi parametre olarak almaktadır. Bu nedenle foo(b) çağrısı - için my hata rapor edecektir: - - sample.py:8: error: Argument 1 to "foo" has incompatible type "list[object]"; expected "list[int]" [arg-type] - Found 1 error in 1 file (checked 1 source file) - - mypy gibi araçların "statik analiz araçları" olduğuna dikkat ediniz. Bu tür statik kod analizi yapan araçlar programı - çalıştırarak bir kontrol yapamadığı için her türlü ihlali kontrol edememektedir. Örneğin: - - def foo(x): - x.append(1.2) - - a: list[int] - - a = [1, 2, 3, 4] - - foo(a) - - print(a) - - Burada foo fonksiyonunun a listesine ekleme yaptığını dolayısıyla kuralın ihlal edildiğini mypy gibi statik analiz araçları - genellikle tespit edememektedir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - list, tuple, dict gibi türlerin köşeli parantezlerle tür açıklamalarında kullanılabilmesi Python 3.9 ile eklenmiştir. - Python 3.8 ve öncesinde list, tuple ve dict sınıfları için tür açıklamaları typing modülü içerisisideki List, Tuple, - Dict isimli sınıflar kullanılarak yapılabiliyordu. Ancak Python 3.9 ile birlikte artık bu işlemler için orijinal list, - tuple ve dict sınıfları doğrudan kullanılabilir hale gelmiştir. typeing modülündeki List, Tuple ve Dict sınıfları orijinal - set, tuple ve dict sınıfları değildir. Bunlar yalnızca tür açıklamaları için bulundurulmuş olan sınıflardır. Tabii eski - sistem geçmişe doğru uyumu korumak için typeingmodülündeki bu sınıflar muhafaza edilmektedir. örneğin list sınıfı için - tür açıklamaları aşağıdaki gibi de yapılabilmektedir: - - from typing import List - - def foo(a: List[int]): - pass - - foo([10, 20]) - - Eski programlarda bu temel veri yapıları için typing modülündeki yukarıda belirttiğimiz özel isimlerin kullanıldığını - görürseniz şaşırmayınız. Ancak artık yeni programlarda doğrudan orijinal sınıf isimleri tercih edilmektedir. Örneğin: - - def foo(a: list[int]): - pass - - foo([10, 20]) - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Benzer biçimde set ve tuple sınıfları için de tür açıklamaları için kullanılabilmektedir. Örneğin: - - def foo(s: set): - pass - - Burada s set türünden olmalıdır. Örneğin: - - def bar(t: tuple): - pass - - Burada da t tuple türünden olmalıdır. - - Tabii tıpkı list örneğinde olduğu gibi aslında biz bu set ve tuple türlerinin elemanları hakkında da açıklama yapabiliriz. - Örneğin: - - def foo(s: set[int]): - pass - - Burada s int değerleri tutan bir küme olmalıdır. - - Demetlerde elemanların türleri sırasıyla tek tek belirtilebilmektedir. Örneğin: - - def foo(t: tuple[int, str]): - pass - - Burada t parametre değişkenine iki elemanlı demetler aktarılmalıdır. Bu demetlerin birinci elemanları int türden ikinci - elemanları str türünden olmalıdır. Örneğin: - - foo((10, 'ankara')) - - Şimdi fonksiyunu şöyle çağıralım: - - foo((10, 20)) - - Mypy şöyle bir hata verecektir: - - C:\Study\Python> mymy sample.py - sample.py:4: error: Argument 1 to "foo" has incompatible type "tuple[int, int]"; expected "tuple[int, str]" [arg-type] - Found 1 error in 1 file (checked 1 source file) - - tuple türü için genel bir eleman türü bu biçimde belirtilememektdir. Örnein: - - def foo(a: tuple[int]): - pass - - Burada a için yapılan açıklama "elemanları int türden olan demet" biçiminde bir açıklama değildir. "Tek bir elemanı olan, - onun da int türden olan demet" açıklamasıdır. - - set ve tuple türlerinin bu biçimde doğrudan kullanılması yine Python 3.9 ile birlikte mümkün hale getirilmiştir. Yukarıda da - belirttiğimiz gibi Python 3.8 ve aşağısında bunların yerine typing modülündeki Set ve Tuple sınıfları kullanılıyordu. Ancak - Python 3.9 ile birlikte artık bu sınıfların kullanılmasına gerek kalmamıştır. Örneğin: - - from typing import Set, Tuple - - def foo(s: Set[int]): - pass - - def bar(t: Tuple[int, str]): - pass - - foo({1, 2, 3}) - bar((10, 'ankara')) - - Örneğin: - - from typing import Set, Tuple - - def foo(s: Set[int]): - pass - - def bar(t: Tuple[int, str]): - pass - - s: Set[int] = {1, 2, 3} - t: Tuple[int, str] = 10, 'ankara' - - foo(s) - bar(t) - - Demetler için şöyle bir sentaks da eklenmiştir. Eğer demetleri belirtirken yalnızca tek tür belirtirsek ve sonra da ... (ellipsis) - getirirsek bu durum ilk belirttiğimiz türden olmak koşulu ile demetin istenildiği sayıda elemandan oluşabileceği anlamına gelir. - Örneğin: - - def foo(t: tuple[int, ...]): - pass - - Bu örnekte foo fonksiyonunun t parametre değişkeni tüm elemanları int olan bir demet almaktadır. Aşağıdaki çağrılar bu tür açıklamalarına - uygundur: - - foo((10, 20, 30)) - foo((10, 20)) - - Ancak aşağıdaki çağrılar tür açıklamalarına uygun değildir: - - foo((10.2, 20, 'ali')) - foo(('veli', 'selami')) - - Bu tür durumlarda boş demetler de soruna yol açmamaktadır. - - Köşeli parantez içerisinde birden fazla türden sonra ... kullanımının böyle bir anlamı yoktur. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Sözlüklerde de tür açıklamaları benzer biçimde dict sınıfı ile yapılabilmektedir. Örneğin: - - def foo(d: dict): - pass - - Bu durumda fonksiyonun d parametre değişkenine bir sözlük geçirilmelidir. Ancak sitenirse yine köşeli parantezler içerisinde - sözlüğün anahtar ve değer türleri ayrı ayrı belirtilebilir. Örneğin: - - def foo(d: dict[int, str]): - pass - - Burada sözlüğün anahtarları int, değerleri ise str türünden olmalıdır. Aşağıdaki çağrıda mypy bir hata rapor etmeyecektir: - - d = {10: 'ali', 20: 'veli', 30: 'selami'} - - foo(d) - - Yukarıda da belirttiğimiz gibi Python 3.8 ve öncesinde bu işlem typing modülü içerisindeki Dict sınıfı ile yapılıyordu. - Örneğin: - - from typing import Dict - - def foo(d: Dict[int, str]): - pass - - d = {10: 'ali', 20: 'veli', 30: 'selami'} - - foo(d) - - Ancak artık bu Dict sınıfına gerek kalmamıştır. - - Sözlüklerde tür açıklaması yapılırken yalnızca anahtar ya da yalnızca değer türü belirtilemez. Aşağıdaki tür açıklaması - mypy tarafından hata olarak rapor edilecektir: - - def foo(d: dict[str]): - pass - - Tabii Python yorumlayıcısı aslında tür açıklamalarını yalnızca sentaks bakımından denetlemektedir. Yukarıdaki açıklama - mypy tarafından bir hata olarak değerlendirilecek olsa da yorumlayıcı tarafından bir hata olarak değerlendirilmeyecektir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - typing modülündeki Any sınıfı tür açıklamalarında "herhangi bir tür olabilir" anlamına gelmektedir. Any kullanmak bazı - durumlarda gereksidir. Örneğin: - - def foo(a: Any): - pass - - Biz zaten burada tür açıklaması yapmasaydık da mypy tarafından a herhangi bir türü kabul edecekti. Yani aşağıdaki fonksiyon - yukarıdakiyle eşdeğerdir: - - def foo(a): - pass - - Örneğin: - - def bar(a: list[Any]): - pass - - Burada da aslında yalnızca list biçiminde tür açıklaması yapsaydık da değişen bir şey olmayacaktı: - - def bar(a: list): - pass - - Ancak bazı durumlarda Any gerçekten gerekebilmektedir. Örneğin: - - from typing import Any - - def foo(d: dict[int, Any]): - pass - - Burada artık foo fonksiyonun parametresi sözlük olmalıdır. Ancak sözlüğün anahtarları int olmak zorundayken değerleri - herhangi bir türden olabilir. Örneğin aşağıdaki çağrı tür açıklamalarına uygundur: - - foo({10: 'ali', 20: 100, 30: 2.3}) - - Örneğin: - - def foo(t: tuple[int, Any, float]): - pass - - Burada parametre değişkeni olan t için bizim üç elemanlı bir demet geçirmemiz gerekir. Bu demetin ilk elemanı int türden, - üçüncü elemanı float türden ancak ikinci elemanı ise herhangi bir türden olabilir. Dolayısıyla aşağıdaki çağrılarda mypy - bir hata rapor etmeyecektir: - - foo((100, 'ali', 12.4)) - foo((100, 200, 12.4)) - - Ancak aşağıdaki bir çağrıda mypy hata rapor edecektir: - - foo(('ankara', 'ali', 12.4)) - - Burada demetin ilk elemanı için verilen açıklamaya uyulmamıştır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Bir fonksiyonun geri dönüş değerinin olmadığı ya da None olduğunu belirtmek için tür açıklamalarında None kullanılmalıdır. - Örneğin: - - def foo() -> None: - pass - - Biz burada foo fonksiyonunu geri dönüş değeri varmış gibi kullanırsak mypy bu durumu error ile rapor edecektir. Örneğin: - - result = foo() - - Burada mypy şöyle bir error rapor eder: - - C:\Study\Python> mymy sample.py - sample.py:4: error: "foo" does not return a value - Found 1 error in 1 file (checked 1 source file) - - Ya da geri dönüş değeri None olarak açıklanmış bir fonksiyonu başka bir değerle geri döndürürsek de mypy yine hata - rapor edecektir. Örneğin: - - def foo(a: int) -> None: - if a < 0: - return 'a must not be negative' - - print('ok') - - Burada mypy şöyle bir hata rapor etmektedir: - - sample.py:3: error: No return value expected [return-value] - Found 1 error in 1 file (checked 1 source file) - - Tabii geri dönüş değeri de '|' operatörü ile seçeneklendirilebilir. Örneğin: - - import math - - def getroots(a: float, b: float, c: float) -> tuple[float, float]|None: - delta = b ** 2 - 4 * a * c - if delta < 0: - return None - - x1 = (-b + math.sqrt(delta)) / (2 * a) - x2 = (-b - math.sqrt(delta)) / (2 * a) - - return x1, x2 - - result: tuple[float, float]|None = getroots(1, 0, -4) - - if result is None: - print('kök yok') - else: - x1, x2 = result - print(f'x1 = {x1}, x2 = {x2}') - - Burada ikinci derece denklemin köklerini bulan getroots fonksiyonu ya elemanları float olan iki elemanlı bir - demet ile ya da None değeri ile geri dönmektedir. Fonksiyonun anımlamasına dikkat ediniz: - - def getroots(a: float, b: float, c: float) -> tuple[float, float]|None: - pass - -#------------------------------------------------------------------------------------------------------------------------ - - import math - - def getroots(a: float, b: float, c: float) -> tuple[float, float]|None: - delta = b ** 2 - 4 * a * c - if delta < 0: - return None - - x1 = (-b + math.sqrt(delta)) / (2 * a) - x2 = (-b - math.sqrt(delta)) / (2 * a) - - return x1, x2 - - result: tuple[float, float]|None = getroots(1, 0, -4) - - if result is None: - print('kök yok') - else: - x1, x2 = result - print(f'x1 = {x1}, x2 = {x2}') - -#------------------------------------------------------------------------------------------------------------------------ - typing modülündeki Optional ismi "ilgili türden ya da None (NoneType türünden)" anlamına gelmektedir. Örneğin: - - from typing import Optional - - a: Optional[int] - - Burada biz a'ya int ya None atayabiliriz. Ancak başka bir türden değer atayamayız. Örneğin: - - from typing import Optional - - def foo(a: int) -> Optional[int]: - if a > 0: - return a - - return None - - result: Optional[int] = foo(10) - print(result) - - Burada biz result değişkenine int türden ya da None atayabiliriz. - - Yukarıda yazmış olduğumuz getroots fonksiyonunda fonksiyonun geri dönüş değeri için tuple[float, float]|None açıklamasını - yapmıştık. Aynı açıklamayı Optional kullnarak Optional[tuple[float, float]] biçiminde de yapabilirdik. -#------------------------------------------------------------------------------------------------------------------------ - -import math -from typing import Optional - -def get_roots(a: float, b: float, c: float) -> Optional[tuple[float, float]]: - delta: float = b ** 2 - 4 * a * c - if delta < 0: - return None - - x1 = (-b + math.sqrt(delta)) / (2 * a) - x2 = (-b - math.sqrt(delta)) / (2 * a) - - return x1, x2 - -result: Optional[tuple[float, float]] = get_roots(1, 0, -4) -if result: - print(result[0], result[1]) -else: - print('Kök yok') - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişkene bir fonksiyon gibi çağrılabilecek (callable) bir değişken atanacaksa tür açıklamasında typing modülündeki - Callable sınıfı kullanılmaktadır. Örneğin: - - from typing import Callable - - def foo(f: Callable, *args, **kwargs): - f(*args, **kwargs) - - def bar(): - print('bar') - - class Sample: - def __call__(self, *args, **kwargs): - print('Sample.__call__') - - s = Sample() - - foo(bar) # geçerli, foo callable bir nesneyle çağrılmış - foo(s) # geçerli, foo callable bir nesneyle çağrılmış - - foo(100) # geçerli değil! mypy hata rapor edecek - - Örneğin: - - from typing import Callable - - def square(a: int) -> int: - return a * a - - def foo(f: Callable, val: int): - result = f(val) - print(result) - - foo(square, 10) # geçerli - - Burada bar fonksiyonun birinci parametresi çağrılabilen bir nesne, ikinci parametresi ise int bir nesne almaktadır. - Dolayısıyla burada mypy herhangi bir hata rapor etmeyecektir. - - Ancak istenirse değişkene atanacak çağrılabilen nesnenin parametrik yapısı ve geri dönüş değeri için de tür açıklaması - yapılabilmektedir. Bunun için Callable siminden sonra köşeli parantezler içerisinde "bir liste biçiminde parametre türleri, - sonra da geri dönüş değerinin türü" girilmelidir. Örneğin: - - from typing import Callable - - def foo(a: int, b: str) -> int: - return a + int(b) - - f: Callable[[int, str], int] - - Burada f değişkenine parametreleri sırasıyla int ve str olan geri dönüş değeri ise int olan çağrılabilen nesneler atanabilir. - Eğer buna uygun atama yapılmazsa mypy hata verecektir. Örneğin: - - f = foo - - result: int = f(10, 20) # tür açıklamasına uyulmamış - print(result) - - Burada tür açıklamasına uyulmamıştır. mypy şöyle bir hata rapor etmektedir: - - sample.py:22: error: Argument 2 to "foo" has incompatible type "int"; expected "str" [arg-type] - Found 1 error in 1 file (checked 1 source file) - - Örneğin: - - def square(a: int) -> int: - return a * a - - def foo(f: Callable[[int], int], val: int): - result = f(val) - print(result) - - foo(square, 10) - - Burada foo fonksiyonunun birinci parametresi "pareametresi int, geri dönüş değeri int olan" bir fonksiyon nesnesi almaktadır. - Örneğimizde bu tür açıklamasına uyulduğuna dikkat ediniz. -#------------------------------------------------------------------------------------------------------------------------ - -from typing import Callable - -def square(a: int) -> int: - return a * a - -def foo(f: Callable[[int], int], val: int): - result = f(val) - print(result) - -foo(square, 10) # geçerli -foo(len) # geçerli değil - -#------------------------------------------------------------------------------------------------------------------------ - typing modülü içerisindeki Union isimli sınıf birden fazla türü belirtmek için kullanılmaktadır. Örneğin: - - a: Union[int, str] - - Burada a'ya int ya da str türünden nesneler atanabilir. Yani a bu iki türden değeri de kabul etmektedir. Örneğin: - - from typing import Union - - a : Union[int, str] - - a = 10 - print(a) - - a = 'ali' - print(a) - - Burada a'ya yapılan atamalar tür açıklamasıyla uyumludur. Ancak örneğin: - - a = 2.3 - print(a) - - Burada a'ya yapılan atama tür açıklamasıyla uyumlu değildir. Örneğin: - - from typing import Union - - def foo(a: Union[int, str]): - pass - - Burada aşağıdaki iki çağrı tür açıklamalarıyla uyumludur. - - foo(10) - foo('ali') - - Ancak aşağıdaki çağrı tür açıklamalarıyla uyumsuzdur: - - foo(1.2) - - Daha önce de belirtitğimiz gibi Python 3.10 ile birlikte Union işlemi '|' operatörü ile de yapılır hale getirilmiştir. - Örneğin: - - def foo(a: int|str): - pass - - Bu açıklama aşağıdakiyle tamamen eşdeğerdir: - - def foo(a: Union[int, str]): - pass -#------------------------------------------------------------------------------------------------------------------------ - -from typing import Union - -a: Union[int, float] - -a = 100 # geçerli -print(a) - -a = 31.14 # geçerli -print(a) - -a = 'ankara' # geçersiz - -#------------------------------------------------------------------------------------------------------------------------ - Bazen bir değişkene ilişkin tür açıklamasını başka bir tür açıklaması ile değiştirmek isteyebiliriz. Aslında ikinci kez - tür açıklaması yapmak Python yorumlayıcısı tarafından geçerlidir. Ancak mypy bu durumda hata rapor etmektedir. Örneğin: - - a: int - - a = 100 - print(a) - - a: float # mypy hata rapor edecektir - - a = 3.14 - print(a) - - Bu durum Python yorumlayıcısı tarafındna geçerli olsa da mypy aşağıdaki gibi bir hata rapor edecektir: - - sample.py:6: error: Name "a" already defined on line 1 [no-redef] - sample.py:8: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] - Found 2 errors in 1 file (checked 1 source file) - - typing modülü içerisindeki cast fonksiyonu atanan değişken üzerinde tür açıklaması yapılmasına olanak sağlayan bir fonksiyondur. - cast fonksiyonu birinci parametre olarak bir tür açıklamasını, ikinci parametre olarak bir değeri almaktadır. Bu değer açıklanmış - bir değişken de olabilir. - - Biz cast fonksiyonu sayesinde bir değişkene hem bir açıklama yapıp hem de bir değer arayabiliriz. Örneğin: - - a: float = 12.2 - b = cast(int, a) - - Örneğimizde b değişkeni artık int olarak açıklanmış durumdadır. Burada önemli bir nokta cast fonksiyonun bir açıklama - amacıyla kullanılmasıdır. cast fonksiyonu bir dönüştürme yapmaz. Örneğimizde her ne kadar b int olarak açıklanmışsa da içerisinde yine 12.2 - değeri bulunacaktır. Yani biz burada b'ye hem bir float değer atamış olduk hem de a'yı int olarak açıklamış olduk. - - Yorumlayıcı cast işleminde dönüştürme yapmamaktadır. Örneğin: - - from typing import cast - - a: float = 12.2 - - b = cast(str, a) - - print(b, type(a)) # 12.2 - - Burada b str olarak açıklanmıştır ancak b içerisine float bir değer atanmıştır. -#------------------------------------------------------------------------------------------------------------------------ - -from typing import cast - -a: int - -a = 100 -print(a) - -a: cast(float, 3.14) # geçerli - -print(a) - -#------------------------------------------------------------------------------------------------------------------------ - Bir değişkenin dolaşılabilir bir tür olduğu typing modülündeki Itearable sınıfı ile açıklanmaktadır. Örneğin: - - from typing import Iterable - - def foo(a: Iterable): - pass - - Burada foo fonksiyonunun a parametre değişkeni dolaşılabilir bir nesne almalıdır. Aşağıdaki çağrılarda argüman olarak - dolaşılabilir nesneler kullanıldığı için tür açıklamasına uygundur. Örneğin: - - foo(range(10)) - foo([1, 2, 3, 4, 5]) - foo('ankara') - - Örneğin: - - def gen(): - for i in range(10): - yield i - - foo(gen()) - - Üretici nesneler de dolaşılabilir nesneler olduğu için çağrı tür açıklamasına uygundur. Ancak aşağıdaki çağrılarda - argümanlar dolaşılabilir nesne belirtmediği için tür açıklamasına uygun değildir: - - foo(10) - - class Sample: - pass - - s = Sample() - foo(s) -#------------------------------------------------------------------------------------------------------------------------ - -from typing import Iterable - -def foo(a: Iterable): - for x in a: - print(x) - -foo([1, 2, 3, 4, 5]) # geçerli -foo(range(10)) # geçerli -foo(123) # geçerli değil - -#------------------------------------------------------------------------------------------------------------------------ - Iterable sınıfı da köşeli parantezler içerisinde tür bilgisi alabilmektedir. Örneğin Iterable[int] biçiminde bir açıklama - değişkenin dolaşılabilir nesne alacağını ancak bu nesne dolaşıldıkça int nesnelerin elde edileceğini belirtmektedir. Örneğin: - - def foo(a: Iterable[int]): - pass - - Burada aşağıdaki gibi bir çağrı tür açıklamasına uygundur: - - foo([1, 2, 3, 4, 5]) - - Çünkü fonksiyona geçirilen dolaşılabilir nesnenin elemanları int türdendir. Ancak aşağıdaki bir çağrı tür açıklamasına uygun - değildir: - - foo([1, 2, 3., 4, 5.0]) - - Örneğin: - - a: Iterable[int|str] - - Burada a değişkenine elemanları int ya da float olabilen dolaşılabilir nesneler atanabilir. Dolayısıyla aşağıdaki atama tür - açıklamasına uygundur: - - a = [1, 2, 3, 4, 'ankara', 'izmit'] - - Örneğin: - - a: Iterable[tuple[int, str]] - - Burada a değişkenine demetlerden oluşan dolaşılabilir bir nesne atanabilir. Ancak bu demetlerin de ilk elemanları int ikinci elemanları - str türünden olmak zorundadır. Örneğin aşağıdaki atama tür açıklamasına uygundur: - - a = [(6, 'Anakara'), (26, 'Eskişehir'), (35, 'İzmir')] -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - Tür açıklamaları iç içe yapıldığı zaman biraz karmaşık bir görüntü oluşturabilmektedir. Bu biçimdeki tür açıklamalarını oluştururken - parantezlere dikkat ediniz. Örneğin: - - from typing import Callable, Iterable - - def foo(a: Iterable[tuple[Callable[[int, int], int], float]]): - pass - - Burada a değişkeninin parametresi dolaşılabilir bir nesne olmalıdır. Bu dolaşılabilir nesne bize iki elemanlı demet vermelidir. - Demetin birinci elemanı parametreleri int, int olan, geri dönüş değeri int olan bir çağrılabilir nesneden ikinci elemanı ise - float bir nesneden oluşmalıdır. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - - typing modülü içerisindeki Sequence sınıfı da string gibi liste gibi __getitem__, __len__ metotları bulunan "reversible - seqeunce" türlerini belirtmek için kullanılmaktadır. Anımsanacağı gibi tipi tipik "seuqence" türleri list, range, tuple, - str" türleridir. dict türünün __getitem__ metodu olsa da dict türü bir "seauence türü değildir. Örneğin: - - from typing import Sequence - - def foo(a: Sequence[int]): - pass - - Burada biz foo fonksiyonunu bir listeyle, bir demetle, string'le ya da bir range nesnesiyle çağırabiliriz. Bu durumda tür - açıklamasına uygun çağrılar oluşturulmuş oluruz. Örneğin: - - foo([1, 2, 3, 4, 5]) - foo('ali') - foo(range(10)) - - Ancak örneğin biz buradaki foo fonksiyonunu bir kümeyle ya da sözlükle çarırsak bu durum tür açıklamasıyla uyumlu olmaz: - - foo({1, 2, 3, 4, 5}) - foo({1: 'ali', 2: 'veli'}) - - Squence sınıfı için köşeli parantezler içerisinde tür de belirtilebilir. Örneğin: - - def foo(a: Sequence[int]): - pass - - Burada a parametre değişkenine biz int elemanlardna oluşan bir "sequence türü" geçirebiliriz. Örneğin: - - foo([1, 2, 3, 4, 5]) - foo(range(100)) - - Çağrıları tür açıklamasına uygundur. Ancak örneğin: - - foo(['ali', 'veli', 'selami']) - - çağrısı tür açıklamasına uygun değildir. -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------------------------------------------------ - - -14 2414 -13 2134 -12 1838 -11 1520 -10 1214 -09 929 -08 649 -07 443 -06 242 -05 83 -04 12 +/*-------------------------------------------------------------------------------------------------------------------------- + + C ve Sistem Programcıları Derneği + + Python Programlama Dili + + Sınıfta Yapılan Örnekler ve Özet Notlar + + Eğitmen: Kaan ASLAN + + Bu notlar Kaan ASLAN tarafından oluşturulmuştur. Kaynak belirtmek koşulu ile her türlü alıntı yapılabilir. + + (Notları okurken editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.) + + Son Güncelleme Tarihi: 15/06/2024 - Cumartesi + +---------------------------------------------------------------------------------------------------------------------------*/ + +#------------------------------------------------------------------------------------------------------------------------ + 1. Ders 15/06/2024 - Cumartesi +#------------------------------------------------------------------------------------------------------------------------ + + Genel tanıtım yapıldı ve katılımcılarla tanışıldı. + +#------------------------------------------------------------------------------------------------------------------------ + 2. Ders 29/06/2024 - Cumartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python ("pay(th)ın, pay(th)an " diye okunuyor) yüksek seviyeli genel amaçlı prosedürel, fonksiyonel ve nesne yönelimli + programlama modellerini destekleyen çok modelli (multiparadigm) bir programlama dilidir. Dil Guido van Rossum + ("guido ven rasım" biçiminde okunuyor) isimli Hollandalı programcı tarafından 1991 yılında tasarlanmıştır. (HTTP protokolünün + yani WWW'nin de aynı yıl tasarlandığını, C ve Sistem Programcıları Derneği'nin de 1993'te kurulduğunu anımsayınız.) + + Temel olarak Python dili yorumlayıcılarla kullanılmaktadır. Python yorumlayıcıları pek çok işletim sistemi için port + edilmiştir. Dolayısıyla biz Windows, Mac OS X ve Linux sistemlerinde Python Programlama Dilinde uygulamalar geliştirebiliriz. + + Python dinamik tür sistemine (dynamic type system) sahip bir programlama dilidir. Dinamik tür sistemine sahip programlama + dillerinde değişkenlerin değil nesnelerin türleri vardır. Başka bir deyişle bu dillerde bir değişkene herhangi bir + türden değer atayabiliriz. Farklı bir türden değer atananan kadar artık o değişken o türden olur. + + Python geniş bir standart kütüphaneye sahip bir programama dilidir. Standart kütüphanesi içerisinde pek çok farklı + konuya ilişkin hazır birtakım öğeler bulunmaktadır. Bu durum Python dünyasında "bataryası içerisinde (batteries + included)" biçimind esprili bir deyişle ifade edilmektedir. + + Python öğrenilmesi nispeten kolay bir programlama dilidir. Bu nedenle asıl alanı programlama olmayan kişilerin + özellikle tercih ettiği dillerdendir. Python son yıllarda büyük bir sıçrama yapmış ve dünyanın neredeyse en popüler + programala dili haline gelmiştir. Kursun yapıldığı tarihlerde Python Tiobe Index'te birinci sıradadır. + + Python diğer derleme dillerine (compiled languages) göre nispeten tavaş bir dildir. Bu yavaşlık pek çok uygulama alanında + önemli bir dezavantaj oluşturmamaktadır. Ancak alçak seviyeli ve etkin kodlamanın gerektiği alanlarda Python'un + kullanılması genellikle tavsiye edilmemektedir. + + Python genel amaçlı (general purpose) bir programlama dilidir. Yani Python pek çok alanda kullanılabilen bir dildir. + Bazı düşük seviyeli uygulamalarda Python bir prototip dil olarak da kullanılabilmektedir. (Yani bazı programların + denemeleri Python'da yapılıp nihai ürün kodlamaları C/C++ gibi dillerde yapılabilmektedir.) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Guido van Rossum Python'ın tasarımına ve gerçekleştirilmesine 1989 yılının sonlarında başlamıştır. Bu tarihten itibaren + dilin pek çok versiyonu oluşturulmuştur. Aşağıda versiyonların çıkış tarihlerini görüyorsunuz: + + Aralık 1989 Tasarım ve gerçekleştirime başlandı + 1990 İlk versiyonlara içsel numnaralar verilmiştir + Şubat 1991 0.9 + Ocak 1994 1.0 + Ekim 1994 1.1 + Ekim 1995 1.3 + Ekim 1996 1.4 + Ocak 1998 1.5 + Eylül 2000 1.6 + Ekim 2000 2.0 + Nisan 2001 2.1 + Aralık 2001 2.2 + Temmuz 2003 2.3 + Kasım 2004 2.4 + Eylül 2006 2.5 + Ekim 2008 2.6 + Temmuz 2008 2.7 + Aralık 2008 3.0 + Haziran 2009 3.1 + Şubat 2011 3.2 + Eylül 2012 3.3 + Mart 2014 3.4 + Eylül 2015 3.5 + Aralık 2016 3.6 + Haziran 2018 3.7 + Mart 2019 3.7.3 + Ekim 2019 3.8.3 + Ekim 2020 3.9 + Kasım-2022 3.10 + Mart-2022 3.10.4 + Ekim 2022 3.11.9 + Ekim 2023 3.12.4 + Ekim 2024 3.13 (prerelase) + + Kursun yapıldığı tarihteki Python'ın en son sürümü 3.12.4'tür. Bu sürüm Aralık 2023'te piyasaya sürülmüştür. Python + Programlama Dilinin geliştirilmesi 2001'den bu yana "Python Software Foundation" isimli kurum tarafından yapılmaktadır + (www.python.org). +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python Prgramalama Dili ile ilgili iki önemli resmi doküman vardır: + + 1) Python Language Reference + 2) Python Standard Library + + Python Language Reference isimli doküman dilin resmi (formal) tanımlamasını içermektedir. Yani bu doküman dili anlatmaktadır. + Tabii bu dokümanın amacı bilmeyenlere Python'u öğretmek değildir. Dilin özelliklerini eksiksiz bir biçimde açıklamaktır. + Dokümana şu bağlantıdan ulaşabilirsiniz: + + https://docs.python.org/3/reference/index.html + + Python Standard Libaray dokümanı ise ismi üzerinde Python'un standart kütüphanesinin resmi dokümanıdır. Bu dokümanın + amacı standart kütüphanenin öeğelerini eksiksiz bir biçimde açıklamaktır. Bu dokümana da aşağıdaki bağlantıdan + ulaşabilirsiniz: + + https://docs.python.org/3/library/index.html + + Python Dilinin sürdürümü Python Software Foundation (python.org) tarafından yapılmaktadır. Sürdürüm sürecinde öneriler + ve yeni özellikle "PEP (Python Enhancement Proposals)" isimli dökümanlar biçiminde arşivlenmektedir. PEP dokğmanlarına + aşağıdaki bağlantıdan erişebilirsiniz: + + https://peps.python.org/ +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda de belirttiğimiz gibi Python standardizasyon komiteleri tarafından resmi olarak standardize edilmiş bir dil + değildir. Dilin sentaks ve semantik özellikleri "Python Language Reference" isimli dokümanlarda tanımlanmıştır. Python + dünyasında "gerçekleştirim (implementation)" denildiğinde Python yorumlayıcıları ve kütüphaneleri anlaşılmaktadır. + Python dilinin pek çok gerçekleştirimi (implementation) vardır. Yukarıda da belirttiğimiz gibi Python yorumlayıcı + (interpreter) ile çalışılan bir dildir. Biz kodumuzu derleyiciye sokarak değil yorumlayıcıya sokarak çalıştırırız. + Python için pek çok yorumlayıcı yazılmıştır. Bu yorumlayıcıların bazıları bağımsız olarak sıfırdan gerçekleştirilirken + bazıları ise bazı diğer gerçekleştirimler temel alınp onlardan klonlanarak gerçekleştirilmiş durumdadır. + + Python'un en önemli ve en çok kullanılan gerçekleştirimi "Python Software Foundation" tarafından sürdürülen (ilk versiyonları + Guido van Rossum tarafından yazılmıştır) olan CPython isimli gerçekleştirimdir. Bu gerçekleştirim doğrudan "python.org" + sitesinden indirilebilir. Biz de kursumuzda CPython yorumlayıcısı ve kütüphanelerini kullanacağız. CPython C Programlama + Dili kullanılarak gerçekleştirilmiştir. CPython arakod tabanlı bir çalışma sistemine sahiptir. İleride ele alınacağı gibi + CPython'da import edilen modüller önce yorumlayıcı tarafından "Python bytecode" denilen bir ara koda dönüştürülmekte, + daha sonra da bu arakod yorumlanarak çalıştırılmaktadır. CPython "cross platform" bir yorumlayıcı sistemidir. Windows, + Mac OS X ve Linux sistemlerinde kullanılan sürümleri vardır. + + Jython (cay(th)ın, cay(th)an biçiminde okunuyor) isimli yorumlayıcı sistem Java'da yazılmıştır. Jython'da yazılmış olan + programlar Java sınıflarını doğrudan kullanabilirler. Örneğin Jython da GUI uygulamaları Java'nın AWT, Swing ya da SWT + kütüphaneleri kullanılarak gerçekleştirilebilmektedir. Jython yorumlayıcısı çıktı olarak "Java Bytecode" üretmektedir. + Üretilen bu çıktı Java çalıştırma ortamı olan JVM (Java Virtual Machine) tarafından çalıştırılabilemektedir. Jython ve + CPython gerçekleştirimleri arasında dil bakımından küçük farklılıklar vardır. + + IronPython gerçekleştirimi Jython gerçekleştiriminin .NET (genel ismiyle CLI) versiyonu gibidir. IronPython C# Programlama + Dili kullanılarak yazılmıştır. Visual Studio IDE'si için de "Python Tools for Visual Studio" isminde bir eklentisi de vardır. + IronPython CLI (Common Language Infrastructure) arakodu üretmektedir. Üretilen bu arakod .NET'in (ya da Mono projesinin) + CLR ortamı tarafından çalıştırılmaktadır. + + PyPy önemli Python gerçekleştirimlerinden biridir. PyPy üretilen ara kodu yorumlayıcı olarak değil JIT Derlemesi (Just + in Time Compilation) yaparak çalıştırır. + + Kursumuzda Python yorumlayıcısı olarak klasik CPython kullanılacaktır. Ayrıca yukarıdakiler dışında Python'ın daha + pek çok gerçekleştirimi de vardır. Bu gerçekleştirimlerin listesini https://wiki.python.org/moin/PythonImplementations + sitesinden görebilirsiniz. Bu yüzden PyPy standard Python gerçekleştirimi olan CPython'dan daha yüksek bir çalışma + zamanı performansına sahiptir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python dağıtımları belli bir Python gerçekleştirimi temel alınıp onlara çeşitli araçlar eklenerek oluşturulmuş paketlerdir. + Yani Python dağıtımları hem Python yorumlayıcılarını hem de uygulama geliştirmeye yardımcı olabilecek birtakım araçları + barındırmaktadır. Burada en çok kullanılan bazı Python dağıtımları üzerinde duracağız. + + CPython dağıtımı kendi içerisinde CPython gerçekleştirimini, çeşitli kütüphaneleri ve IDLE (Integrated Development and + Learning Environment) isimli basit bir IDE'yi barındırmaktadır. Biz de kursumuzun başlangıç bölümlerinde CPython dağıtımını + kullanacağız. + + Continuum Analytics isimli firma tarafından (bu firmanın ismi 2017'de Anaconda olarak değiştirilmiştir açık kaynak kodlu + (dolayısıyla da ücretsiz) olarak dağıtılan Anaconda CPython'dan sonra en çok kullanılan dağıtımlardan biridir. Anaconda + yorumlayıcı sistem olarak CPyton kullanmaktadır. + + ActiveState firması (bu firma aynı zamanda Komodo IDE'sinin üreticisidir) tarafından geliştirilmiş ActivePython dağıtımı + en yaygın kullanılan dağıtımlardan biridir. ActivePython dağıtımının ücretli ve "Community Edition" ismiyle ücretsiz + sürümleri de vardır. ActivePython yorumlayıcı sistem olarak CPython gerçekleştirimini kullanmaktadır. + + Diğer Python dağıtımlarına https://wiki.python.org/moin/PythonDistributions sitresinden göz gezdirebilirsiniz. Kursumuzda + Python dağıtımı olarak CPython ve Anaconda kullanılacaktır. + + Bir gerçekleştirim ya da bir dağıtım olmayan Python IDE'leri de vardır. Örneğin Intelli J IDEA firmasının PyCharm isimli + IDE'si sıkça kullanılmaktadır. Bizde kursumuzda bazen bu IDE'yi kullanacağız. + + O halde bu kurs için şu yazılımların indirilerek kurulması gerekmektedir: + + 1) CPython dağıtımı + 2) Anaconda Dağıtımı + 3) PyCharm IDE'si +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 3. Ders 30/06/2024 - Pazar +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir bilgisayar mimarisi çok kabaca üç bileşenden oluşur: CPU, RAM ve Disk. Bilgisayarda tüm işlemlerin yapıldığı entegre + devre biçiminde üretilmiş çiplere "mikroişlemci" ya da kavramsal olarak "CPU (Central Processing Unit)" denilmektedir. CPU + elektriksel olarak RAM denilen bellek ile bağlantı halindedir. Böyle CPU’nun doğrudan elektriksel olarak bağlantı halinde + olduğu belleklere kavramsal olarak "birincil bellek (primary memory)" ya da "ana bellek (main memory)" de denilmektedir. + Ana bellekler entegre devre modülleri biçiminde üretilmektedir. Bu fiziksel modüller RAM biçiminde isimlendirilmektedir. + CPU çalışırken sürekli RAM ile iletişim halindedir. Oradan bilgileri çeker, işler ve oraya geri yazar. Programlama + dillerindeki değişkenler RAM’de yaratılmaktadır. Bilgisayarın elektriğini kestiğimizde CPU durur. RAM’in içerisindeki + bilgiler de kaybolur. Bilginin kalıcılığını sağlamak için diskler kullanılmaktadır. Diskler manyetik temelli elekromekanik + biçimde olabileceği gibi tamamen yarı iletkenlerle (flash EPROM, SSD de denilmektedir) de gerçekleştirilebilmektedir. + Eletromekanik olarak üretilmiş disklere kişisel bilgisayarlarda "hard disk" de denilmektedir. Bugün artık "flash EPROM" + biçiminde üretilen SSD’ler hard disklerin yerini almaya başlamıştır. Ancak kavramsal olarak hard diskler, SSD’ler, CD + ve DVD ROM’lar, memory sticklere "ikincil bellek (secondary memory)" denilmektedir. Disk kavramı da çoğu kez bunların + hepsini içerecek biçimde kullanılmaktadır. + + Yukarıdada belirttiğimiz gibi diskler bilgisayar kapatıldığında bilgilerin "dosya" kavramı biçiminde kalıcı olarak + saklanması amacıyla kullanılmaktadır. Programlar diskte değil ana bellekte (RAM'de) çalıştırılmaktadır. Biz bir programı + çalıştırmak istediğimizde işletim sistemi programı diskten ana belleğe (RAM'e) yükler ve ana bellekte çalıştırı. + + Programlama dillerindeki tüm değişkenler (nesneler) RAM'de tutulmaktadır. Örneğin aşağıdaki gibi bir kod çalışacak olsun: + + a = b + c; + + Burada a, b ve c RAM'dedir. Bu işlemin yapılabilmesi için önce b ve s değişkenlerinin içerisindeki değerlerin CPU'ya + çekilmesi gerekir. CPU'ya çekilen bu değerler CPU'daki mantık devreleri tarafından toplama işlemine sokulmaktadır. + Sonuç yine CPU tarafından a'ya atanacaktır. Tabii aslında bu işlemlerin bu biçimde yapılacağı da "makine komutları + (machine instruction)" oluşturulmaktadır. Hem değişkenler hem de programın çalışan kodu RAM'de bulunmaktadır. CPU + önce RAM'den komutu okumakta (fetch işlemi) sonra komutu anlamlandırmakta sonra da gereklli işlemleri yapmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İşletim sistemi makinenin donanımını yöneten, makine ile kullanıcı arasında arayüz oluşturan temel bir sistem programıdır. + İşletim sistemi olmasa daha biz bilgisayarı açtığımızda bile bir şeyler göremeyiz. İşletim sistemleri iki katmandan + oluşmaktadır. Çekirdek (kernel), makinenin donanımını yöneten kontrol kısmıdır. Kabuk (shell) ise kullanıcıyla arayüz + oluşturan kısımdır. (Örneğin Windows'ta masaüstü kabuk görevindedir. Biz çekirdeği bakarak göremeyiz.) Tabii işletim + sistemi yazmanın asıl önemli kısmı çekirdeğin yazımıdır. Çekirdek işletim sistemlerinin motor kısmıdır. + + Masaüstü (desktop) bilgisayar denildiğinde "kasası olan klasik PC tarzı bilgisayarlar", "notebook'lar", "küçük gövdeli" + bilgisayarlar kastedilmektedir. Masaüstü bilgisayarlarında kullanılan üç önemli işletim sistemi şunlardır: + + 1) Windows (Microsoft) + 2) macOS (Apple) + 3) Linux (Open Source) + + Masaüstü bilgisayarların %99'una bu işletim sistemleri yüklüdür. Windows kursun yapıldığı zamanlarda %75 civarında, + macOS %15 civarında ve Linux %3 diğerleri de %5 civarında bir kullanına sahiptir. Pek çok masaüstü işletim sistemi + (desktop operating systems) artık neredeyse ortadan kalkmış gibi görünmektedir. + + Günümüzde mobil aygıtlar çok yoğun kullanılmaktadır. Örneğin Internet'e bağlı aygıtların büyük kısmı mobil aygıtlardan + oluşmaktadır. Günümüzde iki önemli mobil işletim sistemi yaygın kullanılmaktadır: + + 1) Android (Open Source) + 2) IOS (Apple) + + Android işletim sistemi Linux çekirdeğinin kaynak kodları kullanılarak kendine özgü bir biçimde Google sponsorluğunda + tasarlanmıştır ve açık kaynak kodludur. Bu nedenle pek çok akıllı telefon üreticisi olan firma Andrid işletim sistemini + kullanmaktadır. IOS (Iphone Operating System) Apple firmasının macOS işletim sistemlerini temel alarak geliştirdiği mobil + işletim sistemidir. Eskiden Apple firmasının mobil aygırlarının hepsi için aynı işletim sistemi kullanılıyordu. Ancak + zamanla Apple bunların arasında bazı farklar koyarak her donanım için işletim sisteminin ismini de değiştirdi. Örneğin + IPad aygıtlarında kullanılan işletim sistemine Apple bir süredir "IPad-OS" demektedir. Bugün Android sistemleri %70 + civarında IOS sistemleri %30 civarında bir pazar payına sahiptir. + + Bazen mikronetleyicilerin kullanıldığı uygulamalarda prgramcılar hiçbir işletim sistemi olmadan çalışabilecek biçimde + programlar yazabilmektedir. Bunlara "bare metal" programlar da denilmektedir. + + Python Programlama Dili temel olarak işletim sistemlerinin yüklü olduğu aygıtlarda kullanılabilecek bir dildir. Bare + metal programlarda Python kullanımı çok sınırlıdır. Bu tür oratamlar için "micro python" gerçekleştiriminden faydalanılmaktadır. + + İşletim sistemlerinin bir kısmı açık kaynak kodlu bir kısmı da mülkiyete bağlıdır. Örneğin Linux, BSD açık kaynak kodlu + olduğu halde Windows mülkiyete sahip bir işletim sistemidir. macOS sistemlerinin çekirdeği açıktır (buna Darwin deniyor) + ancak geri kalan kısmı kapalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Asıl amacı bilgisayar olmayan fakat bilgisayar devresi içeren sistemlere genel olarak gömülü sistemler denilmektedir. + Örneğin elektronik tartılar, biyomedikal aygıtlar, GPS cihazları, turnike geçiş sistemleri, müzik kutuları vs. birer + gömülü sistemdir. Gömülü sistemlerde en çok kullanılan programlama dili C'dir. Ancak son yıllarda Raspberry Pi gibi, + BeagleBoard gibi, Banana Pi gibi, Orange Pi gibi güçlü ARM işlemcilerine sahip kartlar çok ucuzlamıştır ve artık + gömülü sistemlerde de doğrudan kullanılır hale gelmiştir. Bu kartlar tamamen bir bilgisayarın işlevselliğine sahiptir. + Bunlara genellikle Linux işletim sistemi ya da Android işletim sistemi yüklenir. Böylece gömülü yazılımların güçlü + donanımlarda ve bir işletim sistemi altında çalışması sağlanabilmektedir. Örneğin Raspberry Pi’a biz Mono’yu yükleyerek + C#’ta program yazıp onu çalıştırabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir programlama dilinde yazılmış olan programı eşdeğer olarak başka bir dile dönüştüren programlara çevirici programlar + (translators) denilmektedir. Çevirici programlarda dönüştürülmek istenen programın diline kaynak dil (source language), + dönüşüm sonucunda elde edilen programın diline de hedef dil (target/destination language) denir. Örneğin: + + c# ----> Çevirici Program ----> vb + + Burada kaynak dil C#, hedef dil VB'dir. + + Eğer bir çevirici programda hedef dil aşağı seviyeli bir dil ise (saf makine dili, arakod ve sembolik makine dilleri + alçak seviyeli dillerdir) böyle çevirici programlara derleyici (compiler) denilmektedir. Her derleyici bir çevirici + programdır fakat her çevirici program bir derleyici değildir. Bir çevirici programa derleyici diyebilmek için hedef + dile bakmak gerekir. Örneğin sembolik makine dilini saf makina diline dönüştüren program da bir derleyicidir. + + Bazı programlar kaynak programı alarak hedef kod üretmeden onu o anda çalıştırırlar. Bunlara yorumlayıcı (interpreter) + denilmektedir. Yorumlayıcılar birer çevirici program değildir. Yorumlayıcı yazmak derleyici yazmaktan daha kolaydır. + Fakat programın çalışması genel olarak daha yavaş olur. Yorumlayıcılarda kaynak kodun çalıştırılması için onun başka + kişilere verilmesi gerekir. Bu da kaynak kod güvenliğini bozar. + + Bazı diller yalnızca derleyicilere sahiptir (C, C++, C#, Java gibi). Bazıları yalnızca yorumlayıcılara sahiptir + (PHP, Perl gibi). Bazılarının hem derleyicileri hem de yorumlayıcıları vardır (Basic, Swift, Python gibi). Genel + olarak belli bir alana yönelik (domain specific) dillerde çalışma yorumlayılar yoluyla yapılmaktadır. Genel amaçlar + diller daha çok derleyiciler ile derlenerek çalıştırılırlar. + + Derleyiciler aşağı seviyeli kod ürettiğinden dolayı derleyiciler tarafından üretilen kodlar hızlı bir biçimde doğrundan + çalıştırılmaktadır. Ancak yorumlayıcılar her defasında kaynak kodu yeniden okuyup onu anlamlandırırlar. Yorumlayıcılarla + bir program aslında dolaylı bir biçimde çalıştırılmaktadır. Dolayısıyla yorumlayıcılarla çalışmak genel olarak + derleyicilerle çalışmaktan daha yavaş olma eğilimindedir. Yorumlayıcıların nasıl çalıştığı kişiler tarafından zor + anlaşılabilmektedir. Aşağıdaki gibi bir Python programı olsun: + + x = 10 + print(x) + + Yorumlayıcı aslında C gibi bir dilde yazılmış doğal kodlu bir programdır. Bu program yukarıdaki Python kodunu satır + satır okuyabilir. Orada programcı ne yapmak istiyorsa onun yapılmasını sağlayabilir. Örneğin birinci satırda x = 10 + işleminde C'de yazılmış program kendi içerisinde x için bir alan tahsis edip oraya 10 yerleştirebilir. İkinci satırda + ise bu alan yerleştirilmiş olan değeri ekrana yazdırabilir. Görüldüğü gibi bu örnekte aslında işlemci tarafındanm C + programı çalıştırılmaktadır. Bu C programı Python dosyasını okuyarak onu adım adım çalıştırmaktadır. O halde burada + kod aslında doğrudan değil dolaylı bir biçimde çalıştırılmaktadır. Bu da çalışmanın yavaş olmasına yol açmaktadır. + Tabii günümüzde yorumlayıcılar bu kadar basit bir biçimde yazılmamaktadır. Kodun hızlı bir biçimde çalıştırılması + için pek çok tekniği kullanmaktadır. + + Yorumlayıcı dillerinde yazılan programlar "cross platform" çalışma eğilimindedir. Örneğin bir Python kodu "eğer + ilgili platform için bir Python yorumlayıcısı yazılmışsa" o platformda da çalıştırılabilir. Oysa derleme dillerinde + eğer üretilen kod o işlemciye ve işletim sistemine özgüyse o kodun başka bir platforma götürülerek çalıştırılması + mümkün olmaz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Alçak seviyeli dillerden yüksek seviyeli dillere dönüştürme yapan (yani derleyicilerin yaptığının tam tersini yapan) + yazılımlara “decompiler” denilmektedir. Örneğin C#’ta yazılıp derlenmiş olan .exe dosyadan yeniden C# programı oluşturan + bir yazılım “decompiler”dır. Saf makine dilini decompile etmek neredeyse mümkün değildir. Ancak .NET’in arakodu + olan “CIL (Common Intermediate Language)” ve Java’nın ara kodu olan “Java Byte Code” kolay bir biçimde decompile + edilebilmektedir. C#’ta derlenmiş bir .exe dosyayı yeniden C#’a dönüştüren pek çok decompiler vardır (örneğin + Salamander, Dis#, Reflector, ILSpy gibi). İşte bu tür durumlar için C# ve Java programcıları kendiileri bazı önlemler + almak zorundadırlar. Ancak C, C++ gibi doğal kod üreten derleyicilerin ürettiği kodlar geri dönüştürülememektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 4. Ders 06/07/2024 - Cumartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Derleyiciler ve yorumlayıcılar komut satırından çalıştırılan programlardır. Bir programlama faaliyetinde program editör + denilen bir program kullanılarak yazılır. Diske save edilir. Sonra komut satırından derleme ya da yorumlama yapılır. + Ancak bu yorucu bir faaliyettir. İşte yazılım geliştirmeyi kolaylaştıran çeşitli araçları içerisinde barındıran (integrated) + özel yazılımlara IDE (İngilizce "ay di i" biçiminde okunuyor) denilmektedir. IDE'lerin editörleri vardır, menüleri vardır + ve çeşitli araçları vardır. IDE'lerde derleme ya da yorumlama yapılırken derlemeyi ya da yorumlamayı IDE yapmaz. + IDE kendi içerisinde dış dünyada bulunan derleyiciyi ya da yorumlayıcı çalıştırır. IDE yardımcı bir araçtır, mutlak + gerekli bir araç değildir. + + Microsoft'un ünlü IDE'sinin ismi “Visual Studio”dur. Apple'ın “X-Code” isimli bir IDE'si vardır. Bunların dışında başka + şirketlerin malı olan ya da “open source” olan pek çok IDE mevcuttur. Örneğin “Eclipse” ve “Netbeans” yaygın kullanılan + cross-platform “open source” IDE'lerdir. Linux altında Mono'da “Mono Develop” isimli bir IDE tercih edilmektedir. Bu + IDE’nin Windows versiyonu da vardır. + + Microsoft'un ünlü IDE'sinin ismi “Visual Studio”dur. Apple'ın “X-Code” isimli bir IDE'si vardır. Bunların dışında başka + şirketlerin malı olan ya da “open source” olan pek çok IDE mevcuttur. Örneğin "Eclipse" yaygın kullanılan cross-platform + "open source" bir IDE'dir. VisualStudio'nun "Express Edition" ya da "Community Edition" isimli bedava bir sürümü de vardır. + Bu bedava sürüm bu kurstaki gereksinimleri tamamen karşılayabilir. Visual Studio'nun bugün için son versiyonu "Visual + Studio 2022"dir. + + Python dünyasında da çeşitli IDE'lerden faydalanılmaktadır. Bazı Python IDE'leri zaten bazı dağıtımların içerisinde + onların bir parçası biçiminde bulunurlar. Bazıları ise dağıtımdan bağımsız bir biçimde yüklenerek kullanılabilmektedir. + + IDLE (Integrated Development and Learning Environment) isimli minik IDE CPython dağımının IDE'sidir. Ancak IDLE diğer + gelişmiş IDE'lere göre oldukça yetersiz kalmaktadır. + + Sypder isimli IDE Anaconda dağıtımında default olarak gelen bir Python IDE'sidir. Dolayısıyla Spyder da yaygın biçimde + kullanılmaktadır. Tabii Spyder Anaconda olmadan bağımsız olarak da kurulabilmektedir. Biz de kursumuzda ağırlıklı + olarak bu Spyder IDE'sini kullanacağız. + + PyCharm isimli IDE JetBrains (ünlü IntelliJ IDEA isimli Java IDE'sinin üreticisi olan firma) firmsı tarafından + geliştirilmiştir. Herhangi bir dağıtıma bağlı değildir. Ancak Anaconda dağıtımının kütüphanelerini içermektedir + ve Anaconda dağıtımıyla belli bir uyumu vardır. + + Eclipse'in PyDev isimli plugin'i Eclips IDE'sinin Python uygulamaları geliştirme amacıyla kullanımına olanak + sağlamaktadır. + + Son yıllarda Microsoft'un "Visual Studio Code" denilen küçük çaplı bir IDE'si de yaygın bir biçimde kullanılır hale + gelmiştir. Microsoft bu IDE'yi cross paltform uygulama geliştirenler için hazırlamıştı. Dolayısıyla bu IDE hem Windows + sistemlerinde hem macOS sistemlerinde hem de Linux sistemlerinde aynı biçimde kullanılabilmektedir. Visual Studio Code + aslında bir editörle IDE arasında bir araçtır. Bu araç bi plugin mimarisine sahiptir. Çeşitli plugin'ler (buna Microsoft + "extension" demektedir) çeşitli konularda kolaylıklar sağlamaktadır. + + Burada ele alınanlar dışında daha pek çok irili ufaklı Python IDE'leri vardır. Kursumuzda Sypder ve PyCharm IDE'leri + kullanılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yazılımların çoğu bir firma tarafından ticari amaçla yazılmaktadır. Bunlara mülkiyete bağlı yazılım (proprieatary + software) denilmektedir. 1980'li yılların ortalarında Richard Stallman tarafından "Özgür Yazılım (Free Software)" + hareketi başlatılmıştır. Bunu daha sonra "Açık Kaynak (Open Source)" ve türevi akımlar izlemiştir. Bunların çoğu + birbirine benzer lisanslara sahiptir. Özgür yazılımın ve açık kaynak kodlu yazılımın temel prensipleri şöyledir: + + - Program yazıldığında yalnızca çalıştırılabilen (executable) dosyalar değil, kaynak kodlar da verilir. + - Kaynak kodlar sahiplenilemez. + - Bir kişi bir kaynak kodu değiştirmiş ya da onu geliştirmiş ise o da bunun kaynak kodlarını açmak zorundadır. + - Program istenildiği gibi dağıtılıp kullanılabilir. + + Linux dünyasındaki ürünlerin çoğu bu kapsamdadır. Biz bir yazılımı ya da bileşeni kullanırken onun lisansına dikkat + etmeliyiz. Bugün özgür yazılım ve açık kaynak kod hareketi çok ilerlemiştir. Neredeyse popüler pek çok ürürünün + açık kaynak kodlu bir versiyonu vardır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bilgisayarlar ikilik sistemi kullanmaktadır. Bu nedenle bilgisayarların belleğinde, diskinde vs. her şey ikilik sistemde + sayılar biçiminde bulunur. CPU'lar da ikilik sistemdeki sayılar üzerinde işlemler yapmaktadır. İkilik sistemde sayıları + yazarken yalnızca 1'ler ve 0'lar kullanılır. Böylece bilgisayarın içerisinde yalnızca 1'ler ve 0'lar vardır. Her şey + 1'lerden ve 0'lardan oluşmaktadır. İkilik sistemdeki her bir basamağa "bit (binary digit'ten kısaltma)" denilmektedir. + Bu durumda en küçük bellek birimi bit'tir. Bit çok küçük olduğu için 8 bit'e 1 byte denilmiştir. Bellek birimi olarak + byte kullanılır. Bilgisayar bilimlerinde Kilo genellikle "1024 katı" anlamına gelmektedir. Yani 1KB = 1024 byte’tır. + Mega da kilonun 1024 katıdır. Yani 1MB=1024KB'tır. Giga Mega’nın Tera da Giga’nın 1024 katıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kullandığımız CPU'lar ikilik sistemdeki makine komutlarını çalıştırmaktadır. Bir kodun CPU tarafından çalıştırılabilmesi + için o kodun o CPU'nun makine diline dönüştürülmüş olması gerekir. Zaten derleyiciler de bunu yapmaktadır. Eğer bir + çevirici program (yani derleyici) o anda çalışılmakta olan makinenin CPU'sunun işletebileceği makine kodlarını üretiyor + CPU da bunları çalıştırıyorsa buna doğal kodlu (native code) çalışma denilmektedir. Örneğin C ve C++ programlama + dillerinde doğal kodlu çalışma uygulanmaktadır. Biz bu dillerde bir programı yazıp derlediğimizde artık o derlenmiş + program ilgili CPU tarafından çalıştırılabilecek doğal kodları içermektedir. + + Bazı sistemlerde derleyiciler doğrudan doğal kod üretmek yerine hiçbir CPU'nun makine dili olmayan (dolayısıyla hiçbir + CPU tarafından işletilemeyen) yapay bir kod üretmektedir. Bu yapay kodlara genel olarak "ara kodlar (intermediate codes)" + denilmektedir. Bu arakodlar doğrudan CPU'lar tarafından çalıştırılamazlar. Arakodlu çalışma Java ve .NET dünyasında + ve daha başka ortamlarda kullanılmaktadır. Java dünyasında Java derleyicilerinin ürettikleri ara koda "Java Bytecode", + .NET (CLI bunun genel ismidir) dünyasında ise "CIL (Common Intermediate Language)" denilmektedir. Pekiyi bu arakodlar + ne işe yaramaktadır? İşte bu arakodlar çalıştırılmak istendiğinde ilgili ortamın alt sistemleri devreye girerek önce + bu arakodları o anda çalışılan CPU'nun doğal makine diline dönüştürüp sonra çalıştırmaktadır. Bu sürece (yani arakodun + doğal makine koduna dönüştürülmesi sürecine) "tam zamanında derleme (just in time compilation)" ya da kısaca "JIT + derlemesi" denilmektedir. Java ortamında bu JIT derlemesi yapıp programı çalıştıran alt sistem "Java Sanal Makinesi + (Java Virtual Machine)", .NET ortamında ise CLR (Common Language Runtime)" biçiminde isimlendirilmiştir. + + Şüphesiz doğal kodlu çalışma arakodla çalışmnadan daha hızlıdır. Pek çok benchmark testleri aradaki hız farkının %20 + civarında olduğunu göstermektedir. Pekiyi arakodlu çalışmanın avantajları nelerdir? İşte bu çalışma biçimi derlenmiş + kodun platform bağımsız olmasını sağlamaktadır. Buna "binary portability" de denilmektedir. Böylece arakodlar başka + bir CPU'nun ya da işletim sisteminin bulunduğu bir bilgisayara götürüldüğünde eğer orada ilgili ortam (framework) + kuruluysa doğrudan çalıştırılabilmektedir. + + Python dünyasında da bazı Python gerçekleştirimlerinde gizli bir arakodlu çalışma vardır. Bu nedenle bazı Python dil + işlemcisinin bir derleyici mi yoksa yorumlayıcı mı olduğu tartışılabilir. Örneğin CPython gerçekleştiriminde yorumlayıcı + Python kodunu okuyup onu o anda çalıştırmak yerine önce bir arakod üretip o arakodu çalıştırmaktadır. + + Ancak CPython bir JIT derlemesi yapmamaktadır. Çünkü CPython dönüştürdüğü arakodu tane tane alarak o anda yorumlama + sistemiyle çalıştırmaktadır. Oysa Python'ın PyPy gerçekleştirimi tıpkı Java ve .NET dünyasına benzer biçimde bir JIT + derlemesi işlemiyle kodu çalıştırır. Dolayısıyla PyPy gerçekleştirimi çalışma zamanı (run time) bakımından CPython + gerçekleştirimine göre daha iyidir. Aslında CPython tüm Python kodunu değil Python modüllerini arakodlara dönüştürmektedir. + Ana script kodlarını arakodlara dönüştürmemektedir. CPython gerçekleştiriminin arakodlu bir çalışma sağlamasının temel + nedeni aynı programın ikinci kez çalıştırılması sırasında daha hızlı çalıştırılmasını sağlamaktır. Faha önceden de + belirttiğimiz gibi Jython ve IronPython gerçekleştirimleri adeta derleyici çalışmaktadır. Bunların derleyicileri + Java dünyasında kullanılan ve .NET dünyasında kullanılan arakodları üretmektedir. + + Burada önemli bir nokta şudur: Python genelinde bir arakod standardı yoktur. Halbuki Java ve .NET (CLI) dünyalarında + onların arakodları oldukça sağlam biçimde standardize edilmiştir. Fakat belli bir gerçekleştirim (örneğin CPython) + ele alındığında onun farklı platformlardaki arakodlarının aynı olduğunu söyleyebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dil karmaşık bir olgudur. Tek bir cümleyle tanımını yapmak pek mümkün değildir. Fakat kısaca "iletişim için kullanılan + semboller kümesidir" denebilir. Bir dilin tüm kurallarına gramer denir. Gramerin en önemli iki alt alanı sentaks (syntax) + ve semantik (semantic)'tir. Bir dili oluşturan en yalın öğelere atom ya da sembol (token) denilmektedir. Örneğin doğal + dillerdeki atomlar sözcüklerdir. + + Bir olgunun dil olabilmesi için en azından sentaks ve semantik kurallara sahip olması gerekir. Sentaks doğru yazıma + ve dizilime ilişkin kurallardır. Örneğin: + + "I school to am going" + + Burada İngilizce için bir sentaks hatası söz konusudur. Sözcükler doğrudur fakat dizilimleri yanlıştır. Örneğin: + + "Herkez çok mutluydu" + + burada da bir sentaks hatası vardır. Türkçe'de "herkez" biçiminde bir sözcük (yani sembol) yoktur. Örneğin: + + if a > 10) + + Burada da C#'ça bir sentaks hatası yapılmıştır. + + Semantik ise doğru yazılmış ve dizilmiş olan öğelerin ne anlam ifade ettiğine ilişkin kurallardır. Yani bir şey doğru + yazılmıştır fakat ne anlama gelmektedir? Örneğin: + + "I am going to school" + + sentaks bakımından geçerlidir. Fakat burada ne denmek istenmiştir? Bu kurallara semantik denilmektedir. + + Bir olguya dil diyebilmek için o olguda "sentaks" ve "semantik" kuralların bulunuyor olması gerekir. + + Diller doğal (native) ve kurgusal (constructive) ya da biçimsel (formal) olmak üzere ikiye ayrılabilir. Doğal diller + yaşantı sonucunda doğal biçimde oluşmuştur. Doğal dillerde tam bir fomülasyonu yoktur. Kurgusal diller insanlar + tarafından formüle edilebilecek biçimde tasarlanmış dillerdir. Bilgisayar dilleri kurgusal dillerdendir. Kurgusal + dillerde istisnalar ya yoktur ya da çok azdır. Sentaks ve semantik tutarlıdır. Doğal dillerde pek çok istisna vardır. + Doğal dillerin zor öğrenilmesinin en önemli nedenlerinden birisi de istisnalardır. + + Bilgisayar bilimlerinde kullanılan dillere "bilgisayar dilleri (computer languages)" denilmektedir. Bir bilgisayar + dilinde akış varsa ona aynı zamanda "programlama dili de (programming language)" denilmektedir. Örneğin HTML bir + bilgisayar dilidir. Fakat HTML'de bir akış yoktur. Bu nedenle HTML bir programlama dili değildir. HTML'de de sentaks + ve semantik kurallar vardır. Oysa örneğin Python'da bir akış da vardır. O halde Python bir programlama dilidir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 5.Ders 07/07/2024 - Pazar +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programlama dilleri üç biçimde sınıflandırılabilir: + + 1) Seviyelerine Göre Sınıflandırma + 2) Kullanım Alanlarına Göre Sınıflandırma + 3) Programlama Modeline Göre Sınıflandırma + + Seviyelerine Göre Sınıflandırma: Seviye (level) bir programlama dilinin insan algısına yakınlığının bir ölçüsüdür. Yüksek + seviyeli diller kolay öğrenilebilen insana yakın dillerdir. Alçak seviyeli diller bilgisayara yakın dillerdir. Olabilecek + en alçak seviyeli diller 1'lerden 0'lardan oluşan saf makine dilleridir. Bunun biraz yukarısında sembolik makina dilleri + (assembly languages) bulunur. Biraz daha yukarıda orta seviyeli diller bulunmaktadır. Daha yukarıda ise yüksek seviyeli, + en yukarıda da "çok yüksek seviyeli diller" vardır. Aslında aynı kategorideki diller arasında da bir seviye farkı söz + konusudur. Yani örneğin iki yüksek seviyeli programalam dili aslında aynı seviyede olmayabilir. + + Kullanım Alanlarına Göre Sınıflandırma: Bu sınıflandırma biçimi dillerin hangi amaçla daha çok kullanıldığına yöneliktir. + Tipik sınıflandırma şöyle yapılabilir: + + - Bilimsel ve Mühendislik Diller: C, C++, Java, C#, Fortran, Python, Pascal, Matlab gibi... + - Veritabanı Yoğun İşlemlerde Kullanılan Diller: SQL, Foxpro, Clipper, gibi... + - Dinamik Web Sayfalarının Oluşturulmasında Kullanılan Diller: PHP, C#, Java, Python, Perl gibi... + - Animasyon Dilleri: Action Script gibi... + - Yapay Zeka, Makine Öğrenmesi ve Veri Bilimi Uygulamalarında Kullanılan Diller: Lisp, Prolog, Python, C, C++, C#, Java gibi... + - Sistem Programlama Dilleri: C, C++, Rust, Sembolik Makina Dilleri + - Genel Amaçlı Diller: C, C++, Pascal, C#, Java, Python, Basic gibi... + + Programlama Modeline Göre Sınıflandırma: Program yazarken hangi modeli (paradigm) kullandığımıza yönelik sınıflandırmadır. + Altprogramların birbirlerini çağırmasıyla program yazma modeline "prosedürel programlama modeli (procedural programming + paradigm)" denilmektedir. Bir dilde yalnızca alt programlar oluşturabiliyorsak, sınıflar oluşturamıyorsak bu dil için + "prosedürel programlama modeline uygun olarak tasarlanmış" bir dil diyebiliriz. Örneğin klasik Basic, Fortran, C, Pascal + prosedürel dillerdir. Bir dilde sınıflar varsa ve program sınıflar kullanılarak yazılıyorsa böyle dillere "nesne yönelimli + diller (object oriented languages)" denilmektedir. Eğer program formül yazar gibi yazılıyorsa bu modele de fonksiyonel + model (functional paradigm), bu modeli destekleyen dillere de fonksiyonel diller denilmektedir. Bazı dillerde program + görsel olarak fare hareketleriyle oluşturulabilmektedir. Bunlara görsel diller denir. Bazı diller birden fazla programlama + modelinin kullanılmasına olanak sağlayacak biçimde tasarlanmıştır. Bunlara da çok modelli diller (multiparadigm languages) + denilmektedir. Örneğin C++ gibi, Python gibi diller çok modelli dillerdir. + #------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + O halde Python için şunları söyleyebiliri<: Python yğksek seviyeli, bilimsel ve mühendislik uygulamalarda, yapay zeka, + makine öğrenmesi ve veri bilimi uygulamalarında, Web programlamada kaullanılabilen genel amaçlı, prosedürel ve nesne + yönelimli, fonksiyonel özellikleri olan çok modelli" bir programlama dilidir. + + Python dilinin en önemli özelliği kolay olması, hızlı kod yazılabilmesi ve birtakım işlemlerin az tuşa basılarak (diğer + programlama dilleriyle kıyaslandığında) gerçekleştirilmesidir. Pyhthon matematiksel alana yakın bir programlama dilidir. + Bu nedenle makine öğrenmesi ve veri bilimi alanlarında en çok tercih edilen dil olmuştur. Ancak Python yorumlayıcı + temelli çalıştığı için nispeten yavaş bir dildir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programlama dillerinin sentakslarını betimlemek için pek çok meta dil oluşturulmuştur. Bunlardan en ünlüsü ve çok + kullanılanaı BNF (Backus-Naur Form) denilen dildir. Gerçekten de bugün programlama dillerinin resmi standartlarında + ya da referanslarında hep BNF notasyonu ya da bunun türevleri kullanılmaktadır. Python'ın orijinal referans kitabında + da bu notasyon tercih edilmiştir. BNF notasyonu ISO tarafından da standardize edilmiştir. Bunun ismine EBNF denilmektedir. + Ancak BNF notasyonun anlaşılması biraz zordur ve ek çalışma gerektirmektedir. BNF ve EBNF notasyonları Derneğimizde + "Sistem Programalama ve İleri C Uygulamaları - II" kursunda ele alınmaktadır. + + Kursumuzda sentas açıklamak için BNF ya EBNF yerine "açısal parantez köşeli parantez tekniği" kullanılacaktır. Bu teknikte + köşeli parantez içerisindekiler isteğe bağlı (optional) öğeleri açısal parantez içerisindekiler zorunlu öğeleri belirtmektedir. + Diğer tüm atomlar aynı pozisyonda bulunmak zorundadır. Örneğin C dilindeki if deyimi bu notasyonla şöyle belirtilir: + + if () + + [ + else + + ] + + Ayrıca biz bu teknikte anahtar sözcüklerin altını da çizeceğiz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python bir programlama dili olmasına karşın pek çok gerçekleştirim ve dağıtım bize Python ifadelerini bir komut satırında + yazma olanağı da vermektedir. Python tarafından sunulan bu komut yorumlayıcı ortam birtakım kod parçalarının test + edilmesinde sıklıkla kullanılmaktadır. Biz de kursumuzda sık sık bu komut yorumlayıcı ortamı kullanacağız. Bu tür komut + yorumlayıcı ortamlara bir süredir "REPL (Read and Evaluate and Print Loop)" da denilmektedir. Komut yorumlayıcı ortamlarda + bir imleç eşliğinde kullanıcdan bir komut beklenir. Komut yorumlayıcı bu komutu çalıştırır ve yeniden imleç eşliğinde + komut bekler. İşte Python'daki komut satırı ortamları da bu biçimde python deyimlerini tek tek işletmek amacıyla + kullanılmaktadır. Bu tür ortamlar yalnızca Python'da değil Ruby, R, Swift gibi pek çok programlama dilinde de yardım + bir öğe olarak bulunmaktadır. + + Aslında Python programları bu komut yorumlayıcı ortamda yazılan deyimlerin peşi sıra hızlı bir biçimde çalıştırılması + ile çalıştırılmaktadır. CPython yorumlayıcısının komut satırındaki ismi "python" ya da "python3" biçimindedir. Bir + programı çalıştırmak için komut satırında Python yorumlayıcısının ismi ve onun yanına çalışatırılacak program ismi + yazılır. Örneğin: + + F:\Dropbox\Kurslar\Python\Src>python sample.py + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + Burada "sample.py" çalıştırılmak istenen Python programının ismini belirtmektedir. İşte python yorumlayıcısının isminden + sonra çalıştırılacak programın ismi yazılmadan ENTER tuşuna basılırsa CPython yorumlayıcısının komut satırı ortamına + geçilir. Aynı komut satırı CPython dağıtımımın IDLE IDE'sinde de bulunmaktadır. Sypder IDE'sinde editörün sağında da + benzer bir komut satırı bulunmaktadır. Bu komut satırına IPython denilmektedir. + + Aslında IPython Spyder olmadan bağımsız olarak da install edilebilmektedir. IPython komut satırı programını aşağıdaki + gibi install edebilirsiniz: + + F:\> pip install ipython + + Python dünyasında yaygın olarak kullanılan diğer bir araç da "Jupyter Notebook" denilen araçtır. Jupyter Notebook + programı tarayıcı tabanlı çalışmaktadır. Anaconda Navigator içerisinden çalıştırılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python komut satırı uygulamalarında programcı bir Python deyimi girer, komut satırı uygulaması bunu çalıştırır. Sonucu + ekrana yazar ve yeniden imlece düşer. Biz kursumuzun başında bu ortamı yoğun olarak kullanacağız. Bu ortamda bir değişkenin + ismi yazılıp ENTER tuşuna basılırsa o değişkenin içerisindeki değer doğrudan ekrana basılmaktadır. Örneğin: + + >>> a = 10 + >>> b = a * 2 + >>> a + 10 + >>> b + 20 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python dinamik tür sistemine (dynamic type system) sahip bir dildir. Dinamik tür sistemine sahip dillerde değişkenlerin + türleri akışın hangi noktada olduğuna bağlı olarak değişebilir. Başka bir deyişle bu tür dillerde biz aynı değişkene + istediğimiz türden bilgileri atayabiliriz. O anda değişkene hangi türden bir bilgi atamışsak onun türü o anda atadığımız + değerin türünden olur. Yine başka bir deyişle bu tür dillerde aslında değişkenlerin değil o değişkenlerin tuttuğu ya da + referans ettiği nesnelerin türleri vardır. Bunun tersine "statik tür sistemi (static type system)" denilmektedir. Yaygın + pek çok programala dili statik tür sistemine sahiptir. Örneğin C, C++, Java, C#, Pascal gibi. Bu dillerde bir değişken + önce bildirilir. Bildirim sırasında onun türü de belirtilir. Sonra o değişken faaliyet alanı boyunca hep o türden olur. + Türü bir daha değiştirilemez. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 6.Ders 13/07/2024 - Cumartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Atomlar Python'da beş gruba ayrılmaktadır: + + 1) Anahtar Sözcükler (Keywords): Dil için özel anlamı olan değişken ismi olarak kullanılması yasaklanmış atomlardır. + Örneğin programlama dillerinde sıkça gördüğümüz if, while, for gibi atomlar birer anahtar sözcüktür. Anahtar sözcük + olan atomların listesi her programlama dilinde diğerlerinden farklı olabilir. Örneğin: + + a = 10 + + Burada a bir değişkenidr. Ancak örneğin: + + if = 10 + + if ismini bir değişken ismi olarak kullanamayız. Çünkü if Python'da bir anahtar sözcüktür. + + 2) Değişkenler (Identifiers): İsmini bizim ya da başkalarının istediği gibi verebildiği atomlara değişken denilmektedir. + Örneğin: + + a = 10 + + Burada a bir değişken atomdur. Örneğin: + + print(a) + + Burada print de a da birer değişken atomdur. Burada print Python'un standart kütüphanesindeki bir fonksiyon ismidir. + Ancak yorumlayıcı için bu ismin özel bir anlamı yoktur. Bu isim değişken ismi olarak da kullanılabilir. Örneğin: + + print = 20 + + Dolayısıyla print bir anahtar sözcük değildir, değişken atomdur. + + 3) Sabitler (Literals): Programlama dillerinde doğrudan yazılmış olan sayılara sabit (literal) denilmektedir. Örneğin: + + a = b + 10 + + Burada a ve b birer değişken atomdur ancak 10 bir sabit atomdur. Bir değer ya bir değişken içindedir ya da doğrudan + kullanılmıştır. b + 10 işleminde b'nin içerisindeki değer ile doğrudan yazılmış 10 toplanmaktadır. + + 4) Operatörler (Operators): Bir işleme yol açan işlem sonucunda bir değer üretilmesini saplayan atomlara operatör + denilmektedir. Örneğin +, -, * gibi semboller birer operatördür. Örneğin: + + a = b + 10 + + Burada a ve b birer değişken atomdur. = ve + operatör atomlardır. 10 ise bir sabit atomdur. + + 5) Ayıraçlar (Delimiters / Punctuators: Bunlar ifadeleri birbirinden ayırmak için kullanılan atomlardır. Örneğin ';', + ':' gibi atonlar birer ayıraç atomdur. Örneğin: + + if a == 10: + print('Ok') + + Bu program parçasında if satırınn sonundaki ':' karakteri bir ayıraç atomdur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programlama dillerinde boşluk duygusu oluşturmak için kullanılan karakterlere boşluk karakterleri (white space) + denilmektedir. İngilizce SPACE karakteri denildiğinde ASCII ve UNICODE 32 numaralı karakter anlaşılır. Bu karakter + klavyelermizde uzun çubuklu tuşa basınca çıkartılmaktadır. Ancak İngilizce "white space" denildiğinde tüm boşluk karakterleri + anlaşılmaktadır. Tipik boşluk karakterleri şunlardır: + + SPACE + TAB + VTAB + LF (ya da CR/LF) + + Windows'ta ENTER tuşuna bastığımızda aslında iki karakter kodu dosyaya dahil edilmektedir: CR (Carriage Return) ve + LF (Line Feed). Ancak UNIX/Linux ve macOS sistemlerinde yalnızca LF karakteri dosyaya dahil edilmektedir. Windows'ta + yalnızca CR karakteri "bulunulan satırın başına geçmek için", yalnızca LF karakteri "aşağı satırın bulunulan sütuna + geçmek için" kullanılmaktadır. Dolayısıyla Windows'ta "aşağı satırın başına geçmek için" yalnızca CR ya da yalnızca + LF karakterleri yeterli değildir. Bunların CR/LF biçiminde bir arada bulunması gerekir. Halbuki UNIX/Linux ve macOS + sistemlerinde tek başına LF karakteri bu işi yapmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Klavyeden TAB tuşuna basıldığında elde edilen karaktere TAB karakter denilmektedir. TAB karakter ASCII ve UNICODE + karakter tablosunda 9 numaralı karakterdir. TAB karakter aslında yatay biçimde belirli bir miktar zıplamak için + kullanılır. Ancak TAB karakterin ne kadar zıplamaya yol açacağı konusunda bir belirleme yapılmamıştır. Bu tamamen + TAB karakteri ele alan ortamın (tipik olarak editörün) tercihine bağlıdır. Biz TAB karakter görüldüğünde ne kadar + zıplanacağını editörün ayarlarından değiştirebiliriz. Programcıların en çok kullandığı TAB aralığı 4'tür. Ancak bazı + ortamlar ve editörler TAB karakteri gördüğünde bulunulan yerden itibaren n karakter kadar zıplamak yerine ilk "tab + durağına (tab stop)" gidebilmektedir. Aslında bilgisayar klavyesi daktilo temel alınarak geliştirilmiştir. Tab durakları + daktilolarda mekanik bir biçimde oluşturulmaktadır. + + Programalama editörlerinde genellikle isteğe bağlı biçimde TAB karakter yerine n tane SPACE karakterinin basılması + sağlanabilmektedir. Yani programcı isterse editör seçeneklerinden TAB tuşuna basıldığında dosyaya TAB karakter yerine + örneğin 4 tane SPACE karakterinin basılmasını sağlayabilmektedir. Bunun bir avantajı farklı editörlerde aynı görüntünün + elde edilmesidir. Dezavantajı ise kaynak programın dosyada daha fazla yer kaplamasıdır. Örneğin IDLE IDE'sinde, Spyder + IDE'sinde ve PyCharm IDE'sinde TAB yerine her zaman programcı tarafından ayarlanan bir miktarda (default'u 4) SPACE + karakteri basılmaktadır. Python programlamasında TAB tuşuna basıldığında dosyaya TAB karakter yerine belli miktar + SPACE karakterinin basılması tercih edilen bir durumdur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da "Merhaba Dünya" yazısını ekrana basan program aşağıdaki yazılabilir: + + print('Merhaba Dünya') + + Bir Python programını komut satırından şöyle çalıştırabiliriz: + + python + + Örneğin: + + python sample.py + + Eğer python program isminden sonra çalıştrırılacak dosyanın ismi belirtilmezse Python yorumlayıcısı REPL (komut satırı) + ortamına girer. Bu ortamdan quit() yazarak çıkabilirsiniz. + + Mac OS X ve Linux sistemlerinde Python yorumlayıcısının iki versiyonu eşzamanlı biçimde bulundurulabilmektedir. Bu duruma + dikkat ediniz. Genellikle "python" ismi Python'ın 2.7'li sürümlerini çalıştırmak için python3 ismi ise 3'lü sürümlerini + çalıştırmak için kullanılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +print('Merhaba Dünya') + +#------------------------------------------------------------------------------------------------------------------------ + Python'da ifadelerden oluşan basit deyimler tek bir satırda yazılmak zorundadır. Eğer bir satıra birden fazla basit + yerleştirilecekse bunların arasına ';' atomu getirilmelidir. Örneğin: + + a = 10; b = 20 + + Burada satırın sonundaki basit deyimin sonuna da ';' getirilebilirdi. Ancak bu durum gereksizdir. Zaten Python'da + Enter karakteri (LF ya da CR/LF karakterleri) bir basit deyimin bitmiş olduğunu belirtir. Yani yukarıdaki kod parçası + aşağıdaki gibi yazılmış olsaydı da bir sorun oluşturmayacaktı: + + a = 10; b = 20; +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bir yazının tek tırnak içerisine alınmasıyla çift tırnak içerisine alınması arasında bir farklılık yoktur. + Fakat C, C++, Java, C# gibi dillerde tek tırnak ile çift tırnak kullanımı farklı anlamlara gelmektedir. Bu durumda + "Merhaba Dünya" programını şöyle de yazabilirdik: + + print("Merhaba Dünya") + + Python programcılarının bir bölümü yazılar için tek tırnak kullanırken bir bölümü çift tırnak kullanmaktadır. Ancak tek + tırnak Python'da daha yaygın bir kullanıma sahiptir. Biz de kursumuzda zorunluluk olmadıkça (bazı durumlarda zorunluluk + oluşabilmektedir) çift tırnak yerine tek tırnak kulanacağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programlama dillerinde değişkenlerin, operatörlerin ve sabitlerin herbir kombinasyonuna (birleşimine) "ifade (expression)" + denilmektedir. Örneğin: + + a + a + 2 + a + 2 - b * c + 10 + + birer ifadedir. + + Tek başına bir dğeişken ve sabit ifade belirtmektedir. Ancak tek başına bir operatör ifade belirtmez. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir programalama dilini çğrenirken ilk öğrenilmesi gereken şey o programlama dilindeki türlerin neler olduğudur. + Tür (type) bir nesnenin bellekte ne kadar yer kapladığını, onun içerisindeki bilginin formatını, onun hangi operatörlerle + işleme sokulabileceğini belirten temel bir özelliktir. Python tür bakımından minimalist biçimde tasarlanmıştır. + Python'da 6 tane temel tür vardır: + + int + float + str + bool + complex + NoneType (None) + + Python'un tür bakımından minimalist tasarımı programlamayı oldukça kolaylaştırmaktadır. C, C++, Java ve C# gibi dillerdeki + temel türlerin sayısı Python'dakinin iki, üç katı kadardır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Giriş derlerinde Python'un dinamik tür sistemine sahip bir programlama dili olduğunu belirtmiştik. Bu dillerde "bildirim + (declaration)" biçiminde bir kavram yoktur. Bir değişkene en son hangi türden bir değer atanmışsa değişken o türden + olur. Halbuki statik tür sistemine sahip C, C++, Java ve C# gibi dillerde değişkenin türü "bildirim (declaration)" bir + işlemle programcı tarafından belirlenir. Değişken yaşamı boyunca hep o türden kalır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkenin türünü anlamak için type fonksiyonu kullanılmaktadır. type tıpkı print fonksiyonu gibi Python Standart + Kütüphanesindeki built-in bir fonksiyondur. Biz bir değişkeni type fonksiyonuna sokarsak onun türünün ne olduğunu + anlayabiliriz. Örneğin: + + >>> a = 10 + >>> type(a) + + >>> a = 1.23 + >>> type(a) + + >>> a = 'ankara' + >>> type(a) + + >>> a = False + >>> type(a) + + >>> a = 3j+2 + >>> type(a) + + >>> a = None + >>> type(a) + + + Tabii bir program içerisinde bir değişkenin türünü yazdıracaksak bu durumda print fonksiyonunu da kullanmalıyız. + Örneğin: + + x = 10 + print(type(x)) + + x = 'ali' + print(type(x)) +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi Python'da bir değişkene hangi türden değer atabnışsa değişken o türden olur. Python'da + bir değişkene nokta içermeyen bir sayı atandığında o değişken artık int türünden olur. Python'da int türünün bir sınırı + yoktur. Yani sınırsız bir biçimde basamak içerebilir. Halbuki C, C++, Java ve C# gibi dillerde int türünün belli bir + byte uzunluğu vardır. Dolayısıyla bu dillerde biz ancak belli aralıktaki sayıları int türden bir nesnenin içerisine + yerleştirebiliriz. Tabii Python'da her ne kadar int türünün bir sınırı yoksa da Python yorumlayıcıları içsel limitlerinden + dolayı buna bir sınır getirebilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 3928239847982347923749823749827349872398472983749823748982934234 +>>> type(a) + + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki kodu bir program olarak çalıştırınız. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10287364872364872348762348762 + +print(type(a)) # + +#------------------------------------------------------------------------------------------------------------------------ + Python'da noktalı sayılar "float" isimli türle temsil edilmiştir. float türü 8 byte uzunlukta kayan noktalı (IEEE 754 + long real format) bir formata sahiptir. Python'daki float türü C, C++, Java ve C# dillerindeki double türü ile aynıdır. + (O dillerde ayrıca 4 byte uzunluğunda float isimli başka bir tür de vardır.) Örneğin: + + >>> f = 3.14 + >>> type(f) + + >>> f = 3.0 + >>> type(f) + + >>> f = 3 + >>> type(f) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Noktalı sayıların tutuluş formatından kaynaklanan ve ismine "yuvarlama hatası (rounding error)" denilen bir olgu vardır. + Yuvarlama hatası "bir noktalı sayının tam olarak ifade edilemeyip ona yakın bir sayının ifade edilmesiyle oluşan" bir hatadır. + Yuvarlama hataları sayıyı ilk kez depolarken de oluşabilir, aritmetik işlemler sonucunda da oluşabilir. Ancak programcının + bu yuvarlama hataları konusunda bilinçli olması gerekir. + + Yuvarlama hataları her noktalı sayıda ortaya çıkmaz bazı noktalı sayılarda ortaya çıkar. Yuvarlama hataları sayı ilk kez + depolanırken ortaya çıkabildiği gibi, bir işlem sonucunda da ortaya çıkabilmektedir. Bugün işlemcilerin kullandığı IEEE 754 + numaralı formatta yuvarlama hatalarını ortadan kaldırmanın bir yolu yoktur. Eğer yuvarlama hataları istenmiyorsa bu durumda + özel başka türler tercih edilebilir. Örneğin Pyton Standart Kütüphanesindeki "decimal" türü yapay bir tür olsa da yuvarlama + hatalarına olanak vermemktedir. Programlama dillerinde yuvarlama hataları yüzünden iki noktalı sayının tam eşitliğinin + karşılaştırılması uygun kabul edilmemektedir. Çünkü iki noktalı sayı yuvarlama hatalarından dolayı biribirine eşit olacakken + olmayabilir. + + Aşağıda yuvarlama hatasına bir örnek verilmiştir. Burada 0.3'ten 0.1 çıkartıldığında 0.2 elde edilememiştir. 0.2'ye + çok yakın olan başka bir değer elde edilmiştir: + + >>> a = 0.3 + >>> b = 0.1 + >>> c = a - b + >>> c + 0.19999999999999998 + + Yuvarlama hatalarından dolayı "kayan noktalı formatları (floating points)" kullanan programala dillerinde noktalı + sayılar üzerinde eşitlik karşılaştırması hatalı sonuçların elde edilmesine yol açabilmektedir. Bu nedenle bu dillerde + noktalı sayılarda eşitlik karşılaştırması yapılmamalıdır. Örneğin: + + >>> 0.3 - 0.1 == 0.2 + False +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkene True ya da False anahtar sözcükleri atanırsa o değişken bool türünden olur. bool türü True ve False + biçiminde yalnızca iki değere sahiptir. Programcılar "doğru-yanlış", "olumslu-olumsuz", "evet-hayır" gibi iki değer + içeren olguları genellikle bool türü ile temsil ederler. Tabii yalnızca Pyton'da değil diğer programlama dillerinin + çoğunda aslında bool türden bir değişken 0 ya da 1 gibi bir sayı tutmaktadır. Yani biz bir değişkene True değerini + atasak bile aslında bu değişken içsel olarak muhtemelen bu değeri 1 sayısı biçiminde tutacaktır. Diğer programlama + dillerinden geçenlerin dikkat etmesi gereken bir nokta Python'daki True ve False anahtar sözcüklerinin ilk harflerinin + büyük harf olmasıdır. Halbuki pek çok dilde bu anahtar sözcükler küçük harflerle isimlendirilmiştir. Örneğin: + + >>> b = True + >>> b + True + >>> type(b) + + >>> b = False + >>> b + False + >>> type(b) + + >>> b = 'True' + >>> b + 'True' + >>> type(b) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da yazıları tutan türe str denilmektedir. Bu türün ismi str olsa da biz bu türe "string" de diyeceğiz. String + oluşturmak için tek tırnak ya da çift tırnak kullanılır. Daha önceden de belirttiğimiz gibi tek tırnakla çift tırnak + arasında Python'da hiçbir farklılık yoktur. Bazı programlama dillerinde tek tırnak ile çift tırnak tamamen farklı + anlamlara gelmektedir. Bu dillerden geçen kişiler Python'da tek tırmak ile çift tırnak arasında bir fark olmadığına + dikkat etmelidir. Biz kursumuzda ağırlıklı olarak tek tırnak kullanımını tercih edeceğiz. + + Pek çok programlama dilinde tek bir karakteri tutmakta kullanılan char isimli bir tür de bulunmaktadır. Ancak Python'da + böyle bir tür yoktur. Tek karakterli string'ler bu gereksinimi karşılamaktadır. Örneğin C, C++, Java ve C# dillerinde + tek bir karakter tek tırnakla birden fazla karakterden oluşan yazılar da çift tırnakla ifade edilmektedir. Oysa Python'da + karakter ve string biçiminde iki ayrı tür yoktur. Karakterler tek karakterden oluşan string'lerle ifade edilmektedir. + Dolayısıyla Python'da tek tırnakla çift tırnak arasında da bir farklılık kalmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +>>> s = 'Ankara' +>>> s +'Ankara' +>>> type(s) + +>>> k = "Ankara" +>>> k +'Ankara' +>>> type(k) + + +#------------------------------------------------------------------------------------------------------------------------ + 7. Ders 14/07/2024 - Pazar +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da karmaşık sayılar için "complex" isimli bir tür de bulundurulmuştur. Bir complex nesnesi oluşturmak için 'j' + harfi kullanılır. Ancak j harfinden önce ona bitişik olan bir sayı getirilmesi zorunludur. Bu sayı genel olarak int + ya da float olabilir. Complex sayılara '+' ya da '-' operatörü ile bir gerçek kısım da ekleyebiliriz. Örneğin: + + >>> z = 3j+2 + >>> z + (2+3j) + >>> type(z) + + + Karmaşık sayının j kısmı 1 ise biz bunu j biçiminde belirtemeyiz. Çünkü bu durumda j bir değişken gibi ele alınır. + O halde bizim j yerine 1j biçiminde j kısmını belirtmemiz gerekir. Örneğin: + + >>> z = 1j+2 + >>> type(z) + + >>> z + (2+1j) + + Tabii karöaşık sayıları belirtirken önce sanal kısmın belirtilmesi zorunlu değildir. Örneğin: + + >>> z = 3+4j + >>> z + (3+4j) + >>> type(z) + + + Biz kursumuzda iki operand'lı operatörlerin iki tarafına bir SPACE boşluk bırakacağız. Ancak karmaşık sayıları + belirtirken Python programcıları genellikle '+' operatörünün iki yanına boşluk yerleştirmemektedir. + + Karmaşık sayılar üzerinde +, -, *, / gibi işlemler yapılabilmektedir. Örneğin: + + >>> x = 3j+2 + >>> y = 7j-3 + >>> z = x * y + >>> z + (-27+5j) +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 1 + 2j +>>> b = 3j +>>> c = a + b +>>> a +(1+2j) +>>> b +3j +>>> c +(1+5j) +>>> type(a) + +>>> type(b) + +>>> type(c) + + +#------------------------------------------------------------------------------------------------------------------------ + Python'da None anahtar sözcüğü ile temsil edilen özel bir değer vardır. Bu None değeri "NoneType" isimli bir türdendir. + None değeri "yokluk", "boşluk", "hiçlik", "öylesinelik" ve "başarısızlık" gibi durumları ifade etmek için kullanılmaktadır. + Biz bir değişkenin içerisine None değerini atadığımızda artık o değişken NoneType türünden olur. (None sözcüğündeki ilk + harf olan 'N'nin büyük yazıldığına dikkat ediniz.) Komut satırında içerisinde None bulunan bir değişkenin ismini yazdığımızda + ekrana bir şey basılmaz. Ancak print fonksiyonuyla bu değişken yazdırılmak istendiğinde "None" yazısı görüntülenmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = None +>>> a +>>> print(a) +None +>>> type(a) + + +#------------------------------------------------------------------------------------------------------------------------ + Statik tür sistemine sahip programlama dillerinde bir değişken kullanılmadan önce derleyiciye tanıtılmak zorundadır. + Bu tanıtma eylemine "bildirim (declaration)" denilmektedir. Fakat Python gibi dinamik tür sistemine sahip programlama + dillerinde bildirim diye bir kavram yoktur. Zaten bu tür dillerde bildirim işleminin bir anlamı da olamaz. Python'da + bir değişken ona ilk kez değer atandığında yaratılmaktadır. (Halbuki statik tür sistemine sahip programlama dillerinde + değişkenler bildirim sırasında yaratılırlar.) + + Biz Python'da henüz yaratılmamış olan bir değişkeni ifadelerde kullanamayız. Tabii onu yaratma amacıyla atama + operatörünün solunda kullanabiliriz. Bir değişkenin yaratımı ona bir ifadenin atanmasıyla yapılmaktadır. Örneğin: + + a = 10 # a değişkeni yaratılıyor + b = a * 2 # b değişkeni yaratılıyor + c = b # değişkeni yaratılıyor + +#------------------------------------------------------------------------------------------------------------------------ + +a = 12.3 +b = a + 1 # geçerli a yaratılmış, b de yaratılıyor +c = x + 2 # x yaratılmamış error oluşur + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da ifade ettiğimiz gibi Python'da bir değişken ona ilk kez değer atandığında yaratılır ve değişkenin türü de + ona atanan değerin türünden olur. Eninde sonunda değişkenlere bir biçimde sabitlerin atanması gerekmektedir. Programalama + başlayan kişiler yalnızca değişkenlerin türleri olduğunu sanabilmektedir. Halbuki sabitlerin de türleri vardır. Şimdi + sabit türleri üzerinde duracağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programlarda doğrudan yazdığımız sayılara ya da yazılara "sabit (literals)" denilmektedir. Sabitler işin başında + değişkenleri yaratmak için kullanılırlar. Sabitler bir değişkene atandığında değişken de o sabitin türünden olur. + + 1) Sayı nokta içermiyorsa böyle sayılar int türden sabit belirtir. Örneğin: + + 0 + 123 + 1234567 + 18 + + birer int türden sabit belirtmektedir. + + int türden sabitler 0x ya da 0X ile başlatılarak 16'lık sistemde de yazılabilirler. Örneğin: + + a = 0x10 + + Burada 0x10 int türden sabittir. Ancak 16'lık sistemde belirtilmiştir. Dolayısıla aslında a'nın içerisinde 10'luk + sistemde 16 değeri bulunacaktır. Örneğin: + + >>> a = 0x10 + >>> a + 16 + >>> type(a) + + + 16'lık sisteme İngiizce "hex system" ya da "hexaecimal system" denilmektedir. + + int bir sabit 0o ya da 0O öneki ile 8'lik sistemde de belirtilebilmektedir. Örneğin. + + >>> a = 0o10 + >>> a + 8 + >>> type(a) + + + 8'lik sisteme İngilizce "octal system" denilmektedir. + + Nihayet int sabitler 0b ya da 0B önekş ile başlatılarak 2'lik sistemde de belirtilebilmektedir. Örneğin: + + >>> a = 0b1010 + >>> a + 10 + >>> type(a) + + + 2'lik sisteme İngilizce "binary system" denilmektedir. + + Her ne kadar Python'da int türden sabitler 10'luk sistemin yanı sıra 16'lık, 8'lik ve 2'lik sistemde bel,itiliyor + olsa da Python yüksek seviyeli bir dil olduğu için Python programcıları bu sayı sistemleriyle ilgili işlemleri + oldukça seyrek yapmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 123 +>>> a +123 +>>> type(a) + +>>> a = 0o123 +>>> a +83 +>>> type(a) + +>>> a = 0x123 +>>> a +291 +>>> type(a) + +>>> a = 0b101010 +>>> a +42 +>>> type(a) + + +#------------------------------------------------------------------------------------------------------------------------ + Python'da int türden sabitler yazılırken sayıların aralarına alt tire karakteri konulabilir. Bu kural uzun tamsayılarda + okunabilirliği artırmak amacıyla dile sokulmuştur. Ancak iki tane alt tire yan yana bulundurulamaz, sayının başında ve + sonunda da alt tire bulundurulamaz. Alt tire 10'luk, 16'lık, 8'lik ve 2'lik sistemlerdeki yazımlarda da kullanılabilmektedir. + Örneğin: + + a = 1_000_000_000 + + Burada programcı bir milyar sayısı kolay algılanabilsin diye onu alt tire karakterleriyle basamaklarına ayrıştırmıştır. + Tabii alt tire ile binler basamağından ayrıştırma zorunluı değildir. Örneğin aşağıdaki sabit de geçerlidir: + + a = 1_0_0_0_0_00_00_0 + + Tabii bu biçimde basamaklara ayrıştırmanın bir faydası olmadığı gibi okunabilirliği bozucu etkisi vardır. Diğer + sistemlerdeki sayılar da alt tire ile benzer biçimde basamaklara ayrıştrılabilmektedir. Örneğin. + + x = 0x1234_5678 + y = 0o123_456 + z = 0b1010_0001 +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 1_000_000_000 +>>> a +1000000000 +>>> a = 0x1234_5678 +>>> a +305419896 +>>> a = 0B1010_0101 +>>> a +165 +>>> a = 1_0_0_0 +>>> a +1000 + +#------------------------------------------------------------------------------------------------------------------------ + 2) Bir sayı nokta içeriyorsa sabit float türdendir. Noktanın soluna ya da sağına bir şey yazılmamışsa 0 yazılmış olduğu + varsayılır. Örneğin: + + a = 3.14 + + Burada 3.14 sabiti float türdendir. Dolayısıyla a'nın türü de float olacaktır. Örneğin: + + b = 10 + + Burada 10 int türden sabittir. Dolayısıyla b de int türden olacaktır. Örneğin: + + c = 10. + + Noktanın soluna ya da sağına bir şey yazılmazsa 0 yazılmış olduğu kabul edilmektedir. Bu Fortran'dan beri gelen bir gelenektir. + Burada 10. sayısı 10.0 ile aynı anlama gelir. Dolayısıyla float türden bir sabit belirtir. Örneğin: + + d = .1 + + Burada .1 sayısı 0.1 aynı anlamdadır. Dolayısıyla float türdendir. + + float sabitlerde de alt tire benzer amaçla kullanılabilmektedir. float sabitler tıpkı diğer programlama dillerinde + olduğu gibi üstel biçimde de belirtilebilmektedir. Bunun için önce nokta içeren ya da içermeyen bir sayısal kısım sonra 'e' ya da 'E' + harfi sonra da on üzeri anlamına gelen kuvvet değeri bulundurulur. Örneğin 1.2e-3 değeri 1.2 x 10^-3 anlamına gelmektedir. Tabii + üstel biçimdeki sayıda hiç nokta olmasa bile (1e3 örneğindeki gibi) sabit float kabul edilir. float sabitlerde de basamaklamak için + alt tire kullanılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 2.3 +>>> type(a) + +>>> b = .28 +>>> type(b) + +>>> c = 12. +>>> type(c) + +>>> d = 10_3.23_45_57 +>>> type(d) + +>>> e = 1e40 +>>> type(e) + +>>> f = -1.2e-2 +>>> type(f) + +>>> g = 1.23_45e2_3 +>>> type(g) + + +#------------------------------------------------------------------------------------------------------------------------ + Bir int sabiti pratik bir biçimde float sabit yapmak için sayının sonuna nokta konulması yeterlidir. Örneğin: + + >>> a = 123 + >>> type(a) + + >>> a = 123. + >>> type(a) + + + C, C++, Java ve C# gibi dillerde sabitlerin sonlarına bazı sonekler getirilebilmektedir. Python'da böyle bir sonek + getirme durumu yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 3) bool türden iki sabit vardır: True ve False. Python'da bool türü aritmetik işlemlere sokulabilmektedir. Bu durumda + True 1 olarak False 0 olarak işleme girmektedir. Örneğin: + + >>> b = True + >>> x = b + 10 + >>> x + 11 + >>> type(x) + + + bool türü aritmetik işelmlere sokulduğunda diğer operand int ise sonuç int türden float ise sonuç float türden elde + edilir. Örneğin: + + >>> b = True + >>> x = True + 1.2 + >>> x + 2.2 + >>> type(x) + + + İki bool değeri kendi aralarında işleme sokarsak sonuç int türden elde edilmektedir. Örneğin: + + >>> x = True + True + >>> x + 2 + >>> type(x) + + + Python'daki bool sabitlerin ilk harflerinin büyük harf olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = True +>>> type(a) + +>>> b = False +>>> type(b) + +>>> c = a * 3 +>>> c +3 +>>> type(c) + +>>> d = True + True +>>> d +2 +>>> type(d) + + +#------------------------------------------------------------------------------------------------------------------------ + 4) Python'da bir yazı tek tırnak ya da çift tırnak arasına alınırsa bu ifade str türünden sabit belirtir. Bu bağlamda + tek tırnakla çift tırnak arasında hiçbir farklılık yoktur. Python'un 2'li versiyonları stringleri ASCII karakterleri + biçiminde saklıyordu. Ancak 3'lü versiyonlarla birlikte Python'da tüm string'ler UNICODE tablo esas alınarak tutulmaya + başlandı. UNICODE tabloda her karakter 2 byte ile temsil edilmektedir. Böylece Python stringlerinin içerisinde biz + dünyanın bütün karakterlerini yerleştirebiliriz. Boş bir string oluşturmak da geçerlidir. Örneğin: + + >>> s = 'ağrı dağı' + >>> s + 'ağrı dağı' + >>> type(s) + + >>> s = '' + >>> s + '' + >>> type(s) + +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 'ankara' +>>> a +'ankara' +>>> type(a) + +>>> b = 'ağrı dağı' +>>> b +'ağrı dağı' +>>> type(b) + +>>> c = "burdur" +>>> c +'burdur' +>>> type(c) + +>>> d = '' +>>> d +'' +>>> type(d) + + +#------------------------------------------------------------------------------------------------------------------------ + UNICODE tablonun ilk 128 karakteri standart ASCII tablosu ile aynıdır. İkinci 128'lik karakteri ASCII Latin-1 (ISO 8859-1) + code page'i ile aynıdır. ASCII ve UNICODE tablonun ilk 32 karakteri görüntülenemeyen (nonprintable) kontrol karakterlerinden + oluşmaktadır. Bu karakterleri yazdırmaya çalıştığımızda ekranda bir şey görmeyiz. Ancak bir eylem oluşur. Bu özel karakterlerin + bazıları ters bölü tekniği ile kullanıma sunulmuştur. Bir yazı içerisinde önce bir ters bölü sonra da onun yanına onunla + bitişik özel bazı karakterler getirilirse bu iki karakter aslında görüntüsü olmayan özel bazı karakterleri temsil eder. + Bunlara "ters bölü karakterleri" ya da İngilizce olarak "escape sequence" denilmektedir. Ters bölü karakterlerinin listesi + şöyledir: + + \a Alert (ekrana yazdırılırsa beep sesi çıkar) + \b Backspace (imleç backspace tuşuna basılmış gibi bir geri gider) + \f Form feed (bu karakter yazıcıya gönderilirse yazıcı bir sayfa atar) + \n New Line (imleç aşağı satırın başına geçirilir) + \r Carriage Return (imleç bulunulan satırın başına geçirilir) + \t (Horizontal) Tab (imleç yatay olarak ilk tab stop'a atlar) + \v Vertical Tab (İmleç düşey olarak bir tab atlar) + + Bu ters bölü karakterleri string'lerin içerisinde tek bir karakter olarak değerlendirilmektedir. Örneğin: + + s = 'ali\nveli' + print(s) + + Burada şöyle bir görüntü oluşacaktır: + + ali + veli + + Buradaki \n karakteri tek bir karakter olarak ele alınmaktadır. Örneğin: + + s = 'ali\rveli' + print(s) + + Burada önce ali yazısı ekrana yazılacak sonra \r karakteri yazdırılmak istendiğinde imleç bulunulan satırın başına geçecek + ve sonra veli yazısı ali yazısını ezerek oraya yazılacaktır. + + Python'da tek tırnak içerisindeki tek tırnak, çift tırnak içerisindeki çift tırnak soruna yol açmaktadır. Örneğin: + + s = 'Türkiye'nin başkenti Anakara'dır' # sentaks hatası! + + Burada Python yorumlayıcısı yazının içindeki tek tırnağı string oluşturmakta kullanılan tek tırnak olarak ele alır ve + ifade bir sentaks hatası oluşturur. İşte tek tırnak karakterini tek tırnak içerisinde sorunsuz bir biçimde yazabilmek + için \' kullaılmaktadır. \' yazı içerisinde gerçek tek tırmak anlamına gelmektedir. Örneğin: + + s = 'Türkiye\'nin başkenti Anakara\'dır' # geçerli + + Tabii çift tırnak içerisinde tek tırnak kullanmak bu biçimde bir sorun oluşturmaz. Örneğin: + + s = "Türkiye'nin başkenti Anakara'dır" # geçerli + + Tabii biz yine istersek tek tırnak karakterini çift tırnak içerisinde de \' biçiminde belirtiebiliriz. Aynı problem çift + tırnak içerisinde çift tırnak için de oluşmaktadır. Örneğin: + + s = "Bu "altın" değerinde bir kuraldır" # sentaks hatası + + Çift tırnak içerisinde çift tırnak karakterini \" biçiminde kullanabiliriz. Örneğin: + + s = "Bu \"altın\" değerinde bir kuraldır" # geçerli + + Tabii tek tırnak içerisinde çift tırnak kullanmak bir soruna yol açmaz. Örneğin: + + s = 'Bu "altın" değerinde bir kuraldır' # geçerli + + Tabii biz yine tek tırnak içerisinde çift tırnak karakterini \" biçiminde yazabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = 'ankara' +>>> a +'ankara' +>>> type(a) + +>>> b = 'ağrı dağı' +>>> b +'ağrı dağı' +>>> type(b) + +>>> c = "burdur" +>>> c +'burdur' +>>> type(c) + +>>> d = '' +>>> d +'' +>>> type(d) + +>>> e = 'Türkiye\'nin başkenti Anraka\'dır' +>>> print(e) +Türkiye'nin başkenti Anraka'dır +>>> f = "Türkiye'nin başkenti Ankara'dır" +>>> print(f) +Türkiye'nin başkenti Ankara'dır +>>> g = "Bu konuyu \"vurgulamak\" istiyorum" +>>> print(g) +Bu konuyu "vurgulamak" istiyorum +>>> h = 'Bu konuyu "vurgulamak" istiyorum' +>>> print(h) +Bu konuyu "vurgulamak" istiyorum + +#------------------------------------------------------------------------------------------------------------------------ + String içerisindeki ters bölü karakterinin kendisini ters bölü biçiminde yazarsak o karakter eğer yanındaki karakter + yukarıda listelediğimiz özel karakterlerinden biri ise tek karakter olarak değil yanındaki karakterle birlikte özel + anlamı olan başka bir karakter olarak ele alınır. Örneğin: + + path = 'C:\temp\a.dat' + + Burada \t karakteri "tab karakter" olarak, \a karakteri de "alert karakteri" olarak ele alınacak ve yol ifadesi yanlış + biçimde oluşturulacaktır. İşte bu tür durumlarda yazı ieçrisinde ters bölü karakterinin kendisinin yazılması isteniyorsa + bu karakter \\ biçiminde yazılmalıdır. Örneğin: + + >>> path = 'C:\temp\a.dat' + >>> print(path) + C: emp.dat + >>> path = 'C:\\temp\\a.dat' + >>> print(path) + C:\temp\a.dat + + C, C++, Java ve C# gibi dillerde ters bölü karakterinin yanındaki karakter yukarıda listelediğimiz özel karakterlerdne + biri değilse ters bölü karakterlerinden biri değilse bu durum sentaks bakımından geçerli kabul edilmemektedir. Halbuki + Python'da ters bölü karakterinin yanındaki karakter yukarıda listesini verdiğimiz özel karakterlerinden biri değilse + artık orada ters bölü karakteri gerçekten ters bölü karakteri olarak ele alınmaktadır. Örneğin: + + path = "C:\day\month" + + Bu string C'de geçersizdir. Ancak Python'da geçerlidir. Çünkü \d ve \m biçiminde bir özel karakter yoktur. Dolayısıyla + \d karakterleri gerçekten ters bölü karakteri ve d karakteri olarak ele alınmaktadır. Fakat programcının yine de ters + bölü karakterinin kendisini iki ters bölü karakteri ile yazması tavsiye edilmektedir. Örneğin: + + path = "C:\\day\\month" + + Örneğin: + + >>> path = "C:\day\month" + :1: SyntaxWarning: invalid escape sequence '\d' + >>> print(path) + C:\day\month + >>> path = "C:\\day\\month" + >>> print(path) + C:\day\month + + Yeni Python yorumlayıcıları bu tür durumlarda uyarı mesajı da verebilmektedir. + + Komut satırında str türünden bir değişkeni print fonksiyonu ile yazdırdığımızda gerçekten ters bölü karakterleri kendi + işlevleri ile işlem görmektedir. Ancak str türünden değişkenin ismini yazıp ENTER tuşuna bastığımızda bu durumda ilgili + yazı programcıların kolay anlaması için ters bölülü biçimde görüntülenmektedir. Örneğin: + + >>> s = 'ali\nveli' + >>> print(s) + ali + veli + >>> s + 'ali\nveli' +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir string'in önüne (yani soluna) onunla yapışık 'r' ya da 'R' karakteri getirilirse (Buradaki r "regular" sözcüğünden + geliyor) bu string'ler içerisindeki ters bölü karakterleri gerçekten ters bölü karakterleri anlamına gelir. Tabii içinde + hiç ters bölü karakteri olmayan string'lerin başına 'r' ya da 'R' getirmenin bir anlamı yoktur. Ancak yasak da değildir. +#------------------------------------------------------------------------------------------------------------------------ + +>>> path = 'C:\temp\a.dat' +>>> print(path) +C: emp.dat +>>> path = 'C:\\temp\\a.dat' +>>> print(path) +C:\temp\a.dat +>>> path = r'C:\temp\a.dat' +>>> print(path) +C:\temp\a.dat +>>> path = R'C:\temp\a.dat' +>>> print(path) +C:\temp\a.dat +>>> s = r'ali, veli, selami' # başına r getirmenin bir anlamı yok +>>> print(s) +ali, veli, selami + +#------------------------------------------------------------------------------------------------------------------------ + Python'da tek tırnak ve çift tırnak içerisindeki yazılar tek satıra (aynı satıra) yazılmak zorundadır. Örneğin: + + s = 'ali veli + selami' + + Bu yazım geçersizdir. Ancak aralarında hiçbir ooperatmr olmayan "aynı satıra yazılmış olan" iki string sanki string'miş + gibi yorumlayıcı tarafından birleştirilmektedir. Örneğin: + + >>> s = 'bugün hava' ' çok güzel' + >>> print(s) + bugün hava çok güzel +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da string'ler tek tırnağın ve çift tırnağın yanı sıra üç tek tırnak ve üç çift tırnakla da belirtilebilirler. + (İki tek tırnak ya da iki çift tırnak biçiminde bir sentaks yoktur.) Örneğin: + + >>> s = """ankara""" + >>> print(s) + ankara + >>> s = '''izmir''' + >>> print(s) + izmir + + Üç tek tırnak ile üç çift tırnak arasında bir farklılık yoktur. Üçlü tırnakların tekli tırnaklardan iki farklılığı vardır: + + 1) Normalde tek tırnak içerisindeki yazılar tek bir satırda yazılmak zorundadır. Ancak üç tırnaklı yazılar farklı + satırlara bölünerek yazılabilir. + + 2) Üç tek tırnak içerisinde tek tırnak, üç çift tırnak içerisinde çift tırnak bir soruna yol açmaz. + + + + String'in tek tırnak ya da üç tırnak ile ifade edilmesi arasında ters bölü karakter sabitleri bakımından bir farklılık yoktur. + Yine üç tırnaklı string'lerin başına 'r' ya da 'R' öneki getirilebilir. Bu durumda yine buradaki ters bölü karakterleri gerçekten + ters bölü karakteri olarak değerlendirilir. + +#------------------------------------------------------------------------------------------------------------------------ + + +s = '''Bugün hava +çok güzel''' +print(s) + +s = '''Türkiye'nin başkenti Ankara'dır''' +print(s) + +s = '''c:\temp\a.dat''' +print(s) + +s = r'''c:\temp\a.dat''' +print(s) + +s = """"Türkiye'nin başkenti Ankara'dır. "Ankara" büyük bir şehirdir""" +print(s) + +s = '''Türkiye'nin başkenti Ankara'dır. "Ankara" büyük bir şehirdir''' +print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Üç tek tırnağın ya da üç çift tırnağın içerisindeki tek tırnak ya da çift tırnak yazının başında olabilir. Ancak istisna olarak + sonunda olamaz. Çünkü Python'da ardışıl en uzun karakterlerden atom yapılmaktadır. Örneğin: + + s = """"ankara"""" + + Burada iki tırnak içerisinde "ankara" yazılmak istenmiştir. Bunu Python yorumlayıcısı şöyle ayrıştıracaktır ayıracaktır: + + s + = + """ + "ankara + """ + " (burada tek kalıyor) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'un 2'li versiyonlarında bir byte'lık ASCII tablosunu kullanılıyordu. Bu versiyonlarda UNICODE string'ler için + string'lere yapışık 'u' ya da 'U' önekleri kullanılıyordu. Ancak Python'ın stringleri zaten üçlü versiınlarla birlikte + default UNICODE haline getirilmiştir. Bugün hala bu 'u' ya da 'U' öneki geçerli olsa da artık bunun bir işlevi kalmamıştır. +#------------------------------------------------------------------------------------------------------------------------ + +s = u'Ağrı Dağı çok yüksek' # artık u önekine hiç gerek yok +print(s) + +s = 'Ağrı Dağı çok yüksek' +print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Tek tırnak ya da üç tırnağın önüne onunla yapışık 'b' ya da 'B' öneki getirilirse elde edilen nesne "bytes" denilen bir + türden olur. Yani artık bu nesne bir string değildir. Bu konu ileride ele alınacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +s = b'Bu bir denemedir' +print(s) +print(type(s)) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da (tıpkı C Programlama Dilinde olduğu gibi) aralarında hiç bir atom olmayan yan yana iki string otomatik olarak + birleştirilmektedir. Tabii bu iki string'in aynı satır üzerinde olması gerekir. Örneğin: + + s = 'ali' 'veli' + + ile, + + s = 'aliveli' + + aynı anlamdadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 5) None anahtar sözcüğü NoneType türünden sabit belirtmektedir. Zaten bu türden tek sabit None anahtar sözcüğüdür. None + değeri REPL ortamında ekrana bir şey basmamaktadır. None değeri print edilirse ekrana None yazısı çıkar. +#------------------------------------------------------------------------------------------------------------------------ + +>>> a = None +>>> a +>>> print(a) +None +>>> type(a) + + +#------------------------------------------------------------------------------------------------------------------------ + 6) Python'da 'j' harfi önünde yapışık bir float ya da int değerle kullanıldığında Complex türden sabit anlamına gelir. + Complex sabitler 'j' nin yanı sıra + ya da - operatörü ile bir gerçek kısma da sahip olabilirler. Yalızca j değerine + sahip karmaşık sayı oluşturmak için 1j ifadesini kullanmak gerekir. Çünü tek başına j bir değişken ismi olarak ele + alınmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +z = 1j +print(z) + +z = -2j + 5 +print(z) + +z = 0j + 2 +print(z) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da açıklama yazıları oluşturmak için (comment/remark) # kullanılmaktadır. # karakterinden satır sonuna kadarki + karakterler yorumlayıcı tarafından dikkate alınmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +pi = 3.14159 +radius = 2 + +area = pi * radius * radius # dairenin alanı + +#------------------------------------------------------------------------------------------------------------------------ + Python'da etkisiz kod oluşturmak geçersiz değildir. Örneğin: + + 10 + 20 + + Böyle bir kodun bir anlamı yoktur. Çünkü bu kod program içerisinde bir durum değişikliği (side effect) oluşturmaz. Bu + bağlamda bir değişkene atanmamış string'ler de etkisiz koddur ve geçerlidir. O halde Python'da birden fazla satır üzerinde + yorumlama yapabilek için üç tırnaklı string'ler kullanılabilir. Gerçekten de Python programcıları birden fazla satıra + yayılmış yorumlamaları üç tırnaklı string'lerle yaparlar. +#------------------------------------------------------------------------------------------------------------------------ + +""" +Merhaba Dünya +Programı +""" + +print('Hello World') + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bir değişken ona ilk kez değer atandaığında yaratılmaktadır. Değişken isimlendirmesinde diğer programlama + dillerinin çoğunda söz konusu olan kurallar geçerlidir: + + - Değişken ismi sayısal karakterlerle başlatılamaz, ancak alfabetik karakterlerle başlatılıp sayısal karakterlerle + devam ettirilebilir. + - Değişken isimleri anahtar sözcüklerden oluşturulamaz. + - Değişken isimleri boşluk karakterlerini içeremez. + - Değişken isimleri operatör karakterlerini içermez. + - Python "büyük harf-küçük harf duyarlılığı olan (case sensitive)" bir programlama dilidir. Yani değişken isimlerindeki + büyük harflerle küçük harfler birbirinden farklıdır. C, C++, Java ve C# dilleri de böyledir. Ancak Pascal, Basic gibi + dillerde büyük harf küçük harf duyarlılığı yoktur (case insensitive). + - Python 3'lü versiyonlarla birlikte UNICODE karakter tablosunu kullanmaya başlamıştır. Dolayısıyla değişken isimlendirmede + UNICODE tablodaki bütün dillerin alfabetik karakterleri kullanılabilmektedir. Örneğin aşağıdaki gibi bir değişken ismi + geçerlidir: + + ağrı_dağının_yüksekliği = 5137 + + - Python Language Reference içerisinde değişkenlerin maksimum uzunluğu konusunda bir şey söylenmemiştir. Bu durum + değişkenlerin yeterince uzun olabileceği ancak bu limitin yorumlayıcıyı yazanların isteğine bırakılmış olduğu anlamına + gelir. + + Birden fazla sözcükten oluşan değişkenlerin harflendirilmesinde üç yöntem sıklıkla kullanılmaktadır: + + 1) Klasik C tarzı harflendirme (yılan (snake) notasyonu da denilmektedir): Burada sözcüklerin arasına alt tire getirilir. + Örneğin: + + number_of_students + loop_count + + 2) Deve Notasyonu (Camel Casting): Burada ilk sözcüğün tamamı küçük harflerle yazılır. Sonraki her sözcüğün yalnızca ilk + harfi büyük harfle yazılır. Örneğin: + + numberOfStudents + loopCount + + 3) Pascal Notasyonu (Pascal Casting): Pascal dilinde tercih edilen notasyon olduğu için bu isim verilmiştir. Burada her + sözcüğün ilk harfi büyük yazılır. Örneğin: + + NumberOfStudents + LoopCount + + Her ne kadar Python büyük harf-küçük harf duyarlılığı olan bir dil ise de Python'da küçük harf yoğun bir harflendirme + tercih edilmektedir. + + Biz kursumuzda en yaygın harflandirme biçimi olan klasik C tarzı harflendirmeyi (yani sözcüklerin arasına alt tire + getirme yöntemini) kullanacağzı. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + CPU'ya elektriksel olarak doğrudan bağlı belleklere "ana bellek (main memory)" ya da "birincil bellek (primary memory") + ya da halk arasında "RAM" denilmektedir. RAM'deki her byte'a ilk byte 0 olmak üzere artan sırada bir sayı karşılık düşürülmüştür. + Bu sayıya "ilgili byte'ın adresi" denilmektedir. Bellekte erişilebilen bölgere "nesne (object)" denilmektedir. Nesneler bir byte + ya da daha uzun olabilirler. 1 byte'tan daha uzun nesnelerin adresleri onların en küçük adresiyle ifade edilir. + + Programlama dillerindeki değişkenler birer nesne belirtmektedir. Yani onların da adresleri vardır. c = a + b gibi bir işlemde + a, b, c aslında RAM'de bulunur. CPU a ile b'yi kendi içine çeker. İşlem CPU tarafından elektrik devrelerle yapılmaktadır. + Sonuç yeniden RAM'e aktarılmaktadır. CPU nesnelerin isimleriyle değil adresleriyle çalışmaktadır. + + Bir nesnenin içerisinde tipik olarak bir değer tutulur. Ancak bazı nesnelerin içerisinde başka nesnelerin adresleri vardır. + İşte başka nesnelerin adreslerini tutan nesnelere C Programlama Dİlinde "pointer", Java ve C# gibi dillerde ise "reference" + denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da değişkenler her zaman adres tutarlar. Bir değişkene bir değer atandığında aslında yorumlayıcı değişkene o değerin bulunduğu + yeri adresini atamaktadır. Örneğin: + + a = 123 + + Burada yorumlayıcı RAM'de bir int nesne yaratır. Onun içerisine 123 değerini yerleştirir. Onun adresini a'ya atar. Birazdan biz şöyle yapmış olalım: + + a = 3.14 + + Şimdi derleyici RAM'de bir float nesne oluşturur. Onun içerisine 3.14 değerini yerleştirir. Bu kez onun adresini a'ya atar. + Python'da tüm atamalar adres atamasıdır. Biz Python'da "değişken" terimini adres tutan nesneler için kullanıyoruz. Ancak nesne terimini + değişkenlerin gösterdiği yer için kullanıyoruz. Aslında C, C++ gibi programlama dillerinin terminolojisinde hem değişkenler hem de + onların gösterdikleri yerdeki değerler birer nesnedir. Ancak Python'da biz değişken demekle içerisinde adres olan isimli varlıkları kastedeceğiz. + Değişkenlerin gösterdiği yerlere nesne diyeceğiz. + + Dinamik tür sistemine sahip programlama dillerinde genel olarak tür bilgisi değişkenin içerisinde değil nesnenin içerisinde turulmaktadır. + Yani değişken o anda hangi nesneyi gösteriyorsa o türden olmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkenin içerisindeki adres id isimli built-in bir fonksiyonla elde edilebilir. (Aslında Python referans kitabına göre + id fonksiyonu nesne ile ilgili tek (unique) bir değer vermelidir. Anack tipik olarak bu değer o nesnenin adresidir.) + + Aşağıdaki örnekte değişkene farklı değerler atandığında onun içerisindeki adresin değiştiğini göreceksiniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = 123 +print(id(a)) + +a = 'this is a test' +print(id(a)) + +a = 12.3 +print(id(a)) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da tüm atamalar aslında adres atamasıdır. Ancak bir değişken atama dışında kullanıldığında o değişkenin içindeki adresteki nesne kullanılır. + Örneğin: + + a = 10 + b = 20 + c = a + b + + Burada aslında a'nın ve b'nin içerisinde birer adres vardır. Ancak a + b işleminde bu adresler değil bu adreslerin gösterdiği yerdeki int nesnelerin değerleri + toplanır. Toplama sonucunda yeni int nesne yaratılır. c'ye yeni yaratılmış olan int nesnenin adresi atanır. Örneğin: + + >>> a = 10 + >>> b = 20 + >>> c = a + b + >>> id(a) + 140711499592776 + >>> id(b) + 140711499593096 + >>> id(c) + 140711499593416 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da tür bilgisi değişkenin içerisinde saklanmaz. Nensenin içerisinde saklanır. Örneğin: + + a = 123 + + Burada a değişkeni içerisinde 123 olan int bir nesnenin adresini tutulmaktadır. Bu int nesnenin içerisinde yalnızca 123 değeri yoktur. + Bu nesnenin tür bilgisi de vardır. Ancak biz kursumunda çizimlerde nesnenin içerisindeki tür bilgisi alanını göstermeyeceğiz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da tüm atamalar adres atamalarıdır. Bir değişkeni bir değişkene atadığımızda biz aslında o değişkenin içindeki adresi diğer değişkene atamış oluruz. + Örneğin: + + a = 10 + b = a + + Burada b = a işlemi ile aslında biz a değişkeninin içerisindeki adresi b değişkenine atamış olduk. Yani burada hem a hem de b + aynı nesneyi göstermektedir. Örneğin: + + >>> a = 100 + >>> b = a + >>> id(a) + 140711499595656 + >>> id(b) + 140711499595656 + + Python'da bir değişken bir değişkene atandığında aslında o değişkenin içerisindeki adres diğer değişkene atanmaktadır. Atama sonrasında + her iki değişken de aynı nesneyi gösteriyor durumda olur. Örneğin: + + a = b + + Burada artık b de a da aynı nesneyi gösterecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da türler (types), "değiştirilebilir (mutable)" ve "değiştirilemez (immutable)" olmak üzere ikiye ayrılmaktadır. + Değiştirilebilir türler türünden bir nesne yaratıldığında o nesnenin içerisindeki değer herhangi bir zaman değiştirilebilir. + Değiştirilemez türler türünden bir nesne yaratıldığında ise o nesnenin içerisindeki değerler nesne yaratılırken onun içerisine yerleştirilir. + Bir daha da değiştirilemez. Python'ın 6 temel türü de kategorik olarak "değiştirilemez" türlerdir. Daha açık bir biçimde belirtirsek: + + int türü değiştirilemezdir + float türü değiştirilemezdir + str türü değiştirilemezdir + bool türü değiştirilemezdir + complex türü değiştirilemezdir + NoneType türü değiştirilemezdir + + Programcının değiştirilemez türden bir nesne yaratılırken belli bir değerle yaratıldığını bir daha da o değerin asla değişmeyeceğini + biliyor olması gerekir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + int, float, str, bool, complex, NoneType türlerinin "değiştirilemez" olmasının ilginç bazı sonuçları söz konusudur. Örneğin: + + >>> a = 10 + >>> id(a) + 140711499592776 + >>> a = 20 + >>> id(a) + 140711499593096 + + Burada a değişkeni int türdendir. int nesne de kategorik olarak "değiştirilemez" bir türdür. O halde a'ya ikinci kez + değer atandığı durumda a = 20 işlemi için önceki nesnenin içerisindeki 10 değiştirilemeyeceğinden dolayı içerisinde + 20 olan farklı bir int nesne yaratılacak ve a artık o int nesneyi gösterecektir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +print(id(a)) + +a = 20 +print(id(a)) + +#------------------------------------------------------------------------------------------------------------------------ + Bu durumda örneğin iki değişken aynı int nesneyi gösteriyor durumda olsun. Bunlardan birine atama yapıldığında diğerinin değeri + değişmez. Örneğin: + + a = 10 + b = a + + Burada a ve b aslında aynı nesneyi göstermektedir. Şimdi şu işlemi yapmış olalım: + + a = 20 + + Burada int türü değiştirilemez olduğu için yeni bir int nesne yaratılacak onun içerisine 20 yerleştirilecek ve a da artık bu + yeni nesneyi gösterecektir. Halbuki b içerisinde 10 olan eski nesneyi göstermektedir. Yani buradaki semantik + C, C++, Java ve C# gibi dillerdekiyle sonuç itibariyle aynı olacaktır. + +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = a +a = 20 + +print(a) # 20 +print(b) # 10 + +#------------------------------------------------------------------------------------------------------------------------ + Temel türlerin değiştirlemez olması Python'a geçen pek çok kişi tarafından yadırganmaktadır. Çünkü böylesi bir tasarım + bazı basit işlemlerde bile yavaşlığa yol açabilmektedir. Örneğin bir döngü içerisinde sürekli bir değişkeni 1 artırdığımızı + düşünelim. Döngünün her yinelenmesinde yeni bir nesne yaratılacak belki de eski nesne çöp toplayıcı tarafından silinecektir. + Bunun yavaşlığa yol açacağı muhakkaktır. Her ne kadar döngüleri bilmiyor olsak da aşağıda böyle bir örnek veriyoruz: + + a = 0 + + for i in range(10): + print(a, id(a)) + a = a + 1 + + Burada a değişkeninin id'sinin sürekli değiştirğini göreceksiniz: + + 0 2172851716304 + 1 2172851716336 + 2 2172851716368 + 3 2172851716400 + 4 2172851716432 + 5 2172851716464 + 6 2172851716496 + 7 2172851716528 + 8 2172851716560 + 9 2172851716592 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'ın geniş bir standart kütüphanesi vardır. Bu standart kütüphanede neredeyse pek çok konuya ilişkin fonksiyonlar + ve sınıflar hazır bir biçimde bulunmaktadır. + + Standrat kütüphanedeki fonksiyonların ve sınıfların önemli bir bölümü çeşitli modüllerin içerisine yerleştirilmiştir. + Bu fonksiyonları ve sınıfları kullanmadan önce o modülün "import edilmesi" gerekmektedir. Örneğin karekök almakta kullanılan + sqrt fonksiyonu "math" isimli bir modülün içerisindedir. O halde sqrt kullanılmadan önce bu modülün aşağıdaki gibi import edilmesi gerekir: + + import math + + Modülün içerisindeki fonksiyonlar ve sınıflar modül ismi ve '.' operatörüyle niteliklendirilerek kullanılırlar. Örneğin: + + a = math.sqrt(10) + + gibi. Burada biz sqrt fonksiyonunu doğurdan değil math.sqrt biçiminde kullandık. Ancak önce math modülünü import ettik. + import etmenin ne anlama geldiğini ileride ayrı bir başlık altında ele alacağız. + + Python'ın standart kütüphanesi python.org sitesinde "Python Standcart Library" ismiyle dokmante edilmiştir. Bu dokümana + aşağıdaki bağlantıdan erişebilirsiniz: + + https://docs.python.org/3/library/index.html + + O halde Python için iki önemli doküman vardır: + + 1) Python dilinin resmi anlatımını yapan doküman: Python Language Reference + + https://docs.python.org/3/reference/index.html + + 2) Python'ın standart kütüphanesini açıklayan doküman: Python Standard Library + + https://docs.python.org/3/library/index.html + + Tüm Python dokümanları da aşağıdaki bağlantıda bulunmaktadır: + + https://docs.python.org/3/ +#------------------------------------------------------------------------------------------------------------------------ + +import math + +result = math.sqrt(10) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Standart kütüphane içerisindeki bazı fonksiyonları ve sınıfları hiç import işlemi yapmadan doğrudan kullanabilmekteyiz. + İşte bu fonksiyonlara ve sınıflara "built-in" fonksiyonlar ve sınıflar denilmektedir. Örneğin print ve id fonksiyonları built-in + fonksiyonlardır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da ekrana (yani stdout dosyasına) yazan tek bir print fonksiyonu vardır. Benzer biçimde klavyeden (yani stdin dosyasından) + okuyan da tek bir input isimli fonksiyon vardır. input fonksiyonu yazı parametresi alır. Önce yazıyı ekrana basar. Sonra giriş ister. + Kullanıcı bir yazı girerek ENTER tuşuna basar. input fonksiyonu da girilmiş olan yazıyı bize str nesnesi oalrak verir. + input parantezleri boş bırakılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +s = input('Bir yazı giriniz:') +print(type(s)) +print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii Python'da her türlü atama "adres ataması" anlamına geldiğine göre aslında biz input fonksiyonunun geri dönüş değerini + bir değişkene atadığımızda string nesnesinin adresini o değişkene atamış oluru. Örneğin: + + s = input('Bir yazı giriniz:') + + Burada s değişkenine bir str nesnesinin adresi atanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + input fonksiyonu her zaman okunan yazıyı string olarak verir. Eğer biz int ya da float ya da diğer türlerden okuma yapmak istiyorsak bu yazıyı + int türüne, float türüne ya da diğer türlere dönüştürmeliyiz. Bu dönüştürma aşağıdaki gibi yapılmaktadır: + + a = int(input('Bir sayı giriniz:)) + + Burada input fonksiyonundan alınan yazı int fonksiyonuna sokulmuştur. Buradaki int fonksiyonu yazıyı int türüne dönüştürmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) + +print(a * a) + +b = float(input('Bir değer giriniz:')) +print(b * b) + +#------------------------------------------------------------------------------------------------------------------------ + Programın çalışma zamanı sırasında ortaya çıkan ciddi hatalara "exception" denilmektedir. Bir exception oluştuğunda + programcı oluşan bu exception'ı yakalayıp programın çalışmasını devam ettirebilir. Ancak programcı exception'ı yakalamazsa + program çöker. Exception işlemleri ileride ayrıntılarıyla ele alınacaktır. Örneğin biz klavyeden int bir değer okumak isterken + klavyeden girdiğimiz karakterler uygun değilse exception oluşur. Oluşan exception'ların bir türü vardır. Bu tür genellikle + XXXError biçiminde isimlendirilmiştir. Örneğin ValueError, TypeError gibi. + + Aşağıdaki örnekte bir sayı yerine bir isim girerek exception oluşturunuz. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) + +print(a * a) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da altprogramlara "fonksiyon (function)" denilmektedir. Küçük olmayan bir programın tek parça halinde yazılması iyi bir + teknik değildir. Programlar prosedürel teknikte mantıksal bakımdan parçalara ayrılır. Parçalar fonksiyonlar biçimin de yazılır. + Sonra da bu fonksiyonlar çağrılarak program çalıştırılır. Bir işin parçalara bölünüp parçalardan bütünün oluşturulması yalnızca + programlamada değil pek çok mühendislik faaliyette uygulanan bir tekniktir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyon ya bir sınıfın içerisindedir ya da sınıfın içerisinde değildir. Python dünyasında sınıfın içerisindeki fonksiyonlara + "metot (method)" denilmektedir. Yani Python dünyasında "fonksiyon (function)" sınıfın içerisinde olmayan yordamlar için "metot (method)" + ise sınıfın içerisinde bulunan yordamlar için kullanılmaktadır. + + Bir fonksiyon çağrıldığında onun içerisindeki kod çalıştırılır. Fonksiyonun çalışması bitince akış kalınan yerden devam eder. Fonksiyon + çağırma işleminin genel biçimi şöyledir: + + ([argüman listesi]) + + Görüldüğü gibi fonksiyon isminden sonra (...) bulunmakta ve onların içlerine de "argümanlar" yazılmaktadır. Argümanlar herhangi birer ifade + olabilir. Eğer argümanlar birden fazla ise ',' atomu ile ayrılmaktadır. Örneğin: + + print(a) + + Burada a bir argümandır. Örneğin: + + print(a, b, c) + + Burada birden fazla argüman kullanılmıştır. Bir arüman ifadelerden oluşabilir. Örneğin: + + print(a + b, c + d) + + Burada iki argüman vardır. Argümanlardan biri a + b diğeri ise c + d biçimindedir. + + Bir fonksiyon bir modülün içerisindeyse o fonksiyonu çağırmak için modülün ismi de kullanılmaktadır. Örneğin: + + math.sqrt(10) + + gibi. + + Ancak bir metodun çağrılması için o metodun içinde bulunduğu sınıf türünden bir değişkenin bulunuyor olması gerekir. Metot çağırma işleminin + genel biçimi de şöyledir: + + .([argüman listesi]) + + Bir modül içerisindeki fonksiyonu çağırma biçimiyle bir metodu çağırma biçimi birbirne benzemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Son 20 yıldır programalama anlatan dokümanlarda öylesine uydurulmuş fonksiyon isimleri için "foo", "bar", "tar" gibi + isimler kullanılmaktadır. Biz de kursumuzda çeşitli örneklerde bu isimleri kullanacağız. Bu isimlerin hiçbir özel anlamı + yoktur. Öylesine uydurulmuş isimlerdir. Örneğin: + + foo() + bar() + tar() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da is operatörü iki değişkenin aynı nesneyi gösterip göstermediğini anlamak için kullanılmaktadır. Bu operatör bool bir değer + üretmektedir. Eğer bu operatör True değer üretirse iki değişken aynı nesneyi gösteriyor durumdadır (yani onların içerisinde aynı adres vardır). False + değer üretirse bu durum iki değişkenin aynı nesneyi göstermediği anlamına gelir. Bu durumda a is b ile id(a) == id(b) aynı anlamdadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +result = a is b +print(result) # False + +a = 100 +b = a + +result = a is b +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + is not operatörü de is operatörünün tersi işlemi yapmaktadır. Yani a is not b işlemi eğer a'nın içerisindeki adres b'nin içerisindeki + adresten farklıysa True değerini, aynısya False değerini üretmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +result = a is not b +print(result) # True + +a = 100 +b = a + +result = a is not b +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + Python'da None değerini içeren tek bir nesne vardır. Dolayısıyla biz farklı değişkenlere None değerini atadığımızda + aslında bu değişkenlere aynı adresi atamış oluruz. Örneğin: + + a = None + b = None + + Burada a is b her zaman True değerini verecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Derleyiciler ve yorumlayıcılar kodu daha hızlı çalışacak ve/veya daha az yer kaplayacak biçimde yeniden düzenleyebilmektedir. + Buna "kod optimizasyonu" denilmektedir. Optimizasyon kodun anlamı değişmeyecek biçimde özenle yapılmaktadır. Örneğin + Python'da farklı değikenlere aynı sabitleri atadığımızda temel türler değiştirilebilir olmadığı için aslında yorumlayıcı + bu sabitler için tek bir yer ayırıp değişkenlere aynı nesnenin adresini atayabilmektedir. Örneğin: + + >>> a = 10 + >>> b = 10 + >>> c = 8 + 2 + >>> a is b + True + >>> a is c + True + >>> b is c + True + >>> id(a) + 140732013605960 + >>> id(b) + 140732013605960 + >>> id(c) + 140732013605960 + + Kod optimizasyonları tamamen çalışmakta olduğunuz derleyici ya da yorumlayıcıya hatta bazen onların versiyonlarına bağlı + biçimde yapılmaktadır. Bir optimizasyonu bir Python yorumlayıcısı yaparken diğeri yapmayabilir. Önemli olan böyle bir + kodu yeniden düzenleme hakkının yorumlayıcıya verilmiş olmasıdır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 12345 +b = 12345 + +result = a is b +print(result) + +s = 'ali' +k = 'ali' + +result = s is k +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Bir işleme yol açan işlem sonucunda bir değerin üretilmesini sağlayan atomlara operatör (operator) denilmektedir. Operatörler + genellikle üç biçimde sınıflandırılmaktadır: + + 1) İşlevlerine Göre + 2) Operand Sayılarına Göre + 3) Operatörün Konumuna Göre + + İşlevlerine göre sınıflandırma operatörün hangi konu ile ile ilgili işlem yaptığı ile ilgilidir. Tipik sınıflar şunlardır: + + - Artitmetik Operatörler (Aritmetic Op.) + - Karşışaltırma Operatörleri (Comparision Op.) + - Mantıksal Operatörler (Logical Op.) + - Bit Operatörleri (Bitwise Op.) + - Özel Amaçlı Operatörler (Special Pupose Op.) + + Operatörün işleme soktuğu atomlara operand denilmektedir. Operatörler "tek operandlı (unary)", "iki operandlı (binary)" ve "üç operandlı (ternary)" olabilirler. + Örneğin / operatörü "iki opendlı (binary)" bir operatördür. Çünkü a / b biçiminde iki değeri işleme sokmaktadır. Ancak örneğin "not" operatörü + tek operandlı (unary) bir operatördür. not a biçiminde tek bir değeri işleme sokmaktadır. Programlama dillerinde üç operandlı operatörler + seyrek bir biçimde bulunurlar. Python'da üç operandlı tek bir operatör vardır. + + Operatör operandlarının önüne getirilerek kullanılıyorsa bunlara "önek (prefix)", sonuna getirilerek kullanılıyorsa bunlara "sonek (postfix)" + ve arasına getirilerek kullanılıyorsa bunlara da "araek (infix)" operatörler denilmektedir. Örneğin / operatörü araek bir operatördür. Ancak not + operatörü önek bir operatördür. + + Bir operatörü ele alırken bu üç dınıflandırmada da nereye düştüğünün belirtilmesi gerekir. Örneğin "/ operatörü iki openadlı araek (binary infix) + aritmetik operatördür". Ya da örneğin "not operatörü tek operandlı önek (unary prefix)" bir mantıksal operatördür" gibi. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir ifadede birden fazla operatör varsa bu operatörler belli bir sırada yapılır. Buna "operatörler arasındaki öncelik ilişkisi + (operator precedency)" denilmektedir. Örneğin: + + a = b + c * d + + Burada üç operatör vardır. İşlemler şu sırada yapılacaktır: + + İ1: c * d + İ2: b + İ1 + İ3: a = İ2 + + Çünkü * operatörü + operatörüne göre daha önceliklidir. Öncelik sırasını değiştirmek için parantezlerden faydalanılmaktadır. Örneğin: + + a = (b + c) * d + + İ1: (b + c) + İ2 = İ1 * d + İ3: a = İ2 + + Python'da pek çok operatör bulunduğu için programcının hangi operatörün hangi operatörden daha öncelikli olduğunu bilmesi gerekmektedir. + + Operatörler arasındaki öncelik ilişkisi "operatörlerin öncelik tablosu" denilen bir tabloyla betimlenmektedir. Bu tablo satırlardan oluşur. + Her satırın yanında "soldan-sağa" ya da "sağdan sola" ibaresi vardır. Tabloda üst satırdaki operatörler alt satırdaki operatörlerden + daha yüksek önceliklidir. Aynı satırda bulunan operatörler "ifade içerisindeki (tablodaki değil) konumlarına göre soldan sağa ya da sağdan sola öncelikli" + yapılmaktadır. Aşağıda öncelik stablosunun yalın bir biçimi görünmektedir: + + () soldan sağa + * / soldan sağa + + - soldan sağa + = sağdan sola + + Bu durumda örneğin: + + a = b - c * d + e + + İ1: c * d + İ2: b - İ1 + İ3: İ2 + e + İ4: a = İ3 + + Öncelik tablosunda bir satırdaki operatörlerin sırasının hiçbir önemi yoktur. Çünkü "soldan sağa ya da sağdan sola ibaresi + ifadedeki sırayı belirtmektedir, tablodaki sırayı belirtmemektedir." +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + *, /, + ve - operatörleri iki operand'lı araek aritmetik operatörlerdir. Bunlar temel dört işlemi yaparlar. Öncelik tablosounda + * ve / operatörleri + ve - operatörlerinden daha yüksek önceliklidir. + + () soldan sağa + * / soldan sağa + + - soldan sağa + = sağdan sola + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da / operatörünün her iki operandı int olsa bile bu operatör float bir değer üretmektedir. Bu anlamda / operatörünün + davranışı C, C++, Java ve C#'takinden farklıdır. +#------------------------------------------------------------------------------------------------------------------------ + +result = 10 / 4 + +print(result, type(result)) # 2.5, + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da noktadan sonraki kısmı atan // biçiminde ayrı bir operatör de vardır. Bu operatöre "floordiv" operatörü denilmektedir. + Bu operatörde elde edilen sonuç pozitifse sayının noktadan sonraki kısmı atılır. Tam kısmı elde edilir. Operandların + her ikisi de int ise sonuç int türden olur. Operand'ların en az biri float türdense sonuç float olur. +#------------------------------------------------------------------------------------------------------------------------ + +result = 10 // 4 +print(result, type(result)) # 2 + +result = 10. // 4 +print(result, type(result)) # 2.0 + +#------------------------------------------------------------------------------------------------------------------------ + // operatörü sonuç üzerinde "floor" işlemi uygulamaktadır. floor işlemi bir noktalı sayının kendisinden küçük ya da + ona eşit olan en yakın tamsayıya dönüştürülmesine denilmektedir. Bu davranış C, C++, Java ve C#'takinden farklıdır. + Örneği Python'da 10 // 4 bize 2 verir. Çünkü 2.5 değerinin floor işlemine sokulmasından 2 değeri elde edilmektedir. + Ancak -10 // 4 bize -3 verir. Çünkü -2.5 değerinin floor işlemine sokulmasından -3 elde edilmektedir. C, C++, Java + ve C#'taki davranışa "truncation toward zero" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +result = -10 // 4 +print(result, type(result)) # -3 + +#------------------------------------------------------------------------------------------------------------------------ + + // operatörü öncelik tablosunda * ve / ile soldan sağa öncelikli satırda bulunmaktadır. + + () soldan sağa + * / // soldan sağa + + - soldan sağa + = sağdan sola + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + % operatörü iki operandlı araek bir aritmetik operatördür. Bu operatör soldaki operandın sağdaki operanda bölümünden + elde edilen kalan değerini üretir. Operatörün her iki operandı da int türündense sonuç int türünden elde edilir. + Operandlardan en az biri float ise sonuç float türden olur. % operatörü de öncelik tablosunda * , / ve // operatörü + ile aynı satırda bulunmaktadır: + + () soldan sağa + * / // % soldan sağa + + - soldan sağa + = sağdan sola + +#------------------------------------------------------------------------------------------------------------------------ + +result = 20 % 3 +print(result, type(result)) # 2 + +result = 20.5 % 3 +print(result) # 2.5 + +#------------------------------------------------------------------------------------------------------------------------ + a sayısını b ye böldüğümüzde bölüm c kalan d ise aslında a = c * b + d ilişkisi söz konusudur. Burada c = a // b olduğuna + göre d = a - a // b * b olur. Bu nedenle Python'da negatif bir sayının pozitif bir sayıya bölümünden elde edilen kalan + pozitif olmaktadır. Yani örneğin -10 % 4 işleminin sonucu 2'dir. Çünkü -10 // 4 işleminin sonucu "floordiv" nedeniyle -3'tür. + -3 * 4 = -12'dir. -12'ye 2 toplarsak -10 elde ederiz. -10 % 4 işleminin -10 - -10 // 4 * 4 ile eşdeğer olduğuna dikkat + ediniz. (C, C++, Java ve C# gibi dillerde tamsayılı bölme "floordiv" biçiminde yapılmadığı için o dillerde -10 % 4 + işlemi -10 - -10 / 4 * 4 işlemi ile eşdeğerdir. Bu dillerde -10 / 4 işleminin sonucu -2 olduğu için dolayısıyla da + -10 % 4 işlemi de -2'ye eşit olmaktadır.) +#------------------------------------------------------------------------------------------------------------------------ + +result = -10 % 4 +print(result, type(result)) # 2 + +result = 10 % -4 +print(result, type(result)) # -2 + +#------------------------------------------------------------------------------------------------------------------------ + İşaret + ve işaret - operatörleri tek operandlı önek (unary prefix) aritmetik operatörlerdir. İşaret - operatörü operandının + negatif değerini üretir. İşaret + operatörü operandı ile aynı değeri üretir. Bu iki operatör de öncelik tablosunda + diğer aritmetik operatörlerden yüksek öncelikli biçimde "sağdan sola" grupta bulunmaktadır: + + () soldan sağa + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + = sağdan sola + + Örneğin: + + a = ----1 + + Burada - operatörlerinin hepsi işaret - operatörüdür. İşlemler sağdan sola şöyle yapılır: + + İ1: -1 => -1 + İ2: -İ1 => 1 + İ3: -İ2 => -1 + İ4: -İ3 => 1 + İ5: a = İ4 => 1 + + Örneğin: + + a = 3----1 + + Hu ifadenin anlamlı olabilmei için ilk - operatörünün çıkartma operatörü diğer - operatörlerinin işaret - operatörü olması + gerekir. İşlemler şöyle yapılacaktır: + + İ1: -1 => -1 + İ2: -İ1 => 1 + İ3: -İ2 => -1 + İ4: 3 - İ2 => 4 + İ5: a = İ4 => 4 + +#------------------------------------------------------------------------------------------------------------------------ + +a = 3----1 +print(a) # 4 + +#------------------------------------------------------------------------------------------------------------------------ + Python'da üs almak için ** operatörü bulundurulmuştur. ** iki operandlı araek bir operatördür. Bu operatör soldaki + operandının sağdaki kuvvetini elde eder. Sayının 0.5'inci kuvvetinin karekök anlamına geldiğini anımsayınız. Sayının + negatif kuvvetlerinin de geçerli olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +result = 3 ** 3 +print(result) # 27 + +result = 3 ** 1.7 +print(result) # 6.473007839923779 + +result = 3 ** 0.5 +print(result) # 1.7320508075688772 + +result = 2 ** -2 +print(result) # 0.25 + +#------------------------------------------------------------------------------------------------------------------------ + ** operatörü öncelik tablosunda işaret + ve işaret - operatörlerinden daha yüksek öncelikli sağdan sola bir grupta bulunmaktadır. + Bu operatörün öncelik tablosundaki yeri matematiksel alana uygun olsa da programlama dillerine göre biraz yadırganmaktadır: + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + = sağdan sola + +#------------------------------------------------------------------------------------------------------------------------ + +result = -3 ** 2 +print(result) # -9 + +result = (-3) ** 2 +print(result) # 9 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + ** operatörünün sağdan sola öncelikli grupta olduğuna dikkat ediniz. Yani ifade içerisinde birden fazla ** operatörü + varsa önce sağdaki yapılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +result = 2 ** 3 ** 2 +print(result) # 512 + +result = (2 ** 3) ** 2 +print(result) # 64 + +#------------------------------------------------------------------------------------------------------------------------ + Python'da 6 karşılaştırma operatörü vardır. Karşılaştırma operatörlerinin hepsi iki operandlı araek (binary infix) + operatörlerdir: + + < > <= >= == != + + "Eşit mi" karşılaştırmasının == operatörü ile "eşit değil mi" karşılaştırmasının ise != operatörü ile yapıldığına + dikkat ediniz. + + Karşılaştırma operatörleri artimetik operatörlerden daha düşük önceliklidir. Bu operatörler bool türden değer üretirler. +#------------------------------------------------------------------------------------------------------------------------ + +result = 3 > 2 +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + bool türünden değerler karşılaştırma operatörleri de dahil olmak üzere diğer türlerle işleme sokulursa önce int türüne + dönüştürülürler sonra karşılaştırma işlemine girerler. bool değerler int türüne dünüştürülürken True 1 olarak, False + ise 0 olarak dönüştürülmektedir. Örneğin: + + result = True > 0 + + Bu işlemde operandlardan biri bool diğeri int türdendir. Bu durumda True int türüne 1 olarak dönüştürülecek ve sonuç + True olarak elde edilecektir. Benzer biçimde iki bool türü de kendi aralarında karşılaştırma işlemine sokulursa önce + operand'lar int türüne dönüştürülür, sonra karşılaştırma yapılır. Örneğin: + + result = True > False + + Burada aslında 1 > 0 gibi bir karşılaştırma yapılmıştır. Dolayısıyla bu işlem True sonucunu verecektir. +#------------------------------------------------------------------------------------------------------------------------ + +result = True > 0 +print(result) # True + +result = True > False +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + Python'da karşılaştırma operatörleri kombine edildiğinde "and" etkisi oluşur. Örneğin a < b < c işlemi geçerlidir ve + bu işlem a < b and b < c anlamına gelmektedir. Ya da örneğin a == b != c geçerlidir. Bu işlem a == b and b != c anlamına + gelmektedir. Örneğin: + + a == b == c + + işlemi a == b and b == c anlamına gelmektedir. + + Tabii ikiden fazla karşılaştırma operatörü de bu biçimde kombine edilebilir. Örneğin a > b < c > d işleminin eşdeğeri + a > b and b < c and c > b biçimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +val = int(input('Bir değer giriniz:')) + +result = 10 < val < 20 # eşdeğeri: 10 < val and val < 20 +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Karşılaştırma operatörleri öncelik tablosunda aritmetik operatörlerdne düşük öncelikli gruplardadır. + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + = sağdan sola + + Yani örneğin: + + result = a + b > c + d + + gibi bir işlemde a + b ile c + d karşılaştırılmaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + Python'da üç mantıksal operatör vardır: and, or ve not operatörleri. and ve or operatörleri iki operandlı araek + (binary infix), not operatörü ise tek operandlı önek (unary prefix) operatörlerdir. + + Python'da mantıksal operatörlerin operandları herhangi bir türden olabilir. Halbuki Java ve C# gibi bazı dillerde mantıksal + operatörlerin operandları bool türden olmak zorundadır. Yani örneğin Python'da aşağıdaki gibi bir işlem geçerlidir: + + result = 3 and -3.5 + + Python'da mantıksal operatörler karşılaştırma operatörlerinden daha düşük önceliklidir. Ancak mantıksal not operatörü + pek çok programlama dilinde yüksek bir önceliğe sahipken Python'da o dillere göre düşük bir önceliğe sahiptir: + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + not sağdan sola + and soldan sağa + or soldan sağa + = sağdan sola + + Genellikle mantıksal operatörler karşılaştırma operatörlerinin çıktıları üzerinde işlem yapmak için kullanılırlar. + Örneğin: + + result = a > b and c > d + + Burada a > b ve c > d koşılunun aynı anda sağlanıp sağlanmadığına bakılmaktadır. + + and ve or operatörleri şöyle çalışmaktadır: Bu operatörlerin diğer programlama dillerinde olduğu gibi "kısa devre (short circuit)" + özellikleri vardır. Bu operatörlerin önce her zaman sol tarafındaki ifade yapılır. Sağ tarafında ne kadar yüksek öncelikli + operatör olursa olsun önce yalnızca sol tarafları yapılır. Bu operatörler operandlarını True/False olarak yorumlarlar. + int ve float operandlarda sıfır değeri False, sıfır dışı herhangi bir değer True anlamına gelmektedir. and operatörünün sol + tarafındaki ifade False ise bu operatörün sağ tarafındaki ifade hiç yapılmaz, operatör sol tarafındaki değeri üretir. and + operatörünün sol tarafındaki ifade True ise bu kez sağ tarafındaki ifade yapılır. Operatör sağ tarafındaki ifadenin değerini + üretir. or operatörü de benzerdir. Bu operatörün de önce sol tarafındaki ifade yapılır. Bu ifade True ise sağ tarafındaki + ifade hiç yapılmaz operatör sol tarafındaki değeri üretir, eğer bu ifade False ise bu kez sağ tarafındaki ifade yapılır. + Operatör sağ tarafındaki değeri üretir. Bu operatörlerin sol tarfındaki ya da sağ tarafındaki değeri ürettiğine dikkat ediniz. + Örneğin: + + result = -3 and 6.7 + print(result) # 6.7 + + Örneğin: + + result = 0 and 6.7 + print(result) # 0 + + Örneğin: + + result = 3 + 2 or False + print(result) # 5 + + Örneğin: + + result = 3 > 2 and 5 < 0 + print(result) # False + + Örneğin: + + result = 1.2 and 0 + print(result) # 0 + + Örneğin: + + result = 10 and 20 + print(result) # 20 + +#------------------------------------------------------------------------------------------------------------------------ + and ve or operatörleri aynı ifadede kullanıldığında her zaman sol taraftaki operatörün sol tarafı önce yapılmaktadır. + Örneğin: + + ifade1 and ifade2 or ifade3 + + Burada önce ifade1 yapılır eğer ifade1 True ise ifade2 yapılır ve and operatmründen ifade2 elde edilir. ifade2 True + ise ifade3 yapılmaz sonuç olarak ifade2 elde edilir. Örneğin: + + ifade1 or ifade2 and ifade3 + + Burada ifade aslında aşağıdaki sonucu vermelidir: + + ifade1 or (ifade2 and ifade3) + + Yani bu ifade aslında ifade1 ile (ifade and ifade3)'ün or'lanması anlamına gelmektedir. and ve or operatörlerinin her zaman sol tarafı + önce yapılacağına göre burada önce ifade1 yapılır. Eğer ifade1 True ise doğurdan ifade1'in değeri elde edilir. Eğer ifade1 + False ise bu kez ifade2 yapılır. ifade2 False ise ifade3 hiç yapılmaz, ifade2'nin sonucu elde edilir. Eğer ifade1 False ancak + ifade2 True ise ifade3'ün sonucu elde edilir. Özetle and ve or operatörleri kombine edildiğinde her zaman en soldaki operatörün en solu + önce yapılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +result = 10 and 0 or 5 +print(result) # 5 + +result = 0 and -5 or 5 +print(result) # 5 + +result = 100 or -5 and 5 +print(result) # 100 + +result = 0 or 0 and 5 +print(result) # 0 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + not operatörü tek operandlı önek (unary prefix) mantıksal operatördür. Bu operatör True değerini False, False değerini True yapar. + Operatörün ürettiği değer her zaman bool türdendir. Bu operatörün de operandları int ya da float türünden olabilir. Sıfır dışı değerler + (nonzero değerler) True olarak sıfır değeri False olarak ele alınır. +#------------------------------------------------------------------------------------------------------------------------ + +result = not 23.4 +print(result) # False + +result = not False +print(result) # True + +result = not 0 +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + not operatörü öncelik tablosunda and ve or operatörlerinden daha yüksek önceliklidir ve kandi içerisinde sağdan sola + grupta bulunmaktadır. + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + not sağdan sola + and soldan sağa + or soldan sağa + = sağdan sola + +#------------------------------------------------------------------------------------------------------------------------ + +result = not not not not True +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + Python'da mantıksal not operatörü C, C++, Java ve C# gibi dillerle kıyaslandığında düşük önceliklidir. Özellikle bu dillerden geçen + kişiler Python'da not operatörünün aritmetik ve karşılaştırma operatörlerinden daha düşük öncelikli olduğuna dikkat etmelidir. Örneğin: + + result = not a + b + + işleminde ya da: + + result = not a < b + + işleminde önce toplama ve karşılaştırma oeratörleri yapılıp sonra not operetörü yapılır. Halbuki C, C++ gibi dillerde önce not işlemi + yapılmaktadır: + + result = !a < b; + +#------------------------------------------------------------------------------------------------------------------------ + Python'da boş olmayan bir string mantıksal olarak True, boş bir string False biçiminde ele alınmaktadır. Başka bir deyişle + dolu string'ler bool türüne True olarak boş string'ler False olarak dönüştürülmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +result = 'ankara' or 12.3 +print(result) # 'ankara' + +result = 'istanbul' and 'ankara' +print(result) # 'ankara' + +result = not '' +print(result) # True + +result = not '0' +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + Python'da None değeri mantıksal olarak ele alınacağı zaman her zaman False biçimde ele alınır. Bşka bir deyişle + None değeri bool türüne her zaman False olarak dönüştürülür. +#------------------------------------------------------------------------------------------------------------------------ + +result = not None +print(result) # True + +result = None or 'ankara' +print(result) # 'ankara' + +#------------------------------------------------------------------------------------------------------------------------ + Ayrıca henüz görmemiş olsak da önemli türler bool türüne şöyle dönüştürülmektedir: + + - Dolu bir liste True olarak, boş liste False olarak. + - Dolu bir demet True olarak, boş bir demet False olarak. + - Dolu bir sözlük True olarak, boş bir sözlük False olarak. + - Dolu bir küme True olarak, boş bir küme False olarak. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da atama işlemi aslında bir operatör değil bir deyim statüsündedir. Ancak biz geleneksel olarak burada atama işlemi için + "operatör" terimini kullanacağız. Bir operatör olarak atama operatörü iki operandlı araek bir operatördür. Anımsanacağı gibi Python'da + tüm atama işlemleri aslında adres ataması anlamına gelmektedir. Örneğin: + + a = b + + Burada b'nin içerisindeki değer a'ya atanmamaktadır. b'nin içerisindeki adres a'ya atanmaktadır. Çünkü bütün değişkenler adres tutmaktadır. + Bu işlemin sonucunda a is b ifadesi True değerini verecektir. + Örneğin: + + a = 10 + + Burada a'nın içerisine 10 atanmamaktadır. 10 bir int nesneye yerleştirilip a'ya o int nesnenin adresi atanmaktadır. Atama operatörü öncelik + tablosunun en düşük düzeyinde ve sağdan sola öncelikli gruptadır. + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + not sağdan sola + and soldan sağa + or soldan sağa + = sağdan sola + + Örneğin: + + a = b = 10 + + İ1: b = 10 + İ2: a = İ1 + + Yani burada 10 değeri bir nesneye yerleştirilecek, o nesnenin adresi hem b'ye hem de a'ya atanacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da atama işlemi aslında bir operatör olarak değil deyim olarak değerlendirilmektedir. Dolayısıyla atama işleminden bir değer elde edilmez. + Örneğin biz C, C++, Java ve C# gibi dillerde atama operatörünü parantez içerisine alıp oradan elde edilen değeri diğer operatörlerle + işleme sokabiliriz. Ancak Python'da bunu yapamayız. +#------------------------------------------------------------------------------------------------------------------------ + +a = (b = 10) + 20 # C, C++, Java ve C#'ta geçerli, Python'da geçersiz! + +#------------------------------------------------------------------------------------------------------------------------ + Atama işleminden Python'da bir değer elde edilememesi yüzünden diğer dillerde yapılan bazı faydalı işlemler Python'da + atama işlemiyle yapılamamaktadır. Örneğin: + + while (a = int(input())) != 0: # Python'da geçersiz, ama benzeri diğer dillerde geçerli + pass + +#------------------------------------------------------------------------------------------------------------------------ + Python'a 3.8 versiyonu ile birlikte (2019 Ekim) "Walrus operatörü" diye isimlendirilen değer üreten bir atama operatörü de eklenmiştir. + Bu operatör sayesinde yukarıda belirttiğimiz diğer dillerde yapamadığımız işlemleri yapabiliriz. Walrus operatörü := sembolü ile + temsil edilmektedir. İki operandlı araek bir operatördür. +#------------------------------------------------------------------------------------------------------------------------ + +a = (b := 10) + 20 +print(a, b) # 30 10 + +#------------------------------------------------------------------------------------------------------------------------ + Python'da gereksiz bir biçimde Walrus operatörünün kullanılması konusunda programcının cesaretini kırmak için gereksiz + Walrus operatörünün kullanılması error oluşturmaktadır. Örneğin: + + a := 10 + + Burada Walrus operatörüne hiç gerek yoktur. Bu nedenle bu kullanım error oluşturur. Ancak örneğin: + + a = (b := 10) + 20 + + Burada Walrus operatörü yerine atama operatörünü kullanamayız. Buradaki Walrus operatörü doğru kullanılmıştır ve error oluşturmayacaktır. + Örneğin: + + a := b := 10 + + Burada Walrus operatörü gereksiz kullanılmıştır. Error oluşacaktır. Çünkü bu işlem atama operatörleriyle de yaılabilmektedir. Walrus operatörü + yukarıda geçersiz durumlarda paranteze alınırsa geçerli hale gelmektedir. Örneğin: + + (a := 10) + + Bu işlem geçerlidir. Parantezlerin "elde edilen değerin kullanılşması anlamına geldiğine dikkat ediniz.). Örneğin: + + a := 10 # error! + (a := 10) #geçerli + + Örneğin: + + b := (a := 10) # error! + + Fakat örneğin: + + b = (a := 10) # geçerli + (b := (a := 10)) # geçerli + + Örneğin: + + print(a := 10) # geçerli parantez zaten vardır + + Buı kod aşağıdaki ile eşdeğerdir: + + a = 10 + print(a) + + Özetle atama operatörü ile yapabileceğimiz bir işlemi Walrus operatörü ile yapmaya çalışırsak bu durum error oluşturur. Ancak error oluşturmasın istiyorsak + paranteze almalıyız. Ancak atama operatörü ile yapamadığımız bir şeyi Walrus operatörüyle yapabiliyorsak bu durum error oluşturmaz. Örneğin: + + print(a := 10) + + Biz bu işlemi atama operatör ile yapamazdık. O halde burada Walrus operatörünün kullanılması geçerlidir. Ancak örneğin: + + a := 10 # error! + + Biz bu işlemi atama operatörü ile yapabilirdik. Burada paranteze alınmadan Walrus operatörünün kullanılması error oluşturur. + + if, while gibi deyimlerde zaten atama operatörü kullanılamadığı için Walrus operatörünün paranteze alınması gerekmez. Bu tür deyimlerde + atanan değer doğrudan işleme sokulmaktadır. Örneğin : + + while s := input(): + pass + + Biz burada Walrus operatörü yerine atama operatörünü kullanamayız. Bu örnekte Walrus operatörünün en dıştan ayrıca paranteze alınması gerekmemektedir. + + Walrus opeeratörü öncelik tablosunda atama opeeatör ile aynı gruptadır: + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + not sağdan sola + and soldan sağa + or soldan sağa + = := sağdan sola + + Bu nedenle aşağıdaki gibi bir kodda parantez gereklidir: + + while (a := int(input('Bir değer giriniz:'))) != 0: + print(a * a) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da ++ ve -- biçiminde operatörler yoktur. ++ ve -- operatörleri C, C++, Java ve C# gibi dillerde vardır ve çok kullanılmaktadır. + Bu dillerde ++ operatörü değişkenin içerisindeki değeri 1 artıtmak için, -- operatörü değişken içerisindeki değeri 1 eksiltmek için + kullanılmaktadır. Python'da değişkenin içerisindeki değeri 1 artırmak ve 1 eksiltmek için sonraki paragrafta açıklayacak olduğumuz + += ve -= gibi işlemli atama operatörleri (augmented assignment operators) ile yapılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da +=, -=, *=, /=, //=, %= gibi bir grup işlemli atama operatörü (augmented assignment statments) vardır. Bu operatörler iki operandlı araek + operatörlerdir. op bir operatör belirtmek üzere: + + a op= b + + işlemi aşağıdakiyle eşdeğerdir: + + a = a op b + + Bu durumda a += 2 işlemi a = a + 2 ile, a *= 10 işlemi a = a * 10 ile eşdeğerdir. and, or ve not operatörlerinin işlemli biçimi yoktur. + Python'da bir değişkeni 1 artırmak için en pratik yöntem += operatörünü kullanmaktadır. Örneğin: + + a += 1 + + Benzer biçimde bir değişkeni 1 eksiltmek için en pratik yok -= operatörünü kullanmaktır: + + a -= 1 + + Tabii işlemli atama operatörleri temel türlerle kullanıldığında bu temel türler "değiştirilemez (immutable)" olduğu + için yine yeni nesnelerin yaratılmasına yol açacaktır. Örneğin: + + >>> a = 10 + >>> id(a) + 140712253256776 + >>> a += 1 + >>> a + 11 + >>> id(a) + 140712253256808 + + Biz kursumuzda atama işlemi için kullanılan = sembolünü ve işlemli atamalar için kullanılan op= sembollerini birer "operatör" + olarak ele alıyoruz. Halbuki bu semboller aslında "Python Language Reference" içerisinde operatör değil "deyim (statement)" + olarak ele alınmaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + İşlemli atama operatörleri atama operatörleriyle sağdan sola aynı öncelik grubunda bulunmaktadır: + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + not sağdan sola + and soldan sağa + or soldan sağa + = := , +=, -=, *=, ... sağdan sola + +#------------------------------------------------------------------------------------------------------------------------ + *=, /= ve //= operatörlerinin atama operatörü önceliğinde olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 + +a *= 2 + 1 + +print(a) # 30 + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi print fonksiyonu çıktıları imlecin bulunduğu yerden itibaresn ekrana (stdout dosyasına) yazdırıp imleci + aşağıda satırın başına geçirmektedir. print fonksiyonu değişken sayıda argüman alabilmektedir. Yani biz print sonksiyonu + ile birden fazla ifadeyi yazdırabiliriz. Bu durumda print fonksiyonu her argümanın değerini yazdırdıktan sonra bir + SPACE boşluk bırakır. print fonksiyonu tüm işlemini bitirince '\n' karakterini ekrana (stdout dosyasına) yazdırmaktadır. + Yani print işleminden sonra imleç aşağı satırın başına geçmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +print('ali', 'veli', 'selami') # ali veli selami + +a = 10; b = 20; c = 30 +print(a, b, c) # 10 20 30 + +#------------------------------------------------------------------------------------------------------------------------ + print fonksiyonuyla birden fazla argümanı yazdırırken her argümandan sonra print araya bir SPACE karakteri bırakmaktadır. + Ancak programcı isterse "sep" isimli parametreyle boşluk yerine argümanlar arasında istenilen yazının bastırılmasını sağlayabilir. + sep için girilen yazının tek karakter olması gerekmemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 30 + +print(a, b, c, sep='***') # 10***20***30 +print('Ok') + +#------------------------------------------------------------------------------------------------------------------------ + sep aparemtresinin hiç girilmemesiyle bunun sep=' ' biçiminde girilmesi arasında bir fark olmadığına dikkat ediniz. + Yani sep parametresinin default değerini ' ' olarak düşünebilirsiniz. + + Aşağıdaki örnekte her argümandan sonra print ", " yazısını ekrana (stdout dosyasına) bastırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 30 + +print(a, b, c, sep=', ') # 10, 20, 30 +print('Ok') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte her argüman yazdırıldıktan sonra imleç aşağıda staırın başına geçirilecek ve dolayısıyla argümanlar + satır satır alt alta yazdırılmış olacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 30 + +print(a, b, c, sep='\n') + +#------------------------------------------------------------------------------------------------------------------------ + print fonksiyonunun "end" isimli parametresi tüm yazdırma işlemi bittiğinde ekrana basılacak yazıyı belirtmektedir. Default durumda + bu yazı '\n' biçimindedir. Yani imleç aşağı satırın başına geçirilir. Ancak biz end parametresi yoluyla print fonksiyonunun işlemini bitirdikten + sonra istediğimiz bir yazıyı ekrana bastırmasını isteyebiliriz. Örneğin: + + print(10, 20, 30, sep='*', end='---') + print('ankara) + + Burada ekranda şunları göreceğiz: + + 10*20*30---ankara +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 30 + +print(a, b, c, sep='xxx', end='yyy') +print('Ok') + +# # 10xxx20xxx30yyyOk + +#------------------------------------------------------------------------------------------------------------------------ + Bilindiği gibi bir değişkenin içerisinde belli bir türden nesnenin adresi olabilir. Örneğin: + + a = 12.3 + + Burada a değişkeninin içerisinde float bir nesnenin adresi vardır. Yani biz a'yı kullandığımızda float bir değeri işleme sokmuş oluruz. + İşte bir nesnenin içerisindeki değeri değiştirerek başka bir türden bir nesne biçiminde elde etme işlemine programlama dillerinde + "tür dönüştürmesi (type conversion / type cast)" denilmektedir. Örneğin biz yukarıdaki a değişkeninin gösterdiği nesnenin içerisindeki değeri + int bir nesne biçiminde elde etmek isteyebiliriz. + + Tür dönüştürme işleminin genel biçimi şöyledir: + + () + + Örneğin: + + a = 13.2 + + b = int(a) + + Tür dönüştürme işlemiyle yeni bir nesne yaratılmaktadır. Her tür dönüştürme işlemi yeni bir nesnenin yaratılmasına yol açmaktadır. Yani tür + dönüştürmesi mevcut nesnesinin türünü değiştirmemektedir. Mevcut nesnenin değerindne hareketle arzu edilen türden yeni bir nesnenin yaratılmasını + sağlamaktadır. Örneğin: + + a = 13.2 + b = int(a) + + Burada a'nın türü değiştirilkmemiştir. Yeni bir int nesne yaratılmış ve onun adresi b'ye atanmıştır. + + Aslında T bir tür belirtmek üzere T türünden bir nesnenin yaratılması T(...) biçiminde bir ifadeyle yapılmaktadır. + Bu konu ileride "sınıflar" konusunda ayrıntılarıyla ele alınacaktır. + +#------------------------------------------------------------------------------------------------------------------------ + float bir değer int türüne dönüştürüldüğünde noktadan sonraki kısmı atılmış olan bir int değer elde edilir. Burada yuvarlama + yapılmadığına float değer negatif de olsa pozitif de olsa noktadan sonraki kısmın atıldığına dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = 13.99 + +print(id(a)) + +b = int(a) + +print(id(b)) + +print(a) # 13.99 +print(b) # 13 + +a = -13.99 + +b = int(a) +print(b) # -13 + +#------------------------------------------------------------------------------------------------------------------------ + bool bir değer int türüne dönüştürülürse eğer değer True ise 1, False ise 0 elde edilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = True + +b = int(a) + +print(b) # 1 + +a = False + +b = int(a) + +print(b) # 0 + +#------------------------------------------------------------------------------------------------------------------------ + Bir string'i int türüne dönüştürürken string içerisindeki yazı int türü için anlamlı sayısal karakterlerden oluşuyorsa + yeni bir int nesne yaratılır ve yazı int bir nesne biçiminde ifade edilir. Ancak yazının içerisindeki karakterler int + türü için anlamlı değilse bu durumda exception (ValueError) oluşur. Daha önceden de belirttiğimiz gibi bir exception oluştuğunda eğer + exception programcı tarafından ele alınmadıysa program çökmektedir. String'in başındaki boşluk karakterlerine "leading space", string'in + sonundaki boşluk karakterlerine "trailing space" denilmektedir. Dönüştürme sırasında yazının başındaki ve sonundaki boşluk karakterleri + dikkate alınmamaktadır. Örneğin: + + >>> a = '123' + >>> b = int(a) + >>> b + 123 + >>> a = '12.3' + >>> b = int(a) + Traceback (most recent call last): + File "", line 1, in + ValueError: invalid literal for int() with base 10: '12.3' + >>> a = ' -123 ' + >>> b = int(a) + >>> b + -123 +#------------------------------------------------------------------------------------------------------------------------ + +a = '123' + +b = int(a) + +print(b) # 123 + +a = 'ali' + +b = int(a) # exception oluşur! + +print('unreachable code') + +#------------------------------------------------------------------------------------------------------------------------ + Bir string'i int türüne dönüştürürken string içerisindeki yazının kaçlık sistemde bir sayı belirttiğini de int fonksiyonunun + ikinci parametresiyle ifade edebiliriz. Örneğin: + + a = '100' + + b = int(a) + + Default durumda a içerisindeki yazının 10'luk sistemde bir sayı belirttiği varsayılmaktadır. Fakat örneğin: + + a = '100' + + b = int(a, 16) + + Burada artık biz '100' yazısında belirtilen sayının 16'lık sistemde bir sayı olması gerektiğini belirtiyoruz. Örneğin: + + a = '100' + + b = int(a, 2) + + Burada a yazısının belirttiği sayının 2'lik sistemde yorumlanması gerektiği belirtilmektedir. 16'lık, 8'lik ya da 16'lık + sistemden yazı dönüştürürken yazının başında taban belirten önekler bulunabilir. Örneğin: + + >>> s = '0x1234' + >>> a = int(s, 16) + >>> a + 4660 + >>> s = '0o1234' + >>> a = int(s, 8) + >>> a + 668 + >>> s = '0b10101' + >>> a = int(s, 2) + >>> a + 21 + +#------------------------------------------------------------------------------------------------------------------------ + +a = '0x10' + +b = int(a, 16) +print(b) # 16 + + +a = '0b1010' + +b = int(a, 2) +print(b) # 10 + +a = '0x10' +b = int(a) # exception oluşur! + +print(b) + +#------------------------------------------------------------------------------------------------------------------------ + Complex türünden int türüne dönüştürme geçerli kabul edilmemektedir. Böyle bir dönüşüm yapılmak istendiğinde exception (TypeError) + oluşmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + None değerinin int türüne dönüştürülmesi de Python'da geçerli kabul edilmemektedir. Bu dönüştürme de exception'a (TypeError) yol açmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + int bir ifadenin int türüne dönüştürülmesi geçerlidir ancak anlamsızdır. Bu durumda dönüştürüleek ifade ile aynı değer + elde edilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + O halde int türüne dönüştürme özetle şöyle yürütülmektedir: + + 1) float bir değer int türüne dönüştürüldüğünde noktandan sonraki kısım atılırç + + 2) str türü int türüne dönüştürüldüğünde eğer yaqzının içerisindeki karakterler int türü için geçerliyse dönüştürme yapılır + değilse exception (ValueError) oluşur. + + 3) bool bir değer int türüne dönüştürüldüğünde False için 0, True için 1 elde edilmektedir. + + 4) complex türü int türüne dnüştürülemez. Dönüştürülmeye çalışılırsa exception (TypeError) oluşur. + + 5) None değeri (NoneType türü) int türüne dönüştürülemez. Dümüştürülmek istenirse exception (TypeError) olulur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + int bir değer float türüne dönüştürülürken eğer int değer float formatıyla (IEEE 754 long real format) tam olarak ifade + edilebiliyorsa .0 biçiminde float türüne kayıp olmadan dönüştürülür. Ancak Python'da int değerlerin bir sınırının olmadığını anımsayınız. + Bu durumda çok büyük int değerler float türüne dönüştürülemeyecektir. İşte bu tür durumlarda exception (OverrlowError) oluşmaktadır. + Bazı int değerler mantis kayıplarıyla basamaksal bir kayıp olmadan float türe dönüştürülebilmektedir. Bu durumda bir exception + oluşmaz. Sayı float türüyle tam olarak ifade edilemese bile mantis kaybıyla ona en yakın büyük ya da ona en yakın küüçük + sayı elde edilmektedir. Ancak basamaksal kayıplar exception'a (OverflowError) yol açmaktadır. Örneğin: + + >>> a = 12345678901234567890123456789 + >>> b = float(a) + >>> b + 1.2345678901234568e+28 + >>> int(b) + 12345678901234568227576610816 + + >>> a = 1817236817263817263871263871263871263871623876123876128736187263871263817263871263871623812638716238716238761238 + 761283761827361872368712638172638712638712638712638172638126387126387123618723618273618273618723681723618236182361827361 + 283612836128361872361872361823681726381726387126387126381723687126381726387126387126381723618276387126387126387123 + >>> b = float(a) + Traceback (most recent call last): + File "", line 1, in + OverflowError: int too large to convert to float +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir string float türüne dönüştürülebilir. Bu durumda string float ile ifade edilebiliyorsa (yani string'i oluşturan karakterler + float sayı için anlamlıysa) dönüştürme yapılır. String float türüyle ifade edilemiyorsa exception (ValueError) oluşur. + float fonksiyonu her zaman tek parametrelidir. flaot türü ile ifade edilemeyecek çok büyük büyük ya da küçük yazısal değerlerin + float türüne dönüştürülmesi sırasında ne olacağı konusunda "Python Standard Library Reference" açık bir şey söylememiştir. + Mevcut CPythob gerçekleştirimi bu durumda +inf ya da -inf değerlerini üretmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = ' 123.45 ' + +b = float(a) +print(b) + +a = '123.23ali' + +b = float(a) # exception oluşur +print(b) + +#------------------------------------------------------------------------------------------------------------------------ + bool türünden bir ifade float türüne dönüştürüldüğünde eğer bool değer True ise 1.0 biçiminde false ise 0.0 biçiminde + float bir değer elde edilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Complex bir ifade float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur. Benzer biçimde None değeri de + flaot türüne döüştürülememektedir. Dönüştürülmek istenirse yine exception (TypeError) oluşmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + O halde float türüne dönüştürme özetle şöyle yürütülmektedir: + + 1) int bir değer float türüne bilgi kaybı olmadan, mantissel kayıp ile dönüştürülebilir. Ancak basamaksal bir kayıp + oluşacaksa dönüştürme exception (OverflowError) ile sonuçlanır. + + 2) str türü float türüne dönüştürülebilir. Tabii bunun için yazının karakterlerinin float türü için anlamlı olması gerekir. + Aksi takdirde dönüştürme exception (ValueError) ile sonuçlanır. + + 3) bool türü float türüne dönüştürülürse True için 1.0, False için 0.0 değeri elde edilir. + + 4) Complex türü float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur. + + 5) None değeri (NoneType türü) türü float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir int değer bool türüne dönüştürülürken eğer değer sıfır dışı bir değerse True olarak 0 değeri ise False olarak dönüştürülür. + Benzer biçimde float bir değer de bool türüne aynı biçimde dönüştürülmektedir. (Sıfır dışı değer demekle 0'ın dışında pozitif ya da + negatif herhangi bir değer kastedilmektedir.) None değer bool türüne her zaman False olarak dönüştürülmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 123801928309123 + +b = bool(a) +print(b) # True + +a = -12.34 + +b = bool(a) +print(b) # True + +a = None + +b = bool(a) +print(b) # False + +#------------------------------------------------------------------------------------------------------------------------ + Bir string bool türüne dönüştürülürken string'in içerisindeki yazıya bakılmaz. String'in boş mu dolu mu olduğuna bakılır. + Dolu bir string bool türüne True olarak, boş bir string False olarak dönüştürülmektedir. Örneğin aşağıdaki dönüştürme + True değerini verecektir: + + s = 'False' + b = bool(s) + print(b) # True + +#------------------------------------------------------------------------------------------------------------------------ + +a = 'ankara' + +b = bool(a) +print(b) # True + +a = 'False' + +b = bool(a) +print(b) # True + +a = '' + +b = bool(a) +print(b) # False + +#------------------------------------------------------------------------------------------------------------------------ + Ayrıca boş listeler, demetler, sözlükler, kümeler bool türüne False biçiminde, dolu listeler, demetler, sözlükler ve kümeler de True biçiminde + dönüştürülürler. Listeler, demetler, sözlükler, kümeler konusu izleyen bölümlerde ele alınmaktadırç +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir int değer ya da bir float değer complex türüne dönüştürülebilir. Bu durumda sanal kısmı 0 olan bir complex sayı elde edilir. + bool bir değer complex türüne dönüştürülürse gerçek kısmı 1 ya da 0 olan sanal kısmı 0 olan bir complex sayı elde edilmektedir. + Dönüştürülecek tür ne olursa olsun complex türünün gerçek ve sanal kısımları float türdendir. Örneğin: + + >>> z = 3j+1 + >>> z.real + 1.0 + >>> z.imag + 3.0 + >>> a = 10 + >>> z = complex(a) + >>> z + (10+0j) + >>> z.real + 10.0 + >>> z.imag + 0.0 + + complex sayılar print edilirken her gerçek ve sanal kısımları tamsayı ise güzel bir görünüt oluşturmak için nokta kullanılmamaktadır. + Ancak aslında her durumda complex türünün gerçek ve sanal kısımları float bir sayı biçimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 + +b = complex(a) +print(b) # (10+0j) + +a = 10.5 + +b = complex(a) +print(b) # (10.5+0j) + +a = True + +b = complex(a) +print(b) # (1+0j) + +#------------------------------------------------------------------------------------------------------------------------ + Bir string de complex türüne dönüştürülebilir. 'a+bj' biçimindeki bir yazının başında ve sonunda boşluk karakterleri bulunabilir. + Ancak arasında bulunamaz. Ayrıca yazı 'aj+b' biçiminde de belirtilemektedir. Örneğin: + + >>> s = '2+3j' + >>> z = complex(s) + >>> print(z) + (2+3j) + >>> s = '10' + >>> z = complex(s) + >>> print(z) + (10+0j) + >>> s = '3j-2' + >>> z = complex(s) + Traceback (most recent call last): + File "", line 1, in + ValueError: complex() arg is a malformed string + +#------------------------------------------------------------------------------------------------------------------------ + +a = '3+2j' + +b = complex(a) +print(b) # (3+2j) + +a = '10' + +b = complex(a) +print(b) # (10+0j) + +a = 10.5 + +b = complex(a) +print(b) # (10.5+0j) + +#------------------------------------------------------------------------------------------------------------------------ + Temel türlerin hepsi str türüne dönüştürülebilir. Yani örneğin int bir değer, float bir değer, bool bir değer, complex bir değer, + None değeri str türüne dönüştürülebilmektedir. Dönüştürme sonucunda o değere ilişkin bir yazı elde edilmektedir. Örneğin: + + >>> a = 10 + >>> s = str(a) + >>> s + '10' + >>> a = 12.34 + >>> s = str(a) + >>> s + '12.34' + >>> a = True + >>> s = str(a) + >>> s + 'True' + >>> a = None + >>> s = str(a) + >>> s + 'None' + >>> a = 3j+2 + >>> s = str(a) + >>> s + '(2+3j)' +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da klavyeden (stdin dosyasından) okuma yapan input isimli tek bir fonksiyon vardır. input fonksiyonun da str türünden + bir değer verdiğini anımsayınız. Bu durumda biz kalvyeden int, float gibi okumalar yapmak için aslında string'i bu türlere dönüştürmekteyiz. + Örneğin: + + a = int(input('Bir sayı giriniz:')) + + print(a * a) + + Ya da örneğin: + + a = float(input('Bir sayı giriniz:')) + + print(a * a) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programlama dillerinde farklı türler ile işlem yapıldığında nasıl bir sonucun elde edileceği programcı tarafından biliniyor + olması gerekir. C, C++, Java, C# ve Python gibi dillerde farklı türlerle işlem yapılabilmektedir. Ancak Swift gibi bazı dillerde + farklı türlerle işlemler yapılamamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da iki int değeri aritmetik işleme sokarsak sonuç int türden çıkar. Benzer biçimde iki float değeri aritmetik işleme sokarsak + sonuc float türden çıkar. Ancak int bir değer ile float bir değeri aritmetik işleme sokarsak sonuç float türünden çıkacaktır. + Tabii özel bir durum olarak / operatörünün her zaman float değer ürettiğine dikkat ediniz. Yani biz iki int değeri / operatörüyle + işleme sokarsak sonuç int türden değil float türden çıkacaktır. Mantıksal operatörlerin de her zaman bool türden değer ürettiğine dikkat + ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 3.14 +c = a + b +print(c, type(c)) # 13.14 + +#------------------------------------------------------------------------------------------------------------------------ + bool türü ile int türü işleme sokulursa sonuç int türünden, bool türü ile float türü işleme sokulursa sonuç float türünden, + bool türü ile complex türü işleme sokulursa sonuç complex türünden elde edilmektedir. Bu tür işlemlerde bool değer True ise + 1 olarak, False ise 0 olarak işleme sokulmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = True +c = a + b +print(c, type(c)) # 11 + +a = 12.34 +c = a + b +print(c, type(c)) # 13.34 + +a = 3j+2 +c = a + b +print(c, type(c)) # (3+3j) + +#------------------------------------------------------------------------------------------------------------------------ + iki bool değer kendi aralarında işleme sokulduğunda sonuç int türünden çıkmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = True +b = False +c = a + b + +print(c, type(c)) # 1 + +#------------------------------------------------------------------------------------------------------------------------ + int bir değerle float bir değer işleme sokulduğunda sonuç float çıkacaktır ancak sonuç üzerinde bir kayıp oluşabilir. + Eğer oluşan kayıp basamaksal değilse bu bir hata olarak değerlendirilmez. Gerçek değer en yakın ondan büyük olan ya da küçük olan değer + elde edilir. Ancak basamaksal bir kayıp söz konusu olursa exception (OverflowError) oluşmaktadır. Örneğin: + + >>> a = 1987239812739817239817239871293871928371987239817239817239871293871298371982739812379812739182739812739 + 817239817239871239871293871298371982379182739817239817239871298371928379182739817239817298379827349827349872349 + 87239847293847928374982734982734982734987239847293847928374982374982374982374982374982374982371234234234234 + >>> b = a * 2.0 + Traceback (most recent call last): + File "", line 1, in + OverflowError: int too large to convert to float +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + complex türü ile int türü, float türü ve bool türü aritmetik işleme sokulursa sonuç complex türünden elde edilmektedir. Örneğin: + + >>> a = 3j+2 + >>> b = 1 + >>> c = a + b + >>> c + (3+3j) + >>> b = 1.2 + >>> c = a + b + >>> c + (3.2+3j) + >>> b = True + >>> c = a + b + >>> c + (3+3j) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da iki str türündne değişken toplama işlemine sokulduğunda işlem sonucunda yine bir str nesnesi elde edilir. + Elde edilen string iki string'in birleşiminden oluşur. Java, C# gibi pek çok dilde de bu özellik benzer biçimde vardır. + Örneğin: + + >>> s = 'ankara' + >>> k = 'istanbul' + >>> result = s + k + >>> result + 'ankaraistanbul' + + Ancak str türünden bir değişkenle diğer türden bir değer toplanamaz. Oysa Java ve C# gibi bazı dillerde bu durum mümkündür. + O dillerde bu toplama işlemi yapılırken string olmayan tür otomatik olarak string türüne dönüştürülüp sonuç string türünden elde + edilmektedir. Ancak yukarıda da belirttiğimiz gibi Python'da bu durum geçerli değildir. Özellikle diğer dillerden geçen kişilerin + bu duruma dikkat etmesi gerekir. Örneğin: + + >>> s = 'Ankara' + >>> a = 6 + >>> b = s + a + Traceback (most recent call last): + File "", line 1, in + TypeError: can only concatenate str (not "int") to str + + Tabii programcı böyle bir şeyi yapmak isterse diğer operandı kendisi str türüne dönüştürmelidir. Örneğin: + + >>> s = 'Ankara' + >>> a = 6 + >>> b = s + str(a) + >>> b + 'Ankara6' + + String'lerle ilgili diğer özellikler izleyen bölümlerde ele alınmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aralarında fiziksel ya da mantıksal ilişki olan bir grup nesneden oluşan topluluğa "veri yapısı (data structure)" denilmektedir. + Veri yapısı çoğul bir anlam ifade etmektedir. Python'da da built-in çeşitli veri yapıları vardır. Bunlar "listeler", "demetler", + "sözlükler", "kümeler" ve "stringler"dir. Python'daki bu built-in bu veri yapıları doğrudan dilin sentaksıyla desteklenmiş durumdadır. + Bu da programlamayı oldukça kolaylaştırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir (iterable) nesne demek bu nesne dolaşıldığında birtakım değerlerin elde edilmesi demektir. Yani dolaşılabilir nesneler + üzerinde dolaşım (iteration) işlemi yapılabilir. Dolaşım işlemi sırasında biz dolaşılabilir nesneden birtakım değerler elde ederiz. + Dolaşılabilir nesneler genellikle kendi içlerinde birtakım nesneleri tutarlar. Başka nesneleri tutan nesnelere Java, C# gibi fillerde + "collection", C++'da ise "container" denilmektedir. Örneğin str türü dolaşılabilir (iterable) bir türdür. Bu durumda str nesnesi de dolaşılabilir + bir nesnedir. Bir string nesnesi dolaşıldığında tek tek stirng'in karakterleri elde edilmektedir. (Tabii string'in karakterleri de + aslında birer string belirtmektedir.) Özetle: + + - Bir nesne dolaşılabilir ise o nesne dolaşıldığında bize nesneler vermektedir. + - Dolaşılabilir nesnelerin dolaşıldığında bize hangi nesneleri vereceği o dolaşılabilir nesne öğrenilirken öğrenilmelidir. + Örneğin string nesneleri dolaşıldığında yazının karakterleri elde edilmektedir. + + Dolaşım (iterator) nesneleri de aynı zamanda dolaşılabilir nesnelerdir. Dolaşılabilir nesnelerle dolaşım nesneleri arasında küçük + bir fark vardır. Bir dolaşılabilir nesne dolaşıldıktan sonra yeniden dolaşılabilir. Biz ondan yine aynı nesneleri elde ederiz. + Ancak bir dolaşım (iiterator) nesnesi bir kez dolaşıldıktan sonra artık biter. Biz onu bir daha dolaşmak istesek bir şey elde edemeyiz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listeler Python'da en çok kullanılan veri yapısıdır. Listeler diğer programlama dillerindeki "dizilere (arrays)" + benzetilebilir. Bir liste yaratmanın çeşitli yolları vardır. Liste yaratmak için en çok kullanılan yöntem köşeli parantez sentaksıdır. + Köşeli parantezler içerisine ',' atomu ile ayrlmış değerler girilirse bir liste nesnesi yaratılmaktadır. Örneğin: + + a = [10, 'ali', 2.3, True, 20] + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] +print(a) # [10, 20, 30, 40, 50] +print(type(a)) # + +#------------------------------------------------------------------------------------------------------------------------ + Liste elemanları aynı türden olmak zorunda değildir. (Halbuki C, C++, Java ve C# gibi bazı dillerde diziler aynı türden + elemanlara sahip olmak zorundadır.) +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 3.14, 'ankara', False] +print(a) # [1, 3.14, 'ankara', False] + +#------------------------------------------------------------------------------------------------------------------------ + Listenin belli bir elemanına [] operatörü ile erişebiliriz. Yani listelerin elemanları bu sayede sanki bağımsız nesnelermiş gibi + kullanılabilmektedir. Listenin ilk elemanı 0'ıncı indekslş elemanıdır. Bu durumda n elemanlı bir listenin son elemanı (n - 1)'nci + indekste olacaktır. Köşeli parantez içerisine bir ifade yerleştirilebilir. Örneğin a bir liste belirtmek üzere a[2], a[i + 2], + a[i + k - 1] gibi kullanımlar geçerlidir. Bu duurmda önce köşeli parantezin içerisindeki ifadenin sayısal değeri hesaplanır. + Sonra o değerdeki indekse erişilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +val = a[2] + 100 +print(val) # 130 + +#------------------------------------------------------------------------------------------------------------------------ + Köşeli parantez içerisindeki ifade int türden olma zorundadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +i = 1 + +val = a[i + 1] # köşeli parantez içerisindeki ifade int türden geçerli +print(val) + +i = 1.0 +val = a[i] # exception! köşeli parantez içerisindeki ifade int türden olmak zorunda + +i = 1 + +val = a[i + 2.] #exception! köşeli parantez içerisindeki ifade int türden olmak zorunda + +#------------------------------------------------------------------------------------------------------------------------ + Bir listenin olmayan bir elemanına erişmeye çalışırsak IndexError isimli bir exception oluşur. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +val = a[100] # IndexError oluşur + +#------------------------------------------------------------------------------------------------------------------------ + Listeler "değiştirilebilir (mutable)" türlerdir. Bir türün değiştirilebilir olması demek o türdne bir nesnenin içeriğinde + değişiklik yapılabilmesi demektir. Yani list türü değiştirilebilir olduğu iiçin biz bir listenin elemanlarını atama yoluyla değiştebiliriz. + Örneğin: + + a = [10, 20, 30, 40, 50] + print(a[0]) # 10 + a[0] = 'ali' + print(a[0]) # ali + print(a) # ['ali', 20, 30, 40, 50] + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +print(a) # [10, 20, 30, 40, 50] + +a[2] = 'ali' + +print(a) # [10, 20, 'ali', 40, 50] + +#------------------------------------------------------------------------------------------------------------------------ + Bir listedeki eleman sayısı built-in len isimli fonksiyonla elde edilebilir. len fonksiyonu bize listenin eleman sayısını + int bir değer olarak vermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +n = len(a) +print(n) # 5 + +#------------------------------------------------------------------------------------------------------------------------ + Boş liste de söz konusu olabilir. Boş bir liste 0 eleman uzunluğundadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [] + +n = len(a) +print(n) # 0 + +#------------------------------------------------------------------------------------------------------------------------ + Daha önceden de belirttiğimiz gibi T bir tür belirtmek üzere T(...) hem T türüne dönüştürme anlamına gelir hem de T türünden + nesne yaratma anlamına gelir. Örneğin int() ifadesinden içinde 0 olan bir int nesne, float() ifadesinden içinde 0 olan bir float nesne elde ederiz. + bool() ifadesi bize False bir nesne verir. İşte list() ifadesi de içi boş bir listeyi bize vermektedir. Yani aşağıdaki iki ifade eşdeğerdir: + + a = list() + a = [] + + Örneğin: + + >>> a = int() + >>> a + 0 + >>> b = float() + >>> b + 0.0 + >>> c = bool() + >>> c + False + >>> d = str() + >>> d + '' + >>> e = complex() + >>> e + 0j + >>> f = list() + >>> f + [] + +#------------------------------------------------------------------------------------------------------------------------ + +a = list() + +n = len(a) +print(n) # 0 + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfının tür fonksiyonu olan list fonksiyonuna biz dolaşılabilir bir nesne verebiliriz. Bu durumda bu nesne dolaşılır. + Dolaşım sonucunda elde edilen değerlerden liste oluşturulur. Örneğin str nesneleri dolaşılabilir nesnelerdir. Yani str sınıfı dolaşılabilir + bir sınıftır. Bir str nesnesi dolaşıldığında tek tek string'in karakterleri elde edilmektedir. O halde bir list fonksiyonuna + bir string nesnesi verirsek list o string'i dolaşıp o string'in karakterlerini elde edecek ve o karakterlerden liste oluşturacaktır. Örneğin: + + s = 'ankara' + a = list(s) + + Burada s string'i dolaşıldığında sırasıyla 'a', 'n', 'k', 'a', 'r', 'a' str nesneleri elde edilmektedir. İşte bu nesnelerden + liste oluşturulmuştur. + + int türü, float türü, bool türü, complex türü, NonType türü "dolaşılabilir (iterable)" türler değildir. + Bu nedenle biz list fonksiyonuna argüman olarak bu türden değerler veremeyiz. Eğer bunu yaparsak exception (TypeError) oluşur. + Örneğin: + + >>> s = 'ankara' + >>> a = list(s) + >>> a + ['a', 'n', 'k', 'a', 'r', 'a'] + >>> k = 123 + >>> a = list(k) + Traceback (most recent call last): + File "", line 1, in + TypeError: 'int' object is not iterable +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +a = list(s) +print(a) # ['a', 'n', 'k', 'a', 'r', 'a'] + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfı da dolaşılabilir bir sınıftır. Yani list nesneleri de dolaşılabilir nesnelerdir. Bir list nesnesi dolaşıldığında + tek tek liste içerisindeki değerler elde edilmektedir. Örneğin: + + a = [1, 2, 3, 4, 5] + b = list(a) + + Burada a listesi dolaşıldığında sırasıyla 1, 2, 3, 4, 5 değerleri elde edilecektir. İşte bu değerlerden yeni bir list nesnesi + oluşturulmuştur. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] +b = list(a) + +print(a, id(a)) +print(b, id(b)) + +#------------------------------------------------------------------------------------------------------------------------ + Bir list nesnesi elemanların kendilerini değil adreslerini tutmaktadır. Örneğin: + + a = [10, 'ali', 12.3] + + aslında burada bu listenin ilk elemanı (yani a[0] elemanı) içerisinde 10 değeri bulunan nesnenin adresini tutar. Diğer elemanlar da + bu bimimdedir. Örneğin: + + >>> a = [10, 'ali', 12.3] + >>> id(a[1]) + 1667401777200 + >>> val = a[1] + >>> id(val) + 1667401777200 + >>> print(a[1]) + ali + >>> print(val) + ali + + Biz listenin bir elemanını değiştirdiğimizde aslında yeni bir nesne yaratıp o nesnenin adresini o elemana yerleştiririz. Örneğin: + + >>> a = [10, 'ali', 12.3] + >>> id(a[0]) + 1667362351696 + >>> a[0] = 11 + >>> id(a[0]) + 1667362351728 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listelerin değiştirilebilir olması demek liste elemanlarına başka adreslerin atanabilmesi demektir. Örneğin: + + a = [1, 2, 3] + + Burada biz listenin 1'inci indisli elemanını değiştirelim: + + a[1] = 100 + + Burada biz içerisinde 2 değeri olan int nesneyi değiştirmedik. Listenin 1'inci indisli elemanındaki adresi değiştirdik. + Yani artık listenin 1'inci indisli elemanı içerisinde 100 olan başka bir int nesneyi göstermektedir. Örneğin: + + >>> a = [1, 2, 3] + >>> id(a[1]) + 1667362351440 + >>> a[1] = 100 + >>> id(a[1]) + 1667362543056 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir listenin bir elemanı başka bir liste olabilir. Örneğin: + + a = [10, [20, 30, 40], 50] + + Burada bu listenin 1'inci indisli elemanı başka bir list nesnesini göstermektedir. Bir listenin içerisindeki listenin elemanlarına + ikinci bir köşeli parantez ile erişebiliriz. Yani yukarıdaki örnekte a[1]'in türü list biçimindedir. O halde örneğin + a[1][2] gibi bir ifade ile biz 40 değerine erişiriz. Tabii aslında listenin 1'inci indisli elemanı diğer listenin adresini tutmaktadır. Örneğin: + + >>> a = [10, [20, 30, 40], 50] + >>> a[1][2] + 40 + >>> a[1][0] + 20 + +#------------------------------------------------------------------------------------------------------------------------ + Python'da matrissel bir veri yapısı listenin içerisinde liste ile oluşturulur. Bunun başka bir yolu yoktur. Örneğin biz + 3x3'lük bir matris oluşturmak isteyelim: + + a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + + Tabii matrisel bir listenin elemanları aynı uzunlukta listelerden oluşmak zorunda değildir. Örneğin: + + a = [[1, 2, 3, 4], [5, 6, 7], [8, 9]] +#------------------------------------------------------------------------------------------------------------------------ + +a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +print(a) # [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +val = a[2][1] +print(val) # 8 + +val = a[1][2] +print(val) # 6 + +#------------------------------------------------------------------------------------------------------------------------ + liste elemanlarına erişirken köşeli parantez içerisindeki değer negatif olabilir. Eğer köşeli parantez içerisindeki indeks belirten + ifade negatif ise bu özel bir anlam ifade etmektedir. Bu durumda efektif indeks (yani gerçek indeks) bu negatif değerle listenin + uzunluğu toplanarak elde edilir ve erişim bu efektif indekse yapılır. a[i] gibi bir erişimde i'nin negatif bir değerde olduğunu varsayalım. + Bu durumda a[i] ile a[i + len(a)] tamamen eşdeğerdir. Örneğin: + + a = [10, 20, 30, 40, 50] + + Burada a[-1] ile aslında a[-1 + len(a)] aynı anlamdadır. -1 + len(a) burada 4 değerini verir. O zaman a[-1] ifadesi aslında + listenin son elemanını belirtmektedir. O halde a[-2] ifadesi listenin sondan bir önceki elemanını belirtecektir. Yani negatif indeksler + sondan başa doğru listeyi indekslemektedir. n elemanlı bir listenin son negatif indeksinin -n olduğuna ancak son pozitif indeksinin + n - 1 olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +print(a[-1]) # 50 +print(a[-2]) # 40 +print(a[-3]) # 30 +print(a[-4]) # 20 +print(a[-5]) # 10 + +#------------------------------------------------------------------------------------------------------------------------ + Köşeli parantez içerisindeki negatif değer büyük olursa bu durumda efektif indeks gerçekten negatif olur. Yani bu durum dizide + olmayan bir elemana erişmek znlamına gelir. Böylesi durumlarda exception (IndexError) oluşmaktadır. Örneğin: + + a = [10, 20, 30, 40, 50] + + Burada köşeli parantez içerisine yazabileceğimiz mutlak değerce en büyük negatif değer -5'tir. Bundan daha küçük negatif değerler + exception oluşmasına (IndexError) yol açacaktır. Yani biz büyük negatif değerler vererek dizinin başından daha önceki yerlere erişemeyiz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +print(a[-10]) # exception oluşur! efektif indeks (yani gerçek indeks) -5 + +#------------------------------------------------------------------------------------------------------------------------ + Listenin elemanı liste olduğu durumda da negatif indeksler benzer biçimde kullanılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +val = a[-2][1] # 5 + +print(val) + +val = a[-2][-1] # 6 +print(val) + +#------------------------------------------------------------------------------------------------------------------------ + Listelerin negatif indekslenmesi onun son elemanlarına erişme işlemini kolaylaştırmaktadır. Yine bazı algoritmalarda + bu durum daha okunabilir ve kısa yazımlar sağlamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir liste içinde bir grup elemanı bir liste olarak elde edebiliriz. Bunun için "dilimleme (slicing)" denilen bir sentaks + kullanılmaktadır. Dilimleme sentaksının genel biçimi şöyledir: + + a[start:stop] + a[start:stop:step] + + Görüldüğü gibi dilimlemede step kısmı hiç belirtilmeyebilir. Dilimleme ile listenin start indeksli elemanı dahil olacak biçimde ancak + stop indeksli elemanı dahil olmayacak biçimde elemanları yine bir liste olarak elde ederiz. start, stop ve step int türden ifadeler olmak zorundadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[2:7] +print(b) # [30, 40, 50, 60, 70] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimleme sırasında start ya da stop indeks liste uzunluğundan büyük olursa liste uzunluğuna çekilir. Yani bu durum bir + exception oluşturmaz. Dilimleme sırasında start ile stop aynı değerdeyse ya da start stop değerinden büyükse boş liste elde + edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[3:4] +print(b) # [40] + +b = a[3:3] +print(b) # [] + +b = a[5:2] +print(b) # start stop ile aynı olursa ya da stop'tan büyük olursa boş liste elde edilir + +b = a[2:10] +print(b) # [30, 40, 50, 60, 70, 80, 90, 100] + +b = a[2:20] +print(b) # [30, 40, 50, 60, 70, 80, 90, 100] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimlemede start ve stop ifadelerinde negatif indeksler kullanılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[5:-2] +print(b) # [60, 70, 80] + +b = a[-5:-1] +print(b) # [60, 70, 80, 90] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimlemede start ve stop ifadelerinde negatif indeks girildikten sonra efektif indeks hesaplandığında (yani bu değer len(a) ile toplandığında) + eğer negatif çıkıyorsa 0 olarak işleme girer. Bu durumda bir exeption oluşmaz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[-20:3] # efektif indekx -10 ama 0 olarak ele alınacak +print(b) # [10, 20, 30] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimlemede start belirtilmezse start değerinin 0 olarak belirtildiği girildiği gelir, stop belirtilmezse stop değerinin listenin uzunlu biçiminde + girildiği anlamına gelir. Örneğin a[:n] ifadesi a[0:n] ile eşdeğerdir. a[n:] ifadesi ise a[n:len(a)] ile eşdeğerdir. Başka bir deyişle dilimlemde start + belirtilmezse "baştan itibaren", stop belirtilmezse "geri kalan hepsi" anlamına gelmektedir. Tabii start da stop da belirtilmeyebilir. + Bu durumda listenin tüm elemanlardna liste oluşturulur. Yani a[:] ifadesi a[0:len(a)] eşdeğerdir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[:7] # eğer start belirtilmezse 0 belirtilmiş gibi işlem yapılır. +print(b) # [10, 20, 30, 40, 50, 60, 70] + +b = a[2:] # stop değeri yazılmazsa listenin uzunluğu yazılmış gibi kabul edilir (yani geri kalan hepsi) +print(b) # [30, 40, 50, 60, 70, 80, 90, 100] + +b = a[:] # start ve stop yazılmazsa listenin hepsi elde edilir +print(b) # [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimlemede step atlama miktarını belirtir. step hiç belirtilmezse sanki 1 olarak belirtilmiş gibi kabul edilir. + Tabii elde edilecek değerlere hiçbir zaman stop dahil olmaz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[2:9:2] +print(b) # [30, 50, 70, 90] + +b = a[:8:3] +print(b) # [10, 40, 70] + +#------------------------------------------------------------------------------------------------------------------------ + step değeri negatif de olabilir. Ancak bu durumda ilerleme yönü ters olur. İlerleme ters olduğu için start indeksinin, + stop indeksinden daha büyük olması gerekir. Yine start indeksi dahil stop indeksi dahil değildir. step negatif ise + bu durumda start indeksin boş bırakılması "listenin uzunluğı - 1" anlamına stop indeksin boş bırakılması efektif -1 anlamına (yani ilk + eleman dahil olacak biçimde) gelmektedir. Örneğin a[:2:-1] ifadesinde ilerleme yönü terstir. start yazılmamıştır. Bu durumde sanki start + yerine len(a) - 1 yazılmış gibi işlem yapılır. Başka bir deyişle ifadenin eşdeğeri a[len(a) - 1:2:-1] biçimindedir. Örneğin a[4::-1] + ifadesinde ilerleme yönü yine terstir. stop belirtilmemiştir. Bu durumda sanki efektif -1 gibi baka bir deyişle atop inswka "-len(a) - 1" + gibi ele alınmaktadır. İfadenin eşdeğeri a[4:-len(a)-1:-1] biçimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[8:2:-1] # 8 dahil 2 dahil değil ters yönde hareket +print(b) # [90, 80, 70, 60, 50, 40] + +b = a[:2:-1] +print(b) # [100, 90, 80, 70, 60, 50, 40] + +b = a[4::-1] # 4'üncü indeksten başa kadar hepsi +print(b) # [50, 40, 30, 20, 10] + +#------------------------------------------------------------------------------------------------------------------------ + Bir listeyi tersyüz etmek için a[::-1] kalıbı kullanılmaktdır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +b = a[::-1] # listeyi ters olarak elde ederiz +print(b) # [100, 90, 80, 70, 60, 50, 40, 30, 20, 10] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimleme yoluyla liste elemanları güncellenebilir. Eğer step belirtilmezsa bu işlemin genel sentaksı şöyledir: + + a[start:stop] = + + Bu durumda önce dilimlenen elemanlar silinir, sonra onların yerine dolaşılabilir nesnedeki elemanlar start indeksten itibaren insert edilir. + Burada silinen eleman sayısı ile dolaşılabilir nesnedeki eleman sayısının aynı olması gerekmemektedir. (Burada atama operatörü yerine + Walrus operatörünü kullanamayız.) +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +a[2:6] = [1, 2, 3, 4, 5, 6, 7] +print(a) # [10, 20, 1, 2, 3, 4, 5, 6, 7, 70, 80, 90, 100] + +#------------------------------------------------------------------------------------------------------------------------ + String'lerin de dolaşılabilir nesneler olduğunu anımsayınız. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +a[2:6] = 'ankara' +print(a) # [10, 20, 'a', 'n', 'k', 'a', 'r', 'a', 70, 80, 90, 100] + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir nesnede hiç eleman yoksa bu durum bir silme anlamına gelecektir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +a[2:6] = [] +print(a) # [10, 20, 70, 80, 90, 100] + +#------------------------------------------------------------------------------------------------------------------------ + Dilimleme yoluyla liste elemanları güncellenirken eğer step belirtilirse bu durumda ona atanan dolaşılabilir + nesnenin eleman sayısı dilimleme yoluyla silinecek eleman sayısı ile aynı olmak zorundadır. (Python standard kütüphane dokümanlarında + step değerinin 1 olması durumunda da atanan dolaşılabilir nesnenin eleman sayısının dilimlemedne elde edilen eleman sayısı ile aynı olması gerektiği + dolaylı olarak belirtilmektedir. Ancak CPython gerçekleştirimi step değeri 1 ise sanki step değeri hiç belirtilmemiş gibi işlem yapmaktadır.) +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +a[2:6:2] = [1, 2, 3] # exception oluşur! dilimlemeden 2 eleman elde edildi, ancak dolaşılabilir nesnede 3 eleman var + +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Dilimleme yoluyla atama yapılırken dilimleme sonucunda hiçbir eleman seçilmiyorsa bu durum start indeks'ten itibaren insert işlemi + anlamına gelmektedir. Örneğin: + + >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a[3:3] = 'ali' + >>> a + [10, 20, 30, 'a', 'l', 'i', 40, 50, 60, 70, 80, 90, 100] + >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a[4:3] = 'ali' + >>> a + [10, 20, 30, 40, 'a', 'l', 'i', 50, 60, 70, 80, 90, 100] +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listenin belli bir elemanına atama yapmak ile dilimleme yoluyla atama yapmak arasındaki farklılığa dikkat ediniz: + + >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a[0] = [1, 2, 3, 4, 5] + >>> a + [[1, 2, 3, 4, 5], 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a[0:1] = [1, 2, 3, 4, 5] + >>> a + [1, 2, 3, 4, 5, 20, 30, 40, 50, 60, 70, 80, 90, 100] + + Biz dilimleme yapmadan bir dolaşılabilir nesneyi belli bir indekse atadığımızda listenin o indeksinde dolaşılabilir nesne olur. + Halbuki dilimleme yoluyla atama yapıldığında dolaşılabilir nesnenin elemanları dilimlenen elemanların yerlerine insert edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dilimleme yoluyla güncelleme yapılırken step değeri negatif ise yine dolaşılabilir nesnedeki eleman sayısı dilimlenen + eleman sayısı kadar olmak zorundadır. Ancak elemanlar ters yönde insert edilecektir. Örneğin: + + >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a[7:2:-1] = [100, 200, 300, 400, 500] + >>> a + [10, 20, 30, 500, 400, 300, 200, 100, 90, 100] + >>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + >>> a[7:2:-2] = [100, 200, 300] + >>> a + [10, 20, 30, 300, 50, 200, 70, 100, 90, 100] +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listeyi dilimlediğimizde biz yeni bir list nesnesi oluştururuz. Ancak bu yeni oluşturulan list nesnesi aslında dilimlenen list nesnesindeki + dilimlenen elemanların adreslerinden oluşmaktadır. Yani dilimleme işlemi "sığ kopyalama (shallow copy)" biçiminde yapılmaktadır. + Sığ kopyalama demek yerni oluşturulan listenin elemanlarının dilimlene listenin dilimlenen elemanlarıyla aynı nesneleri göstermesi demektir. + Sığ kopyaalama sırasında dilimlenen listedeki adresler yeni oluşturulan listeye kopyalanmaktadır. Böylece dilimlenen listenin ilgili elemanlarıyla + yeni oluşturulan listenin elemanları aynı nesneleri gösterir hale gelmektedir. Aşağıdaki örneği inceleyiniz: + + >>> a = [10, 20, 30, 40, 50] + >>> b = a[2:4] + >>> id(a) + 1620015142592 + >>> id(b) + 1620015144768 + >>> id(a[2]) + 1619975761104 + >>> id(b[0]) + 1619975761104 + >>> id(a[3]) + 1619975761424 + >>> id(b[1]) + 1619975761424 + + Burada a[2] elemanın gösterdiği nesne ile b[0] elemanının gösterdiği nesnenin, a[3] elemanının gösteridği nesne ile b[1] elemanının + gösteridiği nesnenin aynı nesneler olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listeler değiştirilebilir (mutable) nesneler olduğuna göre listenin elemanı bir liste ise dilimlemede yeni oluşturulan listeye + bu eleman olan listenin adresi kopyalanacaktır. Bu durumda bu eleman olan listede yapılacak değişiklik her iki listede de + görünür olacaktır. Aşağıdaki örneğe dikkat ediniz: + + >>> a = [10, [20, 30, 40], 50] + >>> b = a[0:2] + >>> b[1][0] = 100 + >>> a + [10, [100, 30, 40], 50] + >>> b + [10, [100, 30, 40]] + >>> a[1][1] = 200 + >>> a + [10, [100, 200, 40], 50] + >>> b + [10, [100, 200, 40]] + + Bu örnekte id(a[1]) ile id(b[1])'in aynı olduğuna dikkat ediniz: + + >>> b = a[0:2] + >>> id(a[1]) + 2644868980608 + >>> id(b[1]) + 2644868980608 + >>> a[1] is b[1] + True +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi bir sınıf içerisindeki fonksiyonlara Python'da "metot (method)" denilmekteydi. Bir metodun aynı sınıf türünden + bir değişkenle "." operatörü kullanılarak çağrılması gerektiğini anımsayınız. Örneğin: + + a.foo() + + Burada a hangi sınıf türündense foo da o sınıfın bir metodudur. Metotlar belli bir nesne üzerinde işlem yapan fonksiyonlardır. Dolayısıyla + a.foo() ifadesinde foo metodu a nesnesi üzerinde (a değişkeninin gösterdiği nesne üzerinde) işlem yapmaktadır. Örneğin: + + x = [1, 2, 3, 4, 5] + + Burada x list sınıfı türünden bir değişkendir. foo list sınıfının bir metodu olsun: + + x.foo(...) + + Burada foo bu x nesnesi üzerinde onun elemanları ile ilgili bir işlem yapacaktır. eğer fonksiyonlar bir sınıf içerisinde değilse + belli bir nesne üzerinde işlem yapmak yerine genel işlemleri yapmak üzere yazılmış olurlar. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfının append isimli metodu list nesnesinin sonuna yeni bir eleman ekler. Örneğin: + + a = [1, 2, 3, 4, 5] # [1, 2, 3, 4, 5] + + print(a) + a.append('ankara') # [1, 2, 3, 4, 5, 'ankara'] + print(a) + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] +print(a) # [10, 20, 30] + +a.append(100) +print(a) # [10, 20, 30, 100] + +a.append('ali') +print(a) # [10, 20, 30, 100, 'ali'] + +a.append(12.4) +print(a) # [10, 20, 30, 100, 'ali', 12.4] + +#------------------------------------------------------------------------------------------------------------------------ + append her zaman tek bir nesnenin eklenmesine yol açmaktadır. Yani append ile biz listeye liste eklemek istesek listenin içerisindekiler + eklenmeyecektir. Listenin kendisi tek bir eleman olarak eklenecektir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] +print(a) # [10, 20, 30] + +a.append([100, 200, 300]) +print(a) # [10, 20, 30, [100, 200, 300]] + +#------------------------------------------------------------------------------------------------------------------------ + Bir listenin sonuna birden fazla eleman extend metoduyla eklenmektedir. extend metodu bizden dolaşılabilir bir nesne alır. + O nesneyi dolaşarak elde ettiği tüm nesneleri listeye ekler. Listelerin ve string'lerin dolaşılabilir nesneler olduğunu anımsayınız. + Eğer extend metoduna dolaşılabilir olmayan bir argüman gireresek exception (TypeError) oluşacaktır. extend metodu tek bir argüman almaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] +print(a) # [10, 20, 30] + +a.extend([1, 2, 3, 4, 5]) +print(a) # [10, 20, 30, 1, 2, 3, 4, 5] + +#------------------------------------------------------------------------------------------------------------------------ + str dolaşılabilir bir sınıf olduğuna göre bir string'in karakterlerini extend metodu ile listeye ekleyebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] +print(a) # [10, 20, 30] + +a.extend('ankara') +print(a) # [10, 20, 30, 'a', 'n', 'k', 'a', 'r', 'a'] + +#------------------------------------------------------------------------------------------------------------------------ + list sınfının index isimli metodu parametresiyle aldığı nesneyi listede arar. Eğer bulursa ilk bulduğu yerin indeks numarasıyla + geri döner. Eğer bulamazsa exception (ValueError) oluşmaktadır. (Dolayısıyla bu metot genellikle zaten var olduğunu bildiğimiz bir elemanın yerini + aramak için kullanılmaktadır.) Bu metot bize int türünden index belirten bir değer vermektedir. Örneğin: + + >>> a = [10, 2, 7, 8, 19, 41] + >>> result = a.index(19) + >>> result + 4 + >>> result = a.index(30) + Traceback (most recent call last): + File "", line 1, in + ValueError: 30 is not in list + + İleride ayrı bir konuda ele alınacak olsa da şimdiden bir noktayı belirtmek istiyoruz: Python'da farklı sınıflar türünden (int, float ve bool + haricinde) değişkenler == ve != operatörleriyle karşılaştırıldığında == işlemi her zaman False, != işlemi her zaman True vermektedir. Örneğin: + + >>> 'ali' == 19 + False + + DOlayısıyla index metoduyla arama yapılırken liste içerisinde farklı türlerden nesneler varsa bunlar asla aradığımız değere + eşit olamayacağı için geçilmektedir. index metodunun her eleman için == karşılaştırması yaptığını düşünebilirsiniz. Örneğin: + + >>> a = [10, 5, 'ali', 43, 21] + >>> result = a.index(43) + >>> result + 3 + >>> result = a.index('ali') + >>> result + 2 + + Eğer değer liste içerisinde birden fazla yerde varsa list metodu değerin listede ilk bulunduğu indeksi vermektedir. Örneğin: + + >>> a = [1, 4, 7, 8, 4, 7] + >>> result = a.index(4) + >>> result + 1 + + index metodu asında iki argüman ve üç argüman da alabilir. Eğer metodu biz iki argümanlı kullanırsak birinci argüman aranacak + değeri, ikinci argüman ise aramanın başlatılacağı indeksi belirtir. Örneğin: + + >>> a = [10, 4, 5, 8, 9, 4, 8] + >>> result = a.index(4, 2) + >>> result + 5 + + Burada 4 değeri aranmıştır. Ancak arama baştan itibaren değil 2'inci indeksten itibaren başlatılmıştır. Eğer index üç argümanla + kullanılırsa üçüncü argüman aramanın bitirileceği indeksi belirtir. Ancak bu indeks aramaya dahil değildir. Örneğin: + + >>> a = [10, 4, 5, 8, 9, 4, 8] + >>> result = a.index(4, 2, 5) + Traceback (most recent call last): + File "", line 1, in + ValueError: 4 is not in list + + Burada 4 değeri aranmak istenmiştir. Ancak arama 2'inci indeksten başlatılıp 5'inci indekse kadar devam ettirilmiştir. 5'indeks + aramaya dahil olmadığı için exception oluşmuştur. Burada aramanın başlatılacağı ve bitirileceği indeks negatif indeks olarak belirtilebilir. + +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 3, 5, 'ali', 'veli', 7, 'veli'] + +result = a.index('veli') +print(result) # 4 + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfının count isimli metodu bizden bir değer alır. Liste içerisinde o değerden kaç tane olduğunu bize verir. Örneğin: + + >>> a = [1, 3, 7, 4, 3, 8, 3, 4, 3] + >>> result = a.count(3) + >>> result + 4 + >>> result = a.count(10) + >>> result + 0 + >>> result = a.count('ali') + >>> result + 0 +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 5, 7, 7, 4, 7, 9, 7] + +result = a.count(7) +print(result) # 4 + +result = a.count(5) +print(result) # 1 + +result = a.count(70) +print(result) # 0 + +#------------------------------------------------------------------------------------------------------------------------ + Bir listeden tek bir elemanı silmek için pop isimli metot kullanılabilir. Bu metot argümansız kullanılırsa son eleman silinir. + Argümanlı kullanılırsa belirtilen indeksteki eleman silinir. Yani metot argümanlı ya da argümansız kullanılabilmektedir. + Argümanlı kullanılacaksa argüman index belirtmelidir. pop silinen elemanın kendisini de bize geri dönüş değeri olarak vermektedir. + pop metoduna verilen index sınır dışındaysa ya da liste boşsa exception (IndexError) oluşmaktadır. index metodu negatif indeksleri + kabul etmektedir. Bu durumda negatif değerler yine list uzunluğu ile toplanıp efektif indeks elde edilmektedir. Yani örneğin a.pop(-2) + işlemi son elemandan bir önceki elenı siler. Örneğin: + + >>> a = [10, 20, 30, 40, 50] + >>> result = a.pop() + >>> result + 50 + >>> a + [10, 20, 30, 40] + >>> result = a.pop(2) + >>> result + 30 + >>> a + [10, 20, 40] + >>> result = a.pop(-2) + >>> result + 20 + >>> a + [10, 40] + >>> a.pop(10) + Traceback (most recent call last): + File "", line 1, in + IndexError: pop index out of range + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] +print(a) # [10, 20, 30, 40, 50] + +a.pop() + +print(a) # [10, 20, 30, 40] + +a = [10, 20, 30, 40, 50] +a.pop(2) + +print(a) # [10, 20, 40, 50] + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfının remove metodu da silme yapar. Ancak silinecek eleman pop metodunda olduğu gibi indeks numarasıyla değil + bizzat değeriyle belirtilmektedir. remove parametresiyle belirtilen değer, liste içerisinde arar. Eğer bulursa yalnızca ilk + bulduğunu siler, eğer bulamazsa exception (ValueError) oluşur. remove metodu bize herhangi bir geri dönüş değeri vermez. + Örneğin: + + >>> a = [3, 7, 9, 'ali', 3] + >>> a.remove('ali') + >>> a + [3, 7, 9, 3] + >>> a.remove(3) + >>> a + [7, 9, 3] + >>> a.remove('veli') + Traceback (most recent call last): + File "", line 1, in + ValueError: list.remove(x): x not in list + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 40] +print(a) # [10, 20, 30, 40, 50, 40] + +a.remove(40) +print(a) # [10, 20, 30, 50, 40] + +#------------------------------------------------------------------------------------------------------------------------ + clear isimli metot listenin tüm elemanlarını siler. Yani liste 0 elemanlı boş bir liste haline gelir. clear metodunun + parametresi yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] +print(a) # [10, 20, 30, 40, 50] + +a.clear() +print(a) # [] + +#------------------------------------------------------------------------------------------------------------------------ + reverse metodu listeyi ters yüz eder. Bu metot bize bir geri dönüş değeri vermez. Ters yüz etme işlemi nesnenin üzerinde (in place) + yapılmaktadır. a bir liste belirtmek üzere a[::-1] işlemi de listeyi ters yüz eder. Ancak bu işlem ters yüz edilmiş yeni bir liste + vermektedir. Bu işlem sonucunda a listesinde bir değişiklik olmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] +print(a) # [10, 20, 30, 40, 50] + +b = a[::-1] # [10, 20, 30, 40, 50] +print(a) +print(b) # [50, 40, 30, 20, 10] + +a.reverse() +print(a) # [50, 40, 30, 20, 10] + +#------------------------------------------------------------------------------------------------------------------------ + Bir veri yapısının (list, dict vs.) bir metodu işlemi veri yapısının üzerinde yapıyorsa buna "in place" işlem denilmektedir. + Tabii her metot işlemini in place yapmayabilir. Metot işlem yapılmış yeni bir veri yapısını bize verebilir. Bu "in place" bir işlem değildir. + Örneğin a.reverse() ile biz "in place" bir işlem yapmış olduk. Ancak a[::-1] işlemi "in place" bir işlem değildir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfının sort isimli metodu liste elemanlarını sıraya dizmektedir. Default durum küçükten byüğe sıraya dizmedir. + sort bize bir değer vermez. Bizzat elemanları sıraya dizer (in place işlem). sort metodu "stable" sort yapmaktadır. + Stable sort aynı elemanların sort edilmiş listede orijinal listedeki sırasına göre yan yana sıraya dizilmesi anlamına gelmektedir. + Örneğin: + + >>> a = [4, 17, 2, 21, 8, -4] + >>> a.sort() + >>> a + [-4, 2, 4, 8, 17, 21] +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 6, 2, 8, 16, -4, 9, 22] + +a.sort() +print(a) # [-4, 2, 3, 6, 8, 9, 16, 22] + +#------------------------------------------------------------------------------------------------------------------------ + sort metodu sıraya dizme işleminde listenin elemanları arasında < karşılaştırması yapmaktadır. Eğer listenin herhangi iki + elemanı arasında < karşılaştırması yapılamazsa bu durumda exception (TypeError) oluşur. Örneğin listenin bir elemanı str bir elemanı int + türden olsun. Bu iki eleman < operatöryüyle karşılaştırılamaz. Bu durumda sort işlemi exception'a yol açacaktır. Tabii + string'ler kendi aralarında < operatörü ile karşılaştırılabilmektedir: + + >>> a = ['veli', 'ali', 'sacit', 'ayşe', 'talat'] + >>> a.sort() + >>> a + ['ali', 'ayşe', 'sacit', 'talat', 'veli'] +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 6, 'ali', 8, 16.2, -4, True, 'veli'] + +a.sort() # exception oluşur! + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi string'ler kendi aralarında karşılaştırılabilmektedir. Dolayısıyla string'lerden + oluşmuş bir liste sort edilebilr. +#------------------------------------------------------------------------------------------------------------------------ + +a = ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] +print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] + +a.sort() +print(a) # ['adana', 'izmir', 'kayseri', 'samsun', 'sivas'] + +#------------------------------------------------------------------------------------------------------------------------ + sort metodu default durumda listeyi küçükten büyüğe (ascending) sıraya dizmektedir. Listeyi büyükten küçüğe (descending) + sıraya dizmek için reverse=True isimli parametresinin kullanılması gerekir. (Yani a.sort() ile a.sort(reverse=False) aynı + anlamdadır.) +#------------------------------------------------------------------------------------------------------------------------ + +a = ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] +print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] + +a.sort(reverse=True) # büyükten küçüğe +print(a) # ['sivas', 'samsun', 'kayseri', 'izmir', 'adana'] + +a.sort() +print(a) # ['adana', 'izmir', 'kayseri', 'samsun', 'sivas'] + +#------------------------------------------------------------------------------------------------------------------------ + list sınıfının reverse metodunun yanı sıra ayrıca reversed isimli bir built-in bir fonksiyon (metot değil) da vardır. + Bu reversed fonksiyonu bizden dolaşılabilir bir nesne alır. Onun ters yüz edilmiş halini bize dolaşılabilir (iterable) + bir nesne biçiminde verir. reversed fonksiyonu "in place" bir işlem yapmaz. reversed fonksiyonu bize bir liste değil dolaşılabilir + bir nesne vermektedir. Yani biz ters yüz edilmiş değerleri o dolaşılabilir nesneyi dolaşarak elde edebiliriz. list fonksiyonunun + dolaşılabilir nesneyi dolaşarak bir liste yaptığını anımsayınız. Örneğin: + + >>> a = [1, 2, 3, 4, 5] + >>> result = reversed(a) + >>> type(result) + + >>> b = list(result) + >>> b + [5, 4, 3, 2, 1] + >>> a + [1, 2, 3, 4, 5] + + reversed fonksiyonu argüman olarak yalnızca liste almaz. Aslında dolaşılabilir herhangi bir nesneyi argüman alabilmektedir. + Örneğin: + + >>> s = 'ankara' + >>> result = reversed(s) + >>> type(result) + + >>> b = list(result) + >>> b + ['a', 'r', 'a', 'k', 'n', 'a'] + +#------------------------------------------------------------------------------------------------------------------------ + +a = ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] +print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] + +b = reversed(a) +c = list(b) + +print(c) # ['sivas', 'kayseri', 'samsun', 'adana', 'izmir'] +print(a) # ['izmir', 'adana', 'samsun', 'kayseri', 'sivas'] + +#------------------------------------------------------------------------------------------------------------------------ + Aslında biz reversed fonksiyonunu her "dolaşılabilir" nesneyle kullanamayız. reversed fonksiyonu "tersten dolaşılabilir (reverse iterable)" + nesneler ile kullanılabilmektedir. Bu bağlamda listeler, stringler aynı zamanda tersten dolaşılabilir biçimdedir. Bu nedenle biz + reverse fonksiyonunu listeler ve string'lerle kullanabilmekteyiz. +#------------------------------------------------------------------------------------------------------------------------ + +s = list(reversed('ankara')) +print(s) # ['a', 'r', 'a', 'k', 'n', 'a'] + +#------------------------------------------------------------------------------------------------------------------------ + sort işlemi için sorted isimli global built-in bir fonksiyon da bulunmaktadır. Bu fonksiyon bize her zaman sort edilmiş yeni bir liste vermektedir. + Fonksiyon dolaşılabilir herhangi bir nesneyi parametre olarak alabilmektedir. Örneğin: + + >>> a = [5, 4, 3, 2, 1] + >>> b = sorted(a) + >>> a + [5, 4, 3, 2, 1] + >>> b + [1, 2, 3, 4, 5] + >>> a = sorted('ankara') + >>> a + ['a', 'a', 'a', 'k', 'n', 'r'] +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 6, 1, 34, 51, 23, 10] +print(a) # [3, 6, 1, 34, 51, 23, 10] + +b = sorted(a) +print(b) # [1, 3, 6, 10, 23, 34, 51] + +a = 'ankara' +b = sorted(a) +print(b) # ['a', 'a', 'a', 'k', 'n', 'r'] + +#------------------------------------------------------------------------------------------------------------------------ + Python'da pek çok veri yapısı ile kullanılabilen "in" isimli iki operandlı araek özel amaçlı bir operatör bulunmaktadır. + in operatörünün sol tarafındaki operand olup olmadığı kontrol edilecek değeri belirtir. Sağ tarafındaki operand ise arama + yapılacak nesneyi belirtir. Bu operatör eğer sol tarafındaki operand ile belirtilen değer sağ tarafındaki operand ile belirtilen + veri yapısında var ise True değer, yok ise False üretmektedir. Yani in operatörü bool bir değer üretmektedir. Örneğin: + + >>> a = [10, 4, 7, 'ali', 5] + >>> 5 in a + True + >>> 8 in a + False + >>> s = 'istanbul' + >>> 'i' in s + True + >>> 'k' in s + False +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 6, 1, 34, 51, 23, 10] + +result = 34 in a # 34 değeri a içinde var mı? +print(result) # True + +result = 'ali' in a # 'ali' değeri a içinde var mı? +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + Biz bir liste içerisinde başka bir liste var mı diye in operatörü ile bakabiliriz. in operatörü == karşıalştırması yapmaktadır. + İki listenin bu biçimde karşılaştırılması ileride ele alınacaktır. Örneğin: + + >>> a = [1, 2, [3, 4], 5, 6] + >>> [3, 4] in a + True + >>> [4, 3] in a + +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 6, 1, [34, 51], 23, 10] + +result = 34 in a +print(result) # False + +result = [34, 51] in a +print(result) # True + +result = [51, 34] in a +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + not in operatörü ise bir elemanın bir listede olmadığını sorgulamak için kullanılmaktadır. Yani in operatörünün tersini yapmaktadır. + Örneğin: + + >>> a = [1, 2, 3, 4, 5] + >>> 5 in a + True + >>> 5 not in a + False +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +result = 30 not in a +print(result) # False + +result = 15 not in a +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + Veri yapılarından eleman silmek için genel amaçlı "del" isiminde bir deyim de kullanılmaktadır. + del deyimi yalnızca listelerde değil "değiştirilebilir (mutable)" başka nesnelerde de kullanılabilmektedir. del deyimi + listelerle kullanılırken listenin belli bir elemanı köşeli parantezler ile belirtilir. Örneğin del a[3] gibi. Örneğin: + + >>> a = [10, 20, 30, 40, 50] + >>> del a[2] + >>> a + [10, 20, 40, 50] + + Bu örnekte biz aynı işlemi pop metoduyla da yapabilirdik. Yine biz del deyimi ile listenin olmayan bir elemnanını silmek istersek + del deyimi exception (IndexError) oluşturur. Örneğin: + + >>> a = [10, 20, 30, 40, 50] + >>> del a[30] + Traceback (most recent call last): + File "", line 1, in + IndexError: list assignment index out of range +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] +print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] + +del a[0] +print(a) # [3, 'veli', 45, 73, 'ali', 21, 16, 7] + +#------------------------------------------------------------------------------------------------------------------------ + del deyimi ile listelerde silme yapılırken silinecek elemanlar dilimleme ile de belirtilebilmektedir. Bu durumda önce dilimlenen + elemanlar belirlenir sonra hepsi tek hamlede silinir. Örneğin: + + >>> a = [10, 20, 30, 40, 50, 60, 70] + >>> del a[1:6:2] + >>> a + [10, 30, 50, 70] + +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] +print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] + +del a[::2] +print(a) # [3, 45, 'ali', 16] + +#------------------------------------------------------------------------------------------------------------------------ + del deyimi ile ',' atomuyla ayrılmış birden fazla silme işlemi yapılabilir. Bu durumda silme ayrı ayrı soldan sağa biçimde yapılmaktadır. + Örneğin: + + >>> a = [10, 20, 30, 40, 50, 60, 70] + >>> del a[2], a[3] + >>> a + [10, 20, 40, 60, 70] + + Burada önce 2'inci indeksli eleman silinmiştir. Bu durumda 3'üncü indeksli eleman artık başlangıçtaki 4'üncü indeksli eleman + durumuna gelmiştir. Halbuki dilimleme yoluyla eleman silerken önce elemanlar belirlenmekte sonra hepsi tek hamlede silinmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] +b = [4, 8, 2] +print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] + + +del a[3], b[0] +print(a) # ['ali', 3, 'veli', 73, 'ali', 21, 16, 7] +print(b) # [8, 2] + +#------------------------------------------------------------------------------------------------------------------------ + Aynı liste üzerinde silme yapılırken ilk silmeden sonra elemanların indeks numaralarının değişeceğine dikkat ediniz. + Yani: + + del a[i], a[k] + + işlemi aşağıdaki ile eşdeğerdir: + + del a[i] + del a[k] +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] + +print(a) # ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7] +del a[0], a[1] +print(a) # [3, 45, 73, 'ali', 21, 16, 7] + +#------------------------------------------------------------------------------------------------------------------------ + Python'da iki liste nesnesi + operatörü ile toplanabilir. Ancak çıkartılamaz, çarpılamaz ve bölünemez. İki liste toplandığında + bu işlemden yeni bir liste edilmektedir. Bu yeni listenin elemanları + operatörünün solundaki liste elemanlarına sağındaki liste elemanlarının + eklenmesiyle oluşturulmuş bir liste olur. a + b işleminde önce len(a) + len(b) kadar uzunlukta yeni bir liste yaratılır. Bu listeye + önce a listesindeki adresler, sonra b listesindeki adresler eklenir. Böylece c listesi aslında a listesindeki adreslerden ve + b listesindeki adreslerden oluşuyor durumda olur. Bu da bir çeşit sığ kopyalama anlamına gelmekteir. + + Aşağıdaki örnekte aslında id(b[2]) ile id(c[5]) aynı değerleri vermektedir. Çünkü bunlar aslında aynı nesneyi gösterirler. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 'ali', 20] +b = [30, 'veli', 40] + +c = a + b +print(c) # [10, 'ali', 20, 30, 'veli', 40] + +print(id(b[2])) +print(id(c[5])) # b[2] ile c[5] aynı nesnesyi gösteriyorlar + +#------------------------------------------------------------------------------------------------------------------------ + İki list nesnesi toplanırken oluşturulan liste toplanan iki list nesnesinin tuttukları adreslerden oluştuğuna göre list nesnesinin + bir elemanu "değiştirilebilir (mutable)" bir nesne ise orada yapılan değişiklik dolaylı biçimde toplama işlemiyle elde edilen nesnede de + gözükecektir. Aşağıdaki örneği izleyiniz: + + >>> a = [1, 2, [3, 4]] + >>> b = [10, 20] + >>> c = a + b + >>> c + [1, 2, [3, 4], 10, 20] + >>> a[2][0] = 100 + >>> c + [1, 2, [100, 4], 10, 20] + + Aşağıda da benzer bir örnek verilmiştir. Burada b listesinin 1'inci indisli elemanı başka bir listenin adresini tutmaktadır. + Toplama sonucunda c listesinin 4'üncü indisli elemanı da aynı listeyi gösteriyor durumda olur. Listeler değiştirilebilir (mutable) + olduğuna göre biz hem b hem de c tarafından gösterilen bu listeyi her iki liste yoluyla değiştirebiliriz. Örneğin: +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] +b = [40, [50, 60], 70] + +c = a + b +print(c) # [10, 20, 30, 40, [50, 60], 70] + +c[4][0] = 100 +print(b) # [40, [100, 60], 70] + +#------------------------------------------------------------------------------------------------------------------------ + İki listeyi toplarken yeni oluşturulan liste her zaman iki listenin uzunlukları toplamı kadar uzunluğa sahip olur. +#------------------------------------------------------------------------------------------------------------------------ + +a = [[10, 20, 30]] +b = [[40, 50, 60]] + +c = a + b + +print(c) # [[10, 20, 30], [40, 50, 60]] + +#------------------------------------------------------------------------------------------------------------------------ + İki listenin toplanması durumunda listelerden biri ya da her ikisi boş liste olsa bile yine de toplama işlemi sonucunda + yeni bir liste yaratılmaktadır. Tabii genel olarak bir programda "gözlemlenebilir bir yan etki (observable side effects)" oluşmadıktan + sonra derleyiciler ve yorumlayıcılar kodu daha hızlı çalışacak biçimde ya da daha az yer kaplayacak biçimde yeniden düzenleyebilirler. + Yani aşağıdaki örnekte aslında yorumlayıcı bu toplama işlemini hiç yapmayabilir. Çünkü yorumlayıcının bu toplama işlemini yapıp + yapmadığı program içerisinde anlaşılamamaktadır. Başka bir deyişle yorumlayıcı bu toplama işlemini yapsa da yapmasa da biz + bunun yapılıp yapılmadığını gözlemleyemeyiz: + + c = [10, 20, 30] + [] + + print(c) + + Benzer biçimde aşağıdaki örnekte de yorumlayıcı önce iki listeyi oluşturup sonra onları toplamak yerine doğrudan + toplama ilişkin listeyi oluşturabilir. Yorumlayıcı bunu yaptiığında bzim kodumuz bundan hiçbir biçimde etkilenmeyecektir: + + a = [1, 2, 3] + [4, 5] + + Burada belki de yorumlayıcı doğurdan [1, 2, 3, 4, 5] elemanlarına sahip bir liste oluşturup bu listenin adresini a'ya + atamıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi a += b işlemi a = a + b anlamına gelmektedir. Ancak listelerde bu eşdeğerlik söz konusu değildir. a ve b birer liste + olmak üzere a += b aslında b listesinin lemanlarının mevcut a listesinin sonuna eklenmesi anlamına gelmektedir. Dolayısıyla a += b aslında + listelerde adeta a.extend(b) gibi bir etki yaratmaktadır. Örneğin: + + >>> a = [1, 2, 3] + >>> b = [4, 5] + >>> id(a) + 1570379741824 + >>> a = a + b + >>> a + [1, 2, 3, 4, 5] + >>> id(a) + 1570379772992 + >>> a = [1, 2, 3] + >>> b = [3, 4] + >>> id(a) + 1570379782464 + >>> a += b + >>> a + [1, 2, 3, 3, 4] + >>> id(a) + 1570379782464 + + Burada listeler için a = a + b işlemi ile a += b işleminin aynı olmadığını görmekteyiz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] +b = [40, 50] + +print(id(a)) +a = a + b +print(a) +print(id(a)) # a'nın id'si değişiyor, çünkü a artık yeni bir listeyi gösteriyor + +a = [10, 20, 30] +print(id(a)) + +a += b +print(a) +print(id(a)) # a'nın id'si değişmiyor. Çünkü ekleme a'ya yapılıyor + +#------------------------------------------------------------------------------------------------------------------------ + Python dokümanlarına göre (Python Library Reference) a list türünden olmak üzere a.extend(b) ile a += b tamamen eşdeğerdir. + extend metodunda metodun parametresinin herhangi bir dolaşılabilir nesne olabileceğini belirtmiştik. Aynı durum a += b operatöründe de + geçerlidir. Bu işlemde a list türündense b aslında dolaşılabilir herhangi bir nesne belirtebilir. Örneğin: + + >>> a = [1, 2, 3] + >>> a.extend('ali') + >>> a + [1, 2, 3, 'a', 'l', 'i'] + >>> a = [1, 2, 3] + >>> a += 'ali' + >>> a + [1, 2, 3, 'a', 'l', 'i'] + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir liste int bir değerle çarpılabilir. (İki liste çarpılamaz, bölünemez ve çıkartılamaz. Ayrıca bir liste int bir toplanamaz ve bölünemez.) + Bu işleme Python'da "yineleme (repitition)" denilmektedir. a bir liste, n de int bir değer belirtmek üzere a * n ya da n * a tamamen + n defa a yı kendisiyle toplamaya eşdeğerdir. Örneğin a * 3 tamamen a + a + a anlamına gelmektedir. Yani a içerisindeki değerler üç kere yinelenmektedir. + Listeler toplanırken oluşturulan yeni listeye operand olarak kullanılan listelerdekş adreslerin kopyalandığını anımsayınız. Örneğin: + + >>> a = [1, 2, 3] + >>> b = a * 2 + >>> a + [1, 2, 3] + >>> b + [1, 2, 3, 1, 2, 3] + >>> id(a[0]) + 1570340235568 + >>> id(b[0]) + 1570340235568 + >>> id(b[3]) + 1570340235568 + >>> b = a + a + >>> b + [1, 2, 3, 1, 2, 3] + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] + +b = a * 3 # eşdeğeri a + a + a +print(b) # [10, 20, 30, 10, 20, 30, 10, 20, 30] + +b = 3 * a +print(b) # [10, 20, 30, 10, 20, 30, 10, 20, 30] + +b = [0] * 10 +print(b) # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + +#------------------------------------------------------------------------------------------------------------------------ + a * 3 gibi bir ifade a + a + a anlamına geldiğine göre aslında burada yeni yaratılacak listenin elemanları tekrarlı bir biçimde + a listesinin elemanlarındaki adresleri tutacaktır. Yani yineleme işlemi aslında yinelen listenin (örneğimzde a) adreslerinin yinelenmesiyle + oluşturulmaktadır. Sonuçta bir "sığ kopyalama (shallow copy)" durumu oluşur. Yani yineleme liste elemanlarının gösterdiği nesnelerin kopyasını oluşturmamaktadır. Örneğin: + + a = [10, 20] + b = a * 3 + + gibi bir işlemde b listesinin elemanları üç kere a listesinin elemanlarındaki adreslerden oluşmaktadır. Örneğin: + + >>> a = [10, 20] + >>> b = a * 3 + >>> id(a[0]) + 1672940513872 + >>> id(a[1]) + 1672940514192 + >>> id(b[0]) + 1672940513872 + >>> id(b[1]) + 1672940514192 + >>> id(b[2]) + 1672940513872 + >>> id(b[3]) + 1672940514192 + >>> id(b[4]) + 1672940513872 + >>> id(b[5]) + 1672940514192 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yineleme eişlemi bazı durumlarda çok pratik olanaklar sağlamaktadır. Örneğin biz her elemanı 0 olan 100 elemanlık bir listeyi + kolay bir biçimde yineleme ile oluşturabiliriz: + + >>> a = [0] * 100 + + Burada a 100 elemanlı bir listedir ve bu listenin her elemanında 0 vardır. Tabii aslında a listesinin her elemanı içerisinde + 0 olan aynı int nesnenin adresini tutuyor durumdadır: + + >>> id(a[0]) + 1570340235536 + >>> id(a[1]) + 1570340235536 + >>> id(a[99]) + 1570340235536 + + Bu durum bir sorun oluşturmaz. Nasıl olsa int "değiştirilemez (immutable)" bir türdür. Biz listeninin bir elemanına değer + atadığımızda diğer elemanların değeri değişmeyecektir. Örneğin: + + >>> a[0] = 100 + >>> id(a[0]) + 1570340427216 + >>> id(a[1]) + 1570340235536 + >>> id(a[2]) + 1570340235536 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yineleme işleminde çarpılan değer 0 ya da negatif bir değerse yineleme işleminden boş bir liste elde edilmektedir. Örneğin: + + >>> a = [1, 2, 3] + >>> a * 0 + [] + >>> a * -3 + [] +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz Python'da ne zaman bir köşeli parantez açsak yeni bir liste yaratılmaktadır. Örneğin: + + a = [[1, 2], [1, 2], [1, 2]] + + Burada a listesinin elemanları farklı listeleri göstermektedir: + + >>> a = [[1, 2], [1, 2], [1, 2]] + >>> id(a[0]) + 1570379740736 + >>> id(a[1]) + 1570379741760 + >>> id(a[2]) + 1570379739776 + + Ancak aşağıdaki gibi bir listenin her elemanının aynı listeyi göstermesini de saplayabiliriz: + + a = [1, 2] + b = [a, a, a] + + Burada b listesinin her elemanı a'yı göstermektedir: + + >>> a = [1, 2] + >>> b = [a, a, a] + >>> id(b[0]) + 1570379735488 + >>> id(b[1]) + 1570379735488 + >>> id(b[2]) + 1570379735488 + + O halde burada biz a listesi üzerinde değişilik yaparsak bu durumda b'nin elemanları aynı a listesini gösterdiğine göre sanki + onlar üzerinde de eğişiklik yapılmış gibi bir etki oluşacaktır. Örneğin: + + >>> a[1] = 100 + >>> b + [[1, 100], [1, 100], [1, 100]] + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki gibi boş bir listemiz olsun: + + a = [] + + Şimdi biz aşağıdaki gibi bir liste oluşturalım: + + b = [a, a, a] + + Burada b'nin ilk üç elemanına aslında a listesinin adresi yerleştirilmiştir. Dolayısıyla aslında b listesinin elemanlarının hepsi + aynı a listesini göstermektedir. Şimdi aşağıdaki gibi bir işlem uygulayalım: + + b[0].append(100) + + biz aslında bu 100 değerini a listesine eklemiş olduk o zaman b'yi yazdırdığımızda şöyle bir görüntü ile karşılaşırız: + + [[100], [100], [100]] + + Yani biz bir listenin elemanına bir değer yerleştirdiğimzde aslında o değeri liste elemanına yerleştirmeyiz. O elemanın belirttiği nesnenin + adresini liste elemanına yerleştirmiş oluruz. + + >>> a = [] + >>> id(a) + 1672976339968 + >>> b = [a, a, a] + >>> id(b[0]) + 1672976339968 + >>> id(b[1]) + 1672976339968 + >>> id(b[2]) + 1672976339968 + >>> b[0].append(100) + >>> a + [100] + >>> b + [[100], [100], [100]] + >>> a.append(200) + >>> b + [[100, 200], [100, 200], [100, 200]] + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki koda dikkat ediniz: + + a = [[], [], []] + + Burada her köşeli parantez yeni bir listenin yaratılmasına yol açtığı için a'nın elemanları farklı boş listeleri gösterecektir. + Ancak örneğin: + + a = [[]] * 3 + + Burada yinelemeden dolayı oluşturulan a listesinin elemanları aynı boş listeyi gösterecektir. Bunu ispatlayalım: + + >>> a = [[], [], []] + >>> id(a[0]) + 1570379711424 + >>> id(a[1]) + 1570379736832 + >>> id(a[2]) + 1570379738880 + >>> a = [[]] * 3 + >>> id(a[0]) + 1570379700800 + >>> id(a[1]) + 1570379700800 + >>> id(a[2]) + 1570379700800 + +#------------------------------------------------------------------------------------------------------------------------ + +a = [[]] +b = a * 3 +print(b) + +b[0].append(100) +print(b) # [[100], [100], [100]] + +b[1].append(200) +print(b) # [[100, 200], [100, 200], [100, 200]] + +#------------------------------------------------------------------------------------------------------------------------ + * operatörü soldan sağa öncelikli olduğuna göre a bir liste olmak üzere aşağıdaki ifade de geçerlidir: + + b = a * 2 * 3 + + Burada önce a * 2 işlemi yapılacak buradan bir liste elde edilecek sonra elde edilen bu liste 3 ile çarpılacaktır. Yani sonuçta + a'nın elemanlarından 6 kez oluşturulmuş olacaktır. Örneğin: + + >>> a = [1, 2, 3] + >>> b = a * 2 * 3 + >>> b + [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] + + Dolayısıyla Python'da çarpmanın değişme özelliği ve birleşme özelliği muhafaza edilmiştir. Örneğin bu işlem aşağıdakiyl eşdeğerdir: + + b = a * (2 * 3) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listelerde a = a * n ile a *= n işlemi de aynı anlama gelmemektedir. a = a * n işleminde a * n ile yeni bir liste yaratılır, + a artık bu yeni listeyi gösterir. Halbuki a *= n işleminde (n - 1) tane a, a'nın sonuna eklenmektedir. Örneğin: + + >>> a = [1, 2, 3] + >>> id(a) + 1570379775488 + >>> a = a * 2 + >>> a + [1, 2, 3, 1, 2, 3] + >>> id(a) + 1570379885120 + >>> a = [1, 2, 3] + >>> id(a) + 1570379931200 + >>> a *= 2 + >>> id(a) + 1570379931200 + >>> a + [1, 2, 3, 1, 2, 3] +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] + +print(a, id(a)) +a = a * 3 +print(a, id(a)) # a'nın adresi değişiyor, a artık a * 3 ile yaratılan listeyi gösteriyor + +a = [10, 20, 30] + +print(a, id(a)) +a *= 3 +print(a, id(a)) # a'nın adresi değişmiyor, ekleme a'nın sonuna yapılıyor + +#------------------------------------------------------------------------------------------------------------------------ + Demetler (tuples) listelere benzeyen önemli diğer bir veri yapısıdır. Bir demet normal parantezler kullanılarak yaratılabilir. + Örneğin: + + t = (10, 20, 30) + + Demetler tuple isimli sınıfla temsil edilmektedir. ("tuple" sözcüğü "tyupıl" gibi de "tapl" gibi de okunabilmektedir.) +#------------------------------------------------------------------------------------------------------------------------ + +t = (10, 20, 30) +print(t) # (10, 20, 30) + +print(type(t)) # + +#------------------------------------------------------------------------------------------------------------------------ + Bir demet tuple sınıfının tür fonksiyonu olan tuple fonksiyonu ile de yaratılabilir. tuple fonksiyonuna argüman girilmezse + boş bir demet yaratılır. Eğer tuple fonksiyonuna dolaşılabilir bir nesne argüman olarak verilirse tuple fonksiyonu bu nesneyi dolaşarak + onun elemanlarından demet oluşturmaktadır. Örneğin: + + >>> t = tuple() + >>> t + () + >>> a = [1, 2, 3, 4, 5] + >>> t = tuple(a) + >>> t + (1, 2, 3, 4, 5) + >>> t = tuple('ankara') + >>> t + ('a', 'n', 'k', 'a', 'r', 'a') + +#------------------------------------------------------------------------------------------------------------------------ + +t = tuple() +print(t) # () + +t = tuple('ankara') +print(t) # ('a', 'n', 'k', 'a', 'r', 'a') + +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +t = tuple(a) +print(t) # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +#------------------------------------------------------------------------------------------------------------------------ + Demetler listelere pek çok bakımdan benzemektedir. Örneğin: + + - Demetlerin elemanlarına da [] operatörüyle erişilir. + - Demetlerde de elemana erişirken negatif indeksler listelerdeki gibi anlam taşır. + - Demetlerde de tamamen listelerde olduğu gibi dilimleme yapılabilir. Tabii dilimleme işleminden demet elde edilir. + - Demetler de dolaşılabilir (iterable) nesnelerdir. +#------------------------------------------------------------------------------------------------------------------------ + +t = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100) + +val = t[3] +print(val) # 40 + +val = t[-3] +print(val) # 80 + +result = t[1:6] +print(result) # (20, 30, 40, 50, 60) + +result = t[1:6:2] +print(result) # (20, 40, 60) + +result = t[::-1] +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Demetler dolaşılabilir nesnelerdir. Demetler dolaşıldığında sırasıyla demetin elemanları elde edilir. Örneğin: + + >>> t = (1, 2, 3, 4, 5) + >>> a = list(t) + >>> a + [1, 2, 3, 4, 5] +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Demetlerde de eleman uzunluğu built-in len fonksiyonuyla elde edilebilir. +#------------------------------------------------------------------------------------------------------------------------ + +t = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100) + +n = len(t) +print(n) # 10 + +#------------------------------------------------------------------------------------------------------------------------ + Boş bir demet içi boş bir parantezle oluşturulmaktadır. Örneğin: + + >>> t = () + >>> type(t) + + >>> len(t) + 0 + + Burada yorumlayıcı bu parantezleri öncelik parantezi olarak değerlendirmez. Ancak tek elemanlı demetleri oluştururken dikkat etmek gerekir. + Çünkü (10) gibi bir sentaks demet belirtmez. Buradaki parantezler öncelik parantezi anlamına gelir. Tek elemanlı demetleri oluştururken + parantez içerisine ekstra bir ',' eklemek gerekir. Örneğin: + + t = (10, ) + + Aslında listelerde ve demetlerde son elemandan sonra boş virgül bırakılabilmektedir: + + a = [10, 20, 30, ] # geçerli + print(len(a)) # 3 + + Tabii buradaki son ',' atomunun hiçbir işlevi yoktur. Ancak sentaks bakımından bu son ',' geçerlidir. Yani buradaki son ',' + boş bir eleman oluşturmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +t = (10) +print(t, type(t)) # 10 + +t = (10, ) +print(t, type(t)) # (10,) + +#------------------------------------------------------------------------------------------------------------------------ + Demetler de tamamen listelerde oluduğu gibi heterojen yani farklı türlerden elemanlara sahip olabilir. Örneğin: + + >>> t = (10, 'ali', True, 20.5) + >>> t + (10, 'ali', True, 20.5) +#------------------------------------------------------------------------------------------------------------------------ + +t = ('ali', 12, None, 'velli', 3.14, True) +print(t) # ('ali', 12, None, 'velli', 3.14, True) + +#------------------------------------------------------------------------------------------------------------------------ + Bir listenin elemanı bir demet, bir demetin elemanı da bir liste olabilir. Örneğin: + + a = [10, 'ali', ('veli', 20), True] + t = (10, 'veli', [20, 30], 40) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Demet elemanları da tıpkı listelerde olduğu gibi adreslerden oluşmaktadır. Yani bir demet elemanların kendisini değil + onların adreslerini tutmaktadır. Örneğin: + + t = ('ali', 12) + + Burada t demetinin ilk elemanı içerisinde 'ali' yazısının bulunduğu str nesnesinin adresini, ikinci elemanı içerisinde 12 değerinin + bulunduğu int nesnesinin adresini turmaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listelerle demetler arasındaki en önemli farklılı (aslında tek farklılık, diğer farklılıklar bu farklılığın bir sonucu) + listelerin "değiştirilebilir (mutable)", demetlerin ise "değiştirilemez (immuatable)" olmasıdır. Bir demet yaratıldıktan + sonra artık onun elemanları bir daha değiştirilemez. Örneğin: + + t = (10, 20, 30) + t[0] = 100 # geçersiz! exception oluşur + + Burada demetin bir elemanı değiştirilmeye çalışılmıştır. Demet nesnesi "değiştirilebilir (mutable)" değildir. Dolayısıyla bir demet + yaratıldıktan sonra biz onun herhangi bir elemanını değiştiremeyiz. (Yani onun herhangi bir elemanına başka bir nesnenin adresini + atayamayız.) + + tuple sınıfının da çeşitli metotları vardır. Ancak list sınıfında bulunan elemanlar üzerinde değişiklik yapan metotlara tuple + sınıfı sahip değildir. Bir demet nesnesi bir kez yaratılır. Sonra onun üzerinde değişiklik yapılamaz. Ona eleman da eklenemez, ondan + eleman da silinemez. + + Bu nedenle demetlerde listelerde bulunan aşağıdaki işlemler ve metotlar yoktur: + + - Sona eleman ekleme işlemi (append metodu) demetlerde yoktur. + - Sona dolaşılabilir nesnenin elemanlarını ekleme işlemi (extend) demetlerde yoktur. + - Belli bir elemanın silinmesi işlemi (pop ve remove) demetlerde yoktur. + - Tüm elemanların silinmesi işlemi (clear) demetlerde yoktur. + - Araya eleman ekleme işlemi (insert) demetlerde yoktur. + - In-place Sıraya dizme işlemi (sort metodu) demetlerde yoktur. + - In-place ters yüz etme işlemi (reverse) demetlerde yoktur. + - Demetlerde dilimleme yoluyla atama da yapılamamaktadır. + + Ancak demetlerde eleman değişikliği yapmayan index ve count metotları bulunmaktadır. Örneğin: + + >>> t = (10, 'ali', 20, 'veli', 10) + >>> t.index(20) + 2 + >>> t.count(10) + 2 + +#------------------------------------------------------------------------------------------------------------------------ + +t = (10, 20, 'ali', 20, 40, 'veli') + +result = t.index('veli') +print(result) # 5 + +result = t.count(20) +print(result) # 2 + +#------------------------------------------------------------------------------------------------------------------------ + Tabii demetlerin elemanları da aslında nesnelerin adreslerini tutmaktadır. Bu konuda listelerle bir farklı yoktur. + + >>> t = (10, 'ali', 20) + >>> id(t) + 1672976258432 + >>> id(t[0]) + 1672940513872 + >>> id(t[1]) + 1672976339248 + >>> id(t[2]) + 1672940514192 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir grup bilgiden elde edilen onu temsil eden kısa bir bilgiye hash denilmektedir. Bir grup bilgiden bir hash + değerinin elde edilmesi çeşitli amaçlarla kullanılabilmektedir. Örneğin hash değerleri bazı veri yapıları için + anahtar olarak kullanılırlar. Yine hash değerleri bozulmayı tespit etmek için de bazı bilgi güvenliği ve şifreleme gibi + bazı aalanlarda kullanılmaktadır. + + Python'da bazı türler "hash'lenebilir (hashable)" iken bazı türler "hash'lenemez (unshable)" biçimdedir. Genel olarak değiştirilebilir türler + hash'lenebilir değildir. Bu nedenle listeler elemanları ne olursa olsun hash'lenebilir değildir. Öte yandan demetler değiştirilemez + türler olduğu için hash'lenebilir türlerdir. Ancak her demet nesnesi de hash'lenebilir değildir. Bir demetin hash'lenebilir olması + için onun bütün elemanlarının hash'lenebilir olması gerekir. int, float, str, bool, complex, None türleri hash'lenebilirdir. Bu durumda + örneğin bir demetin bir elemanı bir liste ise o demet hash'lenebilir olmaz. Ancak listeler hiçbir durumda "hash'lenebilir" değildir. + + Bir nesnenin içerisindeki değerin hash değeri hash isimli built-in fonksiyonla elde edilebilir. Örneğin: + + >>> a = 'ali' + >>> hash(a) + 3186741938407899844 + >>> t = (1, 2, 3, 4) + >>> hash(t) + 590899387183067792 + + Aşağıdaki demet hash'lenebilir değildir: + + t = (1, 2, [3, 4], 5) + + Çünkü bu demet bir liste elemanına sahiptir. Bu liste de hash'lenebilir değildir: + + >>> t = (1, 2, [3, 4], 5) + >>> hash(t) + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'list' + + Aşağıdaki demet hash'lenebilirdir: + + >>> t = (1, 2, (3, 4), 5) + >>> hash(t) + -2963430713186267524 + + Aşağıdaki demet de hash'lenebilir değildir: + + >>> t = (1, 2, (3, 4, [5, 6]), 7) + >>> hash(t) + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'list' + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Demetin bir elemanı bir liste olsun: + + t = (1, [2, 3], 4) + + Biz şimdi bu listenin elemanlarını değiştirebiliriz: + + t[1][1] = 100 + + Burada bu demeti yazdırdığımızda şöyle bir sonuç göreceğiz: + + (1, [2, 100], 4) + + Aslında bu durum demetin değiştirildiği anlamına gelmemektedir. Çünkü bu işlemle biz aslında demetin elemanlarında değişiklik + yapmış olmuyoruz. Yani demetin elemanları yine aynı adresleri tutmaktadır. Biz burada değişikliği demetin elemanı olan liste üzerinde + yapmış oluyoruz. Bu durum yanlış yorumlanmamalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +t = (1, [2, 3], 4) +print(t) # (1, [2, 3], 4) + +t[1][1] = 100 +print(t) # (1, [2, 100], 4) + +#------------------------------------------------------------------------------------------------------------------------ + 15. Ders - 30/05/2021-Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Demetler oluşturulurken bazı zorunlu durumlar dışında parantezler kullanılmayabilir. Örneğin: + + t = (1, 2, 3) + + ile, + + t = 1, 2, 3 + + aynı anlamdadır. Örneğin: + + t = 10, + + ile, + + t = (10, ) + + aynı anlamdadır. Demet oluştururken pek çok durumda parantez gerekmediğine göre biz de örneklerimizde bazen parantezleri + kullanacağız bazen de kullanmayacağız. + +#------------------------------------------------------------------------------------------------------------------------ + +t = 10, 20, 30 + +print(t) + +t = 10, [20, 30, 40], 50 +print(t) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii bazı durumlarda demeti yaratırken parantezleri mecburen kullanırız. Örneğin listenin ya da demetin bir elemanı demet olacaksa + mecburen demet parantezleri kullanılmak zorundadır. + + a = [10, (20, 30, 40), 50] + print(a) + + t = 10, (30, 40, 50), 50 + print(t) + + Örneğin bir fonksiyona argüman olarak bir demet göndermek istiyorsak yine mecburen demet parantezini kullanmak zorundayız: + + print(10, 20) + print((10, 20)) + + Boş bir demet oluştururken boş parantezlerin kullanılması ya da tuple sınıfının tür fonksiyonun kullanılması gerekir. Örneğin: + + t = () + print(t) + + t = tuple() + print(t) + + Yani aşağıdaki gibi bir sentaks geçerli değildir: + + t = , + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tek elemanlı bir demeti parantezsiz de oluşturabiliriz. Tabii bu durumda değerden sonra bir ',' atomu gerekir. Örneğin: + + t = 10, + print(t) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İki demet + operatörü ile toplanabilir. Toplama işleminden yeni bir demet nesnesi elde edilir. Bu yeni demet nesnesi + iki demet nesnesinin elemanlarının uç uca eklenmesinden oluşmaktadır. Yine tıpkı listelerde olduğu gibi yeni yaratılan + demete bu demetlerin içerisindeki adresler kopyalanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = (10, 20, 30) +b = (40, 50, 60) + +c = a + b +print(c) + +print(id(c[3]), id(b[0])) # aynı adresler + +#------------------------------------------------------------------------------------------------------------------------ + Listelerde a = a + b ile a += b farklı anlamlara geliyordu. Ancak demetlerde bu iki ifade tamamen aynı anlama gelmektedir. + Çünkü demetlerde sona ekleme biçiminde bir işlem yoktur. Örneğin: + + >>> a = 1, 2, 3 + >>> id(a) + 1486505308992 + >>> b = 4, 5, 6 + >>> a += b + >>> id(a) + 1486504943872 + >>> a + (1, 2, 3, 4, 5, 6) +#------------------------------------------------------------------------------------------------------------------------ + +a = 10, 20, 30 +b = 40, 50 + +print(a, id(a)) +a = a + b +print(a, id(a)) + +a = 10, 20, 30 +print(a, id(a)) +a += b +print(a, id(a)) # id farklı çıkacak, çünkü demetlerde sona ekleme mümkün değil + +#------------------------------------------------------------------------------------------------------------------------ + Demetlerde de yineleme (repitition) işlemi yapılabilmektedir. Yani bir demet int bir değerle çarpılabilir. Bu durumda yine bir + demet nesnesi yaratılır. Bu demet nesnesi çarpılan demetin elemanlarının yinelenmesinden oluşur. Tabii iki demeti çarpamayız, + çıkartamayız, bölemeyiz. Yine yineleme işleminde çarpılan değer 0 ya da negatif bir değerse boş bir demet elde edilmektedir. + Operand'lar yine çarpma işlemimde yer değiştirebilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = (10, 20, 30) + +b = 3 * a # a * 3 de yazılabilirdi + +print(b) + +#------------------------------------------------------------------------------------------------------------------------ + Listelerde a = a * n ile a *= n farklı anlamlara geliyordu. Ancak demetlerde sona ekleme işlemi olmadığı için bu iki ifade + de tamamen aynı anlama gelmektedir. Örneğin: + + >>> a = 1, 2, 3 + >>> id(a) + 1486505308992 + >>> a *= 2 + >>> id(a) + 1486504943872 + >>> a + (1, 2, 3, 1, 2, 3) +#------------------------------------------------------------------------------------------------------------------------ + +a = 10, 20, 30 + +print(a, id(a)) +a = a * 3 +print(a, id(a)) + +a = 10, 20, 30 +print(a, id(a)) +a *= 3 +print(a, id(a)) # id farklı çıkacak, çünkü demetlerde sona ekleme mümkün değil + +#------------------------------------------------------------------------------------------------------------------------ + Demetlerle listeler kullanım bakımından birbirine benzemektedir. Pekiyi ne zaman listeleri ne zaman demetleri tercih + etmeliyiz? + + 1) Eğer veri yapısı üzerinde onu yarattıktan sonra ekleme, silme gibi değişikler yapılacaksa mecburen listeleri kullanırız. + Çünkü demetlerde yaratıldıktan sonra değişiklik yapılamamaktadır. + + 2) Liste türünden nesneler demet türünden nesnelere göre daha fazla yer kaplama eğilimindedir. Çünkü onların değiştirilebilir, + büyütülebilir ve küçültülebilir olmaları nedeniyle nesne içerisinde bu işlemler için bazı bilgilerin bulundurulması gerekmektedir. + Bu nedenle eğer veri yapısı içerisindeki elemanlar üzerinde bir değişiklik yapmayacaksak, veri yapısına eleman ekleyip ondan bir şeyler + silmeyeceksek demetleri tercih etmeliyiz. + + 3) Demetler değiştirilebilir olmadığı için eğer onların elemanları hash'lenebilir (hashable) ise hashable nesnelerdir. Bu da onların + sözlük gibi, küme gibi veri yapılarında kullanılabilmesi anlamına gelir. Listeler hiçbir durumda hashable değildir. + + Her ne kadar veri yapısı üzerinde değişlik yapılmayacaksa liste yerine demetlerin kullanılması daha uygunsa da Python programcıları + yine de bu tür durumlarda demet yerine liste kullanabilmektedir. Çünkü pek çok uygulamada demetlerle listelerin kapladıkları yerin + farklı büyüklekte olmasının önemi yoktur. Bazen programcılar "değişikliğin yapılmasının istenmediğini vurgulamak" için de demet kullanabilmektedir. + Örneğin ileride de göreceğimiz gibi bir fonksiyonun birden fazla değeri çağıran fonksiyona iletmesi gerektiği durumda listeler yerine + demetler hem etkinlik bakımından hem de okunabilirlik bakımından daha uygun bir seçenektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir bir nesnenin elemanları "açım (unpacking)" denilen bir sentaksla değişkenlere atanabilmektedir. Açım (unpacking) + işlemi Ruby' Python gibi dillerde eskiden beri vardı. Daha sonraları C# gibi, Swift gibi dillere ve hatta C++'a bile bu özellik + eklenmiştir. Açım işlemi eskiden yalnızca demetler üzerinde yapılabiliyordu. Ancak daha sonra dolaşılabilir her nesne açılabilir + yapıldı. Dolayısıyla açım sentaksı da genelleştirildi. Açım için aşağıdaki üç sentaks tamamen aynı anlama gelmektedir: + + 1) (x, y, z, ...) = + 2) x, y, z, ... = + 3) [x, y, z ...] = + + Açım sırasında = operatörünün sağ tarafında "dolaşılabilir (iterable)" bir nesne bulunmak zorundadır. Açım sırasında bu + nesne dolaşılır, elde edilen değerler sol taraftaki değişkenlere sırasıyla atanır. Örneğin: + + t = 10, 20, 30 + + x, y, z = t + + bu işlem tamamen aşağıdaki eşdeğerdir: + + x = t[0] + y = t[1] + z = t[2] + + Örneğin: + + [x, y, z] = 'ali' + + bu işlem tamamen aşağıdaki ile eşdeğerdir: + + x = 'a' + y = 'l' + z = 'i' + + Açım işleminde sol taraftaki sentaksın bir önemi yoktur. Üç sentaks biçimi de tamamen birbirleriyle eşdeğerdir. +#------------------------------------------------------------------------------------------------------------------------ + +t = (10, 20, 30) + +(x, y, z) = t + +print(x, y, z) # 10 20 30 + +x, y, z = t + +print(x, y, z) # 10 20 30 + +[x, y, z] = t + +print(x, y, z) # 10 20 30 + +x, y, z = 'van' +print(x, y, z) # v a n + +#------------------------------------------------------------------------------------------------------------------------ + Açım (unpacking) yapılırken = operatörünün sağındaki dolaşılabilir nesnenin eleman sayısının açımdaki değişken sayısı + ile tamamen aynı olması gerekir. Aksi takdirde exception oluşmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +t = (10, 20, 30) + +x, y = t # geçersiz! exception oluşur + +x, y, z, k = t # geçersiz! exception oluşur + +#------------------------------------------------------------------------------------------------------------------------ + Açılacak dolaşılabilir nesnedeki elemanlardan bazıları liste, demet gibi başka bir dolaşılabilir nesne olabilir. Örneğin: + + >>> a, b, c = [10, [20, 30], 40] + >>> a + 10 + >>> b + [20, 30] + >>> c + 40 + >>> a, b, c = 100, 'ankara', 200 + >>> a + 100 + >>> b + 'ankara' + >>> c + 200 + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, (20, 30), 40] + +x, y, z = a + +print(x, y, z) # 10 (20, 30) 40 + +#------------------------------------------------------------------------------------------------------------------------ + Açım işlemi özyinelemeli (recursive) bir biçimde de yapılabilir. Yani açılan bir eleman dolaşılabilir bir nesne ise o da açılabilir. + Tabii bu durumda ek parantezler kullanılmalıdır. Ancak bu parantezlerin demet parantezi ya da liste parantezi olması arasında + bir farklılık yoktur. Örneğin: + + >>> a, (b, c), d = [10, [20, 30], 40] + >>> a + 10 + >>> b + 20 + >>> c + 30 + >>> d + 40 + >>> a, [b, c], d = [10, [20, 30], 40] + >>> a + 10 + >>> b + 20 + >>> c + 30 + >>> d + 40 + + Örneğin: + + >>> a, (b, (c, d, e), f), g = [10, [20, (30, 40, 50), 60], 70] + >>> a, b, c, d, e, f, g + (10, 20, 30, 40, 50, 60, 70) + + Tabii özyinelemenin sonuna kadar devam ettirilmesi gerekmez. Örneğin: + + >>> a, b, c = [10, [20, (30, 40, 50), 60], 70] + >>> a, (b, c, d), e = [10, [20, (30, 40, 50), 60], 70] + >>> print(a, b, c, d, e) + 10 20 (30, 40, 50) 60 70 + >>> a, (b, (c, d, e), f), g = [10, [20, (30, 40, 50), 60], 70] + >>> print(a, b, c, d, e, f, g) + 10 20 30 40 50 60 70 + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] + +x, y, z = a + +print(x, y, z) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii açım işlemi aslında bir çeşit atama işlemidir. Yani açım sonucunda değişkenlere aslında dolaşılabilir nesnenin elemanlarının + adresi yerleştirilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30] + +x, y, z = a + +print(id(a[0]), id(a[1]), id(a[2])) +print(id(x), id(y), id(z)) # id'ler aynı + +#------------------------------------------------------------------------------------------------------------------------ + Python'da iki değişkenin değerinin yer değiştirilmesi açım (unpacking) işlemi ile kolay bir biçimde yapılabilmektedir. + Örneğin: + + >>> a = 10 + >>> b = 20 + >>> a, b = b, a + >>> print(a, b) + 20 10 + +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +print(a, b) # 10 20 + +a, b = b, a + +print(a, b) # 20 10 + +#------------------------------------------------------------------------------------------------------------------------ + range isimli fonksiyon bize range isimli dolaşılabilir bir sınıf nesnesi vermektedir. range fonksiyonunun bize verdiği + dolaşılabilir nesne dolaşıldığında start değerinden başlayarak stop değerine kadar (start dahil stop dahil değil) + step miktarı artırımlarla int değerler elde edilmektedir. range fonksiyonu tek argümanlı, iki argümanlı ya da üç argümanlı + kullanılabilmektedir: + + range(stop) + range(start, stop) + range(start, stop, step) + + Tek argümanlı kullanımda dolaşım sırasında 0'dan argümanla belirtilen değere kadar int değerler elde edilir. + Örneğin: + + >>> r = range(10) + >>> a = list(r) + >>> a + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + Yani tek argümanlı kullanımsa aslında start değeri 0, step değeri 1 gibiymiş gibi işlem uygulanmaktadır. Bu durumda tek + argümanlı kullanımda biz aslında argüman olarak stop değerini vermiş oluruz. Dolaşım sırasında start değeri her zaman dolaşıma + dahildir ancak stop değeri dahil değildir. +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10) # start = 0, stop = 10, step = 1 + +a = list(r) +print(a) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +#------------------------------------------------------------------------------------------------------------------------ + range fonksiyonunu iki argümanla çağırırsak birinci argüman start değerini ikinci argüman stop değerini belirtir. + (start dahil stop dahil değildir.) Örneğin: + + >>> a = list(range(10, 20)) + >>> a + [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20) # start = 10, stop = 20, step = 1 + +a = list(r) +print(a) # [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + +#------------------------------------------------------------------------------------------------------------------------ + range fonksiyonu üç argümanla kullanıldığında birinci argüman start değerini, ikinci argüman stop değerini ve üçüncü + argüman da step değerini belirtir. Örneğin: + + >>> a = list(range(10, 20, 3)) + >>> a + [10, 13, 16, 19] + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20, 2) # start = 10, stop = 20, step = 2 + +a = list(r) +print(a) # [10, 12, 14, 16, 18] + +#------------------------------------------------------------------------------------------------------------------------ + range fonksiyonunun üç parametresi de int türden olmak zorundadır. Böylece range nesnesi dolaşıldığında nesne her zaman bize + int değerler verir. Bu durum range fonksiyonu ile noktalı artırımlar yapamayacağımız anlamına da gelmektedir. Örneğin: + + r = range(0, 1, 0.1) + + Böyle range nesnesi yaratmak istersek step değeri float olduğu için exception oluşacaktır. Yuvarlama hataları ile kararsız + durumların oluşmaması noktalı artırımlara, float türden start ve stop değerlerine izin verilmemiştir. Örneğin eğer noktalı + artırımlara izin verilseydi şöyle sorunlar ortaya çıkabilirdi: + + a = list(range(a, b, 0.1)) # bu durum geçersizdir, biz burada "geçerli olsaydı" durumunu değerlendiriyoruz + + Burada a değerinden başlanarak b değerine kadar 0.1 artırımlarla değerler elde edilmek istenmiştir. Elde edilen değerlere + b değeri dahil olmayacaktır. İşte eğer bir yuvarlama hatası olursa ve son değer b'ye çok yakın ama yuvarlama hatasından dolayı + b'den küçük olursa bu da dolaşılma dahil edilecektir. Bu durum da farklı uzunluklarda listelerin elde edilmesine yol açabilecektir. + + Fakat örneğin yaygın kullanılan "NumPy" isimli kütüphanede arange isimli noktalı artırım yapan bir dolaşılabilir sınıf vardır. + NumPy kütüphanesi "Python Uygulamaları" kursunda ayrıntılı biçimde ele alınmaktadır. Örneğin: + + >>> import numpy + >>> a = numpy.arange(0, 1, 0.1) + >>> a + array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]) + + NumPy sayısal analiz, istatistik, matematik ve makine öğrenmesinde çok yaygın kullanılan üçüncü parti bir kütüphanedir. + Python'ın standart kütüphanesi içerisinde değildir. Aşağıdaki gibi install edilebilir: + + pip install numpy + + Anaconda dağıtımı default olarak NumPy kütüphanesini barındırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + range fonksiyonunda step değeri negatif verilebilir. Bu durumda dolaşım sırasında büyükten küçüğe değerler elde edilir. + Tabii eğer step değeri negatif ise start değerinin stop değerinden daha büyük olması gerekir. Yine stop değeri dahil değildir. + Örneğin: + + >>> a = list(range(10, 0, -1)) + >>> a + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + >>> a = list(range(10, -1, -1)) + >>> a + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 0, -1) # start = 10, stop = 0, step = -1 + +a = list(r) +print(a) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + +r = range(10, -1, -1) # start = 10, stop = -1, step = -1 + +a = list(r) +print(a) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + +#------------------------------------------------------------------------------------------------------------------------ + 16. Ders - 01/06/2022-Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii range fonksiyonunun step parametresi 0 olamaz. Eğer bu parametre 0 girilirse exception (ValueError) oluşacaktır. + Örneğin: + + >>> r = range(0, 10, 0) + Traceback (most recent call last): + File "", line 1, in + ValueError: range() arg 3 must not be zero + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + range sınıfı Python Standart Kütüphanesinde bir "sequence container" olarak ele alınmıştır. "Sequence container" terimi Python'da + elemanlar arasında öncelik sonralık ilişkisi olan ve elemanlarına [...] operatörü ile erişilen veri yapılarına betimlemektedir. + + range sınıfı bir "sequence container" belirttiği için listeler ve demetler üzerinde yapılabilen bazı işlemler range nesneleri + üzerinde de yapılabilmektedir. Örneğin bir range nesnesi dilimlenebilir. Bu işlemden yeni bir range nesnesi elde edilir: + + >>> r = range(100) + >>> k = r[30:60:3] + >>> k + range(30, 60, 3) + + Burada biz r range nesnesini dilimleyerek ondan başka bir range nesnesi elde ettik. range nesnelerinin dilimlenmesine seyrek + bir biçimde gereksinim duyulmaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(100) + +k = r[30:60:3] +a = list(k) +print(a) # [30, 33, 36, 39, 42, 45, 48, 51, 54, 57] + +#------------------------------------------------------------------------------------------------------------------------ + range nesnesinin belli bir elemanına [...] operatör ile erişebiliriz. Ancak range nesnesinin elemanları değiştirilememektedir. + Örneğin: + + >>> r = range(100) + >>> r[23] + 23 + >>> r[23] = 100 + Traceback (most recent call last): + File "", line 1, in + TypeError: 'range' object does not support item assignment + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20) + +val = r[3] +print(val) # 13 + +#------------------------------------------------------------------------------------------------------------------------ + Listelerde ve demetlerde kullandığımız in ve not in operatörleri range nesnelerinde de kullanılabilmektedir. Örneğin: + + >>> r = range(30, 60, 3) + >>> 39 in r + True + >>> 40 in r + False + >>> 41 not in r + True + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20) + +result = 15 in r +print(result) # True + +result = 25 in r +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + Teknik olarak range sınıfı "değiştirilemez (immutable)" bir sınıftır. Dolayısıyla bu sınıfta append, extend, remove gibi + metotlar bulunmaz. Ancak index ve count metotları sınıfta bulunmaktadır. Örneğin: + + >>> r = range(100) + >>> r.count(67) + 1 + >>> r = range(30, 60, 3) + >>> r.index(39) + 3 + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20) + +result = r.index(15) +print(result) # 5 + +result = r.count(12) +print(result) # 1 + +#------------------------------------------------------------------------------------------------------------------------ + range nesnelerinin dolaşıldıkları takdirde kaç eleman verecekleri yine built-in len fonksiyonuyla elde edilebilir. + Örneğin: + + >>> r = range(30, 60, 3) + >>> len(r) + 10 + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20) + +result = len(r) +print(result) # 10 + +#------------------------------------------------------------------------------------------------------------------------ + range nesnesinde belirtilen start, stop ve step değerleri sınıfın "start", "stop" ve "step" örnek öznitelikleri (instance attibutes) + ile elde edilebilmektedir. Örneğin: + + >>> r = range(30, 60, 3) + >>> r.start + 30 + >>> r.stop + 60 + >>> r.step + 3 + +#------------------------------------------------------------------------------------------------------------------------ + +r = range(10, 20, 3) + +print(r.start, r.stop, r.step) # 10 20 3 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi "dolaşılabilir (iterable)" nesne her dolaşıldığında yeniden aynı değerleri verebilen nesnelerdi. + Oysa "dolaşım (iterator)" nesneleri bir kez dolaşıldığında biten, yani ikinci kez dolaşıldığında bittiği için artık + bir değer vermeyen nesnelerdir. Teknik olarak her dolaşım nesnesi dolaşılabilir bir nesne gibidir. Ancak dolaşım bitince + artık bir daha dolaşılsa da değer vermez. Özetle bir dolaşılabilir nesne dolaşıldığında bitiyorsa o nesneye özel olarak + "dolaşım (iterator)" nesnesi denilmektedir. + + range nesnei "dolaşılabilir bir nesnedir, dolaşım nesnesi değildir" . Dolayısıyla aynı range nesnesini her dolaştığımızda + aynı elemanlarını yeniden elde ederiz. Örneğin: + + >>> r = range(10) + >>> a = list(r) + >>> a + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> b = list(r) + >>> b + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir değeri doğrudan fonksiyona argüman olarak vermekle önce onu bir değişkene atayıp değişkeni argüman olarak vermek arasında + işlevsel bir farklılk yoktur. Örneğin: + + r = range(10) + a = list(r) + + Biz bu işlemi kısaca şöyle de yapabilirdik: + + a = list(range(10)) + + Bu durumda yine bir range nesnesi yaratılacak ve o nesne (yani onun adresi) list fonksiyonuna geçirilecektir. Örneğin: + + a = list((1, 2, 3, 5)) + + Burada da önce bir demet nesnesi yaratılacak o demet nesnesi list fonksiyonuna geçirilecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Matematikte farklı (distinct) elemanların oluşturduğu topluluğa küme (set) denilmektedir. Python'da da bu matematiksel anlamı + destekleyecek biçimde bir küme veri yapısı bulunmaktadır. + + Bir küme küme parantezleri içerisinde eleman listesi girilerek oluşturulmaktadır. Örneğin: + + >>> s = {'ali', 'ankara', 100, 3.4} + >>> s + {'ali', 'ankara', 3.4, 100} + + Kümeler set isimli sınıfla temsil edilmiştir. Kğme türünden bir değişkene type fonksiyonunu uyguladığımızda onun set + isimli bir sınıf türünden olduğunu görürüz. Örneğin: + + >>> type(s) + + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, True, 30.2} + +print(s, type(s)) # {True, 'ali', 20, 10, 30.2} + +#------------------------------------------------------------------------------------------------------------------------ + Kümelerde elemanlar arasında öncelik sonralık ilişkisi yoktur. Yani kümelerde bir sıra yoktur. (Matematikteki kümelerde de + bir sıra kavramı yktur). Dolayısıyla biz kümenin elemanlarına [...] operatörü ile erişemeyiz. Küme elemanlarını [...] operatörü ile + dilimleyemeyiz. Bir küme print fonksiyonu ile yazdırıldığında onun elemanlarının hangi sırada yazdırıldığının bir önemi yoktur. + Örneğin: + + >>> s = {'ali', 'ankara', 100, 3.4} + >>> print(s) + {'ali', 'ankara', 3.4, 100} + >>> s + {'ali', 'ankara', 3.4, 100} + >>> s[1] + Traceback (most recent call last): + File "", line 1, in + TypeError: 'set' object is not subscriptable + + Küme elemanlarında bir sıra olmadığı için küme elemanlarını yazdırdığımızdaki sıranın da bir önemi yoktur. Bir kümeyi + yazdırırken elemanların bizim girdiğimiz sırada yazdırılması gerekmemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kümelerde [...] operatörü kullanılmadığı için kümeler üzerinde dilimleme (slicing) işlemleri de yapılamamaktadır. Örneğin: + + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> s[2:5] + Traceback (most recent call last): + File "", line 1, in + TypeError: 'set' object is not subscriptable +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kümelerin eleman sayısı built-in len fonksiyonuyla elde edilebilmektedir. Örneğin: + + >>> s = {'ali', 'ankara', 'veli', 123} + >>> len(s) + 4 +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, True, 30.2} + +result = len(s) +print(result) # 5 + +#------------------------------------------------------------------------------------------------------------------------ + Bir kümeye zaten onda var olan bir elemanı eklemeye çalışmak exception oluşturmaz. Eleman ekleneceği zaman elaman zaten varsa + eklenmez. Dolayısıyla aşağıdaki küme oluşturma işlemi tamamen geçerlidir: + + a = {10, 20, 10, 30, 20} + + Burada 10 elemanı ve 20 elemanı olduğu halde yine küme parantezlerinin içerisinde bulunmaktadır. Bu durum bir exception oluşturmaz. + Örneğin: + + >>> s = {1, 2, 1, 1, 3, 4, 1, 5, 2, 3} + >>> print(s) + {1, 2, 3, 4, 5} + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, 'ali', 10} + +print(s) # {10, 'ali', 20} + +#------------------------------------------------------------------------------------------------------------------------ + Boş bir küme söz konusu olabilir. Ancak boş bir küme {} biçiminde oluşturulmaz. Buradaki {} ifadesi boş bir küme değil + boş bir sözlük (dicitonary) oluşturmaktadır. Sözlükler konusu izleyen bölümde ele alınacaktır. + + Sözlüklerle kğmeler benzer sentakslarla oluşturulmaktadır. Ancak sözlüklere kümelerden daha fazla gereksinim duyulmaktadır. + Bu nedenle boş küme parantezinin boş küme değil boş sözlük belirtmesinin daha anlamlı olduğu düşünülmüştür. Örneğin: + + >>> d = {} + >>> type(d) + + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kümeler set isimli sınıfla temsil edilmektedir. Boş bir küme set fonksiyonunun argümansız çağrılmasıyla oluşturulabilmektedir. + Örneğin: + + >>> s = set() + >>> type(s) + + >>> s + set() + >>> len(s) + 0 + +#------------------------------------------------------------------------------------------------------------------------ + +a = {} + +print(a, type(a)) # {} + +s = set() +print(s, type(s)) # set() + +#------------------------------------------------------------------------------------------------------------------------ + set fonksiyonu da tıpkı list ve tuple fonksiyonlarında olduğu gibi argüman olarak bizden dolaşılabilir bir nesne alıp + o nesneyi dolaşarak o değerlerden bir küme oluşturabilmektedir. Örneğin: + + >>> a = [1, 2, 1, 4, 8, 2, 5, 8, 1] + >>> s = set(a) + >>> s + {1, 2, 4, 5, 8} + + Örneğin: + + >>> s = set('ankara') + >>> s + {'n', 'a', 'k', 'r'} + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 'ali', 'veli', 20] +s = set(a) + +print(s) # {10, 'ali', 20, 'veli'} + +s = set(range(10)) +print(s) # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + +s = set('ankara') +print(s) # {'r', 'k', 'a', 'n'} + +#------------------------------------------------------------------------------------------------------------------------ + Kümeler yinelenenn elemanları atmak için iyi bir araç olabilmektedir. + + Aşağıdaki örnekte klavyeden girilen bir yazıdaki farklı karakterlerin sayısı yazdırılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +s = input('Bir yazı giriniz:') + +result = len(set(s)) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Kümeler de "dolaşılabilir (iterable)" nesnelerdir. Bir kümeyi dolaştığımızda onun elemanlarını elde ederiz. Ancak elemanların + hangi sırada elde edileceği hakkında bir belirleme yapılmamıştır. Yani biz kümeyi dolaştığımızda onun tüm elemanlarını elde ederiz + ancak sırası konusunda bir garanti verilmemektedir. Örneğin: + + >>> s = {'ali', 'ankara', 12, 5, 'istanbul'} + >>> a = list(s) + >>> a + ['istanbul', 'ali', 5, 'ankara', 12] + +#------------------------------------------------------------------------------------------------------------------------ + +s = {'ankara', 10, 20.5, False} +a = list(s) + +print(a) # listedeki sıra herhangi bir biçimde olabilir + +#------------------------------------------------------------------------------------------------------------------------ + Bir elemanın küme içerisinde olup olmadığının belirlenmesi için yine "in" ve "not in" operatörleri kullanılmaktadır. + Genel olarak kümeler gibi veri yapıları arka planda "hash tabloları (hash tables)" ya da "ikili ağaçlarla (binary trees)" denilen + algoritmik veri yapılarıyla gerçekleştirilmektedir. Örneğin CPython yorumlayıcısında kümeler algoritmik olarak "hash tabloları" + ile gerçekleştirilmiştir. Bu veri yapıları özellikle algoritmik aramalarda en çok tercih edilen veri yapılarındandır. + Bu nedenle kümelerde "var mı yok mu" testi listeler ve demetlere göre çok daha hızlı yapılabilmektedir. Çok sayıda elemandan + oluşan toplulukta in ve not in operatörleriyle "var mı yok mu" testi yapacaksak listeleri ve demetleri değil kümeleri tercih + etmeliyiz. Az sayıda eleman için böyle bir hız farkı anlamlı değildir. Listeler ve demetlerde "var mı yok mu" testi ancak + "sıralı arama (sequential search)" denilen yöntemle yapılabilmektedir. Bunun ise zaman maliyeti yüksektir. +#------------------------------------------------------------------------------------------------------------------------ + +s = {'ankara', 10, 20.5, False} + +result = 10 in s +print(result) # True + +result = 10 not in s +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + set nesnesindne hash tablosu oluşturabilmek için set elemanlarının "hashlenebilir (hashable)" olması gerekmektedir. + Temel türlerin hash'lenabilir olduğunu, demetlerin hash'lenebilir olduğunu ancak listelerin hash'lenebilir olmadığını + anımsayınız. Bu durumda bir liste bir kümenin elemanı yapılamamaktadır. Kümelerin kendisi de hash'lenebilir değildir. + Bu nedenle kümenin bir elemanı bir küme de olamaz. Eğer biz bir kümeye eleman olarak bir liste ya da küme verirsek bu durumda + exception (TypeError) oluşur. Örneğin: + + >>> s = {1, (2, 3), 'ankara', 12.3} + >>> s + {(2, 3), 1, 'ankara', 12.3} + >>> k = {'ankara', [1, 2, 3], 'izmir'} + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'list' + >>> m = {'ali', 12, {'veli', 'selami'}} + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'set' + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi bir liste (ve küme) hiçbir zaman hash'lenebilir değildir. Ancak demetler "eğer onların elemanları + hash'lenebilir ise" hash'lenebilir. olmaktadır. Yani örneğin bir demetin elemanı bir liste ise artık demet hash'lenebilir + olmaz: + + >>> s = {1, (2, 3, [4, 5]), 6} + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'list' + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bool değerlerden hash değeri elde edilirken True için 1, False için 0 değeri elde edilmektedir. Örneğin: + + >>> hash(b) + 1 + >>> b = False + >>> hash(b) + 0 + + Dolayısıyla bool bir değer bir küme elemanı yapılırken bu hash değeri kullanılacağı için dikkat etmek gerekir. Örneğin: + + >>> s = {'ali', True} + >>> s + {True, 'ali'} + >>> s = {1, 'ali', True} + >>> s + {1, 'ali'} + + Aynı durum float ve int nesneler için de benzer biçimde söz konusudur: + + >>> s = {1.0, 'ali', 1} + >>> s + {1.0, 'ali'} + + Aslında hash tablolarında aynı hash değerine ilişkin elemanlar tabloda bir arada bulunabilmektedir. Yukarıdaki problem aslında + hash değeri ile karşılaştırma sırasında ortaya çıkmaktadır. Çünkü aşağıdaki karşılaştırmalar True değerini vermektedir: + + >>> 1 == True + True + >>> 1.0 == 1 + True + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kümeler değiştirilebilir türlerdir. Yani biz bir set nesnesi içerisindeki değeri kümeden çıkartabiliriz. Yeni bir değeri kümeye + ekleyebiliriz. + + set sınıfının add isimli metodu tek bir elemanı kümeye eklemek için kullanılmaktadır. Örneğin : + + >>> s = {'ali', 'veli', 10, 20} + >>> s.add('istanbul') + >>> s + {'istanbul', 'ali', 10, 20, 'veli'} + + Anımsanacağı gibi listelerde tek bir elemanın eklenmesi append isimli metotla yapılıyordu. "append" sözcüğü sona ekleme + anlamında listeler için daha uygun olabilir. Kümelerde "sona ekleme" diye bir kavram olmadığı için "append" ismi yerine + "add" ismi tercih edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 20, 30, 40, 50} + +s.add(60) +print(s) # {50, 20, 40, 10, 60, 30} + +s.add((100, 200, 300)) + +print(s) # {50, 20, (100, 200, 300), 40, 10, 60, 30} + +#------------------------------------------------------------------------------------------------------------------------ + Bir grup elemanı kümeye tek hamlede eklemek için update metodu kullanılmaktadır. (bu metodu mantıksal olarak listelerdeki extend + metoduna benzetebiliriz. Tabii extend sona ekleme yapmaktadır. Kümelerede sıra olmadığına göre update metodu sona eklemez.) + update metodu "dolaşılabilir bir nesneyi parametre olarak alır. Nesneyi dolaşarak elde edilen değerleri kümeye ekler. + Örneğin: + + >>> s = {'ali', 10, 'veli', 'istanbul'} + >>> s.update([30, 'izmir', 'selami']) + >>> s + {'istanbul', 'selami', 'ali', 10, 'izmir', 'veli', 30} + >>> s.update('sakarya') + >>> s + {'istanbul', 's', 'selami', 'ali', 'r', 'y', 10, 'a', 'izmir', 'k', 'veli', 30} + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 20, 30, 40, 50} + +print(s) # {50, 20, 40, 10, 30} + +s.update((100, 200, 300)) + +print(s) # {50, 20, 100, 40, 10, 300, 30, 200} + +s.update('ankara') + +print(s) # {200, 10, 'a', 'k', 20, 30, 'n', 100, 'r', 40, 300, 50} + +#------------------------------------------------------------------------------------------------------------------------ + Tabii daha önceden de belirttiğimiz gibi zaten var olan bir elemanın yeniden kümeye eklenmeye çalışılması bir exception'a yol + açmayacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +>>> s = {10, 20, 30} +>>> s.add(20) +>>> s +{10, 20, 30} + +#------------------------------------------------------------------------------------------------------------------------ + Kümedeki bir elemanı silmek için remove metodu kullanılmaktadır. remove ile silinecek eleman listede yoksa exception (KeyError) + oluşmaktadır. Örneğin: + + >>> s = {'ali', 10, 'veli', 20} + >>> s.remove(10) + >>> s + {'veli', 20, 'ali'} + >>> s.remove('sacit') + Traceback (most recent call last): + File "", line 1, in + KeyError: 'sacit' +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kümelerden eleman silmek için discard isimli bir metot da bulunmaktadır. discard metodu silinmek istenen eleman yoksa + exception oluşturmaz. Sadece silme işlemini yapmaz. remove metodundan tek farkı budur. Örneğin: + + >>> s = {10, 'ali', 20, 'veli'} + >>> s.discard('veli') + >>> s + {'ali', 10, 20} + >>> s.discard('can') + >>> s + {'ali', 10, 20} + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + set sınıfında eleman silme ile ilgili pop isimli bir metot da vardır. Ancak set sınıfının pop isimli metodu parametresizdir. + Bu metot her çağrıldığında kümeden gelişigüzel bir elemanı siler ve sildiği elemanı bize geri dönüş değeri olarak verir. + Ancak küme boşsa exception (KeyError) oluşmaktadır. Örneğin: + + >>> s = {'ali', 10, 'selami', 5} + >>> s.pop() + 10 + >>> s.pop() + 'selami' + >>> s + {5, 'ali'} + >>> s.pop() + 5 + >>> s + {'ali'} + >>> s.pop() + 'ali' + >>> s + set() + >>> s.pop() + Traceback (most recent call last): + File "", line 1, in + KeyError: 'pop from an empty set' + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, 'veli'} + +val = s.pop() +print(val) + +val = s.pop() +print(val) + +val = s.pop() +print(val) + +val = s.pop() +print(val) + +print(s) # set() + +#------------------------------------------------------------------------------------------------------------------------ + Kümelerde eleman silme işlemi listelere göre çok daha hızlı yapılmaktadır. Listelerde bir eleman silinmesi sırasında listenin + elemanlarının kaydırılması gerekmektedir. Halbuki kümelerde böyle bir kaydırma yapılmadan çok hızlı bir biçimde eleman + silinebilmektedir. Hash tablolarında eleman silme işlemi çok hızlı yapılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + set sınıfının clear metodu kümedeki tüm elemanları silmek için kullanılmaktadır. Metot parametreye sahip değildir. Örneğin: + + >>> s = set(range(10)) + >>> s + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + >>> s.clear() + >>> s + set() + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, 'veli'} + +print(s) # {10, 'ali', 20, 'veli'} + +s.clear() + +print(s) # set() + +#------------------------------------------------------------------------------------------------------------------------ + set sınıfının copy isimli metodu kümenin bir kopyasını oluşturmak için kullanılmaktadır. Tabii buradaki kopyalama da aslında + yine "sığ kopyalama (shallow copy)" biçiminde yapılmaktadır. Yani aslında küme elemanları da asıl nesnelerin adreslerini tutmaktadır. + Dolayısıyla kopyalamada aslında adresler kopyalanmaktadır. Asıl nesneler kopyalanmamaktadır. Örneğin: + + >>> s = set(range(10)) + >>> s + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + >>> s.clear() + >>> s + set() + >>> s = set(range(10)) + >>> s + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + >>> k = s.copy() + >>> k + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + >>> id(s) + 2567818069376 + >>> id(k) + 2567817420576 + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, 'veli'} +k = s.copy() + +print(s) # {10, 'ali', 20, 'veli'} +print(k) # {10, 'ali', 20, 'veli'} + +s.add(100) + +print(s) # {100, 'ali', 10, 20, 'veli'} +print(k) # {10, 'ali', 20, 'veli'} + +#------------------------------------------------------------------------------------------------------------------------ + Biz bir set değişkenini başka bir değişkene atarsak iki farklı değişken aslında aynı nesneyi gösteriyor durumda olur. + Bu durum bir kopyalama anlamına gelmemektedir. Örneğin: + + >>> s = set(range(10)) + >>> s + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + >>> k = s + >>> k + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + >>> id(s) + 2567818070048 + >>> id(k) + 2567818070048 + >>> s.add(100) + >>> s + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100} + >>> k + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100} + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, 'ali', 20, 'veli'} +k = s + +print(s, id(s)) # {'ali', 10, 20, 'veli'} 2594897135872 +print(k, id(k)) # {'ali', 10, 20, 'veli'} 2594897135872 + +s.add(100) + +print(s) # {100, 'ali', 10, 20, 'veli'} +print(k) # {100, 'ali', 10, 20, 'veli'} + +#------------------------------------------------------------------------------------------------------------------------ + 17. Ders - 06/06/2022-Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İki kümenin ortak elemanlarının bulunması işlemine "kesişim (intersection)" denilmektedir. Kesişim işlemi set sınıfının intersection + metoduyla ya da & operatörüyle yapılabilmektedir. a ve b iki küme olmak üzere: + + c = a.intersection(b) + + işlemi ile + + c = a & b + + işlemi aynı sonucu verecektir. Arada şöyle bir farklılık vardır: & operatöründe sağ taraftaki operand yalnızca set ya da ileride görecek olduğumuz + frozenset türünden olmak zorundadır. Ancak intersection metodunun parametresi herhangi bir dolaşılabilir nesne olabilir. + Örneğin: + + >>> s = {'ali', 10, 'veli', 'eskişehir'} + >>> k = {10, 'eskişehir', 'adana', 20} + >>> result = s.intersection(k) + >>> result + {10, 'eskişehir'} + >>> result = s.intersection(['ali', 'adana', 'eskişehir']) + >>> result + {'ali', 'eskişehir'} + >>> result = s & k + >>> result + {10, 'eskişehir'} + +#------------------------------------------------------------------------------------------------------------------------ + +a = {10, 'ali', 20, 'veli', 'selami', 30} +b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} + +c = a & b +print(c) # {'veli', 20, 30} + +c = a.intersection(b) +print(c) # {'veli', 20, 30} + +x = [10, 'ali', 100, 200, 'sibel'] + +c = a.intersection(x) # parametre dolaşılabilir nesne olabilir +print(c) # {10, 'ali'} + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte klavyeden (stdin dosyasından) iki sözcük okunmuştur. Bu iki sözcüğün ortak karakterleri ekrana yazdırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +word1 = input('Bir sözcük giriniz:') +word2 = input('Bir sözcük daha giriniz:') + +s = set(word1) + +result = s.intersection(word2) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Aslında intersection metodunda biz birden fazla dolaşılabilir nesnesyi de agüman olarak verebiliriz. Bu durumda sol + taraftaki nesneyle argüman olarak verilmiş dolaşılabilir nesnelerin kesişimleri bulunmaktadır. Örneğin: + + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> k = ['ali', 'fatma', 'hüseyin', 'ayşe'] + >>> m = ['ayşe', 'ali', 'fatma', 'can'] + >>> result = s.intersection(k, m) + >>> result + {'ali', 'ayşe', 'fatma'} + + Biz aslında s.intersection(k, m) işlemiyle s, k ve m'nin kesişimlerini bulmuş olduk. Yani yapmaya çalıştığımız şey + s & k & m ile aynıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İki kümenin birleşimi '|' operatörü ile ya da set sınıfının union metodu ile elde edilebilmektedir. Burada da yine '|' + operatörünün sağ tarafındaki operand set ya da frozenset türünden olabilir. Ancak union metodunun parametresi herhangi bir dolaşılabilir + nesne olabilir. Yine union metoduna birden fazla dolaşılabilir nesneyi argüman olarak verebiliriz. Örneğin: + + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> k = {'ali', 'can', 'veli', 'hüseyin', 'sibel'} + >>> result = s.union(k) + >>> result + {'veli', 'fatma', 'sibel', 'can', 'ayşe', 'ali', 'hüseyin', 'selami'} + >>> result = s | k + >>> result + {'veli', 'fatma', 'sibel', 'can', 'ayşe', 'ali', 'hüseyin', 'selami'} + >>> result = s.union(['kaan', 'ali', 'sacit']) + >>> result + {'veli', 'kaan', 'ayşe', 'fatma', 'ali', 'selami', 'sacit'} + >>> result = s.union(['kaan', 'ali', 'sacit'], ['umut', 'şükran']) + >>> result + {'veli', 'şükran', 'fatma', 'kaan', 'ali', 'sacit', 'umut', 'ayşe', 'selami'} + +#------------------------------------------------------------------------------------------------------------------------ + +a = {10, 'ali', 20, 'veli', 'selami', 30} +b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} + +c = a | b +print(c) # {'ayşe', 'veli', 40, 10, 'selami', 20, 'fatma', 30, 'ali'} + +c = a.union(b) +print(c) # {'ayşe', 'veli', 40, 10, 'selami', 20, 'fatma', 30, 'ali'} + +x = [10, 'ali', 100, 200, 'sibel'] + +c = a.union(x) # parametre dolaşılabilir nesne olabilir +print(c) # {200, 10, 20, 'sibel', 'ali', 30, 'veli', 100, 'selami'} + +c = a.union('ankara') +print(c) # {'k', 10, 20, 'a', 'ali', 30, 'n', 'veli', 'r', 'selami'} + +#------------------------------------------------------------------------------------------------------------------------ + İki kümenin farkı '-' operatörü ile ya da difference metodu ile elde edilebilir. Yine diğerlerinde olduğu gibi '-' operatörünün + sağ tarafındaki operand set ya da frozenset türünden olmak zorundadır. Ancak difference metodunun parametresi herhangi bir dolaşılabilir + nesne türünden olabilir. Örneğin: + + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> s + {'veli', 'ayşe', 'fatma', 'ali', 'selami'} + >>> k = {'ali', 'sacit', 'fatma', 'hüseyin', 'bora'} + >>> k + {'fatma', 'bora', 'ali', 'hüseyin', 'sacit'} + >>> result = s - k + >>> s + {'veli', 'ayşe', 'fatma', 'ali', 'selami'} + >>> result = s.difference(k) + >>> result + {'veli', 'ayşe', 'selami'} + + Yine difference metodunda birdne fazla dolaşılabilir nesne argüman olarak verilebilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = {10, 'ali', 20, 'veli', 'selami', 30} +b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} + +c = a - b +print(c) # {'selami', 10, 'ali'} + +c = a.difference(b) +print(c) # {'selami', 10, 'ali'} + +x = [10, 'ali', 'selami'] +c = a.difference(x) + +print(c) # {'veli', 20, 30} + +#------------------------------------------------------------------------------------------------------------------------ + İki kümenin ortak olmayan elemanlarının elde edilmesine "exor" işlemi denilmektedir. Exor işlemi '^' operatörü ile ya da + symmetric_diffrence isimli metotla yapılmaktadır. Yine '^' operatörünün sağ tafındaki operand set ya da frozenset türünden + olabilir. Ancak symmetric_difference metodunun parametresi herhangi bir dolaşılabilir nesne türünden olabilmektedir. + Örneğin: + + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> s + {'veli', 'ayşe', 'fatma', 'ali', 'selami'} + >>> k = {'hüseyin', 'ali', 'sacit', 'selami'} + >>> k + {'ali', 'hüseyin', 'selami', 'sacit'} + >>> result = s ^ k + >>> result + {'veli', 'fatma', 'sacit', 'ayşe', 'hüseyin'} + >>> result = s.symmetric_difference(['ali', 'jale', 'mahmut']) + >>> result + {'veli', 'fatma', 'jale', 'mahmut', 'ayşe', 'selami'} + +symmetric_difference metodunda argüman olarak tek bir dolaşılabilir nesne verilebilmektedir. + +#------------------------------------------------------------------------------------------------------------------------ + +a = {10, 'ali', 20, 'veli', 'selami', 30} +b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} + +c = a ^ b +print(c) # {'ayşe', 40, 10, 'selami', 'fatma', 'ali'} + +c = a.symmetric_difference(b) +print(c) # {'ayşe', 40, 10, 'selami', 'fatma', 'ali'} + +x = 'ali', 10, 20, 'ayşe' + +c = a.symmetric_difference(x) +print(c) # {'ayşe', 'veli', 'selami', 30} + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda görmüş olduğumuz temel küme işlemlerinin update'li versyonları da vardır. Bunlar aşağıda listelenmiştir: + + a &= b ya da a.intersection_update(b) + a |= b ya da a.update(b) + a -= b ya da a.diffrence_update(b) + a ^= b ya da a.symmetric_difference_update(b) + + Burada yine operatör versiyonlarının sağ tarafındaki operand set ya da frozenset türünden olmak zorundadır. Metot versiyonlarının parametreleri ise + herhangi bir dolaşılabilir nesne olabilmektedir. Yine metotlu versiyonlarda symmmetric_difference_update dışındaki metotlara birden fazla + dolaşılabilir nesne argüman olarak verilebilmektedir. + + Yukarıdaki işlemlerde sonuç başka bir nesne olarak elde edilmemektedir. Sonuç soldaki nesne değiştirilerek elde edilmektedir. + Yani bu işlemler sonucunda yeni bir nesne yaratılmamaktadır. Örneğin: + + >>> a = {10, 'ali', 20, 'veli', 'selami', 30} + >>> b = {30, 'veli', 'ayşe', 40, 20, 'fatma'} + >>> id(a) + 4373858784 + >>> id(b) + 4373859008 + >>> a &= b + >>> a + {'veli', 20, 30} + >>> id(a) + 4373858784 + + Burada aslında a |= b işlemi ile a.update(b) işlemi aynıdır. Bu nedenle union_update isimli bir metot yoktur. Onun yerine + update isimli metot vardır. Örneğin: + + c = a.intersection(b) + + işleminde a ve b değişmemektedir. Bu işlemden yeni bir set nesnesi elde edilmektedir. Halbuki örneğin: + + a.intersection_update(b) + + Burada a nesnesi artık a ve b'nin kesişimlerinden oluşan bir kğme haline gelmektedir. Bu işlemden yeni bir + küme elde edilmemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir a kümesinin tüm elemanları b kümesinde varsa "a kümesi b kümesinin bir alt kümesidir (subset)". Alt küme işleminin tersine + "üst küme (superset)" denilmektedir. Öz alt küme (proper subset) kendisi dahil olmayan alt kümedir. Her küme kendisinin + bir alt kümesidir ancak öz alt kümesi değildir. Benzer biçimde her küme kendisinin bir üst kümesidir ancak öz üst kümesi değildir. + + Bir kümenin diğer kümenin alt kümesi olup olmadığı "<=" operatörü ile ya da issubset operatörü ile belirlenir. Yine "<=" + operatöründe sağ taraftaki operand set ya da frozenset türünden olabilir. Ancak issubset metodunda parametre herhangi bir dolaşılabilir + sınıf türünden olabilir. Benzer biçimde üst küme kontrolü de ">=" ya da issuperset metoduyla yapılabilmektedir. Benzer biçimde >= operatörünün + sol tarafındaki ve sağ tarafındaki nesneler set ya da frozenset türünden olmak zorundadır. Ancak issuperset metodunun parametresi herhangi + bir dolaşılabilir nesne olabilir. + + Öz alt küme için bir metot yoktur. Bu işlem '<' operatörü ile yapılır. Öz üst küme kontrolü için de bir metot yoktur. Bu işlem '>' operatörü ile yapılır. + Örneğin: + + {'veli', 'ayşe', 'fatma', 'ali', 'selami'} + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> s + {'veli', 'ayşe', 'fatma', 'ali', 'selami'} + >>> k = {'ali', 'veli', 'selami'} + >>> k + {'ali', 'veli', 'selami'} + >>> result = k < s + >>> result + True + >>> result = s < s + >>> result + False + >>> result = s <= s + >>> result + True + +#------------------------------------------------------------------------------------------------------------------------ + +a = {10, 20, 30, 40, 50} +b = {10, 40} + +result = b < a +print(result) # True + +result = a < a +print(result) # False + +result = a <= a +print(result) # True + +result = a > b +print(result) # True + +result = a.issubset(range(10, 100, 10)) +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + İki kümenin hiçbir ortak elemanı yoksa bu iki kümeye "ayrık kümeler (disjoint sets)" denilmektedir. Ayrıklık kontrolü + set sınıfının isdisjoint metodu ile yapılmaktadır. Bu metodun parametresi herhangi bir dolaşılabilir nesne olabilir. + Disjoint işleminin bir operatör karşılığı yoktur. Tabii işlem s & k == set() biçiminde de yapılabilir. Örneğin: + + >>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'} + >>> k = {'hasan', 'hüseyin', 'kazım'} + >>> s.isdisjoint(k) + True + +#------------------------------------------------------------------------------------------------------------------------ + +a = {10, 20, 30, 40, 50} +b = {100, 200} + +result = a.isdisjoint(b) +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + Python'da "seyrek kullanılan" set sınıfının değiştirilemez (immutable) biçimi olan "frozenset" isimli bir sınıf da vardır. + set sınıfı ile frozenset sınıfı arasındaki farklılıklar şunlardır: + + 1) frozenset sınıfı değiştirilebilir olmadığı için onun add gibi, update gibi, intersection_update gibi, remove gibi + metotları yoktur. Ancak değiştirme işlemi yapmayan metotları set sınıf ile aynıdır. + + 2) frozenset sınıfında &=, |=, ^= -= gibi operatörler soldaki nesne üzerinde işlem yapmazlar. Yeni nesne yaratırlar. Yani örneğin, + a |= b işlemi tamamen a = a | b işlemi ile eşdeğerdir. + + 3) frozenset elemanları eğer hash'lenebilir ise frozenset nesnesi de hash'lenebilir durumdadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir frozenset nesnesi küme parantezleriyle yaratılamaz. Ancak frozenset sınıfının tür fonksiyonu olan frozenset fonksiyonu + ile yaratılabilir. Bu fonksiyon herhangi bir dolaşılabilir nesneyi parametre olarak alabilmektedir. Örneğin: + + >>> fs = frozenset(['ali', 'veli', 'selami', 'ayşe', 'fatma']) + >>> fs + frozenset({'veli', 'fatma', 'ayşe', 'ali', 'selami'}) +#------------------------------------------------------------------------------------------------------------------------ + +fs = frozenset([1, 'ali', 'selami', 2]) +print(fs) + +print(len(fs)) + +#------------------------------------------------------------------------------------------------------------------------ + set ile frozenset nesneleri '|', '&', '-', '^' işlemlerine sokulabilmektedir. Bu durumda işlemin sonucu soldaki operandın + türüne bağlıdır. Eğer soldaki operand set türündense sonuç set türünden, frozenset türündense sonuç frozenset türünden + olur. Tabii bunların metot karşılıklarında her zaman sonuç metodun çağrıldığı nesnenin türünden olmaktadır. Başka bir deyişle + yukarıdaki operatör işlemlerinde sol taraftaki ya da sağ taraftaki operand set ya da frozenset olabilmektedir. Ancak işlemin sonucu + her zaman sol taraftaki operand türünden olur. Örneğin (sembolik biçimde yazıyoruz): + + set & frozenset => set + frozenset & set => frozenset + frozenset & frozenset => frozenset + + Örneğin: + + >>> fs = frozenset(['ali', 'veli', 'selami', 'ayşe', 'fatma']) + >>> fs + frozenset({'veli', 'fatma', 'ayşe', 'ali', 'selami'}) + >>> s = {'ali', 'sacit', 'veli'} + >>> s + {'ali', 'veli', 'sacit'} + >>> result = fs & s + >>> result + frozenset({'ali', 'veli'}) + >>> result = s & fs + >>> result + {'ali', 'veli'} + +#------------------------------------------------------------------------------------------------------------------------ + +fs = frozenset([1, 'ali', 'selami', 2]) +s = {1, 'sibel', 10, 'veli'} + +result = fs & s +print(result, type(result)) # frozenset({1}) + +result = s & fs +print(result, type(result)) # {1} + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirtildiği gibi frozenset sınıfında &=, |=, ^=, -= gibi işlemlerde bir update yapılmamaktadır. Örneğin: + + >>> fs = frozenset(['ali', 'veli', 'selami']) + >>> fs + frozenset({'ali', 'selami', 'veli'}) + >>> id(fs) + 1525529415904 + >>> s = {'ali', 'ayşe', 'veli'} + >>> s + {'ali', 'veli', 'ayşe'} + >>> fs |= s + >>> fs + frozenset({'ayşe', 'ali', 'selami', 'veli'}) + >>> id(fs) + 1525529416800 + >>> id(s) + 1525529417024 + >>> s |= fs + >>> s + {'ayşe', 'ali', 'selami', 'veli'} + >>> id(s) + 1525529417024 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Kümenin bir elemanı küme olamaz. Çünkü kümler hash'lenebilir değildir. Ancak kümenin bir elemanı frozenset olabilir. + Çünkü frozenset hash'lenebilir biçimdedir. Örneğin: + + >>> s = {1, 2, {3, 4}, 5} + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'set' + >>> s = {1, 2, frozenset({3, 4}), 5} + >>> s + {frozenset({3, 4}), 1, 2, 5} + + Anımsanacağı gibi bir demetin elemanı bir liste olabiliyordu. Ancak bir frozenset'in elemanlarının hash'lenebilir + olması gerekmektedir. Örneğin: + + >>> fs = frozenset([[1, 2, 3], 4, 5]) + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'list' + +#------------------------------------------------------------------------------------------------------------------------ + +s = {10, frozenset((20, 30)), 40} + +print(s) # {40, 10, frozenset({20, 30})} + +#------------------------------------------------------------------------------------------------------------------------ + Sözlükler (dictionaries) "anahtar-değer" çiftlerini tutan, anahtar verildiğinde değerin hızlı bir biçimde bulunmasını sağlayan + veri yapılarıdır. Sözlükler hızlı aramayı mümkün hale getirmek için özel algoritmik yöntemlerden faydalanmaktadır. Programlama + dillerinde sözlükler "hash tabloları (hash tables)", "dengelenmiş ikili ağaçlar (balanced binary tree)", "sıralı diziler (sorted arrays)" + biçiminde gerçekleştirilebilmektedir. CPython gerçekleştiriminde sözlükler "hash tabloları" yoluyla oluşturulmuştur. + + Sözlükler yine küme parantezleri kullanılarak yaratılırlar. Ancak küme parantezlerinin içerisi "anahtar: değer" çiftleerinden + oluşturulmaktadır. Sözlük yaratma işleminin genel biçimi şöyledir: + + { anahtar: değer, anahtar: değer, anahtar: değer, ...} + + Burada sözlklerin küme partantezleri içerisinde anahtardan sonra ':' ile değer belirtilerek yaratıldığını görüyorsunuz. + Örneğin: + + >>> d = {'ankara': 6, 'istanbul': 34, 'eskişehir': 26, 'izmir': 35, 'denizli': 20} + >>> d + {'ankara': 6, 'istanbul': 34, 'eskişehir': 26, 'izmir': 35, 'denizli': 20} + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 100, 'veli': 200, 'selami': 300, 'ayşe': 1000, 'fatma': 400} + +print(d) # {'ali': 100, 'veli': 200, 'selami': 300, 'ayşe': 1000, 'fatma': 400} +print(type(d)) # + +#------------------------------------------------------------------------------------------------------------------------ + Sözlükler "dict" isimli sınıfla temsil edilmiştir. Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> type(d) + + + Bir sözlüğü print fonksiyonu ile yazdırdığımızda sözlüğün bütün elemanları anahtar-değer çiftleri biçiminde yazdırılmaktadır. + Örneğin: + + >>> print(d) + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerde anahtarların hash'lenebilir olması gerekmektedir. Ancak değerler için böyle bir koşul yoktur. Dolayısıyla anahtarlar + int, float, str gibi türlerden olabilir. Ancak liste ve küme olamaz. Demetlerin eğer elemanları hash'lenebilir ise hash'lenebilir + olduğunu anımsayınız. Benzer biçimde frozenset nesneleri de hash'lenebilir durumdadır. Örneğin: + + >>> d = {'eskişehir': ['mihalıççık', 'sivrihisar', 'seyitgazi'], 'istanbul': ['şişli', 'pendik', 'ataşehir'], + 'izmir': ['konak', 'gaziemir', 'karşıyaka']} + + Burada d sözlüğünün anahtarları str türündendir. str türü de hash'lenebilir bir türdür. Ancak d sözlüğünün değerleri birer listedir. + Değerlerin hash'lenebilir olması gerekmez. Yani sözlüklerin değerleri herhangi bir türden olabilir. + + Aşağıdaki örnekte sözlüğün anahtarının bir liste olamadığına dikkat ediniz. + + >>> d = {[10, 20]: 100} + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'list' + + Görüldüğü gibi biz sözüğün anahtarını hash'able olmayan bir türden yapmak istersek xception (TypeError) oluşmaktadır. + + Demetler hash'able olabildiği için sözlük anahtarı yapılabilmektedir. Örneğin: + + >>> d = {(10, 20): 100} + >>> d + {(10, 20): 100} + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerde anahtarlar ve değerler herhangi bir türden olabilirler (anahtarlar hash'lenebilir olmak zorundadır). Anahtarların ve + değerlerin aslında aynı sözlük içerisinde aynı türden olması gerekmemektedir. Örneğin: + + d = {'ali': 100, 200: 'veli', 20: 30.2} + + Bu sözlük geçerlidir. Ancak uygulamada anahtarların ve değerlerin tutarlı biçimde aynı türlerden olması fayda sağlamaktadır. + Bazen bir anahtar verildiğinde birden fazla değerin elde edilmesi istenebilir. Bu durumda değer bir liste ya da demet gibi + veri yapısı olabilir. Örneğin: + + d = {'eskişehir': ['alpu', 'mihalıççık', 'seyitgazi'], 'ankara': ['keçiören', 'polatlı', 'çankaya'], + 'izmir': ['konak', 'buca', 'karşıyaka']} + + Burada anahtarlar şehir isimlerinden değerler de onların ilçelerini belirten listelerden oluşmaktadır. Programcı bir şehrin + ismini verdiğinde onun ilçelerini hızlı bir biçimde elde etmek istemiş olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'eskişehir': ['alpu', 'mihalıççık', 'seyitgazi'], 'ankara': ['keçiören', 'polatlı', 'çankaya'], + 'izmir': ['konak', 'buca', 'karşıyaka']} + +print(d) + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerin kendisi de hash'lenebilir değildir. Dolayısıyla bir sözlük başka bir sözlüğe anahtar yapılamaz. Ancak bir szölüğün + değeri bir sözlük olabilir. Örneğin: + + >>> d = {'içanadolu': {'eskişehir': 26, 'konya': 42, 'ankara': 6}, 'ege': {'izmir': 35, 'aydın': 9}, 'marmara': {'istanbul': 34, 'kocaeli': 41}} + >>> d + {'içanadolu': {'eskişehir': 26, 'konya': 42, 'ankara': 6}, 'ege': {'izmir': 35, 'aydın': 9}, 'marmara': {'istanbul': 34, 'kocaeli': 41}} + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listelerin, demetlerin, kümelerin ve sözlklerin elemanları sabit yerine değişken de olabilir. Python'da bu bağlamda + sabitle değişken arasında bir farklılık yoktur. Biz bir sabit oluşturduğumuzda zaten o sabit için önce bir nesne yaratılmakta sonra o nesnenin + adresi kullanılmaktadır. Bunun doğrudan yapılmasıyla dolaylı yapılması arasında bir farklılık yoktur. Örneğin: + + a = [10, 20, 30] + + Böyle bir liste nesnesi yaratıldığında listenin elemanları 10, 20 v3 30 değerlerinin tutulduğu int türden nesnelerin adreslerini + tutmaktadır. Bu işlemin aşağıdakinden bir farkı yktur: + + x = 10 + y = 20 + z = 30 + a = [x, y, z] + + Burada da liste elemanlarında yine 10, 20, 30 değerlerinin bulunduğu int türden nesnelerin adresleri tutulmaktadır. + + x = 26 + d = {x: 'eskişehir'} + + Biz her ne kadar atama işlemini bir operatör gibi ele almış olsak da aslında Python'da atama işlemi bir operatör değil + deyim statüsündedir. Dolayısıyla aşağıdaki bir durum geçerli değildir: + + >>> a = [x = 10 + 20, 20] + File "", line 1 + a = [x = 10 + 20, 20] + ^^^^^^^^^^^ + SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + + Tabii walrus gerçekten atama işlemi yapıp değer üreten bir operatördür. Dolayısıyla aşağıdaki gibi bir durum geçerlidir: + + >>> a = [x := 10, 20] + >>> a + [10, 20] + >>> x + 10 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirtildiği gibi sözlükler dict isimli sınıfla temsil edilmiştir. dict sınıfının tür fonksiyonu olan dict fonksiyonu + ile de sözlük yaratabiliriz. Örneğin boş bir sözlük dict() biçiminde yaratılabilir: + + >>> d = dict() + >>> d + {} + + Boş küme parantezleri de boş sözlük yaratmak için kullanılabilmektedir. Örneğin: + + >>> d = {} + >>> d + {} + + Boş küme parantezlerinin boş bir küme yaratmadığına boş bir sözlük yarattığına dikkat ediniz. Boş bir kme yaratmak için + mecburen set() çağrısı kullanılmaktadır: + + >>> s = set() + >>> s + set() + +#------------------------------------------------------------------------------------------------------------------------ + +d = dict() +print(d) # {} + +d = {} +print(d) # {} + +#------------------------------------------------------------------------------------------------------------------------ + dict fonksiyonuna biz iki elemanlı dolaşılabilir nesnelerden oluşan dolaşılabilir bir nesneyi argüman olarak geçirirsek, + dict fonksiyonu bu nesneyi dolaşır. Her dolaşımda iki elemanlı bir dolaşılabilir nesne elde eder. O iki elemanlı dolaşılabilir + nesnenin ilk elemanı anahtar ikinci elemanı değer olacak biçimde bir sözlük nesnesi oluşturur. Örneğin: + + >>> a = [('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)] + >>> a + [('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)] + >>> d = dict(a) + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + + Burada a nesnesini dolaştığımızda biz iki elemanlı dolaşılabilir nesneler elde edilmektedir. İşte dict fonksiyonu bunlardan + sözlük yapmaktadır. Bu örnekte listenin elemanları birer demettir. Ancak tabii bunun tersi de söz konusu olabilirdi. Örneğin: + + >>> a = (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]) + >>> a + (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]) + >>> d = dict(a) + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + + Ya da örneğin: + + >>> a = (('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)) + >>> d = dict(a) + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + + Örneğin: + + >>> a = (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50], 'ak', 'tk', 'xy') + >>> d = dict(a) + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'a': 'k', 't': 'k', 'x': 'y'} + + Dolaşılabilir nesnenin elemanlarının iki elemandan daha fazla eleman içeren dolaşılabilir nesne olması durumu geçerli değildir. + Bu durumda exception (Valueerror) oluşmaktadır. Örneğin: + + >>> d = dict([('ali', 10), ('veli', 20, 30)]) + Traceback (most recent call last): + File "", line 1, in + ValueError: dictionary update sequence element #1 has length 3; 2 is required + +#------------------------------------------------------------------------------------------------------------------------ + +a = [('ali', 10), ('veli', 20), ('selami', 30), ('ayşe', 40), ('fatma', 50)] + +d = dict(a) +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + + +a = [['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]] + +d = dict(a) +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +a = (['ali', 10], ['veli', 20], ['selami', 30], ['ayşe', 40], ['fatma', 50]) + +d = dict(a) +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +s = ['ak', 'sa', 'mk', 're'] + +d = dict(s) +print(d) + +#------------------------------------------------------------------------------------------------------------------------ + 18. Ders - 08/06/2022-Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + dict fonksiyonunda "değişken=değer" biçiminde argümanlar girdiğimizde dict bize o değişkenlerin string halini anahtar, + '=' operatörünün sağındakini de değer yaparak bir sözlük nesnesi oluşturur. Örneğin: + + >>> d = dict(x=10, y=20, z=30) + >>> d + {'x': 10, 'y': 20, 'z': 30} + >>> d = dict(eskişehir=26, istanbul=34, izmir=35) + >>> d + {'eskişehir': 26, 'istanbul': 34, 'izmir': 35} + + Bu biçimde sözlük nesnesi oluşturma işlemi seyrek olarak kullanılmaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + +d = dict(ali=10, veli=20, selami=30, ayşe=40, fatma=50) + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +#------------------------------------------------------------------------------------------------------------------------ + Burada değişken ismi yerine başka bir şey getirilemez. Örneğin aşağıdaki geçerli değildir: + + d = dict('ali'= 10, 'veli'=20, 'selami'=30) # geçersiz! + d = dict(10='ali', 20='veli', 30='selami') # geçersiz! + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sözlükte anahtarı verip değeri elde etmek için [...] operatörü kullanılır. Köşeli parantezin içerisine anahtar yazılır. + Operatör de bize o anahtarın değerini verir. [...] operatöründe verdiğimiz anahtar sözlükte yoksa exception (KeyError) oluşur. + Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> val = d['ayşe'] + >>> val + 40" + >>> val = d['fatma'] + >>> val + 50 + >>> val = d['sacit'] + Traceback (most recent call last): + File "", line 1, in + KeyError: 'sacit' + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +print(d) + +val = d['veli'] +print(val) # 20 + +val = d['fatma'] +print(val) # 50 + +#------------------------------------------------------------------------------------------------------------------------ + Tabii bir sözlükte anahtar ve değerlerin türleri hep aynı olmak zorunda değildir. Örneğin: + + >>> d = {'ali': 10, 20: 'veli', 1.5: 'selami'} + >>> d + {'ali': 10, 20: 'veli', 1.5: 'selami'} + >>> d['ali'] + 10 + >>> d[20] + 'veli' + >>> d[1.5] + 'selami' + +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 'veli': 20, 30: False} + +print(d) + +val = d['veli'] +print(val) # 20 + +val = d[10] +print(val) # ali + +#------------------------------------------------------------------------------------------------------------------------ + dict sınıfının get isimli metodu da anahtar verildiğinde değerin alınması için kullanılır. Ancak get anahtar sözlükte yoksa + exception oluşturmaz. İkinci parametresiyle girdiğimiz değere geri döner. Bu ikinci parametre için argüman girmeyebiliriz. Bu durumda + get anahtar bulunamazsa None değeri ile geri dönmektedir. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> val = d.get('ayşe') + >>> val + 40 + >>> val = d.get('sacit') + >>> print(val) + None + >>> val = d.get('ayşe', 'bulunamadı') + >>> val + 40 + >>> val = d.get('sacit', 'bulunamadı') + >>> val + 'bulunamadı' + >>> val = d.get('sacit', 0) + >>> val + 0 + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +print(d) + +val = d.get('selami', 'anahtar bulunamadı') +print(val) # 30 + +val = d.get('sacit', 'anahtar bulunamadı') +print(val) # Not found + +val = d.get('sacit') +print(val) # None + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerde "in" ve "not in" operatörleri belli bir anahtarın sözlükte olup olmadığını anlamak için kullanılabilir. + Sözlüklerde de tıpkı kümelerde olduğu gibi "in" ve "not in" operatörleri çok hızlı çalışmaktadır. Bu operatörler anahtarın + varlığını sorgulamaktadır, değerlerin değil. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> 'ayşe' in d + True + >>> 20 in d + False + >>> 20 not in d + True + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +print(d) + +result = 'selami' in d +print(result) # True + +result = 'fehmi' not in d +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + Built-in len fonksiyonuna biz bir sözlük nesnesini verirsek fonksiyon bize sözlükte kaç tane anahtar-değer çifti olduğunu verir. + Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> len(d) + 5 + >>> d = {} + >>> len(d) + 0 + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +result = len(d) +print(result) # 5 + +d = {} + +result = len(d) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Sözlükler de dolaşılabilir (iterable) nesnelerdir. Bir sözlük nesnesi dolaşıldığında yalnızca "anahtarlar" elde edilir. + Ancak bu anahtarların elde edilmesi Python 3.6'ya kadar herhangi bir sırada olabiliyordu. Ancak Python 3.6 ve sonrasında + artık sözlük nesneleri dolaşıldığında anahtarlar eklenme sırasıyla elde edilir hale getirildi. Yani mevcut Python süsümlerinde + bir sözlük nesnesi dolaşıldığında her zaman anahtarlar sözlükteki sıraya göre elde edilmektedir. Bu durumun Python 3.6'da + garanti edilmediğine dikkat ediniz. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> a = list(d) + File "", line 1 + a = list(d) + IndentationError: unexpected indent + >>> a = list(d) + >>> a + ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +a = list(d) +print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +#------------------------------------------------------------------------------------------------------------------------ + Sözlükler değiştirilebilir türlerdir. Biz sözlüğe yeni bir anahtar-değer çifti ekleyebiliriz, sözlükten bir anahtar-değer çiftini + silebiliriz. Mevcut bir anahtarın değerini değiştirebiliriz. + + Bir sözlükte anahtarlar "tektir (unique)". Ancak daha önce var olan bir anahtara ilişkin anahtar-değer çiftini sözlüğe eklemek + istersek bu durum herhangi bir soruna yol açmaz. Artık daha önceki anahtarın değeri değiştirilmiş olur. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ali': 40, 'selami': 50} + >>> d + {'ali': 40, 'veli': 20, 'selami': 50} + + Tabii sözlükteki bir anahtarın değiştirilmesi biçiminde bir işlem yoktur. Zaten bu işlem bir anahtar-değer çiftinin silinip yeni bir anahtar-değer + çiftinin eklenmesi ile aynı anlamdadır. + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'ali': 100} + +print(d) # {'ali': 100, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüğe yeni bir anahtar değer çifti eklemenin en basit yolu köşeli parantezli atama yapmaktır. Yani: + + d[key] = value + + ataması sözlüğe yeni bir anahtar-değer çifti eklemektedir. Ancak burada eğer anahtar zaten varsa anahtarın değeri değiştirilir. + Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d['sacit'] = 60 + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 60} + >>> d['selami'] = 100 + >>> d + {'ali': 10, 'veli': 20, 'selami': 100, 'ayşe': 40, 'fatma': 50, 'sacit': 60} + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +d['sacit'] = 60 + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 60} + +d['veli'] = 100 +print(d) # {'ali': 10, 'veli': 100, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 60} + +#------------------------------------------------------------------------------------------------------------------------ + Bir sözlüğe tek hamlede birden fazla anahtar-değer çifti eklemek için update metodu kullanılmaktadır. update metodunun parametresinin + dolaşılabilir bir nesne olması gerekir. Bu durumda bu nesne dolaşıldıkça iki elemanlı dolaşılabilir nesneler elde edilmelidir. + update metodu da onların birinci elemanlarını anahtar, ikinci elemanlarını değer yaparak bunları sözlüğe ekler. Yani update metodunun + parametresi dict fonksiyonundaki gibi olmalıdır. Tabii zaten anahtar sözlükte bulunuyorsa onun yine değeri değiştirilecektir. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d.update([('sacit', 100), ('mehmet', 200), ('sibel', 300)]) + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sacit': 100, 'mehmet': 200, 'sibel': 300} + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +d.update([('sibel', 60), ('kazım', 70), ('hasan', 90)]) + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'sibel': 60, 'kazım': 70, 'hasan': 90} + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi bir sözlük nesnesi dolaşıldığında anahtarlar her zaman Python'ın 3.6 ve sonrasında sözlüğe + eklenme sırası dikkate alınarak elde edilmektedir. Yani biz bir sözlüğe köşeli paramtezlerle ya da update metodu ile ekleme + yaptığımızda bu eklenenlerin anahtarları sözlük dolaşıldığında bizim eklediğimiz sırada elde edilecektir. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d['ahmet'] = 100 + >>> d.update([('sacit', 200), ('mehmet', 300), ('sibel', 400)]) + >>> a = list(d) + >>> a + ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'ahmet', 'sacit', 'mehmet', 'sibel'] +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sözlükten bir anahtar-değer çiftini silmek için pop isimli metot kullanılır. pop metodu bizden anahtarı alır. Anahtar-değer + çiftini sözlükten siler. Ancak silinen değeri bize geri dönüş değeri olarak verir. pop metodu anahtarı bulamazsa ve tek argüman ile + çağrılmışsa exception (KeyError) oluşturmaktadır. Ancak pop iki argümanla da çağrılabilir. Bu durumda anahtar bulunamzsa + ikinci argümandaki değer geri döndürülmektedir. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> val = d.pop('selami') + >>> val + 30 + >>> d + {'ali': 10, 'veli': 20, 'ayşe': 40, 'fatma': 50} + >>> val = d.pop('sibel') + Traceback (most recent call last): + File "", line 1, in + KeyError: 'sibel' + >>> val = d.pop('sibel', 'anahtar yok') + >>> val + 'anahtar yok' + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +value = d.pop('ayşe') +print(value) # 40 + +print(d) # {'ali': 10, 'veli': 20, 'selami': 30, 'fatma': 50} + +value = d.pop('sacit', 'anahtar yok') +print(value) # anahtar yok + +#------------------------------------------------------------------------------------------------------------------------ + Bir sözlüğün tüm anahtarlarını keys isimli metotla, tüm değerlerini values isimli metotla elde edebiliriz. Bu metotlar bize + dolaşılabilir nesne verir. O nesne dolaşıldığında anahtarlar ve değerler elde edilecektir. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> d + {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + >>> a = list(d.keys()) + >>> a + ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + >>> b = list(d.values()) + >>> b + [10, 20, 30, 40, 50] + + Yine keys metodu Python 3.6 ve sonrasında bize anahtarları onların listeye eklenme sırasına göre vermektedir. Benzer biçimde + yine Python 3.6 ve sonrasında values metodu da bize değerleri anahtarların eklenme sırasına göre vermektedir. Ancak Python'un + önceki sürümleri bunu garanti etmemektedir. + + Burada keys metodunun aslında çok da gerekmediği ancak values metodunun gerektiği gibi bir sonuç çıkartabilirsiniz. Çünkü zaten + biz sözlük nesnesini dolaştığımızda onunanahtarlarını elde edebiliyorduk. +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +result = d.keys() +a = list(result) +print(a) # [10, 20, 30, 40, 50] + +result = d.values() +a = list(result) +print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +#------------------------------------------------------------------------------------------------------------------------ + Python'da çeşitli konularda karşımıza "view" nesnesi kavramı çıkabilmektedir. View nesnesi ana bir nesnenin bir bölümünü + ya da tamamını temsil eden bir nesnedir. Ancak ana nesne üzerinde değişiklik yapıldığında bu view nesnesi bu değişikliği görür. + Bazı view nesneleri read only, bazıları read/write olabilmektedir. Eğer bir view nesnesi read/write biçimdeyse o view nesnesi + üzerinde değişiklilkler yapıldığında bundan ana nesne etkilenecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + keys ve values metotlarının bize verdiği dolaşılabilir nesneler "view" nesneleridir. Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> result = d.keys() + >>> a = list(result) + >>> a + [10, 20, 30, 40, 50] + >>> d[60] = 'sacit' + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'sacit'} + >>> a = list(result) + >>> a + [10, 20, 30, 40, 50, 60] + + Burada biz keys() metodunu çağırarak view nesnesini elde ettik. Daha sonra sözlüğe eleman ekledik. Sonra bu view nesnesini + dolaştığımızda elemanın ekli olduğunu gördük. +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +keys = d.keys() +a = list(keys) +print(a) # [10, 20, 30, 40, 50] + +values = d.values() +a = list(values) +print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +d[60] = 'nurettin' + +a = list(keys) +print(a) # [10, 20, 30, 40, 50, 60] + +a = list(values) +print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'nurettin'] + +#------------------------------------------------------------------------------------------------------------------------ + dict sınıfının items isimli metodu dolaşılabilir bir view nesnesi verir. Bu nesne dolaşıldığında anahtar-değer çiftleri + iki elemanlı demetler biçiminde elde edilmektedir. Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> items = d.items() + >>> items + dict_items([(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')]) + >>> a = list(items) + >>> a + [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] + >>> t = tuple(items) + >>> t + ((10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')) + >>> s = set(items) + >>> s + {(30, 'selami'), (40, 'ayşe'), (20, 'veli'), (50, 'fatma'), (10, 'ali')} + >>> d[60] = 'sacit' + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'sacit'} + >>> a = list(items) + >>> a + [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma'), (60, 'sacit')] + + Yine Python 3.6'ya kadar items metodundan elde edilen dolaşılabilir nesne dolaşıldığında elemanların hangi sırada elde + edileceğinin bir garantisi yoktu. Ancak Python 3.6 ile birlikte artık dolaşımdan elde edilen değerler onların sözlüğe eklenme + sırasına göre olmaktdır. +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +result = d.items() + +a = list(result) +print(a) # [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] + +d[60] = 'sacit' + +a = list(result) +print(a) # [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma'), (60, 'sacit')] + +#------------------------------------------------------------------------------------------------------------------------ + dict sınıfının clear isimli metodu sözlük içerisindeki anahtar-değer çiftlerinin hepsini siler. Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> id(d) + 2500835703104 + >>> d.clear() + >>> d + {} + >>> id(d) + 2500835703104 + +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +print(d, id(d)) # {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} 2449660834816 + +d.clear() + +print(d, id(d)) # {} 2449660834816 + +#------------------------------------------------------------------------------------------------------------------------ + dict sınıfının copy isimli metodu sözlüğün bir kopyasını bize verir. Tabii kopya çıkartma işlemi yine "sığ kopyalama (shallow copy)" + biçiminde yapılmaktadır. Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> id(d) + 2500835703104 + >>> d.clear() + >>> d + {} + >>> id(d) + 2500835703104 + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> id(d) + 2500835738496 + >>> k = d.copy() + >>> k + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> id(k) + 2500835703104 + >>> id(d[10]) + 2500835729264 + >>> id(k[10]) + 2500835729264 + + Burada copy metodu ile çıkartılan kopya farklı bir sçzlük nesnesidir. Ancak iki sözlük nesnesindeki değerlerin (ve anahtarlarınd da) + aynı olduğu görülmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +k = d.copy() + +print(d, id(d)) +print(k, id(k)) + +#------------------------------------------------------------------------------------------------------------------------ + Daha önce sözlükten anahtarı verip değer elde etmenin iki yolunu görmüştük: [...] operatörü ve get metodu. Aslında bu iş için + üçüncü bir metot daha vardır. O da setdefault isimli metottur. setdefault metodu bize her zaman anahtarın değerini verir. Eğer anahtarı bulamazsa + setdefault o anahtarı bizim verdiğimiz değerler sözlüğe ekler eklediği anahtarın değerini bize verir. Yani setdefault anahtarı bulnazsa + ikinci parametresiyle belirtilen değerle anahtarı eklemekteve bu değeri bize vermektedir. Metotta ikinci parametre için argüman + girilmeyebilir. Eğer ikinci argüman girilmezse ve setdefault anahtarı bulamazsa anahtar için değer olarak sözlüğe None eklemektedir. + setdefault anahtarı bulursa ikinci argümanı hiç kullanmaz. Bu ikinci argüman "eğer anahtar bulamazsa" kullanılmaktadır. + Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> val = d.setdefault(20, 'kaya') + >>> val + 'veli' + >>> val = d.setdefault(60, 'kaya') + >>> val + 'kaya' + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'kaya'} + >>> val = d.setdefault(30) + >>> val + 'selami' + >>> val = d.setdefault(70) + >>> print(val) + None + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'kaya', 70: None} + +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +val = d.setdefault(20) +print(val) # veli + +result = d.setdefault(60, 'sacit') +print(result) # sacit + +print(d) # {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma', 60: 'sacit'} + +#------------------------------------------------------------------------------------------------------------------------ + Biz daha önce sözlükten pop metodu ile anahtar-değer çiftini silmiştik. Ancak del deyimi ile de sözlükten bir anahtar-değer + çiftini silebiliriz. Bunu sağlamak için del deyiminde anahtar yine köşeli parantez içerisinde verilir. Örneğin: + + del d[10] + + Burada del deyimi d sözlüğündeki 10 anahtarını ve o anahtara karşı gelen değeri sözlükten silecektir. Ancak anahtar sözlükte yoksa + del deyimi exception (KeyError) oluşturmaktadır. Örneğin: + + >>> d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + >>> del d[40] + >>> d + {10: 'ali', 20: 'veli', 30: 'selami', 50: 'fatma'} + >>> del d[100] + Traceback (most recent call last): + File "", line 1, in + KeyError: 100 + +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +del d[40] + +print(d) # {10: 'ali', 20: 'veli', 30: 'selami', 50: 'fatma'} + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi string'ler Python'da str isimli bir sınıfla temsil edilmektedir. Yani biz string'leri tek tırnak, iki tırnak, + üç tek tırnak ya da üç çift tırnak ile yarattığımızda aslında str sınıfı türünden bir nesne yaratmış olmaktayız. Yine biz str + sınıfının "değiştirilemez (immutable)" bir sınıf olduğunu görmüştük. Biz string üzerinde onu yarattıktan sonra bir değişiklik + yapamamaktayız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tıpkı listeler gibi, demetler gibi string'ler de Python'da "sequence type" grubundadır. Yani adreta string'ler onları oluşturan + larakterden oluşan bir dizilim gibi düşünülebilir. + + String'lerin de karakterlerine tıpkı listelerde ve demetlerde olduğu gibi [...] operatörü ile erişebiliriz. Örneğin: + + >>> s = 'ankara' + >>> result = s[2] + >>> result + 'k' + >>> type(result) + + + Tabii Python'da Java, C# gibi bazı dillerde olduğu gibi tek bir karakteri temsil eden bir tür yoktur. Dolayısıyla biz Python'da + bir string'in belli bir indeksteki karakterini yine bir string olarak elde ederiz. + + String'ler değiştirilemez nesneler olduğu için biz bir string'in karakterlerini değiştiremeyiz. Örneğin: + + >>> s = 'ankara' + >>> c = s[2] + >>> c + 'k' + >>> s[2] = 'x' + Traceback (most recent call last): + File "", line 1, in + TypeError: 'str' object does not support item assignment + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir stirng'in karakter uzunluğu built-in len fonksiyonu ile elde edilebilir. Örneğin: + + >>> s = 'ankara' + >>> result = len(s) + >>> result + 6 + >>> k = '' + >>> result = len(k) + >>> result + 0 +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +result = len(s) +print(result) # 6 + +#------------------------------------------------------------------------------------------------------------------------ + Elemana erişimde yine negatif indeksler liste ve demetlerle aynı biçimde kullanılabilmektedir. Örneğin: + + >>> s = 'ankara' + >>> s[-1] + 'a' + >>> s[-2] + 'r' + + String'lerde dilimleme de tamamen listelerde ve demetler olduğu gibi yapılabilmektedir. Tabii bir string dilimlendiğinde + dilimleme işlemi sonucunda yine bir string elde edilmektedir. Örneğin: + + >>> s = 'ankara' + >>> s + 'ankara' + >>> result = s[2:6] + >>> result + 'kara' + >>> result = s[1:3] + >>> result + 'nk' + >>> result = s[2:-2] + >>> result + 'ka' + + Örneğin bir string'i ters çevirmek için yine aynı s[::-1] işlemini uygulayabiliriz: + + >>> s = 'ankara' + >>> k = s[::-1] + >>> s + 'ankara' + >>> k + 'arakna' + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +k = s[2:4] +print(k) # ka + +k = s[::-1] +print(k) # arakna + +k = s[-1] +print(k) # a + +#------------------------------------------------------------------------------------------------------------------------ + İki string '+' operatörü ile toplanabilir. Bu durumda yeni bir string nesnesi yaratılır. Bu yeni string nesnesi iki string'in + birleştirilmesinden oluşur. Örneğin: + + >>> s = 'ankara' + >>> k = 'izmir' + >>> result = s + k + >>> result + 'ankaraizmir' + + Java, C# gibi bazı dillerde bir string ile başka bir türden nesne toplanabilmektedir. Bu durumda o dillerde string olmayan tür + otomatik olarak string türüne dönüştürülüp string toplamı yapılmaktadır. Ancak Python'da bir özellik yoktur. Yani biz Python'da + örneğin bir string ile int bir değeri toplayamayız. (Halbuki bu işlem Java ve C# gibi bazı dillerde yapılabilmektedir.) + Bu tür durumlarda bizim açıkta diğer operand'ı str türüne dönüştürmemiz gerekir. Örneğin: + + >>> a = 10 + >>> s = 'a = ' + a + Traceback (most recent call last): + File "", line 1, in + TypeError: can only concatenate str (not "int") to str + >>> s = 'a = ' + str(a) + >>> s + 'a = 10' + +#------------------------------------------------------------------------------------------------------------------------ + +a = 'ankara' +b = 'izmir' + +c = a + b +print(c) # ankaraizmir + +#------------------------------------------------------------------------------------------------------------------------ + iki string toplandığında yeni bir string elde edilmektedir. Bu yeni string iki string'in uç uca eklenmesinden elde edilen yazıdan + oluşur. Ancak s += k gibi bir işlem mevcut s yazısının sonuna ekleme yapma anlamına gelmez. Çünkü string'ler değiştirilemez (immutable) + nesnelerdir. Dolayısıyla s += k tamamen s = s + k anlamına gelir. Anımsanacağı gibi bu durum değiştirilemez olan demetlerde de + böyleydi. Ancak listeler değiştirilebilir olduğu için orada += işlemi sona ekleme anlamına geliyordu. Örneğin: + + >>> s = 'ali' + >>> k = 'veli' + >>> id(s) + 1986218231280 + >>> id(k) + 1986218230640 + >>> s += k + >>> s + 'aliveli' + >>> id(s) + 1986218223088 + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' +k = 'izmir' + +result = s + k +print(result) # ankaraizmir + +print(id(s)) +s += k # s = s + k +print(id(s)) +print(s) # ankaraizmir + +#------------------------------------------------------------------------------------------------------------------------ + Bir string tıpkı listelerde ve demetler olduğu gibi "yineleme (repitition)" işlemine sokulabilir. Bu durumda string n defa + kendisiyle toplanmaktadır. Yine yineleme işleminde "çarpılan değer" 0 ya da negatif bir değerse boş string elde edilmektedir. + Örneğin: + + >>> s = '*' * 20 + >>> s + '********************' + >>> s = 'ali' * 5 + >>> s + 'alialialialiali' + >>> s = 'veli' * 0 + >>> s + '' + +#------------------------------------------------------------------------------------------------------------------------ + +s = '*' * 10 + +print(s) # ********** + +s = 'ali' * 3 +print(s) # alialiali + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte klavyeden bir yazı girilmektedir. Sonra o yazı üstte ve altta tireler olacak biçimde afiş gibi ekrana + yazdırılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +s = input('Bir yazı giriniz:') + +print('-' * len(s)) +print(s) +print('-' * len(s)) + +#------------------------------------------------------------------------------------------------------------------------ + Şimdi de str sınıfının metotları üzerinde duracağız. Ancak bu konuda bir noktaya dikkat çekmek istiyoruz. str sınıfının + "değiştirilemez" olduğunu yukarıda belirtmiştik. Bu durumda str sınıfının sanki yazıyı değiştirecekmiş gibi işlemler yapan + metotları aslında mevcut yazıyı değiştirmezler. Bize yeni bir yazı verirler. Zaten yaratılmış bir string nesnesi üzerinde + değişiklik yapmanın bir yolu yoktur. Biz aşağıda metotların sanki mevcut yazı üzerinde değişiklik yapıyormuş gibi bir + anlatım tarzı uygulayacağız. Aslında burada söylemek istediğimiz şey metodun değiştirilmiş yeni yazı ile geri döndüğüdür. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının capitalize isimli metodu parametresizdir. Bu metot bize aynı yazıdan yalnızca ilk harfi büyük olan yeni bir + yazı oluşturup onu vermektedir. Örneğin: + + >>> s = 'list sınıfı' + >>> k = s.capitalize() + >>> k + 'List sınıfı' + >>> s + 'list sınıfı' + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'bugün hava çok güzel' + +k = s.capitalize() +print(k) # Bugün hava çok güzel +print(s) # bugün hava çok güzelçok güzel + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının title isimli metodu yazının tüm sözcüklerinin ilk harfini büyük harf yapar (yani büyük harf yapılmış yazıyla + geri döner.) Bu metot da parametresizdir. Örneğin: + + >>> s = 'list sınıfı' + >>> k = s.title() + >>> k + 'List Sınıfı' + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'bugün hava çok güzel' + +k = s.title() +print(k) # Bugün Hava Çok Güzel + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 19. Ders - 13/06/2022-Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının center isimli metodu bir yazıyı belli bir genişlikte ortalar. Metodun birinci parametresi geri döndürülecek yazının + uzunluğunu belirtir. Yani yazı bu uzunluktaki yazının içerisinde ortalanacaktır. Eğer metot tek argümanla çağrılırsa yazının iki tarafındaki + boş alan boşluk karakteriyle doldurulur. Ancak metot iki argümanla da çağrılabilir. Bu durumda ikinci argüman tek karakterli bir string + olarak girilmek zorundadır. Bu ikinci argümanda belirtilen karakter iki tarafın doldurulacağı karakteri belirtir. Ancak eğer birinci + parametre ile belirtilen uzunluktan yazının uzunluğu çıkarıldığında tek sayı kalıyorsa bu durumda fazlalığın ne tarafa verileceği + konusunda "Python Standard Library Reference" içerisinde bir şey söylenmemiştir. Bu durum herhangi bir garantinin verilmediği + anlamına gelmektedir. Örneğin: + + >>> s = 'ankara' + >>> k = s.center(20) + >>> print(':' + k + ':') + : ankara : + >>> s = 'ali' + >>> k = s.center(7) + >>> print(':' + k + ':') + : ali : + >>> k = s.center(6) + >>> print(':' + k + ':') + : ali : + >>> k = s.center(20, '.') + >>> print(':' + k + ':') + :........ali.........: + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +k = s.center(20); +print(':' + k + ':') + +k = s.center(20, 'x'); +print(':' + k + ':') + +k = s.center(11, 'x'); +print(':' + k + ':') + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının find metodu bir yazı içerisinde bir yazıyı aramak ve yerini bulmak için kullanılmaktadır. Metot tek argümanla + çağrılırsa arama baştan sona kadar yapılmaktadır. Eğer yazı bulunursa find metodu bize yazının "ilk bulunduğu yerin" asıl yazıdaki + indeks numarasıyla geri döner. Eğer yazı bulunamazsa find -1 değeri ile geri dönmektdir. Örneğin: + + >>> s = 'ankara' + >>> result = s.find('k') + >>> result + 2 + >>> result = s.find('ar') + >>> result + 3 + >>> result = s.find('a') + >>> result + 0 + >>> result = s.find('x') + >>> result + -1 + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'bugün hava çok güzel, evet evet hava çok güzel' + +index = s.find('hava') +print(index) # 6 + +index = s.find('yarın') +print(index) # -1 + +#------------------------------------------------------------------------------------------------------------------------ + find metodu iki argümanlı da çağrılabilir. Bu durumda arama ikinci argümanla belirtilen indeksten başlatılır. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'bugün hava çok güzel, evet evet hava çok güzel' + +index = s.find('hava', 10) +print(index) # 32 + +#------------------------------------------------------------------------------------------------------------------------ + find metodu üç argümanlı da çağrılabilir. Bu durumda üçüncü argüman aramanın bitirileceği indeksi belirtir. Ancak bu indeks + aramaya dahil değildir. (Yani sanki önce dilimleme yapılıp bu dilimlemenin içerisinde find uygulanıyor gibi düşünebilirsiniz.) +#------------------------------------------------------------------------------------------------------------------------ + +s = 'bugün hava çok güzel, evet evet hava çok güzel' + +index = s.find('z', 10, 20) +print(index) # 17 + +#------------------------------------------------------------------------------------------------------------------------ + rfind isimli metot tamamen find metodu metodu gibidir. Ancak son bulunan yerin indeks numarasını verir. Başka bir deyişle + aramayı sondan başa doğru yapar. Yine metot iki ve üç argümanlı çağrılabilir. Argümanlar yine yazının başından itibaren indeks belirtir. + Yani bu duurmda ikinci ve üçüncü argümanlar sanki aranacak kısmın başını ve sonunu belirtiyor gibidir. (Yani sanki önce dilimleme + yapılıp bu dilimlemenin içerisinde rfind uygulanıyor gibi düşünebilirsiniz.) Örneğin: + + >>> s = 'adıyaman' + >>> pos = s.rfind('a', 1, 6) + >>> pos + 4 + >>> pos = s.rfind('a', 1) + >>> pos + 6 + >>> pos = s.rfind('a') + >>> pos + 6 + + Aşağıdaki örnekte Windows'tai bir yol ifadesinin hedefindeki dosya ismi elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +path = r'c:\windows\system\test.dll' + +index = path.rfind('\\') +fname = path[index + 1:] + +print(fname) # test.dll + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki programı girişi klavyeden isteyecek biçimde de değiştirebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +path = input('Bir yol ifadesi giriniz:') + +index = path.rfind('\\') +fname = path[index + 1:] + +print(fname) + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının find metodu ile aynı işlemleri yapan index isimli bir metodu, rfind metodu ile ile aynı işlemleri yapan + rindex isimli bir metodu da vardır. Yine index ve rindex de tek argümanla, iki argümanla ya da üç argümanla çağrılabilmektedir. + Bu bakımdan bunların find metotlarından bir farkı yoktur. index ve rindex metotlarının find ve rfind metotlarından tek farkları + başarısızlık durumunda exception index ve rindex metotlarının -1 ile geri dönmek yerine exception (ValueEroor) oluşturmasıdır. + Halbuki anımsanacağı üzere find ve rfind başarısızlık durumunda -1 değerine geri dönüyordu. Örneğin: + + >>> s = 'ankara' + >>> pos = s.index('a') + >>> pos + 0 + >>> pos = s.rindex('a') + >>> pos + 5 + >>> pos = s.rindex('x') + Traceback (most recent call last): + File "", line 1, in + ValueError: substring not found + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'bugün hava çok güzel' + +result = s.index('çok') +print(result) # 11 + +result = s.rindex('a') +k = s[result:] +print(k) # a çok güzel + +result = s.index('izmir') # exception oluşur +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + count metodu belli bir karakterin ya da yazının asıl yazı içerisinde kaç tane olduğu bilgisini bize verir. Örneğin: + + >>> s = 'ankara' + >>> result = s.count('a') + >>> result + 3 + >>> s = 'bugün hava çok güzel, evet hava çok güzel' + >>> result = s.count('hava') + >>> result + 2 + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'istanbul\'da iş buldum' + +result = s.count('bul') +print(result) # 2 + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının isxxx isimli bir grup metodu vardır. Bu metotlar yazının tüm karakterlerinin belirtilen koşulu sağlayıp sağlamadığına + bakmaktadır. Örneğin isalpha metodu yazının tüm karakterlerinin alfabetik karakter olup olmadığına bakar. Python 3'lü versüyonlardan + sonra tamamen UNICODE sisteme geçmiştir. Yani bu metotlar UNICODE tablodaki bütün dillerin harflerini bu testte dikkate alırlar. + Önemli isxxx metotları şunlardır: + + isalpha (alfabetik mi?) + isupper (büyük harf mi?) + islower (küçük harf mi?) + isspace (boşuk karakterlerinden mi?) + isalnum (alfanümerik karakter mi?) + ... + + Örneğin: + + >>> s = 'ankara' + >>> s.islower() + True + >>> s.isupper() + False + >>> s = '1234' + >>> s.isdigit() + True + >>> s = 'ali Ankara' + >>> s.islower() + False + >>> s = '1abc' + >>> s.isidentifier() + False + >>> s = 'abc1' + >>> s.isidentifier() + True + >>> s = ' \t \n ' + >>> s.isspace() + True + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ağrı-04' +k = 'AĞRIDağı' +m = '1293456789' +r = ' ' + +result = s.islower() +print(result) # True + +result = k.isupper() +print(result) # False + +result = m.isdigit() +print(result) # True + +result = k.isalpha() +print(result) # True + +result = r.isspace() +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının join isimli metodu argüman olarak dolaşılabilir bir nesne alır. Ancak bu dolaşılabilir nesne dolaşıldıkça + string'ler elde edilmelidir. join metodu bu dolaşılabilir nesnenin elemanlarını aralarına metodun çağrılmasında belirtilen yazıyı + ayraç yaparak bir yazı biçiminde birleştirir ve bize böyle bir yazı verir. Örneğin: + + >>> s = ', ' + >>> result = s.join(['ali', 'veli', 'selami']) + >>> result + 'ali, veli, selami' + >>> result = ', '.join(['ali', 'veli', 'selami']) + >>> result + 'ali, veli, selami' + >>> result = '\n'.join(['adana', 'adıyaman', 'afyon']) + >>> result + 'adana\nadıyaman\nafyon' + >>> print(result) + adana + adıyaman + afyon + >>> result = ''.join(['ali', 'veli', 'selami']) + >>> result + 'aliveliselami' + >>> result = '-'.join('ankara') + >>> result + 'a-n-k-a-r-a' + +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] +s = 'xxx' + +k = s.join(names) +print(k) # alixxxvelixxxselamixxxayşexxxfatma + +s = ', ' +k = s.join(names) +print(k) # ali, veli, selami, ayşe, fatma + +k = ' '.join(names) +print(k) # ali veli selami ayşe fatma + +k = ''.join(names) +print(k) # aliveliselamiayşefatma + +k = ' '.join('ankara') +print(k) # a n k a r a + +k = '\n'.join(names) +print(k) + +""" +ali +veli +selami +ayşe +fatma +""" + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının split metodu adeta join metodunun tersi gibidir. split yazının ayrıştırılacağı yazıyı parametre olarak alır. + Bu parametre yazıyı ayraç kabul ederek yazıyı parçalara ayırır ve bu parçalardan bir string listesi yapar bize o string listesini + verir. split parametresiz kullanılırsa tüm boşluk karakterlerini ayıraç kabul eder. Yani bir yazıyı tüm boşluklardan ayrıştırmak + için split parametresiz kullanılabilir. Örneğin: + + >>> s = 'ali, veli, selami, ayşe, fatma' + >>> result = s.split(', ') + >>> result + ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + >>> result = s.split(',') + >>> result + ['ali', ' veli', ' selami', ' ayşe', ' fatma'] + >>> result = s.split(' ') + >>> result + ['ali,', 'veli,', 'selami,', 'ayşe,', 'fatma'] + >>> result = s.split('xxx') + >>> result + ['ali, veli, selami, ayşe, fatma'] + >>> s = 'ali,,,veli,,,selami' + >>> result = s.split(',') + >>> result + ['ali', '', '', 'veli', '', '', 'selami'] + >>> s = 'ali veli \n\n\t \t selami \t ' + >>> result = s.split() + >>> result + ['ali', 'veli', 'selami'] + >>> + + join ile split metotlarının adresta ters işlemler yaptıklarına dikkat ediniz: + + >>> names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + >>> result = ', '.join(names).split(', ') + >>> result + ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ali, veli, selami, ayşe, fatma' + +a = s.split(',') +print(a) # ['ali', ' veli', ' selami', ' ayşe', ' fatma'] + +a = s.split(', ') +print(a) # ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +a = s.split(' ') +print(a) # ['ali,', 'veli,', 'selami,', 'ayşe,', 'fatma'] + +s = 'ali,,,veli,,,selami' +a = s.split(',') +print(a) # ['ali', '', '', 'veli', '', '', 'selami'] + +s = 'ali veli selami' +a = s.split(' ') +print(a) # ['ali', '', '', 'veli', '', '', 'selami'] + +s = 'ali veli \t\t\t selami' +a = s.split() +print(a) # ['ali', 'veli', 'selami'] + +s = 'ali, veli, selami' +a = s.split('xxx') +print(a) # ['ali, veli, selami'] + +s = 'ali, veli, selami, ayşe, fatma' +a = s.split(', ') +k = ', '.join(a) +print(k) # ali, veli, selami, ayşe, fatma + +d = '13/06/2022' +a = d.split('/') +print(a) # ['13', '06', '2022'] + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının strip isimli metodu argümansız çağrılırsa metot yazının başındaki ve sonundaki boşluk karakterlerini atar. + Bu işlev pek çok programama dilinde "trim" isimli fonksiyonlarla yapılmaktadır. strip fonksiyonu parametreli kullanılırsa + strip edilecek karakteri de belirlememize olanak sağlar. Burada argüman birden fazla karakter olarak girilirse tüm bu karakterler + bireysel olarak strip karakterleri olarak ele alınmaktadır. Örneğin: + + >>> s = ' Ali Serçe ' + >>> k = s.strip() + >>> print(':' + k + ':') + :Ali Serçe: + >>> s = '........Ali Serçe.......' + >>> k = s.strip('.') + >>> print(':' + k + ':') + :Ali Serçe: + >>> s = '.,.,.,.,.,Ali Serçe.,.,.,.,.' + >>> k = s.strip('.,') + >>> print(':' + k + ':') + :Ali Serçe: + >>> k = s.strip(',.') + >>> print(':' + k + ':') + :Ali Serçe: + +#------------------------------------------------------------------------------------------------------------------------ + +s = ' ankara ' + +k = s.strip() +print(':' + k + ':') # :ankara: + +s = 'xxxyyyyyyyyxxxankaraxxxxxxxxxxxxxyyyyyy' +k = s.strip('xy') +print(':' + k + ':') # :ankara: + +s = ' ankara izmir ' +k = s.strip() +print(':' + k + ':') # :ankara izmir: + +#------------------------------------------------------------------------------------------------------------------------ + strip metodunun lstrip ve rstrip isimli benzerleri vardır. lstrip yalnızca sol taraftaki strip karakterlerini atar, rstrip + ise yalnızca sağ taraftaki strip karakterlerini atar. (strip her iki taraftaki strip karakterlerini atmaktadır).Örneğin: + + >>> s = ' Ali Serçe ' + >>> k = s.lstrip() + >>> print(':' + k + ':') + :Ali Serçe : + >>> k = s.rstrip() + >>> print(':' + k + ':') + : Ali Serçe: + >>> s = '......,,,,,,,Ali Serçe,,,,,.....' + >>> k = s.lstrip('.,') + >>> print(':' + k + ':') + :Ali Serçe,,,,,.....: + >>> k = s.rstrip('.,') + >>> print(':' + k + ':') + :......,,,,,,,Ali Serçe: + +#------------------------------------------------------------------------------------------------------------------------ + +s = ' bugün hava çok güzel ' + +result = s.lstrip() +print(':' + result + ':') # :bugün hava çok güzel : + +result = s.rstrip() +print(':' + result + ':') # : bugün hava çok güzel: + +#------------------------------------------------------------------------------------------------------------------------ + 20. Ders - 15/06/2022-Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının partition isimli metodu yazı içinde aranacak bir yazıyı parametre olarak alır. Yazıyı bulursa üçlü bir + demete geri döner. Demetin ilk elemanı yazıda bulunan yerin sol tarafındaki yazıdan, sonraki elemanı bulunan yazının kendisinden ve + sonraki elemanı da bulunan yazının sağ tarafındaki yazıdan oluşacaktır. Örneğin: + + >>> s = 'aliveliselami' + >>> s + 'aliveliselami' + >>> result = s.partition('veli') + >>> result + ('ali', 'veli', 'selami') + >>> s = 'ali veli selami' + >>> s = ' ali veli selami ' + >>> result = s.partition('veli') + >>> result + (' ali ', 'veli', ' selami ') + >>> s = 'aliveliselami' + >>> result = s.partition('selami') + >>> result + ('aliveli', 'selami', '') + >>> result = s.partition('ali') + >>> result + ('', 'ali', 'veliselami') + >>> result = s.partition('xxx') + >>> result + ('aliveliselami', '', '') + + Burada eğer yazı asıl yazının başında bulunursa demetin ilk elemanının boş string olduğuna, sonunda bulunursa demetin son elemanın boş string + olduğuna dikkat ediniz. Eğer Yazı asıl yazıda bulunamazsa demetin ilk elemanı asıl yazıdan ikinci ve üçüncü elemanları boş + string'ten oluşacaktır. + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankaraizmiristanbul' + +t = s.partition('izmir') +print(t) # ('ankara', 'izmir', 'istanbul') + +left, center, right = s.partition('izmir') +print(left) # ankara +print(center) # izmir +print(right) # istanbul + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirtitğimiz gibi eğer partition parametresiyle belirtilen yazıyı bulamazsa bu durumda yine üçlü demete + geri döner. Demetin birinci elemanı tüm yazıdan, ikinci ve üçüncü elemanları boş string'lerden oluşur. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankaraizmiristanbul' + +t = s.partition('xxxx') +print(t) # ('ankaraizmiristanbul', '', '') + +t = s.partition('ankara') +print(t) # ('', 'ankara', 'izmiristanbul') + +t = s.partition('istanbul') +print(t) # ('ankaraizmir', 'istanbul', '') + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının replace isimli metodu bir yazı içerisinde belli bir yazıyı başka yazıyla yer değiştirir. Metodun iki parametresi vardır. + Birinci parametre aranacak yazıyı, ikinci parametre yer değiştirilecek yazıyı belirtir. Örneğin: + + >>> s = 'ali top at, ali ip atla' + >>> k = s.replace('ali', 'veli') + >>> k + 'veli top at, veli ip atla' + >>> s + 'ali top at, ali ip atla' + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'istanbul istanbul güzel istanbul' + +k = s.replace('istanbul', 'ankara') +print(k) # ankara ankara güzel ankara + +#------------------------------------------------------------------------------------------------------------------------ + replace metodunun isteğe bağlı bir üçüncü parametresi de vardır. Bu parametre girilecekse int bir sayı olarak girilmelidir. + Bu durumda yalnızca replace burada belirtilen sayıda değiştirme yapar. Örneğin: + + >>> s = 'istanbul istanbul güzel istanbul' + >>> k = s.replace('istanbul', 'ankara', 2) + >>> k + 'ankara ankara güzel istanbul' + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının startswith isimli metodu yazının parametresiyle belirtilen yazı ile başlayıp başlamadığını belirlemek için + kullanılmaktadır. Örneğin: + + >>> s = '- bu bir denemedir' + >>> result = s.startswith('-') + >>> result + True + >>> result = s.startswith('- ') + >>> result + True + >>> result = s.startswith('- xxx') + >>> result + False + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +result = s.startswith('an') +print(result) # True + +result = s.startswith('anka') +print(result) # True + +result = s.startswith('anki') +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının endswith isimli metodu yazının parametresiyle belirtilen yazı ile bitip bitmediğini belirlemek için kullanılmaktadır. + Örneğin: + + >>> s = 'bu bir denemedir...' + >>> result = s.endswith('...') + >>> result + True + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +result = s.endswith('ra') +print(result) # True + +result = s.endswith('kara') +print(result) # True + +result = s.endswith('an') +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + iki string toplandığında yeni bir string elde edilmektedir. Bu yeni string iki string'in uç uca eklenmesinden elde edilen yazıdan + oluşur. Ancak s += k gibi bir işlem mevcut s yazısının sonuna ekleme yapma anlamına gelmez. Çünkü string'ler değiştirilemez (immutable) + nesnelerdir. Dolayısıyla s += k tamamen s = s + k anlamına gelir. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' +k = 'izmir' + +result = s + k +print(result) # ankaraizmir + +print(id(s)) +s += k # s = s + k +print(id(s)) +print(s) # ankaraizmir + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının upper metodu yazıdaki küçük harfleri büyük harflere lower metodu da büük harfleri küçük harflere dönüştürür. + Ancak upper ve lower büyük ya da küçük harf olamayan karakterleri olduğu gibi bırakır. Örneğin: + + >>> s = 'AnKaRa-06' + >>> k = s.upper() + >>> k + 'ANKARA-06' + >>> k = s.lower() + >>> k + 'ankara-06' + + Türkçe'deki küçük harf 'i'nin UNICODE büyük harf karşılığı 'I' biçimindedir. Benzer biçimde büyük harf 'I' karakterinin de + küçük harf karşılığı 'i' biçimindedir. Bu da bazen Türkçe yazıla riçin istediğimiz sonucun elde edilmesini engeller. Örneğin: + + >>> s = 'iznik gölü' + >>> k = s.upper() + >>> k + 'IZNIK GÖLÜ' + + Bu problem şöyle çözülebilir: + + >>> s = 'iznik gölü' + >>> k = s.replace('i', 'İ').upper() + >>> k + 'İZNİK GÖLÜ' + +#------------------------------------------------------------------------------------------------------------------------ + +s = 'AğRı dAğI-04' + +k = s.upper() +print(k) # AĞRI DAĞI-04 + +k = s.lower() +print(k) # ağrı daği-04 + +#------------------------------------------------------------------------------------------------------------------------ + İki string >, >=, <, <=, == ve != operatörleriyle karşılaştırılabilir. Karşılaştırma leksikografik olarak yapılmaktadır. + Leksikografik karşılaştırma sözlükteki sıraya göre karşılaştırmadır. Yani iki yazıda karşılıklı karakterler aynı olduğu sürece + ilerlenir. İlk aynı olmayan karakterlerin UNICODE sıra numaralarına bakılır. Hangi karakterin UNICODE sıra numarası büyükse + o yazı diğerinden büyüktür. Tabii daha çok iki string'in eşit olması ya da eşit olmaması biçiminde karşılaştırmalar yapılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +password = 'maviay' +s = input('Enter password:') + +result = s == password +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Karşılaştırmanın Türkçe'ye göre değil UNICODE tabloya göre yapıldığına dikkat ediniz. Örneğin: + + >>> s = 'aysel' + >>> k = 'ayçe' + >>> result = s > k + >>> result + False + + Eğer karşılaştımr Türkçe karakterlere göre yapılsaydı "aysel" yazısı "ayçe" yazısından büyük olurdu. Ancak UNICODE + tabloda 'ç' karakteri 's' karakterinden daha ileride bulunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'aysel' +k = 'ayçe' + +result = s > k +print(result) # False 's'nin UNICODE numarası 'ç'nin UNICODE numarasından küçük + +#------------------------------------------------------------------------------------------------------------------------ + Tabii bir yazı diğer yazının ilk bölümü ile aynı ise bu durumda uzun olan yazı daha büyük olur. Yani örneğin 'aliye' yazısı + 'ali' yazısından daha büyüktür. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ali' +k = 'aliye' + +result = k > s +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + UNICODE tabloda önce büyük harfler sonra küçük harfler gelmektedir. Zaten UNICODE toblonun ilk 128 karakteri standart ASCII + tablosu ile aynıdır. Sonraki 128 karakteri ASCII Latin-1 Code Page'i ile aynıdır. Büyük harflerin tabloda önce gelmesi ASCII + tablosundan kaynaklanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ali' +k = 'Ali' + +result = s > k +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir karakterin UNOCODE tablodaki sıra numarası ord isimli built-in fonksiyonla elde edilebilmektedir. Örneğin: + + >>> result = ord('A') + >>> result + 65 + >>> result = ord('a') + >>> result + 97 + >>> result = ord('0') + >>> result + 48 + >>> result = ord('5') - ord('0') + >>> result + 5 + + ord fonksiyonuna biz tek karakterli bir string'i argüman olarak verebiliriz. Aksi takdirde exception (TypeError) oluşur. Örneğin: + + >>> result = ord('ali') + Traceback (most recent call last): + File "", line 1, in + TypeError: ord() expected a character, but string of length 3 found + + (Fonkisyonun birden fazla karakter için TypeError ile exception oluşturması ve exception mesajı biraz uygunsuz olmuştur. Zira Python'da + char diye bir tür yoktur. Dolayısıyla fonksiyonun TypeError yerine ValueError exception'ı oluşturması daha uygun gözükmektedir.) + + (C, C++, Java ve C# gibi dillerde bir karakteri tek tırnak içerisine aldığımızda zaten bu ifade o karakterin ilgili tablodaki + sıra numarasını belirtmektedir. Bu dillerde tek karakterden oluşan yazılar "char" isimli bir türdendir. Bu char türü de zaten + bu dillerde aritmektik işlemlere sokulabilmektedir. Dolayısıyla bu dillerde ord gibi bir fonksiyona gereksinim duyulmamaktadır. ) +#------------------------------------------------------------------------------------------------------------------------ + +s = input('Bir karakter giriniz:') +result = ord(s) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + ord fonksiyonunun yaptığı şeyin terci chr fonksiyonuyla yapılabilmektedir. chr fonksiyonu bizden int bir değeri parametre + olarak alır. Onun UNICODE tablodaki karakter karşılığını tek elemanlı bir string olarak verir. +#------------------------------------------------------------------------------------------------------------------------ + +n = int(input('Bir karakter numarası giriniz:')) +result = chr(n) +print(result) + +k = ord(result) +print(k) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki gibi üç değşiken olsun: + + a = 10 + b = 20 + c = 30 + + Burada a, b, c'nin değerlerini bilmediğimizi varsayalım. Ve aşağıdaki gibi bir yazıyı ekrana basmak isteyelim: + + a = 10, b = 20, c = 30 + + Bunu şimdiye kadarki bilgilerimizle ancak string'leri toplayıp yapabiliriz: + + a = 10 + b = 20 + c = 30 + + + s = 'a = ' + str(a) + ', b = ' + str(b) + ', c = ' + str(c) + print(s) + + Ya da örneğin: + + print('a = ' + str(a) + ', b = ' + str(b) + ', c = ' + str(c)) + + Bu tür yazımlara "formatlı yazım" denilmektedir. Formatlı azım işlemleri çok sık karşımıza çıkmaktadır. Ancak görüldüğü + gibi string toplamlarıyla formatlı yazıların oluşturulması oldukça zordur. Formatlı yazım için Python'da zaman içerisinde + üç değişik yöntem standart kütüphaneye ve dile dahil edilmiştir: + + 1) % operatör metodu yoluyla formatlı yazım + 2) str sınıfının format metoduyla formatlı yazım + 3) string enterpolasyonu yoluyla formatlı yazım + + String enterpolasyonu Python'a çok sonralı 3.6 versiyonuyla girmiştir. String enterpolasyonu diğer iki yönteme göre hem daha pratik hem de + daha hızlı bir formatlama sunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + str sınıfının format isimli metodu istenildiği kadar çok argüman alabilmektedir. Bu metot yazı içerisindeki {n} kalıbını yer tutucu + olarak kabul eder ve bu {n} yer tutucusu yerine format metodunun n'inci argümanın değerini yerleştirir. format metodunun ilk argümanı 0'ıncı + argümanıdır. Örneğin: + + >>> a = 10; b = 20; c = 30 + >>> s = 'a = {0}, b = {1}, c = {2}'.format(a, b, c) + >>> print(s) + a = 10, b = 20, c = 30 + + Burada {0} a ile, {1} b ile ve {2} c ile eşleştirilmiştir. Genellikle programcılar bu format metodunu doğrudan print + fonksiyonun içerisine yerleştirirler. Örneğin: + + >>> print('a = {0}, b = {1}, c = {2}'.format(a, b, c)) + a = 10, b = 20, c = 30 + + Format sentaksındaki sayıların peşi sıra gelme gibi bir zorunluluğu yoktur. Aörneğin: + + >>> print('a = {2}, b = {1}, c = {0}'.format(a, b, c)) + a = 30, b = 20, c = 10 + + Örneğin: + + >>> a = 10; b = 20; c = 30 + >>> print('{0}{1}{2}'.format(a, b, c)) + 102030 + + Uygunsuz durumlarda exception oluşmaktadır. Örneğin yer tutucu içerisindeki sayı argüman sayısından büyükse exception oluşur: + + >> print('a = {10}, b = {1}, c = {2}'.format(a, b, c)) + Traceback (most recent call last): + File "", line 1, in + IndexError: Replacement index 10 out of range for positional args tuple + + Ancak format metodundaki argümanlar string'te kullanılmamışsa bu durum bir soruna yol açmamaktadır. Örneğin: + + >>> a = 10; b = 20; c = 30 + >>> print('{0}'.format(a, b, c)) + 10 + +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 30 + +s = 'a = {0}, b = {1}, c = {2}' +k = s.format(a, b, c) +print(k) # a = 10, b = 20, c = 30 + +s = '{2} {0} {1}' +k = s.format(a, b, c) +print(k) # 30 10 20 + +print('a = {0}, b = {1}, c = {2}'.format(a, b, c)) # a = 10, b = 20, c = 30 + +#------------------------------------------------------------------------------------------------------------------------ + Aynı numaralı yer tutucu yazı içerisinde birden fazla kez kullanılabilir. Örneğin: + + >>> a = 10 + >>> b = 20 + >>> print('{0}, {1}, {0}'.format(a, b)) + 10, 20, 10 +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 30 + +print('{2} {2} {1} {0} {1}'.format(a, b, c)) # 30 30 20 10 20 + +#------------------------------------------------------------------------------------------------------------------------ + format metodundaki argüman türleri herhangi bir türden olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +city = 'Eskişehir' +plate = 26 +region = 'İç Anadolu' + +print('{0}-{1}-{2}'.format(city, plate, region)) # Eskişehir-26-İç Anadolu + +#------------------------------------------------------------------------------------------------------------------------ + Yer turucularda küme parantezinin içi boş bırakılabilir. Bu durumda her boş küme parantezi argümanlarla sırasıyla eşleştirilmektedir. + Ancak yazıdaki bir yer tutucu numaralı diğeri numarasız olamaz. Ya tüm yer tutucular numaralı olmalı ya da hiçbiri numaralı olmamalıdır. + Örneğin: + + >>> a = 10 + >>> b = 20 + >>> c = 30 + >>> print('a = {}, b = {}, c = {}'.format(a, b, c)) + a = 10, b = 20, c = 30 + >>> print('a = {}, b = {}, c = {}'.format(a, b, c)) + + Numarısız yer tutucu kullanımı genellikle tercih edilmektedir. Ancak tabii biçimde aynı argüman birden fazla kez kullanılamamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 +c = 20 + +print('a = {}, b = {}, c = {}'.format(a, b, c)) # a = 10, b = 20, c = 20 + +#------------------------------------------------------------------------------------------------------------------------ + fromat metodunda aslında daha ayrıntılı belirlemeler yapılabilmektedir. Biz şimdilik format metodunun bu ayrıntıları üzerinde + durmayacağız. Bunun için Internet'te çeşitli kaynaklara ya da Python Library Reference içerisindeki aşağıdaki kısma göz gezdirebilirsiniz: + + https://docs.python.org/3/reference/lexical_analysis.html#f-strings + + Ayrıntılı formatlama işlemi küme parantezi içerisinde ':' sentaksı ile yapılmaktadır. Örneğin: + + day = 8 + month = 5 + year = 2007 + + print('{0:02d}/{1:02d}/{2:04d}'.format(day, month, year)) # 08/05/2007 + + Örneğin biz değerleri değişik sayı sistemlerinde yazdırabiliriz: + + x = 100 + + print('{:X}'.format(x)) # 64 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 21. Ders - 20/06/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'a 3.6 ile birlikte "string enterpolasyonu" denilen bir özellik de eklenmiştir. Aslında string enterpolasyonları + bazı programlama dillerinde uzun süredir bulunmaktaydı. Artık yavaş yavaş pek çok programlama diline bu özellik sokulmuştur. + + String enterpolasyonu bir string'e ona yapışık bir 'f' ya da 'F' harfi önek getirilerek oluşturulmaktadır. String enterpolasyonunda + yapılanlar string sınıfının format metoduna benzerdir. Ancak string enterpolasyonunda küme parantezleri içerisinde bir + ifade bulunmak zorundadır. Yorumlayıcı akış sırasında string enterpolasyonu ile karşılaştığında bizzat kendisi bu ifadenin + değerini o anda hesaplayarak yer tutucu yerine yerleştirir. Örneğin: + + a = 10 + b = 20 + + print(f'a = {a}, b = {b}') # yorumlayıcı buradaki string'i 'a = 10, b = 20' haline dönüştürür. + + str sınıfının format metodu ismi üzerinde bir metottur. Yani bu metot yoluyla formatlama yapılacağı zaman formatlama + program çalışırken metot tarafından yapılmaktadır. Oysa string enterpolasyonları doğrudan yorumlayıcı tarafından program + çalıştırılırken işleme sokulur. Bu nedenle string enterpolasyonları hem daha kolay bir yazım sunmakta hem de göreli olarak daha hızlı + sonuç vermektedir. Dolayısıyla artık Python programcılları bu tarz formatlamalar için her zaman string enterpolasyonlarını tercih etmelidir. + Örneğin: + + >>> a = 10; b = 20; c = 30 + >>> s = f'a = {a}, b = {b * b}, c = {c}' + >>> s + 'a = 10, b = 400, c = 30' + >>> print(s) + a = 10, b = 400, c = 30 + +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +print(f'a = {a}, b = {b}') # a = 10, b = 20 + +#------------------------------------------------------------------------------------------------------------------------ + Tabii string enterpolasyonlarında küme parantezlerinin içerisinde aslında herhangi bir ifade olabilir. Örneğin: + + import math + + x = 10 + print(f"karekök {x} = {math.sqrt(x)}") # karekök 10 = 3.1622776601683795 +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +print(f"a'nın karesi' = {a * a}, b'nin karesi = {b * b}") # a'nın karesi' = 100, b'nin karesi = 400 +print(f"a'nın karekökü ' = {math.sqrt(a)}, b'nin karekökü = {math.sqrt(b)}") # a'nın karekökü ' = 3.1622776601683795, b'nin karekökü = 4.47213595499958 + +#------------------------------------------------------------------------------------------------------------------------ + string enterpolasyonunda küme parantezlerinin içerisnnde gerçek tırnaklar kullanılacaksa asıl string'in tırnağının bu tırnaklarla + karışması engellenmelidir. Örneğin: + + >>> s = f'{', '.join(a)}' + File "", line 1 + s = f'{', '.join(a)}' + ^ + SyntaxError: f-string: expecting '}' + + Burada f'{', '.join(a)}' biçimindeki string enterpolasyonu geçerli değildir. Çünkü küme parantezlerinin içerisinde de + tek tırnak karakteri kullanılmıştır. Tabii biz bu tür durumlarda string'in tırnaklarını çft tırnak ya da üç tırnak yaparak + sorunu çözebiliriz. Örneğin: + + >>> s = f"{', '.join(a)}" + >>> s + 'ali, veli, selami, ayşe, fatma' + + String enterpolasyonunda küme parantezleri içerisinde ters karakteri kullanılamamaktadır. Yani aşağıdaki gibi bir + string enterpolasyonu geçerli değildir: + + s = f'{\', \'.join(a)}' + +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +print(f"{', '.join(a)}") # ali, veli, selami, ayşe, fatma + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirtitğimiz gibi artık (3.6 ve sonrasında) string enterpolasyonu str sınıfının format metoduna göre tercih + edilmelidir. Ancak yine de seyrek bazı durumlarda str sınıfının format metodu daha kolay bir kullanım sunabilmektedir. Örneğin + string içerisinde aynı ifadenin değerinin birden fazla kez kullanılması durumunda string enterpolasyonunda küme parantezleri + içerisinde bu ifadenin tekrar tekrar yazılması gerekir. Halbuki str sınıfının format metodunda bu işlem daha az tuşa basılarka + yapılabilir. Örneğin: + + x = 10 + + print(f'{x * x}, {x * x}, {x * x}, {x * x}, {x * x}') + + print('{0}, {0}, {0}, {0}, {0}'.format(x * x)) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + String enterpolasyonundaki format kuralları str sınıfının format metodunndaki gibidir. Örneğin: + + day = 8 + month = 7 + year = 2009 + + print(f'{day:02d}/{month:02d}/{year:04d}') + +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +print(f'{a:<20}{b:>20}') # 10 20 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'ın 2'li versiyonlarında string formatlama string sınıfının % operatör metodu ile yapılıyordu. O zamanlar C Programlama + Dili bütün programlama dillerini etkisi altına almıştı ve bu biçimdeki formatlama aslında C Programlama Dilindeki "printf" gibi, + "scanf" fonksiyonlardan esinlenerek oluşturulmuştu. Bu çeşit formatlamada yer tutucular C'nin printf fonksiyonunda olduğu gibi + % karakterleriyle oluşturulmaktadır. Örneğin %d "int türünü 10'luk sistemde yazdır", %f "float türünü 10'luk sistemde yazdır anlamına gelmektedir. + Bu yontemde string içerisindeki % karakterleri % operatörünün sağındaki demetin elemanlarıyla eşleştirilmektedir. + Bu formatlama biçiminde yine printf fonksiyonundaki formatlama biçimleri ("%8.3f" gibi) burada da kullanılabilmektedir. Ancak artık bu yöntem Python'da + eski bir yöntem olarak değerlendirilmektedir. Yeni programlarda artık bu tarzda formatlama tercih edilmemektedir. Örneğin: + + >>> x = 10 + >>> y = 20 + >>> print('x = %d, y = %d' % (x, y)) + x = 10, y = 20 + + Bu biçimdeki formatlamada % operatörünün solunda string'in sağında ise bir demetin bulunduğuna dikkat ediniz. Örneğin: + + >>> import math + >>> x = 0.5 + >>> print('sin(%.1f) = %.2f' % (x, math.sin(x))) + sin(0.5) = 0.48 + + Bu formatlama biçiminde % operatörünün sağında ya tek bir ifade bulundurulur ya da bir demet biçiminde birden fazla ifade bulundurulur. + Yani başka bir deyişle tek bir değeri formatlamak için demet kullanmaya gerek yoktur. Ancak birden fazla değeri formatlamak için demet kullanmak + gerekir. Demet yerine liste ya da başka bir dolaşılabilir nesne kullanamyız. Örneğin: + + >>> a = 10 + >>> print('a = %d' % a) + a = 10 + + Burada string içerisinde tek bir yer tutucu olduğu için % operatörünün sağında demet yerine doğrudan bir ifade kullanılmıştır. + +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +b = 20 + +s = 'a = %d, b = %d' % (a, b) +print(s) # a = 10, b = 20 + +s = 'a = %x, b = %x' % (a, b) +print(s) # a = a, b = 14 + +c = 12.3456 + +print('%-10.3f' % c) # 12.346 + +#------------------------------------------------------------------------------------------------------------------------ + Daha önce de belirttiğimiz gibi Python'da genel bir silme semantiği için del isimli bir deyim bulundurulmuştur. + del deyiminin genel biçimi şöyledi: + + del + del a[ifade], ... + + del deyimi değişkenleri silebilmektedir. Burada silmek demekle sanki o değişken hiç yaratılmamış gibi bir durum oluşturma + kastedilmektedir. Yani bir değişkeni del deyimi ile sildikten sonra o değişkeni kulanırsak bu durum exception'a yol açar. Örneğin: + + >>> a = 10 + >>> print(a) + 10 + >>> del a + >>> print(a) + Traceback (most recent call last): + File "", line 1, in + NameError: name 'a' is not defined + + Tabii del deyiminde ',' atomu ile tek hamlede birden fazla değişkeni silebiliriz. Örneğin: + + >>> a = 10; b = 20 + >>> print(a, b) + 10 20 + >>> del a, b + >>> print(a, b) + Traceback (most recent call last): + File "", line 1, in + NameError: name 'a' is not defined + + Anımsanacağı gibi Python'da "değişken (variable)" kavramı ile "nesne (object)" kavramı farklı anlamlara gelmektedir. Python'da adres tutan yani bir nesneyi + gösteren isimlere değişken denmektedir. Değişkenin gösterdiği yere nesne denilmektedir. del deyimi değişkenleri siler. Nesnelerin silinmesi yorumlayıcı + tarafından "çöp toplama (gerbage collection)" mekanizması yoluyla otomatik olarak silinmektedir. Bir nesneyi gösteren hiçbir değişken kalmadıysa + Python'ın çöp toplama mekanizması devreye girer ve o nesne silinir. Örneğin: + + s = 'ankara' + s = 'izmir' + + Burada s değişkeni önce "ankara" yazısının bulunduğu nesneyi gösterirken sonra "istanbul" yazısının bulunduğu nesneyi gösterir hale gelmiştir. + İşte Python'ın çöp toplayıcı mekanizması devreye girip "ankara" yazısının bulunduğu nesneyi otomatik olarak silecektir. Her ne kadar del deyimi + nesneleri silmiyorsa da nesnelerin silinmesi için bir zemin de oluşturabilmektedir. Örneğin: + + s = 'ankara' + del s + + Burada del deyimi ile s değişkeni silindiği için artık "ankara" yazısına ilişkin nesneyi gösteren bir değişken kalmayacaktır. Dolayısıyla del deyimi + dolaylı da olsa bu nesnenin silinmesine de önayak olacaktır. + + del deyimi ile köşeli parantez ile erişebildiğimiz veri yapılarının elemanları da silinebilmektedir. Örneğin: + + >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100] + >>> del a[3] + >>> a + [1, 2, 3, 5, 6, 7, 8, 9, 0] + >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100] + >>> del a[2:5] + >>> a + [1, 2, 6, 7, 8, 9, 0] + + Tabii del ile biz ancak değiştirilebilir veri yapılarında silme yapabiliriz. Örneğin bir demet değiştirilemez olduğuna göre + demetin bir elemanını del deyimi ile silemeyiz. + + del deyimi ile bu biçimde silme yapılırken silme işleminin soldan sağa yürütüldüğüne dikkat ediniz. Örneğin: + + del a[2], a[5] + + Burada önce listenin 2'inci indeksli elemanı silinecektir. Bu silinme işleminden sonra 5'inci indeskli eleman bu silinmeden + sonra oluşan listenin 5'inci indeksli elemanı olacaktır. Yani: + + del a[2], a[5] + + işlemi ile aşağıdaki işlem eşdeğerdir: + + del a[2] + del a[5] + + Örneğin: + + >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + >>> del a[2], a[5] + >>> a + [1, 2, 4, 5, 6, 8, 9, 10] + + del deyimi ile dilimleme yapılarak da birden fazla eleman silinebilmektedir. Örneğin: + + >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100] + >>> del a[2:6] + >>> a + [1, 2, 7, 8, 9, 100] + + del deyimi ile sözlüklerden de eleman silinebilir. Çünkü sözlük elemanlarına da aslında köşeli parantez sentaksı ile erişilebilmektedir. + Tabii bu durumda sözlüğün yalnızca anahtarı ya da değeri değil anahtar-değer çifti silinir. Örneğin: + + >>> d = {'ali': 10, 'veli': 20, 'selami': 30} + >>> del d['veli'], d['selami'] + >>> d + {'ali': 10} +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da farklı türler her zaman == ve != operatöryle karşılaştırılabilirler. int, float ve bool türlerinin dışındaki + farklı türler == ve != operatörleriyle karşılaştırıldığında == operatörü ile karşılaştırma her zaman False, != operatör + ile karşılaştırma her zaman True değerini verir. Örneğin: + + >>> a = [1, 2, 3] + >>> b = (1, 2, 3) + >>> a == b + False + >>> a != b + True + >>> a == 10 + False + >>> b != 10 + True + + Burada a list türünden b de tuple türündendir. Dolayısıyla a == b hiçbir zaman eşit olamayacağı için False değerini vermiştir. + Benzer biçimde biz bir list ya da demet ile örneğin int, float gibi türleri == ve != operatörleriyle karşılaştırabiliriz. Bu durumda + == yine her zaman False değerini, != ise True değerini verir. + + Tabii daha önceden de belirttiğimiz gibi int, float ve bool türleri kendi aralarında tüm karşılaştırma operatörleriyle karşılaştırılabilir. + Bu durumda sayıların değerleri karşılaştırılmaktadır. Örneğin: + + >>> a = 10 + >>> b = 10.0 + >>> a == b + True + >>> a = 1.0 + >>> b = True + >>> a == b + True + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20] +b = (10, 20) + +result = a == b +print(result) # False + +result = a != b +print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + Ancak int, float ve bool türlerinin dışındaki türlerin >, <, >= ve <= operatörleriyle karşılaştırılması geçersiz bir durumdur ve exception'a yol açar. + Örneğin: + + >>> a = [10, 20] + >>> b = (10, 20) + >>> a > b + Traceback (most recent call last): + File "", line 1, in + TypeError: '>' not supported between instances of 'list' and 'tuple' + + Özetle int, float ve bool dışındaki farrklı türleri == ve != operatörleriyle karşılaştırabiliriz ancak >, <, >=, <= + operatörleriyle karşılaştıramayız. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi int, float ve bool türleri farklı tür olsalar da birbirleriyle karşılaştırılabilmektedir. Örneğin: + + >>> a = 10 + >>> b = 3.7 + >>> a > b + True + >>> c = True + >>> a > c + True + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İki listeyi ve iki demeti kendi aralarında tüm karşılaştırma operatörleriyle karşılaştırabiliriz. Bu durumda karşılaştırma + leksikografik biçimde yapılır. Yani karşılıklı elemanlar eşit olduğu sürece ilerlenir. İlk eşit olmayan elemanların durumlarına + bakılarak karar verilir. Örneğin: + + >>> a = [1, 2, 3, 4, 5] + >>> b = [1, 2, 3, 5, 1] + >>> a > b + False + >>> a < b + True + >>> a = [1, 2, 3, 4, 5] + >>> b = [1, 2, 3, 4, 5] + >>> a == b + True + >>> a = [1, 2, 3, 4, 5] + >>> b = [1, 2, 3, 4, 5, 6] + >>> a == b + False + >>> a > b + False + >>> a < b + True +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] +b = [10, 20, 40, 3, 5] + +result = a > b +print(result) # False + +result = b > a +print(result) # True + +result = a == b +print(result) # False + +#------------------------------------------------------------------------------------------------------------------------ + Tabii listeler ve demetler heterojen türlere sahip olabildiğine göre karşılıklı elemanların karşılaştırılabilir olması + gerekmektedir. Eğer karşılıklı elemanlar karşılaştırılabilir değilse exception oluşur. Örneğin: + + >>> a = [10, 'ali', 20] + >>> b = [10, 20, 30] + >>> a > b + Traceback (most recent call last): + File "", line 1, in + TypeError: '>' not supported between instances of 'str' and 'int' + + Aşağıdaki gibi bir durumda exception oluşmadığına dikkat ediniz: + + >>> a = [10, 'ali', 20] + >>> b = [20, 30, 40] + >>> a > b + False + + Çünkü burada str ile int türleri karşılaştırılmadan zaten karşılaştırmanın sonucu tespit edilebilmiştir. + + Tabii listenin elemanları liste ya da demet, demetin elemanları da liste ya da demet olabilir. Bu durumda karşılaştırma + özyinelemei biçimde yapılır. Örneğin: + + >>> a = [1, [2, 3, 4], 5] + >>> b = [1, [2, 3, 5], 2] + >>> a > b + False + >>> a < b + True + + Örneğin + + >>> a = [1, [2, 3, 4], 5] + >>> b = [1, (2, 3, 4), 2] + >>> a == b + False + >>> a > b + Traceback (most recent call last): + File "", line 1, in + TypeError: '>' not supported between instances of 'list' and 'tuple' + + İki sözlük nesnesi kendi aralarında yalızca == ve != operatörleriyle karşılaştırılabilir. Bu durumda iki sözlüğün + anahatar değer çiftlerinin bire bir aynı olmasına bakılmaktadır (yani yalnızca anahtarlara bakılmamaktadır). Örneğin: + + >>> d1 = {'ali': 10, 'veli': 20, 'selami': 30} + >>> d2 = {'ali': 10, 'veli': 20, 'selami': 50} + >>> d1 == d2 + False + >>> d1 > d2 + Traceback (most recent call last): + File "", line 1, in + TypeError: '>' not supported between instances of 'dict' and 'dict' + + İki sözlük nesneesinin >, <, >= ve <= operatörleriyle karşılaştırılmasının geçersiz olduğuna dikkat ediniz. + + Her ne kadar Python'da 3.7 ve sonrasında sözlük elemanlarında dolaşım sırasında bir sıra söz konusu olsa da == ve != + operatörlerinde bu biçimde lexikografik bir karşılaştırma yapılmamaktadır. Yani eşitlik koşulu için karşılıklı anahtar-değer + çiftlerinin eşitliğine değil tüm anahtar-değer çiftlerinin eşitliğine bakılmaktadır. Örneğin: + + >>> d = {'ali': 30, 'veli': 30, 'selami': 40} + >>> k = {'ali': 30, 'selami': 40, 'veli': 30} + >>> d == k + True + + İki küme == ve != operatörleriyle karşılaştırılabilir. Bu durumda iki kümenin elemanlarının tamamen aynı olup olmadığına + bakılmaktadır. Örneğin: + + >>> s = {'ali', 100, 'veli', 120} + >>> k = {100, 120, 'veli', 'ali'} + >>> s == k + True + >>> s != k + False + + Kümelerde >, >=, <, <= operatörlerinin küme işlemi yaptığını anımsayınız. (Kümelerde '<' öz alt küme, '>' öz üst küme, '<=' alt küme ve + '>=' üst küme işlemlerini yapmaktadır.) Örneğin: + + >>> a = {10, 'ali', 'veli'} + >>> b = {10, 'veli'} + >>> a > b + True + >>> b < a + True + >>> c = set() + >>> c < a + True + +#------------------------------------------------------------------------------------------------------------------------ + 22. Ders 22/06/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir satırın başından itibaren ilk boşluk olmayan karaktere kadarki SPACE sayısına "girinti düzeyi (indent level)" denilmektedir. + Bir satırın girinti düzeyi eğer satırın başında hep SPACE karakteri varsa SPACE karakterlerinin toplamı olarak hesaplanır. + Ancak satırın başına SPACE ve TAB karakterleri varsa hesap şöyle yapılmaktadır: Her TAB karakteri görüldüğünde bu TAB karakterlerinin + o zamana kadarki SPACE sayısını 8'in katlarına tamamlamak için n tane SPACE anlamına geldiği kabul edilir. Bu biçimdeki SPACE'lerin sayısına + bakılır. Örneğin: + + SPACE SPACE SPACE + + Bu satırın girinti düzeyi 3'tür. Örneğin: + + SPACE TAB SPACE + + Bu satırın girinti düzeyi 9'dur. Çünkü: + + SPACE (1) TAB (7) SPACE + + Örneğin: + + SPACE TAB TAB SPACE + + Bu satırın girinti düzeyi 17'dir. Çünkü: + + SPACE (1) TAB (7) TAB (8) SPACE (1) + + Örneğin aşağıdaki iki satırın girinti düzeyleri editörde bu iki satır alt alta gözükmüyor olsa bile Python yorumlayıcısına göre aynıdır: + + SPACE SPACE SPACE SPACE SPACE SPACE SPACE SPACE SPACE + SPACE TAB SPACE + + Buradaki hesabın editörün tab ayarıyla ilgili olmadığına dikkat ediniz. Yukarıdaki iki satır editörde alt alta gözükmüyor olsa bile + aynı girinti düzeyine sahiptir. + + Daha önceden de belirttiğimiz gibi Python editörlerinin hemen hepsi zaten TAB yerine n tane SPACE karakterini kaynak koda yazmaktadır. + Bu durumda yorumlayıcı zaten artık TAB karakterlerini görmez yalnızca SPACE karakterlerini görür. Girinti düzeyi hesabı oldukça kolay olur. + Tabii Python editörünüzün TAB yerine SPACE basması yönünde bir zounluluk yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python programlarında girinti düzeyi 0 ile başlamak zorundadır. Yani Python programları en soldaki sütuna dayalı bir biçimde yazılır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir programlama dilinde çalıştırma birimlerine "deyim (statement)" denilmektedir. Yani "imperative dillerde" programın çalışması + deyimlerin çalıştırılmasıyla sağlanmaktadır. Python yorumlayıcısı yukarıdan aşağıya doğru deyimleri tek tek sırasıyla çalıştırır. + C, C++, Java ve C# gibi pek çok dilde program main ya da Main isimli özel bir fonksiyondan çalışmaya başlamaktadır. Ancak Python gibi + bazı dillerde program kaynak kodun tepesinden çalışmaya başlar. + + Her programlama dilinde deyimlerin sınıflandırılması o dile özgü bir biçimde yapılır. Python'da deyimler iki gruba + ayrılmaktadır: + + 1) Basit Deyimler (Simple Statements) + 2) Bileşik Deyimler (Compound Statements) + + Python'da basit deyimler tek parçadan oluşan deyimlerdir. Bu deyimler tek satır üzerine yazılabilmektedir. Ancak bileşik deyimler + birden fazla parçadan oluşan ve tek satır üzerinde yazılamayan ya da yazılmak zorunda olmayan deyimlerdir. Basit deyimlerin en önemli + özellikleri birden fazla basit deyimin aynı satıra aralarına ';' atomu getirilerek yazılabilmesidir. + Python'da basit deyimlerin de bileşik deyimlerin de çeşitli biçimler vardır. Örneğin if gibi for gibi deyimler Python'da + bileşik deyimler grubundandır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'daki en yalın basit deyim "ifadesel deyimdir (expression statment)". Bir ifade programın parçası halinde + bulundurulduğunda bu artık deyim olur. Bu tür deyimlere ifadesel deyim denir. Örneğin: + + print(z) + input('Bir yazı giriniz:') + + Bu iki deyim ifadesel deyimdir. + + Biz bir ifadeyi bir satıra yazdığımızda artık o ifadenin artık bir deyim heline geldiğine dikkat ediniz. Örneğin: + + print(a + b) + + Tabii ifadeler başka deyimlerin parçalarını da oluşturabilmektedir. + + Python'da atama işlemi aslında bir operatör değil bir deyim statüsündedir. Buna "atama deyimi (assignment statement)" denilmektedir. Örneğin: + + x = 10 + y = 20 + z = x + y + print(z) + + Burada ilk üç deyim atama deyimidir. Son deyim ifadesel deyimdir. Ancak bunların hepsi kategorik olarak basit deyim statüsündedir. + + Biz basit deyimleri istersek tek satır üzerinde onların aralarına ';' getirerek de yazabiliriz: + + x = 10; y = 20; z = x + y; print(z) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programalama dillerinde bileşik deyimlerin parçalarını oluşturan deyimlerin nasıl yazılması gerektiğine yönelik çeşitli biçimler + bulunmaktadır. Örneğin C, C++, Java ve C# gibi diller bloklama tekniğini kullanırlar. Bu dillerde bileşik deyimin parçalarını oluşturan + deyimler bloklanarak belirlenir. Bloklama bu dillerde küme parantezleri ile yapılmaktadır. Örneğin: + + if (koşul) { + ifade1; + ifade2; + ifade3; + } + ifade4; + + Ancak Python'da bloklama için girinti düzeyi tekniği kullanılmaktadır. Bir bileşik deyimin içindeki deyimlerin neler olduğu o deyimlerin + girinti düzeylerine bakılarak belirlenir. Örneğin: + + if koşul: + ifade1 + ifade2 + ifade3 + ifade4 + + Burada ifade1, ifade2 ve ifade3 aynı girinti düzeyine sahip olduğu için bileşik deyimin parçalarını oluşturmaktadır. + + Bir bileşik deyimin içindeki deyimlerin aynı girinti düzeyine sahip olması gerekir. Örneğin: + + if koşul: + ifade1; + ifade2 + ifade3 + ifade4 + + Bu yazım geçersizdir. Tabii yukarıda açıkladığımız girinti düzeyi kuralına göre editörde alt alta gözükmediği halde iki satır + aslında aynı girinti düzeyine sahip olabilir. Ancak yukarıda da belirttiğimiz gibi Python editörünüz eğer TAB yerine belli miktar + SPACE basıyorsa zaten o koddaki bileşik deyimin parçaları hep aynı hizada görüntülenecektir. + + Python'da bileşik deyimler genel olarak bir anahtar sözcükle başlatılır, sonra bunu bir ya da birden fazla ifade izler sonra da bir ':' + atomu bulundurulur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bileşik deyimin anahtar sözcüğü ile aynı satıra yazılan birden fazla basit deyime ya da farklı satırlara + aynı girinti düzeyiyle yazılan birden fazla deyime "suit" denilmektedir. Pek çok deyim bir suit içermek durumundadır. + Örneğin: + + while ifade: ifade1; ifade2; ifade3 + + Burada ifade1, ifade2 ve ifade3 bir suit belirtmektedir. Örneğin: + + while ifade: + ifade1 + ifade2 + ifade3 + + Burada da ifade1, ifade2 ve ifade3 bir suite belirtir. Örneğin: + + while ifade: ifade1 + ifade2 + ifade3 + + Burada ifade1, ifade2 ve ifade3 bir suit belirtmemektedir. Çünkü suit ya deyimin anahtar sözcüğü ile aynı satıra yazılmış + birden fazla deyimi belirtir ya da farklı satırlara aynı girinti düzeyiyle yazılmış birden fazla deyimi belirtir. Örneğin: + + while ifade: + ifade1 + ifade2; ifade3 + + Bu bir suite belirtmektedir. Görüldüğü gigi farklı satırlara yazılmıi deyimlerdeki satırlarda birden fazla basit deyim + olabilmektedir. Örneğin: + + while ifade: + ifade1 + ifade2 + ifade3 + + Burada ifade1, ifade2 ve ifade3 bir suit belirtmez. Çünkü aynı girinti düzeyine sahip değildir. + + Suit'i oluşturan "farklı satırlardaki aynı girinti düzeyine sahip deyimler" arasında boş satırlar olabilir. Bu suit + kuralını bozmaz. Örneğin: + + while ifade: + ifade1 + ifade2 + + ifade3 + ifade4 + + Buradaki suit yazımı geçerlidir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + if deyimi bir ifadenin doğru ya da yanlış olması durumuna göre farklı işlemlerini yapılmasını sağlayan en temel bileşik deyimdir. + Genel biçimi şöyledir: + + if : + [else: ] + + Buradan da görüldüğü gibi if deyiminin doğruysa ve yanlışsa kısmında ayrı suite'ler vardır. if deyimin else kısmı hiç olmayabilir. + Aşağıdaki if deyimi yazım bakımındna geçerlidir: + + if ifade: + ifade1 + ifade2 + else: + ifade3 + ifade4 + + else anahtar sözcüğü ile if anahtar sözüğünün aynı girinti düzeyine saip olması gerekmektedir. Aşağıdaki if deyimi de geçerlidir: + + if ifade: ifade1; ifade + else: + ifade3 + ifade4 + + Aşağıdaki if deyimi de geçerlidir: + + if ifade: + ifade1; ifade2 + else: + ifade3 + ifade4 + + Yukarıda da belirttiğimiz gibi Python'da genel olarak bileşik deyimlerde deyimin kontrol kısmının sonunda ':' atomu bulunmaktadır. + Bu atom ifadeye yapışık olmak zorunda değildir. Ancak aynı satırda bulunmak zorundadır. Örneğin: + + if ifade : + ifade1; ifade2 + else : ifade3; ifade4 + + Bu if deyimi geçerlidir. Tabii bu yazım iyi bir görüntüye sahip değildir. + + if deyiminde kısmındaki deyimin doğruysa kısmındaki suit'in girinti düzeyiyle yanlışsa kısmındaki suit'in girinti düzeyinin + aynı olması gerekmez. Örneğin: + + if ifade: + ifade1 + ifade2 + else: + ifade3 + ifade4 + + Bu yazım geçerlidir. Tabii böyle bir yazımda okunabilirlik bozuk olacaktır. Bu nedenle Python programcıları her ne kadar + zorunlu olmasa da if deyiminin doğruysa ve yanlışsa kısımlarındaki suit'leri aynı girinti düzeyine sahip olacak biçimde yazarlar. + Örneğin: + + if ifade: + ifade1 + ifade2 + else: + ifade3 + ifade4 + + Suit "aynı satır üzerine yazılmış birden fazla basit deyim ya da farklı staırlara yazılmış olan aynı girinti düzeyine sahip + birden fazla deyim" anlamına geldiğine göre aşağıdaki if sentaksı geçersizdir: + + if ifade: ifade1 + ifade2 + else: ifade3; ifade4 + + Çünkü if deyiminin doğurysa ksmındaki deyimler suite oluşturmamaktadır. Örneğin: + + if ifade: + ifade1 + ifade2 + else: ifade3 + + Bu yazım da yanlıştır. Çünkü if deyimin doğruysa kısmındaki ifadeler suit belirtmemektedir. Örneğin: + + if ifade: ifade1; ifade2 + else: ifade3; ifade4 + ifade5 + + Bu yazım doğrudur. Burada ifade5 if deyiminin else kısmında değildir. Yani buradaki ifade5 if deyimi ile aynı girinti düzeyine + sahip olduğu için artık if deyimi içerisinde değildir. Örneğin: + + if ifade: + ifade1: + ifade2: + else: + ifade3 + ifade4 + ifade5 + + Bu yazım geçersizdir. Çünkü burada ifade5 if dışında değildir. ifade5'in if dışında olabilmesi için if ile aynı hizada yazılması gerekirdi. + Öte yandan ifade5 else kısmındaki suite yazımına da aykırıdır. + + Örneğin: + + if ifade: ifade1 else: ifade2 + + Bu if deyimi geçersizdir. else anahtar sözcüğü kesinlikle if ile aynı girinti düzeyinde yazılmak zorundadır. + + if deyimi şöyle çalışmaktadır: Önce yorumlayıcı if anahtar sözcüğünün yanındaki ifadenin türüne bakar. Eğer bu ifade bool türden değilse onu + bool türüne dönüştürür. Bu dönüşümden sonra eğer bu ifade True ise yaşnızca if deyiminin doğruysa kısmındaki suite çalıştırılır eğer bu ifade False + ise yalnızca if deyiminin yanlışsa kısmındaki suit çalıştırılır. Böylece if deyiminin çalışması biter. Program if deyiminden sonraki deyiminden + çalışmaya devam eder. Örneğin: + + x = 10 + if x > 0: + print('ankara') + print('izmir') + else: + print('adana') + print('eskişehir') + + Burada toplam üç deyim vardır. İlk deyim atama deyimidir ve basit bir deyimdir. İkinci deyim if deyimidir. Üçüncü deyim 'eskişehir'i ekrana yazdıran + basit deyimdir. x > 0 ifadesi zaten bool türdendir. Bu ifade True ise 'ankara' ve 'izmir' yazıları False ise 'adana' yazısı ekrana çıkacaktır. + 'eskişehir' yazısını basan basit deyim if içerisinde değildir. Çünkü bu deyim if ile aynı girinti düzeyinde yazılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +x = int(input('Bir syaı giriniz:')) + +if x % 2 == 0: + print('çift') +else: + print('tek') + +print('program sonlanıyor') + +#------------------------------------------------------------------------------------------------------------------------ + Boş bir listenin, demetin ve strin'in bool türüne False olarak, dolu bir listenin, string'in ve demetin True olarak + dönüştürüldüğünü anımsayınız. Örneğin: + + a = [] + if a: + ifade1 + ifade2 + else: + ifade + ifade4 + + Bu if deyimi yanlışsa kısmındna sapacaktır. Benzer biçimde sıfırdan farklı int ve float değerlerin bool türüne True olarak, + 0 değerinin False olarak dönüştürüldüğünü de anımsayınız. Örneğin: + + a = int(input('Bir değer giriniz:)) + if a: + print('girilen değer sıfır değil') + else: + print('girilen değer sıfır') +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıda ikinci derece bir denklemin köklerini bulan program verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +a = float(input('a:')) +b = float(input('b:')) +c = float(input('c:')) + +delta = b ** 2 - 4 * a * c +if delta < 0: + print('kök yok') +else: + x1 = (-b + math.sqrt(delta)) / (2 * a) + x2 = (-b - math.sqrt(delta)) / (2 * a) + +print(f'x1 = {x1}, x2 = {x2}') + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki genel biçimden de görüldüğü gibi if deyiminin else kısmı olmayabilir. if deyiminin doğruysa kısmındaki + suite'ten sonra else anahtar sözcüğü gelmezse derleyici bunun "else kısmı olmayan bir if" olduğunu kabul eder. Örneğin: + + if ifade: + ifade1 + ifade2 + ifade3 + + Burada toplamda iki deyim vardır. if deyimi ve ifade3'ten oluşan basit deyim. Burada if deyimin else kısmı bulunmamaktadır. + ifade3 if deyimin dışında olan başka bir basit deyimdir. Örneğin: + + a = int(input('Bir sayı giriniz:')) + + if a > 0: + print('Pozitif') + print('Program sonlanıyor') + + Burada if deyiminin else kısmı bulundurulmamıştır. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir sayı giriniz:')) + +if a > 0: + print('Pozitif') +print('Program sonlanıyor') + +#------------------------------------------------------------------------------------------------------------------------ + C, C++, Java ve C# gibi dillerde if anahtar sözcüğündne sonra if ifadeesinin parantezler içerisinde olması zorunludur. + Örneğin: + + if (a > 0) + ifade1; + else + ifade2; + + Pekiyi nedne Python'da böyle bir zorunluluk yoktur? Bu dillerde bu zorunluluğun olmasının asıl nedeni if ifadesi ile + if'in doğruysa kısmının ayrıştırılmasını sağlanmak istenmesidir. Örneğin: + + if a > 10 b = 20; + + O dillerde burada hangi ifadenin if deyiminin kontrol ifadesi olduğu hangi ifadenin if deyiminin doğruysa kısmını oluşturduğu + anlaşılamamaktadır. Halbuki: + + if (a > 10) b = 20; + + Artık her şey çok açıktır. Python'da zaten if deyiminin kontrol ifadesinden sonra ':' atomu gelmek zorunda olduğu için + bu ayrıştırma parantezlere gereksinim duyulmadan da yapılabilmektedir. Örneğin: + + if a > b: b = 20 + + Swift gibi Kotlin gibi yeni bazı dillerde küme parantezleri dozunlu turulduğu için o dillerde de kontrol ifadesinin + paranteze alınması gerekmemiştir. Örneğin: + + if a > 10 { + b = 10; + } + + Tabii Python'da biz istersek if deyiminin kontrol ifadesinde de parantezleri kullanabiliriz. Ne de olsa parantezler + her ifadede kullanılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + if deyiminin doğruysa kısmında başka bir if deyimi olabilir. Örneğin: + + ifade1 + if ifade2: + ifade3 + if ifade4: + ifade5 + ifade6 + else: + ifade7 + ifade8 + else: + ifade9 + ifade10 + + Burada toplamda dışarıdan bakıldığında üç deyim vardır. Dıştaki if deyiminin doğruysa kısmında başka bir if deyimid vardır. + + C/C++, Java ve C# gibi dillerde hizalamanın bir önemi olmadığı için programcılar "dangling else" denilen bir durumda bazen hata yapabilmektedir. + "Dangling else" iki if için tek bir else bulunması durumudur. Ancak Python'da girinti düzeylerine bakılarak else'in aslında hangi if deyiminin + else kısmı olduğu zaten anlaşılmaktadır. Örrneğin: + + if ifade1: + if ifade2: + ifade3 + ifade4 + else: + ifade5 + + Buradaki else dıştaki if'in else kısmıdır. Fakat örneğin: + + if ifade1: + if ifade2: + ifade3 + ifade4 + else: + ifade5 + + Buradaki else artık içteki if'in else kısmıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte üç sayının en büyüğü bulunmuştur. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Sayı giriniz:')) +b = int(input('Sayı giriniz:')) +c = int(input('Sayı giriniz:')) + +if a > b: + if a > c: + print(a) + else: + print(c) +else: + if b > c: + print(b) + else: + print(c) + +#------------------------------------------------------------------------------------------------------------------------ + Bir grup koşuldan bir tanesi doğru iken diğerlerinin doğru olma olasılığı yoksa bu koşullara "ayrık (discrete) koşullar" + denilmektedir. Örneğin: + + a > 0 + a < 0 + + Bu iki koşul ayrıktır. Örneğin: + + a > 0 + a < 0 + a == 0 + + Bu üç koşul da ayrıktır. Örneğin: + + a > 0 + a > 5 + + Bu koşullar ayrık değildir. Örneğin: + + a == 1 + a == 2 + a == 3 + + Buradaki üç koşul da ayrıktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programalamda ayrık koşulların ayrı if'lerle ifade edilmesi kötü bir tekniktir. Örneğin: + + if a > 0: + ifade1 + if a < 0: + ifade2 + if a == 0: + ifade3 + + Burada a > 0 durumunda gereksiz bir biçimde diğer iki karşılaştırma da yapılacaktır. Bu karşılaştırmalar önemsiz olsa da bir bilgisayar zamanının + harcanmasına yol açmaktadır. Örneğin: + + if a == 1: + ifade1 + if a == 2: + iafde2 + if a == 3: + ifade3 + + Bu da kötü bir tekniktir. Ayrık koşulların else-if biçiminde organize edilmesi iyi bir tekniktir. Örneğin: + + if a == 1: + ifade1 + else: + if a == 2: + ifade2 + else: + if a == 3: + ifade3 +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) + +if a == 1: + print('bir') +else: + if a == 2: + print('iki') + else: + if a == 3: + print('üç') + else: + if a == 4: + print('dört') + else: + if a == 5: + print('beş') + else: + print('hiçbiri') + +print('program sonlanıyor') + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örnekte görüldüğü gibi else-if merdivenleri kaydırmalı bir biçimde yazılması gerektiği için görüntüyü bozmaktadır. + Bu görüntünün bozulmaması ve bu tür else-if merdivenlerinin daha kolay yazılması için Python'da if deyimin bir parçası olarak + elif kısmı da bulundurulmuştur. elif (else if'ten kısaltma) tamamen else if anlamına gelmektedir. Ancak yazımda if ile aynı + girinti düzeyine sahip olmak zorundadır. elif bir ifadeyle birlikte bulundurulur. elif kısımlarından sonra son bir else kısmı da + bulundurulabilmektedir. Örneğin: + + if a == 1: + print('bir') + elif a == 2: + print('iki) + elif a == 3: + print('üç') + elif a == 4: + print('dört') + elif a == 5: + print('beş') + else: + print('hiçbiri') + +#------------------------------------------------------------------------------------------------------------------------ +a = int(input('Bir değer giriniz:')) + +if a == 1: + print('bir') +elif a == 2: + print('iki') +elif a == 3: + print('üç') +elif a == 4: + print('dört') +elif a == 5: + print('beş') +else: + print('program sonlanıyor') + +#------------------------------------------------------------------------------------------------------------------------ + Bir program parçasının yinelemeli olarak çalıştırılmasını sağlayan deyimlere döngü deyimleri denilmektedir. Python + döngü deyimleri bakımından minimalist biçimde tasarlanmıştır. Python'da iki döngü deyimi vardır: + + 1) while Döngüleri + 2) for Döngüleri + + C/C++, Java ve C# gibi dillerde while döngüleri kendi aralarında "kontrolün başta yapıldığı while döngüleri" ve "kontrolün + sonda yapıldığı while döngüleri (do-while)" olmak üzere ikiye ayrılmaktadır. Ancak Python'da kontrolün sonda yapıldığı + while döngüleri yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 23. Ders - 27/06/2022-Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + while döngüsünün genel biçimi şöyledir: + + while : + + while anahtar sözcüğünden sonra bir ifade ve sonra da ':' atomu bulunmak zorundadır. Bu ':' atomundan sonra da bir "suite" + bulunmalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + while döngüsü şöyle çalışır: Yorumlayıcı önce while anahtar sözcüğünün sağındaki ifadenin türüne bakar. Eğer bu ifade + bool türden değilse onu bool türüne dönüştürür. Sonra ifadenin değerine bakar. Eğer ifade True ise suit'i oluşturan deyimleri + çalıştırır ve başa döner. Eğer ifade False ise döngü deyiminin çalışması sonlandırılır. Program döngü deyiminden sonraki deyimle + çalışmaya devam eder. Yani while döngüleri "bir ifade doğru olduğu sürece yinelenen" döngülerdir. +#------------------------------------------------------------------------------------------------------------------------ + +i = 0 +while i < 10: + print(i) + i += 1 + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte bir listenin elemanları while döngüsü ile yazdırılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +i = 0 +while i < len(a): + print(a[i]) + i += 1 + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte de bir listenin elemanları while döngüsü ile sondan başa doğru yazdırılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +i = len(a) - 1 +while i >= 0: + print(a[i], end=' ') + i -= 1 + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte 1'den klavyeden girilen sayıya kadar olan tamsayıların toplamı yazdırılmıştır. (Tabii aslında bu toplam + (n * (n + 1)) / 2 biçimindedir.) +#------------------------------------------------------------------------------------------------------------------------ + +n = int(input('Bir sayı giriniz:')) + +i = 1 +total = 0 + +while i <= n: + total += i + i += 1 + +print(total) + +#------------------------------------------------------------------------------------------------------------------------ + while ifadesi bool türünden değilse bool türüne dönüştürülmektedir. Boş bir string'in ya da listenin bool türüne False olarak, + dolu bir string'in ya da listenin True olarak dönüştürüldüğünü anımsayınız. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +while s: + print(s) + s = s[:-1] + +#------------------------------------------------------------------------------------------------------------------------ + int ya da float bir değerin bool türüne sıfır dışı ise True olarak sıfır ise False olarak dönüştürüldüğünü anımsayınız. + Dolaysıyla aşağıdkai örnekte i değeri 0'a geldiğinde döngüden çıkılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +i = 10 +while i: + print(i) + i -= 1 + +#------------------------------------------------------------------------------------------------------------------------ + Python'da atama operatörünün değer üretmediğini bunun için dile Walrus operatörünün eklendiğini anımsayınız. Aşağıdaki + while döngüsünde girilen sayının karesi ekrana yazdırılmaktadır. Ancak 0 girildiğinde döngü sonlandırılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +while (n := int(input('Bir değer giriniz:'))) != 0: + print(n * n) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki kalıp çokça kullanılmaktadır. Pekiyi Walrus operatörü olmasaydı (yani eski Python sürümlerinde çalışyor olsaydık) + aynı şeyi nasıl yapabilirdik? Aşağıdaki gibi bir çözüm akla gelebilir: +#------------------------------------------------------------------------------------------------------------------------ + +val = int(input('Bir değer giriniz:')) + +while val != 0: + print(val * val) + val = int(input('Bir değer giriniz:')) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki çözümde rahatsız edici bir nokta vardır. input satırı kodda takrarlanmaktadır. Bu tekrarın aşağıdaki gibi + engellenmesi de aklınıza gelebilir: + + val = 1 + while val != 0: + val = int(input('Bir değer giriniz:')) + print(val * val) + + Ancak kodun bu biçimde organize edilmesi yine okunabilirliği bozmaktadır. Koda bakan kişi ne yapılmak istendiğini + hemen anlayamayacaktır. O halde Walrus'lu kalıp gerçekten iyi işlev görmektedir: + + while (val := int(input('Bir değer giriniz:'))) != 0: + print(val * val) + + Aslında zaten int değerler 0'dan farklıysa bool türüne True olarak dönüştürüldüğüne göre ifadedeki karşılaştırma + kısmını da atabiliriz: + + while val := int(input('Bir değer giriniz:')): + print(val * val) + + Fakat bu yazım biçimi yerine açıkça karşılaştırma yapmak kodu daha anlaşılabilir hale getirecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İç içe döngüler söz konusu olabilir. Yani bir suit içerisinde başka bir döngü de olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +n = int(input('Bir sayı giriniz:')) + +i = 1 +while i <= n: + k = 0 + while k < i: + print('*', end='') + k += 1 + print() + i += 1 + +#------------------------------------------------------------------------------------------------------------------------ + Tabii yukarıdaki örneği aslında "yineleme (repitition)" ile çok daha kolay aşağıdaki gibi yapabilirdik. +#------------------------------------------------------------------------------------------------------------------------ + +n = int(input('Bir sayı giriniz:')) + +i = 1 +while i <= n: + print('*' * i) + i += 1 + +#------------------------------------------------------------------------------------------------------------------------ + Bilindiği gibi her sayı asal sayıların çarpımı biçiminde yazılabilir. Buna sayının "asal çarpanları (prime factors)" + denilmektedir. Örneğin 100'ün asal çarpanları 2 * 2 * 5 * 5 biçimindedir. Bir sayının asal çarpanlarını bulmak + "düz mantıkla (brute force)" oldukça kolaydır. Sayı 1 olmadığı sürece döngüye sokulur. Sayının bölüneceği sayı bir değişkende + tutulur ve başlangıçta bu değişkenin içerisinde 2 vardır. Sayı bu sayıya bölündüğü sürece bölünerek ilerlenir. Sayı bu sayıya bölünmezse + sonra sayı ile devam edilir. Aşağıda bu algoritma uygulanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +n = int(input('Bir sayı giriniz:')) + +divider = 2 +while n != 1: + if n % divider == 0: + print(divider, end=' ') + n //= divider + else: + divider += 1 +print() + + +#------------------------------------------------------------------------------------------------------------------------ + Bir problemi kesin çözüme götüren adımlar topluluğuna "algoritma (algorithm)" denilmektedir. Tabii söz konusu problemi + çözebilecek birdenfazla alternatif algoritmalar söz konusu olabilir. Bu durumda bunların kıyaslanması gerekebilmektedir. + Algoritmaları kıyaslamak için en çok kullanılan iki temel ölçüt "hız" ve "kaynak kullanımı"dır. Ancak default ölüçüt + olarak her zaman "hız" kullanılmaktadır. Alternatif algoritmaların hızlarını karşılaştırmak da o kadar olmayabilir. + Çünkü algoritmalar listeler gibi birtakım veri yapıları üzerinde işlemler yapıyor olabilir. Onların çalışma hızı o + veri yapısının dağılımına göre değişebilir. Örneğin falanca sort algoritması filance biçimdeki dizilerde daha hızlı çalışırken + başka dizilerde daha yavaş çalışıyor olabilir. Algoritmaların kıyaslanması sürecine genel olarak "algoritma analizi" + denilmektedir. + + Bazen algortimaların kesin sonucu bulması mevcut bilgisayarlarla seneler sürüyor olabilir. Bu tür durumlarda kesin sonucu bulmak + yerine "nispeten tatimin edici" bir sonucun bulunması da arzu edilebilmektedir. Genellikle kesin çözümü bulmayan + bu tür adımlar topluluğuna "sezgisel yöntemler (heuristics)" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da sonsuz döngü oluşturmak için while anahtar sözcüğünün yanındaki ifadeyi True yapabiliriz. Örneğin: + + while True: + ... + + Burada koşul her zaman sağlanacağına göre bu döngü de her zaman yinelenecektir. Tabii bir döngünün sürekli dönmesi çoğu kez + arzu edilen bir durum değildir. Şüphesiz sonsuz döngü için while yanındaki ifadeyi sıfır dışı herhangi bir sayı biçiminde de + yazabiliri. Örneğin: + + while 1: + .... + + Ancak sonsuz döngü oluşturmak için while anahtar sözcüğünün yanındaki ifadeyi açıkça True yapmak daha anlaşılabilir + bir durum oluşturur. Bir Python programı sonsuz döngüye girmişse komut o programı biz komut satırından çalıştırmışsak + Ctrl+C tuşları ile programı zorla durdurabiliriz. IDE'lerde durdurmak için fare durdurma simgesine tıklamak gerekir. + Aşağıdaki programı çalıştırıp zorla sonlandırmayı deneyiniz. +#------------------------------------------------------------------------------------------------------------------------ + +i = 0 +while True: + print(i) + i += 1 + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belrttiğimiz gibi Python'da C/C++, Java ve C# gibi dillerde bulunan "kontrolün sonda yapıldığı while + döngüleri (do-while döngüleri)" yoktur. Bazı algortimik problemlerde kontrolün sonra yapıldığı while döngüleri + işlemleri kolaylaştırabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + for döngülerinin genel biçimi şöyledir: + + for in : + + Python'da for döngüleri C/C++, Java ve C#'taki belli bir miktar yinelemeye yol açan tarzda for döngüleri değildir. Python'daki + for döngüleri diğer bazı dillerdeki "foreach" döngüleri gibidir. Yani Python'da for döngüleri aslında dolaşılabilir nesneleri + dolaşan bir döngüdür. + + for döngüleri şöyle çalışır: Döngünün her yinelenmesinde dolaşılabilir nesnenin sıradaki elemanı döngü değişkenine + atanır. (Tabii aslında onun adresi döngü değişkenine atanır.) Sonra suit çalıştırılır. Böylece elemanlar sırasıyla tek tek + for döngüsündeki değişkene atanmış olur. Dolaşılabilir nesne bittiğinde dolaşım da biter. + Örneğin: + + a = [10, 20, 30, 40, 50] + for x in a: + print(x) + + Burada döngünün her yinelenmesinde x'e listenin elemanları (yani o elemanların adresleri) atanacaktır. Liste elemanları + bittiğinde döngü bitecektir. Tabii aslında döngü değişkenine atama bir adres atamasıdır. Yani yukarıdaki örnekte aslında + x' sırasıyla 10, 20, 30, 40, 50 nesnelerinin adresleri atanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +for x in a: + print(x, end=' ') + +print() + +i = 0 +while i < len(a): + print(id(a[i])) + i += 1 + +print() + +for x in a: + print(id(x)) + +#------------------------------------------------------------------------------------------------------------------------ + Biz for döngüleri ile dolaşılabilir olan her nesneyi dolaşabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +s = 'ankara' + +for c in s: + print(c) + +#------------------------------------------------------------------------------------------------------------------------ + Bir sözlük dolaşıldığında sözlüğün anahtarlarının elde edildiğini anımsayınız. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 58, 'veli': 87, 'selami': 59, 'ayşe': 98, 'fatma': 81} + +for x in d: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir olmayan nesnelere for döngüüsyle dolaşılamazlar. Örneğin: + + a = 10 + + for x in a: + print(x) + + Burada in anahtar sözcüğünün yanındaki ifade int türdendir. int türü de dolaşılabilir bir tür değildir. Dolayısıyla burada + bir error oluşaacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz Python'da C/C++, Java ve C# gibi dillerdeki for döngülerini range fonksiyonu ile emüle edebiliriz. Örneğin: + + for i in range(n): + ... + + Bu for döngüsü C'deki aşağıdaki for döngüsüne benzemektedir: + + for (i = 0; i < n; ++i) { + ... + } +#------------------------------------------------------------------------------------------------------------------------ + +for i in range(10): + print(i, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Bir listeyi dolaşmak isteyelim. for döngüsü ile iki alternatif yöntem kullanılabilir. Birincisi indeks yoluyla dolaşma + olabilir. Örneğin: + + a = [10, 20, 30, 40, 50] + + for i in range(len(a)): + print(a[i], end=' ') + +İkincisi liste dolaşılabilir bir nesne olduğuna göre onu doğrudna dolaşmak olabilir. Örneğin: + + for x in a: + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +for i in range(len(a)): + print(a[i], end=' ') + +print() + +for x in a: + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + for döngüsünde biz döngü değişkenini değiştirmekle dolaşılabilir nesneyi değiştirmiş olmayız. Örneğin bir listenin + elemanlarını onların kareleriyle değiştirmek isteyelim. Bunu şöyle yapamayız: + + for x in a: + x = x ** 2 + + Çünkü biz burada x'i değiştiriyoruz, a'da bir değişiklik yapmıyoruz. Bu tür durumlarda indeks yoluyla listeye erişmek gerekir. Örneğin: + + for i in range(len(a)): + a[i] = a[i] ** 2 + + Burada gerçekten liste elemenaı değiştirilmiştir. + +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +for x in a: + x = x ** 2 + +print(a) + +for i in range(len(a)): + a[i] = a[i] ** 2 + +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii dolaşılabilir bir nesne de dolaşılabilir nesnelerden oluşuyor olabilir. Örneğin bir demet listesi söz konusu olabilir. + Bu durumda biz listeyi dolaştığımızda demetleri elde ederiz: + + a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] + + for t in a: + print(t) + + Burada her dolaşımda elde edilen demetler de iç bir for döngüsüyle dolaşılabilir. Örneğin: + + for t in a: + for k in t: + print(k, end=' ') + print() + +#------------------------------------------------------------------------------------------------------------------------ + +a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] + +for t in a: + print(t) + +print() + +for t in a: + for k in t: + print(k, end=' ') + print() + +#------------------------------------------------------------------------------------------------------------------------ + Eğer for döngüsü ile dolaşımda dolaştıkça elde edilen nesneler de dolaşılabilir ise biz "açım (unpacking)" işlemini for + dönüsünün içerisinde yapabiliriz. Örneğin: + + a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] + + for t in a: + x, y = t + ... + + Bu işlemi for içerisinde de tek hamlede de yapabiliriz: + + for x, y in a: + ... + + Tabii bu açım işleminde köşeli parantezler ya da normal parantezler de kullanılabilir. Ancak gereksizdir. Örneğin: + + for [x, y] in a: + print(x, y) + + for (x, y) in a: + print(x, y) + +#------------------------------------------------------------------------------------------------------------------------ + +a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] + +for t in a: + x, y = t + print(x, y) + +print() + +for x, y in a: + print(x, y) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da _ aslında normal bir değişken ismi olarak kullanılabilmektedir. Ancak genel olarak programcılare _ ismini + "ben bu değişkenle ilgilenmiyorumi yalnızca bu değişkeni yer turucu olarak kullanıyorum" anlamında kullanmaktadır. Örneğin biz + 1000 kere dönen bir döngüde bir şeyler yapacak olalım. Ancak döngü değişkenini hiç döngü içerisinde kullanmayacak olalım. Bu durumu + vurgulamak için döngü değişkeni için _ ismini kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +for _ in range(1000): + pass + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerin de dolaşılabilir olduğunu, sözlükleri dolaştıkça anahtarların elde edildiğini anımsayınız. Tabii elimizde + bir anahtar varsa biz onun değerini de eld edebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +for key in d: + print(key, d[key]) + +#------------------------------------------------------------------------------------------------------------------------ + dict sınıfının items isimli metodunuın bize dolaşılabilir bir nesne verdiğini, o nesne dolaşıldığında da anahtar değer + çiftlerindne oluşan demetlerin elde edildiğini anımsayınız. O zaman biz sözlükteki anahtar değer çiftlerini şöyle de elde edebiliriz: + + for key, value in d: + print(keyi value) +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +for t in d.items(): + print(t) + +print() + +for key, value in d.items(): + print(key, value) + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir bir nesneyi tersten dolaşabilir miyiz? Bunun yanıtı "o nesneye ilişkin sınıfı yazan kişi buna izin verdiyse + dolaşabiliriz" biçimindedir. Tabii str gibi, list gibi, tuple gibi nesneler ters indekslemeyle ya da ters çeviren dilimleme + ile terten dolaşılabilirler. Bu türlere daha önceden de belirttiğimiz gibi "sequence type" denilmektedir. Pekiyi ya + dolaşılabilir nesne bir "sequence type" değilse? + + Aşağıda bir listenin tersten dolaşımına bir örnek görüyorsunuz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +for i in range(len(a) - 1, -1, -1): + print(a[i], end=' ') + +print() + +for x in a[::-1]: + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Tersten dolaşmanın daha genel bir biçimi daha önce görmüş olduğumuz "reversed" fonksiyonunu kullanmaktadır. Anımsanacağı gibi + reversed fonksiyonu bize tersten dolaşılabilir bir nesne vermekteydi. O zaman tersten dolaşımı aşağıdaki gibi de yapabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +for x in reversed(a): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Tabii biz her dolaşılabilir nesneyi reversed fonksiyonuna sokamayız. Bir dolaşılabilir nesnenin tersten dolaşılabilirliği o sınıfı + yazanlar tarafından sağlanmaktadır. Örneğin kümeler dolaşılabilir nesnelerdir. Kümeler dolaşıldığında elemanların hangi sırada elde + edileceğinin bir garantisi yoktur. Ancak kümeler tersten dolaşılabilir değildir. Daha önceden de belirtildiği gibi Python'da sonradan + 3.7 versiyonu ile birlikte sözlükler dolaşılırken elemanların onların ekleme sırasına göre dolaşılması garanti edilmiştir. + Bu eklemeden sonra artık sözlükler de reversed fonksiyonu ile tersten dolaşılabilir hale gelmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 5: 'selami'} + +d[100] = 'sacit' +d[300] = 'fehmi' + +for x in reversed(d): + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + 24. Ders - 29-06/2022-Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + break deyimi yalnızca döngüler içerisinde kullanılabilir. Genel biçimi şöyledir: + + break + + Programın akışı break anahtar sözcüğünü gördüğünde döngü deyimi sonlandırılır ve akış döngüden sonraki ilk deyim ile devam eder. + Tabii genellikle break bir koşul ile birlikte kullanılır. Örneğin: + + while True: + ... + if koşul: + break + ... + +#------------------------------------------------------------------------------------------------------------------------ + +while True: + val = float(input('Bir sayı giriniz:')) + if val == 0: + break + print(val * val) + +#------------------------------------------------------------------------------------------------------------------------ + İç içe döngülerde break yalnızca kendi döngüsünü sonlandırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +for i in range(10): + for k in range(10): + print(f'({i},{k})') + s = input() + if s == 'quit': + break + if s == 'quit': + break + +#------------------------------------------------------------------------------------------------------------------------ + Programın akışı continue anahtar sözcüğünü gördüğünde döngü başa sararak yeni bir yinelemeye geçilir. break deyimi + döngünün kendisini sonlandırırken continue deyimi döngü içerisindeki suite'i sonlandırmaktadır. Yani akış continue + deyimini gördüğünde sanki suit bitmiş de yeni bir yineleme yapılıyormuş gibi bir etki oluşur. Ancak continue + deyimine break deyiminden çok daha seyrek gereksinim duyulmaktadır. + + Aşağıdaki örnekte çift sayılarda programın akışı continue deyimini gördüğü için döngü başa sarmaktadır. Dolayısıyla ekrana + yalnızca tek sayılar basılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +for i in range(10): + if i % 2 == 0: + continue + print(i) + +#------------------------------------------------------------------------------------------------------------------------ + continue deyimi genellikle döngü içerisindeki uzun if bloklarını elimine etmek için kullanılmaktadır. Örneğin: + + while True: + a = int(input()): + if a % 7 == 0: + ... + ... + ... + ... + ... + + Bu döngü continue ile daha analaşılabilir bir hale getirilebilir: + + while True: + a = int(input()): + if a % 7 != 0: + continue + ... + ... + ... + ... + ... + + Aşağıdaki örnekte bir komut satırı (ya da REPL) ortamı yaratılmak istenmiştir. Programda bir prompt eşliğinde bir komut + girilmesi istenmektedir. Eğer komut önceden belirlenen komutlardan bir tanesi ise ilgili işlem yapılmakta, değilse ekrana + "invalid command!" yazısı çıkartılmaktadır. Eğer kişi yalnızca boşluklar girerek ENTER tuşuna basarsa bu durumda + continue ile akış başa sardırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +while True: + print('CSD>', end='') + cmd = input().strip() + if cmd == '': + continue + if cmd == 'quit': + break + if cmd == 'copy': + print('copy command executes...') + elif cmd == 'rename': + print('rename command executes...') + elif cmd == 'dir': + print('dir command executes...') + else: + print('invalid command!') + +#------------------------------------------------------------------------------------------------------------------------ + Bir sayının basamak sayısını bulmak için bir döngü içerisinde sayı sürekli 10'a bölünebilir ve bu işlemin kaç kere yapıldığı + hesaplanabilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir sayı giriniz:')) + +count = 0 +while a: + count += 1 + a //= 10 + +print(count) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii aslında sayının basamak sayısı hiç döngü kullanmadan 10 tabanına göre logaritmasının 1 fazlası olarak elde edilebilir. + 10 tabanına göre logaritma hesabı yapan standart math modülünde log10 isimli bir fonksiyon vardır. Bu fonksiyon sonucu bize + float olarak verir. float sayıyı noktadan kurtarmak için int dönüştürmesi yapılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +a = int(input('Bir sayı giriniz:')) +result = int(math.log10(a)) + 1 +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Alternatif bir çözüm de int türden sayıyı önce yazıya yani str türüne dönüştürmek sonra uzun karakter uzunluğuna bakmak + olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +val = int(input('Bir sayı giriniz:')) + +result = len(str(val)) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da pass deyimi "boş deyim" anlamına gelmektedir. Normalde if gibi while gibi for gibi deyimlerde bir suite bulundurmak + zorunludur. Ancak bazen biz içi boş bir suit oluşturmak isteyebiliriz. İşte o zaman pass deyimi kullanılır. Örneğin: + + for _ in range(10000000): + pass + + Burada programcı for döngüsünün içerisinde bir şey yapmak istememiştir. Burada programcının belki de amacı akışı bir süre + meşgul bir döngüde bekletmektir. C, C++, Java ve C# gibi dillerde ';' boş deyim anlamına gelir. Ancak Python'da böyle + bir kullanım yoktur.. +#------------------------------------------------------------------------------------------------------------------------ + +for i in range(10): + print(i) + for k in range(10000000): + pass + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin biz kullanıcıyı pozitif bir değer girme konusunda zorlamak istebiliriz. Bunu bir while döngüsü içerisinde Walrus + operatörü kullanarak sağlayabiliriz. Ancak bu durumda gerçekten de döngünün içerisinde yapacak bir şey kalmamaktadır. + O halde biz de pass deyimi ile sentaksın gereksinim duyduğu deyimi suite yerine yerleştirebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +while (val := int(input('Pozitif bir sayı giriniz:'))) <= 0: + pass + +print(val ** 0.5) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örneklerde kişiler sanki pass deyimi gereksizmiş gibi bir izlenime kapılabilmektedir. continue bir deyim olduğuna + göre ve sonraki yinelemeye geçmeyi sağladığına göre yukarıdaki örneklerde pass yerine continue yerleştirsek aynı durumu + oluşturabiliriz. Örneğin: + + for _ in range(10000000): + pass + + ile aşağıdaki deyim arasında işleyiş bakımından gerçekten bir fark yoktur: + + for _ in range(10000000): + continue + + Ancak continue deyimi pass deyiminin yerini tutamamaktadır. Çğnkü continue deyimi yalnız döngülerde kullanılabilmektedir. + Halbuki pass deyimi normal bir deyimdir. Her yerde kullanılabilir. Örneğin ilerleyen zamanlarda fonksiyonlar konusunu + göreceğiz. Fonksiyonlar da suit içermektedir. Dolayısıyla örneğin içi boş bir fonksiyon yazmak için continue deyimini kullanamayız. + Fakat pass deyimini kullanabiliriz: + + def foo(): + pass +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii aslında pass deyimi suite içerisinde kullanılmak zorunda değildir. Herhangi bir yerde kullanılabilir. pass deyimi + hiçbir işleme yol açmaz. Şüphesiz herhangi bir yerde gereksiz pass kullanımı da kötü bir tekniktir. Örneğin: + + x = 10 + pass # gerek yok, kötü teknik! + print(x) + pass # gerek yok, kötü teknik! + print(x * x) + +#------------------------------------------------------------------------------------------------------------------------ + pass deyimi ileride görecek olduğumuz fonksiyonlar, sınıflar gibi deyimlerde de boş suit oluşturmak amacıyla da + kullanılabilmektedir: + + def foo(): # içi boş fonksiyon + pass + + class Sample: # içi boş sınıf + pass +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 25. Ders 04/07/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da diğer bazı dillerdeki gibi bir switch deyimi yoktu. Ancak Python'un 3.10 versiyonuyla birlikte dile "match" + adı altında switch benzeri bir deyim eklenmiştir. Kursun yapıldığı sırada Python'un son versiyonu 3.11.4'tür. Dolayısıyla + aşağıda açıklanacak olan match deyiminin çalıştırılması için yorumlayıcınızın versiyonuna dikkat ediniz. Eğer Anaconda dağıtımında + çalışıyorsanız. "Envrionment" sekmesinden yeni bir "Virtual Envirionment" yaratıp Python'un en güncel versiyonunu yükleyebilirsiniz. + + Python'daki match deyimi diğer bazı dillerdeki switch deyimininden daha ayrıntılı ve daha yeteneklidir. match deyimi + "Structural Pattern Matching" başlığı altında PEP-634 dokmanında açıklanmıştır. PEP-636 dokğmanı da eğtici (tutorial) biçimde + hazırlanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + match deyiminin genel biçimi şöyledir: + + match : + case : + + case : + + case : + + .... + + match deyimi şöyle çalışmaktadır: Yorumlayıcı Önce match anahtar sözcüğünün yanındaki ifadeyi inceler. Sonra case + bölümlerini sırasıyla gözden geçirir. match ifadesi ile case bölümlerinde kalıp uyuşursa ilk uyuşan case bölümündeki + suite'i çalıştırır. + + Diğer bazı dillerde olduğu gibi Python'da case bölümleri break gibi bir deyimle sonlandırılmanaktadır. match deyiminde + zaten bir case kalıbı ile uyuşum sağlandığında yalnızca o case bölümündeki deyimler çalıştırılmaktadır. match deyiminde + diğer bazı dillerde olduğu gibi "aşağıya düşme (fall through)" mekazizması yoktur. Bir case bölümü uyuşumu sağlarsa yalnızca + o case bölümü çalıştırılır. Sonra match deyimi biter ve programın akışı sonraki deyim ile devam eder. + + Python'ın match deyiminde case bölümlerinde birden fazla uyuşum söz konusu olabilir. Bu durumda yukarıdan aşağıya doğru ilk uyuşan case bölümü + çalıştırılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) + +match a: + case 1: + print('bir') + case 2: + print('iki') + case 3: + print('üç') + case 4: + print('dört') + case 5: + print('beş') + +#------------------------------------------------------------------------------------------------------------------------ + Diğer bazı dillerde switch deyiminin eğer hiçbir case bölümü uyuşum sağlamazsa çalıştırılan bir "default" bölümü vardır. + Python'ın match deyiminde "default" bölüm yoktur. Ancak onunla aynı anlama gelen _ kalıbı vardır. Bu kalıp eğer yukarıdaki + kalıpların hiçbiri uyum sağlamazsa uyum sağlar. _ kalıbı case bölümlerinin sonuna yerleştirilmek zorundadır. (C, C++, + Java ve C# gibi dillerde default bölümün sonda olması zorunlu değildir.) +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) + +match a: + case 1: + print('bir') + case 2: + print('iki') + case 3: + print('üç') + case 4: + print('dört') + case 5: + print('beş') + case _: + print('hiçbiri') + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda belirttiğimiz gibi Python'a eklenen match deyimi oldukça detaylı ve yetenekli bir deyimdir. case bölümlerindeki + kalıplar değişik biçimde oluşturulabilmektedir. Yukarıdaki örneklerde biz case anahtar sözcüğünün yanına birer sabit yazdık. + (Buraya diğer dillerdeki gibi sabit ifadeleri yazamayız. Tek bir sabit yazmak zorundayız). Bu tür kalıplara "sabit kalıpları + (iteral patterns)" denilmektedir. Sabit kalıpları tek bir sabittten oluşur. Bu sabitler string de olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +s = input('Bir şehir giriniz:') + +match s: + case 'ankara': + print('06') + case 'eskişehir': + print('26') + case 'kocaeli': + print('41') + case 'adana': + print('01') + case 'izmir': + print('35') + case _: + print('hiçbiri') + +#------------------------------------------------------------------------------------------------------------------------ + Bir case bölümünde "veya" biçiminde birden fazla kalıp "|" atomu ile oluşturulabilmektedir. Bu kalıba "veya kalıbı (or pattern)" + denilmektedir. Örneğin: + + case 1 | 2 | 3: + pass + + Burada bu kalıplardan herhangi biri uyuşum sağlarsa ilgili case bölümü çalıştırılır. +#------------------------------------------------------------------------------------------------------------------------ + +while True: + cmd = input('CSD>').strip() + if cmd =='': + continue + match cmd: + case 'copy': + print('copy executes') + case 'rename': + print('rename executes') + case 'del' | 'erase' | 'remove': + print('delete executes...') + case 'quit' | 'exit': + break + case _: + print(f'invalid command: {cmd}') + +#------------------------------------------------------------------------------------------------------------------------ + Veya kalıbında veya kalıbını parantez içerisine alarak case bölümüne bir "as" cümleceği ekleyebiliriz. as cümleceğini + bir değişken izler bu değişken hangi veya kalıbı uyuşum sağladıysa onun değerini barındırır. Örneğin: + + case ('del' | 'erase' | 'remove') as as_cmd: + print(f'{as_cmd} executes...') + + Burada del, erase ya da remove komutlarından hangisi yazılmışsa as_cmd onu belirtecektir. Aşağıdaki örnekte veya kalıbında + as cümleceği kullanılmıştır. Bu örnekte zaten match ifadesi uyuşum sağlayan komutu içerdiğine göre as cümleceği gereksizmiş + düşünülebilir. Ancak as cümleceği diğer kalıplarda da kullanılabilmektedir. Bu tür durumlarda faydalı durumlara yol açabilmektedir. + + Eğer ilgili case bölümü uyuşum sağlamazsa as anahtar sözcüğünün yanındaki değişken hiç yaratılmamış olacaktır. Bu + nedenle buradaki as değişkenini match deyimi dışında kullanırken dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +while True: + cmd = input('CSD>').strip() + if cmd =='': + continue + match cmd: + case 'copy': + print('copy executes') + case 'rename': + print('rename executes') + case ('del' | 'erase' | 'remove') as as_cmd: + print(f'{as_cmd} executes...') + case 'quit' | 'exit': + break + case _: + print(f'invalid command: {cmd}') + +#------------------------------------------------------------------------------------------------------------------------ + case bölümlerinde kullanılabilen diğer önemli bir kalıp da "dizilim (sequence)" kalıbıdır. Eğer match anahtar sözcüğünün + yanındaki ifade bir "dizilim türünden (sequence type)" ise (burada string dizilim türünden kabul edilmemektedir) bu durumda + case bölümleri dizilim kalıbına sahip olabilir. Standartlarda ve PEP 634'te dizilim türlerinin neler olduğu açıklanmıştır. + Ancak biz kursta dizilim türü için geldiğimiz noktaya kadar görmüş olduğumuz "list" ve "tuple" türlerini kullanabiliriz. + O halde özetle bizim bir dizilim kalıbını kullanabilmemiz için match yanındaki ifadenin bir liste ya da demet olması gerekir. + Bu durumda case yanındaki ifade de bir liste, demet olabilir. match anahtar sözcüğünün yanındaki ifadenin liste ya da demet + olması ve case anahtar sözcüğünün yanındaki ifadenin liste demet olması tamamen aynı etkiyi yaratmaktadır. Bu durumda dizilim + kalıbında case anahtar sözcüğünün yanındaki ifade aşağıdaki biçimlerden birine ilişkin olur: + + case [val1, val2, val3, ..., valn]: + case (val1, val2, val3, ..., valn): + case val1, val2, val3, ..., valn: + + Bu biçimlerin hepsi birbirleriyle eşdeğerdir. Aralarında hiçbir farklılık yoktur. Burada uyuşum için dizilimdeki + elemanların sırasıyla val1, val2, val3, ..., valn ile aynı olması gerekmektedir. + + Aşağıdaki örnek yukarıdaki örneğin dizilim kalıplı biçimidir. Ancak aslında dizilim kalıbının kullanılma nedeni aşağıdaki + örnekle örtüşmemektedir. Dizilim kalıplarında as cümleceği matsch yanındaki ifade hangi türden dizilim olursa olsun her zaman bir listedir. +#------------------------------------------------------------------------------------------------------------------------ + +while True: + cmd = input('CSD>').split() + if len(cmd) == 0: + continue + match cmd: + case 'copy', : + print('copy executes') + case ['rename']: + print('rename executes') + case (['del'] | ['erase'] | ['remove']) as as_cmd: + print(f'{as_cmd[0]} executes...') + case ['quit'] | ['exit']: + break + case _: + print(f'invalid command: {cmd[0]}') + +#------------------------------------------------------------------------------------------------------------------------ + Dizilim kalıbında dizilimin elemanları olarak sabit yerine değişken isimleri de bulundurulabilir. Bu durumda uyuşum her + zaman sağlanır ve dizilimin ilgili elemanı o değişkene atanır. Örneğin: + + case ['del', path]: + pass + + Buradaki dizilim kalıbı şu anlama gelmektedir: "Dizilimin birinci elemanı 'del' yazısı ikinci elemanı herhangi bir şey olabilir. + Ancak bu herhangi bir şey her ne ise path değişkenine atanacaktır." Dolayısıyla bu kalıp aşağıdaki gibi dizilimlerle eşleşebilir: + + ['del', 'a.txt'] + ('del', 'b.txt') + ['del', 123] + ... + + Burada biz hem del eşleşmesini sağlayıyoruz hem de del komutunun yanındaki yazıyı elde etmiş oluyoruz. Tabii yukarıdaki kalıp aşağıdaki gibi + bir dizilimle uyuşmaz: + + ['del', 'a.txt', 'b.txt'] + + Çünkü kalıptaki path tek bir eleman anlamına gelmektedir. Yukarıdaki kalıp aşağıdakiyle de eşleşmez: + + ['del'] +#------------------------------------------------------------------------------------------------------------------------ + +while True: + cmd = input('CSD>').split() + if len(cmd) == 0: + continue + match cmd: + case 'copy', source_path, dest_path: + print(f'copy {source_path} {dest_path} executes') + case 'rename', source_path, dest_path: + print(f'rename {source_path} {dest_path} executes') + case (['del', path] | ['erase', path] | ['remove', path]) as as_cmd: + print(f'{as_cmd[0]} {path} executes') + case ['quit'] | ['exit']: + break + case _: + print(f'invalid command: {cmd[0]}') + +#------------------------------------------------------------------------------------------------------------------------ + 26. Ders 04/07/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dizilim kalıbında dizilimin bir elemanı *'lı bir isimden oluşabilir. Ancak dizilimde tek bir *'lı eleman olabilir. + *'lı eleman sıfır ya da daha fazla elemanla uyuşum sağlar. *'lı elemanda *'ın yanındaki değişken her zaman list türünden + olmaktadır. Bu değişken uyuşum sağlayan elemanları barındıran bir list nesnesi biçimindedir. Örneğin: + + a = [10, 20, 30, 40, 50] + + match a: + case 10, 20, *others: + print(others) # [30, 40, 50] + #.... + + Burada birinci case uyuşum sağlayacaktır. Dizilimin 30, 40, 50 elemanları bir liste olarak others değişkenine atanacaktır. + Yukarıda da belirttiğimiz gibi burada dizilim ne olursa olsun *'lı eleman her zaman liste olur. case anahtar sözcüğünün yanında + birden fazla *'lı eleman içeren dizilim kullanılamaz. Ancak *'lı eleman tipik olarak sonda bulunuyor olsa da aslında sonda bulunmak + zorunda değildir. Örneğin: + + a = [10, 20, 30, 40, 50] + + match a: + case 10, *others, 50: + print(others) # [20, 30, 40] + + Dizilim kalıbında uyuşumun sağlanması için her zaman dizilimin tüm elemanlarının case içerisinde eşleştirilmiş olması gerekmektedir. Örneğin + aşağıdaki case uyuşum sağlamaz: + + a = [10, 20, 30, 40, 50] + + match a: + case 10, *others, 40: + print(others) + + Ancak aşağıdaki case uyuşum sağlar: + + a = [10, 20, 30, 40, 50] + + match a: + case 10, *others, 40, 50: + print(others) # [20, 30] + + Dizilim kalıbında case bölümünde birtakım değişken isimleri yazılabilir. Bu durumda bu değişken isimleri her zaman uyuşum sağlar ve + uyuşum sağlandığında bu değişken isimlerine dizilimin ilgili elemanları atanmış olur. Örneğin: + + a = [10, 20, 30, 40, 50] + + match a: + case 10, x, y, 40, 50: + print(x, y) # 20 30 + + Burada x ve y her zaman uyuşum sağlayacaktır ve x'ya 20, y'ye de 30 atanacaktır. + + a = [10, 20, 30, 40, 50] + + match a: + case 10, x, y, 40, 50: + print(x, y) # 20 30 + + Eğer buradaki değişkenin önemi yoksa bu durumda genellikle _ tercih edilir. Normalde Python'da _ aslında geçerli bir değişken ismidir. + Ancak match deyiminde _ bir değişken olarak değil "wildcard pattern" denilen, "her zaman uyuşum sağlama anlamında kullanılmaktadır. Örneğin: + + a = [10, 20, 30, 40, 50] + + match a: + case 10, _, _, 40, 50: + print('matched') # Artık _ değişkenini burada kullanamayız, özel anlamı var + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + case anahtar sözcüğünün yanındaki ifade bir değişken olursa buna "capture pattern" denilmektedir. Bu durumda bu değişkenin + içerisindeki değere bakılmaz. Uyuşumun her zaman sağlandığı kabul edilir ve match ifadesi bu değişkene atanır. Tabii + "capture pattern" her şeye uyum sağladığı için case bölümlerinin sonuna yerleştirilmelidir. Eğer "capture pattern" case + bölümlerinin sonuna yerleştirilmezse sentaks error oluşur. Capture pattern tipik olarak '_' ile kullanılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) +match a: + case 10: + print('on') + case 20: + print('yirmi') + case x: # capture pattern + print(f'x = {x}') # a neyse o yazdırılır + +#------------------------------------------------------------------------------------------------------------------------ + Diğer bir case kalıbı da "sözlük kalıbı (mapping pattern)" denilen kalıptır. Bu kalığta match yanındaki ifade bir sözlüktür + Bu durumda case anahtar sözcüğünün yanında da bir sözlüğün bulunması gerekir. Tabii sözlük doğrudan küme parantezleriyle + yazılmalıdır. Bu durumda case bölümüne yazılan sözlük ifadesinde anahtar-değer çiftleri uyuşursa case uyuşumu sağlanmış + kabul edilir. Dizilim kalıbına benzemeyen biçimde sözlük kalıbında match ifadesindeki sözlüğün her elemanının case bölümündeki + sözlükle uyuşması gerekmemektedir. Önemli olan case bölümündeki sözlüğün yazılan elemanlarının uyuşumudur. Uyuşum hem + anahtar hem değer ile yapılır. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +match d: + case {'ali': 10, 'veli': 60}: + print('match etmeyecek') + case {'selami': 30, 'ayşe': 40}: + print('match edecek') # match edecek + +#------------------------------------------------------------------------------------------------------------------------ + Sözlük kalıbında değerde _ kullanılırsa değerin her zaman uyuştuğu kabul edilir. Örneğin: + + case {'ali': 10, 'veli': _}: + pass + + Burada anahtar 'veli' ancak değer ne olursa olsun uyuşum sağlanmaktadır. _ karakteri yalnızca sözlüğün değerlerinde + kullanılabilmektedir. Anahtarlarında kullanılamamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +match d: + case {'ali': 10, 'veli': _}: + print('match edecek') # match edecek + +#------------------------------------------------------------------------------------------------------------------------ + Aslında kalıptaki sözlüğün değer kısmında bir değişken ismi de getirilebilir. Bu durumda anahtar uygunsa değer her zaman + uyuşma sağlar ve değer de aynı zamanda buradaki değişkene yerleştirilir. Örneğin: + + case {'ali': 10, 'veli': x}: + print(x) + + Burada 'veli' anahtarı uyuşursa bunun değeri de uyuşmuş kabul edilir. Dolayısıyla değeri x değişkenine yerleştirilmektedir. + Ancak anahtar için bir değişken ismi getirilememektedir. + +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +match d: + case {'ali': 10, 'veli': x}: + print(x) # 20 + +#------------------------------------------------------------------------------------------------------------------------ + Sözlük kalıbında sözlük elemanı olarak **'lı bir değişken kullanılabilir. Bu **'lı değişkene geri kalan tüm sözlük elemanlarına + uyuşum sağlayan bir sözlük yerleştirilir. Ancak **'lı değişkenin bir tane olması ve sözlüğün sonunda bulunması zorunludur. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +match d: + case {'selami': 30, 'fatma': 50, **others}: + print(others) # {'ali': 10, 'veli': 20, 'ayşe': 40} + +#------------------------------------------------------------------------------------------------------------------------ + case ifadelerinde *'lı değişkeninin liste ve demetler için kullanıldığına, **'lı değişkenin sözlükler için kullanıldığına + dikkat ediniz. Aslında *'lı ve **'lı değişkenler zaten Python'ın ileride göreceğimiz başka konularında da karşımıza çıkacaktır. + match deyimi dile eklandiğinde zaten var olan *'lı ve **'lı sentakslar burada da kullanılmaya başlanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında match deyiminde "sınıf kalıbı (class paterrn)" da kullanılabilmektedir. Ancak henüz sınıf konusunu ele almadığımızdan dolayı + bu kalıp üzerinde burada durmayacağız. Aşağıda basit bir örnek verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + +s = Sample(10, 20) +match s: + case Sample(a = 10, b = 20): + print('match edecek') # match edecek + +#------------------------------------------------------------------------------------------------------------------------ + 27. Ders 18/07/2022-Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + match deyiminde case bölümlerinde "koruma (guard)" da oluşturulabilmektedir. case bölümlerinde if anahtar söcüğüyle + ayrı bir koşul belirtilebilir. Bu durumda bu case bölümünün uyuşum sağlaması için o if koşulunun da sağlanması gerekir. Guard + oluşturmanın genel biçimi şöyledir: + + case if : + ... + + Örneğin: + + match a: + case 10 if x > 0: + pass + + Burada case bölümünün uyuşum sağlaması için a'nın 10 olmasının yanı sıra aynı zamanda x'in de 0'dan büyük olması + gerekmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir sayı giriniz:')) +x = -2 + +match a: + case 10 if x > 0: + print('Ok') + case _: + print('cannot match...') + +#------------------------------------------------------------------------------------------------------------------------ + case bölümündeki koruma kısmı bazı durumlarda pratik kullanımlara yol açabilmektedir. Örneğin uyuşum dışında ek başka + koşulların kontrolü bu sayede yapılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +cmds = input('Komut giriniz:').split() + +match cmds: + case ['delete', *args] if len(cmds) == 2: + print('delete command') + case _: + print('cannot match...') + +#------------------------------------------------------------------------------------------------------------------------ + Koşul operatörü (conditional operator) üç operandlı (ternary) bir operatördür. Operatör if deyimine benzemekle birlikte bir değer üretir. + Bu bağlamda ifadelerin içerisinde kullanılabilir. C, C++, Java ve C# gibi dillerde bu operatörün benzeri ?: biçiminde bulunmaktadır. + Koşul operatörü -ismi üzerinde- bir deyim değildir. Bir operatördür. Yani bir değer üretmektedir. Genel biçimi şöyledir: + + if else + + Koşul operatörü şöyle çalışmaktadır: if anahtar sözcüğünün yanındaki ifade bool türünden değilse bool türüne dönüştürülür. + Eğer bu ifade True ise yalnızca ifade1 yapılır ve operatör bu değeri üretir. Eğer bu ifade False ise yalnızca ifade3 yapılır ve operatör + bu değeri üretir. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir sayı giriniz:')) + +b = 100 if a % 2 == 0 else 200 + +print(b) + +#------------------------------------------------------------------------------------------------------------------------ + Şüphesiz koşul operatörü ile yapılan şeyler aslında if deyimiyle de yapılabilir. Ancak koşul operatörü bazı durumlarda + okunabilirliği artırmakta ve pratik bazı kullanımlara olanak sağlamaktadır. Örneğin: + + b = 100 if a % 2 == 0 else 200 + + Biz bu işlemi if deyimi ile aşağıdaki gibi de yapabilirdik: + + if a % 2 == 0: + b = 100 + else: + b = 200 + +#------------------------------------------------------------------------------------------------------------------------ + Koşul operatörünü if deyimi gibi kullanmak kötü bir tekniktir. Örneğin: + + a = int(input('Bir sayı giriniz:')) + + print('çift') if a % 2 == 0 else print('tek') # kötü teknik! + + Koşul operatörünün ürettiği değerin bir biçimde aynı ifadede kullanılması gerekir. Örneğin bir karşılaştırma sonucunda + bir değişkene değer atama işlminde koşul operatörünü kullanabiliriz: + + days = 366 if isleap(year) else 365 # doğru kullanım + + Koşul operatörü özellikle üç durumda tercih edilmelidir: + + 1) Bir karşılaştıma sonucunda elde edilen değerin bir değişkene atandığı durumlarda. Örneğin: + + result = 100 if val % 2 == 0 else 200 + + Aynı işlemi if deyimiyle şöyle de yapabilirdik: + + if val % 2 == 0: + result = 100 + else: + result = 200 + + 2) Fonksiyon ya da metot çağrımlarında argüman olarak koşul operatörü bazen yazımı kısaltmak için kullanılabilmektedir. Örneğin: + + val = int(input('Bir sayı giriniz:')) + + print('çift' if val % 2 == 0 else 'tek') + + Bu örnekte val değişkeninin çift ya tek olmasına göre print fonksiyonuna 'çift' ya da 'tek' yazısı argüman olarak + gönderilmiştir. + + Aslında burada yapılmak istenen aşağıdakiyle tamamen eşdeğerdir: + + if val % 2 == 0: + print('çift') + else: + print('tek') + + 3) return ifadelerinde de koşul operatörü bazı durumlarda tercih edilmektedir. Örneğin: + + return 100 if a % 2 == 0 else 200 + + Bu işlemin eşdeğeri şöyledir: + + if a % 2 == 0: + return 100 + else: + return 200 + + return işlemi fonksiyonların anlatıldığı izleyen bölümlerde ele alınmaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + Koşul operatör olmasaydı biz if deyimini kullanarak yine yapmak istediğimiz şeyleri yapardık. Koşul operatörü bazı + ifadelerin daha kompakt yazılabilmesine olanak sağlamaktadır. Örneğin 0'dan 100'e kadar sayıları aşağıdaki gibi + beşer beşer yazdırmak isteyelim: + + 0 1 2 3 4 + 5 6 7 8 9 + ... + + İlk aklımıza gelen şöyle bir döngü olacaktır: + + for i in range(100): + if i % 5 == 4: + print(i, end='\n') + else: + print(i, end=' ') + + Oysa koşul operatörü kullanarak bu kod parçasını daha kompakt bir biçimde yazabiliriz: + + for i in range(100): + print(i, end='\n' if i % 5 == 4 else ' ') + + Burada i'nin durumuna göre print fonksiyonunun end parametresi '\n' olarak ya da ' ' olarak girilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +for i in range(100): + print(i, end='\n' if i % 5 == 4 else ' ') + +#------------------------------------------------------------------------------------------------------------------------ + Koşul operatörü öncelik tablosunda tablonun en aşağısında hemen atama işlemlerinin yukarısında bulunmaktadır. Yani + koşul operatörü düşük öncelikli bir operatördür. + + () soldan sağa + ** sağdan sola + + - sağdan sola + * / // % soldan sağa + + - soldan sağa + < > <= >= == != soldan sağa + not sağdan sola + and soldan sağa + or soldan sağa + if else sağdan sola + = := , +=, -=, *=, ... sağdan sola + + Aşağıdaki örnekte + operatörü koşul operatörnün dışında değildir. Onun bir parçasını oluşturmaktadır: + + result = 100 if val % 2 == 0 else 200 + 300 + + Burada val çift ise result değişkenine 100 değeri tek ise 200 + 300 değeri atanacaktır. Eğer buradaki + operatörü + koşul operatöründen ayrıştırılmak isteniyorsa parantezler kullanılmalıdır. Örneğin: + + result = (100 if val % 2 == 0 else 200) + 300 + + Artık buradaki + operatörü ayrı bir operatördür. Yani val çiftse de tekse de elde edilen değere 300 toplanmaktadır. + Birtakım operatörlerin koşul operatörünün operand'ları olmaktan çıkartmak için parantezlerin kullanılması gerekmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin iki değerden büyük olanını bir değişkene atamak istediğimizde koşul operatörünü tercih edebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) +b = int(input('Bir değer giriniz:')) + +result = a if a > b else b +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii yukarıdaki örnekte aslında biz büyük olan değeri doğrudan da print ile yazdırabilirdik. +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) +b = int(input('Bir değer giriniz:')) + +print(a if a > b else b) + +#------------------------------------------------------------------------------------------------------------------------ + Koşul operatörü de iç içe kullanılabilir. İç içe kullanımda parantez kullanmak zorunlu değildir. Ancak parantezsiz + ifadeyi yazmak operatör öncelikleri dikkate alındığında zordur. Bu nedenle koşul operatörünü iç içe kullanırken + parantezleri kullanınız. Örneğin: + + a = int(input('Bir değer giriniz:')) + b = int(input('Bir değer giriniz:')) + c = int(input('Bir değer giriniz:')) + + result = (a if a > c else c) if a > b else (b if b > c else c) + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + +a = int(input('Bir değer giriniz:')) +b = int(input('Bir değer giriniz:')) +c = int(input('Bir değer giriniz:')) + +result = (a if a > c else c) if a > b else (b if b > c else c) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfta şöyle bir soruldu: Aşağıdaki kodda neden ben matrisin tek bir elemanını değiştirdiğim halde matrisin tüm + satırlarının elemanı değişmiş oluyor? + + a = [[0] * 5] + b = a * 5 + print(b) + b[0][0] = 100 + print(b) + + Buradan öyle bir çıktı elde edilmiştir: + + [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + [[100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0]] + + Bunun nedeni "yineleme (repitition)" işleminde aslında adres kopyalaması yapılmasıdır. Burada yineleme yapıldığında + artık b listesinin her elemanı aslında a listesini göstermektedir. Dolayısıyla a listesinde bir değişiklik yapıldığında + b'bnin her elemanı a'yı gösterdiği için bu değişiklik sanki b'nin her elemanında yapılmış gibi bir duurm oluşmaktadır. + Burada eğer istenen şey b'nin farklı listeleri göstermesi ise bu durum bir öngü ile sağlanmalıdır: + + b = [] + + for _ in range(5): + a = [0] * 5 + b.append(a) + + print(b) + + b[0][0] = 10 + print(b) + + Tabii aslında tytukarıdkai işlem "liste içlemiyle (list comprehension)" tek satırda da yapılabilmektedir. Liste içlemleri + konusu ileride ele alınmaktadır: + + b = [[0] * 5 for _ in range(5)] + + print(b) + + b[0][0] = 100 + print(b) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Şimdiye kadar biz hep var olan fonksiyonları çağırdık. Şimdi kendimiz fonksiyonlar yazacağız. Bir fonksiyonun yazılmasına + "fonksiyonun tanımlanması (function definition)" da denilmektedir. Python'da fonksiyon tanımlamanın genel biçimi şöyledir: + + def ([parametre listesi]): + + Buradaki def bir anahtar sözcüktür, mutlaka bulundurulması gerekir. Fonksiyon ismi isimlendirme kurallarına uygun herhangi bir + isim olabilir. Fonksiyonlar parametre değişkenlerine sahip olabilirler ya da olmayabilirler. Her fonksiyon bir "suit" + içermek içermelidir. Örneğin: + + def foo(): + print('foo') + + Burada fonksiyonun ismi "foo" biçimindedir. Fonksiyonun parametresi yoktur. Örneğin: + + def bar(a, b): + print(a, b) + + Burada fonksiyonun ismi "bar" biçimindedir. a ve b bu fonksiyonun parametre değişkenleridir. Burada parametre + değişkenleri için tür belirtilmediğine yalnızca onların isimlerinin yazıldığına dikkat ediniz. Dinamik tür sistemine + sahip programlama dillerinde zaten bildirim yoktur. + + Biz aşağdaki örneklerimizde fonksiyon ismi uydurmak istediğimizde genellikle "foo", "bar", "tar", "zar" gibi + isimleri kullanacağız. Bu isimlerin hiçbir özel anlamı yoktur. Bunlar öylesine uydurulmuş isimlerdir. + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + +def bar(): print('bar'); print('yes bar') + +foo() +bar() + +#------------------------------------------------------------------------------------------------------------------------ + Aslında Python'da fonksiyon tanımlama işlemi bir deyim statüsündedir. Yorumlayıcı bir fonksiyonun tanımlanadığını gördüğünde + önce bir "fonksiyon nesnesi (function object)" yaratır. Sonra o fonksiyon nesnesinin içerisine fonksiyonun kodlarını (yani suit deyimlerini) + yerleştirir. Fonksiyon nesnesinin adresini de fonksiyon ismine atar. Böylece aslında fonksiyon isimleri sıradan birer değişkendir. + Fonksiyon isimleri fonksiyon nesnelerini göstermektedir. Bir fonksiyonu (...) operatörü ile çağırdığımızda aslında biz + bu operatörün operand'ı olan değişkenin içerisindeki adreste bulunan fonksiyon nesnesinin içerisindeki kodları çalıştırmış oluruz. + Örneğin: + + def foo(): + print('foo') + + Bu tanımlamayı yorumlayıcı gördüğünde önce bir fonksiyon nesnesi oluşturup foo'nun kodlarını (yani suit deyimlerini) o nesnenin + içerisine yerleştirir. O nesnenin adresini de foo değişkenine atar. Artık foo değişkeni fonksiyon nesnesini göstermektedir. + Biz fonksiyonu aşağıdaki gibi çağırmış olalım: + + foo() + + Aslında burada yapılan şey foo değişkenin içerisindeki adreste bulunan fonksiyon nesnesinin kodlarının çalıştırılmasıdır. Örneğin: + + >>> def foo(): + ... print('foo') + ... + >>> id(foo) + 4312805088 + >>> type(foo) + + >>> foo() + foo + + Mademki fonksiyon isimleri aslında sıradan birer değişkendir ve her atama aslında birer adres atamasıdır. O halde + biz bir fonksiyon ismini başka bir değişkene atayabiliriz. Bu durumda aynı fonksiyonu o değişkenle de çağrabiliriz. + Örneğin: + + >>> def foo(): print('foo') + ... + >>> bar = foo + >>> foo() + foo + >>> bar() + foo + >>> id(foo) + 4312805232 + >>> id(bar) + 4312805232 + + Fonksiyonların da aslında böyle normal değişkenler gibi olmasına ve atanmasına yazılım dünyasında "fonksiyonların da birinci + sınıf vatandaş (functions are first class citizens)" olması denilmektedir. Python'da fonksiyonlar da birinci sınıf vatandaştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte f değişkeninin içerisindeki fonksiyon nesnesinin adresi g değişkenine atanmıştır. Artık f ve g aynı + fonksiyon nesnesini gösterir duruma gelmiştir. Dolayısıyla artık f() ve g() aslında aynı fonksiyonun çağrılmasına yol + açmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +def f(): + print('test function') + +g = f + +f() +g() + +#------------------------------------------------------------------------------------------------------------------------ + 28.Ders 20/7/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyon çağrıldığında onu çağıran fonksiyona iletilen değere "geri dönüş değeri (return value)" denilmektedir. + Fonksiyonların geri dönüş değerleri return deyimiyle oluşturulmaktadır. return deyiminin genel biçimi şöyledir: + + return [ifade] + + Programın akışı return deyimini gördüğünde fonksiyon sonlanır ve geri dönüş değeri return anahtar sözcüğünün yanındaki ifade + olacak biçimde oluşturulur. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + return 100 + +a = foo() +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonun geri dönüş değeri kullanılmak zorunda değildir. Yani isteğe bağlı (optional) bir bilgidir. Biz fonksiyonu + çağırıp geri dönüş değerini hiç kullanmayabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + return 100 + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + C, C++, Java ve C# gibi statik tür sistemine sahip dillerde fonksiyonların geri dönüş değerlerinin türleri vardır. + Fonksiyonlar aynı türden değerlerle geri dönebilirler. Ancak Python dinamik tür sistemine sahip bir programlama dili olduğu için + Python'da bir fonksiyon duruma göre farklı türlerle geri dönebilir. Python'da fonksiyonun geri dönüş değerinin türü diye bir + kavram yoktur. return deyiminde return anahtar sözcüğünün yanındaki ifade hangi türdense fonksiyon o türden bir değerle geri + dönecektir. + + Aşağıdaki örnekte klavyeden girilen değer pozitif bir değerse fonksiyon onun karesiyle geri dönmektedir. Eğer klavyeden + girilen değer pozitif değilse fonksiyon "error" yazısıyla geri dönecektir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = int(input('Bir değer giriniz:')) + if a > 0: + return a * a + + return 'error' + +result = foo() +print(result, type(result)) + +#------------------------------------------------------------------------------------------------------------------------ + Akış return deyimini görmeden fonksiyonu bitirirse geri dönüş değeri olarak None değeri elde edilmektedir. Örneğin: + + def foo(): + print('foo') + + val = foo() + + Burada geri dönüş değeri olarak None değeri elde edilecektir. + + Tabii fonksiyonun bazı akışları return deyimini görürken bazı akışları görmeyebilir. Örneğin: + + import math + + def mysqrt(val): + if val >= 0: + return math.sqrt(val) + + Burada eğer val parametresi 0 ya da 0'dna büyükse fonksiyon onun karekökü ile eğer 0'dan küçükse None değeri ile + geri dönecektir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + +result = foo() +print(result) # None + +#------------------------------------------------------------------------------------------------------------------------ + return anahtar sözcüğünün yanına bir ifade yazılmayabilir. Bu durumda fonksiyonun sonlandırılması istenmiştir ancak bir geri + dönüş değeri belirtilmemiştir. Bu durumda da geri dönüş değeri olarak None elde edilmektedir. Yani başka bir deyişle: + + return + + kullanımı ile aşağıdaki kullanım eşdeğerdir: + + return None + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + return + +result = foo() +print(result) # None + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyonun tek bir geri dönüş değeri vardır. Yani fonksiyon tek bir değerle geri dönebilir. Eğer fonksiyonun birden fazla + değer iletmesi isteniyorsa geri dönüş değeri demet, liste gibi bileşik bir nesne yapılmalıdır. Demetler bu bağlamda tercih edilmelidir. + Örneğin: + + def foo(): + print('foo') + return 10, 20 + + Burada foo fonksiyonu bir demetle geri dönmektedir. Yani biz fonksiyonu çağırdığımızda bir demet elde ederiz. Tabii + bu demet doğrudan açılabilir. Örneğin: + + a, b = foo() + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + return 100, 200 + +a, b = foo() +print(a, b) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıda ikinci derece denklemin kökleri ile geri dönen getroots isimli bir fonksiyon örneği verilmiştir. Fonksiyon + eğer kök yoksa None değerine, eğer kök varsa ikili bir demete geri dönmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def getroots(): + a = float(input('a:')) + b = float(input('b:')) + c = float(input('a:')) + + delta = b ** 2 - 4 * a * c + if delta < 0: + return None + + x1 = (-b + math.sqrt(delta)) / (2 * a) + x2 = (-b - math.sqrt(delta)) / (2 * a) + + return x1, x2 + +result = getroots() + +if result: + x1, x2 = result + print(f'x1 = {x1}, x2 = {x2}') +else: + print('no real root!') + +#------------------------------------------------------------------------------------------------------------------------ + Tabii fonksiyon bir listeyle, bir kümeyle ya da bir sözlükle de geri dönebilir. Aşağıdaki örnekte 0'dan klavyedne girilen + değere kadar değerlerin kareleri bir listede toplanmış ve fonksiyon bu listeyle geri dönmüştür. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + val = int(input('Bir sayı giriniz:')) + a = [] + for i in range(val): + a.append(i * i) + + return a + +a = foo() +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonların dış dünyadan aldıkları değerlere "parametre" denilmektedir. Fonksiyonlar parametrelere sahip olabilirler. + Bu durumda parametrelerin isimleri parametre parantezinin içerisinde ',' atomu ile ayrılarak belirtilmektedir. Örneğin: + + def foo(a, b): + pass + + Burada a ve b foo fonksiyonunun parametreleridir. Parametre terimi yerine bazen "parametre değişkeni" terimi de kullanılabilmektedir. + + Diğer statik tür sistemine sahip programlama dillerinde olduğu gibi bir parametre bildirimi Python'da yoktur. Pyton'da parametre parantezinin + içerisine yalnızca parametre değişkenlerinin isimlerinin yazıldığına dikkat ediniz. Örneğin: + + def foo(a, b, c): + print(a, b, c) + + Fonksiyonun parametre değişkenlerine "parametre (parameter)" fonksiyon çağrılırken girilen ifadelere ise "argüman (argument)" + denilmektedir. n tane parametreye sahip olan bir fonksiyon n tane argümanla çağrılmalıdır. Fonksiyon çağrılırken önce argümanların + değerleri hesaplanır. Sonra argümanlardan parametre değişkenlerine karşılıklı bir atama yapılır. Sonra akış fonksiyona aktarılır. + Yani parametreli bir fonksiyonun çağrılması parametre değişkenlerine gizli bir atama işleminin yapılması anlamına gelmektedir. + Python'da her türlü atamanın bir adres ataması olduğuna dikkat ediniz. Dolayısıyla aslında buradaki atama sırasında aslında argüman olan + nesnelerin adresleri atanmaktadır. Örneğin: + + def foo(a, b): + pass + + foo(10 + 20, 30 + 40) + + Burada önce 10 ile 20 toplanıp değeri 30 olan bir int nesne oluşturulur. 30 ile 40 toplanıp değeri değeri 70 olan bir + int nesne yaratılır. Sonra bu int nesnelerin adresleri sırasıyla a ve b değişkenlerine atanır. Örneğin: + + def foo(a): + pass + + x = 10 + foo(x) + + Burada önce içerisinde 10 değeir olan int türden bir nesne yaratılır. Bu nesnenin adresi x'e atanır. Sonra foo fonksiyonu + çağrılınca x'in içerisindeki adres parametre değişkeni olan a'ya atanacaktır. Yani aşağıdaki örnekte x'in id değeri ile + a'nın id değeri (yani adresi) aynı olacaktır: + + def foo(a): # a = x + print(a, id(a)) + + x = 10 + print(x, id(x)) + + foo(x) + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c): + print(a, b, c) + +foo(10 + 20, 'ali', 12.3) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte argümanlara ilişkin nesnenin adresinin aslında parametre değişkenine yerleştirildiğini göreceksiniz. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a): + print(a, id(a)) + +x = 10 +print(id(x)) +foo(x) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii aynı durum return işlemi için de geçerlidir. Yani return işleminde de aslında çağıran fonksiyona iletilen return ifadesindeki + nesnenin adresidir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + print(id(a)) + return a + +result = foo() +print(id(result)) + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonun parametre değişkenleri yalnızca o fonksiyonun içerisinde kullanılabilir. Dışarıdaki aynı isimli başka bir + değişken ile karışmaz. Örneğin: + + def foo(a): + pass + + a = 10 + foo(a) + + Buradaki a ile fonksiyonun parametre değişkeni olan a tamamne farklı iki değişkendir. + + İki fonksiyonun parametre değişkenlerinin aynı isimde olması da bir soruna yol açmamaktadır. Örneğin: + + def foo(a): + pass + + def bar(a): + pass + + Burada hem foo fonksiyonun hem de bar fonksiyonunun parametre değişkenin ismi a'dır. Bunlar birbirleriyle karışmaz. + Çünkü foo'nun parametre değişkeni olan a yalnızca foo içerisinde, bar'ın parametre değişkeni olan a ise yalnızca bar'ın + içerisinde kullanılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonların parametre değişkenleri onlar hangi tüden argümanla çağrılmışsa o türden olurlar. Yani onların belli bir + türleri yoktur. Daha önce de belirttiğimiz gibi statik tür sistemine sahip C, C++, Java, C# gibi dillerde parametre + değişkenlerinin belli bir türü vardır. Bu tür hiç değişmez. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a): + print(a, type(a)) + +foo(10) +foo(1.2) +foo('ali') +foo(True) +foo([1, 2, 3]) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi biz neden fonksiyon yazmak isteriz? İşte fonksiyonlar oluşturmanın gerekçeleri şunlardır: + + 1) Fonksiyonlar "yeniden kullanılabilirliği (reusability)" sağlarlar. Yani belli bir amacı gerçekleştiren fonksiyon başka projelerde + yeniden yazılmadan çağrılarak kullanılabilmektedir. Biz bir işi yapan bir fonksiyon yazdığımızda onu değişikm projelerde kullanabiliriz. + + 2) Fonksiyonlar kod tekrarını engellemektedir. Bir işlem programın çeşitli yerlerinde yineleniyor olabilir. Eğer fonksiyonlar olmasaydı + o işlemlerin tekrar tekrar yapılması gerekirdi. Bu da kodun büyümesine ve karmaşıklığa yol açardı. Halbuki fonksiyonlar kod tekrararını + engellemektedir. Biz bir işi yapan kodu bir fonksiyon olarak yazarsak onu programın çeşitli yerlerinde çağırarak kod tekrarını + engellemiş oluruz. + + 3) Karmaşık bir problem parçalarına ayrılarak daha kolay çözülebilir. Parçalarına ayırma işlemi genellikle fonksiyonlar + yoluyla yapılmaktadır. Yani işin belli kısımlarını yapan fonksiyonlar tanımlanır sonra karmaşık iş o fonksiyonların + belli sırada çağrılmasıyla gerçekleştirilir. İlk bakışta karmaşık gibi gelen pek çok olgu aslında parçalara ayrıştırıldığında + çok daha yönetilebilir bir hale gelmektedir. + + 4) Fonksiyonların isimleri vardır. Dolayısıyla yapılmak istenen şey fonksiyon çağrılarıyla daha iyi ifade edilebilmektedir. + + İşte programlamada bir işlemin fonksiyonlara ayrılarak fonksiyonların birbirlerine çağırması biçiminde gerçekleştirilmesine + "prosedürel programlama modeli (procedural paradigm)" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonlar listelerin, demetlerin sözlüklerin elemanları olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + +def bar(): + print('bar') + +def tar(): + print('tar') + +a = [foo, bar, tar] + +for i in range(len(a)): + a[i]() + +for f in a: + f() + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyon nesneleri hash'lenebilir olmadığı için sözlüklerde anahtar yapılamaz. Ancak fonksiyonlar sözlüklerde + değer biçiminde bulunabilir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + +def bar(): + print('bar') + +def tar(): + print('tar') + +d = {'ali': foo, 'veli': bar, 'selami': tar} + +d['ali']() +d['veli']() +d['selami']() + +for key in d: + d[key]() + +#------------------------------------------------------------------------------------------------------------------------ + Tabii aşağıdaki iki listede yapılan şeyler tamamen farklıdır: + + a = [foo, bar, tar] + b = [foo(), bar(), tar()] + + a listesinde listenin her elemanı bir fonksiyon nesnesini tutmaktadır. Ancak b listesinde listenin elemanları bu fonksiyonların + geri dönüş değerlerinden oluşmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + return 10 + +def bar(): + print('bar') + return 20 + +def tar(): + print('tar') + return 30 + +a = [foo(), bar(), tar()] + +for x in a: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte ikinci derece bir denklemin köklerini buluna bir fonksiyon yazılmıştır. Fonksiyon eğer kök yokse None + değerine geri döner, kök varsa x1 ve x2 köklerini bir demet olarak geri döndürmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def get_roots(a, b, c): + delta = b ** 2 - 4 * a * c + if delta < 0: + return None + + x1 = (-b + math.sqrt(delta)) / (2 * a) + x2 = (-b - math.sqrt(delta)) / (2 * a) + + return x1, x2 + +a = float(input('a:')) +b = float(input('b:')) +c = float(input('c:')) + +result = get_roots(a, b, c) +if result != None: + x1, x2 = result + print(f'x1 = {x1}, x2 = {x2}') +else: + print('Kök yok!') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte banner fonksiyonu tireler arasında parametresiyle aldığı yazıyı ekrana basmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +def banner(s): + print('-' * len(s)) + print(s) + print('-' * len(s)) + +banner('ankara') +banner('izmir') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte bir sayının asal olup olmadığını test eden isprime isimli bir fonksiyon yazılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +def isprime(val): + for i in range(2, val - 1): + if val % i == 0: + return False + + return True + +val = int(input('Bir sayı giriniz:')) + +print('Asal' if isprime(val) else 'Asal değil') + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki fonksiyonu kullnarak 100'e kadar tüm asal sayıları aşağıdaki gibi yazdırabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +def isprime(a): + for i in range(2, a): + if a % i == 0: + return False + + return True + +for i in range(2, 100): + if isprime(i): + print(i, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Aslında asallık testi daha hızlı bir biçimde aşağıdaki gibi yapılabilir. Aşağıdaki programda iki önemli matematiksel + özellikten faydalanılmıştır: + + 1) Sayı çift ise ama 2'ye eşit değilse asal değildir. Başka çift sayıların kontrol edilmesine gerek yoktur. + 2) Asal olmayan her sayının kareköküne kadar bir asal çarpanı vardır. (Öklit teoremi). Dolayısıyla sayının kareköküne + kadar kontrol yapılması yueterlidir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def isprime(a): + if a % 2 == 0: + return a == 2 + + for i in range(3, int(math.sqrt(a)) + 1, 2): + if a % i == 0: + return False + + return True + +for i in range(2, 100): + if isprime(i): + print(i, end=' ') + + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıda parametresiyle girilen değerin faktöriyeline geri dönen fonksiyon örneği verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +def factorial(n): + total = 1 + for i in range(2, n + 1): + total *= i + + return total + +result = factorial(5) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Aslında math modülü içerisinde factorial fonksiyonu hazır olarak bulunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +>>> import math +>>> math.factorial(5) +120 + +#------------------------------------------------------------------------------------------------------------------------ + Python dilinin tasarımı ve sürdürümü ve CPython gerçekleştiriminin yazımı Python Sooftware Foundation (python.org) + tarafından yapılmaktadır. PSP içerisinde çeşitli alt gruplar vardır. Dile ilişkin sentaks ve semantik yenilikler ve + standart kütüphaneye yapılacak eklemeler "PEP (Python Enhancement Proposals)" denilen dokümanlarla yürütülmektedir. + Bir kişi taslak biçiminde bir PEP dokğmanı hazırlar. Önerisini oraya belli bir formatta yazar. Sonra bu öneri tartışılıp + sonuca bağlanır. Eğer kabul edilirse resmi bir biçimde PEP dokümanı olarak yayınlanır. Dolayısıyla PEP dokümanları + Python diline ilişkin pek çok "gerekçeyi (rationale)" de barındırmaktadır. PEP dokğmanlarına numaralar verilmiştir. + Dolayısıyla bir dil özelliğinin geniş bir açıklamasını elde etmek istiyorsanız bu PEP dokümanlarına başvurmalısınız. + Python'ın tüm PEP dokğmanlarına aşağıdaki bağlantıdan erişebilirsiniz: + + https://peps.python.org/ +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python kodlarını yazarken peşi sıra gelen atomlar arasında nasıl boşluklar bulundurulacağı, kodun güzel gözükmesi için + nasıl hizalamalar yapılacağı, değişken isimlendirmelerde hangi kuralların izleneceği gibi konuları kapsayan çeşitli yazım + biçimleri oluşturulmuştur. Yazım biçimleri (style guides) proje grupğları arasında ve kurumlar arasında farklılaşmaktadır. + Örneğin Python Software Foundation tarafından PEP 8 dokümanı ile açıklanan yazım biçimine pek çok Python programcısı uymamaktadır. + Google firmasının da Python için önerdiği kendine özgü bir yazım sitili de bulunmaktadır. Biz kurusumuda CSD'ye özgü bir + yazım sitilini kullanmaktayız. Ancak dahil olduğunuz proje grubunun yazım biçimine kendinizi uydurmanız gerekebilir. + Aşağıda iki yazım biçiminin bağlantısını veriyoruz: + + https://peps.python.org/pep-0008/ + https://google.github.io/styleguide/pyguide.html +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 29. Ders 25/07/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da diğer bazı dillerde olduğu gibi default argüman kullanımı vardır. Yani parametre değişkenleri Python'da default + değer alabilmektedir. Bir parametre değişkeni default değer almışsa çağrı sırasında o parametre değişkeni için argüman girilmezse + sanki o parametre için o default değer girilmiş gibi işlem yapılır. Örneğin: + + def foo(a, b, c = 10): + print(100, 200) + + Biz burada foo fonksiyonunu çağırırken c için default değer girmezsek sanki c için 10 değerini girmiş gibi oluruz. Örneğin: + + foo(100, 200) + + Bu çağrı aşağıdakiyle tamamen eşdeğerdir: + + foo(100, 200, 10) + + Ancak default değer almış olan parametre için argüman girilirse artık o default değerin bir önemi kalmaz. Örneğin: + + foo(100, 200, 300) + + Burada artık c için 300 değeri girilmiştir. Dolayısıyla c parametre değişkenine verilen default değerin bir önemi kalmamıştır. + Yani parametre değişkenine verilen default değer "eğer o parametre değişkeni için argüman girilmezse" devreye girmektedir. + Default değer almamış olan parametre değişkenleri için argüman girilmek zorundadır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c = 10): + print(f'a = {a}, b = {b}, c = {c}') + +foo(100, 200) # foo(100, 200, 10) +foo(100, 200, 300) # foo(100, 200, 300) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii fonksiyonun birden fazla parametresi default değer alabilir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c = 10, d = 20): + print(f'a = {a}, b = {b}, c = {c}') + +foo(100, 200) # foo(100, 200, 10, 20) +foo(100, 200, 300) # foo(100, 200, 300, 20) +foo(100, 200, 300, 400) # foo(100, 200, 300, 400) + +def bar(a = 10, b = 20): + print(f'a = {a}, b = {b}') + +bar() # bar(10, 20) +bar(100) # bar(100, 20) +bar(100, 200) # bar(100, 200) + +#------------------------------------------------------------------------------------------------------------------------ + Parametrelere verilen default değerlerin çok kullanılan değerler olması gerekir. Yoksa öylesine değerleri default değer olarak + vermek kötü bir tekniktir. Fonksiyonu inceleyen kişiler default değerlerin yaygın değerler olduğunu varsaymaktadır. + Örneğin farklı türden bir değeri int türüne dönüştürmek için kullandığımız int fonksiyonun aslında ikinci bir parametresi vardır. + Bu parametre birinci parametredeki yazının kaçlık sistemde yazılmış olduğunu belirten base parametresidir. Biz günlük yaşamımızda + 10'luk sistemi kullandığımıza göre bu parametrenin default değerinin 10 olması oldukça makuldür. Böylece kişiler fonksiyonu + çağırırken boşuna bu ikinci parametre için her defasında 10 değrini girmezler. (int fonksiyonun bu ikinci parametresi ancak birinci parametre + bir yazı ise anlamlıdır. Eğer birinci parametre bir yazı değilse ikinci parametre için argüman girmek Exception'a (TypeError) + yol açmaktadır.) Default argümanlar bu örnekte olduğu gibi fonksiyonu çağıracak kişilere kolaylık sağlamaktadır. Hatta bazen + bir fonksiyonun çok parametresi olabilir. Ancak bunların önemli bir bölümü default değer almış olabilir. Meşgul bir programcı + da yalnızca default değer almamış parametrelerin anlamlarını öğrenip onlar için argüman girebilir. +#------------------------------------------------------------------------------------------------------------------------ + +s = '123' + +val = int(s) # int(s, 10) +print(val) # 123 + +val = int(s, 16) +print(val) # 291 + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonda default değer alan parametre değişkenlerinin parametre listesinin sonunda toplanmış olması gerekir. Başka bir + deyişle "eğer bir parametre değişkeni default değer almışsa onun sağındakilerin hepsinin default değer almış olması" gerekir. + Örneğin: + + def foo(a, b = 10, c): + pass + + Böyle bir fonksiyon tanımlaması geçerli değildir. Programcının fonksiyonu şöyle tanımlaması gerekirdi: + + def foo(a, c, b = 10): + pass + +#------------------------------------------------------------------------------------------------------------------------ + +def banner(s, ch='-'): + print(ch * len(s)) + print(s) + print(ch * len(s)) + +banner('ankara') +banner('ankara', '*') + +#------------------------------------------------------------------------------------------------------------------------ + Python'da fonksiyon parametrelerine verilen default değerler fonksiyonun bir deyim olarak çalıştırılması sırasında yalnızca bir kez + işleme sokulmaktadır. Bu davranış C++, Java ve C# gibi dillerdeki davranıştan farklıdır. Örneğin: + + def foo(a = [1, 2, 3]): + print(a, id(a)) + + Burada [1, 2, 3] elemanlarına sahip olan liste nesnesi yalnızca bir kez yaratılacaktır. Yani fonksiyon her çağrıldığında yaratılmayacaktır. + Bunu basit bir biçimde şöyle test edebilirsiniz: + + foo() + foo() + foo() + + Buradan elde edilen örnek bir çıktı şöyledir: + + [1, 2, 3] 1560817328576 + [1, 2, 3] 1560817328576 + [1, 2, 3] 1560817328576 + + Benzer biçimde örneğin: + + def foo(): + print('foo') + + return 10 + + def bar(a = foo()): + pass + + Burada da foo fonksiyonu bar deyimi çalıştırılırken yalnızca bir kez çağrılacaktır. bar fonksiyonu her çağrıldığında çağrılmayacaktır. + Örneğin: + + bar() + bar() + bar() + + Bu tür durumlarda default argüman olarak "değiştirilebilir (mutable)" nesne kullanırken dikkat etmek gerekir. Örneğin: + + def foo(a = []): + a.append(10) + return a + + x = foo() # dikkat x aslında a parametre değişkeni ile aynı nesneyi gösteriyor + x.append(20) + foo() + + print(x) # [10, 20, 10] + + Burada foo fonksiyonu default argüman olarak yaratılmış olan list nesnesinin adresiyle geri dönmüştür. Dolayısıyla x değişkeninde + artık default argüman olarak yaratılmış olan nesnenin adresi bulunacaktır. Yani kodda x.append(20) aslında aynı nesneye + ekleme yapmaktadır. Daha sonra çağrılan foo'daki a parametre değişkeni yeniden boş bir list nesnesini göstermeyecektir. + Zaten bir kez yaratılmış olan list nesnesini gösterecektir. + #------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da C++, Java ve C# gibi dillerdeki "function/method overloading" özelliği yoktur. Dolayısıyla bu tür bir etki + default argüman alan parametre değişkenleriyle sağlanmaktadır. Örneğin biz range fonksiyonunu (aslında bu bir sınıftır) + tek parametreyle, iki parametreyle ve üç parametreyle kullanabiliyorduk. Aslında üç ayrı range fonksiyonu yoktur. Tek bir range + fonksiyonu vardır. range fonksiyonu aşağıdaki gibi bir mantıkla yazılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +def disp_range(start, stop=None, step = 1): + if stop == None: + stop = start + start = 0 + + for i in range(start, stop, step): + print(i, end = ' ') + print() + +disp_range(10) +disp_range(10, 20) +disp_range(10, 20, 2) + +#------------------------------------------------------------------------------------------------------------------------ + Daha önceden de belirttiğimiz gibi Python'da bir fonksiyonu ikinci kez tanımlamak aslında aynı değişkene ikinci kez + atama yapmak gibi bir etki oluşturmaktadır. Örneğin: + + def foo(a): + pass + + def foo(a, b): + pass + + C++, Java ve C# gibi dillerde aynı isimli farklı parametrik yapılara sahip fonksiyonlar bir arada bulunabilmektedir. + Oysa Python'da böyle bir durum yoktur. Bu yukarıdaki kod parçasında foo fonksiyonunu çağırırsak artık ikinci + fonksiyonu çağırmış oluruz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bazı programlama dillerinde parametre listesinde bulunan bir değişken daha sonra default argüman olarak kullanılabilmektedir. + Python'da böyle bir kullanım geçerli değildir. Örneğin: + + def foo(a, b = a): + pass + + Burada biz b parametre değişkenine a'yı default argüman olarak veremeyiz. Bu durum bir sentaks hatası oluşturacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python gibi dinamik tür sistemine sahip programlama dillerinde fonksiyonların parametre değişkenlerinin belli bir türü + olmadığı için fonksiyonlar yanlış türden argümanlarla çağrılırsa programın çalışma zamanı sırasında exception oluşarak program + çökebilir ya da program çökmeden yanlış bir biçimde çalışabilir. Genel olarak programlamada bir kodun hiç çalışmaması yanlış + çalışmasına tercih edilmektedir. (Yani kodun hatalı çalışmasındansa exception oluşarak hiç çalışmaması daha tercih edilir + bir durumdur.) Örneğin daha önceden yazmış olduğumuz banner fonksiyonunun normal olarak bir string ile çağrılması gerekir. + Ancak biz bu fonksiyonu bir string ile çağırmazsak programımız exception ile çökecektir. İşte Python'da fonksiyon çağrılarında + argümanların uygun bir biçimde girilmesi programcının sorumluluğundadır. Benzer biçimde bu banner örneğinde fonksiyonun + ikinci parametresinin bir karakter uzunluğunda bir string olarak girilmesi gerekmektedir. Aksi takdirde ya exception + oluşacak ya da program yanlış çalışacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +def banner(s, ch='-'): + print(ch * len(s)) + print(s) + print(ch * len(s)) + +banner('ankara') +banner('ankara', 10) # program çökmeyecek ama istenildiği gibi çalışmayacaktır +banner(10) # burada exception oluşacak, çünkü len fonksiyonu int türüne uygulanamaz! + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyon yazarken programcı yine de fonksiyon parametreleri üzerinde programın çalışma zamanı sırasında tür kontrolü + uygulayabilir. Bunun için isinstance isimli built-in fonksiyon kullanılmaktadır. isinstance fonksiyonunun birinci parametresi + bir ifade, ikinci parametresi ise bir tür ismi olarak girilir. Fonksiyon birinci parametresiyle belirtilen ifadenin ikinci + parametresiyle belirtilen türden (ya da o türden türemiş bir sınıf türünden) olup olmadığına ilişkin bool bir değer geri + döndürmektedir. Örneğin: + + if isinstance(s, str): + pass + + Burada s değişkeninin str türünden olup olmadığına bakılmaktadır. Fonksiyonun ikinci parametresi türlerden oluşan bir demet biçiminde de girilebilir. + Bu durum "veya" anlamına gelmektedir. Örneğin: + + if isinstance(a, (int, float, complex)): + pass + + Burada a değişkeni int, float ya da complex türündense isinstance True değerine geri dönecektir. + + Python programcıları eğer fonksiyonun parametreleri üzerinde tür kontrolü yapıp onların uygun türlerdne olmadığını görürlerse + genellikle exception oluşturup fonksiyonun çalışmasını engellerler. Exception raise isimli bir deyimle oluşturulmaktadır. Bu + konu ileride ayrıntılarıyla ele alınmaktadır. Örneğin: + + def disp_square(a): + if not isinstance(a, (int, float)): + raise TypeError('argument must be either int or float') + print(a * a) + + Burada disp_square fonksiyonuna programcının int ya da float türden bir argüman geçmesi istenmektedir. Eğer programcı + int ya da float türden argüman geçmezse exception oluşturulmuştur. + + Ancak Python programcıları fonksiyon yazarken çoğu kez fonksiyon içerisinde isinstance ile parametreler üzerinde tür kontrolü yapmazlar. + Bu durumda eğer fonksiyon uygunsuz türlerle çağrılırsa muhtemelen bir biçimde bir exception oluşacaktır. Fonksiyonun doğru türlerle + çağrılması onu çağıran kişinin sorumlulupundadır. Yani eğer fonksiyon doğru türlerle çağrılmazsa sonucuna onu çağıran programcı + katlanacaktır. + + Aşağıdaki örnekte banner fonksiyonunda isinstance fonksiyonu ile tür kontrolü uygulanmıştır. Burada eğer programcı banner + fonksiyonunu çağırırken birinci pamatreye str türünden bir argüman geçmezse ya da ikinci parametreye tek karakterli bir + str türünden argüman geçmezse exception oluşturulmuştur. +#------------------------------------------------------------------------------------------------------------------------ + +def banner(s, ch='-'): + if not isinstance(s, str): + raise TypeError('first argument must be string') + if not isinstance(ch, str) or len(ch) != 1: + raise TypeError('second argument must be one charater string') + print(ch * len(s)) + print(s) + print(ch * len(s)) + +banner('ankara', 'ali') + +#------------------------------------------------------------------------------------------------------------------------ + Bazen programcılar fonksiyon içerisinde parametre türlerini kontrol edip o türlere uygun bir çalışma sunabilirler. + Aşağıdaki örnekte banner fonksiyonun birinci parametresi int ya da float ise önce o yazıya dönüştürülmüş sonra banner + işlemi yapılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +def banner(s, ch='-'): + if isinstance(s, (int, float)): + s = str(s) + print(ch * len(s)) + print(s) + print(ch * len(s)) + +banner('ankara') +banner(10) +banner(12.4) + +#------------------------------------------------------------------------------------------------------------------------ + Python 3.10 ile birlikte birden fazla türün veya işlemine "|" operatörü ile sokulması sağlanmıştır. Örneğin: + + if isinstance(a, (int , float)): + pass + + Bu işlem Python 3.10 ve sonrasında aşağıdaki gibi de yapılabilmektedir: + + if isinstance(a, int|float): + pass + + Buradaki int|float ifadesi "int türü ya da float türü" anlamına gelmektedir. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyon çağrılırken arümanlara isim verilerek o argümanların hangi parametreler için girildiği belirtilebilmektedir. + Bu tür argümanlara Python referans kitabında "keyword arguments" denilmektedir. Ancak biz kurusumuzda bunlara "isimli argümanlar" + diyeceğiz. İsimli argümanlar "parametre_ismi=ifade" sentaksıyla kullanılmaktadır. Örneğin: + + def foo(a, b, c): + pass + + foo(c=100, a=200, b=300) + + Normal argümanlara (yani isimli olmayan argümanlara) Python referans kitabında "positional arguments" denilmektedir. + Biz kurumuzda bunları vurgulamak için "pozisyonel argümanlar" diyeceğiz. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c): + print(f'a = {a}, b = {b}, c = {c}') + +foo(10, 20, 30) # a = 10, b = 20, c = 30 +foo(c=100, a=200, b=300) # a = 200, b = 300, c = 100 + +#------------------------------------------------------------------------------------------------------------------------ + Pozisyonel argümanlarla isimli argümanları fonksiyon çağrısında bir arada kullanabiliriz. Ancak çağrıda isimli argümanların + sonda toplaşmış olması gerekir. Başka bir deyişle bir argüman isimli olarak girilmişse onun sağındakilerin hepsinin isimli olarak + girilmiş olması gerekmektedir. Örneğin: + + def foo(a, b, c): + pass + + foo(100, c=300, b=200) # geçerli + foo(100, c=300, 200) # error! +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c): + print(f'a = {a}, b = {b}, c = {c}') + +foo(100, c=200, b=300) # a = 100, b = 300, c = 200 + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyon çağrısında default argümanlar da dikkate alınarak tüm parametre değişkenlerine bir ve yalnızca bir kez + değer verilmiş olmalıdır. Eğer bir parametre değişkenine hiç değer verilmemişse ya da birden fazla kez değer verilmişse bu durum + error ile sonuçlanır. Örneğin: + + def foo(a, b, c): + pass + + foo(10, 20) # error! c değer almamış + foo(10, a=20, c=30) # error! a iki kez değer almış, b değer almamış + foo(100, 200, b=300, c=400) # error! b iki kez değer almış. + + Örneğin: + + def bar(a, b, c = 10, d = 20): + pass + + bar(10, 20) # geçerli, tüm parametreler bir ve yalnızca bir kez değer almış + bar(b=100, a=200, d=300) # geçerli, tüm parametreler bir ve yalnızca bir kez değer almış + bar(100, 200, 300, c=400, d=500) # error! c iki kez değer almış + bar(100, 200, 300) # geçerli, c iki kez değer almamış, c'nin değerini biz vermişiz +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi isimli argümanlara neden gereksinim duyulmaktadır. Aslında isimli argümanlara iki nedenden dolayı gereksinim duyulmaktadır: + + 1) Çok sayıda default değer alan parametre değişkenlerinin bulunduğu fonksiyonda default değer almış bir parametre değişkenine + istenilen bir değeri pratik bir biçimde geçirebilmek için. Örneğin: + + def foo(a, b, c=10, d=20, e=30, f=60, g=70): + pass + + Bu fonksiyonda biz en azından a ve b parametreleri için argüman girmek zorundayız. Ancak f parametresi için verilmiş default değerin + dışında bir değer girmek istersek isimli argümanlar bize kolaylık sağlamaktadır: + + foo(100, 200, f=300) + + Eğer isimli argümanlar olmasaydı biz f'ye kadarki parametre değişkenleri için o default değerleri argüman olarak belirtmek + zorunda kalırdık. Örneğin: + + foo(100, 200, 10, 20, 30, 100) + + 2) İsimli argümanlar okunabilirliği artırmak için de kullanılabilmektedir. Örneğin: + + val = int(s, base=16) + + Burada aslında isimli argüman kullanmaya gerek yoktur. Ancak programcı buradaki 16'nın taban belirttiğini vurgulamak için + isimli argüman kullanımını tercih etmiş olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz kursumuzda atama işleminde '=' atomunun iki tarafında birer boşluk karakteri bırakıyoruz. Ancak Python programcıları + genel olarak (ancak hepsi değil) isimli argüman belirtirken '=' atomunun iki yanına boşluk karakteri girmemektedir. Biz de + kursumuzda bu yazım biçimini tercih edeceğiz. Default argümanlarda bazı programcılar boşluk bırakırken bazıları bırakmamaktadır. + Biz kursumuzda default argümanlarda '=' atomunun iki tarafına boşluk bırakacağız. Python yazım stilini anlatan "PEP 8" + dokümanlarında her iki durumda da '=' atomunun iki yanında boşluk nırakılmamaktadır. Bu durum "Google Python Style Guide" + dokğmanında da aynı biçimdedir. Örneğin: + + def disp(a, base=10): + pass + + disp(100, base=16) + + Ancak biz parametre değişkenlerine default argüman verirken '=' atomunun iki yanına boşluk bırakacağız. Örneğin: + + def disp(a, base = 10): + pass + + disp(100, base=16) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonun parametre listesinde parametre değişkeni yerine '*' kullanılırsa '*'ın sağındaki tüm parametreler çağrım sırasında + isimli argümanlarla çağrılmak zorundadır. Böylece fonksiyonu yazan kişi okunabilirliği artırmak amacıyla çağıran kişiyi isimli argüman + kullanmaya zorlayabilmektedir. Örneğin: + + def foo(a, b, *, c, d): + pass + + Parametredeki "*" gerçek bir parametre değildir. Yani yukarıdaki foo fonksiyonunun 4 parametresi vardır. Buradaki "*" + bunun sağındaki parametreler için çağrım sırasında isimli argüman girilmesinin zorunlu olduğunu belirtmektedir. Örneğin: + + foo(100, 200, 300, 400) # error! c ve d isimli kullanılmak zorunda + foo(100, 200, c=300, d=400) # geçerli + foo(100, 200, d=400, c=300) # geçerli + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonun parametre listesinde bir parametre yalnızca '/' biçiminde girilmişse bu durum "onun solundaki tüm parametreler + argümanların pozisyonel olarak girilmesi gerektiği" anlamına gelmektedir. Örneğin: + + def foo(a, b, /, c = 100, d = 200): + pass + + Burada a ve b parametreleri isimli girilemez. Pozisyonel girilmek zorundadır. Yine parametre listesindeki '/' karakteri + gerçek bir parametre değildir. Dolayısıyla yukarıdaki fonksiyonun 4 parametresi vardır. Bu '/' parametresi bunun solundaki + parametreler için çağrım sırasında isimli argüman girilemeyeceği anlamına gelmektedir. Örneğin: + + foo(10, 20, 30, 40) # geçerli + bar(a=10, b=20) # error! a ve b pozisyonel girilmek zorundadır. + + Örneğin: + + def disp_pixel(x, y, /): + pass + + disp_pixel(10, 20) # geçerli + disp_pixel(10, y=20) # error! + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, /, c, d): + print(a, b, c, d) + +foo(10, 20, c=30, d=40) # 10 20 30 40 + +#------------------------------------------------------------------------------------------------------------------------ + * ile / parametre belirleyicileri aynı anda kullanılabilir. Ancak bu durumda önce / sonra * parametre belirleyicilerinin + gelmesi gerekir. Örneğin: + + def foo(a, b, /, c, *, d, e): + print(a, b, c, d, e) + + Yukarıdaki örnekte / parametresinin solu pozisyonel biçimde, * parametresinin sağı isimli biçimde kullanılmak zorundadır. + Ancak / ile * arasındaki parametreler için isimli ya da pozisyonel argüman girilebilir. Yani bu örnekte a ve b parametrelerinin + pozisyonel argümanlarla, d ve e parametrelerinin isimli argümanlarla kullanılması zorunludur. Ancak c parametresi pozisyonel + ya da isili kullanılabilir. Örneğin: + + foo(10, 20, 30, d=40, e=50) # geçerli + foo(10, 20, c=30, d=40, e=50) # geçerli + foo(10, 20, 30, 40, e=50) # error! d isimli kullanılmak zorunda + + Pekiyi programcıyı pozisyonel argüman girmeye zorlamanın bir anlamı olabilir mi? Aslında her ne kadar isimli argüman girmek okunabilirliği + artırıyorsa da bazen tam tersi olabilmektedir. Örneğin sayının sinüsünü alan fonksiyonu düşünelim. Bu fonksiyonun parametresinin isminin + bir önemi var mıdır? Burada isim belirtilirse kodu inceleyen kişide tam tersine bir tereddüt oluşur. Örneğin: + + def sin(x, /): + pass +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, /, c, *, d, e): + print(a, b, c, d, e) + +foo(10, 20, 30, d=40, e=50) # geçerli1 +foo(10, 20, c=30, d=40, e=50) # geçerli + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonların *'lı parametreleri olabilir. Ancak fonksiyonun parametre listesinde yalnızca bir parametre değişkeni + önüne * getirilerek belirtilebilir. Örneğin: + + def foo(a, b, *c): + pass + + Bunlara *'lı parametre diyeceğiz. *'lı parametreler en fazla bir tane olabilir. Örneğin: + + def foo(a, b, *c, *d): # error! + pass + + *'lı parametre genellikle parametre listesinin sonunda olsa da böyle bir zorunluluk yoktur. Örneğin: + + def foo(a, *b, c): # geçerli + pass + + *'lı parametre sıfır tane ya da daha fazla argümanla eşleşir. Yorumlayıcı çağırma ifadesindeki argümanları bir demete yerleştirip + demeti *'lı parametreye geçirmektedir. Yani *'lı parametre her zaman bir demet türündendir. Örneğin: + + def foo(a, b, *c): + pass + + foo(10, 20, 30, 40, 50) + + Burada 10 a parametresi ile, 20 b parametresi ile 30, 40 ve 50 bir demet haline getirilerek c parametresi ile eşleştirilecektir. + Örneğin: + + foo(10, 20) + + Burada yine 10 değeri a parametre değişkenine, 20 değeri b parametre değişkenine atanacaktır. c paramatre değişkenine + ise boş bir demet geçirelecektir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, *c): + print(f'a = {a}, b = {b}, c = {c}') + +foo(10, 20, 30, 40, 50, 60) # a = 10, b = 20, c = (30, 40, 50, 60) +foo(10, 20, 30) # a = 10, b = 20, c = (30,) +foo(10, 20) # a = 10, b = 20, c = () + +#------------------------------------------------------------------------------------------------------------------------ + 30. Ders 27/07/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + *'lı parametre parametre listesinin sonunda olmak zorunda değildir. Ancak her parametre değişkeninin bir ve yalnızca + bir kez değer almış olması gerekir. Örneğin: + + def foo(a, *b, c): + pass + + foo(10, 20, 30) # error! c değer almamış + + Burada 20 ve 30 c için girilmiş gibi olmaktadır. Dolayısıyla c paramere değişkeni değer almamış durumdadır. Bu tür durumlarda + *'lı parametrenin sağındaki parametre değişkenlerine mecburen isimli bir biçimde argüman girilmelidir. Örneğin: + + foo(10, 20, 30, 40, c=50) # geçerli + + Burada 10 argümanı a ile, (20, 30, 40) argümanları b ile eşleşmektedir. 50 argümanı b *'lı parametresinin bir parçası değildir. + Çünkü açıkça isimli bir biçimde belirtilmiştir. Dolayısıyla 50 c argümanı ile eşleşecektir. Burdada her parametre değişkeni daha önce + de belirttiğimöiz gibi en az bir kez ve en fazla bir kez değer almış durumdadır. Örneğin: + + def foo(a, *b, c = 100): + pass + + foo(10, 20, 30) # geçerli, c de değer almış + + Gördüğünüz gibi *'lı parametre parametre listesinin sonunda değilse *'lı parametrenin sağındaki parametrelerin ya default + değer almış olması gerekir ya da argüman listesinde isimli bir biçimde kullanılmış olması gerekir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, *b, c, d): + print(f'a = {a}, b = {b}, c = {c}, d = {d}') + +foo(10, 20, 30, c=100, d=200) # geçerli, a = 10, b = (20, 30), c = 100, d = 200 + +#------------------------------------------------------------------------------------------------------------------------ + *'lı parametrenin her zaman demet belirttiğine dikkat ediniz. Demetler dolaşılabilir nesneler olduğuna göre biz + *'lı parametreyi for döngüsi içerisinde dolaşılbiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +def add(*args): + total = 0 + for x in args: + total += x + + return total + +result = add() +print(result) # 0 + +result = add(10) +print(result) # 10 + +result = add(10, 20, 30) +print(result) # 60 + +#------------------------------------------------------------------------------------------------------------------------ + Aslında print fonksiyonu da *'lı parametre almaktadır. Biz de print fonksiyonunu myprint ismiyle yazabiliriz. Tabii + burada ekrana yazdırma için yine orijinal print fonksiyonunu kullanacağız. Aşağıdaki örnek yalnızca print fonksiyonunun nasıl + yazılmış olabileceğini göstermek amacıyla verilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +def myprint(*objects, sep=' ', end='\n'): + i = 0 + while i < len(objects): + if i != 0: + print(end=sep) + print(objects[i], end='') + i += 1 + print(end=end) + +myprint(10, 20, 30) +myprint(10, 20, 30, sep='*') +myprint(10, 20, 30, sep='*', end='/') + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki myprint fonksiyonunu for döngüsüyle de yapabilirdik. Ancak for döngüsü bu tür durumlarda daha zahmetli + bir çözüm sunabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +def myprint(*objects, sep=' ', end='\n'): + if len(objects): + print(objects[0], end='') + for x in objects[1:]: + print(end=sep) + print(x, end='') + print(end=end) + +myprint(10, 20, 30) +myprint(10, 20, 30, sep='*') +myprint(10, 20, 30, sep='*', end='/') + +#------------------------------------------------------------------------------------------------------------------------ + print fonksiyonuna hiç argüman girilmemişse fonksiyon nasıl çalışacaktır? Örneğin: + + print() + + Burada fonksiyonun *'lı parametresine boş bir demet aktarılacaktır. Dolayısıyla fonksiyon bir şey yazdırmyacaktır. + Fonksiyon bir şey yazdırmayacağına göre sep parametresi de kullanılmayacaktır. Çünkü sep parametresi print fonksiyonu + birden fazla argümanı yazdırırsa onların arasına yerleştirilecek karakteri belirtmektedir. print fonksiyonu işini bitirince + end parametresiyle belirtilen yazıyı ekrana (stdout dosyasına) bastıracağına göre boş print yalnızca imlecin aşağı satırın + başına geçmesine yol açacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii biz *'lı parametreler için listeler, demetler, kümeler ve sözlükler de girebiliriz. Örneğin: + + def foo(*a): + pass + + Burada biz fonksiyonu şöyle çağırmış olalım: + + foo(10, 20, 30) + + 10, 20 ve 30 değerleri bir demet haline getirilerek fonksiyonun a parametre değişkenine geçirilecektir. Şimdi fonksiyonu şöyle + çağırmış olalım: + + foo([10, 20, 30]) + + Burada a parametre değişkenine tek elemanlı bir demet aktarılacaktır. Demetin tek elemanı da liste olacaktır. Örneğin: + + foo((10, 20, 30)) + + Burada a parametre değişkenine yine tek elemanlı bir demet aktarılacaktır. Demetin tek elemanı da (10, 20, 30) demeti + olacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(*a): + for x in a: + print(x, end=' ') + print() + +foo(10) # 10 +foo(10, 20, 30) # 10 20 30 +foo([10, 20, 30]) # [10, 20, 30] +foo((10, 20, 30)) # (10, 20, 30) + +#------------------------------------------------------------------------------------------------------------------------ + Python'ın standart kütüphanesindeki min ve max isimli built-in fonksiyonlar bir dolaşılabilir nesneyi alıp onun en küçük + ve en büyük elemanlarına geri dönmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [34, 56, 21, 76, 18] + +result = max(a) +print(result) # 76 + +result = min(a) +print(result) # 21 + +#------------------------------------------------------------------------------------------------------------------------ + Aslında min ve max fonksiyonlarına birden fazla argüman da girilebilmektedir. Bu durumda bu argümanların en büyük ve + en küçük değerleri elde edilir. +#------------------------------------------------------------------------------------------------------------------------ + +result = max(89, 79, 34) +print(result) # 89 + +result = min(4, 67, 1, 68) +print(result) # 1 + +#------------------------------------------------------------------------------------------------------------------------ + Görüldüğü gibi eğer biz min ve max fonksiyonlarına tek eleman girersek o argümanın dolaşılabilir olması gerekir. + Ancak biz bu fonksiyonlara birden fazla argüman girersek bu fonksiyonlar o argümanların en küçük ve en büyük değerlerini bulular. + Pekiyi min ve max fonksiyonları nasıl yazılmış olabilir? Bu fonksiyonlar birden fazla argümanı kabul ettiğine göre *'lı bir + parametreye sahip olmalıdır. O zaman bu *'lı parametreye aktarılan argüman bir tane ise biz onu dolaşılabilir bir nesne kabul edip + onun en küçük ya da en büyük elemanını bulmaya çalışırız. Eğer bu *'lı parametreye birden fazla argüman girilmişse bu + durumda biz bu *'lı parametreye aktarılan demetin en küçük ya da en büyük elemanını bulmaya çalışırırz. Dolaşılabilir bir nesnenin + en küçük ya da en büyük elemanını bulmak için ilk eleman en küçük ya da en büyük kabul edilip bir değişken de saklanır. Sonraki elemanlar + bu değişkendekiyle karşılaştırılıp değişkenin değeri güncellenir. Biz ileride dolaşılabilir nesnelerin elemanlarını for döngüsü olmadan + da ilerletebileceğiz. Örneğin next isimli built-in fonksiyon bunun için kullanılmaktadır. Ancak biz henüz dolaşılabilir nesnelerin + ayrıntılarını bilmiyoruz. Bu nedenle gerçekleştirimi aşağıdaki gibi yapıyoruz. + #------------------------------------------------------------------------------------------------------------------------ + +def mymax(*args): + iterable = args[0] if len(args) == 1 else args + + maxval = None + for x in iterable: + if maxval == None or x > maxval: + maxval = x + + return maxval + +a = [1, 6, 3, 2, 5] + +result = mymax(a) +print(result) # 6 + +result = mymax(4, 16, 2, 3) +print(result) # 16 + + +def mymin(*args): + iterable = args[0] if len(args) == 1 else args + + minval = None + for x in iterable: + if minval == None or x < minval: + minval = x + + return minval + +a = [1, 6, 3, 2, 5] + +result = mymin(a) +print(result) # 1 + +result = mymin(4, 16, 2, 3) +print(result) # 2 + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonların *'lı parametreleri için fonksiyon çağrılırken isimli argüman girilememektedir. Örneğin: + + def foo(a, b, *c): + pass + + foo(10, 20, c=30) # error! + foo(10, 20, c=(30, 40, 5)) # error! + + Fonksiyonun *'lı parametreleri default argüman alamaz. Örneğin: + + def foo(a, b, *c = (10, 20, 30)): # error! *'lı parametrelere default değer veremeyiz! + pass + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 31. Ders 01/08/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonların *'lı parametrelerinin yanı sıra bir de **'lı parametreleri bulunabilmektedir. Bir fonksiyonun **'lı parametresi + eğer bulundurulacaksa parametre listesinin sonunda bulundurulmak zorundadır. (Halbuki *'lı parametre listesinin herhangi bir yerinde + bulundurulabilmektedir.) Fonksiyonun tek bir **'lı parametresi olabilir. Geleneksel olarak pek çok programcı fonksiyonun *'lı parametresine + args ismini **'lı parametresine ise kwargs ya da kargs ismini vermektedir. + + Bir fonksiyon aslında olmayan parametrelere ilişkin isimli argümanlarla çağrılırsa, yorumlayıcı bu isimli argümanları bir sözlükte + toplayarak sözlüğü fonksiyonun **'lı parametresine geçirmektedir. Olmayan parametrelere ilişkin isimli argümanların argüman isimleri + sözlüğün anahtarları, onlara '=' ile verilen değerler de o anahtarlara karşı gelen değerleri oluşturmaktadır. Buradaki anahtarlar + her zaman str türündendir. Örneğin: + + def foo(a, *args, **kwargs): + print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs)) + + Biz bu fonksiyonu şöyle çağırmış olalım: + + foo(10, 20, 30, 40, xx='ali', yy=100) + + Görüldüğü gibi aslında fonksiyonun xx ve yy isimli parametre değişkenleri yoktur. Yorumlayıcı önce {'xx': 'ali', 'yy': 100} + biçiminde bir sözlük nesnesi oluşturur sonra bu sözlük nesnesini **'lı parametreye aktarır. Örneğin: + + foo(10, 20, 30, 40) + + Burada fonksiyon olmayan bir parametre ismiyle çağrılmamıştır. Bu durumda **'lı parametreye boş sözlük geçirilecektir. + Tıpkı *'lı parametrelerde olduğu gibi fonksiyonun **'lı parametreleri de default değer alamaz ve bunlara isimli olarak + argüman geçirilemez. + + Mademki *'lı ve **'lı parametreler için isimli argüman girilememektedir. Bu durumda eğer bu argümanlar isimli kullanılırsa + ve fonksiyonun da **'lı bir parametresi varsa bu argümanlar "olmayan parametre değişkenleri gibi" değerlendirilecek ve bir + sözlük biçiminde **'lı parametreye aktarılacaktır. Örneğin: + + def foo(a, *args, **kwargs): + print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs)) + + foo(10, args=20, kwargs=30) + + Buradan şöyle bir çıktı elde edilecektir: + + a = 10, args = (), kwargs = {'args': 20, 'kwargs': 30} + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, *args, **kwargs): + print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs)) + +foo(10, 20, 30, 40, xx='ali', yy=100) # a = 10, args = (20, 30, 40), kwargs = {'xx': 'ali', 'yy': 100} +foo(10, 20, 30, 40) # a = 10, args = (20, 30, 40), kwargs = {} +foo(a=10) # a = 10, args = (), kwargs = {} +foo(10, args=(100, 200)) # a = 10, args = (), kwargs = {'args': (100, 200)} +foo(10, 20, 30, kwargs={}) # a = 10, args = (20, 30), kwargs = {'kwargs': {}} + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi fonksiyonların **'lı parametreleri neden kullanılmaktadır? Bunun iki nedeni vardır: Birincisi bir fonksiyon çok fazla + parametreye sahipse parametre sayısını azaltmak için **'lı parametre kullanılabilmektedir. Örneğin bir fonksiyon için 50 tane parametre + söz konusu olsun. Bu 50 parametrenin parametre listesinde belirtilmesi okunabilirliği bozar. Üstelik bu parametreler başka fonksiyonlarda da + söz konusu ise hem programcının bunları yazması zorlaşır hem de okunabilirlik düşer. İşte bu durumda **'lı parametreler + kolaylık sağlamaktadır. Şimdi foo fonksiyonun a, b, c, d, e, f, g, h, i, j, k, l, m parametrelerinin olduğunu düşünelim. Bu parametrelerin + ilk ikisi dışındakiler default değer alıyor olsun. Foksiyonu aşağıdaki gibi tanımlamış olalım: + + def foo(a, b, **kwargs): + pass + + Burada fonksiyonu çağıran onu aşağıdaki gibi çağırabilir: + + foo(10, 20, c=100, f=200, k=300, j=400) + foo(10, 20, m=200) + + Görüldüğü gibi **kwargs parametresi bu çağrımlara izin vermektedir. Tabii bu durumda aşağıdaki gibi bir çağrım da geçerli olacaktır: + + foo(10, 20, r=20) + + Halbuki fonksiyonda r biçiminde bir parametre yoktur. O halde bu tür fonksiyonları yazan kişiler aslında her isimli argümanı kabul + etmemektedir. Yalnızca daha önce belirlemiş oldukları isimli argümanları kabul etmektedir. Bu nedenle bu tür fonksiyonlarda + genellikle işin başında bir parametre kontrolü yapılmaktadır. Örneğin: + + def foo(a, b, **kwargs): # c, d, e, f, g, h, i, j, k, l, m + legal_args = ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'] + + for key in kwargs: + if key not in legal_args: + print(f'invalid argument: {key}={kwargs[key]}') + return + + print('Ok') + + foo(10, 20, c=10, f=20, i=30, k=40) # Ok + foo(10, 20, c=10, r=30) # invalid argument: r=30 + + Burada programcı işin başında isimli argümanlar için geçerlilik kontrolü yapmıştır. Yani fonksiyonun **'lı parametreye sahip + olması onun istenildiği gibi olmayan bir isimli argümanla çağrılabileceği anlamına gelmemektedir. + + Tabii aslında isimli argümanlar için default değerler de söz konusu olabilmektedir. Örneğin c=100, d=200, e=300, f=400, + g=500, h=600, i=700, j=800, k=900, l=1000, m=1100, default değerleri söz konusu olsun. Yani fonksiyonu çağıran bu değerleri + girmezse buradaki değerler kullanılacak olsun. Bu mekanizmayı basit bir biçimde şöyle sağlayabiliriz: + + def foo(a, b, **kwargs): + legal_args = ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'] + + for key in kwargs: + if key not in legal_args: + print(f'invalid argument: {key}={kwargs[key]}') + return + + c = kwargs.get('c', 100) + d = kwargs.get('d', 200) + e = kwargs.get('e', 300) + f = kwargs.get('f', 400) + g = kwargs.get('g', 500) + h = kwargs.get('h', 600) + i = kwargs.get('i', 700) + j = kwargs.get('j', 800) + k = kwargs.get('k', 900) + l = kwargs.get('l', 1000) + m = kwargs.get('m', 1100) + + print(f'c = {c}, d = {d}, e = {e}, f = {f}, g = {g}, h = {h}, i = {i}, j = {j}, k = {k}, l = {l}, m = {m}') + + + foo(10, 20, c=10, f=20, i=30, k=40) # c = 10, d = 200, e = 300, f = 20, g = 500, h = 600, i = 30, j = 800, k = 40, l = 1000, m = 1100 + foo(10, 20) # c = 100, d = 200, e = 300, f = 400, g = 500, h = 600, i = 700, j = 800, k = 900, l = 1000, m = 1100 + + Parametre saysı çok fazlaysa yukarıdaki yöntem biraz zahmetli olmaya başlayabilir. Bu durumda isimli argümanlar default değerleriyle + bir sözlükte toplanabilir. Sonra bu sözlükteki değerler bir döngüyle girilen isimli argümanlar için güncellenebilir: + + def foo(a, b, **kwargs): + legal_args = {'c': 100, 'd': 200, 'e': 300, 'f': 400, 'g': 500, 'h': 600, 'i': 700, 'j': 800, 'k': 900, 'l': 1000, 'm': 1100} + + for key in kwargs: + if key not in legal_args: + print(f'invalid argument: {key}={kwargs[key]}') + return + + for key, value in kwargs.items(): + legal_args[key] = value + + for key, value in legal_args.items(): + print(f'parameter: {key}, value: {value}') + + foo(10, 20, c=10, f=20, i=30, k=40) + print() + foo(10, 20) + + Tabii yukarıdaki örnekte ilk döngü ile ikinci döngü de duruma göre aşağıdaki gibi birleştirilebilir: + + def foo(a, b, **kwargs): + legal_args = {'c': 100, 'd': 200, 'e': 300, 'f': 400, 'g': 500, 'h': 600, 'i': 700, 'j': 800, 'k': 900, 'l': 1000, 'm': 1100} + + for key, value in kwargs.items(): + if key not in legal_args: + print(f'invalid argument: {key}={kwargs[key]}') + return + else: + legal_args[key] = value + + + for key, value in legal_args.items(): + print(f'parameter: {key}, value: {value}') + + foo(10, 20, c=10, f=20, i=30, k=40) + print() + foo(10, 20) + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, **kwargs): + legal_args = {'c': 100, 'd': 200, 'e': 300, 'f': 400, 'g': 500, 'h': 600, 'i': 700, 'j': 800, 'k': 900, 'l': 1000, 'm': 1100} + + for key, value in kwargs.items(): + if key not in legal_args: + print(f'invalid argument: {key}={kwargs[key]}') + return + else: + legal_args[key] = value + + + for key, value in legal_args.items(): + print(f'parameter: {key}, value: {value}') + +foo(10, 20, c=10, f=20, i=30, k=40) +print() +foo(10, 20) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi biz bir fonksiyonda ** parametresi gördüğümüzde ne düşünmeliyiz? İşte böyle bir fonksiyon karşısında bizim şu çıkarımı + yapmamız gerekir: "Bu fonksiyon pek çok parametreye sahip olabilir. Bu parametreleri fonksiyonu yazan tek tek belirtmek istememiş. + Ben geçerli parametreleri fonksiyonu çağırırken isim=değer biçiminde isimli argüman olarak geçebilirim. Benim geçebileceğim + isimli argümanların neler olduğunu anlamak için dokümantasyona bakmalıyım". + + **'lı parametreye örnek olarak matplotlib kütüphanesindeki plot, title ve text gibi fonksiyonlar verilebilir. plot fonksiyonu + aynı uzunlukta x ve y dolaşılabilir nesnelerini alıp onların karşılıklı elemanlarının belirttiği noktaları çizgiyle birleştirmektedir. + Ancak plot grafiğinin çok sayıda özelliği vardır. Bu özelliklerin tek tek parametrelerle ifade edilmesi çok zordur. Bu nedenle plot fonksiyonu + **'lı bir parametreyle tüm parametreleri temsil etmektedir. Benzer biçimde grafiğe başlık atmakta kullanılan title fonksiyonunda ve + yazı yazmakta kullanılan text fonksiyonunda da aynı durum geçerlidir. +#------------------------------------------------------------------------------------------------------------------------ + +import math +import matplotlib.pyplot as plt + +xpoints = [] +ypoints = [] + +x = -6. +while x <= 6: + xpoints.append(x) + ypoints.append(math.sin(x)) + x += 0.01 + +print(xpoints) + +plt.title('Sinüs Grafiği', fontsize=18, color='blue', pad=20, fontweight='bold') +plt.plot(xpoints, ypoints, color='red', linewidth=6, linestyle='--', alpha=0.5) +plt.text(2, -0.50, 'Test', fontsize=16, color='green', fontweight='bold') + +plt.show() + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonun *'lı parametresinden sonraki parametreler eğer default değer almamışsa zaten isimli argüman biçiminde kullanılabileceğine göre + şöyle bir ince kural dile eklenmiştir: Bir parametre değişkeni default değer almışsa *'lı parametreye kadar onun sağındakilerin hepsinin + default değer alması gerekir. *'lı parametreden sonraki parametreler karışık sırada default değer alan ve almayan biçiminde bulunabilir. Örneğin: + + def foo(a, b = 10, c = 20, d): # error! + pass + + def foo(a, b = 10, c = 20, *args, d): # geçerli + pass + + def foo(a, b = 10, c = 20, *args, d, e = 30, f): # geçerli + pass + +#------------------------------------------------------------------------------------------------------------------------ + **'lı parametre kullanımının ikinci nedeni "forwarding (iletme)" yapmak içindir. Buna ilişkin örnek izleyen örneklerde verilecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonları çağırırken argümanların önüne '*' atomu getirilebilir. Bu tür argümanlara "*'lı argümanlar" diyeceğiz. + *'lı argümanlar argüman listesinde birden fazla bulunabilirler. Örneğin: + + foo(a, b, *c, d, *e, f) + + *'lı argümanların "dolaşılabilir (iterable)" nesne belirtmesi gerekir. Yani *'ın yanındaki nesne bir liste olabilir, + demet olabilir, sözlük olabilir, range nesnesi olabilir ya da başka dolaşılabilir nesneler olabilir. Ancak öneğin int ve float olamaz. + Çünkü int ve float dolaşılabilir değildir. + + Bir argümanın önüne * getirildiği zaman derleyici bu dolaşılabilir nesneyi dolaşır ve dolaşımdan elde ettiği değerleri sanki programcı tek tek + girmiş gibi fonksiyona yollar. Yani örneğin: + + x = [10, 20, 30] + foo(*x) + + çağrısı tamamen aşağıdakiyle eşdeğerdir: + + foo(10, 20, 30) + + Örneğin: + + foo(*'ali') + + çağrısı da aşağıdaki eşdeğerdir: + + foo('a', 'l', 'i') + + Tabii buradan da görüldüğü gibi *'ın yanındaki dolaşılabilir nesnenin eleman sayısının fonksiyon ile uyumlu olması gerekmektedir. Örneğin: + + def foo(a, b, c): + pass + + foo(*'ali') # geçerli + foo(*'ankara') # error! fonksiyondaki parametreler yetersiz! + + Ancak örneğin: + + def foo(a, b, *args): + pass + + x = [10, 20, 30, 40, 50] + foo(100, *x) + + Bu çağrı geçerlidir. Çünkü eşdeğeri şöyledir: + + foo(100, 10, 20, 30, 40, 50) + + Burada 100 değeri a parametresine, 10 değeri b parametresine ve diğerleri de args parametresine demet olarak aktarılacaktır. Örneğin: + + def foo(*args): + pass + + Aşağıdaki çağrısı da geçerlidir: + + foo(*'ankara') +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c, d, e): + print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}') + +t = 10, 20, 30 + +foo(1, 2, *t) # a = 1, b = 2, c = 10, d = 20, e = 30 + + +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +b = {100, 200, 300} +print(*a, 10, *b) # 1 2 3 4 5 6 7 8 9 10 10 200 100 300 + + +print(*range(10, 20), sep=', ') # 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 + + +def bar(a, b, *c): + print(f'a = {a}, b = {b}, c = {c}') + +x = [10, 20, 30, 40, 50, 60] + +bar(1, *x) # a = 1, b = 10, c = (20, 30, 40, 50, 60) + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin bir liste içerisindeki değerleri aralarına ', ' koyarak yazdırmak isteyelim. Tabii böyle bir yazımın sonunda ',' + karakteri olmamlıdır. Muhtemel çözüm şöyle olabilir: + + a = [10, 20, 30, 40, 50] + + for i in range(len(a)): + if i != 0: + print(end=', ') + print(a[i], end='') + + print() + + Aslında *'lı argüman sayesinde bu işlem tek bir print ile yapılabilmektedir: + + print(*a, sep=', ') + +#------------------------------------------------------------------------------------------------------------------------ + *'lı argüman sarma fonksiyon yazarken "iletme (forwarding)" amaçlı da kullanılabilmektedir. Örneğin biz myprint isimli + fonksiyon yazmak isteyelim. Ancak fonksiyonumuz aldığı parametreleri print fonksiyonuna yollasın (forward etsin): + + def myprint(*args): + print(*args) + + myprint(10, 20, 30, 40, 50) + + Ancak buradaki iletmede bir kusur vardır. Ya sep ve end parametrelerini kullanmak istersek? + + myprint(10, 20, 30, 40, 50, sep=', ', end='*') + + Aşağıdaki gibi çözüm bu örnekte çalışabilise de genel bir çözüm oluşturmaz: + + def myprint(*args, sep=' ', end='\n'): + print(*args, sep=sep, end=end) + + myprint(10, 20, 30, 40, 50, sep=', ') +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerin de dolaşılabilir nesneler olduğunu unutmayınız. +#------------------------------------------------------------------------------------------------------------------------ + +d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'} + +print(*d) # 10 20 30 40 50 +print(*d.values()) # ali veli selami ayşe fatma + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonların **'lı argümanları da olabilmektedir. Bu argümanlar argüman listesinde birden fazla kez bulundurulabilmektedir. + **'lı argümanlar bir sözlük nesnesi olmak zorundadır. Bu argümanlara ilişkin sözlük nesnelerinin anahtarlarının str türünden olması + gerekir. Ancak değerleri herhangi bir türden olabilir. + + Yorumlayıcı argümanda ** gördüğünde argümana ilişkin sözlük nesnesini dolaşır. Anahtarları argümanların isimleri, değerleri + de o isimli argümanlara onlara verilmiş değerler kabul ederek fonksiyonu çağırır. Örneğin: + + def foo(a, b, c): + pass + + d = {'a': 10, 'b': 'ankara', 'c': 12.3} + + Burada çağrı şöyle yapılmış olsun: + + foo(**d) + + Bu çağrının tamamen eşdeğeri şudur: + + foo(a=10, b='ankara', c=12.3) + + Aşağıdaki çağrıya dikkat ediniz: + + d = {'a': 10, 'b': 20} + + foo(**d, 100) # error! + + Bu çağrı geçersizdir. Çünkü bunun eşdeğeri şöyledir: + + foo(a=10, b=20, 100) + + İsimli argümanlardan sonra isimsiz argümanlar gelemez. Örneğin: + + def foo(a, b, c): + pass + + foo(100, *{'b': 10, 'c': 20}) + + Bu çağrı geçerlidir. Çünkü eşdeğeri şöyledir: + + foo(100, b=10, c=20) + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a, b, c, d, e, f): + print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}, f = {f}') + +d = {'c': 100, 'd': 200, 'e': 300} + +foo(10, 20, **d, f=400) # foo(10, 20, c=100, d=200, e=300, f=400) + +#------------------------------------------------------------------------------------------------------------------------ + **'lı argümanlar sarma fonksiyonlarda "iletme (forwarding)" işlemlerinde kullanılabilmektedir. Örneğin: + + def myprint(*args, **kwargs): + print(*args, **kwargs) # perfect forwarding + + myprint(10, 20, 30, sep=', ', end='*') + + Burada myprint fonksiyonuna geçirilen tüm argümanlar print fonksiyonuna mükemmel biçimde iletilmektedir. Çünkü örnek + çağrıdaki sep ve end isimli argümanları myprint fonksiyonunun kwargs parametresine sözlük nesnesi olarak geçirilecektir. + myprint fonksiyonu da bunu print fonksiyonuna aynı biçimde iletmiştir. + + "forwarding" bir bir fonksiyonun aldığı parametreleri başka bir fonksiyona argüman olarak iletmesi durumuna denilmektedir. + Yukarıdaki örnekte myrint fonksiyonu kendisi hangi argümanlarla çağrılmışsa print fonksiyonunu da o argümanlarla çağırmıştır. + + Aşağıdaki örnekte matplotlib kütüphanesindeki plot fonksiyonu myplot isimli bir fonksiyon tarafından sarmalanmış ve + parametreler plot fonksiyonuna ilketilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +import math +import matplotlib.pyplot as plt + +xpoints = [] +ypoints = [] + +x = -6. +while x <= 6: + xpoints.append(x) + ypoints.append(math.sin(x)) + x += 0.01 + +def myplot(*args, **kwargs): + plt.plot(*args, **kwargs) + +plt.title('Sinüs Grafiği', fontsize=18, color='blue', pad=20, fontweight='bold') +myplot(xpoints, ypoints, color='red', linewidth=6, linestyle='--', alpha=0.5) +plt.text(2, -0.50, 'Test', fontsize=16, color='green', fontweight='bold') + +plt.show() + +#------------------------------------------------------------------------------------------------------------------------ + 32. Ders 03/08/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir listenin ya da demetin elemanı da *'lı bir dolaşılabilir nesne olabilir. Bu durumda bu dolaşılabilir nesne dolaşılır. + Sanki onların değerleri liste ya da demete eleman yapılmış gibi olur. Örneğin: + + a = 1, 2, 3, 4, 5 + b = [10, 20, *a, 30, 40] + + Tabii listeleri ve demetleri oluştururken istediğimiz kadar çok *'lı eleman kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, *range(5), 30, 40] +print(a) # [10, 20, 0, 1, 2, 3, 4, 30, 40] + +t = 10, 20, *'ali', 30, 40 +print(t) # (10, 20, 'a', 'l', 'i', 30, 40) + +#------------------------------------------------------------------------------------------------------------------------ + Benzer biçimde bir sözlük oluşturulurken sözlüğün elemanları da **'lı nesneler olabilir. Bu duurmda **'ın sağındaki sözlüğün + elemanları o sözlüğün elemanlarına eklenmiş olur. Örneğin: + + d = {'süleyman': 100, 'sacit': 200} + k = {'ali': 10, 'veli': 20, 'selami': 30, **d, 'ayşe': 40, 'fatma': 50} + + Tabii burada artık **'ın sağındaki sözlüğün anahtarlarının birer string olması gerekmez. Biz bu biçimde sözlük oluştururken + istediğimiz kadar çok **'lı eleman kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'süleyman': 100, 'sacit': 200} +k = {'ali': 10, 'veli': 20, 'selami': 30, **d, 'ayşe': 40, 'fatma': 50} + +print(k) # {'ali': 10, 'veli': 20, 'selami': 30, 'süleyman': 100, 'sacit': 200, 'ayşe': 40, 'fatma': 50} + +#------------------------------------------------------------------------------------------------------------------------ + Bir çağrı sırasında argümanların girilişi ile ilgili nihai kural şöyledir: + + 1) İsimli argümanlar (keyword arguments) her zaman isimsiz (pozisyonel) argümanların sağında bulunmak zorundadır. İsimli bir argümanın sağında + isimsiz bir argüman bulunamaz.**'lı argümanlar isimli argüman olarak kabul edilmektedir. + + 2) Ancak isimli argümanın sağında bir ya da birden fazla karışık sırada *'lı ve **'lı argüman bulunabilir. + + 3) Her zaman *'lı argümanlar **'lı argümanların solunda bulunmak zorundadır. + + 4) Argüman parametre eşleşmesinde önce yalnızca isimsiz argümanlar ve *'lı argümanlar dikkate alınır. isimli ve **'lı argümanlar + nerede bulunuyor olursa olsun bu aşamada dikkate alınmazlar. İsimsiz argümanlar ve *'lı argümanlar ilk n tane parametre değişkeni ile + sırasıyla eşleştirilir. Sonra isimli argümanlar eşleştirilir. Eğer toplamda bir parametre için birden fazla değer eşleştirilmişse + ya da bir parametre için hiçbir değer eşleştirilmemişse bu durum error oluşturmaktadır. Örneğin aşağıdaki gibi bir fonksiyon olsun: + + def foo(a, b, c, d, e, f): + print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}, f = {f}') + + Aşağıdaki gibi bir çağrı geçerlidir: + + t = (10, 20) + foo(1, 2, f=10, *t, e=30) + + Burada önce isimsiz ve *'lı argümanlar eşleştirilir. 1 --> a ile, 2 --> b ile, 10 --> c ile ve 20 --> d ile eşleştirilecektir. + Sonra isimli argümanlar da eşleştirilir. Burada toplamda her parametreye yalnızca bir kez ve en fazla bir kez eşleştirme y + apılmıştır. Örneğin: + + foo(1, 2, f=10, *t, *t) + + Burada f'ye iki kez eşleştirme yapıldığı için çağrı geçersizdir. Örneğin: + + foo(1, 2, f=100, **d, c=200) + + Bu çağrı da yukarıda belirtilen kurallara tamamen uygundur. Bu nedenle geçerlidir. Örneğin: + + t = (10, 20) + + foo(1, 2, f=100, *t) + + Burada argüman kuralları uygulnamıştır. Argüman parametre eşleştirmesinde önce isimş ve *'lı argümanlar dikkate alınacaktır. + Bu durumda 1 --> a ile, 2 --> b ile, 10 --> c ile, 20 -->d ile ve f --> 100 ile eşleştirilecektir. Ancak e parametre değişkeni + değer almadığı için bu çağrı error ile sonuçlanacaktır. Örneğin: + + t = (10, 20) + d = {'f': 100, 'e': 200} + + foo(1, 2, *t, **d) + + Burada da yukarıda belirtitğimiz tüm kurallara uyulmuştur. Argüman parametre eşleştirmeleri de uygundur. O halde çağrı geçerlidir. + Örneğin: + + t = (10, 20) + d = {'f': 100, 'e': 200} + + foo(1, 2, **d, *t) + + Burada **'lı argümanın *'lı argümanın sağında olması gerekirdi. Bu nedenle bu durum error oluşturacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Açağıdaki örnekte add isimli fonksiyon *'lı paramtresiyle aldığı değerlerin toplamına geri dönmektedir. Ancak bu değerler eğer + bir demet ya da liste ise onlar da bu toplama katılmışlardır. +#------------------------------------------------------------------------------------------------------------------------ + +def add(*args): + total = 0 + for x in args: + if isinstance(x, (tuple, list)): + for y in x: + total += y + else: + total += x + + return total + +result = add(1, (2, 3), (4, 5)) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örnekte biz add fonksiyonunu aşağıdaki gibi çağıramayız: + + result = add(1, (2, (3, 4)), (4, 5)) + print(result) + + Bu tür durumlarda "özyineleme (recursion)" uygulamak gerekir. Özyineleme bir olgunun kendisine benzer bir olguyu barındırması + anlamına gelmektedir. Özyineleme programalamada tipik olarak kendi kendi çağıran fonksiyonlar yoluyla sağlanır. Biz burada + özyineleme üzerinde durmayacağız. Ancak bu özyinelemeli aşağıdaki gibi sağlanabilmktedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Listenin ve demetin elemanlarının *'lı olabileceğini belirtmiştik. Aşağıdaki programı bu bağlamda inceleyiniz. +#------------------------------------------------------------------------------------------------------------------------ + +def add(*args): + total = 0 + for x in args: + total += x + + return total + +result = add(1, 2, 3, 4, 5, *(7, *(8, 9)), *[9, 2, *(3, 5, 6)]) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + 33. Ders 08/08/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Büyük bir projenin tek bir kaynak dosya biçiminde organize edilmesi iyi bir teknik değildir. Bunun tipik nedenlerini + şöyle açıklayabiliriz: + + - Program tek bir kaynak dosyada yazılırsa dosya çok büyük olabilir. Bu da dosyanın edit edilmesini zorlaştırır. + - Programda bir değişiklik yapıldığında yeniden yorumlama işlemi gerekir. + - Programın bir proje ekibi tarafından geliştirileceği durumda tek kaynak dosya bunun için uygun değildir. Kişilerin farklı + kaynak dosyalar üzerinde aynı zaman diliminde çalışması gerekir. + + Python'da ".py" uzantılı kaynak dosyalara "modül (module)" de denilmektedir. Örneğin biz içerisinde birtakım faydalı fonksiyonlar + olan "utility.py" biçiminde bir dosya oluşturmuş olalım. Bu dosyaya aynı zamanda modül de denilmektedir. Eğer biz + Python programcısı olarak bir modül içerisindeki fonksiyonları ve global değişkenleri kendi programımızdan kullanmak istersek + önce o modülü "import etmemiz" gerekir. Import işlemi "bir modülün (yani Python kaynak dosyasının)" kullanıma hazır getirilmesi + işlemidir. Inport işlemi import deyimi ile yapılmaktadır. import deyiminin genel biçimi şöyledir: + + import [as ] [, [as ], ...] + + Burada dosya ismi uzantı içermemelidir. Örneğin biz utility.py dosyasının içerisindekileri kullanmak isteyelim. import işlemini + şöyle yapabiliriz: + + import utility + + import işlemi yapılırken as anahtar sözcüğü ile modüle bir takma isim verilebilir. Örneğin: + + import utility as util + + Aslında Python'ın standart kütüphanesindeki öğeler de .py dosyalarının içerisindedir. Onları kullanmak için bizim o modülleri + import etmemiz gerekir. Örneğin: + + import math + import statistics + + Biren fazla modülün import edilmesi tek bir import deyimi ile de modül isimlerinin arasına ',' atomu getirilerek de yapılabilir. + Örneğin: + + import math, statistics + + Tabii birden fazla modülü tek bir import deyimi ile import ederken herbir modüle as cümleciği ile takma isimler de verebiliriz. + Örneğin: + + import math as mt, statistics as st + + Bir modülü import ettikten sonra o modülün içerisindeki değişkenler modül ismiyle ya da as ile belirttiğimiz isimle "." operatörü ile + niteliklendirilerek edilerek kullanılmak zorundadır. Örneğin: + + import math + + result = math.sqrt(10) + + import deyimindeki as anahtar sözcüğü niteliklendirmede belirtilecek modül ismini değiştirmek amacıyla kullanılmaktadır. + Örneğin: + + import math as mt + + result = mt.sqrt(10) + + as ile takma isim verme genellikle uzun isimleri kısaltmak için as kullanılmaktadır. Örneğin: + + import numpy as np + + result = np.sqrt(10) + + Tabii as ile isim değiştirildiğinde modül ismi artık kullanılamaz. Örneğin: + + import math as mt + + result = mt.sqrt(10) # geçerli + result = math.sqrt(10) # error! math ismi yerine mt isminin kullanılması gerekir. + + Daha önceden belirttiğimiz gibi Python'da yorumlayıcının içine gömülmüş olan yani herhangi bir modül içerisinde olmayan + dolayısıyla da kullanmak için import işlemi gerekmeyen bir grup fonksiyona "built-in" fonksiyon denilmektedir. print, input, + max, min, type, id gibi fonksiyonlar built-in fonksiyonlardır. + + Örneğin biz sample.py dosyası içerisinde utility.py dosyasındaki foo fonksiyonunu çağırmak isteyelim. Bunun için önce + dosyayı import etmemiz gerekir: + + import utility + + utility.foo() + +#------------------------------------------------------------------------------------------------------------------------ + +# utility.py + +def add(*args): + total = 0 + for x in args: + total += x + + return total + +def multiply(*args): + total = 1 + for x in args: + total *= x + + return total + +pi = 3.14156265 + +# sample.py + +import utility as util + +result = util.add(1, 2, 3, 4, 5) +print(result) + +result = util.multiply(1, 2, 3, 4, 5) +print(result) + +print(util.pi) + +#------------------------------------------------------------------------------------------------------------------------ + import deyimi kaynak dosyanın herhangi bir yerinde bulunabilir. Bazı programcılar bütün import dosyalarını programın + tepesinde import ederler. Bazıları gerektiği zaman gerektiği yerde import ederler. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir modül import edildiğinde o modülün içerisindeki tüm kodlar (yani deyimler) çalıştırılmaktadır. Yani import etme + aslında import edilen dosyanın aynı zamanda çalıştırılması anlamına da gelmektedir. Bu nedenle import edilen dosyada + örneğin print gibi komutlar varsa bunlar da işletilecektir. + + Aşağıdaki programda sample.py çalıştırıldığında import işlemi neticesinde ekranda print fonksiyonun yazdırdığı yazılar da + görüntülenecektir. sample.py programının çalıştırılması sonucunda ekranda şu yazıları görmelisiniz: + + sample.py + one + two + three + 15 + 120 + 3.14156265 +#------------------------------------------------------------------------------------------------------------------------ + +# sample.py + +print('sample.py') + +import utility as util + +result = util.add(1, 2, 3, 4, 5) +print(result) + +result = util.multiply(1, 2, 3, 4, 5) +print(result) + +print(util.pi) + +# utility.py + +print('one') + +def add(*args): + total = 0 + for x in args: + total += x + + return total + +print('two') + +def multiply(*args): + total = 1 + for x in args: + total *= x + + return total + +pi = 3.14156265 + +print('three') + +#------------------------------------------------------------------------------------------------------------------------ + Bir modül import edildiğinde yorumlayıcı modülün içindeki kodları çalıştırır. Sonra "module" isimli bir sınıf türünden bir + nesne yaratır. Modülün içerisindeki değişkenleri bu nesneye yerleştirir besbebin adresini de import deyiminde belirtilen + değişkenin içerisine yerleştirir. Yani modül isimleri aslında module türünden nesnelerin adreslerini tutmaktadır. Örneğin: + + >>> import math + >>> type(math) + + >>> math.sqrt(10) + 3.1622776601683795 + >>> x = math + >>> x.sqrt(10) + 3.1622776601683795 + >>> type(x) + + + Burada biz modeule türünden bir değişkeni başka bir değişkene atadık. Artık iki değişken de aynı module nesnesini + gösterdiğine göre fonksiyon çağrılırken hangi ismin kullanılacağının bir önemi kalmamıştır. + + Tabii import komutunda as cümleciği ile module nesnesinin adresinin atanacağı değişkenin ismini değiştirebiliriz. + Örneğin: + + import math as m + + Burada module nesnesinin adresi m değişkenine atanmaktadır. Bu import işleminde math isimli bir değişken yaratılmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir modül birden fazla kez import edilebilir. (Bu durum geçerli olsa da anlamlı değildir.) Bu durumda modülün kodları + yalnızca modül ilk kez import edildiğinde çalıştırılır. Daha sonraki import işlemlerinde modülün içerisinde kodlar + çalıştırılmaz. Bunu siz de deneyebilirsiniz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi bir modül yanlışlıkla kendisini import ederse ne olur? Örneğin: + + # sample.py + + import sample + + print('sample') + + Biz bu sample.py" dosyasını çalıştırırsak ne olur? Burada dosya çalıştırıldığında import deyiminden dolayı "sample.py" + dosyası çalıştırılacaktır. Ancak o dosya çalıştırılırken yeniden import görülse bile bu ikinci import işlemi olacağından + dosyanın içi yeniden çalıştırılmayacaktır. import işlemindne dolayı ekrana "sample" yazısı basılacaktır. import deyiminin + çalışması bittikten sonra yine print deyimi deyimi çalıştırılacağına göre yine ekrana "sample" yazısı çıkacaktır. Yani + şöyle bir çıktı elde edilecektir: + + sample + sample + + Programcıların yanlışlıkla kaynak dosyalarına standart bir modülün ismini vermeleri sık karşılaşılan bir hatadır. Örneğin: + + # math.py + + import math + + result = math.sqrt(10) + print(result) + + Burada programcı standart modül olan math modülü yerine kendi dosyasını import etmiş olabilir. Bu durumda math.sqrt çağrımı + error oluşturacaktır. Tabii bu durum aslında standart modüllerin sys.path listesinde aranması sırasına da bağlıdır (bunu izleyen + paragraflarda ele alacağız). Siz kaynak dosyalarınıza Python'ın standart modüllerinin ismini vermemelisiniz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Modül dosyası yerel düzeyde de import edilebilir. Bu durumda bu import ismine yalnızca o fonksiyonda erişebiliriz. + (Yerel değişkenler konusu izleyen bölümlerde ele alınmaktadır.) Aslında aynı dosya nasıl import edilmiş olursa olsun + her zaman tek bir modül nesnesi yaratılmaktadır. Örneğin: + + def foo(): + import util + + print(id(util)) # 1581194445344 + + foo() + + import util + + print(id(util)) # 1581194445344 + + del util + + import util + + print(id(util)) # 1581194445344 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi bir modül import edildiğinde ilgili modül dosyası (yani .py dosyası) yorumlayıcı tarafından hangi dizinlerde + aranmaktadır? İşte Python yorumlayıcısı modülleri sys.path isimli bir listede belirtilen dizinlerde sırasıyla aramaktadır. + sys modülü Python standart kütüphanesinin içerisindeki bir modüldür. path değişkeni de bu modülün içerisindeki global + bir değişkendir. Örneğin: + + >>> sys.path + ['', 'f:\\', 'C:\\Users\\CSD\\anaconda3\\python39.zip', 'C:\\Users\\CSD\\anaconda3\\DLLs', 'C:\\Users\\CSD\\anaconda3\\lib', + 'C:\\Users\\CSD\\anaconda3', 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages', 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages\\win32', + 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Users\\CSD\\anaconda3\\lib\\site-packages\\Pythonwin'] + + Burada boş string yani '' ifadesi "çalışma dizini (current working directory)" anlamına gelmektedir. Genellikle çalışma dizini + bizim Python programını çalıştırdığımız dizindir. Ancak çalışma dizini program çalışırken de istenildiği gibi değiştirilebilir. + Default durumda çalışma dizini eğer programı komut satırından çalıştırıyorsak içinde bulunduğumuz dizindir. PyCharm IDE'si + her zaman çalışma dizinini proje dizini olarak ayarlamaktadır. Spyder IDE'si ise çalışma dizinini o anda çalıştırılan Python + programının içinde bulunduğu dizin olarak ayarlamaktadır. + + sys.path listesi program her çalıştırıldığında yeniden oluşturulmaktadır. Programcı kendi programı çalışırken bu listeye + ekleme yapabilir ya da bu listeden bazı elemanları (yani dizinleri) silebilir. Ancak bu işlem kalıcı olmaz. Program yeniden + çalıştırıldığında bu listede yine önceki çalıştırmadaki dizinler bulunacaktır. + + Aslında Python standart kütüphane dokümanlarına göre sys.path listesinin ilk elemanı boş string ya da python yorumlayıcısını + çalıştıran script dosyasının bulunduğu dizin olmak zorundadır. Ancak Python yorumlayıcıları ve birtakım IDE'ler burada belirtilen + bu kurala uymayabilmektedir. + + Pekiyi biz sys.path listesine kalıcı bir biçimde nasıl dizin ekleyebiliriz? İşte bunu yapabilmek için PYTHONPATH isimli bir + "çevre değişkeni (envirionment variable)" eklemek gerekir. Çevre değişkenleri konusu kursumuzun kapsamı dışındadır ve Derneğimizde + "Sistem Programlama ve İleri C Uygulamaları" kursunda ele alınmaktadır. Biz burada yalnızca bu çevre değişkeninin nasıl oluşturulacağını + göreceğiz. + + Windows'ta çevre değişkeni oluşturmak için "Denetim Masası/Sistem/Gelişmiş Sistem Ayarları/Ortam Değişkenleri diyalog + penceresi kullanılmaktadır. Eğer birden fazla dizin girilecekse dizinler arasında ';' bulundurulmalıdır. UNIX/Linux + ya da macOS sistemlerinde aynı işlem bash kullanıcıları için şöyle yapılmaktadır: bash kabuğu login olunduğunda + "interaktive login shell" için ~/.bah_profile dosyasını, "interaktif non-login shell" için ~/.bashrc dosyasını çalıştırmaktadır. + Bu dosyaların içerisine şu satır eklenmelidir: + + export PYTHONPATH=/istenilen/dizinin/yol/ifadesi + + UNIX/Linux ve macOS sistemlerinde PYTHONPATH çevre değişkenine birdne fazla dizin eklemek için dizinler arasıbnda ':' + karakteri bulunmalıdır. + + Tabii sys.path listesi yalnızca PYTHONPATH çevre değişkenindeki öğeleri içermemektedir. Yorumlayıcının kendisi de default olarak + bu listeye bazı dizinler eklemektedir. Yorumlayıcı tipik olarak standart Python kütüphanesinin install edildiği dizinleri de + bu listeye eklemektedir. Genel olarak PYTHONPATH ile belirtilen dizinler yorumlayıcının kendi dizinlerinden önce eklenmektedir. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 34. Ders 17/08/2022 - Carsamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir modül import edildiğinde CPython gerçekleştirimi import edilen modülü "arakoda" dünüştürerek __pycache__ isimli bir + dizinde .pyc uzantılı bir dosya içerisinde saklamaktadır. Arakodlar bir sonraki import işleminde yorumlayıcı tarafından + çok daha hızlı bir biçimde işleme sokulmaktadır. Genel olarak CPython gerçekleştirimi arakod dosyasını modül dosyası + neredeyse o modül dosyasının bulunduğu dizin içerisindeki __pycache__ dizinine yerleştirmektedir. Bu durumda örneğin + biz math modülünü import ettiğimizde math.py dosyası hangi dizindeyse arakdo dosyası da o dizinin içerisindeki __pycache__ + dizininde oluşturulacaktır. Tabii biz bu __pycache__ dizininin silersek ilk import işleminde bu dizin yeniden yaratılacaktır. + + Pekiyi ya biz import işleminden sonra modül dosyasının kaynak kodu üzerinde değişiklik yaparsak ne olacaktır? İşte CPython + yorumlayıcısı modül dosyasında değişiklik yapıldığında artık __pycache__ dizinindeki arakodu kullanmadan önce modül dosyasını + yeniden arakoda dönüştürmektedir. Böylece __pycache__ dizini içerisinde içerisinde her zaman modülün en güncel halinin + arakodu bulunmaktadır. (CPython yorumlayıcısı .py dosyasının tarih zaman bilgisiyle __pycache__ dizini içerisindeki arakod + dosyasının tarih zaman bilgisini karşılaştırarak arakod dosya oluşturulduktan sonra .py dosyasında bir değişiklik yapılıp + yapılmadığını anlayabilmektedir.) + + Ancak Python dünyasında standart bir arakod sistemi yoktur. Yani belli bir Python yorumlayıcısı import işleminde modülü + hiç arakoda dönüştürmeyebilir. Kaldı ki farklı Python yorumlayıcılarında farklı arakod sistemleri de kullanılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir modüldeki belli değişkenleri modül ismiyle niteliklendirmeden doğrudan kullanabilmek için from import deyimi + kullanılmaktadır. from import deyiminin genel biçimi şöyledir: + + from import [as + + import ya da from import deyimi global düzeyde kullanılırsa global bir değişkenin yerel düzeyde kullanılırsa yerel + bir değişkenin yaratılmasına yol açmaktadır. Örneğin: + + def foo(): + import math + + result = math.sqrt(10) + print(result) + + def bar(): + result = math.sqrt(10) # error! çünkü math ismi foo'da yerel + print(result) + + foo() + bar() + + Tabii farklı fonksiyonlarda aynı modül yerel olarak import edilmiş olsa bile toplamda modülün içerisindeki kodlar yine yalnızca + bir kez çalıştırılmaktadır. Örneğin: + + def foo(): + import utility + + util.foo() + + def bar(): + import utility + + result = utility.add(1, 2, 3, 4, 5) + print(result) + + foo() + bar() + + Burada utility içerisindkei kodlar toplamda bir kez çalıştırılacaktır. + + Biz global değişkenleri henüz görmedik. İzleyen paragraflarda bu konu üzerinde duracağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + from import deyiminin özel bir biçimi de yıldızlı biçimdir. Örneğin: + + from math import * + + Bu biçimde modüldeki tüm isimler import edilir. Yani o isimlerin hepsini biz doğrudan kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +from math import * + +result = sqrt(10) +print(result) + +result = pow(10, 2) +print(result) + +result = sin(0.5) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da başında ve sonunda iki alt tire olan bazı özel isimler çeşitli nedenlerle kullanılabilmektedir. Örneğin: + + __init__ + __new__ + __main__ + __add__ + + Bu isimlerin kolay okunması için "dunder" ya da "dunderscore" sözcükleri uydurulmuştur. Yani örneğin "dunder foo" + demekle biz "__foo__" demiş olmaktayız. Ya da örneğin "dunder init" demekle biz "__init__" demiş olmaktayız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + import ya da from import deyiminde modülün tüm kodlarının çalıştırılması bazen istenmeyebilir. Örneğin birisi "utility.py" isimli bir + program yazmış olabilir. Bu program çeşitli şeyleri yapıyor olabilir. Ancak bu u"tility.py" içerisinde foo ve bar isimli fonksiyonlar + başka kişilerin de kullanabileceği faydalı fonksiyonlar olabilir: + + # utility.py + + def foo(): + print('foo') + + def bar(): + print('bar') + + print('çeşitli kodlar') + print('çeşitli kodlar') + print('çeşitli kodlar') + + Burada biz bu foo ve bar fonksiyonlarını kullanmak için modülü import etsek modülün kodları çalışacak ve aslında bizim + istemediğimiz şeyler de yapılacaktır. Bu durum nasıl engellenebilir? + + İşte Python'da __name__ isimli özel bir değişken vardır. Bu değişken str türündendir. Eğer modül bağımısız bir program gibi + çalıştırılırsa __name__ değişkeni '__main__' yazısını içerir. Eğer modül import edilerek çalıştırılırsa __name__ değişkeni + modül ismini içerir. Böylece biz yazdığımız programdaki fonksiyonların ve değişkenlerin import edilerek kullanılmasını istiyorsak + va aynı zamanda da onu bağımsız bir program gibi de çalıştırmak istiyorsak o programı aşağıdaki gibi bir uygulayarak yazmalıyız: + + if __name__ == '__main__': + pass + + Örneğin utility.py isimli bir program yazacak olalım. Programımız da 2'den 1000'e kadar asal sayıları ekrana yazdırıyor olsun. + Ancak bu modülde isprime isimli asallık testi yapan fonksiyonu da başkalarının kullanmasına imkan tanıyacak biçimde yazmış olalım. + Şimdi bu utility modülünü import eden kişi 2'den 1000'e kadar asal sayıları yazdırmak istemeyecektir. Yalnızca isprime fonksiyonunu + kullanmak isteyecektir. O zaman "utility.py" dosyası şöyle düzenlenmelidir: + + #utility.py + + import math + + def isprime(val): + if val % 2 == 0: + return val == 2 + + sqrt_val = math.sqrt(val) + for i in range(3, int(sqrt_val) + 1): + if val % i == 0: + return False + + return True + + if __name__ == '__main__': + for i in range(2, 1000): + if isprime(i): + print(i, end=' ') + + Kullanımı da şöyle olabilir: + + # sample.py + + import utility + + result = utility.isprime(101) + print('prime' if result else 'not prime') + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Rastgele sayı üretme yazılımda çeşitli amaçlarla kullanılabilmektedir. Örneğin bir oyun programı rastgele sayılar üreterek + birtakım nesnelerin rasgele hareket etmesini sağlayabilir. Benzer biçimde bir simülayon programı rasgele sayılarla gerçek + bir ortamı simüle etmeye çalışabilir. Rastgele sayı üretimi kriptolojide de kullanılmaktadır. + + Ratsgelelik (rassallık) felsefi açılımları da olan bir konudur. Doğada rastgele sayılar elde edebilmek için rastgele olaylardan + faydalanılmaktadır. Ancak bilgisyayarlar tamamen deterministik biçimde çalışırlar. Yani bilgisayar devrelerinde doğadaki + gibi rassal deney oluşturmak mümkün olmamaktadır. Bilgisayarlar restgele sayıları tamamen sayısal işlemlerle elde ederler. + Böyle elde edilmiş rastgele sayılara "sahte rasgele sayılar (pseudo random numbers)" denilmektedir. Sahte rassal sayı + üretiminde bir "tohum değerden (seed)" başlanır. Sonra bir dizi rasrgele sayı elde edilir. Tohum değer aynı olursa aynı + dizimler elde edilmektedir. + + Python'da rastgele (rassal) sayılar standart random modülündeki fonksiyonlarla elde üretilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + rondom modülündeki random iisimli fonksiyon parametresizdir. Her çağrıldığında [0,1) aralığında rastegele float bir + sayı üretir. eğer bir alalıkta rastgele sayı üretilirken her sayının elde edilme olsaılığı diğerleri ile aynıysa böyle + rassal sayılara "düzgün dağılmış rassal sayılar" denilmektedir. random modülündeki random fonksiyonu istatistiksel + terminolojide "düzgün dağılmış rastgele sayı" üretmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +for _ in range(10): + result = random.random() + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Doğadaki pek çok olgu ismine "normal dağılım" ya da "Gauss dağılımı" denilen dağılıma uymaktadır. Bu dağılımda değerler + ortalama etrafında toplanma eğilimindedir. Oratalamadan uzaklaştıkça o değerlerin görülme sıklığı yani olasılıkları azalır. + + random modülündeki gauss fonksiyonu normal dağılıma uygun rastgele sayılar üretmek için kullanılmaktadır. Fonksiyonun ilk + parametresi ortalamayı, ikinci parametresi standart sapmayı belirtir: + + random.gauss(mu=0.0, sigma=1.0) + + Fonksiyonda ortalamanın default değerinin 0, standart sapmanın default değerinin 1 olduğunu görüyorsunuz. Bu biçimdeki + normal dağılıma istatistikte "standart normal dağılım" da denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +for i in range(10): + result = random.gauss(100, 15) + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + random modülündeki randint isimli fonksiyon [a, b] aralığında rastgele bir tamsayı (int türden sayı) üretir. Yani randint + fonksiyonu yalnızca tamsayı değerlerinden oluşan "kesikli düzgün (discrete uniform) dağılama" uygun rastgele sayılar üretmektedir. + Fonksiyonda aralığın her iki değerinin de üretime dahil olduğuna dikkat edşniz. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +for i in range(10): + result = random.randint(10, 20) + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Biz randint fonksiyonu ile yazı-tura denemesi yapabiliriz. Yazı tura işlemini belli miktarda yapıp oranlara bakarsak + gitgide sonucun 0.5'e yakınsadığını görürüz. Buna istatistikte "büyük sayılar yasası (law of large numbers)" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +def head_tail(n): + head = 0 + tail = 0 + + for _ in range(n): + result = random.randint(0, 1) + if result == 0: + head += 1 + else: + tail += 1 + + return head / n, tail / n + +head, tail = head_tail(10) +print(head, tail) + +head, tail = head_tail(1000) +print(head, tail) + +head, tail = head_tail(1000000) +print(head, tail) + +head, tail = head_tail(100000000) +print(head, tail) + +#------------------------------------------------------------------------------------------------------------------------ + random modülündeki choice isimli fonksiyon bir "sequence container'ı" yani [...] ile indekslenebilen bir nesneyi parametre + olarak alır. O nesnedeki rastgele bir elemanı geri dönüş değeri olarak verir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +result = random.choice(a) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + random modülündeki sample isimli fonksiyon indekslenebilir (yani [] ile kullanılabilen) bir nesneyi ve bir de eleman + sayısını parametre olarak alır. O nesneden o miktarda elemanı rastgele seçer. (Buna istatistikte "rassal örnekleme" + (random sampling)" denilmektedir. Fonksiyon bize rastgele değerlerden oluşan bir liste vermektedir. Fonksiyonun geri + döndürdüğü listede aynı elemanlardan bulunmayacağına dikkat ediniz. sample fonksiyonunun geri döndürdüğü listedeki eleman + sıralamsı rastgeledir. sample fonksiyonunun parametrik yapısı şöyledir: + + random.sample(population, k, *, counts=None) + +#------------------------------------------------------------------------------------------------------------------------ + +import random + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'sacit', 'süleyman'] + +result = random.sample(a, 3) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Sayısal lotoda rastgele bir colon oynamak için bu fonksiyondan faydalanabiliriz. Örneğin: + + column = random.sample(range(1, 50), 6) + + Burada fonksiyon bize 1 ile 50 arasında (50 dahil değil) 6 değerden oluşan bir liste verecektir. Bu 6 değerden hiçbiri + diğeri ile aynı olmayacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +column = random.sample(range(1, 50), 6) +print(column) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi sayısal lotoda bir kolon oynayan kodu randint fonksiyonunu kullanarak yazabilir miydik? İlk akla gelecek yöntem + gemellikle aşağıdaki gibi olmaktadır: + + import random + + column = [] + for i in range(6): + while True: + val = random.randint(1, 49) + if val not in column: + column.append(val) + break + + print(column) + + Buradaki yöntem iyi bir yöntem değildir. Burada "rastgele üretine değer daha önceden lsitede varsa yeniden değer üretilmiştir. + Böylece listede aynı değerden birden fazla kez olması engellenmiştir. Tabii aynı işlemi küme kullanarak da yapabilirdik: + + import random + + s = set() + + while len(s) != 6: + val = random.randint(1, 49) + s.add(val) + + column = list(s) + print(column) + + Aklımıza şöyle bir çözümde gelebilir: Biz sayıları numbers ismindw listede toplayalım. Sonra listeden choice fonksiyonuyla + rastgele bir sayı seçelim. Sonra o sayıyı column listesine ekleyip, numbers listesinden silelim: + + import random + + column = [] + numbers = list(range(1, 50)) + for i in range(6): + val = random.choice(numbers) + column.append(val) + numbers.remove(val) + + print(column) + + Bu yöntem de aslında etkin değildir. Çünkü numbers listesinden remove metoduyla silme yapılırken aslında listede içsel + olarak bir kaydırma yapılmaktadır. Bu da her ne kadar biz görmüyor olsak da bir zaman kaybı oluşturacaktır. Bu tür durumlarda + bir kaydırmayı engellemek için mantıksal olarka liste küçültme yöntemi uygulanmaktadır. Aşağıdaki kodda numbers listesinden + çekilen rastgele eleman listesine eklenmiştir. Ancak o eleman elemana son eleman atanarak liste küçültülmüştür. + + import random + + numbers = list(range(1, 50)) + column = [] + + for i in range(6): + val = random.randint(1, 50 - i) + column.append(numbers[val]) + numbers[val] = numbers[49 - i - 1] + + print(column) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + sample fonksiyonundaki counts parametresi birinci parametredeki nesnenin eleman sayısı kadar olmalıdır. Bu parametre + aslında ağırlıklandırmak için kullanılmaktadır. Yani örneğin: + + result = random.sample(['ali', 'veli', 'selami'], 5, counts=[3, 2, 1]) çağrısı aşağıdakiyle eşdeğerdir: + + result = random.sample(['ali', 'ali', 'ali', 'veli', 'veli', 'selami'], 5) + + counts parametresine belli bir değer girildiğinde artık sample fonksiyonun geri döndürdüğü liste aynı elemanlardan oluşabilmektedir. + + counts parametresinin isimli kullanılmak zorunda olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +a = ['ali', 'veli', 'selami'] + +result = random.sample(a, 4, counts=[10, 5, 2]) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + random modülündeki choices fonksiyonu sample fonksiyonuna benzemektedir. sample fonksiyonu iadesiz (without replacement) + çekim yaparken choices fonksiyonu iadeli (with replacement) çekim yapmaktadır. Fonksiyonun parametrik yapısı şöyledir: + + random.choices(population, weights=None, *, cum_weights=None, k=1) + + isimli kullanılmak zorunda olan k parametresi kaç elemanlık çekim yapılacağını belirtmektedir. Buradaki weights parametresi + sample fonksiyonundaki counts parametresi gibidir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +result = random.choices(names, k=3) +print(result) + +result = random.sample(names, k=7) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + random modülündeki randrange fonksiyonu parametrik kullanım bakımından range fonksiyonuna benzemektedir. Ancak rastgele + tamsayı üretmektedir. Örneğin: + + result = random.randrange(0, 10, 2) + + Burada aslında biz 0, 2, 4, 8 sayıları arasında rastgele bir sayı üretmiş oluruz. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +a = ['ali', 'veli', 'selami'] + +for i in range(10): + result = random.randrange(0, 10, 2) + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + random modülündeki shuffle isimli fonksiyon karıştırma işlemini yapar. Fonksiyonun parametresinin bir liste olması gerekir. + Karıştırma "in-place" biçimde yapılmaktadır. (Örneğin demetler değiştirilebilir olmadıkları için shuffle fonksiyonu ile + karıştırılamazlar.) +#------------------------------------------------------------------------------------------------------------------------ + +import random + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +print(a) +random.shuffle(a) +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + 35. Ders 22/08/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında karıştırma algoritması çok kolaydır. Genellikle izlenen yol dizinin tüm elemanlarını sırasıyla rastgele elemanla + yer değiştirme yöntemidir. Aşağıda bu yöntem uygulanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +def myshuffle(a): + for i in range(len(a)): + k = random.randrange(len(a)) + a[k], a[i] = a[i], a[k] + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] +myshuffle(names) +print(names) + +#------------------------------------------------------------------------------------------------------------------------ + Bir demeti karıştırmak istersek bunu nasıl yapabiliriz? Tabii yöntemlerden biri "önce demet elemanlarından bir liste + elde etmek, sonra listeyi karıştırmak, sonra da yeniden demet oluşturmak" olabilir: + + import random + + names = ('ali', 'veli', 'selami', 'ayşe', 'fatma') + + result = list(names) + random.shuffle(result) + result = tuple(result) + + print(result) + + + Aslında sample fonksiyonu da kendi içerisinde rastgele bir dizilim vermektedir. sample fonksiyonunun in-place işlem + yapmadığını anımsayınız. Bu durumda sample fonksiyonunda uzunluk parametresini nesnenin uzunluğu kadar girersek bir + karıştırma işlemi yapmış oluruz: + + import random + + names = ('ali', 'veli', 'selami', 'ayşe', 'fatma') + + result = tuple(random.sample(names, len(names))) + + print(result) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte bir oyun kartı destesi oluşturulmuş sonra da bu deste dört oyuncuya dağıtılmıştır. Ancak oyunculara dağıtılan + kartlar sıraya da dizilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +card_vals = {'As': 14, 'Papaz': 13, 'Kız': 12, 'Vale': 11, '10': 10, '9': 9, '8': 8, '7': 7, '6': 6, '5': 5, '4': 4, '3': 3, '2': 2} +card_types = {'Kupa': 3, 'Maça': 2, 'Karo': 1, 'Sinek': 0} + +def build_deck(): + deck = [] + + for cval in card_vals: + for ctype in card_types: + deck.append((cval, ctype)) + + return deck + +def distribute(deck): + random.shuffle(deck) + + players = [[], [], [], []] + + for i in range(52): + players[i % 4].append(deck[i]) + + for player in players: + player.sort(key=keyfunc, reverse=True) + + return players + +def keyfunc(t): + cval, ctype = t + return card_vals[cval] * 4 + card_types[ctype] + +def disp_players(players): + for player in players: + print(player) + print('-------') + +def main(): + deck = build_deck() + players = distribute(deck) + disp_players(players) + +main() + +#------------------------------------------------------------------------------------------------------------------------ + Rastgele sayı üreterek belli duyarlılıkta pi sayısını elde edebiliriz. Bunun için bir birim çamberdeki dörtte birlik + daire dilimini dikkate alırız. Bu dörtte birlik daire dilimi aslında 1x1'lik bir karenin içerisindedir. Biz de [0, 1] + aralığında iki rastgele sayı üreterek bu kare içerisinde rastgele noktalar elde ederiz. Kare içerisinde elde ettiğimiz + noktaların bazıları aynı zamanda bu daire diliminin de içerisinde olacaktır. Toplam nokta sayısı n tane olsun. Bu n tane + noktanın k tanesi aynı zamanda daire diliminin de içerisinde olsun. Buradaki 1x1'lik karenin daire dilimine oranı b değerinin + k değerine oranına eşit olacaktır: + + n / k = 1 / (pi / 4) + + Burada içler dışlar çarpımı ile pi değeri çekilirse şu sonuç bulunur: + + pi = 4 * k / n + + Tabii buradaki n değerini n kadar artırırsak pi'ye o kadar yaklaşırız. + + Aşağıda bu örnek verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +import random +import math + +def getpi(n): + k = 0 + for _ in range(n): + x = random.random() + y = random.random() + distance = math.sqrt(x ** 2 + y ** 2) + if distance < 1: + k += 1 + + pi = 4 * k / n + + return pi + +pi = getpi(1000) +print(pi) + +pi = getpi(10000) +print(pi) + +pi = getpi(100000) +print(pi) + +pi = getpi(1000000) +print(pi) + +pi = getpi(10000000) +print(pi) + +#------------------------------------------------------------------------------------------------------------------------ + enumerate isimli built-in fonksiyon bizden dolaşılabilir bir nesne alır, bize dolaşılabilir bir nesne verir. enumerate + fonksiyonun verdiği dolaşılabilir nesne dolaşıldığında iki elemanlı demetler elde edilecektir. Öyle ki bu demetlerin + ilk elemanları 0'dan başlayan indeks numarasından ikinci elemanları da bizim verdiğimiz dolaşılabilir nesnedeki elemnalardan + oluşur. Örneğin: + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + + for t in enumerate(names) + print(t) + + Buradan sırasıyla (0, 'ali'), (1, 'veli'), (2, selami), (3, 'ayşe'), (4, 'fatma') biçiminde demetler elde edilecektir. + Tabii biz genellikle unpack yaparak demet elemanlarını elde ederiz: + + for index, x in enumerate(names) + print(index, x) + +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +iterator = enumerate(names) +for t in iterator: + print(t) + +iterator = enumerate(names) +for index, x in iterator: + print(index, x) + +for index, x in enumerate(names): + print(index, x) + +#------------------------------------------------------------------------------------------------------------------------ + enumerate fonksiyonu bir dolaşılabilir nesneyi for döngüsü ile dolaşırken hem elemanların index numaralarını hem de + elemanların değerlerini elde etmek için kullanılmaktadır. enumerate fonksiyonunun ikinci bir parametresi de vardır. Bu ikinci + parametreye default olarak 0 değeri verilmiştir. Bu ikinci parametre indeksin nereden başlatılacağını belirtir. Örneğin: + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + + for t in enumerate(names, 10): + print(t) + + Burada artık şu demetler elde edilecektir: (10, 'ali'), (11, 'veli'), (12, selami), (13, 'ayşe'), (14, 'fatma') +#------------------------------------------------------------------------------------------------------------------------ + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +for t in enumerate(names, 10): + print(t) + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkenin kullanılabildiği program aralığına "faaliyet alanı (scope)" denilmektedir. Python'da değişkenler faaliyet alanları + bakımından üç gruba ayrılmaktadır: + + 1) Yerel Değişkenler (Local Variables) + 2) Global Değişkenler (Global Variables) + 3) Sınıf Değişkenleri (Class Variables) + + Biz sınıf değişkenlerini "sınıflar" konusunda ele alacağız. + + Python'da "değişken (variable)" ile "nesne (object)" kavramları farklı anlamlara gelmektedir. Değişkenler isimli olan varlıklardır. + Değişkenler adres tutarlar. Değişkenlerin içerisindeki adresteki varlıklara "nesne (object)" denilmektedir. Örneğin: + + a = 10 + b = a + + Burada a ve b iki ayrı değişkendir. Ancak aynı nesnenin adresini tutmaktadır. Yani bu kod parçasında iki değişken ancak tek bir nesne vardır. + + Bir değişkene bir fonksiyon içerisinde ilk kez bir değer atandığında yeni bir değişken yaratılır. Fonksiyon içerisinde yaratılan değişkenlere + "yerel değişkenler (local variables)" denilmektedir. Yerel değişkenler yaratıldıkları noktadan yaratıldıkları fonksiyonun sonuna + kadarki bölgede faaliyet gösterirler. Başka yerden kullanılamazlar. Örneğin: + + def foo(): + x = 10; # x bir yerel değişken + print(x) # geçerli, x buarada faaliyet gösteriyor + + print(x) # error! x burada kullanılamaz, burada faaliye göstermiyor. + + Farklı fonksiyonlarda aynı isimli yerel değişkenler farklı değişkenlerdir. Bunlar birbirlerine karışmazlar. Örneğin: + + def foo(): + x = 10 + print(x) + + def bar(): + x = 20 + print(x) + + Burada foo fonksiyonundaki x ile bar fonksiyonundaki x farklı değişkenlerdir. + + Fonksiyonların parametre değişkenleri de yerel değişken gibi faaliyet alanına sahiptir. Yani yalnızca o fonksiyonda kullanılabnilirler. + Farklı fonksiyonların aynı isimli parametre değişkenleri farklı değişkenlerdir. Örneğin: + + def foo(x): # bu x foo'ya özgü bir x + print(x) + + def bar(x): # bu x bar'a özgü bir x + print(x) + + Bir değişken fonksiyonların dışında yaratılmışsa böyle değişkenlere global değişkenler denilmektedir. Global değişkenler + yaratıldıkları yerden dosyanın sonuna kadar her yerde (fonksiyonların içerisinde de) kullanılabilmektedir. Örneğin: + + a = 10 + + def foo(): + print(a) # global olan a + + def bar(): + print(a) # global olan a + + foo() + bar() + + Tabii global fonksiyonların isimleri de birer gobal değişkendir. + + Bir fonksiyon içerisinde global değişkenle aynı isimli bir değişkene atama yapılırsa bu durum global değişkene atama yapıldığı + anlamına gelmez. Aynı isimli yeni bir yerel değişken yaratılır. Atama ona yapılmış olur. Örneğin: + + x = 10 + + def foo(): + x = 20 # x yeni bir yerel değişken, global olan değil + print(x) # buradaki x yerel olan x + + foo() + print(x) # buradaki x global x, zaten yerel x burada faaliyet göstermiyor, 10 çıkacak + + Bazen bir fonksiyonun global bir değişkeni değiştirmesi istenebilir. Bu durumda yorumlayıcıya bunun belirtilmesi + gerekir. Bu işlem global bildirimi ile yapılmaktadır. global bildiriminin genel biçimi şöyledir: + + global + + Örneğin: + + global x + global y, z, k + + Örneğin: + + a = 10 + + def foo(): + global a + a = 20 # global olan a + print(x) # global olan a + + foo() + print(a) # 20 + + Burada artık foo'nun içeisindeki a global olan a'dır. Dolayısıyla foo'nun içerisinde global olan a'ya 20 atanmıştır. global + bildirimi fonksiyon içerisinde bir global değişkenin değerini değiştirmek için kullanılır. Yoksa global değişkenin değeri + değiştirilmeyecekse zaten global değişkenler doğrudan fonksiyonlar içerisinde kullanılabilmektedir. Bunun için gobal bildiriminin + yapılmasına gerek yoktur. Örneğin: + + a = 10 + + def foo(): + global a # burada global bildirimine gerek yok + + print(a) # global olan a + + foo() + + Bir fonksiyon içerisinde önce bir global değişken kullanılmışsa artık aynı isimli bir yerel değişken yaratılamaz. Eğer yaratılmak + istenirse bu durum error oluşturur. Örneğin: + + a = 10 + + def foo(): + print(a) + a = 20 # geçersiz! çünkü daha önce aynı isimli global değişken fonksiyonda kullanılmış + print(a) + + foo() + + Fonksiyon ieçrisinde önce bir global değişken kullanılıp sonra aynı değişkene ilişkin global bildirimi de yapamayız. Örneğin: + + a = 10 + + def foo(): + print(a) # global olan a + global a # error önce bir global değişken kullanılıp sonra o değişkene ilişkin global bildirimi yapılamaz! + a = 20 + print(a) + + foo() + + C/C++, Java, C# gibi dillerde fonksiyonlar ve metotlar içerisinde ayrıca bloklarla yeni faaliyet alanları oluşturulabilmektedir. + Python'da böyle bir durum söz konusu değildir. Python'da biz for döngüsü, if deyimi vs. içinde bir değişken yarattığımızda + o değişkeni bu deyimlerin dışında da kullanabiliriz. Yani Python'da fonksiyonun içerisindeki bloklar ayrı bir faaliyet alanı + belirtmemektedir. Örneğin: + + def foo(): + for i in range(10): + print(i) + x = 10 + + print(x) # geçerli + print(i) # geçerli + +#------------------------------------------------------------------------------------------------------------------------ + global bildiriminde henüz ilgili global değişken yaratılmamış olabilir. Bu durum geçerlidir. Eğer global bildirimi görüldüğünde + global değişken yaratılmadıysa yine de bu bildirim global bir değişkeni belirtir. Yani bu sayede global değişken fonksiyonun + içerisinde yaratılabilir. Örneğin: + + def foo(): + global a + + a = 10 # a global değişikeni yaratılıyor, çünkü henüz yaratılmamış + + foo() + print(a) + + Tabii aslında global bildirimi global değişkenin yaratılacağı anlamına geşmez. Yalnızca kullanılacak değişkenin + global olduğu anlamına gelir. Örneğin: + + def foo(): + global a + + print(a) # error! henüz global nesne yaratılmadı + + foo() + print(a) + + Buradaki problem foo çağrıldığında henüz a global değişkeninin yaratılmamışi olmasıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 36. Ders 24/08/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişken (nesneyi kastetmiyoruz) programın belli bir aşamasında yaratılır, bir süre faaliyet gösterdikten sonra yok edilir. + Değişkenin bellekte kaldığı zaman aralığına "ömür (duration)" denilmektedir. + + - Python'da bir yerel değişken fonksiyon çağrıldıktan sonra akış o değişkene ilk kez değer atandığı noktaya geldiğinde yaratılır, + akış o fonksiyon bittiğinde değişken otomatik olarak yok edilir. Yani yerel değişkenler fonksiyon çağrılmadan bellekte yer kaplamazlar. + Fonksiyon bittiğinde de yok edilmiş olurlar. Zaten bir yerel değişkenin fonksiyon dışında kullanılamamasının temel nedeni de budur. Örneğin: + + def foo(): + x = 10 + # ... + + print(x) + + Burada x henüz fonksiyon çağrılmadığına göre ya da çağrılıp sonlandığına göre aslında yaşamamaktadır. Bu nedenle biz x'i + fonksiyonun dışında kullanamayız. + + - Fonksiyonların parametre değşkenleri de fonksiyon çağrıldığında yaratılır, fonksiyon sonlandığında otomatik olarak yok edilir. + Bir fonksiyon çağrıldığında önce parametre değişkenleri yaratılır, sonra argümanlardan parametre değişkenlerine atama yapılır, ondan sonra + akış fonksiyona aktarılır. Örneğin: + + def foo(a, b): + pass + + foo(10, 20) + + Burada fonksiyon çağrıldığında önce a ve b yaratılır. Sonra a = 10, b = 20 atamaları yapılır. Sonra da programın akışı + fonksiyona aktarılır. Fonksiyon sonlandığında da a ve b yok edilir. + + - Bir global değişken o değişkene ilk kez değer atandığında yaratılır, program sonuna kadar yaşamaya devam eder. Bu nedenle + global değişkenler her yerden yani fonksiyonlardan da kullanılabilmektedir. Örneğin: + + x = 10 + + def foo(): + print(x) + + print(x) + foo() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkenin ne zaman yaratılıp ne zaman yok edildiğini yukarıda açıkladık. Pekiyi değişkenlerin gösterdikleri nesneler + ne zaman yaratılıp yok edilmektedir. Örneğin: + + x = 10 + x = 20 + + Burada x değişkeni yaratılırken aynı zamanda int bir nesne de yaratılır. Daha sonra int türü "değiştirilemez (immutable)" olduğu için + yeni bir int nesnesi yaatılıp x değişkeni artık o yeni yaratılan x nesnesini gösterecektir. Pekiyi eski nesneye ne olacaktır? İşte Python'da + "çöp toplayıcı (garbage collector)" denilen mekanizma bir nesneyi hiçbir değişken göstermiyorsa artık o nesneyi arka planda bellekten silmektedir. + Çöp toplama tamamen yorumlayıcı sistem tarafından yönetilir. Dolayısıyla programcı bu konuda bir şey yapmaz. Örneğin: + + def foo(a): # bu noktada int nesnesyi x ve a gösteriyor + pass # fonksiyon bittiğinde a yok edilecek, böylece int nsneyi yalnızca x gösterir durumda olacaktır. + + def bar(): + x = 10 # bu noktada int nesneyi yalnızca x gösteriyor + foo(x) + print(x) # bu noktada yine int nesnesi yalnızca x gösteriyor + + bar() + + Buradaki kod parçasında önce bar fonksiyonu çağrılmıştır. bar fonksiyonunun içerisinde yerel x değişkeni yaratılmıştır. Bu + yerel x değişkeni içerisinde 10 değeri olan bir int nesneyi göstermektedir. Sonra foo fonksiyonu çağrılmış ve bu x değişkeninin + içerisindeki adres foo fonksiyonunun a parametre değişkenine atanmıştır. Artık içerisinde 10 olan int nesnesi iki değişken + gösteriyor durumdadır. foo bitince a parametre değişkeniş yok edilecektir. Akış bar fonksiyonuna geri döndüğünde artık yine + nesneyi yalnızca x değişkeni gösteriyor durumdadır. Nihayet bar da bitince artık x de yok edilecek ve nesneyi hiçbir değişken + göstermiyor durumda olacaktır. İşte bu durumda yorumlayıcının çöp toplayıcı mekanizması devreye girecek ve çöp haline gelmiş + içerisinde 10 olan nesneyi silecektir. + + Aşağıdaki örnekte döngünün her ynelenemesinde yeni bir int nesne yaratılır, ancak öncekiler çöp toplayıcı tarafından yok edilir. + + i = 0 + for _ in range(100): + print(id(i)) # her defasınde değişik bir adres yazıdırılacaktır + i += 1 + + Python standart dokümanlarında çöp toplayıcının kullandığı yöntem ve algoritma açıklanmamıştır. Bu nedenle çöp tıoplayıcılar + arasında işleyiş bakımından farklılıklar olabilmektedir. CPython gerçekleştirimi "referans sayacı (reference counting)" temelinde + bir çöp toplama mekanizması kullanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da çokça kullanılan bir "built-in" fonksiyon da map isimli fonksiyondur. Bu fonksiyon bizden bir fonksiyonu + ve dolaşılabilir bir nesneyi parametre olarak olarak alır. Bize bir dolaşım (şiterator) nesnesi verir. map fonksiyonun + verdiği dolaşım nesnesi dolaşıldığında bizim verdiğimiz dolaşılabilir nesnenin elemanları verdiğimiz fonksiyona argüman + yapılıp fonksiyonun geri dönüş değerleri elde edilecektir. Bizim map fonksiyonuna verdiğimiz fonksiyonun bir parametresi + olmak zorundadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +def square(x): + return x * x + +iterable = map(square, a) + +x = list(iterable) +print(x) + +for x in map(square, a): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte isimlerin karakter uzunlukları elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +for x in map(len, names): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte yazı içerisindeki sayılar toplanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +s = '1 2 3 4 5' + +total = 0 +for x in map(int, s.split()): + total += x + +print(total) + +#------------------------------------------------------------------------------------------------------------------------ + Built-in sum fonksiyonu dolaşılabilir bir nesne alıp onun tüm elemanlarının toplamanına geri dönmektedir. O halde yukarıdaki + örneği daha kompakt bir biçimde aşağıdaki gibi oluşturabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +s = '1 2 3 4 5' + +total = sum(map(int, s.split())) +print(total) + +#------------------------------------------------------------------------------------------------------------------------ + Built-in max ve min fonksiyonları bizden dolaşılabilir bir nesne alıp onların en büyük ve en küçük elemanlarını verir. + Aşağıdaki örnekte en uzun ismin karakter uzunluğu elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +result = max(map(len, names)) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii map fonksiyonunun birinci parametresi bir metot da olabilir. Ancak metotlar tek başına kullanılamazlar. Metotlar + ancak ilişkin oldukları sınıf türünden bir değişkenle '.' operatörü ile kullanılabilirler. Bu durumda map fonksiyonunun + birinci parametresibe metot vereceksek metodu isimle değil . biçiminde vermeliyiz. Örneğin: + + d = {'ali': 1, 'veli': 2, 'selami': 3, 'ayşe': 4, 'fatma': 5} + + names = ['ali', 'selami', 'ayşe', 'güray', 'fatma', 'veli', 'can'] + + for x in map(d.get, names): + print(x, end=' ') + + Burada names listesi içerisindeki her bir isim d.get metoduna sokulacak ve oradan elde edilen değerler dolaşım sırasında + elde edilecektir. Örneğin: + + s = 'ankara' + + for x in map(s.count, s): + print(x, end=' ') + + Burada yazının her karakterinin yazı içerisinde kaç tane bulunduğu elde edilmek istenmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 1, 'veli': 2, 'selami': 3, 'ayşe': 4, 'fatma': 5} + +names = ['ali', 'selami', 'ayşe', 'güray', 'fatma', 'veli', 'can'] + +for x in map(d.get, names): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Aslında map fonksiyonu birden fazla dolaşılabilir nesne alabilmektedir. Bu durumda bu dolaşılabilir nesnenin karşılıklı elemanları + birinci parametresiyle verilen fonksiyona parametre olarak aktarılır. Yani map fonksiyonuna biz kaç tane dolaşılabilir nesne verirsek + birinci parametre ile geçirdiğimiz fonksiyonun o kadar parametresi olmak zorundadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] +b = [10, 20, 30, 40, 50] +c = [100, 200, 300, 400, 500] + +def foo(a, b, c): + return a + b + c + +iterable = map(foo, a, b, c) +for x in iterable: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + map fonksiyonuna birden fazla dolaşılabilir nesne geçirdiğimizde bunların eleman sayısı aynı olmak zorunda değildir. + Bunlardan herhangi birinde sona gelindiğinde tüm dolaşım sonlandırılır. Aşağıdaki örnekte yalnızca iki dolaşım + yapılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] +b = [10, 20] +c = [100, 200, 300, 400, 500] + +def foo(a, b, c): + return a + b + c + +iterable = map(foo, a, b, c) +for x in iterable: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii burada map fonksiyonuna geçirdiğimiz fonksiyonun da parametresi *'lı olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] +s = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'cumhur'] +w = [72.4, 69.5, 84.2, 51.6, 56.2] + +def foo(*x): + return x + +for x in map(foo, a, s, w): + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da iç içe (nested) fonksiyon tanımlamaları yapılabilmektedir. Örneğin: + + def foo(): + ... + def bar(): + ... + ... + + Tabii iç fonksiyonun içerisinde de başka bir fonksiyon tanımlanabilir. Bu tür durumlarda iç fonksiyon ismi yerel bir değişken olmaktadır. + Dolayısıyla iç fonksiyon ancak dıştaki fonksiyonun içerisinden çağrılabilir. + + Aşağıdaki örnekte biz bar fonksiyonunu ancak foo fonksiyonun içerisinde ve bar tanımlandıktan sonra çağırabiliriz. + bar fonksiyonunu dışarıdan çağıramayız: + + def foo(): + print('foo') + def bar(): + print('bar') + bar() + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + def bar(): + print('bar') + + bar() + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + Eğer bir fonksiyon genel değil de yalnızca başka bir fonksiyonun yazımı için oluşturuluyorsa bu fonksiyonu asıl fonksiyonun + iç fonksiyonu olarak tanımlamak iyi bir tekniktir. Çünkü dışarıdaki fonksiyonlar herkesin ilgisini çeker. Halbuki iç + fonksiyonlar dışarıdan kullanılamayacağından dolayı zaten kişilerin ilgisini çekmez. Dolayısıyla onların kafalarını karıştırmaz. + Örneğin 2'den parametresiyle belirtilen sayıya kadar asal sayıları yazdıran bir fonksiyon yazmak isteyelim. Bu fonksiyon + sayının asal olup olmadığını test eden isprime gibi bir fonksiyonu kullanıyor olsun. Bu isprime fonksiyonu dışarıyı + ilgilendiren bir fonksiyon değilse bir iç fonksiyon olarak yazılabilir. Bir fonksiyon yalnızca başka bir fonksiyonun yazımında + kullanılmak için oluşturuluyor ise onu iç fonksiyon yapmak iyi bir tekniktir. Tabii iç bir fonksiyonu dış fonksiyon içerisinde + kullanmadıktan sonra onu tanımlamanın da bir anlamı yokturç +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def print_primes(n): + def isprime(val): + if val % 2 == 0: + return val == 2 + for i in range(3, int(math.sqrt(val)) + 1, 2): + if val % i == 0: + return False + return True + + for x in range(2, n + 1): + if isprime(x): + print(x, end=' ') + +print_primes(100) + +#------------------------------------------------------------------------------------------------------------------------ + 37. Ders 31/08/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İç bir fonksiyon dış fonksiyonun o ana kadar yaratılmış olan yerel değişkenlerini kullanabilir. Ancak dış fonksiyon iç fonksiyonun + yerel değişkenlerini kullanamaz. Örneğin: + + def foo(): + a = 10 + def bar(): + print(a) # 10 + b = 20 + print(b) # 20 + bar() + + foo() + + Burada bar fonksiyonu foo fonksiyonun a yerel değişkenini kullanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + def bar(): + print(a) # 10 + b = 20 + print(b) # 20 + bar() + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + Tabii Python'da isim araması fonksiyon çağılıp akış ilgili noktaya geldiğinde yapıldığına göre dış fonksiyonun yerel + değişkeni iç fonksiyondan sonra da oluşturulmuş olsa eğer çağrılma sırasında bu yerel değişken yaratılmışsa iç + fonksiyon içerisinden kullanılabilir. Örneğin: + + def foo(): + a = 10 + def bar(): + print(a) # 10 + print(b) # 20 + b = 20 + bar() + + Burada bar fonksiyonu çağrıldığında foo fonksiyonunun b yerel değişkeni de yaratılmış durumda olacaktır. Dolyısıyla + bar çağrımında bir sorun oluşmayacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + def bar(): + print(a) # 10 + print(b) # 20 + b = 20 + bar() + +#------------------------------------------------------------------------------------------------------------------------ + İç bir fonksiyon içerisinde dış fonksiyonun yerel değişkeni ile aynı isimli bir değişkene atama yaptığımızda biz dış + fonksiyounun yerel değişkenini değiştirmiş olmayız. İç fonksiyonda yeni bir yerel değişken yaratmış oluruz. Örneğin: + + def foo(): + a = 10 + def bar(): + a = 20 # bar'a ilişkin yeni bir yerel a oluşturulmaktadır + print(a) # 20 + bar() + print(a) # foo'nun yerel a'sı, 10 + + Burada foo fonksiyonu çağrıldığında bar içerisindeki a değişkenine atama yapıldığında üst fonksiyon olan foo fonksiyonunun + yerel a değişkenine atama yapılmamaktadır. bar içerisinde yeni bir yerel a değişkeni yaratılmış olmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + + def foo(): + a = 10 + def bar(): + a = 20 # bar'a ilişkin yeni bir yerel a oluşturulmaktadır + print(a) # 20 + bar() + print(a) # foo'nun yerel a'sı, 10 + + foo() + +#------------------------------------------------------------------------------------------------------------------------ + İç fonksiyonun dış fonksiyonun yerel değişkenini değiştirebilmesi için nonlocal bildiriminin yapılması gerekir. nonlocal + bildiriminin genel biçimi şöyledir: + + nonlocal ; + + Örneğin: + + def foo(): + a = 10 + def bar(): + nonlocal a + a = 20 # buradaki a foo'nun a'sı + print(a) # foo'nun a'sı, 20 + bar() + print(a) # foo'nun a'sı, 20 + + foo() + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + def bar(): + nonlocal a + + a = 20 # buradaki a foo'nun a'sı + print(a) # foo'nun a'sı, 20 + bar() + print(a) # foo'nun a'sı, 20 + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + Tabii iç fonksiyonun da iç fonksiyonu olabilir. Bu durumda nonlocal bildirimi benzer biçimde etki gösterir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + def bar(): + nonlocal a + def tar(): + nonlocal a + + a = 20 # foo'nun a'sı + print(a) # foo'nun a'sı yazdırılıyor, 20 + tar() + print(a) # foo'nun a'sı yazdırılıyor, 20 + bar() + print(a) # foo'nun a'sı yazdırılıyor, 20 + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + nonlocal bildirimi ile belirtilen ismin aranması içten dışa doğru yapılmaktadır. Dolayısıyla nonlocal bildirimi için bildirilen + değişkenin hemen dış fonksiyonda bulunyor olması gerekmez. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + def bar(): + def tar(): + nonlocal a # bar'da a olmadığı için foo'nun a'sı + + a = 20 + tar() + bar() + print(a) # foo'nun a'sı yazdırılıyor, 20 + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi global bildirimi fonksiyon içerisinde henüz global değişken yaratılmamış olsa bile yapılabiliyordu. Ancak + nonlocal bildirimi için durum böyle değildir. nonlocal bildirimi ile bildirilen değişken dıştaki herhangi bir fonksiyonda + bulunamazsa bu durum error oluşturur. Örneğin: + + def foo(): + def bar(): + nonlocal a # error! dış fonksiyonda a yok! + + a = 10 + bar() + + foo() + + nonlocal bildiriminde bildirilen değişken dış fonksiyonların yerel değişkeni olarak aranır. Bu aramda global değişkenlere + bakılmaz. Örneğin: + + a = 10 + def foo(): + def bar(): + nonlocal a # error! dış fonksiyonda yerel a yok! global bir a'nın olması önemli değil + + a = 10 + bar() + foo() + + Dış fonksiyonda aynı isimli değişken global bildirimi ile bildirilmiş olsa bile nonlocal bildirimi bu değişkeni görmez. + Çünkü global bildirimi de hiçbir zaman yerel bir değişkene ilişkin değildir. + + a = 10 + def foo(): + global a + def bar(): + nonlocal a # error! dış fonksiyonda yerel a yok! global bir a'nın olması önemli değil + + a = 10 + bar() + + foo() + + Benzer biçimde dış fonksiyonda bir global bildirimi yapılmışsa daha iç bir fonksiyonda yaratılan aynı isimli değişken + o iç fonksiyonun değişkeni olur. Örneğin: + + a = 10 + + def foo(): + global a + + a = 20 # global a değiştiriliyor + def bar(): + a = 30 # yeni bir yerel a yaratılıyor + bar() + + foo() + + print(a) # global a yazdırılıyor + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii her modülün global değişkenleri o modüle özgüdür. Yani her modül kendi içerisinde farklı bir isim alanı (name space) oluşturmaktadır. + Bu nedenle farklı modüllerdeki aynı isimli global değişkenler (fonksiyonlar da birer global değişken gibidir) birbirlerine karışmazlar. + Aşağıdaki sörnekte "sampl.py" modülünde, "a.py" modülünde ve "b.py" modülünde aynı isimli x global değişkeni oluşturulmuştur. "a.py" ve + "b.py" modülü "sample.py" modülünden import edilerek bu global değişkenler kullanılmıştır. Bu örnekten de görüleceği gibi "a.py" modülündeki + x değişkeni ile "b.py" modülündeki x değişkeni ve "sample.py" modülündeki x değişkeni aynı isimli olmalarına karşın farklı değişkenlerdir. + Ayrıca örnekte "a.py" modlünde ve "b.py" modülünde foo isminde iki ayrı fonksiyon da bulundurulmuştur. Bu fonksiyonlar kendi modüllerindeki + x global değişkenlerini kullanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +# sample.py + +import a +import b + +x = 10 + +print(x) # 10 +print(a.x) # 20 +print(b.x) # 30 + +a.foo() # 20 +b.foo() # 30 + +x = 100 +a.x = 200 +b.x = 300 + +print(x) # 100 +print(a.x) # 200 +print(b.x) # 300 + +a.foo() # 200 +b.foo() # 300 + +# a.py + +x = 20 + +def foo(): + print(x) + +# b.py + +x = 30 + +def foo(): + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + from import deyiminin eşdeğerini anımsayınız. Örneğin: + + from a import x + + Bu işlemin tamamen eşdeğeri şöyledir: + + import a + x = util.x + del a + + Bu durumda: + + from a import x + + deyimi ile biz x'i kullandığımızda a'daki x'i kullanmış olmayız. Çünkü int değiştirilmez bir tür olduğu için x = a.x atamasında + x artık başka bir int değişkeni belirtmektedir. Dolayısıyla: + + from a import x + + x = 100 + + print(x) # x bu modüldeki x, a modülündeki x değil + + import a + + print(a.x) # a modülündeki x + + Tabii from import deyimi ile import ettiğimiz değişken değiştitilebilir bir nesneye ilişkin ise bu durumda gerçekten o nesne + üzerinde yapılan değişiklik o modüldeki nesneyi etkileyecektir. Örneğin: + + # a.py + + x = [1, 2, 3, 4, 5] + + # sample.py + + from a import x + + print(x) # [1, 2, 3, 4, 5] + + + x[0] = 100 + + import a + + print(a.x) # [100, 2, 3, 4, 5] + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir modül yerel düzeyde import edildiğinde her ne kadar modül değişkenini yalnızca o fonksiyonda kullanabiliyor olsak da + bu modülü başka bir yerde import ettiğimizde aynı modülü kullanmış oluruz. Çünkü program içerisinde yerel de olsa global da + olsa aslında modül bir kez import edilmekte diğer import işlemleri gerçek anlamda yapılmamaktadır. + + Aşağıdaki örnekte "sample.py" modülündeki foo fonksiyonu içerisinde "a.py" modülü import edilip oradaki global değişken + değiştirilmiştir. Fonksiyon sonlandıktan sonra yeniden aynı modül global düzeyde import edilmiştir. Bu değişiklik + görülecektir. Çünkü modül yerel düzeyde import edilmiş olsa bile modül nesnesi bir kez oluşturulup saklanmaktadır ve modül + ikinci kez import edildiğinde saklanan modül nesnesi yeniden verilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +# sample.py + +def foo(): + import a + + a.x = 20 + +foo() + +import a + +print(a.x) # 20 + +# a.py + +x = 10 + +#------------------------------------------------------------------------------------------------------------------------ + Python'da globals isimli built-in fonksiyon o andaki tüm global değişkenleri bir sözlük nesnesi biçiminde bize verir. + Sözlüğün anahtarları global değişkenlerin isimlerinden değerleri ise onların değerlerinden oluşur. globals fonksiyonu ile + global değişkenleri elde ettiğinizde sizin yaratmadığınız başka değişkenleri de görürseniz şaşırmayınız. Örneğin biz + daha önce __name__ isimli global değişkenin yorumlayıcı tarafından oluşturulduğunu görmüştük. Benzer biçimde bütün + built-in değişkenler __builtins__ isimli bir global sözlük nesnesi içerisinde bulunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +name = 'ali' + +def foo(): + pass + +g = globals() +print(g) +print(list(g)) # ['__name__', '__file__', '__nonzero__', '__builtins__', 'a', 'name', 'foo', 'g'] + +#------------------------------------------------------------------------------------------------------------------------ + globals fonksiyonuyla elde etmiş olduğumuz sözlüğe eleman ekleyerek yeni global değişkenleri bu yolla oluşturabiliriz. + Yorumlayıcı da zaten bütün global değişkenleri aslında kendi içerisinde bir sözlükte tutmaktadır. Zaten globals fonksiyonu + da yorumlayıcının global değişkenleri tutmakl için kullandığı sözlüğü bize vermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 +g = globals() +g['b'] = 20 +print(b) # 20 +print(a) # 10 + +#------------------------------------------------------------------------------------------------------------------------ + globals fonksiyonuna benzeyen locals isimli bir built-in fonksiyon daha vardır. locals fonksiyonu hangi fonksiyon + içerisinde çağrılmışsa o fonksiyonun yerel değişkenlerini bir sözlük olarak vermektedir. locals fonksiyonu global + düzeyde çağrılırsa tamamen globals fonksiyonu çağrılmış gibi etki göstermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = 10 + +def foo(): + b = 20 + d = locals() + print(d) # {'b': 20} + print(list(d)) # ['b'] + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + İçteki fonksiyonun dıştaki fonksiyonun yerel değişkenlerini kullanabildiğini anımsayınız. Ancak locals fonksiyonu iç + bir fonksiyonda da çağrılabilir. Bu durumda yalnızca kendi iç fonksiyonundaki yerel değişkenlerini verir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + def bar(): + b = 20 + d = locals() + print(d) # {'b': 20} + + bar() + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + locals fonksiyonu ile elde edilen sözlüğe bir ekleme yapıldığında gerçekten de sözlüğe ekleme yapılmış olur. Ancak eklenen + isim yerel değişken olarak kullanılamaz. Bu durum globals fonksiyonundaki duruma bu bakımdan benzememektedir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + a = 10 + d = locals() + print(d) # {'a': 10} + d['b'] = 20 + print(d) # {'a': 10, 'b': 20} + print(b) # error! b eklenmiş olsa da bu biçimde kullanılamaz! + +foo() + +#------------------------------------------------------------------------------------------------------------------------ + Daha önceden de belirttiğimiz gibi "fonksiyonel proıgramlama modeli (functional programming paradigm)" bir fonksiyonun + çıktısının başka bir fonksiyona girdi yapılması onun çıktısının başka bir fonksiyona girdi yapılması biçiminde formül + yazar gibi tek bir satırda pek çok şeyin yapılabildiği programlama modelidir. Artık klasik programlama dillerine de çeşitli + yoğunlukta fonksiyonel modeli destekleyebilecek özellikler eklenmektedir. İşte Python'da "içlemler (comprehensions)" konusu da + fonksiyonel programlama modelini desteklemek amacıyla dile eklenmiştir. Python'da ki içlemlere benzer öğeler bazı programlama + dillerinde değişik biçimlerde bulunabilmektedir. Ancak popüler programlama dillerinin çoğunda içlemlere benzer öğeler yoktur. + Bu bölümde Python'da içlemler konusunu ele alacağız. ("Comprehension" sözcüğü İngilizce çeşitli anlamlara gelmektedir. Matematikte + buna Türkçe "içlem" de denildiği için biz bu terimin Türkçe karşılığı olarak "içlem" sözcüğünü tercih ediyoruz.) + + İçlemler üçe ayrılmaktadır: + + 1) Liste içlemleri (list comprehensions) + 2) Küme içlemleri (set comprehensions) + 3) Sözlük içlemleri (dictionary comprehensions) + + Eğer içlemden bir liste elde ediliyorsa buna "liste içlemi", küme elde ediliyorsa buna "küme içlemi", sözlük elde + ediliyorsa buna da "sözlük içlemi" denilmektedir. Demet içlemi biçiminde bir özellik yoktur. Ancak diğer içlemlere benzer + sentaks demetlerde kullanılırsa bu tamamen farklı bir anlama gelmektedir. Buna "üretici ifadeler (generator expressions)" + denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İçlemlerin genel biçimleri birbirine çok benzemektedir. Liste içlemlerinin genel biçimi şöyledir: + + [ for in [if koşul] ] + + En çok karşılaşılan biçim köşeli, poarantez içerisinde bir ifade ve onun yanında bir for döngüsüdür. Örneğin: + + [i * i for i in range(10)] + + İçlemin çalışma mekanizması oldukça basittir. for göngüsünün her yinelenmesinden sonra for döngüsünün solundaki ifade + çalıştırılır. Bu ifadenin değeri bir listeye eklenir. Böylece for döngüsü her çalştırıldığında listeye yeni bir eleman + eklenmiş olacaktır. İçlemden de sonuç olarak bu liste elde edilmektedir. Örneğin: + + a = [i * i for i in range(10)] + print(a) + + Burada for döngüsünden sırasıyla 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 değerleri i olarak elde edilir. Her değer elde edildiğinde soldaki i * i + ifadesi çalıştırılırsa ve bu değerler bir listede biriktirilirse sonuçta [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] biçiminde + bir liste oluşturulacaktır. Aşağıdaki içleme dikkat ediniz: + + [ifade for i in iterable] + + Bunun eşdeğeri şöyledir: + + temp = [] + for i in iterable: + temp.append(ifade) + + Örneğin: + + a = [i * i for i in range(10)] + print(a) + + Bu işlemin eşdeğeri şöyledir: + + temp = [] + for i in range(10): + temp.append(i * i) + a = temp + print(a) + + Her ne kadar yukarıdaki iki ifade eşdeğer gibi gözükse de içlemler genel olarak daha hızlı olma eğilimindedir. + Çünkü içlemler genellikle daha temel düzeyde ve tek bir operasyon biçiminde yapılmaktadır. + + Tabii içlemlerin önemi aslında hızdan ziyade kompakt bir görünüm sağlamasındadır .İçlemler bir ifade (expression) + durumundadır. Dolayısıyla başka ifadelerin içerisinde kullanılabilrler. Örneğin: + + total = sum([i * i for i in range(10)]) + print(total) + + Burada 10'a kadar sayıların karelerinin toplamı bulunmuştur. Eğer içlemler olmasaydı bu ifadeyi bu kadar kompakt yazamazdık. + + Tabii aslında for döngüsünün solundaki ifadenin döngü değişkeni ile ilgili olması zorunluluk değildir. Örneğin: + + a = [100 for i in range(10)] + print(a) # [100, 100, 100, 100, 100, 100, 100, 100, 100, 100] + + Örneğin: + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + names_len = [len(name) for name in names] + print(names_len) + + Burada biz isimlerin karakter uzunluklarından oluşan bir liste elde etmiş olduk. Örneğin: + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + print(*[len(name) for name in names]) + + Burada biz tek hamlede isimlerin uzunluklarını ekrana yazdırdık. + +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +names_len = [len(name) for name in names] +print(names_len) + +print(*[len(name) for name in names]) + +#------------------------------------------------------------------------------------------------------------------------ + Liste içlemleriyle yapılmak istenen şeylerin bir bölümü map fonksiyonuyla da yapılabilir. liste içlemleri bize ürün olarak + liste vermektedir. Halbuki map fonksiyonu bize ürün olarak dolaşılabilir bir nesne verir. Tabii biz map fonksiyonunun çıktısını + list fonksiyonuna sokarak bir liste elde edebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +names_len = [len(name) for name in names] +print(names_len) + +names = list(map(len, ['ali', 'veli', 'selami', 'ayşe', 'fatma'])) +print(names) + +#------------------------------------------------------------------------------------------------------------------------ + İçlemlerde istenirse for cümlesinin sağına if anahtar sözcüğü ile bir koşul cümleceği de eklenebilir. Bu durumda for döngüsü + her işletildiğinde sağdaki koşula bakılır. Eğer koşul doğruysa soldaki ifade işletilir. Koşul yanlışsa soldaki ifade işletilmez + ve bu ifadenin sonucu listeye eklenmez. Örneğin: + + a = [i for i in range(10) if i % 2 == 0] + print(a) # [0, 2, 4, 6, 8] + + Burada koşul i çift ise sağlanmaktadır. Dolayısıyla listede çift sayılar bulunacaktır. O halde örneğin: + + [ifade for i in iterable if koşul] + + içleminin işlevsel eşdeğeri şöyledir: + + temp = [] + + for i in iterable: + if koşul: + temp.append(ifade) + + Örneğin: + + total = sum([i for i in range(10) if i % 2 == 1]) + print(total) + + Burada 10'a kadar tek sayıların toplamı bulunmaktadır. Örneğin: + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + + a = [name for name in names if 'a' in name] + print(a) + + Burada için 'a' harfi geçen isimler bir liste biçiminde elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 38. Ders 05/09/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte başı 'a' ya da 'A' harfi ile başlayan isimler liste içlemi yoluyla elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +a = [name for name in names if name[0] == 'a' or name[0] == 'A'] +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte küçük harf olan şehir isimleri büyük harfe dönüştürülerek bir liste biçiminde elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +cities = ['ankara', 'izmir', 'eskişehir', 'muğla', 'kastamonu'] + +upper_cities = [city.upper() for city in cities] +print(upper_cities) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki liste içlemi tam palindrom olan cümleleri elde etmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +sentences = ['anastas mum satsana', 'izmir', 'ey edip adanada pide ye', 'eskişehir', 'adamla çeneç almada'] + +palindromes = [sentence for sentence in sentences if sentence == sentence[::-1]] +print(palindromes) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte tam palindrom olmayan cümleler de elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +sentences = ['anastas mum satsana', 'izmir', 'ey edip adanada pide ye', 'eskişehir', 'adamla çene çalmada'] + +palindromes = [sentence for sentence in sentences if ''.join(sentence.split()) == ''.join(sentence.split())[::-1]] +print(palindromes) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte palindrom sayılar liste içlemi yoluyla elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +numbers = [12, 1221, 13431, 12345, 197262] + +palindrome_numbers = [number for number in numbers if str(number) == str(number)[::-1]] +print(palindrome_numbers ) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte (şehir_ismi, plaka_numarası) biçimindeki demet listesinde "şehir_ismi-plaka_numarası" biçiminde + bir string listesi elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +cities = [('ankara', 6), ('izmir', 35), ('eskişehir', 26), ('muğla', 48), ('kastamonu', 37)] + +result = [city + '-' + str(plate) for city, plate in cities] +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örnek aşağıdaki gibi de yapılabilirdi +#------------------------------------------------------------------------------------------------------------------------ + +cities = [('ankara', 6), ('izmir', 35), ('eskişehir', 26), ('muğla', 48), ('kastamonu', 37)] + +result = [f'{city}-{plate}' for city, plate in cities] +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin biz belli bir sayıya kadar olan asal sayıları bir liste biçiminde liste içlemi yoluyla elde edebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def isprime(val): + if val % 2 == 0: + return val == 2 + root_val = int(math.sqrt(val)) + for i in range(3, root_val + 1, 2): + if val % i == 0: + return False + return True + +result = [i for i in range(2, 1000) if isprime(i)] +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Aslında içlemlerin içerisinde birden fazla for döngüsü de olabilir. Yani aşağıdaki gibi içlemler de söz konusu olabilir: + + result = [ifade for x in iterable1 for y in iterable2 if koşul] + + İç içe for döngüleri nasıl çalışıyorsa buradaki çalışma da benzer biçimdedir. Dıştaki döngünün her bir yinelemesinde içteki + döngü baştan sona çalıştırılmaktadır. Yukarıdaki içlemin kod karşılığı şöyle ifade edilebilir: + + temp = [] + for x in iterable1: + for y in iterable2: + if koşul: + temp.append(ifade) + result = temp + + Örneğin: + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + + names_chars = [char for name in names for char in name ] + print(names_chars) +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +names_chars = [char for name in names for char in name ] +print(names_chars) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte iki kümenin kartezyen çarpımları bir demet listesi biçiminde elde edilmişir. İçlemlerde demet oluştururken + parantezler gerekmektedir. Örneğin: + + a = ['x', 'y', 'z'] + b = [1, 2, 3] + + cp = [(i, k) for i in a for k in b] + print(cp) + + Eğer burada içlemdeki ifadeyi oluşturan demeti parantezsiz biçimde aşağıdaki gibi oluşturmaya çalışsaydık error oluşurdu: + + cp = [i, k for i in a for k in b] # error! + + Çünkü Python'da bu anlamda virgül operatörü düşük önceliklidir. Örneğin: + + t = 1, 2 + 3 + + Burada (1, 5) demeti elde edilecektir. +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] +cities = ['ankara', 'izmir', 'adana', 'iskenderun', 'fatsa'] + +cartesian_product = [(name, city) for name in names for city in cities] +print(cartesian_product) + +#------------------------------------------------------------------------------------------------------------------------ + Bir içlemdeki ifade başka bir içlem olabilir. Örneğin bu yöntemle biz liste listeleri elde edebiliriz. Aşağıdaki içleme + dikkat ediniz: + + [[ifade for y in x] for x in a] + + Burada for döngüsü her işletildiğinde ifade olarak soldaki içlem yapılacaktır. Soldaki içlem de bize bir liste vereceğine göre + burada listelerden oluşan bir liste elde edilecektir. + + Aşağıdaki örnekte listelerden oluşan bir listedaki sayılar string'e dönüştürülmüştür. +#------------------------------------------------------------------------------------------------------------------------ + +a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +b = [[str(y) for y in x] for x in a] +print(b) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte liste içeisindeki listelerdeki çift elemanlar elde elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +b = [[y for y in x if y % 2 == 0] for x in a] +print(b) # [[2], [4, 6], [8]] + +#------------------------------------------------------------------------------------------------------------------------ + Amacımız her biri 5 elemandan oluşan 10 elemanlı tüm elemanları 0 olan bir liste listesi oluşturmak olsun. + Bu işlemi "repitition" ile yapamayız: + + >>> a = [[0, 0, 0, 0, 0]] * 10 + >>> a + [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + + Her ne kadar görünüşte bu işlem yapılmış gibi olsa da aslında burada bir kusur vardır. Listeler "değiştirilebilir (mutable)" + olduğu için "repitition" işlemi de kopyalama yoluyla yapıldığı için iç listenin bir elemanı değiştirildiğinde sanki tüm + listelerin ilgili elemanları değiştirilmiş gibi olur. Örneğin: + + >>> a[0][0] = 100 + >>> a + [[100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0], [100, 0, 0, 0, 0]] + >>> id(a[0]) + 2608002477184 + >>> id(a[1]) + 2608002477184 + >>> id(a[2]) + 2608002477184 + + Pekiyi bu işlemi doğru olarak nasıl yapabiliriz? İlk akla gelecek yöntem manuel bir for döngüsü kullanmaktadır: + + >>> a = [] + >>> for _ in range(10): + ... a.append([0, 0, 0, 0, 0]) + ... + >>> a + [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + >>> a[0][0] = 100 + >>> a + [[100, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + + Ancak bu işlemin en pratik yöntemi liste içlemi kullanmaktadır. Örneğin: + + >>> a = [[0, 0, 0, 0, 0] for _ in range(10)] + >>> a + [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + >>> a[0][0] = 100 + >>> a + [[100, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + + Bir içlemde ifade kısmında [....] ile bir lits yaratımı varsa for döngüsünün her çalışmasında artık yeni bir liste nesnesi + yaratılacaktır. Tabii bu örnekte aslında ifade olan listeyi yineleme (repition) ile de oluşturabiliriz. Çünkü int nesneler + "değiştirilemez" olduğu için bir sorun oluşmayacaktır. Örneğin: + + >>> a = [[0] * 5 for _ in range(10)] + >>> a + [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + >>> a[0][0] = 100 + >>> a + [[100, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir liste içleminde for cümleciğinin "in" kısmında da içlem kullanılabilir. Örneğin: + + [ifade for x in [for y in z]] + + Burada aslında içlemle elde edilen liste dönülmektedir. + + Aşağıdaki örnekte biz yazı sözcüklere ayrılıp sözcükler ters çevrilmiştir. Ters çevrilen sözcüklerin ilk karakteri 'n' + olanlar bir liste olarak elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +text = 'bugün hava çok güzel, sen de parka gittin mi?' +a = [revword for revword in [word[::-1] for word in text.split()] if revword[0] == 'n'] +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Genel olarak içlemler bir ifade oluşturduğu için hem daha kompakt yazımlara olanak sağlamakta hem de daha hızlı olma + eğilimindedir. Bu nedenle eğer içlem kullanabiliyorsanız içlemi tercih etmelisiniz. + + Aşağıdaki örnekte bir listenin manuel yolla ve içlem yoluyla oluşturulması örneği verilmiştir. Her ikisinin de zamanı + ölçülmüştür. +#------------------------------------------------------------------------------------------------------------------------ + +import time + +start = time.time() + +a = [] + +for i in range(10000000): + a.append(str(i)) + +stop = time.time() + +print(stop - start) # 3.7108449935913086 + +start = time.time() + +a = [str(i) for i in range(10000000)] +stop = time.time() + +print(stop - start) # 2.890331983566284 + +#------------------------------------------------------------------------------------------------------------------------ + Küme içlemleri (set comprehension) aslında sentaktik biçim olarak tamamen liste içlemleri ile aynıdır. Yalnızca dıştaki + köşeli parantezler yerine küme parantezleri kullanılmaktadır. Örneğin: + + s = {ch for ch in 'ankara'} + + Bu işlemden artık bir liste değil bir küme elde edilecektir. Küme parantezlerinin içi tamamen liste içlemlerindeki gibidir. +#------------------------------------------------------------------------------------------------------------------------ + +s = {ch for ch in 'ankara'} +print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte listenin iki elemanın toplamları bir küme olarak elde edilmiştir. Burada yinelenen toplamların + alınmadığına dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +s = {i + k for i in a for k in a} +print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii küme içlemi kullanmak yerine liste içlemi oluşturup elde edilen liste kümeye de dönüştürülebilir. Ancak bu işlem + küme içlemine göre daha yavaş bir çalışmaya neden olmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +s = set([i + k for i in a for k in a]) +print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Sözlük içlemleri (dictionary comprehensions) tamamen küme içlemleri gibidir. Yani yine küme parantezleriyle oluşturulur. + Ancak içlemin ifade kısmının "anahtar:değer" biçiminde olması gerekir. Tabii anahtar ve değer birer ifade olabilir. + Örneğin: + + {key: value for x in iterable} + + Burada for döngüsünün her çalışmasında öszlüğe yeni bir anahtar-değer çifti eklenecektir. Örneğin: + + d = {x: str(x) for x in itearble} + +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +d = {x: str(x) for x in a} +print(d) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki işlemi biz manuel olarak bir for döngüsüyle de yapabilirdik. Ancak içlemler hem kompkt bir ifade oluşturmak da + hem de daha hızlı olma eğilimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] +d = {} + +for x in a: + d[x] = str(x) + +print(d) + +#------------------------------------------------------------------------------------------------------------------------ + Yine aslında biz bir liste içlemi oluşturup oradan sözlük elde edebiliriz. Ancak bu yöntem dolaylı ve yavaş bir yöntemdir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +d = dict([(x, str(x)) for x in a]) +print(d) + +#------------------------------------------------------------------------------------------------------------------------ + Bir sözlükteki anahtarları değer, değerleri anahtar yapan kod parçasını daha önce aşağıdaki gibi oluşturmuştuk: + + d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + + result = {} + + for key, value in d.items(): + result[value] = key + + print(result) + + Aslında bu işlem aşağıdaki gibi sözlük içlemiyle kompakt bir biçimde de yapılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +result = {value: key for key, value in d.items()} +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki işlem items metodu kullanılmadan da aşağıdaki gibi yapılabilirdi. +#------------------------------------------------------------------------------------------------------------------------ + +d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50} + +result = {d[key]: key for key in d} +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + İçlemler bir nesne oluşturduğuna göre onları ara bir değişkende saklamadan doğrudan işleme sokabiliriz. Daha önce de + bunun çeşitli örneklerini yapmıştık. Örneğin: + + a = [1, 2, 3, 4, 5] + + for t in {x: str(x) for x in a}.items(): + print(t) + + Biz burada sözlük içlemi ile sözlük nesnesi elde ettikten sonra hemen nokta operatörü ile onun metodunu çağırabilmekteyiz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Daha önceden de çeşitli defalar belirttiğimiz gibi Python'da liste içlemleri, küme içlemleri ve sözlük içlemleri vardır. + Ancak demet içlemleri diye bir şey yoktur. Aslında sentaks olarak demet içlemi gibi bir sentaks vardır. Ancak bu sentaks + tamamen farklı bir anlama gelmektedir. Örneğin: + + (ifade for x in iterable) + + Bu sentaks Python'da geçerlidir ama "üretici ifade (generator expression)" anlamına gelmektedir. Üreticiler (generators) + ileride ayrı bir konu olarak ele alınacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +ge = (str(i) for i in range(100)) # dikkat! bu bir demet içlemi değildir! Üretici ifadedir! +for s in ge: + print(s) + +#------------------------------------------------------------------------------------------------------------------------ + zip fonksiyonu çok kullanılan "built-in" bir fonksiyondur. Parametrik yapısı şöyledir: + + def zip(*iterables): + pass + + Yani fonksiyon istenildiği kadar çok dolaşılabilir nesneyi parametre olarak alabilmektedir. Başka bir deyişle + zip fonksiyonu birden fazla argümanla kullanılabilir. Ancak argümanların hepsinin dolaşılabilir nesneler olması gerekir. + + zip fonksiyonu bizden dolaşılabilir nesneleri alır ve bize geri dönüş değeri olarak bir dolaşım nesnesi verir. + zip fonksiyonun geri döndürdüğü dolaşım nesnesini dolaştığımızda demetler elde ederiz. Öyle demetler elde ederiz ki + bu demetin elemanları bizim zip fonksiyonuna verdiğimiz dolaşılabilir nesnenin karşılıklı elemanlarıdır. Örneğin: + + a = ['ali', 'veli', 'selami'] + b = [10, 20, 30] + c = [5.2, 3.8, 4.6] + + z = zip(a, b, c) + + for t in z: + print(t) + + Burada z nesnesi her dolaşıldığında üç elemanlı aşağıdaki demetler elde edilecektir: + + ('ali', 10, 5.2) + ('veli', 20, 3.8) + ('selami', 30, 4.6) + +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 'veli', 'selami'] +b = [10, 20, 30] +c = [5.2, 3.8, 4.6] + +z = zip(a, b, c) + +for t in z: + print(t) + +#------------------------------------------------------------------------------------------------------------------------ + zip fonksiyonuna verdiğimiz dolaşılabilir nesnelerin uzunlukları aynı olmak zorunda değildir. En kısa nesne bittiğinde + dolaşım biter. Aşağıdaki örnekte en kısa nesne 3 eleman uzunlukta olduğuna göre dolaşım üç kere devam edecektir. +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] +b = [10, 20, 30] +c = [5.2, 3.8, 4.6, 5.8, 9.6] + +z = zip(a, b, c) + +for t in z: + print(t) + +#------------------------------------------------------------------------------------------------------------------------ + 39. Ders 07/09/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii biz zip fonksiyonunun bize verdiği dolaşılabilir nesneyi dolaşırken açım işlemi (unpacking) de yapabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] +b = [10, 20, 30, 40, 50] +c = [5.2, 3.8, 4.6, 5.8, 9.6, 3.4, 10.2] + +for x, y, z in zip(a, b, c): + print(x, y, z) + +#------------------------------------------------------------------------------------------------------------------------ + zip yapılmış bir nesneyi unzip yapabiliriz. Aslında bunun için de yine zip fonksiyonunun kendisi kullanılmaktadır. + Şöyle ki, zip fonksiyonu bize z isimli bir dolaşım nesnesi vermiş olsun. Şimdi biz bu dolaşım nenesini her dolaştığımızda + aslında demetler elde ederiz. O zaman biz zip(*z) gibi bir çağrı yaptığımız zaman bu demetleri sanki zip fonksiyonun + argümanları yapmış gibi oluruz. Buradan bize verilen dolaşım nesnesi dolaşıldığında eski değerler elde edilecektir. + #------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] +b = [10, 20, 30, 40, 50] +c = [5.2, 3.8, 4.6, 5.8, 9.6, 3.4, 10.2] + +z = zip(a, b, c) + +for t in zip(*z): + print(list(t)) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki unzip işleminin daha iyi anlaşılması için aşağıdaki örneği inceleyiniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [(10, 'ali'), (20, 'veli'), (30, 'selami')] + +result = zip(*a) +for t in result: + print(t) + +# yukarıdakinin eşdeğeri + +result = zip((10, 'ali'), (20, 'veli'), (30, 'selami')) +for t in result: + print(t) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii biz zip fonksiyonu ile unzip yaparak elde ettiğimiz demetleri de açabiliriz. Örneğin: + + a = [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] + + x, y = zip(*a) + + Burada biz zip(*a) işleminde iki elemanlı dolaşılabilir bir nesne elde etmiş olduk. Her türlü dolaşılabilir nesneyi + açabildiğimizi (unpacking) anımsayınız. O halde bu işlemden ayrıştrılmış iki ayrı demet elde edilecektir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [(10, 'ali'), (20, 'veli'), (30, 'selami'), (40, 'ayşe'), (50, 'fatma')] + +x, y = zip(*a) +print(x) # (10, 20, 30, 40, 50) +print(y) # ('ali', 'veli', 'selami', 'ayşe', 'fatma') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte önce liste enumerate fonksiyonuna sokulmuş sonra oradan elde edilen dolaşılabilir nesne zip fonksiyonuna + *'lı argüman olarak verilmiştir. Nasıl bir sonuç elde edildiğine dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +for t in zip(*enumerate(a)): + print(t) + +#------------------------------------------------------------------------------------------------------------------------ + n elemanlı kümenin k elemanlı farklı dizilimlerine permütasyon denilmektedir. n elemanlı kümenin k'lı alt kümelerine + ise kombinasyon denilmektedir. n elemanlı kümenin k'lı permütasyonlarının sayısı şöyle hesaplanmaktadır: + + P(n, k) = n! / (n - k)! + + Benzer biçimde n elemanlı kümenin k'lı kombinasyonlarının sayısı da şöyle hesaplanmaktadır: + + C(n, k) = n! / ((n - k)! * k!) + + Eskiden bu değerleri veren Python'da standart fonksiyonlar yoktu. Ancak Python 3.8 ile birlikte math modülüne perm ce comb + fonksiyonları eklenmiştir. Örneğin: + + >>> import math + >>> math.perm(6, 3) + 120 + >>> math.comb(6, 3) + 20 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + itertools modülündeki permutations isimli fonksiyon bizden dolaşılabilir bir nesneyi ve bir sayıyı parametre olarak alır + ve bize dolaşılabilir bir nesne verir. İşte permutations fonksiyonunun bize verdiği dolaşılabilir nesneyi dolaştığımızda + bizim birinci parametreyle verdiğimiz kümenin ikinci parametreyle belirtilen permütasyonlarını demetler halinde elde ederiz. + Örneğin: + + import itertools + + names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + + for t in itertools.permutations(names, 3): + print(t) + + Burada itertools.permutations fonksiyonun geri döndürdüğü nesne her dolaşıldığında permüstasyonlar birer demet biçiminde + elde edilecektir. +#------------------------------------------------------------------------------------------------------------------------ + +import itertools + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +for t in itertools.permutations(names, 3): + print(t) + +#------------------------------------------------------------------------------------------------------------------------ + Şimdi de permutations fonksiyonun bize verdiği dolaşılabilir nesneyi enumerate fonksiyonuna sokarak dolaşalım. +#------------------------------------------------------------------------------------------------------------------------ + +import itertools + +a = ['a', 'b', 'c', 'd', 'e'] + +result = itertools.permutations(a, 3) + +for index, t in enumerate(result): + print(index, '--->', t) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte yazdırma işlemi yan yana yapılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +import itertools + +a = ['a', 'b', 'c', 'd', 'e'] + +for t in itertools.permutations(a, 5): + print(*t, sep='') + +#------------------------------------------------------------------------------------------------------------------------ + itertools modülündeki combinations isimli fonksiyon permutations isimli fonksiyonla aynı parametrik yapıya sahiptir. + Ancak bu fonksiyon kombinasyonları bize vermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import itertools + +a = ['a', 'b', 'c', 'd', 'e'] + +for t in itertools.combinations(a, 3): + print(*t, sep='') + +#------------------------------------------------------------------------------------------------------------------------ + Bir grup oyuncu bir karşılaşmada birbirleriyle eşlendirilerek oynayacak olsunlar. Her oyuncu her oyuncuyla oynasın. + Oyuncuların players isimli bir dolaşılabilir nesnede bulunduğunu düşünelim. Aslında burada bylunmak istenen şey bu oyuncuların + ikili kombinasyonlarıdır. +#------------------------------------------------------------------------------------------------------------------------ + +import itertools + +players = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +for player1, player2 in itertools.combinations(players, 2): + print(player1, '<--->', player2) + +#------------------------------------------------------------------------------------------------------------------------ + Bilindiği gibi temel bellek birimi byte'tır. Bugünkü bilgisayar sistemlerinde adresleme byte esasına göre yapılmaktadır. + Bu nedenle programcılar çeşitli biçimlerde bir grup byte'ı saklamak durumunda kalabilirler. Bu yüzden programlamlama dillerinde + byte kavramını temsil eden türler bulundurulmuştur. İşte Python'da da byte kavramını temsil etmek için "bytes" isimli bir + tür bulundurulmuştur. + + Aslında byte kavramı 8 bitten oluşur. 8 bit de [0, 255] arasında bir syaı belirtir. Ancak bute kavramını int türüyle temsil etmek verimsizdir. + Çünkü int türü sınırsız uzunlukta tamsayıları temsil etmek içimn düşünülmüştür. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bugün kullandığımız dijital bilgisayarlarda RAM'deki ve diskteki tüm bilgiler byte yığnları biçimindedir. Örneğin çalıştırılabilen + bir program artık byte yığınlarından ibarettir. Tabii en küçük bellek birimi aslında bittir. Bit 1 ya da 0 olabilen 2'lik + sistemdeki basamaklara denilmektedir. 1 byte 8 bitten oluşmaktadır. + + Byte temel bellek birimi olarak kullanıldığından programala dillerinde byte kavramını temsil eden türler bulundurulmaktadır. + Python'da byte kavramı "bytes" isimli türle temsil edilmektedir. 1 byte sayısal olarak [0, 255] arasında bir tamsayı belirtmektedir. + Bir programdaki her şey (komutlar, yazılar, sayılar) hep byte yığınlarından oluşmaktadır. + + Bir bytes nesnesini pratik bir biçimde oluşturabilmek için tek tırnak, iki tırnak ya da üç tırnağa yapışık bir 'b' ya da + 'B' harfi kullanmak gerekir. Örneğin: + + >>> b = b'ankara' + >>> type(b) + + >>> b = B'izmir' + >>> type(b) + + + bytes türü her ne kadar byte kavramı için düşünülmüşse de Python'da sanki bir string gibi yaratılmaktadır. bytes türünden nesneleri + sanki birer string'miş gibi yaratmak kişilerin kafasını karıştırmaktadır. Biz bytes nesnesi oluştururken tırnak içerisindeki + karakterlerin ASCII tablosundaki sıra numaralarından bu nesnenin sayısal değerini oluşturmuş oluruz. Örneğin: + + b = b'a' + + Biz aslında burada 'a' karakterinin ASCII tablosundaki sıra numarası olan 97 sayısına ilişkin bir bytes nesnesi oluşturmuş oluruz. + Ancak bir bytes nesnesini bu biçimde oluştururken tırnak içerisindeki karakterlerin ASCII tablosunun ilk 128 karakterlerinden + oluşturulmuş olması gerekir. Örneğin: + + >>> b = b'ağrı' + File "", line 1 + b = b'ağrı' + ^ + SyntaxError: bytes can only contain ASCII literal characters. + + Byte kavramı [0, 255] arasında bir sayı belirttiğine göre biz de ancak ASCII tablosunun ilk 128 karakterini bu biçimde kullanabildiğimize göre + o zaman geri kalan byte değerlerini nasıl temsil ederiz? İşte tırnak içerisinde \x ve yanına iki hex digit (yani \xhh biçiminde) sentaksı + hh numaralı hex sayıya karşılık gelen byte'ı temsil etmektedir. İki hex digit [0, 255] arası tüm sayıları ifade edebildiği için bir byte'ın + her değerini ifade edebilmektedir. Örneğin: + + >>> b = b'\xFC\x17\x56' + >>> len(b) + 3 + >>> b + b'\xfc\x17V' + + Burada b nesnesi içerisinde toplam 3 byte vardır. Ancak yukarıdaki örnekten de görüldüğü gibi bytes nesnesi yazdırılırken + eğer byte değeri [0, 127] arasında ise yazdırmada doğrudan o sayının ASCII karakter karşılığı basılmaktadır. Başka bir deyişle + bytes nesnesi yazdırılırken yazdırılabilen karakterler karakter olarak diğerleri ise \xhh biçiminde yazdırılmaktadır. Örneğin: + + >>> b = b'\x168\x41\xFF' + >>> b + b'\x168A\xff' + >>> len(b) + 4 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir bytes nesnesini oluşturmanın diğer bir yolu bytes fonksiyonunu kullanmaktadır. Bu fonksiyonu biz argümansız kullanırsak + boş bir bytes nesnesi elde ederiz. Eğer bu fonksiyona dolaşılabilir bir nesneyi argüman olarak verirsek bu durumda + o dolaşılabilir nesnenin içerisindeki sayılardan bytes nesnesi oluşturulur. Tabii bu durumda bizim dolaşılabilir nesne içeriside + [0, 255] arası sayılar bulundurmamız gerekir. Örneğin: + + >>> a =[34, 56, 129, 227, 78] + >>> b = bytes(a) + >>> len(b) + 5 + >>> b + b'"8\x81\xe3N' + + Örneğin: + + >>> a = [34, 556, 129, 227, 78] + >>> b = bytes(a) + Traceback (most recent call last): + File "", line 1, in + ValueError: bytes must be in range(0, 256) + +#------------------------------------------------------------------------------------------------------------------------ + Bir string nesnesinden de bir bytes nesesi oluşturulabilir. Ancak bu durumda işin içine "encoding" kavramı girmektedir. + Bir yazı farklı karakter tablolarının farklı encoding'lerine göre farklı biçimlerde byte'lara dönüştürülebilmektedir. + Encoding konusu nispeten karmaşık bir konudur ve bu konu ileride daha ayrıntılı biçimde ele alınacaktır. UNICODE tablonun + "utf-8", "utf-16", "utf-32" gibi değişik encoding'leri vardır. Tabii bir string eğer mümkünse ASCII biçimine ya da ASCII + tablosunun çeşitli code page'lerine de dönüştürülebilir. + + Python'da bir string'ten bytes nesnesi oluşturabilmek için iki yol vardır. Birinci yol bytes fonksiyonu kullanmaktır. + Eğer bytes fonksiyonunun birinci parametresi string girilirse bytes fonksiyonu bu string'ten byte'lar oluşturarak onu + bytes nesnesine dönüştürmektedir. Bu durumda bytes fonksiyonuna encoding belirten ikinci bir argüman girilmesi gerekir. + Örneğin: + + >>> s = 'ağrı dağı' + >>> b = bytes(s, 'utf-8') + >>> b + b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1' + + Bir yazıyı byte'larına ayrıştırarak bytes nesnesi elde etmenin diğer bir yolu da str sınıfının encode metodunu kullanmaktır. + Bu metot da encoding parametresi almaktadır. Ancak bu encoding girilmezse default durumda 'utf-8' kullanılır. Örneğin: + + >>> s = 'ağrı dağı' + >>> s.encode('utf-8') + b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1' + >>> s.encode() + b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1' + + Encoding belirten parametrenin ismi "encoding" biçimindedir. Bazı programcılar okunabilirliği artırmak için parametreninismini + açıkça yazmaktadır. + Örneğin: + + >>> b = bytes('ankara', encoding='utf-8') + >>> b + b'ankara' + >>> s = 'ankara' + >>> b = s.encode(encoding='utf-8') + >>> b + b'ankara' + + Bazen yukarıdaki işlemin tersini yapmak da gerekebilir. Yani elimizde bir bytes nesnesi vardır. Bu nesnenin içerisinde belli bir + encoding'e göre bir yazıyı belirten byte'lar bulunmaktadır. Biz de bu bytes nesnesinden bir str nesnesi elde etmek isteyebiliriz. + Bunun için str fonksiyonu kullanılabilir. Fonksiyonun birinci parametresi bir bytes nesnesi alırsa ikinci parametrede encoding + belirtmelidir. Örneğin: + + >>> b = b'\x61\x62\x63' + >>> s = str(b, encoding='ascii') + >>> s + 'abc' + + Bir bytes nesnesinin içerisindeki byte'lardan bir string elde etmenin ikinci yolu da bytes sınıfının decode metodunu kullanmaktır. + Yine metot parametre olarak encoding bilgisini alacaktır. Encoding eblirtilmeyebilir. Bu durumda default "utf-8" anlaşılmaktadır. + Örneğin: + + >>> b = b'\x61\x62\x63' + >>> s = b.decode(encoding='ascii') + >>> s + 'abc' + + Özetle string'ten bytes'a dönüştürme için bytes fonksiyonu ve str sınıfının encode metodu kullanılırken, bytes'tan string'e + dönüştürme için str fonksiyonu ve bytes sınıfının decode metodu kullanılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 40. Ders 12/09/2022 Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + bytes nesnelerinin herhangi bir byte'ına yine [...] operatörü ile erişilir. Ancak bu operatör bize int bir değer vermektedir. + Örneğin: + + >>> b = b'ankara' + >>> val = b[0] + >>> type(val) + + >>> val + 97 + + bytes türü de "değiştirilemez (immutable)" bir türdür. Dolayısıyla biz bir bytes nesnesinin içerisindeki yazıyı değiştiremeyiz. + Bu bakımdan bytes nesneleri str nesnelerine çok benzemektedir. + + Örneğin: + + >>> b = b'ankara' + >>> b + b'ankara' + >>> b[0] = 'x' + Traceback (most recent call last): + File "", line 1, in + TypeError: 'bytes' object does not support item assignment + + bytes sınıfının str sınıfında olan pek çok metodu bulunmaktadır. Örneğin: split, strip, count, index, find gibi. + + bytes nesneleri tamamen str nesneleri gibi dilimlenebilmektedir. Tabii bytes nesnelerinin dilimlenmesinden bytes nesneleri + elde edilmektedir. Örneğin: + + >>> b = b'ankara' + >>> b + b'ankara' + >>> b[2:4] + b'ka' + >>> b[:-2] + b'anka' + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + bytes sınıfının pek çok faydalı metodu da vardır. Bu metotların çoğu str sınıfının metotlarına benzemektedir. Ancak + bytes sınıfının metotları tıpkı str sınıfında olduğu gibi asıl nesne üzerinde değişiklik yapmaz bize değiştirilmiş yeni + bir bytes nesnesi verir. Örneğin: + + >>> b = b'ali veli selami' + >>> result = b.split() + >>> result + [b'ali', b'veli', b'selami'] + >>> result = b.upper() + >>> result + b'ALI VELI SELAMI' +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da built-in bytearray isimli bir tür de vardır. Aslında bytes türü ile bytearray türü birbirlerine çok benzemektedir. + Aralarındaki tek fark bytes türü "değiştirilemez (immutable)" olduğu halde bytearray türünün "değiştirilebilir (mutable)" + olmasıdır. Bir bytearray nesnesi ancak bytearray fonksiyonu ile yaratılabilir. String gibi başına önekler getirilerek yaratılmaz. + + bytearray fonksiyonunda biz parametre olarak dolaşılabilir bir nesne verirsek bu dolaşılabilir nesnenin elemanlarının [0, 255] arasında + int değerler olması gerekir. Bu durumda bu değerlerden bytearray nesnesi yaratılır. Örneğin: + + >>> ba = bytearray([1, 2, 3, 4, 5]) + >>> ba + bytearray(b'\x01\x02\x03\x04\x05') + + bytearray nesnesi benzer biçimde bytes nesnesinden de yaratılabilir. Örneğin: + + >>> ba = bytearray(b'ankara') + >>> ba + bytearray(b'ankara') + + bytearray nesnesi string nesnesinden de yaratılabilir. Bu durumda encoding belirtmek gerekir. Örneğin: + + >>> ba = bytearray('ankara', encoding='utf-16') + >>> ba + bytearray(b'\xff\xfea\x00n\x00k\x00a\x00r\x00a\x00') +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + bytearray türü değiştirilebilir olduğu için biz bir bytearray nesnesinin içerisindeki byte'ları değiştirebiliriz. Zaten bytearray + türünün bytes türünden tek farkı budur. Örneğin: + + >>> b = b'\x00\x01\x02' + >>> len(b) + 3 + >>> b + b'\x00\x01\x02' + >>> b[0] = 10 + Traceback (most recent call last): + File "", line 1, in + TypeError: 'bytes' object does not support item assignment + >>> ba = bytearray(b'\x00\x01\x02') + >>> ba + bytearray(b'\x00\x01\x02') + >>> len(ba) + 3 + >>> ba[0] = 10 + >>> ba + bytearray(b'\n\x01\x02') + + bytearray türü üzerinde de dilimler yapılabilmektedir. + + bytearray türü de bytes türü gibi str sınıfında olan pek çok metodu içermektedir: Örneğin count, index, strip, split gibi. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + bytes türü değiştirilemez olduğu için yorumlayıcının bazı optimizasyonlarına katılabilmektedir. Aynı zamanda bytes türü + yine değiştirilemez olduğu için toplamda bellekte daha az yer kaplamaktadır. Bu nedenle eğer byte dizisinin elemanlarını + değiştirmeyecekseniz bytes türünü, değiştirecekseniz bytearray türünü tercih etmelisiniz. Yine değiştirilemez türlerin + hash'lenebilir olduğunu ama değiştirilebilir türlerin hash'lenebilir olmadığını anımsayınız. Bu nedenle örneğin bytes + türü bir sözlüğe anahtar yapılabilir ancak bytearray türü yapılamaz. Örneğin: + + >>> d = {b'ali': 100} + >>> d + {b'ali': 100} + >>> d = {bytearray(b'ali'): 100} + Traceback (most recent call last): + File "", line 1, in + TypeError: unhashable type: 'bytearray' + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + bytearray sınıfının da upper gibi lower gibi strip gibi bytes ve str sınıflarında bulunan metotları vardır. Bu metotların + bazıları in-place işlem yapmazlar tıpkı str ve bytes sınıflarına olduğu gibi yeni bir nesne verirler. Ancak bytearray + sınıfının append, remove, pop gibi metotları in-place işlem yapmaktadır. Ancak append gibi, remove gibi metotlar bytearray + elemanını bizden int bir değer gibi istemektedir. Örneğin: + + >>> ba = bytearray(b'ankara') + >>> ba + bytearray(b'ankara') + >>> ba.append(97) + >>> ba + bytearray(b'ankaraa') + >>> result = ba.pop() + >>> result + 97 + >>> ba + bytearray(b'ankara') + >>> ba.remove(ord('k')) + >>> ba + bytearray(b'anara') + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da Nesne Yönelimli Programalama Modeline İlişkin Özellikler +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz şimdiye kadar hep fonksiyonları kullanarak kodlar oluşturduk. Bir programın fonksiyonlar yoluyla oluşturulmasına + "prosedürel programlama tekniği" denilmektedir. Sınıflar (classes) "Nesne Yönelimli Programlama Tekniğini (NYPT)" + uygulamak için gereken yapı taşlarıdır. NYPT'in tek cüöleyle tanımını yapmak oldukça zordur. Ancak NYPT'yi "sınıflar + kullanarak program yazma" tekniği biçiminde tanımlayabiliriz. Sınıflar (classes) NYPT'nin yapı taşlarıdır. Bu nedenle bu + tekniği öğrenebilmek için öncelikle sınıf kavramını, sınıfların oluşturulması ve kullanımını öğrenmek gerekir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıf tanımlamanın (sınıf oluşturmanın) genel biçimi şöyledir: + + class : + + Örneğin: + + class Sample: + pass + + Sınıf isimleri Python programcıları tarafından genellikle "Pascal harflendirmesiyle (Pascal casting)" yazılmaktadır. + Yani isim tek bir sözcükten oluşuyorsa sözcüğün ilk harfi büyük olur. Birden fazla sözcükten oluşuyorsa her sözcüğün + ilk harfi büyük olur. Biz de kurusumuzda sınıf isimlerini genel olarak Pascal casting biçiminde oluşturacağız. Python + standart kütüphanesinde built-in sınıflar (list gibi, tuple gibi, dict gibi) küçük harflerle genellikle C tarzı harflendirmeyle + oluşturulmuştur. Ancak standart kütüphanenin çeşitli modüllerinde C tarzı harflendirmeyle Pascal tarzı harflendirmeye + rastlanmaktadır. Yukarıda da belirtitğimiz gibi biz kursumuzda kendi oluşturacağımız sınıfları Pascal tarzı harrflendirmeyle + ifade edeceğiz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da şimdiye kadar gördüğümüz int, float, bool gibi temel türlerin hepsi birer sınıf kabul edilmektedir. Benzer biçimde + list, tuple, dict gibi türler de birer sınıftır. Kısacası Python'da her tür aslında bir sınıf belirtmektedir. Python + referans dokğmanlarında hiç import etmeden kullanabildiğimiz int, float, str, list, set, dict gibi sınıflara "built-in türler + (built-in types)" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıflar aynı zamanda birer tür de belirtmektedir. Oluşturduğumuz bir sınıf türünden nesneler yaratabiliriz. Bir sınıf türünden + nesne yaratmanın genel biçimi şöyledir: + + sınıf_ismi([argüman listesi]) + + Örneğin: + + s = Sample() + + Burada s artık Sample türünden bir nesnenin adresini tutmaktadır. + + Bir sınıf türünden nesnenin yaratılmasının adeta bir fonksiyon çağrısına benzediğine dikkat ediniz. Aslında list, tuple, str + gibi sınıflar türünden de nesneleri biz aynı sentaksla yaratabiliyorduk. Örneğin: + + s = str() + t = tuple() + a = list() +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + pass + +s = Sample() +print(type(s)) # + +#------------------------------------------------------------------------------------------------------------------------ + NYPT'de bir sınıf türünden nesneye o sınıf türünden bir "örnek (instance)" da denilmektedir. Yani "örnek (instance)" + sözcüğü sınıf türünden nesneler için kullanılmaktadır. Sınıflar elemanlara sahip olabilirler. Sınıf nesneleri (yani örnekler) + bileşik nesnelerdir. Yani parçalara sahiptir. Python'da bir sınıf nesnesinin elemanlarına yani parçalarına "öznitelik (attribute)" + denilmektedir. (Halbuki sınıf nesnelerinin parçalarına C++'ta "veri elemanı (data member)", Java ve C#'ta "alan (field)" + denilmektedir.) Eğer öznitelik bir nesnenin parçası durumunda ise buna "örnek özniteliği (instance attribute)" de denilmektedir. + Özetle sınıf nesneleri parçalardan oluşur. Nesnenin bu parçalarına "örnek özbiteliği (instance attribute)" denilmektedir +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir nesne yaratıldığında henüz nesnenin içi boştur. Örneğin: + + class Sample: + pass + + s = Sample() + + Burada s değişkeninin gösterdiği yerdeki nesnenin içi henüz boştur. Yani s'in henüz bir özniteliği yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf türünden birden fazla nesne (yani örnek) yaratabiliriz. Her yarattığımız nesne aslında farklı bir nesnedir. Örneğin: + + class Sample: + pass + + s = Sample() + k = Sample() + + Burada s değişkeni başka bir nesneyi k değişkeni de başka bir nesneyi göstermektedir. Örneğin: + + >>> s = Sample() + >>> k = Sample() + >>> id(s) + 3104147128816 + >>> id(k) + 3104147434752 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf nesnesi yaratıldığında içi boştur demiştik. İşte bir nesnenin "öznitelikleri (attributes)" aşağıdaki sentaksla + yaratılmaktadır: + + .<öznitel_ismi> = değer + + Örneğin: + + s = Sample() + s.a = 10 + s.b = 20 + s.c = 'ankara' + + Burada biz s nesnnesinin içerisinde üç tane öznitelik (örnek özniteliği) oluşturduk. Yani artık s'nin üç parçası bulunmaktadır. + Tabii Python'da her zaman değişkenler adres tutarlar. s nesnesinin içerisindeki a, b, ve c de aslında sırasıyla int, int ve str nesnelerinin + adreslerini tutmaktadır. Başka bir deyişle s nesnesinin a, b ve c parçaları içerisinde adresler vardır. Bu adreslerde sırasıyla + int bir nesne, int bir nesne ve str türünden bir nesne bulunmaktadır. + + s ----> Sample nesnesi + a ----> int nesne + b ----> int nesne + c ----> str nesnesi + + Python terminolojisine göre s Sample sınıfı türünden bir değişkendir. Bu s değişkeni bir Sample nesnesinin adresini + tutmaktadır. Bu nesne üç parçadan oluşmaktadır. Nesnenin parçalarına "örnek öznitelikleri (instance attribute)" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da "öznitelik (attribute)" sınıfa ilişkin ya da nesneye ilişkin olabilmektedir. Eğer öznitelik sınıfa ilişkinse + buna "sınıf özniteliği (class attribute)" denilmektedir. Eğer öznitelik nesneye ilişkinse buna da "örnek özniteliği (instance + attribute) denilmektedir. Biz "nesnenin özniteliği" dediğimizde "örnek özniteliği", "sınıfın özniteliği" dediğimizde + "sınıf özniteliği" anlaşılmalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + C++, Java ve C# gibi dillerde bir sınıf türünden farklı nesnelerin elemanları hep aynı isimli ve aynı türden olur. Ancak + Python'da böyle olmak zorunda değildir. Örneğin Sample türünden iki sınıf nesnesi olsun. Bunların elemanları yani öznitelikleri + farklı olabilir: + + class Sample: + pass + + s = Sample() + k = Sample() + + s.a = 10 + s.b = 20 + + k.x = 'ali' + k.y = 12.3 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf nesnesi için öznitelikler yaratıldıktan sonra bu öznitelikler istenildiği zaman .<öznitelik_ismi> + sentaksıyla kullanılabilmektedir. Nesnenin özniteliklerini "nesnenin içerisindeki değişkenler" gibi düşünebilirsiniz. + Yukarıda belirttiğimiz gibi nesnenin öznitelikleri tüm değişkenler gibi aslında birer adres turmaktadır. Asıl nesneler + o adreslerde bulunmaktadır. Örneğin: + + >>> class Sample: + ... pass + ... + >>> s = Sample() + >>> s.a = 10 + >>> s.b = 'ankara' + >>> s.a + 10 + >>> s.b + 'ankara' + >>> id(s) + 1565042138544 + >>> id(s.a) + 1565005316624 + >>> id(s.b) + 1565042285808 + +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + pass + +s = Sample() + +s.a = 10 +s.b = 'ankara' + +print(s.a) # 10 +print(s.b) # ankara + +#------------------------------------------------------------------------------------------------------------------------ + Kursumuzun ilk bölümlerinde de belirttiğimiz gibi Python'da sınıfların içerisinde fonksiyonlar olabilir. Sınıfların + içerisindeki fonksiyonlara "metot (method)" denilmektedir. Eğer bir fonksiyon sınıfların dışındaysa ona "fonksiyon (function)", + bir sınıfın içerisindeyse ona "metot (method)" denilmektedir. + + Python'da metotların en azından bir parametresi olur. Metotların ilk parametreleri özel bir anlama sahiptir. Programcılar genel olarak + metotların bu ilk parametrelerini "self" biçiminde isimlendirirler. Ancak bu ilk parametrenin "self" biçiminde isimlendirilmesi + zorunlu değildir. Örneğin: + + class Sample: + def foo(self): + pass + + def bar(self, a): + pass + + def tar(self, a, b): + pass + + def zar(): + pass + + Burada foo, bar ve tar Sample sınıfının metotlarıdır. zar ise sınıf içerisinde olmadığı için bir metot değil fonksiyondur. + + Bir metodun çağrılması için o metodun içinde bulunduğu sınıf türünden bir değişkenin olması gerekir. Metot çağırma işleminin genel + biçimi şöyledir: + + .([argüman listesi]) + + Yani biz önce ilgili sınıf türünden bir nesne yaratırız. Nesnenin adresi sınıf türünden değişkende tutulur. Sonra o değişkenle "nokta" + operatörünü kullanarak sınıfın metotlarını çağırırız. Örneğin: + + s = Sample() + + s.foo() + s.bar(10) + s.tar(10, 20) + + Metot bu biçimde çağrılırken birinci self parametresi için argüman girilmez. Metoda girilen argümanlar self parametresinden + sonraki parametrelere ilişkindir. Örneğin: + + s = Sample() + + self.foo() + + Burada foo metodunun self parametresinden başka parametresi olmadığı için bir argüman girilmemiştir. Örneğin: + + self.bar(10) + + Burada 10 değeri bar metodunun a parametre değişkeni için girilmiştir. Örneğin: + + s.tar(10, 20) + + Burada 10 değeri a parametresi için, 20 değeri ise b parametresi için girilmiştir. + + Yukarıda da belirttiğimiz gibi aslında metotların birinci parametrelerinin isminin self olması zorunlu değildir. + Python yorumlayıcısı self ismine değil birinci parametreye izleyen paragraflarda açıklanacak olan işlevi yüklemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + print('foo') + + def bar(self, a): + print(f'bar: {a}') + + def tar(self, a, b): + print(f'tar: {a}, {b}') + +def zar(): + print('zar') + +s = Sample() + +s.foo() +s.bar(10) +s.tar(10, 20) + +zar() + +#------------------------------------------------------------------------------------------------------------------------ + Aslında biz şimdiye kadar zaten metot çağrımının nasıl yapıldığını görmüştük. Örneğin list sınıfının append metodunu + list sınıfı türünden bir değişkenler çağıyorduk: + + a = [1, 2, 3, 4, 5] + a.append(100) + + Ya da örneğin dict sınıfının pop metodunu yine dict sınıfı türünden bir değişkenle çağırıyorduk: + + d = {'ali': 10, 'veli': 20, 'selami': 30} + + d.pop('veli') +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi Python'da her atama aslında bir adres atamasıdır. O halde bir sınıf türünden değişkeni başka bir değişkene + atarsak o değişken de asıl değişkenle aynı nesneyi gösteriyor olur. Örneğin: + + class Sample: + pass + + s = Sample() + s.a = 10 + s.b = 20 + + k = s + + print(k.a) # 10 + print(k.b) # 20 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Metotların en azından bir parametresi olmak zorunda olduğunu belirtmiştik. Bu parametrenin de genellikle "self" + biçiminde isimlendirildiğini söylemiştik. Pekiyi bu self parametresinin anlamı nedir? İşte metotlar ilgili sınıf türünden + değişkenlerle çağrılırlar. Her zaman self parametresine metodun çağrılmasında kullanılan değişken (yani onun içindeki adres) + atanmaktadır. Başka bir deyişle self metodun çağrılmasında kullanılan nesneyi gösteren bir değişkendir. Örneğin: + + class Sample: + def foo(self, a, b): + pass + + s = Sample() + s.foo(10, 20) + + Burada s değişkeni (yani içindeki adres) self parametresine, 10 (yani 10 nesnesinin adresi) değeri a parametre değişkenine + ve 20 değeri de (yani 20 nesnesinin adresi) b parametre değişkenine atanmaktadır. Burada self tamamen s ile aynı nesneyi + gösterir duruma gelmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi bir sınıf türünden değişken başka bir değişkene atanabilir. Bu durumda iki değişken + de aynı nesneyi gösterir. Nesneye hangi değişkenle erişildiğinin bir önemi yoktur. Örneğin: + + s = Sample() + + s.a = 10 + k = s + + Python'da bütün atamalar adres ataması olduğuna göre k = s işleminde s'in içerisindeki adres k'ya atanmıştır. Bu durumda + s ve k aynı nesneyi göstermektedir. O halde s.a ile k.a arasında hiçbir farklılık yoktur. Çünkü s.a "s değişkenin + içerisindeki adresteki nesnenin a özniteliği" anlamına gelmektedir. k.a da "k değişkenin içerisindeki adresteki, nesnenin a + özniteliği" anlamına gelir. O halde iki ifade arasında hiçbir farklılık yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + pass + +s = Sample() +s.a = 10 + +k = s + +print(s.a) +print(k.a) + +print(id(s)) # 1781626597584 +print(id(k)) # 1781626597584 + +print(id(s.a)) # 1781443422800 +print(id(k.a)) # 1781443422800 + +#------------------------------------------------------------------------------------------------------------------------ + NYPT'te aslında bir sınıf ortak data'lar üzerinde işlem yapan fonksiyonların oluşturduğu bir veri yapısıdır. Sınıfın + metotları aynı nesne üzerinde işlem yapmaktadır. Örneğin: + + s = Sample() + ... + + s.foo() + s.bar() + s.tar() + + Burada foo, bar ve tar metotlarının self parametrelerine aynı s nesnesi geçirilmiştir. O halde bu metotlar aslında s + üzerinde işlem yapma potansiyeline sahiptir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi bir metot çağrılırken her zaman metodun çağrılmasında kullanılan değişken (yani onun + içerisindeki adres) metodun birinci parametresi olan self parametresine atanmaktadır. Bu durumda self parametresi + metodun çağrılmasında kullanılan değişken ile aynı nesneyi gösterir durumda olur. Metotlar ilgili sınıf türünden bir + değişkenle çağrıldığına göre o değişkenin atanacağı metotta bir self parametresinin bulunması gerekmektedir. Aşağıdaki + örnekte self parametresi ile s aynı nesneyi gösteriyor durumdadır. Dolayısıyla nesnenin a özniteliğine self parametresiyle de + erişilebilmiştir: + + class Sample: + def foo(self): + print(self.a) # 10 + + s = Sample() + s.a = 10 + + s.foo() + + Burada s ile self aslında aynı nesneyi göstermektedir. Dolayısıyla biz self.a ifadesi ile nesnenin a örnek özniteliğine + erişebilmekteyiz. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + print(self.a) # 10 + +s = Sample() +s.a = 10 + +s.foo() + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki, işlemin tersini de yapabiliriz. Yani metodun içerisinde nesenin öznitelikleri yaratılabilir. Yaratılan bu öznitelikler + daha sonra kullanılabilir: + + class Sample: + def foo(self): + self.a = 10 + self.b = 20 + + s = Sample() + s.foo() + + print(s.a, s.b) # 10 20 + + Burada foo metodu içerisinde self.a = 10 ve self.b = 20 ile self değişkeninin gösterdiği yerdeki nesnenin a ve b öznitelikleri + oluşturulmuştur. self ile s aynı nesneyi gösterdiğine göre biz bu a ve b örnek özniteliklerine s yoluyla da erişebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + self.a = 10 + self.b = 20 + +s = Sample() +s.foo() + +print(s.a, s.b) # 10 20 + +#------------------------------------------------------------------------------------------------------------------------ + 41. Ders 14/09/2022 Carsamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Modüller konusunda de belirtmiştik, Python'da başında ve sonunda iki alt tire olan özel bazı isimler kullanılmaktadır. + Örneğin __init__, __add__ gibi. Bu isimleri kolay ifade edebilmek için __xxx__ için "dunderscore xxx" ya da "dunder xxx" + ifadeleri kullanılmaktadır. Yani örneğin biz "dunder init" demekle "__init__" ismini, "dunder add" demekle __add__ ismini + kastediyor olacağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da sınıfların __init__ isimli özel bir metotları vardır. Bu metotlara "initializer" ya da bazen "constructor" da + denilmektedir. __init__ metodu bir sınıf türünden nesne yaratıldığında otomatik olarak çağrılan bir metottur. Örneğin: + + class Sample: + def __init__(self): + pass + + s = Sample() + + Burada Sample nesnesi Sample() ifadesi ile yaratılmak istendiğinde Python yorumlayıcısı önce nesneyi yaratır. Sonra yaratılan + nesnenin adresini self parametresine geçirerek __init__ metodunu çağırır, ondan sonra s değişkenine atama yapılır. Yani önce __init__ + metodu çağrılıp sonra nesnenin adresi s değişkenine atanmaktadır. + + Aşağıdaki örnekte Sample sınıfı türünden her nesne yaratıldığında yorumlayıcı tarafından sınıfın __init__ metodu otomatik + bir biçimde çağrılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self): + print('__init__ called') + +s = Sample() +print('Ok') +k = Sample() + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda de belirtitğimiz gibi bir sınıf türünden nesne yaratıldığında önce nesne yaratılır. Sonra sınıfın __init__ + metodu çağrılır. Ondan sonra nesnenin adresi değişkene atanır. Örneğin: + + s = Sample() + + Burada __init__ çağrıldıktan sonra s'e atama yapılacaktır. __init__ metodunun self parametresi henüz (yani yeni) yaratılmış + olan nesneyi belirtmektedir. İşte programcı tipik olarak oluşturduğu nesnenin nesnenin özniteliklerini __init__ metodu + içerisinde yaratmaktadır. Böylece yaratılan her nesne aynı elemanlara yani özniteliklere sahip olur. Örneğin: + + class Sample: + def __init__(self): + self.a = 10 + self.b = 20 + + s = Sample() + k = Sample() + + print(s.a, s.b) + print(k.a, k.b) + + Burada s ve k değişkenlerinin gösteridği yereki nesnelerin öznitelikleri __init__ tarafından yaratılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self): + self.a = 10 + self.b = 20 + +s = Sample() +k = Sample() + +print(s.a, s.b) +print(k.a, k.b) + +#------------------------------------------------------------------------------------------------------------------------ + C++, Java ve C# gibi diğer nesne yönelimli programlama dillerinde de bir nesne yaratıldığında sınıfın bir metodu otomatik + olarak çağrılmaktadır. Bu dillerde bu otomatik çağrılan metoda "constructor (yapıcı fonksiyon ya da metot)" denilmektedir. + Python'daki __init__ metodu bu dilelrdeki "constructor" metotlarına benzemektedir. Ancak Python'da __init__ metoduna "constructor" + değil "initializer" denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + __init__ metodunun self dışında başka parametreleri de olabilir. Bu durumda nesne yaraılırken bu parametreler için de + argüman girilmelidir. Örneğin: + + class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + Burada nesneyi yaratırken parantezler içerisinde a ve b için iki argüman girmeliyiz. Örneğin: + + s = Sample(10, 20) + + Burada Sample(10, 10) ifadesi ile yeni yaratılan nesnenin adresi self parametresine, 10 nesnesinin adresi a parametresine, + 20 nesnesinin adresi de b parametresine aktarılır. Bu parametreler de nesnenin özniteliklerine atanmıştır. + + Python programcıları genellikle (ama her zaman değil) diğer metotlarda da kullanabilmek için __nit__ metoduna geçirilen + parametreler için nesnede örnek öznitelikleri yaratarak o örnek öznitelikrlerine yerleştirmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + +s = Sample(10, 20) +print(s.a, s.b) # 10 20 + +k = Sample(30, 40) +print(k.a, k.b) # 30 40 + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte sınıfın __init__ metodunda a ve b isimli iki örnek özniteliği yaratılmıştır. disp metodunda da bunlar + ekrana yazdırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + def disp(self): + print(self.a, self.b) + +s = Sample(10, 20) +k = Sample(30, 40) + +s.disp() # 10 20 +k.disp() # 30 40 + +#------------------------------------------------------------------------------------------------------------------------ + Tabii biz nesnenin örnek özniteliklerini başka bir metotta da yaratabiliriz. __init__ metodunun diğer metotlardan tek + farkı yorumlayıcı tarafından oromatik çağrılmasıdır. Aşağıdaki örnekte nesnenin a ve öznitelikleri __init__ metodunda değil + set metodunda yaratılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def set(self, a, b): + self.a = a + self.b = b + + def disp(self): + print(self.a, self.b) + +s = Sample() +k = Sample() + +s.set(10, 20) +k.set(30, 40) + +s.disp() +k.disp() + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi bir sınıf nedir? Aslında sınıf örnek özniteliklerinden ve metotlardan oluşan bir veri yapısıdır. Sınıflar belli + bir konuda işlemleri yapan metotlara sahiptirler. Bu metotlar da nesnenin özniteliklerini ortak bir biçimde kullanmaktadır. + NYPT'de bir konuda bir işi yapabilecek sınıflar yazılır ve program sınıflar yoluyla oluşturulur. + + Aşağıdaki örnekte Date sınıfı bir tairh bilgisini tutup onu ekrana yazdıran metoda sahiptir. +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def disp(self): + print(f'{self.day:05d}/{self.month:02d}/{self.year:02d}') + +d = Date(19, 9, 2023) # 19/06/2023 +d.disp() + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte Complex sınıfı bir karmaşık sayıyı temsil etmektedir. Complex nesnesinin tutacağı karmaşık sayının + gerçek ve sanal kısımları __init__ metodunda sınıfın örnek özniteliklerinde saklanmış disp metodunda bunlar ekrana + yazdırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real, imag): + self.real = real + self.imag = imag + + def disp(self): + print(f'{self.real}+{self.imag}i') + +z = Complex(3, 2) +z.disp() + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte de bir nokta Point isimli bir sınıfla temsil edilmiştir. Sınıfın __init__ metodunda noktanın x ve y + bileşenleri nesnenin x ve y özniteliklerine atanmıştır. disp metodu da nesne içerisindeki bu x ve y özniteliklerini + yazdırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def disp(self): + print(f'({self.x},{self.y})') + +pt = Point(3, 2) +pt.disp() + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf bi konuyla ilgili belli işlemleri yapmak amacıyla yazılır. Tipik olarak biz sınıfbir nesnesini yaratırken + __init__ metoduna argümanlar geçeriz. __init__ metodu bunları nesnenin özniteliklerinde saklar. Diğer metotlar da + self yoluyla bunlara erişerek faydalı işlemler yaparlar. Yukarıda verdiğimiz Date, Complex, Point gibi sınıflarda + biz yalnızca birkaç bilgiyi nesnenin özniteliklerinde tutup disp metodunda onları ekrana yazdırdık. Oysa örneğin + Date gibi bir sınıfın tarihler üzerinde faydalı işlemler yapan pek çok metodu olabilir. Örneğin Date sınıfı bir tarihten + belli gün sonranın ya da öncenin bulunması gibi, iki tarih arasındaki gün farkının bulunması gibi, iki tarihin + karşılaştırılması gibi pek çok faydalı işlemi yapan metotlara sahip olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'ın standart kütüphanesindeki modüller içerisinde hem fonksiyonlar hem de sınıflar vardır. Örneğin datetime modülünün + içerisindeki date sınıfı tarih bilgisini temsil etmektedir. Biz bir date nesnesini yıl, ay, gün değerlerini vererek yaratırız. + Sonra da bu tarih bilgisi üzerinde çeşitli işlemler yaparız. Örneğin sınıfın weekday isimli metodu bizim verdiğimiz + tarihin hangi gün olduğunu bize bir sayı olarak verir (0 = Pazartesi, 1 = Salı, 2 = Çarşamba, ...). + + Biz date nesnesinin içerisindeki tarih bileşenlerini nesnenin day, month ve year özniteliklerinden istersek geri alabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +import datetime + +d = datetime.date(2022, 9, 14) + +print(d) +print(d.day, d.month, d.year) + +days = ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'] +print(days[d.weekday()]) + +#------------------------------------------------------------------------------------------------------------------------ + Şimdi karmaşık sayıyı temsil eden Complex sınıfına add ve sub metotlarını ekleyelim. +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real, imag): + self.real = real + self.imag = imag + + def disp(self): + print(f'{self.real}+{self.imag}i') + + def add(self, z): + real = self.real + z.real + imag = self.imag + z.imag + + result = Complex(real, imag) + + return result + + def sub(self, z): + real = self.real - z.real + imag = self.imag - z.imag + + result = Complex(real, imag) + + return result + +x = Complex(6, 5) +y = Complex(2, 3) + +z = x.add(y) +z.disp() + +z = x.sub(y) +z.disp() + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi aslında daha önce görmüş olduğumuz list, tuple, set, dict birer sınıftır. Zaten biz daha + önce bu sınıflar türünden nesneler yaratıp onların metotlarını çağırarak faydalı işlemler yapmıştık. Örneğin sort metodu + list sınıfının içerisindedir. Biz de bir listeyi sıraya dizmek için sort metodunu list türünden bir değişkenler çağırırız. + Örneğin: + + a = [19, 12, 8, 40, 43] + a.sort() + print(a) + +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +print(a) + +a.pop(2) +print(a) + +a.append(100) +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi bir metodun ilgili sınıf türünden bir değişkenle çağrılmasının anlamı nedir? İşte örneğin x.foo() gibi bir çağrı + foo metodunun x değişkeninin gösterdiği yerdeki nesne üzerinde (kısaca buna x nesnesi üzerinde diyebiliriz) işlem yapacağı + anlamına gelir. Yani metotlar spesifik bir nesne üzerinde işlem yaparlar. Bu nedenle bir nesne ile çağrılırlar. Yani biz + bir metodu hangi nesneyle çağırırsak o metot aslında o nesnenin elemanları yani öznitelikleri üzerinde işlem yapar. Örneğin: + + a.append(10) + + Burada append metodu a listesine eleman eklemektedir. Fakat örneğin: + + b.append(10) + + Burada append metodu b listesine eleman eklemektedir. Örneğin: + + val = d.get(10) + + Burada get metodu d isimli sözlük nesnesinin içerisindeki 10 anahtarına karşı gelen değeri bize vermektedir. Halbuki + örneğin: + + val = k.get(10) + + Burada get metodu k sözlüğünün içerisindeki 10 anahtarına karşı gelen değeri vermektedir. + + O halde metotlar "bir kavrama ilişkin bir nesne (örnek) üzerinde belli işlemleri yapan fonksiyonlardır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Daha önceden de fonksiyonlar konusunda belirttiğimiz gibi Python'da diğer bazı nesne yönelimli dillerde bulunan "function + overloading" ya da "method overloading" denilen özellik yoktur. Yani Python'da aynı isimli tek bir fonksiyon ya da metot + bulunabilir. Fakat istersek metotlarımızın parametre değişkenlerine default değerler verebiliriz. Bu sayede diğer dillerdeki + "method overloading" benzeri bir durumu oluşturmuş oluruz. Örneğin C++, Java ve C# gibi dillerde sınıfın "yapıcı fonksiyonu + (constructor)" overload edilebilmektedir. Böylece nesne farklı biçimlerde yaratılabilmektedir. Halbuki Python'da sınıf içerisinde + yalnızca bir tane __inir__ metodu bulunabilir. Farklı parametrelerle nesnenin yaratılması default argümanlarla yapılabilmektedir. + Örneğin: + + class Complex: + def __init__(self, real = 0, imag = 0): + self.real = real + self.imag = imag + + Burada biz Complex nesnesini aşağıdaki gibi farklı parametrelerle yaratabiliriz: + + x = Comple() + y = Complex(10) + z = Complex(10, 20) + +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real = 0, imag = 0): + self.real = real + self.imag = imag + + def disp(self): + print(f'{self.real}', end='') + if self.imag != 0: + print(f' + {self.imag}i') + else: + print() + +x = Complex(6, 5) +y = Complex(6) +z = Complex() + +x.disp() # 6+5i +y.disp() # 6 +z.disp() # 0 + +#------------------------------------------------------------------------------------------------------------------------ + Sınıflar "değiştirilebilir (mutable)" türlerdir. Yani bir sınıfın örnek özniteliklerini biz değiştirebiliriz. Örneğin: + + class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + s = Sample(10, 20) + + Burada s değişkeni Sample nesnesinin adresini tutmaktadır. Tabii nesnenin a ve b öznitelikleri de aslında adres tutar. + Yani nesne yaratıldığında bellekte şöyle bir durum oluşacakatır: + + s -----> Sample nesnesi + a ---> 10 + b ---> 20 + + Sınıf nesnelerinin değiştirilebilir olması demek nesnenin özniteliklerine başka nesnelerin adreslerinin atanabilmesi demektir. + Örneğin: + + s = Sample(10, 20) + s.a = 30 + + Burada artık nesnenin a özniteliği 30 değerinin adresini göstermektedir: + + s -----> Sample nesnesi + a ---> 30 + b ---> 20 + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların metotları alternatif biçimde sınıf ismi ile de çağrılabilmektedir. Ancak bu çağrı biçiminde self parametresini + açıkça argüman olarak geçirmek gerekir. Örneğin: + + class Sample: + def foo(self): + print('foo') + + def bar(self, a): + print(f'bar: {a}') + + s = Sample() + + s.foo() + s.bar(10) + + Sample.foo(s) + Sample.bar(s, 10) + + Burada örneğin s.foo() çağrısı ile Sample.foo(s) çağrısı tamamen eşdeğerdir. Ancak birinci çağrı yani s.foo() çağrısı + tercih edilmelidir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + print('foo') + + def bar(self, a): + print(f'bar: {a}') + +s = Sample() + +s.foo() +s.bar(10) + +Sample.foo(s) +Sample.bar(s, 10) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii alternatif sentaksı biz diğer built-inm sınıflarda da kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] +print(a) + +list.append(a, 10) # a.append(10) +list.append(a, 20) # a.append(20 + +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Aslında sınıflar da Python'da deyim statüsündedir. Yani yorumlayıcı bir sınıf tanımlaması ile karşılaştığında onun içerisindeki + deyimleri de çalıştırmaktadır. Örneğin: + + class Sample: + print('one') + + def foo(self): + pass + + print('two') + + def bar(self): + pass + + print('three') + + Biz bu sınıf türünden hiçbir nesne yaratmasak bile yorumlayıcı yine de sınıfın içerisndeki kodları çalıştırmaktadır. + Biz bu programı çalıştırdığımızda aşağıdaki yazıları ekranda görececeğiz: + + one + two + three + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz "bir nesnenenin özniteliği" demekle nesnenin elemanlarını kastetmekteyiz. "Nesnenin özniteliği" ile "sınıfın örnek + özniteliği" aynı anlamdadır. Nesnenin özniteliklerine İngilizce "instance attributes" denilmektedir. Ancak Pyton'da + bir de "sınıf öznitelikleri (class attribute)" denilen bir kavram vardır. Biz "sınıf özniteliği" yerine bazen viz + "sınıf değişkenleri" de diyeceğiz. Pekiyi dınıf öznitelikleri nasıl yaratılmaktadır ve nasıl kullanılmaktadır? + + Anımsanacağı gibi bir değişken tüm fonksiyonların dışında yaratılmışsa ona "global değişken" diyorduk. Bir fonksiyonun + ya da metodun içerisinde yaratılmışsa ona "yerel değişken" diyorduk. Fonksiyonların parametre parantezleri içerisindeki + değişkenlere de "parametreler" ya da "parametre değişkenleri" diyorduk. "İşte bir değişken bir sınıfın içerisinde de + yaratılabilir. Örneğin: + + x = 10 # x global bir değişken + + def foo(): + y = 20 # y yerel bir değişken + + class Sample: + z = 30 # z sınıf değişkeni (sınıfın özniteliği) + + def bar(self): + k = 40 # k yerel değişken + + s = Sample() + s.a = 10 # a örnek özniteliği + + Yukarıdaki örnekte z değişkeninin sınıf içerisinde yaratıldığını görüyorsunuz. Bu değişken sınıfın bir özniteliğidir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıfın öznitelikleri sınıfın içerisinde doğrudan ismiyle kullanılabilir. Ancak sınıfın dışından ve sınıfın metotları + içerisinden ancak sınıf ismi ve nokta operatörü ile niteliklendirilerek kullanılabilmektedir. Örneğin: + + class Sample: + x = 10 + + def foo(): + print(Sample.x) # metot içerisinden x'i doğrudan kullanamayız, sınıf ismiyle kullanmalıyız + + print(x) # doğrudan kullanılabilir + + print(Sample.x) # dışarıdan x'i doğrudan kullanamayız sınıf ismiyle kullanmalıyız + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi fonksiyon tanımlama işlemi de aslında Python'da bir deyim statüsündeydi. Yorumlayıcı bir fonksiyonun + tanımlandığını gördüğünde bir fonksiyon nesnesi yaratıyordu, fonksiyonun iç kodlarını o nesnenin içerisine yerleştiriyordu. + Sonra da bu nesnenin adresini fonksiyon ismi olan değişkene atıyordu. Örneğin: + + def foo(): + pass + + Burada aslında foo sıradan bir değişkendir. İçerisinde bir fonksiyon nesnesinin adresi vardır. Python'da (...) operatörü + "ilgili değişkenin içerisindeki adresteki fonksiyon nesnesinin içerisinde kondu çalıştır" anlamına gelmektedir. Örneğin: + + foo() + + Bu işlem aslında "foo değişkeninin içerisindeki adreste bulunan fonksiyon nesnesinin içerisindeki kodun çalıştırılacağı" + anlamına gelmektedir. Dolayısıyla anımsanacağı gibi Pythonda "fonksiyonlar birinci sınıf vatandaştır". Örneğin: + + bar = foo + + bar() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında sınıfın metotları da deyim statüsündedir. Bir fonksiyon için yapılanların aynısı metotlar için de yapılır. Bu + durumda metot isimleri aslında sınıf değişkenleri gibidir. Başka bir deyişle sınıfın özniteliği durumundadır. Örneğin: + + class Sample: + x = 10 + + def foo(self): + pass + + Burada teknik olarak x de foo da bu sınıfın birer değişkenidir. Biz sınıf değişkenlerini dışarıdan sınıf ismi ile kullanabiliyorduk. + İşte zaten metodun alternatif çağrımı bu yüzden geçerlidir. Örneğin: + + Sample.x = 20 # geçerli + + s = Sample() + Sample.foo(s) # geçerli +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + x = 10 + + def foo(self): + pass + +print(type(Sample.x)) # +print(type(Sample.foo)) # + +#------------------------------------------------------------------------------------------------------------------------ + Aslında Python'da sınıfın öznitelikleri (yani sınıf değişkenleri) dışarıdan sınıf ismiyle kullanılabildiği gibi o sınıf + türünden bir değişkenle de kullanılabilmektedir. Ancak sınıf değişkenlerinin sınıf türünden değişkenlerle kullanılması yanlış + anlaşılmalara yol açtığı için pek tavsiye edilmemektedir. Örneğin: + + class Sample: + x = 10 + + def foo(self): + pass + + s = Sample() + + s.foo() + Sample.foo(s) + + print(s.x) # geçerli ama yanlış anlaşılabilir! + print(Sample.x) # tavsiye edilen biçim + + Tabii hem nesnenin hem de sınıfın aynı isimli öznitelikleri olabilir. Bu durumda sınıf türünden değişkenlerle nokta operatörü + kullanılarak bu değişkene erişildiğinde nesnenin özniteliği anlaşılır. (Tabii nesnenin özniteliği olmasaydı sınıfın özniteliği + anlaşılacaktı.) Örneğin: + + class Sample: + def __init__(self): + self.x = 100 + + x = 10 + + def foo(self): + pass + + s = Sample() + + print(s.x) # 100 + print(Sample.x) # 10 + + Sınıf değişkenleri C++, Java ve C# gibi dillerdeki sınıfın static elemanlarına benzemektedir. Ancak o dillerde sınıfın + metotları içerisinde sınıfın static elemanları doğrudan kullanılabilmektedir. Oysa Python'da sınıf değişkenleri bir metot + içerisinde doğrudan kullanılamaz. Sınıf değişkenleri ya sınıf ismiyle ya da self parametresi yoluyla kullanılmak zorundadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfın bir metodu diğer bir metodunu yine self parametresini kullanarak çağırabilir. Örneğin: + + class Sample: + def foo(self): + print('foo') + self.bar() + + def bar(self): + print('bar') + + s = Sample() + s.foo() + + Burada sınıfın foo metodu aynı sınıfın bar metodunu çağırmaktadır. Tabii bu çağırma yine alternatif yöntemle de aşağıdaki + gibi yapılabilirdi: + + class Sample: + def foo(self): + print('foo') + Sample.bar(self) + + def bar(self): + print('bar') + +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + print('foo') + self.bar() + + def bar(self): + print('bar') + +s = Sample() +s.foo() + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf türünden bir nesnenin yaratılma ifadesi bir fonksiyon çağrısına benzemektedir. Örneğin: + + class foo: + def __init__(self): + pass + + x = foo() + + Burada aslında foo fonksiyonu çağrılmamıştır. foo sınıfı türünden bir nesne yaratılmıştır. Örneğin: + + a = list('ankara') + + Burada aslında lits fonksiyonu çağrılmamıştır. list sınıfı türünden bir nesne yaratılmıştır. Python'da bazen + anlatımı kolaylaştırmak için (...) ile kullanılabilen ifadelerin hepsine "fonksiyon" da denilebilmektedir. Yani örneğin + bir kaynakta list(...) gibi bir ifade için "list fonksiyonu" denebilmektedir. Gerçekten de Python'un Standart Kütüphanesindeki + "Built-in Funcitons" başlığı altında pek çok sınıf sanki bir fonksiyon gibi ele alınmıştır. +#------------------------------------------------------------------------------------------------------------------------ + + +#------------------------------------------------------------------------------------------------------------------------ + Python'da fonksiyonlar birinci sınıf vatandaş olduğuna göre ve metotlar da aslında birer fonksiyon olduğuna göre biz + bir metodun adresini bir değişkene nasıl atayabiliriz? Örneğin: + + class Sample: + def foo(self): + pass + + Burada foo değişkeninin adresini bar değişkenine atamak isteyelim. Bunu aşağıdaki gibi yapanayız: + + bar = foo + + Çünkü foo Sample sınıfının bir özniteliğidir (yani Sample sııfının bir sınıf değişkenidir). Mademki foo Sample sınıfının + bir değişkenidir o halde ona sınıf ismiyle erişmemiz gerekir: + + bar = Sample.foo + + Şimdi biz bar değişkeni ile çağrı yaptığımızda artık foo metodu çağrılacaktır. Tabii bu durumda self parametresi için artık + bizim argüman olarak girmemiz gerekir. Örneğin: + + s = Sample() + bar(s) + + Tabii biz foo metodunu s.bar() biçiminde çağıramayız. Çünkü s.bar() ifadesinde bar ismi önce s nesnesinin özniteliği olarak + sonra da sınıfın özniteliği olarak aranmaktadır. Başka da bir yerde aranmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + print('foo') + +bar = Sample.foo + +s = Sample() +bar(s) + +#------------------------------------------------------------------------------------------------------------------------ + Ancak bir metodun adresinin bir değişkene atanmasının alternatif bir biçimi de vardır. Bir sınıf türünden değişken ile + nokta operatörü kullanılarak bir ifade oluşturulursa bunun için yorumlayıcı "içerisinde nesneyi ve fonksiyonu tutan + (yani nesnenin ve fonksiyonun adresini tutan)" bir metot nesnesi oluşturmamktadır. Bu nesne (...) operatörü ile kullanıldığında + saklanmış olan sınıf nesnesi ile metot çağrılmaktadır. Örneğin: + + class Sample: + def foo(self): + print('foo') + + + s = Sample() + bar = s.foo + bar() + + Burada bar değişkeni bir metot nesnesini gösterir. Onun içerisinde de s ve foo birlikte bulunmaktadır. Dolayısıyla bar + çağrıldığında sanki s.foo çağrılmış gibi olmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def foo(self): + print('foo') + +s = Sample() +bar = s.foo +bar() + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte list sınıfının append metodunun adresi f değişkeninde tutulup metot çağrılmıştır. Daha sonra da + aynı işlem list nesnesi ile metot birlikte tutularak yapılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +f = list.append + +f(a, 10) +print(a) + +f = a.append +f(20) +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + 42.Ders 21/09/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların birer deyim statüsünde olduğunu daha önce belirtmiştik. Python yorumlayıcısı bir sınıf ile karşılaştığında + type isimli bir sınıf türünden nesne yaratır. Sınıfın bilgilerini bu nesnenin içerisine yerleştirir. Bu nesnenin adresini + de sınıf ismi ile belirtilen değişkene atar. Yani sınıf isimleri aslında sıradan birer değişkendir. Sınıf isimleri type + türünden bir nesnesyi gösterirler. Bu nesnenin içerisinde de sınıfın bilgileri vardır. Ancak sınıflar türünden de nesneler + yaratılabilmektedir. Sınıflar türünden yaratılan nesneler o sınıf türünden olur. Örneğin: + + class Sample: + pass + + print(type(Sample)) # + + s = Sample() + print(type(s)) # + + Burada Sample değişkeninin türü type, s değişkeninin türü ise Sample biçimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + pass + +print(type(Sample)) # + +s = Sample() +print(type(s)) # + +#------------------------------------------------------------------------------------------------------------------------ + Meta sözcüğü (Türkçeye "üst" biçiminde de çevrelimektedir) bir kavramın kendisine ilişkin kavramlar için kullanılan bir + sözcüktür. Örneğin "data" genel olarak "veri" anlamına gelmektedir. Ancak bir verinin kendisine ilişkin bilgiler içeren + verilere "meta data" denilmektedir. İşte Python'da type sınıfı gibi bir sınıfın bilgilerini tutan sınıflara da "meta sınıflar + (meta classes)" denilmektedir. type bir meta sınıftır. Aslında Python'da bir sınıfın merta sınıfı değiştirilebilmektedir. + Bu konu ileride ele alınacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz bir değişkenin türünü type isimli built-in fonksiyon ile ekrana yazdırmıştık. Örneğin: + + a = 10 + print(type(a)) + + Pekiyi type fonksiyonu aslında bize ne vermektedir? type fonksiyonu aslında bize değişkenin gösterdiği yerdeki nesneye + ilişkin sınıfın bilgilerinin bulunduğu type türünden nesneyi (yani onun adresini) vermektedir. Örneğin: + + a = 10 + t = type(a) + + Burada biz aslında "int" isimli sınıfın bilgilerinin bulunduğu type nesnesini elde etmekteyiz. Ancak onu print ile + yazdırdığımızda o nesnenin hangi sınıf türünden olduğu bilgisi yazdırılmaktadır. Örneğin: + + class Sample: + pass + + s = Sample() + t = type(s) + + Burada t aslında Sample sınıf bilgilerinin bulunduğu nesnenin adresini tutmaktadır. t'nin gösteriği nesne type sınıfı + türündendir. Ancak t print edildiğinde t'nin gösterdiği nesnenin hangi sınıfın bilgilerini tuttuğu print edilmektedir. + Başka bir deyişle burada Sample değişkeni ile t değişkeni aynı nesneyi göstermektedir: + + print(Sample is t) # True + + Mademki type fonksiyonu aslında bize değişkenin göstermiş olduğu nesnenin ilişkin olduğu sınıfın type nesnesini vermektedir, + o hhalde biz bu type nesnesini kullanarak aynı sınıf türünden nesneler yaratabiliriz. Örneğin: + + Sample Sample: + pass + + s = Sample() + t = type(s) + + k = t() + + Biz burada t() işlemi ile aslında Sample sınıfı türünden nesne yaratmış olduk. + + Pekiyi type sınıfı türünden nesnenin type'ı nedir? Yani type ismi aslında hangi sınıf türünden nesneyi göstermektedir? + İşte type ismi de aslında yine type türünden bir sınıf nesnesini göstermektedir. Dolayısıyla örneğin: + + class Sample: + pass + + t = type(Sample) # + print(t) + print(type(t)) # + +#------------------------------------------------------------------------------------------------------------------------ + Aslında biz de hiç class bildirimi yapmadan doğrudan type sınıfının __init__ metodu yoluyla bir sınıf da oluşturabiliriz. + Örneğin: + + class Sample: + pass + + Bu işlemin tamamen eşdeğeri şöyledir: + + Sample = type('Sample', (), {}) + + Aslında bir değişkenin türünü elde etmek için kullandığımız type fonksiyonu da aynı fonksiyondur. type fonksiyonun + tek parametreli özel kullanımı yeni bir type nesnesi yaratmaz bize değişkenin ilişkin olduğu sınıfın type nesnesini verir. + + Python'da meta sınıflar nispeten ileri bir konudur. Biz kursumuzda meta sınıfları uygulama konuları içerisinde ele alacağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında bir sınıf türünden nesne type sınıfının __init__ metodu yoluyla yaratılmaktadır. Şöyle ki: + + class Sample: + pass + + Burada Sample değişkeni bu sınıfın bilgilerinin tutulduğu type türünden bir nesnesinin adresini göstermektedir. Biz + Sample(...) yaptığımızda aslında type sınıfının __call__ metodu çağrılmaktadır. Sample nesnesini bu metot yaratıp + bize vermektedir. Sınıf isimleri aslında sıradan birer değişkendir. Örneğin biz bir sınıf ismini başka bir değişkene + atayabiliriz. Bu durumda ilgili sınıf türünden nesneyi bu değişkenle de yaratabiliriz. Örneğin: + + class Sample: + pass + + Mample = Sample + + s = Mample() # s = Sample() ile aynı + print(type(s)) + + Burada Mample değişkeni de Sample değişkeni de aynı type nesnesini görmektedir. Dolayısıyla Mample() ile Sample() + aynı etkiyi yaratmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Meta sınıflar (meta classes) kavramı Python'da biraz zor anlaşılan bir kavramdır. İleride bu konuya yeniden döneceğiz. + Ancak konu şöyle özetlenebilir: Biz bir sınıf tanımladığımızda aslında başka bir sınıf türünden (type sınıfı) bir nesne + yaratmış oluruz. Bu başka sıbıfa (burada type sınıfı) "meta sınıf" denilmektedir. Python'da bu default meta sınıf olan + type sınıfı değiştirilerek de nesneler yaratılabilmektedir. Bu durumda ilginç sonuçlar oluşabilmektedir. Yani Python'da + bir sınıf tanımlanması demek aslında type türünden bir sınıf nesnesi oluşturup o sınıfın bilgilerini bu nesnenin içerisine + yerleştirmek demektir. İşte biz bir sınıf tanımladığımızda type sınıfı yerine başka bir sınıf türünden de nesne yaratılmasını + sağlayabiliriz. Burada ilginç birtakım sonuçlar ortaya çıkmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + O halde bir sınıf nedir? Bir sınıf "belli bir konuda işlemler yapan metotlardan ve bu metotların ortak kullandığı verilerden + (bu veriler nesnenin öznitelikleridir) oluşan bir veri yapısıdır." Sınıflar sayesinde birbirlerinden kopuk izlenim veren + fonksiyonlar mantıksal bir bağ içerisinde bir araya getirilmiş ve algısal kolaylıklar sağlanmıştır. Prosedürel teknikte + her şey fonksiyonlarla yapılmaktadır. Elimizde sanki birbirlerinden bağımsız olan pek çok fonksiyon var gibidir. Nesne + yönelimli teknikte bir konuya ilişkin fonksiyonlar metot adı altında bir sınıfın içerisine yerleştirilir. Böylece "çok + fazla fonksiyon var" duygusu yerine "şu sınıf bunu yapar, bu sınıf şunu yapar" biçiminde algısal kolaylık sağlanmış olur. + + Tabii Python "multiparadigm" bir programlama dildir. Programcı isterse Python'ın sınıf özelliklerini hiç kullanmadan + prosedürel teknikle program yazabilir. Ya da isterse sınıflar oluşturarak programını sınıflar kullanarak da yazabilir. + Ya da her iki tekniği bir arada kullanabilir. Daha önceden de belirttiğimiz gibi Python'ın standart kütüphanesinde + hem fonksiyonlar hem de sınıflar bulunmaktadır. + + Java ve C# gibi bazı diller "pure object oriented" biçimdedir. Bu diller prosedürel tekniğin kullanılmasına izin vermemektedir. + Çünkü bu dillerde dışarıda fonksiyon yazılamamaktadır. Tüm fonksiyonlar sınıfların içerisinde bulunmak zorundadır. Bu diller + adeta nesne yönelimli tekniğin kullanılması yönünde programcıyı zorlamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında Python'da sınıflar minimalist biçimde tasarlanmıştır. Yani C++, Java ve C# gibi dillere kıyasla Python'daki sınıfların + çok detayları yoktur. Bu nedenle Pyton'daki sınıfsal özellikleri C++, Java ve C# gibi dillerle karşılaştırmamak gerekir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Built-in dir isimli fonksiyon bir sınıfın ya da bir nesnenin öznitekilklerini elde etmek için kullanılmaktadır. dir + fonksiyonu parametre olarak bir sınıof ismini (yani type türünden bir değişkeni) ya da bir sınıf türünden değişkeni + alabilmektedir. dir fonksiyonu o sınıf ismi ile ya da o sınıf türünden değişken ile kullanılabilecek bütün isimleri + string'lerden oluşan bir liste biçiminde bize vermektedir. Pytoh programcıları bir sınıfın elemanlarını hatırlamak + istediklerinde bu fonksiyonu sıkça kullanmaktadır. Örneğin: + + >>> class Sample: + ... x = 10 + ... + >>> s = Sample() + >>> s.y = 20 + >>> dir(Sample) + ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', + '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', + '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x'] + >>> dir(s) + ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', + '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', + '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y'] + + Buarada Sample sınıfının ve s nesnesinin öznitelikleri olmayan çeşitli isimler görüyorsunuz. Bu isimler aslında object + sınıfından gelmektedir. object sınıfı izleyen paragraflarda ele alınmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Ağaç, kedi, okul, öğrenci gibi sözcüklerin hepsi birer kavram belirtmektedir. Kavramlar gerçek nesneler değildir. Bir + grup nesnenin ortak özelliklerinden hareketle uydurulmuş zihinsel temsillerdir. Örneğin aslında dünyada ağaç diye bir + şey yoktur. Somut ağaçlar vardır. Ağaç bizim birbirine benzeyen bir grup nesne için uydurduğumuz bir temsildir. Ağaç + bir kavramdır ancak evimizin önündeki ağaç o kavramdan gerçek bir bir örnektir (instance). İşte NYPT'de sınıflar kavramları + belirtirler. Sınıflar türünden nesneler de gerçek nesneleri belirtirler. + + O halde bir proje nesne yönelimli olarak modellenecekse önce proje içerisindeki bütün kavramlar sınıflarla temsil edilmeli + ve sonra da bu sınıflar türünden nesneler yaratılarak örnekler oluşturulmalıdır. Nihayet bu nesneler kullanılarak da işlemler + yapılmalıdır. Örneğin bir hastane otomasyonu için gerekli kavramların bazıları şunlardır: Hastane, doktor, hasta, hastalık, + hasta odası, hemşire, ilaç. İşte bizim önce bu sınıfları yazmamız gerekir. Sonra örneğin hastanemizde 10 tane doktor varsa + Doktor sınıfından 10 tane nesne yaratırız. 5 tane hemşire için Hemşire sınıfından 5 nesne yaratırız. Programı nesnler ile + bu sınıfların metotlarını çağırarak yazarız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii bir projedeki sınıflar arasında da birtakım ilişkiler söz konusu olmaktadır. Örneğin Hastane sınıfı ile Doktor sınıfı + arasında bir ilişki vardır. Doktor ile Hasta sınıfları arasında da bir ilişki vardır. + + NYPT'de sınıflar arasındaki ilişkiler dört başlıkta ele alınmaktadır: + + 1) İçerme İlişkisi (Composition) + 2) Birleşme İlişkisi (Aggregation) + 3) Türetme İlişkisi (Inheritance) + 4) Çağrışım İlişkisi (Association) + + Tabii iki sınıf arasında hiçbir ilişki de olmayabilir. Eğer ilişki olmamasını da bir ilişki biçimi olarak ele alırsak o zaman + beşinci maddeyi "ilişki yok" biçiminde de oluşturabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf türünden bir nesne başka bir sınıf türünden bir nesnenin bir parçasını oluşturuyorsa bu iki sınıf arasında + "içerme (composition)" ilişkisi vardır. Örneğin "insan" ile "karaciğer" sınıfları arasında, "araba" ile "motor" sınıfları + arasında bu biçimde içerme ilişkisi vardır. İki sınıf arasında içerme ilişkisi olması için aşağıdaki iki özelliğin ikisinin + de sağlanması gerekir: + + 1) İçeren nesneyle içerilen nesnenin ömürleri aynı olmalıdır. + 2) İçerilen nesne tek bir nesne tarafından içerilmelidir. + + Bu durumda örneğin "hastane" sınıfı ile "doktor" sınıfı arasında içerme ilişkisi yoktur. Çünkü bunların ömürleri aynı + değildir. Ayrıca bir doktor aynı anda birden fazla hastanede de çalışabilmektedir. Ancak insan ile karaciğer arasında, + arabayla motor arasında, "saat" ile "akrep" arasında, "dünya" ile "kıtalar" arasında, "cep telefonu" ile "işlemcisi" + arasında içerme ilişkisi vardır. "Oda" ile "duvar" arasında içerme ilişkisi yoktur. Her ne kadar oda ile duvar aynı + ömre sahipse de duvar aynı zamanda yan odanın da duvarıdır. Tabii yukarıda belirttiğimiz iki özellik "tipik durumlar" + dikkate alınarak değerlendirilmelidir. İnsan öldükten sonra karaciğeri organ nakli yoluyla yaşayabilir. Yapışık + ikizler bazı organları ortak kullanıyor da olabilir. Ancak bu durumlar tipk değil istisnai durumlardır. + + UML (Unfied Modeling Language) bir projeyi nesne yönelimli olarak modellemek için kullanılan diyagramlardan oluşan bir + dildir. UML diyagralarından biri de "sınıf diyagramları (class diagrams)" denilen diyagramlardır. Burada projedeki + sınıflar ve onların arasındaki ilişkiler betimlenmektedir. UML sınıf diyagramlarında içerme ilişkisi "içeren sınıf tarafında + içi dolu bir baklavacık (diamond)" ile temsil edilmektedir. + + İçerme ilişkisine İngilizce "has a" ilişkisi de denilmektedir. İçerme ilişkisi 1'e 1 olabileceği gibi 1'e N de olabilir. + Örneğin "insan" sınıfı ile "böbrek" sınıfı arasında içerme ilişkisi vardır. Ancak bu ilişki 1'e 2'dir. Yani bir tane insan + iki böbreğe sahiptir. UML sınıf diyagramlarında bu durum da belirtilmektedir. Satranç tahtası ile tahtanın kareleri arasında + 1'e 64 olan bir içerme ilişkisi vardır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da içerme ilişkisi içeren sınıfın __init__ metodunda içerilen nesnenin yaratılarak içeren sınıfın örnek özniteliğinde + tutulması yoluyla sağlanabilir. Örneğin: + + class Araba: + def __init__(self): + self.motor = Motor() + + class Motor: + pass + + a = Araba() + + Burada Araba sınıfı türünden nesne yaratıldığında Araba sınıfının __init__ metodu çağrılacak ve Motor nesnesi de yaratılacaktır. + Böylece araba nesnesi motor nesnesine sahip olmaktadır (has a ilişkisi). Henüz görmemiş olsak da "çöp toplayıcı (garvage collector)" + mekanizma Araba nesnesini yok ettiğinde motor nesnesi de yok olacaktır. Örneğin: + + class Board: + def __init__(self): + self.squares = [[Square() for _ in range(8)] for _ in range(8)] + + class Square: + pass + + board = Board() + + Burada Board sınıfı Square sınıfını içermektedir. Ancak 1 Board nesnesi 64 tane Square nesnesini içerir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Birleşme ilişkisinde (aggregation) bir sınıf türünden nesne başka bir sınıf türünden nesneyi bünyesine katarak kullanmaktadır. + Ancak kullanılan nesne tek bir nesne tarafından kullanılmak zorunda değildir. Kullanan nesneyle kullanılan nesnenin + ömürleri de aynı olmak zorunda değildir. İşte bu durumda bu nesnelerin sınıfları arasında "birleşme" ilişkisi vardır. + Aslında çoğu zaman "içerme" ilişkisine benzeyen ancak içerme ilişkisi olmayan iişkiler "birleşme" ilişkisidir. Örneğin + "hastane" ile "doktor" sınıfları arasında, "bilgisayar" ile "fare" sınıfları arasında "oda" ile "duvar" sınıfları arasında + içerme ilişkisi yoktur, birleşme ilişkisi vardır. + + UML sınıf diyagramlarında birleşme ilişkisi "kullanan sınıf tarafında içi boş bir baklavacık (diamond)" ile temsil + edilmektedir. Yine birleşme ilişkisi 1'e 1 olabileceği gibi 1'e N olabilmektedir. Birleşme ilişkisine İngilizce "holds a" + ilişkisi de denilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da birleşme ilişkisi kullanan sınıfta kullanılan nesneyi geçici olarak tutma yoluyla sağlanabilir. Yani birleşme + ilişkisinde kullanılacak nesnenin kullanan nesneye dahil edilmesi ve çıkartılması gibi işlemler söz konusu olmaktadır. + Örneğin: + + class Doctor: + def __init__(self, name, specialty): + self.name = name + self.specialty = specialty + + class Hospital: + def __init__(self, name): + self.name = name + self.doctors = [] + + def add_doctor(self, doctor): + self.doctors.append(doctor) + + def remove_doctor(self, name): + self.doctors.remove(name) + + def another_metods(self): + print('self.doctors is using...') + + hospital1 = Hospital('Yaşam') + doctor1 = Doctor('Ali Serçe', 'Kalp Damar') + hospital1.add_doctor(doctor1) + doctor2 = Doctor('Mehmet güneş', 'Kulak Burun Boğaz') + hospital1.add_doctor(doctor2) + + hospital2 = Hospital('Gelecek') + hospital2.add_doctor(doctor1) + + Burada bir Hospital nesnesi birden fazla Doctor nesnesini kullanabildiği gibi bir Doctor nesnesi de birden fazla + Hospital nesnesi tarafından kullanılabilmektedir. Hospital nesnesinin ve Doctor nesnelerinin ömürlerinin farklı + olabildiğine dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 43. Ders 26/09/2022 Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıflar arasındaki diğer bir ilişki biçimi de "türetme" ilişkisidir. Türetme ilişkisine İngilizce "inheritance" yani + "kalıtım" da denilmektedir. Türetme mevcut bir sınıfa dokunmadan onu genişletme anlamına gelir. Burada asıl sınıfa + "taban sınıf (base class)", genişletilmiş olan sınıfa da "türemiş sınıf (derived class)" denilmektedir. Türetmede türemiş + sınıf tamamen taban sınıf gibi de işlem görür ancak fazlalıkları da vardır. UML sınıf diyagramlarında türetme ilişkisi + türemiş sınıftan taban sınıfa doğru çekilen içi boş bir okla gösterilmektedir. Biz text ekranda ok çizemedeiğimiz için + yalnızca sınıfların ismini alt alta yazacağız. Örneğin: + + A + B + + Burada B sınıfı A sınıfından türetilmiştir. Yani B sınıfı türünden bir nesne ile hem B sınıfının elemanlarını hem de A + sınıfının elemanlarını kullanabiliriz. + + Türemiş sınıftan yeniden türetme yapılabilir. Böylece bir dizi türetme söz konusu olabilir. Örneğin: + + A + B + C + + Burada C sınıfı B sınıfından, B sınıfı da A sınıfından türetilmiştir. C sınıfı türünden bir nesneyle biz hem C'nin hem + B'nin hem de A'nın elemanlarını kullanabiliriz. Bşir sınıf birden fazla sınıfın taban sınıfı durumunda olabilir. Bu gayet + normal bir durumdur. Örneğin: + + A + B C + + Burada B de C de A sınıfından türetilmiştir. B ile C arasında bir ilişki yoktur. Ancak A ikisin de taban sınıfıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Türetme işleminin en önemli faydası tekrarı engellemektir. Prosedürel teknikte tekrarın engellenmesi için tekrar eden + kodun fonksiyon biçiminde ifade edilmesi gerekir. Ancak nesne yönelimli teknikte tekrar eden metotlar ortak taban sınıflarda + bulundurularak tekrar engellenmektedir. Örneğin B sınıfında foo, bar ve tar metotları bulunuyor olsun. C sınıfında da foo, + bar ve zar metotlarının bulunduğunu kabul edelim. Burada foo ve bar ortak metotlardır ve tekrar etmektedirler. İşte biz + foo ve bar metotlarını A gibi bir taban snıfta toplayıp B'yi ve C'yi A'dan türetebiliriz. Böylece B'de yalnızca tar, + C'de de yalnızca zar bulunmuş olur. + + A (foo, bar) + B (tar) C (zar) + + Bir sınıf kütüphanesinde bir dizi türetmelerle bir türetme şeması oluşabilir. + + Bir türetme şemasında yukarıya çıktıkça genelleşme, aşağıya indikçe özelleşme olur. Yani yukarıdaki sınıflar diğer + sınıflarda olan ortak özellikleri barındırır. NYPT'de türetme ilişkisine İngilizce "is a" ilişkisi de denilmektedir. + Örneğin "işçi bir çalışandır". O zaman İşçi sınıfı Çalışan sınıfından türetilebilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıfın birden fazla sınıfa taban sınıflık yapması tamamen normal bir durumdur. Örneğin: + + A + B C + + Burada A hem B'nin hem de C'nin taban sınıfı durumundadır. Ancak bir sınıfın birden fazla taban sınıfa sahip olması durumu + özel bir durumdur. Buna NYPT'de "çoklu türetme (multiple inheritance)" denilmektedir. Doğada çoklu türetme az olmasına karşın + karşılaşılmaktadır. Örneğin: + + A B + C + + Burada C'nin iki taban sınıfı vardır. C sınıfı A ve B sınıflarından çoklu türetilmiştir. Örneğin hava taşıtlarının + ortak özellikleri bir sınıfla, deniz taşıtlarının ortak özellikleri başka bir sınıfla temsil edilmiş olsun. Hem havada + hem de denizde giden bir taşıt bu iki sınıftan çoklu türetme yapılarak oluşturulabilir. Örneğin istream sınıfı bir dosyadan + okuma yapmak için, ostream sınıfı ise bir dosyaya yazma yapmak için oluşturulmuş olsun. Dosyadan hem okuma hem de yazma + yapan bir sınıf bu iki sınıftan çoklu türetilerek oluşturulabilir. + + Bazı programlama dillerinde çoklu türetme yoktur. Örnenğin Java ve C# dillerinde bir sınıf yalnızca tek bir sınıftan + türetilebilir. Ancak C++, Python gibi bazı dillerde çoklu türetme bulunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Türetme işlemlerine bazı örnekler vermek istiyoruz: + + - Bir personel takip programında iş yerinde çalışanlar görevlerine göre sınıflarla temsil ediliyor olsun. Tüm çalışanların + ortak birtakım özellikleri vardır. Örneğin çalışan hangi görevde olursa olsun onun bir ismi, telefon numarası, sigorta bilgileri, + departmanı gibi bilgileri vardır. İşte tüm bu ortak bilgiler tepedeki Employee isimli bir taban sınıfta toplanabilir: + + Employee + + Worker SalesPerson Manager + + Executive + + Burada işçi de, satış elemanı da yönetici de bir çalışandır. Öte yandan üst düzey yönetici (Executive) de bir çeşit + yöneticidir. + + - Bir satranç programında satranç taşları farklı sınıflarla temsil edilebilir. Ancak her taşın rengi gibi üzerine oluşturduğu + kare gibi diğerleriyle ortak özellikleri de vardır. İşte biz bu ortak özellikleri Figure isimli bir sınıfta toplayabiliriz. + Taş sınıflarını da bu Figure sınıfından türetebiliriz. Örneğin: + + Figure + + Pawn Knight Bishop Rook Queen King + + - Power Point benzeri bir program yazacak olalım. Bu programda birtakım şekiller oluşturulup sonra seçilerek işlem + yapılıyor olsun. Programda çeşitli şekiller kullanılıyor olabilir. Tüm bu şekillerin ortak birtakım özellikleri vardır. + Örneğin tüm şekillerin sınır çizgi renkleri, tüm şekillerin zemin renkleri, tüm şekillerin çizgi kalınlıkları vardır. + İşte bu şekillerin ortak özellikleri Shape isimli bir sınıfta toplanmış olabilir. Biz de diğer şekil sınıflarını bu + sınıftan türeterek oluşturabiliriz. + + Shape + + RectangleShape EllipseShape LineShape + + - Pencereli programlar (GUI uygulamaları) yazmak için GUI kütüpaheneleri (ya da framework'leri) kullanılmaktadır. GUI + uygulamalarında ekrandaki bağımsız kontrol edilebilen öğelere "pencere (window)" ya da "widget" denilmektedir. Ger GUI + eleman bir penceredir. Dolayısıyla onların pencere olmasından kaynaklanan ortak özellikleri vardır. Örneğin .NET Forms + isimli GUI kütüphanesinde tüm pencerelerin ortak özellikleri Control isimli bir sınıfta topanmıştır. Bazı GUI elemanları + da birbirine benzemektedir. Yani onların da ortak birtakım özellikleri vardır. İşte GUI kütüphanelerinde sınıflar bir + türetme şeması biçiminde oluşturulmuş durumdadır. Örneğin: + + Control + .... ButtonBase ListControl ..... + Button CheckBox RadioButton ListBox ComboBox + + Görüldüğü gibi Button (push button), CheckBox ve RadioButton GUI elemanlarının ortak özellikleri ButtonBase sınıfında, + ListBox ve ComboBox sınıflarının ortak özellikleri ise ListControl sınıfında toplanmıştır. Tüm GUI elemanlarının ortak + özellikleri ise en tepedeki Control sınıfında toplanmıştır. Tabii aslında bu kütüphanede çok sayıda GUI eleman ve çok + sayıda sınıf bulunmaktadır. Biz yalnızca yukarıda bir fikir versin diye kütüphanenin küçük bir bölümünün temsilini verdik. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi bir türetme şeması ile karşılaştığımızda oradaki sınıfları nereden başlayarak öğrenmeliyiz? En makul başlangıç + yeri en tepedeki taban sınıf olabilir. Bu sınıf ne de olsa tüm türemiş sınıflardaki ortak özellikleri yansıtmaktadır. + Genelden özele giderek kütüphaneyi öğrenmek daha uygun olabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıflararası son ilişki biçimine "çağrışım ilişkisi (association)" denilmektedir. Çağrışım ilişkisinde bir sınıf nesnesi + başka bir nesneyi kullanır. Ancak bu kullanma yüzeyseldir. Bünyesine katarak bir kullanma değildir. Örneğin Taksi sınıfı + Müşteri sınıfını kullanır. Ancak Taksi ile Şoför arasındaki ilişki birleşme ilişkisiyken Taksi ile Müşteri arasındaki ilişki + çağrışım ilişkisidir. Çağrışım ilişkisi yüzeysel bir ilişkidir. Örneğin bir sınıf diğer sınıfı yalnızca bir metodunda kullanıyorsa, + bünyesine katarak kullanmıyorsa burada bir çağrışım ilişkisinden bahsedilebilir. Örneğin Hastane sınıfı reklam yapılacağı zaman + Reklam Şirketi sınıfını kullanıyor olabilir. + + Çağrışım ilişkisi UML sınıf diyagramlarında kullanan sınıftan kullanılan sınıfa çekilen ince bir ok ile temsil edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İçerme ilişkisi, birleşme ilişkisi ve çağrışım ilişkisi şimdiye kadar görmüş olduğumuz bilgilerle oluşturulabilmektedir. + Ancak türetme ilişkisi ayrı bir sentaks ile oluşturulmaktadır. Biz de şimdi Python'da türetme ilişkisinin oluşturulması + ve kullanılması üzerinde duracağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da türetme sentaksının genel biçimi şöyledir: + + class (): + pass + + Örneğin: + + class A: + pass + + class B(A): + pass + +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('A.foo') + + def bar(self): + print('A.bar') + +class B(A): + def tar(self): + print('B.tar') + +b = B() + +b.foo() +b.bar() +b.tar() + +#------------------------------------------------------------------------------------------------------------------------ + Türetme ilişkisinde türemiş sınıf taban sınıfı kullanmaktadır. Taban sınıf türemiş sınıfı kullanmamaktadır. Aşağıdaki + örnekte B ve C sınıflarının ortak elemanları A taban sınıfında toplanmış ve kod tekrarı bu sayede elimine edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('foo') + + def bar(self): + print('bar') + +class B(A): + def tar(self): + print('tar') + +class C(A): + def zar(self): + print('zar') + +b = B() +b.foo() +b.bar() +b.tar() + +c = C() +c.foo() +c.bar() +c.zar() + + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi nesnelerin öznitelikleri tipik olarak __init__ metotlarında yaratılmaktadır. Türemiş sınıf türünden + bir nesne yaratıldığında eğer türemiş sınıfın __init__ metodu yazılmışsa türemiş sınıfın __init__ metodu otomatik çağrılır. + Ancak türemiş sınıfın __init__ metodu yazılmamışsa bu durumda taban sınıfın __init__ metodu çağrılmaktadır. Türemiş sınıfın + __init__ metodunun türetmeyi yapan programcı tarafından yazılmış olduğunu varsayalım. Bu durumda türemiş sınıf türünden + bir nesne yaratıldığında türemiş sınıfın __init__ metodu çağrılacaktır. Taban sınıfın __init__ metodu çağrılmayacaktır. + Oysa taban sınıfın __init__ metodunun içerisinde taban sınıf gereken birtakım şeyler yapılmış olabilmektedir. Türemiş + sınıf türünden nesne yaratıldığında türemiş sınıfın __init__ metodunun çağrılması ancak taban sınıfın __init__ netodunun + çağrılmaması önemli sorunlara yol açabilmektedir. Örneğin: + + class A: + def __init__(self): + self.x = 10 + + def dispA(self): + print(self.x) + + class B(A): + def __init__(self): + self.y = 20 + + def dispB(self): + print(self.y) + + + a = A() + a.dispA() # sorun yok! nesnenin a özniteliği yaratılmış durumda + + b = B() + b.dispA() # dikkat! nesnenin bir a örnek özniteliği yaratılmamış! + b.dispB() + + Burada A sınıfı türünden nesne yaratıldığında A sınıfının __init__ metodu çağrılmaktadır. Bu metotta nesne içerisinde + x isimli bir özniteliğin yaratıldığını görüyorsunuz. dispA metodu da bu özniteliği kullanmaktadır. Ancak B nesnesi + yaratıldığında yalnızca B sınıfının __init__ metodu çağrılmaktadır. Ancak türemiş sınıf nesnesi taban sınıf gibi de + kullanılabildiğine göre bu B nesnesi ile A sınıfının dispA metodu çağrıldığında sorun ortaya çıkacaktır. Çünkü A sınıfının + __init__ metodu çağrılmadığı için x özniteliği nesnede yaratılmamıştır. + + Yukarıdaki sorun tam olarak neden kaynaklanmaktadır? Eğer türemiş sınıfın __init__ metodu taban sınıfın __init__ metodunu + çağırsaydı böyle bir sorun oluşmayacaktı. Çünkü bu durumda yorumlayıcı türemiş sınıfın __init__ metodunu otomatik çağıracaktı. + Türemiş sınıfın __init__ metodu da taban sınıfın __init__ metodunu çağırdığığı için sorun oluşmayacaktı. Daha önceden de + belirttiğimiz gibi Python'daki __init__ metodu C++, Java ve C# gibi dillerdeki "constructor" metotlarına karşılık gelmektyedir. + Ancak bu dillerde türemiş sınıfların "constructor" metotları taban sınıfın "constructor" metotlarını zaten otomatik çağırmaktadır. + Böylece bu dillerde yukarıdaki sorun ortaya çıkmamaktadır. Ancak Python'da böyle bir otomatik çağırma yoktur. Yani Python'da + programcı türemiş sınıfın __init__ metodu içerisinde taban sınıfın __init__metodunu kendisi açıkça çağırmalıdır. Pekiyi bu + nasıl yapılacaktır? Çağrı aşağıdaki gibi yapılamaz: + + class A: + def __init__(self): + self.a = 10 + + def dispA(self): + print(self.a) + + class B(A): + def __init__(self): + self.__init__() # dikkat metot kendi kendisini çağırıyor! + self.b = 20 + + b = B() + b.dispA() + + Burada B sınıfının __init__ metodu yine kendisini çağırmaktadır. Bir fonksiyonun ya da metodun kendisi çağırmasına + "özyineleme (recursion)" denilmektedir. Çağırmanın açıkça taban sınıf ismi belirtilerek alternatif yöntemle yapılması + gerekir: + + class A: + def __init__(self): + self.a = 10 + + def dispA(self): + print(self.a) + + class B(A): + def __init__(self): + A.__init__(self) # açıkça A'nın __init__ metodunun çağrıldığı anlaşılıyor + self.b = 20 + + b = B() + b.dispA() # geçerli, sorun yok + + Pekiyi türemiş sınıfın __init__ metodu taban sınıfın __init__ metodunu nerede çağırmalıdır? İşte en normal olan ve uygun + durum türemiş sınıfın __init__ metodunun taban sınıfın __init__ metodunu hemen türemiş sınıfın __init__ metdonun başında + çağırmasıdır. Çünkü önce nesnenin taban sınıf kısmının ilkdeğer alması normal olan durumdur. Ne de olsa türemiş sınıf + taban sınıfı kullanabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def __init__(self): + self.a = 10 + + def dispA(self): + print(self.a) + +class B(A): + def __init__(self): + A.__init__(self) + self.b = 20 + + def dispB(self): + print(self.b) + +b = B() +b.dispA() +b.dispB() + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıfın "taban sınıfları (base classes)" denildiğinde onun yukarıya doğru tüm taban sınıfları anlaşılmalıdır. Ancak + sınıfın "doğrudan taban sınıfları (direct base classes)" denildiğinde sınıfın hemen bir yukarısındaki taban sınıflar + anlaşılmalıdır. Sınıfın "dolaylı taban sınıfları (indirect base classes)" ise sınıfın doğrudan taban sınıflarının taban + sınıflarıdır. Örneğin aşağıdaki gibi bir türetme şeması olsun: + + A + B + C + D + + Burada D'nin taban sınıfları C, B ve A'dır. D'nin doğrudan taban sınıfı C'dir. D'nin dolaylı taban sınıfları A ve B'dir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da türemiş sınıfın taban sınıfn __init__ metodunu çağırması gerektiğini belirtmiştik. İşte türemiş sınıflar tüm + taban sınıfların __init__ metotlarını çağırmamalıdır. Yalnızca doğrudan taban sınıflarının __init__ metotlarını çağırmalıdır. + Zaten her sınıf kendi doğrudan taban sınıflarının __init__ metotlarını çağırınca tüm taban sınıfların __init metotları + çağrılmış olur. Örneğin: + + class A: + def __init__(self): + pass + + class B(A): + def __init__(self): + A.__init__(self) + pass + + class C(B): + def __init__(self): + B.__init__(self) + pass + + c = C() + + Burada C sınıfı türünden bir nesne yaratıldığında aslında A, B, C sırasına göre sınıfların __init__ metotları çağrılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Taban sınıfla türemiş sınıf aynı isimli metotlara sahip olabilir. Bu durumda bu metotlar hangi sınıf türünden nesneyle + çağrılmışsa o sınıfın aynı isimli metodu çağrılmış olur. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + + b = B() + b.foo() # b nesnesi B sınıfı türündne olduğu için B.foo çağrılır + + a = A() + a.foo() # A.foo çağrılır, zaten taban sınıf türemiş sınıfa erişemez! + + Eğer biz türemiş sınıf türünden bir değişkenle türemiş sınıfın değil de taban sınıfın aynı isimli metodunu çağırmak istersek + alternatif çağırma biçimini kullanmalıyız. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + + b = B() + + b.foo() # türemiş sınıftaki foo metodu çağrılır + A.foo(b) # taban sınıftaki foo metodu çağrılır + + Taban sınıftaki bir metodun türemiş sınıfta aynı isimle yeniden yazılmasına NYPT'de "taban sınıftaki metodun override edilmesi" + denilmektedir. Override terimi pek çok dilde "çokbiçimli (polymorphic)" mekanizma için kullanılmaktadır. + + Bazen programcı kasten taban sınıftaki metot ile aynı isimli metodu türemiş sınıfta yazmaktadır. Böylece türemiş sınıf + türünden bir dğeişkenle bu metot çağrıldığında türemiş sınıfın metodu çağrılmaktadır. İşte türemiş sınıfın metodu içerisinde + de programcı tana sınıfın aynı isimli metodunu çağırabilmektedir. Böylece asıl işlevselliği taban sınıftaki metot yapar. Ancak + programcı bunu türemiş sınıfta genişletimiş olur. Buna NYPT'te de "augmentation (artırma)" denilmektedir. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print("B.foo araya girerek A.foo'ya eklemeler yapıyor") + A.foo(self) + + b = B() + + b.foo() +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirtildiği gibi Python'da çoklu türetme vardır. Ancak Java ve C# gibi bazı dillerde çok türetme yapılamamaktadır. + Fakat örneğin C++'ta ve Object Pascal'da da çoklu türetme bulunmaktadır. Çoklu türetmede türemiş sınıf türünden bir değişkenle + hem türemiş sınıf metotları hem de onun tüm taban sınıf metotları çağrılabilmektedir. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B: + def bar(self): + print('B.bar') + + class C(A, B): + def tar(self): + print('C.tar') + + c = C() + + c.tar() + c.bar() + c.foo() + + Burada C sınıfı türünden bir değişkenle hem C'nin hem A'nın hem de B'nin metotları çağrılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('A.foo') + +class B: + def bar(self): + print('B.bar') + +class C(A, B): + def tar(self): + print('C.tar') + +c = C() + +c.foo() +c.bar() +c.tar() + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi türemiş sınıfın __init__ metodunun kendi doğrudan taban sınıflarının __init__ metotlarını çağırması + gerekiyordu. Çoklu türetmede türemiş sınıf bütün doğrudan taban sınıflarının __init__ metotlarını çağırmalıdır. Örneğin: + + class A: + def __init__(self): + self.x = 10 + print('A.__init__') + + def dispA(self): + print(self.x) + + class B: + def __init__(self): + self.y = 20 + print('B.__init__') + + def dispB(self): + print(self.y) + + class C(A, B): + def __init__(self): + A.__init__(self) + B.__init__(self) + self.c = 30 + print('C.__init__') + + def dispC(self): + print(self.c) + + c = C() + + c.dispA() + c.dispB() + c.dispC() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Çoklu türetmede isim araması hangi sıraya göre yapılmaktadır. Örneğin: + + A B + C + + Burada C sınıfının A ve B'den çoklu türetildiğini düşünelim. A ve B sınıflarında foo metodu olsun ancak C sınıfında foo + metodu olmasın. C sınıfı türünden bir dğeişkenle foo metodu çağrıldığında hangi foo metodu çağrılacaktır. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B: + def foo(self): + print('B.foo') + + class C(A, B): + pass + + c = C() + + c.foo() # A.foo çağrılacaktır + + Python'da genel olarak bu tür durumlarda çoklu türetmede belirtilen sıra önemli olmaktadır. Yani yorumlayıcı önce ilk + belirtilen tana sınıf kolundan yukarıya doğru ilerlemekte eğer metot bulunamazsa sonra sırasıyla diğer taban sınıf kollarından + yukarıya doğru ilerlemektedir. Yukarıdaki örnekte A.foo metodu çağrılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Ancak bazen türetme şeması oldukça karışık olabilmektedir. Özellikle baklava (dimaond) tarzı türetmeler kişilerin + kafalarını karıştırabilmektedir. Baklava tarzı türetme iki koldan ilerlenip birleşerek devam eden türetme şemalarına + denilmektedir. Örneğin: + + A + B C + D + E + + Bu tür karmaşık türetme şemaalarında metotların aranma sırasına Python'da MRO (Member Resolution Order) denilmektedir. + MRO konusu izleten paragraflarda ele alınacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki gibi bir türetme şeması olsun. Bu şemadaki bazı sınıflarda foo metodu bulunuyopr olsun: + + A (foo) B (foo) + C E (foo) + D + + Burada D sınıfı türünden bir değişkenle foo metodunun çağrıldığını düşünelim: + + d = D() + d.foo() + + Hangi sınıfın foo metodu çağrılacaktır? Daha önce de belirttiğimiz gibi kaba kural taban sınıf kollarından sırasıyla + aşağıdan yukarıya doğru arama yapılmasıdır. Yani arama D, C, A, E, B sırasına göre yapılacaktır. Başka bir deyişle D + sınıfı için MRO sırası bu biçimdedir. Örneğimizde foo metodunun ilk bulunduğu sınıf A'dır. Bu nedenle A sınıfının foo + metodu çağrılacaktır. Bu örnekte D sınıfında taban sınıflar önce E sonra C biçiminde belirtilmiş olsaydı E sınıfının + foo metodu çağrılırdı. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('A.foo') + +class B: + def foo(self): + print('B.foo') + +class C(A): + pass + +class E(B): + def foo(self): + print('E.foo') + +class D(C, E): + pass + +d = D() +d.foo() + +#------------------------------------------------------------------------------------------------------------------------ + Çoklu türetmede baklava (diamond) biçiminde türetme şemasında her sınıfın kendi doğrudan taban sınıflarının __init__ + metotlarını çağırması önemli bir soruna yol açmaktadır. Aşağıda baklava biçiminde bir türetme şeması görüyorsunuz: + + A + B C + D + + Burada her sınıfın __init__ metodu kendi doğrudan taban sınıfının __init__ metodunu çağırdığında A sınıfının __init__ + metodu iki kez çağrılmış olur. Bir sınıfın __init__ metodunun iki kez çağrılması bazen sorunlarlara yol açmayabilir + ancak bazen sorunlara yol açabilir. Bunun bir biçimde engellenmesi gerekir. C++'ta bu problem "virtual base class" + denilen bir kavramla çözülmeye çalışılmıştır. Python'da bu problemin nasıl çözlüdüğü izleyen paragraflarda ele alınmaktadır. + + Aşağıda baklava biçiminde türetme uygulanıp her sınıfın kendi doğrudan taban sınıflarını çağırması ile tepedeki + taban sınıfının __init__ metodunun iki kez çağrılmasına örnek verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def __init__(self): + print('A.__init__') + +class B(A): + def __init__(self): + A.__init__(self) + print('B.__init__') + +class C(A): + def __init__(self): + A.__init__(self) + print('C.__init__') + +class D(B, C): + def __init__(self): + B.__init__(self) + C.__init__(self) + print('D.__init__') + +d = D() + +#------------------------------------------------------------------------------------------------------------------------ + 44. Ders 28/09/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi türetme durumlarında taban sınıflarda aynı isimli metotlar varsa isim araması kabaca + aşağıdan yukarıya doğru yapılmaktadır. Ancak çoklu türetme söz konusu olduğunda taban sınıfların belirtilme sırasına + göre kollarda aşağıdan yukarıya doğru arama yapılmaktadır. Örneğin: + + A B + C + D + + Böyle bir türetme şemasında aşağıdaki gibi bir foo metodu çağrılmış olsun: + + d = D() + + d.foo() + + Burada önce D sınıfına bakılacaktır. Çünkü metot çağrılmasında kullanılan nesne D sınıfı türündendir. Pekiyi D'de foo yoksa + ne olacaktır? Bu durumda C'ye bakılacaktır? Pekiyi ya C'de yoksa ne olacaktır? Önce sol koldan yukarıya doğru sonra sağ koldan + yukarıya doğru arama yapılacaktır. Yani D sınıfı için buradaki MRO sırası D, C, A, B biçimindedir. Örneğin: + + + A + B C + D + E + + E sınıfı için buradaki MRO sırası E, D, B, A, C, object biçimindedir. (object sınıfı izleyen paragraflarda ele alınacaktır.) + + Bir sınıfın MRO sırasını elde etmek type sınıfının __mro__ örnek özniteliği kullanılmaktadır. (Sınıf isimlerinin aslında + type sınıfı türünden birer nesne belirttiğine dikkat edniz.) Örneğin yukarıdaki şemada E sınıfının MRO sırası E.__mro__ ifadesiyle + elde edilebilmektedir. type sınıfının __mro__ örnek özniteliği aramaların yapılacağı sınıflara ilişkin type nesnelerinden + oluşan bir demet vermektedir. Karmaşık türetmelerde isimlerin arama sırası için __mro__ özniteliğine başvurabilirsiniz. + + Ayrıca type sınıfının mro isimli bir örnek metodu da vardır. Aslında type nesnesi yaratılırken bu metot çağrılıp buradan + elde edilen değer __mro__ değişkenine atanmaktadır. Bu durumda örneğin biz E.__mro__ yerine E.mro() çağrısını da uygulayabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + pass + +class B(A): + pass + +class C: + pass + +class D(B, C): + pass + +class E(D): + pass + +print(E.__mro__) + +#------------------------------------------------------------------------------------------------------------------------ + Ayrıca MRO sırası inspect modülündeki getmro fonksiyonuyla da elde edilebilmektedir. Bu fonksiyona biz bir sınıf ismini + (yani tytpe nesnesini veririz). Örneğin: + + import inspect + + t = inspect.getmro(E) + print(t) + + Tabii getmro fonksiyonu şöyle yazılmıştır: + + def getmro(cls): + return cls.__mro__ + + Genel olarak MRO sırası şu biçimdedir: Eğer çoklu türetme yoksa aşağıdan yukarıya doğrudur. Çoklu türemte varsa soldan sağa taban sınıflarda + bir kol bitirildikten sonra diğerine geçilecek biçimde bir sıra izlenir. Örneğin: + + A + B D + C E + F + G + + Burada G'nin MRO sırası şöyle olacaktır: G, F, C, B, A, E, D, object (object sınıfı izleyen paragraflarda ele alınacaktır.) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi MRO sırası isim aramasında etkili olmaktadır. Bir sınıf türünden değişkenle ya da sınıf ismi + kullanılarak nokta operatörü ile bir eleman belirtilirse bu elemanın sırasıyla hangi sınıflarda aranacağına MRO sırası + denilmektedir. Tabii isim bulunursa arama devam etmez. Örneğin: + + A + B C + D + E + + e = E() + e.foo() + + Burada foo'nun yalnızca A ve C sınıflarında bulunduğunu varsayalım. Çağrılan foo hangi foo'dur? İşte bu durum E'nin MRO + sırası ile ilgilidir. E'nin MRO sırası ise şöyledir: E, D, B, A, C, object. (object sınıfı izleyen paragraflarda ele alınacaktır.) + Bu şu anlama gelmektedir: "İsim önce E'de aranır, bulamazsa D'de aranır, bulamazsa B'de aranır, bulunamazsa A'da aranır, + orada da bulunamazsa C'de aranır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Baklava biçimindeki türetme şemalarında tepedeki taban sınıf ortak olduğu için durum biraz ilginç hale gelmektedir. Örneğin: + + A + B C + D + + Normal olarak buradaki D sınıfının MRO sırasının sanki D, B, A, C, A gibi olması gerektiğini düşünebilirsiniz. Amcak MRO + sırasında her zaman bir sınıftan bir tane bulunmaktadır. Burada D sınıfının MRO sırası şöyledir: D, B, C, A, object. + +#------------------------------------------------------------------------------------------------------------------------ + +class A: + pass + +class B(A): + pass + +class C(A): + pass + +class D(B, C): + pass + +print(D.__mro__) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da "her şey bir çeşit nesne" olduğuna göre tüm sınıflar aslında doğrudan ya da dolaylı olarak object isimli bir + sınıftan türetilmiş durumdadır. Biz bir sınıfta taban sınıf belirtmezsek Python yorumlayıcısı onun object sınıftan + türetildiğini varsaymaktadır. Örneğin: + + class Sample: + pass + + Burada Sample sınıfı object sınıfından türetilmiştir. Tabii biz buna açıkça da belirtebilirdik: + + class Sample(object): + pass + + Örneğin: + + class A: + pass + + class B(A): + pass + + Burada B sınıfı A sınıfından A sınıfı da object sınıfından türetilmiştir. İşte böyle yukarıya çıktığımızda her zaman + tepede object sınıfıyla karşılaşırız. Yani "her sınıf doğrudan ya da dolaylı olarak object sınıfından türetilmiş" durumdadır. + + Pekiyi object sınıfının içerisinde neler vardır? İşte object sınıfının içerisinde her nesne için geçerli olabilecek metotlar + bulunmaktadır. + + Biz çizimlerde ve gösterimlerde kolaylık sağlamak amacıyla türetme durumlarında en tepedeki object sınıfını göstermedik + ve göstermeyeceğiz. Ancak aslında türetme şemasının en tepeseinde bu object sınıfının bulunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıfın doğrudan taban sınıfları bir demet halinde type sınıfının __bases__ örnek özniteliği ile elde edilebilir. + Yani biz bir sınıf ismi ile __bases__ özniteliğini kullanırsak o sınıfın doğrudan taban sınıflarını elde ederiz. Örneğin: + + >>> class A: + ... pass + ... + >>> class B(A): + ... pass + ... + >>> B.__bases__ + (,) + >>> class C: + ... pass + ... + >>> class D(C, B): + ... pass + ... + >>> D.__bases__ + (, ) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Daha önceden de belirttiğimiz gibi NYPT'de taban sınıftaki bir metodun türemiş sınıfta aynı isimle yeniden yazılmasına + "taban sınıftaki metodun türemiş sınıfta override edilmesi" denilmektedir. Override etme durumunda MRO sırasına göre + türemiş sınıf türünden bir değişken ile aynı isimli metot çağrıldığında türemiş sınıfın metodu çağrılır. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + + b = B() + b.foo() # B.foo çağrılır + + Bu örnekte biz b değişkeni ile A'daki foo metodunu çağırmak istersek alternatif çağrım sentaksını kullanabiliriz. Örneğin: + + A.foo(b) + + NYPT'de taban sınıftaki metodu override eden programcı o metoda birtakım eklemeler yapmak isteyebilir. Bu nedenle + bazı eklemeler sonrasında taban sınıftaki metodu da çağırmak ister. Daha önceden de bunun self parametresi ile yapılamayacağını + görmüştük: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + self.foo() # dikkat! özyineleme + + Burada B'deki foo metodu self.foo() çağrısı ile A'daki foo metodunu çağırmamaktadır. Kendi kendini çağırmaktadır. + O halde burada da alternatif çağrının kullanılması gerekir: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + A.foo(self) + + Biz bu durumu daha önce incelemiştik. Türemiş sınıfın __init__ metodunun taban sınıfın __init__ metodunu çağırmak zorunda + olduğunu anımsayınız. Örneğin: + + class A: + def __init__(self): + self.x = 10 + + class B(A): + def __init__(self): + A.__init__(self) + self.y = 20 + + b = B() + print(b.x, b.y) + + Baklava tarzı türetmelerdeki önemli problemden de bahsetmiştik: + + A + B C + D + + Burada B sınıfını yazan kişi mecburen B sınıfının __init__ metodu içerisinde A sınıfının __init__ metodunu çağıracaktır. + Aynı durum C sınıfını yazan kişi için de geçerlidir. D sınıfını yazan kişi de mecburen B ve C sınıflarının __init__ + metodunu çağıracaktır. Bu durumda A sınıfının __init__ metodu iki kere çağrılmış olur bu da programın çökmesine ya da + yanlış çalışmasına yol açabilir. Aşağıdkai örnekle bunu yendien test ediniz: + + class A: + def __init__(self): + print('A.__init__') + + class B(A): + def __init__(self): + A.__init__(self) + print('B.__init__') + + class C(A): + def __init__(self): + A.__init__(self) + print('C.__init__') + + class D(B, C) : + def __init__(self): + B.__init__(self) + C.__init__(self) + + d = D() + + Burada ekrana şunlar çıkacaktır: + + A.__init__ + B.__init__ + A.__init__ + C.__init__ + + İşte bu tür baklava biçiminde yapılan türetmelerde tepedeki taban sınıfın __init__ metodunun iki kere çağrılmasını + engellemek için super isimli bir fonksiyon bulundurulmuştur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + super fonksiyon built-in bir fonksiyondur. İki kullanım biçimi vardır: + + super(sınıf_ismi, değişken_ismi) + super() + + Yani super fonksiyonunu biz iki argümanla ya da argümansız çağırabiliriz. İki argümanla çağrım genel bir kullanım sunmaktadır. + super fonksiyonunun birinci parametresi bir sınıf ismini (yani bir sınıfın type referansını), ikinci parametresi ise bir sınıf + türünden değişkeni alır. super fonksiyonu bir "proxy" nesne geri döndürmektedir. super fonksiyonun geri dönüş değeri ile bir + metot çağrılırsa sanki ikinci argümanla belirtilmiş değişkenle o metot çağrılmış gibi bir etki oluşmaktadır Örneğin: + + d = D() + super(D, d).foo() + + Burada aslında oluşan etki d.foo() gibi bir etkidir. Ancak bu etki oluşturulurken isim araması d değişkenin ilşkin olduğu + sınıfın MRO sırasında D sınıfından sonraki sınıftan başlatılmaktadır. Örneğin aşağıdaki gibi bir türetme şeması olsun: + + A + B + C + D + + D sınıfı türünden bir nesne yaratılım: + + d = D() + + Burada d değişkeninin ilişkin olduğiu sınıfın MRO sırası şöyledir: D, B, C, A, object. Şimdi şöyle bir çağrı yapmış olalım: + + super(C, d).foo() + + Burada aslında d.foo() çağrısı yapılmış gibidir. Ancak foo metodunun aranması B sınıfından başlatılacaktır. Çünkü d + değişkeninin işişkin olduğu sınıf D sınıfıdır. D'nin MRO sırası D, C, B, A biçimindedir. Bu sırada C'den sonraki sınıf + B sınıfıdır. O halde arama B'den bşlatılacaktır. Yani eğer B'nin foo metodu varsa B'ninki yoksa A'nınki çağrılacaktır. + A'da da yoksa exception oluşacaktır. + + Aşağıdaki örnekte B sınıfında foo olmadığı için A sınıfındaki foo çağrılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('A.foo') + +class B(A): + pass + +class C(B): + def foo(self): + print('C.foo') + +class D(C): + def foo(self): + print('D.foo') + +d = D() + +super(B, d).foo() # A.foo çağrılır + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi super fonksiyonu ne işe yaramaktadır? Kabaca elimizde bir sınıf türünden bir değişken varsa biz de bu değişkenle + kendi sınıfımızın değil taban sınıfın metodunu çağırmak istiyorsak super fonksiyonundan faydalanabiliriz. Örneğin: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + + b = B() + + Şimdi biz b.foo() biçiminde bir çağrı yaparsak B sınıfının foo metodu çağrılır. Pekiyi biz B sınıfını bypass ederek A + sınıfının foo metodunu nasıl çağırmalıyız. Yöntemlerden biri şöyle olabilir: + + A.foo(b) + + Burada açıkça A sınıfının foo metodu b ile çağrılmıştır. Aynı şeyi super fonksiyonu ile de yapabilirdik: + + super(B, b).foo() + + super fonksiyonun birinci parametresi isim aramasının başlatılacağı sınıf üzerinde etkili olmaktadır. Şöyle ki: İsim araması + b değişkeninin ilişkin olduğu sınıfın MRO sırasında B sınıfından sonraki sınıflan yapılır. super fonksiyonun ikinci + parametresinin çağrılacak metotta kullanılacak nesneyi ve aynı zamanda da söz konusu MRO sırasını ilişkin olduğu Sınıfı + belirttiğine dikkat ediniz. Şimdi C sınıfı B'den, B sınıfı da A'dan türetilmiş olsun: + + A + B + C + + Bu sınıfların foo metotların olduğunu varsayalım: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + + class C(B): + def foo(self): + print('C.foo') + + c = C() + + Burada c değişkeni ile A'nın foo'su şöyle çağrılabilir: + + super(B, c).foo() + + Burada c değişkeni C sınıfı türündendir. C'nin MRO sırası C, B, A biçimindedir. Bu durumda super(B, c).foo() çağrısında C'nin + MRO sırasında B'den sonraki sınıfı A'dır. + +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('A.foo') + +class B(A): + def foo(self): + print('B.foo') + +class C(B): + def foo(self): + print('C.foo') + +c = C() +c.foo() # C.foo +super(C, c).foo() # B.foo +super(B, c).foo() # A.foo + +#------------------------------------------------------------------------------------------------------------------------ + super fonksiyonu özellikle türemiş sınıf metodunda taban sınıfın aynı isimli metodunu çağırmak için kullanılmaktadır. + Örneğin aşağıdaki gibi bir türetme şeması olsum: + + A + B + C + D + + Bu sınıfların şöyle yazıldığını varsayalım: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + super(B, self).foo() + + class C(B): + def foo(self): + print('C.foo') + super(C, self).foo() + + class D(C): + def foo(self): + print('D.foo') + super(D, self).foo() + + Burada her foo metodu bir üst sınıftaki foo metodunu çağırmaktadır. Çağrının şöyle başlatıldığını varsayalım: + + d = D() + d.foo() + + Burada D sınıfının foo metodu çağrılacaktır. D sınıfındaki foo metodunda super(D, self).foo() çağırısı yapılmıştır. + Buradaki self D sınıfı türündendir. D sınııfnın MRO sırası D, C, B, A biçimindedir. O halde D sınıfındaki self parametresi + aslında D sınıfı türündendir. D sınının MRO sırasında D sınıfından sonraki sınıf C'dir. Bu durumda sanki d nesnesi ile + C'nin foo metodu çağrılıyor gibi bir etki oluşturmuştur. Aynı durum diğer fo metotları için de benzer biçimdedir. + Akrada şunlar görülecektir: + + D.foo + C.foo + B.foo + A.foo +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def foo(self): + print('A.foo') + +class B(A): + def foo(self): + print('B.foo') + super(B, self).foo() + +class C(B): + def foo(self): + print('C.foo') + super(C, self).foo() + +class D(C): + def foo(self): + print('D.foo') + super(D, self).foo() + +d = D() +d.foo() + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi mademki aslında taban sınıf ismini belirterek taban sınıfın metodunu çağırabiliyoruz bu durumda super fonksiyonuna ne + gerek vardır? Yani yukarıdaki örneği super fonksiyonunu çağırmadan da aşağıdaki gibi yazabilirdik: + + class A: + def foo(self): + print('A.foo') + + class B(A): + def foo(self): + print('B.foo') + A.foo(self) + + class C(B): + def foo(self): + print('C.foo') + B.foo(self) + + class D(C): + def foo(self): + print('D.foo') + C.foo(self) + + d = D() + d.foo() + + İşte bu biçimde taban sınıf ismi ile çağrı yapıldığında MRO sırası dikkate alınmamaktadır. super fonksiyonu MRO sırasını + dikkate aldığı için önemli bir işleve sahiptir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi aslında super fonksiyonun en önemli işlevi baklava şeklindeki çoklu türetme durumunda + baklavanın tepesindeki taban sınıfın __init__ metodunun birden fazla kez çağrılmasını engellemek içindir. Anımsanacağı + gibi super(X, r).foo() gibi bir çağırmada foo metodunun aranması r değişkeninin ilişkin olduğu sınıfın MRO sırasında + X sınıfından sonraki sınıftan başlatılmaktadır. O halde örneğin: + + class X(Y): + def __init__(self): + super(X, self).__init__() + + Burada self'in ilişkin olduğu sınıfın MRO sırasında X'ten sonraki sınıfan başlanarak __init__ metodu aranacaktır. + + O halde baklava biçimindeki türetme şemasında "eğer her sınıfın __init__ metodunda super fonksiyonunun birinci parametresi + kendi sınıf ismi, ikinci parametresi self olacak biçimde __init__ metodu çağrılırsa bu durumda baklava biçiminde türetme + olsa bile her sınıfın __init__ metodu toplamda bir kez çağrılmış olacaktır. Örneğin: + + + + class A: + def __init__(self): + super(A, self).__init__ + print('A.__init__') + + class B(A): + def __init__(self): + super(B, self).__init__() + print('B.__init__') + + class C(A): + def __init__(self): + super(C, self).__init__() + print('C.__init__') + + class D(B, C): + def __init__(self): + super(D, self).__init__() + print('D.__init__') + + d = D() + + Burada D'nin MRO sırtası D, B, C, A biçimindedir. O halde D() biçiminde nesne yaratıldığında D'nin __init__ metodundaki self + D sınıfı türündendir: + + class D(B, C): + def __init__(self): + super(D, self).__init__() # D'nin MRO sırasında D'den sonraki sınıf B'dir, o halde B sınıfın __init__ metodu çağrılır + print('D.__init__') + + Burada super(d, self).__init__ ile self D sınıfı türünden olduğu için ve D'nin MRO sırasında D'den sonra gelen sınıf B olduğu + için B'nin__init__ metodu çağrılacaktır B'nin __init__ metodu ise şöyledir: + + class B(A): + def __init__(self): + super(B, self).__init__() # self hala D sınıfı türünden D'nin MRO sırasında göre B'den sonraki sınıf C'dir + print('B.__init__') + + Burada da self yine D sınıfı türündendir. D'nin MRO sırasına göre B'den sonraki sınıf C olduğuna göre aslında + super(B, self).__init__ çağrısı ile C sınıfının __init__ metodu çağırılır. C sınıfının __init__ metodu şöyledir: + + class C(A): + def __init__(self): + super(C, self).__init__() # self hala D türünden, D'nin MRO sırasına göre C'den sonraki sınıf A'dır + print('C.__init__') + + Burada da self hala D türündendir. D'nin MRO sırasına göre C'den sonraki sınıf A olduğu için aslında A'nın __init__ + metodu çağrılacaktır. Ekranda şunlar görülecektir: + + A.__init__ + C.__init__ + B.__init__ + D.__init__ + + Biz bir sınıf yazarken bizden çoklu türetme yapılıp yapılmayacağını bilmedeiğimize göre taban sınıf ismi ile değil + super fonksiyonu ile sıradaki sınıfın __init__ metodunu çağırmalıyız. İyi teknik __init__ metodu çağrılırken taban sınıf + isminin değil super fonksiyonunun kullanılmasıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi biz her zaman taban sınıfın __init__ metodunu taban sınıf ismiyle değil super fonksiyonuyla + çağırmalıyız. Bu durumda baklava biçiminde bir çoklu türetme olsa bile her zaman taban sınıfların __init__ metotları bir + kez çağrılmış olur. Her sınıfın __init__ metodunda taban sınıfın __init__ metodunu super fonksiyonu ile şöyle çağırmalıyız: + + super(kendi_sınıfımızın_ismi, self).__init__(...) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi türetme şemasının en yukarısında her zaman object sınıfı olduğuna göre biz object sınıfının da __init__ metodunu + çağırmalı mıyız? Örneğin: + + class Sample: + def __init__(self): + super(Sample, self).__init__() + # ... + + object sınıfın __init__ metodunun içinib boş olduğunu varsayabilirsiniz. Bu durumda programcının object sınıfının + __init__ metodunu çağırması gerekmez. Ancak çğırmasında de bir sakınca yoktur. + + Anımsacağı gibi biz bir sınıfı başka bir sınıftan türetmiş olmasak bile onun object sınıfından türüetilmiş olduğu varsayılıyordu. + Bu durumda eğer biz sınıfımız için __init__ metodunu yazmamışsak object sınıfındaki __init__ metodu çalıştırlacaktır. Örneğin: + + class Sample: + pass + + s = Sample() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz yukarıda genel olarak türemiş sınıfın __init__ metodunun taban sınıfın __init__ metodunu super fonksiyonuyla çağırması + gerektiğini belirtmiştik. Sonra da taban object sınıfı için __init__ metodunun çağrılmasına gerek olmadığını da ifade + etmiştik. Pekiyi bu durumda aşağıdaki gibi bir çoklu türetmede ne olacaktır: + + A B + C + + Burada C'nin MRO sırası C, A, B biçimindedir. Eğer A'nın object sınıfından türetilmiş olduğu fikriyle A'nın __init__ + metodunda super çağrısı yapılmazsa B'nin __init__ metodu çağrılmayacaktır. O zaman A sınıfını oluşturan kişi object + sınıfının __init__ metodunu super fonksiyonu ile çağırmalı mıdır? Örneğin: + + class A: + def __init__(self): + print('A.__init__') + + class B: + def __init__(self): + print('B.__init__') + + class C(B, A): + def __init__(self): + super(C, self).__init__() + print('C.__init__') + + c = C() + + Burada C'nin MRO sırası C, A, B olduğu için C ve A'nin __init__ metotları çağrılacaktır ancak B'ninki çağrılmayacaktır. + Genellikle object sınıfından türetme yapıldığında (default durum) programcılar super çağrısıyla __init__ metotlarını + çağırmazlar. Bu durumda da çoklu türetmede sorunlar çıkabilir. Pekiyi çözüm nedir? + + - Aslında bir sınıfı yazan kişi o sınıfın çoklu türetmede kullanılıp kullanılmayacağını belirleyip bunu dokümantasyonda + belirtmelidir. Yani sınıfı yazan kişinin sınıfın çoklu türetmede kullanılıp kullanılmayacağını baştan öngörebilmesi gerekir. + Eğer programcının sınıfı çoklu türetmeyi destekleyecekse sınıf object sınıfından türetilmiş olsa bile super çağrısıyla MRO sırasına + göre sıradaki sınıfın __init__ metodunu çağırmalıdır. Eğer sınıfı çoklu türetmeyi desteklemiyorsa bu durumda böyle bir çağrı + yapmasına gerek yoktur. Örneğin: + + class A: + def __init__(self): + super(A, self).__init__() + print('A.__init__') + + class B: + def __init__(self): + super(B, self).__init__() + print('B.__init__') + + class C(A, B): + def __init__(self): + super(C, self).__init__() + print('C.__init__') + + c = C() + + - Çoklu türetme yapacak kişi taban sınıflarının çoklu türetmeyi destekleyip desteklemediğine dikkat etmelidir. Eğer taban + sınıfları çoklu türetmeyi desteklemiyorsa taban sınıfın __init__ metodunu super çağrısıyla değil isimsel olarak yapabilir. + Tabii bu durumda baklava biçiminde türetme yapılmışsa yine sorun oluşabilecektir. Örneğin: + + class A: + def __init__(self): + print('A.__init__') + + class B: + def __init__(self): + print('B.__init__') + + class C(A, B): + def __init__(self): + A.__init__(self) + B.__init__(self) + print('C.__init__') + + c = C() + + Eğer sınıfın dokümantasyonunda özellikle çoklu türetme desteği için bir şey söylenmemişse sınıfın çoklu türetmeyi + desteklemediği sonucu çıkartılmalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 45. Ders 03/10/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Çoklu türetmede diğer bir sorun da çoklu türetilmiş sınıfın __init__ metotlarının MRO sırasına göre diğer sınıfların __init__ + metotlarına iletilmesidir. Burada genel uygulama çoklu türetmeyi destekleyen sınıfların __init__ metotlarının *args ve **kwargs + parametrelerini bulundurmaları ve bu parametreleri *'lı bir biçimde super fonksiyonunda kullanmalarıdır. Böylece aktarım + başarıyla yapılabilmektedir. + + Aşağıdaki örnekte C sınıfı A ve B'den çoklu türetilmiştir. C sınıfının __init__ metodu kendisi için değeri alıp MRO + sırasına göre sonraki sınıfın __init__ metodunu çağırmıştır. Buradaki * ve ** parametreleri ve argümanları pass etme + (forwarding) amacıyla kullanılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def __init__(self, a, *args, **kwargs): + super(A, self).__init__(*args, **kwargs) + self.a = a + print('A.__init__') + +class B: + def __init__(self, b, *args, **kwargs): + super(B, self).__init__(*args, **kwargs) + self.b = b + print('B.__init__') + +class C(A, B): + def __init__(self, a, b, c): + super(C, self).__init__(a, b) + self.c = c + +c = C(10, 20, 30) + +print(c.a, c.b, c.c) + +#------------------------------------------------------------------------------------------------------------------------ + super fonksiyonu parametresiz de kullanılabilir. Bu durumda fonksiyonun birinci parametresi içinde bulunulan sınıfın + ismi olarak, ikinci parametresi self olarak geçilmiş kabul edilir. Tabii bu kullanım yalnıca metotlar içerisinde yapılabilir. + Örneğin A sınıfının __init__ metodunda şöyle bir çağırma yapılmış olsun: + + super().__init__() + + Bu çağrı şununla eşdeğerdir: + + super(A, self).__init__() +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def __init__(self): + print('A.__init__') + +class B(A): + def __init__(self): + super().__init__() # super(B, self).__init__() + print('B.__init__') + +class C(B): + def __init__(self): + super().__init__() # super(C, self).__init__() + print('C.__init__') + +c = C() + +#------------------------------------------------------------------------------------------------------------------------ + NYPT'nin önemli bir prensibi "kapsülleme (encapsulation)" denilen prensiptir. Kapsülleme bir kavramın bir sınıfla temsil + edilmesi ve sınıfın dışarıyı ilgilendirmeyen, iç işleyişe ilişkin olan öğelerinin dış dünyadan gizlenmesi anlamına + gelmektedir. Bu gizleme hem algısal bir açıklık sağlamakta hem de yanlış kullanımları engellemektedir. Aslında kapsülleme + dış dünyada da sıklıkla karşılaştığımız bir olgudur. Örneğin bir otomobilin pek çok aksamı kaput içerisinde gizlenmiştir. + Yalnızca kullanıcıyı ilgilendiren kısımları görünür hale getirilmiştir. Bir televizyon için de aynı durum söz konusudur. + Televizyonu kullanabilmemiz için onun karmaşık yapısını bilmemize gerek yoktur. + + C++, Java ve C# gibi dillerde kapsülleme için sınıfların "public", "private", "protected" gibi bölümleri vardır. Sınıfı + yazan kişi birtakım elemanları private bölüme yerleştirire o öğelere dışarıdan erişilemez. Sınıfın veri elemanlarının + (örnek dözniteliklerinin) dış dünyadan gizlenmesine ise "veri elemanlarının gizlenmesi (data hiding)" prensibi denilmektedir. + Sınıfın veri elemanları iç işleyişe ilişkindir. Programcılar da C++, Java ve C# gibi dillerde veri elemanlarını sınıfın private + bölümine yerleştirerek dış dünyadan gizlerler. + + Ancak Python'da bu anlamda bir gizleme mekanizması yoktur. Yani C++, Java ve C# gibi dillerdeki "public", "protected", + private" gibi erişim belirten kavramlar Python'da bulunmamaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da her ne kadar C++, Java ve C# gibi dillerde bulunan "public", "private", "protcted" bölümler olmasa da bir + sınıfın bir metodunun ya da nesnenin bir özniteliğinin dış dünyadan gizlenmesi isimsel biçimde sağlanmaya çalışılmıştır. + Öyle ki eğer bir sınıfın ya da nesnenin özniteliğinin ismi '_' ile başlatılırsa bu durum diğer dillerdeki private etkisi + yaratmaktadır. Ancak bu etki yalnızca bir tavsiye oluşturmaktadır. Başı '_' ile başlayan isimler için yorumlayıcı tarafından + erişimde bir denetleme yapılmamaktadır. Başka bir deyişle biz Python'da sınıfın ya da nesnenin özniteliğinin '_' ile başladığını + gördüğümüzde "bu özniteliklere dışarıdan (sınıfın daşından) erişilmesinin istenmediği" anlamını çıkartmalıyız. Ancak yine + de biz istersek bu özniteliklere dışarıdan erişebiliriz. Burada bir zorlayıcılığın olmadığna yalnızca yazım biçimiyle sınıf kullananlara + bir tavsiyede bulunulduğuna dikkat ediniz. Örneğin: + + class Sample: + def do_someting_important(self): + # .... + self._foo() + # .... + self._bar() + # .... + self._tar() + #.... + + def _foo(self): + pass + + def _bar(self): + pass + + def _tar(self): + pass + + s = Sample() + + s.do_someting_important() + + Burada sınıfın _foo, _bar ve _tar metotlarının dışarıdan çağrılması istenmemektedir. Bu metotlar yalnızca sınıf içerisindeki + diğer metotlardan çağrılmaktadır. Tabii biz istersek gerçekten yine de onları dışarıdan kullanabiliriz. Bunun için yorumlayıcı + tarafından zorlayıcı bir denetim uygulanmamaktadır. Örneğin: + + s._foo() + + Aynı durum nesnenin öznitelikleri için de geçerlidir. Örneğin: + + import math + + class Circle: + def __init__(self, radius): + self.radius = radius + self._area = radius * radius * math.pi + + # ... + + c = Circle(10) + + print(c.radius) + + Burada sınıfın radius örnek özniteliğine dışarıdan erişebiliriz ancak _area örnek özniteliğine dışarıdan erişmemiz + istenmemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfın ve nesnenin özniteliklerinin dış dünyadan gizlenmesi için diğer bir yöntem de isimlerin başına '__' (iki alt tire) + getirmektir. Bir isim iki alt tire ile başlanarak isimlendirilmişse bu durum bu ismin "dışarıdan kullanımının daha katı bir biçimde + istenmediği" anlamına gelmektedir. İki alt tire ile verilen isimlere gerçekten dışarıdan erişilemez. Ancak _sınıfismi__ + öneki ile erişilebilir. Yani başka bir deyişle Sample sınıfının __xxx elemanına biz s.__xxx gibi bir ifadeyle erişemeyiz. + Ancak s._Sample__xxx ismiyle erişebiliriz. Bunun amacı kişilerin ilgili özniteliğe yanlışlıkla erişmelerinin engellenmesidir. + Mutlak anlamda erişmelerinin engellenmesi değildir. Tabii başı iki alt tire ile başlayan isimlere sınıf içerisinden yine iki + alt tire ile erişebiliriz. + + Örneğin: + + class Sample: + def do_someting_important(self): + # .... + self.__foo() + # .... + self.__bar() + # .... + self.__tar() + #.... + + def __foo(self): + pass + + def __bar(self): + pass + + def __tar(self): + pass + + s = Sample() + + s.do_someting_important() + s.__foo() # error! + s._Sample__foo() # geçerli, ama kötü teknik + + Nesnenin özniteliğini iki alt tire ile başlatarak isimlendirirsek yine bu özniteliklere dışarıdan erişemeyiz. Bunlara + dışarıdan erişim yine _sınıfismi__ önekiyle yapılır. Örneğin: + + import math + + class Circle: + def __init__(self, radius): + self.radius = radius + self.__area = math.pi * radius * radius + + def disp(self): + print(self.radius, self.__area) + #... + + c = Circle(1) + + print(c._Circle__area) # geçerli + print(c.__area) #error! + + Başka bir deyişle biz sınıf içerisinde bir özniteliği ya da bir metodu iki alt tire ile isimlendirirsek aslında + yorumlayıcı bu özniteliği ya da metodu _sınıfismi__ öneki ile isimlendirmektedir. Biz sınıf içerisinde iki alt tireli + isimlere iki alt tire ile erişebilmeteyiz. Ancak dışarıdan iki alt tire ile erişemeyiz. Dışarıdan ancak _sınıfismi__ + öneki ile erişebiliriz. + + Ancak iki altireli bir örnek özniteliği sınıfın içerisinde değil de dışarıda oluşturulmuşsa bu durumda erişim iki alt tireli + isimle yapılabilmektedir. Örneğin: + + class Sample: + def __init__(self): + self.__a = 10 + + s = Sample() + s.__b = 20 + print(s.__b) # dışarıdan erişilebilir + print(s.__a) # dışarıdan erişilemez! + + İsimlerin önüne tek alt tire getirilmesine "Python Language Specification" dokümanında bir atıfta bulunulmamıştır. Ancak + isimlerin başına iki alt tire getirilmesi konusu yukarıda açıklandığı gibi "Python Language Specification" dokümanında (6.2.1) + belirtilmektedir. + + Sınıfların __xxx__ biçiminde isimlendirilmiş elemanlarına "dunder" elemanlar dendiğini belirtmiştik. Bu dunder elemanlar + semantik olarak başı iki alt tire ile başlayan isimler gibi değerlendirilmemektedir. Yani iki alt tire ile başlayan + isimlerin sonunda da iki alt tire varsa bu isimlere dışarıdan aynı biçimde erişilebilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi biz dışarıdan kullanılmasını istemediğimiz isimlerin başına tek alt tire mi yoksa iki alt tire mi getirmeliyiz? + Bu durum tamamen bizim isteğimize bağlıdır. Eğer biz "dışarıdan erişme" tavsiyesini vurgulamak istiyorsak ik alt tire + kullanabiliriz. Ancak programcıların çoğu tek alt tireyi tercih etmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pek çok teorisyene göre bir dilin "nesne yönelimli" olması için dilin şu üç özelliği destekliyor olması gerekmektedir: + + - Sınıf kavramı + - Türetme kavramı + - Çokbiçimlilik (polymophism) + + Eğer bir dilde sınıflar olduğu halde çokbiçimlilik yoksa böyle dillere "nesne tabanlı (object based)" diller denilmektedir. + Başka bir deyişle dilin nesne yönelimli olabilmesi için "çokbiçimli mekanizmaya" sahip olması gerekmektedir. C++, Java, + C# gibi dillerde çokbiçimli mekanizma bulunmaktadır. Dolayısıyla bu diller "nesne yönelimli" dillerdir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + NYPT'de çokbiçimlilik (polymorphism) biyolojiden aktarılmış bir terimdir. Biyolojide çokbiçimlilik "canlıların çeşitli + doku ve organlarının temel işlevleri aynı kalmak üzere türlere göre farklılıklar göstermesi" anlamına gelmektedir. Yani + örneğin kulak pek çok canlıda vardır. Temel işlevi duymaktır. Ancak her canlı kendine göre bir kulak yapısına sahiptir. + Yani kendine göre duymaktadır. Örneğin köpekler daha tiz sesleri duyabilirler. Benzer biçimde göz pek çok canlıda vardır. + Temel işlevi görmektir. Ancak her canlıda göz o canlıya göre değişik bir evrim geçirerek farklılaşmıştır. Kartal çok + keskin görebilmektedir. Bazı hayvanlar dünyayı siyah beyaz görmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + NYPT'de bir eylem temel işlevi aynı olmak üzere sınıflar arasında farklılıklar gösteriyorsa bu eyleme "çokbiçimli" eylem + denilmektedir. Örneğin A, B, C, D, E sınıflarının disp isimli metotları olsun. Bu metotlar kendi sınıflarının birtakım + elemanlarını kendilerine özgü biçimde ekrana yazdırıyor olsun. Burada disp metodu "çokbiçimli" bir metottur. Çünkü çeşitli + sınıflarda bu metot vardır. Temel işlevi sınıf hakkında bilgileri ekrana yazdırmaktır. Ancak her sınıfın disp metodu kendine + özgüdür ve temel işlevi aynı olmasına karşın birbirlerinden farklıdır. Örneğin bir personel takip programında iş yerinde çalışan + kişilerin görevlerine göre Worker, Manager, Executive, SalesPerson gibi farklı sınıflarla temsil edildiğini düşünelim. Bu + sınıfların hepsinde kişilerin maaşlarını hesaplayan calculate_salary isimli bir metot olsun. Buradaki maaş hesaplama eylemi + çokbiçimlidir. Bu sınıfların hepsinde vardır ama her sınıfın maaş hesabı kendine özgü bir biçimde yapılmaktadır. Çokbiçimli + metotlar farklı sınıflarda aynı isimle bulunurlar. Bunların yaptıkları işler ana hatlarıyla aynıdır ancak o sınıfa özgü + farklılıklar içermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + C++, Java, C# gibi dillerde çokbiçimli mekanizma "türetme yoluyla" sağlanmaktadır. Bu dillerde tipik olarak bir taban + sınıf oluşturulur. O taban sınıftan sınıflar türetilir. Taban sınıftaki bir metot türemiş sınıfta aynı isimle yeniden + yazılır. Buna taban sınıftaki metodun türemiş sınıfta "override" edilmesi denilmektedir. Ancak Python dinamik tür sistemine + sahip olduğu için zaten doğuştan çokbiçimlidir. Python'da zaten isim araması programın çalışma zamanında yapıldığı için + çokbiçimli mekaznizma türetmeye bağlı değildir. Bu nedenle Python'da çokbiçimli mekanizmayı oluşturmak için özel bir + özel bir sentaks da yoktur. + + Çokbiçimlilik NYPT'de "türden bağımsız" kod paraçalarının (fonksiyonların metotların) oluşturulmasına olanak sağlamaktadır. + Çokbiçimlilik sayesinde birbirine benzeyen ancak farklı olan nesnelere "sanki aynı nesneymiş" muamalesi yapabilmekteyiz. + + Aşağıdaki örnekte "duy" metodu çokbiçimli bir metottur. Farklı sınıflarda aynı isimle bulunmaktadır. Ancak bu duy metodunun + o sınıflara özgü bir gerçekleştirimi vardır. Örneğimizde foo fonksiyonu bir canlıyı alır. O canlının hangi canlı olduğunu + bilmeden onun duyma eyleminden hareketle ona özgü eylemleri gerçekleştirmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Kedi: + def duy(self): + print('kedi duyuyor') + +class Köpek: + def duy(self): + print('köpek duyuyor') + +class At: + def duy(self): + print('at duyuyor') + +class Aslan: + def duy(self): + print('aslan duyuyor') + +class İnsan: + def duy(self): + print('insan duyuyor') + +def foo(canlı): + print('--------') + canlı.duy() + #.... + canlı.duy() + #... + canlı.duy() + +kedi = Kedi() +köpek = Köpek() +at = At() +aslan = Aslan() +insan = İnsan() + +foo(kedi) +foo(köpek) +foo(at) +foo(aslan) +foo(insan) + +#------------------------------------------------------------------------------------------------------------------------ + 46. Ders 05/10/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi çokbiçimlilikten en önemli amaç "türden bağımsız kod parçalarının" oluşturulmasıdır. + Çokbiçimlilik biribirine benzeyen ancak farklı olan nesnelerin sanki aynı nesneymiş gibi işleme sokulmasına olanak sağlamaktadır. + Bu sayede biz programımıza yeni öğeler eklediğimizde program üzerinde daha az değişiklik yaparız. + + Örneğin bir Tetris oyunu yazacak olalım. Bu oyunda çelşitli şekiller düşmektedir. Bu şekiller duruma göre sola, sağa + hareket ettirilmekte ve döndürülebilmektedir. Şeklin sola, sağa hareket ettrilmesi, düşmesi ve döndürülmesi "çokbiçimli + (polymoprhic)" eylemlerdir. Yani bu eylemler bu şekil sınıflarının hepsinde vardır. Ancak her şekil kendine göre bu + eylemleri yerine getirmektedir. + + Aşağıdaki örnekte böyle bir Tetris oyunu mantıksal olarak simüle edilmeye çalışılmıştır. Burada biz Microsoft Windows'a + özgü msvcrt modülünü kullandık. Bu modül standart Python kütüphanesinde olmasına karşın yalnızca Windows sistemlerinde + kullanılabilmektedir. Ayrıca maalesef bu modül Spyder konsolunda çalışmamaktadır. Bu nedenle bu örneği Windows konsoluna + geçerek çalıştırınız. msvcrt modülündeki kbhit isimli fonksiyon bekleme yapmaz. O anda klavyeden bir tuşa basılıp + basılmadığına bakar. Eğer klavyeden bir tuşa basılmışsa True değerini, basılmamışsa False değerini geri döndürür. + Böylece hiç bekleme yapmadan o anda klavyeden bir tuşa basılıp basılmadığı anlaşılabilmektedir. msvcrt modülündeki + getch fonksiyonu ise ENTER tuşuna gereksinim duymadan tuşa basılır basılmaz tuşu okur. Bu fonksiyon basılan tuşa ilişkin + bir byte nesnesi vermektedir. O halde hiç bekeleme yapmadan tuş okumak için aşağıdaki gibi bir yol izlenebilir: + + While True: + ... + if msvcrt.kbhit(): + ch = msvcrt.getch() + ... + + UNIX/Linux sistemlerinde ENTER tuşuna gereksinim duymadan tuşa basar basmaz klavye okuması yapmak için Python standart + kütüphanesinde basit bir fonksiyon bulunmamaktadır. curses kütüphanesi bu amaçla kullanılabilir. Ancak kullanımı biraz + daha zordur. Başkaları tarafındna yazılmış olan "getch" modülü pip programıyle indirilip kurulabilir. Örneğin: + + pip install getch +#------------------------------------------------------------------------------------------------------------------------ + +import random +import time +import msvcrt + +class Shape: + def __init__(self): + pass + +class BarShape(Shape): + def __init__(self): + super().__init__() + + def move_down(self): + print('BarShape moves down') + + def move_left(self): + print('<>') + + def move_right(self): + print('<>') + + def rotate(self): + print('<>') + +class SquareShape(Shape): + def __init__(self): + super().__init__() + + def move_down(self): + print('SquareShape moves down') + + def move_left(self): + print('<>') + + def move_right(self): + print('<>') + + def rotate(self): + print('<>') + +class ZShape(Shape): + def __init__(self): + super().__init__() + + def move_down(self): + print('ZShape moves down') + + def move_left(self): + print('<>') + + def move_right(self): + print('<>') + + def rotate(self): + print('<>') + +class LShape(Shape): + def __init__(self): + super().__init__() + + def move_down(self): + print('LShape moves down') + + def move_left(self): + print('<>') + + def move_right(self): + print('<>') + + def rotate(self): + print('<>') + +class TShape(Shape): + def __init__(self): + super().__init__() + + def move_down(self): + print('TShape moves down') + + def move_left(self): + print('<>') + + def move_right(self): + print('<>') + + def rotate(self): + print('<>') + + +class Tetris: + def __init__(self): + pass + + def get_random_shape(self): + shapes = [BarShape, TShape, ZShape, LShape, SquareShape] + shape_type = random.choice(shapes) + return shape_type() + + def run(self): + while True: + shape = self.get_random_shape() + for _ in range(20): + shape.move_down() + if msvcrt.kbhit(): + ch = msvcrt.getch() + if ch == b'a': + shape.move_left() + elif ch == b's': + shape.move_right() + elif ch == b'd': + shape.rotate() + elif ch == b'q': + return + time.sleep(0.5) + +tetris = Tetris() +tetris.run() + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf türünden nesneyi str türüne dönüştürebiliriz. Böyle bir dönüştürme için ilgili sınıfın __str__ isimli bir + metodunun bulunuyor olması gerekir. s bir sınıf türünden değişken olmak üzere str(s) işlemi tamamen s.__str__() ile + aynı anlamdadır. Programcı sınıfın __str__ metodunu bir str nesnesiyle geri döndürmelidir. + + Aslında print fonksiyonu yalnızca string'leri bastırmaktadır. Eğer print fonksiyonuna girdiğimiz argüman string değilse + print fonksiyonu onu str türüne dönüştürüp yazıyı ekrana basmaktadır. s string türünden olmayan bir türden olsun. Bu durumda: + + print(s) + + ile + + print(str(s)) + + ya da + + print(s.__str__()) + + tamamen aynı anlamdadır. Örneğin: + + class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __str__(self): + return f'({self.x}, {self.y})' + + pt = Point(3, 2) + + print(pt) + print(str(pt)) + print(pt.__str__()) + + Burada biz kendi sınıf nesnemizi print fonksiyonuyla yazdırmak istediğimizde aslında print fonksiyonu onu str türüne + dönüştürüp yazdırmaktadır. str türüne dönüştürme sırasında sınıfımızın __str__ metodu çağrılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __str__(self): + return 'this is a test' + +s = Sample() + +print(s) # this is a test + +k = str(s) # k = s.__str__() +print(k) # this is a test + +#------------------------------------------------------------------------------------------------------------------------ + Tipik olarak programcı kendi sınıfı için __str__ metodunu yazarak o nesnenin tuttuğu bilgileri bu metotta bir yazıya + dönüştürüp bu yazıyla geri dönmektedir. Böylece ilgili türden bir sınıf nesnesi print ile yazdırılmak istendiğinde + ekranda o nesneyi betimleyen bir yazı görünecektir. +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __str__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + +d = Date(4, 10, 2022) +print(d) + +k = Date(11, 10, 2008) +print(k) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi ya sınıfımız için __str__ metodunu yazdırmamışsak bu durumda bu sınıf türünden nesneyi str türüne dönüştürürken + ya da print ederken nasıl bir yazı görünecektir? İşte eğer biz sınıfımız için __str__ metodunu yazmamışsak MRO sırasına + taban sınıflardan birinin __str__ metodu çağrılır. Tüm sınıflar doğrudan ya da dolaylı olarak object sınıfından türetildiğine + göre en kötü olasılıkla object sınıfının __str__ metodu çağrılacaktır. obecjt sınıfının __str__ metodu da nesnenin türünü + ve bellek adresini bir yazı biçiminde vermektedir. Örneğin: + + >>> class Sample: + ... pass + ... + >>> s = Sample() + >>> s.__str__() + '<__main__.Sample object at 0x000002C64EF290C0>' + >>> str(s) + '<__main__.Sample object at 0x000002C64EF290C0>' + >>> print(s) + <__main__.Sample object at 0x000002C64EF290C0> +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların __str__ metotlarının nasıl bir yazı geri döndüreceği konusunda bir standart yoktur. Sınıfları yazanlar nesnenin + içerisindeki bilgilere ilişkin özet bir yazı geri döndürmektedir. Bir nesnenin çok fazla özniteliği olabilir. Bu özniteliklerin + hepsinin __str__ metodunda bir yazı biçiminde geri döndürülmesi uygun olmaz. Genellikle nesneyi temsil eden en önemli + özniteliklerin yazı biçiminde geri verilmesi uygun olur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'un standart kütüphanesindeki sınıflarda genel olarak __str__ metodu zaten yazılmış durumdadır. Böylece biz + standart kütüphanedeki bir sınıf nesnesini doğrudan yazdırırsak o nesneye ilişkin özet bilgileri görüntülemiş oluruz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında biz list gibi, tuple gibi sınıf nesnelerini print ile yazdırdığımızda önce o nesneler string türüne dönüştürülüp + elde edilen yazılar yazdırılmaktadır. Tabii bu sınıflarda sınıflarda __str__ metotları bulunmaktadır. Bir listenin print + ile nasıl yazdırılabildiği hakkında fikir edinmeniz için aşağıdaki örneği veriyoruz. +#------------------------------------------------------------------------------------------------------------------------ + +class MyList: + def __init__(self, *args): + self.args = args + + def __str__(self): + s = '[' + for x in self.args: + if s != '[': + s += ', ' + s += str(x) + s += ']' + + return s + +ml = MyList(1, 2, 3, 4, 5) +print(ml) + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin bir karmaşık sayıları temsil eden bir sınıfın __str__ metodu nesnenin tuttuğu karmaşık sayıyı yazı olarak verebilir. +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real, imag): + self.real = real + self.imag = imag + + def __str__(self): + return f'{self.real}+{self.imag}i' + +z = Complex(3, 2) +print(z) # 3+2i + +#------------------------------------------------------------------------------------------------------------------------ + Programcı bir sınıf yazmışsa onun için bir __str__ metodunu yazması iyi bir tekniktir. Yukarıda da belirttiğimiz gibi + Programcı bu metotlarda genel olarak sınıfın örnek özniteliklerini yazısal olarak kendisinin istediği biçimde verir. + Python'ın standart kütüphanesindeki sınıflarda da hep bu __str__ metotları yazılmış durumdadır. Örneğin: + + >>> import datetime + >>> d = datetime.date(2023, 10, 17) + >>> d.__str__() + '2023-10-17' + >>> str(d) + '2023-10-17' + >>> print(d) + 2023-10-17 + + Burada da görüldüğü gibi datetime modülündeki date sınıfı için __str__ metodu yazılmıştır. Bu metot nesnesinin tuttuğu + tarihi bize bir yazı olarak vermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import datetime + +d = datetime.date(2022, 10, 4) +print(d) # 2022-10-04 + +s = str(d) +print(s) # 2022-10-04 + +#------------------------------------------------------------------------------------------------------------------------ + __str__ metodunun çokbiçimli (polymorphic) bir metot olduğuna dikkat ediniz. Yani her nesnenin bir betimleyici yazısı + vardır. Bu yazı ilgili nesneye göre değişiklik göstermektedir. Bu sayede biz nensnenin türünü bilmesek bile onu print + fonksiyonu ile yazdırabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + __str__ metodunun __repr__ isminde bir benzeri de vardır. __repr__ metodu da tıpkı __str__ netodunda olduğu gibi bir + string ile geri dönmelidir. Pekiyi iki metot arasında ne farklılık vardır? İşte __str__ metodu "daha çok kullanıcıya + yönelik", __repr__ metodu ise "daha çok programcıya yönelik" bir yazı verme iddiasındadır. Tabii bu iki metodun veridiği + yazılar aynı da olabilir. Örneğin Python'ın komut satırında, Spyder'ın komut satırında (IPython) bir değişkeni yazıp ENTER + tuşuna bastığımızda bu komut satırı programları değişken ile __repr__ metodunu çağırıp buradan elde edilen yazıyı bastırmaktadır. + Örneğin: + + >>> class Sample: + ... def __str__(self): + ... return '__str__' + ... + ... def __repr__(self): + ... return '__repr__' + ... + >>> s = Sample() + >>> s + __repr__ + >>> print(s) + __str__ + + Özetle: + + 1) Bir sınıf türünden değişken str türüne dönüştürüldüğünde (ya da print fonksiyonu ile yazdırıldığında) __str__ metodu + çağrılarak onun geri döndürdüğü yazı elde edilmektedir. + + 2) Bir değişkenin ismi komut satırında yazılıp ENTER tuşuna basıldığında __repr__ metodunun geri döndürdüğü yazı + görüntülenmektedir. + + Aslında programcının bu iki metodu ayrı ayrı yazması da gerekmemektedir. Çünkü: + + 1) Eğer sınıfın __repr__ metodu varsa fakat __str__ metodu yoksa str türüne dönüştürmede __repr__ metodu kullanılmaktadır. Aynı + zamanda komut satırında değişken ismi yazılıp ENTER tuşuna basıldığında da __repr__ metodu kullanılılır. Yani biz sınıf için + yalnızca __repr__ metodunu yazarsak hem str türüne dönüştürmelerde hem de komut satırında değişken ismini yazıp ENTER tuşuna + basıldığında bu metot çağrılacaktır. + + 2) Sınıfta hem __str__ hem de __repr__ metodu varsa bu durumda str türüne dönüştürmede __str__ metodu, komut satırında değişken + ismi yazılıp ENTER tuşuna basıldığında __repr__ metodu çağrılmaktadır. + + 3) Sınıfta yalnızca __str__ metodu varsa str türüne dönüştürmede __str__ metodu çağrılır. Komut satırında değişken ismi yazılıp + ENTER tuşuna basıldığında onject sınıfının __repr__ metodu çağrılır. + + Yukarıda da belirtildiği gibi object sınıfında da __repr__ metodu vardır. Bu metot da yine değişkenin ilişkin olduğu sınıfın ismini + ve nesnenin id'sini vermektedir. + + >>> class Sample: + ... def __str__(self): + ... return '__str__' + ... + >>> s = Sample() + >>> print(s) + __str__ + >>> s + <__main__.Sample object at 0x0000026954AC5850> + + O halde bu metotların yazımı konusunda ne tavsiye edilebilir? Eğer programcı tek bir metot yazacaksa __repr__ metodunu yazması daha uygun olur. + Eğer programcı bu iki yazıyı birbirinden ayırmak istiyorsa her iki metodu da yazmalıdır. Python'ın standart kütüphanesindeki sınıflarda genellikle + bu metotlar ayrı ayrı yazılmıştır. Örneğin: + + >>> import datetime + >>> d = datetime.date(2022, 10, 4) + >>> print(d) + 2022-10-04 + >>> d + datetime.date(2022, 10, 4) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Ayrıca standart kütüphanede repr isimli bir built-in fonksiyon da vardır. Bu fonksiyon aslında parametresiyle aldığı + nesne ile __repr__ metodunu çağırıp onun geri döndürdüğü yazıyla geri dönmektedir. Yani gerçekleştirimi kabaca şöyledir: + + def repr(o): + return o.__repr__() + + Bazen programcı komut satırında değil de kendi programı içerisinde de __str__ yerine __repr__ metodunu kullanmak isteyebilir. Örneğin: + + import datetime + + d = datetime.date(2022, 10, 4) + print(d) + print(repr(d)) # print(d.__repr__()) + + Örneğin bazen programcı birtakım yazılardaki özel karakterleri görebilmek için yazıyı __repr__ metodu ile yazdırmak isteyebilir: + + f = open('test.txt', encoding='utf-8') + s = f.read() + print(repr(s)) + f.close() + + Biz henüz dosya işlemlerini görmedik. Ancak burada bir dosya açılmış, içerisindeki bilgiler okunup ekrana yazdırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bazen bir sınıfın ya da nesnenin bir özniteliğinin (metotların da aslında sınıf öznitelikleri olduğunu anımsayınız) var + olup olmadığını anlamak isteyebiliriz. Örneğin Sample sınıfının bir foo metodunun olup olmadığını anlamak bilmek isteyebiliriz. + Bunun için standart kütüphanede bulunan hasattr isimli built-in fonksiyon kullanılmaktadır. hasttar fonksiyonu iki parametreye + sahiptir. Fonksiyonun birinci parametresei ilgili sınıf türünden değişkeni ikinci parametresi ise söz konusu özniteliğin + string biçiminde ismini almaktadır. Fonksiyon bool bir değere geri dönmektedir. Eğer değişken bir sınıf türündense bu + fonksiyon önce o sınıf nesnesinin içerisindeki örnek özniteliklerine bakmakta sonra o nesnenin ilişkin olduğu sınıf + içerisindeki özniteliklere bakmaktadır. Eğer birinci parametre type türündense fonksiyon ilgili sınıfın özniteliklerine + bakmaktadır. + + Örneğin: + + class Sample: + def foo(self): + pass + + s = Sample() + s.a = 10 + + result = hasattr(s, 'foo') + print(result) # True + + result = hasattr(Sample, 'foo') + print(result) # True + + result = hasattr(s, 'a') + print(result) # True + + hasttar fonksiyonu sınıfın taban sınıflarına da bakmaktadır. Örneğin: + + class Sample: + def foo(self): + pass + + class Mample(Sample): + pass + + m = Mample() + + result = hasattr(m, 'foo') + print(result) # True + + + result = hasattr(Mample, 'foo') + print(result) # True + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 47. Ders 10/10/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi Python'da değişken kavramı ile nesne kavramı farklı anlamlara geliyordu. Python'da değişkenler nesnelerin + adreslerini tutmaktadır. Biz Python'da dğeişken dediğimizde adres tutan varlıkları, nesne dediğimizde gerçek bilgileri + tutan varklıkları kastetmekteyiz. Örneğin: + + s = 'ankara' + + Burada s bir değişkendir. "ankara" yazısı str türündne bir nesnede tutulmaktadır. s'nin içerisinde o nesnenin adresi + vardır. Yine anımsanacağı gibi Python'da her türlü atama "adres ataması" anlamına geliyordu. Örneğin: + + s = 'ankara' + k = s + + Burada bir tek nesne vardır. İki değişken aynı nesneyi göstermektedir. + + Pekiyi bir değişken bir nesneyi gösterirken başka bir nesneyi göstermeye başladığında ya da del deyimi ile silindiğinde + onun gösterdiği nesneye ne olacaktır? Örneğin: + + s = 'ankara' + s = None + + Burada s değişkeni bir str nesnesini gösterirken daha sonra onu göstermez hale gelmiştir. Pekiyi o nesne ne olacaktır? + + İşte programlama dillerinde "kullanılmayan nesnelerin otomatik biçimde" silinmesini sağlayan mekanizmalara "çöp toplama + (garbage collection)" mekanizması denilmektedir. Bu işlemi yapan alt sistemlere de "çöp toplayıcı (garbage collector)" + denilmektedir. C#, Java, Swift, Kotlin, Python gibi dillerde çöp toplama mekanizması vardır. Ancak C, C++ gibi aşağı + seviyeli dillerde böyle bir mekanizma bulunmamaktadır. + + Çöp toplama mekanizması bir nesne artık kimse tarafından kullanılamaz noktaya geldiğinde o nesneyi silen bir mekanizmadır. + Örneğin: + + s = 'ankara' + s = 100 + + Burada çöp toplama mekanizması içerisinde "ankara" yazısının bulundurğu str nesnesini silebilir. Ancak örneğin: + + s = 'ankara' + k = s + s = 100 + + Burada çöp toplama mekanizması içerisinde "ankara" yazısının bulunduğu str nesnesini silmememilidir. Çünkü o + nesneyi kullanan başka bir değişken de vardır. Örneğin: + + def foo(): + s = 'ankara' + print(s) + + foo() + + Burada foo fonksiyonu çağrıldıktan sonra içerisinde "ankara" yazısının bulunduğu nesne silinebilir mi? Yerel değişkenler + fonksiyon ya da metot sonlandığında otomatik yok edilmektedir. Dolayısıyla bu örnekte fonksiyon bittiğinde söz konusu + nesneyi gösteren herhangi bir değişken kalmayacaktır. O halde bu nesne çöp toplayıcı tarafından silinebilir durumda + olacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Çöp toplama mekanizmalarının gerçekleştiriminde çeşitli yöntemler kullanılabilmektedir. Python referans kitaplarında + çöp toplama mekanizmasının gerçekleştirimi hakkında bir şey söylenmemiştir. Yani gerçekleştirim yöntemi Python yorumlayıcına + bağlı olarak değişebilmektedir. Örneğin "Iron Python" .NET ortamı için kod ürettiğinden .NET ortamının "mark and sweep" + denilen çöp toplama yöntemini kullanmaktadır. Halbuki CPython gerçekleştirimi "referans sayacı" tekniği ile çöp + toplaması yapmaktadır. Biz de kursumuzda CPython yorumlayıcısının kullandığı referans sayacı tekniğini açıklayacağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bir nesne yaratıldığında yorumlayıcı o nesnesnin kaç değişken tarafından referans edildiğini (yani gösterildiğini) + nesnenin içerisinde tutmaktadır. Buna nesnenin "referans sayacı (reference count)" denilmektedir. Nesnenin referans sayacı + programın çalışması sırasında azalıp artabilmektedir. Nesnenin referans sayacı 0 olduğunda artık o nesneyi hiçbir değişken + göstermiyor durumda olur. + + Aşağıdaki örnekte list nesnesinin çeşitli durumlardaki referans sayacının kaç olduğu gösterilmiştir: + + def foo(x): + # RC: 3 + y = x + # RC: 4 + + a = [100, 200, 300] + # RC: 1 + b = a + # RC: 2 + foo(b) + # RC: 2 + b = None + # RC: 1 + a = None + # RC: 0 + + İşte CPython yorumlayıcısının çöp toplama mekanizmasında bir nesnenin referans sayacı 0'a düştüğünde yorumlayıcı hemen + o nesneyi silmektedir. Çünkü referans sayacı 0'a düşmüş bir nesnenin artık programcı tarafından kullanılma olanağı + kalmamıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir nesnenin referans sayacı sys modülündeki getrefcount isimli fonksiyonla elde edilebilir. Bu fonksiyon nesnenin referans + sayacını her zaman bir fazla olarak vermektedir. Çünkü bu fonksiyon referans sayacı bulunacak nesnenin adresini parametre + yoluyla aldığı için fonksiyonun içerisinde nesnenin referans sayacı bir artmış olmaktadır. Ayrıca örneğin bir değişken + bir fonksiyona parametre olarak geçirildiğinde Python yorumlayıcısı fonksiyonun parametre değişkenini de ayrıca bir sözlükte + tuttuğu için nesnenin referans sayacı bir değil daha fazla da artabilmektedir. Tabii burada aslında sayıların pek önemi yoktur. + Buradaki en önemli nokta nesnesinin referans sayacının sıfıra düşmesidir. + + Tabii Python'da aslında int, float, str, bool gibi temel türler de birer sınıf biçimindedir. Dolayısıyla örneğin int + bir nesnenin de referans sayacı vardır, float bir nesnenin de referans sayacı vardır. + + Aşağıdkai programda bir nesnenin referans sayacı çeşitli aşamalarda yazdırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +import sys + +def foo(x): + print(sys.getrefcount(x)) + y = x + print(sys.getrefcount(x)) + +a = [100, 200, 300] +print(sys.getrefcount(a)) +b = a +print(sys.getrefcount(a)) +foo(b) +print(sys.getrefcount(a)) +b = None +print(sys.getrefcount(a)) +a = None + +#------------------------------------------------------------------------------------------------------------------------ + getrefcount fonksiyonu ile nesnelerin referans sayaçlarını elde ettiğinizde bu sayılar umduğunuz gibi çıkmayabilir. + Çünkü Python yorumlayıcıları kendi içlerinde çeşitli nedenlerden dolayı nesnenin başka değişkenler tarafından da gösterilmesini + sağlayabilmektedir. Ayrıca CPython gibi yorumlayıcılar belli sabitleri işin başında bir kez yaratıp o sabitler kullanıldığında + zaten yaratılmış olan nesnelerin adreslerini kullanabilmektedir. Bu nedenle pek çok sabitin yüksek bir referans sayacı + olabilmektedir. Örneğin: + + >>> a = 0 + >>> sys.getrefcount(a) + 313 + + Buradaki yüksek değer kişiler tarafından şüpheyle karşılanabilmektedir. Ancak yukarıda da belirtitğimiz gibi CPython + yorumlayıcısı belli sabitleri işin başında yaratıp o sabitler bir değişkene atandığında zaten yaratılmış olan nesnenin + adresini o değişkene atamaktadır. Böylece çok kullanılan sabitler için her defasında ayrı ayrı nesneler yaratılmamış + olmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi CPython'da bir nesne yaratıldıktan sonra o nesnenin referans sayacı sıfıra düştüğünde + nesne artık çöp duruma gelmektedir ve "çöp toplayıcı (garbage collector)" mekanizma sayesinde nesne bellekten yok + edilmektedir. Yani Python'da programcı nesneleri yaratır ancak onların yok edilmesiyle uğraşmaz. Onların yok edilmesi + yorumlayıcı sistem tarafından otomatik yapılmaktadır. + + Yukarıda da belirttiğimiz gibi çöp toplama mekanizması Python'a özgü değildir. Java, C# gibi dillerde ve onların kullanıldığı + ortamlarda da çöp toplama mekanizması bulunmaktadır. Yine yukarıda belirttiğimiz gibi çöp toplama mekanizması için değişik + yöntemler kullanılmaktadır. Çöp toplama sisteminin gerçekleştiriminde kullanılan bu yöntemlerin birbirlerine göre avantajları + ve dezavantajları söz konusu olabilmektedir. Örneğin CPython yorumlayıcısı "referans sayacı yoluyla çöp toplama yöntemini" + kullanır. Bu yöntemde bir nesnenin referans sayacı sıfıra düşer düşmez çöp toplayıcı onu hemen silmektedir. Ancak örneğin + .NET ve Java ortamlarında "mark and sweep" yöntemi tercih edilmektedir. O ortamlarda nesnenin silinmesi hemen değil belli + bir zaman sonra sistem durdurularak yapılmaktadır. Örneğin Iron Python ve Jython yorumlayıcıları bu ortamlarda çalıştığı + için bu ortamların çöp toplama mekanizmasını kullanmaktadır. Yukarıda da belirttiğimiz gibi Python referans kitaplarında + çöp toplama yöntemi hakkında ayrıntılar verilmemiş dolayısıyla bu konu yorumlayıcıları yazanların isteğine bırakılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bazen (seyrek olarak) bir sınıfı yazan programcı sınıfın __init__ metodu içerisinde Python dünyasının dışındaki (örneğin + işletim sistemi düzeyindeki) bir kaynağı tahsis edebilir. Bu kaynak bir bellek alanı olabileceği gibi, işletim sisteminin + kontrol ettiği çeşitli nesneler de olabilmektedir. Bazen nesne yaratılırken bir dosya yaratılır, nesne bu dpsyayı kullanır. + İşte bu tür durumlarda nesnenin yaratımı sırasında __init__ metodunda yapılan tahsisatların nesne yok edilmeden önce otomatik + olarak serbest bırakılması gerekmektedir. Örneğin: + + s = Sample() + s.foo() + s.bar() + + Burada konu kolay anlaşılsın diye Sample sınıfının __init__ metodunun bir dosya yaratıp sınıfın metotlarının o dosyayı + kullandığını varsayalım. Bu nesnenin kullanımı bittiğinde bu dosyayı da silmek isteriz. Örneğin: + + s = None + + Burada çöp toplayıcı nesneyi yok edecektir. Fakat tabii yaratılmış olan bu dosyayı silmeyecektir. İşte bu tür geri alma + işlemlerinin otomatize edilmesi için __del__ isimli bir metottan faydalanılmaktadır. + + Python'ın çöp toplayıcı mekanizması nesneyi yok etmeden hemen önce o nesne için "eğer varsa" ilgili sınıfın __del__ isimli + metodunu çağırmaktadır. Böylece programcı nesne yok edilmeden önce nesne ile ilgili birtakım son işlemleri yapabilir. Tabii + programcılar genel olarak Python dünyasının içerisinde kaldıklarından dolayı bu metodu yazmaya pek gereksinim duymamaktadır. + Ancak yukarıda da belirttiğimiz gibi eğer programcı Python dünyasının dışında birtakım tahsisatları sınıfın __init__ metdounda + yapmışsa bu tahsisatların otomatik geri bırakılmasını __del__ metodunda sağlayabilir. İşletim sistemi düzeyinde tahsis edilen + kaynaklara .NET ve Java dünyasında "unmanaged" kaynaklar denilmektedir. Aslında Python'ın standart kütüphanesi zaten bu tür + unmanaged kaynakları kullanmaktadır ve onların boşaltımını ilgili sınıfların __del__ metotlarında zaten yapmaktadır. Yukarıda + da belirttiğimiz gibi programcılar genel olarak bu __del__ metotlarını yazmayı gerektirecek uygulamalar içerisinde seyrek olarak + bulunurlar. + + Program bittiğinde hala birtakım nesnelerin referans sayaçları 0'dan büyükse onlar için de son kez __del__ metotları + çağrılmaktadır + + Aşağıdaki programı çalıştırarak çıktısını dikkatle inceleyeniz. CPython yorumlaycısının tam olarak nesneyi nerede yok + ettiğini tespit etmeye çalışınız. Program çalıştırıldığında şöyle bir çıktı elde edilecektir: + + bir + işletim sistemi düzeyinde bir kaynak tahsis ediliyor + iki + tahsis edilen kaynak boşaltılıyor + üç + +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self): + print('işletim sistemi düzeyinde bir kaynak tahsis ediliyor') + + def __del__(self): + print('tahsis edilen kaynak boşaltılıyor') + +print('bir') +s = Sample() +print('iki') +s = 10 +print('üç') + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte File sisimli sınıfın __init__ metodunda içi boş bir dosya yaratılmıştır. Sınıfın write metodu bu dosyaya + bir şeyler yazmaktadır. disp metodu ise dosyanın içerisindekileri ekrana yazdırmaktadır. Burada istenilen şey bu yaratılan + dosyanın nesnenin kullanımı bittiğinde otomatik yok edilmesidir. İşte örneğimizde sınıfın __del__ metodunda bu işlem otomatize + edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +import os + +class File: + def __init__(self, path): + self.path = path + self.f = open(path, 'w+') + + def write(self, text): + self.f.seek(0, 0) + self.f.write(text) + + def disp(self): + self.f.seek(0, 0) + text = self.f.read() + print(text) + + def __del__(self): + self.f.close() + os.remove(self.path) + +file1 = File('test.txt') +file1.write('This is a test') +file1.disp() + +file2 = File("mest.txt") +file2.write('Another example') +file2.disp() + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfın __del__ metodu ile ilgili soru ve yanıtlar şunlar olabilir: + + SORU: __del__ metodu ne zaman çağrılmaktadır? + YANIT: __del__ metodu nesnenin referans sayacı 0'a düştüğünden nesene yorumlayıcının çöp toplayıcı alt sistemi tarafından + yok edilmeden az önce çağrılmaktadır. + + SORU: __del__ metodunu kim çağırmaktadır? + YANIT: Bu metodu yorumlayıcı (yani çöp toplayıcı) çağırmaktadır. + + SORU: Ben sınıfım için __del__ metodu yazmalı mıyım? + YANIT: Sınıfınız için __del__ metodunu yazmanız için bir gerekçenizin olması gerekir. Aslında __del__ metodunun yazılması + gerektiği durumlarla seyrek karşılaşılmaktadır. Bu metot özellikle sınıfın __init-_ metodunda Python dünyasının dışında + yapılan birtakım kaynak tahsisatlarının otomatik geri bırakılması için kullanılmaktadır. + + SORU: __del__ metodu yazılmamışsa yorumlayıcı ne yapar? + YANIT: __del__ metodu yorumlayıcı (çöp toplayıcı) tarafından "eğer varsa" çağrılmaktadır. Siz sınıfınız için bu metodu + yazmadıysanız yorumlayıcı (çöp toplayıcı) bunu çağırmaya çalışmayacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi CPython'daki __del__ metodunun çağrılması tamamen deterministik midir? Aslında büyük ölçüde CPython'da bu __del__ + metodunun çağrılması deterministik gibi gözükmektedir. Ancak yine de tam olarak böyle bir durum söz konusu değildir. + Örneğin: + + def foo(): + print('foo begins') + s = Sample() + print('foo ends') + + Burada s ne zaman hayatını kaybetmektedir? CPython yorumlayıcısı s'in bir daha kullanılmadığını fark edip hemen son + print'ten önce nesneyi silebilmektedir. Halbuki örneğin C++'ta kesinlikle bu durumda "destructor" fonksiyonun nerede + çağrılacağı olarak bellidir. Ayrıca bazı durumlarda nesne referanslarının gizlice başka collection nesnelerde + tutulduğu durumlarda nesnenin tam olarak ne zaman silineceği yine kestirilememektedir. Program bittiğinde referans + sayacı 0'a düşmemiş olan nesneler için de __del__ metodunun çağrılıp çağrılmayacağı Python'da standrat bir biçimde + belirlenmemiştir.Örneğin: + + class Sample: + def __del__(self): + print('Sample.__del__') + + def foo(self): + print('foo') + + s = Sample() + s.foo() + + Burada s nesnesinin referans sayacı program bittiğinde henüz 0'a düşmemiştir. Pekiyi program bitmedne önce __del__ + metodu yine de çağrılacak mıdır? İşte bu durum Python yorumlayıcıları arasında farklılıklar oluşturabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + __del__ metodu kullanılarken dikkat edilmelidir. Çünkü bu metodun kullanımında yukarıda belirttiğimiz bazı özel durumlar + programların umulduğu gibi çalışmamasına yol açabilmektedir. Bunları bir kez daha özetlemek istiyoruz: + + - __del__ metodunun hangi noktada çağrılacağı konusunda bazen belirsizlikler olabilmektedir. Dolayısıyla Python'da ve özel + olarak CPython gerçekleştiriminde çöp toplama mekanizaması tamamen deterministik değildir. + + - Bir nesnenin başka bir nesneyi göstermsi, onunda onu göstermesi durumunda çöp tolayıcının çalışması sorunlara + yol açabilmektedir. + + - Program bittiğinde referans sayacı 0'a düşmemiş olan nesneler için __del__ metodunun çağrılıması ile ilgili kesin + bir belirleme yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz bir sınıftan türetme yapıyorsak ve sınıfımız için __del__ metodunu yazmışsak bu durumda türemiş sınıf türünden nesnemiz + çöp toplayıcı tarafından yok edilirken bizim türemiş sınıfımızın __del__ metodu çağrılacaktır. Ancak eğer taban sınıfın + da __del__ metodu varsa bizim türemiş sınıfın __del__ metodu içerisinde taban sınıfın __del__ metodunu çağırmamız gerekir. + Bu çağırma işlemi tipik olarak türemiş sınıfın __del__ metodunun sonunda yapılmalıdır. Örneğin: + + class A: + def __init__(self): + print('A.__init__') + + def __del__(self): + print('A.__del__') + + class B(A): + def __init__(self): + super().__init__() + print('B.__init__') + + def __del__(self): + print('B.__del__') + super().__del__() + + b = B() + b = None + + Burada yaratılan B nesnesinin referans sayacı 0'a düştüğünde B sınıfının __del__ metodu çağrılacaktır. Bu metot da + taban sınıfın __del__ metodunu çağırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def __init__(self): + print('A.__init__') + + def __del__(self): + print('A.__del__') + +class B(A): + def __init__(self): + super().__init__() + print('B.__init__') + + def __del__(self): + print('B.__del__') + super().__del__() + +b = B() +b = None + +#------------------------------------------------------------------------------------------------------------------------ + Tabii eğer taban sınıfın bir __del__ metodu yoksa biz türemiş sınıfın __del__ metodu içerisinde taban sınıfın __del__ + metodunu çağırmamalıyız. Çünkü Python'da object sınıfınn bir __del__ metodu yoktur. O halde biz türemiş sınıf için + __del__ metodunu yazarken eğer taban sınıfta __del__ metodu varsa onu çağırmaya çalışmalıyız. Pekiyi biz taban sınıfın + __del__ metodunun olup olmadıpını nasıl anlayabiliriz? Aslında en sağlam yol dökümantasyona bakmak olabilir. Ya da + biz manuel olarak dir gibi bir fonksiyonla taban sınıfın __del__ metodunun olup olmadığını anlayabiliriz. Eğer + elimizde taban sınıfa ilişkin bir dokümantasyon yoksa biz de taban sınıfın __del__ metoduna sahip olup olmadığını + bilemeyecek durumdaysak o zaman hasattr fonksiyonuyla bu kontrolü yapabiliriz. Örneğin: + + class B(A): + def __del__(self): + ... + base = super() + if hasattr(base, '__del__'): + base.__del__() + + Aşağıdaki örnekte türemiş sınıfın __del__ metodu önce hasattr fonksiyonu ile taban sınıfın __del__ metodu var mı diye bakmıştır. Sonra eğer varsa taban sınıfın __del__ metodunu çapırmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + def __init__(self): + print('A.__init__') + + def __del__(self): + print('A.__del__') + +class B(A): + def __init__(self): + super().__init__() + print('B.__init__') + + def __del__(self): + print('B.__del__') + base = super() + if hasattr(base, '__del__'): + base.__del__() + +b = B() +del b + +#------------------------------------------------------------------------------------------------------------------------ + 48. Ders 12/10/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + "Operatör metotları" konusu nesne yönelimli dillerin çoğunda bulunmaktadır. Örneğin bu özellik C++'ta, C#'ta, Swift'te + vardır. Fkata örneğin Java'da bulunmamaktadır. Python da operatör metotlarını desteklemektedir. Operatör metotları aslında + dile ekstra bir özellik katmamaktadır. Yalnızca okunabilirlik bakımından bir avantaj sağlamaktadır. Yani başka bir deyişle + nesne yönelimli bir dilde operatör metotları olmasa da (örneğin Java'da yok) bu işlemler normal metotlarla sağlanabilir. + Ancak operatör metotları güzel bir görünüm sunmakta ve kodun daha anlaşılabilir olmasını sağlamaktadır. + + Aşağıdaki örnekte bir Complex sayı sınıfı oluşturulmuştur. Bu sınıfa iki Complex sayıyı toplayabilen ve bir Complex sayıdan + başka bir Complex sayıyı çıkartabilen add ve sub metotları eklenmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real = 0, imag = 0): + self.real = real + self.imag = imag + + def add(self, z): + real = self.real + z.real + imag = self.imag + z.imag + + return Complex(real, imag) + + def sub(self, z): + real = self.real - z.real + imag = self.imag - z.imag + + return Complex(real, imag) + + def __repr__(self): + return f'{self.real}+{self.imag}i' + +x = Complex(7, 4) +y = Complex(5, 2) + +result = x.add(y) +print(result) + +result = x.sub(y) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Operatör metotları özel dunder isimli metotlardır. Yani operatör metotlarının isimleri dilin içerisinde belirlenmiştir. + Örneğin toplama işlemini yapan operatör metodu __add__, çıkartma işlemini yapan opereatör metodu __sub__, çarpma işlemini + yapan operatör metodu __mul__ ismindedir. Programcı bu metotları yazarak Python opereatörlerinde kendi sınıfları için + bu metotların çağrılmasını sağlayabilir. + + Python yorumlayıcısı sınıf türünden bir değişkenin bir operatör ile kullanıldığını gördüğünde bu ifadeyi eşğder metot + çağrısına dönüştürmektedir. Örneğin a bir sınıf türünden olmak üzere a + b ifadesi tamamen a.__add__(b) ile eşdeğerdir. + Örneğin a > b ifadesi de a.__gt__(b) ile eşdeğerdir. Böylece aslında biz bir sınıf nesnesini operatörlerle işleme soktuğumuzda + arka planda sınıfın dunder'lı operatör metotları çağrılmaktadır. Tabii biz de a + b yerine aslında a.__add__(b) yazabilirdik. + İki ifade arasında bir farklılık yoktur. + + Pekiyi örneğin __add__ metodunun parametrik yapısı nasıl olacaktır? Mademki a + b işleminin eşdeğeri a.__add__(b) biçimindedir. + O halde __add__ metodunun iki parametresi olmalıdır. İlk parametre zorunlu self parametresidir. a + b ifadesindeki a + değeri bu self parametresine aktarılacaktır. İkinci parametre buradaki b değerini alan parametredir. Örneğin: + + def __add(self, x): + pass + + Burada a + b işleminde a değeri self parametresine b değeri ise x parametresine atanacaktır. Tabii bizim de mantıksal + olarak iki değeri toplayıp bunun sonucuyla geri dönmemiz gerekir. + + Tabii Python yorumlayıcısı operatör metotlarında o operatöre uygun bir işlem yapılıp yapılmadığını denetleyememektedir. + Dolayısıyla bu konuda asorumluluk tamamne programcıdadır. (Yani biz __add__ metodu içerisinde çıkartma işlemi de yapabiliriz. + Bu konuda bir denetim uygulanmamaktadır. Tabii operatör metodunda operatöre uygun olmayan bir işlem yapmak kötü bir + tekniktir. ) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Operatör metotları kombine edilebilmektedir. Örneğin a, b ve c bir sınıf türünden değişkenler olsun. a + b + c işleminin + eşdeğeri a.__add__(b).__add__(c) biçimindedir. Yani burada ilgili sınıfın __add__ metodu iki kere çağrılacaktır. Önce + a + b işlemi yapılacak buradan bir sınıf nesnesi elde edilecek o nesne bu kez c ile toplanacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real = 0, imag = 0): + self.real = real + self.imag = imag + + def __add__(self, z): + real = self.real + z.real + imag = self.imag + z.imag + + return Complex(real, imag) + + def __sub__(self, z): + real = self.real - z.real + imag = self.imag - z.imag + + return Complex(real, imag) + + def __repr__(self): + return f'{self.real}+{self.imag}i' + +x = Complex(7, 4) +y = Complex(5, 2) +z = Complex(1, 2) + +result = x + y + z +print(result) + +result = x.__add__(y).__add__(z) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Operatör metotları operatör önceliklerini değiştirmez. Örneğin a, b, c birer sınıf türünden olmak üzere a + b * c işleminde + yine önce b * c yapılıp bunun sonucu a ile toplanacaktır. Dolayısıyla bu işlemin çağrı karşılığı a.__add__(b.__mull__(c)) + biçimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Complex: + def __init__(self, real = 0, imag = 0): + self.real = real + self.imag = imag + + def __add__(self, z): + real = self.real + z.real + imag = self.imag + z.imag + + return Complex(real, imag) + + def __sub__(self, z): + real = self.real - z.real + imag = self.imag - z.imag + + return Complex(real, imag) + + def __mul__(self, z): + real = self.real * z.real - self.imag * z.imag + imag = self.real * z.imag + self.imag * z.real + + return Complex(real, imag) + + def __repr__(self): + return f'{self.real}+{self.imag}i' + +x = Complex(7, 4) +y = Complex(5, 2) +z = Complex(1, 2) + +result = x + y * z # x.__add__(y.__mul__(z)) +print(result) + +result = x.__add__(y.__mul__(z)) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi a + b gibi bir işlemde b'nin a ile aynı sınıf türünden olma zorunluluğu var mıdır? Hayır yoktur. Bu durumda + farklı b türleri için farklı işlemlerin yapılması gerekebilmektedir. C++, C# gibi dillerde "function/method overloading" + özelliği olduğundan her farklı tür için farklı bir operatör fonksiyonu/metodu yazılabilmektedir. Python'da bir sınıf + için aynı isimli tek bir operatör metodu bulunabileceğinden dolayı bu işlem tür kontrolleriyle sağlanabilmektedir. + Aşağıdaki örnekte bir sayıyı temsil eden bir Number sınıfı oluşturulmuştur. Sınıfın __add__, __sub__ ve __mul__ metotları + iki Number nesnesini ve bir Number nesnesi ile int ve float nesneyi toplayabilecek, çıkartabilecek ve çarpabilecek biçimde + yazılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class Number: + def __init__(self, val = 0): + self.val = val + + def __repr__(self): + return str(self.val) + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __sub__(self, number): + if isinstance(number, int|float): + result = self.val - number + elif isinstance(number, Number): + result = self.val - number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __mul__(self, number): + if isinstance(number, int|float): + result = self.val * number + elif isinstance(number, Number): + result = self.val * number.val + else: + raise TypeError('invalid type') + + return Number(result) + +x = Number(10) +y = Number(20) +z = Number(2) + +result = (x + 10) * 2 +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi Python'da iki tane bölme operatörü vardır. / operatörü her zaman float değer vermektedir. // operatörü + (floordiv) ise bölüm sonucunda noktadan sonraki kısmı atmaktadır. İşte / operatörüne ilişkin operatör metodu __truediv__ + ismiyle // operatörüne ilişkin operatör metodu ise __floordiv__ ismiyle yazılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Number: + def __init__(self, val = 0): + self.val = val + + def __repr__(self): + return str(self.val) + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __sub__(self, number): + if isinstance(number, int|float): + result = self.val - number + elif isinstance(number, Number): + result = self.val - number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __mul__(self, number): + if isinstance(number, int|float): + result = self.val * number + elif isinstance(number, Number): + result = self.val * number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __truediv__(self, number): + if isinstance(number, int|float): + result = self.val / number + elif isinstance(number, Number): + result = self.val / number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __floordiv__(self, number): + if isinstance(number, int|float): + result = self.val // number + elif isinstance(number, Number): + result = self.val // number.val + else: + raise TypeError('invalid type') + + return Number(result) + +x = Number(30) +y = Number(20) + +result = x / y # x.__truediv__(y) +print(result) + +result = x // y # x.__floordiv__(y) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python'a 3.5 versiyonuyla birlikte @ operatörü de eklenmiştir. Bu operatöre "matris çarpımı" operatörü de denilmektedir. + Ancak bu @ operatörü standart Python sınıfları tarafından kullanılmamaktadır. Fakat NumPy gibi Pandas gibi kütüphaneler + bu operatörü kullanmaktadır. Bu operatör aslında ilgili sınıfın __matmul__ isimli operatör metodunu çağırmaktadır. Başka + bir deyişle a @ b işlemi a.__matmul__(b) ile eşdeğerdir. Python'da matris işlemi yapan standart bir sınıf yoktur. Bu operatör + matris işlemleri yapan üçüncü parti kütüphanelerde (NumPy, Pandas gibi) eklenme sebebiokunabilirlik sağlamak için kullanılmaktadır. + + Aşağıda matris çarpımının sınıfın __matmul__ metodu tarafından yaptırılmasına bir örnek verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class Matrix: + def __init__(self, matrix): + self.matrix = matrix + self.rowsize = len(matrix) + self.colsize = len(matrix[0]) + + def __matmul__(self, a): + result = [[0] * a.colsize for i in range(self.rowsize)] + for i in range(self.rowsize): + for j in range(self.colsize): + total = 0 + for k in range(self.colsize): + total += self.matrix[i][k] * a.matrix[k][j] + result[i][j] = total + + return Matrix(result) + + def __repr__(self): + s = '' + for i in range(len(self.matrix)): + for k in range(len(self.matrix[i])): + if k != 0: + s += ' ' + s += str(self.matrix[i][k]) + + s += '\n' + + return s + +m = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +k = Matrix([[1, 2, 1], [3, 2, 1], [1, 3, 4]]) + +result = m @ k # m.__matmul__(k) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Sınıf türünden değişkenleri karşılaştırma operatörleriyle karşılaştırabilmek için ilgili sınıfta karşılaştırma operatör + metotlarının yazılmış olması gerekir. Karşılaştırma operatörleri için operatör metotlarının isimleri şunlardır: + + > __gt__ + < __lt__ + >= __ge__ + <= __le__ + == __eq__ + != __ne__ + + Karşılaştırma operatörlerine ilişkin operatör metotlarının geri dönüş değerleri herhangi bir türden olabilirse de + bunların bool türüyle geri dönmesi en normal durumdur. Çünkü karşılaştırma operatörleri Python'da bool değer üretmektedir. + + Aşağıdaki örnekte Number sınıfı için karşılaştırma operatör netotları yazılmıştur +#------------------------------------------------------------------------------------------------------------------------ + +class Number: + def __init__(self, val = 0): + self.val = val + + def __repr__(self): + return str(self.val) + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __sub__(self, number): + if isinstance(number, int|float): + result = self.val - number + elif isinstance(number, Number): + result = self.val - number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __mul__(self, number): + if isinstance(number, int|float): + result = self.val * number + elif isinstance(number, Number): + result = self.val * number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __truediv__(self, number): + if isinstance(number, int|float): + result = self.val / number + elif isinstance(number, Number): + result = self.val / number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __floordiv__(self, number): + if isinstance(number, int|float): + result = self.val // number + elif isinstance(number, Number): + result = self.val // number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __gt__(self, number): + if isinstance(number, int|float): + result = self.val > number + elif isinstance(number, Number): + result = self.val > number.val + else: + raise TypeError('invalid type') + + return result + + def __lt__(self, number): + if isinstance(number, int|float): + result = self.val < number + elif isinstance(number, Number): + result = self.val < number.val + else: + raise TypeError('invalid type') + + return result + + def __ge__(self, number): + if isinstance(number, int|float): + result = self.val >= number + elif isinstance(number, Number): + result = self.val >= number.val + else: + raise TypeError('invalid type') + + return result + + def __le__(self, number): + if isinstance(number, int|float): + result = self.val <= number + elif isinstance(number, Number): + result = self.val <= number.val + else: + raise TypeError('invalid type') + + return result + + def __eq__(self, number): + if isinstance(number, int|float): + result = self.val == number + elif isinstance(number, Number): + result = self.val == number.val + else: + raise TypeError('invalid type') + + return result + + def __ne__(self, number): + if isinstance(number, int|float): + result = self.val != number + elif isinstance(number, Number): + result = self.val != number.val + else: + raise TypeError('invalid type') + + return result + +val1 = int(input('Bir değer giriniz:')) +val2 = int(input('bir değer giriniz:')) + +x = Number(val1) +y = Number(val2) + +if x > y: + print('x > y') +elif x < y: + print('x < y') +elif x == y: # kasten yapıldı + print('x == y') + +#------------------------------------------------------------------------------------------------------------------------ + Karşılaştırma operatör metotları Python standart kütüphanesindeki bazı sınıflarda bulunmaktadır. Örneğin datettime + müdlü içerisindeki date ve datetime sınıfları tarih ve zaman karşılaştırmasını yapan karşılaştırma operatörlerine sahiptir. +#------------------------------------------------------------------------------------------------------------------------ + +import datetime + +d = datetime.date(2023, 10, 31) +k = datetime.date(2022, 7, 21) + +if d > k: + print('d > k') +elif d < k: + print('d > k') +else: + print('d == k') + +#------------------------------------------------------------------------------------------------------------------------ + Bazı operatörlerin değişme özelliği vardır. Örneğin a + b ile b + a aynı sonucu vermelidir. Ya da örneğin a * b ile + b * a da aynı sonucu vermelidir. Eğer a ve b değişkenlerinin her ikisi de bizim sınıfımız türündense değişme özelliğinin + sağlanması bakımından bir sorun oluşmaz. Eğer a.__add__(b) işlemi yapılabiliyorsa b.__add__(a) da aynı sonucu verecek biçimde + doğal olarak yapılabilecektir. Ancak bu operatörlerin sağ tarafındaki operand'lar farklı türdense değişme özelliğinin + sağlanmasında sorunlar ortaya çıkabilmektedir. Örneğin a + 3 gibi bir ifadeyi biz 3 + a gibi de yazabiliriz. Ancak operatör + metotlarında soldaki operandın bizim sınıfımız türünden olması gerekmektedir. Kendi sınıfımızda a.__add__(3) işlemini yapabilecek + bir operatör metodu yazabiliriz ancak kendi sınıfımızda 3.__add__(a) işlemini yapacak bir operaratör metodunu yazamayız. Özetle + sol taraftaki operand bizim sınıfımız türünden değilse __add__ metodu ile değişme özelliğini sağlayamayız. + + İşte iki operand'lı operatör metodunun ismi __op__ olmak üzere bir bunların __rop__ biçiminde başı "r" ile başlayan (reverse + sözcüğünden geliyor) versiyonları da vardır. Python yorumlayıcısı "op" bir operatör belirtmek üzere a op b işlemini yapacak + operatör metodunu önce sol taraftaki operandın sınıfında __op__ biçiminde arar. Bulursa bu ifadeyi a.__op__(b) olarak değerlendirir. + Eğer bu sınıfta bu operatör metodu yoksa bu kez sağ taraftaki operandın sınıfında __rop__ metodunu arar. Eğer bu metodu + bulursa ifadeyi b.__rop__(a) biçiminde değerlendirir. + + Programcının hem ___op__ hem de __rop__ metotlarını ayrı ayrı yazmasına gerek yoktur. Bunlar aynı işi yapacağına göre + ve metotlar aslında sınıf değişkenleri olduğuna göre bu işlem basit bir atama ile de sağlanabilir. Örneğin: + + class Number: + def __init__(self, val = 0): + self.val = val + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __radd__ = __add__ + + def __repr__(self): + return str(self.val) + + Tabii bazen bunların ayrı ayrı yazılması da gerekebilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Number: + def __init__(self, val = 0): + self.val = val + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __radd__ = __add__ + + def __repr__(self): + return str(self.val) + +x = Number(10) + +result = x + 2 +print(result) + +result = 2 + x # x.__radd__(2) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi hangi operatörler için __rop__ metotlarını da yazmamız gerekir? Aslında bu durum programcıya kalmıştır. Programcı + yalnızca + ve * gibi değişme özelliği olan operatörlere ilişkin operatör metotlarının r'li versiyonlarını yazabilir. + Ancak öte yandan bazı durumlarda değişme özelliği olmayan operatörler için de bu r'li operatör metotlarının yazılması + gerekebilir. Örneğin a > b işleminin eşdeğeri aslında b < a biçimindedir. Benzer biçimde b < a işleminin de eşdeğeri + a > b biçimindedir. Örneğin biz a > 10 gibi bir işlemi yapabiliyorsak 10 < a gibi bir işlemi de yapabilmek isteriz. + Bu durumda karşılaştırma operatörlerinin de r'li biçimlerinin yazılması uygun olacaktır. Tabii bu kararı verecek olan + programcıdır. + + Aşağıdaki örnekte Number sınıfının operatör fonksiyonlarına r'i biçimler eklenmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class Number: + def __init__(self, val = 0): + self.val = val + + def __repr__(self): + return str(self.val) + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __radd__ = __add__ + + def __sub__(self, number): + if isinstance(number, int|float): + result = self.val - number + elif isinstance(number, Number): + result = self.val - number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __rsub__ = __sub__ + + def __mul__(self, number): + if isinstance(number, int|float): + result = self.val * number + elif isinstance(number, Number): + result = self.val * number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __rmul__ = __mul__ + + def __truediv__(self, number): + if isinstance(number, int|float): + result = self.val / number + elif isinstance(number, Number): + result = self.val / number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __rtruediv__ = __truediv__ + + def __floordiv__(self, number): + if isinstance(number, int|float): + result = self.val // number + elif isinstance(number, Number): + result = self.val // number.val + else: + raise TypeError('invalid type') + + return Number(result) + + __rfloordiv__ = __floordiv__ + + def __gt__(self, number): + if isinstance(number, int|float): + result = self.val > number + elif isinstance(number, Number): + result = self.val > number.val + else: + raise TypeError('invalid type') + + return result + + __rgt__ = __gt__ + + def __lt__(self, number): + if isinstance(number, int|float): + result = self.val < number + elif isinstance(number, Number): + result = self.val < number.val + else: + raise TypeError('invalid type') + + return result + + __rlt__ = __lt__ + + def __ge__(self, number): + if isinstance(number, int|float): + result = self.val >= number + elif isinstance(number, Number): + result = self.val >= number.val + else: + raise TypeError('invalid type') + + return result + + __rge__ = __gt__ + + def __le__(self, number): + if isinstance(number, int|float): + result = self.val <= number + elif isinstance(number, Number): + result = self.val <= number.val + else: + raise TypeError('invalid type') + + return result + + __rle__ = __le__ + + def __eq__(self, number): + if isinstance(number, int|float): + result = self.val == number + elif isinstance(number, Number): + result = self.val == number.val + else: + raise TypeError('invalid type') + + return result + + __req__ = __eq__ + + def __ne__(self, number): + if isinstance(number, int|float): + result = self.val != number + elif isinstance(number, Number): + result = self.val != number.val + else: + raise TypeError('invalid type') + + return result + + __rne__ = __ne__ + +x = Number(5) +result = 10 < x +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + op bir operatör sembolü olmak üzere her zaman a = a op b ile a op= b aynı anlamda olmayabilir. Örneğin a + b ile a += b + int, float gibi temel türlerde eşdeğerdir. Ancak örneğin listelerde bu iki ifade eşdeğer değildir. Listelerde bilindiği gibi + a += b aslnda a üzerinde eklemeye yol açmaktadır. Yani programcı op= operatörleri için farklı operatör metotları yazarak + semantik farklılık oluşturabilmektedir. İşte op= operatörleri için __iop__ biçiminde başı "i" ile başlayan operatörler + bulundurulmuştur. Yorumlayıcı a op= b ifadesini gördüğünde eğer sınıfta bu işi yapacak bir __iop__ metodu yoksa bu ifadeyi + a = a op b olarak ele alır. Ancak eğer sınıfta bu işi yapacak bir __iop__ metodu varsa ifadeyi a = a.__iop__(b) olarak ele + almaktadır. Yani özetle biz bu "işlemli atama operatörleri" için operatör metotları yazmak zorunda değiliz. Bu durumda + normal operatör metodu devreye girecektir. Ancak biz işlemli atama operatörlerinde daha farklı bir şeylerin yapılmasını + istiyorsak bu operatörlerin i'li versiyonlarını oluşturmalıyız. + + Pekiyi biz __iop__ operatör metotlarını nasıl yazmalıyız? Öncelikle bu metotların yine bu işlemin sonucu olan bir + nesneyle geri dönmesi gerekir. Çünkü a += b gibi bir işlem eğer sınıfta bunu yapabilecek bir __iadd__ metodu varsa + a = a.__iadd__(b) biçiminde ele alınmaktadır. Yani görüldüğü gibi bu i'li metotların geri dönüş değerleri yine sol + taraftaki operanda atanmaktadır. Böylece programcı isterse hiç yeni nesne yaratmadan işlemi soldaki operand üzerinde yapıp + onunla geri dönebilir. Tabii bu durumda i'li metot self ile geri dönmelidir. + + Örneğin: + + class Number: + def __init__(self, val = 0): + self.val = val + + def __repr__(self): + return str(self.val) + + def __add__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + return Number(result) + + def __iadd__(self, number): + if isinstance(number, int|float): + result = self.val + number + elif isinstance(number, Number): + result = self.val + number.val + else: + raise TypeError('invalid type') + + self.val = result + + return self + + Burada __iadd__ metodu aslında toplamı self üzerinde oluşturup yeniden self nesnesine geri dönmüştür. Örneğin: + + x = Number(10) + y = Number(20) + + print(x) # 10 + print(id(x)) # 2825741528960 + + x += y + + print(x) # 30 + print(id(x)) # 2825741528960 + + Yani özetle a += b gibi bir işlemde eğer sınıfta __iadd__ operatör metodu yoksa bu işlem a = a.__add__(b) biçiminde eğer + sınıfta __iadd__ metodu varsa bu işlem a = a.__iadd__(b) biçiminde yapılmaktadır. Bu durumda a += b işleminde zaten a sınıfı + "değiştirilebilir (mutable)" olmadıktan sonra ya da a = a + b farklı semantik uygulanmadıktan sonra __iadd__ metodunun + yazılmasına da gerek yoktur. Örneğin list sınıfında __iadd__ metodu vardır. Ancak tuple sınıfında yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 49. Ders 17/10/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + a bir sınıf türünden değişken olmak üzere a(...) biçiminde biz bu değişkeni sanki fonksiyonmuş gibi fonksiyon çağırma + operatörü ile kullanabiliriz. Ancak bunun için ilgili sınıfın __call__ isimli bir operatör metodunun bulunuyor olması + gerekir. Yani: + + a(...) + + çağrısı aslında aşağıdaki çağrı tamamne eşdeğerdir: + + a.__call__(...) + + Bu nedenle Python'da fonksiyon çağırma operatörü ile çağrılabilen nesnelere "callable" nesneler denilmektedir. Bir + fonksiyon "callable" bir nesnedir. __call__ metodu buunan bir sınıf nesnesi de "callable" bir nesnedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __call__(self): + print('this is a test') + +s = Sample() + +s() + +#------------------------------------------------------------------------------------------------------------------------ + Tabii __call__ metotları ekstra parametrelere ve geri dönüş değerlerine sahip olabilir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, text): + self.text = text + + def __call__(self, n): + return self.text * n + +s = Sample('test') + +result = s(5) # s.__call__(5) +print(result) # testtesttesttesttest + +#------------------------------------------------------------------------------------------------------------------------ + Fonkisyon yerine sanki fonksiyonmuş gibi sınıf nesnelerinin kullanılması bazı durumlarda faydalar sağlamaktadır. Sınıf + nesneleri örnek özniteliklerinde bilgi tutabildiği için çağrılar arasında aynı değerlerin kullanılması mümkün olabilmektedir. + Fonksiyon yerine sınıf nesnelerinin kullanılması "callback" mekanizmasında yoğun olarak kullanılabilmektedir. Bir foksiyon + bir işi yaparken arka planda bizim ona verdiğimiz bir fonksiyonu çağırıyor olabilir. Bu mekanizmaya "callback" mekanizaması, + burada çağrılan fonksiyona da "callback" fonksiyon denilmektedir. İşte bu tür callback fonksiyonların "callable" nesnelerle + oluşturulmasının bazı avantajları söz konusu olabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz daha önce string'lerin, listelerin, demetlerin, kümelerin, sözlüklerin len fonksiyonuna sokulabildiğini gördük. + Aslında len fonksiyonu ilgili sınıfın __len__ isimli metodunu çağırıp onun geri dönüş değeri ile geri dönmektedir. Yani: + + len(a) + + ile + + a.__len__() + + aynı anlamdadır. Bu durumda len fonksiyonu aslında şöyle yazılmıştır: + + def len(a): + return a.__len__() + + len fonksiyonunun çokbiçimli (polymorpic) oldupuna dikkat ediniz. Yani biz pek nesnenin uzunluğunu len ile elde edebiliriz. + Ancak elde ettiğimiz değer o nesneye bağlıdır. + + Biz de kendi sınıfımız türünden nesnelerin len fonksiyonuna sokulmasını istersek sınıfımızda __len__ metodunu yazmalıyız. + __len__ metodunun yalnızca self parametresi vardır. Bu self parametresi len fonksiyonun argümanını oluşturmaktadır. Örneğin: + + class Sample: + def __init__(self, *args): + self.args = args + + def __len__(self): + return len(self.args) + + s = Sample(10, 20, 30, 40) + + result = len(s) + + print(result) # 4 + +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, *args): + self.args = args + + def __len__(self): + return len(self.args) + +s = Sample(1, 2, 3, 4, 5, 10, 20) + +result = len(s) + +print(result) # 7 + +print(s.__len__()) # 7 + +#------------------------------------------------------------------------------------------------------------------------ + Kendi sınıfımız türünden bir nesneyi biz int, float, bool, str, complex türlerine dönüştürmek istediğimizde bu dönüştürme + işlemi için sınıfımızın sırasıyla __int__, __float__, __bool__, __str__ ve __complex__ metotları çağrılmaktadır. Bu metotların + yalnızca self parametreleri bulunmaktadır. Aslında biz daha önce __str__ metodunu görmüştük. Bu metot str türüne dönüştürülürken + çağrılıyordu. İşte diğer metotlar da diğer türlere dönüştürülürken çağrılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Rational: + def __init__(self, num, denom): + self.num = num + self.denom = denom + + def __str__(self): + return f'{self.num}/{self.denom}' + + def __float__(self): + return self.num / self.denom + +r = Rational(2, 3) +print(r) + +result = float(r) +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf türünden değişkeni bool türüne dönüştürürken eğer biz sınıfımızda bu dönüşüm için __bool__ bulundurmuşsak + bu metot çağrılır. Ancak biz sınıfımızda __bool__ metodu bulundurmamışsak bu durumda değişken içerisinde None değeri + yoksa dönüştürme True olarak None değeri varsa False olarak yapılır. Örneğin: + + class Number: + def __init__(self, val): + self.val = val + + x = Number(0) + result = bool(x) + + print(result) # True + + Nesnenin özniteliklerinde ne olursa olsun ilgili değişken bool türüne True olarak dönüştürülür. Ancak biz sınıf için + __bool__ metodunu yazarsak bu durumda bu metot çağrılacaktır. Örneğin: + + class Number: + def __init__(self, val): + self.val = val + + def __bool__(self): + return bool(self.val) + + x = Number(0) + result = bool(x) + + print(result) # False +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıf türünden değişken köşeli parantez operatörüyle kullanıldığında sınıfın __getitem__ metodu çağrılmaktadır. + Böylece sanki sınıf türünden değişken bir liste gibi köşeli parantez operatörleriyle kullanılabilmektedir. Sınıf türünden + değişken atama operatörünün solunda da kullanılabilir. Bu durumda da sınıfın __setitem__ metodu çağrılmaktadır. Başka bir + deyişle eğer biz değişkeni köşeli parantezli bir biçimde ancak atama operatörünün solunda kullanmıyorsak ilgili sınıfın + __getitem__ metodu, atama operatörünün solunda kullanıyorsak ilgili sınıfın __setitem__ metodu çağrılmaktadır. + + __getitem__ metodu self parametresinin yanı sıra indeks belirten bir parametre daha sahiptir. __setitem__ ise self + parametresinin yanı sıra hem indeks belirten bir parametreye hem de atanacak değeri belirten bir parametreye sahiptir. + Bu iki metodun parametik yapıları şöyledir: + + def __getitem__(self, index): + pass + + def __setitem__(self, index, value): + pass + + Kullanım sırasında köşeli parantez içerisindeki ifade metotların index parametresine aktarılmaktadır. __setitem__ metodunun + üçüncü parametresi ise atanan değeri belirtmektedir. a bir sınıf nesnesi olmak üzere: + + b = a[index] + + işleminin eşdeğeri şöyledir: + + b = a.__getitem__(index) + + Benzer biçimde: + + a[index] = value + + işleminin de eşdeğeri şöyledir: + + a.__setitem__(index, value) + + Aşağıdaki örnekte Sample sınıfı türünden nesne yaratılırken alınan argümanlar nesnenin list türünden bir özniteliğine + yerleştirilmiştir. Sonra bu elemanları sınıf için __getitem__ ve __setitem__ metotları yazılarak bu değerleirn get ve set + edilmeleri sağlanmıştır. Ayrıca sınıfa bir de __len__ metoıdunun eklendiğine dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, *args): + self.args = list(args) + + def __getitem__(self, index): + return self.args[index] + + def __setitem__(self, index, value): + self.args[index] = value + + def __len__(self): + return len(self.args) + +s = Sample(10, 20, 30, 40, 50) + +for i in range(len(s)): + print(s[i], end=' ') # print(s.__getitem__(i), end=' ') +print() + +s[0] = 100 # s.__setitem(0, 100) +s[2] = 300 # s.__setitem__(2, 300) + +for i in range(len(s)): + print(s[i], end=' ') # print(s.__getitem__(i), end=' ') +print() + +#------------------------------------------------------------------------------------------------------------------------ + Tabii köşeli parantezlerin içerisinde int türden bir değer olmak zorunda değildir. Herhangi bir türden değer olabilir. + Index parametresini programcı anlamlandırmalıdır. Örneğin: + + class Sample: + def __getitem__(self, index): + if not isinstance(index, str): + raise TypeError('invalid type') + + return index.upper() + + s = Sample() + + result = s['ankara'] + print(result) + + Burada programcı köşeli parantezler içerisinde bir string beklemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __getitem__(self, index): + if not isinstance(index, str): + raise TypeError('invalid type') + + return index.upper() + +s = Sample() + +result = s['ankara'] +print(result) +#------------------------------------------------------------------------------------------------------------------------ + Python'daki listelerde ve demetlerde iki boyutluluk demetin ya da listenin elemanı olarak demet ya da liste kullanmakla + sağlanmaktadır. Dolayısıyla bir matrisin elemanına erişmek için iki ayrı [...] operatörü kullanılmaktadır. Örneğin: + + >>> a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + >>> a[2][1] + 8 + >>> a[2] + [7, 8, 9] + + Burada matris elemanına erişimin tek köşeli parantez ile değil iki köşeli parantez ile yapıldığına dikkat ediniz. + Yani a[i, k] gibi bir kullanım geçerli değildir. Örneğin: + + >>> a[2, 1] + Traceback (most recent call last): + File "", line 1, in + TypeError: list indices must be integers or slices, not tuple + + Ancak bu kullanımın üçüncü parti bazı kütüphanelerde oludğunu da görmekteyiz. Örneğin: + + >>> import numpy as np + >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> a + array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + >>> a[2, 1] + 8 + >>> a[2, 1] = 100 + >>> a + array([[ 1, 2, 3], + [ 4, 5, 6], + [ 7, 100, 9]]) + + Pekiyi köşeli parantez içerisinde virgüllü kullanım nasıl sağlanmaktadır? İşte Python yorumlayıcısı köşeli parantezin + içerisinde tek bir değer varsa onu o türden bir nesne olarak __getitem__ ve __setitem__ metotlarına geçirmektedir. + Eğer köşeli parantezin içerisinde virgül ile ayrılmış birden fazla değer varsa bu kez Python yorumlayıcısı bu değerleri + bir demete yerleştirip demeti __getitem__ ve __setitem__ metotlarına geçirmektedir. Yani örneğin: + + val = s[x, y, z] + + işlemi aşağıdakiyşe eşdeğerdir: + + val = s[(x, y, z)] # val = s.__getitem__((x, y, z)) + + Benzer biçimde örneğin: + + s[x, y, z] = val + + işlemi de aşağıdakiyle eşdeğerdir: + + s[(x, y, z)] = val # s.__setitem__((x, y, z), val) + + Bu durumu aşağıdaki kodla test edebilirsiniz. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __getitem__(self, index): + return f'type: {type(index)}, value: {index}' + +s = Sample() + +result = s[1, 2, 3] +print(result) + +result = s[1] +print(result) + +result = s['ankara'] +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da a[x, y] işleminin a[(x, y)] işleminden hiçbir farkı olmadığına dikkat ediniz. Örneğin: + + >>> import numpy as np + >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> a[1, 2] + 6 + >>> a[(1, 2)] + 6 + >>> t = 1, 2 + >>> a[t] + 6 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte Matrix sınıfı bir liste listesini alıp nesnenin özniteliğinde saklamaktadır. Sonra __getitem__ + netodunda köşeli parantez içerisindeki index değerinin tek bir int değerden mi yoksa iki değerden mi oluştuğu kontrol + edilmiştir. Sınıf için bir __setitem__ metodu da yazılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class Matrix: + def __init__(self, a): + self.a = a + + def __getitem__(self, index): + if isinstance(index, int): + return self.a[index] + + if isinstance(index, tuple): + if len(index) != 2: + raise ValueError('matrix must must have two dimensions') + return self.a[index[0]][index[1]] + + raise TypeError('index invalid type') + + def __setitem__(self, index, value): + if isinstance(index, tuple): + if len(index) != 2: + raise ValueError('matrix must must have two dimensions') + self.a[index[0]][index[1]] = value + else: + raise TypeError('index invalid type') + + +a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +m = Matrix(a) + +result = m[1, 2] +print(result) # 6 + +result = m[1] +print(result) # [4, 5, 6] + +m[1, 2] = 100 + +result = m[1, 2] +print(result) # 100 + +#------------------------------------------------------------------------------------------------------------------------ + Köşeli parantez operatöründe dilimleme yapabilmek için slice isminde bir sınıftan faydalanılmaktadır. slice sınıfı built-in + bir sınıftır. Bir slice nesnesi tek argümanla, iki argümanla ya da üç argümanla yaratılabilir. slice sınıfının start, stop ve + step isimli üç örnek özniteliği vardır. Eğer slice nesnesi tek argümanla yaratılmışsa start ve step None değerinde ancak stop + ise girilen argüman değerinde olur. slice nesnesi iki argümanla yaratılmışsa start birinci argümanın, stop ikinci argümanın + değerinde olur ancak step None değerinde olur. slice nesnesi üç argümanla yaratılmışsa argümanlar sırasıyla start, stop + ve step değerlerinde olur. Örneğin: + + >>> s = slice(10) + >>> print(s.start, s.stop, s.step) + None 10 None + >>> s = slice(10, 20) + >>> print(s.start, s.stop, s.step) + 10 20 None + >>> s = slice(10, 20, 2) + >>> print(s.start, s.stop, s.step) + 10 20 2 + + İşte biz köşeli parantez içerisinde dilimle yaparsak Python yorumlayıcısı bu dilimleme için __getitem__ ve __setitem__ + metotlarına slice nesnesi geçirmektedir. Yani yorumlayıcı dilimleme sentaksını gördüğünde bir slice nesnesi yaratıp onu + da __getitem__ ve __setitem__ metotlarına argüman olarak geçirmektedir. Örneğin: + + result = a[10:20:2] + + işleminin eşdeğeri şöyledir: + + result = a.__getitem__(slice(10, 20, 2)) + + result = a[10:20] + + işleminin eşdeğeri şöyledir: + + result = a.__getitem__(slice(10, 20, None)) + + Örneğin: + + result = a[10:20:] + + işleminin eşdeğeri şöyledir: + + result = a.__getitem__(slice(10, 20, None)) + + Örneğin: + + result = a[:10:2] + + işleminin eşdeğeri şöyledir: + + result = a.__getitem__(slice(None, 10, 2)) + + Örneğin: + + result = a[::] + + işleminin eşdeğeri şöyledir: + + result = a.__getitem__(slice(None, None, None)) + + Tabii aynı durum __setitem__ metodu için de geçerlidir. Örneğin: + + s[10:20:2] = 100 + + Bu işlemin eşdeğeri de şöyledir: + + s.__setitem__(slice(10, 20, 2), 100) + + Aşağıda hangi dilimleme sentaksında nasıl bir slice nesnesinin yaratılacağı bir liste halinde verilmiştir: + + s[x:y:z] ---> slice(x, y, z) + s[:y:z] ---> slice(Nobe, y, z) + s[x:y] ---> slice(x, y, None) + s[x:y:] ---> slice(x, y, None) + s[x:y] ---> slice(x, y, None) + s[x:y:z] ---> slice(x, y, z) + s[x::z] ---> slice(x, None, z) + s[::] ---> slice(None, None, None) + + Örneğin biz listelerde, demetlerde ve string'lerde dilimleme yaparken dilimleme yerine doğrudan slice nesnelerini + kullanabiliriz. Zaten yorumlayıcı dilimlemeyi gördüğünde onu slice nesnelerine dönüştürmektedir. Örneğin: + + >>> a = [10, 20, 30, 40, 50, 60] + >>> a[2:4] + [30, 40] + >>> a[slice(2, 4, 1)] + [30, 40] + + O halde biz __getitem__ ve __setitem__ metotlarını yazarken index parametresinin bir slice nesnesi olup olmadığını isinstance + fonksiyonu ile kontrol edip dilimleme mantığına uygun işlemleri yapabiliriz. Böylece sınıfımıza dilimleme detseği + vermiş oluruz. + + Aşağıdaki örnekte aslında __getitem__ metodunun index parametresi bir slice nesnesi olsa da zaten args örnek özniteliği + bir liste olduğu için o listede doğrudan kullanılabilirdi. Ancak biz bu örnekte genel olarak bu metotlar içerisinde slice + nesnelerinin fark edilip işleme sokulması hakkında ipucu veriyoruz. +#------------------------------------------------------------------------------------------------------------------------ + + +class Sample: + def __init__(self, *args): + self.args = list(args) + + def __getitem__(self, index): + if isinstance(index, int): + return self.args[index] + + if isinstance(index, slice): + return self.args[index] + + raise TypeError('index invalid type') + + """ + def __getitem__(self, index): + return self.args[index] + """ + + def __setitem__(self, index, value): + self.args[index] = value + + def __len__(self): + return len(self.args) + + def __repr__(self): + return repr(self.args) + +s = Sample(10, 20, 30, 40, 50, 60, 70, 80, 90, 100) +print(s) + +result = s[2:5] +print(result) + +result = s[:5] +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte Date sınıfına köşeli parantez desteği verilmiştir. Yani bu örnekte Date nesnesinin gün, ay, yıl bileşenlerine + sanki onlar bir diziymiş gibi erişebilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __getitem__(self, index): + if isinstance(index, int): + match index: + case 0: + return self.day + case 1: + return self.month + case 2: + return self.year + case _: + raise ValueError('invalid index') + + raise TypeError('invalid index type') + + def __setitem__(self, index, value): + if isinstance(index, int): + match index: + case 0: + self.day = value + case 1: + self.month = value + case 2: + self.year = value + case _: + raise ValueError('invalid index') + else: + raise TypeError('invalid index type') + + def __repr__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + +d = Date(2, 11, 2023) +print(d) + +result = d[0] # result = d.__getitem__(0) +print(result) + +result = d[1] +print(result) # result = d.__getitem__(1) + +result = d[2] +print(result) # result = d.__getitem__(2) + +d[1] = 8 # d.__setitem__(1, 8) + +print(d) + +#------------------------------------------------------------------------------------------------------------------------ + Python'a 3'lü versiyonlarla birlikte "... (Ellipsis)" biçiminde bir sabit de eklenmiştir. Önceleri bu sabit yalnızca + köşeli parantez içerisinde kullanılıyordu. Sonradan genelleştirildi. "..." sabiti aslında Ellipsis isimli anahtar sözcük + olan bir değişkenle de temsil edilmektedir. Başka bir deyişle: + + a = ... + + ile + + a = Ellipsis + + aynı anlamdadır. Tıpkı None sabitinde olduğu gibi Ellipsis sabiti için de toplamda tek bir nesne vardır. Yani program içerisindeki + bütün "..." sabitleri ve Ellipsis değişkeni aslında aynı nesneyi göstermektedir. Bunun için iki Ellipsis nesnesi == ve != + operatörleriyle karşılaştırılabilir ya da benzer biçimde is ve is not operatötleriyle de karşılaştırmalar yapılabilir. Örneğin: + + >>> a = ... + >>> a + Ellipsis + >>> type(a) + + >>> id(a) + 140734384027912 + >>> b = Ellipsis + >>> type(b) + + >>> id(b) + 140734384027912 + >>> a == b + True + >>> a is b + True + >>> ... is Ellipsis + True + + Eskiden Ellipsis değişkeninin türü için Python referans kitaplarında bir belirlemede bulunulmamıştı. Python 3.10 ile + birlikte Ellipsis değişkeninin types modülündeki EllipsisType isimli bir sınıf türünden olduğu kabul edilmiştir. + Bu EllipsisType sınıfının __repr__ ve __str__ metotları "Ellipsis" yazısını vermektedir. + + Pekiyi Ellipsis ne işe yaramaktadır? Aslında Python referans kitabında Ellipsis değerinin ne işe yaradığı konusunda + bir açıklamada bulunulmamıştır. Programcılar ve kütüphaneleri geliştirenler bu Ellipsis değerine kendileri işlevler + yüklemektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python çok modelli (multi-paradigm) bir programalama dilidir. Biz Python'ı istersek sınıf konusuna girmeden tamamen + prosedürel bir biçimde kullanabiliriz. NYPT özellikle büyük projeleri mantıksal bakımdan oluşturabilmek için düşünülmüştür. + Eğer kodlarımız çok uzun ve kapsamlı değilse Python'ın sınıfsal özelliklerini kullanmamıza gerek olmayabilir. Tabii daha + önceden de belirttiğimiz gibi Pythoon'ın standart kütüphanesinde hem fonksiyonlar hem de sınıflar bulunmaktadır. Temel veri + yapıları bile (list, tuple, dict gibi) birer sınıf belirttiğine göre Python programcılarının sınıf kavramını biliyor ve sınıfları + kullanabiliyor olması gerekir. + + Daha önceden de belirttiğimiz gibi NYPT'de olgular sınıflarla temsil edilmektedir. Örneğin tarih olgusu Date isimli bir + sınıfla, Soket olgusu Socket isimli bir sınıfla, çalışan olgusu Employee isimli bir sınıfla temsil edilebilir. Sınıflardaki + metotlar nesnenin özniteliklerini ortak bir biçimde kullanmaktadır. Yani öznitelikler aslında metotlar tarafından ortak + kullanılan veri elemanlarıdır. Ancak bazı fonksiyonları nesnenin özniteliklerini hiç kullanmadıkları halde konu itibari ile + bazı sınıflarla ilgili olabilmektedir. Örneğin tarih işlemlerini yapan Date isimli bir sınıfımız olsun. Bir yılın artık + olup olmadığını veren bir isleap isimli bir fonksiyonun da olduğunu düşünelim. isleap fonksiyonu parametre olarak bir + yıl bilgisini alıp onun artık olup olmadığına yönelik bool bir değer geri döndürüyor olabilir. Örneğin: + + def isleap(year): + return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 + + Buradaki isleap fonksiyonu mantıksal olarak Date sınıfının konusuyla ilgilidir. NYPT'de bu fonksiyonun Date sınıfının + içerisinde bulundurulması iyi bir tekniktir. Ancak biz bu fonksiyonu Date sınıfının içine alırsak ona bir self parametresini + de eklememiz gerekir. Örneğin: + + class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __repr__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + + def isleap(self, year): + return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 + + Burada aslında isleap nesnenin özniteliklerini kullanmadığı halde metot olabilmek için bir self parametresi almak zorunda + kalmıştır. Üstelik artık bizim bu isleap metodunu çağırabilmemiz için metodun kullanmadığı bir nesne yaratmamız gerekir. + Örneğin: + + d = Date(0, 0, 0) + + result = d.isleap(2024) + + O halde bizim şöyle bir özelliğe gereksinimimiz vardır: Bir metot hem bir sınıf içerisinde bulunsun hem dezaten kullanmayacağı + self parametresini almasın. Bu işlem aşağıdaki gibi sağlanamaz: + + class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __repr__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + + def isleap(year): + return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 + + Burada islep metodunda self parametresini silmek bize bir fayda sağlamaz. Çünkü self parametresinin ismi self olmak zorunda + değildir. Metodun ilk parametresi her zaman self parametresi olarak ele alınacaktır. İşte nesne yönelimli programlama + dillerinde bu gereknimin saplanabilmesi için "static metot" kavramı kullanılmaktadır. static metot demek self parametresi + olmayan dolayısıyla nesnenin özniteliklerini kullanmayan ancak konu bakımından sınıfla ilişkili olan metot demektir. + + Python'da bir metodu static yapabilmek için metodun @staticmethod isimli bir dekoratörle dekore edilmesi gerekmektedir. + Dekoratörler konusu izleyen bölümlerde ele alınmaktadır. Bir dekoratör bir fonksiyonu, bir metodu ya da bir sınıfı + dekore edebilir. Dekoratör kullanımının genel biçimi şöyledir: + + @dekoratör_ismi + + Dekoratörler fonksiyonların metotların ve sınıfların hemen üstünde aynı girinti düzeyine sahip biçimde bulundurulmalıdır. + Örneğin: + + @xxx + def foo(): + pass + + O halde islep metodunu aşağıdaki gibi static bir metot haline getirebiliriz: + + class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __repr__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + + @staticmethod + def isleap(year): + return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 + + Artık burada isleap metodunun year parametresi birinci parametre olmasına karşın self anlamında dğeildir. Yani metot artuk + self parametresine sahip değildir. + + Static metotlar nesnenin özniteliklerini kullanmadığına göre onların çağrılmaıs için bir nesneye gereksinim yoktur. İşte + static metotlar sınıf ismi ve metot ismi belirtilerek çağrılırlar. Örneğin: + + result = Date.isleap(2024) + +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __repr__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + + @staticmethod + def isleap(year): + return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 + +result = Date.isleap(2024) + +print('Artık yıl' if result else 'artık yıl değil') + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi biz bir metodun static olup olmaması gerektiğini nasıl anlayabiliriz? İşte eğer bir metot aslında metot olarak + değil de global bir fonksiyon olarak da yazaılabiliyorsa bu metot static bir metot olmaya adaydır. Metodun belli bir + nesnenin özniteliklerini kullanıp kullanmadığına da bakabilirsiniz. Eğer metot nensnein özniteliklerini kullanmıyorsa + static metot yapılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 50. Ders 19/10/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin Date sınıfına o anki tarih bilgini bize bir Date nesnesi olarak veren today isimli bir metot eklemek isteyelim. + Bu metot işletim sisteminden bu tarih bilgisini alıp onu bir Date nesnesi haline getirip bu Date nesnesini bie verecektir. + Söz konusu today metodu belli bir nesnenin özniteliklerini kullanmamaktadır. Bize yeni bir nesne vermektedir. O halde bu + metot static bir metot yapılabilir. Aşağıdaki örneği inceleyiniz. +#------------------------------------------------------------------------------------------------------------------------ + +import datetime + +class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + + def __repr__(self): + return f'{self.day:02d}/{self.month:02d}/{self.year:04d}' + + @staticmethod + def isleap(year): + return year % 400 == 0 or year % 4 == 0 and year % 100 != 0 + + @staticmethod + def today(): + dt = datetime.date.today() + return Date(dt.day, dt.month, dt.year) + +date = Date.today() +print(date) + +#------------------------------------------------------------------------------------------------------------------------ + static metotların normal olarak sınıf ismiyle çağrılması gerekir. Çünkü onlar nesnenin veri elemanlarını (özniteliklerini) + kullanmamaktadır. Ancak ek bir özellikl olarak C++, Java ve Python gibi dillerde static metotların bir nesneyle (yani + sınıf türünden bir değişkenle) çağrılabilmesine de izin verilmiştir. Ancak static metotlar bir değişkenle çağrılsa bile + bu değişken metodun birinci parametresine (self parametresine) aktarılmamaktadır. Bu değişken yanızca sınıfı belirlemektedir. + Örneğin Date sınıfının isleap metodu normalde aşağıdaki gibi çağrılmalıdır: + + result = Date.isleap(2024) + + Ancak elimizde Date sınıfı türünden bir nesne (değişken) varsa biz bu static metodu onunla da çağırabiliriz. Örneğin: + + d = Date(5, 12, 2009) + + result = d.isleap(2024) + + Aslında bu çağrı aşağıdakiyle eşdeğerdir: + + result = type(d).isleap(2024) + + Görüldüğü gibi static metotlar ilgili sınıf türünden değişkenlerle çağrılabiliyorsa da aslında bu değişkenler metot + tarafından kullanılmamaktadır. Üstelik bu çağrı biçimi yanlış anlaşılmalara da zemin hazırlamaktadır. Örneğin: + + result = d.isleap(2024) + + Böyle bir çağrıyı gören kişi isleap metodunun static metot olduğunu anlamayabilir. Bu durumda kodu yanlış anlamlandırabilir. + Halbuki örneğin: + + result = Date.isleap(2024) + + Burada metodun static bir metot olduğu anlaşılmaktadır. + + Sonuç olarak her ne kadar static metotlar ilgili sınıf türünden değişkenlerle çağrılabiliyorsa da bu iyi bir teknik + değildir. Kodu inceleyenleri yanlış yönlendirebilmektedir. C++ ve Java'da da bu özellik vardır. Ancak C# bunun yanlış + anlaşılmalara yol açabileceği nedeniyle bunu yasaklamıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların statik metotlara benzer ismine "sınıf metotları (class methods)" denilen metotları da olabilmektedir. Sınıf + metotları ile statik metotlar tamamen benzer amaçlarla kullanılırlar. Bunların kullanım gerekçeleri ve kullanım biçimleri + tamamen aynıdır. Ancak sınıf metotlarının ekstra bir parametresi bulunmaktadır. Sınıf metotlarının birinci parametreleri + metodun çağrılmasında kullanılan sınıfın type nesnesini almaktadır. Bu parametre geleneksel olarak "cls" biçiminde + isimlendirilmektedir. Sınıf metotları @classmethod isimli dekoratörle dekore edilirler. Örneğin: + + class Sample: + @staticmethod + def foo(): + pass + + @classmethod + def bar(cls): + pass + + def tar(self): + pass + + Burada foo bir static metottur. bar ise bir sınıf metodudur. tar metodu normal bir metottur. bar metodunun cls parametresi + self anlamında değildir. Bu parametreye sınıfın type nesnesi geçirilmektedir. + + Sınıf metotları da ilgili sınıf türünden bir değişkenle çağrılabilmektedir. Ancak bu çağrı yine yanlış anlaşılmalara + yol açtığı için iyi bir teknik değildir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + @classmethod + def foo(cls): + print(cls) # + print(cls is Sample) # True + +Sample.foo() + +s = Sample() +s.foo() + +#------------------------------------------------------------------------------------------------------------------------ + Static metotlar varken sınıf metotlarına neden gereksinim duyulduğunu merak edebilirsiniz. Aslında sınıf metotları static + metotlarle aynı mantığa sahip olsa da static metot yerine sınıf metodunun kullanılması gerektiği yerler olabilmektedir. + Bir static metot türemiş sınıf sınıf ismiyle de çağrılabilmektedir. Örneğin: + + class A: + @staticmethod + def foo(): + pass + + class B(A): + pass + + A.foo() # geçerli + B.foo() # geçerli + + Taban sınıfta bir sınıf metodu bulunyor olabilir. Bu sınıf metodu türemiş sınıf ismiyle çağrılırsa metodun cls parametresine + türemiş sınıfın type nesne referansı, taban sınıf ismiyle çağrılırsa taban sınıfın type nesne referansı geçirilecektir. + Böylece biz bu metodun hangi sınıf ismi ile çağrıldığını anlayıp bazı durumlarda bu bilgiden faydalanabilir. + + Aşağıda örnekte taban sınıftaki foo sınıf metodu static bar metodunu çağırmıştır. Ancak foo sınıf metodu hangi sınıfla + çağrılmışsa onun bar metodunu çağırmak istemektedir. Bu işlem static metotlarla sağlanamaz. Siz özellikle gerekmedikçe + sınıf metodu yerine static metodu tercih etmelisiniz. +#------------------------------------------------------------------------------------------------------------------------ + +class A: + @classmethod + def foo(cls): + print('A.foo') + cls.bar() + + @staticmethod + def bar(): + print('A.bar') + +class B(A): + @staticmethod + def bar(): + print('B.Bar') + +A.foo() +print('-------------') +B.foo() + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi metotlarda alternatif bir çağrı biçimi de bulunmaktadır. Örneğin: + + class Sample: + def foo(self, a, b): + pass + + Biz buaradaki normal foo metodunu normal olarak Sample türünden bir değişkenle çağrırız: + + s = Sample() + s.foo(10, 20) + + Burada s, metodun self parametresine, 10 metodun a parametresine 20 de metodun b parametresine aktarılacaktır. + Bu çağrının alternatif olarak şöyle de yapılabileceğini belirtmiştik: + + s = Sample() + + Sample.foo(s, 10, 20) + + O halde static metotları @staticmethod ile dekore etmeden bu alternatif yöntemle çağıramaz mıyız? Örneğin: + + class Sample: + def foo(a, b): + print(f'a = {a}, b = {b}') + + Sample.foo(10, 20) + + Her ne kadar bu yöntem bu haliyle uygulanabilir bir yöntem gibi görünüyorsa da bu metot Sample sınıfı türünden bir + değişkenle çağrıldığında sorun ortaya çıkacaktır. Örneğin: + + s = Sample() + s.foo(10, 20) # exception oluşur + + Çünkü foo static bir metot olmadığı için buradaki s self parametresine atanacaktır. self parametresinin ise isminin self + olması gerekmemektedir. fonksiyonun a parametresi self gibi ele alınacak ve exception oluşacaktır. Halbuki metodu staticmethod + dekoratörü ile dekore edersek her iki durumda da sorun oluşmayacaktır: + + class Sample: + @staticmethod + def foo(a, b): + print(f'a = {a}, b = {b}') + + s = Sample() + s.foo(10, 20) # sorun yok, s kullanılmayacak + + Sample.foo(10, 20) # sorun yok +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + NYPT'de "dekoratör (decorator)" denilen bir "tasarım kalıbı (design pattern)" vardır. Python'da bu tasarım kalıbı sentaks + bakımından desteklenerek bir dil öğesi haline getirilmiştir. Bu sayede bazı tasarımlar daha özlü bir biçimde yapılabilmektedir. + + Python'da dekoratörler fonksiyonlar, metotlara ve sınıflara uygulanabilmektedir. Bir dekoratör bir fonksiyona ya da metoda uygulanmışsa + bunlara "fonksiyon dekoratörleri", bir sınıfa uygulanmışsa bunlara da "sınıf dekoratörleri" denilmektedir. Bir fonksiyonu ya da sınıfı + dekora edebilmek için fonksiyon ya da sınıfın üstüne aynı girinti düzeyine sahip olacak biçimde @dekoratör_ismi biçiminde bir sentaksın + eklenmesi gerekir. Örneğin: + + @foo + def bar(): + pass + + Burada bar fonksiyonu dekore edilmiştir. Buradaki dekoratör foo'dur. Örneğin: + + @foo + class Sample: + pass + + Burada da Sample sınıfı dekore edilmiştir. Yine buradaki sekoratör foo'dur. + + Bir dekorasyon yaparken @ sembolünün sağındaki ismin "çağrılabilen (callable)" bir nesne belirtmesi gerekir. Yani bu isim + tipik olarak bir fonksiyon ismi olabilir ya da __call__ metodu bulunan bir sınıf nesnesi olabilir. Buradaki çağrılabilen + nesnenin bir parametrenin olması gerekmektedir. Eğer çağrılabilen (callable) nesne bir sınıf nesnesi ise __call_ metodunun + self dışında ekstra bir parametresinin bulunması gerekmektedir. Örneğin: + + def foo(f): + pass + + @foo + def bar(): + pass + + Burada foo dekoratördür. foo fonksiyonunun bir parametresi vardır. O halde kullanım geçerlidir. Örneğin: + + class Sample: + def __call_(self, f): + pass + + @Sample + def bar(): + pass + + Burada da kullanım geçerlidir. Çünkü Sample sınıfının __call_ metodunun self parametresi dışında ekstra bir parametresi + daha vardır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki gibi dekore edilmş bir fonksiyon olsun: + + @foo + def bar(): + pass + + Bunun tamamen eşdeğeri şöyledir. + + def bar(): + pass + bar = foo(bar) + + Yani @ atomunun yanındaki bir fonksiyondur (genel olarak calllable bir nesne). Dekore edilmiş fonksiyon bu fonksiyona + parametre yapılıp yeniden bu fonksiyon ismine atanmıştır. Bu durumda artık dekore edilmiş fonksiyonu çağıran birisi + aslında dekoratör fonksiyonun geri döndürdüğü fonksiyonu çağırmış olacaktır. Yani örneğin: + + @foo + def bar(): + pass + + Burada artık bar ismi buradaki bar fonksiyonunu değil foo fonksiyonun geri döndürüğü fonksiyonu belirtmektedir. Biz bu + dekoratör işleminden sonra bar fonksiyonunu çağırdığımızda artık bar fonksiyonunu çağırmış olmayacağız. foo fonksiyonunun + geri döndürdüğü fonksiyonu çağırmış olacağız. Örneğin: + + def foo(f): + print('foo') + return f + + @foo + def bar(): + print('bar') + + Bu işlemin eşdeğeri şöyledir: + + def foo(f): + print('foo') + return f + + def bar(): + print('bar') + + bar = foo(bar) + + Burada aslında foo fonksiyonu çağrılmış foo fonksiyonunun geri dönüş değeri yine bar değişkenine atanmıştır. bar değişkeni + yine bar fonksiyonunu belirtmektedir. Ancak ekranda "foo" yazısı gözükecektir. + + Örneğin: + + def foo(): + print('foo') + + def bar(f): + print('bar') + return foo + + @bar + def tar(): + pass + + tar() + + Bu örnekte dekoratör fonksiyonu olan bar fonksiyonu foo fonksiyonu ile geri dönmektedir. O halde burada taslında tar + fonksiyonu çağrıldığında foo fonksiyonu çağrılacaktır. Yukarıdaki kodun eşdeğeri şöyledir: + + def foo(): + print('foo') + + def bar(f): + print('bar') + return foo + + def tar(): + pass + tar = bar(tar) + + tar() + + Dekoratör kalıbında programcı dekorasyon işleminden sonra bir fonksiyonu çağırmak istediğinde aslında başka bir + fonksiyonu çağırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('foo') + +def bar(f): + print('bar') + return foo + +@bar +def tar(): + pass + +tar() + +#------------------------------------------------------------------------------------------------------------------------ + Dekoratörlerin en önemli kullanım nedeni "araya girme" işlemini gerçekleştirmektir. Yani biz bir fonksiyon çağırırken + aslında başka bir fonksiyonu çağırırız, o fonksiyon da birşeyler yaptıktan sonra asıl fonksiyonu çağırır. Böylece biz + fonksiyonu her çağırdığımızda aslında bir araya girme işlemi yapılmış olur. Bu araya girme işlemi iki biçimde + gerçekleştirilmektedir: + + 1) İç bir fonsiyon kullanılarak + 2) __call__ metodu bulunan bir sınıf kullanılarak + + Birinci yöntemde dekoratör fonksiyonu iç bir fonksiyn ile geri döner. Böylece asıl fonksiyon çağrıldığında aslında iç + fonksiyon çağrılmış olur. İç fonksiyon dış fonksiyonun yerel değişkenlerini ve parametre değişkenlerini kullanabildiği + için asıl fonksiyonu çağırabilmektedir. Örneğin: + + def foo(f): + def bar(): + print('araya giren kod') + f() + + return bar + + @foo + def tar(): + print('tar') + + tar() + print('---------') + tar() + + + Burada aslında tar fonksiyonu çağrıldığında iç fonksiyon olan bar fonksiyonu çağrılacaktır. Ancak bar fonksiyonu araya + girme işlemini yaptıktan sonra asıl fonksiyon olan tar fonksiyonunu da çağırmaktadır. Böylece bu dekorasyondan sonra + tar fonksiyonu çağrıldığında yine tar fonksiyonu çağrılacaktır. Ancak araya bir kod da girilmiş olacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(f): + def bar(): + print('araya giren kod') + f() + + return bar + +@foo +def tar(): + print('tar') + +tar() +tar() + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki araya girme işleminde bir sorun vardır. Burada dekore edilen fonksiyon (tar fonksiyonu) parametreliyse + sorun çıkacaktır. Çünkü bu fonksiyon bar içerisinde sanki parametresiz gibi çağrılmıştır. Bu sorunu engellemenin basit + yolu bar fonksiyonunun *'lı ve **'lı parametrelerinin olması ve bu parametrelerin de *'lı ve **'lı argümanlarla orijinal + fonksiyona aktarılmasıdır. Örneğin: + + def foo(f): + def bar(*args, **kwargs): + print('araya giren kod') + return f(*args, **kwargs) + + return bar + + @foo + def tar(a, b, c): + print(f'tar: {a}, {b}, {c}') + + Burada tar çağrılırken girilen argümanlar aslında bar fonksiyonuna girilmiş gibi lacaktır. Çünkü yukarıdaki dekorasyonun + eşdeğeri şöyledir: + + tar = foo(tar) + + O halde örneğin biz tar(10, 20, 30) gibi bir çağrı yaptığımızda aslında bar(10, 20, 30) gibi bir çağrı yapmış oluruz. + Bu 10, 20, 30 parametreleri bar fonksiyonunun args parametresine bir demet olarak aktarılacaktır. bar fonksiyonunun kargs + parametresi boş sözlükten oluşacaktır. bar fonksiyonu içerisinde f(*args, **kwargs) çağrımı aslında tar fonksiyonunun + 10, 20, 30 argümanlarıyla çağrımı anlamına gelecektir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(f): + def bar(*args, **kwargs): + print('araya giren kod') + f(*args, **kwargs) + + return bar + +@foo +def tar(a, b, c): + print(f'a = {a}, b = {b}, c = {c}') + +tar(10, 20, 30) + +#------------------------------------------------------------------------------------------------------------------------ + Araya girme işlemi yukarıda da ikinci maddede belirttiğimiz gibi __call__ metodu uygun bir biçimde yazılmış olan bir + sınıf yoluyla da yapılabilir. Sınıflar durumsal bilgiyi tutabildikleri için genel olarak bu yöntem tercih edilmekedir. + + @foo + def bar(a, b, c): + print(f'a = {a}, b = {b}, c = {c}') + + Burada foo'nuın bir sınıf olduğunu düşünelim. Bu durumda yukarıdaki kodun eşdeğeir şöyle olur: + + bar = foo(bar) + + Artık bar bir fonksiyon değil foo sınıfı türünden bir nesne belirtmektedir. O halde biz artık bar(...) + çağrısını yaptığımızda foo sınıfının __call__ metodu çağrılacaktır. + + class foo: + def __init__(self, f): + self.f = f + + def __call__(self, *args, **kwargs): + print('araya girilen kod') + self.f(*args, **kwargs) + + @foo + def bar(a, b, c): + print(f'a = {a}, b = {b}, c = {c}') + + bar(10, 20, 30) + + foo sınıfının __init__ metodu içerisinde alınan fonksiyonun nesnenin örnek özniteliğinde saklandığına dikkat ediniz. + Sonra bu fonksiyon __call__ metodunda çağrılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class foo: + def __init__(self, f): + self.f = f + + def __call__(self, *args, **kwargs): + print('araya girilen kod') + self.f(*args, **kwargs) + +@foo +def bar(a, b, c): + print(f'a = {a}, b = {b}, c = {c}') + +bar(10, 20, 30) +print('---------------') +bar(40, 50, 60) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi dekoratörler araya girmeyi sağlıyorsa araya girmenin na anlamı olabilir? İşte araya giren kod arka planda bizim + için bazı faydalı işlemleri yapıyor olabilir. Örneğin araya giren kod bir log oluşturabilir ya da faydalı başka şeyleri + yapıyor olabilir. Aşağıdaki örnekte counter isimli dekoratör sınıfı fonksşyon her çağrıldığında count sınıf değişkenini + (class attribute) 1 artırmaktadır. Böylece counter.count ifadesi ile biz dekore edilen fonksiyonun program çalışırken + kaç kere çağrılmış olduğunu anlayabiliriz. Örneğin: + + class counter: + count = 0 + + def __init__(self, f): + self.f = f + + def __call__(self, *args, **kwargs): + counter.count += 1 + self.f(*args, **kwargs) + + @counter + def foo(): + print('foo') + + foo() + foo() + foo() + foo() + foo() + + print(counter.count) # 5 + + Burada foo fonksiyonunu çağıracak kişinin tek yapacağı şey fonksiyonunu counter sınıfıyla dekore etmektir. +#------------------------------------------------------------------------------------------------------------------------ + +class counter: + count = 0 + + def __init__(self, f): + self.f = f + + def __call__(self, *args, **kwargs): + counter.count += 1 + self.f(*args, **kwargs) + +@counter +def bar(): + print('bar') + +bar() +bar() +bar() +bar() +bar() + +print(counter.count) # 5 + +#------------------------------------------------------------------------------------------------------------------------ + Tabii dekorasyonun illa da @ atomuyla yapılması gerekmez. Biz dekorasyonla eşdeğer olan çağrıyı kendimiz de yapabiliriz. + Özellikle bizim yazmadığımız sınıfları dekore etmek için @ sentaksı yerine açıkça çağırma sentaksı kullanılmaktadır. + Örneğin: + + import math + + class abs_decorator: + def __init__(self, f): + self.f = f + + def __call__(self, val): + val = abs(val) + return self.f(val) + + sqrt = abs_decorator(math.sqrt) + + result = sqrt(10) + print(result) + + result = sqrt(-10) + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin daha önce görmüş olduğumuz @staticmethod ve @classmethod dekoratörleri de aslında built-in birer çarılabilir (callable) + nesnelerdir. Dolayısıyla biz bunları @ sentaksını kullanmadan alternatif biçimde de kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + @staticmethod + def foo(): + print('foo') + + def bar(): + print('bar') + bar = staticmethod(bar) + +Sample.foo() +Sample.bar() + +#------------------------------------------------------------------------------------------------------------------------ + 51. Ders 24/10/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Görüldüğü gibi dekoratörler aslında arka planda birtakım işlemlerin pratik bir biçimde yapılması için araç bir oluşturmaktadır. + Örneğin bir dekoratör fonksiyonun çalışma zamanını ölçerek geri dönüş değeri biçiminde bize verebilir: + + import time + + class profiler: + def __init__(self, f): + self.f = f + + def __call__(self, *args, **kwargs): + start = time.time() + result = self.f(*args, **kwargs) + stop = time.time() + + return stop - start, result + + @profiler + def add_total(n): + total = 0 + for i in range(n): + total += i + + return total + + rtime, result = add_total(100000000) + print(result, rtime) + + Burada herhangi bir fonksiyon profiler isimli dekoratörle dekore edildiğinde artık fonksiyon bir demete geri döner. + Demetin birinci elemanı fonksiyonun çalışma zamanını, ikinci elemanı da fonksiyonun geri dönüş değerini belirtmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import time + +class profiler: + def __init__(self, f): + self.f = f + + def __call__(self, *args, **kwargs): + start = time.time() + result = self.f(*args, **kwargs) + stop = time.time() + + return stop - start, result + +@profiler +def add_total(n): + total = 0 + for i in range(n): + total += i + + return total + +rtime, result = add_total(100000000) +print(result, rtime) + +rtime, result = add_total(10000000) +print(result, rtime) + +#------------------------------------------------------------------------------------------------------------------------ + Dekoratörler fonksiyonların yanı sıra sınıf tanımlamalarında da kullanılabilmektedir. Bunlara sınıf dekoratörleri + denilmektedir. Sınıf dekoratörlerinin genel biçimi şöyledir: + + @dekoratör_ismi + class sınıf_ismi: + pass + + Sınıf dekoratörlerinde de dekoratör yine bir fonksiyon ya da sınıf olabilir. Mekanizmanın çalışma biçimi aynıdır. Yani: + + @foo + class bar: + pass + + işleminin eşdeğeri şöyledir: + + class bar: + pass + + bar = foo(bar) + + Örneğin: + + def foo(cls): + print('araya giren kod') + return cls + + @foo + class Sample: + pass + + Buradaki kodun eşdğeri şöyledir: + + def foo(cls): + print('araya giren kod') + return cls + + class Sample: + pass + + Sample = foo(Sample) + + Burada foo fonksiyonu çağrıldığında ekrana "araya giren kod" yazısı çıkacaktır. foo fonksiyonunun paramatresiyle aldığı + aynı sınıf ile geri döndüğüne dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(cls): + print('araya giren kod') + return cls + +@foo +class Sample: + pass + +#------------------------------------------------------------------------------------------------------------------------ + Sınıf dekoratörleri sayesinde örneğin biz bir sınıf türünden nesne yaratıldığında araya girip bir şeyler yapabiliriz. + Genellikle burada araya giren kodlar nesne üzerinde bazı öznitelikleri de arka planda yaratmaktadır. + + Aşağıdaki örnekte Sample sınıfı türünden bir nesne yaratıldığında bar fonksiyonu çağrılmaktadır. Sample nesnesi bar + fonksiyonu tarafından yaratılmaktadır. Yani Sample(...) çağrısı salında bar(...) çağrısı anlamına gelmektedir. Bu + bar(...) çağrısı araya girme işlemini yapmaktadır. + + def foo(cls): + + def bar(*args, **kwargs): + obj = cls(*args, **kwargs) + print('araya giren kod') + + return obj + + return bar + + @foo + class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + def __repr__(self): + return f'a = {self.a}, b = {self.b}' + + s = Sample(10, 20) + k = Sample(30, 40) + + print(s) + print(k) +#------------------------------------------------------------------------------------------------------------------------ + +def foo(cls): + + def bar(*args, **kwargs): + obj = cls(*args, **kwargs) + print('araya giren kod') + + return obj + + return bar + +@foo +class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + def __repr__(self): + return f'a = {self.a}, b = {self.b}' + +s = Sample(10, 20) +k = Sample(30, 40) + +print(s) +print(k) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii yukarıdaki örnekte dekoratör olarak iç fonksiyon kullanmak yerine dekoratör bir sınıf da kullanılabilirdi. Yukarıda + belirttiğimiz gibi genellikle dekoratör olarak sınıfların kullanılması tercih edilmektedir. Örneğin: + + class foo: + def __init__(self, cls): + self.cls = cls + + def __call__(self, *args, **kwargs): + obj = self.cls(*args, **kwargs) + print('araya giren kod') + + return obj + + @foo + class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + def __repr__(self): + return f'a = {self.a}, b = {self.b}' + + Buradaki dekoratörün eşdeğeri aşağıdaki gibidir: + + Sample = foo(Smaple) + + Dolayısıyla Sample türünden nesne yaratan kişi aslında foo sınıfının __call__ metodunu çağırmaktadır. Orada da + Sample sınıfı türünden nesne yaratılıp araya girme işlemi yapılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +class foo: + def __init__(self, cls): + self.cls = cls + + def __call__(self, *args, **kwargs): + obj = self.cls(*args, **kwargs) + print('araya giren kod') + + return obj + +@foo +class Sample: + def __init__(self, a, b): + self.a = a + self.b = b + + def __repr__(self): + return f'a = {self.a}, b = {self.b}' + +s = Sample(10, 20) +k = Sample(30, 40) + +print(s) +print(k) + +#------------------------------------------------------------------------------------------------------------------------ + Bu tür sınıf dekoratörleri bazı framework'ler tarafından bazı karmaşık işlemleri yapmak için kullanılabilmektedir. + Örneğin bir dekoratörün sınıf içerisine gizlice bir metot eklemesi, sınıf türünden nesnelerin içerisine özniteliklerin + eklenmesi sık karşılaşılan durumlardandır. Örneğin biz bir sınıfa test isimli bir metot eklemek isteyelim ve sınıf + türünden her nesneye de count isimli bir özitelik eklemek isteyelim. Bunu şöyle yapabiliriz: + + def test(self): + print('test') + + class foo: + def __init__(self, cls): + self.cls = cls + cls.test = test + + def __call__(self, *args, **kwargs): + obj = self.cls(*args, **kwargs) + print('araya giren kod') + + return obj + + @foo + class Sample: + pass + + s = Sample() + s.test() + + Burda test fonksiyonun bir parametresinin olduğuna dikkat ediniz. Okunabilirliği artırmak için biz bu parametresyi self + biçiminde isimlendirdik. Burada test fonksiyonu aslında Sample sınıfına bir metot gibi eklenmektedir. O yüzden bu + self parametresini almıştır. +#------------------------------------------------------------------------------------------------------------------------ + +def test(self): + print('test') + +class foo: + def __init__(self, cls): + self.cls = cls + cls.test = test + + def __call__(self, *args, **kwargs): + obj = self.cls(*args, **kwargs) + print('araya giren kod') + + return obj + +@foo +class Sample: + pass + +s = Sample() +s.test() + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örnekler bu tür temalarla ilk kez karşılaşanlara kavramsal olarak karmaşık ve biraz da soyut gelmektedir. + Bu konuyla ilgili olan diğer ileri bir konu da "meta sınıflar (meta classes)" konusudur. Biz bu "meta sınıf" konusunu + Python Uygulamaları kursunda göreceğiz. Bu nispeten soyut konunun kişiler tarafından anlaşılması bazı konuları gördükten + ve uygulamaları yaptıktan sonra daha kolay olacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dekoratörler parametre de alabilmektedir. Örneğin log isimli bir dekoratör olsun. Bu dekoratör bir fonksiyon çağrıldığında + çağrılma zamanını, fonksiyonun çalışma zamanını bir log dosyasına yazıyor olsun. ama onun yazacağı dosyayı biz belirleyecek + olalım. İşte bu tür durumlarda dekoratörlerin parametre alması gerekebilmektedir. Örneğin: + + @log('test.log') + def foo(): + pass + + Parametreli dekoratörler aslında çağrılabilir (callable) bir nesnenin çağrısı sonucunda elde edilen çağrılabilir nesneyi + dekoratör olarak kullanmaktadır. Başka bir deyişle örneğin: + + @foo(a, b, c) + def bar(): + pass + + Tanımlamasının eşdeğeri şöyledir: + + bar = foo(a, b, c)(bar) + + Burada aslında foo nesnesi çağrılmış bunun geri dönüş değeri yeniden çağrılmıştır. Yani parametreli dekoratörün + çağrılması sonucunda yine bir çağrılabilir nesne elde edeilmelidir. Buradaki işlemi adım adım yeniden çözümleyelim: + + 1) Burada önce foo nesnesi ile çağırma yapılmıştır: + + temp1 = foo(a, b, c) + + 2) Bu çağrının sonucunda çağrılabilir bir nesne elde edilmelidir. Bu çağrılabilir nesne fobaro argüman yapılarak çağrılmıştır: + + temp2 = temp1(bar) + + 3) Bu nesne yeniden bar değişkenine atanmıştır: + + bar = temp2 + + Dolayısıyla burada artık bar fonksiyonunu çağırdığını sanan kişi aslında foo çağrısı sonucunda elde edilen nesnenin çağrısı + sonucunda elde edilen çağrılabilir nesneyi çağırmaktadır. + + Parametreli dekoratör yine iç fonksiyonlar yoluyla ya da sınıflar yolula gerçekleştirilebilir. İç fonksiyon yoluyla + gerçekleştirim yapılacaksa iç içe üç fonksiyon kullanılmalıdır. Örneğin: + + @foo(a, b, c) + def bar(): + pass + + Buradaki foo fonksiyonu şöyle tanımlanabilir: + + def foo(x, y, z): + def wrapper1(f): + def wrapper2(*args, **kwargs): + return f(*args, **kwargs) + return wrapper2 + return wrapper1 + + Aşağıdaki örnekte parametreli foo isimli dekoratör fonksiyonu iç fonksiyonlar kullanılarak yazılmıştır. Kodu çalıştırdıktan + sonra ekranda şu yazıları göreceksiniz: + + foo called + wrapper1 + wrapper2: 10, 20, 30 + bar + wrapper2: 10, 20, 30 + bar + + Örneğimizde aşağıdaki gibi bir dekorasyon yaapılmıştır: + + @foo(10, 20, 30) + def bar(): + print('bar') + + Bu dekorasyonun tamamen eşdeğeri şöyledir: + + def bar(): + print('bar') + + bar = foo(10, 20, 30)(bar) + + Burada önce foo fonksiyonu 10, 20, 30 argümanlarıyla çağrılmıştır. Bu çağrı ekrana "foo called" yazısını basacaktır. + Bu çağrıdan wrapper1 fonksiyonu ile dönülmüştür. Dolaysıyla bu geri döndürülen fonksiyon bar argümanıyla çağrılmıştır. + Şimdi ekrana "wrapper1" yazısı da basılacaktır. Bu fonksiyon da wrapper2 fonksiyonuyla geri döndürülmüş ve nihayetinde + wrapper2 fonksiyonu bar değişkenine atanmıştır. Artık bar fonksiyonunu çağırdıını sanan kişi aslında wrapper2 fonksiyonunu + çağırmış olmaktadır. İki kez bar çağrıldığından dolayı ekrana şunlar basılacaktır: + + foo called + wrapper1 + wrapper2: 10, 20, 30 + bar + wrapper2: 10, 20, 30 + bar + +#------------------------------------------------------------------------------------------------------------------------ + +def foo(x, y, z): + print('foo called') + def wrapper1(f): + print('wrapper1') + def wrapper2(*args, **kwargs): + print(f'wrapper2: {x}, {y}, {z}') + return f(*args, **kwargs) + return wrapper2 + return wrapper1 + +@foo(10, 20, 30) +def bar(): + print('bar') + +""" +Eşdeğeri +foo = foo(10, 20, 30)(bar) +""" + +bar() +bar() + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte her ne kadar henüz dosya işlemlerini görmemiş olsak da fonksiyon çağrıldıkça bir dosyaya log bilgilerini + yazan parametreli bir dekoratör örneği verilmiştir. Örnekte henüz görmediğimiz bazı modülleri ve fonksiyonları kullandık. + Burada log bilgisi olarak fonksiyonun çağrılma zamanı ve çalışma süresi kaydedilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import datetime +import time + +def log(path): + def wrapper1(f): + def wrapper2(*args, **kwargs): + file = open(path, 'a+', encoding='utf-8') + now = datetime.datetime.now() + file.write(f'Call time: {now}' + '\n') + + start = time.time() + retval = f(*args, **kwargs) + stop = time.time() + + file.write(f'Execution time: {stop - start}' + '\n') + file.write('-----------------------------------\n') + file.close() + return retval + return wrapper2 + return wrapper1 + +@log('log.txt') +def bar(): + for i in range(100000000): + pass + +bar() +time.sleep(2) +bar() + +#------------------------------------------------------------------------------------------------------------------------ + Parametreli dekoratörler de bir sınıf biçiminde yazılabilirler. Bu durumda dekoratör bir sınıf olur. Dolayısıyla dekorasyon + işlemi bir sınıf türünden nesnenin yaratılmasına yol açacaktır. İlgili sınıf çağrılabilir (callable) bir sınıftır. Dolayısıyla + elde edilen nesneyle çağrı uygulandığında aslında sınıfın __call__ metodu çağrılır. Bu metot da bize bir metot verir. Böylece + asıl fonksiyonu çağırdığını sanan kişi aslında söz konusu sınıfının bir metodunu çağırmaktadır. Örneğin: + + @foo(10, 20, 30) + def bar(): + pass + + Bu dekorasyonun eşdeğeri şöyledir: + + def bar(): + pass + + bar = foo(10, 20, 30)(bar) + + Burada şu açıklamaları yapabiliriz: + + 1) Eğer foo bir sınıf ise foo(10, 20, 30) çağrısı ile aslında foo sınıfı türünden bir nesne yaratılacaktır. Dolayısıyla + 10, 20 ve 30 foo sınıfının __init__ metoduna parametre olarak aktarılacaktır: + + class foo: + def __init__(self, x, y, x): + self.x = X + self.y = y + self.z = z + + 2) Elde edilen foo nesnesi ile foo(10, 20, 30)(bar) çağrısı yapılmıştır. Bu durumda sınıfın __call__ metodu çağrılacaktır: + + class foo: + def __init__(self, x, y, x): + self.x = X + self.y = y + self.z = z + + def __call__(self, f): + self.f = f + return self.proc + + Görüldüğü gibi burada biz bar fonksiyonunu da nesnenin içerisinde saklayabildik. __call__ metodunun da sınıfın bir metodu + ile geri döndüüğüne dikkat ediniz. + + 3) foo(10, 20, 30)(bar) çağrısı sonucunda geri döndürülen metot yine bar değişkenine atanmıştır. Artık bar çağrıldığında + aslında sınıfın proc metodu çağrılacaktır. Böylece artık biz bar fonksiyonunu çağırdığımızda bu fonksiyon kaydedieln bütün + bilgilere sekf yoluyla erişebilecektir: + + class foo: + def __init__(self, x, y, x): + self.x = X + self.y = y + self.z = z + + def __call__(self, f): + self.f = f + return self.proc + + def proc(self, *args, **kwargs): + print(f'Ayara giren kod: {self.x}, {self.y}, {self.z}') + return self.f(*args, **kwargs) + + Örnek bir bütün olarak aşağıda verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class foo: + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def __call__(self, f): + self.f = f + return self.proc + + def proc(self, *args, **kwargs): + print(f'Ayara giren kod: {self.x}, {self.y}, {self.z}') + return self.f(*args, **kwargs) + +@foo(10, 20, 30) +def bar(): + print('bar') + +bar() +bar() + +#------------------------------------------------------------------------------------------------------------------------ + Dekoratör olarak sınıf kullanarak yukarıdaki log örneğini de aşağıdaki gibi yapabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +import datetime +import time + +class log: + def __init__(self, file): + self.file = file + + def __call__(self, f): + self.f = f + return self.proc + + def proc(self, *args, **kwargs): + now = datetime.datetime.now() + self.file.write(f'Call time: {now}' + '\n') + + start = time.time() + retval = self.f(*args, **kwargs) + stop = time.time() + + file.write(f'Execution time: {stop - start}' + '\n') + file.write('-----------------------------------\n') + + return retval + +file = open('log.txt', 'w') + +@log(file) +def bar(): + for i in range(100000000): + pass + +""" +bar = log(f)(bar) +""" + +bar() +time.sleep(2) +bar() + +file.close() + +#------------------------------------------------------------------------------------------------------------------------ + Sonuç olarak biz bir fonksiyonun dekore edildiğini gördüğümüzde şunu düşünmeliyiz: "Bu fonyksiyonu çağırdığımda aslında + ben muhtemelen başka bir fonksiyonu çağırmış olacağım. Ama o fonksiyon da benim fonksiyonumu çağıracak. Fakat bu arada + benim faydama bazı şeyler de arka planda yapılmış olacak". Benzer biçimde bir sınıf dekoratörünü gördüğümüzde de + şunu düşünmeliyiz: "Bu sınıfa benim eklediklerimden başka şeyler de ekleniyor olabilir. Ben bu sınıf türünden nesne + yarattığımda arka planda başka şeyler de yapılıyor olabilir. Örneğin yarattığım nesneye bazı öznitelikler de ekleniyor + olabilir." +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 52. Ders 26/10/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İngilizce "exception" sözcüğü "istisna" anlamına gelmektedir. Ancak bu sözcük yazılımda "programın çalışma zamanı sırasında + oluşan problemli durumları" anlatmak için kullanılmaktadır. Exception mekanizması genel olarak nesne yönelimli programlama + dillerinde bulunmaktadır. Prosedürel dillerin çoğunda bu mekanizma yoktur. Exception programın çalışma zamanına ilişkin + bir kavramdır. Etimolojik kökeni donanımsal sorunlara dayanmaktadır. + + Bir exception oluştuğunda exception'ın ele alınması (handle edilmesi) gerekir. Eğer exception ele alınmazsa program çöker. + Python'da exception'ların birer sınıf ismi vardır. Bu isimler XXXError biçimindedir (örneğin TypeError, IndexError, ValueError gibi). + Örneğin: + + >>> int('ali') + Traceback (most recent call last): + File "", line 1, in + ValueError: invalid literal for int() with base 10: 'ali' + >>> 10 + 'ali' + Traceback (most recent call last): + File "", line 1, in + TypeError: unsupported operand type(s) for +: 'int' and 'str' + >>> a = [1, 2, 3] + >>> a[10] + Traceback (most recent call last): + File "", line 1, in + IndexError: list index out of range + + Bir exception oluştuğunda programın çökmemesi için exception'ın ele alınması (handle edilmesi) gerekir. Böylece hatalı bir + durum oluştuğunda programcı akış başka bir biçimde devam ettirebilir ya da hatayı düzeltip programın devam etmesini sağlayabilir. + Programcı exception'ı yakaladığında bunlardan hiçbirini yapamıyor olsa bile programın düzgün bir biçimde birtakım geri alım + işlemlerini yaparak sonlandırabilir. + + Bir exception oluştuğunda programcının devreye girmesine "exception'ın yakalanması" da denilmektedir. Dillerin çoğunda + exception'ın ortaya çıkmasına "exception'ın fırlatılması (throwing)" denilmektedir. Python'da bu bağlamda "fırlatma (throw) + yerine "raise etme" terimi kullanılmaktadır. + + Python'da exception'ların ele alınması için "try", "except", "else", "finally" ve "raise" anahtar sözcükleri kullanılmaktadır. + try deyiminin genel biçimi şöyledir: + + try: + + try deyimi tek başına bulundurulamaz. try deyimini except blokları ya da finally bloğu izlemek zorundadır. except ve finally + bloklarının genel biçimi şöyledir: + + except [ [as ]]: + finally: + + try bloğunu bir ya da birden fazla except bloğu izleyebilir. Ya da try bloğunu hiç except bloğu olmadan finally bloğu + da izleyebilir. Eğer try bloğunu except bloğu izliyorsa finally bloğu isteğe bağlı olarak except bloklarının sonuna + yerleştirilebilir. except anahtar sözcüğünün yanında bir exception sınıf ismi bulunur. Eğer birden fazla exception + sınıf ismi bulundurulacaksa parantezler içerisinde demet sentaksıyla bu sınıfların belirtilmesi gerekir. Exception sınıf + isimlerinden sonra isteğe bağlı olarak "as" anahtar sözcüğü ve değişken ismi getirilebilir. except bloklarından sonra istenirse + bir else bloğu da bulundurulabilmektedir. Bu durumda try blokları şu biçimlerde oluşturulabilir: + + 1) try bloğundan sonra bir grup except bloğu bulunabilir. + 2) try bloğundan sonra bir grup except bloğu ve en sonunda finally bloğu bulunabilir. + 3) try bloğundan sonra except bloğu olmadan hemen finally bloğu bulunabilir. + 4) try bloğundan sonra ve except bloklarından sonra ancak finally bloğundan önce bir else bloğu da bulunabilir. + + try anahtar anahtar sözcüğünden sonra, except cümleciğinden sonra, else anahtar sözcüğünden sonra ve finnfinally anahtar + sözcüğünden sonra bunları bir "suit" izlemek zorundadır. Biz burada suit yerine "blok" terimini kullanacağız. Örneğin + "try bloğu" dediğimizde "try" anahtar sözcüğü ve bir "suit" anlaşılmaıdır. + + except blokları oluşturulurken beş seçenek söz konusudur: + + 1) except anahtar sözcüğünü bir exception sınıf ismi izleyebilir. Örneğin: + + except ValueError: + + + 2) except anahtar sözcüğünü bir exception sınıf ismi ve "as" anahtar sözcüğü ile bir değişken ismi izleyebilir. Örneğin: + + except ValueError as e: + + + 3) except anahtar sözcüğünü parantezler içerisinde (yani demet sentaksıyla) bir ya da birden fazla exception sınıf ismi + izleyebilir. Örneğin: + + except (ValueError, TypeError): + + + 4) except anahtar sözcüğünü parantezler içerisinde (yani demet sentaksıyla) bir ya da birden fazla exception sınıf ismi + izleyebilir. Bunu da as anahtar sözcüğü ile bir değişken ismi izleyebilir. Örneğin: + + except (ValueError, TypeError) as e: + + + 5) except anahtar sözcüğünü bir şey izlemeyebilir. Örneğin: + + except: + + + Yukarıda da belirttiğimiz gibi finally bloğu bulundurulacaksa her zaman en sonda bulundurulmak zorundadır. Örneğin: + + try: + pass + except IndexError: + pass + except ValueError: + pass + finally: + pass + + Aşağıdaki sentaks da geçerlidir: + + try: + pass + finally: + pass + + Tabii finally bloğu da bulunmak zorunda değildir: + + try: + pass + except IndexError: + pass + + Ancak yalnızca try bloğu bulunamaz. except bloklarını bir else bloğu da izleyebilmektedir. Örneğin: + + try: + pass + except IndexError: + pass + except ValueError: + pass + else: + pass + finally: + pass + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Exception mekanizması şöyle çalışlmaktadır: Programın akışı try bloğuna girdikten sonra bir exception kontrolü uygulanır. + Akış try bloğuna girdikten sonra herhangi bir yerde exception oluşursa akış bir goto işlemi gibi oluşan exception'ın + türüne uygun olan except bloğuna aktarılır. İlgili except bloğu çalıştırılır, diğer except blokları atlanır. Akış except + bloklarının sonundan devam eder. Eğer programın akışı try bloğuna girdikten sonra hiç exception oluşmazsa akış try + bloğunun sonuna geldiğinde except blokları atlanır ve akış except bloklarının sonundan devam eder. Yani except blokları + "exception oluşursa çalıştırılmaktadır". Exception oluşmazsa onların bir işlevi kalmaz. Bir exception oluştuğunda akış + o exception ile aynı türden except bloğuna aktarılmaktadır. except bloklarının yalnızca bir tanesinin çalıştırıldığına + dikkat ediniz. (Yani bunlar adeta match/case gibi işlem görmektedir). Exception nerede oluşursa oluşsun akış except + bloğuna aktarıldıktan sonra bir daha geri dönmez. except bloklarının sonundan devam eder. finally bloğu ve parametresiz + except bloğu daha ileride ele alınacaktır. Bir exception oluştuğunda bu exception'ın türüne uygun bir except bloğunun + bulunyor olması gerekir. Aksi takdirde yine program çökecektir. +#------------------------------------------------------------------------------------------------------------------------ + +def tar(): + print('tar başladı') + int('xxxxx') # ValueError oluşacak + print('tar bitti') + +def bar(): + print('bar başladı') + tar() + print('bar bitti') + +def foo(): + print('foo başladı') + bar() + print('foo bitti') + +try: + foo() +except ValueError: + print('ValueError yakalandı') +print('program devam ediyor') + +#------------------------------------------------------------------------------------------------------------------------ + Python'da en çok karşılaşılan altı Exception sınıfı vardır: ValueError, TypeError, IndexError, KeyError, NameError ve + AttributeError. Bir fonksiyonun ya da metodun parametresi uygun girilmemişse genel olarak ValueError oluşmaktadır. Ancak + bir fonksiyona argüman olarak yanlış bir tür verilmişse ya da bir işlemde türler uygunsuz ise TypeError oluşmaktadır. + Örneğin int('xxx') işlemi ValueError oluşturur. Çünkü burada int fonksiyonu str türünden bir argüman alabilir. Ama + aldığı argümanın içeriği hatalıdır. Fakat örneğin z bir complex nesnesi olmak üzere int(z) gibi bir işlem TypeError + oluşturur. Çünkü int fonksiyonu argüman olarak complex türünden bir nesne alamaz. Yani tür uygun ancak içerik uygun değilse + ValueError, tür uygun değilse TypeError oluşmaktadır. Liste, demet, str gibi indekslenebilir türden nesnelerde indeksin + uygun bir değer belirtmemesi durumunda (örneğin 10 elemanlı bir listede 20'inci elemana erişmek istediğimiz bir durumda) + IndexError oluşmaktadır. Sözlük tarzı bir veri yapısında köşeli parantezler içerisinde anahtar verildiğinde böyle bir anahtar + bulunamadıysa KeyError oluşmaktadır. Bir değişken kullanıldığında henüz o değişken yaratılmamışsa NameError oluşmaktadır. + Bir sınıf türünden değişken nokta operatörüyle kullanıldığında o değişken yaratılmış olabilir ama nokta operatörünün + sağındaki öznitelik yaratılmamış olabilir. Bu durumda ise AttributeError exception'ı oluşmaktadır. Yani a.b gibi bir ifadede + a yoksa NameError ancak a var fakat b yoksa AttributeError oluşmaktadır. Tüm exception sınıfları built-in sınıflardır. + Yani hiçbir şeyi import etmeden bu exception sınıflarını kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte klavyeden bir sayı okunmak istenmiştir. Kullanıcı sayı olarak yanlış bir giriş yapmışsa sayı yeniden + istenmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +while True: + try: + val = int(input('Bir sayı giriniz:')) + print(val * val) + break + except ValueError: + print('Girdiğiniz sayı geçersiz!') + +#------------------------------------------------------------------------------------------------------------------------ + Şimdiye kadar biz hep başkaları tarafından oluşturlan exception'ları yakalamaya çalıştık. Aslında biz de exception + oluşturabiliriz. + + Exception'ı oluşturan asıl deyim raise deyimidir. Exception'lar kendiliğinde oluşmaz. Exception'ı oluşturmk için raise + anahtar sözcüğünü kullanmak gerekir. raise deyiminin genel biçimi şöyledir: + + raise + + raise anahtar sözcüğünün yanında bir exception nesnesi bulunmalıdır. Bir problem ortaya çıktığında programcı bir exception + nesnesi oluşturur. Probleme ilişkin bazı bilgileri eğer gerekiyorsa o nesnenin örnek özniteliklerine yazar ve o nesneyle + raise işlemi yapar. C++ gibi bazı programlama dillerinde bu tür işlemlerde herhangi türden nesneler kullanılabilmektedir. + Ancak Java gibi, C# gibi, Python gibi dillerde exception nesneleri özel sınıflardan türetilmiş olan sınıflar türünden + olmak zorundadır. İzleyen pragraflarda bunun ayrıntıları ele alınacaktır. + + Daha önce sözünü ettiğimiz ValueError, TypeError, IndexError gibi exception sınıflarının __init__ metorları bizden + bir yazıyı parametre olarak almaktadır. Eğer exception yakalanmazsa program çökerken bu yazı da ekrana bastırılmaktadır. + Örneğin: + + ve = ValueError('değer negatif olamaz') + raise ve + + Burada ValueError türünden bir nesne ile raise işlemi yapılmıştır. Tabii bu işlem aslında tek bir satırla da yapılabilirdi: + + raise ValueError('değer negatif olamaz') + + Aşağıdaki örnekte foo fonksiyonu negatif parametreleri kabul etmemektedir. Eğer fonksiyon negatif bir değerle çağrılırsa exception + oluşturmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a): + print('foo başladı') + if a < 0: + raise ValueError('parametre negatif olamaz') + print('foo bitti') + +try: + foo(1) + print('her şey yolunda') +except ValueError: + print('hata oluştu') +print('program bitiyor') + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin parametresiyle aldığı sayının karekökünü ekrana yazdıran bir disp_sqrt isimli bir fonlsiyon yazmak isteyelim. + Bu fonksiyon negatif değerleri kabul etmemelidir. Çünkü negatif değerlerin gerçek kökleri yoktur. O halde biz fonksiyonun + parametresini kontrol edip eğer değer negatif ise ValueError ilse raise işlemi yapabiliriz. Benzer biçimde bu fonksiyonun + parametresinin float ya da int türdne olması anlamlıdır. Biz bu fonksiyon içerisinde parametrenin türünü de isinstance + fonksiyonu ile kontrol edip duruma göre TypeError türünden bir nesne ile raise işlemi yapabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +def disp_sqrt(val): + if not isinstance(val, float|int): + raise TypeError('parameter must be float or int') + if val < 0: + raise ValueError('value must not be negative') + print(val ** 0.5) + +try: + disp_sqrt(10) + disp_sqrt(100) + disp_sqrt('ali') +except ValueError: + print('fonksiyon yanlışlıkla negatif bir değerle çağrıldı') + +print('son...') + +#------------------------------------------------------------------------------------------------------------------------ + raise deyiminde yalnızca exception sınıfının ismi de yazılabilir. Bu durumda yorumlayıcı bu sınıf türünden nesneyi yaratıp + o nesne ile raise işlemi yapmaktadır. Yani örneğin: + + raise ValueError + + ile + + raise ValueError() + + tamamen aynı anlamdadır. raise anahtar sözcüğünün yanında exception sınıf ismi görürseniz şaşırmayınız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'daki ValueError gibi, TypeError gibi, IndexError gibi standart Exception sınıflarının hepsi doğrudan ya da dolaylı + olarak Exception isimli bir sınıftab türetilmiş durumdadır. Bu Exception sınıfı da BaseException isimli bir sınıftan + türetilmiştir. Yani Python'daki exception sınıf hiyerarşisi tipik olarak şöyledir: + + BaseException + Exception + ValueError TypeError AttributeError LookupError .... + KeyError IndexError + + Tüm bu built-in exception sınıflarının hepsinin __init__ metodu *args parametrelidir. Bunlar kullanıcıdan aldıkları + argümanları taban sınıfın __init__ metodu yoluyla taban sınıfa ilettirler. En tepedeki BaseException sınıfı da bu + argümanları args isimli örnek özniteliğinde saklamakltadır. Yani bu sınıfların __init__ metotları aşağıdaki gibi + yazılmış durumdadır: + + class BaseException: + def __init__(self, *args): + self.args = args + + class Exception(BaseException): + def __init__(selfs, *args): + super().__init__(*args) + + class ValueError(Exception): + def __init__(selfs, *args): + super().__init__(*args) + + Örneğin: + + >>> ve = ValueError('Parameter may not be negative', 123, 456) + >>> ve.args + ('Parameter cannot be negative', 123, 456) + + Programcılar bir exception nesnesi ile raise işlemi yaparken en azından bu exception nesnesine hatanın nedeni hakkında + bir fikir veren bir yazıyı da argüman olarak geçirirler. Örneğin: + + def foo(a): + if a < 0: + raise ValueError('parameter may not be negative') + ... + + BaseException sınıfının __str__ ve __repr__ metodu vardır. Bu metotlar eğer exception nesnesine tek argüman girilmişse o + argümanı bir yazı olarak verirler. Örneğin: + + >>> ve = ValueError('Parameter cannot be negative') + >>> str(ve) + 'Parameter cannot be negative' + >>> print(ve) + Parameter cannot be negative + + Ancak eğer exception nesnesi birden fazla argümanla yaratılmışsa bu BaseException sınıfının __str__ metodu bu argümanları + bir yazısı biçiminde (demet biçiminde değil) vermektedir. Örneğin: + + >>> ve = ValueError('Parameter cannot be negative', 123, 456) + >>> str(ve) + "('Parameter cannot be negative', 123, 456)" + >>> print(ve) + ('Parameter cannot be negative', 123, 456) + + BaseException sınıfının __repr__ metodu biraz daha aşağı seviyeli biçimde sınıf ismini de belirterek arümanlara ilişkin + yazıları vermektedir. Örneğin: + + >>> ve = ValueError('Parameter cannot be negative') + >>> repr(ve) + "ValueError('Parameter cannot be negative')" + >>> ve + ValueError('Parameter cannot be negative') + >>> print(repr(ve)) + ValueError('Parameter cannot be negative') + >>> ve = ValueError('Parameter cannot be negative', 123, 456) + >>> repr(ve) + "ValueError('Parameter cannot be negative', 123, 456)" + >>> ve + ValueError('Parameter cannot be negative', 123, 456) + >>> print(repr(ve)) + ValueError('Parameter cannot be negative', 123, 456) +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir exception except bloğu ile yakalanırken exception sınıf isminin yanı sıra as anahtar sözcüğü ve bir değişken + kullanılabilir. Örneğin: + + except ValueError as e: + pass + + Bu durumda raise ile fırlatılan exception'daki exception nesnesi (yani onun adresi) as ile belirtilen değişkene atanmaktadır. + Böylece bir exception fırlatıldığında programcı o exception yakalayarak oradan exception arümanlarına erişebilir. + Örneğin: + + def disp_sqrt(val): + if not isinstance(val, float|int): + raise TypeError('parameter must be float or int') + if val < 0: + raise ValueError('value must not be negative') + print(val ** 0.5) + + try: + disp_sqrt(-10) + except ValueError as e: + print(e) + + Bu örnekte programcı fırlatılan exception nesnesini as cümleciği ile elde etmiş ve onun argümanlarını yazdırmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi değişik türlere ilişkin exception'lar demet sentaksıyla tek bir except bloğu tarafından yakalanabiliyordu. + Örneğin: + + except (TypeError, ValueError): + pass + + Burada bu except bloğu hem TypeError hem de ValueError exception'larını yakalayabilmektedir. Pekiyi bu sentaks ile + aynı zamanda as ile bir değişken ismi belirtiresek bu durumda ne olmaktadır? Örneğin: + + except (TypeError, ValueError) as e: + pass + + İşte böylesi bir durumda hangi exception oluşrsa (örneğimizde TypeError ya da ValueError) o exception nesnesi as ile + belirtilen değişkene atanacaktır. Yani burada TypeError oluşursa e değişkeni TypeError nesnesini, ValueError oluşursa + e değişkeni ValueError nesnesini tutacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +def disp_sqrt(val): + if not isinstance(val, float|int): + raise TypeError('parameter must be float or int') + if val < 0: + raise ValueError('value must not be negative') + print(val ** 0.5) + +try: + disp_sqrt('xxx') +except (ValueError, TypeError) as e: + print(e) + +print('son...') + +#------------------------------------------------------------------------------------------------------------------------ + Programcı kendi exception sınıflarını yazabilir ve onları kullanabilir. Bir sınıf ile raise işleminin yapılabilmesi + için ve o sınıfın except anahtar sözcüğünün yanında kullanılabilmesi için BaseException sınıfından türetilmiş olması + gerekir. Ancak Python standartları programcının kendi exception sınıflarının doğrudan BaseException sınıfından değil + bu sınıftan türetilmiş olan Exception sınıfından türetilmesi gerektiğini belirtmektedir. Tabii biz istersek zaten var + olan exception sınıflarından da türetme yapabiliriz. Örneğin: + + class NegativeError(ValueError): + pass + + def disp_sqrt(val): + if val < 0: + raise NegativeError('value must not be negative') + print(val ** 0.5) + + try: + disp_sqrt(-10) + except NegativeError as e: + print(e) + + Bu örnekte biz Python Standart Kütüphanesinde olmayan NegativeError isimli bir exceptionm sınıfı yazdık. Bu sınıfımızı + ValueError sınıfından türettik. Sınıfımız için __init__ metodu yazmadık. Bu durumda taban sınıfın __init__ metodu deveye + girecektir. Exception yakalırken yine except anahtar sözcüğünün yanına kendi exception sınıf ismini yazdığımıza dikkat + ediniz. + + Pekiyi mademki hazır pek çok built-in exception sınıfı varken programcının kendi exception sınıflarını yazmasına gerek + var mı? Exception'lar bir grup olarak yakalanacaksa böyle bir çabaya gerek olabilmektedir. Bazı kütüphanelerde bazı + konulara ilişkin exception'lar standart exception sınıflarıyla mantıksal olarak ifade edilemeyebilirler. Bu tür durumlarda + prohramcılar kendi exception sınıflarını yazabilmektedir. Gerçekten de standart kütüphanenin çeşitli modüllerinde o + konuya ilişkin özel exception sınıfları bulunmaktadır. + #------------------------------------------------------------------------------------------------------------------------ + +class NegativeError(ValueError): + pass + +def disp_sqrt(val): + if val < 0: + raise NegativeError('value must not be negative') + print(val ** 0.5) + +try: + disp_sqrt(-10) +except NegativeError as e: + print(e) + +print('son...') + +#------------------------------------------------------------------------------------------------------------------------ + 53. Ders 31/10/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Türemiş sınıf türünden oluşan bir exception taban sınıf türünden bir except bloğu tarafından yakalanabilir. Başka bir + deyişle bir except bloğu yalnızca o sınıf türünden exception'ları değil aynı zamanda o sınıftan türetilmiş olan + exception'ları da yakalayabilmektedir. Örneğin biz NegativeError isimli exception sınıfının ValueError sınıfından + türetmiştik. O halde biz bu NegativeError exception'ını istersek ValueError except bloğu ile de yakalayabiliriz. + Örneğin: + + class NegativeError(ValueError): + pass + + def disp_sqrt(val): + if val < 0: + raise NegativeError('value must not be negative') + print(val ** 0.5) + + try: + disp_sqrt(-10) + except ValueError as e: + print(e) + + Burada NegativeError exception'ı NegativeError içeren except bloğu tarafından da yakalanabilir, ValueError içeren + except bloğu taraından da yakalanabilir. + + Bu sayede farklı exception'lar eğer aynı taban sınıftan türetilmişlerse taban sınıf belirtilerek tek bir except bloğu + tarafından yakalanabilmektedir. Bu özellik yalnızca Python'da değil C++, Java ve C# gibi dillerde de benzer biçimde + bulunmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class NegativeError(ValueError): + pass + +def disp_sqrt(val): + if val < 0: + raise NegativeError('value must not be negative') + print(val ** 0.5) + +try: + disp_sqrt(-10) +except ValueError as e: + print(e) + +#------------------------------------------------------------------------------------------------------------------------ + Bütün exception sınıfları taban Exception sınıfından türetildiğine göre biz aslında bütün exception'ları Exception + parametreli ya da BaseException parametreli bir except bloğu ile yakalabiliriz. Örneğin: + + try: + do_something() + except Exception as e: + print(e) + + Tabii bu biçimde farklı exception'ları tek bir except bloğu ile yakalamak pratik olsa da bazen değişik exception'lar için + değişik ele alım işlemlerinin yapılması gerekebilmektedir. Bu tür durumlarda farklı except bloklarının oluşturulması + gerekir. + + Aşağıdaki örnekte klavye kuması sırasında oluşan hatalar doğrudan Exception sınıfı ile yakalanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +try: + val = int(input('Bir sayı giriniz:')) + print(val * val) +except Exception as e: + print(e) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi exception ele alınırken hem taban sınıfa hem de türemiş sınıfa ilişkin except blokları bir arada bulunudurlursa + ne olur? Python'da exception oluştuğunda yorumlayıcı except bloklarına yukarıdan aşağıya doğru sırasıyla bakmaktadır. + Eğer bu tür durumlarda taban sınıfa ilişkin except bloğu türemiş sınıfa ilişkin except bloğunun yukarısında bulundurulursa + taban sınıfa ilişkin except bloğu türemişl sınıfa ilişkin exception'ı da yakalayacağı için anlamsız bir durum oluşur. Bu + tür durumlarda türemiş sınıfa ilişkin except bloğunun taban sınıfa except bloğunun yukarısında bulundurulması anlamlıdır. + (C++, Java ve C# gibi derleyicilerin kullanıldığı dillerde zaten taban sınıfa ilişkin catch bloklerının türemiş sınıfa ilişkin + catch bloklarının yukarısında bulundurulması derleme zamanında error oluşmasına yol açmaktadır.) Örneğin: + + try: + do_something() + except ValueError as e: + pass + except Exception as e: + pass + + Burada ValueError oluşursa bunu ValueError parametreli except bloğu yakalayacaktır. Ancak diğer tüm exception'ları Exception + parametreli except bloğu yakalayacaktır. Bu durum anlamlıdır. Bu örnekte programcı ValueError için özel bir işlem uygulamak, + diğer bütün exception'lar için aynı işlemleri uygulamak istemiş olabilir. Ancak buradaki sıra ters olsaydı kod anlamsız olurdu: + + try: + do_something() + except Exception as e: + pass + except ValueError as e: + pass + + Burada tüm exception'lar zaten Exception parametreli except bloğu tarafından yakalanacağı için ValueError parametreli except + bloğu boşuna yerleştirilmiştir. Yukarıda da belirttiğimiz gibi bu tür durumlarda türemiş sınıfa ilişkin except blokları + taban sınıfa ilişkin except bloklarının yukarısında bulundurulmalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Programın akışı birden fazla kez try bloğuna girebilir. Bu durumda bir exception oluşursa try bloklarının except blokları + içten dışa doğru (yani son girilen try bloğundan ilk girilene doğru) gözden geçirilir. Eğer exception bir try bloğunun + except bloğu tarafından yakalanırsa orada ele alınmış alur. Artık dış try bloklarına bu exception yansıtılmaz. Eğer exception + dış hiçbir try bloklerının except blokları tarafından da yakalanamazsa program çöker. + + Aşağıdaki örnekte main fonksiyonu foo, fonksiyonunu, foo fonksiyonu bar fonksiyonunu, bar fonksiyonu da tar fonksiyonunu + çağırmaktadır. + + main ---> foo ---> bar ---> tar + + Örneğimizde tar fonksiyonunda exception oluşmuştur. Programın akışı iki kez try bloğuna girmiştir. Örneğimizde oluşan + ValueError exception'ı için önce son girilen (bar'daki) try bloğunun except bloklarına bakılır. Bu exception burada + yakalanamadığı için bu kez foo fonksiyonundaki except bloklarına bakılacaktır. ValueError exception'ı foo fonksiyonunda + yakalanmıştır. Artık sanki exception oluşmamış gibi programın çalışması oradan devam edecektir. Programı çalıştırdığınızda + ekranda şu yazıları göreceksiniz: + + main begins... + foo begins... + bar begins... + tar begins... + parameter may not be negative!... + foo ends + main ends... +#------------------------------------------------------------------------------------------------------------------------ +def foo(a): + print('foo begins...') + try: + bar(a) + except ValueError as e: + print(e) + print('foo ends') + +def bar(a): + print('bar begins...') + try: + tar(a) + except TypeError as e: + print(e) + print('bar ends') + +def tar(a): + print('tar begins...') + if a < 0: + raise ValueError('parameter may not be negative!...') + print('tar begin...') + +def main(): + print('main begins...') + foo(-2) + print('main ends...') + +main() + +#------------------------------------------------------------------------------------------------------------------------ + Özel bir except bloğu da parametresiz except bloğudur. Parametresiz except bloğu tüm exception'ları yakalar. Ancak + parametresiz except blokları bulundurulacaksa tüm except bloklarının sonunda bulundurulmalıdır. Aksi takdirde "sentaks + hatası" ile programın çalıştırılması da başlatılmaz. Örneğin: + + try: + foo() + except ValueError: + pass + except TypeError: + pass + except: + pass + + Burada ValueError ve TypeError ayrı except bloklarıyla yakalanmıştır. Ancak diğer tüm exception'lar parametresiz except bloğu ile + yakalanır. Parametresiz except bloğunda bir as cümleceği bulunamaz. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def disp_sqrt(val): + if not isinstance(val, int|float): + raise TypeError('argument must be int or float!') + if val < 0: + raise ValueError('argumant must be positive or zero') + + result = math.sqrt(val) + print(result) + +try: + disp_sqrt(10) + disp_sqrt('ankara') +except ValueError as e: + print(e) +except: + print('argument error') + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi Exception parametreli except bloğu parametresi except bloğu gibi de zaten işlev görmüyor mu? Ne de olsa Python'da + tüm exception'şarın BaseError sınıfından türetilmiş olan Exception sınıfından türetildiğini görmüştük. Yani örneğin: + + try: + pass + except Exception: + pass + + ile aşağıdaki arasında işlevsel bir farklılıkm var mıdır? + + try: + pass + except: + pass + + İşte aslında parametresiz except bloğu toplamda Exception parametreli except bloğundan daha geneldir. Biz başka dillerde + yazılan kodları Python'dan belirli koşullar çerçevesinde çağırabilmekteyiz. O dillerin exception mekanizması farklıdır. + Dolayısıyla oradaki exception'lar Exception parametreli except blokları tarafından yakalanamazlar. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + try bloğunun bir de finally bloğu olabilmektedir. finally bloğu parametresiz olmak zorundadır. finally bloğu except + blokları ile birlikte ya da except blokları olmadan kullanılabilir. Her durumda finally bloğu en sonda olmak zorundadır. + Örneğin: + + try: + foo() + except ValueError: + pass + except TypeError: + pass + except: + pass + finally: + pass + + Konuya girişte try bloklarının tek başlarına bulunamadığını belirtmiştik. Geçerli blok dizilimleri şöyledir: + + - try, except blokları + - try, except blokları, finally + - try, finally + + finally bloğu exception oluşsa da oluşmasa da çalıştırılır. Yani exception oluşursa önce exception'ı yakalayan except + bloğu çalıştırılır sonra finally bloğu çalıştırılır. Eğer exception oluşmazsa yine finally bloğu çalıştırılır. + + Bu durumu aşağıdaki programla test ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def disp_sqrt(val): + if not isinstance(val, int|float): + raise TypeError('argument must be int or float!') + if val < 0: + raise ValueError('argumant must be positive or zero') + + result = math.sqrt(val) + print(result) + +try: + val = float(input('Bir sayı giriniz:')) + result = disp_sqrt(val) +except TypeError as e: + print(e) +except ValueError as e: + print(e) +finally: + print('finally') + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi mademki finally bloğu exception oluşsa da oluşmasa da çalıştırılmaktadır. O halde finally bloğu yerine oradakileri + except bloklarının sonuna taşırsak değişen ne olacaktır? Örneğin: + + try: + foo() + except: + print('exception oluştu') + finally: + print('finally bloğunda bir işlem yapılıyor') + + Bununla aşağıdaki kod arasında işlevsel ne farklılık vardır? + + try: + foo() + except: + print('exception oluştu') + print('finally bloğunda bir işlem yapılıyor') + + İşte finally bloğu her zaman çalıştırılmaktadır. Yani try bloğu nasıl sonlandırılmış olursa olsun finally bloğu çalıştırılır. + Örneğin bir döngü içerisinde bir try bloğu olabilir.Bu try bloğunun da finally bloğu olabilir. Bu döngüden ve dolayısıyla + try bloğundan break ya continue deyimleriyle çıkılmış olabilir. Bu durumda yine finally bloğu çalıştırılır. Örneğin: + + while True: + try: + val = int(input('Bir değer giriniz:')) + if val == 0: + break + print(val * val) + except: + print('giriş geçersiz!') + finally: + print('finally işlemi yapılıyor') + + print('program devam ediyor') + + Burada try bloğundan break ile çıkılmış olsa da finally bloğu çalıştırılacaktır. Halbuki kod aşağıdaki gibi olsaydı finally kodu + çalıştırılmayacaktı: + + while True: + try: + val = int(input('Bir değer giriniz:')) + if val == 0: + break + print(val * val) + except: + print('giriş geçersiz!') + print('finally işlemi yapılıyor') + + print('program devam ediyor') + + Tabii return işleminde de aynı durum söz konusudur. Örneğin: + + def foo(): + try: + val = int(input('Bir değer giriniz:')) + if val == 0: + return + print(val * val) + except: + print('giriş geçersiz!') + finally: + print('finally işlemi yapılıyor') + + foo() + + Görüldüğü gibi try bloğundan nasıl çıkılmış olursa olsun her zaman finally bloğu çalıştırılmaktadır. + + Ayrıca iç içe try bloklarının olduğu durumda iç try bloklarında exception oluştuğunda exception dış try bloğu tarafından + yakalansa bile iç try bloklarının finally blokları yine çalıştırılmaktadır. Yani try bloğundan raise ile çıkılsa bile + finally bloüu yine çalıştırılmaktadır. Örneğin: + + import math + + def disp_sqrt(val): + try: + if not isinstance(val, int|float): + raise TypeError('argüman int ya da float olmak zorunda!') + if val < 0: + raise ValueError('argüman negatif olamaz!') + + result = math.sqrt(val) + print(result) + finally: + print('finally bölümü çalıştıtılıyor...') + + def main(): + try: + val = float(input('Bir değer giriniz:')) + disp_sqrt(val) + except Exception as e: + print(e) + + main() + + Burada iç try bloğunda TypeError ya da ValueError oluştuğunda bu exception dış try bloğuğun except bloğu tarafından + yakalanacaktır. Ancak exception'ın yakalandığı yere kadar içeriden dışarıya doğru tüm try bloklarının finally blokları + yine çalıştırılacaktır. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi finally bloğunda neden gereksinim duyulmaktadır? finally bloğu "yapılmış tahisisatların garantili bir biçimde + geri bırakılması amacıyla" kullanılmaktadır. Burada "tahsisat" demekle bir kaynak kullanımından bahsediyoruz. Bazı kaynaklar + sınırlı ölçüdedir. Kaynak kullanıldıktan sonra onun geri bırakılması gerekir. Örneğin: + + def foo(): + ... + + ... + ... + ... + + ... + + Burada kaynak tahsis edildikten sonra bir exception oluşursa bu exception dış bir try bloğu tarafından ele alınıyorsa + artık bu kaynağın geri bırakılma olanağı kalmayacaktır. Halbuki bu geri bırakma işlemini otomatize etmek için finally + bloğundan faydalanılabilir: + + def foo(): + try: + ... + + ... + ... + ... + finally: + + + Burada kaynak tahsis edildikten sonra bir exception oluşsa da kaynağın geri bırakılması finally bloğu sayesinde mümkün + olmaktadır. Örneğin bir fonksiyon içerisinde bir dosya açılmış olabilir. Ancak dosya kapatılmadan exception oluşabilir. + İşte dosya finally bloğunda kapatılırsa bir sorun oluşmaz: + + def foo(): + f = None + try: + f = open('test.txt') + s = f.read() + #... + s = f.read() + #... + finally: + if f: + f.close() + + Burada programcının read metodundaki IO hatalarını dışarıda ortak bir yerde ele aldığını düşünelim. read metotlarının + birinde IO hatası olduğunda akış başka bir faaliyet alanına gidecek dolayısıyla dosyanın kapatılma imkanı kalmayacaktır. + (Gerçi dosya bu tür sınıfların __del__ metotlarında da kapatılmaktadır ancak çöp toplayıcı mekanizmanın da nasıl + gerçekleştirileceği belli değildir.) İşte bu tür durumlarda kaynakların finally bloğunda boşaltılması en uygun durumdur. + Özellikle çöp toplama mekanizmasının etkili olmadığı Python dünyasının dışındaki tahsisatların garantili boşaltılması + için finally bloğuna gereksinim duyulmaktadır. Programcı herhangi bir noktada exception oluştuğunda o zamana kadar + yapılan birtakım işlemleri geri almak istiyorsa bunu finally bloğunda yapmalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + raise deyiminde raise anahtar sözcüğünün yanına ifade getirilmeyebilir. Buna "reraise" denilmektedir. Bu biçimdeki reaise + deyimi ancak except bloklarında kullanılabilmektedir. Reraise işlemi aslında "exception'ı yakalanmamış hale getirmektedir. + Başka bir deyişle reraise işlemi aynı exception aynı parametreyle yeniden fırlatılıyor gibi bir etki oluşturmaktadır. Reraise + işlemleri tipik olarak iç içe try bloklarında exception'ın içteki try bloğunun except blokları tarafından yakalanması durumunda + aynı yakalamanın dışarıda da yapılması amacıyla kullanılmaktadır. + + Aşağıdaki örnekte main fonksiyonu foo fonksiyonunu, foo fonksiyonu da bar fonksiyonunu çağırmaktadır. Burada iç içe + iki try bloğu kullanılmıştır. try bloklarından biri main içerisinde diğeri ise foo içerisindedir. Exception foo + içerisindeki try bloğu tarafından yakalandığında reraise işlemi uygulanmıştır. Bu durumda sanki exception aynı exception + nesnesiyle yeniden fırlatılmış gibi bir etki oluşacaktır. Klavyeden negatif bir değer girdiğinizde ekranda şu yazıları + göreceksiniz: + + main başlıyor + Bir değer giriniz:-1 + foo başlıyor + bar başlıyor + exception foo'da yakalandı: argüman negatif olamaz! + exception main'de yakalandı: argüman negatif olamaz! + main bitiyor +#------------------------------------------------------------------------------------------------------------------------ + +def foo(a): + print('foo başlıyor') + try: + bar(a) + except ValueError as e: + print(f"exception foo'da yakalandı: {e}") + raise + print('foo bitiyor') + +def bar(a): + print('bar başlıyor') + if a < 0: + raise ValueError('argüman negatif olamaz!') + print('bar bitiyor') + +def main(): + print('main başlıyor') + try: + val = int(input('Bir değer giriniz:')) + foo(val) + except ValueError as e: + print(f"exception main'de yakalandı: {e}") + + print('main bitiyor') + +main() + +#------------------------------------------------------------------------------------------------------------------------ + Python'ın standart sys modülünde exc_info isimli exception mekainizması ile ilgili bir fonksiyon vardır. Bu fonksiyon + bir except bloğu içerisinde çağrılmalıdır. Bu fonksiyon bize üçlü bir demet verir. Demetin ilk elemanı oluşan exception'ın + türünü, ikinci elemanı exception nesnesini ve üçüncü elemanı da trace bilgisini belirtmektedir. Programcı isterse as cümleciği + ile elde edebileceği exception nesnesini bu fonksiyonla da elde edebilir. Örneğin parametresiz except bloklarında as cümleciği + kullanılamayacağından dolayı exception nesnesi ve türü bu yolla elde edilebilmektedir. Fonksiyon except bloğunun dışında + çağrılmasının bir anlamı yoktur. Eğer fonksiyon except bloğunun dışında çağrılırsa fonksiyonun verdiği demetin üç elemanı da + Nobe olmaktadır. + + Aşağıdaki örnekte parametresiz except bloğunda exception bilgileri sys.exc_info fonksiyonu ile elde edilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +import sys + +try: + val = int(input('Bir sayı giriniz:')) + print(val * val) +except: + print('exception oluştu') + exc_type, exc_obj, exc_trace = sys.exc_info() + print(exc_type, exc_obj, exc_trace, sep='|') + +#------------------------------------------------------------------------------------------------------------------------ + sys.exc_info fonksiyonundan elde edilen trace bilgisi bir bağlı liste biçimindedir. Bu bağlı liste bize exception'ın + oluştuğu noktaya kadarki fonksiyon akışlarını vermektedir. trace nesnelerinin tb_next öznitelikleri bir sonraki tarce + nesnesini belirtir. tb_lineno özniteliği ise exception oluşumuna ilişkin fonksiyon çağrılarının kaynak koddaki satır + numaralarını belirtmektedir. Bu bilgiler genellikle debugger gibi, REPL gibi ortamlar tarafından kullanılmaktadır. + + Aşağıdaki exception satır numaralarının ekrana yazdırılmasına yönelik bir örnek görüyorsunuz. +#------------------------------------------------------------------------------------------------------------------------ + +import sys + +def foo(): + bar() + +def bar(): + tar() + +def tar(): + raise ValueError() + +def main(): + try: + foo() + except: + _, _, trace = sys.exc_info() + while trace: + print(trace.tb_lineno) + trace = trace.tb_next + +main() + +#------------------------------------------------------------------------------------------------------------------------ + Bir exception yakalanmadığında program çökerken bu trace bilgileri de ekrana basılmaktadır. Böylece programcı buradaki + bilgileri izeleyerek çökmenin akıştaki yerini tespit edebilir. Aşağaıdaki programda oluşan exception ele alınmadığından + program çökecektir. Program çöktüğünde ekrana çıkan yazılara dikkat ediniz: + + Traceback (most recent call last): + + File ~\anaconda3\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec + exec(code, globals, locals) + + File c:\dropbox\shared\kurslar\python\src\sample.py:15 + main() + + File c:\dropbox\shared\kurslar\python\src\sample.py:13 in main + foo() + + File c:\dropbox\shared\kurslar\python\src\sample.py:4 in foo + bar() + + File c:\dropbox\shared\kurslar\python\src\sample.py:7 in bar + tar() + + File c:\dropbox\shared\kurslar\python\src\sample.py:10 in tar + raise ValueError() + + ValueError +#------------------------------------------------------------------------------------------------------------------------ + +import sys + +def foo(): + bar() + +def bar(): + tar() + +def tar(): + raise ValueError() + +def main(): + foo() + +main() + +#------------------------------------------------------------------------------------------------------------------------ + Python 3.11 ile birlikte exception mekanizmasına oldukça ayrıntı bir özellik olan "exception group" özelliği de eklenmiştir. + Biz kursumunda henüz çok yeni olduğu gerekçesiyle bu konuyu ele almayacağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 54. Ders 02/11/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İçerisinde bilgilerin bulunduğu ikincil belleklerdeki bölgelere "dosya (file)" denilmektedir. Dosya aslında işletim + sistemleri tarafından oluşturulan yüksek seviyeli bir organizasyona ilişkin bir terimdir. İşletim sistemlerinin dosya + işlemlerini yapan alt sistemlerine "dosya sistemi (file system)" denilmektedir. Dosyaların parçaları aslında disklerdeki + bloklarda bulunur. İşletim sistemi bir biçimde hangi dosyaların hangi parçalarının diskte hangi bloklarda olduğunu tutmaktadır. + Bu tutuş biçimine göre dosya sistemleri çeşitli isimlerle anılmaktadır. Örneğin NTFS, Ext-2, HFS gibi. İşletim sistemleri + uygulama programcılarına dosyaları sanki ardışıl byte topluluklarından oluşan varlıklarmış gibi göstermektedir. Dosya + işlemleri hangi dille yapılıyor olursa olsun eninde sonunda işletim sistemlerinin sistem fonksiyonları yoluyla + gerçekleştirilmektedir. Çünkü dosya işlemlerinden birincil derecede işletim sistemleri sorumludur. + + İşletim sistemlerinin çoğu diskleri sanki dizinlerden (directories) ve dosyalardan oluşan varlıklar biçiminde göstermektedir. + Bir dosya bir volümdeki dizinlerin içerisinde bulunur. Bir disk volümlerden oluşabilmektedir. Microsoft'un kullandığı dosya + sistemlerinde (NTFS, FAT gibi) her volümün ayrı bir kök dizini bulunmaktadır. Örneğin elimizde fiziksel bir disk olsun. Biz bu + fiziksel diski kendi içerisinde üç ayrı volüme ayırabiliriz. Microsoft her volüme bir sürücü ismi atamaktadır. Örneğin "C sürücüsü", + "D sürücüsü", "E sürücüsü" gibi. Böylece Microsoft dosya sistemlerinde bir dosya belli bir volümün (yani sürücünün) belli + bir dizini içerisinde bulunur. Volümdeki en dışta bulunan dizine "kök dizin (root directory)" denilmektedir. Ancak UNIX/Linux + sistemlerinde ve macOS sistemlerinde "sürücü (drive)" diye bir kavramı yoktur. Toplamda bu sistemlerde tek bir kök dizin + vardır. UNIX/Linux ve macOS sistemlerinde disk yine birden fazla volümden oluşabilir. Ancak bu volümler ayrı sürücüler + biçiminde değil tek bir kökteki dizinler biçiminde organize edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir dosyanın dizinler içerisindeki yerini belirten yazısal ifadelere "yol ifadesi (path)" denilmektedir. Yol ifadelerinde + dizin geçişlerinde Microsoft sistemlerinde "\" karakteri, UNIX/Linux ve macOS sistemlerinde ise "/" karakteri kullanılmaktadır. + Microsoft sistemleri programlama söz konusu olduğunda UNIX/Linux uyumunu korumak için dizin geçişlerinde "/" karakterini de + kabul etmektedir. + + Yol ifadeleri "mutlak (absolute)" ve "göreli (relative)" olmak üzere ikiye ayrılmaktadır. Eğer bir yol ifadesinin ilk karakteri + UNIX/Linux sistemlerinde "/", Windows sistemlerinde "\" ise böyle yol ifadelerine mutlak yol ifadeleri, değilse göreli + yol ifadeleri denilmektedir. Örneğin: + + "/home/kaan/study/test.txt" ---> Mutlak yol ifadesi + "a/b/c.txt" ---> Göreli yol ifadesi + "test.txt" --> Göreli yol ifadesi + "\a.txt" --> Mutlak yol ifadesi (Windows) + + Mutlak yol ifadeleri her zaman kök dizinden itibaren yer belirtmektedir. Göreli yol ifadeleri ise prosesin "çalışma + dizininden (current working directory)" itibaren yer belirtir. İşletim sistemleri dünyasında çalışmakta olan programlara + "prosess (process)" denilmektedir. İşletim sistemleri her proses için bir çalışma dizini (current working directory) + tutmaktadır. İşte göreli yol ifadeleri bu çalışma dizininden itibaren bir yer belirtir. Örneğin programımızın çalışma + dizini "C:\temp" olsun. Biz de "a\b\test.txt" biçiminde göreli bir yol ifadesi vermiş olalım. Bu dosya işletim sistemi + tarafından "c:\temp\a\b\test.txt" biçiminde ele alınacaktır. Eğer yol ifadesi "\a\b\c.txt" biçiminde olsaydı prosesin + çalışma dizininin bir önemi kalmayacaktı. Çünkü mutlak yol ifadeleri her zaman kök dizinden itibaren bir yer belirtmektedir. + Örneğin "test.txt" biçimindeki bir yol ifadesi göreli bir yol ifadesidir. Bu yol ifadesindeki "test.txt" dosyası prosesin + çalışma dizininde aranır. + + Windows sistemlerinde yol ifadesine sürücü ismi de dahi edilebilir. Örneğin "c:\temp\test.txt" gibi. Bu tür yol ifadelerine + Microsoft "tam yol ifadeleri (full path)" demektedir. Microsoft sistemlerinde mutlak bir yol ifadesinde sürücü belirtilmezse + bu durumda bu yol ifadesinin prosesin çalışma dizinine ilişkin sürücüdeki bir yol ifadesi olduğu sonucu çıkartılır. Örneğin + prosesimizin çalışma dizini "F:\test\study" olun. Biz "\a\b\c.txt" biçiminde mutlak bir yol ifadesi verirsek buradaki kök + F sürücüsünün köküdür. + + Windows sistemlerinde sürücü içeren göreli yol ifadeleri de söz konusu olabilmektedir. Örneğin "c:a\b\test.txt" gibi. + Bu özel bir durumdur. Bu durumda işletim sistemi proseste bazı çevre değişkenlerine bakıp göreli yol ifadesi için orijini + belirlemeye çalışır. Ancak proseste bu çevre değişkenleri yoksa (ki genellike yoktur) bu durumda bu yol ifadesi tam yol + ifadesi biçiminde yani "C:\a\b\test.txt" biçiminde ele alınır. + + Bir yol ifadesindeki ters bölüler ya da düz bölüler arasındaki bileşene "yol bileşeni (path component)" denilmektedir. + Örneğin: + + "/ali/veli/selami/test.txt" + + Burada "ali", "veli", "selami" ve "test.txt" yol bileşenleridir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Windows, UNIX/Linux ve macOS sistemlerinde yol bileşenlerinde "." ve ".." ifadeleri özel bir anlama gelmektedir. "." + ifadesi "o andaki dizin" anlamına ".." ifadesi o andaki dizinin üst dizini anlamına gelmektedir. Örneğin: + + "/a/b/c/../test.txt" + + Bu yol ifadesi aşağıdakiyle eşdeğerdir: + + "/a/b/test.txt" + + Örneğin: + + "/a/b/./c.txt" + + Bu yoli fadesi de aşağıdaki yok ifadesi ile tamamen aynıdır: + + "/a/b/c.txt" +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Prosesin çalışma dizini "os" modülündeki getcwd fonksiyonu ile bir string biçiminde elde edilebilir. Örneğin: + + import os + + cwd = os.getcwd() + print(cwd) + + Python programının çalışma dizini default durumda neresidir? Spyder IDE'si hangi program çalıştırılıyorsa o program + dosyasının bulunduğu dizini programın default çalışma dizini yapmaktadır. Zaten bu dizin aynı zamanda IDE'de sağ üst + köşede görüntülenmektedir. PyCharm IDE'si ise proje dizinini programın default çalışma dizini yapmaktadır. Eğer biz + bir Python programını komut satırından çalıştırıyorsak programın default çalışma dizini o anda çalıştırmayı yaptığımız + dizin olur. +#------------------------------------------------------------------------------------------------------------------------ + +import os + +cwd = os.getcwd() +print(cwd) + +#------------------------------------------------------------------------------------------------------------------------ + Bir program (yani proses) çalışırken onun çalışma dizini değiştirilebilmektedir. Prosesin çalışma dizinini değiştirmek + için "os" modülü içerisindeki chdir fonksiyonu kullanılır. Eğer bu fonksiyonda geçersiz bir dizin girilirse + FileNotFoundError isimli exception oluşmaktadır. Örneğin: + + import os + + os.chdir(r'c:\windows') + + Burada prosesin çalışma dizini "c:\windows" biçiminde ayarlanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +import os + +cwd = os.getcwd() +print(cwd) + +try: + os.chdir(r'C:\xxxx') +except Exception as e: + print(e) + +print('program continues...') + +cwd = os.getcwd() +print(cwd) + +#------------------------------------------------------------------------------------------------------------------------ + Bir dosya ile işlem yapmadan önce dosyanın açılması gerekir. İşlemler bitince de dosya kapatılmalıdır. Eğer dosya kapatılmazsa + program bittiğinde zaten otomatik olarak dosya işletim sistemi tarafından kapatılır. Dosyanın açılması sırasında işletim + sistemi dosya ile ilgili bazı hazırlık işlemlerini yapmaktadır. Bir dosya açılırken "açılacak dosya" yol ifadesiyle belirtilir. + Aynı zamanda açık sırasında programcı o dosya üzerinde hangi işlemleri yapacağını da belirtmektedir. Buna dosyaının "açış modu" + denilmektedir. + + Python'da dosyalar built-in open fonksiyonu ile açılmaktadır. open fonksiyonunun parametrik yapısı şöyledir: + + open(file, mode='r', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None) + + Fonksiyonun ilk iki parametresi çok önemlidir. Diğer parametrelere özel durumlarda gereksinim duyulmaktadır. Fonksiyonun + birinci parametresi açılacak dosyasının yol ifadesini, ikinci parametresi açış modunu belirtmektedir. Açım işlemi başarısız olursa + open fonksiyonu OSError sınıfınından türetilmiş çeşitli exception sınıflarıyle raise işlemi yapmaktadır. OSError sınıfı + da Exception sınıfından türetilmiştir. + + open fonksşyonunun birinci parametresi açılacak dosyanın yol ifadesini belirtir. İkinci parametre ise açış modunu belirtmektedir. + Yukarıda da belirttiğimiz gibi açış modu "dosya üzerinde yapılmak istenen eylemi" belirtmektedir. Açış modu bir yazı olarak + verilir. Açış modu olarak verilen yazıların aşağıdakilerdne biri olması gerekir: + + 'r': Bu modda ancak var olan dosyalar açılabilir. Açılan dosyadan yalnızca okuma yapılabilir. Yani bu mod "olan dosyayı yalnızca + yazma amacıyla aç" anlamına gelmektedir. Dosya yoksa FileNotFoundError exception'ı oluşur. Açış modu hiç girilmezse default + durum 'r' kabul edilmektedir. + + 'r+': Bu modda yine var olan dosya açılabilir. Ancak dosyadan hem okuma hem de dosyaya yazma yapılabilir. Dosya yoksa yine + FileNotFoundError exception'ı oluşmaktadır. Açış modlarındaki '+' karakteri "read/write" anlamına gelmektedir. + + 'w': Bu modda dosya yoksa yaratılır ve açılır, dosya varsa sıfırlanarak açılır (yani dosyanın içerisindekiler silinir). + Bu modda dosyaya yalnızca yazma yapılabilir. Olan bir dosyanın içinin sıfırlanmasına işletim sistemleri terminolojisinde + dosyanın "truncate edşlmesi" denilmektedir. + + 'w+': Bu modda yine dosya yoksa yaratılır ve açılır, dosya varsa sıfırlanarak açılır. Ancak dosyadan okuma ve dosyaya yazma + yapılabilir. + + 'a': Bu modda eğer dosya yoksa yaratılır ve açılır, dosya varsa olan dosya açılır (sıfırlanmaz). Ancak bu modda dosyaya her + yazılan hep sona eklenir. Dosyanın başka bir yerine bir şey yazmak mümkün değildir. Bu modda dosyadan okuma yapılamaz. + + 'a+': Bu modda eğer dosya yoksa yine yaratılır ve açılır, dosya varsa yine olan dosya açılır (sıfırlanmaz). Yine bu modda + dosyaya her yazılan hep sona eklenir. Dosyanın başka bir yerine bir şey yazmak mümkün değildir. Bu modda dosyanın herhangi + bir yerinden okuma yapılabilir. + + Genel olarak dosyaların gereksinimi karşılayacak en dar modda açılması tavsiye edilmektedir. Örneğin: + + - Var olan bir dosyanın bir yerindeki yazının bir kısmını değiştirmek isteyelim. Bu durumda dosyayı 'r+' modunda açmalıyız. + + - Sıfırdan bir dosyanın içerisine bir şeyler yazmak isteyelim. Bu durumda dosyayı 'w' modunda açmalıyız. Ancak dosya varsa + dosya varsa dosyanın içeriği silinecektir. Dikkat etmemiz gerekir. + + - A dosyasının içerisindekileri okuyup yeni bir B dosyasına yazmak isteyelim. Başka bir deyişle A dosyasının B isminde bir + kopyasını oluşturmak isteyelim. Bu durumda A dosyasını 'r' modunda B dosyasını 'w' modunda açmalıyız. + + Dosya başarılı bir biçimde açılmışsa open fonksiyonu bir "dosya nesnesine (file object)" geri döner. Artık programcı + işlemleri bu geri döndürülen sınıf nesnesinin metotlarıyla yapar. Örneğin: + + f = open('test.txt', 'r') + + Dosya işlemlerinde exception çok karşılaşılabilecek bir durumdur. Eğer programınızın çökmesini istemiyorsanız dosya + işlemlerini exception kontrolü içerisinde yapabilirsiniz. Örneğin: + + try: + f = open('test.txt', 'r') + ... + except OSError as e: + print(e) + ... + +#------------------------------------------------------------------------------------------------------------------------ + +try: + f = open('test.txt', 'r') + print('Ok') +except OSError as e: + print(e) + +print('continues...') + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi dosyayı açıp kullandıktan sonra artık o dosyayla ilgili işlem yapmayacaksak dosyayı + kapatmalıyız. Aslında çöp toplayıcı dosya nesnesini toplarken de __del__ metodunda zaten close işlemi uygulanmaktadır. + Ancak programcının dosyayı ilgili dosya nesnesinin close metoduyla kapatması uygun olur. close metodunun parametresi + yoktur. Örneğin: + + try: + f = open('test.txt', 'r') + ... + f.close() + except OSError as e: + print(e) + ... + + Dosya işlemi yaparken başka bir exception oluşup akış dış bir try bloğunun except bloğuna geçebiliyorsa kapatmanın + finally bloğunda yapılması uygun olur. Tabii open içerisinde exception oluşursa yine finally bloğu çalıştırılacağı için + açılmamış dosya close edilmeye çalışılmamalıdır. Bu işlem şöyle yapılabilir: + + f = None + try: + f = open('test.txt', 'r') + ... ===> bu kısımda exception oluşup akış bir yere gidecekse dosya kapatılarak gitmelidir + f.close() + except OSError as e: + print(e) + finally: + if f: ===> open içerisinde exception oluşursa dosya açılmadığı için kapatılmamalıdır + f.close() + ... + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İşletim sistemi bir dosyadaki her bir byte'a ilk byte 0 olmak üzere ardışıl bir pozisyon numarası vermektedir. Buna + "ilgili byte'ın offset'i" ya da "offset numarası" denilmektedir. Dosya işlemleri adeta kalemin ucu görevini yapan + "dosya göstericisi (file pointer)" denilen bir offset'ten itibaren yapılmaktadır. Dosya göstericisi bir offset belirtir. + Okuma ve yazma işlemleri o offset'ten itibaren yapılır. Dosya açıldığında dosya göstericisi 0'ıncı offset'tedir. Yani dosyaının + başındadır. Okuma ve yazma yapıldığında okunan ya da yazılan byte miktarı kadar dosya göstericisi otomatik ilerletilmektedir. + Örneğin: + + 0 1 2 3 4 5 6 + x x x x x x x + ^ + DG + + Burada dosya göstericisi 0'ıncı offset'te olsun. Biz dosyaya iki byte yazdığımızda artık 0 ve 1 numaralı offset'lerdeki + byte'lar güncellenecektir. Bu işlemden sonra dosya göstericisi otomatik olarak 2 artırılacak ve artık 2 numaralı offset'i + gösterir duruma gelecektir. + + 0 1 2 3 4 5 6 + y y x x x x x + ^ + DG + + Şimdi göstericisi aşağıdaki durumda olsun: + + 0 1 2 3 4 5 6 + x x x x x x x + ^ + DG + + Buradan biz 2 byte okursak 3'üncü ve 4'üncü offset'lerdeki byte'ları okuruz. Tabii dosya göstericisi bundan sonra artık 5'inci + offset'i gösterir duruma gelir: + + 0 1 2 3 4 5 6 + x x x x x x x + ^ + DG + + Şimdi dosya göstericisinin 6'ıncı offset'ti gösterdeiğini düşünelim: + + 0 1 2 3 4 5 6 + x x x x x x x + ^ + DG + + Buradan 1 byte okumak isteyelim. Artık dosya göstericisi dosyanın son byte'ındna sonraki byte'ı yani aslında dosyada + olmayan byte'ı gösteriyor durumda olur: + + 0 1 2 3 4 5 6 7 + x x x x x x x + ^ + DG (EOF durumu) + + İşte dosya göstericisinin dosyanın son byte'ından sonraki byte'ı göstermesi durumunda "EOF (End of File) durumu" denilmektedir. + EOF durumundan okuma yapamayız. Ancak EOF durumunda açış modu da uygunsa dosyaya yazma yapılabilmektedir. Bu durumda, + yazılanlar dosyaya eklenmektedir. Örneğin yukarıdaki durumda 3 byte dosyaya yazmak isteyelim. Şöyle bir durum oluşacaktır: + + 0 1 2 3 4 5 6 7 8 9 10 + x x x x x x x y y y + ^ + DG (EOF durumu) + + Dosyayı "w" modunda ya da "w+" modunda açtığımızı varsayalım. Dosya yoksa yaratılacak ve açılacak, dosya varsa sıfırlanacak + ve açılacaktır: + + 0 + ^ + DG (EOF durumu) + + Biz bu dosyaya 5 byte yazmak isteyelim. Şöyle bir durum oluşacaktır: + + 0 1 2 3 4 5 + y y y y y + ^ + DG (EOF durumu) + + Bir dosyaya ekleme yapabilmek için dosyanın ötesine yazma yapmak gerekir. Örneğin: + + 0 1 2 3 4 5 6 + x x x x x x x + ^ + DG + + Burada dosyaya 6 byte yazmak isteyelim. Şöyle bir durum oluşacaktır: + + 0 1 2 3 4 5 6 7 8 9 10 + x x x x y y y y y y + ^ + DG (EOF durumu) + + Genel olarak işletim sistemlerinde (dolayısıyla programlama dillerinde) dosya göstericisinin gösterdiği yerden itibaren + dosyanın sonuna kadar olan byte sayısından daha fazla byte okunmak istenebilir. Bu durum patolojik kabul edilmemektedir. + Dolayısıyla exception oluşturmaz. Bu tür durumlarda okunabilen kadar byte okunur. Örneğin: + + 0 1 2 3 4 5 6 + x x x x x x x + ^ + DG + + Burada dosyadan 10 byte okumak isteyelim. Bu durum exception oluşturmayacaktır. 3 byte okunacak ve okuma işlemi başarı + ile sonuçlanacaktır. Şöyle bir durum elde edilecektir: + + 0 1 2 3 4 5 6 7 + x x x x x x x + ^ + DG (EOF durumu) + + Bir dosyanın istenilen bir yerinden okuma yapmak için ya da i,stenilen bir yerine yazma yapmak için önce dosya göstericisinin + uygun biçimde konumlandırılması gerekir. Programlama dillerinde dosya göstericisini konumlandıran programlar ya da metotlar + bulunmaktadır. + + İşletim sistemleri her dosya açıldığında o açışa ilişkin ayrı bir dosya göstericisi oluşturmaktadır. Aynı dosyayı iki + kere açtığımızda iki farklı dosya nesnesi elde ederiz. Dolayısıyla iki farklı dosya göstericisi söz konusu olur. Örneğin: + + f1 = open('test.txt) + f2 = open('test.txt) + + Burada örneğin f1 nesnesi ile okuma yaptığımızda f1 nesnesine ilişkin dosya göstericisi ilerletilecektir. Bu işlemden + f2 nesnesine ilişkin dosya göstericisi etkileneyecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dosyalar "text" ve "binary" olmak üzere ikiye ayrılmaktadır. (Aslında işletim sistemi böyle bir ayrım yapmamaktadır. + Ancak bazı programlama dilleri işlemleri kolaylaştırmak için bu ayrımı yaparlar.) İçerisinde yalnızca yazıların bulunduğu + dosyalara "text" dosyalar, içerisinde yazıların dışında başka bilgilerin de bulunduğu dosyalara "binary" dosyalar denilmektedir. + Örneğin bir Python kaynak dosyası, bir notepad dosyası text dosyadır. Ancak örneğin bir jpeg dosyası, bir exe dosyası + binary dosyadır. Text dosyaların içerisinde yalnızca yazılar bulunmaktadır. Örneğin bir html dostası onu bilmeyenlere + tuhaf gelebilir. Ancak html dosyaları aslında yazı tutmaktadır ve dolayısıyla bu dosyalar text dosyalardır. + + Dosyalar Python'da text ya da binary modda açılabilmektedir. Yukarıda açıkladıpımı default açış modları text moddur. Binary modda + açış yapmak için açış modunun sonuna 'b' harfi getirilir. Örneğin: + + f = open('text.txt', 'r') # text modda açılıyor + + Fakat örneğin: + + f = open('test.jpg', 'rb') # binary modda açılıyor + + Bir dosyayı text modda açtığımızda ondan yazı okuruz, ona yazı yazarız. Ancak binary modda açtığmızda ondan byte'lar + okuyup byte'lar yazarız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dosya nesnesine ilişkin sınıfın read metodu dosya göstericisinin gösterdiği yerden n byte ya da n karakter okumak için + kullanılır. ASCII yazılarda ve ASCII karakterlerinin kullanıldığı UTF-8 yazılarda yazının her bir karakteri 1 byte'tır. + Eğer dosya text modda açılmışsa read metodu n karakter okur ve bize onu bir str nesnesi olarak verir. Eğer dosya binary + modda açılmışsa read metodu n byte okur ve bize onu bytes nesnesi olarak verir. Ukarıda da belirttiğimiz gibi read metodu + ile dosya göstericisinin gösterdiği yerden dosya sonuna kadar olan karakter ya da byte sayısından daha fazla okuma yapmak + istenirse bu durum normal karşılanmaktadır. Bu durumda okunabilen kadar karakter ya da byte okunur. Dosya göstericisi EOF + konumuna gelir. Eğer dosya göstericisi EOF durumundaysa read metodu hiçbir karakter ya da byte okuyamaz. Bu durumda dosya + text modda açılmışsa boş string, binary modda açılmışsa boş bytes nesnesi elde edilir. Eğer okuma sırasında bir IO + hatası oluşursa exception fırlatılmaktadır. + + read fonksiyonu text modda okuma yaparken default olarak "utf-8" encoding'ini kullanmaktadır. Bu encoding'te İngilizce + karakterler 1 byte, Türkçe karakterler 2 byte yer kaplamaktadır. (Diğer bazı ülkelerin karakterleri 2 byte'tan da daha + fazla yer kaplayabilmektedir.) + Örneğin "test.txt" dosyasının UTF-8 encoding'ine sahip olduğunu varsayalım ve içerisinde + "ağrıdağı" yazınının bulunduğunu düşünelim: + + >>> f = open('test.txt', 'r') + >>> s = f.read(2) + >>> s + 'ağ' + >>> s = f.read(2) + >>> s + 'rı' + >>> s = f.read(2) + >>> s + 'da' + >>> s = f.read(2) + >>> s + 'ğı' + >>> s = f.read(2) + >>> s + '' + + Burada hep yazıdan ikişer karakter okunarak okunanlar yazdırılmıştır. Eğer read metoduna okunacak miktar girilmezse + (yani read metodu argümansız çağrılırsa) bu durumda dosya göstericisinin gösterdiği yerden EOF durumuna kadar bütün + karakterler okunur. Örneğin: + + >>> f = open('test.txt', 'r') + >>> s = f.read() + >>> s + 'ağrıdağı' + >>> s = f.read() + >>> s + '' + +#------------------------------------------------------------------------------------------------------------------------ + +f = None + +try: + f = open('sample.py', 'r') + s = f.read() + print(s) +except OSError as e: + print(e) +finally: + if f: + f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Bir dosyanın içerisindekileri tek hamlede değil de bir döngü içerisinde okumak isteyelim. Yani her defasında örneğin + 1024 karakter okuya okuya dosyanın tamamını okumak isteyelim. Bunu şöyle yapabiliriz: + + whie True: + s = f.read(1024) + if s == '': + break + print(s, end='') + +#------------------------------------------------------------------------------------------------------------------------ + +SIZE = 1024 + +f = open(r'F:\Dropbox\Kurslar\Python\Doc\Python-Examples.txt', 'r') + +while True: + s = f.read(SIZE) + if s == '': + break + print(s, end='') + +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Tabii yukarıdaki kod walrus operatörüyle aşağıdaki gibi daha sade yazılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +SIZE = 1024 + +f = open(r'F:\Dropbox\Kurslar\Python\Doc\Python-Examples.txt', 'r') + +while s := f.read(SIZE): + print(s, end='') + +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + 55. Ders 07/11/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıda da belirttiğimiz gibi bir binary dosyadan read metodu ile okuma yapmak için binary dosyayı açarken açış moduna + 'b' eklememiz gerekir. Bundan sonra artık biz read işlemi yaptığımızda read metodu bize str nesnesi değil bytes nesnesi + verecektir. Tabii biz istersek text dosyaları da binary modda açabiliriz. Bu durumda read metodu bize yine bytes nesnesi + verir. Ancak binary bir dosyanın text modda açılması çoğu kez anlamsızdır. Örneğin bir PDF dosyası binary bir dosyadır. + Bizim onu binary modda açıp okumamız uygun olur. Bu durumda read metodu bize bytes nesnesi verecektir. Ancak pdf dosyasının + içerisindeki byte'ları yazı gibi yorumlayıp yazdırmaya çalışırsak anlamlı şeyler göremeyiz. Aşağıda dosya hangi modda + açılırsa read metodunun ne geri döndüreceğini bir tablo halinde veriyoruz: + + Dosya türü Açış Modu read Metodunun Geri Dönüş Değeri + + text text str + text binary bytes + binary binary bytes + binary text str ama anlamsız + + Aşağıda bir pdf dosyasından 10 byte okuma örneği verilmiştir. Tabii pdf gibi jpg gibi bmp gibi binary dosyaları okuyup + birtakım faydalı işlemleri yapabilmemiz için bizim bu dosyalara ilişkin dosya formatları hakkında bilgi sahibi olmamız + gerekir. +#------------------------------------------------------------------------------------------------------------------------ + +f = open('../doc/python.pdf', 'rb') +b = f.read(10) +print(b) +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Bir dosyaya yazma yapmak için write metodu kullanılmaktadır. write metodunun tek bir parametresi vardır. Dosya text + modda açılmışsa write metodu bir string argüman alır, dosya binary modda açılmışsa bir bytes nesnesini argüman olarak alır. + Tabii dosyaya yazma yapılabilmesi için açış modunun uygun olması gerekir. Örneğin: + + f = open('test.txt', 'w') + f.write('this is a test' + f.close() + + Dosyaya yazma yapıldığında dosya göstericisinin gösteridği offset'te zaten birtakım bilgiler varsa write onları + ezerek yazma yapmaktadır. Dosya işlemlerinde "insert etme" diye bir işlem yoktur. Dosyaya ekleme ancak sona yapılabilmektedir. + +#------------------------------------------------------------------------------------------------------------------------ + +f = open('test.txt', 'w') + +f.write('this is a test') +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Bir text dosyada aslında satır (line) kavramı yoktur. Satır kavramı dosyaya \n karakterinin yazılmasıyla sağlanmaktadır. + Örneğin: + + f.write('ali\nveli') + + Burada dosyadaki tüm karakterler aslında yan yanadır. Ancak dosya editöre çekildiğinde editör \n (LF) karakterini görünce + imleci aşağı satırın başına geçirdiği için "sanki dosya satırlardan oluşuyormuş gibi" bir görüntü elde edilmektedir. + Tersten gidersek text dosyanın içini aşağıdaki gibi görmüş olalım: + + ankara + izmir + + Aslında dosyanın içerisindeki gerçek dizilim UNIX/Linux ve macOS sistemlerinde şöyledir: + + ankara\nizmir + + Windows sistemlerinde biz dosyaya \n karakterini yazdığımızda Windows dosyaya yalnızca \n karakterini basmaz. + \r\n karakter çiftini basar. Bu çifte CR/LF çifti denilmektedir. Dolayısıyla yukarda iki satır görünümündeki dosyanın + içeriği Windows sistemlerinde şöyle olacaktır: + + ankara\r\nizmir + + Ancak UNIX/Linux ve macOS sistemlerinde biz text dosyaya \n karakterini bastığımızda dosyaya yalnızca \n karakteri + basılmaktadır. \n karakterinin byte olarak hex karşılığı 0A, \r karakteerinin byte olarak hex karşılığı ise 0D biçimindedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte binary modda açılan bir dosyaya write metodu ile 5 byte'lık bir bilgi yazdırılmıştır +#------------------------------------------------------------------------------------------------------------------------ + +f = None + +try: + f = open('test.dat', 'wb') + f.write(b'\x12\x56\xFC\x1B\x32') +except Exception as e: + print(e) +finally: + if f: + f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Dosyalar ister text olsun ister binary olsun ardışıl byte topluluklarından oluşmaktadır. Dolayısıyla bir dosyanın arasına + bir şey insert etmek ya da dosyanın arasından bir şeyler silmek otomatik yapılabilecek işlemler değildir. Bir dosyanın + sonuna eklemeancak EOF ötesine yazma yapmakla mümkün olur. Bir dosyanın belirli bölümüne bir şeyler insert etmek zor bir + işemdir. Bu tür işlemlerin başka bir dosya kullanılarak o dosya üzerinde yapılması uygun olur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Dosya nesneleri (file objects) dolaşılabilir nesnelerdir. Bir dosya nesnesi dolaşıldığında dosyadaki satırlar elde edilir. + Tabii elde edilen satırların sonunda '\n' karakterleri de vardır. (Yani satırları print fonksiyonuyla yazdırırken imleci iki kere + aşağı satıra geçirmemek için end parametresini '' biçiminde geçebilirsiniz.) Dosyanın satırları bittiğinde dolaşma da biter. + + Her ne kadar binary dosyalar da bu biçimde dolaşılabiliyorsa da binary dosyaların bu biçimde satır satır dolaşılması + genellikle anlamsızdır. +#------------------------------------------------------------------------------------------------------------------------ + +f = open('test.txt') + +for line in f: + print(line, end='') +f.close() + +f = open('test.txt') +a = list(f) +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Dosya nesnesine ilişkin sınıfın readline isimli metodu da vardır. Bu metot dosya göstericisinin gösterdiği yerden satır + sonuna kadar ('\n' karakteri de dahil olmak üzere) karakterleri okur ve o satır yazısıyla geri döner. Eğer dosya sonuna + gelinirse metot boş string'le geri dönmektedir. Bu durumda biz bir text dosyayı readline metotlarını çağırarak da satır + satır aşağıdaki gibi okuyabiliriz: + + f = open('test.txt', 'r') + + while True: + s = f.readline() + if s == '': + break + print(s, end='') + + f.close() + + Tabii bu işlem yine Walrus operatörü ile daha pratik yapılabilir: + + f = open('test.txt', 'r') + + while (s := f.readline()) != '': + print(s, end='') + + f.close() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bazen dosyanın içerisinde bir yerlerden okuma yapmak ya da içerisinde bir yerlere yazma yapk istenebilir. Bunun için + önce dosya göstericisinin istenilen yere konumlandırılması konumlnadırılması gerekir. Konumlandırma için seek metodu + kullanılmaktadır. seek metodunun iki parametresi vardır: + + seek(offset, origin) + + seek metodunun birinci parametre offset belirtir. İkinci parametresi ise konumlandırmanın nereden itibaren yapılacağını + anlatan orijin belirtmektedr. İkinci parametre 0, 1 ya da 2 değerini alır. Bu değerler için sıraıyla os modülündeki + SEEK_SET, SEEK_CUR ve SEEK_END sembolik sabitleri de kullanılabilmektedir. 0 konumlandırmanın baştan itibaren yapılacağını, + 1 konumlandırmanın o anda dosya göstericisinin gösterdiği yerden itibaren yapılacağını, 2 ise konumlandırmanın EOF + pozisyonundan itibaren yapılacağını belirtir. İkinci parametre 0 ise birinci parametre >= 0 olmak zorundadır. İkinci + parametre 1 ise birinci parametre pozitif, negatif ya da 0 olabilir. Pozitif bulunulan yerden ileriye, negatif bulunulan + yerden geriye ve 0 ise bulunulan yere konumlandırma anlamına gelir. İkinci parametre 2 ise birinci parametre <= 0 olmak + zorundadır. Bu durumda konumlandırma EOF'tan itibaren geriye yapılır. Metodun ikinci parametresi 0 default değerini almıştır. + Dosya text modda açıldığında göreli konumlandırma (yani ikinci parametre için 1 değerinin geçilmesi) yapılamamaktadır. + Ancak göreli konumlandırma bulunulan yere (yani f.seek(0, 1) biçiminde) yapılabilmektedir. (Bulunulan yere konumlandırma + okumadna yazmaya, yazamadan okumaya geçişti zorunlu olarak gerekmektedir.) Benzer biçimde text modda EOF'a göre konumlandırmada + da negatif offset kullanılamamaktadır. Ancak binary modda istenilen offset'e göre istenildiği biçimde konumlandırma + yapılabilmektedir. + + Aşağıda bazı örnekler ve anlamları verilmiştir: + + - f.seek(100, 0) ya da f.seek(100) burada konumlandırma dosyanın başından itibaren 100'üncü offset'e yapılır. + + - f.seek(-1, 1) burada konumlandırma dosya göstericisi neredeyse onun bir gerisine yapılır. Ancak text modda bu çağrı + exception oluşturacaktır. + + - f.seek(0, 2) burada konumlandırma EOF pozisyonuna yapılır. + + - f.seek(-1, 2) burada konumlandırma son byte'a yapılır. Ancak text modda bu çağrı exception oluşturacaktır. + + - f.seek(0, 1) burada konumlandırma bulunulan offset'e yapılır. Bulunulan offset'e konumlandırma tuhaf ve saçma gibi + geliyorsa da okumdan yazmaya, yazmadan okumaya geçişti yapılması gerekmektedir. + + Aşağıdaki örnekte bir text dosyanın 20'inci offset'inden itibaren 10 karakter okunmuştur. +#------------------------------------------------------------------------------------------------------------------------ + +f = open('test.txt', 'r') + +f.seek(20, 0) +s = f.read(10) +print(s) +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Dosyanın sonua bir şeyler eklenmek isteniyorsa önce dosya göstericisi f.seek(0, 2) çağrısı ile EOF poziasyonuna + konumlandırılmalıdır. Anımsanacağı gibi EOF durumunda dosyaya yazma yapıldığında bu durum dosyaya ekleme anlamına gelmektedir. + Örneğin: + + f = open('test.txt', 'r+') + + f.seek(0, 2) + f.write('ankara') + + f.close() + + Eğer dosya 'a' modunda açılırsa her yazılan dosyanın sonuna eklenir. Yani başka bir deyişle 'a' modunda yazma yapılmadan + önce zaten işletim sistemi dosya göstericisini EOF durumuna çekmektedir. 'a' modunda dosya göstericisinin konumlandırılmasının + bir anlamı yoktur. Çünkü her yazma işleminden önce zaten otomatik olarak dosya göstericisi EOF durumuna çekilmektedir. + 'a+' modunda ise her yazılan dosyanın sonuna yaılmakta ancak dosyanın herhangi bir yerinden okuma yapılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +f = open('test.txt', 'r+') + +f.seek(0, 2) +f.write('ankara') + +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Dosya nesnesine ilişkin sınıfın tell isimli metodu dosya göstericisinin baştan itibaren konumunu geri döndürmektedir. + Örneğin bir dosyanın uzunluğunu aşağıdaki gibi elde edebiliriz: + + f.seek(0, 2) + size = f.tell() + print(size) + +#------------------------------------------------------------------------------------------------------------------------ + +f = open('test.txt', 'r') + +f.seek(0, 2) + +result = f.tell() +print(result) + +f.close() + +#------------------------------------------------------------------------------------------------------------------------ + Biz dosyaları text dosyalar ve binary dosyalar olmak üzere ikiye ayırmıştık. Ancak text dosyalar da içerisindeki karakterlerin + kodlanma biçimine göre farklılıklar gösterebilmektedir. Text dosyalarda karakterler belli bir karakter tablosuna göre + kodlanmaktadır. Aslında karakterler de birer sayı gibi tutulmaktadır. Bilgisayarın belleğinde yalnızca sayılar (ikilik sistemde) + tutulmaktadır. Dolayısıyla aslında bir yazı bir sayı dizilimi gibidir. Yazının her bir elemanına programlamada + "karakter (character)" denilmektedir. Bilgisayarların ilk günlerinden bu yana çeşitli kurumlar ve şirketler tarafından + çeşitli karakter tabloları geliştirilmiştir. Bu karakter tablolarında sembollerim numaralandırmaları birbirinden farklı + olabilmektedir. + + Bir karakter tablosu oluşturulurken dört kavram kullanılmaktadır: + + - Karakter Repertuarı (Character Repertoire): Bir karakter tablosundaki karakterlerin kümesine denilmektedir. + - Sembol (Glyph): Karakter tablosundaki karakterlerin görüntüsüne yani şekline denilmektedir. + - Kod Numarası (Code Point): Karakter tablosu içerisindeki karakterlere tek tek 0'dan başlanarak numaralar verilmiştir. + Bu numaralara İngilizce "code point" denilmektedir. + - Karakter Kodlaması (Character Encoding): Karakter tablosundaki bir code point'in ikilik sisteme yani byte formatına + dönüştürülme biçimidir. + + Dünyanın ilk karakter tablosu ASCII (American Standard Code Information Interchange) denilen tablodur. ASCII tablosunun karakter + repertuarı 128 karakterden oluşmaktadır. Yani ASCII tablosu 128 karakterden oluşmaktadır ve karakterlerin kod numaraları + (code points) 0-127 arasındadır. ASCII tablosunda her kod numarası doğrudan 2'lik sisteme dönüştürülmektedir. Yani ASCII tablosuun + özel bir karakter kodlaması yoktu. + + ASCII tablosu Amerika için yani İngilizce için oluşturulmuş bir tabloydu. Bilgisayarlar yaygınlaşıp Avrupa ülkelerinde kullanılmaya + başlanınca o ülkelerdeki özel karakterler sorun yaratmaya başladı. Bu problemi ortadan kaldırmak için ASCII tablosu 256 karaktere + yükseltildi. (Orijinal ASCII tablosu hiçbir zaman kendini 256 karaktere yükseltmedi. Bu girişim değişik kurumlar tarafından ASCII + tablosuna bir ek olarak yapıldı.) Ancak ASCII tablosuna çeşitli ülkeler ve çeşitli kurumlar bir koordinasyon sağlamadan 128 karakteri + eklediler. ASCII tablosunun bu genişletilmiş biçimlerine "code page" denilmektedir. İşte zamanla ilk 128 karakteri standart ASCII + tablosundaki karakterler olan ancak sonraki 128 karakteri birbirinden farklı olan pek çok code page'ler orataya çıktı. Farklı latin + dilleri için farklı code page'lerin ortaya çıkması karışıklığı hepten artırmıştır. Üstelik aynı dil için bile farklı code page'ler + de bulunmaktadır. Örneğin Türkçe için ACII tablosunun üç farklı code page'i vardır: OEM 754, Microsoft 1254, ISO 8859-9. + ISO geç kalmış olsa da bu code page'leri zamanla standart hale getirmiştir. ISO'nun ASCII page'lerini tanımladığı standartlara + 8850 denilmektedir. 8859-1 code page'ine "Latin 1" code page'i denilmektedir. Türkçe code page ISO 8859-9 code page'idir. + + ASCII tablosunun değişik code page'leri oluşturulmuş olsa da temel bazı problemler bu yöntemle çözülememiştir. Bazı dilelrin + karakter sayıları 256'dan fazladır. Örneğin Japonca ve Çince gibi uzak doğu dillerinde 4000'e yakın "kanji" denilen karakter + bulunmaktadır. İşte bu karışıklığı engellemek için ismine UNICODE denilen bir karakter tablosu geliştirilmiştir. Eski devirlerde + bellekler çok küçük olduğu için karakterlerin bir byte ifade edilmesi anlamlıydı. Ancak zamanla bellek miktarları üstel bir + biçimde artınca artık karakterlerin bir byte alan tutulması konusu da sorgulanmaya başlanmıştır. UNICODE tablo özünde her karakterin + 2 byte yer kapladığı dolayısıyla içerisine çok fazla karakterin yerleştirildiği bir tablodur. Dünyanın bütün dillerinin karakterleri + bu tabloya yerleştirilmiştir. + + UNICODE tablonun ilk 128 code point'ine karşı gelen karakterleri standart ASCII tablosu ile aynı karakterlerdir. Sonraki + 128 code point'ine karşı gelen karakterler ise Latin-1 code page'inin (ISO 8859-1) karakterleridir. + + UNICODE tablonun değişik encoding'leri vardır. En yaygın kullanılan encoding'i UTF-8'dir. UTF-8 encoding'inde UNICODE'un ilk 128 + code point'i 1 byte ile kodlanır. Sonraki code point'leri 2 byte, 3 byte, 4 byte ile kodlanmaktadır. Bunun bir algoritması vardır. + UTF-8 kodlaması adeta UNICODE tablonun sıkıştırılmış bir kodlaması gibidir. Ayrıca standart ASCII metinler tamamen UTF-8 encoding'i + ile aynı olmaktadır. Yani UTF-8 encoding'i ASCII uyumludur. UTF8-8 encoding'inde Türkçe karakterler 2 byte yer kaplamaktadır. Örneğin: + + "ağrı dağı" + + bu yazı 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 2 = 13 byte yer kaplar. + + Bu anlatımlardan çıkan en önemli sonuç programcı bir text dosyayla çalışıyorsa onun encoding'ini bilme zorunluluğudur. Örneğin biz + UTF-8 olarak kodlanmış bir yazıyı ISO 8859-9 olarak okumak istersek tuhaf karakterler elde edebiliriz. Pekiyi bir text dosyanın + encoding'ini nasıl bilebiliriz? UNICODE text text dosların başında encoding için bir BOM (Byte of Order Mark) marker bulundurulabilmektedir. + Böylece programlar bu BOM marker'a bakarak encoding tespitini yapmaya çalışabilmektedir. Ancak BOM marker yalnızca UNICODE encoding'ler + için söz konusudur ve BOM marker UNICODE text dosyaların başında bulunmak zorunda değildir. (Yani isteğe bağlıdır.) Text editörler + genellikle BOM marker'a bakarlar eğer bu BOM marker'ı göremezlerse default bir encoding ile yazıyı yorumlarlar. Tabii en iyi durum + zaten kullanıcının bunu biliyor olması ve ona göre dosyayla ilgili işlem yapmasıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bir dosya text modda open fonksiyonuyla açılırken open fonksiyonun encoding parametresi ile encoding + belirtilebilmektedir. Örneğin: + + f = open('test.txt', 'r', encoding='utf-8') + + Dosyalar open fonksiyonuyla açılırken open fonksiyonun kullandığı default encoding sistemden sisteme değişebilmektedir. + Bu durum işletim sistemindeki locale ayarlarına bağlıdır. Default encoding standart locale modülündeki getpreferredencoding + fonksiyonuyla ya da 3.11 ile eklenen getencoding fonksiyonu ile elde edilebilmektedir. + + encoding parametresi için encoding belirten pek çok yazı girilebilir. Örneğin 'cp1254', 'iso8859_9' gibi. Encoding parametresi + için kullanılabilecek encoding belirten yazıların listesini aşğıdaki bağlantıdan elde edebilirsiniz: + + https://docs.python.org/3.11/library/codecs.html#standard-encodings + +#------------------------------------------------------------------------------------------------------------------------ + 56. Ders 14/11/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir exception oluştuğunda exception başka bir yerde ele alınıyor olabilir. Ancak bu tür durumlarda exception'ın oluştuğu + noktadan önce bir kaynak tahsis edilmişse akış başka bir yere gitmeden o kaynağın geri bırakılması gerekmektedir. Biz bunun + için daha önce try deyiminin finally bölümünü kullanmıştır. Örneğin: + + f = None + try: + f = open(...) + ... + ... + ... + finally: + if f: + f.close() # finally bölümü her zaman çalıştırılacağıu için dosya her zaman kağatılacaktır + + İşte genel olarak with deyimi bu tür durumlarda kolay bir yazım sağlamak için kullanılmaktadır. with deyiminin genel biçimi + şöyledir: + + with [ as ]: + + + Burada ifade as anahtar sözcüğünün yanındaki değişkene atanır ve suit çalıştırılır. Örneğin: + + with open('test.txt') as f: + s = f.read() + print(s) + + Burada open fonksiyonunun geri dönüş değeri f değişkenine atanıp ilgili suit çalıştırılmaktadır. with deyiminde as kısmı + olmak zorunda değildir. Yani with deyimi şöyle de kullanılabilir: + + with : + + + Örneğin: + + f = open('test.txt') + with f: + s = f.read() + print(s) + + with benzeri deyimler diğer nesme yönelimli programlama dillerinin bazılarında da bulunmaktadır. Örneğin bu deyim C#'ta karşımıza using + deyimi biçiminde çıkmaktadır.S + + Bir ifadenin with deyimi ile kullanılabilmesi için o ifadenin türünün "bağlam yönetim protokolüne (context management protocol)" + uygun olması gerekir. Python'un temel türleri bağlam yönetim protokolüne uymamaktadır. Ancak örneğin open fonksiyonundan elde ettiğimiz + dosya nesnesine ilişkin sınıf bağlam yönetim protokolüne uymaktadır. Biz de kendi sınıflarımızın bağlam yönetim protokolüne uymasını + sağlayabiliriz. + + Bir sınıfın bağlam yönetim protokolüne uygun olması için sınıfta __enter__ ve __exit__ isimli iki metodun bulunuyor olması gerekir. + __enter__ metodunun yalnızca self parametresi vardır. Ancak __exit__ metodunun self dışında üç parametresi daha bulunur. + Bu parametreler istenildiği gibi isimlendirilebilir. Ancak programcılar genellikle bunları xc_type, exc_value, traceback biçiminde + isimlendirirler. Örneğin: + + class Sample: + def __enter__(self): + pass + def __exit__(self, xc_type, exc_value, traceback): + pass + + with Sample() as s: + pass + + with deyimi kabaca şöyle çalışmaktadır: Önce with anahtar sözcüğünün yanındaki ifade yapılır. Sonra bu nesne ile __enter__ metodu çağrılır, + __enter__ metodunun geri dönüş değeri as değişkenine atanır. Akış with deyiminden hangi yolla çıkarsa çıksın (exception dahil olmak üzere) with deyiminin yanındaki nesne ile __exit__ metodu çağrılır. + + Örneğin: + + class Sample: + def __init__(self): + print('__init__') + def __enter__(self): + print('__enter__') + def __exit__(self, xc_type, exc_value, traceback): + print('__exit__') + + with Sample() as s: + print('suite') + print('ends') + + Burada ekrana şu yazılar çıkacaktır: + + __init__ + __enter__ + suite + __exit__ + ends + + with deyimi bir sınıf nesnesi yaratılırken yapılan bazı tahsisatların otomatik bırakılmasını mümkün hale getirmek için + düşünülmüştür. Tabii bu tür kaynaklar genellikle Python dünyasının dışında tahsis edilen "unmanaged" denilen kaynaklardır. + Fakat herhangi bir kaynak bu deyimle otomatik bırakılabilir. Örneğin: + + with open('test.txt') as f: + s = f.read() + print(s) + + Burada open fonksiyonun geri döndürdüğü dosya nesnesine ilişkin sınıf "bağlam yönetim protokolünü" desteklemektedir. Bu nedenle + with deyiminden çıkılırken yorumlayıcı tarafından adeta f.__exit__(...) biçiminde sınıfın __exit__ metodu çağrılacaktır. İşte + bu metotta sınıfı yazanlar dosyayı zaten kapatmışilardır. O halde biz open fonksiyonunu bu biçimde kullanırsak with deyiminden + çıkılırken dosya otomatik kapatılacaktır. Bizim ayrıca dosyayı close ile kapatmamıza gerek kalmayacaktır. + + with deyimi try-finally gibi düşünülmelidir. with deyimi ile exception ele alınmamaktadır. Yalnızca otomatik boşaltım mekanizması + oluşturulmaktadır. Exception ele alınmak isteniyorsa ayrıca try deyimi uygulamalıdır. Örneğin: + + class Sample: + def __init__(self): + print('kaynak tahsis ediliyor') + + def __enter__(self): + print('__enter__ called') + + def __exit__(self, xc_type, exc_value, traceback): + print('kaynak boşaltılıyor') + + def foo(): + print('bir') + with Sample() as s: + print('s suit içerisinde kullanılıyor ve exception oluşuyor') + raise ValueError() + print('iki') + + def main(): + try: + foo() + except: + print('exception yakalandı') + + main() + + Yukarıdaki kod çalıştırıldığnda ekrana aşağıdaki yazılar basılacaktır: + + bir + kaynak tahsis ediliyor + __enter__ called + s suit içerisinde kullanılıyor ve exception oluşuyor + kaynak boşaltılıyor + exception yakalandı + + Programcı bağlam yönetim protokülüne uygun bir sınıf yazarken gerekli boşaltım işlemlerini __exit__ metodu içerisinde yapmalıdır. + + Aslında with deyiminin çalışması biraz ayrıntılara sahiptir. Python Language Reference dokğmanına göre: + + with EXPRESSION as TARGET: + SUITE + + deyiminin eşdeğeri şöyledir: + + manager = (EXPRESSION) + enter = type(manager).__enter__ + exit = type(manager).__exit__ + value = enter(manager) + hit_except = False + + try: + TARGET = value + SUITE + except: + hit_except = True + if not exit(manager, *sys.exc_info()): + raise + finally: + if not hit_except: + exit(manager, None, None, None) + + Yukarıdaki kod daha basit bir biçimde şöyle de ifade edilebilirdi: + + manager = (EXPRESSION) + value = manager.__enter__() + + hit_except = False + + try: + TARGET = value + SUITE + except: + hit_except = True + if not manager.__exit__(*sys.exc_info()): + raise + finally: + if not hit_except: + manager.__exit__(None, None, None) + + + Bu eşdeğerlilikten deyimin çalışması hakkında şunlar söylenebilir: + + 1) Öncelikle with anahtar sözcüğünün yanındaki ifade işletilir. + + 2) with anahtar sözcüğünün yanındaki ifadenin değeri ile sınıfın __enter__ metodu çağrılır. Bu __enter__ metodunun + geri dönüş değeri as anahtar sözcüğünün yanındaki değişkene atanmaktadır. Yani: + + with EXPRESSION as TARGET: + SUITE + + Burada aslında TARGET = EXPRESSION.__enter__() ataması yapılmaktadır. Görüldüğü gibi aslında with deyiminde with anahtar + sözcüğünün yanındaki ifade as ile belirtilen değişkene atanmamaktadır. __enter-_ metodunun geri dönüş değeri as ile belirtilen + değişkene atanmaktadır. + + O halde bizim en azından __enter__ metodu içerisinde self ile geri dönmemiz gerekir. Örneğin: + + class Sample: + def __enter__(self): + return self + ... + + Böylece gerçekten artık with anahtar sözcüğünün yanındaki ifadenin sonucu as ile beliretilen değişkene atanmış olur. + + 3) with deyimindeki suite'ten bir exception ile çıkıldığında exception'ın bilgileri ile __exit__ metodu çağrılmaktadır. + Ancak with deyiminden exception ile çıkılmadıysa bu durumda __exit__ metodu None, None, None parametreleriyle çağrılmaktadır. + + 4) Eğer with deyiminden exception ile çıkılmışsa bu durumda __exit__ metodunun geri dönüş değerine bakılmaktadır. Eğer __exit__ + metodu True ile geri dönmüşse sanki exception oluşmamış gibi akış with deyiminden sonra devam etmektedir. Yani bu durumda adeta + oluşan exception yakalnmış gibi işlem yapılmaktadır. Ancak __exit__ metodundan False ile geri dönülmüşse exception yakalanmamış + gibi davranış oluşmaktadır. Bu durumda __exit__ metodunun geri dönüş değeri önemli olmaktadır. Bir fonksiyonda return kullanmadıysanız + fonksiyonun None değeriyle geri döndüğünü anımsayınız. Nonde değeri de if deyiminde False olarak ele alınmaktadır. + + with deyimi bazı potansiyel olanakları programcı kullanabilsin diye bu biçimde biraz karışık tasarlanmıştır. Ancak programcılar + çoğunlukla bu protokülü desteklerken oldukça yalın bir kod kullanırlar. Programcılar genellikle __enter__ metodundan self ile geri + dönerler. __exit__ metodunda kaynak boşaltımını yaparlar ve __exit__ metodunda return kullanmazlar. Bu durumda __exit__ metodu + None ile geri döner ve exception oluşursa exception dışarıya verilir. + + Pekiyi __enter__ metoduna neden gereksinim duyulmuştur? İşte bazen programcı __enter__ metodunda aslında başka bir sınıf + türünden nesne vermek isteyebilir. Ancak böylesi durumlarla çok seyrek karşılaşılmaktadır. + + with anahtar sözcüğünün yanındaki ifadede exception oluşursa __exit__ metodunun çağrılmayacağına dikkat ediniz. Bu ifade + semantik olarak with deyimi ile bağlantılı değildir. Örneğin: + + with open('test.txt) as f: + pass + + Burada open fonksiyonunda bir exception oluşursa bu exception dıştaki bir try bloğu tarafından yakalanabilir. Çünkü henüz + with deyiminin içerisine girilmemiştir. + + Aşağıdaki örnekte bir dosyayı sarmalayan bir sınıf örneği verilmiş ve __exit__ metodunda dosyanın kapatılması sağlanmıştır. + +#------------------------------------------------------------------------------------------------------------------------ + +class FileWrapper: + def __init__(self, *args, **kwargs): + self.f = open(*args, **kwargs) + + def __enter__(self): + return self + + def __exit__(self, xc_type, exc_value, traceback): + self.f.close() + + def read(self, *args, **kwargs): + return self.f.read(*args, **kwargs) + + def write(self, *args, **kwargs): + return self.f.write(*args, **kwargs) + + def seek(self, *args, **kwargs): + return self.f.seek(*args, **kwargs) + +with FileWrapper('test.txt', 'r', encoding='cp1254') as fw: + s = fw.read() + print(s) + +#------------------------------------------------------------------------------------------------------------------------ + Aslında with deyiminde birden fazla ifade de kullanılabilmektedir. Yani örneğin: + + with ifade1 as değişken1, ifade2 as değşken2, ifade3 as değişken3: + + + Bu biçimdeki gibi bir with deyimi geçerlidir. Birden fazla ifadenin bulunduğu with deyimleri "iç içe" gibi ele alınmaktadır. + Yani yukarıdaki with deyiminin eşdeğeri şöyledir: + + with ifade1 as değişken1: + with ifade2 as değişken2: + with ifade3 değişken3: + + + Dolayısıla örneğin: + + with ifade1 as değişken1, ifade2 as değşken2, ifade3 as değişken3: + + + Bu biçimdeki with deyiminde ifadelere ilişkin sınıfların __enter__ metotları soldan sağa çağrılacaktır. __exit__ metotları + da ters sırada sağdan sola çağrılacaktır. Ayrıca burada baş taraftaki ifadelerde exception oluşrsa artık sol taraftaki + nesneler için __exit__ metodunun çağrılacağına dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Biz şimdiye kadar dolaşılabilir (iterable) nesneler ve dolaşım nesneleri üzerinde çalıştık. Onları for döngüsüyle + doğrudan, list gibi tuple gibi sınıflarla dolaylı olarak dolaştık. Pekiyi kendimiz nasıl dolaşılabilir bir sınıf + yazabiliriz? Bu bölümde bu konu ele alınacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Öncelikle "dolaşılabilir (iterable)" nesne ile "dolaşım (terator)" nesnesi kavramları arasındaki farkı yeniden anımsatmak + istiyoruz. Bir nesne "dolaşılabilir (iterable)" ise onu her defasında yeniden dolaşabiliriz. Örneğin list, tuple, set, + dict gibi sınıflar türünden nesneler dolaşılabilir nesnelerdir. Bir nesne "dolaşım (iterator)" nesnesi ise onu bir kez + dolatığımızda bitirmiş oluruz. İkinci kez dolaşamayız. Python'un zip gibi, map gibi, enumerate gibi metotları bize + dolaşılabilir bir nesne değil bir dolaşım nesnesi vermektedir. Hem dolaşılabilir nesne hem de dolaşım nesnesi for + döngüsü ile dolaşılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir sınıfın dolaşılabilir olması için sınıfın içerisinde __iter__ isimli bir metodun bulunuyor olması gerekir. __iter__ + metodunun self parametresi dışında başka bir parametresi yoktur. Öneğin: + + class Sample: + def __iter__(self): + pass + + Burada artık Sample dolaşılabilir (iterable) bir sınıftır. __iter__ metodunun bir "dolaşım (iterator)" nesnesiyle geri + döndürülmesi gerekmektedir. Bu durumda dolaşılabilir bir sınıf bize bir dolaşım nesnesi vermelidir. Asıl dolaşım işlemi + bu dolaşım nesnesiyle yapılmaktadır. Bu durumda iki sınıf söz konusu olmaktadır: + + 1) Dolaşılabilir (iterable) sınıf + 2) Dolaşım (iterator) sınıfı + + Yukarıda da belirttiğimiz gibi bir sınıfın dolaşılabilir olması için __iter__ metoduna sahip olması gerekir. Ancak sınıfın + dolaşım sınıfı olması için __next__ metoduna sahip olması gerekmektedir. __next__ metodunun da self dışında bir parametresi + yoktur. Dolaşılabilir sınıf ile dolaşım sınıfları farklı olabilmektedir. Ancak çoğu kez bunlar aynı sınıf da olabilirler. Bu + durumda sınıfın hem __iter__ hem de __next__ metotları bulunur. __iter__ metodu self ile geri döndürülür. Aşağıdaki + örnekte dolaşılabilir sınıf ile dolaşım sınıfı farklı sınıflar olarak oluşturulmuştur. Dolaşılabilir sınıfın __iter__ + metodunda dolaşım sınıfı türünden bir nesne ile geri dönülmüştür: + + class SampleIterator: + def __next__(self): + pass + + class SampleIterable: + def __iter__(self): + si = SampleIterator() + return si + + Burada dolaşılabilir sınıfının __iter__ metodunun dolaşım sınıfı türünden bir nesneye geri döndüğüne dikkat ediniz. __iter__ + ve __next__ metotları yalnızca self parametresine sahiptir. + + Aslında Python Language Reference dokümanlarına göre dolaşım sınıfınlarının yalnızca __next__ metoduna değil aynı zamanda + __iter__ metoduna da sahip olması gerekmektedir. Bunun nedenini izleyen paragraflarda anlayacaksınız. Genellikle dolaşım sınıflarının + __next__ metotları self ile geri döndürülür. Örneğin: + + class SampleIterator: + def __next__(self): + pass + + def __iter__(self): + return self + + class SampleIterable: + def __iter__(self): + si = SampleIterator() + return si + + Aşağıda ise dolaşılabilir sınıf ile dolaşım sınıfı aynı sınıf yapılmıştır: + + class SampleIterable: + def __iter__(self): + return self + + def __next__(self): + pass + + Burada SampleIterable sınıfı hem dolaşılabilir bir sınıftır hem de dolaşım sınıfıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki gibi bir for döngüsü olsun: + + for x in iterable: + + + Bu döngünün tam eşdeğeri aslında şöyledir: + + iterator = iterable.__iter__() + try: + while True: + x = iterator.__next__() + + except StopIteration: + pass + + Buradaki eşdeğer kodun sözel açıklaması şöyle yapılabilir. Python yorumlayıcısı önce dolaşılabilir nesne ile __iter__ + metodunu çağırır ve ondan dolaşım nesnesini elde eder. Sonra sürekli olarak dolaşım nesnesi ile __next__ metodu + çağrılmaktadır. Her __next__ çağrısı bir değer verir ve o değer döngü değişkenine yerleştirilerek suit çalıştırılır. + Bu döngüden çıkış StopIteration exception'ı ile yapılmaktadır. Başka bir deyişle programcı dolaşım sınıfının __next__ + metodu ile vereceklerini verir. Artık verecek bir şeyi kalmayınca StopIteration exception'ını fırlatır. + + Aşağıdaki örnekte Counter isimli sınıf __init__ metodunda bir stop değeri almıştır. Bu sınıf türünden nesne her + dolaşıldığında 0'dan bu stop değerine kadar tamsayılar elde edilecektir: + + class Counter: + def __init__(self, stop): + self.stop = stop + + def __iter__(self): + self.count = 0 + return self + + def __next__(self): + if self.count == self.stop: + raise StopIteration() + self.count += 1 + return self.count - 1 + + Burada her __next__ çağrıldığında nesnenin count özniteliğinin içerisindeki değerle geri dönülmüştür. Eğer bu count + değeri stop değerine ulaşmışsa dolaşımın sonlandırılması için StopIteration exception'ı raise edilmiştir. Örneğin: + + c = Counter(10) + + for x in c: + print(x) + + Bu kodun eşdeğeri şöyledir: + + c = Counter(10) + + iterator = c.__iter__() + try: + while True: + x = iterator.__next__() + print(x) + except StopIteration: + pass + + Burada count örnek özniteliğinin __iter__ metodu içerisinde sıfırlandığına dikkat ediniz. Böylece her dolaşımda __iter__ + metodu çağrılacağı için dolaşım baştan başlayacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, stop): + self.stop = stop + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == self.stop: + raise StopIteration() + self.i += 1 + return self.i - 1 + +s = Sample(5) + +for x in s: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + 57. Ders 16/11/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Şimdiye kadar gördüğümüz bütün dolaşılabilir sınıflar da aslında burada belirttiğimiz gibi yazılmıştır. Aşağıda bir + list nesnesinin for döngüsü ve eşdeğer döngü ile dolaşımına örnek veriyoruz. +#------------------------------------------------------------------------------------------------------------------------ + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +for name in names: + print(name) + +print('----------------') + +iterator = names.__iter__() +try: + while True: + name = iterator.__next__() + print(name) +except StopIteration: + pass + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte bir sınıf __init__ metodu ile aldığı parametreleri bir demet biçiminde nesnenin örnek özniteliğinde + saklamaktadır. Daha sonra her __next__ metodunda sırasuyla bunlardan brini vermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Args: + def __init__(self, *args): + self.args = args + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == len(self.args): + raise StopIteration() + self.i += 1 + return self.args[self.i] + +args = Args('ali', 'veli', 123, True) + +for x in args: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + İki değer arasında rastgele n tane değer veren dolaşılabilir bir sınıf örneğini aşağıdaki gibi yazabiliriz. Bu örnekte + sınıfın __next__ metodu random modülündeki randint fonksiyonunu kullanarak rastgele değer üretmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import random + +class RandomIterable: + def __init__(self, beg, end, n): + self.beg = beg + self.end = end + self.n = n + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == self.n: + raise StopIteration() + self.i += 1 + return random.randint(self.beg, self.end) + +a = list(RandomIterable(0, 100, 10)) +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir (iterable) sınıf ile dolaşım (iterator) sınıfı aynı sınıf olmak zorunda değildir. Ancak daha önce de + belirttiğimiz gibi "Python Language Reference" dokümanına göre dolaşım sınıfı aynı zamanda dolaşılabilir bir sınıf gibi + de davranmak zorundadır. Yani dolaşım sınıfının __next__ metodunun yanı sıra __iter__ metodu da bulunmalıdır. Tabii bu + __iter__ metodu tipik olarak self ile geri dönmelidir. + + Aşağıda dolaşılabilir sınıf ile dolaşım sınıflarının farklı sınıflar olduğu bir örnek verilmiştir. Bu örnekte SqrtIterable + sınıfı bir değer almaktadır. Sınıf nesnesi dolaşıldığında 0'dan bu değere kadar (bu değer dahil değil) sayıların karekökleri + elde edilmektedir. Örneğin: + + si = SqrtIterable(10) + + for x in si: + print(x) + + Burada 0'dan 10'a kadar sayıların karekökleri yazdırılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class SqrtIterable: + def __init__(self, n): + self.n = n + + def __iter__(self): + return SqrtIterator(self.n) + +class SqrtIterator: + def __init__(self, n): + self.n = n + self.i = 0 + + def __next__(self): + if self.i == self.n: + raise StopIteration() + self.i += 1 + + return math.sqrt(self.i - 1) + + def __iter__(self): + return self + +si = SqrtIterable(10) + +for x in si: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte orijinal built-in range sınıfının myrange isminde bir benzeri yazılmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class myrange: + def __init__(self, start, stop = None, step = 1): + if stop is None: + self.stop = start + self.start = 0 + else: + self.start = start + self.stop = stop + + self.step = step + + def __iter__(self): + self.i = self.start + return self + + def __next__(self): + if self.i >= self.stop: + raise StopIteration() + self.i += self.step + return self.i - self.step + + def __getitem__(self, index): + if isinstance(index, slice): + start = index.start + stop = index.stop + step = index.step + + if start is None: + start = 0 + elif start < 0: + start = self.__len__() + start + if stop is None: + stop = self.__len__() + elif stop < 0: + stop = self.__len__() + stop + if step is None: + step = self.step + else: + step = step * self.step + + if start < 0: + start = 0 + elif start > self.__len__(): + start = self.__len__() + + if stop < 0: + stop = 0 + elif stop > self.__len__(): + stop = self.__len__() + + start = self.start + start * self.step + stop = self.start + stop * self.step + + return myrange(start, stop, step) + + if isinstance(index, (int, bool)): + if index < 0: + index = self.__len__() + index + if index < 0: + raise IndexError('range object index out of range') + + val = self.start + index * self.step + if val >= self.stop: + raise IndexError('range object index out of range') + return val + + raise TypeError('myrange indices must be integers or slices, not float') + + def __len__(self): + return math.ceil((self.stop - self.start) / self.step) + + def __repr__(self): + return f'myrange({self.start}, {self.stop}, {self.step})' + +mr = myrange(10, 20, 2) + +for x in mr: + print(x) + +print('------------') + +for x in mr: + print(x) + +print('------------') + +print(len(mr)) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii dolaşılabilir bir nesne dolaşılırken dolaşım sırasında nesne bize tek bir değer vermek zorunda değildir. Örneğin + bir demet nesnesi de verebilir. Aşağıdaki örnekte SqrtIterable sınıfında nesne dolaşıldığında ilgili sayı ve onun karekökünden + oluşan bir demet elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class SqrtIterable: + def __init__(self, n): + self.n = n + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == self.n: + raise StopIteration() + self.i += 1 + + return self.i - 1, math.sqrt(self.i - 1) + + +si = SqrtIterable(10) + +for val, root in si: + print(val, root) + +#------------------------------------------------------------------------------------------------------------------------ + Built-in enumerate fonksiyonunu biz de basit bir biçimde aşağıdaki gibi yazabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +class myenumerate: + def __init__(self, iterable, start = 0): + self.iterator = iterable.__iter__() + self.i = start + + def __iter__(self): + return self + + def __next__(self): + self.i += 1 + return self.i - 1, self.iterator.__next__() + +a = ['ali', 'veli', 'selami', 'ayşe', 'fatma'] + +me = myenumerate(a, 10) + +for index, name in me: + print(index, name) + +for index, name in me: + print(index, name) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örnekte olduğu gibi bazen manuel bir biçimde __next__ metodunun çağrılması gerekebilir. Örneğin elimzde + dolaşılabilir bir nesne olsun. Biz bu nesneyi for döngüsü ile dolaşmak isteyelim. Ancak ilk elemanı pas geçmek isteyelim. + Ya da elimizde yine bir dolaşılabilir nesne olsun. Biz bu nesnenin elemanlarını bir list nesnesine yerleştirelim. Ancak ilk elemanı + pas geçmek isteyelim. Dolaşım nesnelerinin de dolaşılabilir nesneler gibi davrandığını anımsayınız. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50, 60] + +iterator = a.__iter__() +iterator.__next__() # ilk elemanı pas geçmek için + +for x in iterator: + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin dolaşılabilir bir nesnenin en büyük elemanını bulan bir fonksiyon yazmak isteyelim. Bilindiği bu fonksiyon aslında built-in + biçimde max ismiyle bulunmaktadır. Biz en büyük elemanı bulurken ilk elemanın en büyük olduğunu kabul edip sonraki elemanlarla + karşılaştırırız. Bu tür durumlarda manule __next__ işlemi gerekebilmektedir. Aşağıdaki örnekte böyle bir mymax fonksiyonu yazılmıştır. + (Orijinal max fonksiyonu eğer dolaşılabilir nesnede eleman yoksa ValueError ile raise işlemi yapmaktadr.) +#------------------------------------------------------------------------------------------------------------------------ + +import random + +class RandomIterable: + def __init__(self, beg, end, n): + self.beg = beg + self.end = end + self.n = n + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == self.n: + raise StopIteration() + self.i += 1 + + return random.randint(self.beg, self.end) + +def mymax(iterable): + iterator = iterable.__iter__() + + maxval = iterator.__next__() + + for x in iterator: + if x > maxval: + maxval = x + + return maxval + +ri = RandomIterable(0, 100, 10) +result = mymax(ri) + +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python standart kütüphanesinde iter ve next isimli iki built-in fonksiyon bulunmaktadır. Bu fonksiyonlar aslında parametresiyle + verilen nesne üzerinde __iter__ ve __next__ metotlarını çağırmaktadır. Başka bir deyişle iterable.__iter__() çağırısı ile + iter(iterable) çağrısı eşdeğerdir. Benzer biçimde iterator.__next__() çağrısı ile next(iterator) çağrısı da eşdeğerdir. Yani + bu fonksiyonların aşağıdaki gibi yazılmış olduğunu varsayabilirsiniz: + + def iter(i): + return .__iter__() + + def next(i): + return i.__next__() + + Aslında burada eşğderlik tam olarak böyle değildir. Bu konuda çok küçük ayrıntılar vardır. Biz bunların üzerinde + durmayacağız. +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +iterator = iter(a) # a.__iter__() + +try: + while True: + val = next(iterator) # iterator.__next__() + print(val) +except StopIteration: + pass + +#------------------------------------------------------------------------------------------------------------------------ + Dolaşılabilir nesnelerdeki dolaşım işlemi ileriye doğru yapılmaktadır. Pekiyi geriye doğru dolaşım yapılamaz mı? + İşte Python'da bunun için built-in global reversed isimli bir fonksiyon bulundurulmuştur. Biz bu fonksiyonu daha önce + yüzeysel bir biçimde görmüştük. reversed foksiyonu bizden dolaşılabilir bir nesneyi alır, bize tersten dolaşıma izin + veren yeni bir dolaşım nesnesi verir. Yani kullanımı şmöyledir: + + ri = reversed(iterable) + + Biz bu nesneyi dolaşırsak elemanları tersten elde ederiz. Örneğin: + + a = [10, 20, 30, 40, 50] + + ri = reversed(a) + + for x in ri: + print(x) +#------------------------------------------------------------------------------------------------------------------------ + +a = [10, 20, 30, 40, 50] + +ri = reversed(a) + +for x in ri: + print(x, end=' ') + +print() + +for x in ri: # bu dolaşımdan bir şey elde edilmeyecek + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Ancak her dolaşılabilir nesne reversed fonksiyonuyla tersten dolaşılamamaktadır. Nesnenin reversed fonksiyonuyla tersten + dolaşılabilmesi için o sınıfı yazanların bunu sağlamaları gerekir. İşte aslında reversed fonksiyonu ilgili dolaşılabilir + nesne üzerinde __reversed__ isimli metodu çağırmaktadır. Sınıf yazan programcı da eğer tersten dolaşıma izin verecekse bu + __reversed__ metodunu yazar ve bu metottan tersten dolaşım yapabilecek bir itertor nesnesi ile geri döner. Başka bir deyişle + aslında reversed(iterable) çağrısı ile itreable.__reversed__ çağrısı bazı ayrıntılar dışında eşdeğerdir. reversed built-in + fonksiyonunun şöyle yazılmış olduğunu varsayabilirsiniz: + + def reversed(iterable): + return iterable.__reversed__() + + Bu durumda bizim bir sınıf nesnesini reversed fonksiyonuyla kullanabilmemiz için sınıfın __reversed__ isimli bir metodunun + bulunuyor olması gerekir. + + Aşağıdaki örnekte daha önce yazmış olduğumuz SqrtIterable sınıfını tersten dolaşılabilir hale getiriyoruz. Bu örnekte + sınıfın __reversed__ metodu ReverseSqrtIterator isimli tersten dolaşımı yapan bir sınıf nesnesiyle geri döndürülmüştür. + Böylece tersten dolaşım için artık bu sınıfın __next__ metodu çağrılacaktır. Bu sınıfa dolaşımın son değeir olan n değerinin + aktarıldığına dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class SqrtIterable: + def __init__(self, n): + self.n = n + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == self.n: + raise StopIteration() + self.i += 1 + + return math.sqrt(self.i - 1) + + def __reversed__(self): + return ReverseSqrtIterator(self.n) + +class ReverseSqrtIterator: + def __init__(self, n): + self.n = n + self.i = self.n - 1 + + def __iter__(self): + return self + + def __next__(self): + if self.i < 0: + raise StopIteration() + self.i -= 1 + + return math.sqrt(self.i + 1) + +si = SqrtIterable(10) + +for x in si: + print(x) + +print('-------------') + +for x in reversed(si): + print(x) + +#------------------------------------------------------------------------------------------------------------------------ + Yukarıdaki örnekte olduğu gibi geriye doğru dolaşımı sağlamak için __reversed__ metodunda farklı bir iterator sınıfının + oluşturulması gerekir. Aynı sınıfın hem ileriye doğru hem de geriye doğru dolaşım yapabilmesi bazen mümkün olsa da karmaşık + bir tasarımı gerektirir. Bu nedenle siz de yukarıdaki örnekte olduğu gibi tersten dolaşım yapan Iterator sınıfını ayrı + bir sınıf olarak yazınız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'ın temel veri yapılarını gerçekleştiren sınıflarından hangileri tersten dolaşılabilmektedir? + + list sınıfı, tuple sınıfı ve str sınıfı tersten dolaşılabilir sınıflardır. Örneğin: + + a = [10, 20, 30, 40 , 50] + + for x in reversed(a): + print(x, end=' ') + + print() + + t = (10, 20, 30, 40 , 50) + + for x in reversed(t): + print(x, end=' ') + + print() + + s = 'ankara' + + for c in reversed(s): + print(c, end=' ') + + print() + + set sınıfının tersten dolaşımı mümkün değildir. Zaten set sınıfında dolaşım yapıldığında dolaşımın hangi sırada yapılacağı + hakkında bir belirlemede bulunulmamıştır. Hal böyleyken bu sınıf için tersten dolaşımın da zatren anlamı yoktur. + + dict sınıfı dolaşıldığında anahtarların elde edildiğini anımsayınız. Önceden de belirttiğimiz gibi dict sınıfının dolaşılmasının + hangi sırada yapılacağı Python 3.7'ye kadar belirli değildi. Dolayısıyla set sınıfıyla aynı gerekçeler yüzünden dict sınıfı da + Python 3.7'ye kadar tersten dolaşılamıyordu. Ancak Python 3.7 ve sonrasında artık dict sınıfının dolaşılması sırasında + anahtarların eklenme sırasına göre elde edileceği garanti edilmiştir. Dolayısıyla 3.7 ve sonrasında dict sınıfı da Tersten + dolaşılabilir hale getirilmiştir. + + Daha önce görmüş olduğumuz map gibi, filter gibi enumerate gibi fonksiyonların verdiği sınıf nesneleri de tersten + dolaşılabilir değildir. Yani bu nesneleri biz reversed fonksiyonuyla tersten dolaşamayız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi map isimli built-in fonksiyon bizden bir fonksiyon ve dolaşılabilir nesneyi parametre olarak alıp bize bir dolaşım + nesnesi veriyordu. Biz bu nesneyi dolaştığımızda dolaşılabilir nesnedeki elemanların bu fonksiyona argüman olarak geçilmesi sonucunda + elde edilen değerleri elde ediyorduk. Örneğin: + + def foo(x): + return x ** 2 + + a = [1, 2, 3, 4, 5] + + result = map(foo, a) + + for x in result: + print(x, end= ' ') + + Built-in map fonksiyonu aşağıdaki gibi basit bir biçimde yazılabilir. Bu örnekte biz mymap sınıfının __next__ metodunda + StopIteration uygulamadık. Çünkü zaten aldığımız dolaşılabilir nesne üzerinde __next__ işlemi yaptığımızda StopIteration + oluşacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +class mymap: + def __init__(self, f, iterable): + self.f = f + self.iterator = iter(iterable) + + def __iter__(self): + return self + + def __next__(self): + val = next(self.iterator) + return self.f(val) + +a = [3, 6, 2, 8, 9] + +def square(val): + return val * val + +for x in mymap(square, a): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Python Standart Kütüphanesinde filter isimli built-in bir fonksiyon da vardır. Bu fonksiyon tıpkı map fonksiyonunda + olduğu gibi bir fonksiyonu ve dolaşılabilir nesneyi parametre olarak alır ve bize bir dolaşım nesnesi verir. (Tabii filter + aslında bir sınıf biçiminde yazılmıştır. Ancak anlatımlarda bir fonksiyonmuş gibi ele alınmaktadır.) filter fonksiyonun + verdiği dolaşım nesnesi dolaşıldığında şunlar olmaktadır: filter fonksiyonuna verilen dolaşılabilir nesnenin elemanları + filter fonksiyonuna verilen fonksiyona tek tek sokulur, true değerini veren elemanlar dolaşım sürecinde elde edilir. + Örneğin: + + a = [34, 12, 15, 9, 26, 41] + + def foo(val): + return val % 2 == 0 + + for x in filter(foo, a): + print(x, end=' ') + + Burada a listesi içerisindeki çift elemanlar elde edilmektedir. Örneğin: + + def bar(val): + return val > 20 + + for x in filter(foo, a): + print(x, end=' ') + + Burada a listesindeki 20'den büyük elemanlar elde edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +a = [34, 12, 15, 9, 26, 41] + +def foo(val): + return val % 2 == 0 + +for x in filter(foo, a): + print(x, end=' ') + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'sibel'] + +def f(s): + return 'a' in s + +for name in filter(f, names): + print(name, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + filter sınıfını basit bir biçimde aşağıdaki gibi yazabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +class myfilter: + def __init__(self, f, iterable): + self.f = f + self.iterator = iter(iterable) + + def __iter__(self): + return self + + def __next__(self): + while True: + val = next(self.iterator) + if f(val): + return val + +names = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'sibel'] + +def f(s): + return 'a' in s + +for name in myfilter(f, names): + print(name, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi dolaşılabilir nesnelere neden gereksinim duyarız? Örneğin 0'dan n'e kadar sayıların kareköklerini elde eden ve bunu + bize bir liste olarak veren (neticede liste de dolaşılabilir bir nesnedir) fonksiyon ile aynı işlem için oluşturulan dolaşılabilir + sınıf arasında ne fark vardır? Biz bu işlemi bir içlem içeren bir fonksiyonla da aşağıdaki gibi yapabilirdik: + + def get_sqrts(n): + return [math.sqrt(i) for i in range(n)] + + a = get_sqrts(100000) + for x in a: + print(x, end=' ') + + İşte sonucu bir liste biçiminde veren fonksiyonlar sonucu baştan oluşturup bir listeye yerleştirmek zroundadırlar. Eğer bizim + amacımız bu değerler üzerinde işlem yapmaksa bütün değerlerin işin başında listeye yerleştirilmesine ve onların bellekte yer + kaplamasına gerek yoktur. Dolaşılabilir nesneler değerleri dolaşım sırasında vermektedir. Böylece değerlerin baştan liste gibi + bir nesnede depolanmasına gerek kalmamaktadır. Bu tür durumlarda değerleirn bir listeyle geri döndürülmesi aynı zamanda bu listenin + baştan oluşturulması sırasında uzun beklemelere neden olabilmektedir. Halbuki değerler talep edildiğinde verildiği zaman bu uzun + bekleme parçalara bölünmüş olacaktır. Tabii bazen değerlerin bir liste biçiminde elde edilmesi de istenebilir. Programcı bu listenin + elemanları üzerinde işlemler isteyebilir. Bu tür durumlarda ilgili dolaşılabilir nesneden liste oluşturulabilir. + + Örneğin diskimizdeki tüm dosyalar üzerinde işlemler yapmak isteyelim. Yani kökten başlayarak her dizine tek tek geçip tüm + dosyaları elde etmek isteyelim. Bu çok yorucu ve uzun zaman alan bir işlemdir. Diskimizde binlerce dosya bulunuyor olabilir. + Bunlarının hepsinin elde edilmesi ve bize bir liste olarak verilmesi oldukça zordur. Oysa biz istediğimzde bize kalınan yerden + devam edilerek dosyalar verilse bu işlem çok daha uygun olur. İşte dolaşılabilir nesneler bir işi adım adım yapmak için + tercih edilmektedir. Eğer dizin ağacını dolaşan bir fonksiyonu dolaşılabilir bir nesne yoluyla gerçekleştirmezsek hem + bizim tüm dosyaları bir listede saklamamız gerekir hem de bu işlem çok uzun zaman alabilir. Gerçekten de os modülü + içerisindeki walk fonksiyonu dolaşılabilir bir nesne vermektedir. +#------------------------------------------------------------------------------------------------------------------------ + +import os + +for root, dirs, files in os.walk('f:\\'): + print(root) + +#------------------------------------------------------------------------------------------------------------------------ + 58. Ders 21/11/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Üretici fonksiyonlara (generators) özellikle yorumlayıcılarla çalışılan dillerde karşılaşılmaktadır. Ancak derleyici + temelli bazı dillerede de özellikle son yıllarda üretici fonksiyonlar özelliği eklenmiştir. + + Python'da bir fonksiyonda en az bir tane yield isimli deyim kullanılırsa artık o fonksiyona "üretici fonksiyon (generator)" + denilmektedir. Örneğin: + + def foo(): + print('one') + yield 1 + print('two') + yield 2 + print('three') + yield 3 + print('ends') + + Burada foo fonksiyonu bir üretici fonksiyondur. + + yield deyiminin genel biçimi şöyledir: + + yield [ifade] + + Bir üretici fonksiyonun türü normal bir fonksiyondur. Ancak üretici fonksiyon çağrıldığında bir "üretici nesne (generator object)" + elde edilmektedir. Bu üretici nesneye Python Language Reference dokümanlarında "generator iterator" da denilmektedir. Örneğin: + + g = foo() + + Burada g bir üretici nesnedir. Yani bir üretici fonksiyon çağrıldığında fonksiyonun kodları çalıştırılmamaktadır. Bu çağrı + ifadesi ismine "üretici nesne (generator object)" denilen bir nesne vermektedir. Üretici fonksiyonlar normal bir fonksiyon + gibidir. Üretici nesneler ise "generator" isimli bir sınıf türünden nesnelerdir. +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('one') + yield 1 + print('two') + yield 2 + print('three') + yield 3 + print('ends') + +print(type(foo)) # + +g = foo() + +print(type(g)) # + +#------------------------------------------------------------------------------------------------------------------------ + Üretici nesneler birer dolaşım (iterator) nesnesidir dolayısıyla da dolaşılabilir nesnelerdir. Yani biz bir üretici nesneyi + for döngüsüyle dolaşabiliriz. İstersek next metodunu sürekli çağırarak da (__iter__ metodunu çağırmadan) da dolaşabiliriz. + Bir üretici nesne dolaşılmak istendiğinde (next yapıldığında) üretici fonksiyonun kodları çalıştırılır ve yield deyiminin + olduğu yerde çalışma geçici olarak durur. yield deyiminin yanındaki ifadenin değeri next işleminden elde edilen değer olur. + Yani biz bir üretici nesneyi dolaşırsak aslında yield deyimlerinde belirtilen ifadelerin değerlerini elde ederiz. Üretici nesneler + dolaşılırken yield deyimi fonksiyonu durdurmaktadır. Yeni bir yinelemede akış durdurulmanın yapıldığı yield deyiminden itibaren + devam eder ve sonraki yield deyimine kadar çalışır, sonraki yield deyiminde yeniden durdurulur. yield deyiminin fonksiyonu + sonlandırmadığına geçici olarak durdurduğuna dikkat ediniz. Üretici fonksiyon bittiğinde (akış üretici fonksiyonun sonuna geldiğinde + ya da üretici fonksiyonda return kullanıldığında) StopIteration oluşturulmaktadır. Yani üretici nesnenin dolaşımı üretici + fonksiyon bittiğinde sona ermektedir. Örneğin aşağıdaki gibi bir üretici fonksiyon olsun: + + def foo(): + print('one') + yield 1 + print('two') + yield 2 + print('three') + yield 3 + print('ends') + + Biz şimdi bir üretici nesne elde edelim: + + g = foo() + + Bu üretici nesne bir dolaşım nesnesi dolayısıyla da dolaşılabilir bir nesnedir. Şimdi onu bir adım dolaşalım: + + val = next(g) + print(f'next return value: {val}') + + Burada foo fonksiyonu ilk yield görülene kadar çalıştırılıp yield deyiminde durdurulacaktır ve next işleminden yield deyiminin + yanındaki değer elde edilecektir. Dolayısıyla ekranda şunlar görünecektir: + + one + next return value: 1 + + Şimdi next fonksiyonu ile bir adım daha dolaşım uygulayalım: + + val = next(g) + print(f'next return value: {val}') + + Bu durumda akış durdurulan yield deyiminden devam eder ve ilk yield deyimi görüldüğünden yeniden durdurulur. Ekranda şunlar görünecektir: + + two + next return value: 2 + + Şimdi bir adım daha dolaşalım. Ekranda şunları göreceksiniz: + + three + next return value: 3 + + Akış yield 3 deyiminde durdurulmuştur. Şimdi bir adım daha dolaşmak isteyelim. Artık fonksiyon gerçekten bitmektedir. İşte bu durumda + StopIteration oluşacaktır. + + Tabii biz üretici nesnesyi aşağıdaki gibi for dmngüsüyle de dolaşabilirdik: + + g = foo() + + for x in g: + print(f'x: {x}') + + Bu dolaşımda akış her yield deyiminde durduğunda o yield deyiminin yanındaki ifadenin değeri x'e atanacaktır. Ekranda şunları göreceksiniz: + + one + x: 1 + two + x: 2 + three + x: 3 + ends +#------------------------------------------------------------------------------------------------------------------------ + +def foo(): + print('one') + yield 1 + print('two') + yield 2 + print('three') + yield 3 + print('ends') + +g = foo() + +for x in g: + print(f'x: {x}') + +#------------------------------------------------------------------------------------------------------------------------ + Üretici fonksiyonlar sayesinde biz dolaşılabilir nesneleri çok daha kolay oluşturabiliriz. Örneğin: + + def sqrt_iterable(n): + for i in range(n): + yield math.sqrt(i) + + for x in sqrt_iterable(10): + print(x) + + Burada üretici nesne dolaşıldığında her yinelemede 0'dan itibaren n değerine kadar (n değeri dahil değil) sayıların + karekökleri elde edilmektedir. Biz bu işlemi yapan dolaşılabilir sınıfı daha önce yazmıştık. Aşağıda aynı işlemi yapan + dolaşılabilir sınıf ile üretici fonksiyon bir arada verilmiştir. İkisini karşılaştırarak yazım üretici fonksiyonların + yazım kolaylığına dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class SqrtIterable: + def __init__(self, n): + self.n = n + + def __iter__(self): + self.i = 0 + return self + + def __next__(self): + if self.i == self.n: + raise StopIteration + self.i += 1 + + return math.sqrt(self.i - 1) + +for x in SqrtIterable(10): + print(x) + +print('------------------') + +def sqrt_iterable(n): + for i in range(n): + yield math.sqrt(i) + +for x in sqrt_iterable(10): + print(x) + +print('------------------') + +a = list(sqrt_iterable(10)) +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Biz daha önce range fonksiyonunu dolaşılabilir bir sınıf olarak yazmıştık. Standart kütüphanedeki range fonksiyonu da + aslında dolaşılabilir bir sınıftır. Ancak istersek bu range işlemini yapan dolaşılabilir nesneyi bir üretici fonksiyon + kullanarak da yazabiliriz. Tabii range fonksiyonunu üretici fonksiyon olarak yazdığımızda artık üretici nesneyle [] + operatörünü kullanamayız. Aşağıda range fonksiyonunun temel işlevini yapan üretici bir fonksiyon verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +def myrange(start, stop = None, step = 1): + if stop == None: + stop = start + start = 0 + + i = start + while i < stop: + yield i + i += step + +for x in myrange(10): + print(x, end=' ') + +print() + +for x in myrange(10, 20, 2): + print(x, end=' ') + +print() + +for x in myrange(10, 20): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + 2'den itibaren parametresiyle belirtilen sayıya kadarki asal sayıları veren üretici fonksiyon aşağıdaki gibi yazılabilir. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +def get_primes(n): + def isprime(val): + if val % 2 == 0: + return val == 2 + sqrt_val = int(math.sqrt(val)) + for i in range(3, sqrt_val + 1, 2): + if val % i == 0: + return False + return True + + if n < 2: + raise ValueError('argument must greater or equal 2') + i = 2 + for i in range(2, n): + if isprime(i): + yield i + +for x in get_primes(100): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi dolaşılabilir bir nesne elde oluşturabilmek için dolaşılabilir sınıflar mı yoksa üretici fonksiyonlar mı tercih + edilmelidir? İşte bu tercih içinde bulunulan duruma göre değişebilmektedir: + + - Dolaşılabilir sınıflar her __next__ metodunda bize yeni değeri verirler. Oysa üretici fonksiyonlar her yield işleminde + bize değer verirler. Üretici fonksiyonların durudulması ve o noktadan çalışma devam ettirilmesi daha uzun zaman almaktadır. + Oysa dolaşılabilir sınıflarda değerler __next__ çağrısı ile daha hızlı edilmektedir. Yani dolaşılabilir sınıflar üretici + fonksiyonlara göre daha hızlı olma eğilimindedir. + + - Üretici fonksiyonları yazmak çok daha kolaydır. Çünkü herhangi bir fonksiyon hemen üretici fonksiyon haline getirilebilir. + Oysa dolaşılabilir sınıfları yazmak daha zordur. Dolaşılabilir sınıflarda akış durdurulmadığı için her __next__ metodunda + durumsal bilginin saklanarak o noktadan devam ettirilmesi gerekmektedir. Bu da daha fazla çaba anlamına gelir. Üretici + fonksiyonlar çok daha pratik yazılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Daha önce bizbuilt-in enumerate fonksiyonunu dolaşılabilir bir sınıf biçiminde yazmıştık. Şimdi de bu fonksiyonu + üretici fonksiyon olarak yazalım: + + def myenumerate(iterable, start = 0): + i = start + for val in iterable: + yield i, val + i += 1 + + Aşağıda daha önce yazmış olduğumuz sınıf gerçekleştirimi ile üretici fonksiyon gerçekleştirimini birlikte veriyoruz. +#------------------------------------------------------------------------------------------------------------------------ + +class myenumerate1: + def __init__(self, iterable, start = 0): + self.iterator = iterable.__iter__() + self.i = start + + def __iter__(self): + return self + + def __next__(self): + self.i += 1 + return self.i - 1, self.iterator.__next__() + +def myenumerate2(iterable, start = 0): + i = start + for val in iterable: + yield i, val + i += 1 + +a = [10, 20, 30, 40, 50] + +for index, val in myenumerate1(a): + print(index, val) + +print('--------------------') + +for index, val in myenumerate2(a): + print(index, val) + +#------------------------------------------------------------------------------------------------------------------------ + Daha önce biz built-in map fonksiyonunu dolaşılabilir bir sınıf biçiminde yazmıştık. Gerçekten de standrat kütüphanedeki + map fonksiyonu aslında bir sınıf biçiminde yazılmıştır. Şimdi de bu map fonksiyonunu bir üretici fonksiyon biçiminde + yazalım. Bu fonksiyonun ne kadar kolay yazıldığına dikkat ediniz: + + def mymap(f, iterable): + for x in iterable: + yield f(x) + +#------------------------------------------------------------------------------------------------------------------------ + +a = [3, 6, 2, 8, 4, 9] + +def square(val): + return val * val + +def mymap(f, iterable): + for x in iterable: + yield f(x) + +for x in map(square, a): + print(x, end=' ') + +print() + +for x in mymap(square, a): + print(x, end=' ') + +#------------------------------------------------------------------------------------------------------------------------ + Üretici nesneler birer dolaşım nesnesi belirtiyordu. Dolayısıyla o nesnelerle __next__ metodunu çağırsak ya da eşdeğer + olarak next fonksiyonuna o nesneleri versek akış ilk yield deyimine kadar çalışıyor ve yield değerini bize veriyordu. + Ancak bu işlemin tersi de mümkündür. Yani yield deyimi de bir değer üretebilmektedir. Sonraki yinelemeye geçildiğinde + akış yield eyiminden devam ederken yield deyimi ürettiği değeri vermektedir. Default durumda next işlemleri sırasında + yield deyimi None değer üretir. Örneğin: + + def foo(): + print('one') + val = yield 1 + print(f'yield returns {val}') + + print('two') + val = yield 2 + print(f'yield returns {val}') + + print('three') + val = yield 3 + print(f'yield returns {val}') + + g = foo() + + try: + val = g.__next__() + print(f'{val} gets from yield') + + print('----------------') + + val = g.__next__() + print(f'{val} gets from yield') + + print('----------------') + + val = g.__next__() + print(f'{val} gets from yield') + + print('----------------') + + val = g.__next__() + print(f'{val} gets from yield') + + except StopIteration: + pass + + Örneğimizde akış yield deyiminde durdurulduktan sonra devam ettirilirken yield deyiminin ürettiği değer val değişkenine + atanmıştır. Hem yield deyiminin ürettiği değer hem de yield deyiminden elde edilen değer ekrana yazdırılmıştır. + Örnek programdan şöyle bir çıktı elde edilecektir: + + one + 1 gets from yield + ---------------- + yield returns None + two + 2 gets from yield + ---------------- + yield returns None + three + 3 gets from yield + ---------------- + yield returns None + + İşte biz üretici nesne ile send metodunu çağırırsak aslında hem akışı durduğu yerden devam ettirmiş oluruz hem de yield işleminden + istediğimiz bir değerin elde edilmesini sağlarız. Ancak send metodu yield'de durmuş akış için kullanılır. Dolayısıyla üretici + nesne üzerinde dolaşım send ile başlatılmaz. Ancak devam ettirilebilir. Yukarıdaki örneği send metodunu kullanarak aşağıdaki + gibi değiştirelim: + + def foo(): + print('one') + val = yield 1 + print(f'yield returns {val}') + + print('two') + val = yield 2 + print(f'yield returns {val}') + + print('three') + val = yield 3 + print(f'yield returns {val}') + + g = foo() + + try: + val = g.__next__() + print(f'{val} gets from yield') + + print('----------------') + + val = g.send(100) + print(f'{val} gets from yield') + + print('----------------') + + val = g.send(200) + print(f'{val} gets from yield') + + print('----------------') + + val = g.send(300) + print(f'{val} gets from yield') + + except StopIteration: + pass + + Artık ekrana şunlar çıkacaktır: + + one + 1 gets from yield + ---------------- + yield returns 100 + two + 2 gets from yield + ---------------- + yield returns 200 + three + 3 gets from yield + ---------------- + yield returns 300 + + send metodu üretici nesnenin ilişkin olduğu "generator" isimli sınıfın bir metodudur. send metodunun kullanımına dikkat ediniz: + + g.send(ifade) + + metoda verilen argüman akışın yield deyiminden devam ettirilmesi sırasında yield deyiminin üreteceği değeri belirtmektedir. + + Yukarıda da belirttiğimiz gibi her send işlemi yield'de duran akışı devam ettirmektedir. send işleminin next işleminden farkı + akış devam ettrilirken yield deyiminin bir değer üretmesini sağlamasıdır. Yani biz yield'de duran akışı devam ettirirken aynı + zamanda yield'den bir değer de elde edilmesini istiyorsak bu durumda send uygulamalıyız. + + Üretici fonksiyonu next ile kaldığı yerden devam ettirirsek yield None üretmektedir. Başka bir deyişle: + + g.__next__() ya da next(g) işlemi ile aşağıdaki işlem eşdeğerdir: + + g.send(None) + + Üetici fonksiyon nesnesinin ilişkimn olduğu sınıfta (generator sınıfı) bir __send__ metodu yoktur. Bu metot send ismindedir. + Anımsanacağı gibi next isimli built-in fonksiyon aslında __next__ metodunu çağırmaktadır. Yani şöyle yazılmıştır: + + def next(iterator): + return iterator__next__() + + Ancak built-in bir send fonksiyonu yoktur. Yani send üretici nesneyle kullanılmalıdır. + + Üretici fonksiyonlarda yield deyiminin değer üretme özelliği Python'a sonradan eklenmiştir. Bu özelliğin kullanım + alanı oldukça sınırlıdır. Bazen durmuş durumda olan üretici fonksiyonları kaldığı yerden devam ettirirken üretici fonksiyona + değer iletmek gerekebilmektedir. İşte bu tür seyrek durumlarda send metodu kullanılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Anımsanacağı gibi liste içlemleri, küme içlemleri ve sözlük içlemleri vardı ancak demet içlemleri biçiminde bir içlem + yoktu. Bunun ana nedeni demetlerin değiştirilebilir nesneler olmamasıdır. Ancak demet içlemi sentaksı Python'da + bulunmaktadır fakat başka bir anlama gelmektedir. Demet içlemi sentaksına Python'da "üretici ifadeler (generator expresssions)" + denilmektedir. Örneğin: + + a = [1, 2, 3, 4, 5] + + b = [x * x for x in a] # listte içlemi + print(b) + + g = (x * x for x in a) # üretici ifade (generator expression) + + Bir üretici ifade aslında üretici bir fonksiyonun basit bir yazım biçimidir. Üretici ifade bize aslında bir üretici nesne + vermektedir. Yani: + + g = (ifade for değişken in dolaşılabilir_nesne) + + İşleminin eşdeğeri şöyedir: + + def some_generator_name(): + for x in dolaşılabilir_nesne: + yield ifade + + g = some_generator_name() + + Üretici ifadelerin doğrudan üretici nesne verdiğine dikkat ediniz. Halbuki üretici fonksiyonlardan üretici nesne elde + edebilmek için onların çağrılması gerekmektedir. Örneğin: + + >>> g = (i * i for i in range(10)) + >>> type(g) + + >>> a = list(g) + >>> a + [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + + Örneğin aşağıdaki gibi bir üretici ifade olsun: + + g = (i * i for i in range(10)) + + Bunun üretici fonksiyon karşılığı şöyle oluşturulabilir: + + def some_name(): + for i in range(10): + yield i * i + + g = some_name() + +#------------------------------------------------------------------------------------------------------------------------ + +a = [1, 2, 3, 4, 5] + +g = (x * x for x in a) # üretici ifade (generator expression) + +for x in g: + print(x, end=' ') +print() + +#------------------------------------------------------------------------------------------------------------------------ + 59. Ders 23/11/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi üretici ifade de aslında üretici fonksiyon belirttiğine göre arada ne fark vardır? Yani örneğin: + + g = (i * i for i in range(100)) + + gibi bir üretici ifade ile aşağdıaki arasında ne farklılık vardır? + + def some_name(): + for i in range(100): + yield i * i + + g = some_name() + + İşte üretici ifadeler "ifade (expression)" tanımına uymaktadır. Dolayısıyla başka ifadelerin içerisinde kullanılabilirler. + Örneğin: + + import statistics + + result = statistics.mean((i * i for i in range(10))) + + print(result) + + Biz burada üetici ifadeyi doğrudan fonksiyon çağırma işleminde argüman olarak kullandık. Halbuki aynı işlemi üretici + fonksiyonlarla yapmak isteseydik daha zor olacaktı: + + def some_name(): + for i in range(10): + yield i * i + + result = statistics.mean(some_name()) + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Üretici ifadeler eğer bir fonksiyon ya da metot çağrılırken argüman olarak kullanılıyorsa ve çağrım işleminde başka + da bir argüman bulundurulmuyorsa üretici ifadelerdeki dıştaki parantezler hiç kullanılmayabilir. Örneğin: + + import statistics + + result = statistics.mean((i * i for i in range(10))) + + print(result) + + Yukarıdaki çağrımda dıştaki parantezler hiç kullanılmayabilirdi: + + result = statistics.mean(i * i for i in range(10)) + + Ancak eğer çağrımda başka bir argüman da kullanılıyorsa bu durumda dıştaki parantezler ihmal edilemez. Örneğin: + + for index, val in enumerate(i * i for i in range(10), 100): # geçersiz! + print(f'{index} => {val}') + + Burada enumerate fonksiyonu iki argümanla çağrıldığı için üretici ifadedeki dış parantezler ihmal edilemez. Tabii + enumerate fonksiyonu tek argümanla çağrılsaydı dıştaki parantezler ihmal edilebilirdi: + + for index, val in enumerate(i * i for i in range(10)): # geçerli! + print(f'{index} => {val}') + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi üretici ifadeler bize ne fayda sağlamaktadır? İşte üretici ifadeler aslında dolaşılabilir nesneleri oluşturmanın + en kolay yollarından biridir. Daha önce de belirttiğimiz gibi dolaşılabilir nesneler içlemlerle de benzer sentaks ile + oluşturulabilmektedir. Ancak içlemler liste, küme ya da sözlük nesnesi yaratmaktadır. Bazı işlemlerde elemanlar bu + nesnelerin içerisinde gereksiz bir biçimde yer kaplayabilmektedir. Örneğin: + + result = statistics.mean([i * i for i in range(1000000)]) + + Burada ortalama elde edildikten sonra oluşturulan buı liste nesnesi zaten yok edilmektedir. Halbuki bunun boşuna 1000000 + elemandan oluşan bir liste yaratmış olduk. Aynı işlemi üretici ifadelerle daha etkin biçimde gerçekleştirebiliriz: + + result = statistics.mean((i * i for i in range(1000000))) + + Artık burada 1000000 eleman gereksiz bir biçimde yer kaplamamaktadır. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aşağıdaki örnekte print fonksiyonuna dolaşılabilir bir üretici ifade *'lı bir biçimde verilmiştir. Bu durumda bu üretici + ifade dolaşılıp elde edilen değerler ekrana yazdırılacaktır. +#------------------------------------------------------------------------------------------------------------------------ + +print(*(i for i in range(100) if i % 7 == 0)) + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyonun bir ifade içerisinde tanımlanarak o ifade içerisinde kullanılmasına programlama dillerinde "lambda + ifadeleri (lambda expressions)" denilmektedir. Aslında lambda ifadeleri eskiden fonksiyonel dillerde karşımıza çıkan + yapılardı. Ancak son yıllarda pek çok dile fonksiyonel özellikler katmak için lambda ifadeleri sokulmuştur. C++, C# + ve Java gibi dillerde lambda ifadeleri daha geniş bir sentaks yapısına dolayısıyla kullanım alanına sahiptir. Python'daki + lambda ifadeleri bu dillerdekilere gör oldukça kısıtlı ve minimalist bir yapıdadır. Lambda ifadelerinin genel biçimi şöyledir: + + lambda [parametre_listesi]: + + Örneğin: + + f = lambda a: a * a + result = f(10) + print(result) + + lambda ifadeleri aslında bir fonksiyon oluşturmaktadır. lambda anahtar sözcüğünün yanındaki değişken listesi fonksiyonun + parametreleridir. Burada fonksiyon tanımlamasında olduğu gibi (...) atomları kullanılmaz. İki nokta üst üste atomunun yanında + bir ifadenin olması gerekir. Bu ifade de aslında fonksiyonun geri dönüş değerini belirtir. Burada return anahtar sözcüğü + kullanılmaz. Zaten bu ifade return ifadesi anlamına gelmektedir. lambda ifadelerinden elde edilen değer bir fonksiyon nesnesinin + adresidir. Dolayısıyla biz onu bir değişkene atarsak o değişken fonksiyon nesnesini gösterecektir. Bu da tamamen fonksiyon + ile aynı etkinin yaratılması anlamına gelir. Örneğin: + + >>> f = lambda a: a * a + >>> type(f) + + >>> f(10) + 100 + >>> f(5) + 25 + + Bu durumda: + + f = lambda a: a * a + + işleminin eşdeğeri şöyledir: + + def f(a): + return a * a + + + Örneğin: + + f = lambda a, b: a + b + + Bu işlemin eşdeğeri de şöyledir: + + def f(a, b): + return a + b + + Tabii lambda ifadeleri bir deyim dğildir, ifadedir. Dolayısıyla başka ifadelerin içerisinde kullanılabilir. Örneğin: + + for x in map(lambda val: val * val, range(100)): + print(x, end=' ') + + Burada biz map fonksiyonuna bir fonksiyon oluşturup onu vermiş olduk. Bu işlemin eşdeğeri şöyledir: + + def square(val): + return val * val + + for x in map(square, range(100)): + print(x, end=' ') + + Örneğin: + + a = [12, 23, 14, 81, 64, 72, 17, 48] + + for x in filter(lambda val: val % 2 == 0, a): + print(x, end=' ') + + Burada biz listenin koşulu sağlayan elemanlarını elde etmiş olduk. + + Lambda ifadelerindeki ':' atomundan sonraki ifadede içinde bulunulan fonksiyonun yerel değişkenleri ve global değişkenler + kullanılabilir. Yani lambda ifadeleri bir fonksiyonun içerisinde kullanılmışsa adeta bir iç fonksiyon (nested funstion) gibi + işlem görmektedir. Örneğin: + + def foo(val): + for x in map(lambda a: a * val, [1, 2, 3, 4, 5]): + print(x, end=' ') + print() + + foo(5) + foo(10) + + Bu durumda yukarıdaki kodun eşdeğeri şöyledir: + + def foo(val): + def f(a): + return a * val + for x in map(f, [1, 2, 3, 4, 5]): + print(x, end=' ') + print() + + foo(5) + foo(10) + + lambda ifadelerinin parametresi olmak zorunda değildir. Ancak genellikle lambda ifadeleri parametreli olur. Örneğin: + + f = lambda: 100 + result = f() + print(result) + + lambda ifadelerinin birden fazla parametresi de olabilir. Örneğin: + + f = lambda a, b: a + b + result = f(10, 20) + print(result) # 30 + + Lambda ifadelerinde ':' atomunun sağında bir deyim olamaz. (Yani if, for return gibi deyimler kullanılamaz.) ':' atomunun + sağında yalnızca bir ifade olabilir. Tabii koşul operatörü bir operatör olduğu için lambda ifadelerinde kullanılabilir. Örneğin: + + f = lambda a, b: a if a > b else b + + result = f(10, 20) + print(result) # 20 + + Buradaki if deyim değildir bir operatör görevindedir. Örneğin: + + a = [3, 6, 1, 8, 9, 2, 4, 7] + + for x in map(lambda val: 1 if val % 2 == 0 else 0, a): + print(x, end=' ') + + Burada listenin çift elemanları için 1 değeri tek elemanları için 0 değeri elde edilmektedir. + + Lambda ifadeleri fonksiyon belirttiğine göre hiç ara değişken kullanılmadan fonksiyon çağırma operatör ile çağrılabilir. Ancak + tabii dıştan bir parantez zorunludur. Örneğin: + + result = (lambda a, b: a if a > b else b)(10, 20) + + print(result) # 20 + + Mademki aslında metotlar birer sınıf değişkenidir. O halde bir lambda ifadesi ile bir metot da oluşturabiliriz. Örneğin: + + class Sample: + def __init__(self, a): + self.a = a + + get_a = lambda self: self.a + + s = Sample(10) + + result = s.get_a() + print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python'daki lambda ifadeleri diğer dillerdeki lambda ifadeleriyle kıyaslandığında oldukça basit bir yapıdadır. Diğer + dillerin bazılarında lambda ifadelerinde deyimler kullanılabilmektedir. Python'daki lambda ifadelerinde yalnızca + ifadelerin kullanılıyor olması lambda ifadelerinin gücünü düşürebilmektedir. Ancak Python'ın sentaktik yapısı dikkate + alındığında dile ayrıntılı bir lambda ifadesiyerleştirmenin zoruluğu da anlaşılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + 60. Ders 28/11/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların eleman erişiminde kullanılan __getattr__ isimli bir metodu vardır. Bir sınıf türünden değişkenle o sınıfın + olmayan bir örnek özniteliğine ya da bir metoduna erişildiğinde yorumlayıcı tarafından sınıfın __getattr__ metodu + çağrılmaktadır. __getattr__ metodunun self parametresi dışında erişilmek istenen elemanın ismini alan bir name parametresi + (ismi name olmak zorunda değildir) de vardır. __getattr__ metodunun geri dönüş değeri erişim sonucunda elde edilen değerdir. + Örneğin: + + class Sample: + def __getattr__(self, name): + print(name) + return 0 + + s = Sample() + x = s.val + print(x) # 0 + + Burada s.val ifadesinde sınıfın olmayan bir örnek özniteliğine erişilmek istenmiştir. Bu durumda sınıfın __getattr__ metodu + çağrılacak ve name parametresine erişimde kullanılan gerçekte olmayan "val" ismi geçirilecektir. Bu erişimden elde edilen + değer __getattrr__ metodundan döndürülen değerdir. Eğer nesne ile zaten var olan bir elemana erişiyorsa bu durumda + __getattr__ çağrılmamaktadır. Örneğin: + + class Sample: + def __init__(self): + self.val = 10 + def __getattr__(self, name): + print(name) + return 0 + + s = Sample() + x = s.val + print(x) # 10 + + Burada s.val erişiminde val zaten vardır. Bu durumda __getattr__ çağrılmayacaktır. + + Normal olarak eğer sınıfta __getattr__ metodu yoksa biz sınıfın olmayan bir elemanına erişmeye çalıştığımızda AttributeError + isimli exception oluşur. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self): + self.a = 10 + + def __getattr__(self, name): + print(f'__getattr__ called: {name}') + return 0 + +s = Sample() + +print(s.x) +print(s.y) + +result = s.x + s.y +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Tabii nesne ile olmayan bir örnek özniteliğine değer atama işlemi zaten o örnek özniteliğinin yaratılmasına yol açtığı + için bu durumda __getattr__ metodu çağrılmamaktadır. Örneğin: + + class Sample: + def __init__(self): + self.a = 10 + + def __getattr__(self, name): + print(f'__getattr__ called: {name}') + return 0 + + s = Sample() + + s.x = 100 # burada __getattr__ çağrılmayacak + print(s.x) # burada da __getattr__ çağrılmayacak, çünü artık x örnek özniteliği var + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfın __getattr__ metodu "property" kavramını oluşturmak amacıyla ve başka birtakım amaçlarla kullanılabilmektedir. + Nesne yönelimli programlama dillerinin bir bölümünde var olan "property" kavramı metotların adeta birer örnek özniteliği + gibi kullanılmasını sağlayan bir mekanizmadır. Yani bu kavram sayesinde programcı nesnenin bir örnek özniteliğine eriştiğinde + aslında arka planda bir metot çağrılmaktadır. C++ ve Java gibi dillerde property kavramı yoktur. Fakat örneğin C#'ta vardır. + Python'da property'ler __getattr__ metodu yoluyla ya da property isimli dekoratör yoluyla oluşturulabilmektedir. + Örneğin Circle isimli bir sınıf yazmak isteyelim. Sınıfın area isimli bir örnek özniteliğinin olmasına gerek yoktur. + Çünkü area aslında yarıçaptan hareketle hesaplanabilmektedir. İşte biz __getattr__ yoluyla sanki area isimli örnek bir + özniteiği sınıfta varmış gibi bir durum oluşturabiliriz: + + class Circle: + def __init__(self, radius, x, y): + self.radius = radius + self.x = x + self.y = y + + def __getattr__(self, name): + if name == 'area': + return 3.14159 * self.radius ** 2 + raise AttributeError(f"Circle object has no attribute '{name}'") + + def __repr__(self): + return f'radius: {self.radius}, center: ({self.x},{self.y})' + + c = Circle(10, 3, 4) + print(c) + + print(c.area) + + Bu örnekte nesne yoluyla aslında olmayan area örnek özniteliğine erişildiğinde __getattr__ çağrılacak ve dairenin alanı + o anda hesaplanarak sanki bu area örnek özniteliğinin değeriymiş gibi verilecektir. Örneğimizde area ismi dışında olmayan + başka bir örnek özniteliği kullanılırsa AttributeError exception'ının oluşturulduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class Circle: + def __init__(self, radius, x, y): + self.radius = radius + self.x = x + self.y = y + + def __getattr__(self, name): + if name == 'area': + return 3.mat.pi * self.radius ** 2 + raise AttributeError(f"Circle object has no attribute '{name}'") + + def __repr__(self): + return f'radius: {self.radius}, center: ({self.x},{self.y})' + +c = Circle(10, 3, 4) +print(c) + +print(c.area) +print(c.xxxxxxxx) # exception oluşacak + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi property kavramına neden gereksinim duyulmaktadır? Bunu basit bir örnekle açıklayabiliriz. Biz __getattr__ metodu + ile property oluşturmadan aynı işlemi sınıfa area isimli örnek özniteliğini yerleştirerek yaptığımızı düşünelim: + + import math + + class Circle: + def __init__(self, radius, x, y): + self.radius = radius + self.x = x + self.y = y + self.area = math.pi * radius ** 2 + + def __repr__(self): + return f'radius: {self.radius}, center: ({self.x},{self.y})' + + c = Circle(10, 3, 4) + print(c) + print(c.area) + + Burada sınıfı kullanan programcı dışarıdan radius örnek özniteliğini değiştirirse area bilgisi değiştirilmeyecektir. + Örneğin: + + c = Circle(10, 3, 4) + print(c.area) + c.radius = 20 + print(c.area) + + Halbuki biz area örnek özniteliğine erişildiğinde bir metodun çağrılmasını saplarsak bu metot o andaki radius değerini + kullanacağı için sorun olmayacaktır. İşte sınıfın birbirleriyle ilişkili veri elemanları varsa onların arasındaki koordinasyon + property'lerle sağlanabilmektedir. C++ ve Java gibi dillerde property kavramı olmadığı için bu tür işlemler getter/setter + metotlarıyla yapılmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfta olmayan elemanların kullanılması teması property dışında başka yerlerde de karşımıza çıkabilmektedir. + Örneğin biz __getattr__ sayesinde sınıf nesnesi yaratırken verdiğimiz isimli argümanları sanki onlar birer örnek + özniteliğiymiş gibi kullanabiliriz. Aşağıdaki örnekte sınıfın __init__ metodunda olmayan isimli argümanlar saklanmış, + __getattr__ metodunda da bunlara erişildiğinde bunların değerleri geri döndürülmüştür. Yani adeta nesnenin öznitelikleri + __init__ metodunda verilmiş gibi olmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __init__(self, **kwargs): + self.kwargs = kwargs + + def __getattr__(self, name): + if val := self.kwargs.get(name): + return val + + raise AttributeError(f"Sample object has no attribute '{name}'") + +s = Sample(x = 10, y = 20, z = 30) + +print(s.x) +print(s.y) +print(s.z) + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfın __getattr__ metoduna benzer bir de __getattribute__ metodu vardır. __getattribute__ metodu bir sınıf türünden + değişken ile sınıfın bir elemanına erişilirken eleman olsa da olmasa da çağrılmaktadır. Halbuki __getattr__ metodunun + eleman yoksa çağrıldığını anımsayınız. Tabii nesnenin olmayan bir elemanına değer atandığında __getattribute__ metodu + çağrılmamaktadır. Çünkü olmayan elemana değer atama işlemi aslında onun yaratılmasına yol açmaktadır. Tabii yine tıpkı + __getattr__ metodunda olduğu gibi eleman olsa da olmasa da __getattribute__ metodunun geri dönüş değeri erişimden elde + edilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + def __getattribute__(self, name): + print(f'__getattr__ called: {name}') + return 0 + + def _foo(self): + print('foo') + +s = Sample() +s.x = 10 # __getattribute__ çağrılmayacak +print(s.x) # __getattribute__çağrılacak, ekrana 0 basılacak +print(s.y) # __getattribute__ çağrılacak, ekrana 0 basılacak + +#------------------------------------------------------------------------------------------------------------------------ + 61. Ders 30/11/2022 - Çarşamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfın __getattribute__ metodunda sınıfın bir elemanına erişmeye çalışırsak yeniden __getattribute__ çağrılacağı için + özyinelemeli bir durum oluşşacaktır. Programcı genellikle böyle b,r durumun oluşmasını istemez. Örneğin: + + class Sample: + def __init__(self, **kwargs): + self._x = 10 + + def __getattribute__(self, name): + if name == 'x': + return self._x + + s = Sample() + + val = s.x + print(val) # None + + Burada s.x erişiminde sınıfın __getattribute__ metodu çağrılacaktır. Bu metot çağrıldığında name parametresinde 'x' ismi + bulunacaktır. Böylece metot return self._x deyimini görecektir. Ancak self._x erişimi de yeniden __getattribute__ metodunun + çağrılmasına yol açacaktır. Ancak bu kez akış if içerisine girmeyecek ve metot None değeri ile geri dönecektir. + + __getattribute__ metodu içerisinde özyineleme yapmadan nesnenin örnek özniteliğine erişmenin tek yolu object sınıfının + __getattribute__ metodunu kullanmaktır. Eğer object sınıfının __getattribute__ metodu kullanılırsa artık ilgili sınıfın + __getattribute__ metodu çağrılmaz. Örneğin: + + class Sample: + def __init__(self, **kwargs): + self._x = 10 + + def __getattribute__(self, name): + if name == 'x': + return object.__getattribute__(self, '_x') + + s = Sample() + + val = s.x + print(val) # None + + Burada object.__getattribute(self, '_x'') çağrısı ile artık özyineleme yapmadan _x isimli örnek özniteliğinin değerine + erişilmiştir. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi sınıfın hem __getattr__ hem de __getattribute__ metotları varsa ne olur? İşte bu durumda __getattr__ hiç devreye + girmez. Olan elemana erişimde zaten __getattr__ devreye girmeyecektir. Olmayan elemana erişimde de __getattr__ devreye + girmez. Yani bu durumda __getattr__ metodunu yazmanın bir anlamı kalmaz. Örneğin: + + class Sample: + def __getattr__(self, name): + print('__getattribute__ called') + return 0 + + def __getattribute__(self, name): + print('__getattribute__ called') + return 0 + + s = Sample() + + s.x = 10 + val = s.x # __getattribute__ çağrılır + val = s.y # __getattribute__ çağrılır + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların __setattr__ metotları bir sınıf nesnesi ile nesnenin olan ya da olmayan elemanlarına değer atandığı durumda + çağrılmaktadır. Bu anlamda __setattr__ adeta __getattribute__ metodunun set yapan biçimi gibidir. Ayrıca bir __setattribute__ + metodu bulunmamaktadır. __setattr__ metodunun self parametresi dışında iki parametresi daha vardır. Bunlardan ilki atama + yapılmak istenen elemanın ismini belirtir. İkincisi ise o elemana atanmak istenen değeri belirtmektedir. Yani __setattr__ + metodunun parametrik yapısı şöyle olmalıdır: + + def __setttr__(self, nanme, value): + pass + + Örneğin: + + class Sample: + def __init__(self): + self.a = 10 # dikkat! yine __setattr__ çağrılacak + + def __setattr__(self, name, value): + print(name, value) + + s = Sample() + + s.a = 20 # dikkat! _setattr__ çağrılacak + + __setattr__ metodu çağrıldığında artık elemana atama yapılmamaktadır. Elemana atama yapılmak isteniyorsa bunu programcının + __setattr__ metodu içerisinde yapması gerekmektedir. Tabii __setattr__ içerisinde nesnenin bir örnek özniteliğine atama + yapılırsa yine "özyineleme (resursion)" oluşur. Bunun oluşması istenmiyorsa atama izleyen paragraflarda açıklayacağımız + nesnenin __dict__ elemanı yoluyla ya da object sınıfının __setattr__ metodu yoluyla yapılmalıdır. Örneğin: + + class Sample: + def __init__(self): + self._x = 0 + + def __getattr__(self, name): + if name == 'x': + return self._x + + raise AttributeError(f"Sample object has no attribute '{name}'") + + def __setattr__(self, name, value): + if name == 'x': + object.__setattr__(self, '_x', value) + else: + object.__setattr__(self, name, value) + + s = Sample() + print(s.x) + s.x = 10 + print(s.x) + + Burada aslında var olmayan bir x özniteliği var olan _x ismi ile kullanılmaya çalışılmıştır. Biz Sample türünden bir değişken + ile nesnenin x özniteliğine erişmek istediğimizde sınıfın __getattr__ metodu çağrılacak, bu metot da aslında sınıfın _x + özniteliğinin değerini verecektir. Biz x özniteliğine değer atamak istediğimizde sınıfın __setattr__ metodu çağrılacaktır. + Biz de bu metot içerisinde aslında nesnenin _x özniteliğine değer atamaktayız. Ancak bu atamanın özyinelemeyi engellemek + için object sınıfı yoluyla yapıldığına dikkat ediniz. + + Aslında __setattr__ çağrılmadan nesnenin bir özniteliğine eleman atamak için __dict__ örnek özniteliğini de kullanabiliriz. + __dict__ örnek özniteliği izleyen bölümde ele alınmaktadır. Ancak burada bir örnek vermek amacıyla bunu kullanmak istiyoruz: + + class Sample: + def __init__(self): + self._x = 0 + + def __getattr__(self, name): + if name == 'x': + return self._x + + raise AttributeError(f"Sample object has no attribute '{name}'") + + def __setattr__(self, name, value): + if name == 'x': + self.__dict__['_x'] = value + else: + self.__dict__[name] = value + + s = Sample() + print(s.x) + s.x = 10 + print(s.x) + + Sınıfın __setattr__ metodu yine bir property etkisi yaratmak için kullanılabilmektedir. Aşağıdaki örnekte Circle sınıfında + aslında area isimli bir örnek özniteliği yoktur. Ancak __getattr__ ve __setattr__ yoluyla sanki böyle bir öznitelik varmış + gibi bir etki oluşturulmuştur. Burada area özniteliğine erişildiğinde aslında o anda dairenin alanı hesaplanıp verilmektedir. + area özniteliğine atamaya yapıldığında ise dairenin yarı çapı hesaplanıp radius özniteliğine atanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +import math + +class Circle: + def __init__(self, radius, x, y): + self.radius = radius + self.x = x + self.y = y + + def __repr__(self): + return f'radius: {self.radius}, center: ({self.x},{self.y}), area: {self.area}' + + def __getattr__(self, name): + if name == 'area': + return math.pi * self.radius ** 2 + + raise AttributeError(f"Circle object has no attribute '{name}'") + + def __setattr__(self, name, value): + if name == 'area': + self.radius = math.sqrt(value / math.pi) + else: + self.__dict__[name] = value + +c = Circle(3, 10, 12) +print(c) + +c.radius = 20 +print(c) + +c.area = 10 +print(c) + +#------------------------------------------------------------------------------------------------------------------------ + Daha önce de bahsettiğimiz gibi property'ler veri elemanı gibi kullanılan metotlardır. Biz yukarıda Circle sınıfı örneğinde + Circle sınıfın area isimli bir örnek özniteliğini kullanmıştık. Ancak gerçekte böyle bir örnek özniteliği sınıfta yoktu. Aslında + böyle bir örnek özniteliğinin sınıfta olmasına gerek de yoktu. Çünkü eğer biz dairenin yarıçapını biliyorsak zaten alanını + hesaplayabiliriz. Onun için ayrıca yer kaplayan bir eleman bulundurmamıza gerek yoktur. İşte yukarıdaki örnekte Circle + sınıfında gereksiz bir area örnek özniteliğini tutmak yerine biz sanki bu örnek özniteliği varmış gibi durum oluşturduk. + + Ancak property kullanımının dşiğer bir amacı da (birinci amaçtan daha aşağı bir amaç değildir) "veri elemanlarının gizlenmesi + (data hiding)" prensibinin uygulanmasını sağlamaktır. Veri elemanlarının gizlenmesi NYPT'nin önemli prensiplerinden biridir. + Genellikle Python'da programcılar bu prensibi kullanmazlar. Ancak bazı programcılar property'ler yoluyla bu prensibi + kullanabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Veri elemanlarının gizlenmesi (data hiding) veri elemanlarının dışarıdan kullanımını engelleyip onlara kodlar yoluyla + erişimi mümkün hale getirmek anlamına gelmektedir. Python'da dışarıdan erişilmesini istemediğimiz öniteliklerin başına + '_' ve '__' ekliyorduk. Tabii bu ekleme erişimi engellemiyordu. Yalnızca dışarıya bir "erişme" biçiminde mesaj veriyordu. + Halbuki C++, Java ve C# gibi diğer dillerde dışarıdan erişimin tamamen engellenmesi için sınıflarda "private" bulundurulmuştur. + + NYPT'de sınıfın örnek öznitelikleri (veri elemanları) iç işleyişe ilişkin olma eğilimindedir. Özniteliklere dışarıdan + erişilmesini engellemenin ve onlara kodlar yoluyla erişilmesine izin verilmesinin dört temel neden vardır: + + 1) Örnek özniteliklerine dışarıdan erişilirse onlar üzerinde yapılacak değişikliklerden onları kullanan kodlar etkilenir. + Örneğin: + + class Date: + def __init__(self, day, month, year): + self.day = day + self.month = month + self.year = year + # ... + + d = Date(10, 20, 2007) + print(d.day, d.month, d.year) + + Burada sınıfın day, month ve year örnek öznitelikleri yerine programcı daha sonra tarih bilgisini bir yazıy ile tutacak biçimde + tek bir eleman kullanırsa bu sınıfı kullanan kodlar geçersiz hale gelir. Halbuki bu özniteliklere doğrudan erişimi engelleyip + onlara kodlarla erişimi mümkün hale getirirsek bu durumda biz sınıfın bu örnek özniteliklerinde değişiklik yapsak bile bu + değişiklikten onları kullanmış olan kodların etkilenmesişni engelleyebiliriz. + + 2) Sınıfın bir örnek özniteliğine programcı geçersiz değerler atayabilir. Bu da tamamen sınıfın hatalı çalışmasına yol açabilir. + Örneğin: + + d.day = 100 + + Burada aslında gün bilgisi geçersiz bir biçimde oluşturulmuştur. Muhtemelen bu durum geçiştirilirse nesnenin davranışı + bozulacaktır. Halbuki sınıfın örnek özniteliklerine bir kod ile değer atanırsa sınır kontrolü uygulanabilir. + + 3) Sınıfın birbirleriyle ilişkili örnek öznitelikleri olabilir. Yani bunlardan birine değer atandığında diğerlerinin + de değiştirilmesi gerekebilir. İşte bu tür işlemlerin kodla yapılması bu karmaşık ilişkinin arka planda doğru bir biçimde + oluşturulmasını sağlayabilmektedir. + + 4) Bazen nesnenin bir özniteliğinden değer alırken ya da ona değer yerleştirirken aynı zamanda başka bir işlemin de + yapılması gerekebilmektedir. İşte örnek özniteliklerine doğrudan değil kodla erişilirse arka planda bu işlemler + yapılabilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Propert'ler aslında yukarıda da örneklerini verdiğimiz gibi sınııfn __getattr__ ve __setattr__ metotları yoluyla + oluşturulabilmektedir. Ancak bu yöntem biraz zahmetlidir. Python'da property oluşturulmasını kolaylaştırmak için property + isminde bir dekoratör sınıf bulundurulmuştur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python'da property kullanımı aslında property isimli bir dekoratör sınıf yoluyla kolay bir biçimde yapılabilmektedir. + property sınıfı built-in bir sınıftır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: + + property(fget=None, fset=None, fdel=None, doc=None) + + Bunu sınıfsal olarak aşağıdaki gibi de gösterebiliriz: + + class property: + def __init__(self, fget = None, fset = None, fdel = None, doc = None) : + pass + + Property kullanımı şöyle yapılmaktadır: + + 1) Programcı sınıfında gizlediği örnek özniteliğinin değerini alacak ve değerini set edecek iki metot bulundurur. (Aslında + delete işlemi ve doc işlemi için de iki metot daha bulundurabilmektedir.) get metodunun yalnızca self parametresi olur ve + bu metot get edilecek değer ile geri döndürülür. set metodunun ise self dışında bir parametresi daha vardır. Bu parametre + örnek özniteliğine atanan değeri belirtmektedir. set metodunun bir geri dönüş değeri yoktur. + + 2) Programcı sonra property sınıfı türünden bir sınıf değişkeni ekler. Bu sınıf değişkeni aslında değişken yoluyla erişimde + kullanılacak örnek özniteliğini belirtmektedir. Artık bu sınıf türünden bir değişken oluşturup, o değişken ile ilgili öziteliğe + onun değerini alacak bir ifadeyle erişildiğinde get metodu, ona değer yerleştirmek istendiğinde de set metodu çalışacaktır. + Örneğin: + + class Sample: + def _get_x(self): + pass + + def _set_x(self, value): + pass + + x = property(_get_x, _set_x) + + Burada artık adeta nesnenin x isimli bir örnek özniteliği varmış gibi bir durum oluşturulmuştur. Örneğin: + + s = Sample() + + s.x = 10 # _set_x metodu çağrılır + print(s.x) # _get_x metodu çağrılacak + + Şimdi de daha önce __getattr__ ve __setattr__ kullanarak yapmış olduğumuz Circle sınıfın area property'sini bu yöntemle + gerçekleştirelim: + + import math + + class Circle: + def __init__(self, radius, x, y): + self._radius = radius + self._x = x + self._y = y + + def __repr__(self): + return f'radius: {self._radius}, center: ({self._x},{self._y}), area: {self.area}' + + def _get_area(self): + return math.pi * self._radius ** 2 + + def _set_area(self, value): + self._radius = math.sqrt(value / math.pi) + + area = property(_get_area, _set_area) + + c = Circle(3, 10, 12) + print(c) + + c.radius = 20 + print(c) + + c.area = 10 + print(c) + + Burada Circle sınıfına area isimli bir property eklenmiştir. Bu property bir örnek özniteliği gibi kullanılmaktadır. + Ancak aslında bu property kullanıldığında sınıfın _get_area ve _set_area metotları çalıştırılmaktadır. + + Tabii sınıfa property eklendiğinde artık sınıfın örnek özniteliklerine bu property yoluyla erişilecektir. Bu durumda + sınıfın proporty'si olan örnek özniteliklerinin başına '_' karakteri getirilerek isimlendirilmesi uygun olur. Benzer + biçimde property fonksiyonuna verdiğimiz getter, setter ve deleter metotları da bu biçimde isimlendirilmelidir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tabii property'lerin read/write olması gerekmez. Örneğin "read-only" property'ler söz konusu olabilmektedir. Read-only + property demek yalnızca get yapılan ama set yapılamayan property demektir. + + Aşağıdaki örnekte Date sınıfına tarih bilgisinin gün, ay ve yıl bileşenlerinin elde edildiği day, month ve year property'leri + eklenmiştir. Bu property'lerin read-only olduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self._day = day + self._month = month + self._year = year + + def _get_day(self): + return self._day + + def _get_month(self): + return self._month + + def _get_year(self): + return self._year + + day = property(_get_day) + month = property(_get_month) + year = property(_get_year) + +d = Date(9, 1, 2024) + +print(d.day, d.month, d.year) + +#------------------------------------------------------------------------------------------------------------------------ + property sınıfının __init__ metodunun üçüncü parametresi (başka bir deyişle property fonksiyonunun üçüncü parametresi) + ilgili örnek özniteliğini del deyimi ile silmeye çalıştığımızda çağrılacak metodu belirtmektedir. Örneğin: + + class Sample: + def __init__(self, a): + self._a = a + + def _get_a(self): + return self._a + + def _set_a(self, value): + self._a = value + + def _del_a(self): + print('deleting self._a') + del self._a + + a = property(_get_a, _set_a, _del_a) + + s = Sample(10) + + print(s.a) + + s.a = 20 + print(s.a) + + del s.a + + property için delete metodunun yazılmasına seyrek bir biçimde gereksinim duyulmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Aslında property'ler dekoratör sentaksıyla çok daha kolay bir biçimde oluşturulabilmektedir. Örneğin: + + class Sample: + def __init__(self, a): + self._a = a + + @property + def a(self): + return self._a + + Anımsanacağı gibi burada @property dekoratörünün eşdeğeri şöyledir: + + class Sample: + def __init__(self, a): + self._a = a + + def a(self): + return self._a + + a = property(a) + + Yani biz yine property fonksiyonuna a metodunu verip property ismi olarak a ismini oluşturmuş olmaktayız. Bu sentaks + property oluşturmak için oldukça kolaydır. Tabii burada biz yalnızca örnek özniteliğini get ettik. Bunun set edilmesini + izleyen paragrafta ele alacağız. +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self._day= day + self._month = month + self._year = year + + @property + def day(self): + return self._day + + @property + def month(self): + return self._month + + @property + def year(self): + return self._year + +d = Date(9, 1, 2024) + +print(d.day, d.month, d.year) + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi dekoratör yoluyla setter metodu nasıl yazılmaktadır? İşte setter metodu için dekoratör fonksiyonu görevini + property sınıfının setter metodu yapmaktadır. Programcı önce property dekoratörü ile getter metodunu oluşturur. Sonra da + getter ismini kullanarak property sınıfının setter metodunu dekoratör yapar. Örneğin: + + Örneğin: + + class Sample: + def __init__(self, a): + self._a = a + + @property + def a(self): + return self._a + + # a = property(a) + + @a.setter + def a(self, val): + self._a = val + + # a = a.setter(a) + + s = Sample(10) + + print(s.a) + + s.a = 20 + print(s.a) + + Burada aşağıdaki dekoratöre dikkat ediniz: + + @a.setter + def a(self, val): + self._a = val + + Bu dekoratörün eşdeğer kodu şöyledir: + + a = a.setter(a) +#------------------------------------------------------------------------------------------------------------------------ + +class Date: + def __init__(self, day, month, year): + self._day= day + self._month = month + self._year = year + + @property + def day(self): + return self._day + + @day.setter + def day(self, value): + self._day = value + + # day = day.setter(day) + + @property + def month(self): + return self._month + + @month.setter + def month(self, value): + self._month = value + + # month = month.setter(month) + + @property + def year(self): + return self._year + + @year.setter + def year(self, value): + self._year = value + + # year = year.setter(year) + +d = Date(9, 1, 2024) + +print(d.day, d.month, d.year) + +d.day = 10 +d.month = 12 +d.year = 2003 + +print(d.day, d.month, d.year) + +#------------------------------------------------------------------------------------------------------------------------ + property sınıfının kendisinin tam olarak yazılması izleyen paragraflarda ele alacağımız "descriptor" kullanımı ile mümkün + olabilmektedir. Ancak biz yukarıdaki setter işleminde property nesnesinin naısl oluşturulduğu konusunda bir fikir vermek + istiyoruz. Bunun için property sınıfının myproperty isminde eksik bir halini oluşturalım: + + class myproperty: + def __init__(self, fget = None, fset = None, fdel = None, fdoc = None): + self._fget = fget + self._fset = fset + self._fdel = fdel + self._fdoc = fdoc + + def setter(self, fset): + self._fset = fset + return self + + Tabii burada oluşturduğumuz myproperty sınıfı yalnızca setter metodunun nasıl property nesnesine eklendiğini açıklamak + için oluşturulmuştur. Yukarıda da belirttiğimiz gibi property işlevinin yerine getirilmesi için "descriptor" konusunun + kullanılması gerekmektedir. Buradaki myproperty sınıfının aşağıdaki gibi kullanıldığını varsayalım: + + class Sample: + def __init__(self, a): + self._a = a + + @myproperty + def a(self): + return self._a + + @a.setter + def a(self, value): + self._a = value + return self + + Burada yorumlayızı önce myproperty dekoratörünü görecektir. Bu dekoratörün eşdeğeri şöyledir: + + a = myproperty(a) + + Böylece aslında myproperty nesnesinin _fget özniteliğine bu a metodu yerleştirilmiş olacaktır. Yani bu metot bir getter + olarak myproperty nesnesine yerleştirilecektir. Sonra yotumlayıcı diğer dekoratörü örecektir. Onun da eşdeğeri şöyledir: + + a = a.setter(a) + + Burada aslında myproperty sınıfının setter metodu çalıştırılmaktadır. Bu metot self ile geri döndürüldüğü için a'ya yine aynı + myproperty nesnesi atanmış olacaktır. Böylece aslında yine myproperty nesnesinin içerinde getter ve setter metotları + bulunuyor olacaktır. + + Burada bir yer kafa karıştırabilir. a eğer myproperty sınıfı türündense aşağıdaki metotta a ismi değişmeyecek midir? + + @a.setter + def a(self, val): + self._a = val + + Çünkü bu kodun eşdeğerinin aşağıdaki gibi olduğunu görmüştük: + + def a(self, val): + self._a = val + + a = a.setter(a) + + Bu eşdeğerlilikten hareketle a metodunu gören yorumlayıcı a değişkenine değer atadığında a değişkeninin içinin bozulacağını + düşünebilirsiniz. İşte dekoratör sentaksında aslında dekoratöre konu olan değişken boşuna hiç oluşturulmamaktadır. Yani burada a + ismi aslında bozulmamaktadır. Başka bir deyişle aslında yukarıdaki dekoratörün eşdeğeri şöyledir: + + def some_temporary_name(self, val): + self._a = val + + a = a.setter(some_temporary_name) + + Başka bir deyişle örneğin: + + @foo + def bar(): + pass + + Burada aslında bar değişkenine iki kere değer atanmamaktadır. Yani yorumlayıcı önce fonksiyon nesnesinin adresini bar değişkenine + atayıp sonra onu dekatöre vermemektedir. Fonksiyon nesnesini yaratıp henüz bar değişkenine atamdan dekoratöre vermektedir. Dolayısıyla + aslında bar değişkenine yalnızca dekoratörün döndürdüğü değer atanmaktadır. + + deleter metodu da yine property nesnesinin deleter isimli metoduyla oluşturulmaktadır. Örneğin: + + class Sample: + def __init__(self, a): + self._a = a + + @property + def a(self): + return self._a + + # a = myproperty(a) + + @a.setter + def a(self, value): + self._a = value + + # a = a.setter(a) + + @a.deleter + def a(self): + print('a deleter') + + # a = a.deleter + + s = Sample(10) + + del s.a + + Bu dekoratörün oluşturulma biçimi de tamamen setter property'sinde olduğu gibidir. Örneğin: + + class myproperty: + def __init__(self, fget = None, fset = None, fdel = None, fdoc = None): + self._fget = fget + self._fset = fset + self._fdel = fdel + self._fdoc = fdoc + + def setter(self, fset): + self._fset = fset + return self + + def deleter(self, fdel): + self._del = fdel + return self + +#------------------------------------------------------------------------------------------------------------------------ + 62. Ders 05/12/2022 - Pazartesi +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıfların diğer bir özel metodu ise __new__ isimli metottur. Biz bir sınıf türünden nesne yarattığımızda aslında bu + nesne iki aşamada kullanıma hazır hale getirilmektedir: + + 1) Önce ilgili sınıfın __new__ isimli static static metodu çağrılır. Bu işlem sonucunda nesnenin bellekte tahsis edilmesi + sağlanır. + + 2) Nesne için bellekte yer tahsis edildikten sonra onun örnek özniteliklerinin yaratılması ve birtakım gerekli ilk işlemlerin + yapılması için sınıfın __init__ metodu çağrılır. + + Örneğin: + + s = Sample() + + Burada aslında yorumlayıcı önce Sample sınıfının __new__ isimli static metodunu çağrır, daha sonra __init__ metodunu çağırır. + __new__ metodunun amacı tahsisat yapmak, __init__ ise amacı nesneye ilkdeğerlerini vermektir. + + Aslında nesne için tahsisat object sınıfının __new__ static metodu ile yapılmaktadır. Yani programcı kendi sınıfı için __new__ + static metodunu yazmış olsa bile aslında asıl tahsisatın yine object sınıfının __new__ metoduyla yapılmasını sağlamalıdır. + O halde programcı aslında "tahsisatı yapmak için değil, yalnızca araya girmek için" bu __new__ static metodunu yazmak ister. + + Programcılar sınıfları için __new__ metodunu genellikle yazmazlar. __new_ metodunun yazılmasına seyrek olarak gereksinim + duyulmaktadır. Yukarıda da belirttiğimiz gibi __new__ metodu static bir metottur ve bu metodun bir parametresi olmak zorundadır. + Bu parametre yaratılmak istenen sınıfın bilgilerini belirten o sınıfa ilişkin type nesnesini almaktadır. __new__ metodu + tahsis edilen nesne ile geri dönmelidir. Tabii yukarıda belirttiğimiz gibi asıl tahsisat object sınıfının __new__ metodu + ile yapıldığı için programcının kendi sınıfı için yazdığı __new__ metodunun object sınıfının __new__ metodunun geri dönüş + değeri ile geri dönmesi gerekir. Bu durumda sınıfın __new__ metodu tipik olarak şöyle yazlmalıdır: + + @staticmethod + def __new__(cls): + # araya girme işlemi + return object.__new__(cls) + + Görüldüğü gibi programcı tahsisatı kendisi yapmamaktadır. Tahisat object sınıfının __new__ metodu tarafından yapılmaktadır. + Programcı yalnızca bu mekanizmada araya girmektedir. + + Pekiyi biz sınıfımız için __new__ metodunu yazmadığımızda ne olmaktadır? Bu durumda taban sınıfın yani object sınıfının __new__ + metodu çağrılacaktır. Zaten tahsisat da bu object sınıfının __new__ metodu tarafından yapılmaktadır. Ancak burada ince bir nokta + üzerinde durmak istiyoruz. Bizim __new__ metodunu yazdığımız sınıfın taban sınıfı da __new__ metodunu araya girme amaçlı + yazmış olabilir. Bu durumda bizim doğrudan object sınıfının __new__ metodunu çağırmak yerine taban sınıfın __new__ metodnu + çağırmamız daha uygun olur. Yani __new__ metodu aslında aşağıdaki gibi yazılmalıdır: + + @staticmethod + def __new__(cls): + # araya girme işlemi + return super().__new__(cls) + + Biz burada taban sınıfın __new__ metodunu çağırmış olduk. Aynı işlemi taban sınıflar da yapacağına göre yine tahsisat object + sınıfının __new__ metodu tarafından yapılmış olacaktır. Örneğin: + + class A: + def __init__(self): + print('A.__init__') + + @staticmethod + def __new__(cls): + print('A.__new__') + return super().__new__(cls) + + class B(A): + def __init__(self): + super().__init__() + print('B.__init__') + + @staticmethod + def __new__(cls): + print('B.__new__') + return super().__new__(cls) + + s = B() + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıf nesnesinin tahsis edilmesi sürecinde bazı ayrıntılar vardır. Anımsanacağı gibi bir sınıf türünden değişkenle + fonksiyon çağırma operatörü kullanıldığında sınıfın __call__ isimli metodu çağrılmaktaydı. Örneğin: + + class Sample: + def __call__(self): + print('__call__') + + s = Sample() + + s() # s.__call__() + + Yine anımsanacağı gibi bir sınıfın tanımlamasını gören yorumlayıcı önce type sınıfı türünden bir nesne yaratıp sınıfın + bilgilerini o nesneye yerleştiriyordu ve o nesnenin adresini de sınıf ismine ilişkin değişkene atıyordu. Yani sınıf isimleri + aslında type sınıfı türünden bir nesneyi gösteren değişken biçimindeydi. Örneğin: + + class Sample: + pass + + Burada Sample değişkeni type sınıfı türündendir. Bir sınıf türünden nesneyi fonksiyon çağırma operatörü ile yarattığımızı + anımsayınız: + + s = Sample(...) + + O halde aslında bir sınıf nesnesi type sınıfının __call__ metodu ile yaratılmaktadır. İşte type sınıfının __call__ metodu + aşağıdaki gibi yazılmıştır: + + class type: + def __call__(self, *args, **kwargs): # 1 + instance = self.__new__(self, *args, **kwargs) # 2 + if isinstance(instance, self): + instance.__init__(*args, **kwargs) # 3 + return instance # 4 + + Burada önemli noktaları tek tek belirtelim: + + 1) Bir sınıf türünden nesne yaratılmak istendiğinde aslında type sınıfının __call__ metodu çağrılmaktadır. Neeneyi yaratırken + kullandığımız tüm isimsiz ve isimli argümanlar bu __call__ metoduna geçirilmektedir. + + 2) type sınıfının __call__ metodunda ilgili sınıfın static __new__ metodu çağrılmaktadır. Tahsisat bu çağrı ile yapılmaktadır. + __new__ metoduna nesneyi yaratırken geçirilen argümanların da geçirildiğine dikkat ediniz. O halde aslında __new__ metodunun + nesneyi yaratırken kullanılan argümanların hepsini alabilecek biçimde yazılması gerekir. Örneğin: + + @staticmethod + def __new__(cls, *args, **kwargs): + # araya giren kod + return super().__new__(cls) + + Burada __new__ metodu nesnenin yaratılması sırasında kullanılan argümanları da almıştır. Bu argümanlara bazı uygulamalarda + gereksinim duyulabilmektedir. Asıl tahsisatı yapan object sınıfının __new__ metodunun yalnızca tahsisat yapılacak sınıfı + alan parametreye (yani yalnızca cls parametresine) sahip olduğunu da belirtmek istiyoruz. Bu nedenle türemiş sınıfın __new__ + metodu içerisinde taban sınıfın __new__ metodu çağrılırken taban sınıfın __new__ metoduna taban sınıfın __init__ metodundaki + parametrelerin aktarılması uygun olmaktadır. + + 3) İlgisi sınıfın __new__ metodunun çağrılması sonucunda tahsis edilen nesne ilgili sınıf türündense bu kez aynı arümanlarla + o sınıfın __init__ metodu çağrılmaktadır. Başka bir deyişle eğer __new__ metodundan elde edilen nesne başka bir sınıf türünden + olursa ilgili sınıfın __init__ metodu çağrılmamaktadır. + + 4) Nihayet __call__ metodu tahsis edilen nesnenin adresiyle geri dönmektedir. + + Örneğin: + + s = Sample(10, 20) + + Burada aslında type sınıfının __call__ metodu çağrılmaktadır. type sınıfının __call__ metodu Sample sınıfının __new__ metodunu + çağırır. Eğer __new__ metodunun geri dönüş değeri Sample sınıfı türündense (genellikle böyle olur) bu kez Sample sınıfının + __init__ metodu çağrılır. Nihayet tahsis edilen nesnenin adresi s değişkenine atanacaktır. + + Burada bir noktayı yeniden vurgulamak istiyoruz: Biz bir sınıf için __new__ metodunu yazdığımızda eğer bu metodu o sınıf + türüden bir nesneyle geri döndürmezsek bu durumda o sınıfın __init__ metodu çağrılmayacaktır. Aşağıdaki örnekte Sample sınıfının + __init__ metodu çağrılmayacaktır. + + class Mample: + def __init__(self): + print('Mample.__init__') + + class Sample: + @staticmethod + def __new__(cls, *args, **kwargs): + print('Sample.__new__') + instance = object.__new__(Mample) + return instance + + def __init__(self): + print('Sample.__init__') + + s = Sample(10, 20) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + type sınıfı ile object sınıfı arasında mantıksal bir ilişki yoktur. type sınıfı bir "meta sınıf"tır. Yani bir sınıfın + bilgilerini tutan sınıftır. Meta sözcüğü üst kavram olarak kullanılmaktadır. Sınıf bilgi tutar, ancak sınıfın kendi + bilgileri de bir sınıf tarafından tutulmaktadır. İşte o type sınıfıdır. Bu durumda type meta bir sınıftır. object sınıfı + ise her sınıfın taban sınıfı görevinde olan nesne yaratma işlemini yapan sınıftır. Kaldı ki type sınıfı da object + sınıfından türetilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi biz neden bir sınıf için __new__ metodunu yazmak isteriz? İşte bunun nedeni biraz ileri düzey konularla ilgilidir. + Örneğin "singleton" kalıbı __new__ metodu kullanılarak aoluşturulabilir. NYPT'de singleton kalıbı bir sınıf türünden + tek bir nesnenin yaratıldığı (yani kişiler nesne yarattığını sansalar da aslında yaratımdan hep aynı nesnenin elde edildiği) + bir kalıptır. Singleton kalıbında belli bir anda ilgili sınıf türünden toplamda tek bir nesne bulunmaktadır. Singleton + kalıbı bazı işlemlerin gerçekleştirilmesi için kullanılabilmektediri + + Aşağıda singleton kalıbına örnek verilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + @staticmethod + def __new__(cls, *args, **kwargs): + if Sample._singleton is None: + Sample._singleton = Sample._singleton = object.__new__(cls) + + return Sample._singleton + + _singleton = None + +s = Sample() +print(s, id(s)) + +k = Sample() +print(k, id(k)) + +#------------------------------------------------------------------------------------------------------------------------ + Betimleyiciler (descriptors) ya da betimleyici sınıflar (descriptor classes) Python'a sonradan eklenmiştir. property + sınıfı gibi birtakım sınıfların yazılabilmesi için böyle bir kavrama ihtiyaç duyulmuştur. Bir sınıfın betimleyici + sınıf olması için o sınıfta __get__, __set__ ya da __delete__ metotlarının en bir tanesinin bulunuyor olması gerekir. + Genellikle betimleyici sınıflarda __get__ ve __set__ metotları birlikte bulunurkar. Ancak bazı sınıflarda yalnızca + __get__ metodu bulunuyor olabilir. + + __get__ metodunun self dışında iki parametresi olmalıdır. Bu parametrelee genellikle instance ve owner biçiminde + isimlendirilmektedir. Örneğin: + + def __get__(self, instance, owner): + pass + + __set__ metodunun da self dışında iki parametresi bulunur. Bu parametreler de genellikle instance ve value biçiminde + isimlendirilmektedir. Örneğin: + + def __set__(self, instance, value): + pass + + __delete__ metodunun ise self parametresi dışında tek bir parametresi olmalıdır. Bu parametre de genellikle instance + biçiminde isimlendirilmektedir. Örneğin: + + def __delete__(self, instance): + pass + +#------------------------------------------------------------------------------------------------------------------------ + +class MyDescriptor: + def __get__(self, instance, owner): + pass + + def __set__(self, instance, value): + pass + + def __delete__(self, instance): + pass + +#------------------------------------------------------------------------------------------------------------------------ + Metimleyici sınıflar başka sınıflarda kullanılsın diye oluştrulurlar. Betimleyici sınıf türünden bir nesne yaratılır + ve ilgili sınıfın bir sınıf değişkenine atanır. Betimleyici sınıfların bu kullanım dışında başka anlamlı kullanımları + yoktur. Örneğin: + + class MyDescriptor: + def __get__(self, instance, owner): + pass + + def __set__(self, instance, value): + pass + + def __delete__(self, instance): + pass + + class Sample: + a = MyDescriptor() + + Burada a Sample sınıfının bir özniteliğidir yani bir sınıf değişkenidir. Bilindiği gibi sınıf değişkenlerine normalde + ismiyle erişilir. Ancak onlara ilgili sınıf türünden değişkenlerle de erişebiliriz. + + Bir betimleyici nesne (yani betimleyici sınıf türünden yaratılmış olan sınıf değişkeni) adeta yerleştirildiği sınıfın bir + örnek özniteliği gibi davranmaktadır. Örneğin: + + s = Sample() + + s.a = 10 + + val = s.a + print(val) + + del s.a + + Bir betimleyici sınıf elemanına sahip olan bir sınıf türünden nesne yaratıldıktan sonra bu sınıf değişkeni ile betimleyici + elemana değer almak amacıyla erişimde betimleyici sınıfın __get__ metodu, değer yerleştirmek amacıyla erişimde betimleyici + sınıfın __set__ metodu ve del operatörü ile silme amaçlı kullanımda da betimleyici sınıfın __delete__ metodu çağrılmaktadır. + Örneğin: + + s = Sample() + + Burada şöyle kod yazmış olalım: + + result = s.a + + Burada s nesnesi yoluyla sınıfın a isimli betimleyici elemanına erişilmektedir. Bu erişim değer almak amacıyla yapılmıştır. + O zaman betimleyici sınıfın __get__ metodu çağrılacakltır. __get__ metodunun self parametresi a sınıf değişkenini belirten + MyDescriptor sınıfı türünden olur. instance parametresine betimleyiciye erişmekte kullanılan nesne (yani örneğimizde s nesnesi) + geçirilecektir ve owner parametresine de Sample sınıfının type nesne referansı geçirilecektir. Genellikle bu owner parametresine + __get__ metodunda gereksinim duyulmamaktadır. Betimleyiciye erişimden elde edilen değer __get__ metodundan elde edilen değerdir. + Daha açık bir anlatımla örneğin: + + result = s.a + + Burada adeta şu işlem yapılıyor gibidir: + + result = a.__get__(s, type(s)) + + Şimdi s.a ifadesine atama yapmak isteyelim. Örneğin: + + s.a = 100 + + Burada artık betimleyici sınıfın __set__ metodu çağrılacaktır. Metodun self parametresine yine a betimleyici nesnesi + geçirilecektir. s nesnesi yine instance parametresine geçirilecektir. value paramtresine ise atanan değer olan 100 değeri + geçirilecektir. Yani yukarıdaki atama işleminin eşdeğeri şöyledir: + + a.__set__(s, 100) + + Şimdi de bu s.a ifadesini del operatörü ile silmeye çalışalım. Örneğin: + + del s.a + + Burada da betimleyici sınıfın __delete__ metodu çağrılacaktır. (Bu metodu __del__ metoduyla karıştırmayınız. __del__ + metodu çöp toplayıcı tarafından çağrılmaktadır.) __delete__ metodunun self parametresine yine a betimleyici nesnesi + geçirilir. s yine metodun instance parametresine aktarılmaktadır. Yani bu işlemin eşdeğeri şöyledir: + + a.__delete__(s) + + Aşağıdaki örnekte çıkan sonuçlara dikkat ediniz. + +#------------------------------------------------------------------------------------------------------------------------ + +class MyDescriptor: + def __get__(self, instance, owner): + print(instance, owner) + print(type(instance), type(owner)) + + return 100 + + def __set__(self, instance, value): + print(instance, value) + print(type(instance), type(value)) + + def __delete__(self, instance): + print(instance) + +class Sample: + a = MyDescriptor() + + def __repr__(self): + return 'Sample object' + +s = Sample() + +val = s.a # val = a.__get__(s, type(s)) +print(val) + +print('-' * 10) + +s.a = 200 # a.__set__(s, 200) + +print('-' * 10) + +del s.a + +#------------------------------------------------------------------------------------------------------------------------ + Pekiyi betimleyici sınıflar ve değişkenler neden kullanılmaktadır? İşte bazı işlemler ancak bu betimleyici konusu ile + yapılabilmektedir. Örneğin daha önceki konumuzda property isimli bir sınıf görmüştük ve o sınıfı dekoratör olarak da + kullanmıştık. İşte property gibi bir sınıf yazabilmek için mecburen bir betimleyici sınıfa gereksinimimiz olur. + + Aşağıdaki property kullanımına bir daha dikkat ediniz: + + class Sample: + def __init__(self, a): + self._a = a + + def get_a(self): + print('get_a called ') + return self._a + + def set_a(self, val): + print('set_a called ') + self._a = val + + def del_a(self): + print('del_a called') + + a = property(get_a, set_a, del_a) + + s = Sample(100) + + result = s.a + print(result) + + s.a = 20 + + del s.a + + Buradaki property sınıfı bir betimleyici sınıftır. Ve böyle bir betimleyici işlevi olmadan daha önceki bilgilerimizle biz property + gibi bir sınıf yazamayız. + + Aşağıda orijinalk property sınıfının tamamen bir benzeri yazılmıştır. Yazıma dikkat ediniz. Betimleyici olmadan property sınıfını yazmaya + çalışınız ve yazamadığınızı görünüz. + +#------------------------------------------------------------------------------------------------------------------------ + +cclass myproperty: + def __init__(self, fget = None, fset = None, fdel = None, fdoc = None): + self._fget = fget + self._fset = fset + self._fdel = fdel + self._fdoc = fdoc + + def setter(self, fset): + self._fset = fset + return self + + def deleter(self, fdel): + self._fdel = fdel + return self + + def __get__(self, instance, owner): + if self._fget is not None: + return self._fget(instance) + + raise AttributeError('unreadable attribute') + + def __set__(self, instance, value): + if self._fset is not None: + self._fset(instance, value) + else: + raise AttributeError("can't set attribute") + + def __delete__(self, instance): + if self._fdel is not None: + self._fdel(instance) + else: + raise AttributeError("can't delete attribute") + +class Sample: + def __init__(self, a): + self._a = a + + @myproperty # a = property(a) + def a(self): + return self._a + + @a.setter # a = a.setter(a) + def a(self, value): + self._a = value + + @a.deleter # a = a.deleter(a) + def a(self): + print('delete _a') + +s = Sample(10) + +print(s.a) # 10 + +s.a = 20 + +print(s.a) # 20 + +del s.a # delete s_a + +#------------------------------------------------------------------------------------------------------------------------ + Python install edildiğinde onun bütün standart kütüphaneleri yerel makineye çekilmektedir. Ancak programcılar başkaları + tarafından yazılmış olan yüzlerce farklı kütüphaneyi kullanabilmektedir. İşte bu üçüncü parti kütüphaneler Internet'te + ismine İngilizce "repository" denilen server'larda tutulmaktadır. Bir kütüphaneyi bu server'lardan indirip yerel makinede + kullanıma hazır hale getirmek için "pip (python installer program)" denilen bir program kullanılmaktadır. pip programı + tipik olarak komut satırından şöyle kullanılır: + + pip install kütüphane_ismi + + pip programı bu üçüncü parti kütüphaneyi indirir ve onu kullanıma hazır hale getirir. Nevcut bir paketi yerel makineden + silmek için ise pip komutu şöyle kullanılmaktadır: + + pip uninstall kütüphane_ismi + + Yerel makinede kurulmuş olan üçünücü parti kütüphaneleri görebilmek için ise pip komutu şöyle kullanılmaktadır: + + pip list kütüphane_ismi + + pip programı otomatik olarak ilgili kütüphanenin en son versiyonunu indirip kurmaktadır. Kütüphanenin belli bir versiyonu + == ile versiyon numarası belirtilerek kurulabilmektedir. Örneğin: + + pip install kütüphane_ismi == versiyon_numarası + + pip komutunun ayrıntıları vardır. Biz bu aytıntılar üzerinde burada durmayacağız. Bunun için Python dokümanlarına + başvurabilirsiniz. + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Paketler (packages), içerisinde birden fazla Python kaynak dosyası bulunan dizinlere denilmektedir. Genellikle kütüphaneler + tek bir kaynak dosya olarak yazılmazlar. Birden fazla dosya biçiminde organize edilirler. İşte bir paket, içerisinde + birden fazla kaynak dosyadan oluşan bir dizini belirtmektedir. Örneğin pip programıyla bir kütüphaneyi indirip kurmak + istediğinizi düşünelim: + + pip install paket_ismi + + pip programı Internet'te üçüncü parti kütüphanelerin bulunduğu server'lara başvurur oradan kütüphanenin içeriğini yerel + makineye indirir ve kütüphaneyi oluşturan Python dosyalarını bir dizine yerleştirir. Yerel makinedeki bu dizin Python + için birden fazla Python dosyasından oluşan bir pakettir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + + Python'da bir dizin'in bir paket olarak ele alınması için tek gereken şey o dizin'in içinde "__init__.py" isimli bir + dosyanın bulunyor olmasıdır. Paketler de tamamen modüller gibi import edilmektedir. + + Örneğin bulunduğumuz dizin'in altında mypackage isimli bir dizin yaratıp onun içerisine "__init__.py" dosyasını yerleştirelim. + Dosyanın içi boş olabilir. Biz bir paketi nasıl bir dosyayı import ediyorsak dosya gibi import edebiliriz. Örneğin: + + import mypackage + + Yine import işlemi sırasında as cümleciği de kullanılabilir. Örneğin: + + import mypackage as mp + + Bir paket import edildiğinde paketin içerisindeki "__init__.py" dosyası otomatik çalıştırılmaktadır. Örneğin mypackage + isimli paketteki "__init__.py" dosyasının içeriği şöyle olsun: + + # __init__.py + + print('__init__.py') + + Biz aşağıdaki gibi paketi import ettiğimizde bu dosyanın içindekiler çalıştırılacaktır: + + import mypackage + + Tabii paketi iki kere import edersek paket yalnızca ilk import edildiğinde "__init__.py" dosyası çalıştırılır. + + Şimdi mypackage dizini içerisine "__init__.p"y dosyasının yanı sıra "a.py" ve "b.py" dosyalarını da yerleştirelim. + Dosyaların içeriği şöyle olsun: + + # a.py + + print('this is mypackage.a module') + + def foo(): + print('a.foo) + + # b.py + + print('this is mypackage.b module') + + def bar(): + print('b.bar') + + Paketler import edildiğinde tıpkı kaynak dosyalar gibi module nesneleri oluşturulmaktadır. Paket isimleri de bu + module nesnelerini gösteren değişken durumunda olurlar. Örneğin: + + import mypackage + + print(type(mypackage)) # + + Bir paketin içerisindeki spesifik bir dosya da import edilebilir. Örneğin: + + import mypackage.a + + Bu biçimde bir paketin içerisindeki dosyayı import etmeden önce paketin import edilmesine gerek yoktur. Bu tür durumlarda + zaten paketin kendisi de otomatik olarak import edilmektedir. Yani yukarıdaki import işleminde sanki önce paketin kendisi + import edilmiş sonra da paketin içerisindeki dosya import edilmiş gibi bir etki oluşacaktır. Dolayısıyla yine __init__.py + dosyasının içeriği ve a.py dosyasının içeriği çalıştırılacaktır. Yukarıdaki gibi bir paketin içerisindeki bir module import + edildiğinde iki modül nesnesi oluşturulacaktır. Birinci module nesnesi mypackage ismine atanacak, ikinci module nesnesi ise + mypackage.a ismine atanacaktır. Örneğin: + + import mypackage.a + import mypackage.b + + Burada ilk import işleminde paketin __init__.py dosyası çalıştırılır. Ancak ikinci import işleminde artık çalıştırılmaz. + Yani burada ekrana şunlar çıkacaktır: + + __init__.py + this is mypackage.a module + this is mypackage.b module + + Bir paketteki bir dosyanın içerisindeki değişkenleri (örneğin fonksiyonları) kullanabilmek için önce o paketin içerisindeki + dosyanın import edilmesi gerekir. Sonra paket_ismi.modül_ismi.değişken_ismi biçiminde paketteki modül içerisinde bulunan + değişken kullanılabilir. Örneğin: + + import mypackage.a + import mypackage.b + + mypackage.a.foo() + mypackage.b.bar() + + Yalnızca paketi import edip dosyayı import etmeden o dosyanın içerisindeki değişkenleri kullanamayız. Örneğin: + + import mypackage + + mypackage.a.foo() # error! + mypackage.b.bar() # error! + + Burada mypackage import edildiğinde a ve b modül isimleri oluşturulmamaktadır. + + Tabii import deyimindeki as cümleciği ile paket içerisindeki modül ismini kısaltabiliriz. Örneğin: + + import mypackage.a as a + import mypackage.b as b + + a.foo() + b.bar() + + Örneğin matplotlib paketinin içerisindeki pyplot kütüphanesini programcılar genellikle aşağıdaki biçimde import + ederek kullanılar: + + import matplotlib.pyplot as plt + + plt.plot(...) + + Paketteki dosyanın içerisinde bulunan spesifik bir değişkeni form import deyimi ile de import edebiliriz. Tabii bu durumda + yine paketin __init__.py dosyası ve paket içerisindeki dosyanın içerisindeki kodlar çalıştırılacaktır. Örneğin: + + from mypackage.a import foo + from mypackage.b import bar + + foo() + bar() + + Ekrana şu yazılar çıkacaktır: + + __init__.py + this is mypackage.a module + this is mypackage.b module + foo + bar + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir paketin __init__.py dosyasında paketin içerisindeki dosyalar import edilebilir. Bu durumda biz o paketi import + ettiğimizde o dosyaları da import etmiş gibi oluruz. Örneğin mypackage dizininindeki __init__.py dosyası şöyle yazılmış + olsun: + + # __init__.py + + print('__init__.py') + + import mypackage.a + import mypackage.b + + Şimdi biz paketi import edelim: + + import mypackage + + Artık paketin içerisindeki dosyaların içerisindeki değişkenleri paket ismi ve dosya ismi belirterek kullanabiliriz. + Örneğin: + + import mypackage + + mypackage.a.foo() + mypackage.b.bar() + + Ancak __init__.py içerisinde o paketteki dosyalar import edilirken yine paket ismi kullanılmak zorundadır. Yani + import işlemi aşağıdaki gibi yapılamaz: + + # __init__. py + + print('__init__.py') + + import a # error! + import b # error! + + Örneğin numpy kütüphanesini import ettiğimizde onun __init__.py dosyasında paket içerisindeki birtakım dosyalar + zaten import edilmektedir. Biz de aşağıdaki gibi işlemler yapabilmekteyiz: + + import numpy + + a = numpy.random.randint(0, 10, 100) + print(a) + + Burada numpy bir pakettir. random ise bir dosyadır. Bu random dosyasının import işlemi paketin __init__ dosyasında + yapıldığı için biz randint fonksiyonunu numpy.random.randint biçiminde kullanabildik. + + Bazen programcı paketin __init__.py dosyasında from import deyimi ile ilgili modülün içerisideki değişkenleri paketin + içerisine taşır. Böylece artık yalnızca paket ismi ile o değişkenlere erişilebilir. Örneğin mypackage paketindeki __init__.py + dosyasının içeriği şöyle olsun: + + # mypackage.__init__.py + + print('__init__.py') + + from mypackage.a import foo + from mypackage.b import * + + Şimdi biz bu paketi import ettiğimizde buradaki from import deyimleri çalıştırılacak (tabii bu deyimler çalışırken a.py + ve b.py dosyalarının içi de çalıştırılşacaktır) ve foo ile b modülünün içerisindeki değişken isimleri sankii bu paketin + içerisindeymiş gibi bir etki olulacaktır. Şimdi paketi aşağıdaki gibi kullanalım: + + import mypackage + + mypackage.foo() + mypackage.bar() + + Artık foo ve bar değişkenlerine mypackage ismiyle eriştik. Ekranda şu yazılar görünecektir: + + __init__.py + this is mypackage.a module + this is mypackage.b module + foo + bar + + Burada dikkat edilecek nokta artık mypackage dizinin içerisindeki a dosyasının içerisinde bulunan foo fonksiyonunun Sanki + mypackage içerisindeymiş gibi kullanılabildiğidir. Bu da kullanım kolaylığı oluşturmaktadır. + + Burada kullandığımız teknik aslında çok yaygın kullanılmaktadır. Yani biz bir paketi import ettiğimizde o paketin __init__.py + dosyası içerisinde from import deyimleriyle aslında o paketin ieçrisindeki modüllerin (dosyaların) içerisindeki isimler paket + isim alanına taşınmaktadır. Örneğin aslında pandas içerisinde yüzlerce dosyanın olduğu bir pakettir. Ancak biz sanki bütün + isimler pandas isim alanındaymış gibi onları kullanırız. Örneğin: + + import pandas + + s = pandas.Series([1, 2, 3, 4, 5]) + print(s) + + Aslında Series sınıfı pandas paketinin içerisindeki bir dosyanın içerisinde bulunmaktadır. Ancak from import deyimi ile pandas + paket isim alanına taşınmıştır. + + Yukarıdaki örnekte mypackage paketinin __init__.py dosyasında import as uygulamanın kısaltma bakımından bir faydasının olmayacağına + dikkat ediniz: + + # __init__.py + + print('__init__.py') + + import mypackage.a as a + + Burada a ismi yine paket ismiyle kullanılmak zorundadır. + +#------------------------------------------------------------------------------------------------------------------------ + 63. Ders 07/12/2022 - Carsamba +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + İç içe paketler de oluşturulabilmektedir. Bir paketin içerisindeki pakete "alt paket (subpackage)" de denilmektedir. + Yani paketlerin içerisinde modüller (python dosyaları) olabileceği gibi başka paketler de olabilmektedir. + + Şimdi yukarıda oluşturmuş olduğumuz mypackage ismli paketin içerisinde util isimli bir paket daha oluşturalım. util + paketinin içerisinde de __init__.py dosyası ve bir de "c.py" dosyası olsun. Bu dosyaların içeriği de şöyle olsun: + + # mypackage/util/__init__.py + + print('util.__init__.py') + + # mypackage/util/c.py + + print('this is mypackage.util.c module') + + def tar(): + print('tar') + + Örneğimizdeki dizin yapısı şöyledir: + + mypackage + __init__.py + a.py + b.py + util + __init__.py + c.py + + Şimdi bu alt paket içeisindeki "c.py" dosyasını import edecek olalım: + + import mypackage.util.c + + Burada her ne kadar biz c modülünü import etmek istiyorsak da aslında mypackage ve util paketleri de import edilmektedir. + Yani burada önce mypackage içerisindkei __init__.py dosyası, sonra "mypackage/util" içerisindeki __init__.py dosyası nihayet + "mypackage/util içerisindeki "c.py" dosyası çalıştırılacaktır. + + Aynı durum from import deyimi için de geçerlidir: + + from mypackage.util.c import tar + + Tabii biz dış paketin __init__.py dosyasında iç paketlerdeki isimleri de from import deyimi ile dış paketin isim alanına + taşıyabiliriz. Örneğin mypackage paketinin __init__.py dosyası şöyle olsun: + + print('__init__.py') + + from mypackage.a import * + from mypackage.b import * + from mypackage.util.c import * + + Burada "a.py", "b.py" ve "c.py" dosyalarının ieçrisindeki isimlerin hepsi sanki mypackage modülünün (paketinin) içerisindeymiş + gibi kullanılabilecektir. + + Pekiyi iç paketin __init__.py dosyası içerisinde nasıl import işlemi yapabiliriz? Biz iç pakette de import işlemi yaparken + yine en dıştan itibaren import edeceğimiz dosyayı niteliklendirmeliyiz. Yani örneğin mypackage dizini içerisindeki util + dizininindeki __init__.py dosyasında aşağıdaki gibi bir import işlemi yapamayız: + + import c + + aşağıdaki gibi de bir import işlemi yapamayız: + + import util.c + + Şöyle import işlemi yapabiliriz: + + import mypackage.util.c + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Modüllerin (yani .py dosyalarının) __all__ isminde özel bir global değişkenleri vardır. Bu global değişken dolaşılabilir + bir nesne biçiminde olmalıdır. Bu dolaşılabilir nesne string elemanlarından oluşur. Ancak pratikte genellikle bu __all__ + değişkeni programcı tarafından bir liste biçiminde oluşturulmaktadır. İşte "from import" deyimi ile *'lı import yapıldığında + yalnızca modülün __all__ değişkeninde belirtilen değişkenler dışarıya import edilir. Örneğin "x.py" isminde bir Python + dosyamız olsun ve bu dosyanın içeriği aşağıdaki gibi olsun: + + # x.py + + def foo(): + print('foo') + + def bar(): + print('bar') + + def tar(): + print('tar') + + a = 10 + b = 20 + c = 30 + + Normal olarak biz bu modülü *'lı import ettiğimizde foo, bar, tar, a, b, c değişken isimlerinin hepsini kullanabiliriz. Örneğin: + + from x import * + + foo() + bar() + tar() + + print(a) + print(b) + print(c) + + Şimdi x modülüne __all__ global değişkenini aşağıdaki gibi ekleyelim: + + __all__ = ['foo', 'bar', 'a', 'b'] + + def foo(): + print('foo') + + def bar(): + print('bar') + + def tar(): + print('tar') + + a = 10 + b = 20 + c = 30 + + Şimdi biz bu modülü *'lı bir biçimde import edersek yalnızca foo, bar, a ve b'yi kullanabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python dünyası diğer dillere ve framework'lere kıyasla nispeten zayıf dokümantasyon içermektedir. Bu zayıf dokümantasyon + diğer dillerden Python'a geçenler tarafından bazen şaşkınlıkla karşılanmaktadır. Zayıf dokümantasyon adeta bu dilde doğal + karşılanan bir durum haline gelmiştir. Pek çok Python programcısı yazdığı fonksiyonların, sınıfların ve modüllerin + dokümantasyonlarını kodun içerisine gömmektedir. Bunu sağlamak için Python'da "doküman yazıları (document strings)" denilen + bir dil özelliği kullanılmaktadır. Yani Python'da kodu yazan kişi aynı zamanda temel bir dokümantasyon da oluşturmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyon için doküman yazısı fonksiyonun hemen suit'inin başında (yani ikinci satırında) bir string olarak belirtilir. + Bu string tek tırnaklı ya da üç tırnaklı olabilir. Bilindiği gibi üç tırnaklı string'ler birden fazla satır üzerine + yazılabilmektedir. Örneğin: + + def square(a): + """square fonksiyonu parametresiyle aldığı değerin karesine geri döner.""" + return a * a + + Bir fonksiyonun doküman yazısı yorumlayıcı tarafından fonksiyon nesnesinin __doc__ isimli değişkeninin içerisine + yerleştirilmektedir. Dolayısıyla biz doküman yazısını __doc__ değişkeni yoluyla elde edebiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +def square(a): + """square fonksiyonu parametresiyle aldığı değerin karesine geri döner""" + return a * a + +result = square.__doc__ +print(result) + +#------------------------------------------------------------------------------------------------------------------------ + Python'da bazen programcılar bir fonksiyon hakkında bir açıklama bulamayabilirler. Bu tür durumlarda fonksiyonun __doc__ + elemanına başvurulabilir. Örneğin: + + >>> import math + >>> math.floor.__doc__ + 'Return the floor of x as an Integral.\n\nThis is the largest integer <= x.' + + Python'da built-in help isimli fonksiyon bir değişkenin ismini alıp ona ilişkin bilgileri ekrana yazdırmaktadır. Bu yazdırma + sırasında doküman yazıları da yazdırılmaktadır. Örneğin: + + >>> help(math.floor) + Active code page: 65001 + Help on built-in function floor in module math: + + floor(x, /) + Return the floor of x as an Integral. + + This is the largest integer <= x. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sınıflar için de sınıfların metotları için de doküman yazıları oluşturulabilir. Sınıflar için doküman yazısı oluşturulurken + yine yazı sınıfı oluşturan suit'in hemen başında (yani class anahtar sözcüğünün hemen alt satırında) bulundurulmalıdır. + + Örneğin: + + class Sample: + "Bu sınıf çok önemli işlemler yapan metotlara sahiptir." + + def foo(self): + "foo metodu falanca işi yapar." + + def bar(self): + "bar metodu filanca işi yapar." + + Yine bir sınıfın doküman yazısı sınıfın __doc__ isimli sınıf değişkeni biçiminde oluşturulmaktadır. Biz bu doküman + yazılarına sınıf isimleriyle ya da ilgili sınıf türünden değişkenlerle erişebiliriz. Örneğin: + + print(Sample.__doc__) + print(Sample.foo.__doc__) + print(Sample.bar.__doc__) + + s = Sample() + + print(s.__doc__) + print(sfoo.__doc__) + + Üç tırnaklı yazıların satırın başından itibaren boşluklu bir yazı oluşturacağına dikkat ediniz. Dolayısıyla ilgili + deyimin doküman yazısını __doc__ özniteliği ile eldeettikten sonra bunu yazdırmadan önce baştaki boşlukları atabilirsiniz. + Built-in help fonksiyonu zaten bu işlemleri yaparak doküman yazılarını düzgün bir biçimde yazdırmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +class Sample: + "Bu sınıf çok önemli işlemler yapan metotlara sahiptir." + + def foo(self): + "foo metodu falanca işi yapar." + + def bar(self): + "bar metodu filanca işi yapar." + +print(Sample.__doc__) +print(Sample.foo.__doc__) +print(Sample.bar.__doc__) + +s = Sample() + +print(s.__doc__) +print(s.foo.__doc__) +print(s.bar.__doc__) + +#------------------------------------------------------------------------------------------------------------------------ + Programcı isterse tüm modüle de doküman yazısı iliştirebilir. Modülün doküman yazıları hemen modülün ilk string'i olmak + zorundadır. Öneğin: + + # x.py + + """Bu modül falanca işleri yapar """ + + def foo(): + print('foo') + + def bar(): + print('bar') + + Biz yine modülün doküman yazısını modülün __doc__ değişkeni ile elde edebiliriz. Örneğin: + + import x + + print(x.__doc__) + + Kendi modülümüzün doküman yazısını ise doğrudan __doc__ değişkeni ile yazdırabiliriz. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Doküman yazıları için herkes tarafından kabul edilen bir standart yoktur. Değişik proje grupları doküman yazılarını + kendilerine özgü bir oluşturabilmektedir. Örneğin NumPy'ın doküman yazı standardına ilişkin bağlantı aşağıda verilmiştir: + + https://numpydoc.readthedocs.io/en/latest/format.html + + Google genel stili de aşağıda bağlantıda açıklanmıştır: + + https://google.github.io/styleguide/pyguide.html + + Tabii programcı kendi proje grubu tarafından benimsenmiş olan stili kullanabilir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bu bölümde gittikçe daha fazla kullanılır hale gelmekte olan "tür açıklamaları (type annotations)" konusu üzerinde + duracağız. Tür açıklamaları konusu Python'ın 3'lü versiyonlarıyla birlikte eklenmiş olan bir özelliktir. Bu özellik + Python2ın neredeyse her sürümünde genişletile genişletile bugünkü haline gelmiştir. Dolayısıyla kursumuzda güncel + son versiondaki tür açıklamaları üzerinde duracağız. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Python dinamik tür sistemine sahip bir programlama dili olduğu için değişkenlerin, fonksiyon parametrelerinin, fonksiyonların + geri dönüş değerlerinin türleri değişebilmektedir. Dinamik tür sistemine sahip programlama dillerinde en önemli sorunlardan + biri tür kontrolünün çalışma zamanı sırasında yapılabilmesidir. Örneğin bir fonksiyon yanlış türden bir argümanla çağrıldığında + problem kod çalışırken akış o noktaya geldiğinde ortaya çıkmaktadır. Bu da kodu yazanın çok dikkatli olmasını gerektirmektedir. + İşte tür açıklamaları bir değişkenin niyet edilen türünün program çalışmadan önce üçüncü parti araçlar tarafından kontrol + edilmesini sağlamak amacıyla dile eklenmiştir. Tür açıklamaları yorumlayıcı için bir direktif ya da kontrol sağlamamaktadır. + Yalnızca insanlar ve üçüncü parti statik analiz araçları için kontrol imkanları sunmaktadır. Başka bir deyişle tür açıklamaları + tamamen yorumlayıcı tarafından görmezden gelinmektedir. + + Aşağıdaki banner fonksiyonuna dikkat ediniz: + + def banner(s, ch='-'): + print(ch * len(s)) + print(s) + print(ch * len(s)) + + Bu fonksiyonun bir string ile çağrılması gerekir. Eğer bu fonksiyon bir string ile çağrılmazsa muhtemelen bir exception + oluşacaktır. Pekiyi dalgın bir programcı bu fonksiyonu aşağıdaki gibi int bir değerle çağıramaz mı? + + banner(123) + + İşte bu durumda yukarıda da belirttiğimiz gibi kod çalışırken exception oluşacaktır (çünkü int türü len fonksiyonuna sokulamaz). + Eğer biz yukarıdaki fonksiyonu örneğin bir listeyle çağırırsak exception oluşmaz ancak fonksiyon istediğimizi de yapmaz. + Aslında bu durum exception oluşmasından da kötüdür. + + Pekiyi biz bir Python programında yukarıdaki gibi hataları nasıl tespit edebiliriz? Bu tür hatalar yazılımın iyi bir + biçimde test edilmesiyle büyük ölçüde düzeltilebilmektedir. Örneğin "birim testleri (unit testing)" bu tür işlemlerin + test edilmesi için kullanılabilir. İşte tür uyumluluğu üçüncü parti statik analiz araçları tarafından da belirli koşullar + sağlanırsa tür açıklamaları sayesinde kontrol edilebilmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tür kontrolü için kullanılan statik analiz araçlarının en yaygınları şunlardır: mypy, Pytype, Pyright, Pyre. Biz kursumuzda + mypy kullanacağız. mypy programını şöyle kurabilirsiniz: + + pip install mypy + + Bu analiz araçlarının tür kontrollerini yapabilmesi için kodun "tür açıklamaları (type annotations)" ile oluşturulmuş + olması gerekir. Bu nedennle bizim tür açıklamalarının nasıl oluşturulacağını bilmemiz gerekmektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tür açıklamalarının genel biçimi şöyledir: + + : [= ] + + Aslında tür açıklamaları daha genel olarak düşünülmüştür. Yukarıdaki genel biçimde "ifade" yerine tür bilgisi yazılırsa + (int, str, float gibi tür isimleri Python'da birer ifadedir) tür açıklaması yapılmış olur. Aslında bu açıklamalar tür + açıklaması biçiminde olmak zorunda değildir. Ancak pratikte açıklamaların (annotations) en yaygın kullanımı tür açıklamaları + biçimindedir. Örneğin: + + a: int = 123 + b: float = 12.3 + + Yukarıda da belirttiğimiz gibi ':' atomundna sonra tipik olarak bir tür ismi getirilmektedir. Ancak tasarımda daha + genel bir yol izlenmiş ve buradaki ifadenin herhangi bir türden olabilmesi sağlanmıştır. Örneğin: + + a: 'ankara' = 123 + + Bu ifade sentaks olarak geçerlidir. Ancak tür açıklaması için kullanılan 'ankara' ifadesi mypy ya da diğer araçlar + tarafından tanınmayacaktır. Yani tür açıklamaları sentaks bakımından geniş bir biçimde tasarlanmıştır. Fakat mevcut + araçlar tür açıklamaları için tür isimlerini kullanmaktadır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Global ya da yerel değişkenlere tür açıklaması yazılırken onun ilkdeğer verilerek yaratılması zorunlu değildir. Örneğin: + + x: int + + x = 10 + print(x) + + x = 2.3 + print(x) + + Burada x değişkenin int türden olduğu belirtilmiştir. Biz bir değişkeni tür açıklamasıyla aşağıdaki gibi belirtmiş olalım: + + a: int + + Burada biz bu a değişkenini yaratmış değiliz. Yani a değişkenini henüz kullanamayız. Ancak Python yorumlayıcısı bunun + bir tür açıklaması olduğunu anlar ve bunun için herhangi bir error mesajı vermez. Tabii bir değişkeni yaratmadan aşağıdaki + gibi bir kullanım geçerli değildi: + + a + + Python'da bu tür etkisiz kodların oluşturulmasının yasak olmadığını anımsayınız. Ancak buradaki sorun a'nın yaratılmamış + olmasıdır. + + Yukarıda da belirttiğimiz gibi bir program çalıştırılırken Python yorumlayıcısı tür açıklamalarını dikkate almaz. Bu tür + açıklamaları üçüncü parti programlar tarafından (örneğin mypy) dikkate alınmaktadır. Yukarıdaki "sample.py" dosyasını mypy + ile şöyle işleme sokabiliriz: + + mypy sample.py + + Spyder IDE'sindeki IPython içerisinden de şu biçimde işleme sokabiliriz: + + !mypy sample.py + + Burada mypy şöyle bir çıktı oluşturmuştur: + + C:\Study\Python>mypy sample.py + sample.py:6: error: Incompatible types in assignment (expression has type "float", variable has type "int") + Found 1 error in 1 file (checked 1 source file) + + Tabii biz tür açıklaması yaparken genellkle aynı zamanda ona değer atayarak aynı zamanda değişkeni yaratabiliriz. Örneğin: + + x: int = 10 +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tür açıklamaları IDE'lere entegre edilmiş araçlar tarafından da dikkate alınabilmektedir. Örneğin PyCharm IDE'sinde + "Code/Inspect Code" menüsü ile tür kontrolü yapılabilir. Mypy aynı zamanda PyCharm IDE'sine bir plugin olarak eklenebilmektedir. + Bunun için "File/Settings/Plugins" sekmesine gelinit. Burada "mypy" seçilerek araç IDE'ye dahil edilir. Visual Studio + Code IDE'sine de bir plugin olarak mypy eklenebilmektedir. Maalesef henüz tür kontrolü yapmak için Spyder IDE'sine entegre + edilmiş bir araç yoktur. + + mypy programı başarısız olursa sıfır dışında bir exit kodu üretmektedir. Başarı durumunda mypy programının exit kodu 0'dır. + Örneğin: + + C:\Study\Python>mypy sample.py + sample.py:3: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] + Found 1 error in 1 file (checked 1 source file) + + C:\Study\Python>echo %errorlevel% + 1 + + Ancak örneğin: + + C:\Study\Python>mypy sample.py + Success: no issues found in 1 source file + + :\Study\Python>echo %errorlevel% + 0 + + Böylece build araçları ya da programcının oluşturduğu make dosyaları önce mypy programını çalıştırıp eğer bir hata varsa + programı python yorumlayıcına hiç vermeyebilirler. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Parametre değişkenlerine tür açıklaması yapılırken de aynı sentaks kullanılmaktadır. Örneğin: + + def banner(s: str, ch: str = '-'): + print(ch * len(s)) + print(s) + print(ch * len(s)) + + banner('ankara') + banner(123) + + Bu programı mypy programına sokalım: + + C:\Study\Python>mypy sample.py + sample.py:7: error: Argument 1 to "banner" has incompatible type "int"; expected "str" + Found 1 error in 1 file (checked 1 source file) + + Görüldüğü gibi mypy fonksiyonun yanlışlıkla int bir değerle çağrıldığını tespit edip bize bildirebilmiştir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Fonksiyonun geri dönüş değeri hakkında açıklama oluşturmak için -> sembolü kullanılmaktadır. Örneğin: + + def square(a: int) -> int: + return a * a + + Burada biz fonksiyonun parametresinin int türden, geri dönüş değerinin de int türden olması gerektiğini belirtiyoruz. + Şimdi "sample.py" programının aşağıdaki gibi olduğunu kabul edelim: + + def square(a: int) -> int: + return a * a + + result = square(1.2) + print(result) + + Programı mypy'a sokalım: + + C:\Study\Python>mypy sample.py + sample.py:4: error: Argument 1 to "square" has incompatible type "float"; expected "int" + Found 1 error in 1 file (checked 1 source file) + + Şimdi program şöyle olsun: + + def square(a: int) -> int: + return a * a + + result: str + + result = square(1) + print(result) + + Buradaki hata square'in geri dönüş değerinin str türünden olması gereken bir değişkene atanmasıdır. Bu kodu aşağıdaki + gibi mypy'a sokalım: + + C:\Study\Python>mypy sample.py + sample.py:6: error: Incompatible types in assignment (expression has type "int", variable has type "str") + Found 1 error in 1 file (checked 1 source file) + + Tabii yukarıdaki örnekten de gördüğünüz gibi eğer tür açıklaması yapılmamışsa değişken yaratılırken mymy ve üçüncü parti + statik analiz araçları herhangi bir hata rapor etmemktedir. Örneğin: + + def square(a: float) -> float: + return a * a + + result = square(10.2) + print(result) + + Burada biz result değişkeni şçin bir tür açıklaması yapmadık. Ancak square fonksiyonunun geri dönüş değerinin float türden + olması gerektiğini belirttik. Ancak result değişkenine atama için mypy bir hata rapor etmeyecektir. + + mypy programı da versiyondan versiyona genişletilmektedir. Örneğin artık biz bir değişkene ilk kez değer atayıp onu + yarattığımızda mypy sanki o değişken için atanan türe ilişkin tür açıklaması yapılmış gibi işlem uygulamaktadır. Örneğin: + + a = 10 + print(a) + + a = 3.14 + print(a) + + mypy sanki burada a için int açıklaması yapılmış gibi bir işlem uygulamaktadır. Dolayısıyla a değişkenine daha sonra float + bir değer atandığında bunu hata olarak rapor etmektedir: + + sample.py:4: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] + Found 1 error in 1 file (checked 1 source file) + +#------------------------------------------------------------------------------------------------------------------------ + + #------------------------------------------------------------------------------------------------------------------------ + Biz bir değişkenin kendi sınıfımız türünden olmasını da sağlayabiliriz. Yani tür açıklamalarında kendi sınıflarımızı da + kullanabiliriz. Örneğin: + + class Sample: + pass + + def foo(a: Sample): + print(a) + + s = Sample() + + foo(s) + + Burada foo fonksiyonu Sample sınıfı türünden parametre almaktadır. Biz onu başka türden bir argümanla çağırırsak mypy hata + verecektir. Tabii türemiş sınıf taban sınıf gibi de kullanılabildiği için biz buradaki foo fonksiyonuna Sample sınıfından + türetilmiş bir sınıf türünden değişken de geçebiliriz. Örneğin: + + class Sample: + pass + + class Mample(Sample): + pass + + def foo(a: Sample): + print(a) + + m = Mample() + + foo(m) + + Burada mypy herhangi bir hata mesajı vermeyecektir. + #------------------------------------------------------------------------------------------------------------------------ + +class Sample: + pass + +class Mample(Sample): + pass + +def foo(a: Sample): + print(a) + +m = Mample() + +foo(m) + +#------------------------------------------------------------------------------------------------------------------------ + Örneğin biz bir değişkenin list türünden olması gerektiğini benzer biçimde açıklayabiliriz: + + def foo(a: list): + pass + + Burada a parametre değişkeni, elemanları herhangi bir biçimde olan bir list nesnesini alabilir. Ancak istersek listenin + elemanlarının belli bir türden olmasını da sağlayabiliriz. Bunun için list isminden sonra köşeli parantezler içerisinde + ilgili tür ismi belirtilir. Örneğin: + + a: list[int] + + Burada a int elemanlardan oluşan bir liste olmalıdır. Örneğin: + + a = [1, 2, 'ali'] + + Böyle bir atama mypy tarafından hata olarak değerlendirilecektir. Örneğin: + + def foo(a: list[int]): + print(a) + + a = [10, 20, 30, 40, 50] + foo(a) + + b = [10, 20, 'ali', 'veli'] + foo(b) + + Burada foo fonksiyonu elemanları int türden olan bir listeyi parametre olarak almaktadır. Bu nedenle foo(b) çağrısı + için my hata rapor edecektir: + + sample.py:8: error: Argument 1 to "foo" has incompatible type "list[object]"; expected "list[int]" [arg-type] + Found 1 error in 1 file (checked 1 source file) + + mypy gibi araçların "statik analiz araçları" olduğuna dikkat ediniz. Bu tür statik kod analizi yapan araçlar programı + çalıştırarak bir kontrol yapamadığı için her türlü ihlali kontrol edememektedir. Örneğin: + + def foo(x): + x.append(1.2) + + a: list[int] + + a = [1, 2, 3, 4] + + foo(a) + + print(a) + + Burada foo fonksiyonunun a listesine ekleme yaptığını dolayısıyla kuralın ihlal edildiğini mypy gibi statik analiz araçları + genellikle tespit edememektedir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + list, tuple, dict gibi türlerin köşeli parantezlerle tür açıklamalarında kullanılabilmesi Python 3.9 ile eklenmiştir. + Python 3.8 ve öncesinde list, tuple ve dict sınıfları için tür açıklamaları typing modülü içerisisideki List, Tuple, + Dict isimli sınıflar kullanılarak yapılabiliyordu. Ancak Python 3.9 ile birlikte artık bu işlemler için orijinal list, + tuple ve dict sınıfları doğrudan kullanılabilir hale gelmiştir. typeing modülündeki List, Tuple ve Dict sınıfları orijinal + set, tuple ve dict sınıfları değildir. Bunlar yalnızca tür açıklamaları için bulundurulmuş olan sınıflardır. Tabii eski + sistem geçmişe doğru uyumu korumak için typeingmodülündeki bu sınıflar muhafaza edilmektedir. örneğin list sınıfı için + tür açıklamaları aşağıdaki gibi de yapılabilmektedir: + + from typing import List + + def foo(a: List[int]): + pass + + foo([10, 20]) + + Eski programlarda bu temel veri yapıları için typing modülündeki yukarıda belirttiğimiz özel isimlerin kullanıldığını + görürseniz şaşırmayınız. Ancak artık yeni programlarda doğrudan orijinal sınıf isimleri tercih edilmektedir. Örneğin: + + def foo(a: list[int]): + pass + + foo([10, 20]) + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Benzer biçimde set ve tuple sınıfları için de tür açıklamaları için kullanılabilmektedir. Örneğin: + + def foo(s: set): + pass + + Burada s set türünden olmalıdır. Örneğin: + + def bar(t: tuple): + pass + + Burada da t tuple türünden olmalıdır. + + Tabii tıpkı list örneğinde olduğu gibi aslında biz bu set ve tuple türlerinin elemanları hakkında da açıklama yapabiliriz. + Örneğin: + + def foo(s: set[int]): + pass + + Burada s int değerleri tutan bir küme olmalıdır. + + Demetlerde elemanların türleri sırasıyla tek tek belirtilebilmektedir. Örneğin: + + def foo(t: tuple[int, str]): + pass + + Burada t parametre değişkenine iki elemanlı demetler aktarılmalıdır. Bu demetlerin birinci elemanları int türden ikinci + elemanları str türünden olmalıdır. Örneğin: + + foo((10, 'ankara')) + + Şimdi fonksiyunu şöyle çağıralım: + + foo((10, 20)) + + Mypy şöyle bir hata verecektir: + + C:\Study\Python> mymy sample.py + sample.py:4: error: Argument 1 to "foo" has incompatible type "tuple[int, int]"; expected "tuple[int, str]" [arg-type] + Found 1 error in 1 file (checked 1 source file) + + tuple türü için genel bir eleman türü bu biçimde belirtilememektdir. Örnein: + + def foo(a: tuple[int]): + pass + + Burada a için yapılan açıklama "elemanları int türden olan demet" biçiminde bir açıklama değildir. "Tek bir elemanı olan, + onun da int türden olan demet" açıklamasıdır. + + set ve tuple türlerinin bu biçimde doğrudan kullanılması yine Python 3.9 ile birlikte mümkün hale getirilmiştir. Yukarıda da + belirttiğimiz gibi Python 3.8 ve aşağısında bunların yerine typing modülündeki Set ve Tuple sınıfları kullanılıyordu. Ancak + Python 3.9 ile birlikte artık bu sınıfların kullanılmasına gerek kalmamıştır. Örneğin: + + from typing import Set, Tuple + + def foo(s: Set[int]): + pass + + def bar(t: Tuple[int, str]): + pass + + foo({1, 2, 3}) + bar((10, 'ankara')) + + Örneğin: + + from typing import Set, Tuple + + def foo(s: Set[int]): + pass + + def bar(t: Tuple[int, str]): + pass + + s: Set[int] = {1, 2, 3} + t: Tuple[int, str] = 10, 'ankara' + + foo(s) + bar(t) + + Demetler için şöyle bir sentaks da eklenmiştir. Eğer demetleri belirtirken yalnızca tek tür belirtirsek ve sonra da ... (ellipsis) + getirirsek bu durum ilk belirttiğimiz türden olmak koşulu ile demetin istenildiği sayıda elemandan oluşabileceği anlamına gelir. + Örneğin: + + def foo(t: tuple[int, ...]): + pass + + Bu örnekte foo fonksiyonunun t parametre değişkeni tüm elemanları int olan bir demet almaktadır. Aşağıdaki çağrılar bu tür açıklamalarına + uygundur: + + foo((10, 20, 30)) + foo((10, 20)) + + Ancak aşağıdaki çağrılar tür açıklamalarına uygun değildir: + + foo((10.2, 20, 'ali')) + foo(('veli', 'selami')) + + Bu tür durumlarda boş demetler de soruna yol açmamaktadır. + + Köşeli parantez içerisinde birden fazla türden sonra ... kullanımının böyle bir anlamı yoktur. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Sözlüklerde de tür açıklamaları benzer biçimde dict sınıfı ile yapılabilmektedir. Örneğin: + + def foo(d: dict): + pass + + Bu durumda fonksiyonun d parametre değişkenine bir sözlük geçirilmelidir. Ancak sitenirse yine köşeli parantezler içerisinde + sözlüğün anahtar ve değer türleri ayrı ayrı belirtilebilir. Örneğin: + + def foo(d: dict[int, str]): + pass + + Burada sözlüğün anahtarları int, değerleri ise str türünden olmalıdır. Aşağıdaki çağrıda mypy bir hata rapor etmeyecektir: + + d = {10: 'ali', 20: 'veli', 30: 'selami'} + + foo(d) + + Yukarıda da belirttiğimiz gibi Python 3.8 ve öncesinde bu işlem typing modülü içerisindeki Dict sınıfı ile yapılıyordu. + Örneğin: + + from typing import Dict + + def foo(d: Dict[int, str]): + pass + + d = {10: 'ali', 20: 'veli', 30: 'selami'} + + foo(d) + + Ancak artık bu Dict sınıfına gerek kalmamıştır. + + Sözlüklerde tür açıklaması yapılırken yalnızca anahtar ya da yalnızca değer türü belirtilemez. Aşağıdaki tür açıklaması + mypy tarafından hata olarak rapor edilecektir: + + def foo(d: dict[str]): + pass + + Tabii Python yorumlayıcısı aslında tür açıklamalarını yalnızca sentaks bakımından denetlemektedir. Yukarıdaki açıklama + mypy tarafından bir hata olarak değerlendirilecek olsa da yorumlayıcı tarafından bir hata olarak değerlendirilmeyecektir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + typing modülündeki Any sınıfı tür açıklamalarında "herhangi bir tür olabilir" anlamına gelmektedir. Any kullanmak bazı + durumlarda gereksidir. Örneğin: + + def foo(a: Any): + pass + + Biz zaten burada tür açıklaması yapmasaydık da mypy tarafından a herhangi bir türü kabul edecekti. Yani aşağıdaki fonksiyon + yukarıdakiyle eşdeğerdir: + + def foo(a): + pass + + Örneğin: + + def bar(a: list[Any]): + pass + + Burada da aslında yalnızca list biçiminde tür açıklaması yapsaydık da değişen bir şey olmayacaktı: + + def bar(a: list): + pass + + Ancak bazı durumlarda Any gerçekten gerekebilmektedir. Örneğin: + + from typing import Any + + def foo(d: dict[int, Any]): + pass + + Burada artık foo fonksiyonun parametresi sözlük olmalıdır. Ancak sözlüğün anahtarları int olmak zorundayken değerleri + herhangi bir türden olabilir. Örneğin aşağıdaki çağrı tür açıklamalarına uygundur: + + foo({10: 'ali', 20: 100, 30: 2.3}) + + Örneğin: + + def foo(t: tuple[int, Any, float]): + pass + + Burada parametre değişkeni olan t için bizim üç elemanlı bir demet geçirmemiz gerekir. Bu demetin ilk elemanı int türden, + üçüncü elemanı float türden ancak ikinci elemanı ise herhangi bir türden olabilir. Dolayısıyla aşağıdaki çağrılarda mypy + bir hata rapor etmeyecektir: + + foo((100, 'ali', 12.4)) + foo((100, 200, 12.4)) + + Ancak aşağıdaki bir çağrıda mypy hata rapor edecektir: + + foo(('ankara', 'ali', 12.4)) + + Burada demetin ilk elemanı için verilen açıklamaya uyulmamıştır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Bir fonksiyonun geri dönüş değerinin olmadığı ya da None olduğunu belirtmek için tür açıklamalarında None kullanılmalıdır. + Örneğin: + + def foo() -> None: + pass + + Biz burada foo fonksiyonunu geri dönüş değeri varmış gibi kullanırsak mypy bu durumu error ile rapor edecektir. Örneğin: + + result = foo() + + Burada mypy şöyle bir error rapor eder: + + C:\Study\Python> mymy sample.py + sample.py:4: error: "foo" does not return a value + Found 1 error in 1 file (checked 1 source file) + + Ya da geri dönüş değeri None olarak açıklanmış bir fonksiyonu başka bir değerle geri döndürürsek de mypy yine hata + rapor edecektir. Örneğin: + + def foo(a: int) -> None: + if a < 0: + return 'a must not be negative' + + print('ok') + + Burada mypy şöyle bir hata rapor etmektedir: + + sample.py:3: error: No return value expected [return-value] + Found 1 error in 1 file (checked 1 source file) + + Tabii geri dönüş değeri de '|' operatörü ile seçeneklendirilebilir. Örneğin: + + import math + + def getroots(a: float, b: float, c: float) -> tuple[float, float]|None: + delta = b ** 2 - 4 * a * c + if delta < 0: + return None + + x1 = (-b + math.sqrt(delta)) / (2 * a) + x2 = (-b - math.sqrt(delta)) / (2 * a) + + return x1, x2 + + result: tuple[float, float]|None = getroots(1, 0, -4) + + if result is None: + print('kök yok') + else: + x1, x2 = result + print(f'x1 = {x1}, x2 = {x2}') + + Burada ikinci derece denklemin köklerini bulan getroots fonksiyonu ya elemanları float olan iki elemanlı bir + demet ile ya da None değeri ile geri dönmektedir. Fonksiyonun anımlamasına dikkat ediniz: + + def getroots(a: float, b: float, c: float) -> tuple[float, float]|None: + pass + +#------------------------------------------------------------------------------------------------------------------------ + + import math + + def getroots(a: float, b: float, c: float) -> tuple[float, float]|None: + delta = b ** 2 - 4 * a * c + if delta < 0: + return None + + x1 = (-b + math.sqrt(delta)) / (2 * a) + x2 = (-b - math.sqrt(delta)) / (2 * a) + + return x1, x2 + + result: tuple[float, float]|None = getroots(1, 0, -4) + + if result is None: + print('kök yok') + else: + x1, x2 = result + print(f'x1 = {x1}, x2 = {x2}') + +#------------------------------------------------------------------------------------------------------------------------ + typing modülündeki Optional ismi "ilgili türden ya da None (NoneType türünden)" anlamına gelmektedir. Örneğin: + + from typing import Optional + + a: Optional[int] + + Burada biz a'ya int ya None atayabiliriz. Ancak başka bir türden değer atayamayız. Örneğin: + + from typing import Optional + + def foo(a: int) -> Optional[int]: + if a > 0: + return a + + return None + + result: Optional[int] = foo(10) + print(result) + + Burada biz result değişkenine int türden ya da None atayabiliriz. + + Yukarıda yazmış olduğumuz getroots fonksiyonunda fonksiyonun geri dönüş değeri için tuple[float, float]|None açıklamasını + yapmıştık. Aynı açıklamayı Optional kullnarak Optional[tuple[float, float]] biçiminde de yapabilirdik. +#------------------------------------------------------------------------------------------------------------------------ + +import math +from typing import Optional + +def get_roots(a: float, b: float, c: float) -> Optional[tuple[float, float]]: + delta: float = b ** 2 - 4 * a * c + if delta < 0: + return None + + x1 = (-b + math.sqrt(delta)) / (2 * a) + x2 = (-b - math.sqrt(delta)) / (2 * a) + + return x1, x2 + +result: Optional[tuple[float, float]] = get_roots(1, 0, -4) +if result: + print(result[0], result[1]) +else: + print('Kök yok') + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkene bir fonksiyon gibi çağrılabilecek (callable) bir değişken atanacaksa tür açıklamasında typing modülündeki + Callable sınıfı kullanılmaktadır. Örneğin: + + from typing import Callable + + def foo(f: Callable, *args, **kwargs): + f(*args, **kwargs) + + def bar(): + print('bar') + + class Sample: + def __call__(self, *args, **kwargs): + print('Sample.__call__') + + s = Sample() + + foo(bar) # geçerli, foo callable bir nesneyle çağrılmış + foo(s) # geçerli, foo callable bir nesneyle çağrılmış + + foo(100) # geçerli değil! mypy hata rapor edecek + + Örneğin: + + from typing import Callable + + def square(a: int) -> int: + return a * a + + def foo(f: Callable, val: int): + result = f(val) + print(result) + + foo(square, 10) # geçerli + + Burada bar fonksiyonun birinci parametresi çağrılabilen bir nesne, ikinci parametresi ise int bir nesne almaktadır. + Dolayısıyla burada mypy herhangi bir hata rapor etmeyecektir. + + Ancak istenirse değişkene atanacak çağrılabilen nesnenin parametrik yapısı ve geri dönüş değeri için de tür açıklaması + yapılabilmektedir. Bunun için Callable siminden sonra köşeli parantezler içerisinde "bir liste biçiminde parametre türleri, + sonra da geri dönüş değerinin türü" girilmelidir. Örneğin: + + from typing import Callable + + def foo(a: int, b: str) -> int: + return a + int(b) + + f: Callable[[int, str], int] + + Burada f değişkenine parametreleri sırasıyla int ve str olan geri dönüş değeri ise int olan çağrılabilen nesneler atanabilir. + Eğer buna uygun atama yapılmazsa mypy hata verecektir. Örneğin: + + f = foo + + result: int = f(10, 20) # tür açıklamasına uyulmamış + print(result) + + Burada tür açıklamasına uyulmamıştır. mypy şöyle bir hata rapor etmektedir: + + sample.py:22: error: Argument 2 to "foo" has incompatible type "int"; expected "str" [arg-type] + Found 1 error in 1 file (checked 1 source file) + + Örneğin: + + def square(a: int) -> int: + return a * a + + def foo(f: Callable[[int], int], val: int): + result = f(val) + print(result) + + foo(square, 10) + + Burada foo fonksiyonunun birinci parametresi "pareametresi int, geri dönüş değeri int olan" bir fonksiyon nesnesi almaktadır. + Örneğimizde bu tür açıklamasına uyulduğuna dikkat ediniz. +#------------------------------------------------------------------------------------------------------------------------ + +from typing import Callable + +def square(a: int) -> int: + return a * a + +def foo(f: Callable[[int], int], val: int): + result = f(val) + print(result) + +foo(square, 10) # geçerli +foo(len) # geçerli değil + +#------------------------------------------------------------------------------------------------------------------------ + typing modülü içerisindeki Union isimli sınıf birden fazla türü belirtmek için kullanılmaktadır. Örneğin: + + a: Union[int, str] + + Burada a'ya int ya da str türünden nesneler atanabilir. Yani a bu iki türden değeri de kabul etmektedir. Örneğin: + + from typing import Union + + a : Union[int, str] + + a = 10 + print(a) + + a = 'ali' + print(a) + + Burada a'ya yapılan atamalar tür açıklamasıyla uyumludur. Ancak örneğin: + + a = 2.3 + print(a) + + Burada a'ya yapılan atama tür açıklamasıyla uyumlu değildir. Örneğin: + + from typing import Union + + def foo(a: Union[int, str]): + pass + + Burada aşağıdaki iki çağrı tür açıklamalarıyla uyumludur. + + foo(10) + foo('ali') + + Ancak aşağıdaki çağrı tür açıklamalarıyla uyumsuzdur: + + foo(1.2) + + Daha önce de belirtitğimiz gibi Python 3.10 ile birlikte Union işlemi '|' operatörü ile de yapılır hale getirilmiştir. + Örneğin: + + def foo(a: int|str): + pass + + Bu açıklama aşağıdakiyle tamamen eşdeğerdir: + + def foo(a: Union[int, str]): + pass +#------------------------------------------------------------------------------------------------------------------------ + +from typing import Union + +a: Union[int, float] + +a = 100 # geçerli +print(a) + +a = 31.14 # geçerli +print(a) + +a = 'ankara' # geçersiz + +#------------------------------------------------------------------------------------------------------------------------ + Bazen bir değişkene ilişkin tür açıklamasını başka bir tür açıklaması ile değiştirmek isteyebiliriz. Aslında ikinci kez + tür açıklaması yapmak Python yorumlayıcısı tarafından geçerlidir. Ancak mypy bu durumda hata rapor etmektedir. Örneğin: + + a: int + + a = 100 + print(a) + + a: float # mypy hata rapor edecektir + + a = 3.14 + print(a) + + Bu durum Python yorumlayıcısı tarafındna geçerli olsa da mypy aşağıdaki gibi bir hata rapor edecektir: + + sample.py:6: error: Name "a" already defined on line 1 [no-redef] + sample.py:8: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] + Found 2 errors in 1 file (checked 1 source file) + + typing modülü içerisindeki cast fonksiyonu atanan değişken üzerinde tür açıklaması yapılmasına olanak sağlayan bir fonksiyondur. + cast fonksiyonu birinci parametre olarak bir tür açıklamasını, ikinci parametre olarak bir değeri almaktadır. Bu değer açıklanmış + bir değişken de olabilir. + + Biz cast fonksiyonu sayesinde bir değişkene hem bir açıklama yapıp hem de bir değer arayabiliriz. Örneğin: + + a: float = 12.2 + b = cast(int, a) + + Örneğimizde b değişkeni artık int olarak açıklanmış durumdadır. Burada önemli bir nokta cast fonksiyonun bir açıklama + amacıyla kullanılmasıdır. cast fonksiyonu bir dönüştürme yapmaz. Örneğimizde her ne kadar b int olarak açıklanmışsa da içerisinde yine 12.2 + değeri bulunacaktır. Yani biz burada b'ye hem bir float değer atamış olduk hem de a'yı int olarak açıklamış olduk. + + Yorumlayıcı cast işleminde dönüştürme yapmamaktadır. Örneğin: + + from typing import cast + + a: float = 12.2 + + b = cast(str, a) + + print(b, type(a)) # 12.2 + + Burada b str olarak açıklanmıştır ancak b içerisine float bir değer atanmıştır. +#------------------------------------------------------------------------------------------------------------------------ + +from typing import cast + +a: int + +a = 100 +print(a) + +a: cast(float, 3.14) # geçerli + +print(a) + +#------------------------------------------------------------------------------------------------------------------------ + Bir değişkenin dolaşılabilir bir tür olduğu typing modülündeki Itearable sınıfı ile açıklanmaktadır. Örneğin: + + from typing import Iterable + + def foo(a: Iterable): + pass + + Burada foo fonksiyonunun a parametre değişkeni dolaşılabilir bir nesne almalıdır. Aşağıdaki çağrılarda argüman olarak + dolaşılabilir nesneler kullanıldığı için tür açıklamasına uygundur. Örneğin: + + foo(range(10)) + foo([1, 2, 3, 4, 5]) + foo('ankara') + + Örneğin: + + def gen(): + for i in range(10): + yield i + + foo(gen()) + + Üretici nesneler de dolaşılabilir nesneler olduğu için çağrı tür açıklamasına uygundur. Ancak aşağıdaki çağrılarda + argümanlar dolaşılabilir nesne belirtmediği için tür açıklamasına uygun değildir: + + foo(10) + + class Sample: + pass + + s = Sample() + foo(s) +#------------------------------------------------------------------------------------------------------------------------ + +from typing import Iterable + +def foo(a: Iterable): + for x in a: + print(x) + +foo([1, 2, 3, 4, 5]) # geçerli +foo(range(10)) # geçerli +foo(123) # geçerli değil + +#------------------------------------------------------------------------------------------------------------------------ + Iterable sınıfı da köşeli parantezler içerisinde tür bilgisi alabilmektedir. Örneğin Iterable[int] biçiminde bir açıklama + değişkenin dolaşılabilir nesne alacağını ancak bu nesne dolaşıldıkça int nesnelerin elde edileceğini belirtmektedir. Örneğin: + + def foo(a: Iterable[int]): + pass + + Burada aşağıdaki gibi bir çağrı tür açıklamasına uygundur: + + foo([1, 2, 3, 4, 5]) + + Çünkü fonksiyona geçirilen dolaşılabilir nesnenin elemanları int türdendir. Ancak aşağıdaki bir çağrı tür açıklamasına uygun + değildir: + + foo([1, 2, 3., 4, 5.0]) + + Örneğin: + + a: Iterable[int|str] + + Burada a değişkenine elemanları int ya da float olabilen dolaşılabilir nesneler atanabilir. Dolayısıyla aşağıdaki atama tür + açıklamasına uygundur: + + a = [1, 2, 3, 4, 'ankara', 'izmit'] + + Örneğin: + + a: Iterable[tuple[int, str]] + + Burada a değişkenine demetlerden oluşan dolaşılabilir bir nesne atanabilir. Ancak bu demetlerin de ilk elemanları int ikinci elemanları + str türünden olmak zorundadır. Örneğin aşağıdaki atama tür açıklamasına uygundur: + + a = [(6, 'Anakara'), (26, 'Eskişehir'), (35, 'İzmir')] +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + Tür açıklamaları iç içe yapıldığı zaman biraz karmaşık bir görüntü oluşturabilmektedir. Bu biçimdeki tür açıklamalarını oluştururken + parantezlere dikkat ediniz. Örneğin: + + from typing import Callable, Iterable + + def foo(a: Iterable[tuple[Callable[[int, int], int], float]]): + pass + + Burada a değişkeninin parametresi dolaşılabilir bir nesne olmalıdır. Bu dolaşılabilir nesne bize iki elemanlı demet vermelidir. + Demetin birinci elemanı parametreleri int, int olan, geri dönüş değeri int olan bir çağrılabilir nesneden ikinci elemanı ise + float bir nesneden oluşmalıdır. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + + typing modülü içerisindeki Sequence sınıfı da string gibi liste gibi __getitem__, __len__ metotları bulunan "reversible + seqeunce" türlerini belirtmek için kullanılmaktadır. Anımsanacağı gibi tipi tipik "seuqence" türleri list, range, tuple, + str" türleridir. dict türünün __getitem__ metodu olsa da dict türü bir "seauence türü değildir. Örneğin: + + from typing import Sequence + + def foo(a: Sequence[int]): + pass + + Burada biz foo fonksiyonunu bir listeyle, bir demetle, string'le ya da bir range nesnesiyle çağırabiliriz. Bu durumda tür + açıklamasına uygun çağrılar oluşturulmuş oluruz. Örneğin: + + foo([1, 2, 3, 4, 5]) + foo('ali') + foo(range(10)) + + Ancak örneğin biz buradaki foo fonksiyonunu bir kümeyle ya da sözlükle çarırsak bu durum tür açıklamasıyla uyumlu olmaz: + + foo({1, 2, 3, 4, 5}) + foo({1: 'ali', 2: 'veli'}) + + Squence sınıfı için köşeli parantezler içerisinde tür de belirtilebilir. Örneğin: + + def foo(a: Sequence[int]): + pass + + Burada a parametre değişkenine biz int elemanlardna oluşan bir "sequence türü" geçirebiliriz. Örneğin: + + foo([1, 2, 3, 4, 5]) + foo(range(100)) + + Çağrıları tür açıklamasına uygundur. Ancak örneğin: + + foo(['ali', 'veli', 'selami']) + + çağrısı tür açıklamasına uygun değildir. +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------------------------------------------------ + + +14 2414 +13 2134 +12 1838 +11 1520 +10 1214 +09 929 +08 649 +07 443 +06 242 +05 83 +04 12 diff --git a/SysProg-1-OzetNotlar-Ornekler.txt b/SysProg-1-OzetNotlar-Ornekler.txt index 6febe4f..aff2510 100644 --- a/SysProg-1-OzetNotlar-Ornekler.txt +++ b/SysProg-1-OzetNotlar-Ornekler.txt @@ -40339,12 +40339,986 @@ double divide(double a, double b) aynı zamanda birer POSIX fonksiyonudur. -------------------------------------------------------------------------------------------------------------------------------------------*/ - +/*------------------------------------------------------------------------------------------------------------------------------------------ + 96. Ders 16/06/2024 - Pazar +-------------------------------------------------------------------------------------------------------------------------------------------*/ +/*------------------------------------------------------------------------------------------------------------------------------------------ + Bir TCP/IP uygulamasında iki ayrı program yazılır: "TCP Server Program" ve "TCP Client Program". Biz önce TCP server programın + daha sonra da TCP client programın yazımı üzerinde duracağız. Tabii TCP server programın üzerinde dururken zaten bazı ortak + soket fonksiyonlarını da göreceğiz. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------ + Bir TCP server program tipik olarak aşağıdaki soket API'lerinin sırayla çağrılmasıyla gerçekleştirilmektedir: + + (Windows'ta WSAStartup --->) socket ---> bind ---> listen ---> accept ---> send/recv (ya da UNIC/Linux'ta read/write) + ---> shutdown ---> close (Windows'ta closesocket) (---> Windows'ta WSACleanup) + + Buradan da gördüğünüz gibi her ne kadar Windows UNIX tarzı Bekeley soket kütüphanesini destekliyorsa da şu küçük + farklılıklara sahiptir: + + 1) Windows'ta soket sistemini başlatmak ve sonlandırmak için WSAStartup ve WSACleanup API fonksiyonları kullanılmaktadır. + + 2) Windows'ta soket nesnesini yok etmek için close yerine closesocket API fonksiyonu bulunmaktadır. + + 3) Windows'ta bazı istisnalar dışında bir soket fonksiyonu başarısız olduğunda başarısızlığın nedeni GetLastError fonksiyonuyla değil + WSAGetLastError fonksiyonuyla elde edilmektedir. Bu nedenle Windows örneğimizde hataları rapor etmek için ExitSys fonksiyonunu aşağıdaki + biçimde tanımlayacağız: + + void ExitSys(LPCSTR lpszMsg, DWORD dwLastErr) + { + LPTSTR lpszErr; + + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { + fprintf(stderr, "%s: %s", lpszMsg, lpszErr); + LocalFree(lpszErr); + } + + exit(EXIT_FAILURE); + } + + 4) Windows'ta soket kütüphanesi API fonksiyonlarının bulunduğu kütüphaneler içerisinde değildir. Bu nedenle link amasında soket API'lerinin + bulunduğu dinamik kütüphanenin import kütüphanesi ("Ws2_32.lib") linker ayarlarında belirtilmelidir. + + 5) Windows sistemlerinde soket fonksiyonlarının protoipleri dosyası içerisindedir. Halbuki UNIX/Linux sistemlerinde fonksiyonların + prototipleri farklı başlık dosyalarında bulunabilmektedir. Eğer dosyası da include edilecekse önce dosyası sonra + dosyası include edilmelidir. + + Windows'ta soket sistemini başlatmak için WSAStartup fonksiyonu kullanılmaktadır. Fonksiyonun prototipi şöyledir: + + #include + + int WSAStartup( + WORD wVersionRequired, + LPWSADATA lpWSAData + ); + + Fonksiyonun birinci parametresi talep edilen soket kütüphanesinin versiyonunu belirtmektedir. Buradaki WORD değer iki ayrı byte'ın birleşiminden + oluşmaktadır. MAKEWORD(high, low) makrosu ile bu parametre için argüman oluşturulabilir. Windows soket kütüphanesinin son versiyonu 2.2 versiyonudur. + Dolayısıyla bu parametre için argümanı MAKEWORD(2, 2) biçiminde geçebiliriz. Eğer talep edilen versiyon yüksekse bu durum hataya yol açmamakta + talep edilenden küçük olan en yüksek versiyon kullanıma hazır hale getirilmektedir. Fonksiyonun ikinci parametresi WSADATA isimli bir yapı + nesnesinin adresini almaktadır. Fonksiyon bu yapıya bazı bilgiler yerleştirmektedir. Fonksiyon başarı durumunda 0 değerine başarısızlık durumunda + hata kodunun kendisine geri dönmektedir. (Yani bu fonksiyon için WSAGetLastError çağrısına gerek yoktur.) Fonksiyon tipik olarak şöyle kullanılır: + + WSADATA wsaData; + int result; + ... + + if ((result = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) + ExitSys("WSAStartup", result); + + Windows'ta WSACleaanup fonksiyonun prototipi ise şöyledir: + + #include + + int WSACleanup(void); + + Fonksiyon başarı durumunda 0 değerine, başarısızlık durumunda SOCKET_ERROR özel değerine (bu değer genellikle -1 olarak define edilmektedir) + geri dönmektedir. Hatanın nedeni için WSAGetLastError fonksiyonuna başvurulmalıdır. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------ + Haberleşme için öncelikle bir soket nesnesinin yaratılması gerekmektedir. Bu işlem socket isimli fonksiyonla yapılmaktadır. socket fonksiyonu + bir soket nesnesi (handle alanı) yaratır ve bize handle değeri verir. Windows sistemlerinde socket fonksiyonunun geri döndürdüğü handle + değeri SOCKET isimli bir türdendir. Ancak UNIX/Linux sistemlerinde bir dosya betimleyicisi biçimindedir. Yani UNIX/Linux sistemlerinde + socket nesneleri tamamen bir dosya gibi kullanılmaktadır. Windows sistemlerinde socket fonksiyonunun prototipi şöyledir: + + #include + + SOCKET socket( + int af, + int type, + int protocol + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + int socket(int domain, int type, int protocol); + + Fonksiyonların parametreleri her iki sistemde de aynıdır. Yukarıda da gördüğünüz gibi tek fark Windows sistemlerinde soketin handle değerinin + SOCKET türüyle UNIX/Linux sistemlerinde ise int türüyle temsil edilmesidir. + + socket fonksiyonunun birinci parametresi kullanılacak protokol ailesini belirtir. Bu parametre AF_XXX (Address Family) biçimindeki sembolik + sabitlerden biri olarak girilir. IPv4 için bu parametreye AF_INET, IPv6 için AF_INET6 girilmelidir. UNIX domain soketler için bu parametre + AF_UNIX olarak girilmelidir. + + Fonksiyonun ikinci parametresi kullanılacak protokolün stream tabanlı mı yoksa datagram tabanlı mı olacağını belirtmektedir. Stream soketler + için SOCK_STREAM, datagram soketler için SOCK_DGRAM kullanılmalıdır. Ancak başka soket türleri de vardır. TCP protokolünde bu parametre + SOCK_STREAM biçiminde UDP protokülünde ise bu parametre SOCK_DGRAM biçiminde girilmelidir. + + Fonksiyonun üçüncü parametresi aktarım (transport) katmanındaki protokolü belirtmektedir. Ancak zaten ikinci parametreden aktarım protokolü + anlaşılıyorsa üçüncü parametre 0 olarak geçilebilmektedir. Örneğin IP protokol ailesinde üçüncü parametreye gerek duyulmamaktadır. Çünkü ikinci + parametredeki SOCK_STREAM zaten TCP'yi, SOCK_DGRAM ise zaten UDP'yi anlatmaktadır. Fakat yine de bu parametreye istenirse IP ailesi için + IPPROTO_TCP ya da IPPROTO_UDP girilebilir. (Bu sembolik sabitler UNIX/Linux sistemlerinde içerisindedir.) + + socket fonksiyonu başarı durumunda Windows'ta soket handle değerine UNIX/Linux sistemlerinde ise soket betimeleyicisine geri dönemktedir. + Fonksiyon başarısızlık durumunda Windows'ta SOCKT_ERROR değerine UNIX/Linux sistemlerinde ise -1 değerine geri dönmektedir. Windows sistemlerindeki + SOCKET_ERROR sembolik sabiti de zaten -1 biçiminde define edilmiştir. Ancak Windows sistemlerinde SOCKET_ERROR sembolik sabitinin kullanılması + gerekir. + + Örneğin Windows sistemlerinde socket nesnesi şöyle yaratılabilir: + + SOCKET serverSock; + ... + + if ((serverSock = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR) + ExitSys("socket", WSAGetLastError()); + + Aynı işlem UNIX/Linux sistemlerinde şöyle yapılabilir: + + int server_sock; + ... + + if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + exit_sys("socket"); +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------ + Server program soketi yarattıktan sonra onu bağlamalıdır (bind etmelidir). bind işlemi sırasında server'ın hangi portu dinleyeceği ve + hangi network arayüzünden (kartından) gelen bağlantı isteklerini kabul edeceği belirlenir. Ancak bind fonksiyonu dinleme işlemini başlatmaz. + Yalnızca soket nesnesine bu bilgileri yerleştirir. Fonksiyonun Windows sistemlerindeki prototipi şöyledir: + + #include + + int bind( + SOCKET s, + const sockaddr *addr, + int namelen + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + int bind(int socket, const struct sockaddr *addr, socklen_t addrlen); + + Görüldüğü gibi fonksiyonların iki sistemde de prototipi aynıdır. Ancak Windows sistemlerinde soket nesnesi SOCKET türüyle temsil edilmektedir. + Biz kursumuzda anlatımı kolaylaştırmak için her iki sistemde de socket handle değerine soket betimelyicisi diyeceğiz. + + bind fonksiyonunun birinci parametresi yaratılmış olan soket betimleyicisini belirtir. İkinci parametre her ne kadar sockaddr isimli bir yapı + türünden gösterici ise de de aslında her protokol için ayrı bir yapı nesnesinin adresini almaktadır. Yani sockaddr yapısı burada genelliği + (void gösterici gibi) temsil etmek için kullanılmıştır. IPv4 için kullanılacak yapı sockaddr_in, IPv6 için sockaddr_in6 ve örneğin UNIX + domain soketler için ise sockaddr_un biçiminde olmalıdır. Üçüncü parametre, ikinci parametredeki yapının uzunluğu olarak + girilmelidir. + + sockaddr_in yapısı UNIX/Linux sistemlerinde dosyası içerisindedir. Windows sistemlerinde bu yapı şöyle bildirilmiştir: + + #include + + typedef struct sockaddr_in { + short sin_family; + u_short sin_port; + struct in_addr sin_addr; + char sin_zero[8]; + } SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN; + + UNIX/Linux sistemlerinde ise bu yapı şöyle bildirilmiştir: + + #include + + struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + }; + + Yapı elemanlarının türleri için iki sistemde farklı typedef isimleri kullanılmış olasa da elemanlar aynı anlamdadır. Yapının sin_family + elemanına protokol ailesini belirten AF_XXX değeri girilmelidir. Bu eleman tipik olarak short biçimde bildirilmiştir. Yapının sin_port + elemanı her iki sistemde de unsigned short türdendir. Server programın hangi portu dinleyeceği bu elemanla belirlenmektedir. Yapının + sin_addr elemanı IP numarası belirten bir elemandır. Bu eleman in_addr isimli bir yapı türündendir. Bu yapı Windows sistemlerinde şöyle + bildirilmiştir: + + struct in_addr { + union { + struct { + u_char s_b1; + u_char s_b2; + u_char s_b3; + u_char s_b4; + } S_un_b; + struct { + u_short s_w1; + u_short s_w2; + } S_un_w; + u_long S_addr; + } S_un; + }; + + #define s_addr S_un.S_addr + + UNIX/Linux sistemlerinde ise in_addr yapısı dosyası içerisinde şöyle bildirilmiştir: + + #include + + struct in_addr { + in_addr_t s_addr; + }; + + Aslında her iki sistemde de yapının s_addr elemanı IPV4 için 4 byte'lık işaretsiz tamsayı türü belirtmektedir. İşte bu 4 byte'lık işaretsiz + tamsayı türü IPV4 için IP adresini belirtmektedir. Eğer buradaki IP adresi INADDR_ANY biçiminde geçilirse bu durum "herhangi bir network + kartından gelen bağlantı isteklerinin kabul edileceği" anlamına gelmektedir. + + Yukarıda da belirttiğimiz gibi IP ailesinde tüm sayısal değerler "big endian" formatıyla belirtilmek zorundadır. Bu ailede + "network byte ordering" denildiğinde "big endian" format anlaşılır. Oysa makinelerin belli bir bölümü (örneğin Intel ve default ARM) + "little endian" kullanmaktadır. İşte elimizdeki makinenin endian'lığı ne olursa olsun onu big endian formata dönüştüren htons + (host to network byte ordering short) ve htonl (host to network byte ordering long) isimli iki fonksiyon vardır. Bu işlemlerin tersini + yapan da ntohs (network byte ordering to host short) ve ntohl (network byte ordering to host long) fonksiyonları da bulunmaktadır. + Fonksiyonların Windows sistemlerindeki prototipleri şöyledir: + + #include + + u_short htons( + u_short hostshort + ); + + u_long htonl( + u_long hostlong + ); + + u_short ntohs( + u_short netshort + ); + + u_long ntohl( + u_long netlong + ); + + Fonksiyonların UNIX/Linux sistemlerindeki prototipleri şöyledir: + + #include + + uint32_t htonl(uint32_t hostlong); + uint16_t htons(uint16_t hostshort); + uint32_t ntohl(uint32_t netlong); + uint16_t ntohs(uint16_t netshort); + + Bu durumda sockaddr_in yapısı her iki sistemde de tipik olarak şöyle doldurulabilir: + + struct sockaddr_in sinaddr; + + sinaddr.sin_family = AF_INET; + sinaddr.sin_port = htons(SERVER_PORT); + sinaddr.sin_addr.s_addr = htonl(INADDR_ANY); + + bind fonksiyonu başarı durumunda sıfır değerine, başarısızlık durumunda Windows sistemlerinde SOCKET_ERROR değerine, UNIX/Linux sistemlerinde + ise -1 değerine geri dönmektedir. Örneğin Windows sistemlerinde fonksiyon şöyle çağrılabilir: + + if (bind(serverSock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == SOCKET_ERROR) + ExitSys("bind", WSAGetLastError()); + + UNIX/Linux sistemlerinde de çağrı şöyle olabilir: + + if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) + exit_sys("bind"); +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------ + server program bind işleminden sonra soketi aktif dinleme konumuna sokmak için listen fonkiyonunu çağırmalıdır. Fonksiyonun Windows + sistemlerindeki prototipi şöyledir: + + #include + + int listen( + SOCKET s, + int backlog + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + int listen(int socket, int backlog); + + Fonksiyonun birinci parametresi soket betimleyicisini, ikinci parametresi kuyruk uzunluğunu belirtir. listen işlemi blokeye yol açmamaktadır. + İşletim sistemi listen işleminden sonra ilgili porta gelen bağlantı isteklerini uygulama için oluşturduğu bir bağlantı kuyruğuna yerleştirmektedir. + Kuyruk uzunluğunu yüksek tutmak meşgul server'larda bağlantı isteklerinin kaçırılmamasını sağlayabilir. Linux'ta default durumda verilebilecek + en yüksek değer 128'dir. Ancak "/proc/sys/net/core/somaxconn" dosyasındaki değer değiştirilerek bu default uzunluk artırılabilir. Fonksiyon + başarı durumunda 0 değerine, başarısızlık durumunda Windows sistelerinde SOCKET_ERROR değerine, UNIX/Linux sistemlerinde ise -1 değerine + geri dönmektedir. Örneğin Windows sistemlerinde fonksiyon şöyle çağrılabilir: + + if (listen(serverSock, 8) == SOCKET_ERROR) + ExitSys("listen", WSAGetLastError()); + + UNIX/Linux sistemlerinde de çağrı şöyle yapılabilir: + + if (listen(server_sock, 8) == -1) + exit_sys("listen"); + + Bu fonksiyon işletim sistemlerinin "firewall mekanizması" tarafından denetlenebilmektedir. Eğer çalıştığınız sistemde söz konusu port + firewall tarafından kapatılmışsa bunu açmanız gerekir. (Windows sistemlerinde listen fonksiyonu bir pop pencere çıkartarak uyarı mesajı + görüntülemektedir.) + + Yukarıda da belirttiğimiz gibi listen fonksiyonu herhangi bir blokeye yol açmaz. Bu fonksiyon çağrıldıktan sonra işletim sistemi ilgili + porta gelecek bağlantı isteklerini prosese özgü bir bağlantı kuyruğuna yerleştirmektedir. Bu bağlantı kuyruğundan bağlantı isteklerini + alarak bağlantısıyı sağlayan asıl fonksiyon accept fonksiyonudur. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------ + 97. Ders 22/06/2024 - Cumartesi +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------ + accept fonksiyonu bağlantı kuyruğuna bakar. Eğer kuyrukta bir bağlantı isteği varsa onu alır ve hemen geri döner. Eğer orada bir bağlantı + isteği yoksa default durumda blokede bekler. Ancak accept fonksiyonu blokesiz modda da kullanılabilmektedir. Fonksiyonun Windows + sistemlerindeki prototipi şöyledir: + + #include + + SOCKET accept( + SOCKET s, + struct sockaddr *addr, + int *addrlen + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + int accept(int socket, struct sockaddr *address, socklen_t *address_len); + + Fonksiyonların birinci parametreleri dinleme soketinin dosya betimleyicisini almaktadır. İkinci parametre bağlanılan client'a ilişkin bilgilerin + yerleştirileceği sockaddr_in yapısının adresini almaktadır. Bu parametre yine genel bir sockaddr yapısı türünden gösterici ile temsil edilmiştir. + Bizim bu parametre için IPv4'te sockaddr_in türünden, IPv6'da sockaddr_in6 türünden bir yapı nesnesinin adresini argüman olarak vermemiz gerekir. + sockaddr_in yapısının üç elemanı olduğunu anımsayınız. Biz bu parametre sayesinde bağlanan client programın IP adresini ve client makinedeki + port numarasını elde edebilmekteyiz. Client program server programa bağlanırken bir IP adresi ve port numarası belirtir. Ancak kendisinin de bir + IP adresi ve port numarası vardır. Client'ın port numarası kendi makinesindeki (host'undaki) port numarasıdır. Client'ın IP adresine ve oradaki + port numarasına "remote end point" de denilmektedir. Örneğin 178.231.152.127 IP adresinden bir client programın 52310 port'u ile server'ın bulunduğu + 176.234.135.196 adresi ve 55555 numaralı portuna bağlandığını varsayalım. Burada remote endpoint "178.231.152.127:52310" biçiminde ifade edilmektedir. + İşte biz accept fonksiyonunun ikinci parametresinden client hakkında bu bilgileri almaktayız. + + Client (178.231.152.127:52310) ---> Server (176.234.135.196:55555) + + accept fonksiyonunun üçüncü parametresi yine ikinci parametredeki yapının (yani sockaddr_in yapısının) byte uzunluğunu belirtmektedir. Ancak bu + parametre bir adres olarak alınmaktadır. Yani programcı Windows'ta int türünden UNIX(Linux sistemlerinde socklen_t türünden bir nesne tanımlamalı, + bu nesneye bu sizeof değerini yerleştirmeli ve nesnenin adresini de fonksiyonun üçüncü parametresine geçirmelidir. Fonksiyon bağlanılan client'a + ilişkin soket bilgilerinin byte uzunluğunu yine bu adrese yerleştirmektedir. Tabii IP protokol ailesinde her iki taraf da aynı yapıyı kullanıyorsa + fonksiyon çıkışında bu sizeof değerinde bir değişiklik olmayacaktır. Ancak tasarım genel yapıldığı için böyle bir yola gidilmiştir. + + accept fonksiyonu başarı durumunda bağlanılan client'a ilişkin yeni bir soket betimleyicisine geri dönmektedir. Artık bağlanılan client ile bu + soket yoluyla konuşulacaktır. accept fonksiyonu başarısızlık durumunda Windows sistemlerinde SOCKET_ERROR değerine UNIC/Linux sistemlerinde ise -1 + değeri ile geri dönmektedir. Örneğin Windows sistemlerine accpet fonksiyonu şöyle kullanılabilir: + + struct sockaddr_in sin_client; + int sin_len; + SOCKET serverSock; + ... + + sin_len = sizeof(sin_client); + if ((clientSock = accept(serverSock, (struct sockaddr *)&sin_client, &sin_len)) == SOCKET_ERROR) + ExitSys("accept", WSAGetLastError()); + + UNIX/Linux sistemlerinde de fonksiyon şöyle kullanılabilir: + + struct sockaddr_in sin_client; + socklen_t sin_len; + int client_sock; + ... + + sin_len = sizeof(sin_client); + if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) + exit_sys("accept"); + + Server tarafta temelde iki soket bulunmaktadır. Birincisi bind, listen, accept işlemini yapmakta kullanılan sokettir. Bu sokete TCP/IP + terminolojisinde ""pasif soket (passive socket)" ya da "dinleme soketi (listening socket)" denilmektedir. İkinci soket ise client ile + konuşmakta kullanılan accept fonksiyonunun geri döndürdüğü sokettir. Buna da "aktif soket (active socket)" denilmektedir. Tabii server + program birden fazla client ile konuşacaksa accept fonksiyonunu bir kez değil, çok kez uygulamalıdır. Her accept o anda bağlanılan client + ile konuşmakta kullanılabilecek yeni bir soket vermektedir. bind, listen işlemleri bir kez yapılmaktadır. Halbuki accept işlemi her client + bağlantısı için ayrıca uygulanmalıdır. + + accept fonksiyonu default durumda blokeli modda çalışmaktadır. Eğer accept çağrıldığında o anda bağlantı kuyruğunda hiç bir client isteği + yoksa accept fonksiyonu blokeye yol açmaktadır. Eğer accept çağrıldığında zaten bağlantı kuyruğunda bağlantı için bekleyen client varsa + accept bloke olmadan bağlantıyı gerçekleştirir ve geri döner. + + accept fonksiyonu ile elde edilen client bilgilerindeki IP adresini ve port numaraları "big endian" formatında yani "network byte ordering" + formatındadır. Bunları sayısal olarak görüntülemek için ntohl ve ntohs fonksiyonlarının kullanılması gerekir. Tabii izleyen paragrafta ele + alacağımız gibi aslında IP adresleri genellikle "noktalı desimal format (dotted decimal format)" denilen bir format ile yazı biçiminde + görüntülenmektedir. + + IPV4 adresini alarak noktalı desimal formata dönüştüren inet_ntoa isimli bir fonksiyon vardır. Fonksiyonun prototipi her iki sistemde de + şöyledir: + + #include + #include + + char *inet_ntoa(struct in_addr in); + + Fonksiyon parametre olarak sockaddr_in yapısının içerisindeki IP adresinin bulunduğu in_addr yapısını (adresini değil) parametre olarak + alır ve noktalı desimal format yazısının adresiyle geri döner. Bu durumda accept işlemindne elde edilen client bilgileri aşağıdaki gibi + ekrana yazdırabilir: + + printf("waiting for client...\n"); + sin_len = sizeof(sin_client); + if ((clientSock = accept(serverSock, (struct sockaddr *)&sin_client, &sin_len)) == SOCKET_ERROR) + ExitSys("accept", WSAGetLastError()); + + printf("Connected %s:%d\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port)); + + inet_nto fonksiyonunun tersini yapan inet_aton isimli bir fonksiyon da bulunmaktadır. Bu fonksiyon noktalı desimal formattaki ip adresini + in_addr yapısınn içerisine yerleştirmektedir. Bu fonksiyon Windows sistemlerinde yoktur. Yalonızca UNIX/Linux sistemlerinde bulunmaktadır: + + #include + + int inet_aton(const char *cp, struct in_addr *inp); + + Fonksiyonun birinci parametresi noktalı desimal formattaki ip adresini almaktadır. İkinci paranetresi ise ip adresinin yerleştirileceği + in_addr yapısının adresini almaktadır. Fonksiyon başarı durumunda 0 değerine başarısızlık durumunda ise -1 değerine geri dönmektedir. + + inet_ntoa ve inet_aton fonksiyonlarının inet_ntop ve inet_pton isimli versiyonları da vardır. Aslında artık inet_ntoa ve inet_aton fonksiyonları + "deprecated" yapılmıştır. Ancak inet_ntop ve inet_pton fonksiyonlarının kullanımı biraz zordur. Biz kursumuzda "deprecated" olmasına karşın + kolaylığı nedeniyle inet_ntoa ve inet_aton fonksiyonlarını kullanacağız. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------- + Aşağıda Windows ve UNIX/Linux sistemlerinde geldiğimiz noktaya kadar server programların kodları bir bütün olarak verilmiştir. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* Windows ""server.c" */ + +#include +#include +#include +#include + +#define PORT_NO 55555 + +void ExitSys(LPCSTR lpszMsg, DWORD dwLastErr); + +int main(void) +{ + WSADATA wsaData; + int result; + SOCKET serverSock, clientSock; + struct sockaddr_in sinServer, sinClient; + int sin_len; + + if ((result = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) + ExitSys("WSAStartup", result); + + if ((serverSock = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR) + ExitSys("socket", WSAGetLastError()); + + sinServer.sin_family = AF_INET; + sinServer.sin_port = htons(PORT_NO); + sinServer.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(serverSock, (struct sockaddr *)&sinServer, sizeof(sinServer)) == SOCKET_ERROR) + ExitSys("bind", WSAGetLastError()); + + if (listen(serverSock, 8) == SOCKET_ERROR) + ExitSys("listen", WSAGetLastError()); + + printf("waiting for client...\n"); + sin_len = sizeof(sinClient); + if ((clientSock = accept(serverSock, (struct sockaddr *)&sinClient, &sin_len)) == SOCKET_ERROR) + ExitSys("accept", WSAGetLastError()); + + printf("Connected %s:%d\n", inet_ntoa(sinClient.sin_addr), ntohs(sinClient.sin_port)); + + WSACleanup(); + + printf("Ok\n"); + + return 0; +} + +void ExitSys(LPCSTR lpszMsg, DWORD dwLastErr) +{ + LPTSTR lpszErr; + + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { + fprintf(stderr, "%s: %s", lpszMsg, lpszErr); + LocalFree(lpszErr); + } + + exit(EXIT_FAILURE); +} + +/* UNIX/Linux "server.c" */ + +#include +#include +#include +#include + +#define PORT_NO 55555 + +void exit_sys(const char* msg); + +int main(void) +{ + int server_sock, client_sock; + struct sockaddr_in sin_server, sin_client; + socklen_t sin_len; + + if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + exit_sys("socket"); + + sin_server.sin_family = AF_INET; + sin_server.sin_port = htons(PORT_NO); + sin_server.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) + exit_sys("bind"); + + if (listen(server_sock, 8) == -1) + exit_sys("listen"); + + printf("waiting for client..\n"); + sin_len = sizeof(sin_client); + if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) + exit_sys("accept"); + printf("Client connected %s:%d\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port)); + + return 0; +} + +void exit_sys(const char* msg) +{ + perror(msg); + + exit(EXIT_FAILURE); +} + +/*------------------------------------------------------------------------------------------------------------------------------------------- + TCP client program, server programa bağlanabilmek için tipik bazı adımları uygulamak zorundadır. Bu adımlar sırasında çağrılacak fonksiyonlar + şunlardır: + + (Windows'ta WSAStartup) ---> socket ---> bind (isteğe bağlı) ---> gethostbyname (isteğe bağlı) ---> connect ---> send/recv (ya da read/write) + ---> shutdown ---> close (Windows'ta closesocket) (---> Windows'ta WSACleanup) + + Client taraf önce yine socket fonksiyonuyla bir soket yaratır. Soketin bind edilmesi gerekmez. Zaten genellikle client taraf soketi bind etmez. + Eğer client taraf belli bir port'tan bağlanmak istiyorsa bu durumda bind işlemini uygulayabilir. Eğer client bind işlemi yapmazsa zaten işletim + sistemi connect işlemi sırasında sokete boş bir port numarasını atamaktadır. İşletim sisteminin bind edilmemiş client programa connect işlemi + sırasında atadığı bu port numarasına İngilizce "ephemeral port (ömrü kısa olan port)"" denilmektedir. Seyrek olarak bazı server programlar + client için belli bir remote port numarası talep edebilmektedir. Bu durumda client'ın bu remote port'a sahip olabilmesi için bind işlemini + uygulaması gerekir. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------- + Client bağlantı için server'ın IP adresini ve port numarasını bilmek zorundadır. IP adreslerinin akılda tutulması zordur. Bu nedenle IP + adresleri ile eşleşen "host isimleri" oluşturulmuştur. Ancak IP protokol ailesi host isimleriyle değil, IP numaralarıyla çalışmaktadır. + İşte host isimleriyle IP numaralarını eşleştiren ismine DNS (Domain Name Server) denilen özel server'lar bulunmaktadır. Bu server'lar + IP protokol ailesindeki DNS isimli bir protokol ile çalışmaktadır. Dolayısıyla client programın elinde IP adresi yerine host ismi varsa + DNS işlemi yaparak o host ismine karşı gelen IP numarasını elde etmesi gerekir. DNS server'lar dağıtık biçimde bulunmaktadır. Bir kayıt + bir DNS server'da yoksa başka bir DNS server'a referans edilmektedir. + + DNS server'larda host isimleriyle IP numaraları bire bir karşılık gelmemektedir. Belli bir host ismine birden fazla IP numarası eşleştirilmiş + olabileceği gibi belli bir IP numarasına da birden fazla host ismi eşleştirilmiş olabilmektedir. + + DNS işlemleri yapan iki geleneksel fonksiyon vardır: gethostbyname ve gethostbyaddr. Bu fonksiyonların kullanımları kolaydır. Ancak bu + fonksiyonlar artık "deprecated" yapılmış ve POSIX standartlarından da silinmiştir. Bunların yerine getnameinfo ve getaddrinfo fonksiyonları + oluşturulmuştur. Bu fonksiyonlar POSIX standartlarında bulunmaktadır. Biz kursumuzda artık "deprecated" hale getirilmiş "gethostbyname" + ve "gethostbyaddress" kullanmayacağız. Bunun yerine "getaddrinfo" fonksiyonunu açıklayıp onu kullanacağız. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------- + getaddrinfo isimli fonksiyon inet_addr ve gethosybyname fonksiyonlarının IPv6'yı da içerecek biçimde genişletilmiş bir biçimidir. Yani + getaddrinfo hem noktalı desimal formatı nümerik adrese dönüştürür hem de eğer geçersiz bir noktalı desimal format söz konusuysa (bu durumda + server isimsel olarak girilmiş olabilir) DNS işlemi yaparak ilgili host'un IP adresini elde eder. Maalesef fonksiyon biraz karışık tasarlanmıştır. + Fonksiyonun Windows sistemlerindeki prototipi şöyledir: + + #include + + INT getaddrinfo( + PCSTR pNodeName, + PCSTR pServiceName, + const ADDRINFOA *pHints, + PADDRINFOA *ppResult + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); + + Aslında her iki sistemde de typedef isimleri farklı olmasına karşın fonksiyon aynı biçimde kullanılmaktadır. Fonksiyonun birinci parametresi + "noktalı desimal formatlı IP adresi" ya da "host ismini" belirtmektedir. İkinci parametre NULL geçilebilir ya da buraya port numarası girilebilir. + Ancak bu parametreye port numarası girilecekse yazısal biçimde girilmelidir. Fonksiyon bu port numarasını htons yaparak "big endian" formata + dönüştürüp bize verecektir. Bu parametreye aynı zamanda IP ailesinin uygulama katmanına ilişkin spesifik bir protokolün ismi de girilebilmektedir + (Örneğin "http" gibi, "ftp" gibi). Bu durumda bu protokollerin port numaraları bilindiği için sanki o port numaraları girilmiş gibi işlem + yapılır. Eğer bu parametreye NULL girilirse bize port olarak 0 verilecektir. Port numarasını biz yerleştiriyorsak bu parametreye NULL girebiliriz. + Fonksiyonun üçüncü parametresi nasıl bir adres istediğimizi anlatan filtreleme seçeneklerini belirtir. Bu parametre addrinfo isimli bir yapı + türündendir. Bu yapının yalnızca ilk dört elemanı programcı tarafından girilebilmektedir. Ancak POSIX standartları bu yapının elemanlarının + sıfırlanmasını öngörmektedir (buradaki sıfırlanmak terimi normal türdeki elemanlar için 0 değerini, göstericiler için NULL adres değerini + belirtmektedir). addrinfo yapısı şöyledir: + + struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; + }; + + Yapının ai_flags elemanı pek çok bayrak değeri alabilmektedir. Bu değer 0 olarak da geçilebilir. Yapının ai_family elemanı AF_INET girilirse + host'a ilişkin IPv4 adresleri, AF_INET6 girilirse host'a ilişkin IPv6 adresleri, AF_UNSPEC girilirse hem IPv4 hem de IPv6 adresleri elde edilir. + Yapının ai_socktype elemanı 0 girilebilir ya da SOCK_STREAM veya SOCK_DGRAM girilebilir. Fonksiyonun ayrıntılı açıklaması için dokümanlara başvurunuz. + Bu parametre NULL adres de girilebilir. Bu durumda ilgili host'a ilişkin tüm adresler elde edilir. + + getaddrinfo fonksiyonunun son parametresine bir bağlı listenin ilk elemanını gösteren adres yerleştirilmektedir. Buradaki bağlı listenin bağ + elemanı addrinfo yapısının ai_next elemanıdır. Bu bağlı listenin boşaltımı freeaddrinfo fonksiyonu tarafından yapılmaktadır. + + getaddrinfo fonksiyonu başarı durumunda 0 değerine, başarısızlık durumunda doğrudan hata koduna geri döner. Bu hata kodları Windows şistemlerinde + WSAGetLastError fonksiyonuyla elde ettiğimiz hata kodlarıdır. Ancak UNIX/Linux sistemlerinde bu hata kodları errno kodları değildir. Bu hata kodlarının + UNIX/Linux sistemlerinde gai_strerror fonksiyonuyla yazıya dönüştürülmesi gerekir. + + Bağlı listenin düğümlerini free hale getirmek için freeaddrinfo fonksiyonu kullanılmaktadır. Fonksiyonun prototipi şöyledir: + + #include + + void freeaddrinfo(struct addrinfo *ai); + + Fonksiyon getaddrinfo fonksiyonunun verdiği bağlı listenin ilk düğümünün (head pointer) adresini parametre olarak alır ve tüm bağlı listeyi boşaltır. + gai_strerror fonksiyonunun prototipi de şöyledir: + + #include + + const char *gai_strerror(int ecode); + + getaddrinfo fonksiyonunun client programda tipik kullanımı aşağıda verilmiştir. + + Bu fonksiyon bize connect için gereken sockaddr_in ya da sockadd_in6 yapı nesnelerini kendisi oluşturup sockaddr türünden bir adres gibi vermektedir. + Örneğin biz "microsoft.com" host isminin bütün IPV4 AIP adreslerini aşağıdaki gibi elde edebiliriz: + + struct addrinfo *ainfoHead, *ainfo; + struct sockaddr_in *sinHost; + struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; + ... + + if ((result = getaddrinfo(HOST_NAME, "55555", &hints, &ainfoHead)) != 0) + ExitSys("bind", result); + + for (ainfo = ainfoHead; ainfo != NULL; ainfo = ainfo->ai_next) { + sinHost = (struct sockaddr_in *)ainfo->ai_addr; + printf("%s\n", inet_ntoa(sinHost->sin_addr)); + } + + freeaddrinfo(ainfoHead); + + Programcı host isminden elde ettiği tüm IP numaralarını bağlantı için deneyebilir. + + getaddrinfo fonksiyonunun tersini yapan getnameinfo isminde bir fonksiyon da sonraları soket kütüphanesine eklenmiştir. Fonksiyon temel + olarak ilgili host'un IP adresini alıp bize aynı biçimde host isimlerini vermektedir. Biz burada bu fonksiyonu açıklamayacağız. + + Eğer elimizde zaten server'ın IP adresi noktalı desimal formatta varsa biz getaddrinfo fonksiyonunu kullanmak yerine inet_addr ile de bu + noktalı desimal formatı IP adresine dönüştürebiliriz. Ancak genel olarak getaadrinfo fonksiyonu daha genel ve daha yeteneklidir. Bu + fonksiyonun kullanılması tavsiye edilebilir. inet_addr fonksiyonunun Windows sistemlerindeki prototipi şöyledir: + + #include + + unsigned long inet_addr(const char *cp); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + in_addr_t inet_addr(const char *cp); + + Fonksiyonlar noktasıl desimal formattaki IP adresini 4 byte'lık IPV4 adresine dönüştürmektedir. Fonksiyon başarısızlık durumunda Windows + sistemlerinde INADDR_NONE değerine, UNIX/Linux sistemlrinde ise -1 değerine geri dönmektedir. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------------------------------------- + Artık client program connect fonksiyonuyla TCP bağlantısını sağlayabilir. connect fonksiyonunun Windows sistemlerindeki prototipi şöyledir: + + #include + + int WSAAPI connect( + SOCKET s, + const sockaddr *name, + int namelen + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + int connect(int socket, const struct sockaddr *address, socklen_t address_len); + + Fonksiyonun birinci parametresi soket betimleyicisini belirtir. İkinci parametre bağlanılacak server'a ilişkin sockaddr_in + yapı nesnesinin adresini belirtmektedir. Fonksiyonun üçüncü parametresi, ikinci parametredeki yapının uzunluğunu almaktadır. + Fonksiyon başarı durumunda sıfır değerine, başarısızlık durumunda -1 değerine geri dönmektedir. + + Eğer connect fonksiyonu çağrıldığında server program çalışmıyorsa ya da server programın bağlantı kuyruğu doluysa connect + belli bir zaman aşımı süresi kadar bekler ve sonra başarısız olur ve errno değeri ECONNREFUSED ("Connection refused") ile + set edilir. Örneğin: + + if (connect(client_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) + exit_sys("connect"); + + Aşağıda örnektlerde bağlantı için gereken minimum client program verilmiştir. Burada henüz görmediğimiz işlemleri hiç uygulamadık. + Client programda bind işlemini yorum satırı içerisine aldık. Yukarıda da belirttiğimiz gibi eğer client program bind işlemi yapmazsa + (ki genellikle yapmaz) bu durumda işletim sistemi client program için o programın çalıştığı makinede boş bir port numarası atamaktadır. + Ayrıca biz bu örneklerde inet_addr fonksiyonunun nasıl kullanılacağını da yorum satırları içerisinde gösterdik. +-------------------------------------------------------------------------------------------------------------------------------------------*/ + +/* Windows "client.c" */ + +#include +#include +#include +#include +#include + +#define PORT_NO "55555" +#define HOST_NAME "127.0.0.1" + +void ExitSys(LPCSTR lpszMsg, DWORD dwLastErr); + +int main(void) +{ + WSADATA wsaData; + int result; + SOCKET clientSock; + struct addrinfo *ainfoHead, *ainfo; + struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; + + if ((result = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) + ExitSys("WSAStartup", result); + + if ((clientSock = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR) + ExitSys("socket", WSAGetLastError()); + + /* + { + #define CLIENT_PORT_NO 50000 + + struct sockaddr_in sinClient; + + sinClient.sin_family = AF_INET; + sinClient.sin_port = htons(CLIENT_PORT_NO); + sinClient.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(clientSock, (struct sockaddr *)&sinClient, sizeof(sinClient)) == SOCKET_ERROR) + ExitSys("bind", WSAGetLastError()); + } + */ + + + if ((result = getaddrinfo(HOST_NAME, PORT_NO, &hints, &ainfoHead)) != 0) + ExitSys("getaddrinfo", result); + + for (ainfo = ainfoHead; ainfo != NULL; ainfo = ainfo->ai_next) + if (connect(clientSock, ainfo->ai_addr, sizeof(struct sockaddr_in)) == 0) + break; + + if (ainfo == NULL) + ExitSys("connect", WSAGetLastError()); + + freeaddrinfo(ainfoHead); + + /* + { + struct sockaddr_in sinServer; + + sinServer.sin_family = AF_INET; + sinServer.sin_port = htons(55555); + if ((sinServer.sin_addr.s_addr = inet_addr(HOST_NAME)) == INADDR_NONE) + ExitSys("inet_addr", WSAGetLastError()); + if (connect(clientSock, (struct sockaddr *)&sinServer, sizeof(sinServer)) == SOCKET_ERROR) + ExitSys("connect", WSAGetLastError()); + } + */ + + printf("connected...\n"); + + WSACleanup(); + + return 0; +} + +void ExitSys(LPCSTR lpszMsg, DWORD dwLastErr) +{ + LPTSTR lpszErr; + + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { + fprintf(stderr, "%s: %s", lpszMsg, lpszErr); + LocalFree(lpszErr); + } + + exit(EXIT_FAILURE); +} + +/* Unix/Linux "client.c" */ + +#include +#include +#include +#include +#include + +#define PORT_NO "55555" +#define HOST_NAME "127.0.0.1" + +void exit_sys(const char* msg); + +int main(void) +{ + int client_sock; + struct addrinfo *ainfo_head, *ainfo; + struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; + int result; + + if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + exit_sys("socket"); + + /* + { + #define CLIENT_PORT_NO 50000 + + struct sockaddr_in sin_client; + + sin_client.sin_family = AF_INET; + sin_client.sin_port = htons(CLIENT_PORT_NO); + sin_client.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) + exit_sys("bind"); + } + */ + + if ((result = getaddrinfo(HOST_NAME, PORT_NO, &hints, &ainfo_head)) != 0) + exit_sys("getaddrinfo"); + + for (ainfo = ainfo_head; ainfo != NULL; ainfo = ainfo->ai_next) + if (connect(client_sock, ainfo->ai_addr, sizeof(struct sockaddr_in)) == 0) + break; + + if (ainfo == NULL) + exit_sys("connect"); + + freeaddrinfo(ainfo_head); + /* + { + struct sockaddr_in sin_server; + + sin_server.sin_family = AF_INET; + sin_server.sin_port = htons(55555); + if ((sin_server.sin_addr.s_addr = inet_addr(HOST_NAME)) == -1) + exit_sys("inet_addr"); + if (connect(client_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) + exit_sys("connect"); + } + */ + + printf("connected...\n"); + + return 0; +} + +void exit_sys(const char* msg) +{ + perror(msg); + + exit(EXIT_FAILURE); +} + +/*------------------------------------------------------------------------------------------------------------------------------------------- + Bağlantı sağlandıktan sonra artık gönderme ve alma işlemleri yapılabilir. Soketten karşı tarafa byte göndermek için send karşı taraftan + byte okumak için recv fonksiyonları kullanılmaktadır. UNIX/Linux sistemlerinde soketler de dosya gibi betimleyici oldukları için bu + sistemlerde send yerine write, recv yerine read POSIX fonksiyonları da kullanılabilir. TCP full duplex bir haberleşme sunmaktadır. Yani + client ve server programlar aynı soket ile hem bilgi gönderip hem de bilgi alabilmektedir. + + recv fonksiyonunun Windows sistemlerindeki prototipi şöyledir: + + #include + + int recv( + SOCKET s, + char *buf, + int len, + int flags + ); + + UNIX/linux sistemlerindeki prototipi ise şöyledir: + + #include + + ssize_t recv(int socket, void *buffer, size_t length, int flags); + + Fonksiyonların birinci parametresi aktif soketin betimleyicisini belirtmektedir. İkinci parametre alınacak bilginin yerleştirileceği dizinin + adresini almaktadır. Üçüncü parametre ise okunmak istenen byte sayısını belirtmektedir. Fonksiyonun son parametresi aşağıdaki üç sembolik + sabitin bit OR işlemine sokulmasıyla oluşturulabilir: + + MSG_PEEK + MSG_OOB + MSG_WAITALL + + Biz şimdilik bu değerlerin anlamlarını açıklamayacağız. Ancak MSG_PEEK değeri bilginin network tamponundan alındıktan sonra oradan atılmayacağını + belirtmektedir. Bu parametre 0 da geçilebilir. Zaten recv fonksiyonunun read fonksiyonundan tek farkı bu son parametredir. Bu son parametrenin + 0 geçilmesiyle read kullanılması arasında hiçbir farklılık yoktur. + + recv fonksiyonu blokeli modda (default durum blokeli moddur) tıpkı borularda olduğu gibi eğer hazırda en az 1 byte varsa okuyabildiği kadar + bilgiyi okur ve okuyabildiği byte sayısına geri döner. Eğer o anda network tamponunda hiç byte yoksa recv fonksiyonu en az 1 byte okuyana + kadar blokede bekler. (Yani başka bir deyişle recv tıpkı borularda olduğu gibi eğer okunacak bir şey yoksa blokede bekler, ancak okunacak + en az 1 byte varsa okuyabildiğini okur ve beklemeden geri döner.) + + recv fonksiyonu başarı durumunda okunabilen byte sayısına, başarısızlık durumunda Windows sistemlerinde SOCKET_ERROR, UNIX/Linux sistemlerinde + -1 değerine geri dönmektedir. Eğer karşı taraf soketi (peer socket) kapatmışsa bu durumda tıpkı borularda olduğu gibi recv fonksiyonu 0 + ile geri dönmektedir. Soketlerle boruların kullanımlarının birbirlerine çok benzediğine dikkat ediniz. + + Soketten bilgi göndermek için send ya da UNIX/Linux sistemleribde write fonksiyonu kullanılmaktadır. send fonksiyonunun Windows sistemlerindeki + prototipi şöyledir: + + #include + + int send( + SOCKET s, + const char *buf, + int len, + int flags + ); + + UNIX/Linux sistemlerindeki prototipi ise şöyledir: + + #include + + ssize_t send(int socket, const void *buffer, size_t length, int flags); + + Fonksiyoların birinci parametresi aktif soketin betimleyicisini belirtmektedir. İkinci parametre gönderilecek bilgilerin bulunduğu dizinin adresini + belirtir. Üçüncü parametre ise gönderilecek byte miktarını belirtmektedir. Son parametre aşağıdaki sembolik sabitlerin bit düzeyinde OR işlemine + sokulmasıyla oluşturulabilir: + + MSG_EOR + MSG_OOB + MSG_NOSIGNAL + + Bu parametre 0 da geçilebilir. Biz şimdilik bu bayraklar üzerinde durmayacağız. + + send fonksiyonu bilgileri karşı tarafa o anda göndermez. Onu önce network tamponuna yerleştirir. İşletim sistemi o tampondan TCP (dolayısıyla + IP) paketleri oluşturarak mesajı göndermektedir. Yani send fonksiyonu geri döndüğünde bilgiler network tamponuna yazılmıştır, ancak henüz + karşı tarafa gönderilmemiş olabilir. Pekiyi o anda network tamponu doluysa ne olacaktır? İşte UNIX/Linux sistemlerinde send fonksiyonu, + gönderilecek bilginin tamamı network tamponuna aktarılana kadar blokede beklemektedir. Ancak bu konuda işletim sistemleri arasında farklılıklar + olabilmektedir. Örneğin Windows sistemlerinde send fonksiyonu eğer network tamponununda gönderilmek istenen kadar yer yoksa ancak en az + bir byte'lık boş bir yer varsa tampona yazabildiği kadar byte'ı yazıp hemen geri dönmektedir. Diğer UNIX/Linux sistemleri arasında da + send fonksiyonunun davranışı bakımından bu yönde farklılıklar olabilmektedir. Ancak POSIX standartları blokeli modda tüm bilginin network + tamponuna yazılana kadar send fonksiyonunun bloke olacağını belirtmektedir. Linux çekirdeği de buna uygun biçimde çalışmaktadır. + + send fonksiyonu network tamponuna yazılan byte sayısı ile geri dönmektedir. Blokeli modda bu değer UNIX/Linux sistemlerinde yazılmak istenen + değerle aynı olur. Ancak Windows sistemlerinde daha az olabilmektedir. send fonksiyonu Windows'ta başarısızlık durumunda SOCKET_ERROR değeri + ile UNIX/Linux sistemlerinde ise -1 değeri ile geri döner.Tıpkı borularda olduğu gibi UNIX/Linux sistemlerinde send fonksiyonunda da eğer + karşı taraf soketi kapatmışsa send fonksiyonu default durumda SIGPIPE sinyalinin oluşmasına yol açmaktadır. Eğer bu sinyalin oluşturulması + istenmiyorsa bu durumda send fonksiyonunun son parametresi (flags) MSG_NOSIGNAL olarak geçilmelidir. Bu durumda karşı taraf soketi kapatmışsa + send fonksiyonu başarısız olur ve errno değeri EPIPE olarak set edilir. send fonksiyonunun soketlerdeki davranışının borulardaki davranışa + çok benzediğine dikkat ediniz. + + send fonksiyonunun son parametresi 0 geçildiğinde bu fonksiyonun davranışı tamamen write fonksiyonunda olduğu gibidir. +------------------------------------------------------------------------------------------------------------------------------------------*/ + /*------------------------------------------------------------------------------------------------------------------------------------------ Aşağıdaki iskelet bir TCP/IP client-server uygulama verilmiştir. Burada client klavyeden birtakım yazılar girer. Bunu server'a gönderir. Server da bu yazının tersini client'a geri yollamaktadır. Client "exit" yazdığında her iki taraf da işlemini sonlandırmaktadır. --------------------------------------------------------------------------------------------------------------------------------------------*/ +------------------------------------------------------------------------------------------------------------------------------------------*/ /* Server.c */ diff --git a/Unix-Linux-SysProg-OzetNotlar-Ornekler.txt b/Unix-Linux-SysProg-OzetNotlar-Ornekler.txt index b1be2d0..0069726 100644 --- a/Unix-Linux-SysProg-OzetNotlar-Ornekler.txt +++ b/Unix-Linux-SysProg-OzetNotlar-Ornekler.txt @@ -10,7 +10,7 @@ (Notları okurken editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.) - Son Güncelleme: 09/06/2024 - Pazar + Son Güncelleme: 12/07/2024 - Cuma ---------------------------------------------------------------------------------------------------------------------------*/ @@ -47,60 +47,59 @@ int main(void) Argümansız seçenekler "-" karakterine yapışık tek bir harften oluşmaktadır. Harflerde büyük harf - küçük harf duyarlılığı (case sensitivity) dikkate alınmaktadır. Örneğin: - ls -l -i /usr/include + $ ls -l -i /usr/include Burada -l ve -i argümansız seçeneklerdir. /usr/include argümanının bu seçeneklerle hiçbir ilgisi yoktur. Argümansız seçenekler tek bir karakterden oluşturulduğu için birleştirilebilmektedir. Örneğin: - ls -li + $ ls -li Buradaki -li aslında -l -i ile tamamen aynı anlamdadır. Genel olarak GNU stilinde seçenekler arasındaki sıranın bir önemi yoktur. Yani örneğin: - ls -l -i + $ ls -l -i ile - ls -i -l + $ ls -i -l arasında bir farklılık yoktur. Argümanlı seçeneklerde bir seçeneğin yanında o seçenekle ilişkili bir argüman da bulunur. Örneğin: - gcc -o sample sample.c + $ gcc -o sample sample.c Burada -o seçeneği seçeneği tek başına kullanılmaz. Hedef dosyanın ismi seçeneğin argümanını oluşturmaktadır. O halde buradaki -o seçeneği tipik olarak argümanlı seçeneğe bir örnektir. Argüman seçeneklerin birleştirilmesi tavsiye edilmez. Ancak birleştirme yapılabilmektedir. Örneğin: - gcc -co sample.o sample.c + $ gcc -co sample.o sample.c Bu yazım biçimini pek çok program kabul etse de biz tavsiye etmiyoruz. Buradaki argümanların aşağıdaki gibi belirtilmesi daha uygundur: - gcc -c -o sample.o sample.c + $ gcc -c -o sample.o sample.c Programlar, argümanlı seçeneklerde seçeneğin argümanı hiç boşluk karakterleriyle ayrılmasa bile bunu kabul edebilmektedir. Örneğin: - gcc -osample sample.c + $ gcc -osample sample.c Burada -o argümanlı seçenek olduğu için onu başka bir seçenek izleyemeyeceğinden dolayı "sample" -o seçeneğinin argümanı olarak ele alınmaktadır. Seçeneklerle ilgisi olmayan argümanlara "seçeneksiz argüman" denilmektedir. Örneğin: - gcc -o sample sample.c + $ gcc -o sample sample.c Burada "sample.c" argümanı herhangi bir seçenekle ilgili değildir. Örneğin: - cp x.txt y.txt + $ cp x.txt y.txt Buradaki "x.txt" ve "y.txt" argümanları da seçeneklerle ilgili değildir. Seçeneksiz argümanların sonda bulunması gerekmez. Örneğin: - gcc sample.c -o sample - + $ gcc sample.c -o sample ---------------------------------------------------------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------------------------------------------------------- @@ -3215,12 +3214,12 @@ char *get_ls(struct stat *finfo, const char *name) Bunlar gerçek anlamda birer dosya değildir. Adeta bir "pointer" dosyadır. İşletim sistemleri sembolik bağlantı dosyaları için diskte yalnızca bir i-node elemanı tutmaktadır. Sembolik bağlantı dosyaları komut satırında ln -s komutuyla yaratılabilirler. Örneğin: - ln -s x.dat y.dat + $ ln -s x.dat y.dat Burada "x.dat" dosyanının "y.dat" isimli bir sembolik bağlantı dosyası oluşturulmuştur. ls -l komutunda sembolik bağlantı dosyaları ok işaretiyle gösterilmektedir. Örneğin: - kaan@kaan-virtual-machine:~/Study/Unix-Linux-SysProg$ ls -l x.dat y.dat + $ ls -l x.dat y.dat -rwxr-xr-x 1 kaan study 0 Kas 27 13:07 x.dat lrwxrwxrwx 1 kaan study 5 Ara 10 10:11 y.dat -> x.dat @@ -3228,7 +3227,7 @@ char *get_ls(struct stat *finfo, const char *name) Sembolik bağlantı dosyaları "l" dosya türü ile gösterilmektedir. Bir sembolik bağlantı dosyası başka bir sembolik bağlantı dosyasını gösterebilir. Örneğin: - kaan@kaan-virtual-machine:~/Study/Unix-Linux-SysProg$ ls -l x.dat y.dat z.dat + $ ls -l x.dat y.dat z.dat -rwxr-xr-x 1 kaan study 0 Kas 27 13:07 x.dat lrwxrwxrwx 1 kaan study 5 Ara 10 10:11 y.dat -> x.dat lrwxrwxrwx 1 kaan study 5 Ara 10 10:48 z.dat -> y.dat @@ -3259,7 +3258,7 @@ char *get_ls(struct stat *finfo, const char *name) stat fonksiyonun bu bağlantının gösterdiği dosyanın bilgisini alması ancak lstat fonksiyonun sembolik bağlantı dosyasının kendi bilgisini almasıdır. Diğer dosyalar için bu fonksiyon arasında bir farklılık yoktur. Örneğin: - parallels@ubuntu-linux-20-04-desktop:~/Study/Unix-Linux-SysProg$ ls -l sample.c x + $ ls -l sample.c x -rw-rw-r-- 1 parallels parallels 1748 Dec 4 13:46 sample.c lrwxrwxrwx 1 parallels parallels 8 Dec 4 13:47 x -> sample.c @@ -3341,7 +3340,7 @@ char *get_ls(struct stat *finfo, const char *name) Bir dosyanın stat bilgilerini görüntülemek için stat isimli kabuk komutu da bulundurulmuştur. Tabii bu komut aslında stat ve lstat POSIX fonksiyonlarını çağırarak elde ettikleri bilgileri yazdırmaktadır. Örneğin: - kaan@kaan-virtual-machine:~/Study/Unix-Linux-SysProg$ stat sample.c + $ stat sample.c File: sample.c Size: 329 Blocks: 8 IO Block: 4096 normal dosya Device: 805h/2053d Inode: 1207667 Links: 2 @@ -3440,9 +3439,9 @@ int main(int argc, char *argv[]) Bir dosyanın hard link'ini oluşturmak için ln kabuk komutu kullanılmaktadır. Örneğin: - ln sample.c mample.c + $ ln sample.c mample.c - kaan@kaan-virtual-machine:~/Study/Unix-Linux-SysProg$ ls -li sample.c mample.c + $ ls -li sample.c mample.c 1207667 -rw-r--r-- 2 kaan study 329 Ara 10 10:59 mample.c 1207667 -rw-r--r-- 2 kaan study 329 Ara 10 10:59 sample.c @@ -3718,9 +3717,9 @@ void exit_sys(const char *msg) Dosyanın kullanıcı ve grup id'lerini değiştirebilmek için chown isimli bir kabuk komutu da bulundurulmuştur. Komut aşağıdaki biçimlerde kullanılmaktadır: - sudo chwon kaan:study test.txt - sudo chown kaan test.txt - sudo chown :study test.txt + $ sudo chwon kaan:study test.txt + $ sudo chown kaan test.txt + $ sudo chown :study test.txt ---------------------------------------------------------------------------------------------------------------------------*/ @@ -6529,7 +6528,7 @@ void exit_sys(const char *msg) if (dup2(2, 1) == -1) exit_sys("dup2"); - Aşağdaki örnekte bu işlem uygulanmıştır. Ancak burada bir fflush çağırması da yapılmıştır. Bunun nedeni izleyen konularda + Aşağıdaki örnekte bu işlem uygulanmıştır. Ancak burada bir fflush çağırması da yapılmıştır. Bunun nedeni izleyen konularda anlaşılabilecektir. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -6679,7 +6678,7 @@ void exit_sys(const char *msg) IO yönlendirmesi kabuk üzerinden de yapılabilmektedir. Kabukta ">" sembolü 1 numaralı betimleyicinin yönlendirileceği anlamına gelmektedir. Örneğin: - ./sample > test.txt + $ ./sample > test.txt Bu durumda kabuk önce ">" sembolünün sağındaki dosyayı O_WRONLY|O_TRUNC modunda açar. Sonra ./sample programını çalıştırarak bu prosesin 1 numaralı betimleyicisini dup2 fonksiyonu ile bu dosyaya yönlendirir. Böylece ./sample, sample programının @@ -6688,19 +6687,18 @@ void exit_sys(const char *msg) ls gibi, cat gibi kabuk komutlarının da aslında birer program olduğuna bunların da 1 numaralı betimleyiciyi kullanarak yazdırma yaptığına dikkat ediniz. Örneğimn biz kabuk üzerinde şu komutu uygulayabiliriz: - ls -l > test.txt + $ ls -l > test.txt Eğer kabukta ">" yerine ">>" sembolü kullanılırsa bu durumda ">>" sembolünün sağındaki dosya O_CREAT|O_WRONLY|O_APPEND modunda açılmaktadır. Yani dosya varsa bu durumda olan dosyanın sonuna ekleme yapılacaktır. Örneğin: - ls -l >> test.txt - + $ ls -l >> test.txt ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Kabuk üzerinde "<" sembolü de 0 numaralı betimleyiciyi yönlendirmektedir. Örneğin: - ./sample < test.txt + $ ./sample < test.txt Burada kabuk "test.txt" dosyasını O_RDONLY modda açar. Sonra ./sample programını çalıştırır. Prosesin 0 numaralı betimleyicisini "test.txt" dosyasına dup2 fonksiyonuyla yönlendirir. Böylece program sanki klavyeden okuduğunu sanırken aslında dosyadan @@ -6714,8 +6712,7 @@ void exit_sys(const char *msg) Programı kabuktan aşağıdaki gibi çalıştırıp sonucu inceleyiniz: - ./sample < test.txt - + $ ./sample < test.txt ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -7078,7 +7075,7 @@ int main(void) O halde aslında bir proses yaşamının önemli bir kısmını user mode'da geçrirken bir kısmını da kernel mode'da geçirebilmektedir. Örneğin "time" isimli kabuk komutuyla biz prosesin ne kadar zamanı kernel mode'da ne kadar zamanı user mode'da geçirdiğini görebiliriz: - kaan@kaan-virtual-machine:~/Study/Unix-Linux-SysProg$ time ./sample + $ time ./sample real 0m0,189s user 0m0,185s @@ -8733,21 +8730,21 @@ void exit_sys(const char *msg) } /*-------------------------------------------------------------------------------------------------------------------------- - waitpid fonksiyonu, wait fonksiyonunun daha gelişmiş bir biçimidir. Fonksiyonun prototipi şöyledir: + waitpid fonksiyonu wait fonksiyonunun daha gelişmiş bir biçimidir. Fonksiyonun prototipi şöyledir: #include pid_t waitpid(pid_t pid, int *status, int options); - Fonksiyonun birinci parametresi beklenecek alt prosesin proses id değerini belirtir. Bu sayede programcı belli bir alt prosesi bekleyebilmektedir. - Bu birinci parametre aslında birkaç biçimde geçilebilmektedir. Eğer bu parametre negatif bir proses id değeri olarak geçilirse - bu durumda fonksiyon proses grup id'si bu değerin pozitifi olan herhangi bir alt prosesi beklemektedir. Eğer bu parametre -1 - olarak geçilirse bu durumda fonksiyon tamamen wait fonksiyonundaki gibi davranmaktadır. Yani herhangi bir alt prosesi beklemektedir. - Eğer bu parametre 0 olarak geçilirse fonksiyon proses grup id'si waitpid fonksiyonunu çağıran prosesin id'si ile aynı olan - herhangi bir alt prosesi beklemektedir. Tabii normal olarak bu parametreye programcı pozitif olan bir proses id geçer. Bu durumda - fonksiyon o alt prosesi bekleyecektir. (Tabii bu parametreye geçilen proses id, o prosesin bir alt prosesi değilse fonksiyon yine - başarısız olmaktadır.) Fonksiyonun ikinci parametresi exit bilgisinin yerleştirileceği int türden nesnenin adresini alır. Üçüncü - parametre bazı özel değerlerin bit düzeyinde OR'lanmasıyla oluşturulabilmektedir: + Fonksiyonun birinci parametresi beklenecek alt prosesin proses id değerini belirtir. Bu sayede programcı belli bir alt prosesi + bekleyebilmektedir. Bu birinci parametre aslında birkaç biçimde geçilebilmektedir. Eğer bu parametre negatif bir proses id + değeri olarak geçilirse bu durumda fonksiyon proses grup id'si bu değerin pozitifi olan herhangi bir alt prosesi beklemektedir. + Eğer bu parametre -1 olarak geçilirse bu durumda fonksiyon tamamen wait fonksiyonundaki gibi davranmaktadır. Yani herhangi bir + alt prosesi beklemektedir.Eğer bu parametre 0 olarak geçilirse fonksiyon proses grup id'si waitpid fonksiyonunu çağıran prosesin + id'si ile aynı olan herhangi bir alt prosesi beklemektedir. Tabii normal olarak bu parametreye programcı pozitif olan bir proses + id geçer. Bu durumda fonksiyon o alt prosesi bekleyecektir. (Tabii bu parametreye geçilen proses id, o prosesin bir alt prosesi + değilse fonksiyon yine başarısız olmaktadır.) Fonksiyonun ikinci parametresi exit bilgisinin yerleştirileceği int türden nesnenin + adresini alır. Üçüncü parametre bazı özel değerlerin bit düzeyinde OR'lanmasıyla oluşturulabilmektedir: WNOHANG: Bu durumda waitpid eğer alt proses henüz sonlanmamışsa bekleme yapmaz, başarısızlıkla sonuçlanır. WUNTRACED, WCONTINUED: Prosesin durdurulması ve devam ettirilmesi ile ilgili bilginin elde edilmesinde kullanılmaktadır. @@ -8868,12 +8865,11 @@ void exit_sys(const char *msg) uygulamadan yoluna devam etmesini sağlamaktır. Zombie prosesler "ps -l" komutunda "defunct" olarak gösterilmektedir. Bunların "proses durumları da (process state)" "Z" harfi ile belirtilmektedir. Örneğin: - kaan@kaan-virtual-machine:~/Study/Unix-Linux-SysProg$ ps -la + $ ps -la F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 10612 1868 0 80 0 - 622 hrtime pts/1 00:00:00 sample 1 Z 1000 10613 10612 0 80 0 - 0 - pts/1 00:00:00 sample 4 R 1000 10621 1618 0 80 0 - 3540 - pts/0 00:00:00 ps - ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -9796,7 +9792,7 @@ void exit_sys(const char *msg) Örneğin biz "sample" isimli bir program yazalım. Bu program da komut satırı argümanlarıyla aldığı programı çalıştırsın. Yani "sample" programı şöyle çalıştırılsın: - ./sample /bin/ls -l + $ ./sample /bin/ls -l Eğer böyle bir programı execl ile yazamaya çalışırsak bunu pratik bir biçimde başaramayız. Çünkü çalıştıracağımız programın kaç komut satırı argümanı ile çalıştırılacağını baştan bilmemekteyiz. Aşağıda böyle bir programa örnek verilmiştir. Programı @@ -9805,7 +9801,6 @@ void exit_sys(const char *msg) $ ./sample /bin/ls -l $ ./sample /bin/cp sample.c x.c $ ./sample mample ali veli selami - ---------------------------------------------------------------------------------------------------------------------------*/ /* sample.c */ @@ -10583,7 +10578,7 @@ void exit_sys(const char *msg) açmış olduğu dosyanın betimleyici numarasını bilmediği için "sample" programı komut satırı argümanıyla bu bilgiyi "mample" programına iletmiştir. - Aşağıdaki programı daha sonra dosyanın close onexec bayrağı set set ederek yeniden deneyiniz: + Aşağıdaki programı daha sonra dosyanın close-on-exec bayrağı set set ederek yeniden deneyiniz: if ((fd = open("sample.c", O_RDONLY|O_CLOEXEC)) == -1) exit_sys("open"); @@ -10967,7 +10962,7 @@ for i in range(10): kullanılması gerekir. O halde make için shebang oluşturulurken -f seçeneğinin shebang satırında isteğe bağlı argüman olarak belirtilmesi gerekir. Örneğin: - #! /bin/make -f + #! /bin/make -f sample: sample.o gcc -o sample sample.o @@ -11047,7 +11042,7 @@ for i in range(10): Pek çok sistemde kabuk programları "interaktif olmayan (noninteractive)" bir modda çalıştırılabilmektedir. Örneğin UNIX/Linux kabuk programları "-c" seçeneği ile çalıştırılırsa yalnızca bir komutu çalıştırıp sonlanmaktadır. Örneğin: - bash -c "ls -l; cat sample.c" + $ bash -c "ls -l; cat sample.c" Benzer biçimde Windows sistemlerinde de "cmd.exe" kabuk programı /C seçeneği ile benzer biçimde çalıştırılabilmektedir. @@ -11058,7 +11053,7 @@ for i in range(10): fonksiyonuyla elde edilen değerine (status) geri dönmektedir. Tabii kabuk programları da interaktif olmayan modda çalıştırılan komutun waitpid fonksiyonu ile elde edilen (status) değere geri dönerler. Örneğin: - system("ls -l"); + $ system("ls -l"); Burada system fork/exec ile "/bin/sh" programını -c seçeneği ile çalıştırmaktadır. Tabii kabuk programı da "ls" programını fork/exec ile çalıştıracaktır. (Bazen kabuk programları interaktif modda eğer tek bir komut işletiliyorsa boşuna fork yapmayabilir). @@ -11078,7 +11073,7 @@ for i in range(10): } Biz kabuk programını system fonksiyonu ile çalıştırırken komutlar arasına ";" koyarak birden fazla komutun çalıştırılmasını - sağlayabiliriz. Genel olarak kabuk bu durumda son komutun exit kodunu bize vermektedir. + sağlayabiliriz. Genel olarak kabuk bu durumda son komutun exit kodunu bize vermektedir. Aslında programcılar genellikle system fonksiyonu için yalnızca -1 kontrolünü yapmaktadır. Yani çalıştırdıkları komutun başarısını kontrol etmemektedir. Örneğin: @@ -11242,10 +11237,11 @@ int main(void) Dosyanın set-grup-id bayrağının set edilmesi de chmod komutunda g+s ile yapılmaktadır. İşlem sonrasında yine grup bilgisinde "x" hakkı yerinde "s" ya da "S" görünür. chmod komutunda +s kullanılırsa bu durumda dosyanın hem set-user-id hem de set-group-id - bayrakları set edilir. Dosyanın sticky bayrağını set etmek için chmod komutunda +t kullanılmaktadır. Bu işlem yapıldığında görüntü - olarak grup hakkında "x" varsa x hakkının olduğu yerde "t", yoksa orada "T" görülmektedir. + bayrakları set edilir. Dosyanın sticky bayrağını set etmek için chmod komutunda +t kullanılmaktadır. Bu işlem yapıldığında + görüntü olarak grup hakkında "x" varsa x hakkının olduğu yerde "t", yoksa orada "T" görülmektedir. - Benzer biçinde istersek yine chmod komutunda octal digit'lerle set-user-d, set-group-id ve sticky bitlerini set edebiliriz. Örneğin: + Benzer biçinde istersek yine chmod komutunda octal digit'lerle set-user-d, set-group-id ve sticky bitlerini set edebiliriz. + Örneğin: $ chmod 4755 sample @@ -11269,7 +11265,7 @@ int main(void) if (stat("sample", &finfo) == -1) exit_sys("stat"); - if (chmod("sample", (finfo.st_mode & ~S_IFMT) | S_ISUID) == -1) /* ~S_IMT maskelemesi yapılmasa da genel olarak bir sorun oluşmaz */ + if (chmod("sample", (finfo.st_mode & ~S_IFMT) | S_ISUID) == -1) /* ~S_IMT maskelemesi yapılmasa da genel olarak bir sorun oluşmaz */ exit_sys("chmod"); ---------------------------------------------------------------------------------------------------------------------------*/ @@ -11321,35 +11317,39 @@ void exit_sys(const char *msg) Bu mample programı prosesin gerçek ve etkin kullanıcı ve grup id'lerini ekrana isim olarak yazdırmaktadır. Bu deneyi önce mample programının set-user-id bayrağı set edilmeden ve set edildikten sonra yineleyiniz. Aşağıda yapılanlar özetlenmiştir: - gcc -o sample sample.c - gcc -o mample mample.c - ls -l mample + $ gcc -o sample sample.c + $ gcc -o mample mample.c + $ ls -l mample -rwxr-xr-x 1 kaan study 17088 Şub 19 13:42 mample - sudo chown root:root mample - ls -l mample + $ sudo chown root:root mample + $ ls -l mample -rwxr-xr-x 1 root root 17088 Şub 19 13:42 mample - ./sample + $ ./sample Real user id: kaan Effective user id: kaan Real group id: study Effective group id: study - sudo chmod u+s mample - ls -l mample + $ sudo chmod u+s mample + $ ls -l mample -rwsr-xr-x 1 root root 17088 Şub 19 13:42 mample - ./sample + $ ./sample Real user id: kaan Effective user id: root Real group id: study Effective group id: study - sudo chmod g+s mample - ls -l mample + $ sudo chmod g+s mample + $ ls -l mample -rwsr-sr-x 1 root root 17088 Şub 19 13:42 mample - ./sample + $ ./sample Real user id: kaan Effective user id: root Real group id: study Effective group id: root + Biz bir programı sudo ile root önceliğinde (etkin proses id'si 0 olacak biçimde) çalıştırıyor olalım. Dosyanın set-user-id + bayrağı da set edilmiş olsun. Bu durumda program çalışırken prosesin etkin kullanıcı id'si root değil program dosyasının + kullanıcı id'si olacaktır. Yani set-user-id bayrağı set edilmiş olan programların sudo ile root önceliğinde çalıştırılmasının + bir anlamı kalmamaktadır. ---------------------------------------------------------------------------------------------------------------------------*/ /* sample.c */ @@ -12145,18 +12145,18 @@ void exit_sys(const char *msg) ya da Linux sistemlerinde CAP_SETGID yeterliliğine sahip olması gerekir.) Aşağıdaki program set-user-id bayrağı set edilmiş sahibi root olan bir program dosyasına dönüştürülmüştür. Program aşağıdaki - gibi derlenmiştir: + gibi derlenmiştir: - gcc -o sample sample.c + $ gcc -o sample sample.c Sonra program dosyasının sahibi root olarak değiştirilmiştir. Ondan sonra da program dosyasının set-user-id bayrağı set edilmiştir: - sudo chown root sample - ls -l sample + $ sudo chown root sample + $ ls -l sample -rwxr-xr-x 1 root study 17400 Şub 26 12:18 sample - sudo chmod u+s sample - ls -l sample + $ sudo chmod u+s sample + $ ls -l sample -rwsr-xr-x 1 root study 17400 Şub 26 12:18 sample Burada önce dosyanın modunun değiştirildiğine daha sonra set-user-id bayrağının set edildiğine dikkat ediniz. Çünkü @@ -12339,16 +12339,16 @@ void exit_sys(const char *msg) bir şey yoktur. Bu durum tıpkı yemek verilen bir kurumda yemeğin birden fazla koldan fazla verilmesi gibidir. İşletim sisteminin zaman paylaşımlı çalışma için oluşturduğu kuyruğa işletim sistemleri dünyasında "çalıştırma kuyruğu (run queue)" denilmektedir. Bu çalıştırma kuyruğu çok CPU söz konusu olduğunda her CPU için oluşturulmaktadır. Böylece her CPU yine zaman - paylaşımlı bir biçimde çalıştırma kuyruğundaki thread'leri çalıştırmaktadır. Yani yukarıda açıkladığımız temel prensip değişmemektedir. - Tabii burada işletim sisteminin bazı kararları da vermesi gerekir. Örneğin yeni bir thread (ya da proses) yaratıldığında bunun - hangi CPU'ya atanacağı gibi. Bazen işletim sistemi thread'i bir CPU'nun çalıştırma kuyruğuna atar. Ancak diğer kuyruklar - daha boş hale gelirse (çünkü o sırada çeşitli prosesler ve thread'ler sonlanmış olabilir) işletim sistemi başka bir CPU'nun - çalıştırma kuyruğundaki thread'i kuyruğu daha boş olan CPU'nun çalıştırma kuyruğuna atayabilir. (Biz bir süpermarkette işin - başında boş bir kasanın kuyruğuna girmiş olabiliriz. Sonra başka bir kasadaki kuyruk çok azalmış duruma gelebilir. Biz de o - kuyruğua geçmeyi tercih ederiz. İşletim sistemi de buna benzer davranmaktadır.) Linux işletim sistemi, Windows sistemleri ve - macOS sistemleri buna benzer bir çizelgeleme algoritması kullanmaktadır. Bir ara Linux O(1) çizelgelemesi denilen bir yöntem - denemiştir. Bu yöntemde işletim sistemi tek bir çalıştırma kuyruğu kullanıyordu. Hangi CPU'daki parçalı çalışma süresi biterse - bu kuyruktan seçme yapılıyordu. + paylaşımlı bir biçimde çalıştırma kuyruğundaki thread'leri çalıştırmaktadır. Yani yukarıda açıkladığımız temel prensip + değişmemektedir. Tabii burada işletim sisteminin bazı kararları da vermesi gerekir. Örneğin yeni bir thread (ya da proses) + yaratıldığında bunun hangi CPU'ya atanacağı gibi. Bazen işletim sistemi thread'i bir CPU'nun çalıştırma kuyruğuna atar. Ancak + diğer kuyruklar daha boş hale gelirse (çünkü o sırada çeşitli prosesler ve thread'ler sonlanmış olabilir) işletim sistemi + başka bir CPU'nun çalıştırma kuyruğundaki thread'i kuyruğu daha boş olan CPU'nun çalıştırma kuyruğuna atayabilir. (Biz bir + süper markette işin başında boş bir kasanın kuyruğuna girmiş olabiliriz. Sonra başka bir kasadaki kuyruk çok azalmış duruma + gelebilir. Biz de o kuyruğua geçmeyi tercih ederiz. İşletim sistemi de buna benzer davranmaktadır.) Linux işletim sistemi, + Windows sistemleri ve macOS sistemleri buna benzer bir çizelgeleme algoritması kullanmaktadır. Bir ara Linux O(1) çizelgelemesi + denilen bir yöntem denemiştir. Bu yöntemde işletim sistemi tek bir çalıştırma kuyruğu kullanıyordu. Hangi CPU'daki parçalı + çalışma süresi biterse bu kuyruktan seçme yapılıyordu. Çok CPU'lu zaman paylaşımlı çalışmada CPU sayısı artırıldıkça total performans artacaktır. Çünkü CPU'lar için düzenlenen çalıştırma kuyruklarında daha az thread bulunacaktır. @@ -14725,13 +14725,13 @@ void exit_sys(const char *msg) /*-------------------------------------------------------------------------------------------------------------------------- mypipe bir isimli boru dosyası olsun. Aşağıdaki gibi bir işlem yapsak ne olur? - ls > mypipe + $ ls > mypipe Bu durumda kabuk programı IO yönlendirmesi yapmak için "mypipe" dosyasını "write" modda open fonksiyonu ile açmak isteyecektir. Ancak "mypipe" isimli boru dosyası olduğu için açım sırasında bloke oluşacaktır. Pekiyi bu blokeden nasıl kurtulunabilir? Tabii başka bir terminalden biz boruyu bu kez "read" modda açarak. Örneğin: - cat < mypipe + $ cat < mypipe Burada yine kabuk programı "mypipe" dosyasını yönlendirme için "read" modda açacaktır. Bu durumda diğer kabuk prosesi blokeden kurtulacaktır. @@ -15189,7 +15189,7 @@ void exit_sys(const char *msg) default olarak tüm IPC nesnelerini görüntülemektedir. Ancak -q komut satırı argümanıyla yalnızca mesaj kuyrukları, -m komutuyla yalnızca paylaşılan bellek alanları ve -s komutuyla da yalnızca semaphore nesneleri görüntülenebilir. Örneğin: - kaan@kaan-virtual-machine:~$ ipcs -q + $ ipcs -q ----- İleti Kuyrukları ----- anahtar iltkiml sahibi izinler kull-bayt ileti-sayısı @@ -16414,8 +16414,7 @@ void exit_sys(const char *msg) içerisinde değil librt kütüphanesinin içerisinde bulunmaktadır. Bu nedenle POSIX IPC nesnelerini kullanan programları derlerken -lrt seçeneğinin kullanılması gerekir. Örneğin: - gcc -o sample sample.c -lrt - + $ gcc -o sample sample.c -lrt ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -16874,11 +16873,11 @@ void exit_sys(const char *msg) Linux dağıtımları genellikle açılış sırasında bu dosya sistemini otomatik mount etmektedir. Ancak sistem yöneticisi isterse /dev/mqueue dizinini unmount edebilir. Örneğin: - sudo unmount /dev/mqueue + $ sudo unmount /dev/mqueue Ya da sistem yöneticisi isterse bu dosya sistemini başka bir yere de mount komutuyla mount edebilir. Örneğin: - sudo mount -t mqueue somename posix-mqueue + $ sudo mount -t mqueue somename posix-mqueue Burada "somename" mount noktalarını görüntülerken kullanılacak bir isimdir. Mesaj kuyrukları bu biçimde mount edilirken dizinin "sticky" biti set edilmektedir. Böylece bu dizinin birisi sahibi olsa da ancak oradaki dosyaları silebilmek için @@ -16913,7 +16912,7 @@ void exit_sys(const char *msg) Uygun önceliğe sahip prosesler yukarıdaki limitlerden etkilenmezler. Ayrıca, buradaki değerler proc dosya sisteminden bu dosyalara yazma yapılarak değiştirilebilmektedir. Örneğin: - sudo sh -c "echo 20 > /proc/sys/fs/mqueue/msg_max" + $ sudo sh -c "echo 20 > /proc/sys/fs/mqueue/msg_max" Buradaki değerlerin ayrı bir tavan limiti de vardır. Örneğin msg_max değerinin tavan limiti 3.5 ve sonrasındaki çekirdeklerde 65536'dır. Yine örneğin msgsize_max değerinin de tavan limiti 3.5 ve sonrasındaki çekirdeklerde 16,777,216 b @@ -17098,7 +17097,7 @@ void exit_sys(const char *msg) Yine sistemdeki o anda yaratılmış olan paylaşılan bellek alanları "ipcs" komutuyla ya da "ipcs -m" komutuyla görüntülenebilir. Örneğin: - kaan@kaan-virtual-machine:~/Study/UnixLinux-SysProg$ ipcs -m + $ ipcs -m ----- Paylaşımlı Bellek Bölütleri ----- anahtar shmid sahibi izinler bayt ekSayısı durum @@ -17116,7 +17115,7 @@ void exit_sys(const char *msg) Aslında daha önceden de belirttiğimiz gibi "ipcs" komutu bu bilgileri "/proc/sysvipc" dizinindeki üç dosyadan elde etmektedir. Klasik Sistem 5 paylaşılan bellek alanları için "/proc/sysvipc/shm" dosyası kullanılmaktadır. Örneğin: - kaan@kaan-virtual-machine:~/Study/UnixLinux-SysProg$ cat /proc/sysvipc/shm + $ cat /proc/sysvipc/shm bu biçimde benzer bilgileri elde edebiliriz. @@ -17277,7 +17276,7 @@ void exit_sys(const char *msg) Paylaşılan bellek alanları komut satırında "ipcrm" komutuyla da silinebilmektedir. Komutta -M seçeneği anahtar ile silmek için -m seçeneği id ile silmek için kullanılmaktadır. Örneğin: - ipcrm -m 131135 + $ ipcrm -m 131135 Burada 131135 id'sine sahip paylaşılan bellek alanı silinmektedir. Tabii bize ait olmayan paylaşılan bellek alanlarını silmek için sudo uygulamak gerekir. @@ -18678,11 +18677,11 @@ void exit_sys(const char *msg) Proses kavramı çalışmakta olan programın tüm bilgilerini belirtmektedir. Thread ise yalnızca bir akış belirtmektedir. Örneğin: - - Thread'lerin gerçek ve etkin kullanıcı id'leri ve grup id'leri yoktur. Proseslerin vardır. Yani bir prosesin tüm thread'leri aynı - kullancı ve grup id'sine sahiptir. + - Thread'lerin gerçek ve etkin kullanıcı id'leri ve grup id'leri yoktur. Proseslerin vardır. Yani bir prosesin tüm thread'leri + aynı kullancı ve grup id'sine sahiptir. - - Thread'lerin çalışma dizinleri, çevre değişkenleri yoktur. Proseslerin vardır. Örneğin biz bir proseste chdir POSIX fonksiyonu - ile çalışma dizinini (current working directory) değiştirdiğimizde tüm thread'ler bunu değiştirmiş oluruz. + - Thread'lerin çalışma dizinleri, çevre değişkenleri yoktur. Proseslerin vardır. Örneğin biz bir proseste chdir POSIX + fonksiyonu ile çalışma dizinini (current working directory) değiştirdiğimizde tüm thread'ler bunu değiştirmiş oluruz. - Dosya betimleyici tablosu prosese özgüdür. Dolayısıyla tüm thread'ler aynı dosya betimleyici tablosunu kullanmaktadır. @@ -18731,10 +18730,10 @@ void exit_sys(const char *msg) Biz burada benzer işlemi fork ile yapmaya çalıştık. Ancak fork yaptığımızda biz maliyetli bir biçimde yeni bir proses yarattık. Bu prosesin bilgileri üst prosesten alınmaktadır. Ayrıca yarattığımız alt proses ayrı bir bellek alanına sahiptir. Dolayısıyla - örneğin aynı global değişkenleri kendi aralarında paylaşmamaktadır. Örneğin biz alt proseste dosya açsak üst proses bunu görmeyecektir. - Halbuki thread'ler aynı prosesin akışlarıdır. Prosesin bir thread'i bir dosya açsa diğer thread'i onu açık olarak görmektedir. - Proseslerle thread'ler akış bakımından benzemektedir. Her iki durumda da farklı akış oluşturulmaktadır. Thread'lere ilk - zamanlar bu benzerlikten hareketle "lightweight (hafif siklet) proses" de denilmektedir. + örneğin aynı global değişkenleri kendi aralarında paylaşmamaktadır. Örneğin biz alt proseste dosya açsak üst proses bunu + görmeyecektir. Halbuki thread'ler aynı prosesin akışlarıdır. Prosesin bir thread'i bir dosya açsa diğer thread'i onu açık + olarak görmektedir. Proseslerle thread'ler akış bakımından benzemektedir. Her iki durumda da farklı akış oluşturulmaktadır. + Thread'lere ilk zamanlar bu benzerlikten hareketle "lightweight (hafif siklet) proses" de denilmektedir. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -18775,11 +18774,11 @@ void exit_sys(const char *msg) ... } - Burada iki thread foo üzerinde ilerlerken aslında yerel değişken olan a'nın kendi kopyaları üzerinde işlem yapmaktadır. Yani - bir thread buradaki yerel "a" değişkenini değiştirdiğinde diğer thread bu değişikliği görmez. Başka bir deyişle her thread'in - kendine özgü farklı bir "a" değişkeni vardır. Bu durumun teknik açıklaması şöyledir: Yerel değişkenler stack'te yaratılmaktadır. - Thread'lerin de stack'leri birbirinden ayrıldığı için bu "a" değişkeni hangi thread akışı o fonksiyonu çağırmışsa onun - stack'inde yaratılmaktadır. + Burada iki thread foo üzerinde ilerlerken aslında yerel değişken olan a'nın kendi kopyaları üzerinde işlem yapmaktadır. + Yani bir thread buradaki yerel "a" değişkenini değiştirdiğinde diğer thread bu değişikliği görmez. Başka bir deyişle her + thread'in kendine özgü farklı bir "a" değişkeni vardır. Bu durumun teknik açıklaması şöyledir: Yerel değişkenler stack'te + yaratılmaktadır. Thread'lerin de stack'leri birbirinden ayrıldığı için bu "a" değişkeni hangi thread akışı o fonksiyonu + çağırmışsa onun stack'inde yaratılmaktadır. Ancak prosesin bütün thread'leri aynı ".data" ve ".bss" ve "heap" alanını kullanmaktadır. Bu nedenle thread'lerden biri yukarıdaki örnekte "++g_x" global değişkenini artırdığında diğeri onu artırılmış olarak görecektir. Anımsanacağı gibi @@ -18844,8 +18843,11 @@ void exit_sys(const char *msg) - Thread kullanan programları derlerken link aşaması için "-lpthread" komut satırı argümanını kullanmayı unutmayınız. Örneğin: - gcc -o sample sample.c -lpthread + $ gcc -o sample sample.c -lpthread + gcc ve clang derleyicilerinin ileri versiyonlarında başlık dosyalarındaki pragma direktifleri ile belli bir başlık dosyası + include edildiğinde ilgili kütüphanenin link aşamasına otomatik biçimde sokulması sağlanabilmektedir. Ancak siz her zaman + thread'li programları derlerken "-lpthread" seçeneği ile bağlayıcının bu kütüphaneye bakmasını sağlamalısınız. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -21047,8 +21049,8 @@ void exit_sys_errno(const char *msg, int eno) ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- - Bir thread, bir mutex nesnesinin sahipliğini pthread_mutex_lock fonksiyonu ile aldıktan sonra yeniden aynı fonksiyonu çağırarak - aynı mutex nesnesinin sahipliğini almaya çalışırsa ne olur? Muhtemel üç durum söz konusudur: + Bir thread, bir mutex nesnesinin sahipliğini pthread_mutex_lock fonksiyonu ile aldıktan sonra yeniden aynı fonksiyonu + çağırarak aynı mutex nesnesinin sahipliğini almaya çalışırsa ne olur? Muhtemel üç durum söz konusudur: 1) Thread kendi kendini kilitler ve "deadlock" oluşur. @@ -21213,9 +21215,9 @@ void exit_sys_errno(const char *msg, int eno) } /*-------------------------------------------------------------------------------------------------------------------------- - Şimdi de yukarıdaki örneği mutex türünü PTHREAD_MUTEX_RECURSIVE yaparak yeniden düzenleyelim. Burada artık mutex nesnesi "recursive" - duruma sokulduğu için aynı thread mutex nesnesinin sahipliğini almaya çalıştığında bloke oluşmayacaktır. Ekrana çıkan yazıları dikkatlice - inceleyiniz. + Şimdi de yukarıdaki örneği mutex türünü PTHREAD_MUTEX_RECURSIVE yaparak yeniden düzenleyelim. Burada artık mutex nesnesi + "recursive" duruma sokulduğu için aynı thread mutex nesnesinin sahipliğini almaya çalıştığında bloke oluşmayacaktır. Ekrana + çıkan yazıları dikkatlice inceleyiniz. ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -21647,17 +21649,18 @@ namespace CSD sistemlerinde isimleri olsaydı bu kullanım kolaylıkla sağlanabilirdi. Ancak UNIX/Linux sistemlerinde mutex nesnelerinin isimleri yoktur. O halde tek yol mutex nesnesini paylaşılan bir bellek alanında yaratmaktır. Ancak POSIX standartlarına göre mutex nesnelerinin paylaşılan bellek alanlarında yaratılması proseslerarası kullanım için yeterli değildir. Aynı zamanda mutex - nesnesinin proseslerarası paylaşılabilirliğini set etmek gerekir. Bu da mutex özellikleriyle yapılmaktadır. pthread_mutexattr_setpshared - fonksiyonu ile mutex paylaşımlı moda sokulabilmektedir: + nesnesinin proseslerarası paylaşılabilirliğini set etmek gerekir. Bu da mutex özellikleriyle yapılmaktadır. + pthread_mutexattr_setpshared fonksiyonu ile mutex paylaşımlı moda sokulabilmektedir: #include int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); - Fonksiyonun birinci parametresi mutex özellik nesnesinin adresini almaktadır. İkinci parametresi ise nesnenin proseslerarası paylaşımını - belirtmektedir. Bu parametre PTHREAD_PROCESS_SHARED geçilirse nesne proseslerarasında paylaşılmakta, PTHREAD_PROCESS_PRIVATE geçilirse - nesne proseslerarasında paylaşılmamaktadır. Fonksiyon başarı durumunda 0 değerine, başarısızlık durumunda errno değerine geri - dönmektedir. Mutex özellik nesnesinin paylaşılabilirlik özelliği pthread_mutexattr_getpshared fonksiyonu ile elde edilebilir: + Fonksiyonun birinci parametresi mutex özellik nesnesinin adresini almaktadır. İkinci parametresi ise nesnenin proseslerarası + paylaşımını belirtmektedir. Bu parametre PTHREAD_PROCESS_SHARED geçilirse nesne proseslerarasında paylaşılmakta, + PTHREAD_PROCESS_PRIVATE geçilirse nesne proseslerarasında paylaşılmamaktadır. Fonksiyon başarı durumunda 0 değerine, başarısızlık + durumunda errno değerine geri dönmektedir. Mutex özellik nesnesinin paylaşılabilirlik özelliği pthread_mutexattr_getpshared + fonksiyonu ile elde edilebilir: #include @@ -21701,9 +21704,8 @@ namespace CSD Programları aşağıdaki gibi derleyip farklı terminallerden çalıştırabilirsiniz: - gcc -o prog1 prog1.c -lrt -lpthread - gcc -o prog2 prog2.c -lrt -lpthread - + $ gcc -o prog1 prog1.c -lrt -lpthread + $ gcc -o prog2 prog2.c -lrt -lpthread ---------------------------------------------------------------------------------------------------------------------------*/ /* sharing.h */ @@ -21876,48 +21878,48 @@ void exit_sys_errno(const char *msg, int eno) } /*-------------------------------------------------------------------------------------------------------------------------- - Mutex nesneleri, UNIX/Linux sistemlerinde nispeten hızlı senkronizasyon nesneleridir. Linux sistemlerinde senkronizasyon nesnelerinin - hepsi zaten "futex (fast user space mutex)" denilen sistem fonksiyonu çağrılarak gerçekleştirilmektedir. mutex fonksiyonları - duruma göre hiç kernel moda geçmeden işlemini user mode'da yapabilmektedir. Şöyle ki: Biz mutex nesnesini pthread_mutex_lock - fonksiyonu ile kilitlemek isteyelim. Mutex nesnesi kendi içerisinde bayrak değişkenleri tutarak kilitleme işlemini atomik bir biçimde - yapmak isteyecektir. Atomikliği sağlamanın bir yolu "kernel moda geçerek CLI gibi makine komutuyla kesme mekanizmasını kapatıp bayrak - değişkenlerini set etmek" olabilir. Ancak kernel moda geçmenin maliyeti yüksektir. İşte daha önceden de belirttiğimiz gibi yeni - modern işlemcilere "compare and set" gibi atomik makine komutları eklenmiştir. Bu makine komutları sayesinde bu tür flag değişkenleri - hiç kernel moda geçmeden user mode'da atomik bir biçimde set edilebilmektedir. + Mutex nesneleri, UNIX/Linux sistemlerinde nispeten hızlı senkronizasyon nesneleridir. Linux sistemlerinde senkronizasyon + nesnelerinin hepsi zaten "futex (fast user space mutex)" denilen sistem fonksiyonu çağrılarak gerçekleştirilmektedir. + mutex fonksiyonları duruma göre hiç kernel moda geçmeden işlemini user mode'da yapabilmektedir. Şöyle ki: Biz mutex nesnesini + pthread_mutex_lock fonksiyonu ile kilitlemek isteyelim. Mutex nesnesi kendi içerisinde bayrak değişkenleri tutarak kilitleme + işlemini atomik bir biçimde yapmak isteyecektir. Atomikliği sağlamanın bir yolu "kernel moda geçerek CLI gibi makine komutuyla + kesme mekanizmasını kapatıp bayrak değişkenlerini set etmek" olabilir. Ancak kernel moda geçmenin maliyeti yüksektir. İşte + daha önceden de belirttiğimiz gibi yeni modern işlemcilere "compare and set" gibi atomik makine komutları eklenmiştir. Bu + makine komutları sayesinde bu tür flag değişkenleri hiç kernel moda geçmeden user mode'da atomik bir biçimde set edilebilmektedir. Ancak pthread_mutex_lock gibi bir fonksiyonun hiç kernel moda geçmeden işlem yapma olanağı da yoktur. Eğer mutex nesnesi kilitli değilse onun kilitlenmesi user mode'da "compare and set" komutlarıyla yapılabilmektedir. Ancak ya mutex nesnesi zaten kilitliyse? İşte bu durumda mecburen fonksiyon kernel moda geçerek thread'i bloke edecektir. Tabii aslında mutex kilitliyken bu fonksiyonlar hemen kernel moda geçip bloke oluşturmazlar. Çünkü pek çok senkronizasyon nesnesi çok kısa bir süre için kilitlenip açılmaktadır. - Bu nedenle pthread_mutex_lock, mutex'in kilitli olduğunu gördüğünde birden fazla işlemci ya da çekirdek varsa başka bir işlemci ya da - çekirdekteki mutex'i kilitleyen thread'in kısa süre içerisinde kilidi bırakacağını umarak meşgul bir döngüde sürekli bayrak değişkenine bakıp - bir süre beklemektedir. Bu tür meşgul döngülere senkronizasyon dünyasında "spin lock" denilmektedir. + Bu nedenle pthread_mutex_lock, mutex'in kilitli olduğunu gördüğünde birden fazla işlemci ya da çekirdek varsa başka bir işlemci + ya da çekirdekteki mutex'i kilitleyen thread'in kısa süre içerisinde kilidi bırakacağını umarak meşgul bir döngüde sürekli + bayrak değişkenine bakıp bir süre beklemektedir. Bu tür meşgul döngülere senkronizasyon dünyasında "spin lock" denilmektedir. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Yaygın seknronizasyon nesnelerinden bir diğeri de "koşul değişkenleri (condition variables)" denilen nesnelerdir. - Bu nesneler UNIX/Linux sistemlerinde uzunca bir süredir bulunmaktadır. Windows sistemlerine de belli bir süreden sonra sokulmuştur. - Koşul değişken nesneleri tek başlarına kullanılmaz mutex nesneleriyle beraber kullanılmaktadır. Koşul değişkenlerinin temel kullanım amacı - "belli bir koşul sağlanana kadar" thread'in blokede bekletilmesidir. Bu koşul programcı tarafından oluşturulur. Örneğin - programcı global bir g_count değişkeni 0'dan büyük olana kadar thread'i bloke edip bekletmek isteyebilir. Ya da örneğin programcı - bir g_flag değişkeni 1 olana kadar thread'ini blokede bekletmek isteyebilir. + Bu nesneler UNIX/Linux sistemlerinde uzunca bir süredir bulunmaktadır. Windows sistemlerine de belli bir süreden sonra + sokulmuştur. Koşul değişken nesneleri tek başlarına kullanılmaz mutex nesneleriyle beraber kullanılmaktadır. Koşul değişkenlerinin + temel kullanım amacı "belli bir koşul sağlanana kadar" thread'in blokede bekletilmesidir. Bu koşul programcı tarafından + oluşturulur. Örneğin programcı global bir g_count değişkeni 0'dan büyük olana kadar thread'i bloke edip bekletmek isteyebilir. + Ya da örneğin programcı bir g_flag değişkeni 1 olana kadar thread'ini blokede bekletmek isteyebilir. - Maalesef koşul değişkenleri anlaşılması en zor senkronizasyon nesnelerinden biridir. Bu nesnelerin düzgün kullanılabilmesi için belli bir - yöntemin izlenmesi gerekmektedir. Programcılar koşul değişkenlerinin kullanımına ilişkin kalıpları anlamakta zorluk çekebilmektedir. - Aynı zamanda programcıların kafası bu senkronizasyon nesnelerinin neden gerektiği konusunda da kafası karışabilmektedir. Biz önce - koşul değişkenlerinin tipik olarak nasıl kullanılması gerektiği üzerinde duracağız. Sonra konunun ayrıntılarına gireceğiz. + Maalesef koşul değişkenleri anlaşılması en zor senkronizasyon nesnelerinden biridir. Bu nesnelerin düzgün kullanılabilmesi + için belli bir yöntemin izlenmesi gerekmektedir. Programcılar koşul değişkenlerinin kullanımına ilişkin kalıpları anlamakta + zorluk çekebilmektedir. Aynı zamanda programcıların kafası bu senkronizasyon nesnelerinin neden gerektiği konusunda da kafası + karışabilmektedir. Biz önce koşul değişkenlerinin tipik olarak nasıl kullanılması gerektiği üzerinde duracağız. Sonra konunun + ayrıntılarına gireceğiz. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Koşul değişkenleri tipik olarak şu adımlardan geçilerek kullanılmaktadır: - 1) Koşul değişkenleri pthread_cond_t türü ile temsil edilmiştir. Bu tür ve içerisinde typedef - edilmiştir. pthread_cont_t türü, sistemlerde tipik olarak bir yapı biçiminde typedef edilmektedir. Örneğin: + 1) Programcı önce global düzeyde pthread_cond_t türünden bir koşul değişken nesnesi tanımlar. Koşul değişkenleri pthread_cond_t + türü ile temsil edilmiştir. Bu tür ve içerisinde typedef edilmiştir. pthread_cond_t türü sistemlerde + tipik olarak bir yapı biçiminde typedef edilmektedir. Örneğin: pthread_cond_t g_cond; - Programcı yine koşul değişkenlerini farklı thread'ler erişebilsin diye tipik olarak global bir değişken biçiminde tanımlar. - 2) Koşul değişkenlerine tıpkı mutex nesnelerinde olduğu gibi statik ya da dinamik olarak ilk değer verilebilmektedir. Biz koşul değişkenlerine statik düzeyde PTHREAD_COND_INITIALIZER makrosuyla ilk değer verebiliriz. Örneğin: @@ -22056,12 +22058,13 @@ void exit_sys_errno(const char *msg, int eno) pthread_cond_signal(&g_cond); pthread_mutex_unlock(&g_mutex); - En normal durum, koşul değişkeninin koşulun sağlanması biçiminde ayarlanması işleminin ve pthread_cond_signal ya da - pthread_cond_broadcast işleminin de aynı mutex'i kullanarak kritik kod içerisinde yapılmasıdır. Çünkü programda başka bir - thread bu değişkene aynı anda erişirse yine sorun ortaya çıkabilir. Tabii böyle bir durum yoksa koşul kritik kod içerisinde - set edilmeye de bilir. Özetle diğer bir thread koşul değişkenindeki blokeyi pthread_cond_signal ya da pthread_cond_broadcast - ile kaldırmadan önce koşul değişkenini koşul sağlanacak biçimde set etmesi gerekir. Aksi takdirde koşul sağlanmadığı için - pthread_cond_wait fonksiyonundan uyanan thread döngü içerisinde yeniden uykuya dalacaktır. + Burada koşulun sağlanma işleminin kritik kod içerisinde yapıldığını görüyorsunuz. Normal olarak koşul değişkeninin koşulun + sağlanması biçiminde ayarlanması işleminin ve pthread_cond_signal ya da pthread_cond_broadcast işleminin de aynı mutex'i + kullanarak kritik kod içerisinde yapılmasıdır. Çünkü programda başka bir thread bu değişkene aynı anda erişirse yine sorun + ortaya çıkabilir. Tabii böyle bir durum yoksa koşul kritik kod içerisinde set edilmeye de bilir. Özetle diğer bir thread + koşul değişkenindeki blokeyi pthread_cond_signal ya da pthread_cond_broadcast ile kaldırmadan önce koşul değişkenini koşul + sağlanacak biçimde set etmesi gerekir. Aksi takdirde koşul sağlanmadığı için pthread_cond_wait fonksiyonundan uyanan thread + döngü içerisinde yeniden uykuya dalacaktır. 7) Kullanım bittikten sonra koşul değişkeni pthread_cond_destroy fonksiyonu ile boşaltılmalıdır. Fonksiyonun prototipi şöyledir: @@ -22877,9 +22880,8 @@ void exit_sys_errno(const char *msg, int eno) değişkenleri, mutex nesnesi ve kuyruk bilgileri paylaşılan bellek alanında oluşturulmuştur. Önce "producer" programı çalıştırılmalıdır. Tüm bu nesneleri producer yaratıp kendisi silmektedir. Derleme işlemlerini şöyle yapabilirsiniz: - gcc -Wall -o producer producer.c -lpthread -lrt - gcc -Wall -o consumer consumer.c -lpthread -lrt - + $ gcc -Wall -o producer producer.c -lpthread -lrt + $ gcc -Wall -o consumer consumer.c -lpthread -lrt ---------------------------------------------------------------------------------------------------------------------------*/ /* sharing.h */ @@ -23691,8 +23693,7 @@ void exit_sys_errno(const char *msg, int eno) Programın testi için derlemeyi şöyle yapabilirsiniz: - gcc -o sample sample.c syncqueue.c -lpthread - + $ gcc -o sample sample.c syncqueue.c -lpthread ---------------------------------------------------------------------------------------------------------------------------*/ /* syncqueue.h */ @@ -23911,9 +23912,8 @@ void exit_sys_errno(const char *msg, int eno) Aşağıda iki proses arasında isimsiz semaphore nesneleri ile üretici tüketici problemi uygulanmıştır. Burada önce "producer" programını çalıştırınız. Derleme işlemlerini aşağıdaki gibi yapabilirsiniz: - gcc -o producer producer.c - gcc -o consumer consumer.c - + $ gcc -o producer producer.c + $ gcc -o consumer consumer.c ---------------------------------------------------------------------------------------------------------------------------*/ /* sharing.h */ @@ -24182,8 +24182,8 @@ void exit_sys_errno(const char *msg, int eno) Aşağıda prosesler arasında üretici-tüketici problemi isimli semaphore nesneleri ile çözülmüştür. Derleme işlemleri aşağıdaki gibi yapılabilir: - gcc -o producer producer.c - gcc -o consumer consumer.c + $ gcc -o producer producer.c + $ gcc -o consumer consumer.c Test işleminde önce "producer" programının çalıştırılması gerekmektedir. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -26387,7 +26387,7 @@ void *thread_proc4(void *param) gereksinim duyulmadan tanınan ve ne yaptığı bilinen özel fonksiyonlara "built-in" ya da "intrinsic" fonksiyonlar denilmektedir. Tabii bu kavramlar C standartlarında yoktur. Eklenti (extension) biçiminde C derleyicilerinde bulunmaktadır. Bazı built-in ya da intrinsic fonksiyonlar makro gibi derleyici tarafından özel makine komutları ile açılmaktadır. Bazı built-in ya da - intrinsic fonksiyonlar ise makro gibi açılmamalarına karşın derleyici bunların ne yaptığını bildiği için o işlemlerde + intrinsic fonksiyonlar ise makro gibi açılmamalarına karşın derleyici bunların ne yaptığını bildiği için o işlemlerde optimizasyon uygulayabilmektedir. Örneğin: for (int i = 0; i < strlen(s); ++i) { @@ -27536,16 +27536,16 @@ void exit_sys(const char *msg) Aşağıdaki örnekte komut satırından alınan proses id'ye ilişkin (argv[1]) prosesin nice değeri yine komut satırından alınan göreli nice değeri (argv[2]) olarak değiştirilmektedir. Örneğin: - ./sample 0 -1 + $ ./sample 0 -1 Burada biz kendi prosesimizin değerini düşürerek önceliğini yükseltmeye çalıştık. Bu işlem başarısızlıkla sonuçlanacaktır. Bunu yapabilmemiz için programı root önceliği ile çalıştırmamız gerekir. Örneğin: - sudo ./sample 0 -1 + $ sudo ./sample 0 -1 Tabii biz kendi prosesimizin nice değerini yükselterek önceliğini düşürebiliriz. Örneğin: - sudo ./sample 0 1 + $ sudo ./sample 0 1 Bu işlem başarıyla sonuçlanacaktır. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -27648,7 +27648,7 @@ void exit_sys(const char *msg) bir thread yarattık. Sonra da prosesin nice değerini değiştirdik. Prosesin nice değerini -1 yaptığımızda bundan yalnızca prosesin ana thread'inin etkilendiğini göreceksiniz. Programı bir terminalde aşağıdaki gibi çalıştırabilirsiniz: - $sudo ./sample 0 -1 + $ sudo ./sample 0 -1 Diğer bir terminalden prosesin thread'lerine aşağıdaki gibi bakabilirsiniz: @@ -27755,11 +27755,11 @@ void exit_sys_errno(const char *msg, int eno) Aşağıdaki örnekte prosesin nice değeri, nice fonksiyonuyla iki kez üst üste değiştirilmiştir. Programı aşağıdaki gibi çalıştırabilirsiniz: - $sudo ./sample + $ sudo ./sample Diğer bir terminalden prosesin durumuna baktığınızda aşağıdakine benzer bir rapor elde edeceksiniz: - $ps -t pts/1 -T -l + $ ps -t pts/1 -T -l F S UID PID SPID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 1 S 0 6516 6516 6515 0 80 0 - 4708 - pts/1 00:00:00 sudo 4 S 0 6517 6517 6516 0 76 -4 - 2742 - pts/1 00:00:00 sample @@ -27836,12 +27836,12 @@ void exit_sys_errno(const char *msg, int eno) Örneğin: - nice -1 ./sample + $ nice -1 ./sample Burada sample programı 1 nice değeri ile çalıştırılmaktadır. Burada "-" karakteri seçenek için kullanılan "-" karakteridir. Örneğin aşağıdaki programı nice komutuyla şöyle çalıştıralım: - $nice -1 ./sample + $ nice -1 ./sample Burada başka bir terminale geçip duruma bakalım: @@ -27857,11 +27857,11 @@ void exit_sys_errno(const char *msg, int eno) Şimdi de nice değerini düşürüp aynı denemeyi yapalım: - $sudo nice --1 ./sample + $ sudo nice --1 ./sample Diğer terminalden duruma bakalım: - $ps -t pts/1 -T -l + $ ps -t pts/1 -T -l F S UID PID SPID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 1 S 0 6777 6777 6776 0 80 0 - 4707 - pts/1 00:00:00 sudo 4 S 0 6778 6778 6777 0 79 -1 - 2742 - pts/1 00:00:00 sample @@ -27978,8 +27978,7 @@ void exit_sys_errno(const char *msg, int eno) tepesinde ( include işleminin yukarısına) _GNU_SOURCE makrosunu define etmelisiniz. Tabii bunun yerine gcc ve clang derleyicilerinde -D seçeneğini de kullabilirsiniz. Örneğin: - gcc -D _GNU_SOURCE -o sample sample.c - + $ gcc -D _GNU_SOURCE -o sample sample.c ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -28541,7 +28540,7 @@ void exit_sys(const char *msg) çalıştırmayı unutmayınız. Programı çalıştırdıktan sonra diğer bir terminalden durumu gözlediğimizde aşağıdaki gibi bir durum ortaya çıkmaktadır: - $ps -T --tty pts/2 -o pid,pri,policy,cmd,tid + $ ps -T --tty pts/2 -o pid,pri,policy,cmd,tid PID PRI POL CMD TID 9251 19 TS sudo ./sample 9251 9252 19 TS ./sample 9252 @@ -28627,12 +28626,11 @@ void exit_sys_errno(const char *msg, int eno) statik önceliği de 10 olacak biçimde pthread_setschedparam fonksiyonu ile değiştirilmiştir. Yine değişikliği başka bir terminalden ps komutu ile görebilirsiniz: - $ps -T --tty pts/2 -o pid,pri,policy,cmd,tid + $ ps -T --tty pts/2 -o pid,pri,policy,cmd,tid PID PRI POL CMD TID 9909 19 TS sudo ./sample 9909 9910 19 TS ./sample 9910 9910 50 FF ./sample 9911 - ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -29845,8 +29843,7 @@ void exit_sys_errno(const char *msg, int eno) Programın testi için derlemeyi şöyle yapabilirsiniz: - gcc -o sample sample.c rand.c -lpthread - + $ gcc -o sample sample.c rand.c -lpthread ---------------------------------------------------------------------------------------------------------------------------*/ /* rand.h */ @@ -30270,8 +30267,7 @@ void exit_sys_errno(const char *msg, int eno) gcc ve clang derleyicilerinde C11 thread kütüphanesini kullanan programları derlerken -pthread seçeneğinin kullanılması gerekmektedir. (pthread kütüphanesi için -lpthread seçeneğini kullandığımızı anımsayınız.) Örneğin: - gcc -o sample sample.c -pthread - + $ gcc -o sample sample.c -pthread ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -30889,7 +30885,7 @@ void exit_sys(const char *msg) restart yapmamaktadır. Ancak yukarıda da belirttiğimiz gibi signal fonksiyonu glibc kütüphanesinin belli bir versiyonundan sonra sigaction sistem fonksiyonu çağrılarak yazılmıştır ve otomatik restart işlemi yapılmaktadır. POSIX standartlarında signal fonksiyonu ile bir sinyal set edildiğinde otomatik restart işleminin yapılıp yapılmayacağı işletim sistemlerini - yazanların isteğine bırakılmıştır. + yazanların isteğine bırakılmıştır. Bu durumda Linux sistemlerindeki glibc kütüphanesinde bulunan signal fonksiyonu "sinyali default'a çekmemekte, sinyali sinyal fonksiyonu çalıştığı sürece bloke etmekte ve otomatik restart işlemini uygulamaktadır." @@ -33479,7 +33475,7 @@ void exit_sys(const char *msg) "ulimit -c" ile yalnızca "core file size" bilgisi de görüntülenebilmektedir. Bu sınırın "unlimited" hale getirilmesi şöyle yapılabilir: - ulimit -c unlimited + $ ulimit -c unlimited Core dosyasının üretildiği yer Linux sistemlerinde kullanılan bazı sistem paketlerine göre değişebilmektedir. Eskiden core dosyaları prosesin çalışma dizininde yaratılıyordu. Daha sonra "systemd" paketi ile birlikte core dosyalarının yaratılması @@ -33504,19 +33500,19 @@ void exit_sys(const char *msg) Core dosyalarını kolay yüklemek için ayrıca "coredumpctl" isimli bir programdan da faydalanılabilir. Ancak bu program default olarak kurulu durumda değildir. Bunun kurulabilmesi için aşağıdaki komut uygulanabilir: - sudo apt install systemd-coredump + $ sudo apt install systemd-coredump coredumpctl programı core dosyaları üzerinde işlem yapmayı kolaylaştıran bir programdır. Örneğin üretilmiş olan core dosyalarının listeleri şöyle alınabilir: - coredumpctl list + $ coredumpctl list Bu komutla tüm üretilmiş olan core dosyaları listelenecektir. En son üretilen dosya listenin sonunda olacaktır. Ancak ters sırada görüntüleme için -r seçeneği kullanılabilmektedir. Artık coredumptctl programı yüklendiğine göre ondan faydalanabiliriz. En son core dosyasını gdb ile yüklemek için şöyle yapılır: - coredumpctl gdb + $ coredumpctl gdb Bu komut ile her zaman son core dosyası yüklenmektedir. Spesifik bir core dosyasının yüklenmesi de sağlanabilir. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -33533,7 +33529,7 @@ void exit_sys(const char *msg) proses id'si proses grup id'sine eşit olan prosese, o proses grubunun "proses grup lideri (process group leader)" denilmektedir. Proses grup lideri genellikle proses grubunu yaratan prosestir. fork işlemi sırasında alt prosesin proses grubu onu yaratan üst prosesten alınmaktadır. Yani üst proses hangi proses grubundaysa fork işlemi sonucunda yaratılan proses de aynı proses - grubunda olur. + grubunda olur. Bir prosesin ilişkin olduğu proses grubunun id'sini alabilmek için getpgrp ya da getpgid POSIX fonksiyonları kullanılır. @@ -33550,8 +33546,8 @@ void exit_sys(const char *msg) pgid = getpgid(0); Proses grup lideri olan proses sonlanmış olsa bile proses grubunun id'si aynı biçimde kalmaya devam eder. Yani işletim sistemi - bu durumda prosesin id'sini o proses grubu bu id ile temsil edildiği için başka proses'te kullanmaz. Proses grubu gruptaki - son prosesin sonlanması ya da grup değiştirmesiyle ömrünü tamamlamaktadır. + bu durumda prosesin id'sini o proses grubu bu id ile temsil edildiği için başka proses'te kullanmaz. Proses grubu gruptaki son + prosesin sonlanması ya da grup değiştirmesiyle ömrünü tamamlamaktadır. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -33572,7 +33568,7 @@ void exit_sys(const char *msg) if ((pid = fork()) == -1) exit_sys("fork"); - + pause(); return 0; @@ -33594,7 +33590,7 @@ void exit_sys(const char *msg) 29577 26158 29577 ./sample 29578 29577 29577 ./sample - Burada ilk çalıştırdığımız sample programının yaratılan proses grubunun grup lideri olduğu görülmektedir. sample rpogramında + Burada ilk çalıştırdığımız sample programının yaratılan proses grubunun grup lideri olduğu görülmektedir. sample programında fork yapılarak oluşturulmuş olan prosesin de proses grup id'sinin aynı olduğuna dikkat ediniz. Proses grup id'leri fork işlemi sırasında üst prosesten alt prosese aktarılmaktadır. @@ -33647,8 +33643,8 @@ void exit_sys(const char *msg) setpgid(0, 0); Örneğin kabuktan bir program çalıştırdırğımızda kabuk önce fork işlemini yapar sonra alt proseste setpgid fonksiyonu ile - yeni bir proses grubu yaratır. Çalıştırılan programı da yeni yaratılan proses grubunun proses grup lideri yapar. - + yeni bir proses grubu yaratır. Çalıştırılan programı da yeni yaratılan proses grubunun proses grup lideri yapar. + Aşağıdaki örnekte üst proses fork yaparak alt prosesi oluşturmuştur. Alt proseste de setpgid fonksiyonu ile yeni bir proses grubu yaratılmıştır. Programın örnek çıktısı şöyledir: @@ -33741,6 +33737,7 @@ void exit_sys(const char *msg) - Diğer bir terminalden proses grubuna SIGUSR1 sinyali gönderilir: $ kill -USR1 -49792 + ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -33926,6 +33923,7 @@ void exit_sys(const char *msg) Parent process id of the child: 52812 Child process group id: 52812 Child process session id: 31684 + ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -35643,7 +35641,7 @@ void exit_sys(const char *msg) ağacı içerisinde sabit ise bu sembolik sabitlerin define edilmesi, ancak sabit değilse define edilmemesi gerektiğini belirtmektedir. Yani örneğin PATH_MAX sembolik sabiti bir UNIX türevi sistemde define edilmişken başka bir sistemde edilmemiş olabilir. İşte "Pathname Variable Values" grubundaki sembolik sabitler eğer içerisinde define edilmemişse bu - durumda onların değerleri pathconf ya da fpathconf POSIX fonksiyonuyla alınmalıdır. + durumda onların değerleri pathconf ya da fpathconf POSIX fonksiyonuyla alınmalıdır. içerisindeki "Runtime Increasable Values" grubundaki sembolik sabitler sistemden sisteme değişebilir ve işletiminin çalışma zamanı sırasında artırılabilir. Bu sembolik sabitlerin ilgili sistemdeki minimum değerleri bu başlık altında define @@ -36194,7 +36192,7 @@ void exit_sys(const char *msg) ile değiştirilebilmektedir. Bunun için proc dosya sistemindeki /proc/sys/fs/nr_open dosyası kullanılmaktadır. Bu dosyadaki değeri aşağıdaki gibi bir komutla değiştirebilirsiniz: - sudo sh -c "echo 20 > /proc/sys/fs/mqueue/msg_max" + $ sudo sh -c "echo 20 > /proc/sys/fs/mqueue/msg_max" Anımsanacağı gibi bu bilgi aynı zamanda sysconf fonksiyonundan da elde edilebilmektedir. @@ -36814,7 +36812,7 @@ void exit_sys(const char *msg) 1) Dosyanın içinde bulunduğu dosya sisteminin -o mand ile mount edilmiş olması gerekir. Mevcut mount edilmiş olan bir dosya sisteminin mount parametreleri aşağıdaki gibi değiştirilebilir: - sudo mount -o mand,remount + $ sudo mount -o mand,remount Hangi dosya sisteminin "mand" parametresi ile mount edileceğini tespit etmeniz gerekir. Bunun için df komutunu argümansız biçimde kullanabilirsiniz. Örneğin: @@ -36838,18 +36836,18 @@ void exit_sys(const char *msg) Aslında mount komutu bu bilgileri /proc/mounts dosyasından elde etmektedir. Örneğin: - sudo mount -o mand,remount /dev/sda5 / + $ sudo mount -o mand,remount /dev/sda5 / Sistem açıldığında ilgili dosya sisteminin "mand" parametresi ile mount edilebilmesi için bazı start-up dosyaları kullanabilirsiniz. Örneğin /etc/fstab dosyası bu amaçla kullanılabilmektedir. 2) İlgili dosyanın set-group-id bayrağı set edilip gruba x hakkı varsa reset edilmelidir. Bu işlem şöyle yapılmalıdır: - chmod g+s,g-x + $ chmod g+s,g-x Örneğin: - chmod g+s,g-x test.txt + $ chmod g+s,g-x test.txt Bir dosyanın bir proses tarafından zorunlu kilitlenmiş olması dosyanın silinmesini engellememektedir. Dosyanın truncate ve ftruncate fonksiyonu ile genişletilmesi ya da budanması işleminde zorunlu kilitleme mekanizması devreye girmektedir. @@ -36872,11 +36870,11 @@ void exit_sys(const char *msg) Burada "r" okuma, "w" ise yazma yapma anlamına gelmektedir. Örneğin: - ./rw-test test.txt w 60 10 + $ ./rw-test test.txt w 60 10 Bu çalıştırmayla program write fonksiyonu ile "test.txt" dosyasına 60'ıncı offset'inden itibaren 10 byte yazacaktır. Örneğin: - ./rw-test test.txt r 0 64 + $ ./rw-test test.txt r 0 64 Burada da program "test.txt" dosyasının 0'ıncı offset'inden 64 byte okuyacaktır. @@ -40601,7 +40599,7 @@ void exit_daemon(const char *msg) Sisteminizde hangi init paketinin kurulu olduğunu çeşitli biçimlerde anlayabilirsiniz. Örneğin: - sudo ls -l /proc/1/exe + $ sudo ls -l /proc/1/exe Buradan aşağıdakine benzer bir çıktı elde edilmiştir: @@ -40609,11 +40607,11 @@ void exit_daemon(const char *msg) Hangi init paketinin kullanıldığının anlaşılması için diğer bir yöntem de şöyle olabilir: - cat /proc/1/status + $ cat /proc/1/status Bunun için ps komutundan da faydalanılabilir: - ps -p 1 (ya da "ps -p 1 -o comm=") + $ ps -p 1 (ya da "ps -p 1 -o comm=") ---------------------------------------------------------------------------------------------------------------------------*/ @@ -40743,28 +40741,28 @@ void exit_daemon(const char *msg) Buradaki service unit dosyasının ismidir. Uzantı belirtilmeyebilir. Örneğin: - sudo systemctl enable mydaemon + $ sudo systemctl enable mydaemon Eğer daemon'ın o anda yüklenmesi isteniyorsa ayrıca komuta --now seçeneği de eklenmelidir. Örneğin: - sudo systemctl enable mydaemon --now + $ sudo systemctl enable mydaemon --now - Daemon'ımızın boot sırasında devreye sokulmasının kaldırılması için "systemctl disable" komutu kullanılmaktadır. Komutun genel biçimi şöyledir: - sudo systemctl disable + $ sudo systemctl disable Örneğin: - sudo systemctl disable mydaemon + $ sudo systemctl disable mydaemon - Daemon'ı çalıştırmak için "systemctl start" komutu kullanılmaktadır. Komutun genel biçimi şöyledir: - sudo systemctl start + $ sudo systemctl start - Daemon'ı durdurmak için "systemctl stop" komutu kullanılmalıdır. Komutun genel biçimi şöyledir: - sudo systemctl stop + $ sudo systemctl stop Daemon durdurulurken ona default SIGTERM sinyali gönderilmektedir. systemd SIGTERM sinyalini gönderdikten sonra bir süre bekler, daemon hala sonlanmamışsa bu kez ona SIGKILL sinyalini gönderir. @@ -40775,7 +40773,7 @@ void exit_daemon(const char *msg) Örneğin: - systemctl status mydaemon + $ systemctl status mydaemon - Bazen daemon'ımızı "restart" etmek isteyebiliriz. restart önce durdurup sonra çalıştırmak anlamına gelmektedir. Komutun genel biçimi şöyledir: @@ -40784,18 +40782,18 @@ void exit_daemon(const char *msg) Örneğin: - sudo systemctl restart mydaemon + $ sudo systemctl restart mydaemon - Daemon'ımızın boot zamanında devreye girip girmeyeceğini "systemctl status" komutunun yanı sıra "systemctl is-enabled" komutuyla da anlayabiliriz. Örneğin: - systemctl is-enabled mydaemon + $ systemctl is-enabled mydaemon - systemd tüm unit dosyalarını inceleyerek bir çalıştırma ağacı oluşturmaktadır. Biz bir unit dosyasını değiştirdiğimizde bu ağacın yeniden oluşturulması gerekmektedir. Bunun için "systemctl daemon-reload" komutu kullanılmaktadır. Komutun genel biçimi şöyledir: - sudo systemctl daemon-reload + $ sudo systemctl daemon-reload Komutun parametresiz olduğuna dikkat ediniz. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -40816,13 +40814,13 @@ void exit_daemon(const char *msg) önceden yapılmış olması gerekir. İşte ağ haberleşmesinde önceden belirlenmiş kurallar topluluğuna "protokol" denilmektedir. Ağ haberleşmesi için tarihsel süreç içerisinde pek çok protokol ailesi gerçekletirilmiştir. Bunların bazıları büyük şirketlerin kontrolü altındadır ve hala kullanılmaktadır. Ancak açık bir protokol ailesi olan "IP protokol ailesi" günümüzde farklı - makinelerin prosesleri arasındaki haberleşmede hemen her zaman tercih edilen protokol ailesidir. + makinelerin prosesleri arasındaki haberleşmede hemen her zaman tercih edilen protokol ailesidir. Protokol ailesi (protocol family) denildiğinde birbirleriyle ilişkili bir grup protokol anlaşılmaktadır. Bir protokol ailesinin pek çok protokolü başka protokollerin üzerine konumlandırılmış olabilmektedir. Böylece protokol aileleri katmanlı (layered) bir yapıya sahip olmuştur. Üst seviye bir protokol alt seviye protokolün "zaten var olduğu fikriyle" o alt seviye protokol kullanılarak oluşturulmaktadır. Bu katmanlı yapıyı prosedürel programlama tekniğinde "zaten var olan bir fonksiyonu kullanarak - daha yüksek seviyeli bir fonksiyon yazmaya" benzetebiliriz. + daha yüksek seviyeli bir fonksiyon yazmaya" benzetebiliriz. Ağ haberleşmesi için katmanlı bir protokol yapısının kavramsal olarak nasıl oluşturulması gerektiğine yönelik ISO tarafından 80'li yılların başlarında "OSI Model (Open System Interconnection Model)" isimli bir referans dokümanı oluşturulmuştur. OSI @@ -40840,7 +40838,7 @@ void exit_daemon(const char *msg) - En aşağı seviyeli elektriksel tanımlamaların yapıldığı katmana "fiziksel katman (physical layer)" denilmektedir. (Örneğin kabloların, konnektörlerin özellikleri, akım, gerilim belirlemeleri vs. gibi.) Yani bu katman iletişim için gereken fiziksel - ortamı betimlemektedir. + ortamı betimlemektedir. - Veri bağlantı katmanı (data link layer) artık bilgisayarlar arasında fiziksel bir adreslemenin yapıldığı ve bilgilerin paketlere ayrılarak gönderilip alındığı bir ortam tanımlarlar. Yani bu katmanda bilgilerin gönderildiği ortam değil, gönderilme biçimi @@ -40854,8 +40852,8 @@ void exit_daemon(const char *msg) gönderilmektedir. Ancak bunlardan yalnızca biri gelen bilgiyi sahiplenmektedir. Bugün kablosuz haberleşmede kullanılan "IEEE 802.11" protokolü de tıpkı Ethernet protokolü gibi hem bir fiziksel katman hem de veri bağlantı katmanı tanımlamaktadır. - Fiziksel katman ve veri katmanı oluşturulduğunda artık biz yerel ağda bir birimden diğerine paket adı altında bir grup byte'ı - gönderip alabilir duruma gelmekteyiz. + Fiziksel katman ve veri katmanı oluşturulduğunda artık biz yerel ağda bir birimden diğerine paket adı altında bir grup byte'ı + gönderip alabilir duruma gelmekteyiz. - Ağ Katmanı (network layer) artık "internetworking" yapmak için gerekli kuralları tanımlamaktadır. "Internetworking" terimi "network'lerden oluşan network'ler" anlamına gelir. Aynı fiziksel ortamda bulunan ağlara "Yerel Ağlar (Local Area Networks)" @@ -40866,7 +40864,7 @@ void exit_daemon(const char *msg) katmandır. Ağ katmanında artık fiziksel adresleme değil, mantıksal adresleme sistemi kullanılmaktadır. Ayrıca bilgilerin paketlere ayrılarak router'lardan dolaşıp hedefe varması için rotalama mekanizması da bu katmanda tanımlanmaktadır. Yani elimizde yalnızca ağ katmanı ve onun aşağısındaki katmanlar varsa biz artık "internetworking" ortamında belli bir kaynaktan - belli bir hedefe paketler yollayıp alabiliriz. + belli bir hedefe paketler yollayıp alabiliriz. - Aktarım katmanı (transport layer) network katmanının üzerindedir. Aktarım katmanında artık kaynak ile hedef arasında mantıksal bir bağlantı oluşturulabilmekte ve veri aktarımı daha güvenli olarak yapılabilmektedir. Aynı zamanda aktarım @@ -40874,19 +40872,19 @@ void exit_daemon(const char *msg) gönderilebilmektedir. Bu işleme "port numaralandırması" da denilmektedir. Bu durumda aktarım katmanında tipik şu işlemlere yönelik belirlemeler bulunmaktadır: - - Bağlantnın nasıl yapılacağına ilişkin belirlemeler - - Ağ katmanından gelen paketlerin stream tabanlı organizasyonuna ilşkin belirlemeler - - Veri aktarımını güvenli hale getirmek için akış kontrolüne ilişkin belirlemeler - - Gönderilen bilgilerin hedefte ayrıştıtılmasını sağlayan protokol port numaralandırmasına ilişkin belirlemeler + - Bağlantının nasıl yapılacağına ilişkin belirlemeler + - Ağ katmanından gelen paketlerin stream tabanlı organizasyonuna ilişkin belirlemeler + - Veri aktarımını güvenli hale getirmek için akış kontrolüne ilişkin belirlemeler + - Gönderilen bilgilerin hedefte ayrıştırılmasını sağlayan protokol port numaralandırmasına ilişkin belirlemeler - - Oturum katmanı (session) katmanı pek çok protokol ailesinde yoktur. Görevi oturum açma kapama gibi yüksek seviyeli bazı + - Oturum katmanı (session) katmanı pek çok protokol ailesinde yoktur. Görevi oturum açma kapama gibi yüksek seviyeli bazı belirlemeleri yapmaktır. Örneğin bu katmanda bir grup kullanıcıyı bir araya getiren oturumların nasıl açılacağına ve nasıl - kapatılacağına ilişkin belirlemeler bulunmaktadır. IP protokol ailesinde OSI'de belirtilen biçimde bir oturum katmanı yoktur. + kapatılacağına ilişkin belirlemeler bulunmaktadır. IP protokol ailesinde OSI'de belirtilen biçimde bir oturum katmanı yoktur. - Sunum katmanı (presentation layer) verilerin sıkıştırılması, şifrelenmesi gibi tanımlamalar içermektedir. Yine bu katman - IP protokol ailesinde OSI'de belirtildiği biçimde bulunmamaktadır. + IP protokol ailesinde OSI'de belirtildiği biçimde bulunmamaktadır. - - Nihayet protokol ailesini kullanarak yazılmış olan tüm kullanan bütün programlar aslında uygulama katmanını oluşturmaktadır. + - Nihayet protokol ailesini kullanarak yazılmış olan tüm kullanan bütün programlar aslında uygulama katmanını oluşturmaktadır. Yani ağ ortamında haberleşen her program zaten kendi içerisinde açık ya da gizli bir protokol oluşturmuş durumdadır. Örneğin IP protokol ailesindeki somut işleri yapmakta kullanılan Telnet, SSH, HTTP, POP3, FTP gibi protokoller uygulama katmanı protokolleridir. @@ -40936,15 +40934,15 @@ void exit_daemon(const char *msg) IP protokolü tek başına kullanılırsa ancak ağa bağlı bir birimden diğerine bir paket gönderip alma işini yapar. Bu nedenle bu protokolün tek başına kullanılması çok seyrektir. Uygulamada genellikle "aktarım (transport) katmanına" ilişkin TCP ve UDP ptotokolleri kullanılmaktadır. IP ailesinin uygulama katmanındaki HTTP, SSH, POP3, IMAP, FTP gibi önemli protokollerinin - hepsi TCP protokolü üzerine oturtulmuştur. Ailede genellikle TCP protokolü kullanıldığı için buna kısaca "TCP/IP" de denilmektedir. + hepsi TCP protokolü üzerine oturtulmuştur. Ailede genellikle TCP protokolü kullanıldığı için buna kısaca "TCP/IP" de denilmektedir. IP protokolü ailenin en önemli ve taban protokolüdür. IP protokolünde ağa bağlı olan ve kendisine IP adresiyle erişilebilen her birime "host" denilmektedir. IP protokolü bir host'tan diğerine bir paket (buna IP paketi denilmektedir) bilginin gönderimine ilişkin tanımlamaları içermektedir. IP protokolünde her host'un ismine "IP adresi" denilen mantıksal bir adresi vardır. Paketler belli bir IP adresinden diğerine gönderilmektedir. IP protokolünün iki önemli versiyonu vardır: IPv4 ve IPv6. Bugün her iki versiyon da aynı anda kullanılmaktadır. IPv4'te IP adresleri 4 byte uzunluktadır. (Protokolün tasarlandığı - 70'li yıllarda 4 byte adres alanı çok geniş sanılmaktaydı). IPv6'da ise IP adresleri 16 byte uzunluğundadır. - ---------------------------------------------------------------------------------------------------------------------------*/ + 70'li yıllarda 4 byte adres alanı çok geniş sanılmaktaydı). IPv6'da ise IP adresleri 16 byte uzunluğundadır. + ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- TCP bağlantılı (connection-oriented), UDP bağlantısız (connectionless) bir protokoldür. Buradaki bağlantı IP paketleriyle yapılan @@ -41648,7 +41646,7 @@ void exit_sys(const char *msg) ssize_t recv(int socket, void *buffer, size_t length, int flags); - Fonksiyonun birinci parametresi aktif soketin betimleyicisini beelirtmektedir. İkinci parametre alınacak bilginin + Fonksiyonun birinci parametresi aktif soketin betimleyicisini belirtmektedir. İkinci parametre alınacak bilginin yerleştirileceği dizinin adresini almaktadır. Üçüncü parametre ise okunmak istenen byte sayısını belirtmektedir. Fonksiyonun son parametresi aşağıdaki üç sembolik sabitin bit OR işlemine sokulmasıyla oluşturulabilir: @@ -49880,12 +49878,12 @@ void exit_sys(const char *msg) asıl işlevsellik bu kütüphanededir. Wireshark adeta libpcap kütüphanesinin bir önyüzü (frontend) gibidir. Bu kütüphanenin Windows versiyonuna "npcap" denilmektedir. Linux Debian türevi sistemlerde kütüphane aşağıdaki gibi indirilebilir: - sudo apt-get install libpcap-dev + $ sudo apt-get install libpcap-dev Benzer biçimde Linux'ta wireshark programını da GUI arayüzü yazılım yöneticisinden yüklenebileceği gibi komut satırından Debian türevi sistemlerde aşağıdaki gibi yüklenebilir: - sudo apt-get install wireshark + $ sudo apt-get install wireshark Wireshark programının kullanımına ilişkin pek çok "tutorial" bulunmaktadır. Kursumuzun EBooks klasöründe de birkaç kitap bulunmaktadır. @@ -50583,16 +50581,16 @@ void exit_sys(const char *msg) /*-------------------------------------------------------------------------------------------------------------------------- Statik kütüphaneler aslında "object modülleri (yani .o dosyalarını)" tutan birer kap gibidir. Yani statik kütüphaneler object modüllerden oluşmaktadır. Statik kütüphanelere link aşamasında linker tarafından bakılır. Bir program statik kütüphane dosyasından - bir çağırma yaptıysa (ya da o kütüphaneden bir global değişkeni kullandıysa) linker o statik kütüphane içerisinde ilgili fonksiyonun - bulunduğu object modülü link aşamasında statik kütüphane dosyasından çekerek çalıştırılabilir dosyaya yazar. (Yani statik kütüphaneden - bir tek fonksiyon çağırsak bile aslında o fonksiyonun bulunduğu object modülün tamamı çalıştırılabilen dosyaya yazılmaktadır.) - Statik kütüphaneleri kullanan programlar artık o statik kütüphaneler olmadan çalıştırılabilirler. + bir çağırma yaptıysa (ya da o kütüphaneden bir global değişkeni kullandıysa) linker o statik kütüphane içerisinde ilgili + fonksiyonun bulunduğu object modülü link aşamasında statik kütüphane dosyasından çekerek çalıştırılabilir dosyaya yazar. + (Yani statik kütüphaneden bir tek fonksiyon çağırsak bile aslında o fonksiyonun bulunduğu object modülün tamamı çalıştırılabilen + dosyaya yazılmaktadır.) Statik kütüphaneleri kullanan programlar artık o statik kütüphaneler olmadan çalıştırılabilirler. Statik kütüphane kullanımının şu dezavantajları vardır: 1) Kütüphaneyi kullanan farklı programlar aynı fonksiyonun (onun bulunduğu object modülün) bir kopyasını çalıştırılabilir dosya içerisinde bulundururlar. Yani örneğin printf fonksiyonu statik kütüphanede ise her printf kullanan C programı aslında printf - fonksiyonunun bir kopyasını da barındırıyor durumda olur. Bu da programların fazla yer kaplamasına yol açacaktır. + fonksiyonunun bir kopyasını da barındırıyor durumda olur. Bu da programların diskte fazla yer kaplamasına yol açacaktır. 2) Aynı statik kütüphaneyi kullanan programlar belleğe yüklenirken işletim sistemi aynı kütüphane kodlarınını yeniden fiziksel belleğe yükleyecektir. İşletim sistemi bu kodların ortak olarak kullanıldığını anlayamamaktadır. @@ -50609,7 +50607,6 @@ void exit_sys(const char *msg) 3) Statik kütüphane kullanan programların yüklenmesi dinamik kütüphane kullanan programların yüklenmesinden çoğu kez daha hızlı yapılmaktadır. Ancak bu durum çeşitli koşullara göre tam ters bir hale de gelebilmektedir. - ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -50617,7 +50614,7 @@ void exit_sys(const char *msg) ar programına önce bir seçenek, sonra statik kütüphane dosyasının ismi, sonra da bir ya da birden fazla object modül ismi komut satırı argümanı olarak verilir. Örneğin: - ar r libmyutil.a x.o y.o + $ ar r libmyutil.a x.o y.o Burada r seçeneği belirtmektedir. ar eski bir komut olduğu için burada seçenekler '-' ile başlatılarak verilmemektedir. Komuttaki "libmyutil.a" işlemden etkilenecek statik kütüphane dosyasını "x.o" ve "y.o" argümanları ise object modülleri belirtmektedir. @@ -50626,85 +50623,89 @@ void exit_sys(const char *msg) r (replace) seçeneği (yanında "-" olmadığına dikkat ediniz) ilgili object modüllerin kütüphaneye yerleştirilmesini sağlar. Eğer kütüphane dosyası yoksa komut aynı zamanda onu yaratmaktadır. Örneğin: - ar r libmyutil.a x.o y.o + $ ar r libmyutil.a x.o y.o Burada "libmyutil.a" statik kütüphane dosyasına "x.o" ve "y.o" object modülleri yerleştirilmiştir. Eğer "libmyutil.a" dosyası yoksa aynı zamanda bu dosya yaratılacaktır. t seçeneği kütüphane içerisindeki object modüllerin listesini almakta kullanılır. Örneğin: - ar t libsample.a + $ ar t libsample.a d (delete) seçeneği kütüphaneden bir object modülü silmekte kullanılır. Örneğin: - ar d libmyutil.a x.o + $ ar d libmyutil.a x.o x (extract) seçeneği kütüphane içerisindeki object modülü bir dosya biçiminde diske save etmekte kullanılır. Ancak bu object modül kütüphane dosyasından silinmeyecektir. Örneğin: - ar x libmyutil.a x.o + $ ar x libmyutil.a x.o m (modify) seçeneği de bir object modülün yeni versiyonunu eski versiyonla değiştirmekte kullanılır. O halde "x.c" ve "y.c" dosyalarının içerisindeki fonksiyonları statik kütüphane dosyasına eklemek için sırasıyla şunlar yapılmalıdır: - gcc -c x.c - gcc -c y.c - ar r libmyutil.a x.o y.o - + $ gcc -c x.c + $ gcc -c y.c + $ ar r libmyutil.a x.o y.o ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Statik kütüphane kullanan programları derlerken statik kütüphane dosyaları komut satırında belirtilebilir. Bu durumda gcc - ve clang derleyicileri o dosyayı link işleminde kullanmaktadır. Örneğin: + ve clang derleyicileri o dosyayı bağlama (link) işleminde kullanmaktadır. Örneğin: - gcc -o app app.c libmyutil.a + $ gcc -o app app.c libmyutil.a - Burada "libmyutil.a" dosyasına C derleyicisi bakmamaktadır. gcc aslında bu dosyayı linker'a iletmektedir. Biz bu işlemi - iki adımda da yapabilirdik: + Burada "libmyutil.a" dosyasına C derleyicisi bakmamaktadır. gcc aslında bu dosyayı bağlayıcıya (linker) iletmektedir. Biz bu + işlemi iki adımda da yapabilirdik: - gcc -c app.c - gcc -o app app.o libmyutil.a + $ gcc -c app.c + $ gcc -o app app.o libmyutil.a - Her ne kadar GNU'nun linker programı aslında "ld" isimli programsa da genellikle programcılar bu ld linker'ını doğrudan - değil yukarıdaki gibi "gcc" yoluyla kullanırlar. Çünkü ld linker'ı kullanılırken "libc" kütüphanesi gibi start-up object - modüller gibi modüllerin de programcı tarafından ld linker'ına verilmesi gerekmektedir. Bu da oldukça sıkıcı bir işlemdir. - Halbuki biz ld linker'ını gcc yoluyla çalıştırdığımızda ld linker'ına bu dosyalar zaten default kütüphaneler ve object modüller - gcc tarafından verilmektedir. + Her ne kadar GNU'nun bağlayıcı programı aslında "ld" isimli programsa da genellikle programcılar bu ld bağlayıcısını doğrudan + değil yukarıdaki gibi gcc yoluyla kullanırlar. Çünkü ld bağlayıcısını kullanılırken "libc" kütüphanesinin start-up amaç dosyaların + (start-up object modules) programcı tarafından ld bağlayıcısına verilmesi gerekmektedir. Bu da oldukça sıkıcı bir işlemdir. + Halbuki biz ld bağlayıcısını gcc yoluyla çalıştırdığımızda libc kütüphanesi ve bu start-up amaç dosyalar ld bağlayıcısına gcc + tarafından verilmektedir. + + gcc eskiden C derleyicisi anlamına geliyordu (GNU C Compiler). Ancak zamanla derleyicileri çalıştıran bir önyüz (frontend) + program haline getirildi ve ismi de "GNU Compiler Collection" biçiminde değiştirildi. Yani aslında uzunca bir süredir gcc + programı ile yalnızca C programlarını değil diğer programlama dillerinde yazılmıl olan programları da derleyebilmekteyiz. Komut satırında kütüphane dosyalarının komut satırı argümanlarının sonunda belirtilmesi uygundur. Çünkü gcc programı kütüphane - dosyalarının solundaki dosyalar link edilirken ilgili kütüphane dosyasını bu işleme dahil ederler. Örneğin: + dosyalarını yalnızca onların solunda belirtilen dosyaların bağlanmasında kullanmaktadır. Örneğin: - gcc -o app app1.o libmyutil.a app2.o + $ gcc -o app app1.o libmyutil.a app2.o - Böylesi bir kullanımda "libmyutil.a" komut satırı argümanının solunda yalnızca "app1.o" dosyası vardır. Dolayısıyla yalnızca - bu modül için bu kütüphaneye bakılacaktır, "app2.o" bu kütüphaneye bakılmayacaktır. + Böylesi bir kullanımda "libmyutil.a" kütüphanesinin solunda yalnızca "app1.o" dosyası vardır. Dolayısıyla bağlayıcı yalnızca + bu modül için bu kütüphaneye bakacaktır, "app2.o" için bu kütüphaneye bakılmayacaktır. - Şüphesiz statik kütüphane kullanmak yerine aslında object modülleri de doğrudan link işlemine sokabiliriz. Örneğin: + Şüphesiz statik kütüphane kullanmak yerine aslında amaç dosyaları da doğrudan bağlama işlemine sokabiliriz. Örneğin: - gcc -o sample sample.c x.o y.o + $ gcc -o sample sample.c x.o y.o - Ancak çok sayıda object modül söz konusu olduğunda bu işlemin zorlaşacağına dikkat ediniz. Yani "object modüller dosyalara - benzetilirse statik kütüphane dosyaları dizinler gibi" düşünülebilir. + Ancak çok sayıda object modül söz konusu olduğunda bu işlemin zorlaşacağına dikkat ediniz. Yani amaç dosyalar (object modules) + dosyalara benzetilirse statik kütüphane dosyaları dizinler gibi düşünülebilir. Derleme işlemi sırasında kütüphane dosyası -l biçiminde de belirtilebilir. Bu durumda arama sırasında "lib" öneki ve ".a" uzantısı aramaya dahil edilmektedir. Yani örneğin: - gcc -o sample sample.c -lmyutil + $ gcc -o sample sample.c -lmyutil - İşleminde aslında libmyutil.a (ya da libmyutil.so) dosyaları aranmaktadır. Arama işlemi sırasıyla bazı dizinlerde yapılmaktadır. - Örneğin /lib dizini, /usr/lib dizini, /usr/local/lib dizini gibi dizinlere bakılmaktadır. Ancak "bulunulan dizine" bakılmamaktadır. - -l seçeneği ile "belli bir dizine de bakılması isteniyorsa" -L seçeneği ile ilgili dizin belirtilebilir. Örneğin: + İşleminde aslında "libmyutil.a" (ya da "libmyutil.so") dosyaları aranmaktadır. Arama işlemi sırasıyla bazı dizinlerde yapılmaktadır. + Örneğin "/lib" dizini, "/usr/lib dizini", "/usr/local/lib" dizini gibi dizinlere bakılmaktadır. Ancak "bulunulan dizine (current + working directory)" bakılmamaktadır. -l seçeneği ile belli bir dizine de bakılması isteniyorsa "-L" seçeneği ile ilgili dizin + belirtilebilir. Örneğin: - gcc -o sample sample.c -lmyutil -L. + $ gcc -o sample sample.c -lmyutil -L. Buradaki '.' çalışma dizinini temsil etmektedir. Artık "libmyutil.a" kütüphanesi için bulunulan dizine de (current working directory) bakılacaktır. Birden fazla dizin için -L seçeneğinin yinelenmesi gerekmektedir. Örneğin: - gcc -o sample sample.c -lmyutil -L. -L/home/csd + $ gcc -o sample sample.c -lmyutil -L. -L/home/csd - Geleneksel olarak -l ve -L seçeneklerinden sonra boşluk bırakılmamaktadır. Ancak boşluk bırakılmasında bir sakınca yoktur. + Geleneksel olarak "-l" ve "-L" seçeneklerinden sonra boşluk bırakılmamaktadır. Ancak boşluk bırakılmasında bir sakınca yoktur. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -50712,8 +50713,7 @@ void exit_sys(const char *msg) kütüphanesindeki fonksiyonları kullanmış olabiliriz. Bu durumda "liby.a" kütüphanesini kullanan program "libx.a" kütüphanesini de komut satırında belirtmek zorundadır. Örneğin: - gcc -o sample sample.c libx.a liby.a - + $ gcc -o sample sample.c libx.a liby.a ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -50745,19 +50745,19 @@ void exit_sys(const char *msg) Yani işletim sistemi arka planda aslında aynı dinamik kütüphaneyi kullanan programlarda bu kütüphaneyi tekrar tekrar fiziksel belleğe yüklememektedir. Bu da statik kütüphanelere göre önemli bir bellek kullanım avantaj oluşturmaktadır. Bu durumda eğer dinamik kütüphanenin ilgili kısmı daha önce fiziksel belleğe yüklenmişse bu durum dinamik kütüphane kullanan programın daha hızlı - yüklemesine de yol açabilmektedir. Prog1 ve Prog2 biçiminde iki programın çalıştığını düşünelim. Bunlar aynı dinamik kütüphaneyi - kullanıyor olsun. İşletim sistemi bu dinamik kütüphaneyi bu proseslerin sanal bellek alanlarının farklı yerlerine yükleyebilir. - Ancak aslında işletim sistemi sayfa tablolarını kullanarak mümkün olduğunca bu iki dinamik kütüphaneyi aynı fiziksel sayfaya - eşlemeye çalışacaktır. Tabii bu durumda proseslerden biri dinamik kütüphane içerisindeki bir statik nesneyi değiştirdiğinde - artık "copy on write" mekanizması devreye girecek ve dinamik kütüphanenin o sayfasının yeni bir kopyası oluşturulacaktır. - Aslında bu durum fork fonksiyonu ile yeni bir prosesin yaratılması durumuna çok benzemektedir. + yüklemesine de yol açabilmektedir. Prog1 ve Prog2 biçiminde iki programın çalıştığını düşünelim. Bunlar aynı dinamik + kütüphaneyi kullanıyor olsun. İşletim sistemi bu dinamik kütüphaneyi bu proseslerin sanal bellek alanlarının farklı yerlerine + yükleyebilir. Ancak aslında işletim sistemi sayfa tablolarını kullanarak mümkün olduğunca bu iki dinamik kütüphaneyi aynı + fiziksel sayfaya eşlemeye çalışacaktır. Tabii bu durumda proseslerden biri dinamik kütüphane içerisindeki bir statik + nesneyi değiştirdiğinde artık "copy on write" mekanizması devreye girecek ve dinamik kütüphanenin o sayfasının yeni bir kopyası + oluşturulacaktır. Aslında bu durum fork fonksiyonu ile yeni bir prosesin yaratılması durumuna çok benzemektedir. Burada anlatılan + unsurların ayrıntıları "sayfalama ve sanal bellek" kullanımın açıklandığı paragraflarda ele alınmıştır. 3) Dinamik kütüphaneleri kullanan programlar bu dinamik kütüphanelerdeki değişikliklerden etkilenmezler. Yani biz dinamik - kütüphanenin yeni bir versiyonunu oluşturduğumuzda bunu kullanan programları yeniden erlemek ya da bağlamak zorunda kalmayız. - Örneğin bir dinamik kütüphaneden foo fonksiyonunu çağırmış olalım. Bu foo fonksiyonunun kodları bizim çalıştırılabilir - dosyamızın içerisinde değil de dinamik kütüphanede olduğuna göre dinamik kütüphanedeki foo fonksiyonu değiştirildiğinde - bizim programımız artık değişmiş olan foo fonksiyonunu çağıracaktır. - + kütüphanenin yeni bir versiyonunu oluşturduğumuzda bunu kullanan programları yeniden derlemek ya da bağlamak zorunda kalmayız. + Örneğin bir dinamik kütüphaneden foo fonksiyonunu çağırmış olalım. Bu foo fonksiyonunun kodları bizim çalıştırılabilir dosyamızın + içerisinde değil de dinamik kütüphanede olduğuna göre dinamik kütüphanedeki foo fonksiyonu değiştirildiğinde bizim programımız + artık değişmiş olan foo fonksiyonunu çağıracaktır. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -50771,10 +50771,10 @@ void exit_sys(const char *msg) o dinamik kütüphanenin "relocation" tablosuna bakarak gerekli makine komutlarını düzeltmektedir. Dinamik kütüphane fonksiyonlarının çağrımı için de "import tablosu" ve "export tablosu" denilen tablolar kullanılmaktadır. UNIX/Linux dünyasında dinamik kütüphanelerin herhangi bir yere yüklenebilmesi ve minimal düzeyde relocation uygulanabilmesi için "Konumdan Bağımsız Kod (Position Independent - Code)" denilen teknik kullanılmaktadır. Konumdan bağımsız kod "nereye yüklenirse yüklenilsin çalışabilen kod" anlamına gelmektedir. - Konumdan bağımsız kod oluşturabilmek derleyicinin yapabileceği bir işlemdir. Konumdan bağımsız kod oluşturabilmek için gcc - ve clang derleyicilerinde derleme sırasında "-fPIC" seçeneğinin bulundurulması gerekmektedir. Biz kursumuzda konumdan bağımsız - kodun ayrıntıları üzerinde durmayacağız. + Code - PIC)" denilen teknik kullanılmaktadır. Konumdan bağımsız kod "nereye yüklenirse yüklenilsin çalışabilen kod" anlamına + gelmektedir. Konumdan bağımsız kod oluşturabilmek derleyicinin yapabileceği bir işlemdir. Konumdan bağımsız kod oluşturabilmek + için gcc ve clang derleyicilerinde derleme sırasında "-fPIC" seçeneğinin bulundurulması gerekmektedir. Biz kursumuzda konumdan + bağımsız kod oluşturmanın ayrıntıları üzerinde durmayacağız. Pekiyi Windows sistemlerinin kullandığı "relocation" tekniği ile UNIX/Linux sistemlerinde kullanılan "konumdan bağımsız kod tekniği" arasında performans bakımından ne farklılıklar vardır? İşte bu tekniklerin kendi aralarında bazı avantaj @@ -50811,15 +50811,15 @@ void exit_sys(const char *msg) g_a = 10; - Burada derleyicinin yukarıdaki ifadeye ilişkin makine kodlarını üretebilmesi için g_a değişkenin tüm bellekteki adresini - (yani tepeden itibaren adresini) bilmesi gerekir. Bir nesnenin belleğin tepesinden itibaren ki adresine "mutlak adres (absolute + Burada derleyicinin yukarıdaki ifadeye ilişkin makine kodlarını üretebilmesi için g_a değişkeninin tüm bellekteki adresini + (yani tepeden itibaren adresini) bilmesi gerekir. Bir nesnenin belleğin tepesinden itibarenki adresine "mutlak adres (absolute address) de denilmektedir. Örneğin Intel işlemcilerinde yukarıdaki ifade aşağıdaki gibi makine komutlarına dönüştürülmektedir: - MOV EAX, 10 - MOV [g_a'nın mutlak adresi], EAX + MOV EAX, 10 + MOV [g_a'nın mutlak adresi], EAX - İşte sorun buradaki g_a değişkeninin mutlak adresinin program yüklenene kadar bilinmemesidir. Bu sorunu çözmenin de iki - yolu vardır: + İşte sorun buradaki g_a değişkeninin mutlak adresinin program yüklenene kadar bilinmemesidir. Bu sorunu çözmenin de iki yolu + vardır: 1) Derleyici ve linker g_a'nın mutlak adresinin bulunduğu yeri boş bırakır. Yükleyicinin bu yeri yükleme adresine göre doldurmasını ister. İşte bu işlem yükleyicinin yaptığı "relocation" işlemidir. Bu tür relocation işlemlerine "load time @@ -50877,11 +50877,11 @@ void exit_sys(const char *msg) Dinamik kütüphane kullanan bir program bağlanırken kullanılan dinamik kütüphanenin komut satırında belirtilmesi gerekir. Örneğin: - gcc -o app app.c libmyutil.so + $ gcc -o app app.c libmyutil.so Tabii bu işlem yine -l seçeneği ile de yapılabilirdi: - gcc -o app app.c -lmyutil -L. + $ gcc -o app app.c -lmyutil -L. Bu biçimde çalıştırılabilir dosya oluşturulduğunda linker bu çalıştırılabilir dosyanın çalıştırılabilmesi için hangi dinamik kütüphanelerin yüklenmesi gerektiğini ELF formatının ".dynamic" isimli bölümüne yazmaktadır. Böylece yükleyici @@ -50895,7 +50895,7 @@ void exit_sys(const char *msg) İster statik kütüphane isterse dinamik kütüphane yazacak olalım yazdığımız kütüphaneler için bir başlık dosyası oluşturmak iyi bir tekniktir. Örneğin içerisinde çeşitli fonksiyonların bulunduğu "libmyutil.so" dinamik kütüphanesini "libmyutil.c" dosyasından hareketle oluşturmak isteyelim. İşte "libmyutil.c" dosyasındaki fonksiyonların prototipleri, gerekli olan sembolik - sabitler, makrolar, inline fonksiyonlar, yapı bildirimleri gibi "nesne yaratmayan bildirimler" bir başlık dosyasına yerleştirilmelidir. + sabitler, makrolar, inline fonksiyonlar, yapı bildirimleri gibi "nesne yaratmayan bildirimler" bir başlık dosyasına yerleştirilmelidir. Böylece bu kütüphaneyi kullanacak kişiler bu dosyayı include ederek gerekli bildirimlerin kodlarını oluşturmuş olurlar. Başlık dosyaları oluşturulurken iki önemli noktaya dikkat edilmelidir: @@ -50932,53 +50932,52 @@ void exit_sys(const char *msg) ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- - Standart C fonksiyonlarının ve POSIX fonksiyonlarının bulunduğu "libc" kütüphanesi gcc ve clang programlarıyla derleme - yapıldığında otomatik olarak bağlama aşamasında devreye sokulmaktadır. Yani biz standart fonksiyonları ve POSIX fonksiyonları - için bağlama aşamasında kütüphane belirtmek zorunda değiliz. Default durumda gcc ve clang programları standart C fonksiyonlarını - ve POSIX fonksiyonlarını dinamik kütüphaneden alarak kullanır. Ancak programcı isterse "-static" seçeneği ile statik bağlama - işlemi de yapabilir. Bu durumda bu fonksiyonlar statik kütüphanelerden alınarak çalıştırılabilen dosyalara yazılacaktır. - Örneğin: + Standart C fonksiyonlarının ve POSIX fonksiyonlarının bulunduğu "libc" kütüphanesi gcc ve clang programlarıyla derleme yapıldığında + otomatik olarak bağlama aşamasında devreye sokulmaktadır. Yani biz standart fonksiyonları ve POSIX fonksiyonları için bağlama + aşamasında kütüphane belirtmek zorunda değiliz. Default durumda gcc ve clang programları standart C fonksiyonlarını ve POSIX + fonksiyonlarını dinamik kütüphaneden alarak kullanır. Ancak programcı isterse "-static" seçeneği ile statik bağlama işlemi + de yapabilir. Bu durumda bu fonksiyonlar statik kütüphanelerden alınarak çalıştırılabilen dosyalara yazılacaktır. Örneğin: - gcc -o app -static app.c + $ gcc -o app -static app.c "-static" seçeneği ile bağlama işlemi yapıldığında artık üretilen çalıştırılabilir dosyanın dinamik kütüphanelerle hiçbir ilgisi kalmamaktadır. Zaten "-static" seçeneği belirtildiğinde artık dinamik kütüphaneler bağlama aşamasına programcı tarafından da dahil edilememektedir. Tabii bu biçimde statik bağlama işlemi yapıldığında çalıştırılabilen dosyanın boyutu çok büyüyecektir. - Eğer "libc" kütüphanesinin default olarak bağlama aşamasında devreye sokulması istenmiyorsa "-nodefaultlibs" seçeneğinin - kullanılması gerekir. Örneğin: + Eğer "libc" kütüphanesinin default olarak bağlama aşamasında devreye sokulması istenmiyorsa "-nodefaultlibs" seçeneğinin kullanılması + gerekir. Örneğin: - gcc -nodefaultlibs -o app app.c + $ gcc -nodefaultlibs -o app app.c Burada glibc kütüphanesi devreye sokulmadığı için bağlama aşamasında hata oluşacaktır. Tabii bu durumda da kütüphane açıkça belirtilebilir: - gcc -nodefaultlibs -o app app.c -lc + $ gcc -nodefaultlibs -o app app.c -lc Bir kütüphanenin statik ve dinamik biçimi aynı anda bulunuyorsa ve biz bu kütüphaneyi "-l" seçeneği ile belirtiyorsak bu durumda default olarak kütüphanenin dinamik versiyonu devreye sokulmaktadır. Eğer bu durumda kütüphanelerin statik versiyonlarının devreye sokulması isteniyorsa "-static" seçeneğinin kullanılması ya da komut satırında açıkça statik kütüphaneye referans edilmesi gerekir. Örneğin: - gcc -o app app.c -lmyutil -L. + $ gcc -o app app.c -lmyutil -L. - Burada eğer hem "libmyutil.so" hem de "libmyutil.a" dosyaları varsa "libmyutil.so" dosyası kullanılacaktır. Yani dinamik - bağlama yapılacaktır. Tabii biz açıkça statik kütüphanenin ya da dinamik kütüphanenin kullanılmasını sağlayabiliriz: + Burada eğer hem "libmyutil.so" hem de "libmyutil.a" dosyaları varsa "libmyutil.so" dosyası kullanılacaktır. Yani dinamik bağlama + yapılacaktır. Tabii biz açıkça statik kütüphanenin ya da dinamik kütüphanenin kullanılmasını sağlayabiliriz: - gcc -o app app.c libmyutil.a + $ gcc -o app app.c libmyutil.a Aynı etkiyi şöyle de sağlayabilirdik: - gcc -static -o app app.c -lmyutil -L. + $ gcc -static -o app app.c -lmyutil -L. Burada "libc" kütüphanesinin dinamik biçimi devreye sokulacaktır. Ancak "libmyutil" kütüphanesi statik biçimde bağlanmıştır. - Eğer "-static" seçeneği kullanılırsa bu durumda tüm kütüphanelerin statik versiyonları devreye sokulmaktadır. Tabii bu - durumda biz açıkça dinamik kütüphanelerin bağlama işlemine sokulmasını isteyemeyiz. Örneğin: + Eğer "-static" seçeneği kullanılırsa bu durumda tüm kütüphanelerin statik versiyonları devreye sokulmaktadır. Tabii bu durumda + biz açıkça dinamik kütüphanelerin bağlama işlemine sokulmasını isteyemeyiz. Örneğin: - gcc -static -o app app.c libmyutil.so + $ gcc -static -o app app.c libmyutil.so - Bu işlem başarısız olacaktır. Çünkü "-static" seçeneği zaten "tüm kütüphanelerin statik olarak bağlanacağı edileceği" - anlamına gelmektedir. + Bu işlem başarısız olacaktır. Çünkü "-static" seçeneği zaten "tüm kütüphanelerin statik olarak bağlanacağı" anlamına + gelmektedir. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -51055,13 +51054,14 @@ void exit_sys(const char *msg) 5) Nihayet dinamik bağlayıcı dinamik kütüphaneleri sırasıyla "/lib", /usr/lib" dizinlerinde de aramaktadır. 64 bit Linux sistemlerininin bir bölümünde 64 bit dinamik kütüphaneler için "/lib64" ve "/usr/lib64" dizinlerine de bakılabilmektedir. ---------------------------------------------------------------------------------------------------------------------------*/ + +---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Yukarıdaki üçüncü maddede aranacak yol ifadesini çalıştırılabilir dosyanın DT_RUNPATH tag'ına yerleştirmek için ld bağlayıcısında "-rpath " bağlayıcı seçeneği kullanılmalıdır. Buradaki yol ifadelerinin mutlak olması zorunlu değilse de şiddetle tavsiye edilmektedir. gcc ve clang derleyicilerinde "-rpath" seçeneğini bağlayıcıya geçirebilmek için - "-Wl" seçeneği kullanılabilir. "-Wl" seçeneği bitişik yazılan virgüllü alanlardan oluşmalıdır. gcc ve clang bu komut satırı + "-Wl" seçeneği kullanılabilir. "-Wl" seçeneği bitişik yazılan virgüllü alanlardan oluşmalıdır. gcc ve clang bu komut satırı argümanını "ld" bağlayıcısına virgüller yerine boşluklar (SPACE) koyarak geçirmektedir. Örneğin: $ gcc -o app app.c -Wl,-rpath,/home/kaan/Study/UnixLinux-SysProg libmyutil.so @@ -51079,28 +51079,28 @@ void exit_sys(const char *msg) Birden fazla kez "-rpath" seçeneği kullanıldığında bu seçenekler tek bir DT_RUNPATH tag'ına aralarına ':' karakteri getirilerek yerleştirilmektedir. Yani aşağıdaki işlem yukarıdaki ile eşdeğerdir: - gcc -o app app.c -Wl,-rpath,/home/csd/Study/UnixLinux-SysProg,-rpath,/home/kaan libmyutil.so + $ gcc -o app app.c -Wl,-rpath,/home/csd/Study/UnixLinux-SysProg,-rpath,/home/kaan libmyutil.so "-rpath" bağlayıcı seçeneğinde default durumda DT_RUNPATH tag'ına yerleştirme yapıldığına dikkat ediniz. Eğer DT_RPATH tag'ına yerleştirme yapılmak isteniyorsa bağlayıcı seçeneklerine ayrıca "--disable-new-dtags" seçeneğinin de girilmesi gerekmektedir. Örneğin: - gcc -o app app.c -Wl,-rpath,/home/csd/Study/UnixLinux-SysProg,--disable-new-dtags libmyutil.so + $ gcc -o app app.c -Wl,-rpath,/home/csd/Study/UnixLinux-SysProg,--disable-new-dtags libmyutil.so DT_RUNPATH tag'ını da aşağıdaki gibi görüntüleyebiliriz: $ readelf -d app | grep "RUNPATH" 0x000000000000001d (RUNPATH) Library runpath: [/home/kaan/Study/UnixLinux-SysProg] - Çalıştırılabilir dosyaya DT_RUNPATH tag'ının mutlak ya da göreli yol ifadesi biçiminde girilmesi bazı kullanım sorunlarına - yol açabilmektedir. Çünkü bu durumda dinamik kütüphaneler uygulamanın kurulduğu dizine göreli biçimde konuşlandırılacağı - zaman uygulamanın kurulum yeri değiştirildiğinde sorunlar oluşabilmektedir. Örneğin biz çalıştırılabilir dosyanın DT_RUNPATH - tag'ına "home/kaan/test" isimli yol ifadesini yazmış olalım. Programımızı ve dinamik kütüphanemizi bu dizine yerleştirirsek - bir sorun oluşmayacaktır. Ancak başka bir dizine yerleştirirsek dinamik kütüphanemiz bulunamayacaktır. İşte bunu engellemek - için "-rpath" seçeneğinde '$ORIGIN' argümanı kullanılmaktadır. Buradaki '$ORIGIN' argümanı "o anda çalıştırılabilen dosyanın - bulunduğu dizini" temsil etmektedir. Örneğin: + Çalıştırılabilir dosyaya DT_RUNPATH tag'ının mutlak ya da göreli yol ifadesi biçiminde girilmesi bazı kullanım sorunlarına yol + açabilmektedir. Çünkü bu durumda dinamik kütüphaneler uygulamanın kurulduğu dizine göreli biçimde konuşlandırılacağı zaman + uygulamanın kurulum yeri değiştirildiğinde sorunlar oluşabilmektedir. Örneğin biz çalıştırılabilir dosyanın DT_RUNPATH tag'ına + "home/kaan/test" isimli yol ifadesini yazmış olalım. Programımızı ve dinamik kütüphanemizi bu dizine yerleştirirsek bir sorun + oluşmayacaktır. Ancak başka bir dizine yerleştirirsek dinamik kütüphanemiz bulunamayacaktır. İşte bunu engellemek için "-rpath" + seçeneğinde '$ORIGIN' argümanı kullanılmaktadır. Buradaki '$ORIGIN' argümanı "o anda çalıştırılabilen dosyanın bulunduğu dizini" + temsil etmektedir. Örneğin: - gcc -o app app.c -Wl,-rpath,'$ORIGIN'/. libmyutil.so + $ gcc -o app app.c -Wl,-rpath,'$ORIGIN'/. libmyutil.so Burada artık çalıştırılabilen dosya nereye yerleştirilirse yerleştirilsin ve nereden çalıştırılırsa çalıştırılsın dinamik kütüphaneler çalıştırılabilen dosyanın yerleştirildiği dizinde aranacaktır. @@ -51139,11 +51139,11 @@ void exit_sys(const char *msg) Biz "ldconfig" programını çalıştırdığımızda bu program "/lib", "/usr/lib" ve "/etc/ld.so.conf" (dolayısıyla "/etc/ld.so.conf.d" dizinindeki ".conf" dosyalarına) bakarak "/etc/ld.so.cache" dosyasını yeniden oluşturmaktadır. O halde bizim bu cache'e ekleme - yapmak için tek yapacağımız şey "/etc/ld.so.conf.d" dizinindeki bir ".conf" dosyasına yeni bir satır olarak bir dizinin yol - ifadesini girmektir. (".conf" dosyaları her satırda bir dizinin yol ifadesinden oluşmaktadır.) Tabii programcı isterse bu - dizine yeni bir ".conf" dosyası da ekleyebilir. İşte programcı bu işlemi yaptıktan sonra "/sbin/ldconfig" programını çalıştırınca - artık onun eklediği dizinin içerisindeki ".so" dosyaları da "/etc/ld.so.cache" dosyasının içerisine eklenmiş olacaktır. Daha - açık bir anlatımla programcı bu cache dosyasına ekleme işini adım adım şöyle yapar: + yapmak için tek yapacağımız şey "/etc/ld.so.conf.d" dizinindeki bir ".conf" dosyasına yeni bir satır olarak bir dizinin yol ifadesini + girmektir. (".conf" dosyaları her satırda bir dizinin yol ifadesinden oluşmaktadır.) Tabii programcı isterse bu dizine yeni bir + ".conf" dosyası da ekleyebilir. İşte programcı bu işlemi yaptıktan sonra "/sbin/ldconfig" programını çalıştırınca artık onun + eklediği dizinin içerisindeki ".so" dosyaları da "/etc/ld.so.cache" dosyasının içerisine eklenmiş olacaktır. Daha açık bir + anlatımla programcı bu cache dosyasına ekleme işini adım adım şöyle yapar: 1) Önce ".so" dosyasını bir dizine yerleştirir. 2) Bu dizinin ismini "/etc/ld.so.conf.d" dizinindeki bir dosyanın sonuna ekler. Ya da bu dizinde yeni ".conf" dosyası oluşturarak @@ -51154,11 +51154,11 @@ void exit_sys(const char *msg) için bulundurulmuştur. Programcı "/etc/ld.so.conf.d" dizinindeki herhangi bir dosyaya değil de "-f" seçeneği sayesinde kendi belirlediği bir dosyaya - da ilgili dizinleri yazabilmektedir. Başka bir deyişle "-f" seçeneği "şu config dosyasına da bak" anlamına gelmektedir. - "ldconfig" her çalıştırıldığında sıfırdan yeniden cache dosyasını oluşturmaktadır. + da ilgili dizinleri yazabilmektedir. Başka bir deyişle "-f" seçeneği "şu config dosyasına da bak" anlamına gelmektedir. "ldconfig" + her çalıştırıldığında sıfırdan yeniden cache dosyasını oluşturmaktadır. - Programcı "/lib" ya da "/usr/lib" dizinine bir ".so" dosyası eklediğinde "ldconfig" programını çalıştırması -zorunlu olmasa - da- iyi bir tekniktir. Çünkü o dosya da cache dosyasına yazılacak ve daha hızlı bulunacaktır. + Programcı "/lib" ya da "/usr/lib" dizinine bir ".so" dosyası eklediğinde "ldconfig" programını çalıştırması -zorunlu olmasa da- + iyi bir tekniktir. Çünkü o dosya da cache dosyasına yazılacak ve daha hızlı bulunacaktır. ldconfig programında "-p" seçeneği ile cache dosyası içerisindeki tüm dosyalar görüntülenebilmektedir. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -51171,7 +51171,7 @@ void exit_sys(const char *msg) bir tag'ına yerleştirilmektedir. "-soname" komut satırı argümanı linker'a ilişkin olduğu için "-Wl" seçeneği ile kullanılmalıdır. Örneğin biz libx.so isimli bir dinamik kütüphaneyi "so" ismi vererek oluşturmak isteyelim. Bu işlemi şöyle yapabiliriz: - gcc -o libx.so -fPIC -shared -Wl,-soname,liby.so libx.c + $ gcc -o libx.so -fPIC -shared -Wl,-soname,liby.so libx.c Burada "libx.so" kütüphane dosyasına "liby.so" "so" ismi verilmiştir. Kütüphane dosyalarına iliştirilen "so" isimleri readelf ile aşağıdaki gibi görüntülenebilir: @@ -51190,7 +51190,7 @@ void exit_sys(const char *msg) kütüphanenin ismini değil "so" ismini yazmaktadır. Yukarıdaki örneğimizde "libx.so" kütüphanesi "so" ismi olarak "liby.so" ismini içermektedir. Şimdi libx.so dosyasını kullanan "app.c" dosyasını derleyip link edelim: - gcc -o app app.c libx.so + $ gcc -o app app.c libx.so Burada link işleminde "libx.so" dosya ismi kullanılmıştır. Ancak oluşturulan "app" dosyasının içerisine linker bu ismi değil, "so" ismi olan "liby.so" ismini yazacaktır. Örneğin: @@ -51275,16 +51275,15 @@ void exit_sys(const char *msg) Örneğin: - gcc -o libmyutil.so.1.0.0 -shared -fPIC libmyutil.c (gerçek isimli kütüphane dosyası oluşturuldu) - ln -s libmyutil.so.1.0.0 libmyutil.so.1 (so ismi oluşturuldu) - ln -s libmyutil.so.1 libmyutil.so (linker ismi oluşturuldu) + $ gcc -o libmyutil.so.1.0.0 -shared -fPIC libmyutil.c (gerçek isimli kütüphane dosyası oluşturuldu) + $ ln -s libmyutil.so.1.0.0 libmyutil.so.1 (so ismi oluşturuldu) + $ ln -s libmyutil.so.1 libmyutil.so (linker ismi oluşturuldu) Burada oluşturulan üç dosyayı "ls -l" komutu ile görüntüleyelim: lrwxrwxrwx 1 kaan study 14 Şub 25 15:45 libmyutil.so -> libmyutil.so.1 lrwxrwxrwx 1 kaan study 18 Şub 25 15:45 libmyutil.so.1 -> libmyutil.so.1.0.0 -rwxr-xr-x 1 kaan study 15736 Şub 25 15:45 libmyutil.so.1.0.0 - ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -51296,17 +51295,16 @@ void exit_sys(const char *msg) kullanılan isimlerdir. Bu sayede link işlemini yapan programcıların daha az tuşa basarak genel bir isim kullanması sağlanmıştır. Bu durumda örneğin biz libmyutil isimli kütüphaneyi kullanan programı link etmek istersek şöyle yapabiliriz: - gcc -o app app.c libmyutil.so + $ gcc -o app app.c libmyutil.so Ya da şöyle yapabiliriz: - gcc -o app app.c -lmyutil -L. + $ gcc -o app app.c -lmyutil -L. Burada aslında "libmyutil.so" dosyası "so ismine" "so" ismi de "gerçek isme link yapılmış" durumdadır. Yani bu komutun aslında eşdeğeri şöyledir: - gcc -o app app.c libmyutil.so.1.0.0 - + $ gcc -o app app.c libmyutil.so.1.0.0 ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -51518,8 +51516,7 @@ void exit_sys(const char *msg) Aşağıda bir dinamik kütüphane dinamik olarak yüklenmiş ve oradan bir fonksiyon ve data adresi alınarak kullanılmıştır. Buradaki dinamik kütüphaneyi daha önce yaptığımız gibi derleyebilirsiniz: - gcc -fPIC -shared -o libmyutil.so.1.0.0 -Wl,-soname,libmyutil.so.1 libmyutil.c - + $ gcc -fPIC -shared -o libmyutil.so.1.0.0 -Wl,-soname,libmyutil.so.1 libmyutil.c ---------------------------------------------------------------------------------------------------------------------------*/ /* libmyutil.c */ @@ -51689,9 +51686,8 @@ int main(void) dışarıdan çağrılamayacaktır. Tabii kütüphane içerisindeki foo fonksiyonu bar fonksiyonunu çağırabilmektedir. Dosyaları aşağıdaki gibi derleyebilirsiniz: - gcc -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.c - gcc -o app app.c libmyutil.so.1.0.0 -ldl - + $ gcc -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.c + $ gcc -o app app.c libmyutil.so.1.0.0 -ldl ---------------------------------------------------------------------------------------------------------------------------*/ /* libmyutil.c */ @@ -51792,7 +51788,6 @@ int main(void) main ends... destructor bar begins... destructor bar ends... - ---------------------------------------------------------------------------------------------------------------------------*/ #include @@ -51936,7 +51931,6 @@ int main(void) $ g++ -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.cpp $ g++ -o app app.cpp libmyutil.so.1.0.0 - ---------------------------------------------------------------------------------------------------------------------------*/ /* util.hpp */ @@ -52008,11 +52002,11 @@ int main() $ gcc -c app1.c $ gcc -c app2.c - # gcc -c app3.c + $ gcc -c app3.c ... - # gcc -c app10.c + $ gcc -c app10.c - # gcc -o app app1.o app2.o app3.o ... app10.o + $ gcc -o app app1.o app2.o app3.o ... app10.o Bu çalışma biçiminde bir kaynak dosyada değişiklik yapıldığında yalnızca değişikliğin yapılmış olduğu kaynak dosya yeniden derlenir ancak link işlemine yine tüm amaç dosyalar dahil edilir. Örneğin app3.c üzerinde bir değişilik yapmış olalım: @@ -52076,7 +52070,7 @@ int main() Burada eğer "a.o" ya da "b.o" ya da "c.o" dosyalarının tarih ve zamanı "app" dosyasının tarih ve zamanından ilerideyse aşağıdaki kabuk komutu çalıştırılacaktır: - gcc -o app a.o b.o c.o + $ gcc -o app a.o b.o c.o Bu link işlemi anlamına gelir. Link işleminden sonra artık "app" dısyasının tarih ve zamanı ön koşul dosyalarından daha ileride olacağı için kural "güncel (up to date)" hale gelir. Artık bu kural işletildiğinde bu link işlemi yapılmayacaktır. @@ -52226,7 +52220,7 @@ int main() Burada executable dosyanın hedefi komut satırından elde edilmektedir. Örneğin biz make programını şöyle çalıştırabiliriz: - make executable=app + $ make executable=app Bu durumda "app" dosyası hedef olarak ele alınacaktır. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -52286,7 +52280,6 @@ int main() $ gcc -c x.c $ ld -o x x.o --entry=foo - ---------------------------------------------------------------------------------------------------------------------------*/ /* x.c */ @@ -52367,7 +52360,7 @@ void foo() kütüphaneler ve başlık dosyaları zaten yüklü biçimde bulunmaktadır. Tabii programcı kernel kodlarını da kendi makinesine indirmek isteyebilir. Bunun için aşağıdaki komut kullanılabilir: - sudo apt-get install linux-source + $ sudo apt-get install linux-source Eğer sisteminizde Linux'un kaynak kodları yüklü ise bu kaynak kodlar "/usr/src" dizininde bulunmaktadır. Bu dizindeki "linux-headers-$(uname -r)" dizini kaynak kodlar yüklü olmasa bile bulunan bir dizindir ve bu dizin çekirdek modülleri @@ -52518,7 +52511,7 @@ clean: Kernel modüller istenildiği zaman "rmmod" isimli programla kernel'dan çıkartılabilirler. Bu programın da yine sudo ile "root" önceliğinde çalıştırılması gerekir. Örneğin: - sudo rmmod helloworld.ko + $ sudo rmmod helloworld.ko Aşağıda örnek için gerekli olan dosyalar verilmiştir. make işlemi şöyle yapılabilir: @@ -52590,15 +52583,16 @@ clean: ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- - helloworld modülünde kullanmış olduğumuz printk fonksiyonu "kernel'ın printf fonksiyonu" gibi düşünülebilir. printk fonksiyonunun - genel kullanımı printf fonksiyonu gibidir. Default durumda bu fonksiyon mesajların "/var/log/syslog" dosyasına yazdırılması - sağlamaktadır. printk fonksiyonunun prototipi dosyası içerisindedir. printk fonksiyonunun örnek kullanımı şöyledir: + helloworld modülünde kullanmış olduğumuz printk fonksiyonu "kernel'ın printf fonksiyonu" gibi düşünülebilir. printk + fonksiyonunun genel kullanımı printf fonksiyonu gibidir. Default durumda bu fonksiyon mesajların "/var/log/syslog" dosyasına + yazdırılması sağlamaktadır. printk fonksiyonunun prototipi dosyası içerisindedir. printk fonksiyonunun + örnek kullanımı şöyledir: printk(KERN_INFO "This is test\n"); - Mesajın solundaki KERN_XXX biçimindeki makrolar aslında bir string açımı yapmaktadır. Dolayısıyla yan yana iki string birleştirildiği - için mesaj yazısının başında küçük bir önek bulunur. Bu önek (yani bu makro) mesajın türünü ve aciliyetini belirtmektedir. Tipik - KERN_XXX makroları şunlardır: + Mesajın solundaki KERN_XXX biçimindeki makrolar aslında bir string açımı yapmaktadır. Dolayısıyla yan yana iki string + birleştirildiği için mesaj yazısının başında küçük bir önek bulunur. Bu önek (yani bu makro) mesajın türünü ve aciliyetini + belirtmektedir. Tipik KERN_XXX makroları şunlardır: KERN_EMERG KERN ALERT @@ -52611,16 +52605,29 @@ clean: Bu makroların tipik yazım biçimi şöyledir: - #define KERN_EMERG "<0>" - #define KERN_ALERT "<1>" - #define KERN_CRIT "<2>" - #define KERN_ERR "<3>" - #define KERN_WARNING "<4>" - #define KERN_NOTICE "<5>" - #define KERN_INFO "<6>" - #define KERN_DEBUG "<7>" + #define KERN_SOH "\001" /* ASCII Start Of Header */ + #define KERN_SOH_ASCII '\001' - Ancak bu makrolarda çeşitli kernel versiyonlarında değişiklikler yapılabilmektedir. + #define KERN_EMERG KERN_SOH "0" /* system is unusable */ + #define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */ + #define KERN_CRIT KERN_SOH "2" /* critical conditions */ + #define KERN_ERR KERN_SOH "3" /* error conditions */ + #define KERN_WARNING KERN_SOH "4" /* warning conditions */ + #define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ + #define KERN_INFO KERN_SOH "6" /* informational */ + #define KERN_DEBUG KERN_SOH "7" /* debug-level messages */ + + Ancak bu makrolarda çeşitli kernel versiyonlarında değişiklikler yapılabilmektedir. C'de aralarında hiçbir operatör bulunmayan + iki string'in derleyici tarafından birleştirildiğini anımsayınız.Bu durumda aslında örneğin: + + printk(KERN_INFO "Hello World...\n"); + + ile aşağıdaki çağrı eşdeğerdir: + + printk("\0017Hello World...\n"); + + Ancak yukarıda da belirttiğimiz gibi bu makrolar üzerinde değişiklikler yapılabilmektedir. Dolayısıyla makroların kendisinin + kullanılması gerekir. Aslında KERN_XXX makroları ile printk fonksiyonunu kullanmak yerine pr_xxx makroları da kullanılabilir. Şöyle ki: @@ -52706,7 +52713,6 @@ clean: Aşağıda örnek bütünsel olarak verilmiştir. make işlemi şöyle yapılabilir: $ make file=helloworld - ---------------------------------------------------------------------------------------------------------------------------*/ /* helloworld.c */ @@ -52829,7 +52835,6 @@ clean: -r--r--r-- 1 root root 4096 Mar 22 21:25 srcversion -r--r--r-- 1 root root 4096 Mar 22 21:25 taint --w------- 1 root root 4096 Mar 22 21:22 uevent - ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -52839,7 +52844,7 @@ clean: Kernel modüllere parametre geçirme işlemi insmod ile modül yüklenirken komut satırında modül isminden sonra "değişken=değer" çiftleriyle yapılmaktadır. Örneğin: - sudo insmod helloworld.ko number=10 msg="\"This is a test\"" values=10,20,30,40,50 + $ sudo insmod helloworld.ko number=10 msg="\"This is a test\"" values=10,20,30,40,50 Bu örnekte number parametresi int bir değerden, msg parametresi ise bir yazıdan oluşmaktadır. values parametresi birden fazla int değerden oluşmaktadır. Bu tür parametrelere modülün dizi parametreleri denilmektedir. @@ -52847,7 +52852,7 @@ clean: Kernel modüllere geçirilen parametreleri modül içerisinde almak için module_param ve module_param_array isimli makrolar kullanılır. module_param makrosunun üç parametresi vardır: - module_param(name, type, perm) + module_param(name, type, perm); name parametresi ilgili değişkenin ismini belirtmektedir. Biz makroyu çağırmadan önce bu isimde bir global değişkeni tanımlamalıyız. Ancak buradaki değişken isminin komut satırında verilen parametre (argüman da diyebiliriz) ismi ile aynı olması gerekmektedir. @@ -52880,7 +52885,7 @@ clean: zorunlu hale getirmemektedir. Bu durumda bu parametreler default değerlerde kalacaktır. Yukarıdaki parametreleri helloworld modülüne aşağıdaki gibi geçirebiliriz: - sudo insmod helloworld.ko count=100 msg="\"this is a test\"" + $ sudo insmod helloworld.ko count=100 msg="\"this is a test\"" Burada neden iç içe tırnakların kullanıldığını merak edebilirsiniz. Kabuk üzerinde tırnaklar "boşluklarla ayrılmış olan yazıların tek bir komut satırı argümanı olarak ele alınacağını belirtmektedir. Ancak bizim ayrıca yazısal argümanları modüllere parametre @@ -52936,7 +52941,7 @@ clean: module_param_array makrosuyla bir diziye değer aktarırken değerlerin virgüllerle ayrılmış bir biçimde girilmesi gerekmektedir. Örneğin: - sudo insmod helloworld.ko values=1,2,3,4,5 + $ sudo insmod helloworld.ko values=1,2,3,4,5 Burada eğer verilen değerler dizinin uzunluğundan fazla olursa zaten modül yüklenmemektedir. Bu örnekte biz girilen değerlerin sayısını "size" nesnesinden alabiliriz. @@ -52945,12 +52950,11 @@ clean: değişken isimlerinin aynı olması gerektiğine dikkat ediniz. Yazıların geçirilmesinde iki tırnaklar kullanılır. Dizi geçirirken yanlışlıkla virgüllerin arasına boşluk karakterleri yerleştirmeyiniz. Programı şöyle make yapabilirsiniz: - make file=helloworld + $ make file=helloworld Yüklemeyi şöyle yapabilirsiniz: - sudo insmod helloworld.ko count=100 msg="\"this is a test\"" values=1,2,3,4,5 - + $ sudo insmod helloworld.ko count=100 msg="\"this is a test\"" values=1,2,3,4,5 ---------------------------------------------------------------------------------------------------------------------------*/ /* helloworld.c */ @@ -53061,6 +53065,8 @@ clean: return (long) ptr; } + Yani PTR_ERR makrosu bize aslında adres olarak kodlanmış olan negatif errno değerini geri döndürmektedir. + Pekiyi bir adres değerinin içerisinde errno hata kodunun olduğunu nasıl anlarız? İşte negatif errno değerleri bir adres gibi ele alındığında adeta adres alanının sonundaki adresler gibi bir görünümde olacaktır. errno değerleri için toplamda ayrılan sayılar da sınırlı olduğu için kontrol kolaylıkla yapılabilir. Ancak bu kontrol için IS_ERR isimli bir makro ya da @@ -53351,12 +53357,11 @@ void exit_sys(const char *msg) Örneğin: - sudo mknod devfile c 25 0 + $ sudo mknod devfile c 25 0 mknod komutunu sudo ile çalıştırmayı unutmayınız. Yukarıdaki komut uygulandığında oluşturulan dosya şöyle olacaktır: crw-rw-rw- 1 root root 25, 0 Mar 29 22:05 mydriver - ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -53477,16 +53482,16 @@ clean: için "slab allocator" denilen bir heap sistemi kullanılmaktadır.) Eğer bu yapı nesnesi programcı tarafından global bir biçimde tanımlanacaksa yapının elemanlarına ilk değer vermek için cdev_init fonksiyonu çağrılmalıdır. Eğer cdev yapısı cdev_alloc fonksiyonuyla dinamik bir biçimde tahsis edilecekse bu işlem cdev_init ile yapılmaz, çünkü zaten cdev_alloc bu işlemi de yapmaktadır. Fakat yine - de programcının bu kez manuel olarak bu yapının bazı elemanlarına değer ataması gerekir. Bu iki yoldan biriyle oluşturulmuş olan - cdev yapısının en sonunda cdev_add isimli fonksiyonla çekirdek veri yapılarına yerleştirilmeleri gerekir. Tabii aygıt sürücü - boşaltılırken bu yerleştirme işlemi cdev_del fonksiyonuyla geri alınmalıdır. cdev_del fonksiyonu, struct cdev yapısı cdev_alloc - ile tahsis edilmişse aynı zamanda onu free hale de getirmektedir. Özetle çekirdek modülümüzün tam bir karakter aygıt sürücüsü haline - getirilmesi için şunlar yapılmalıdır: + de programcının bu kez manuel olarak bu yapının bazı elemanlarına değer ataması gerekir. Bu iki yoldan biriyle oluşturulmuş + olan cdev yapısının en sonunda cdev_add isimli fonksiyonla çekirdek veri yapılarına yerleştirilmeleri gerekir. Tabii aygıt + sürücü boşaltılırken bu yerleştirme işlemi cdev_del fonksiyonuyla geri alınmalıdır. cdev_del fonksiyonu, struct cdev yapısı + cdev_alloc ile tahsis edilmişse aynı zamanda onu free hale de getirmektedir. Özetle çekirdek modülümüzün tam bir karakter aygıt + sürücüsü haline getirilmesi için şunlar yapılmalıdır: 1) struct cdev isimli bir yapı türünden nesne global olarak (statik ömürlü olarak) tanımlanmalı ya da cdev_alloc fonksiyonu ile çekirdeğin heap sistemi içerisinde tahsis edilmelidir. Eğer bu nesne global olarak tanımlanacaksa nesneye cdev_init fonksiyonu - ile ilk değerleri verilmelidir. Eğer nesne cdev_alloc fonksiyonu ile çekirdeğin heap alanında tahsis edilecekse bu durumda ilk değer - verme işlemi bu fonksiyon tarafından yapılmaktadır. Ancak programcının yine yapının bazı elemanlarını manuel olarak doldurması + ile ilk değerleri verilmelidir. Eğer nesne cdev_alloc fonksiyonu ile çekirdeğin heap alanında tahsis edilecekse bu durumda ilk + değer verme işlemi bu fonksiyon tarafından yapılmaktadır. Ancak programcının yine yapının bazı elemanlarını manuel olarak doldurması gerekmektedir. 2) Oluşturulan bu struct cdev nesnesi cdev_add çekirdek fonksiyonu ile çekirdeğe eklenmelidir. @@ -53614,7 +53619,7 @@ clean: kullanmaktadır. Dolayısıyla aşağıdaki programın testi için şöyle bir aygıt dosyasının yaratılmış olması gerekir. Yaratımı aşağıdaki gibi yapabilirsiniz: - sudo mknod mydriver -m=666 c 25 0 + $ sudo mknod mydriver -m=666 c 25 0 Bu aygıt sürücü insmod ile yüklendiğinde artık biz user mode'da "mydriver" dosyasını açıp kapattığımızda file_operations yapısına yerleştirdiğimiz generic_open ve generic_release fonksiyonları çağrılacaktır. @@ -53682,7 +53687,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -53801,7 +53806,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -53879,7 +53884,7 @@ void exit_sys(const char *msg) biz geçersiz bir user mode adresi verirsek bu sistem fonksiyonları da -EFAULT değeri ile geri dönmektedir. Bu hata kodunun yazısal karşılığı "Bad address" biçimindedir.) Örneğin: - if (copy_to_user(...) != 0) + if (copy_to_user(...) != 0) return -EFAULT; Bazen user alanındaki adresin zaten geçerliliği sınanmıştır. Bu durumda yeniden geçerlilik sınaması yapmadan yukarıdaki @@ -53905,11 +53910,10 @@ void exit_sys(const char *msg) Makrolar başarı durumunda 0, başarısızlık durumunda negatif hata koduna geri dönmektedir. Kullanım şöyle olabilir: - if (put_user(...) != 0) + if (put_user(...) != 0) return -EFAULT; - - Bu makroların da geçerlilik kontrolü - yapmayan __put_user ve __get_user isimli versiyonları vardır: + + Bu makroların da geçerlilik kontrolü yapmayan __put_user ve __get_user isimli versiyonları vardır: #include @@ -54016,6 +54020,7 @@ void exit_sys(const char *msg) static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off) { ... + *off += n; return n; @@ -54104,7 +54109,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -54273,7 +54278,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -54495,7 +54500,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -54746,7 +54751,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -54908,7 +54913,7 @@ void exit_sys(const char *msg) dev_t g_dev; ... - if ((result = alloc_chrdev_region(&g_dev, 0, 1, "generic-char-driver")) < 0) { + if ((result = alloc_chrdev_region(&g_dev, 0, 1, "generic-char-driver")) != 0) { printk(KERN_ERR "cannot register device!...\n"); return result; } @@ -55049,7 +55054,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "generic-char-driver-closed...\n"); + printk(KERN_INFO "generic-char-driver closed...\n"); return 0; } @@ -55239,7 +55244,7 @@ void exit_sys(const char *msg) Aygıt sürücünüzü önce build edip sonra aşağıdaki gibi yüklemelisiniz: - $ make file=pipe-dirver + $ make file=pipe-driver $ sudo ./load pipe-driver Buradaki boru aygıt sürücüsünü test etmek için "prog1" ve "prog2" isimli iki program yazılmıştır. "prog1" klavyeden @@ -55495,7 +55500,7 @@ int main(void) printf("Size:"); scanf("%d", &size); if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); + printf("size is very long!...\n"); continue; } if (size == 0) @@ -55713,7 +55718,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -55770,15 +55775,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_pipebuf + g_tail, buf, size1) != 0) { - mutex_unlock(&g_mutex); - return -EFAULT; - } + mutex_unlock(&g_mutex); + return -EFAULT; + } if (size2 != 0) if (copy_from_user(g_pipebuf, buf + size1, size2) != 0) { - mutex_unlock(&g_mutex); - return -EFAULT; - } + mutex_unlock(&g_mutex); + return -EFAULT; + } g_tail = (g_tail + esize) % PIPE_BUFSIZE; g_count += esize; @@ -55898,7 +55903,7 @@ int main(void) printf("Size:"); scanf("%d", &size); if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); + printf("size is very long!...\n"); continue; } if (size == 0) @@ -56078,7 +56083,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -56100,15 +56105,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_pipebuf + g_head, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_pipebuf, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_head = (g_head + esize) % PIPE_BUFSIZE; g_count -= esize; @@ -56135,15 +56140,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_pipebuf + g_tail, buf, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(g_pipebuf, buf + size1, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_tail = (g_tail + esize) % PIPE_BUFSIZE; g_count += esize; @@ -56263,7 +56268,7 @@ int main(void) printf("Size:"); scanf("%d", &size); if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); + printf("size is very long!...\n"); continue; } if (size == 0) @@ -56474,7 +56479,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -56495,15 +56500,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_pipebuf + g_head, size1) != 0) { - spin_unlock(&g_spinlock); + spin_unlock(&g_spinlock); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_pipebuf, size2) != 0) { - spin_unlock(&g_spinlock); - return -EFAULT; - } + spin_unlock(&g_spinlock); + return -EFAULT; + } g_head = (g_head + esize) % PIPE_BUFSIZE; g_count -= esize; @@ -56529,15 +56534,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_pipebuf + g_tail, buf, size1) != 0) { - spin_unlock(&g_spinlock); + spin_unlock(&g_spinlock); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(g_pipebuf, buf + size1, size2) != 0) { - spin_unlock(&g_spinlock); - return -EFAULT; - } + spin_unlock(&g_spinlock); + return -EFAULT; + } g_tail = (g_tail + esize) % PIPE_BUFSIZE; g_count += esize; @@ -56657,7 +56662,7 @@ int main(void) printf("Size:"); scanf("%d", &size); if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); + printf("size is very long!...\n"); continue; } if (size == 0) @@ -57240,13 +57245,13 @@ void exit_sys(const char *msg) variable)" denilen senkronizasyon nesnelerindeki yöntemin aynısı olmalıdır. Okuyan thread kuyruktaki byte sayısını belirten g_count == 0 olduğu sürece bekleme kuyruğunda beklemelidir. Tabii bizim kuyruk üzerinde işlem yaptığımız kısımları senkronize etmemiz gerekir. Bunu da bir binary semaphore nesnesi ya da mutex nesnesi yapabiliriz. Semaphore nesnesini ve bekleme kuyruğunu - aşağdaki gibi yaratabiliriz: + aşağıdaki gibi yaratabiliriz: static wait_queue_head_t g_wq; DEFINE_SEMAPHORE(g_sem); Okuyan taraf önce semaphore kilidini eline almalı ancak eğer uykuya dalacaksa onu serbest bırakıp uykuya dalmalıdır. Kuyruk - üzerinde aynı anda işlemler yapılabiceği için tüm işlemlerin kritik kod içerisinde yapılması uygun olur. O halde read işleminin + üzerinde aynı anda işlemler yapılabileceği için tüm işlemlerin kritik kod içerisinde yapılması uygun olur. O halde read işleminin tipik çatısı şöyle olmalıdır: ... @@ -57404,7 +57409,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -57437,15 +57442,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_pipebuf + g_head, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_pipebuf, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_head = (g_head + esize) % PIPE_BUFSIZE; g_count -= esize; @@ -57483,15 +57488,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_pipebuf + g_tail, buf, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(g_pipebuf, buf + size1, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_tail = (g_tail + esize) % PIPE_BUFSIZE; g_count += esize; @@ -57602,39 +57607,39 @@ void exit_sys(const char *msg); int main(void) { - int pdriver; - char buf[BUFFER_SIZE + 1]; - int size; - ssize_t result; + int pdriver; + char buf[BUFFER_SIZE + 1]; + int size; + ssize_t result; - if ((pdriver = open("pipe-driver", O_RDONLY)) == -1) - exit_sys("open"); + if ((pdriver = open("pipe-driver", O_RDONLY)) == -1) + exit_sys("open"); - for (;;) { - printf("Size:"); - scanf("%d", &size); - if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); - continue; - } - if (size == 0) - break; - if ((result = read(pdriver, buf, size)) == -1) - exit_sys("read"); - buf[result] = '\0'; - printf("%jd bytes read: %s\n", (intmax_t)result, buf); - } + for (;;) { + printf("Size:"); + scanf("%d", &size); + if (size > BUFFER_SIZE) { + printf("size is very long!...\n"); + continue; + } + if (size == 0) + break; + if ((result = read(pdriver, buf, size)) == -1) + exit_sys("read"); + buf[result] = '\0'; + printf("%jd bytes read: %s\n", (intmax_t)result, buf); + } - close(pdriver); + close(pdriver); - return 0; + return 0; } void exit_sys(const char *msg) { - perror(msg); + perror(msg); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } /*-------------------------------------------------------------------------------------------------------------------------- @@ -57645,9 +57650,9 @@ void exit_sys(const char *msg) Aslında bekleme kuyrukları wait_queue_entry isimli yapı nesnelerinden oluşan bir çift bağlı listedir. wait_queue_head_t yapısı da bağlı listenin ilk ve son elemanlarının adresini tutmaktadır: - wait_queue_head ---> wait_queue_entry <-----> wait_queue_entry <-----> wait_queue_entry <-----> wait_queue_entry ... + wait_queue_head <-----> wait_queue_entry <-----> wait_queue_entry <-----> wait_queue_entry <-----> wait_queue_entry ... - Çekirdek kodlarında bu yapılar "include/linux/wait.h" dosyası içersinde aşağıdaki gibi bildirilmiştir: + Çekirdek kodlarında bu yapılar "include/linux/wait.h" dosyası içerisinde aşağıdaki gibi bildirilmiştir: struct wait_queue_head { spinlock_t lock; @@ -57664,7 +57669,7 @@ void exit_sys(const char *msg) /*-------------------------------------------------------------------------------------------------------------------------- Biz aygıt sürücü kodumuzda o anda quanta süresini bırakıp çizelgeleyicinin kendi algortimasına göre sıradaki thread'i - çizelgelemesini sağlayabiliriz. Bunun schedule isimli fonksiyon kullanılmaktadır. Bu fonksiyon bloke oluşturmamaktadır. + çizelgelemesini sağlayabiliriz. Bunun için schedule isimli fonksiyon kullanılmaktadır. Bu fonksiyon bloke oluşturmamaktadır. Yalnızca thread'ler arası geçiş (context switch) oluşturmaktadır. Fonksiyon herhangi bir parametre almamaktadır: #include @@ -57689,7 +57694,7 @@ void exit_sys(const char *msg) DEFINE_WAIT(wqentry); - Ya da açıkça tanımlanıp init_wait makrosuyle ilk değerlenebilir. Örneğin: + Ya da açıkça tanımlanıp init_wait makrosuyla ilk değerlenebilir. Örneğin: struct wait_queue_entry wqentry; ... @@ -57698,16 +57703,16 @@ void exit_sys(const char *msg) DEFINE_WAIT makrosu global tanımlamalarda kullanılamamktadır. Çünkü bu makro küme parantezleri içerisinde sabit ifadesi olmayan ifadeler barındırmaktadır. Ancak makro yerel tanımlamalarda kullanılabilir. - Dolayısıyla prepare_to_wait ve prepare_to_wait_exclusive fonksiyonları da aslında bekleme kuruğuna bir wait_queue_enty nesnesi + Dolayısıyla prepare_to_wait ve prepare_to_wait_exclusive fonksiyonları da aslında bekleme kuyruğuna bir wait_queue_entry nesnesi eklemektedir. Yani programcının bunun için yeni bir wait_queue_entry nesnesi oluşturması gerekmektedir. prepare_to_wait_exclusive exclusive uyuma için kullanılmaktadır. prepare_to_wait ve prepare_to_wait_exclusive fonksiyonları şunları yapmaktadır: - 1) Thread'i çalışma kuyruğundan çıkartıp bekleme kuyruğuna yerleştirir. (Çalışma kuruğunun organizasyonu ve bu işlemin gerçek + 1) Thread'i çalışma kuyruğundan çıkartıp bekleme kuyruğuna yerleştirir. (Çalışma kuyruğunun organizasyonu ve bu işlemin gerçek ayrıntıları biraz karmaşıktır.) 2) Thread'in durum bilgisini (task state) state parametresiyle belirtilen duruma çeker. - 3) prepare_to_wait fonksiyonu kuyruk elemanını eclusive olmaktan çıkartırken, prepare_to_wait_exclusive onu exclusive yapar. + 3) prepare_to_wait fonksiyonu kuyruk elemanını exclusive olmaktan çıkartırken, prepare_to_wait_exclusive onu exclusive yapar. Thread'in çalışma kuyruğundan bekleme kuyruğuna aktarılması onun uykuya dalması anlamına gelmemektedir. Programcı artık thread çalışma kuyruğunda olmadığına göre schedule fonksiyonu ile thread'ler arası geçiş (context switch) uygulamalı ve akış kontrolünü @@ -57716,11 +57721,11 @@ void exit_sys(const char *msg) Tabii biz prepare_to_wait ya da prepare_to_wait_exclusive fonksiyonlarını çağırdıktan sonra bir biçimde koşul durumuna bakmalıyız. Eğer koşul sağlanmışsa hiç prosesi uykuya daldırmadan hemen bekleme kuyruğundan çıkarmalıyız. Eğer koşul sağlanmamışsa gerçekten - artık schedule fonksiyonuyla "thread'lerarası geçiş" yapmalıyız. Thread'imiz schedule fonksiyonunu çağırdıktan sonra artık - uyandırılana kadar bir daha çizelgelenmyecektir. Bu da bizim uykuya dalmamız anlamına gelmektedir. + artık schedule fonksiyonuyla "thread'ler arası geçiş" yapmalıyız. Thread'imiz schedule fonksiyonunu çağırdıktan sonra artık + uyandırılana kadar bir daha çizelgelenmeyecektir. Bu da bizim uykuya dalmamız anlamına gelmektedir. Pekiyi thread'imiz uyandırıldığında nereden çalışmaya devam edecektir? İşte schedule fonksiyonu thread'ler arası geçiş yaparken - kalınan yeri thread'e ilişki task_struct yapısının içerisine kaydetmektedir. Kalının yer schudule fonksiyonunun içerisinde bir yerdir. + kalınan yeri thread'e ilişkin task_struct yapısının içerisine kaydetmektedir. Kalınan yer schedule fonksiyonunun içerisinde bir yerdir. O halde thread'imiz uyandırıldığında schedule fonksiyonunun içerisinden çalışma devam eder. schedule fonksiyonu geri dönecek ve thread akışı devam edecektir. @@ -57745,7 +57750,7 @@ void exit_sys(const char *msg) Tabii eğer thread INTERRUPTIBLE olarak uyuyorsa schedule fonksiyonundan çıkıldığında sinyal dolayısıyla da çıkılmış olabilir. Bunu anlamak için signal_pending isimli fonksiyon çağrılır. Bu fonksiyon sıfır dışı bir değerle geri dönmüşse uyandırma işleminin - sinyal yoluyla yapıldığı anlaşılır. Bu durumda tabii aygıt sürücüdeki fonksiyon bu durumda -ERESTARTSYS ile geri döndürülmelidir. + sinyal yoluyla yapıldığı anlaşılır. Bu durumda tabii aygıt sürücüdeki fonksiyon -ERESTARTSYS ile geri döndürülmelidir. signal_pending fonksiyonunun prototipi şöyledir: #include @@ -57764,13 +57769,12 @@ void exit_sys(const char *msg) return -ERESTARTSYS; finish_wait(&wqentry); - wake_up makrolarının şunları yaptıpını anımsayınız: + wake_up makrolarının şunları yaptığını anımsayınız: 1) Wait kuyruğundaki prosesleri çıkartarak run kuyruğuna yerleştirir. 2) Prosesin durumunu TASK_RUNNING haline getirir. - Aşağıda boru örneğinde manuel uykuya dalma işlemi uygulamıştır. - + Aşağıdaki boru örneğinde manuel uykuya dalma işlemi uygulanmıştır. ---------------------------------------------------------------------------------------------------------------------------*/ /* pipe-driver-manual-wait.c */ @@ -57819,7 +57823,7 @@ static int __init generic_init(void) { int result; - printk(KERN_INFO "pipe-driver module initialization...\n"); + printk(KERN_INFO "pipe-driver-manual-wait module initialization...\n"); if ((result = alloc_chrdev_region(&g_dev, 0, 1, "pipe-driver-manual-wait")) < 0) { printk(KERN_INFO "Cannot alloc char driver!...\n"); @@ -57851,19 +57855,19 @@ static void __exit generic_exit(void) cdev_del(g_cdev); unregister_chrdev_region(g_dev, 1); - printk(KERN_INFO "pipe-driver module exit...\n"); + printk(KERN_INFO "pipe-driver-manual-wait module exit...\n"); } static int generic_open(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver opened...\n"); + printk(KERN_INFO "pipe-driver-manual-wait opened...\n"); return 0; } static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver-manual-wait closed...\n"); return 0; } @@ -57871,7 +57875,7 @@ static int generic_release(struct inode *inodep, struct file *filp) static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off) { size_t esize, size1, size2; - DEFINE_WAIT(wqentry); + DEFINE_WAIT(wqentry); if (size == 0) return 0; @@ -57882,10 +57886,10 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o while (g_count == 0) { up(&g_sem); - prepare_to_wait(&g_wqread, &wqentry, TASK_INTERRUPTIBLE); - schedule(); - if (signal_pending(current)) - return -ERESTARTSYS; + prepare_to_wait(&g_wqread, &wqentry, TASK_INTERRUPTIBLE); + schedule(); + if (signal_pending(current)) + return -ERESTARTSYS; if (down_interruptible(&g_sem)) return -ERESTARTSYS; @@ -57901,15 +57905,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_pipebuf + g_head, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_pipebuf, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_head = (g_head + esize) % PIPE_BUFSIZE; g_count -= esize; @@ -57924,7 +57928,7 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off) { size_t esize, size1, size2; - DEFINE_WAIT(wqentry); + DEFINE_WAIT(wqentry); if (down_interruptible(&g_sem)) return -ERESTARTSYS; @@ -57932,10 +57936,10 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo while (PIPE_BUFSIZE - g_count < size) { up(&g_sem); - prepare_to_wait(&g_wqwrite, &wqentry, TASK_INTERRUPTIBLE); - schedule(); - if (signal_pending(current)) - return -ERESTARTSYS; + prepare_to_wait(&g_wqwrite, &wqentry, TASK_INTERRUPTIBLE); + schedule(); + if (signal_pending(current)) + return -ERESTARTSYS; if (down_interruptible(&g_sem)) return -ERESTARTSYS; @@ -57951,15 +57955,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_pipebuf + g_tail, buf, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(g_pipebuf, buf + size1, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_tail = (g_tail + esize) % PIPE_BUFSIZE; g_count += esize; @@ -58081,7 +58085,7 @@ int main(void) printf("Size:"); scanf("%d", &size); if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); + printf("size is very long!...\n"); continue; } if (size == 0) @@ -58104,6 +58108,10 @@ void exit_sys(const char *msg) exit(EXIT_FAILURE); } +/*-------------------------------------------------------------------------------------------------------------------------- + 143. Ders 26/05/2024 - Pazar +---------------------------------------------------------------------------------------------------------------------------*/ + /*-------------------------------------------------------------------------------------------------------------------------- Aslında wait_event fonksiyonları yukarıda açıkladığımız daha aşağı seviyeli fonksiyonlar kullanılarak gerçekleştirilmiştir. Mevcut son Linux çekirdeğinde wait_event_interruptible fonksiyonu şöyle yazılmıştır: @@ -58158,31 +58166,31 @@ void exit_sys(const char *msg) için open POSIX fonksiyonunda fonksiyonun ikinci parametresine O_NONBLOCK bayrağının eklenmelidir. Normal disk dosyalarında O_NONBLOCK bayrağının bir anlamı yoktur. Ancak boru gibi özel dosyalarda ve aygıt sürücülerde bu bayrak şu anlama gelmektedir: - 1) Okuma sırasında eğer okunacak bir bilgi yoksa read fonksiyonu bloke oluşturmaz başarısızlıkla geri döner ve errno değeri + 1) Okuma sırasında eğer okunacak bir bilgi yoksa read fonksiyonu bloke oluşturmaz, başarısızlıkla geri döner ve errno değeri EAGAIN olarak set edilir. - 2) Yazma sırasında yazma eylemi meşguliyet yüzünden yapılamıyorsa write fonksiyonu bloke oluşturmaz başarısızlıkla geri döner + 2) Yazma sırasında yazma eylemi meşguliyet yüzünden yapılamıyorsa write fonksiyonu bloke oluşturmaz, başarısızlıkla geri döner ve errno değeri yine EAGAIN olarak set edilir. - Aygıt sürücü açıldığında open fonksiyonun ikinci parametresi file yapısının (dosya nesnesinin) f_flags elemanına - yerleştirilmektedir. Dosya nesnesinin adresinin aygıt sürücüdeki fonksiyonları filp parametresiyle aktarıldığını anımsayınız. + Aygıt sürücü açıldığında open fonksiyonunun ikinci parametresi file yapısının (dosya nesnesinin) f_flags elemanına + yerleştirilmektedir. Dosya nesnesinin adresinin aygıt sürücüdeki fonksiyonlara filp parametresiyle aktarıldığını anımsayınız. Bu durumda biz aygıt dosyasının blokesiz modda açılıp açılmadığını şöyle test edebiliriz: if (filp->f_flags & O_NONBLOCK) { /* blokesiz modda mı açılmış */ - /* open fonksiyonunda aygıt O_NONBLOCK bayrağı ile açılmış */ + /* open fonksiyonunda aygıt O_NONBLOCK bayrağı ile açılmış */ } Aygıt sürücümüz blokesiz modda işlemlere izin vermiyorsa biz bu durumu kontrol etmeyebiliriz. Yani böyle bir aygıt sürücüde - programcının aygıt sürücüyğ O_NONBLOCK bayrağını kullanarak açmışsa bu durumu hiç dikkate almayabiliriz. (Örneğin disk dosyalarında + programcı aygıt sürücüyü O_NONBLOCK bayrağını kullanarak açmışsa bu durumu hiç dikkate almayabiliriz. (Örneğin disk dosyalarında blokesiz işlemlerin bir anlamı olmadığı halde Linux çekirdeği disk dosyaları O_NONBLOCK bayrağıyla açıldığında hata ile geri dönmeden bayrağı dikkate almamaktadır.) Eğer bu kontrol yapılmak isteniyorsa aygıt sürücünün açılması sırasında kontrol - aygıt sürücünün open fonksiyonund yapılabilir. Bu durumda open fonksiyonunu -EINVAL değeriyşe geri döndürebilirsiniz. Örneğin: + aygıt sürücünün open fonksiyonunda yapılabilir. Bu durumda open fonksiyonunu -EINVAL değeriyle geri döndürebilirsiniz. Örneğin: static int generic_open(struct inode *inodep, struct file *filp) { - if (filp->f_flas & O_NONBLOK) + if (filp->f_flags & O_NONBLOCK) return -EINVAL; return 0; @@ -58190,11 +58198,11 @@ void exit_sys(const char *msg) Pekiyi boru aygıt sürücümüze nasıl blokesiz mod desteği verebiliriz? Aslında bunun için iki şeyi yapmamız gerekir: - 1) Yazma yapıldığı zaman boruda yazılanları alacak kadar yoksa aygıt sürücümüzün write fonksiyonunu -EAGAIN değeriyşe + 1) Yazma yapıldığı zaman boruda yazılanları alacak kadar yer yoksa aygıt sürücümüzün write fonksiyonunu -EAGAIN değeriyle geri döndürmeliyiz. Örneğin: ... - if (down_interruptible(&g_sem)) + if (down_interruptible(&g_sem)) return -ERESTARTSYS; while (PIPE_BUFSIZE - g_count < size) { @@ -58211,11 +58219,11 @@ void exit_sys(const char *msg) } ... - 2) Okuma yapıldığı zaman eğer boruda hiç bilgi yoksa aygıt sürücümüzün read fonksiyonunu -EAGAIN geri döndürmeliyiz. + 2) Okuma yapıldığı zaman eğer boruda hiç bilgi yoksa aygıt sürücümüzün read fonksiyonunu -EAGAIN değeriyle geri döndürmeliyiz. Örneğin: ... - if (down_interruptible(&g_sem)) + if (down_interruptible(&g_sem)) return -ERESTARTSYS; while (g_count == 0) { @@ -58232,10 +58240,10 @@ void exit_sys(const char *msg) } ... - read ve write fonksiyonlarının -EAGAIN değeriyle geri döndürülmeden önce aygıt dosyasının blokesiz modda açlıp açılmadığının + read ve write fonksiyonlarının -EAGAIN değeriyle geri döndürülmeden önce aygıt dosyasının blokesiz modda açılıp açılmadığının kontrol edilmesi gerektiğine dikkat ediniz. - Aşaşğıdaki örnekte boru aygıt sürücüsüne blokesiz okuma ve yazma desteği verilmiştir. + Aşağıdaki örnekte boru aygıt sürücüsüne blokesiz okuma ve yazma desteği verilmiştir. ---------------------------------------------------------------------------------------------------------------------------*/ /* pipe-driver.c */ @@ -58328,7 +58336,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -58365,15 +58373,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_pipebuf + g_head, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_pipebuf, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_head = (g_head + esize) % PIPE_BUFSIZE; g_count -= esize; @@ -58415,15 +58423,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_pipebuf + g_tail, buf, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(g_pipebuf, buf + size1, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_tail = (g_tail + esize) % PIPE_BUFSIZE; g_count += esize; @@ -58551,7 +58559,7 @@ int main(void) printf("Size:"); scanf("%d", &size); if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); + printf("size is very long!...\n"); continue; } if (size == 0) @@ -58579,7 +58587,7 @@ void exit_sys(const char *msg) } /*-------------------------------------------------------------------------------------------------------------------------- - 144. Ders 31/05/2024 - Cuma + 144. Ders 31/05/2024 - Cuma ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -58592,21 +58600,21 @@ void exit_sys(const char *msg) /*-------------------------------------------------------------------------------------------------------------------------- Anımsanacağı gibi Linux sistemlerinde proseslerin bellek alanları sayfa tabloları yoluyla izole edilmişti. Ancak çekirdek - tüm proseslerin sayfa tablosunda aynı yerde bulunmaktadır. Başka bir deyişle her prosesin sayfa tablosunda çekirdek hep aynısanal - adreslerde bulunmaktadır. Örneğin sys_open sistem fonksiyonuna girildiğinde bu fonksiyonun sanal adresi her proseste aynıdır. + tüm proseslerin sayfa tablosunda aynı yerde bulunmaktadır. Başka bir deyişle her prosesin sayfa tablosunda çekirdek hep aynı sanal + adreslerde bulunmaktadır. Örneğin sys_open sistem fonksiyonuna girildiğinde bu fonksiyonun sanal adresi her proseste aynıdır. 32 Bit linux sistemlerinde proseslerin sanal bellek alanları 3GB User, 1GB Kernel olmak üzere 2 bölüme ayrılmıştır. 64 bir Linux sistemlerinde ise yalnızca sanal bellek alanının 256 TB'si kullanılmaktadır. Bu sistemlerde user alanı için 128 TB, Kernel alanı için de 128 TB yer ayrılmıştır. 32 Bit Linux sistemlerindeki prosesin sanal bellek alanı şöyle gösterilebilir: - 00000000 + 00000000 USER ALANI (3 GB) - C0000000 + C0000000 KERNEL ALANI (1GB) 64 Bit Linux sistemlerindeki sanal bellek alanı ise kabaca şöyledir: - 0000000000000000 + 0000000000000000 USER ALANI (128 TB) 0000800000000000 BOŞ BÖLGE (yaklaşık 16M TB) @@ -58629,10 +58637,10 @@ void exit_sys(const char *msg) fiziksel RAM'in ilk 896MB'sine doğrudan ancak bunun ötesine sayfa tablosunun son 128 MB'lik bölgesini değiştirerek erişmektedir. 32 bit sistemlerde 896MB'nin ötesine dolaylı biçimde erişildiği için bu bölgeye "high memory zone" denilmektedir. Tabii 64 bit sistemlerde böyle bir problem yoktur. Çünkü bu sistemlerde yine sayfa tablolarının kernel alanı fiziksel RAM'i başından itibaren - haritlandırmaktadır. Ancak 128TB'lik alan zaten şimdiki bilgisayarlara takılabilecek fiziksel RAM'in çok ötesindedir. Bu nedenle - 64 bit sistemlerde "high memory zone" kavramı yoktur. + haritalandırmaktadır. Ancak 128TB'lik alan zaten şimdiki bilgisayarlara takılabilecek fiziksel RAM'in çok ötesindedir. Bu nedenle + 64 bit sistemlerde "high memory zone" kavramı yoktur. - Çekirdek kodların kernel alanın başlangıcı PAGE_OFFSET makrosuyla belirlenmiştir. + Çekirdek kodların kernel alanın başlangıcı PAGE_OFFSET makrosuyla belirlenmiştir. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -58642,7 +58650,7 @@ void exit_sys(const char *msg) için de __pa makrosu kullanılmaktadır. Biz bu makroya sanal adresi veririz o da bize o sanal adresin aslında RAM'deki hangi fiziksel adres olduğunu verir. __va makrosu parametre olarak unsigned long biçiminde fiziksel adresi alır, o fiziksel adrese erişmek için gerekli olan sanal adresi void * türünden bize verir. __pa makrosu bunun tam tersini yapmaktadır. Bu makro bizden - unsigned long biçiminde sanal adresi alır. O sanal adrese sayfa tablo tablosunda karşı gelen fiziksel adresi bize verir. + unsigned long biçiminde sanal adresi alır. O sanal adrese sayfa tablo tablosunda karşı gelen fiziksel adresi bize verir. Kernel mode'da RAM'in her yerine erişebildiğimize ve bu konuda bizi engelleyen hiçbir mekanizmanın olmadığına dikkat ediniz. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -58668,23 +58676,23 @@ void exit_sys(const char *msg) bu bağlı listede uygun bir elemanı bağlı listeyi dolaşarak bulmaktadır. free fonksiyonu da tahsis edilmiş olan alanı bu boş bağlı listeye eklemektedir. Tabii free fonksiyonu aynı zamanda bağlı listedeki komşu alanları da daha büyük bir boş alan oluşturacak biçimde birleştirmektedir. Ancak bu klasik yöntem çekirdek heap sistemi için çok yavaş kalmaktadır. Bu nedenle - çekirdeğin heap sistemi için hızlı çalışan tahsisat algoritmaları kullanılmaktadır. + çekirdeğin heap sistemi için hızlı çalışan tahsisat algoritmaları kullanılmaktadır. Eğer tahsis edilecek bloklar eşit uzunlukta olursa bu durumda tahsisat işlemi ve geri bırakmak işlemi O(1) karmaşıklıkta yapılabilir. Örneğin heap içerisindeki tüm blokların 16 byte uzunlukta olduğunu düşünelim. Bu durumda 16 byte'lık tahsisat sırasında uygun bir boş alan aramaya gerek kalmaz. Bir dizisi içerisinde boş alanlar tutulabilir. Bu boş alanlardan herhangi - biri verilebilir. Tabii uygulamalarda tahsis edilecek alanların büyükleri farklı olmaktadır. - + biri verilebilir. Tabii uygulamalarda tahsis edilecek alanların büyükleri farklı olmaktadır. + İşte BSD ve Linux sistemlerindekullanılan "dilimli tahsisat sistemi (slab allocator)" denilen tahsisat sisteminin anahtar noktası eşit uzunlukta ismine "dilim (slab)" denilen blokların tahsis edilmesidir. Kernel içerisinde çeşitli nesneler için o nesnelerin uzunluğuna ilişkin farklı dilimli tahsisat sistemleri oluşturulmuştur. Örneğin bir proses yaratıldığında task_struct yapısı çekirdeğin heap alanında tahsis edilmektedir. İşte dilimli tahsisat sistemlerinden biri sizeof(struct task_struct) kadar - dilimlerdne oluşan sistemdir. Böylece pek çok kernel nesnesi için ayrı dilimli tahisat sistemleri luşturulmuştur. Bunların + dilimlerdne oluşan sistemdir. Böylece pek çok kernel nesnesi için ayrı dilimli tahisat sistemleri oluşturulmuştur. Bunların yanı sıra ayrıca bir de genel kullanım için blok uzunlukları 32, 64, 96, 128, 192, 256 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, ... biçiminde olan farklı dilimli tahsisat sistemleri de bulundurulmuştur. Böylece kernek mod programcısı belli uzunlukta bir alan tahsis etmek istediğinde bu uzunlupa en yakın bu uzunluktan büyük bir dilimli tahsisat sistemini kullanır. Tabii kernel mode programcılar isterse kendi nesneleri için o nesnelerin uzunluğu kadar yeni dilimli tahsisat - sistemleri de oluşturabilmektedir. + sistemleri de oluşturabilmektedir. Aslında dilimli tahsisat sisteminin hazırda bulundurduğu dilimler işletim sisteminin sayfa tahsisatı yapan başka bir tahsisat algoritmasından elde edilmektedir. Linux sistemlerinde sayfa temelinde tahsisat yapmak için kullanılan tahsisat sistemine @@ -58719,12 +58727,12 @@ void exit_sys(const char *msg) kmalloc fonksiyonu başarı durumunda tahsis edilen alanın sanal bellek adresiyle başarısızlık durumunda NULL adresle geri dönmektedir. Çekirdek modülleri ve aygıt sürücüler dinamik tahsisat başarısız olursa tipik olarak -ENOMEM değerine geri - dönmelidir. - - kfree fonksiyonu ise daha önce kmalloc ile tahsis edilmiş olan alanın başlangıç adresini parametre olarak almaktadır. + dönmelidir. + + kfree fonksiyonu ise daha önce kmalloc ile tahsis edilmiş olan alanın başlangıç adresini parametre olarak almaktadır. Aşağıda daha önce yapmış olduğumuz boru aygıt sürücüsündeki kuyruk sistemini kmalloc fonksiyonu ile tahsis edilip kfree - fonksiyonu ile serbest bırakılmasına örnek verilmiştir. + fonksiyonu ile serbest bırakılmasına örnek verilmiştir. ---------------------------------------------------------------------------------------------------------------------------*/ /* pipe-driver.c */ @@ -58829,7 +58837,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -58866,15 +58874,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_queue->pipebuf + g_queue->head, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_queue->pipebuf, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_queue->head = (g_queue->head + esize) % PIPE_BUFSIZE; g_queue->count -= esize; @@ -58916,15 +58924,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_queue->pipebuf + g_queue->tail, buf, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(g_queue->pipebuf, buf + size1, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_queue->tail = (g_queue->tail + esize) % PIPE_BUFSIZE; g_queue->count += esize; @@ -59035,43 +59043,43 @@ void exit_sys(const char *msg); int main(void) { - int pdriver; - char buf[BUFFER_SIZE + 1]; - int size; - ssize_t result; + int pdriver; + char buf[BUFFER_SIZE + 1]; + int size; + ssize_t result; - if ((pdriver = open("pipe-driver", O_RDONLY)) == -1) - exit_sys("open"); + if ((pdriver = open("pipe-driver", O_RDONLY)) == -1) + exit_sys("open"); - for (;;) { - printf("Size:"); - scanf("%d", &size); - if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); - continue; - } - if (size == 0) - break; - if ((result = read(pdriver, buf, size)) == -1) - exit_sys("read"); - buf[result] = '\0'; - printf("%jd bytes read: %s\n", (intmax_t)result, buf); - } + for (;;) { + printf("Size:"); + scanf("%d", &size); + if (size > BUFFER_SIZE) { + printf("size is very long!...\n"); + continue; + } + if (size == 0) + break; + if ((result = read(pdriver, buf, size)) == -1) + exit_sys("read"); + buf[result] = '\0'; + printf("%jd bytes read: %s\n", (intmax_t)result, buf); + } - close(pdriver); + close(pdriver); - return 0; + return 0; } void exit_sys(const char *msg) { - perror(msg); + perror(msg); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } /*-------------------------------------------------------------------------------------------------------------------------- - 145. Ders 02/06/2024 - Pazar + 145. Ders 02/06/2024 - Pazar ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -59090,25 +59098,25 @@ void exit_sys(const char *msg) için kullanılmaktadır. Buradaki bayrakların önemli birkaç tanesi şöyledir: SLAB_NO_REAP: Fiziksel RAM'in dolması nedeniyle kullanılmayan dilimlerin otomatik olarak sisteme iade edileceği anlamına gelir. - Uç durumlarda bu bayrak kulanılabilir. - + Uç durumlarda bu bayrak kulanılabilir. + SLAB_HWCACHE_ALIGN: Bu bayrak özellikle SMP sistemlerinde işlemci ya da çekirdeklerin cache alanları için hizalama yapılmasının sağlamaktadır. Yaratım sırasında bu parametreyi kullanabilirsiniz. - SLAB_CACHE_DMA: Bu parametre DMA alanında (DMA zone) tahsisat için kullanılmaktadır. - + SLAB_CACHE_DMA: Bu parametre DMA alanında (DMA zone) tahsisat için kullanılmaktadır. + Fonksiyonun son parametresi dilim sistemi yaratıldığında çağrılacak callback fonksiyonu belirtmektedir. Bu parametre NULL geçilebilir. Fonksiyon başarı durumunda kmem_cache_create fonksiyonu kmem_cache türünden bir yapı nesnesinin adresiyle başarısızlık durumunda NULL adrese geri dönmektedir. Başarısızlık durumunda aygıt sürücü fonksiyonunun -ENOMEM değeri ile - geri döndürülmesi uygundur. - + geri döndürülmesi uygundur. + Örneğin: - if ((g_queue_cachep = kmem_cache_create("pipe-driver-cachep", sizeof(struct QUEUE), 0, SLAB_HWCACHE_ALIGN, NULL)) == NULL) { + if ((g_queue_cachep = kmem_cache_create("pipe-driver-cachep", sizeof(struct QUEUE), 0, SLAB_HWCACHE_ALIGN, NULL)) == NULL) { ... - return -ENOMEM; + return -ENOMEM; } - + Yaratılmış olan bir dilim sisteminden tahsisatlar kmem_cache_alloc fonksiyonu ile yapılmaktadır. Fonksiyonun parametresi şöyledir: @@ -59133,7 +59141,7 @@ void exit_sys(const char *msg) void kmem_cache_free(kmem_cache_t *cache, const void *obj); - Fonksiyonun birinci parametresi dilim sisteminin handle değerini, ikincisi parametresi ise serbest bırakılacak dilimin + Fonksiyonun birinci parametresi dilim sisteminin handle değerini, ikincisi parametresi ise serbest bırakılacak dilimin adresini belirtmektedir. Örneğin: kmem_cache_free(g_queue_cachep, g_queue); @@ -59155,7 +59163,7 @@ void exit_sys(const char *msg) Ancak "çok sayıda aynı büyüklükte alanların" tahsis edildiği durumlarda programcının talep ettiği uzunlukta kendi dilim sistemini yaratması tavsiye edilebilir. Bunun dışında genel amaçlı kmalloc fonksiyonu tercih edilebilir. Örneğin boru aygıt sürücümüzde yeni bir dilim sisteminin yaratılmasına hiç gerek yoktur. Ancak bir aşağıda örnek vermek amacıyla - boru aygıt sürücünde yeni bir dilim sistemi yarattık. Örneği inceleyiniz. + boru aygıt sürücünde yeni bir dilim sistemi yarattık. Örneği inceleyiniz. ---------------------------------------------------------------------------------------------------------------------------*/ /* pipe-driver.c */ @@ -59270,7 +59278,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -59307,15 +59315,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, g_queue->pipebuf + g_queue->head, size1) != 0) { - up(&g_sem); + up(&g_sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, g_queue->pipebuf, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_queue->head = (g_queue->head + esize) % PIPE_BUFSIZE; g_queue->count -= esize; @@ -59357,15 +59365,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(g_queue->pipebuf + g_queue->tail, buf, size1) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } if (size2 != 0) if (copy_from_user(g_queue->pipebuf, buf + size1, size2) != 0) { - up(&g_sem); - return -EFAULT; - } + up(&g_sem); + return -EFAULT; + } g_queue->tail = (g_queue->tail + esize) % PIPE_BUFSIZE; g_queue->count += esize; @@ -59393,7 +59401,7 @@ module_exit(generic_exit); 1 ----> dosya nesnesi (struct file) 2 ----> dosya nesnesi (struct file) 3 ----> dosya nesnesi (struct file) - .... + ... Dosya nesnesi "açık dosyaların bilgilerini" tutmaktadır. Aşağıda file yapısının mevcut çekirdeklerdeki içeriğini görüyorsunuz: @@ -59442,18 +59450,18 @@ module_exit(generic_exit); inode yapısı dosyanın diskteki bilgilerini tutmaktadır. Yani aynı dosya üç kez açılsa çekirdek üç farklı file nesnesi oluşturmaktadır. Ancak bu dosya diskte bir tane olduğuna göre çekirdek bunun için toplamda bir tane inode yapısı oluşturacaktır. - file yapısının içerisinde dosyanın diskteki bilgilerine ilişkin bu inode yapısınıa f_inode elemanı yoluyla erişilebilmektedir. + file yapısının içerisinde dosyanın diskteki bilgilerine ilişkin bu inode yapısınıa f_inode elemanı yoluyla erişilebilmektedir. Daha önceden de gördüğümüz gibi bir dosya isimleri diskte dizin girişlerinde tutulmaktadır. Örneğin "/home/kaan/Study/test.c" isimli dosyanın i-node elemanına erişmek için işletim sistemi sırasıyla "/home", "/home/kaan", "ome/kaan/Stduy" ve "/home/kaan/Study/test.txt" dizin girişlerini taramak zorundadır. Bu işleme işletim sistemlerinde "yol ifadelerinin çözümlenmesi (pathname resolution)" denilmektedir. Aynı dosyaların tekrar tekrar açılması durumunda bu işlemlerin yeniden yapılması oldukça zahmetlidir. Dolayısıyla bulunan dizin girişlerinin bir yapı ile temsil edilerek bir cache sisteminde - saklanması uygundur. İşte Linux çekirdeğinde dizin girişleri "dentry" isimli bir yapıyla temsil edilmektedir. + saklanması uygundur. İşte Linux çekirdeğinde dizin girişleri "dentry" isimli bir yapıyla temsil edilmektedir. Linux sistemleri yukarıda açıkladığımız "inode" ve "dentry" nesnelerini bir cache sisteminde tutmaktadır. Böylece bir dosya yeniden açıldığında onun bilgilerine diske hiç başvurmadan hızlı bir biçimde erişilmektedir. Linux dünyasında bu cache sistemlerine "inode cache" ve "dentry cache" denilmektedir. file, inode ve denrty nesneleri için bu yapıların büyüklüğünde - ayrı dilimli tahsisat sistemleri oluşturulmuştur. + ayrı dilimli tahsisat sistemleri oluşturulmuştur. Pekiyi yukarıdaki nesneler arasındaki ilişki nasıldır? Dosya sistemine dosya betimleyicisi yoluyla erişildiğini anımsayınız. Dosya betimleyicisinden dosya nesnesi (file nesnesi) elde edilmektedir. Dosya nesnesinin içerisinde o dosyanın dizin @@ -59467,7 +59475,7 @@ module_exit(generic_exit); Bir aygıt sürücü üzerinde dosya işlemi yapıldığında çekirdek aygıt sürücü fonksiyonlarına dosya nesnesinin adresini (filp göstericisi) geçirmektedir. Yalnızca aygıt sürücü open fonksiyonuyla açılırken ve close fonksiyonu ile kapatılırken indode nesnesinin adresi de bu fonksiyonlara geçirilmektedir. Aygıt sürücünün fonksiyonlarının parametrik yapılarını - aşağıda yeniden veriyoruz: + aşağıda yeniden veriyoruz: int open(struct inode *inodep, struct file *filp); int release(struct inode *inodep, struct file *filp); @@ -59484,8 +59492,8 @@ module_exit(generic_exit); O aynı kod birden fazla aygıt için işlev görmektedir. Örneğin seri portu kontrol eden bir aygıt sürücü söz konusu olsun. Ancak bilgissayarımızda dört seri port olsun. İşte bu durumda bu seri porta ilişkin aygıt dosyalarının hepsinin majör numaraları aynıdır. Ancak minör numaraları farklıdır. Ya da örneğin terminal aygıt sürücüsü bir tanedir. Ancak bu aygıt sürücü birden - fazla terminali yönetebilmektedir. O halde her terminale ilişkin aygıt dosyasının majör numaraları aynı minör numaraları - farklı olacaktır. Örneğin: + fazla terminali yönetebilmektedir. O halde her terminale ilişkin aygıt dosyasının majör numaraları aynı minör numaraları + farklı olacaktır. Örneğin: /dev$ ls -l tty1 tty2 tty3 tty4 tty5 crw--w---- 1 root tty 4, 1 Haz 2 15:05 tty1 @@ -59496,7 +59504,7 @@ module_exit(generic_exit); ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- - 146. Ders 07/06/2024 - Cuma + 146. Ders 07/06/2024 - Cuma ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -59505,7 +59513,7 @@ module_exit(generic_exit); birden fazla aynı türden bağımsız aygıtı idare edecektir. Dolayısıyla bu tür durumlarda bazı nesnelerin senkronize edilmesi gerekebilir. - Birden fazla minör numara üzerinde çalışacak aygıt sürücüleri tipik olarak şöyle yazılmaktadır. + Birden fazla minör numara üzerinde çalışacak aygıt sürücüleri tipik olarak şöyle yazılmaktadır. 1) Minör numara sayısının aşağıdaki gibi ndevices isimli parametre yoluyla komut satırından aşağıdaki gibi aygıt sürücüye aktarıldığını varsayacağız: @@ -59514,22 +59522,22 @@ module_exit(generic_exit); ... static int ndevices = NDEVICES; module_param(ndevices, int, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); - + Programcının majör ve minör numaraları tahsis etmesi gerekir. Yukarıda da yaptığımız gibi majör numara alloc_chrdev_region fonksiyonuyla dinamik olarak belirlenebilmektedir. Bu fonksiyon aynı zamanda belli bir minör numaradan başlayarak n tane minör numarayı da tahsis edebilmektedir. Örneğin: if ((result = alloc_chrdev_region(&g_dev, 0, ndevices, "pipe-driver")) < 0) { - printk(KERN_INFO "Cannot alloc char driver!...\n"); - return result; - } + printk(KERN_INFO "Cannot alloc char driver!...\n"); + return result; + } Burada 0'ıncı minör numaradan ndevices tane minör numara için aygıt tahsisatı yapılmıştır. 2) Her aygıt bir yapıyla temsil edilmelidir. Bunun için N elemanlı bir yapı dizisi yaratabilirsiniz. Bu dizi global düzeyde tanımlanabileceği gibi kmalloc fonksiyonuyla dinamik biçimde de tahsis edilebilir. Oluşturulan bu yapının içerisine struct cdev nesnesi de eklenmelidir. Örneğin: - + struct PIPE_DEVICE { unsigned char pipebuf[PIPE_BUFSIZE]; size_t head; @@ -59545,10 +59553,10 @@ module_exit(generic_exit); ... if ((g_pdevices = (struct PIPE_DEVICE *)kmalloc(sizeof(struct PIPE_DEVICE) * ndevices, GFP_KERNEL)) == NULL) { - unregister_chrdev_region(g_dev, ndevices); - return -ENOMEM; - } - + unregister_chrdev_region(g_dev, ndevices); + return -ENOMEM; + } + Burada görüldüğü gibi her farklı borunun farklı bekleme kuyrukları ve semaphore nesnesi vardır. cdev yapı nesnesinin yapının içerisine yerleştirilmesinin amacı şleride görüleceği gibi bu adresten hareketle yapı nesnesinin adresinin elde edilmesini sağlamaktır. Bunun nasıl yapıldığı izleyen pragraflarda görülecektir. @@ -59564,7 +59572,7 @@ module_exit(generic_exit); dev = MKDEV(MAJOR(g_dev), i); if ((result = cdev_add(&g_pdevices[i].cdev, dev, 1)) < 0) { - for (k = 0; k < i; ++k) + for (k = 0; k < i; ++k) cdev_del(&g_pdevices[k].cdev); kfree(g_pdevices); unregister_chrdev_region(dev, ndevices); @@ -59589,7 +59597,7 @@ module_exit(generic_exit); private_data elemanı programcının kendisinin isteğe bağlı olarak yerleştirebileceği bilgiler için bulundurulmuştur. Bu işlemler aşağıdaki gibi yapılabilir: - + static int generic_open(struct inode *inodep, struct file *filp) { struct PIPE_DEVICE *pdevice; @@ -59601,8 +59609,8 @@ module_exit(generic_exit); return 0; } - - 5) Aygıt sürücünün read ve write fonksiyonları yazılır. + + 5) Aygıt sürücünün read ve write fonksiyonları yazılır. 6) release (close) işleminde yapılacak birtakım son işlemler varsa yapılır. @@ -59655,7 +59663,6 @@ module_exit(generic_exit); #!/bin/bash module=$2 - mode=666 /sbin/rmmod ./$module.ko || exit 1 for ((i = 0; i < $1; ++i)) @@ -59666,7 +59673,7 @@ module_exit(generic_exit); Bu scrip'te biz modülü önce çekirdekten sonra da "loadmulti" ile yarattığımız aygıt dosyalarını dosya sisteminden sildik. Script aşağıdaki örnekteki gibi kullanılmalıdır: - $ sudo ./unloadmulti 10 pipe-driver + $ sudo ./unloadmulti 10 pipe-driver Daha önce yapımış olduğumuz boru aygıt sürücücüsünün 10 farklı minör numarayı destekleyen biçimini aşağıda veriyoruz. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -59737,7 +59744,7 @@ static int __init generic_init(void) unregister_chrdev_region(g_dev, ndevices); return -ENOMEM; } - + for (i = 0; i < ndevices; ++i) { g_pdevices[i].head = g_pdevices[i].tail = g_pdevices[i].count = 0; sema_init(&g_pdevices[i].sem, 1); @@ -59747,7 +59754,7 @@ static int __init generic_init(void) dev = MKDEV(MAJOR(g_dev), i); if ((result = cdev_add(&g_pdevices[i].cdev, dev, 1)) < 0) { - for (k = 0; k < i; ++k) + for (k = 0; k < i; ++k) cdev_del(&g_pdevices[k].cdev); kfree(g_pdevices); unregister_chrdev_region(dev, ndevices); @@ -59763,7 +59770,7 @@ static void __exit generic_exit(void) { int i; - for (i = 0; i < ndevices; ++i) + for (i = 0; i < ndevices; ++i) cdev_del(&g_pdevices[i].cdev); kfree(g_pdevices); unregister_chrdev_region(g_dev, ndevices); @@ -59785,7 +59792,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -59825,15 +59832,15 @@ static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *o size2 = esize - size1; if (copy_to_user(buf, pdevice->pipebuf + pdevice->head, size1) != 0) { - up(&pdevice->sem); + up(&pdevice->sem); return -EFAULT; - } + } if (size2 != 0) if (copy_to_user(buf + size1, pdevice->pipebuf, size2) != 0) { - up(&pdevice->sem); - return -EFAULT; - } + up(&pdevice->sem); + return -EFAULT; + } pdevice->head = (pdevice->head + esize) % PIPE_BUFSIZE; pdevice->count -= esize; @@ -59849,7 +59856,7 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo { struct PIPE_DEVICE *pdevice; size_t esize, size1, size2; - + pdevice = (struct PIPE_DEVICE *)filp->private_data; if (down_interruptible(&pdevice->sem)) @@ -59878,15 +59885,15 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo size2 = esize - size1; if (copy_from_user(pdevice->pipebuf + pdevice->tail, buf, size1) != 0) { - up(&pdevice->sem); + up(&pdevice->sem); return -EFAULT; - } + } if (size2 != 0) if (copy_from_user(pdevice->pipebuf, buf + size1, size2) != 0) { - up(&pdevice->sem); - return -EFAULT; - } + up(&pdevice->sem); + return -EFAULT; + } pdevice->tail = (pdevice->tail + esize) % PIPE_BUFSIZE; pdevice->count += esize; @@ -59932,7 +59939,6 @@ done #!/bin/bash module=$2 -mode=666 /sbin/rmmod ./$module.ko || exit 1 for ((i = 0; i < $1; ++i)) @@ -59998,60 +60004,60 @@ void exit_sys(const char *msg) #include #include -#define BUFFER_SIZE 4096 +#define BUFFER_SIZE 4096 void exit_sys(const char *msg); int main(void) { - int pdriver; - char buf[BUFFER_SIZE + 1]; - int size; - ssize_t result; + int pdriver; + char buf[BUFFER_SIZE + 1]; + int size; + ssize_t result; - if ((pdriver = open("pipe-driver5", O_RDONLY)) == -1) - exit_sys("open"); + if ((pdriver = open("pipe-driver5", O_RDONLY)) == -1) + exit_sys("open"); - for (;;) { - printf("Size:"); - scanf("%d", &size); - if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); - continue; - } - if (size == 0) - break; - if ((result = read(pdriver, buf, size)) == -1) - exit_sys("read"); - buf[result] = '\0'; - printf("%jd bytes read: %s\n", (intmax_t)result, buf); - } + for (;;) { + printf("Size:"); + scanf("%d", &size); + if (size > BUFFER_SIZE) { + printf("size is very long!...\n"); + continue; + } + if (size == 0) + break; + if ((result = read(pdriver, buf, size)) == -1) + exit_sys("read"); + buf[result] = '\0'; + printf("%jd bytes read: %s\n", (intmax_t)result, buf); + } - close(pdriver); + close(pdriver); - return 0; + return 0; } void exit_sys(const char *msg) { - perror(msg); + perror(msg); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } /*-------------------------------------------------------------------------------------------------------------------------- - 147. Ders 09/06/2024 - Pazar -/*-------------------------------------------------------------------------------------------------------------------------- + 147. Ders 09/06/2024 - Pazar +---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Aygıt sürücüden bilgi okumak için read fonksiyonun, aygıt sürücüye bilgi göndermek için ise write fonksiyonun kullanıldığını - gördük. Ancak bazen aygıt sürücüye write fonksiyonunu kullanmadan bazı bilgilerin gönderilmesi, aygıt sürücüden read + gördük. Ancak bazen aygıt sürücüye write fonksiyonunu kullanmadan bazı bilgilerin gönderilmesi, aygıt sürücüden read fonksiyonunu kullanmadan bazı bilgilerin alınması gerekebilmektedir. Bazen hiç bilgi okumadan ve bilgi göndermeden aygıt sürüceden bazı şeyleri yapmasını da isteyebiliriz. Bu tür bazı işlemlerin read ve write fonksiyonlarıyla yaptırılması mümkün olsa bile kullanışsızdır. Örneğin yukarıdaki boru aygıt sürücümüzde (pipe-driver) biz aygıt sürücüden kullandığı FIFO alanın uzunluğunu isteyebiliriz. - Ya da bu alanın boyutunu değiştirmek isteyebiliriz. Bu işlemleri read ve write fonksiyonlarıyla yapmaya çalışsak aygıt + Ya da bu alanın boyutunu değiştirmek isteyebiliriz. Bu işlemleri read ve write fonksiyonlarıyla yapmaya çalışsak aygıt sürücümüz sanki boruyu temsil eden kuyruktan okuma yazma yapmak istediğimizi sanacaktır. Tabii yukarıda da belirttiğimiz gibi zorlanırsa bu tür işlemler read ve write fonksiyonlarıyla yine de yapılabilir. Ancak böyle bir kullanımım mümkün hale getirilmesi ve user mode'tan kullanılması oldukça zor olacaktır. @@ -60074,7 +60080,7 @@ void exit_sys(const char *msg) ioctl fonksiyonu başarı durumunda 0 değerine, başarısızlık durumunda -1 değerine geri döner. errno uygun biçimde set edilmektedir. -/*-------------------------------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- User mode'dan bir program aygıt sürücü için ioctl fonksiyonunu çağırdığında akış user mode'dan kernel moda geçer ve aygıt @@ -60086,7 +60092,7 @@ void exit_sys(const char *msg) Fonksiyonun birinci parametresi yine dosya nesnesinin adresini, ikinci parametresei ioctl fonksiyonunda kullanılan komut kodunu (yani ioctl fonksiyonuna geçirilen ikinci argümanı) ve üçüncü parametresi de ek argümanı (yani ioctl fonksiyonuna geçirilen üçüncü argümanı) belirtmektedir. Tabii programcının eğer ioctl fonksiyonu iki argümanlı çağrılmışsa bu üçüncü - parametreye erişmemesi gerekir. + parametreye erişmemesi gerekir. Bu fonksiyon başarı durumunda 0 değerine başarısızlık durumunda negatif hata koduna geri dönmelidir. Fakat bazen programcı doğrudan iletilecek değeri geri dönüş değeri biçiminde oluşturabilir. Bu durumda geri dönüş değeri pozitif değer olabilir. @@ -60103,38 +60109,38 @@ void exit_sys(const char *msg) .release = generic_release, .unlocked_ioctl = generic_ioctl }; -/*-------------------------------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- ioctl işleminde ioctl fonksiyonunun ikinci parametresi olan kontrol kodu dört parçanın bit düzeyinde birleştirilmesiyle oluşturulmaktadır. Bu parçaların belli bir uzunlukları vardır. Ancak bu parçalara ilişkin bitlerin 32 bit içerisinde belli pozisyonlara yerleştirilmesini kolaylaştırmak için _IOC isimli bir makro bulundurulmuştur. Bu makronun parametreleri şöyledir: - + _IOC(dir, type, nr, size) Bu makro buradaki parçaları bit düzeyinde birleştirerek bir 4 byte'lık bir tamsayı biçiminde vermektedir. Makronun parametrelerini - oluşturan dört parçanın anlamları ve bit uzunlukları şöyledir: + oluşturan dört parçanın anlamları ve bit uzunlukları şöyledir: dir (direction): Bu 2 bitlik bir alandır ([30, 31] bitler). Burada kullanılacak sembolik sabitler _IOC_NONE, _IOC_READ, _IOC_WRITE ve _IOC_READ|_IOC_WRITE biçimindedir. Buradaki _IOC_READ aygıt sürücüden bilgi alınacağını _IOC_WRITE ise aygıt sürücüye bilgi gönderileceğini belirtmektedir. Buradaki yön ioctl sistem fonksiyonu tarafından dosyanın açış moduyla kontrol edilmemektedir. Örneğin biz buradaki yönü _IOC_READ|_IOC_WRITE biçiminde vermiş olsak bile dosyası O_RDONLY modunda açıp bu ioctl işlemini yapabiliriz. Eğer programcı böyle bir kontrol yapmak istiyorsa aygıt sürücünün ioctl fonksiyonu içerisinde - bu kontrolü yapabilir. + bu kontrolü yapabilir. type: Bu 8 bitlik bir alandır ([8, 15] bitleri). Bu alana aygıt sürücüyü yazan istediği herhangi bir byte'ı verebilir. - Genellikle bu byte bir akarkter sabiti olarak verilmektedir. Buna "magic number" da denilmektedir. + Genellikle bu byte bir akarkter sabiti olarak verilmektedir. Buna "magic number" da denilmektedir. nr: Bu 8 bitlik bir alandır ([0, 7] bitleri). Programcı tarafından kontrol koduna verilen sıra numarasını temsil etmektedir. - Genellikle aygıt sürücü programcıları 0'dan başlayarak her koda bir numara vermektedir. + Genellikle aygıt sürücü programcıları 0'dan başlayarak her koda bir numara vermektedir. size: Bu 14 bitlik bir alandır ([16:29] bitleri). Bu alan kaç byte'lık bir transferin yapılacağını belirtmektedir. Buradaki size - değeri aslında çekirdek tarafından kullanılmamaktadır. Dolayısıyla biz 14 bitten daha büyük transferleri de yapabiliriz. + değeri aslında çekirdek tarafından kullanılmamaktadır. Dolayısıyla biz 14 bitten daha büyük transferleri de yapabiliriz. Kullanım kolaylığı sağlamak için genellikle _IOC makrosu bir sembolik sabit biçiminde define edilir. Örneğin: - #define PIPE_MAGIC 'x' - #define IOC_PIPE_GETBUFSIZE _IOC(_IOC_READ, PIPE_MAGIC, 0, 4) + #define PIPE_MAGIC 'x' + #define IOC_PIPE_GETBUFSIZE _IOC(_IOC_READ, PIPE_MAGIC, 0, 4) Aslında _IOC makrosundan daha kolay kullanılabilen aşağıdaki makrolar da oluşturulmuştur: @@ -60153,14 +60159,14 @@ void exit_sys(const char *msg) söz konusu olmadığı durumda kullanılır. _IOR aygıt sürücüden okuma yapıldığı durumda, _IOW aygıt sürücüye yazma yapıldığı durumda, _IOWR ise aygıt sürücüden hem okuma hem de yazma yapıldığı durumlarda kullanılmaktadır. Örneğin: - #define PIPE_MAGIC 'x' - #define IOC_PIPE_GETBUFSIZE _IOR(PIPE_MAGIC, 0, int) + #define PIPE_MAGIC 'x' + #define IOC_PIPE_GETBUFSIZE _IOR(PIPE_MAGIC, 0, int) ioctl için kontrol kodları hem aygıt sürücünün içerisinden hem de user mode'dan kullanılacağına göre ortak bir başlık dosyasının - oluşturulması uygun olabilir. Burada ioctl kontrol kodları bulundurulabilir. Örneğin boru aygıt sürücümüz için "pipedriver.h" + oluşturulması uygun olabilir. Burada ioctl kontrol kodları bulundurulabilir. Örneğin boru aygıt sürücümüz için "pipe-driver.h" dosyası açağıdaki gibi düzenlenebilir: - /* pipedriver.h" + // pipe-driver.h #ifndef PIPEDRIVER_H_ #define PIPEDRIVER_H_ @@ -60168,8 +60174,8 @@ void exit_sys(const char *msg) #include #include - #define PIPE_DRIVER_MAGIC 'p' - #define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 0, size_t) + #define PIPE_DRIVER_MAGIC 'p' + #define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 0, size_t) #endif @@ -60188,15 +60194,15 @@ void exit_sys(const char *msg) fonksiyonun -ENOTTY ile geri döndrülmesi uygundur. Bazı aygıt sürücülerinde başarı durumunda aygıt sürücüden bilgi ioctl fonksiyonunun üçüncü parametresi yoluyla değil geri dönüş değeri yoluyla elde edilmektedir. Bu durumda aygıt sürücüdeki ioctl fonksiyonu pozitif değerle de geri döndürülebilir. Ancak bu durum seyrektir. Biz transferinioctl fonksiyonunun üçüncü parametresi - yoluyla yapılmasını tavsiye ediyoruz. + yoluyla yapılmasını tavsiye ediyoruz. Aygıt sürücüdeki ioctl fonksiyonu tipik olarak bir switch deyimi ile gerçekleştirilmektedir. Örneğin: - + static long generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { case IOC_PIPE_GETBUFSIZE: - /* ... */ + // ... break; default: return -ENOTTY; @@ -60206,18 +60212,18 @@ void exit_sys(const char *msg) } Burada switch deyiminin default bölümünde fonksiyonun -NOTTY değeri ile geri döndürüldüğüne dikkat ediniz. Tabii fonksiyon - üçüncü parametresi ile belirtilen transfer adresi geçersiz bir adrezse yine -EFAULT değeri ile döndürülmelidir. + üçüncü parametresi ile belirtilen transfer adresi geçersiz bir adrezse yine -EFAULT değeri ile döndürülmelidir. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte boru yagıt sürücüsüsünün kullandığı boru uzunluğu IOC_PIPE_GETBUFSIZE ioctl koduyla elde edilip IOC_PIPE_SETBUFSIZE fonksiyomıyla değiştirilebilmektedir. Buradaki IOCTL kodları şöyle oluşturulmuştur: - #define PIPE_DRIVER_MAGIC 'p' - #define IOC_PIPE_GETCOUNT _IOR(PIPE_DRIVER_MAGIC, 0, size_t) - #define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 1, size_t) - #define IOC_PIPE_SETBUFSIZE _IOW(PIPE_DRIVER_MAGIC, 2, size_t) - #define IOC_PIPE_PEEK _IOWR(PIPE_DRIVER_MAGIC, 3, struct PIPE_PEEK) + #define PIPE_DRIVER_MAGIC 'p' + #define IOC_PIPE_GETCOUNT _IOR(PIPE_DRIVER_MAGIC, 0, size_t) + #define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 1, size_t) + #define IOC_PIPE_SETBUFSIZE _IOW(PIPE_DRIVER_MAGIC, 2, size_t) + #define IOC_PIPE_PEEK _IOWR(PIPE_DRIVER_MAGIC, 3, struct PIPE_PEEK) Ancak bu örnek için yukarıda vermiş olduğumuz boru aygıt sürücüsünde bazı değişiklikler yaptık. Bu değişiklikler şunlardır: @@ -60236,20 +60242,20 @@ void exit_sys(const char *msg) }; Buradaki bufsize elemanı ilgili borunun uzunluğunu belirtmektedir. Default uzunluk test için kolaylık sağlamak amacıyla - yine 10 olarak tutulmuştur. Bu değişiklik sayesinde artık her minör numaraya ilişkin boru uzunluğu farklılaşabilecektir. + yine 10 olarak tutulmuştur. Bu değişiklik sayesinde artık her minör numaraya ilişkin boru uzunluğu farklılaşabilecektir. - Aygıt sürücümüz içerisindeki ioctl fonksiyonu aşağıdaki gibi yazılmıştır: static long generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct PIPE_DEVICE *pdevice; - + printk(KERN_INFO "ioctl"); pdevice = (struct PIPE_DEVICE *)filp->private_data; switch (cmd) { - case IOC_PIPE_GETCOUNT : + case IOC_PIPE_GETCOUNT: return put_user(pdevice->count, (size_t *)arg); case IOC_PIPE_GETBUFSIZE: return put_user(pdevice->bufsize, (size_t *)arg); @@ -60266,7 +60272,7 @@ void exit_sys(const char *msg) Burada gördüğünüz gibi ilgili minör numaradaki borudaki byte sayısı IOC_PIPE_GETCOUNT, uzunluğu ise IOC_PIPE_GETBUFSIZE ioctl kodu ile alınmakta ve bu boru uzunluğu uzunluk IOC_PIPE_SETBUFSIZE ioctl kodu ile değiştirilebilmektedir. Boru için kullanılan tampon - değiştirilirken eşzamanlı erişimlere dikkat edilmelidir. Çünkü daha önceden de belirttiğimiz gibi aygıt sürücünün içerisindeki + değiştirilirken eşzamanlı erişimlere dikkat edilmelidir. Çünkü daha önceden de belirttiğimiz gibi aygıt sürücünün içerisindeki fonksiyonlar farklı prosesler tarafından aynı anda çağrılabilmektedir. Bu tür durumlarda daha önce görmüş olduğumuz çekirdek senkronizasyon nesneleri ile işlemlerin senktronize edilmesi gerekmektedir. Örneğimizdeki senkronizasyon PIPE_DEVICE yapısının içeriisndeki semaphore nesnesi yoluyla yapılmıştır. IOC_PIPE_PEEK ioctl kodu borudan atmadan okuma yapmakta kullanılmaktadır. @@ -60280,8 +60286,8 @@ void exit_sys(const char *msg) Yapının size elemanı kaç byte peek işleminin yapılacağını, buf elemanı ise peek edilen byte'ların yerleştirileceği adresi belirtmektedir. Tabii boruda mevcut olan byte sayısından daha fazla byte peek edilmek istenirse boruda olan kadar byte peek - edilmektedir. Peek edilen byte sayısı aygıt sürücü tarafından yapının size elemanına aktarılmaktadır. - + edilmektedir. Peek edilen byte sayısı aygıt sürücü tarafından yapının size elemanına aktarılmaktadır. + Aygıt sürücümüzü yine "loadmulti" script'i ile aşağıdaki gibi yükleyebilirsiniz: $ sudo ./loadmulti 10 pipe-driver ndevices=10 @@ -60290,7 +60296,7 @@ void exit_sys(const char *msg) $ sudo ./unloadmulti 10 pipe-driver - Aşağıda örneğin tüm modlarını veriyoruz. + Aşağıda örneğin tüm modlarını veriyoruz. ---------------------------------------------------------------------------------------------------------------------------*/ /* pipe-driver.h */ @@ -60306,11 +60312,11 @@ struct PIPE_PEEK { void *buf; }; -#define PIPE_DRIVER_MAGIC 'p' -#define IOC_PIPE_GETCOUNT _IOR(PIPE_DRIVER_MAGIC, 0, size_t) -#define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 1, size_t) -#define IOC_PIPE_SETBUFSIZE _IOW(PIPE_DRIVER_MAGIC, 2, size_t) -#define IOC_PIPE_PEEK _IOWR(PIPE_DRIVER_MAGIC, 3, struct PIPE_PEEK) +#define PIPE_DRIVER_MAGIC 'p' +#define IOC_PIPE_GETCOUNT _IOR(PIPE_DRIVER_MAGIC, 0, size_t) +#define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 1, size_t) +#define IOC_PIPE_SETBUFSIZE _IOW(PIPE_DRIVER_MAGIC, 2, size_t) +#define IOC_PIPE_PEEK _IOWR(PIPE_DRIVER_MAGIC, 3, struct PIPE_PEEK) #endif @@ -60324,7 +60330,7 @@ struct PIPE_PEEK { #include #include #include -#include "pipedriver.h" +#include "pipe-driver.h" #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define NDEVICES 10 @@ -60388,7 +60394,7 @@ static int __init generic_init(void) unregister_chrdev_region(g_dev, ndevices); return -ENOMEM; } - + for (i = 0; i < ndevices; ++i) { g_pdevices[i].head = g_pdevices[i].tail = g_pdevices[i].count = 0; g_pdevices[i].bufsize = DEF_PIPE_BUFSIZE; @@ -60408,7 +60414,7 @@ static int __init generic_init(void) cdev_del(&g_pdevices[k].cdev); kfree(g_pdevices[k].pipebuf); } - + kfree(g_pdevices); unregister_chrdev_region(dev, ndevices); printk(KERN_ERR "Cannot add device!...\n"); @@ -60424,7 +60430,7 @@ static void __exit generic_exit(void) { int i; - for (i = 0; i < ndevices; ++i) + for (i = 0; i < ndevices; ++i) cdev_del(&g_pdevices[i].cdev); kfree(g_pdevices); unregister_chrdev_region(g_dev, ndevices); @@ -60446,7 +60452,7 @@ static int generic_open(struct inode *inodep, struct file *filp) static int generic_release(struct inode *inodep, struct file *filp) { - printk(KERN_INFO "pipe-driver-closed...\n"); + printk(KERN_INFO "pipe-driver closed...\n"); return 0; } @@ -60510,7 +60516,7 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo { struct PIPE_DEVICE *pdevice; size_t esize, size1, size2; - + pdevice = (struct PIPE_DEVICE *)filp->private_data; if (down_interruptible(&pdevice->sem)) @@ -60567,13 +60573,13 @@ static ssize_t generic_write(struct file *filp, const char *buf, size_t size, lo static long generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct PIPE_DEVICE *pdevice; - + printk(KERN_INFO "ioctl"); pdevice = (struct PIPE_DEVICE *)filp->private_data; switch (cmd) { - case IOC_PIPE_GETCOUNT : + case IOC_PIPE_GETCOUNT: return put_user(pdevice->count, (size_t *)arg); case IOC_PIPE_GETBUFSIZE: return put_user(pdevice->bufsize, (size_t *)arg); @@ -60596,7 +60602,7 @@ static int set_bufsize(struct PIPE_DEVICE *pdevice, unsigned long arg) if (arg > MAX_PIPE_BUFSIZE) return -EINVAL; - if (arg <= pdevice->count) + if (arg <= pdevice->count) return -EINVAL; if (down_interruptible(&pdevice->sem)) @@ -60611,12 +60617,12 @@ static int set_bufsize(struct PIPE_DEVICE *pdevice, unsigned long arg) if (pdevice->tail <= pdevice->head) { size = pdevice->bufsize - pdevice->head; memcpy(new_pipebuf, pdevice->pipebuf + pdevice->head, size); - memcpy(new_pipebuf + size, pdevice->pipebuf, pdevice->count - size); + memcpy(new_pipebuf + size, pdevice->pipebuf, pdevice->count - size); } - else - memcpy(new_pipebuf, pdevice->pipebuf + pdevice->head, pdevice->count); + else + memcpy(new_pipebuf, pdevice->pipebuf + pdevice->head, pdevice->count); } - + pdevice->head = 0; pdevice->tail = pdevice->count; @@ -60625,7 +60631,7 @@ static int set_bufsize(struct PIPE_DEVICE *pdevice, unsigned long arg) pdevice->bufsize = arg; up(&pdevice->sem); - + return 0; } @@ -60635,7 +60641,7 @@ static int read_peek(struct PIPE_DEVICE *pdevice, unsigned long arg) struct PIPE_PEEK *userpp = (struct PIPE_PEEK *)arg; struct PIPE_PEEK pp; int status = 0; - + if (copy_from_user(&pp, userpp, sizeof(struct PIPE_PEEK)) != 0) return -EFAULT; @@ -60665,7 +60671,7 @@ static int read_peek(struct PIPE_DEVICE *pdevice, unsigned long arg) goto EXIT; } - if (put_user(esize, &userpp->size) != 0) + if (put_user(esize, &userpp->size) != 0) status = -EFAULT; EXIT: @@ -60677,7 +60683,6 @@ EXIT: module_init(generic_init); module_exit(generic_exit); - # Makefile obj-m += $(file).o @@ -60709,7 +60714,6 @@ done #!/bin/bash module=$2 -mode=666 /sbin/rmmod ./$module.ko || exit 1 for ((i = 0; i < $1; ++i)) @@ -60725,7 +60729,7 @@ done #include #include #include -#include "pipedriver.h" +#include "pipe-driver.h" #define PIPE_SIZE 4096 @@ -60789,7 +60793,7 @@ void exit_sys(const char *msg) #include #include #include -#include "pipedriver.h" +#include "pipe-driver.h" #define BUFFER_SIZE 4096 @@ -60797,100 +60801,160 @@ void exit_sys(const char *msg); int main(void) { - int pdriver; - char buf[BUFFER_SIZE + 1]; - int count, size; - ssize_t result; - struct PIPE_PEEK pp; - char *peekbuf; + int pdriver; + char buf[BUFFER_SIZE + 1]; + int count, size; + ssize_t result; + struct PIPE_PEEK pp; + char *peekbuf; - if ((pdriver = open("pipe-driver5", O_RDONLY)) == -1) - exit_sys("open"); + if ((pdriver = open("pipe-driver5", O_RDONLY)) == -1) + exit_sys("open"); - for (;;) { - if (ioctl(pdriver, IOC_PIPE_GETCOUNT, &count) == -1) - exit_sys("ioctl"); - printf("There are (is) %d byte(s) in the pipe\n", count); - printf("Size:"); - scanf("%d", &size); - if (size > BUFFER_SIZE) { - printf("size is very long!..\n"); - continue; - } - if (size == 0) - break; - if (size < 0) { - pp.size = -size; - if ((pp.buf = malloc(-size)) == NULL) { - fprintf(stderr, "cannot allocate memory!..\n"); - exit(EXIT_FAILURE); - } + for (;;) { + if (ioctl(pdriver, IOC_PIPE_GETCOUNT, &count) == -1) + exit_sys("ioctl"); + printf("There are (is) %d byte(s) in the pipe\n", count); + printf("Size:"); + scanf("%d", &size); + if (size > BUFFER_SIZE) { + printf("size is very long!...\n"); + continue; + } + if (size == 0) + break; + if (size < 0) { + pp.size = -size; + if ((pp.buf = malloc(-size)) == NULL) { + fprintf(stderr, "cannot allocate memory!...\n"); + exit(EXIT_FAILURE); + } - if (ioctl(pdriver, IOC_PIPE_PEEK, &pp) == -1) - exit_sys("ioctl"); + if (ioctl(pdriver, IOC_PIPE_PEEK, &pp) == -1) + exit_sys("ioctl"); - peekbuf = (char *)pp.buf; - for (size_t i = 0; i < pp.size; ++i) - putchar(peekbuf[i]); - putchar('\n'); + peekbuf = (char *)pp.buf; + for (size_t i = 0; i < pp.size; ++i) + putchar(peekbuf[i]); + putchar('\n'); - free(pp.buf); + free(pp.buf); - } - else { - if ((result = read(pdriver, buf, size)) == -1) - exit_sys("read"); - buf[result] = '\0'; - printf("%jd bytes read: %s\n", (intmax_t)result, buf); - } - } + } + else { + if ((result = read(pdriver, buf, size)) == -1) + exit_sys("read"); + buf[result] = '\0'; + printf("%jd bytes read: %s\n", (intmax_t)result, buf); + } + } - close(pdriver); + close(pdriver); - return 0; + return 0; } void exit_sys(const char *msg) { - perror(msg); + perror(msg); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } - +/*-------------------------------------------------------------------------------------------------------------------------- + 150. Ders 28/06/2024 - Cuma +---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- - Anımsanacağı gibi proc dosya sistemi disk tabanlı bir dosya sistemi değildir. Kernel çalışması sırasında dış dümyaya bilgi vermek - için bazen de davranışını dış dünyadan gelen verilerle değiştirebilmek için proc dosya sistemini kullanmaktadır. Daha sonra proc - gibi sys isimli dosya sistemi de Linuc sistemlerine eklenmiştir. + Anımsanacağı gibi proc dosya sistemi disk tabanlı bir dosya sistemi değildir. Çekirdek çalışması sırasında dış dünyaya + bilgi vermek için bazen de davranışını dış dünyadan gelen verilerle değiştirebilmek için proc dosya sistemini kullanmaktadır. + Daha sonra proc gibi sys isimli bir dosya sistemi de Linux'a eklenmiştir. - proc dosya sistemi aslında yalnızca kernel tarafından değil aygıt sürücüler tarafından da kullanılabilmektedir. Ancak bu dosya sisteminin - içerisinde user mode'dan dosyalar ya da dizinler yaratılamamaktadır. + proc dosya sistemi aslında yalnızca çekirdek tarafından değil aygıt sürücüler tarafından da kullanılabilmektedir. Ancak bu + dosya sisteminin içerisinde user moddan dosyalar ya da dizinler yaratılamamaktadır. proc dosya sistemindeki tüm girişlerin + dosya uzunluları 0 biçiminde rapor edilmektedir. - proc dosya sisteminin kernel ve aygıt sürücüler tarafından kullanılmasına ilişkin fonksiyonlar birkaç kere değişik kernel versiyonlarında değiştirilmiştir. - Dolayısıyla eski kernel'larda çalışan kodlar yeni kernel'larda derlenmeyecektir. Biz burada en yeni fonksiyonları ele alacağız. + proc dosya sisteminin kullanımına yönelik çekirdek fonksiyonları çekirdeğin versyionları ile zamanla birkaç değiştirilmiştir. + Dolayısıyla eski çekirdeklere çalışan kodlar yeni çekirdeklerde derlenmeyecektir. Biz burada en yeni fonksiyonları ele + alacağız. - proc dosya sisteminde bir dosya yaratabilmek için proc_create isimli fonksiyon kullanılmaktadır. + User moddan prog dosya sistemindeki bir dosya üzerinde open, read, write, lseek, close işlmeler yapıldığında aslında + aygıt sürücülerin belirlediği fonksiyonlar çağrılmaktadır. Yani örneğin biz user moddan proc dosya sistemi içerisindeki + bir dosyadan okuma yapmak istediğimizde aslında onu oluşturan aygıt sürücünün içerisindeki bir fonksiyon çalışıtırılır. + Bu fonksiyon bize okuma sonucunda elde edilecek bilgileri verir. Benzer biçimde proc dosya sistemindeki bir dosyaya + user moddan yazma yapılmak istendiğinde aslında o dosyaya ilişkin aygıt sürücünün bir fonksiyonu çağrılmaktadır. + Yani proc dosya sistemi aslında aygıt sürücüden fonksiyon çağıran bir mekanizmaya sahiptir. +---------------------------------------------------------------------------------------------------------------------------*/ - struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct proc_ops *proc_ops); +/*-------------------------------------------------------------------------------------------------------------------------- + proc dosya sisteminde bir dosya yaratabilmek için proc_create isimli fonksiyon kullanılmaktadır. Fonksiyonun prototipi + şöyledir: - Fonksiyonun birinci parametresi yaratılacak dosyanın ismini belirtir. İkinci parametresi erişim haklarını belrtmektedir. Bu parametre 0 geçilirse - default erişim hakları kullanılır. Üçüncü parametre dosyanın hangi dizinde yaratılacağını belirtmektedir. Bu parametre NULL geçilirse dosya ana - /proc dizini içerisinde yaratılır. Son parametre proc dosya sistemindeki ilgi dosyaya yazma ve okuma yapldığında çalıştırılacak fonksiyonları belirtir. - Aslında birkaç sene önceki kernel'larda bu fonksiyonun son parametresi struct proc_ops biçiminde değil, struct file_operations biçimindeydi. Dolayısıyla - kernelınızda hangi fonksiyonun bulunuyor olduğuna dikkat ediniz. Kursun yapıdlığı sistemde bu fonksiyonun son parametresi struct file_operations biçimindedir. - Fonksiyon başarı durumunda yaratılan dosyanın bilgilerini içeren proc_dir_entry türünden bir yapı nesnesinin adresiyle, başarısızlık durumunda NULL adresle - geri dönmektedir. + #include - proc dosya sisteminde yaratılan dosya remove_proc_entry fonksiyonuyla silinebilmektedir. + struct proc_dir_entry *proc_create(const char *name, umode_t mode, + struct proc_dir_entry *parent, const struct proc_ops *proc_ops); + + Fonksiyonun birinci parametresi yaratılacak dosyanın ismini belirtir. İkinci parametresi erişim haklarını belirtmektedir. + Bu parametre 0 geçilirse default erişim hakları kullanılır. Üçüncü parametre dosyanın hangi dizinde yaratılacağını + belirtmektedir. Bu parametre NULL geçilirse dosya ana "/proc" dizini içerisinde yaratılır. proc dosya sistemi içerisinde + dizinlerin nasıl yaratıldığını izleyen paragraflarda açıklayacağız. Son parametre proc dosya sistemindeki ilgi dosyaya yazma + ve okuma yapldığında çalıştırılacak fonksiyonları belirtir. Aslında birkaç sene önceki çekirdeklerde (3.10 çekirdeklerine + kadarki çekirdeklerde) bu fonksiyonun son parametresi proc_ops yapısını değil, file_operations yapısını kullanıyordu. Dolayısıyla + çekirdeğinizdeki fonksiyonun son parametresinin ne olduğuna dikkat ediniz. Örneğin önceki kursun yapıldığı makinede bu son + parametre file_operations yapısına ilişkinken bu kursun yapıldığı makinede proc_ops yapısına ilişkindir. proc_ops yapısı + şöyle bildirilmiştir: + + #include + + struct proc_ops { + unsigned int proc_flags; + int (*proc_open)(struct inode *, struct file *); + ssize_t (*proc_read)(struct file *, char __user *, size_t, loff_t *); + ssize_t (*proc_read_iter)(struct kiocb *, struct iov_iter *); + ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *); + /* mandatory unless nonseekable_open() or equivalent is used */ + loff_t (*proc_lseek)(struct file *, loff_t, int); + int (*proc_release)(struct inode *, struct file *); + __poll_t (*proc_poll)(struct file *, struct poll_table_struct *); + long (*proc_ioctl)(struct file *, unsigned int, unsigned long); + #ifdef CONFIG_COMPAT + long (*proc_compat_ioctl)(struct file *, unsigned int, unsigned long); + #endif + int (*proc_mmap)(struct file *, struct vm_area_struct *); + unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); + }; + + proc_ops yapısının elemanlarına ilişkin fonksiyon göstericilerinin türlerinin file_operations yapısındaki elemanlara ilişkin + fonksiyon gösterişcilerinin türleri ile aynı olduğuna dikkat ediniz.ç Bu fonksiyonların kullanımı tamamen aygıt sürücü + için oluşturduğumuz file_operations yapısı ile aynı biçimdedir. + + proc dosya sistemi genel olarak text tabanlı bir dosya sistemi biçiminde düşünülmüştür. Yani buradaki dosyalar genel olarak + text içeriğe sahiptir. Siz de aygıt sürücünüz için proc dosya sisteminde dosya oluşturacaksınız onların içeriğini text + olarak oluşturmalısınız. + + Fonksiyon başarı durumunda yaratılan dosyanın bilgilerini içeren proc_dir_entry türünden bir yapı nesnesinin adresiyle, + başarısızlık durumunda NULL adresle geri dönmektedir. Bu durumda çağıran fonksiyonun -NOMEM gibi bir hata değeriyle geri + döndürülmesi yaygındır. + + proc dosya sisteminde yaratılan dosya remove_proc_entry fonksiyonuyla silinebilmektedir. + + #include void remove_proc_entry(const char *name, struct proc_dir_entry *parent); - Aşağıdaki örnekte modülün init fonksiyonunda proc dosya sisteminin kökünde "generic-char-driver" isimli bir dosya yaratılmıştır. Modülün exit fonksiyonunda - bu dosya remove_proc_entry fonksiyonuyla silinmiştir. + Fonksiyonun birinci parametresi silinecek dosyanın ismini, ikinci parametresi dosyanın içinde bulunduğu dizine ilişkin + proc_dir_entry nesnesinin adresini almaktadır. Yine bu parametre NULL adres girilirse dosyanın ana "/proc" dizininde + olduğu kabul edilmektedir. + + Aşağıdaki örnekte proc sisteminde dosya yaratan iskelet bir aygıt sürücü programı verilmiştir. Bu aygıt sürücüde"/proc" + dizininde "procfs-driver" isminde bir dosya yaratılmaktadır. Aygıt sürücüyü install ettikten sonra "/proc" dizininde + bu dosyanın yaratılıp yaratılmadığını kontrol ediniz. Bu dosyayı "cat" ile komut satırından okumak istediğinizde "cat" + programı bu dosyayı açıp, read işlemi uygulayıp kapatacaktır. "cat" işleminden sonra "dmesg" komutu ile aygıt sürücümüzde + belirlediğimiz fonksiyonların çağrıldığını doğrulayınız. ---------------------------------------------------------------------------------------------------------------------------*/ -/* generic-char-driver.c */ +/* procfs-driver.c */ #include #include @@ -60898,28 +60962,47 @@ void exit_sys(const char *msg) #include #include +#define PIPE_BUFSIZE 4096 + MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("General Character Device Driver"); MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("procfs driver"); + +static int generic_open(struct inode *inodep, struct file *filp); +static int generic_release(struct inode *inodep, struct file *filp); +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off); + +static int proc_open(struct inode *inodep, struct file *filp); +static int proc_release(struct inode *inodep, struct file *filp); +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off); static dev_t g_dev; static struct cdev *g_cdev; -static struct file_operations g_file_ops = { +static struct file_operations g_fops = { .owner = THIS_MODULE, + .open = generic_open, + .read = generic_read, + .write = generic_write, + .release = generic_release }; -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); - -static struct file_operations g_proc_ops = { - .owner = THIS_MODULE, - .read = proc_read, +static struct proc_ops g_proc_ops = { + .proc_open = proc_open, + .proc_release = proc_release, + .proc_read = proc_read, + .proc_write = proc_write }; static int __init generic_init(void) { int result; + struct proc_dir_entry *pde; - if ((result = alloc_chrdev_region(&g_dev, 0, 1, "generic-char-driver")) < 0) { + printk(KERN_INFO "procfs-driver module initialization...\n"); + + if ((result = alloc_chrdev_region(&g_dev, 0, 1, "procfs-driver")) < 0) { printk(KERN_INFO "Cannot alloc char driver!...\n"); return result; } @@ -60930,43 +61013,93 @@ static int __init generic_init(void) } g_cdev->owner = THIS_MODULE; - g_cdev->ops = &g_file_ops; + g_cdev->ops = &g_fops; - if ((result = cdev_add(g_cdev, g_dev, 1)) != 0) { + if ((result = cdev_add(g_cdev, g_dev, 1)) < 0) { unregister_chrdev_region(g_dev, 1); - printk(KERN_INFO "Cannot add character device driver!...\n"); + printk(KERN_ERR "Cannot add device!...\n"); return result; } - if (proc_create("generic-char-driver", S_IRUSR|S_IRGRP|S_IROTH, NULL, &g_proc_ops) == NULL) { - unregister_chrdev_region(g_dev, 1); + if ((pde = proc_create("procfs-driver", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, NULL, &g_proc_ops)) == NULL) { cdev_del(g_cdev); - printk(KERN_INFO "Cannot create proc file!...\n"); + unregister_chrdev_region(g_dev, 1); return -ENOMEM; } - printk(KERN_INFO "Module initialized with %d:%d device number...\n", MAJOR(g_dev), MINOR(g_dev)); - return 0; } static void __exit generic_exit(void) { + remove_proc_entry("procfs-driver", NULL); cdev_del(g_cdev); unregister_chrdev_region(g_dev, 1); - remove_proc_entry("generic-char-driver", NULL); - printk(KERN_INFO "Goodbye...\n"); + printk(KERN_INFO "procfs driver module exit...\n"); +} + +static int generic_open(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver opened...\n"); + + return 0; +} + +static int generic_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver closed...\n"); + + return 0; +} + +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver read\n"); + + return 0; +} + +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver write...\n"); + + return 0; +} + +static int proc_open(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver proc file opened...\n"); + + return 0; +} + +static int proc_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver proc file closed...\n"); + + return 0; } static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) { + printk(KERN_INFO "procfs-driver proc file read...\n"); + + return 0; +} + +static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver proc file write...\n"); + return 0; } module_init(generic_init); module_exit(generic_exit); +# Makefile + obj-m += $(file).o all: @@ -60974,6 +61107,9 @@ all: clean: make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean + +/* load (bu satırı dosyaya kopyalamayınız) */ + #!/bin/bash module=$1 @@ -60985,11 +61121,280 @@ rm -f $module mknod $module c $major 0 chmod $mode $module +/* unload (bu satırı dosyaya kopyalamayınız) */ + +#!/bin/bash + +module=$1 +mode=666 + +/sbin/rmmod ./$module.ko || exit 1 +rm -f $module + /*-------------------------------------------------------------------------------------------------------------------------- - proc dosya sisteminde yaratılmış olan dosyadan okuma ve yazma yapılması + Aşağıdaki örnekte aygıt sürücüsü içerisindeki count isimli global değişken proc dosya sistemindeki "profs-driver" isimli + bir dosya ile temsil edilmiştir. Bu dosyadan okuma yapıldığında bu count değişkeninin değeri elde edilmektedir. Dosyaya + yazma yapıldığında bu count değişkeninin değeri güncellenmektedir. Yazma işleminde dosya göstericisi dikkate alınmamış ve + yazma işlemi her zaman sanki ilgili dosyanın başından itibaren yapılıyormuş gibi bir etki oluşturulmuştur. ---------------------------------------------------------------------------------------------------------------------------*/ -/* generic-char-driver.c */ +/* procfs-driver.c */ + +#include +#include +#include +#include +#include + +#define PIPE_BUFSIZE 4096 + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("procfs driver"); + +static int generic_open(struct inode *inodep, struct file *filp); +static int generic_release(struct inode *inodep, struct file *filp); +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off); + +static int proc_open(struct inode *inodep, struct file *filp); +static int proc_release(struct inode *inodep, struct file *filp); +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off); + +static dev_t g_dev; +static struct cdev *g_cdev; +static struct file_operations g_fops = { + .owner = THIS_MODULE, + .open = generic_open, + .read = generic_read, + .write = generic_write, + .release = generic_release +}; + +static struct proc_ops g_proc_ops = { + .proc_open = proc_open, + .proc_release = proc_release, + .proc_read = proc_read, + .proc_write = proc_write +}; + +static int g_count = 123; +static char g_count_str[32]; + +static int __init generic_init(void) +{ + int result; + struct proc_dir_entry *pde; + + printk(KERN_INFO "procfs-driver module initialization...\n"); + + if ((result = alloc_chrdev_region(&g_dev, 0, 1, "procfs-driver")) < 0) { + printk(KERN_INFO "Cannot alloc char driver!...\n"); + return result; + } + + if ((g_cdev = cdev_alloc()) == NULL) { + printk(KERN_INFO "Cannot allocate cdev!...\n"); + return -ENOMEM; + } + + g_cdev->owner = THIS_MODULE; + g_cdev->ops = &g_fops; + + if ((result = cdev_add(g_cdev, g_dev, 1)) < 0) { + unregister_chrdev_region(g_dev, 1); + printk(KERN_ERR "Cannot add device!...\n"); + return result; + } + + if ((pde = proc_create("procfs-driver", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, NULL, &g_proc_ops)) == NULL) { + cdev_del(g_cdev); + unregister_chrdev_region(g_dev, 1); + return -ENOMEM; + } + + return 0; +} + +static void __exit generic_exit(void) +{ + remove_proc_entry("procfs-driver", NULL); + cdev_del(g_cdev); + unregister_chrdev_region(g_dev, 1); + + printk(KERN_INFO "procfs driver module exit...\n"); +} + +static int generic_open(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver opened...\n"); + + return 0; +} + +static int generic_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver closed...\n"); + + return 0; +} + +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver read\n"); + + return 0; +} + +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver write...\n"); + + return 0; +} + +static int proc_open(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver proc file opened...\n"); + + return 0; +} + +static int proc_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver proc file closed...\n"); + + return 0; +} + +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) +{ + size_t esize; + size_t left; + + sprintf(g_count_str, "%d\n", g_count); + + left = strlen(g_count_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_count_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + printk(KERN_INFO "procfs-driver proc file read...\n"); + + return esize; +} + +static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off) +{ + size_t esize; + char count_str[31]; + int count; + + esize = size > 31 ? 31 : size; + + if (esize != 0) { + if (copy_from_user(count_str, buf, esize) != 0) + return -EFAULT; + } + + count_str[esize] = '\0'; + if (kstrtoint(count_str, 10, &count) != 0) + return -EINVAL; + + if (count < 0 || count > 1000) + return -EINVAL; + + g_count = count; + strcpy(g_count_str, count_str); + + printk(KERN_INFO "procfs-driver proc file write...\n"); + + return esize; +} + +module_init(generic_init); +module_exit(generic_exit); + +# Makefile + +obj-m += $(file).o + +all: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} modules +clean: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean + +/* load (bu satırı dosyaya kopyalamayınız) */ + +#!/bin/bash + +module=$1 +mode=666 + +/sbin/insmod ./$module.ko ${@:2} || exit 1 +major=$(awk "\$2 == \"$module\" {print \$1}" /proc/devices) +rm -f $module +mknod $module c $major 0 +chmod $mode $module + +/* unload (bu satırı dosyaya kopyalamayınız) */ + +#!/bin/bash + +module=$1 +mode=666 + +/sbin/rmmod ./$module.ko || exit 1 +rm -f $module + +/*-------------------------------------------------------------------------------------------------------------------------- + Biz yukarıdaki örneklerde dosyayı proc dosya sisteminin kök diininde yarattık. İstersek proc dizininde bir dizin yaratıp + dosyalarımızı o dizinin içerisinde de oluşturabilirdik. proc dosya sisteminde bir dizin yaratmak için proc_mkdir fonksiyonu + kullanılmaktadır: + + #include + + struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent); + + Fonksiyonun birinci parametresi yaratılacak dizin'in simini, ikinci parametresi dizinin hanfi dizin içerisinde yaratılacağını + belirtir. Bu parametre NULL geçilirse dizin proc dosya sisteminin kök dizininde yaratılır. Buradan aldığımız geri dönüş değerini + proc_create fonksiyonun parent parametresinde kullanırsak ilgili dosyamızı bu dizinde yaratmış oluruz. Tabii benzer biçimde + dizin içerisinde dizin de yaratabiliriz. proc_mkdir fonksiyonu başarısızlık durumunda NULL adrese geri dönmektedir. Çağıran + fonksiyonun yine -ENOMEM değeriyle geri döndürülmesi uygundur. + + Örneğin: + + struct proc_dir_entry *pdir; + + pdir = proc_mkdir("procfs-driver", NULL); + proc_create("info", 0, pdir, &g_proc_ops); + + Dizinlerin silinmesi yine remove_proc_entry fonksiyonuyla yapılmaktadır. Tabii dizin içerisindeki dosyaları silerken + remove_proc_entry fonksiyonda dosyanın hangi dizin içerisinde olduğu belirtilmelidir. Bu fonksiyon ile dizin silinirken + dizin'in içi boş değilse bile o dizin ve onun içindeki girişlerin hepsi silinmektedir. Ayrıca kök dizindeki girişleri silmek + için proc_remove fonksiyonu da bulundurulmuştur. Fonksiyonun prototipi şöyledir: + + #include + + void proc_remove(struct proc_dir_entry *de); + + Bu fonksiyon parametre olarak proc_create ya da proc_mkdir fonksiyonun verdiği geri dönüş değerini alır. proc dosya sisteminin + kök dizininde silme yapılmak isteniyorsa aşağıdaki her iki çağrım eşdeğerdir: + + remove_proc_entry("file_name", NULL); + proc_remove("file_name"); + + Aşağıdaki örnekte proc dosya sisteminin kök dizininde "procfs-driver" isimli bir dizin yaratılmış, onun içerisinde de + "count" bir dosya yaratılmıştır. Bu örneğin yukarıdaki örnekten tek farkı dosyanın proc dosya sisteminin kökünde değil + bir dizinin içerisinde yaratılmış olmasıdır. +---------------------------------------------------------------------------------------------------------------------------*/ + +/* procfs-driver.c */ #include #include @@ -60998,36 +61403,49 @@ chmod $mode $module #include #include -#define BUF_SIZE 4096 - MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("General Character Device Driver"); MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("procfs driver"); + +static int generic_open(struct inode *inodep, struct file *filp); +static int generic_release(struct inode *inodep, struct file *filp); +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off); + +static int proc_open(struct inode *inodep, struct file *filp); +static int proc_release(struct inode *inodep, struct file *filp); +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off); static dev_t g_dev; static struct cdev *g_cdev; -static struct file_operations g_file_ops = { +static struct file_operations g_fops = { .owner = THIS_MODULE, + .open = generic_open, + .read = generic_read, + .write = generic_write, + .release = generic_release }; -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); -static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off); -static loff_t proc_llseek(struct file *filp, loff_t off, int whence); - -static struct file_operations g_proc_ops = { - .owner = THIS_MODULE, - .read = proc_read, - .write = proc_write, - .llseek = proc_llseek, +static struct proc_ops g_proc_ops = { + .proc_open = proc_open, + .proc_release = proc_release, + .proc_read = proc_read, + .proc_write = proc_write }; -static char g_text[BUF_SIZE] = "this is a test\n"; +static int g_count = 123; +static char g_count_str[32]; static int __init generic_init(void) { int result; + struct proc_dir_entry *pde_dir; + struct proc_dir_entry *pde; - if ((result = alloc_chrdev_region(&g_dev, 0, 1, "generic-char-driver")) < 0) { + printk(KERN_INFO "procfs-driver module initialization...\n"); + + if ((result = alloc_chrdev_region(&g_dev, 0, 1, "procfs-driver")) < 0) { printk(KERN_INFO "Cannot alloc char driver!...\n"); return result; } @@ -61038,98 +61456,135 @@ static int __init generic_init(void) } g_cdev->owner = THIS_MODULE; - g_cdev->ops = &g_file_ops; + g_cdev->ops = &g_fops; - if ((result = cdev_add(g_cdev, g_dev, 1)) != 0) { + if ((result = cdev_add(g_cdev, g_dev, 1)) < 0) { unregister_chrdev_region(g_dev, 1); - printk(KERN_INFO "Cannot add character device driver!...\n"); + printk(KERN_ERR "Cannot add device!...\n"); return result; } - if (proc_create("generic-char-driver", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, NULL, &g_proc_ops) == NULL) { - unregister_chrdev_region(g_dev, 1); + if ((pde_dir = proc_mkdir("procfs-driver", NULL)) == NULL) { cdev_del(g_cdev); - printk(KERN_INFO "Cannot create proc file!...\n"); + unregister_chrdev_region(g_dev, 1); return -ENOMEM; } - printk(KERN_INFO "Module initialized with %d:%d device number...\n", MAJOR(g_dev), MINOR(g_dev)); + if ((pde = proc_create("count", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_proc_ops)) == NULL) { + remove_proc_entry("procfs-driver", NULL); + cdev_del(g_cdev); + unregister_chrdev_region(g_dev, 1); + return -ENOMEM; + } return 0; } static void __exit generic_exit(void) { + remove_proc_entry("procfs-driver", NULL); cdev_del(g_cdev); unregister_chrdev_region(g_dev, 1); - remove_proc_entry("generic-char-driver", NULL); - printk(KERN_INFO "Goodbye...\n"); + printk(KERN_INFO "procfs driver module exit...\n"); +} + +static int generic_open(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver opened...\n"); + + return 0; +} + +static int generic_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver closed...\n"); + + return 0; +} + +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver read\n"); + + return 0; +} + +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off) +{ + printk(KERN_INFO "procfs-driver write...\n"); + + return 0; +} + +static int proc_open(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver proc file opened...\n"); + + return 0; +} + +static int proc_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "procfs-driver proc file closed...\n"); + + return 0; } static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) { - ssize_t left, n; + size_t esize; + size_t left; - left = strlen(g_text) - *off; - n = left < size ? left : size; + sprintf(g_count_str, "%d\n", g_count); - if (n != 0) { - if (copy_to_user(buf, g_text, n) != 0) + left = strlen(g_count_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_count_str + *off, esize) != 0) return -EFAULT; - *off += n; + *off += esize; } - return n; + printk(KERN_INFO "procfs-driver proc file read...\n"); + + return esize; } static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off) { - ssize_t left, n; + size_t esize; + char count_str[31]; + int count; - if (filp->f_flags & O_APPEND) - *off = strlen(g_text); + esize = size > 31 ? 31 : size; - left = BUF_SIZE - *off; - n = left < size ? left : size; - if (n != 0) { - if (copy_from_user(g_text + *off, buf, n) != 0) + if (esize != 0) { + if (copy_from_user(count_str, buf, esize) != 0) return -EFAULT; - *off += n; } - return n; -} - -static loff_t proc_llseek(struct file *filp, loff_t off, int whence) -{ - loff_t toff; - - switch (whence) { - case SEEK_SET: - toff = off; - break; - case SEEK_CUR: - toff = filp->f_pos + off; - break; - case SEEK_END: - toff = (loff_t)strlen(g_text) - off; - break; - default: - return -EINVAL; - } - - if (toff > BUF_SIZE || toff < 0) + count_str[esize] = '\0'; + if (kstrtoint(count_str, 10, &count) != 0) return -EINVAL; - filp->f_pos = toff; + if (count < 0 || count > 1000) + return -EINVAL; - return toff; + g_count = count; + strcpy(g_count_str, count_str); + + printk(KERN_INFO "procfs-driver proc file write...\n"); + + return esize; } module_init(generic_init); module_exit(generic_exit); +# Makefile + obj-m += $(file).o all: @@ -61137,6 +61592,8 @@ all: clean: make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean +/* load (bu satırı dosyaya kopyalamayınız) */ + #!/bin/bash module=$1 @@ -61148,29 +61605,703 @@ rm -f $module mknod $module c $major 0 chmod $mode $module -/* sample.c */ +/* unload (bu satırı dosyaya kopyalamayınız) */ + +#!/bin/bash + +module=$1 +mode=666 + +/sbin/rmmod ./$module.ko || exit 1 +rm -f $module + +/*-------------------------------------------------------------------------------------------------------------------------- + 153. Ders 07/07/2024 - Pazar +---------------------------------------------------------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------------------------------------------------------- + Şimdi de boru aygıt sürücüsüne proc dosya sistemi desteği verelim. Aygıt sürücümüz proc kök dizininde minör numara + kadar ayrı dizin oluşturmaktadır. Sonra da bu dizinlerin içerisinde ilgili aygıtların bufsize ve count değerlerini iki dosya + ile dış dünyaya vermektedir. Örneğimizde dikkat edilmesi gereken birkaç nokta üzerinde durmak istiyoruz: + + - proc dosya sistemi içerisindeki bir dosya user moddan açıldığında aygıt sürücümüz hangi dosyanın açıldığını nereden + bilecektir? İşte dosya nesnesi görevinde olan file yapısının f_path elemanı path isimli bir yapı türündendir. Bu yapı + şöyle bildirilmiştir: + + struct path { + struct vfsmount *mnt; + struct dentry *dentry; + } __randomize_layout; + + Yapının deentry elemanı dosya hakkında bilgiler içermektedir: + + struct dentry { + /* RCU lookup touched fields */ + unsigned int d_flags; /* protected by d_lock */ + seqcount_spinlock_t d_seq; /* per dentry seqlock */ + struct hlist_bl_node d_hash; /* lookup hash list */ + struct dentry *d_parent; /* parent directory */ + struct qstr d_name; + struct inode *d_inode; /* Where the name belongs to - NULL is negative */ + unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ + + /* Ref lookup also touches following */ + struct lockref d_lockref; /* per-dentry lock and refcount */ + const struct dentry_operations *d_op; + struct super_block *d_sb; /* The root of the dentry tree */ + unsigned long d_time; /* used by d_revalidate */ + void *d_fsdata; /* fs-specific data */ + + union { + struct list_head d_lru; /* LRU list */ + wait_queue_head_t *d_wait; /* in-lookup ones only */ + }; + struct hlist_node d_sib; /* child of parent list */ + struct hlist_head d_children; /* our children */ + /* + * d_alias and d_rcu can share memory + */ + union { + struct hlist_node d_alias; /* inode alias list */ + struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */ + struct rcu_head d_rcu; + } d_u; + }; + + Burada yapının d_name elemanı qstr isimli bir yapı türündedir: + + struct qstr { + union { + struct { + HASH_LEN_DECLARE; + }; + u64 hash_len; + }; + const unsigned char *name; + }; + + İşte buradaki name elemanı ilgili dosyanın ismini belirtmektedir. Özetle biz file yapısından hareketle dosyanın ismini + elde edebilmekteyiz. + + Böylece biz proc dosya sistemi içerisinde bir dosya açıldığında o dosyanın isminden hareketle hangi dosyanın açılmış + olduğunu anlayabiliriz. Ayrıca dentry yapısının d_parent elemanı dosya ya da dizin'in içinde bulunduğu dizine ilişkin + dentry nesnesini vermektedir. Yani biz istersek dosyanın içinde bulunduğu dizinin ismini de alabiliriz. + + Aşağıda örneği bir bütün olarak veriyoruz. Aygıt sürücüyü yine aşağıdaki gibi derleyebilirsiniz: + + $ make file=pipe-driver + + Yüklemeyi aşağıdaki gibi yapabilirsiniz: + + $ sudo ./loadmulti 5 pipe-driver ndevices=5 + + Aygıt sürüyü yükledikten sonra artık proc dosya sisteminde "pipe-driver" isimli bid dizin oluşturulacak ve bu dizin + içerisinde de aşağıdaki gibi 3 dizin yaratılmış olacaktır: + + $ ls /proc/pipe-driver + pipe0 pipe1 pipe2 pipe3 pipe4 + + Bu dosyaların her birinin içerisinde de "bufsize" ve "count" isimli iki dosya bulunacaktır. Burada teti "prog1.c" ve + "prog2.c" programları yardımıyla yapabilirisiniz. Örneğin "prog1" programı ile "pipe-driver3" borusunu açıp içerisine + bir şeyler yazarsanız "/proc/pipe-driver/pipe3/count" dosyasının içerisinde yazılan byte sayısını görebilirsiniz. Aygıt + sürücümüzü yine "unloadmulti" script'i ile boşaltabilirisiniz: + + $ sudo ./unloadmulti 5 pipe-drive +---------------------------------------------------------------------------------------------------------------------------*/ + +/* pipe-driver.h */ + +#ifndef PIPEDRIVER_H_ +#define PIPEDRIVER_H_ + +#include +#include + +struct PIPE_PEEK { + size_t size; + void *buf; +}; + +#define PIPE_DRIVER_MAGIC 'p' +#define IOC_PIPE_GETCOUNT _IOR(PIPE_DRIVER_MAGIC, 0, size_t) +#define IOC_PIPE_GETBUFSIZE _IOR(PIPE_DRIVER_MAGIC, 1, size_t) +#define IOC_PIPE_SETBUFSIZE _IOW(PIPE_DRIVER_MAGIC, 2, size_t) +#define IOC_PIPE_PEEK _IOWR(PIPE_DRIVER_MAGIC, 3, struct PIPE_PEEK) + +#endif + +/* pipe-driver.c */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pipe-driver.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define NDEVICES 10 +#define DEF_PIPE_BUFSIZE 10 +#define MAX_PIPE_BUFSIZE 131072 /* 128K */ + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("Pipe Driver"); + +static int generic_open(struct inode *inodep, struct file *filp); +static int generic_release(struct inode *inodep, struct file *filp); +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off); +static long generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); + +static int proc_open(struct inode *inodep, struct file *filp); +static int proc_release(struct inode *inodep, struct file *filp); +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); + +static struct file_operations g_fops = { + .owner = THIS_MODULE, + .open = generic_open, + .read = generic_read, + .write = generic_write, + .release = generic_release, + .unlocked_ioctl = generic_ioctl +}; + + +static struct proc_ops g_proc_ops = { + .proc_open = proc_open, + .proc_release = proc_release, + .proc_read = proc_read, +}; + +/* +static struct file_operations g_proc_ops = { + .open = proc_open, + .release = proc_release, + .read = proc_read, +}; +*/ + +struct PIPE_DEVICE { + unsigned char *pipebuf; + size_t head; + size_t tail; + size_t count; + size_t bufsize; + struct semaphore sem; + wait_queue_head_t wqread; + wait_queue_head_t wqwrite; + struct cdev cdev; +}; + +struct PROC_INFO { + struct PIPE_DEVICE *pdevice; + int filetype; /* 0 = count, 1 = bufsize */ + char strbuf[32]; +}; + +static int set_bufsize(struct PIPE_DEVICE *pdevice, unsigned long arg); +static int read_peek(struct PIPE_DEVICE *pdevice, unsigned long arg); + +static dev_t g_dev; +static struct PIPE_DEVICE *g_pdevices; +static int ndevices = NDEVICES; + +module_param(ndevices, int, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + +static int __init generic_init(void) +{ + int result; + dev_t dev; + int i, k; + struct proc_dir_entry *pde_root, *pde_pipe; + char namebuf[16]; + + printk(KERN_INFO "pipe-driver module initialization...\n"); + + if ((result = alloc_chrdev_region(&g_dev, 0, ndevices, "pipe-driver")) < 0) { + printk(KERN_INFO "Cannot alloc char driver!...\n"); + return result; + } + + if ((g_pdevices = (struct PIPE_DEVICE *)kmalloc(sizeof(struct PIPE_DEVICE) * ndevices, GFP_KERNEL)) == NULL) { + unregister_chrdev_region(g_dev, ndevices); + return -ENOMEM; + } + + if ((pde_root = proc_mkdir("pipe-driver", NULL)) == NULL) { + kfree(g_pdevices); + unregister_chrdev_region(g_dev, 1); + return -ENOMEM; + } + + for (i = 0; i < ndevices; ++i) { + sprintf(namebuf, "pipe%d", i); + if ((pde_pipe = proc_mkdir(namebuf, pde_root)) == NULL) { + kfree(g_pdevices); + unregister_chrdev_region(g_dev, 1); + remove_proc_entry("pipe-driver", NULL); + return -ENOMEM; + } + g_pdevices[i].head = g_pdevices[i].tail = g_pdevices[i].count = 0; + g_pdevices[i].bufsize = DEF_PIPE_BUFSIZE; + sema_init(&g_pdevices[i].sem, 1); + init_waitqueue_head(&g_pdevices[i].wqread); + init_waitqueue_head(&g_pdevices[i].wqwrite); + cdev_init(&g_pdevices[i].cdev, &g_fops); + dev = MKDEV(MAJOR(g_dev), i); + g_pdevices[i].pipebuf = (char *)kmalloc(DEF_PIPE_BUFSIZE, GFP_KERNEL); + result = cdev_add(&g_pdevices[i].cdev, dev, 1); + + if (g_pdevices[i].pipebuf == NULL || result < 0) { + if (g_pdevices[i].pipebuf != NULL) + kfree(g_pdevices[i].pipebuf); + + for (k = 0; k < i; ++k) { + cdev_del(&g_pdevices[k].cdev); + kfree(g_pdevices[k].pipebuf); + } + + kfree(g_pdevices); + unregister_chrdev_region(dev, ndevices); + printk(KERN_ERR "Cannot add device!...\n"); + + return result; + } + + if ((proc_create("count", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_pipe, &g_proc_ops)) == NULL + || proc_create("bufsize", S_IRUSR|S_IRGRP|S_IROTH, pde_pipe, &g_proc_ops) == NULL) { + remove_proc_entry("pipe-driver", NULL); + for (k = 0; k < i; ++k) { + cdev_del(&g_pdevices[k].cdev); + kfree(g_pdevices[k].pipebuf); + } + unregister_chrdev_region(g_dev, 1); + return -ENOMEM; + } + } + + return 0; +} + +static void __exit generic_exit(void) +{ + int i; + + for (i = 0; i < ndevices; ++i) + cdev_del(&g_pdevices[i].cdev); + kfree(g_pdevices); + remove_proc_entry("pipe-driver", NULL); + unregister_chrdev_region(g_dev, ndevices); + + printk(KERN_INFO "pipe-driver module exit...\n"); +} + +static int generic_open(struct inode *inodep, struct file *filp) +{ + struct PIPE_DEVICE *pdevice; + + pdevice = container_of(inodep->i_cdev, struct PIPE_DEVICE, cdev); + filp->private_data = pdevice; + + printk(KERN_INFO "pipe-driver opened...\n"); + + return 0; +} + +static int generic_release(struct inode *inodep, struct file *filp) +{ + printk(KERN_INFO "pipe-driver closed...\n"); + + return 0; +} + +static ssize_t generic_read(struct file *filp, char *buf, size_t size, loff_t *off) +{ + struct PIPE_DEVICE *pdevice; + size_t esize, size1, size2; + + pdevice = (struct PIPE_DEVICE *)filp->private_data; + + if (size == 0) + return 0; + + if (down_interruptible(&pdevice->sem)) + return -ERESTARTSYS; + + while (pdevice->count == 0) { + up(&pdevice->sem); + + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(pdevice->wqread, pdevice->count > 0)) + return -ERESTARTSYS; + if (down_interruptible(&pdevice->sem)) + return -ERESTARTSYS; + } + + esize = MIN(pdevice->count, size); + + if (pdevice->tail <= pdevice->head) + size1 = MIN(pdevice->bufsize - pdevice->head, esize); + else + size1 = esize; + + size2 = esize - size1; + + if (copy_to_user(buf, pdevice->pipebuf + pdevice->head, size1) != 0) { + up(&pdevice->sem); + return -EFAULT; + } + + if (size2 != 0) + if (copy_to_user(buf + size1, pdevice->pipebuf, size2) != 0) { + up(&pdevice->sem); + return -EFAULT; + } + + pdevice->head = (pdevice->head + esize) % pdevice->bufsize; + pdevice->count -= esize; + + up(&pdevice->sem); + + wake_up_interruptible_all(&pdevice->wqwrite); + + return esize; +} + +static ssize_t generic_write(struct file *filp, const char *buf, size_t size, loff_t *off) +{ + struct PIPE_DEVICE *pdevice; + size_t esize, size1, size2; + + pdevice = (struct PIPE_DEVICE *)filp->private_data; + + if (down_interruptible(&pdevice->sem)) + return -ERESTARTSYS; + + if (size > pdevice->bufsize) { + up(&pdevice->sem); + return -EINVAL; + } + + while (pdevice->bufsize - pdevice->count < size) { + up(&pdevice->sem); + + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(pdevice->wqwrite, pdevice->bufsize - pdevice->count >= size)) + return -ERESTARTSYS; + + if (down_interruptible(&pdevice->sem)) + return -ERESTARTSYS; + } + + esize = MIN(pdevice->bufsize - pdevice->count, size); + + if (pdevice->tail >= pdevice->head) + size1 = MIN(pdevice->bufsize - pdevice->tail, esize); + else + size1 = esize; + + size2 = esize - size1; + + if (copy_from_user(pdevice->pipebuf + pdevice->tail, buf, size1) != 0) { + up(&pdevice->sem); + return -EFAULT; + } + + if (size2 != 0) + if (copy_from_user(pdevice->pipebuf, buf + size1, size2) != 0) { + up(&pdevice->sem); + return -EFAULT; + } + + pdevice->tail = (pdevice->tail + esize) % pdevice->bufsize; + pdevice->count += esize; + + up(&pdevice->sem); + + wake_up_interruptible_all(&pdevice->wqread); + + return esize; +} + +static long generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct PIPE_DEVICE *pdevice; + + printk(KERN_INFO "ioctl"); + + pdevice = (struct PIPE_DEVICE *)filp->private_data; + + switch (cmd) { + case IOC_PIPE_GETCOUNT: + return put_user(pdevice->count, (size_t *)arg); + case IOC_PIPE_GETBUFSIZE: + return put_user(pdevice->bufsize, (size_t *)arg); + case IOC_PIPE_SETBUFSIZE: + return set_bufsize(pdevice, arg); + case IOC_PIPE_PEEK: + return read_peek(pdevice, arg); + default: + return -ENOTTY; + } + + return 0; +} + +static int set_bufsize(struct PIPE_DEVICE *pdevice, unsigned long arg) +{ + char *new_pipebuf; + size_t size; + + if (arg > MAX_PIPE_BUFSIZE) + return -EINVAL; + + if (arg <= pdevice->count) + return -EINVAL; + + if (down_interruptible(&pdevice->sem)) + return -ERESTARTSYS; + + if ((new_pipebuf = (char *)kmalloc(arg, GFP_KERNEL)) == NULL) { + up(&pdevice->sem); + return -ENOMEM; + } + + if (pdevice->count != 0) { + if (pdevice->tail <= pdevice->head) { + size = pdevice->bufsize - pdevice->head; + memcpy(new_pipebuf, pdevice->pipebuf + pdevice->head, size); + memcpy(new_pipebuf + size, pdevice->pipebuf, pdevice->count - size); + } + else + memcpy(new_pipebuf, pdevice->pipebuf + pdevice->head, pdevice->count); + } + + pdevice->head = 0; + pdevice->tail = pdevice->count; + + kfree(pdevice->pipebuf); + pdevice->pipebuf = new_pipebuf; + pdevice->bufsize = arg; + + up(&pdevice->sem); + + return 0; +} + +static int read_peek(struct PIPE_DEVICE *pdevice, unsigned long arg) +{ + size_t esize, size1, size2; + struct PIPE_PEEK *userpp = (struct PIPE_PEEK *)arg; + struct PIPE_PEEK pp; + int status = 0; + + if (copy_from_user(&pp, userpp, sizeof(struct PIPE_PEEK)) != 0) + return -EFAULT; + + if (pp.size == 0) + return 0; + + if (down_interruptible(&pdevice->sem)) + return -ERESTARTSYS; + + esize = MIN(pdevice->count, pp.size); + + if (pdevice->tail <= pdevice->head) + size1 = MIN(pdevice->bufsize - pdevice->head, esize); + else + size1 = esize; + + size2 = esize - size1; + + if (copy_to_user(pp.buf, pdevice->pipebuf + pdevice->head, size1) != 0) { + status = -EFAULT; + goto EXIT; + } + + if (size2 != 0) + if (copy_to_user(pp.buf + size1, pdevice->pipebuf, size2) != 0) { + status = -EFAULT; + goto EXIT; + } + + if (put_user(esize, &userpp->size) != 0) + status = -EFAULT; + +EXIT: + up(&pdevice->sem); + + return status; +} + +static int proc_open(struct inode *inodep, struct file *filp) +{ + const char *file_name = filp->f_path.dentry->d_name.name; + const char *parent_file_name = filp->f_path.dentry->d_parent->d_name.name; + struct PROC_INFO *pi; + + printk(KERN_INFO "pipe-driver proc file opened...\n"); + + if ((pi = (struct PROC_INFO *)kmalloc(sizeof(struct PROC_INFO), GFP_KERNEL)) == NULL) + return -ENOMEM; + + pi->pdevice = &g_pdevices[parent_file_name[4] - '0']; + + if (!strcmp(file_name, "bufsize")) + pi->filetype = 1; + else if (!strcmp(file_name, "count")) + pi->filetype = 0; + + filp->private_data = pi; + + return 0; +} + +static int proc_release(struct inode *inodep, struct file *filp) +{ + struct PROC_INFO *pi; + + pi = (struct PROC_INFO *)filp->private_data; + + kfree(pi); + + return 0; +} + +static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) +{ + struct PROC_INFO *pi; + size_t esize, left; + + pi = (struct PROC_INFO *)filp->private_data; + + switch (pi->filetype) { + case 0: + sprintf(pi->strbuf, "%lu\n", (unsigned long)pi->pdevice->count); + left = strlen(pi->strbuf) - *off; + esize = left < size ? left : size; + if (esize != 0) { + if (copy_to_user(buf, pi->strbuf + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + break; + case 1: + sprintf(pi->strbuf, "%lu\n", (unsigned long)pi->pdevice->bufsize); + left = strlen(pi->strbuf) - *off; + esize = left < size ? left : size; + if (esize != 0) { + if (copy_to_user(buf, pi->strbuf + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + break; + } + + return esize; +} + +module_init(generic_init); +module_exit(generic_exit); + +# Makefile + +obj-m += $(file).o + +all: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} modules +clean: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean + +/* loadmulti (bu satırı dosyaya kopyalamayınız) */ + +#!/bin/bash + +module=$2 +mode=666 + +/sbin/insmod ./${module}.ko ${@:3} || exit 1 +major=$(awk "\$2 == \"$module\" {print \$1}" /proc/devices) + +for ((i = 0; i < $1; ++i)) +do + rm -f ${module}$i + mknod ${module}$i c $major $i + chmod $mode ${module}$i +done + +/* unloadmulti (bu satırı dosyaya kopyalamayınız) */ + +#!/bin/bash + +module=$2 + +/sbin/rmmod ./$module.ko || exit 1 +for ((i = 0; i < $1; ++i)) +do + rm -f ${module}$i +done + +/* prog1.c */ #include #include #include #include #include +#include +#include "pipe-driver.h" + +#define PIPE_SIZE 4096 void exit_sys(const char *msg); int main(void) { int fd; - ssize_t n; + char buf[PIPE_SIZE]; + char *str; + size_t len, bufsize, new_bufsize; - if ((fd = open("/proc/generic-char-driver", O_RDWR|O_APPEND)) == -1) + if ((fd = open("pipe-driver3", O_WRONLY)) == -1) exit_sys("open"); - n = write(fd, "xxx", 3); - if (n == -1) - exit_sys("write"); + for (;;) { + printf("Enter text:"); + fflush(stdout); + fgets(buf, PIPE_SIZE, stdin); + if ((str = strchr(buf, '\n')) != NULL) + *str = '\0'; + if (!strcmp(buf, "quit")) + break; - printf("%ld bytes written...\n", (long)n); + if (buf[0] == '!') { + new_bufsize = atoi(&buf[1]); + printf("%zd\n", new_bufsize); + if (ioctl(fd, IOC_PIPE_SETBUFSIZE, new_bufsize) == -1) + exit_sys("ioctl"); + if (ioctl(fd, IOC_PIPE_GETBUFSIZE, &bufsize) == -1) + exit_sys("ioctl"); + printf("new pipe buffer size is %zu\n", bufsize); + } + else { + len = strlen(buf); + if (write(fd, buf, len) == -1) + exit_sys("write"); + + printf("%lu bytes written...\n", (unsigned long)len); + } + } close(fd); @@ -61184,30 +62315,146 @@ void exit_sys(const char *msg) exit(EXIT_FAILURE); } +/* prog2.c */ + +#include +#include +#include +#include +#include +#include +#include +#include "pipe-driver.h" + +#define BUFFER_SIZE 4096 + +void exit_sys(const char *msg); + +int main(void) +{ + int pdriver; + char buf[BUFFER_SIZE + 1]; + int count, size; + ssize_t result; + struct PIPE_PEEK pp; + char *peekbuf; + + if ((pdriver = open("pipe-driver3", O_RDONLY)) == -1) + exit_sys("open"); + + for (;;) { + if (ioctl(pdriver, IOC_PIPE_GETCOUNT, &count) == -1) + exit_sys("ioctl"); + printf("There are (is) %d byte(s) in the pipe\n", count); + printf("Size:"); + scanf("%d", &size); + if (size > BUFFER_SIZE) { + printf("size is very long!...\n"); + continue; + } + if (size == 0) + break; + if (size < 0) { + pp.size = -size; + if ((pp.buf = malloc(-size)) == NULL) { + fprintf(stderr, "cannot allocate memory!...\n"); + exit(EXIT_FAILURE); + } + + if (ioctl(pdriver, IOC_PIPE_PEEK, &pp) == -1) + exit_sys("ioctl"); + + peekbuf = (char *)pp.buf; + for (size_t i = 0; i < pp.size; ++i) + putchar(peekbuf[i]); + putchar('\n'); + + free(pp.buf); + + } + else { + if ((result = read(pdriver, buf, size)) == -1) + exit_sys("read"); + buf[result] = '\0'; + printf("%jd bytes read: %s\n", (intmax_t)result, buf); + } + } + + close(pdriver); + + return 0; +} + +void exit_sys(const char *msg) +{ + perror(msg); + + exit(EXIT_FAILURE); +} + /*-------------------------------------------------------------------------------------------------------------------------- - Biz yukarıdaki örneklerde dosyayı proc dizininin kökünde yarattık. İstersek proc dizininde bir dizin yaratıp dosyalarımızı - o dizinin içerisinde de yaratbilirdik. Bunun için proc_mkdir fonksiyonu kullanılmaktadır: + Şimdi de aygıt sürücülerde zamanla işlemlerinin nasıl yapılacağı üzerinde duracağız. Çekirdeğin zamanlama mekanizması + periyodik oluşturulan donanım kesmeleriyle sağlanmaktadır. Bu kesmelere genel olarak "timer kesmeleri" ya da Linux + terminolojisinde "jiffy" denilmektedir. Eskiden tek CPU'lu makineler kullanıyordu ve eski işlemcilerde işlemcinin içerisinde + periyodik kesme oluşturacak bir mekanizma yoktu. Ancak daha sonraları işlemcilere kendi içerisinde periyodik kesme + oluşturabilecek timer devreleri eklendi. Bugün ağırlıklı olarak birden fazla çekirdeğe sahip işlemcileri kullanıyoruz. Bu + işlemcilerin içerisindeki çekirdeklerin her birinde o çekirdekte periyodik kesme oluşturan (local interrupts) timer devereleri + bulunmaktadır. Böylece her çekirdek kendi timer devresiyle "context switch" yapmakta ve proses istatistiklerini güncellemektedir. + Bugün PC mimarisinde yerel çekirdeklerin kesme mekanizmaları dışında ayrıca bir de eski sistemlerde zaten var olan IRQ0 harttına + bağlı global bir timer devresi de bulunmaktadır. Bu global timer devresi "context switch" yapmakta değil sistem zamanının + ilerletilmesi amacıyla kullanılmaktadır. - struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent); - - Fonksiyonun birinci parametresi yaratılacak dizin'in simini, ikinci parametresi dizinin hanfi dizin içerisinde yaratılacağını belirtir. Bu parametre NULL - geçilirse dizin proc dizininin kökünde yaratılır. Buradan aldığımız geri dönüş değerini proc_create fonksiyonun parent parent parametresinde - kullanırsak ilgili dosyamızı bu dizinde yaratmış oluruz. Örneğin: - - struct proc_dir_entry *pdir; - - pdir = proc_mkdir("generic-char-driver", NULL); - proc_create("info", 0, pdir, &g_proc_ops); - - Dizinlerin silinmesi yine remove_proc_entry fonksiyonuyla yapılabilmektedir. Dizin içerisindeki dosyaları silerken remove_proc_entry fonksiyonund ayine - dosyanın hangi dizin içerisinde olduğu belirtilmelidir. Aslında bütün silme işlemleri proc_remove fonksiyonuyla da yapılabilmektedir. Bu fonksiyon - parametre olarak proc_create ya da proc_mkdir fonksiyonun verdiği geri dönüş değerini alır. - - Aşağıda generic-char-driver isimli aygıt sürücü proc dizinin altında "generic-char-driver" isimli bir dizin yaratmıştır. Bu dizinin içerisinde de - "info" isimli bir dosya yaratmıştır. + Bugünkü Linux sistemlerinde söz konusu olan bu timer devrelerinin hepsi 1 milisaniye, 4 milisaniye ya da 10 milisaniyeye + kurulmaktadır. Eskiden ilk Linux çekirdeklerinde 10 milisaniyelik periyotlar kullanılıyordu. Sonra bilgisayarlar hızlanınca + 1 milissaniye periyot yaygın olarak kullanılmaya başlandı. Ancak bugünlerde 4 milisaniye periyotları kullanan çekirdekler de + yaygın biçimde bulunmaktadır. Aslında timer frekansı çekirdek konfigüre edilirken kullanıcılar tarafından da değiştirilebilmektedir. + (Çekirdek derlenmeden önce çekirdeğin davranışları üzerinde etkili olan parametrelerin belirlenmesi sürecine "çekirdeğin + konfigüre" edilmesi denilmektedir.) ---------------------------------------------------------------------------------------------------------------------------*/ -/* generic-char-driver.c */ +/*-------------------------------------------------------------------------------------------------------------------------- + 154. Ders 12/07/2024 - Cuma +---------------------------------------------------------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------------------------------------------------------- + Global timer kesmelerine (PC mimarisinde IRQ0) ilişkin kesme kodları çekirdek içerisindeki jiffies isimli bir global + değişkeni artırmaktadır. Böylece eğer timer kesme periyodu biliniyorsa iki jiffies değeri arasındaki farka bakılarak + bir zaman ölçümü mümkün olabilmektedir. + + Timer frekeansı Linux kernel içerisindeki HZ isimli sembolik sabitle bildirilmiştir. Timer periyodu çekirdek konfigüre + edilirken değiştirilebilmektedir. Genellikle bu süre 1 ms, 4 ms ya da 10 ms olmaktadır. (Ancak değişik mimarilerde farklı + değerlerde olabilir.) Örneğin kursun yapıldığı sanal makinede timer periyodu 4 milisaneye'dir. Bu da saniyede 250 kez timer + kesmesinin oluşacağı anlamına gelmektedir. Başka bir dyeişle bu makşnede HZ sembolik sabiti 250 olarak define edilmiştir. + + İşte timer kesmesi her oluşturduğunda işletim sisteminin kesme kodu (interrupt handler) devereye girip "jiffies" isimli global + değişkeni 1 artırmaktadır. Bu jiffies değişkeni unsigned long türdendir. Bildindiği gibi unsigned long türü 32 bit Linux + sistemlerinde 32 bit 64 bit Linux sistemlerinde 64 bittir. 32 bit Linux sistemlerinde ayrıca jiffies_64 isimli bir değişken + daha vardır. Bu değişken hem 32 bit sistemde hem de 64 bit sistemde 64 bitliktir. 32 bit sistemde jiffies değişkeni 32 bit + olduğu için bilgisayar uzun süre açık kalırsa taşma (overflow) oluşabilmektedir. Ancak 64 bit sistemlerde taşma mümkün değildir. + 32 bit sistemlerde jiffies_64 değeri çekirdek tarafından iki ayrı makine komutuyla güncellenmektedir. Çünkü 32 bit sistemlerde + 64 bit değeri belleğe tek hamlede yazmak mümkün değildir. Bu nedenle jiffies_64 değerinin taşma durumunda yanlış okunabilme + olasılığı vardır. Hem 32 bit hem 64 bit sistemlerde 64 bitlik jiffies değerini düzgün bir biçimde okuyabilmek için get_jiffies_64 + isimli fonksiyon bulundurulmuştur: + + include + + u64 get_jiffies_64(void); + + Biz 32 bit sistemde de olsak bu fonksiyonla 64 bitlik jiffies değerini düzgün bir biçimde okuyabiliriz. + + Aşağıdaki örnekte çekirdek modülü içerisinde proc dosya sisteminde "jify-module" isimli bir dizin, dizin'in içerisinde de + "jiffy" ve "hertz" isimli iki dosya yaratılmıştır. jiffy dosyası okunduğunda o anki jiffies değeri elde edilmektedir. + hertz dosyası okundauğunda ise timer frekansı elde edilmektedir. Aygıt sürücüyü aşağıdaki gibi derleyip yükleyebilirsiniz: + + $ make file=jiffy-module + $ sudo insmod jiffy-module.ko + + Boşaltımı da şöyle yapabilirsiniz: + + $ sudo rmmod jiffy-driver.ko +---------------------------------------------------------------------------------------------------------------------------*/ + +/* jiffy-module.c */ #include #include @@ -61216,394 +62463,133 @@ void exit_sys(const char *msg) #include #include -#define BUF_SIZE 4096 - MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("General Character Device Driver"); MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("procfs driver"); -static dev_t g_dev; -static struct cdev *g_cdev; -static struct file_operations g_file_ops = { - .owner = THIS_MODULE, +static ssize_t proc_read_jiffy(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read_hertz(struct file *filp, char *buf, size_t size, loff_t *off); + +static struct proc_ops g_procops_jiffy = { + .proc_read = proc_read_jiffy, }; -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); -static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off); -static loff_t proc_llseek(struct file *filp, loff_t off, int whence); - -static struct file_operations g_proc_ops = { - .owner = THIS_MODULE, - .read = proc_read, - .write = proc_write, - .llseek = proc_llseek, +static struct proc_ops g_procops_hertz = { + .proc_read = proc_read_hertz, }; -static struct proc_dir_entry *g_pdir; -static char g_text[BUF_SIZE] = "this is a test\n"; +static char g_jiffies_str[32]; +static char g_hertz_str[32]; static int __init generic_init(void) { - int result; + struct proc_dir_entry *pde_dir; + + if ((pde_dir = proc_mkdir("jiffy-module", NULL)) == NULL) + return -ENOMEM; + - if ((result = alloc_chrdev_region(&g_dev, 0, 1, "generic-char-driver")) < 0) { - printk(KERN_INFO "Cannot alloc char driver!...\n"); - return result; - } - - if ((g_cdev = cdev_alloc()) == NULL) { - printk(KERN_INFO "Cannot allocate cdev!...\n"); + if (proc_create("jiffy", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_jiffy) == NULL) { + remove_proc_entry("jiffy-module", NULL); return -ENOMEM; } - g_cdev->owner = THIS_MODULE; - g_cdev->ops = &g_file_ops; - - if ((result = cdev_add(g_cdev, g_dev, 1)) != 0) { - unregister_chrdev_region(g_dev, 1); - printk(KERN_INFO "Cannot add character device driver!...\n"); - return result; - } - - if ((g_pdir = proc_mkdir("generic-char-driver", NULL)) == NULL) { - unregister_chrdev_region(g_dev, 1); - cdev_del(g_cdev); - printk(KERN_INFO "Cannot create proc directory!...\n"); + if (proc_create("hertz", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_hertz) == NULL) { + remove_proc_entry("jiffy-module", NULL); return -ENOMEM; } - if (proc_create("info", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, g_pdir, &g_proc_ops) == NULL) { - remove_proc_entry("generic-char-driver", NULL); - unregister_chrdev_region(g_dev, 1); - cdev_del(g_cdev); - printk(KERN_INFO "Cannot create proc file!...\n"); - return -ENOMEM; - } - - printk(KERN_INFO "Module initialized with %d:%d device number...\n", MAJOR(g_dev), MINOR(g_dev)); return 0; } static void __exit generic_exit(void) { - cdev_del(g_cdev); - unregister_chrdev_region(g_dev, 1); - remove_proc_entry("generic-char-driver", NULL); - remove_proc_entry("info", g_pdir); - printk(KERN_INFO "Goodbye...\n"); + remove_proc_entry("jiffy-module", NULL); + + printk(KERN_INFO "jiffy-module module exit...\n"); } -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) +static ssize_t proc_read_jiffy(struct file *filp, char *buf, size_t size, loff_t *off) { - ssize_t left, n; + size_t esize; + size_t left; - left = strlen(g_text) - *off; - n = left < size ? left : size; + sprintf(g_jiffies_str, "%lu\n", jiffies); - if (n != 0) { - if (copy_to_user(buf, g_text, n) != 0) + left = strlen(g_jiffies_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_jiffies_str + *off, esize) != 0) return -EFAULT; - *off += n; + *off += esize; } - return n; + printk(KERN_INFO "jiffy file read...\n"); + + return esize; } -static ssize_t proc_write(struct file *filp, const char *buf, size_t size, loff_t *off) +static ssize_t proc_read_hertz(struct file *filp, char *buf, size_t size, loff_t *off) { - ssize_t left, n; + size_t esize; + size_t left; - if (filp->f_flags & O_APPEND) - *off = strlen(g_text); + sprintf(g_hertz_str, "%d\n", HZ); - left = BUF_SIZE - *off; - n = left < size ? left : size; - if (n != 0) { - if (copy_from_user(g_text + *off, buf, n) != 0) + left = strlen(g_hertz_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_hertz_str + *off, esize) != 0) return -EFAULT; - *off += n; + *off += esize; } - return n; -} + printk(KERN_INFO "hertz file read...\n"); -static loff_t proc_llseek(struct file *filp, loff_t off, int whence) -{ - loff_t toff; - - switch (whence) { - case SEEK_SET: - toff = off; - break; - case SEEK_CUR: - toff = filp->f_pos + off; - break; - case SEEK_END: - toff = (loff_t)strlen(g_text) - off; - break; - default: - return -EINVAL; - } - - if (toff > BUF_SIZE || toff < 0) - return -EINVAL; - - filp->f_pos = toff; - - return toff; + return esize; } module_init(generic_init); module_exit(generic_exit); -obj-m += $(file).o +# Makefile + +obj-m += ${file}.o all: make -C /lib/modules/$(shell uname -r)/build M=${PWD} modules clean: make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean -#!/bin/bash - -module=$1 -mode=666 - -/sbin/insmod ./$module.ko ${@:2} || exit 1 -major=$(awk "\$2 == \"$module\" {print \$1}" /proc/devices) -rm -f $module -mknod $module c $major 0 -chmod $mode $module - /*-------------------------------------------------------------------------------------------------------------------------- - Kesmeler (interrupts) konusunda bugün kullandığımız PC mimarisindeki kesme mekanizmasının donanımsal tarafını ele alacağız. - Eskiden tek CPU'lu makineler kullanıyordu. Bugün ağırlıklı olarak birden fazla çekirdeğe sahip işlemcileri kullanıyoruz. - Bu çekirdeklerin her birinin içerisinde o çekirdeğe özgü periyodik kesme oluşturan bir timer devresi vardır. Böylece her çekirdek - kendi timer devresiyle "context switch" yapmakta ve proses istatistiklerini güncellemektedir. Fakat bunların dışında ayrıca bir de - eski sistemlerde zaten var olan IRQ0 harttına bağlı global bir timer bulunmaktadır. Bu global timer "context switch" değil sistem zamanının - ilerletilmesi görevini yapmaktadır. Bugünki Linux sistemlerinde söz konusu olan bu timer'ların hepsi 1 milisaniye ya da 10 milisaniye - periyoda kurulmuş durumdadır. Yani böylece her 1 milisaniye ya da 10 milisaniyede araya timer kesmesi gelip sistem zamanını güncellemktedir. - İşte global timer'ın (IRQ0) her periyotta oluşturduğu kesmeler "jiffies" isimli kernel içerisindeki global bir değişkeni aryırmaktadır. - Dolayısıyla bu jiffies değişkeni iki farklı zamanda okunduğunda geçen gerçek zaman timer periyodu da biiniyorsa elde edilebilmektedir. - - Timer periyodu Linux kernel içerisindeki HZ isimli sembolik sabitle bildirilmiştir. Ancak bu HZ sembolik sabiti bu timer değeri için nihai - belirlemeyi içermez, default durumu belirtir. Timer periyodunun ne olacağı aslında boot işlemi sırasında kernel konfigürasyonlarına da bakılarak - belirlenmektedir. İşte genellikle bu süre 1 ms 4 ms ya da 10 ms olmaktadır. (Ancak değişik mimarilerde farklı değerlerde olabilir.) - - İşte Global timer her bir kesme oluşturduğunda işletim sisteminin kesme kodu (interrupt handler) devereye girip "jiffies" isimli global değişkeni - 1 artırmaktadır. Bu jiffies değişkeni unsigned long türdendir. Bildindiği gibi unsigned long türü 32 bit Linux sistemlerinde 32 bit 64 bit Linux sistemlerinde - 64 bittir. 32 bit Linux sistemlerinde ayrıca jiffies_64 isimli bir değişken daha vardır. bu değişken hem 32 bit sistemde hem de 64 bit sistemde 64 bitliktir. - - Aşağıdaki örnekte aygıt sürücü proc dosya sisteminde bir dosya yaratmış ve o andaki jiffies değerini bu dosyaya yazdırmıştır. Yani biz proc içerisindeki - generic-char-driver isimli dosyayı okuduğumuzda aslında aygıt sürücünün en son yazmış olduğu jiffies değerini görmüş oluruz. ----------------------------------------------------------------------------------------------------------------------------*/ - -/* generic-char-driver.c */ - -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("General Character Device Driver"); -MODULE_AUTHOR("Kaan Aslan"); - -static dev_t g_dev; -static struct cdev *g_cdev; -static struct file_operations g_file_ops = { - .owner = THIS_MODULE, -}; - -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off); - -static struct file_operations g_proc_ops = { - .owner = THIS_MODULE, - .read = proc_read, -}; - -static int __init generic_init(void) -{ - int result; - - if ((result = alloc_chrdev_region(&g_dev, 0, 1, "generic-char-driver")) < 0) { - printk(KERN_INFO "Cannot alloc char driver!...\n"); - return result; - } - - if ((g_cdev = cdev_alloc()) == NULL) { - printk(KERN_INFO "Cannot allocate cdev!...\n"); - return -ENOMEM; - } - - g_cdev->owner = THIS_MODULE; - g_cdev->ops = &g_file_ops; - - if ((result = cdev_add(g_cdev, g_dev, 1)) != 0) { - unregister_chrdev_region(g_dev, 1); - printk(KERN_INFO "Cannot add character device driver!...\n"); - return result; - } - - if (proc_create("generic-char-driver", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, NULL, &g_proc_ops) == NULL) { - cdev_del(g_cdev); - unregister_chrdev_region(g_dev, 1); - printk(KERN_INFO "Cannot create proc file!...\n"); - return -ENOMEM; - } - - printk(KERN_INFO "Module initialized with %d:%d device number...\n", MAJOR(g_dev), MINOR(g_dev)); - - return 0; -} - -static void __exit generic_exit(void) -{ - cdev_del(g_cdev); - unregister_chrdev_region(g_dev, 1); - remove_proc_entry("generic-char-driver", NULL); - printk(KERN_INFO "Goodbye...\n"); -} - -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) -{ - static char sbuf[32]; - ssize_t left, n; - - sprintf(sbuf, "%lu\n", jiffies); - - left = strlen(sbuf) - *off; - n = left < size ? left : size; - if (n != 0) { - if (copy_to_user(buf, sbuf + *off, n) != 0) - return -EFAULT; - *off += n; - } - - return n; -} - -module_init(generic_init); -module_exit(generic_exit); - -obj-m += $(file).o - -all: - make -C /lib/modules/$(shell uname -r)/build M=${PWD} modules -clean: - make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean - -#!/bin/bash - -module=$1 -mode=666 - -/sbin/insmod ./$module.ko ${@:2} || exit 1 -major=$(awk "\$2 == \"$module\" {print \$1}" /proc/devices) -rm -f $module -mknod $module c $major 0 -chmod $mode $module - -/*-------------------------------------------------------------------------------------------------------------------------- - Eğer 64 bit sistemde çalışılıyorsa jiffies değerinin taşması (overflow olması) mümkün değildir. Ancak 32 bit sistemlerde - 49 günde taşma meydana gelebilmektedir. Aygıt sürücü programcısı bazen geçen zamanı hesaplamak için iki noktada jiffies değerini - alıp aradaki farka bakmak isteyebilmektedir. Ancak bu durumda 32 bit sistemlerde "overflow" olasılığının ele alınması gerekir. - İşaretli sayıların ikili sistemdeki temsiline dayanarak iki jiffies arasındaki fark aşağıdaki gibi tek bir ifadeyle de hesaplanabilmektedir: + Yukarıda da belirttiğimiz gibi eğer 64 bit sistemde çalışılıyorsa jiffies değerinin taşması (overflow olması) mümkün değildir. + Ancak 32 bit sistemlerde timer frekansı 1000 ise 49 günde taşma meydana gelebilmektedir. Aygıt sürücü programcısı bazen geçen + zamanı hesaplamak için iki noktada jiffies değerini alıp aradaki farka bakmak isteyebilmektedir. Ancak bu durumda 32 bit + sistemlerde "overflow" olasılığının ele alınması gerekir. İşaretli sayıların ikili sistemdeki temsiline dayanarak iki jiffies + arasındaki fark aşağıdaki gibi tek bir ifadeyle de hesaplanabilmektedir: unsigned long int prev_jiffies, next_jiffies; ... net_jiffies = (long) next_jiffies - (long) prev_jiffies; - Kernel içerisinde iki jiffies değeri alarak bool bir değere geri dönen dört makro bulunmaktadır. Bunlar 32 bit sistemlerde taşma (overflow) durumunu da - değerlerndirmektedir: - + Çekirdek içerisinde iki jiffy değerini alarak bunları öncelik sonralık ilişkisi altında karşılaştıran aşağıdaki fonksiyonlar + bulunmaktadır: + time_after(jiffy1, jiffy2) time_before(jiffy1, jiffy2) time_after_eq(jiffy1, jiffy2) time_before_eq(jiffy1, jiffy2) - Aşağıdaki örnekte proc dosya sisteminde iki okuma arasındaki geçen süre jiffy cinsinden verilmektedir. - ---------------------------------------------------------------------------------------------------------------------------*/ + Bu fonksiyonların hepsi bool bir değere geri dönmektedir. Bu fonksiyonlar 32 bit sistemlerde taşma durumunu da dikkate + almaktadır. time_after fonksiyonu birinci parametresiyle belirtilen jiffy değerinin ikinci parametresiyle belirtilen jiffy + değerinden sonraki bir jiffy değeri olup olmadığını belirlemekte kullanılmaktadır. Diğer fonksiyonlar da bu biçimde birinci + parametredeki jiffy değeri ile ikinci parametredeki jiffy değerini karşılaştırmaktadır. -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) -{ - static char sbuf[1024]; - loff_t left, n; - long int net_jiffies; + Çekirdek içerisinde jiffies değerini çeşitli biçimlere dönüştüren aşağıdaki fonksiyonlar da bulunmaktadır: - net_jiffies = (long)jiffies - (long)g_prev_jiffies; - - sprintf(sbuf, "Son okumadan geçen zaman: %10ld\n", net_jiffies); - - left = (loff_t)strlen(sbuf) - *off; - printk(KERN_INFO "Left: %lld, size=%lu\n", left, size); - n = left < size ? left : size; - - if (n != 0) { - if (copy_to_user(buf, sbuf + *off, n) != 0) - return -EFAULT; - *off += n; - } - - g_prev_jiffies = jiffies; - - return (ssize_t)n; -} - -/*-------------------------------------------------------------------------------------------------------------------------- - Aygıt sürücü içerisinde bazen belli bir süre beklemek gerekebilmektedir. Bekleme işlemi kısa ise meşgul bir döngüde yapılabilir. - Ancak beklenecek süre uzun ise meşgul döngü uygun bir yöntem olmaz. Bu durumda daha önceden de görmüş olduğumuz wait kuyruklarında - bekleme uygun olabilir. - - Uzun beklemelerin aşağıdaki gibi yapılması uygun değildir: - - while (time_before(jiffies, jiffies_target)) - schedule(); - - Buradaki temel sorun CPU'nun çok meşgul edilmesidir. Her ne kadar aygıt sürücü hemen schedule fonksiyonu ile CPU'yu bırakıyor olsa da - context switch önemli bir zaman kaybına (throughput düşmesine) neden olmaktadır. Bu nedenle uzun beklemelerin gerçekten zaman aşımlı wait kuyruklarında - yapılması gerekir. - - Uzun beklemeler için bir wait kuyruğu oluşturulup wait_event_timeout ya da wait_event_interrptible_timeaout fonksiyonlarıyla koşul - 0 yapılarak gerçekleştirilebilir. Ancak bunun için bir wait kuyruğunun oluşturulması gerekir. Bu işlemi zaten kendi içerisinde yapan özel fonksiyonlar vardır. - - schedule_timeout fonksiyonu belli bir jiffy zamanı geçene kadar thread'i önceden oluşturulmuş bir wait kuyruğunda bekletir. - - signed long schedule_timeout(signed long timeout); - - Fonksiyon parametre olarak beklenecek jiffy değerini alır. Eğer sinyal dolayısıyla fonksiyon sonlanırsa kalan jiffy sayısına, eğer zaman aşımının dolması nedeniyle - fonksiyon sonlanısa 0 değerine geri döner. Fonksiyon başarısız olmamaktadır. Fonksiyonu kullanmadan önce prosesin durum bilgisini set_current_state isimli - fonksiyonla değiştirmek gerekir. Değiştirilecek durum TASK_UNINTERRUPTIBLE ya da TASK_INTERRUPTIBLE olabilir. Bu işlem yapılmazsa bekleme gerçekleşmemektedir. - Örneğin: - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(jiffies + 5 * HZ); - - Uzun beklemeyi kendi içerisinde yapan (yani schedule_timeout) fonksiyonunu kullnarak yapan üç yardımcı fonksiyon da vardır: - - void msleep(unsigned int msecs); - unsigned long msleep_interruptible(unsigned int msecs); - void ssleep(unsigned int secs); - void ssleep_interruptible(unsigned int secs); - - Aşağıdaki örnekte /proc/generic dosyasından okuma yapılmaya çalışıldığında 5 saniye kernel mode'da thread bekletilecektir. - Burada yalnızca proc_read fonksiyonu verilmiştir. Yukarıdaki programı bu fonksiyonu değiştirerek kullanabilirsiniz. ----------------------------------------------------------------------------------------------------------------------------*/ - -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) -{ - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(5 * HZ); - - /* ssleep_interruptible(5) */ - - return 0; -} - -/*-------------------------------------------------------------------------------------------------------------------------- - milisaniye, mikrosaniye ve nano saniye değerlerini jiffies değerine dönüştüren üç fonksiyon bulunmaktadır: + #include unsigned long msecs_to_jiffies(const unsigned int m); unsigned long usecs_to_jiffies(const unsigned int m); @@ -61618,24 +62604,400 @@ static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) Bu fonksiyonlar o andaki aktif HZ değerini dikkate almaktadır. Ayrıca jiffies değerini saniye ve nano saniye biçiminde ayırıp bize struct timespec64 biçiminde bir yapı nesnesi olarak veren - jiffies_to_timespec64 isimli bir fonksiyon da vardır. Bunun tersi timespec64_to_jiffies fonksiyonuyla yapılmaktadır. + jiffies_to_timespec64 isimli bir fonksiyon da vardır. Bunun tersi timespec64_to_jiffies fonksiyonuyla yapılmaktadır - Örneğin aygıt sürücü içerisinde 5 saniye bekleme işlemi aşağıdaki gibi de yapılabilirdi ----------------------------------------------------------------------------------------------------------------------------*/ + timespec64 yapısı da şöyledir: -static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) + struct timespec64 { + time64_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ + }; + + Eski çekirdeklerde bu fonksiyonların yerine aşağıdaki fonksiyonlar bulunyordu: + + #include + + unsigned long timespec_to_jiffies(struct timespec *value); + void jiffies_to_timespec(unsigned long jiffies, struct timespec *value); + unsigned long timeval_to_jiffies(struct timeval *value); + void jiffies_to_timeval(unsigned long jiffies, struct timeval *value); + + Aşağıdaki örnekte proc dosya sisteminde "jiffy-module" dizini içerisinde ayrıca "difference" isimli bir dosya da yaratılmıştır. + Bu dosya her okunduğunda önceki okumayla aradaki jiffy farkı yazdırılmaktadır. + ---------------------------------------------------------------------------------------------------------------------------*/ + +/* jiffy-module.c */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("procfs driver"); + +static ssize_t proc_read_jiffy(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read_hertz(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read_difference(struct file *filp, char *buf, size_t size, loff_t *off); + +static struct proc_ops g_procops_jiffy = { + .proc_read = proc_read_jiffy, +}; + +static struct proc_ops g_procops_hertz = { + .proc_read = proc_read_hertz, +}; + +static struct proc_ops g_procops_difference = { + .proc_read = proc_read_difference, +}; + +static char g_jiffies_str[32]; +static char g_hertz_str[32]; +static char g_difference_str[512]; + +static int __init generic_init(void) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(msecs_to_jiffies(5000)); + struct proc_dir_entry *pde_dir; + + if ((pde_dir = proc_mkdir("jiffy-module", NULL)) == NULL) + return -ENOMEM; + + + if (proc_create("jiffy", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_jiffy) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } + + if (proc_create("hertz", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_hertz) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } + + if (proc_create("difference", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_difference) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } return 0; } +static void __exit generic_exit(void) +{ + remove_proc_entry("jiffy-module", NULL); + + printk(KERN_INFO "jiffy-module module exit...\n"); +} + +static ssize_t proc_read_jiffy(struct file *filp, char *buf, size_t size, loff_t *off) +{ + size_t esize; + size_t left; + + sprintf(g_jiffies_str, "%lu\n", jiffies); + + left = strlen(g_jiffies_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_jiffies_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + printk(KERN_INFO "jiffy file read...\n"); + + return esize; +} + +static ssize_t proc_read_hertz(struct file *filp, char *buf, size_t size, loff_t *off) +{ + size_t esize; + size_t left; + + sprintf(g_hertz_str, "%d\n", HZ); + + left = strlen(g_hertz_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_hertz_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + printk(KERN_INFO "hertz file read...\n"); + + return esize; +} + +static ssize_t proc_read_difference(struct file *filp, char *buf, size_t size, loff_t *off) +{ + static unsigned long prev_jiffies; + loff_t left, esize; + long int net_jiffies; + struct timespec64 ts; + + net_jiffies = (long)jiffies - (long)prev_jiffies; + + jiffies_to_timespec64(net_jiffies, &ts); + + sprintf(g_difference_str, "Jiffy difference: %10ld (%ld seconds + %ld nonoseconds)\n", net_jiffies, (long)ts.tv_sec, (long)ts.tv_nsec); + + left = (loff_t)strlen(g_difference_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_difference_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + prev_jiffies = jiffies; + + return (ssize_t)esize; +} + +module_init(generic_init); +module_exit(generic_exit); + +# Makefile + +obj-m += ${file}.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} modules +clean: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean + /*-------------------------------------------------------------------------------------------------------------------------- - Aygıt sürücü içerisinde kısa beklemeler gerebilmektedir. Çünkü bazı donanım aygıtlarının programlanabilmesi için bazı beklemelere gereksinim - duyulabilmektedir. Kısa beklemeler megul döngü yoluyla yani hiç sleep yapğılmadan sağlanmaktadır. Ayrıca kısa bekleme yapan fonksiyonlar atomiktir. - Atomiklikten kastedilen şey preemption işleminin kapatılmasıdır. Yani kısa bekleme yapan fonksiyonlar context switch işlemini o işlemci için kapatırlar. - Bu sırada thread'ler arası geçiş söz konusu olmamaktadır. Ancak donanım kesmeleri bu süre içerisinde oluşabilmektedir. + Aygıt sürücü içerisinde bazen belli bir süre bekleme yapmak gerekebilmektedir. Biz kursumuzda daha önce user modda bekeleme + yapan fonksiyonları görmüştük. Ancak o fonksiyonlar kernel modda kullanılamamtadır. Kermel modda çekirdek içerisindeki + olanaklarla bekleme yapılabilmektedir + + Eğer bekleme süresi kısa ise bekleme işlemi meşgul bir döngü ile yapılabilir. Örneğin: + + while (time_before(jiffies, jiffies_target)) + schedule(); + + Burada o anki jiffies değeri hedef jiffies değerinden küçükse schedule fonksiyonu çağrılmıştır. schedule fonksiyonu thread'i + uykuya yatırmamaktadır. Yalnızca thread'ler arası geçiş oluşmasına yol açmaktadır. Yani bu fonksiyon uykuya dalmadan CPU'yu + bırakmak için kullanılmaktadır. schedule fonksiyonunu çağıran thread çalışma kuyruğunda (run queue) kalmaya devam eder. Yine + çalışma sırası ona geldiğinde kaldığı yerden çalışmaya devam eder. Ancak meşgul bir döngü içerisinde schedule işlemi yine + önemli bir CPU zamanın harcanmasına yol açmaktadır. Bu nedenle uzun beklelemelerin yukarıdaki gibi yapılması tavsiye edilmemektedir. + Uzun beklemelerin uykuya dalarak yapılması gerekir. + + Uzun beklemeler için bir wait kuyruğu oluşturulup wait_event_timeout ya da wait_event_interrptible_timeout fonksiyonlarıyla koşul + 0 yapılarak gerçekleştirilebilir. Ancak bunun için bir wait kuyruğunun oluşturulması gerekir. Bu işlemi zaten kendi içerisinde + yapan özel fonksiyonlar vardır. + + schedule_timeout fonksiyonu belli bir jiffy zamanı geçene kadar thread'i çekirdek tarafından bu amaçla oluşturulmuş olan bir + wait kuyruğunda bekletir. + + signed long schedule_timeout(signed long timeout); + + Fonksiyon parametre olarak beklenecek jiffy değerini alır. Eğer sinyal dolayısıyla fonksiyon sonlanırsa kalan jiffy sayısına, + eğer zaman aşımının dolması nedeniyle fonksiyon sonlanısa 0 değerine geri döner. Fonksiyon başarısız olmamaktadır. Fonksiyonu + kullanmadan önce prosesin durum bilgisini set_current_state isimli fonksiyonla değiştirmek gerekir. Değiştirilecek durum + TASK_UNINTERRUPTIBLE ya da TASK_INTERRUPTIBLE olabilir. Bu işlem yapılmazsa bekleme gerçekleşmemektedir. Örneğin: + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(jiffies + 5 * HZ); + + Uzun beklemeyi kendi içerisinde schedule_timeout kullanarak yapan üç yardımcı fonksiyon da vardır: + + #include + + void msleep(unsigned int msecs); + unsigned long msleep_interruptible(unsigned int msecs); + void ssleep(unsigned int secs); + void ssleep_interruptible(unsigned int secs); + + Aşağıdaki örnekte "jiffy-module" dizinindeki "sleep" dosyasından okuma yapıldığında (denemeyi cat yapabilirsiniz) 10 saniye + bekleme oluşacaktır. +---------------------------------------------------------------------------------------------------------------------------*/ + +/* jiffy-module.c */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kaan Aslan"); +MODULE_DESCRIPTION("procfs driver"); + +static ssize_t proc_read_jiffy(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read_hertz(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read_difference(struct file *filp, char *buf, size_t size, loff_t *off); +static ssize_t proc_read_sleep(struct file *filp, char *buf, size_t size, loff_t *off); + +static struct proc_ops g_procops_jiffy = { + .proc_read = proc_read_jiffy, +}; + +static struct proc_ops g_procops_hertz = { + .proc_read = proc_read_hertz, +}; + +static struct proc_ops g_procops_difference = { + .proc_read = proc_read_difference, +}; + +static struct proc_ops g_procops_sleep = { + .proc_read = proc_read_sleep, +}; + +static char g_jiffies_str[32]; +static char g_hertz_str[32]; +static char g_difference_str[512]; + +static int __init generic_init(void) +{ + struct proc_dir_entry *pde_dir; + + if ((pde_dir = proc_mkdir("jiffy-module", NULL)) == NULL) + return -ENOMEM; + + + if (proc_create("jiffy", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_jiffy) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } + + if (proc_create("hertz", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_hertz) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } + + if (proc_create("difference", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_difference) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } + + if (proc_create("sleep", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, pde_dir, &g_procops_sleep) == NULL) { + remove_proc_entry("jiffy-module", NULL); + return -ENOMEM; + } + + return 0; +} + +static void __exit generic_exit(void) +{ + remove_proc_entry("jiffy-module", NULL); + + printk(KERN_INFO "jiffy-module module exit...\n"); +} + +static ssize_t proc_read_jiffy(struct file *filp, char *buf, size_t size, loff_t *off) +{ + size_t esize; + size_t left; + + sprintf(g_jiffies_str, "%lu\n", jiffies); + + left = strlen(g_jiffies_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_jiffies_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + printk(KERN_INFO "jiffy file read...\n"); + + return esize; +} + +static ssize_t proc_read_hertz(struct file *filp, char *buf, size_t size, loff_t *off) +{ + size_t esize; + size_t left; + + sprintf(g_hertz_str, "%d\n", HZ); + + left = strlen(g_hertz_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_hertz_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + printk(KERN_INFO "hertz file read...\n"); + + return esize; +} + +static ssize_t proc_read_difference(struct file *filp, char *buf, size_t size, loff_t *off) +{ + static unsigned long prev_jiffies; + loff_t left, esize; + long int net_jiffies; + struct timespec64 ts; + + net_jiffies = (long)jiffies - (long)prev_jiffies; + + jiffies_to_timespec64(net_jiffies, &ts); + + sprintf(g_difference_str, "Jiffy difference: %10ld (%ld seconds + %ld nonoseconds)\n", net_jiffies, (long)ts.tv_sec, (long)ts.tv_nsec); + + left = (loff_t)strlen(g_difference_str) - *off; + esize = left < size ? left : size; + + if (esize != 0) { + if (copy_to_user(buf, g_difference_str + *off, esize) != 0) + return -EFAULT; + *off += esize; + } + + prev_jiffies = jiffies; + + return (ssize_t)esize; +} + +static ssize_t proc_read_sleep(struct file *filp, char *buf, size_t size, loff_t *off) +{ + /* + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ * 10); + */ + + ssleep(10); + + return 0; +} + +module_init(generic_init); +module_exit(generic_exit); + +# Makefile + +obj-m += ${file}.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} modules +clean: + make -C /lib/modules/$(shell uname -r)/build M=${PWD} clean + +/*-------------------------------------------------------------------------------------------------------------------------- + Aygıt sürücü içerisinde kısa beklemeler gerebilmektedir. Çünkü bazı donanım aygıtlarının programlanabilmesi için bazı + beklemelere gereksinim duyulabilmektedir. Kısa beklemeler meşgul döngü yoluyla yani hiç sleep yapılmadan sağlanmaktadır. + Ayrıca kısa bekleme yapan fonksiyonlar atomiktir. Atomiklikten kastedilen şey threadler arası geçiş işleminin kapatılmasıdır. + Yani kısa bekleme yapan fonksiyonlar threadler arası geçiş işlemini o işlemci için kapatırlar. Bu sırada thread'ler arası + geçiş söz konusu olmamaktadır. Ancak donanım kesmeleri bu süre içerisinde oluşabilmektedir. Kısa süreli döngü içerisinde bekleme yapan fonksiyonlar şunlardır: @@ -61643,50 +63005,83 @@ static ssize_t proc_read(struct file *filp, char *buf, size_t size, loff_t *off) void udelay(unsigned int usecs); void mdelay(unsigned int msecs); + Burada delay nano saniye cinsinden bekleme yapmak için, udelay mikro saniye cinsinden bekeleme yapmak için mdelay ise + mili saniye cinsinden bekleme yapmak için kullanılmaktadır. ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- - Linux çekirdeklerine belli versionyondan sonra bir timer mekanizması da eklenmiştir. Bu sayede aygıt sürücü programcısı belli bir zaman sonra - belirlediği bir fonksiyonun çağrılmasını saplayabilmektedir. Bu mekanizmaya "kernel timer" mekanizması denilmektedir. Maalesf kernel timer mekanizması da - birkaç kere arayüz olarak değiştirilmiştir. Belli zaman sonra çağrılacak fonksiyonun bir proses adına çalışmadığına dikkat etmek gerekir. Yani belirlenen fonksiyon - çaprıldığında biz current değişkeni ile o andaki prosese erişemeyiz. O anda çalışan prosesin user alanına kopyalamalar yapamayız. Çünkü bu fonksiyon timer tick kesmeleri - tarafından çağrılmaktadır. Son Linux çekirdeklerindeki kernel timer kullanımı şöyledir: + Linux çekirdeklerine belli versiyonyondan sonra bir timer mekanizması da eklenmiştir. Bu sayede aygıt sürücü programcısı + belli bir zaman sonra belirlediği bir fonksiyonun çağrılmasını saplayabilmektedir. Bu mekanizmaya "kernel timer" mekanizması + denilmektedir. Maalesef kernel timer mekanizması da birkaç kere arayüz olarak değiştirilmiştir. Bu mekanizma kullanılıken + dikkat edilmesi gereken bir nokta callback fonksiyonun bir proses bağlamında çağrılmadığıdır. Yani callback fonksiyon + çağrıldığında biz current makrosu ile o andaki prosese erişemeyiz. O anda çalışan prosesin user alanına kopyalamalar + yapamayız. Çünkü callback fonksiyon timer kesmeleri tarafından çağrılmaktadır. Dolayısıyla callback fonksiyon çağrıldığında + o anda hangi prosesin çalışmakta olduğu belli değildir. + + Son Linux çekirdeklerindeki kernel timer kullanımı şöyledir: - 1) struct timer_list türünden bir yapı nesnesi statik düzeyde tanımlanır ve bu yapı nesnesine ilk değeri verilir. İlkdeğer verme işlemi DEFINE_TIMER makrosuyla - yapılabilir. + 1) struct timer_list türünden bir yapı nesnesi statik düzeyde tanımlanır ve bu yapı nesnesine ilk değeri verilir. DEFINE_TIMER + makrosu ile hem tanımlama hem de ilkdeğer verme işlemi birlikte yapılabilir. Makro şöyledir: + + #include #define DEFINE_TIMER(_name, _function) - ya da timer_setup fonksionuyla yapılabilmektedir: + Örneğin: + + DEFINE_TIMER(g_mytimer, timer_proc); + + Ya da alternatif olarak struct timer_list nesnesi yaratılıp timer_setup makrosuyle de ilkdeğer verilebilir. Makronun parametrik + yapısı sşöyledir: + + #include #define timer_setup(timer, callback, flags) - Makronun birinci parametresi timer nesnesinin adresini almaktadır. İkinci parametresi çağrılacak fonksiyonun adresidir. flags parametresi 0 geçilebilir. + Makronun birinci parametresi timer nesnesinin adresini almaktadır. İkinci parametresi çağrılacak fonksiyonun belirtir. + flags parametresi 0 geçilebilir. - 2) Tanımlanan struct timer_list nesnesi add_timer fonksiyonu ile bir bağlı listeye yerleştirilir. + Örneğin: + + static struct timer_list g_mytimer; + + timer_setup(&g_mytimer, timer_proc, 0); + + Buradaki timer fonksiyonun parametrik yapısı şöyle olmalıdır: + + void timer_proc(struct timer_list *tlisr); + + 2) Tanımlanan struct timer_list nesnesi add_timer fonksiyonu ile bir bağlı listeye yerleştirilir. Bu bağlı liste çekirdeğin + içerisinde çekirdek tarafından oluşturulmuş bir listedir. add_timer fonksiyonunun prototipi şöyledir: + + #include void add_timer(struct timer_list *timer); - 3) Daha sonra ne zaman fonksiyonun çağrılacağını anlatmak için modtimer fonksiyonu kullanılır. + 3) Daha sonra ne zaman fonksiyonun çağrılacağını anlatmak için mod_timer fonksiyonu kullanılır. + + #include int mod_timer(struct timer_list *timer, unsigned long expires); - Buradaki expiry parametresi jiffy türündendir. Bu parametre hedef jiffy değerini içermeliir. (Yani jiffies + gecikme jiffy değeri) + Buradaki expiry parametresi jiffy türündendir. Bu parametre hedef jiffy değerini içermelidir. (Yani jiffies + gecikme + jiffy değeri) - 4) Timer nesnesinin silinmesi için del_timer fonksiyonu kullanılmaktadır: + 4) Timer nesnesinin silinmesi için del_timer ya da del_timer_sync fonksiyonu kullanılmaktadır: + + #include int del_timer(struct timer_list * timer); + int del_timer_sync(struct timer_list * timer); - Normal olarak belirlenen fonksiyon yalnızca 1 kez çağrılmaktadır. Ancak bu fonksiyonun içerisinde yeniden mod_timer ile yeniden çağırma sağlanabilmektedir. + del_timer fonksiyonu eğer timer fonksiyonu o anda başka bir işlemcide çalışıyorsa asenkron biçimde silme yapar. Yani fonksiyon + sonlandığında henüz silme gerçekleşmemiş göreli bir süre sonra gerçekleşecek olabilir. Halbuki del_timer_sync fonksiyonu + geri dönünce timer silinmesi gerçekleşmiş olur. Eğer timer silinmezse modül çekirdekten atıldığında tüm sistem çökebilir. - Çağrılması istenen fonksiyonun parametrik yapısı şöyle olmalıdır: - - void call_back_func(struct timer_list *tl); - - Bu fonksiyon add_timer sırasında verdiğimiz struct timer_list adresi parametre yapılarka çağrılmaktadır. - - Aşağıda kernel timer'larının kullanımına ilişkin bir örnek görülmektedir. Bu örnekte kernel timer /proc/generic-char-driver dosyasından - okuma yapıldığında yaratılıp periyodik bir biçimde çağrılmaktadır. 5 çağrımdam sonra yaratılan timer yok edilmiştir. + Normal olarak belirlenen fonksiyon yalnızca 1 kez çağrılmaktadır. Ancak bu fonksiyonun içerisinde yeniden mod_timer çağrılarak + çağırma periyodik hale getirilebilir. + + ---------------------------------------------------------------------------------------------------------------------------*/ /* generic-char-driver.c */ @@ -63139,7 +64534,7 @@ void exit_sys(const char *msg) request: bio ---> bio ---> bio ---> bio ---> bio ---> ... bio: bio_vec[N] - İşte transfer fonksiyonunun kernel tarafından çağrıldığında "request_queue içersindeki request nesnelerine elde edip, bu request nesnelerinin içerisindeki bio nesnelerini elde edip, + İşte transfer fonksiyonunun kernel tarafından çağrıldığında "request_queue içerisindeki request nesnelerine elde edip, bu request nesnelerinin içerisindeki bio nesnelerini elde edip, bio nesneleri içerisindeki bio_vec dizisinde belirtilen transfer bilgilerine ilişkin transfeleri" yapması gerekir. Şüphesiz bu işlem ancak açık ya da örtük iç içe 3 döngü ile yapılabilir. Yani bir döngü request nesnelerini elde etmeli, onun içerisindeki bir döngü bio nesnelerini elde etmeli, onun içerisindeki bir döngü de bio_vec nesleerini elde etmelidir. @@ -63490,7 +64885,7 @@ chmod $mode $module Yukarıdaki blok aygıt sürücüsü bir dosya sistemi ile formatlanarak sanki bir disk bölümüymüş gibi de kullanılabilir. Bunun için önce aygıt sürücünün idare ettiği alanın formatlanması gerekir. Formatlama işlemi mkfs isili utility programla yapılmaktadır: - sudo mkfs -t ext2 generic-bdriver + $ sudo mkfs -t ext2 generic-bdriver Burada -t seçeneği volümün hangi dosya sistemi ile formatlanacağını belirtmektedir. Formatlama aslında volümdeki bazı sektörlerde özel meta alanlarının yaratılması anlamına gelmektedir. Dolayısıyla mkfs komutu aslında ilgili aygıt sürücüyü açıp onun bazı sektörlerine @@ -63499,7 +64894,7 @@ chmod $mode $module Formatlama işleminden sonra artık blok aygıt sürücüsünün mount edilmesi gerekmektedir. mount işlemi bir dosya sisteminin dizin ağacının belli bir dizinine monte edilmesi anlamına gelmektedir. Dolayısıyla mount komutunda kullanıcı block aygıt sürücüsünü ve mount edilecek dizini girmektedir. Örneğin: - sudo mount generic-bdriver /mnt/myblock + $ sudo mount generic-bdriver /mnt/myblock Burada mount noktası (mount point) /ment dizinin altında myblock isimli dizindir. Bu dizinin kullanıcı tarafından önceden mkdir komutu ile yaratılması gerekir. Tabii mount noktalarının /mnt dizinin altında bulndurulması gibi zorunluluk yoktur. Mount noktasına ilişkin dizinin içinin @@ -63510,7 +64905,7 @@ chmod $mode $module Aygıt sürücümüzü mount ettikten sonra artık onu unmount etmeden rmmod komutuyla boşaltamayız. mount edilen dosya sistemi umount komutuyla eski haline getirilmektedir. Örneğin: - sudo umount /mnt/myblock + $ sudo umount /mnt/myblock umount komutunun komutu argümanının mount noktasını belirten dizin olduğuna dikkat ediniz. Tabii aslında umount komutu da işlemini umount isimli sistem fonksiyonuyla yapmaktadır. ---------------------------------------------------------------------------------------------------------------------------*/ @@ -63528,18 +64923,18 @@ chmod $mode $module 2) Şimdi losetup isimli programla bu dosyanın bir loopback aygıt sürücüsü ile ilişkilendirilmesi gerekmektedir. Örneğin: - sudo losetup /dev/loop0 mydisk.dat + $ sudo losetup /dev/loop0 mydisk.dat Artık biz /dev/loop0 blok aygıt sürücüsü ile işlem yaptığımızda bu aygıt sürücü hedef olarak mydisk.dat dosyasını kullanacaktır. 3) Artık dosya sistemini mkfs ile formatlayabiliriz: - sudo mkfs -t ext2 /dev/loop0 + $ sudo mkfs -t ext2 /dev/loop0 4) Artık mount işlemi yapabiliriz: - mkdir mydisk - sudo mount /dev/loop0 mydisk + $ mkdir mydisk + $ sudo mount /dev/loop0 mydisk Artık mydisk dizini bizim için adeta bir disk volümü gibidir. Ancak orada yapacağımız işlemler aslında yalnızca mydisk.dat dosyasını etkileyecektir. @@ -63547,12 +64942,11 @@ chmod $mode $module 5) Önce volüm umont yapılır: - sudo umount mydisk + $ sudo umount mydisk 5) Şimdi /dev/loop0 ile dosya (yani mydisk.dat) arasında bağlantı losetup -d komutuyla koparılır - sudo losetup -d /dev/loop0 - + $ sudo losetup -d /dev/loop0 ---------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------- @@ -63580,12 +64974,12 @@ chmod $mode $module versiyonu yerel makineye indirilmelidir. İndirme işlemi doğrudan tarayıcı yoluyla yapılabileceği gibi komut satırından wget utility'si ile de yapılabilmektedir. Örneğin: - wget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.0.10.tar.xz + $ wget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.0.10.tar.xz Kaynak kodlar indirildikten sonra bunlar açılır. Kaynak kodların bulundurulması için en normal yer /usr/src dizinidir. Bu dizine yazma hakkımız olmadığı için açım işleminin sudo ile yapılması gerekir. Örneğin: - sudo tar -xvf linux-5.0.10.tar.gz -C /usr/src + $ sudo tar -xvf linux-5.0.10.tar.gz -C /usr/src Bu komutla artık kaynak kodlar /usr/src dizinin altına açılmıştır. @@ -63594,15 +64988,15 @@ chmod $mode $module çalıştırarak build işlemini yapmaktadır. Kernel'ın build edilmesi için gereken başka yardımcı araçlar da bulunmaktadır. Dolayısıyla bu araçlar eğer sistemimizde yoksa onları da indirip kurmalıyız. Genellikle olmayan araçlar şunlardır: - sudo apt-get install libssl-dev - sudo apt-get install libelf-dev + $ sudo apt-get install libssl-dev + $ sudo apt-get install libelf-dev Eğer build işleminde sorun çıkarsa sisteminizde başka gerekli araçların olmadığından şüphelenmelisiniz. Pek çok sistemde aşağıdaki araçlar default durumda bulunmaktadır. Ancak eğer build işlemi başarısız olursa aşağıdaki araçların olmadığından şüphelenmelisiniz: - sudo apt-get install libncurses5-dev - sudo apt-get install flex - sudo apt-get install bison + $ sudo apt-get install libncurses5-dev + $ sudo apt-get install flex + $ sudo apt-get install bison Debian türevi sistemlerde ayrıca paket yöneticisinin repository dosyasının sudo apt-get update komutu ile güncellenmesi gerekebilmektedir. @@ -63611,23 +65005,23 @@ chmod $mode $module belirtmektedir. Normal olarak kernel konfigürasyon dosyası utility'ler tarafında oluşturulmaktadır. Bu utility'ler bize onlarca soru sormaktadır. Tipik utility'ler şunlardır: - make config - make menuconfig - make xconfig + $ make config + $ make menuconfig + $ make xconfig Söz konusu konfigürasyon dosyasının ismi ".config" biçimindedir. Bu konf,igürasyon dosyasının oluşturulması biraz zahmetli olduğu için daha çok mevcut bir .config dosyasını kullanma yöntemine gidilmektedir. Aslında aşağıdaki komutla default bir .config dosyası oluşturulabilir: - sudo make defconfig + $ sudo make defconfig Ancak daha sağlam bir yöntem /boot dizini içerisinde bulunan mevcut sistemin konfigürasyon dosyasını yeni kaynak kod dizinine .config ismiyle kopyalamak olabilir. Örneğin: - sudo cp /boot/config-4.15.0-20-generic .config + $ sudo cp /boot/config-4.15.0-20-generic .config Kernel konfigürasyon dosyası bu biçimlerle elde edilmişse oluşturacağımız yeni kernel'a uyumunu sağlamak için aşağıdaki komut uygulanmalıdır: - sudo make oldconfig + $ sudo make oldconfig Bu komut referans aldığımız .config dosyasının içine bakar. Versiyon uyuşmazlığı ile ilgili satırları siler. Bu komut uygulandığında biz uyuşmazlıklarla ilgili birtakım sorular sorabilmektedir. Bu sorular default seçeneklerle geçilebilir. Tabii biz yine istersek bu .config @@ -63635,18 +65029,18 @@ chmod $mode $module Artık tüm hazırlıklar yapılmıştır ve uzun bir zaman alabilen build işlemi yapılacaktır. Build işlemi aşağıdaki komut ile başlatılabilir: - sudo make + $ sudo make Ancak işlemi hızlandırmak içib birden fazla çekirdeğin kullanılmasını -j seçeneği ile sağlayabiliriz. Örneğin: - sudo make -j 4 + $ sudo make -j 4 Burada 4 çekirdek birlikte build işlemine katılacaktır. Build işlemi bittikten sonra artık yeni sistem için son iki işlem yapılmalıdır. Bunlardan birincisi oluşturulan ama kernel image içerisinde olmayan bazı aygıt sürücülerin uygun dizinlere çekilmesidir. Bu işlem aşağıdaki komutla yapılır: - sudo make modules_install + $ sudo make modules_install Böylece biz aygıt sürücüleri de yenilemiş olmaktayız. Burada söz konusu modüller kernel image içerisinde olmayan fakat çeşitli aşamalarda yüklenen aygıt sürücü modülleridir. @@ -63654,7 +65048,7 @@ chmod $mode $module Nihayet ikinci olarak gerçek kernel image dosyasının ve yardımcı birkaç dosyann /boot dizinine çekilip boot-loader'ın güncellenmesidir. Bu da şu komutla yapılmaktadır: - sudo make install + $ sudo make install Linux işletim sisteminin boot edilmesi sırasında yüklenecekgerçek kernel image dosyasının /boot dizininde bulunması gerekmektedir. Genellikle bu kernel image dosyası vmlinuz-xxxx biçiminde isimlendirilmiş durumadır. Ancak kernel'ın boot edilmesi için "kök dosya sistemini @@ -63673,14 +65067,14 @@ chmod $mode $module temel alınarak ayarlanır. Ama bu dosyanın güncellenmesi zordur. Bunun için daha basit yöntem /etc/default/grub dosyası üzerinde basit değişiklikler yapmak ama bunu yaptıktan sonra aşağıdaki komutu uygulamaktır: - sudo update-grub + $ sudo update-grub Bu komutla birkte aslında /boot/grub/grub.cfg dosyası da güncellenmektedir. GRUB güncellemesinin diğer bir yolu da bunun için yazılmış olan GUI programlarını kullanmaktır. Yaygın olanlardan biri grub-customizer isimli programdır. Bu program şöyle kurulabilir: - sudo add-apt-repository ppa:danielrichter2007/grub-customizer - sudo apt-get update - sudo apt-get install grub-customizer + $ sudo add-apt-repository ppa:danielrichter2007/grub-customizer + $ sudo apt-get update + $ sudo apt-get install grub-customizer Böylece artık grub-customizer yazıldığnda bir GUI program görüntülenecektir. diff --git a/YapayZeka-MakineOgrenmesi-VeriBilimi-OzetNotlar-Ornekler.txt b/YapayZeka-MakineOgrenmesi-VeriBilimi-OzetNotlar-Ornekler.txt index 2769006..813f09d 100644 --- a/YapayZeka-MakineOgrenmesi-VeriBilimi-OzetNotlar-Ornekler.txt +++ b/YapayZeka-MakineOgrenmesi-VeriBilimi-OzetNotlar-Ornekler.txt @@ -1,4 +1,4 @@ -#---------------------------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------------------------- Yapay Zeka, Makine öğrenmesi ve Veri Bilimi Kursu @@ -1482,10 +1482,12 @@ result = norm.ppf([0.50, 0.68, 0.95], 100, 15) print(result) #---------------------------------------------------------------------------------------------------------------------------- - norm nesnesinin ilişkin olduğu sınıfın pdf (probability density function) isimli metodu yine x değerlerinin Gaus eğrisindeki + norm nesnesinin ilişkin olduğu sınıfın pdf (probability density function) isimli metodu yine x değerlerinin Gauss eğrisindeki y değerlerini vermektedir. Metodun parametrik yapısı şöyledir: pdf(x, loc=0, scale=1) + + Burada yine x hesaplanacak değeri, loc ortalamayı ve scale de standart sapmayı belirtmektedir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np @@ -1511,7 +1513,6 @@ plt.show() rvs(loc=0, scale=1, size=1) #---------------------------------------------------------------------------------------------------------------------------- -import numpy as np from scipy.stats import norm import matplotlib.pyplot as plt @@ -1626,19 +1627,19 @@ axis.arrow(2.5, 0.25, -2, -0.1, width=0.0255) plt.show() #---------------------------------------------------------------------------------------------------------------------------- - Diğer çok karşılaşılan sürekli dağılım "sürekli düzgün dağılım (continous uniform distribution)" denilen dağılımdır. - Burada dağılımı temsil eden a ve b değerleri vardır. Sürekli düzgün dağılımın olasılık yoğunluk fonksiyonu dikdörtgensel - bir alandır. Dolayısıyla kümülatif dağılım fonksiyonu x değeriyle orantılı bir değer vermektedir. Sürekli düzgün dağılımın - olasılık yoğunluk fonksiyonu şöyle ifade edilebilir: + Diğer çok karşılaşılan sürekli dağılım "sürekli düzgün dağılım (continuos uniform distribution)" denilen dağılımdır. Burada + dağılımın a ve b biçiminde isimlendirebileceğimiz iki parametresi vardır. Sürekli düzgün dağılımın olasılık yoğunluk fonksiyonu + dikdörtgensel bir alandır. Dolayısıyla kümülatif dağılım fonksiyonu b - a değeriyle orantılı bir değer vermektedir. Sürekli düzgün + dağılımın olasılık yoğunluk fonksiyonu şöyle ifade edilebilir: f(x) = { 1 / (b - a) a < x < b 0 diğer durumlarda } - Sürekli düzgün dağılım için Python'ın standart kütüphanesinde bir sınıf bulunmamaktadır. NumPy'da da böyle bir sınıf yoktur. + Sürekli düzgün dağılım için Python'un standart kütüphanesinde bir sınıf bulunmamaktadır. NumPy'da da böyle bir sınıf yoktur. Ancak SciPy içerisinde stats modülünde uniform isimli bir singleton nesne bulunmaktadır. Bu nesneye ilişkin sınıfın yine - cdf, ppf, pdf ve rvs metotları vardır. Bu metotlar sırasıyl a değerini ve a'dan uzunluğu parametre olarak almaktadır. + cdf, ppf, pdf ve rvs metotları vardır. Bu metotlar sırasıyla a değerini ve a'dan uzunluğu parametre olarak almaktadır. Örneğin: result = uniform.pdf(15, 10, 10) @@ -1664,7 +1665,7 @@ B = 20 x = np.linspace(A - 5, B + 5, 1000) y = uniform.pdf(x, A, B - A) -plt.title('Continupos Uniform Distribution', fontweight='bold') +plt.title('Continuous Uniform Distribution', fontweight='bold') plt.plot(x, y) x = np.linspace(10, 12.5, 1000) @@ -10295,8 +10296,9 @@ print(convert_text(word_numbers_list[0])) model.summary() Burada ağın girdi katmanında tek sütunlu bir veri kümesi olduğuna dikkat ediniz. Çünkü biz ağa artık yazıları girdi olarak - vereceğiz. Bu yazılar TextVectorization katmanına sokulacak abcak bu katmandan sözcük hanzesi kadar çıktı elde edilecektir. - Bu çıktılarda sonraki Dense katmana verilmiştir. + vereceğiz. Bu yazılar TextVectorization katmanına sokulacak ancak bu katmandan sözcük hanzesi kadar süundan oluşan vektör + çıktısı elde edilecektir. Bu çıktılarda sonraki Dense katmana verilmektedir. Yani TextVectorization katmanı aldığı bir + batch yazıyı o anda vektör haline getirip sonraki katmana iletmektedir. TextVectorization sınıfı diğer katman nesnelerinde olduğu gibi tensorflow.keras.layers modülü içerisinde bulunmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: @@ -10467,8 +10469,8 @@ for presult in predict_result[:, 0]: #---------------------------------------------------------------------------------------------------------------------------- Parçalı eğitim ve test işlemi için fit, evaluate ve predict metotlarının birinci parametrelerinde x verileri yerine bir - "üretici fonksiyon (generator)" ya da "Sequence sınıfından türetilmiş olan bir sınıf nesnesi" girilir. Biz burada önce - üretici fonksiyon yoluyla sonra da Sequence sınıfından türetilmiş olan sınıf yoluyla parçalı işlemlerin nasıl yapılacağını + "üretici fonksiyon (generator)" ya da "PyDataset sınıfından türetilmiş olan bir sınıf nesnesi" girilir. Biz burada önce + üretici fonksiyon yoluyla sonra da PyDataset sınıfından türetilmiş olan sınıf yoluyla parçalı işlemlerin nasıl yapılacağını göreceğiz. Eskiden Keras'ta normal fit, evaluate ve predict metotlarının ayrı fit_generator, evalute_generator ve predict_generator biçiminde parçalı eğitim için kullanılan biçimleri vardı. Ancak bu metotlar daha sonra kaldırıldı. Artık fit, evaluate ve predcit metotları hem bütünsel hem de parçalı işlemler yapabilmektedir. @@ -10513,7 +10515,7 @@ def data_generator(): model = Sequential(name='Diabetes') -model.add(Input(NFEATURES, )) +model.add(Input((NFEATURES, ))) model.add(Dense(16, activation='relu', name='Hidden-1')) model.add(Dense(16, activation='relu', name='Hidden-2')) model.add(Dense(1, activation='sigmoid', name='Output')) @@ -10527,9 +10529,7 @@ model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH) verilebilmektedir. Bunun için yine fit metodunun validation_data parametresine bir üretici fonksiyon nesnesi girilir. fit metodu da her epcoh sonrasında validation_steps parametresinde belirtilen miktarda bu üretici fonksiyon üzerinde iterasyon yaparak bizden sınama verilerini almaktadır. Böylece biz her epoch sonrasında kullanılacak sınama verilerini fit metoduna - üretici fonksiyon nesnesi yoluyla parça parça vermiş oluruz. Sınama verilerinin parçalı oluşturulması sırasında üretici - fonksiyonlarda her epoch için validation_steps parametresi kadar değil bundan 1 fazla yield işlemi yapılmalıdır. Bu fit - metodunun içsel tasarımıyla ilgilidir. + üretici fonksiyon nesnesi yoluyla parça parça vermiş oluruz. Aşağıda sınama verilerinin parçalı bir biçimde nasıl verildiğine ilişkin bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- @@ -10555,12 +10555,12 @@ def validation_generator(): x = np.random.random((BATCH_SIZE, NFEATURES)) y = np.random.randint(0, 2, BATCH_SIZE) for _ in range(EPOCHS): - for _ in range(VALIDATION_STEPS + 1): + for _ in range(VALIDATION_STEPS): yield x, y model = Sequential(name='Diabetes') -model.add(Input(NFEATURES, )) +model.add(Input((NFEATURES,))) model.add(Dense(16, activation='relu', name='Hidden-1')) model.add(Dense(16, activation='relu', name='Hidden-2')) model.add(Dense(1, activation='sigmoid', name='Output')) @@ -10683,6 +10683,14 @@ predict_result = model.predict(prediction_generator(), steps=PREDICTION_STEPS) 45. Ders - 29/06/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- + 46. Ders - 30/06/2024 - Pazar +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + 47. Ders - 06/07/2024 - Cumartesi +#---------------------------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------------------------- Şimdi de gerçekten bellekte büyük bir yer kaplayan vektörizasyon işlemi gerektiren bir örneği parçalı bir biçimde eğitelim ve test edelim. Bu örnek için IMDB veri kümesini kullanalım. Burada karşılaşacağımız önemli bir sorun "karıştırma (shuffling)" @@ -10695,357 +10703,990 @@ predict_result = model.predict(prediction_generator(), steps=PREDICTION_STEPS) 1) Veri kümesi yine read_csv fonksiyonu ile tek hamlede belleğe okunabilir. Her batch işleminde bellekteki ilgili batch'lik kısım verilebilir. Karıştırma da bellek üzerinde yapılabilir. Ancak veri kümesini belleğe okumak yapılmak istenen şeye - bir tezat oluşturmaktadır. Ancak yine de text işlemlerinde asıl bellekte yer kaplayan unsur vektörizasyon işleminden elde + bir tezat oluşturmaktadır. Fakat yine de text işlemlerinde asıl bellekte yer kaplayan unsur vektörizasyon işleminden elde edilen matris olduğu için bu yöntem kullanılabilir. 2) Veri kümesi bir kez okunup dosyadaki kaydın offset numaraları bir dizide saklanabilir. Sonra bu dizi karıştırılıp dizinin elemanlarına ilişkin kayıtlar okunabilir. Eğer veritabanı üzerinde doğrudan çalışılıyorsa da işlemler benzer biçimde yürütülebilir. - + Pekiyi biz vektörizasyon işlemini TextVectorization sınıfı türünden katman nesnesiyle yapmaya çalışırken tüm verileri yine + belleğe çekmek zorunda değil miyiz? Aslında TextVectorization sınıfının adapt metodu henüz görmediğimiz Tensorflow kütüphanesindeki + Dataset nesnesini de parametre olarak alabilmektedir. Bu sayede biz bir Dataset nesnesi oluşturarak adapt metodunun da + verileri parçalı bir biçimde almasını sağlayabiliriz. Scikit-learn kütüphanesindeki CountVectorizer sınıfının fit metodu + da aslında dolaşılabilir nesneyi parametre olarak alabilmektedir. Dolayısıyla CountVectorizer kullanılırken yine üretici + fonksiyonlar yoluyla biz verileri fit metoduna parça parça verebilmekteyiz. Dosya nesneslerinin de dolaşılabilir nesneler + olduğunu anımsayınız. #---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- + Aşağıdaki örnekte IMDB veri kümesi tek hamlede değil parça parça okunarak eğitim yapılmıştır. HEr epcoh2tan sonra karıştırma + yapılacağı için asıl dosyanın karıştırılması uygun olmadığından dolayı her satırın offset'i bir listede toplanıp bu liste + karıştırılmıştır. Ancak orijinal IMDB verki kümesinde maalesef bazı yorumlar birden fazla satırdan oluşmaktadır. Bu yorumlarındaki + sayısı çok azdır. Bu nedenle biz orijinal IMDB veri kümesinde bu satırları atan ve bu bu veri kümesini dönüştüren bir program + yazdık. Program şöyldir: + + import pandas as pd + + df = pd.read_csv('IMDB Dataset.csv', encoding='latin-1') + + for index, text in enumerate(df['review']): + rtext = text.replace('\n', ' ') + rtext = rtext.replace('\r', ' ') + df.iloc[index, 0] = rtext + + df.to_csv('imdb.csv', index=False) + + Burada hedef olarak "imdb.csv" dosyası oluşturulmuştur. Bu dosyadaki satırların bir offset listesinde oluşturulması da aşağıdaki + gibi bir kodla gerçekleştirilmiştir: + + def record_offsets(f, skiprows=1): + offsets = [] + for i in range(skiprows): + f.readline() + offsets.append(f.tell()) + while f.readline() != '': + offsets.append(f.tell()) + offsets.pop() + + return offsets + + Burada biz her satırın offset numarasını offsets isimli listede toplayıp bu listeyle geri döndük. Ancak okuma işleminden sonra + offset bilgisi saklandığı için son satır okununca EOF offset'i de listenin sonunda bulunmaktadır. pop işlemi ile bu son elemanı + sildiğimize dikkat ediniz. Programımızdaki üretici fonksiyon şöyle yazılmıştır: + + def data_generator(f, epochs, steps_per_epoch, batch_size, offsets): + reader = csv.reader(f) + for _ in range(epochs): + random.shuffle(offsets) + for batch_no in range(steps_per_epoch): + x = [] + y = [] + for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]: + f.seek(offset, 0) + result = next(reader) + x.append(result[0]) + y.append(1 if result[1] == 'positive' else 0) + yield tf.convert_to_tensor(x), tf.convert_to_tensor(y) + + Bu örneğimizde sınama veri kümesi ve test veri kümesi kullanılmadığı için yalnızca epoch-loss grafiği çizdirilmiştir. +#---------------------------------------------------------------------------------------------------------------------------- + +import math +import random +import csv +import pandas as pd +import tensorflow as tf + EPOCHS = 5 BATCH_SIZE = 32 -import pandas as pd +def record_offsets(f, skiprows=1): + offsets = [] + for i in range(skiprows): + f.readline() + offsets.append(f.tell()) + while f.readline() != '': + offsets.append(f.tell()) + + offsets.pop() + + return offsets + +f = open('imdb.csv', encoding='latin-1') +offsets = record_offsets(f) -df = pd.read_csv('IMDB Dataset.csv') - -from sklearn.feature_extraction.text import CountVectorizer - -cv = CountVectorizer(dtype='int8') -cv.fit(df['review']) - -df_x = df['review'] -df_y = df['sentiment'] - -import numpy as np - -dataset_y = np.zeros(len(df_y), dtype='int8') -dataset_y[df['sentiment'] == 'positive'] = 1 - -from sklearn.model_selection import train_test_split - -temp_df_x, test_df_x, temp_y, test_y = train_test_split(df_x, dataset_y, test_size=0.20) -training_df_x, validation_df_x, training_y, validation_y = train_test_split(temp_df_x, temp_y, test_size=0.20) - -def data_generator(data_df_x, data_y, steps, shuffle=True): - indexes = list(range(steps)) - for _ in range(EPOCHS): - if shuffle: - np.random.shuffle(indexes) - for i in range(steps): - start_index = indexes[i] * BATCH_SIZE; - stop_index = (indexes[i] + 1) * BATCH_SIZE - - x = cv.transform(data_df_x.iloc[start_index:stop_index]).todense() - y = data_y[start_index:stop_index] - - yield x, y +def data_generator(f, epochs, steps_per_epoch, batch_size, offsets): + reader = csv.reader(f) + for _ in range(epochs): + random.shuffle(offsets) + for batch_no in range(steps_per_epoch): + x = [] + y = [] + for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]: + f.seek(offset, 0) + result = next(reader) + x.append(result[0]) + y.append(1 if result[1] == 'positive' else 0) + yield tf.convert_to_tensor(x), tf.convert_to_tensor(y) + +df = pd.read_csv('imdb.csv') from tensorflow.keras import Sequential -from tensorflow.keras.layers import Dense +from tensorflow.keras.layers import Input, Dense, TextVectorization + +tv = TextVectorization(output_mode='count') +tv.adapt(df['review']) model = Sequential(name='IMDB') -model.add(Dense(64, activation='relu', input_dim=len(cv.vocabulary_), name='Hidden-1')) -model.add(Dense(64, activation='relu', name='Hidden-2')) -model.add(Dense(1, activation='sigmoid', name='Output')) +model.add(Input((1, ), dtype='string')) +model.add(tv) +model.add(Dense(128, activation='relu', name='Hidden-1')) +model.add(Dense(128, activation='relu', name='Hidden-2')) +model.add(Dense(1, activation='sigmoid', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) - -steps_per_epoch = len(training_df_x) // BATCH_SIZE; -steps_per_validation = len(validation_df_x) // BATCH_SIZE; -steps_per_test = len(test_df_x) // BATCH_SIZE; - -hist = model.fit(data_generator(training_df_x, training_y, steps_per_epoch), steps_per_epoch=steps_per_epoch, validation_data=data_generator(validation_df_x, validation_y, steps_per_validation + 1, False), validation_steps=steps_per_validation, epochs=EPOCHS) +hist = model.fit(data_generator(f, EPOCHS, math.ceil(len(offsets) / BATCH_SIZE), BATCH_SIZE, offsets), + batch_size=BATCH_SIZE, steps_per_epoch=math.ceil(len(offsets) / BATCH_SIZE), epochs=EPOCHS) import matplotlib.pyplot as plt -plt.figure(figsize=(15, 5)) -plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold') -plt.xlabel('Epochs') -plt.ylabel('Loss') -plt.xticks(range(0, 210, 10)) - +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Loss Graph', pad=10, fontsize=14) +plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['loss']) -plt.plot(hist.epoch, hist.history['val_loss']) -plt.legend(['Loss', 'Validation Loss']) +plt.legend(['Loss']) plt.show() -plt.figure(figsize=(15, 5)) -plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold') -plt.xlabel('Epochs') -plt.ylabel('Loss') -plt.xticks(range(0, 210, 10)) +# prediction +predict_df = pd.read_csv('predict-imdb.csv') +predict_result = model.predict(predict_df) + +for presult in predict_result[:, 0]: + if (presult > 0.5): + print('Positive') + else: + print('Negative') +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + Peki parçalı verilerle eğitim yapılırken sınama, test ve kestirim işlemlerini de parçalı bir biçimde yapabilir miyiz? + Evet bu işlemlerin hepsi parçalı bir biçimde yapılabilmektedir. fit metodunda validation_data parametresi bir üretici + nesne olarak (ya da bir PyDataset nesnesi olarak) girilirse bu durumda her epoch'tan sonra sınama verileri bu + üretici fonksiyondan (ya da PyDataset nesnesinden) elde edilecektir. Ancak bu durumda fit metodunun validation_steps + parametresinde kaç dolaşımla (yield işlemi ile) sınama verilerinin elde edileceği de girilmelidir. Örneğin: + + model.fit(..., validation_data=validation_generator(), validation_steps=32) + + Burada sınama verilerinin elde edilmesi için toplma 32 kez dolaşım (yield işlemi) yapılacaktır. Her epoch sonrasındaki + sınamada sınama veri kümesinin karıştırılmasına gerek yoktur. + + Test işlemi de benzer biçimde parçalı olarak yapılabilir. Bunun için Sequential sınıfının x parametresine bir üretici + nesne (ya da bir Sequential nesnesi) girilir. Test işlemi yapılırken kaç kere dolaşım uygulanacağı da steps parametresiyle + belirtilmektedir. Örneğin: + + eval_result = model.evalute(test_generator(), steps=32) + + Kestirim işleminde parçalı veri kullanılmasına genellikle gereksinim duyulmuyor olsa da kestirim işlemi yine parçalı + verilerle yapılabilir. Bunun için Sequential sınıfının predict metdounda x parametresine bir üretici nesne (ya da + PyDataset nesnesi) nesne gerilir. Yine metodun steps parametresi sınama verileri için kaç kez dolaşım uygulanacağını + (yani yield yapılacağını) beelirtir. Örneğin: + + predict_result = model.predict(predict_generator(), steps=32) +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + Aşağıda IMDB veri kümesi üzerinde üretici fonksiyonlar yluyla parçalı üretim, parçalı sınama, parçalı test ve + parçalı kestirim işlemlerine örnek verilmiştir. Sınamaların (validation) her epoch sonrasında yapıldığını anımsayınız. + Burada parçalı sınama yapılırkesn iç içe iki döngü kullanılmıştır. Birinci dönü epoch döngüsü, ikinci döngü batch döngüsüdür: + + def training_validation_test_generator(f, epochs, steps, batch_size, offsets, shuffle=False): + reader = csv.reader(f) + for _ in range(epochs): + if shuffle: + random.shuffle(offsets) + for batch_no in range(steps): + x = [] + y = [] + for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]: + f.seek(offset, 0) + result = next(reader) + x.append(result[0]) + y.append(1 if result[1] == 'positive' else 0) + yield tf.convert_to_tensor(x), tf.convert_to_tensor(y) + + Fonksiyonda eopchs * steps kadar yield işlemi yapıldığına dikkat ediniz. Bu fonksiyon hem eğitim, hem sınama hemi de test + amacıyla kullanılmıştır. Tabii test işleminde bir epoch kavramı yoktur. Bu nedenle test işleminde burada epochs parametresi + 1 olarak grilmiştir. Yukarıda da belirttiğimiz gibi kestirimlerde genellikle parçalı işlemlere gereksinim duyulmamaktadır. + Ancak biz burada parçalı kestirime de bir örnek vermek istedik. Parçalı kestirim için kullanılan üretici fonksiyon şöyledir: + + def predict_generator(f, steps, batch_size, offsets): + reader = csv.reader(f) + for batch_no in range(steps): + x = [] + for offset in offsets[batch_no * batch_size:batch_no * batch_size + batch_size]: + f.seek(offset, 0) + result = next(reader) + x.append(result[0]) + yield tf.convert_to_tensor(x), + + Parçalı kestirimde de bir epcoh kavramı yoktur. Dolayısıyla fonksiyonda bir epoch döngüsü oluşturulmamıştır. +#---------------------------------------------------------------------------------------------------------------------------- + +import math +import random +import csv +import pandas as pd +import tensorflow as tf + +EPOCHS = 5 +BATCH_SIZE = 32 +TEST_RATIO = 0.2 +VALIDATION_RATIO = 0.2 + +def record_offsets(f, skiprows=1): + offsets = [] + for i in range(skiprows): + f.readline() + offsets.append(f.tell()) + while f.readline() != '': + offsets.append(f.tell()) + offsets.pop() + + return offsets + +f = open('imdb.csv', encoding='latin-1') +offsets = record_offsets(f) + +random.shuffle(offsets) + +test_split_index = int(len(offsets) * (1 - TEST_RATIO)) +validation_split_index = int(test_split_index * (1 - VALIDATION_RATIO)) + +training_offsets = offsets[:validation_split_index] +validation_offsets = offsets[validation_split_index:test_split_index] +test_offsets = offsets[test_split_index:] + +training_steps = math.ceil(len(training_offsets) / BATCH_SIZE) +validation_steps = math.ceil(len(validation_offsets)/BATCH_SIZE) +test_steps = math.ceil(len(test_offsets)/BATCH_SIZE) + +def training_validation_test_generator(f, epochs, steps, batch_size, offsets, shuffle=False): + reader = csv.reader(f) + for _ in range(epochs): + if shuffle: + random.shuffle(offsets) + for batch_no in range(steps): + x = [] + y = [] + for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]: + f.seek(offset, 0) + result = next(reader) + x.append(result[0]) + y.append(1 if result[1] == 'positive' else 0) + yield tf.convert_to_tensor(x), tf.convert_to_tensor(y) + +def predict_generator(f, steps, batch_size, offsets): + reader = csv.reader(f) + for batch_no in range(steps): + x = [] + for offset in offsets[batch_no * batch_size:batch_no * batch_size + batch_size]: + f.seek(offset, 0) + result = next(reader) + x.append(result[0]) + yield tf.convert_to_tensor(x), + +df = pd.read_csv('imdb.csv') + +from tensorflow.keras import Sequential +from tensorflow.keras.layers import Input, Dense, TextVectorization + +tv = TextVectorization(output_mode='count') +tv.adapt(df['review']) + +model = Sequential(name='IMDB') + +model.add(Input((1, ), dtype='string')) +model.add(tv) +model.add(Dense(128, activation='relu', name='Hidden-1')) +model.add(Dense(128, activation='relu', name='Hidden-2')) +model.add(Dense(1, activation='sigmoid', name='Output')) +model.summary() + +model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) +hist = model.fit(training_validation_test_generator(f, + EPOCHS, training_steps, BATCH_SIZE, training_offsets, True), + batch_size=BATCH_SIZE, + steps_per_epoch=training_steps, + epochs=EPOCHS, + validation_data=training_validation_test_generator(f, EPOCHS, validation_steps, BATCH_SIZE, validation_offsets), + validation_steps=validation_steps) + +import matplotlib.pyplot as plt + +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Loss Graph', pad=10, fontsize=14) +plt.xticks(range(0, 300, 10)) +plt.plot(hist.epoch, hist.history['loss']) +plt.legend(['Loss']) +plt.show() + +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14) +plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['binary_accuracy']) plt.plot(hist.epoch, hist.history['val_binary_accuracy']) -plt.legend(['Binary Accuracy', 'Validation Binary Accuracy']) +plt.legend(['Accuracy', 'Validation Accuracy']) plt.show() -eval_result = model.evaluate(data_generator(test_df_x, test_y, steps_per_test), steps=steps_per_test) - +eval_result = model.evaluate(training_validation_test_generator(f, 1, test_steps, BATCH_SIZE, test_offsets), steps=test_steps) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') -#---------------------------------------------------------------------------------------------------------------------------- -#---------------------------------------------------------------------------------------------------------------------------- - Parçalı biçimde eğitim, test ve kestirim işlemi yapmanın diğer bir yolu tensorflow.keras.utils modülü içerisindeki Sequence - sınıfından türetme yapmaktadır. Programcı türetmiş sınıf içerisinde __len__ ve __getiitem__ metotlarını yazar. fit metodu - bir epoch'un kaç tane batch içerdiğini tespit etmek için sınıfın __len__ metodunu çağırmaktadır. Daha sonra fit eğitim sırasında - her batch bilgiyi elde etmek için sınıfın __getitem__ metodunu çağırır. Bu metodu çağırırken batch numarasını metodun indeks - parametresine geçirir. Bu metottan programcı batch büyüklüğü kadar x ve y verisinden oluşan bir demetle geti dönmelidir. - Sonra programcı fit metodunın training_dataset_x parametresine bu sınıf türünden bir nesneyi girer. +# prediction - Sınama işlemi yine benzer bir biçimde yapılmaktadır. Yani programcıo yine Sequence sınıfından sınıf türetip __len__ ve __getiitem__ - metotlarını yazar. Tabii durumda her epoch sonrasında bu sınama verileri __getitem__ metodu yoluyla elde edilecektir. +predict_f = open('predict-imdb.csv') +predict_offsets = record_offsets(predict_f) +predict_steps = int(math.ceil(len(predict_offsets) / BATCH_SIZE)) - Ayrıca Sequence sınıfından türetilmiş olan sınıfta on_epoch_end isimli bir metot da yazılabilmektedir. Eğitim sırasında her epoch bittiğinde - bu metot çağrılır. Programcı da tipik olarak bu metotta eğitim veri kümesibi karıştırır. Bu biçimdeki parçalı eğitimde artık fit metodunun - steps_per_epoch gibi bir parametresi kullanılmamaktadır. Zaten bu bilgi metot tarafından __len__ metodu çağrılarak elde edilmektedir +predict_result = model.predict(predict_generator(predict_f, predict_steps, BATCH_SIZE, predict_offsets), steps=predict_steps) - Benzer biçimde evaluate işleminde de yine Sequence sınıfından sınıf türetilerek test edilecek veriler parçalı bir biçimde evalaute metoduna - verilebilmektedir. Tabii bu metotta on_epoch_end metodunun bir anlamı yoktur. evaluate metodunda yine validation_steps parametresine gereksinim - duyulmamaktadır. Bu değer zaten __len__ metodundan elde edilmektedir. +for presult in predict_result[:, 0]: + if (presult > 0.5): + print('Positive') + else: + print('Negative') - Uygulamada parçalı verilerle eğitim işleminde aslında üretici fonksiyonlardan ziyada bu sınıfsal yöntem daha çok tercih edilmektedir. - Bu yöntemi uygulamak daha kolaydır. Ayrıca toplamda bu yöntem daha hızlı olma eğilimindedir. +#---------------------------------------------------------------------------------------------------------------------------- + 48. Ders - 07/07/2024 - Pazar +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + Parçalı biçimde eğitim, test ve kestirim işlemi üretici fonksiyon yerine sınıfsal bir biçimde de yapılabilmektedir. Bunun + için tensorflow.keras.utils modülü içerisindeki PyDataset sınıfından türetilmiş sınıflar kullanılmaktadır. (Aslında bir süre + öncesine kadar bu amaçla Sequence isimli bir sınıftan türetme yapılıyordu. Ancak Keras ekibi bunun yerine TenserFlow + kütüphanesi içerisindeki Dataset sınıfını Keras'tan kullanılabilir hale getirdi. Dokümantasyondan da Sequence sınıfını + kaldırıldı.) + + Programcı türetmiş sınıf içerisinde __len__ ve __getiitem__ metotlarını yazar. fit metodu bir epoch'un kaç tane batch içerdiğini + tespit etmek için sınıfın __len__ metodunu çağırmaktadır. Daha sonra fit metodu eğitim sırasında her batch bilgiyi elde etmek + için sınıfın __getitem__ metodunu çağırır. Bu metodu çağırırken batch numarasını metoda parametre olarak geçirir. Bu metottan + programcı batch büyüklüğü kadar x ve y verisinden oluşan bir demetle geri dönmelidir. Tabii aslında metodun geri döndürdüğü x ve Y + değerleri her defasında aynı uzunlukta olmak zorunda da değildir. Sonra programcı fit metodunun training_dataset_x parametresine + bu sınıf türünden bir nesne yaratarak o nesneyi girer. Örneğin: + + class DataGenerator(PyDataset): + def __init__(self): + super().__init__() + pass + + def __len__(self): + pass + + def __getitem__(self, batch_no): + pass + ... + model.fit(DataGenrator(...), epochs=100) + + Tabii artık bu yöntemde fit metodunun steps_per_epoch parametresi kullanılmamaktadır. Metodun bath_size parametresinin de + bu yöntemde bir anlamı yoktur. Ancak epochs parametresi ine kaç epoch uygulanacağını belirtmek içn kullanılmaktadır. + + Sınama işlemi yine benzer bir biçimde yapılmaktadır. Yani programcı yine PyDataset sınıfından sınıf türetip __len__ ve + __getitem__ metotlarını yazar. Tabii durumda her epoch sonrasında bu sınama verileri __getitem__ metodu yoluyla elde + edilecektir. Programcı yine bunun için validation_data parametresine PyDataset sınıfından türettiği sınıf türünden bir + nesne girer. Örneğin: + + model.fit(DataGenrator(...), epochs=100, validation_data=DataGenerator(....)) + + Ayrıca PyDataset sınıfından türetilmiş olan sınıfta on_epoch_end isimli bir metot da yazılabilmektedir. Eğitim sırasında her + epoch bittiğinde fit tarafından bu metot çağrılmaktadır. Programcı da tipik olarak bu metotta eğitim veri kümesini karıştırır. + Bu biçimdeki parçalı eğitimde artık fit metodunun steps_per_epoch gibi bir parametresinin kullanılmamaktadır. Zaten bu bilgi + metot tarafından __len__ metodu çağrılarak elde edilmektedir. Benzer biçimde evaluate işleminde de yine Sequence sınıfından + sınıf türetilerek test edilecek veriler parçalı bir biçimde evalaute metoduna verilebilmektedir. + + Uygulamada parçalı verilerle eğitim işleminde aslında üretici fonksiyonlardan ziyada bu sınıfsal yöntem daha çok tercih + edilmektedir. Bu yöntemi uygulamak daha kolaydır. Ayrıca toplamda bu yöntem daha hızlı olma eğilimindedir. Aşağıda rastgele verilerle bu işlemin nasıl yapılacağına yönelik bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np - -EPOCHS = 10 -BATCH_SIZE = 32 - -from tensorflow.keras.utils import Sequence - -class DataGenerator(Sequence): - def __init__(self, number_of_batches): - super().__init__() - self.number_of_batches = number_of_batches - - def __len__(self): - return self.number_of_batches - - def __getitem__(self, index): - x = np.random.random((BATCH_SIZE, 10)) - y = np.random.randint(0, 2, BATCH_SIZE) - - return x, y - - def on_epoch_end(self): - print('on_epoch_end') - from tensorflow.keras import Sequential -from tensorflow.keras.layers import Dense +from tensorflow.keras.layers import Input, Dense +from tensorflow.keras.utils import PyDataset -model = Sequential(name='PartialDataTraining') -model.add(Dense(64, activation='relu', input_dim=10, name='Hidden-1')) -model.add(Dense(64, activation='relu', name='Hidden-2')) -model.add(Dense(1, activation='sigmoid', name='Output')) - -model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) -hist = model.fit(DataGenerator(5), epochs=EPOCHS, validation_data=DataGenerator(2)) - -eval_result = model.evaluate(DataGenerator(20)) - -for i in range(len(eval_result)): - print(f'{model.metrics_names[i]}: {eval_result[i]}') - -#---------------------------------------------------------------------------------------------------------------------------- - Şimdi gerçekten parçalı verilerle eğitimin gerektiği IMDB örneğini üretici fonksiyonlar yerine bu yöntemi kullanarak gerçekleştirelim. - Aşağıdaki örnekte şu anahtar noktalara dikkat ediniz: - - - DataGenerator isimli sınıf hem eğitimi hem sınama hem de test veri kğmesi için kullanılacapından dolayı __init__ metodunda - kullanılacak x ve y veri kümelerini alarak nesnenin özniteliklerinde saklamıştır. - - - Bir epoch2un kaç tane batch işleminden oluşacağını fit metodu __len__ metodu yoluyla elde etmektedir. Dolayısıyla bu metotta - eğitim için gereken parça sayısını hesaplamaktayız. - - - __getitem__ metodu her defasında epoch2un hangi batch'i için çağrılmışsa metodun indez parametresine o batch'in numarası - geçirilmiştir. Dolayısıyla bizim __getitem__ metodunda o batch'e ilişkin verileri vektörel hale getirip geri döndürmemiz - gerekir. - - - Her epoch'tan sonra karıştırma işlemi yine asıl diziler üzerinde değil indekslerden oluşan dizi üzerinde yapılmıştır. - Karıştırma işlemi on_epoch_end metodunda yapılmaktadır. - - - Sınama verileri için de aynı sınıf kullanılmıştır. Tabii sınama işlemind ebir epoch kavramı yoktur. __len__ metodu - sınama işleminin kaç parçada gerçekleşeceğini belirlemektedir. - - - Test işlemi de benzer biçimde yapılmaktadır. -#---------------------------------------------------------------------------------------------------------------------------- - -EPOCHS = 5 +EPOCHS = 2 +NFEATURES = 10 BATCH_SIZE = 32 -import pandas as pd - -df = pd.read_csv('IMDB Dataset.csv') - -from sklearn.feature_extraction.text import CountVectorizer - -cv = CountVectorizer(dtype='int8') -cv.fit(df['review']) - -df_x = df['review'] -df_y = df['sentiment'] - -import numpy as np - -dataset_y = np.zeros(len(df_y), dtype='int8') -dataset_y[df['sentiment'] == 'positive'] = 1 - -from sklearn.model_selection import train_test_split - -temp_df_x, test_df_x, temp_y, test_y = train_test_split(df_x, dataset_y, test_size=0.20) -training_df_x, validation_df_x, training_y, validation_y = train_test_split(temp_df_x, temp_y, test_size=0.20) - -from tensorflow.keras.utils import Sequence - -class DataGenerator(Sequence): - def __init__(self, x_df, y, batch_size, shuffle=True): +class DataGenerator(PyDataset): + def __init__(self, batch_size, nfeatures, *, steps): super().__init__() - self.x_df = x_df - self.y = y self.batch_size = batch_size - self.shuffle = shuffle - self.indexes = list(range(len(self.x_df) // self.batch_size)) - + self.nfeatures = nfeatures + self.steps = steps + def __len__(self): - return len(self.x_df) // self.batch_size + return self.steps - def __getitem__(self, index): - start_index = self.indexes[index] * self.batch_size; - stop_index = (self.indexes[index] + 1) * self.batch_size - - x = cv.transform(self.x_df.iloc[start_index:stop_index]).todense() - y = self.y[start_index:stop_index] - + def __getitem__(self, batch_no): + x = np.random.random((self.batch_size, self.nfeatures)) + y = np.random.randint(0, 2, self.batch_size) return x, y - + def on_epoch_end(self): - if self.shuffle: - np.random.shuffle(self.indexes) - - -from tensorflow.keras import Sequential -from tensorflow.keras.layers import Dense + print('shuffle') -model = Sequential(name='IMDB') -model.add(Dense(64, activation='relu', input_dim=len(cv.vocabulary_), name='Hidden-1')) -model.add(Dense(64, activation='relu', name='Hidden-2')) +model = Sequential(name='Test') + +model.add(Input((NFEATURES, ))) +model.add(Dense(16, activation='relu', name='Hidden-1')) +model.add(Dense(16, activation='relu', name='Hidden-2')) model.add(Dense(1, activation='sigmoid', name='Output')) - model.summary() model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) +model.fit(DataGenerator(BATCH_SIZE, NFEATURES, steps=50), epochs=EPOCHS, validation_data=DataGenerator(BATCH_SIZE, NFEATURES, steps=5)) -hist = model.fit(DataGenerator(training_df_x, training_y, BATCH_SIZE), validation_data=DataGenerator(validation_df_x, - validation_y, BATCH_SIZE, False), epochs= EPOCHS) - -import matplotlib.pyplot as plt - -plt.figure(figsize=(15, 5)) -plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold') -plt.xlabel('Epochs') -plt.ylabel('Loss') -plt.xticks(range(0, 210, 10)) - -plt.plot(hist.epoch, hist.history['loss']) -plt.plot(hist.epoch, hist.history['val_loss']) -plt.legend(['Loss', 'Validation Loss']) -plt.show() - -plt.figure(figsize=(15, 5)) -plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold') -plt.xlabel('Epochs') -plt.ylabel('Loss') -plt.xticks(range(0, 210, 10)) - -plt.plot(hist.epoch, hist.history['binary_accuracy']) -plt.plot(hist.epoch, hist.history['val_binary_accuracy']) -plt.legend(['Binary Accuracy', 'Validation Binary Accuracy']) -plt.show() - -eval_result = model.evaluate(DataGenerator(test_df_x, test_y, BATCH_SIZE, False)) - +eval_result = model.evaluate(DataGenerator(BATCH_SIZE, NFEATURES, steps=10)) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') +predict_result = model.predict(DataGenerator(BATCH_SIZE, NFEATURES, steps=1)) + +for presult in predict_result[:, 0]: + if (presult > 0.5): + print('Positive') + else: + print('Negative') + +#---------------------------------------------------------------------------------------------------------------------------- + Aşağıda IMDB veri kümesinin PyDataset sınıfından türetme yapılarak parçalı eğitilmesine bir örnek verilmiştir. Bu örnekte + biz bir tane DataGenerator sınıfı oluşturkduk. Hem eğitim işleminde, hem sınama işleminde, hem test işleminde hem de kestirim + işleminde aynı sınıfı kullandık. Aslında bu örnekte yapılanlar genel mantık olarak üretici fonksiyon örneğinde yapılanlarla + benzerdir. Yukarıda da belirttiğimiz gibi parçalı eğitim için üretici fonksiyonlar yerine bu yöntemin uygulanmasını tavsiye + etmekteyiz. #---------------------------------------------------------------------------------------------------------------------------- - Elemanlarının çok büyük kısmı 0 olan matrislere "seyrek matrisler (sparse matrixes)" denilmektedir. Seyrek matrisleri ifade - etmek için alternatif birkaç veri yapısı kullanılmaktadır. Bunlardan biri DOK (Dictionary Of Keys) denilen veri yapısıdır. - Burada yalnızca 0'dan farklı olan elemanlar bir sözlükte tutulurlar. Sözlüğün biçimi aşağıdaki gibidir: - matrix = {(34674,17000): 1, (542001, 170): 4, ...} +import math +import random +import csv +import pandas as pd +import tensorflow as tf +from tensorflow.keras.utils import PyDataset - Burada anahtar satır ve sütun numarasını belirten demettir. Değer ise o elemandaki değeri belirtir. +EPOCHS = 5 +BATCH_SIZE = 32 +TEST_RATIO = 0.2 +VALIDATION_RATIO = 0.2 - Böyle bir sınıfı temsili olarak aşağıdaki gibi yazabiliriz. +def record_offsets(f, skiprows=1): + offsets = [] + for i in range(skiprows): + f.readline() + offsets.append(f.tell()) + while f.readline() != '': + offsets.append(f.tell()) + offsets.pop() + + return offsets + +f = open('imdb.csv', encoding='latin-1') +offsets = record_offsets(f) + +random.shuffle(offsets) + +test_split_index = int(len(offsets) * (1 - TEST_RATIO)) +validation_split_index = int(test_split_index * (1 - VALIDATION_RATIO)) + +training_offsets = offsets[:validation_split_index] +validation_offsets = offsets[validation_split_index:test_split_index] +test_offsets = offsets[test_split_index:] + +training_steps = math.ceil(len(training_offsets) / BATCH_SIZE) +validation_steps = math.ceil(len(validation_offsets)/BATCH_SIZE) +test_steps = math.ceil(len(test_offsets)/BATCH_SIZE) + +class DataGenerator(PyDataset): + def __init__(self, f, steps, batch_size, offsets, *, shuffle=False, predict=False): + super().__init__() + self.f = f + self.steps = steps + self.batch_size = batch_size + self.offsets = offsets + self.shuffle = shuffle + self.predict = predict + self.reader = csv.reader(f) + + def __len__(self): + return self.steps + + def __getitem__(self, batch_no): + x = [] + if not self.predict: + y = [] + for offset in self.offsets[batch_no * self.batch_size: batch_no * self.batch_size + self.batch_size]: + f.seek(offset, 0) + result = next(self.reader) + x.append(result[0]) + if not self.predict: + y.append(1 if result[1] == 'positive' else 0) + + if not self.predict: + return tf.convert_to_tensor(x), tf.convert_to_tensor(y) + return tf.convert_to_tensor(x), + + def on_pech_end(self): + random.shuffle(self.offsets) + +df = pd.read_csv('imdb.csv') + +from tensorflow.keras import Sequential +from tensorflow.keras.layers import Input, Dense, TextVectorization + +tv = TextVectorization(output_mode='count') +tv.adapt(df['review']) + +model = Sequential(name='IMDB') +model.add(Input((1, ), dtype='string')) +model.add(tv) +model.add(Dense(128, activation='relu', name='Hidden-1')) +model.add(Dense(128, activation='relu', name='Hidden-2')) +model.add(Dense(1, activation='sigmoid', name='Output')) +model.summary() + +model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) +hist = model.fit(DataGenerator(f, training_steps, BATCH_SIZE, training_offsets, shuffle=True), + epochs=EPOCHS, + validation_data = DataGenerator(f, validation_steps, BATCH_SIZE, validation_offsets)) + +import matplotlib.pyplot as plt + +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Loss Graph', pad=10, fontsize=14) +plt.xticks(range(0, 300, 10)) +plt.plot(hist.epoch, hist.history['loss']) +plt.legend(['Loss']) +plt.show() + +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14) +plt.xticks(range(0, 300, 10)) +plt.plot(hist.epoch, hist.history['binary_accuracy']) +plt.plot(hist.epoch, hist.history['val_binary_accuracy']) +plt.legend(['Accuracy', 'Validation Accuracy']) +plt.show() + +eval_result = model.evaluate(DataGenerator(f, test_steps, BATCH_SIZE, test_offsets)) +for i in range(len(eval_result)): + print(f'{model.metrics_names[i]}: {eval_result[i]}') + +# prediction + +predict_f = open('predict-imdb.csv') +predict_offsets = record_offsets(predict_f) +predict_steps = int(math.ceil(len(predict_offsets) / BATCH_SIZE)) + +predict_result = model.predict(DataGenerator(f, predict_steps, BATCH_SIZE, predict_offsets, predict=True)) + +for presult in predict_result[:, 0]: + if (presult > 0.5): + print('Positive') + else: + print('Negative') + +#---------------------------------------------------------------------------------------------------------------------------- + 49. Ders - 13/07/2024 - Cumartesi +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + Pekiyi biz parçalı eğitimi fit metodunu birden fazla kez çağırarak yapamaz mıyız? Keras'ın orijinal dokğmanlarında bu + konuda çok açık örnekler verilmemiştir. Ancak kaynak kodlar incelendiğinde fit işleminin artırımlı bir biçimde yapıldığı + görülmektedir. Yani birden fazla kez fit metodu çağrıldığında eğitim kalınan yerden devam ettirilmektedir. Bu nedenle + biz eğitimi farklı zamanlarda fit işlemlerini birden fazla kez yaparak devam ettirebiliriz. Ancak fit metodunun bu biçimde + birden fazla kez çağrılması işleminde dikkat edilmesi gereken bazı noktalar da olabilmektedir. Keras ekibi bu tür parçalı + eğitimler için daha aşağı seviyeli xxx_on_bath isimli metotlar bulundurmuştur. Programcının birden fazla kez fit metodu + çağırmak yerine bu metotları kullanması daha uygundur. PArçalı işlem için Sequential sınıfının şu metotları bulundurulmuştur: + + train_on_batch + test_on_batch + predict_on_batch + + Ancak bu yöntemde sınama işlemleri otomatik olarak train_on_batch içerisinde yapılmamaktadır. Programcının sınamayı kendisinin + yapması gerekmektedir. + + train_on_batch metodunun parametrik yapısı şöyledir: + + train_on_batch(x, y=None, sample_weight=None, class_weight=None, return_dict=False) + + Burada x ve y parametreleri parçalı eğitimde kullanılacak x ve y değerlerini almaktadır. sample_weight ve class_weight + parametreleri ağırlıklandırmak için kullanılmaktadır. return_dict parametresi True geçilirse metot bize geri dönüş değeri + olarak loss değerini ve metrik değerleri bir sözlük nesnesi biçiminde vermektedir. + + train_on_batch metodu ile parçalı eğitim biraz daha düşük seviyeli olarak yapılmaktadır. Bu biçimde parçalı eğitimde epoch + döngüsünü ve batch döngüsünü programcı kendisi oluşturmalıdır. Örneğin: + + for epoch in range(EPOCHS): + + for batch_no in range(NBATCHES): + + model.train_on_batch(x, y) + + Tabii yukarıda da belirttiğimiz gibi bu biçimde çalışma aşağı seviyelidir. Yani bazı şeyleri programcının kendisibin + yapması gerekmektedir. Örneğin fit metodu bize bir History sınıfı türünden bir callback nesnesi veriyordu. Bu nesnenin + içerisinden de biz tüm epoch'lara ilişkin değerleri elde edebiliyorduk. train_on_batch işlemleriyle eğitimde bu işelmlerin + bizim tarafımızdan yapılması gerekmektedir. train_on_batch metodunun return_dict parametresi True geçilirse batch işlemi + sonucundaki loss ve metik değerler bize bir sözlük biçiminde verilmektedir. Örneğin: + + for epoch in range(EPOCHS): + + for batch_no in range(NBATCHES): + + rd = model.train_on_batch(x, y, return_dict=True) + + Burada her spoch sonrasında değil her batch sonrasında değerlerin elde edildiğine dikkat ediniz. Aslında biz Tensorflow + ya da PyTorch kütüphanelerini aşağı seviyeli olarak kullanacak olsaydık zaten yine işlemleri bu biçimde iki döngü yoluyla + yapmak durumunda kalacaktık. Genellikle uygulamacılar her batch işleminde elde edilen değerlerin bir ortalamasını epoch + değeri olarak kullanmaktadır. + + Bu yöntemde epoch sonrasındaki sınama işlemlerinin de programcı tarafından manuel olarak yapılması gerekmektedir. + Yani programcı sınama veri kümesini kendisi oluşturmalı ve sınamayı kendisi yapmalıdır. Aslıdna evaulate metodu test + amaçlı kullanılsa da sınama amaçlı da kullanılabilir. Bu durumda sınama işlemi şöyle yapılabilir: + + for epoch in range(EPOCHS): + + for batch_no in range(NBATCHES): + + rd = model.train_on_batch(x, y, return_dict=True) + val_result = model.evaluata(validation_x, validation_y) + + Tabii buarad evaluate işlemini de parça parça yapmak isteyebilirsiniz. Bu durumda yine bir döngü oluşturup test_on_batch + fonksiyonunu kullanabilirsiniz. test_on_batch metodunun parametrik yapısı şöyledir + + test_on_batch(x, y=None, sample_weight=None, return_dict=False) + + Kullanım tamamen train_batch metodu gibidir. + + Test işlemi de tüm epoch'lar bittiğinde yine parçalı bir biçimde test_on_batch metoduyla yapılabilir. Kestirim işlemi de + yine benzer bir biçimde predcit_on_batch metoduyla yapılmaktadır. Metodun parametrik yapısı şöyledir + + predict_on_batch(x) + + Metot kestirim değerleriyle geri dönmektedir. Örneğin: + + for batch_no in range(NBATCHES): + + predict_result = model.predict_on_batch(predict_x) +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + 50. Ders - 14/07/2024 - Pazar +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + Aşağıda IMDB veri kümesinin xxx_on_batch metotlarıyla parçalı biçimde ele alınmasına bir örnek verilmiştir. Bu örnekte + tüm veri kümesi tek hamlede DataFrame olarak okunmuştur. Aslında burada parçalı işlemler için daha önce yapmış olduğmuz + işlemlerin uygulanması daha uygundur. Ancak biz örneği karmaşık hale getirmemek için tüm veri kümesini tek hamlede okuyup + xxx_on_batch metotlarına onu parçalara ayırarak verdik. +#---------------------------------------------------------------------------------------------------------------------------- + +import numpy as np +import pandas as pd +import tensorflow as tf +from tensorflow.keras import Sequential +from tensorflow.keras.layers import Input, Dense, TextVectorization + +EPOCHS = 5 +BATCH_SIZE = 32 +TEST_SPLIT_RATIO = .20 +VALIDATION_SPLIT_RATIO = .20 + +def create_model(df): + tv = TextVectorization(output_mode='count') + tv.adapt(df['review']) + + model = Sequential(name='IMDB') + + model.add(Input((1, ), dtype='string')) + model.add(tv) + model.add(Dense(128, activation='relu', name='Hidden-1')) + model.add(Dense(128, activation='relu', name='Hidden-2')) + model.add(Dense(1, activation='sigmoid', name='Output')) + model.summary() + + return model + +def train_test_model(training_df, validation_df, epochs, verbose = 1): + history_loss = [] + history_accuracy = [] + val_history_loss = [] + val_history_accuracy = [] + + + for epoch in range(epochs): + training_df = training_df.sample(frac=1) + + print('-' * 30) + + mean_loss, mean_accuracy = batch_train(training_df, int(np.ceil(len(training_df) / BATCH_SIZE)), model.train_on_batch, verbose=1) + history_loss.append(mean_loss) + history_accuracy.append(mean_accuracy) + + if verbose == 1: + print(f'Epoch: {epoch + 1}') + print(f'Epoch mean loss: {mean_loss}, Epoch Binary Accuracy: {mean_accuracy}') + + val_mean_loss, val_mean_accuracy = batch_train(validation_df, int(np.ceil(len(validation_df) / BATCH_SIZE)), model.test_on_batch) + + val_history_loss.append(val_mean_loss) + val_history_accuracy.append(val_mean_accuracy) + + if verbose == 1: + print(f'Validation Loss: {val_mean_loss}, Validation Binary Accuracy: {val_mean_accuracy}') + + return history_loss, history_accuracy, val_history_loss, val_history_accuracy + +def batch_train(df, nbatches, batch_method, verbose=0): + loss_list = [] + accuracy_list = [] + + for batch_no in range(nbatches): + x = tf.convert_to_tensor(df['review'].iloc[batch_no * BATCH_SIZE: batch_no * BATCH_SIZE + BATCH_SIZE], dtype='string') + y = tf.convert_to_tensor(df['sentiment'].iloc[batch_no * BATCH_SIZE: batch_no * BATCH_SIZE + BATCH_SIZE]) + rd = batch_method(x, y, return_dict=True) + + loss_list.append(rd['loss']) + accuracy_list.append(rd['binary_accuracy']) + + if verbose: + print(f'Batch No: {batch_no}') + if verbose == 2: + print(f"Batch Loss: {rd['loss']}, Batch Binary Accuracy: {rd['accuracy']}") + + mean_loss = np.mean(loss_list) + mean_accuracy = np.mean(accuracy_list) + + return mean_loss, mean_accuracy + +df = pd.read_csv('IMDB Dataset.csv').iloc[:10000, :] +df['sentiment'] = (df['sentiment'] == 'positive').astype(dtype='uint8') + +df = df.sample(frac=1) +test_zone = int(len(df) * (1 - TEST_SPLIT_RATIO)) +training_validation_df = df.iloc[:test_zone, :] +test_df = df.iloc[test_zone:, :] +validation_zone = int(len(training_validation_df) * (1 - VALIDATION_SPLIT_RATIO)) +training_df = training_validation_df.iloc[:validation_zone, :] +validation_df = training_validation_df.iloc[validation_zone:, :] + +model = create_model(df) +model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) + +history_loss, history_accuracy, val_history_loss, val_history_accuracy = train_test_model(training_df, validation_df, EPOCHS) + +import matplotlib.pyplot as plt + +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Loss Graph', pad=10, fontsize=14) +plt.xticks(range(0, EPOCHS)) +plt.plot(range(EPOCHS), history_loss) +plt.plot(range(EPOCHS), val_history_loss) +plt.legend(['Loss', 'Validation Loss']) +plt.show() + +plt.figure(figsize=(14, 6)) +plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14) +plt.xticks(range(0, EPOCHS)) +plt.plot(range(EPOCHS), history_accuracy) +plt.plot(range(EPOCHS), val_history_accuracy) +plt.legend(['Accuracy', 'Validation Accuracy']) +plt.show() + +# evaluation + +eval_mean_loss, eval_accuracy = batch_train(test_df, int(np.ceil(len(test_df) / BATCH_SIZE)), model.test_on_batch) +print(f'Test Loss: {eval_mean_loss}, Test Binary Accuracy: {eval_accuracy}') + +# prediction + +predict_df = pd.read_csv('predict-imdb.csv') + +for i in range(int(np.ceil(len(predict_df) / BATCH_SIZE))): + predict_result = model.predict_on_batch(predict_df) + for presult in predict_result[:, 0]: + if (presult > 0.5): + print('Positive') + else: + print('Negative') + +#---------------------------------------------------------------------------------------------------------------------------- + Elemanlarının çok büyük kısmı 0 olan matrislere "seyrek matrisler (sparse matrices)" denilmektedir. Seyreklik (sparsity) + 0 olan elemanların tüm elemanlara oranıyla belirlenmektedir. Bit matirisn seyrek olarak ele alınması için herkes tarafından + kabul edilen bir seyreklik oranı yoktur. Seyreklik oranı ne kadar yüksek olursa onların alternatif veri yapılarıyla ifade + edilmeleri o kadar verimli olmaktadır. Makine öğrenmesinde seyrek matrisle sıkça karşılaşılmaktadır. Örneğin bir grup yazıyı + vektörel hale getirdiğimizde aslında bir seyrek matris oluşmaktadır. Benzer biçimde one-hot encoding dönüştürmesi de bir + seyrek matris oluşturmaktadır. scikit-learn kütüphanesindeki OneHotEncoder sınıfının ve CountVectorizer sınıfının çıktı + olarak seyrek matris verdiğini anımsayınız. + + Seyrek matrislerin daha az yer kaplayacak biçimde tutulmasındaki temel yaklaşım matirsin yalnızca sıfırdan farklı elemanlarının + ve onların yerlerinin tutulmasıdır. Örneğin bir milyon elemana sahip bir seyrek matriste yalnızca 100 eleman sıfırdan + faklıysa biz bu 100 elemanın değerini ve matristeki yerini tutarsak önemli bir yer kazancı sağlayabiliriz. +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + Seyrek matrisleri ifade etmek için alternatif birkaç veri yapısı kullanılmaktadır. Bunlardan biri DOK (Dictionary Of Keys) + denilen veri yapısıdır. Bu veri yapısında matrisin yalnızca 0'dan farklı olan elemanları bir sözlükte tutulur. Sözlüğün + biçimi aşağıdaki gibidir: + + {(34674,17000): 1, (542001, 170): 4, ...} + + Burada anahtar satır ve sütun numarasını belirten demettir. Değer ise o elemandaki değeri belirtir. Örneğin aşağıdaki gibi + bir matris söz konusu olsun: + + 0 0 5 + 3 0 0 + 0 6 0 + + Buradaki dok sözlüğü şöyle olacaktır: + + {(1, 2): 5, (1, 0): 3, (2, 1): 6} + + Aşağıda DOK veri yapısı ile seyrek matris oluşturan DokMatrix isimli bir sınıf örneği verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np class DokMatrix: - def __init__(self, shape): - self.shape = shape - self.matrix = {} - - def __setitem__(self, index, value): - if not isinstance(index, tuple) or len(index) != 2: - raise IndexError('Invalid index') - - if index[0] >= self.shape[0] or index[1] >= self.shape[1]: - raise IndexError('inde out of range') - - self.matrix[index] = value + def __init__(self, nrows, ncols): + self._nrows = nrows + self._ncols = ncols + self._dok = {} def __getitem__(self, index): - if not isinstance(index, tuple) or len(index) != 2: - raise IndexError('Iinvalid index') - - if index[0] >= self.shape[0] or index[1] >= self.shape[1]: - raise IndexError('index out of range') - - return self.matrix.get(index, 0) + if not isinstance(index, tuple) or len(index) != 2: + raise TypeError('index must have twho dimension') + if index[0] < 0 or index[0] >= self._nrows: + raise IndexError('index out of range') + if index[1] < 0 or index[1] >= self._ncols: + raise IndexError('index out of range') - def __repr__(self): - s = '' - for key, value in self.matrix.items(): - if s != '': - s += '\n' - s += f'({key[0]},{key[1]}): {value}' - return s; + return self._dok.get(index, 0) + + def __setitem__(self, index, val): + if not isinstance(index, tuple) or len(index) != 2: + raise TypeError('index must have twho dimension') + if index[0] < 0 or index[0] >= self._nrows: + raise IndexError('index out of range') + if index[1] < 0 or index[1] >= self._ncols: + raise IndexError('index out of range') + + self._dok[index] = val + + @property + def shape(self): + return self._nrows, self._ncols + + @property + def size(self): + return self._nrows * self._ncols + + def __len__(self): + return self._nrows + + @staticmethod + def array(a): + if not isinstance(a, list): + raise TypeError('argument must be Python list') + nrows = len(a) + ncols = len(a[0]) + for i in range(1, nrows): + if len(a[i]) != ncols: + raise ValueError('matrix rows must have the same number of elements') + dm = DokMatrix(nrows, ncols) + for i in range(nrows): + for k in range(ncols): + if a[i][k] != 0: + dm._dok[(i, k)] = a[i][k] + return dm def todense(self): - result = np.zeros(self.shape) - for key, value in self.matrix.items(): - result[key] = value - - return result + array = np.zeros((self._nrows, self._ncols)) + for index, val in self._dok.items(): + array[index] = val + return array -dok = DokMatrix((10, 10)) - -dok[3, 5] = 10 -dok[4, 6] = 20 - -print(dok[3, 5]) -print(dok[4, 6]) -print(dok[7, 7]) - -a = dok.todense() -print(a) + def __str__(self): + smatrix = '' + for i in range(self._nrows): + sline = '' + for k in range(self._ncols): + if sline != '': + sline += ' ' + sline += str(self._dok.get((i, k), 0)) + if smatrix != '': + smatrix += '\n' + smatrix += sline + + return smatrix + + def __repr__(self): + smatrix = '' + for index, val in self._dok.items(): + if smatrix != '': + smatrix += '\n' + smatrix += f'({index[0]}, {index[1]}) ---> {val}' + + return smatrix + +a = [[1, 0, 3], [0, 0, 1], [5, 0, 0]] +dok = DokMatrix.array(a) +print(dok) +print('-' * 10) +dok[1, 1] = 8 +print(dok) +print('-' * 10) +print(repr(dok)) #---------------------------------------------------------------------------------------------------------------------------- - DOK biçimindeki seyrek matrisler SciPy kütüphanesinde scipy.sparse modülü içerisinde dok_matrix sınıfıyla temsil edilmiştir. - Biz bir dok_marix sınıfı türünden nesneyi yalnızca boyut belirterek yaratbiliriz. Daha sonra bu nesne sanki bir NumPy dizisiymiş - gibi onu kullanabiliriz. Seyrek matrisi normal bir Numpy Dizisine dönüştürmek için sınıfın todense ya da toarray metotları - kullanılabilir. + Kursumuzun bu noktasında sözünü ettiğimiz kütüphanelerin işlevlerini yeniden anımsatmak istiyoruz: + + NumPy ---> Vektörel işlemler yapan C'de yazılmış taban bir kütüphanedir. Pek çok proje NumPy kütüphanesini kendi içerisinde + kullanmaktadır. + + Pandas ---> Bu kütüphane sütunlu veri yapılarını (yani istatistiksel veri tablolarını) ifade etmek için kullanılmaktadır. + Kütüphanenin en önemli özelliği farklı türlere ilişkin sütunsal bilgilerin DataFrame isimli bir veri yapısı ile temsil + edilmesidir. Bu kütüphane de NumPy kullanılarak yazılmıştır. + + scikit-learn ---> Bir makine öğrennesi kütüphanesidir. Ancak yapay sinir ağlarıyla ilgili özellikler yoktur (minimal + düzeydedir). Yani bu kütüphane yapay sinir ağlarının dışındaki makine öğrenmesi için kullanılmaktadır. scikit-learn + kendi içerisinde NumPy, Pandas ve SciPy kütüphanelerini kullanmaktadır. + + SciPy ---> Genel amaçlı matematik ve nümerik analiz kütüphanesidir. Bu kütüphanenin doğrudan makine öğrenmesiyle bir ilgisi + yoktur. Ancak matematiğin çeşitli alanlarına ilişkin nümeraik analiz işlemleri yapan geniş bir taban kütüphanedir. Bu + kütüphane de kendi içerisinde NumPy ve Pandas kullanılarak yazılmıştır. + + TensorFlow ---> Google tarafından yapay sinir ağları ve makine öğrenmesi için oluşturulmuş taban bir kütüphanedir. Bu kütüphane + scikit-learn kütüphanesinden farkı olarak Tensor adı altında biren fazla CPU ya da çekirdek kullanacak biçimde özellikle + yapay sinir ağları için oluşturulmuş taban bir kütüphanedir. Kütüphane Google tarafından geliştirilmiştir. + + Keras ---> Yapay sinir ağı işlemlerini kolaylaştırmak için oluşturulmuş olan yüksek seviyeli bir kütüphanedir. Eskiden bu + kütüphane "backend" olarak farklı kütüphaneleri kullanbiliyordu. Eski hali devam ettirilse de kütüphane taamamen TensorFlow + içerisine dahil edilmiştir ve TensorFlow kütüphanesinin yüksek seviyeli bir katmanı haline getirilmiştir. + + PyTorch ---> Tamamen TensorFlow kütüphanesinde hedeflenen işlemleri yapan taban bir yapay sinir ağı ve makine öğrenmesi + kütüphanesidir. Facebook (Meta) tarafından geliştirilmiştir. + + Theano --> TensorFlow, PyTorch SciPy benzeri bir taban kütüphanedir. Akademik çevreler tarafından geliştirilmiştir. +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + NumPy kütüphanesi içerisinde seyrek matrislerle işlem yapan sınıflar ya da fonksiyonlar bulunmamaktadır. Ancak SciPy + kütüphanesi içerisinde seyrek matrislerle ilgili işlemler yapan sınıflar ve fonksiyonlar vardır. scikit_learn kütüphanesi + doğrudan SciPy kütüphanesinin seyrek matris sınıflarını kullanmaktadır. +#---------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------------------------------------------------------------------------- + DOK biçimindeki seyrek matrisler SciPy kütüphanesinde scipy.sparse modülü içerisindeki dok_matrix sınıfıyla temsil edilmiştir. + Biz bir dok_marix sınıfı türünden nesneyi yalnızca boyut belirterek yaratabiliriz. Daha sonra bu nesneyi sanki bir NumPy + dizisiymiş gibi onu kullanabiliriz. Seyrek matrisi normal bir Numpy dizisine dönüştürmek için sınıfın todense ya da toarray + metotları kullanılmaktadır. Örneğin: + + from scipy.sparse import dok_matrix + + dok = dok_matrix((10, 10), dtype='int32') + dok[1, 3] = 10 + dok[3, 5] = 20 + + a = dok.todense() + print(dok) + print('-' * 20) + print(a) + + dok_matrix sınıfının minimum, maximum, sum, mean gibi birtakım faydalı metotları bulunmaktadır. nonzero metodu sıfır + dışındaki elemanların indekslerini vermektedir. #---------------------------------------------------------------------------------------------------------------------------- from scipy.sparse import dok_matrix @@ -11081,7 +11722,24 @@ print(dok) Biz seyrek matrislerin karşılıklı elemanları üzerinde işlemler yapabiliriz. Ancak her türlü işlem değişik veri yapılarına sahip seyrek matrislerde aynı verimlilikte yapılamamaktadır. Örneğin iki dok_matrix nesnesini toplayabiliriz ya da - çarpabiliriz. Ancak bu işlemler yavaş olma eğilimindedir. + çarpabiliriz. Ancak bu işlemler yavaş olma eğilimindedir. Örneğin: + + from scipy.sparse import dok_matrix + + dok1 = dok_matrix((5, 5), dtype='int32') + dok1[1, 2] = 10 + dok1[0, 1] = 20 + + dok2 = dok_matrix((5, 5), dtype='int32') + dok2[3, 2] = 10 + dok2[4, 1] = 20 + dok2[1, 2] = 20 + + result = dok1 + dok2 + print(result) + + result = dok1 * dok2 + print(result) #---------------------------------------------------------------------------------------------------------------------------- from scipy.sparse import dok_matrix @@ -11100,10 +11758,27 @@ dok3 = dok1 * dok2 print(dok3) #---------------------------------------------------------------------------------------------------------------------------- - Diğer bir seyrek matris veri yapısı da "LIL (List of List)" denilen veri yapısıdır. Bu veri yapısında matrisin satır satır + Diğer bir seyrek matris veri yapısı da "LIL (List of Lists)" denilen veri yapısıdır. Bu veri yapısında matrisin satır satır 0 olmayan elemanları ayrı listelerde tutulur. Başka bir listede de bu sıfır olmayan elemanların sütunlarının indeksi - tutulmaktadır. LIL matrisler SciPy kütüphanesinde scipy.sparse modülünde lil_matrix sınıfyla temsil edilmektedir. Bu sınıfın - genel kullanımı dok_matrix sınıfında olduğu gibidir. + tutulmaktadır. LIL matrisler SciPy kütüphanesinde scipy.sparse modülündeki lil_matrix sınıfyla temsil edilmektedir. Bu sınıfın + genel kullanımı dok_matrix sınıfında olduğu gibidir. Sınıfın data ve rows örnek öznitelikleri bize bu bilgileri vermektedir. + Örneğin aşağıdaki gibi bir matrisi lil_matrix yapmış olalım: + + [[ 0 0 10 20 0] + [15 0 0 0 40] + [12 0 51 0 16] + [42 0 18 0 16] + [ 0 0 0 0 0]] + + Buradaki data listesi şöyle olacaktır: + + array([list([10, 20]), list([15, 40]), list([12, 51, 16]), list([42, 18, 16]), list([])], dtype=object) + + rows lsistesi de şöyle olacaktır: + + array([list([2, 3]), list([0, 4]), list([0, 2, 4]), list([0, 2, 4]), list([])], dtype=object) + + LIL matrisler de artimetik işlemlerde yavaştır. Dilimleme işlemleri de bu matrisler de nispeten yavaş yapılmaktadır. #---------------------------------------------------------------------------------------------------------------------------- from scipy.sparse import lil_matrix @@ -11124,31 +11799,24 @@ lil2 = lil_matrix(np.random.randint(0, 2, (100, 100))) lil3 = lil1 + lil2 print(lil3) -#---------------------------------------------------------------------------------------------------------------------------- - lil_matrix sınıfının data örnek özniteliği satırlardaki sıfırdan farklı elemanları, rows örnek özniteliği ise sütun - indekslerini vermektedir. -#---------------------------------------------------------------------------------------------------------------------------- - -from scipy.sparse import lil_matrix - -a = [[0, 0, 10, 0, 5], [12, 0, 11, 0, 7], [0, 0, 0, 0, 0], [0, 0, 5, 0, 9], [0, 0, 0, 0, 0]] -lil = lil_matrix(a) - -print(a) -print(f'data={lil.data}') # data=[list([10, 5]) list([12, 11, 7]) list([]) list([5, 9]) list([])] -print(f'rows={lil.rows}') # rows=[list([2, 4]) list([0, 2, 4]) list([]) list([2, 4]) list([])] - #---------------------------------------------------------------------------------------------------------------------------- Aslında uygulamada DOK ve LIL matrisler seyrek kullanılmaktadır. Daha çok CSR ve CSC veri yapıları tercih edilmektedir. - CSR (Compressed Sparse Row), ve CSC (Compress Sparse Column) matrisleri genel veri yapısı olarak birbirlerine çok benzemektedir. - Bu ver yapısı setrek matrislerin karşılıklı elemanlarının işleme sokulması durumunda diğer veri yapılarına göre daha avantajlıdır. - CSR fotmatı satırsal dilimlerde, CSC formatı ise sütunsal dilimlemelerde daha hızlı sonuç veröektedir . + CSR (Compressed Sparse Row), ve CSC (Compressed Sparse Column) matrisleri genel veri yapısı olarak birbirlerine çok + benzemektedir. Bunlar adeta birbirlerinin tersi durumundadır. - CSR matrislerde sıfırdan farklı elemanları üç dizi (liste) haline tutulmaktadır: data, indices, indptr. data dizisi sıfır - olmayan elemanların tutulduğu tek boyutlu dizidir. indices dizisi data dizisindeki, elemanların matristekü sütun indekslerinden - oluşur. indptr ise sıfır olmayan elemanların hangi satırlarda olduğuna ilişkin ilk ve son indeks (ilk indeks dahil, son indeks - dahil değil) değerlerinden oluşmaktadır. Bu veri yapısı da SciPy kütüphanesinde scipy.sparse modülünde csr_matrix sınıfıyla - temsil edilmektedir. Örneğin: + Bu veri yapıları seyrek matrislerin karşılıklı elemanlarının işleme sokulması durumunda DOK ve LIL veri yapılarına göre + daha avantajlıdır. CSR satır dilimlemesini CSC ise sütun dilimlemesi hızlı yapabilmektedir. Ancak bu matrislerde + sparse bir matrisin 0 olmayan bir elemanına atama yapmak nispeten yavaş bir işlemdir. + + CSR veri yapısı da SciPy kütüphanesinde scipy.sparse modülünde csr_matrix sınıfıyla temsil edilmektedir. CSR matrislerde + sıfırdan farklı elemanlar üç dizi (liste) haline tutulmaktadır: data, indices, indptr. Bu diziler sınıfın aynı isimli örnek + özniteliklerinden elde edilebilmektedir. data dizisi sıfır olmayan elemanların tutulduğu tek boyutlu dizidir. indices dizisi + data dizisindeki elemanların kendi satırlarının hangi sütunlarında bulunduğunu belirtmektedir. indptr dizisi ise sıfır olmayan + elemanların hangi satırlarda olduğuna ilişkin ilk ve son indeks (ilk indeks dahil, son indeks dahil değil) değerlerinden + oluşmaktadır. indptr dizisi hep yan yana iki eleman olarak değerlendirilmelidir. SOldaki eleman ilk indeksi, sağdaki eleman + ise son indeksi belirtir. + + Örneğin: 0, 0, 9, 0, 5 8, 0, 3, 0, 7 @@ -11164,12 +11832,14 @@ print(f'rows={lil.rows}') # rows=[list([2, 4]) list([0, 2, 4]) list([]) li csr_matrix sınıfının genel kullanımı diğer seyrek matris sınıflarındaki gibidir. Ancak CSR ce CSC matrislerde sıfır olan bir elemana atama yapmak yavaş bir işlemdir. Çünkü bu işlemler yukarıda belirtilen üç dizide kaydırmalara yol açmaktadır. Bu tür - durumlarda DOK ya da LIL matrisler daha hızlı işleme yol açmaktadır. Bu nedenle bu matrisler kullanılırken sıfır olmayan bir - elemena atama yapıldığında bir uyarı mesajıyla karşılaşabilirsiniz. O halde CSR ve CSC matrisleri iin başında oluşturulmalı + durumlarda DOK ya da LIL matrisler daha hızlı işleme yol açarlar. Bu nedenle bu matrisler kullanılırken sıfır olmayan bir + elemana atama yapıldığında bir uyarı mesajıyla karşılaşabilirsiniz. O halde CSR ve CSC matrisleri işin başında oluşturulmalı ve sonra da onların elemanları bir daha değiştirilmemelidir. CSR matrislerinde satırsal, CSC matrislerinde sütunsal dilimlemeler hızlıdır. Aynı zamanda bu iki matrisin karşılıklı elemanları üzerinde hızlı işlemler yapılabilmektedir. + + #---------------------------------------------------------------------------------------------------------------------------- from scipy.sparse import csr_matrix