CSDKursNotlari/Python-OzetNotlar-Ornekler.txt

33707 lines
1.5 MiB
Text
Raw Permalink Normal View History

2024-11-07 13:00:04 +03:00
/*--------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
C ve Sistem Programcıları Derneği
Python Programlama Dili
Sınıfta Yapılan Örnekler ve Özet Notlar
Eğitmen: Kaan ASLAN
2024-08-27 01:07:02 +03:00
Bu notlar Kaan ASLAN tarafından oluşturulmuştur. Kaynak belirtmek koşulu ile her türlü alıntı yapılabilir.
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
(Notları sabit genişlikli font kullanan programlama editörleri ile açınız.)
(Editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Son Güncelleme Tarihi: 07/01/2025 - Salı
2024-07-15 13:23:34 +03:00
---------------------------------------------------------------------------------------------------------------------------*/
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Python ("pay(th)ın, pay(th)an, "pay(th)on" biçiminde 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.)
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Python genel olarak yorumlayıcı tabanlı (interpretive) bir dildir. Çeşitli kurumlar tarafından yazılmış olan Python
yorumlayıcıları Windows, macOS ve Linux gibi yaygın kullanılan işletim sistemlerine port edilmiş durumdadır. Dolayısıyla
biz Windows, macOS ve Linux sistemlerinde Python Programlama Dilinde uygulamalar geliştirebiliriz.
2024-07-15 13:23:34 +03:00
Python dinamik tür sistemine (dynamic type system) sahip bir programlama dilidir. Dinamik tür sistemine sahip programlama
2024-08-12 17:16:10 +03:00
dillerinde "bildirim (declaration)" biçiminde bir kavram yoktur. Bu tür dillerde değişkenlere istediğimiz türden değerler
atayabiliriz. Bir değişkene en son hangi türden değer atadıysak değişken o atamadan sonra o türden olmaktadır.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Python geniş bir standart kütüphaneye sahip bir programama dilidir. Standart kütüphanesi içerisinde pek çok konuya
ilişkin hazır birtakım fonksiyonlar ve sınıflar bulunmaktadır. Bu durum Python dünyasında "bataryası içerisinde
(batteries included)" biçiminde esprili bir deyişle ifade edilmektedir.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Python öğrenilmesi nispeten kolay bir programlama dilidir. Bu nedenle asıl alanı programlama olmayan, başka disiplinlerden
gelen kişilerin 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.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Python diğer derleme dillerine (compiled languages) göre nispeten yavaş 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 uygun bir
seçenek değildir.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Python genel amaçlı (general purpose) bir programlama dilidir. Yani Python pek çok uygulama alanında kullanılabilmektedir.
Her ne kadar Pytthon bazı uygulamalar için yavaş bir dil durumundaysa da bu tür uygulamalarda basitliğinden dolayı bir
ptototip dil olarak da kullanılabilmektedir. (Yani bazı programların denemeleri Python'da yapılıp nihai ürün kodlamaları
C/C++, Java, C# gibi gibi dillerde de yapılabilmektedir.)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Guido van Rossum Python'un tasarımına ve gerçekleştirimine 1989 yılının sonlarında başlamıştır. Bu tarihten itibaren
dilin pek çok versiyonu oluşturulmuştur. Aşağıda Python versiyonların çıkış tarihlerini veriyoruz:
2024-07-15 13:23:34 +03:00
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)
2024-08-12 17:16:10 +03:00
Kursun yapıldığı tarihteki Python'un 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
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
2) Python Standard Library Reference
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
ve yeni özellikle "PEP (Python Enhancement Proposals)" isimli dökümanlar biçiminde arşivlenmektedir. PEP dokümanlarına
2024-07-15 13:23:34 +03:00
aşağıdaki bağlantıdan erişebilirsiniz:
https://peps.python.org/
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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 temelde yorumlayıcı
2024-11-07 13:00:04 +03:00
ile çalışılan (interpretive) bir dildir. Biz kodumuzu derleyiciye sokarak değil yorumlayıcıya sokarak çalıştırırız. Her
ne kadar Python temelde yorumlayıcı tabanlı bir dil olsa da Python için derleyicilere benzer biçimde çalışan gerçekleştirimler
2024-08-12 17:16:10 +03:00
de bulunmaktadır. Bazı Python gerçekleştirimleri sıfırdan yazılmışken bazıları ise bazı diğer gerçekleştirimler temel
alınıp onlardan klonlanarak yazılmış durumdadır.
2024-07-15 13:23:34 +03:00
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"
2024-08-27 01:07:02 +03:00
sitesinden indirilebilir. Biz de kursumuzda CPython yorumlayıcısını 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
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Jython (cay(th)ın, cay(th)an, “jay(th)on” 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.
2024-07-15 13:23:34 +03:00
IronPython gerçekleştirimi Jython gerçekleştiriminin .NET (genel ismiyle CLI) versiyonu gibidir. IronPython C# Programlama
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Kursumuzda Python yorumlayıcısı olarak klasik CPython kullanılacaktır. Ayrıca yukarıdakiler dışında python'un daha
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
Learning Environment) isimli basit bir IDE'yi barındırmaktadır. Biz kursumuzun başlangıç bölümlerinde CPython dağıtımını
da kullanacağız.
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
Bir gerçekleştirim ya da bir dağıtım olmayan Python IDE'leri de vardır. Örneğin Jet Brains firmasının PyCharm isimli
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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 CPUnun doğrudan elektriksel olarak bağlantı
halinde olduğu belleklere kavramsal olarak "birincil bellek (primary memory)" ya da "ana bellek (main memory)" de denilmektedir.
2024-07-15 13:23:34 +03:00
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 RAMde yaratılmaktadır. Bilgisayarın elektriğini kestiğimizde CPU durur. RAMin 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 SSDler hard disklerin yerini almaya başlamıştır. Ancak kavramsal olarak hard diskler, SSDler, CD
ve DVD ROMlar, 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ıı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 Pia biz Monoyu 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 .NETin arakodu
olan “CIL (Common Intermediate Language)” ve Javanı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
IDEnin 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
ı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 bytetır.
Mega da kilonun 1024 katıdır. Yani 1MB=1024KB'tır. Giga Meganın Tera da Giganı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
2025-01-23 02:00:34 +03:00
sistemiyle çalıştırmaktadır. Oysa python'un PyPy gerçekleştirimi tıpkı Java ve .NET dünyasına benzer biçimde bir JIT
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
ya da referanslarında hep BNF notasyonu ya da bunun türevleri kullanılmaktadır. python'un orijinal referans kitabında
2024-07-15 13:23:34 +03:00
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 (<ifade>)
<deyim>
[
else
<deyim>
]
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 <dosya ismi>
Ö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
2025-01-23 02:00:34 +03:00
dikkat ediniz. Genellikle "python" ismi python'un 2.7'li sürümlerini çalıştırmak için python3 ismi ise 3'lü sürümlerini
2024-07-15 13:23:34 +03:00
ç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)
<class 'int'>
>>> a = 1.23
>>> type(a)
<class 'float'>
>>> a = 'ankara'
>>> type(a)
<class 'str'>
>>> a = False
>>> type(a)
<class 'bool'>
>>> a = 3j+2
>>> type(a)
<class 'complex'>
>>> a = None
>>> type(a)
<class 'NoneType'>
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)
<class 'int'>
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki kodu bir program olarak çalıştırınız.
#------------------------------------------------------------------------------------------------------------------------
a = 10287364872364872348762348762
print(type(a)) # <class 'int'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'float'>
>>> f = 3.0
>>> type(f)
<class 'float'>
>>> f = 3
>>> type(f)
<class 'int'>
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'bool'>
>>> b = False
>>> b
False
>>> type(b)
<class 'bool'>
>>> b = 'True'
>>> b
'True'
>>> type(b)
<class 'str'>
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'str'>
>>> k = "Ankara"
>>> k
'Ankara'
>>> type(k)
<class 'str'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'complex'>
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)
<class 'complex'>
>>> 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)
<class 'complex'>
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)
<class 'complex'>
>>> type(b)
<class 'complex'>
>>> type(c)
<class 'complex'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'NoneType'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'int'>
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)
<class 'int'>
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)
<class 'int'>
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)
<class 'int'>
>>> a = 0o123
>>> a
83
>>> type(a)
<class 'int'>
>>> a = 0x123
>>> a
291
>>> type(a)
<class 'int'>
>>> a = 0b101010
>>> a
42
>>> type(a)
<class 'int'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'float'>
>>> b = .28
>>> type(b)
<class 'float'>
>>> c = 12.
>>> type(c)
<class 'float'>
>>> d = 10_3.23_45_57
>>> type(d)
<class 'float'>
>>> e = 1e40
>>> type(e)
<class 'float'>
>>> f = -1.2e-2
>>> type(f)
<class 'float'>
>>> g = 1.23_45e2_3
>>> type(g)
<class 'float'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'int'>
>>> a = 123.
>>> type(a)
<class 'float'>
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)
<class 'int'>
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)
<class 'float'>
İ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)
<class 'int'>
Python'daki bool sabitlerin ilk harflerinin büyük harf olduğuna dikkat ediniz.
#------------------------------------------------------------------------------------------------------------------------
>>> a = True
>>> type(a)
<class 'bool'>
>>> b = False
>>> type(b)
<class 'bool'>
>>> c = a * 3
>>> c
3
>>> type(c)
<class 'int'>
>>> d = True + True
>>> d
2
>>> type(d)
<class 'int'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'str'>
>>> s = ''
>>> s
''
>>> type(s)
<class 'str'>
#------------------------------------------------------------------------------------------------------------------------
>>> a = 'ankara'
>>> a
'ankara'
>>> type(a)
<class 'str'>
>>> b = 'ağrı dağı'
>>> b
'ağrı dağı'
>>> type(b)
<class 'str'>
>>> c = "burdur"
>>> c
'burdur'
>>> type(c)
<class 'str'>
>>> d = ''
>>> d
''
>>> type(d)
<class 'str'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'str'>
>>> b = 'ağrı dağı'
>>> b
'ağrı dağı'
>>> type(b)
<class 'str'>
>>> c = "burdur"
>>> c
'burdur'
>>> type(c)
<class 'str'>
>>> d = ''
>>> d
''
>>> type(d)
<class 'str'>
>>> 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"
<stdin>: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.
2024-07-22 23:32:15 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
8. Ders 20/07/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Üç 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 yazının sonunda olamaz. Çünkü Python'da ardışıl en uzun karakterlerden atom yapılmaktadır. Örneğin:
s = """"Bugün" hava çok sıcak""" # geçerli, çift tırnak üç çıft tırnağın başına olabilir
Ancak örneğin:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
s = """Bugün hava çok "sıcak"""" # geçersiz! çifr tırnak üç çift tırnağın sonunda olamaz.
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Python yorumlayıcısı yukarıdaki ifadeyi şöyle atomlarına ayrıştıracaktır ayıracaktır:
2024-07-15 13:23:34 +03:00
s
=
2024-07-22 23:32:15 +03:00
"""Bugün hava "sıcak"""
"
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Burada yazının sonundaki " soruna yol açacaktır. Tabii biz yine ters bölü karakter sabitlerini üç tırnakların
içerisinde kullanabiliriz:
s = """Bugün hava çok "sıcak\"""" # geçerli artık \" yazının bir parçası durumunda
Burada söylenilenlerin hepsi üç tırnak için de benzer biçimde geçerlidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Python'un 2'li versiyonlarında bir byte'lık ASCII tablosu kullanılıyordu. (ASCII tablosunu aslında 7 bitlik bir tablodur.
Ancak tablonun ikinci 128'lik kısmı çeşitli ülkelere göre düzenlenmiştir.) Bu versiyonlarda UNICODE string'ler için
string'lere yapışık 'u' ya da 'U' önekleri kullanılıyordu. Ancak Python'un stringleri zaten 3'lü versiyonlarla birlikte
2024-07-15 13:23:34 +03:00
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.
2024-07-22 23:32:15 +03:00
Dolayısıyla biz artık uzunca bir süredir Python'da string'leri UNICODE karakterlerle oluşturabiliriz. Örneğin:
s = "Ağrı dağı çok yüksek'
Bu yazıdaki özel karakterler dünyanın her yerinde Türkçe'deki özel karakterler olarak ele alınmaktadır. Karakter
kodlaması (character encoding) önemli ve biraz ayrıntıları olan bir konudur. Bu konu ayrıntılı biçimde ileride ayrı
bir başlık halinde ele alınacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Tek tırnakların ya da üç tırnakların önüne onlarla yapışık 'b' ya da 'B' öneki getirilirse elde edilen nesne "bytes"
denilen bir türden olur. Yani artık bu nesne str türünden değildir. bytes türü ileride ayrı bir başlık altında ele
alınacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = b'Bu bir denemedir'
2024-07-22 23:32:15 +03:00
print(s) # b'Bu bir denemedir'
print(type(s)) # <class 'bytes'>
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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'
2024-07-22 23:32:15 +03:00
aynı anlamdadır. Ancak string'lerden en az biri üç tırnaklı ise bunlar farklı staırlarda bulunabilir. Önemli olan
birinci string'in bittiği satırda ikinci string'in başlamış olmasıdır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
s = 'ali' """veli
selami"""
ifadesi geçerlidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
5) Python'da 'j' harfi önünde yapışık bir float ya da int sabitle kullanıldığında complex türden bir sabit belirtir.
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. Çünkü tek başına j bir değişken ismi olarak ele
alınmaktadır. Aslında 3j+2 gibi bir ifadede 3j complex sabit belirtmektedir. Bir complex sayı int ya da float ile
topalnır ya da çıkartılırsa sonuç complex türünden olmaktadır.
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Complex türünden sabitlerde bir noktaya dikkat ediniz. j harfi önüne bir sayı getirilmemişse bir değişken atom
olarak ele alınmaktadır. Eğer j harfinin önüne bir sayı getirilirse bu durumda j artık bir değişken olarak değil
karmaşık sayının i'li kısmını belirtir hale gelmektedir. Örneğin:
j = 5
z = 3j + j
print(z) # (5+3j)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
z = 1j
print(z)
z = -2j + 5
print(z)
z = 0j + 2
print(z)
2024-07-22 23:32:15 +03:00
#------------------------------------------------------------------------------------------------------------------------
6) 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 yazılıp ENTER tuşuna basılırsa ekranda bir şey görülmez. Ancak None değeri print fonksiyonu ile
yazıdırlırsa ekranda None yazısı görülür.
#------------------------------------------------------------------------------------------------------------------------
>>> a = None
>>> a
>>> print(a)
None
>>> type(a)
<class 'NoneType'>
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-07-22 23:32:15 +03:00
yayılmış yorumlamaları üç tırnaklı string'lerle yaparlar.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
"""
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
2024-07-22 23:32:15 +03:00
dillerinin çoğunda söz konusu olan kurallar geçerlidir. Bu kuralları şöyle özeteleybiliriz:
2024-07-15 13:23:34 +03:00
- Değişken ismi sayısal karakterlerle başlatılamaz, ancak alfabetik karakterlerle başlatılıp sayısal karakterlerle
2024-07-22 23:32:15 +03:00
devam ettirilebilir. Alt tire karakteri alfabetik karakter kabul edilmektedir.
2024-07-15 13:23:34 +03:00
- Değişken isimleri anahtar sözcüklerden oluşturulamaz.
2024-07-22 23:32:15 +03:00
2024-07-15 13:23:34 +03:00
- Değişken isimleri boşluk karakterlerini içeremez.
2024-07-22 23:32:15 +03:00
2024-07-15 13:23:34 +03:00
- Değişken isimleri operatör karakterlerini içermez.
2024-07-22 23:32:15 +03:00
2024-07-15 13:23:34 +03:00
- Python "büyük harf-küçük harf duyarlılığı olan (case sensitive)" bir programlama dilidir. Yani değişken isimlerindeki
2024-07-22 23:32:15 +03:00
büyük harflerle küçük harfler birbirinden farklıdır. C, C++, Java ve C# dilleri de böyledir. Ancak Pascal, Basic, Fortan
gibi dillerde büyük harf küçük harf duyarlılığı yoktur (case insensitive). Yani o dillerde küçük harf karakterlerle onların
büyük harf karşılıkları aynı karakterlermiş gibi ele alınmaktadır.
2024-07-15 13:23:34 +03:00
- 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.
2024-07-22 23:32:15 +03:00
Çok eskiden programcılar değişken isimlerini kısa tutuyordu. Çünkü pek çok dilde değişkenlerin maksimum uzunlukları konusunda
yüksek olmayan limitler vardı. Sonraları bilgisayar sistemlerinin gelişmesiyle programların satır sayıları artmaya başladı.
Programcılar programları içerisinde çok fazla değişken kullanmaya başladılar. Böylece değişken isimleri de uzamaya başladı.
Birden fazla sözcük içeren değişkenlerde sözcükler arasında boşluk bırakılamadığı için okunabilirliği sağlamak için bazı
yazım biçimleri oluşturuldu. Bunlara değişken harflendirrmesi biçimi (casting style) de denilmektedir. Günümüzde birden
fazla sözcük içeren değişkenler için üç yazım biçimi (ya da notasyon) kullanılmaktadır:
2024-07-15 13:23:34 +03:00
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
2024-07-22 23:32:15 +03:00
student_number
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
2) Deve Notasyonu (Camel Casting): Bu notasyonda ilk sözcüğün tamamı küçük harflerle yazılır. Sonraki her sözcüğün yalnızca
ilk harfleri büyük harflerle yazılır. Örneğin:
2024-07-15 13:23:34 +03:00
numberOfStudents
loopCount
2024-07-22 23:32:15 +03:00
studentNumber
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
3) Pascal Notasyonu (Pascal Casting): Pascal dilinde tercih edilen notasyon olduğu için bu isim verilmiştir. Bu notasyonda
her sözcüğün ilk harfi büyük yazılır. Örneğin:
2024-07-15 13:23:34 +03:00
NumberOfStudents
LoopCount
2024-07-22 23:32:15 +03:00
StudentNumber
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
C programalama dilinde C tarzı harflendirme tercih edilmektedir. Java'da ağırlıklı olarak deve notasyonu kullanılmaktadır.
Python'da programcıların büyük bölümü C tarzı harflandirmeyi tercih etmektedir. Biz de kurusumuzda birden fazla sözcükten
oluşan değişken isimlerinde sözcüklerin arasına alt tire getireceğiz.
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
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. Ancak sembolik sabit görevindeki değişkenler geleneksel olarak büyük harflerle isimlendirilir.
Sınıf isimlerini ise programcıların bir bölümü Pascal tarzı harflendirmektedir.
s#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
CPU'ya elektriksel olarak doğrudan bağlı belleklere "ana bellek (main memory)" ya da "birincil bellek (primary memory")
2024-07-22 23:32:15 +03:00
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.
Programalama dillerinde bellekte erişilebilen bölgere "nesne (object)" denir. Nesneler ana bellekte bir byte ya da daha
uzun byte yer kaplayabilirler. Her nesne bellekte bir yerdedir ve dolayısıyla her nesnenin bir adresi vardır. 1 byte'tan
uzun nesnelerin adresleri onların en küçük adresiyle ifade edilir. Örneğin bir nesne bellekte 4 byte yer kaplıyor olsun.
Bu byte'ların adresleri de 100102, 100103, 10004, 100105 olsun. Bu nesnenin adresi belirtilirken onun tüm byte'larının
adresleri ayrı ayrı belirtilmez. Yalnızca en küçük adres olan adres (örneğimizde 100102 adresi) belirtilir. Çünkü
işlemciler başlangıç adresi bilinen nesnelere çok hızlı bir biçimde (bunun için İngilizce "random access" terimi
kullanılmaktadır) erişebilmektedir.
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Bir değişken başka bir nesnenin adresini tutuyorsa bu tür değişkenlere bazı dillerde "gösterici (pointer)" ya da
"referans (reference)" denilmektedir. Örneğin a nesnesi bellekte 200104 adresinde bulunuyor olsun eğer b nesnesi
a'nın adresi olan 200104 değerini tutuyorsa burada b değişkeni bir gösterici ya da referans durumundadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
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 yerin adresini atamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
a = 123
2024-07-22 23:32:15 +03:00
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:
2024-07-15 13:23:34 +03:00
a = 3.14
2024-07-22 23:32:15 +03:00
Bu durumda yorumlayıcı RAM'de bir float nesne oluşturur. Onun içerisine 3.14 değerini yerleştirir. Bu kez onun adresini
a'ya atar. Görüldüğü gibi Python'da değişkenler adres tutmaktadır. Asıl bilgiler (yani değerler) o adreste bulunmaktadır.
Biz Python'da adres tutan varlıklara değişken, değişkenin gösterdiği yere ise nesne denilmektedir. Örneğin:
a = 'ankara'
Burada yorumlayıcı str türünden bir nesne oluşturur. Onun içerisine "ankara" yazısını yerleştirir. a değişkenine de o
nesnenin adresini atar. Burada a bir değişkendir. Adres tutmaktadır. Ancak a'nın göstediği yerde str türünden bir nesne
vardır.
Pekiyi Python'da bir değişkeni type fonksiyonuna soktuğumuzda değişkenin türünü bu fonksiyon nasıl bilmektedir? İşte
nesnelerin içerisinde aslında yalnızca onların değerleri değil aynı zamanda türleri de tutulmaktadır. Örneğin:
a = 12.3
print(type(a))
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Burada type fonksiyonu a değişkeninin içerisindeki adrese bakar oradan nesnenin float türdne olduğunu anlar. Bu işlemden
sonra şöyle yapmış olalım:
a = 'izmir'
print(type(a))
Şimdi artık a önceki float nesneyi değil yeni yaratılmış olan str nesnesini göstermektedir. Yani a önceden float nesnesini
adresini tutarken artık str türünden nesnenin aresini tutmaya başlamıştır. type fonksiyonu a adresine eriştiğinde oradaki
türün str olduğunu görmektedir. Tür bilgisinin değişkenin içerisinde değil nesnenin içerisinde saklandığına dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Python'da tüm atamalar adres atamasıdır. Yani bir atama işleminde her zaman atama operatörünün solundaki değişkene
bir adres atanmaktadır. Örneğin:
a = 10
b = a
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Burada a = 10 ile önce 10 değeri int bir nesne yaratılarak onun içerisine yerleştirilir. a'ya da bu nesnenin adresi
atanır. Sonra b = a işleminde a'nın içerisindeki adres b'ye atanmaktadır. Python'da değişkenler her zaman adres
tutarlar ve atamalar da her zaman adresler atanır. Yukarıdaki kod parçasında artık a'nın ve b'nin içerisinde aynı
nesnenin adresi bulunur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir değişkenin içerisindeki adres id isimli built-in 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.)
Örneğin:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
>>> a = 100
>>> id(a)
140720174990872
>>> a = 'ankara'
>>> id(a)
2486054903920
>>> a = 12.3
>>> id(a)
2486055027376
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Burada a başlangıçta int bir nesnenin adresini tutarken daha sonra bir str nesnesinin ve daha sonra da float bir
nesnennadresini tutar hale gelmiştir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
>>> x = 123
>>> y = x
>>> id(x)
140720174991608
>>> id(y)
140720174991608
Yukarıda da belirtitğimiz gibi Python'da her atama adres ataması anlamına gelir. Burada y = x işleminde aslında
x'in içerisindeki adres y'ye atanmaktadır. Dolayısıyla artık x'in ve y'nin içerisinde aynı adresler bulunmaktadır.
Yani burada x ve y aynı int nesnesi göstermektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'da atama dışında her zaman biz bir değişkeni kullandığımızda değişkenin içerisindeki adresi değil, o adreste
bulunan nesnenin değerini kullanmış oluruz. Örneğin:
a = 1
b = 2
2024-07-15 13:23:34 +03:00
c = a + b
2024-07-22 23:32:15 +03:00
Burada önce belekte int türden bir nesne yaratılacak onun içerisine 1 yer yeleştirilecek ve onun adresi a'ya atanacaktır.
Sonra bellekte yeni bir int nesne yaratılacak onun içerisine 2 değeri yerleştirilip o nesnenin adresi b'ye atanacaktır.
Şimdi a ve b iki farklı int nesneyi göstermektedir. c = a + b işleminde a'nın içerisindekiş adres ile b'nin içerisindeki
adres toplanmamaktadır. a'nın içerisindeki adreste bulunan değerle (burada 1) b'nin içerisinde bulunan adresteki değer
(burada 2) toplanmaktadır. Yorumlayıcı toplamdan elde edilen bu 3 değerini yeni int nesne yaratıp onun ieçrisine
yerleştirir ve artık c içerisinde 3 olan int nesneyi gösterir hale gelir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
>>> a = 1
>>> b = 2
2024-07-15 13:23:34 +03:00
>>> c = a + b
>>> id(a)
2024-07-22 23:32:15 +03:00
140720174987704
2024-07-15 13:23:34 +03:00
>>> id(b)
2024-07-22 23:32:15 +03:00
140720174987736
2024-07-15 13:23:34 +03:00
>>> id(c)
2024-07-22 23:32:15 +03:00
140720174987768
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
9. Ders 21/07/2024 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Her ne kadar Python'da her atama bir adres ataması anlamına geliyorsa da programcıların bazıları bunun farkında
değildir. Örneğin:
2024-07-15 13:23:34 +03:00
a = 10
2024-07-22 23:32:15 +03:00
Aslında burada Python'da önce bir int nesne yaratılıp a'ya o int nesnenin adresi yerleştirilmektedir. Ancak bazı kişiler
sanki doğrudan 10 değerinin a'ya yerleştirildiğini sanmaktadır. Bu tür bir sanı genellikle soruna yol açmaz. Ancak bazı
konuların anlaşılmasını engellemektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2024-07-22 23:32:15 +03:00
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'un 6 temel türü de kategorik olarak "değiştirilemez (immutable)"
türlerdir. Daha açık bir biçimde belirtirsek:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
int türü değiştirilemez bir türdür
float türü değiştirilemez bir türdür
str türü değiştirilemez bir türdür
bool türü değiştirilemez bir türdür
complex değiştirilemez bir türdür
NoneType değiştirilemez bir türdür
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Python programcının değiştirilemez türden bir nesne yaratılırken onun başlangıçta belli bir değerle yaratıldığını ancak
bir daha da onun değerin asla değişmeyeceğini biliyor olması gerekir. Bazı süreçleri değerlendirirken her zaman
bu özellik dikkate alınmalıdır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
int, float, str, bool, complex ve NoneType türlerinin "değiştirilemez" olmasının ilginç bazı sonuçları söz konusudur.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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))
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
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:
2024-07-15 13:23:34 +03:00
a = 10
b = a
Burada a ve b aslında aynı nesneyi göstermektedir. Şimdi şu işlemi yapmış olalım:
a = 20
2024-07-22 23:32:15 +03:00
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 hala içerisinde 10 olan eski nesneyi göstermektedir. Yani buradaki
semantik C, C++, Java ve C# gibi dillerdekiyle sonuç itibariyle aynı olacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 10
b = a
a = 20
print(a) # 20
print(b) # 10
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Temel türlerin "değiştirlemez (immutable) 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:
2024-07-15 13:23:34 +03:00
a = 0
2024-07-22 23:32:15 +03:00
while a < 10:
2024-07-15 13:23:34 +03:00
print(a, id(a))
a = a + 1
2024-07-22 23:32:15 +03:00
Burada a değişkeninin id'sinin sürekli değiştiğini göreceksiniz:
2024-07-15 13:23:34 +03:00
0 2172851716304
1 2172851716336
2 2172851716368
3 2172851716400
4 2172851716432
5 2172851716464
6 2172851716496
7 2172851716528
8 2172851716560
9 2172851716592
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Python'un geniş bir standart kütüphanesinin olduğunu söylemiştik. Bu standart kütüphanede pek çok konuya ilişkin
fonksiyonlar ve sınıflar hazır bir biçimde bulunmaktadır. Standart 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 fonksiyonunu kullanılmadan önce bu math isimli modülün aşağıdaki gibi import edilmesi gerekir:
2024-07-15 13:23:34 +03:00
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)
2024-07-22 23:32:15 +03:00
gibi. Burada biz sqrt ismini doğrudan değil math.sqrt biçiminde kullandık. Ancak önce math modülünü import ettik. Bir
modülün import edilmesinin ne anlama geldiğini ileride ayrı bir başlık altında ele alacağız.
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Python'un standart kütüphanesi python.org sitesinde "Python Standard Library" ismiyle dokğmante edilmiştir. Bu dokümana
2024-07-15 13:23:34 +03:00
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:
2024-07-22 23:32:15 +03:00
1) Python dilinin resmi anlatımını yapan doküman: "Python Language Reference"
2024-07-15 13:23:34 +03:00
https://docs.python.org/3/reference/index.html
2024-07-22 23:32:15 +03:00
2) Python'un standart kütüphanesini açıklayan doküman: "Python Standard Library"
2024-07-15 13:23:34 +03:00
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.
2024-07-22 23:32:15 +03:00
İşte bu fonksiyonlara ve sınıflara "built-in" fonksiyonlar ve sınıflar denilmektedir. Örneğin print, type ve id
fonksiyonları built-in fonksiyonlardır. Built-in fonksiyonların listesine "Python Standard Library" dokümanından
erişebilirsiniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
Python'da ekrana (yani stdout dosyasına) yazan tek bir print fonksiyonu vardır. Benzer biçimde klavyeden (yani stdin
dosyasından) okuma yapan tek bir input isimli fonksiyon bulunmaktadır.
input fonksiyonu argüman olarak bir yazı alır. Önce yazıyı ekrana basar. İmleci yazının sonunda bırakır. Sonra giriş
ister. Kullanıcı bir yazı girerek ENTER tuşuna basar. input fonksiyonu da girilmiş olan yazıyı bize str nesnesi olarak
verir. Örneğin:
s = input('Bir yazı giriniz:')
print(s)
Burada önce ekrana "Bir yazı giriniz:" yazısı çıkacaktır. Biz bir yazı girip ENTER tuşuna bastığmızda input fonksiyonu
str türünden bir nesne yaratıp girdiğimiz yazıyı o nesneye yerleştircek ve o nesnenin adresini bize verecektir. Örneğimizde
bu adres s değişkenine atanmıştır.
input fonksiyonu çağrılırken parantezlerin içi boş bırakılabilir. Bu durumda input herhangi bir yazı basmadan giriş
ister. Çrneğn
s = input()
print(s)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = input('Bir yazı giriniz:')
print(type(s))
print(s)
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
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 str nesnesinin adresini o değişkene atamış oluruz. Örneğin:
2024-07-15 13:23:34 +03:00
s = input('Bir yazı giriniz:')
2024-07-22 23:32:15 +03:00
Burada s değişkenine input fonksiyonu tarafından oluşturulmuş olan bir str nesnesinin adresi atanmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
input fonksiyonu her zaman okunan yazıyı str nesnesi 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ürme aşağıdaki gibi
yapılmaktadır:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
val = T(input('yazı'))
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Burada T elde edilmek istenen türü temsil etmaktadır. Örneğin klavyeden int bir değer şöyle okunabilir:
val = 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. Aslında T bir tür belirtmek üzere T(...) ifadesi "T türüne dönüştürme" ytapmaktadır. Dolayısıyla
aslında int(input('Bir değer giriniz')) ifadesinde aslında önce input fonksiyonu çalıştırılır. Pradan bir str nesnesi
elde edilir. Bu str nesnesi int türüne dönüştürülmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-07-22 23:32:15 +03:00
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 birer türü vardır.
Bu tür genellikle XXXError biçiminde isimlendirilmiştir. Örneğin ValueError, TypeError, IndexError gibi.
2024-07-15 13:23:34 +03:00
Aşağıdaki örnekte bir sayı yerine bir isim girerek exception oluşturunuz.
#------------------------------------------------------------------------------------------------------------------------
a = int(input('Bir değer giriniz:'))
print(a * a)
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
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çiminde 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 faaliyetinde uygulanan bir tekniktir. Bir
işin parçalara ayrılıp parçaların oluşturulması ve sonra bir araya getirilmesi yazılımda sıkça uygulanan bir yöntemdir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
Zaten var olan (yani yazılmış olan) bir fonksiyonun çalıştırılmasına programlamada "fonksiyonun çağrılması (function call)"
denilmektedir. 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:
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
<fonksiyon_ismi>([argüman_listesi])
2024-07-15 13:23:34 +03:00
2024-07-22 23:32:15 +03:00
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:
2024-07-15 13:23:34 +03:00
print(a)
Burada a bir argümandır. Örneğin:
print(a, b, c)
2024-07-22 23:32:15 +03:00
Burada birden fazla argüman kullanılmıştır. Yukarıda da belirttiğimiz gibi argümanlar herhangi ifadelerden oluşturulabilir.
Örneğin:
2024-07-15 13:23:34 +03:00
print(a + b, c + d)
Burada iki argüman vardır. Argümanlardan biri a + b diğeri ise c + d biçimindedir.
2024-07-22 23:32:15 +03:00
Daha önce gördüğümüz gibi bir fonksiyon bir modülün içerisindeyse o fonksiyonu çağırmak için modülün ismi de kullanılmaktadır.
Örneğin:
import math
2024-07-15 13:23:34 +03:00
math.sqrt(10)
gibi.
2024-07-22 23:32:15 +03:00
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:
2024-07-15 13:23:34 +03:00
<sınıf türünde değişken>.<metodun ismi>([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ı
2024-07-22 23:32:15 +03:00
yoktur. Örneğin:
2024-07-15 13:23:34 +03:00
foo()
bar()
tar()
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-22 23:32:15 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-07-22 23:32:15 +03:00
Python'da farklı değişkenlere aynı sabitleri atadığımızda temel türler değiştirilebilir olmadığından aslında yorumlayıcı
2024-07-15 13:23:34 +03:00
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
2024-07-22 23:32:15 +03:00
kodu yeniden düzenleme hakkının yorumlayıcıya verilmiş olduğunun programcı tarafından bilinmesidir. Aşağıdaki koda
dikkat ediniz:
a = 5
b = 10
c = a + a
Burada yorumlayıcı a + a değerini 10 olarak hesapladığında yeniden bit int nesne yaratıp içerisine 10 yerleştirmek yerine
zaten içerisinde 10 olan daha önce yarattığı nesnenin adresini c'yeyerleştirebilir. Kodu deneyerek yorumlayıcınızın bu
optimizasyonu yapıp yapamadığına bakınız. Örneğin:
a = 5
b = int(input('Bir değer giriniz:'))
Burada klavyeden girilen değer 5 ise yorumlayıcı boşuna yeni nesne yaratmadan daha önce yaratmış olduğu nesnenin adresini
b'ye atayabilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 12345
b = 12345
result = a is b
print(result)
s = 'ali'
k = 'ali'
result = s is k
print(result)
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
10.Ders 27/07/2024 Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Bir işleme yol açan ve işlem sonucunda bir değerin üretilmesini sağlayan atomlara operatör (operator) denilmektedir.
Örneğin, +, *, /, -, >, < gibi semboller birer operatördür.
Operatörler genellikle üç biçimde sınıflandırılmaktadır:
2024-07-15 13:23:34 +03:00
1) İşlevlerine Göre
2) Operand Sayılarına Göre
3) Operatörün Konumuna Göre
2024-08-12 17:16:10 +03:00
İşlevlerine göre sınıflandırma operatörün hangi konu ile ile ilgili işlem yaptığına ilişkindir. İşlevlerine göre
sınıflandırma tipik olarak operatörler şu sınıflara ayrılmaktadır:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
- Artitmetik Operatörler (Aritmetic Operators)
- Karşışaltırma Operatörleri ya da İlişkisel Operatörler (Comparision Operators / Releational Operators()
- Mantıksal Operatörler (Logical Operators)
- Bit Operatörleri (Bitwise Operators)
- Özel Amaçlı Operatörler (Special Purpose Operators)
*, / +, - gibi artitmetik işlemler yapan operatörlere "artimetik opretaörler" denilmektedir. İki değerin karşılaştırılmasında
kullanılan >, <, >=, <= gibi operatörlere karşılaştıma operatörleri denilmektedir. İki karşılaştırma işlemi and, or gibi
mantıksal operatörlerle birbirine bağlanabilmektedir. Sayıları bir bütün bütün olarak değil de onların karşılıklı bitlerini
işleme sokan ya da sayıların bitleri üzerinde işlemler yapan operatörlere "bit operatörleri" denilmektedir. Bazı operatörler
özel bazı konulara ilişkin işlemler yapmaktadır. Bu operatörlere "özel maçlı operatörler (special purpose operators)"
denilmektedir.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Operatörün işleme soktuğu ifadelere "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.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Operatör operandlarının önüne getirilerek kullanılıyorsa bunlara "önek (prefix)", sonuna getirilerek kullanılıyorsa
bunlara "sonek (postfix)" ve alarına getirilerek kullanılıyorsa bunlara da "araek (infix)" operatörler denilmektedir.
Örneğin / operatörü araek bir operatördür. Çünkü a / b gibi bir ifadede / operatörü a ve operandlarının arasına getirilerek
kullanılmaktadır. Ancak örneğin not operatörü önek bir operatördür. Operand'nın önüne getirilerek kullanılmaktadır.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Bir operatörü teknik olarak ele alırken bu üç sınıflandırmada da nereye düştüğünün belirtilmesi gerekir. Örneğin
"/ operatörü iki operand'lı araek (binary infix) bir aritmetik operatördür" gibi. Ya da örneğin "not operatörü tek
operandlı önek (unary prefix)" bir mantıksal operatördür" gibi. Bu biçimde operatörü genel olarak tanımladıktan sonra
ona özgü diğer özelliklerin açıklanması gerekir. Biz de kursumuzda böyle yapacağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Bir ifadede birden fazla operatör varsa bu operatörler belli bir sırada işleme sokulmaktadır. Buna "operatörler arasındaki
öncelik ilişkisi (operator precedency)" denilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
Çünkü * operatörü + operatörüne göre daha önceliklidir. Yani b + c * d işleminde aslında b ile c * d'nin sonucu toplanmaktadır.
Öncelik sırasını değiştirmek için parantezlerden faydalanılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
a = (b + c) * d
İ1: (b + c)
İ2 = İ1 * d
İ3: a = İ2
2024-08-12 17:16:10 +03:00
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. Tabii programcılar bazen karmaşıl ifadelerde parantezleri zorunluluktan değil ifadenin daha kolay algılanabilmesi
için de kullanabilmektedir.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
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" olarak işleme sokulurlar. Aşağıda öncelik stablosunun
yalın bir biçimi görünmektedir:
2024-07-15 13:23:34 +03:00
() soldan sağa
* / soldan sağa
+ - soldan sağa
= sağdan sola
2024-08-12 17:16:10 +03:00
Bu tablodaki () operatörü fonksiyon çağırma operatörünü ve öncelik parantezlerini temsil etmektedir. Bu durumda örneğin:
2024-07-15 13:23:34 +03:00
a = b - c * d + e
2024-08-12 17:16:10 +03:00
ifadesinde toplam dört operatör vardır. İşlemler şu sırada yapılacakır:
2024-07-15 13:23:34 +03:00
İ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
2024-08-12 17:16:10 +03:00
ifadedeki sırayı belirtmektedir, tablodaki sırayı belirtmemektedir." Yani satırın sonundaki "soldan sağa" ibaresi
o satırdakiler aynı ifadede bulunursa "ifade içerisinde o satırdakilerin hangisi soldaysa o önce yapılır" anlamına
gelmektedir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Biz bu bölümde Python'daki temel operatörleri ele alıp inceleyeceğiz. Ancak bazı opetaörler bazı özel konulara ilişkindir.
Bu nedenle o konuların açıklandığı bölümlerde ele alınacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
*, /, + ve - operatörleri iki operand'lı araek aritmetik operatörlerdir. Bunlar temel dört işlemi yaparlar. Öncelik
tablosonda * ve / operatörleri + ve - operatörlerinden daha yüksek önceliklidir.
2024-07-15 13:23:34 +03:00
() soldan sağa
* / soldan sağa
+ - soldan sağa
2024-08-27 01:07:02 +03:00
= sağdan sol
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-08-12 17:16:10 +03:00
davranışı C, C++, Java ve C# programlama dillerinden farklıdır. Örneğin:
result = 10 / 4;
Bu işlemde 10 ve 4 int türdendir. C, C++, Java ve C# gibi dillerde buradan elde edilecel sonuç 2.5 değil 2'dir.
Ancak Python'da buradan elde edilecej sonuş 2.5'tir vr float türdendir. / operatörü Python'da operand'lar int ya da
float türden ise her zmaan float değer üretmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = 10 / 4
print(result, type(result)) # 2.5, <class 'float'>
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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 türden ise sonuç int türden olur. Operand'ların en az biri float türdense sonuç float
türden elde edilir. Örneğin:
result = 10 // 4
print(result, type(result)) # 2 <class 'int'>
result = 10. // 4
print(result, type(result)) # 2.0 <class 'float'>
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = 10 // 4
print(result, type(result)) # 2 <class 'int'>
result = 10. // 4
print(result, type(result)) # 2.0 <class 'float'>
#------------------------------------------------------------------------------------------------------------------------
// 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.
2024-08-12 17:16:10 +03:00
Ayrıca bu konuyla ilgili olmasa da programalamada "floor" işleminin terisne de "ceil" işlemi denilmektedir. ceil
işlemi bir naoktaslı sayının kendisinden büyük ilk tamsayıya dönüştürülmesi anlamına gelmektedir. Örneğin 2.5 değeri
ceil işlemine sokulursa 3 değeri, -2.5 değeri ceil işlemine sokulursa -2 değeri elde edilmektedir. Yubarlama (round)
ise nereye yakınsa oraya dönüştürme anlamına gelmektedir. Örneğşn 2.9 değeri 3'e -2.9 değeri de -3'e yuvarlanmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = -10 // 4
print(result, type(result)) # -3 <class 'int'>
#------------------------------------------------------------------------------------------------------------------------
// 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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
% 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.
Operand'lardan en az biri float ise sonuç float türden elde edilmektedir. % operatörü de öncelik tablosunda * , /
ve // operatörü ile aynı öncelik grubunda (yani aynı satırda) bulunmaktadır:
2024-07-15 13:23:34 +03:00
() soldan sağa
* / // % soldan sağa
+ - soldan sağa
= sağdan sola
#------------------------------------------------------------------------------------------------------------------------
result = 20 % 3
print(result, type(result)) # 2 <class 'int'>
result = 20.5 % 3
print(result) # 2.5 <class 'float'>
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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. Yani burada kalanı temsil
eden d aslında a - b * c'dir. Şlte bu noktada C, C++, Java ve C# gibi dillerle Python arasında yine bir farklılık
oluşmaktadır. Python'da bölümden elde edilen kalan için bölme işlemi // operatörü ile C, C++, Java ve C# dillerinde ise
/ operatörüyle yapılmaktadır. Bu durumda örneğin C'de -10 % 4 işlemi bize -2 değerini verir. Çünkü -10 / 4 = -2'dir.
-2 * 4 ise -8'dir. -10 - -8 ise -2'dir. Halbuki Python'da -10 // 4 = -3'tür. Dolayısıyla -3 * 4 = -12'dir. -10 - -12
ise +2'dir. Python'da % işleminde bölme işleminin // operatörü ile diğer bazı dillerde / opearörüyle yapıldığına bir kez
daha dikkat ediniz.
Pekiyi Python'da -10 % -4 işleminin sonucu nedir? Bu ifade yukarıda da belirttiğimiz gibi aslında -10 - (-10 // -2) * -4
işleminin sonucudur. Bunun sonıcı da -2'dir. Şimdiş aynı ifadenin sonucunu C, C++, Java ve C# dilleri elde edelim.
Bu dillerde de bu işlem -10 - (-10 / -2) * -4 ile eşdeğerdir ve yine -2 sonucunu verecektir. Örneğin:
>>> -10 % 4
2
>>> -10 % -4
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = -10 % 4
print(result, type(result)) # 2 <class 'int'>
result = 10 % -4
print(result, type(result)) # -2 <class 'int'>
2024-08-12 17:16:10 +03:00
result = -10 % -4
print(result, type(result)) # -2 <class 'int'>
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
İş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:
2024-07-15 13:23:34 +03:00
() 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
2024-08-12 17:16:10 +03:00
Bu ifadenin anlamlı olabilmesi için ilk - operatörünün çıkartma operatörü diğer - operatörlerinin işaret - operatörü olması
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
operand'ının sağdaki operand ile belirtilen kuvvetini üretir. (Sayının 0.5'inci kuvvetinin syaının karekök anlamına
geldiğini anımsayınız.) Matematikte sayının negatif kuvvetlerinin de geçerli olduğuna dikkat ediniz. Örneğin:
>>> 2 ** 2
4
>>> 2 ** -2
0.25
>>> 2.5 ** 1.5
3.952847075210474
>>> 4 ** 0.5
2.0
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
** 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ırgatıcıdır. ** operatörünün işaret + ve - operatörlerinden yüksek önceliğe sahip olması kanımızca pek
uygun değildir.
2024-07-15 13:23:34 +03:00
() soldan sağa
** sağdan sola
+ - sağdan sola
* / // % soldan sağa
+ - soldan sağa
= sağdan sola
2024-08-12 17:16:10 +03:00
Bu durumda örneğin -3 ** 2 işleminin sonucu 9 değil -9'dur. Çünkü bu işlem şöyle önceliklendirilmektedir:
İ1: 3 ** 2 => 9
İ2: -İ1 => -9
Tabii biz gerçekten bu işlemden +9 elde etmek istiyorsak -3 değerini paranteze almalıyız. Örneğin:
>>> (-3) ** 2
9
Matematikte -3^2 gibi bir yazım gerçekten Pythob'daki gibi 3'ün karesinin negatifi anlamına gelmektedir. Python'un bu
tür durumlarda matematiksel alana yakınlaşmak istediğine dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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ü
2024-08-12 17:16:10 +03:00
varsa önce sağdaki yapılmaktadır. Örneğin:
>>> 2 ** 3 ** 2
512
>>> (2 ** 3) ** 2
64
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = 2 ** 3 ** 2
print(result) # 512
result = (2 ** 3) ** 2
print(result) # 64
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da 6 karşılaştırma operatörü vardır. Karşılaştırma operatörlerinin hepsi iki operand'lı araek (binary infix)
2024-07-15 13:23:34 +03:00
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.
2024-08-12 17:16:10 +03:00
Bu operatörler bool türden değer üretirler. Örneğin:
>>> 3 > 2
True
>>> 3 != 3
False
>>> 3.2 > 1
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-08-12 17:16:10 +03:00
dönüştürülürler sonra işleme sokulurla girerler. bool değerler int türüne dünüştürülürken True 1 olarak, False
2024-07-15 13:23:34 +03:00
ise 0 olarak dönüştürülmektedir. Örneğin:
result = True > 0
2024-08-12 17:16:10 +03:00
Bu işlemde operand'lardan biri bool diğeri int türdendir. Bu durumda True int türüne 1 olarak dönüştürülecek ve sonuç
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
Burada aslında 1 > 0 gibi bir karşılaştırma yapılmıştır. Dolayısıyla bu işlem True sonucunu verecektir. Örneğin:
>>> True > False
True
>>> True == True
True
>>> 0 == False
True
>>> 1 >= True
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = True > 0
print(result) # True
result = True > False
print(result) # True
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
11. Ders 28/07/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da karşılaştırma operatörleri kombine edildiğinde "and" etkisi oluşmaktadır. Örneğin a < b < c ifadesi geçerlidir
ve bu ifade a < b and b < c anlamına gelmektedir. Ya da örneğin a == b != c ifadesi geçerlidir. Bu ifade a == b and
b != c anlamına gelmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
a == b == c
2024-08-12 17:16:10 +03:00
işlemi a == b and b == c anlamına gelmektedir. Yani burada bu üç değerin biribirne şit olup olmadığı sorgulanmıştır.
2024-07-15 13:23:34 +03:00
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.
2024-08-12 17:16:10 +03:00
Python'da mantıksal operatörlerin operand'ları herhangi bir türden olabilir. Halbuki Java ve C# gibi bazı dillerde mantıksal
operatörlerin operand'ları bool türden olmak zorundadır. Yani örneğin Python'da aşağıdaki gibi bir işlem geçerlidir:
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
Burada a > b ve c > d koşulunun aynı anda sağlanıp sağlanmadığına bakılmıştı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 bulunursa bulunsun önce yalnızca sol tarafları yapılmaktadır. Bu operatörler operandlarını True/False
olarak yorumlarlar. int ve float operand'larda 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
ifadenin değerini ü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 ifadenin değeri üretir, eğer bu operatörün sol tarafındaki
True-False değil "sol tarafındaki ya da sağ tarafındaki ifadenin değerini üretiğine" dikkat ediniz. Örneğin:
2024-08-27 01:07:02 +03:00
ifade False ise bu kez sağ tarafındaki ifade yapılır, operatör sağ tarafındaki değeri üretir. and ve or operatörlerinin
2024-07-15 13:23:34 +03:00
result = -3 and 6.7
print(result) # 6.7
2024-08-12 17:16:10 +03:00
Burada -3 True olarak ele alınır. Bu durumda and operatörünün sağ tarafı yapılır ve operatör sağ taraftaki ifadenin değerini üretecektir.
Bu da 6.7'dir.
2024-07-15 13:23:34 +03:00
Örneğin:
result = 0 and 6.7
print(result) # 0
2024-08-12 17:16:10 +03:00
Burada and operatörünün sol tarafındaki ifade Fals olarak ele alınır. Bu durumda operatörün sağ tarafı hiç yapılmaz. Operatör
sol taraftaki ifadenin değerini üretir. O da 0'dır.
2024-07-15 13:23:34 +03:00
Örneğin:
result = 3 + 2 or False
print(result) # 5
2024-08-12 17:16:10 +03:00
Burada 3 + 2 işleminden 5 değeri elde edilecektir. Bu değer True olarak ele alınır ve sağ taraf yapılmaz doğrudan 5 değeri
elde edilir. Örneğin:
2024-07-15 13:23:34 +03:00
result = 3 > 2 and 5 < 0
print(result) # False
2024-08-12 17:16:10 +03:00
Burada sol tarafta 3 > 2 işleminden True değeri elde edilir. Bu durumda sağ taraf yapılır. 5 < 0 işleminden False değeri
elde edilecektir. Operatör sağ taraftaki ifadenin değeri olan False değerini üretecektir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
result = -1.2 and 0
2024-07-15 13:23:34 +03:00
print(result) # 0
2024-08-12 17:16:10 +03:00
Burada -1.2 True olarak ele alınır. Bu durumda and operatörünün sağ tarafı yapılır ve onun değeri üretlir. O da 0'dır.
2024-07-15 13:23:34 +03:00
Örneğin:
result = 10 and 20
print(result) # 20
2024-08-12 17:16:10 +03:00
Burada 10 değeri True olduğu için and operatörünün sağ tarafındaki değer üretilecektir. O da 20'dir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Teknik olarak and operatörü or operatöründen daha önceliklidir. Ancak kısa devre özelliğinden dolayı ifadelerin yapılış
sırası programcıların kaafasını karıştırabilmektedir. Örneğin:
ifade1 and ifade2 or ifade3
ifadesi aslında aşağıdakiyle eşdeğerdir:
(ifade1 and ifade2) or ifade3
2024-07-15 13:23:34 +03:00
Örneğin:
2024-08-12 17:16:10 +03:00
ifade1 or ifade2 and ifade3
ifadesi de aslında aşağıdaki ile eşdeğerdir:
ifade1 or (ifade2 and ifade3)
Kısa devre özelliği and operatörünün or operatöründen öncelikli biçimde ele alınmasıyla aynı sonucu ancak daha az işlem
yapılarak elde edilmesini sağlamaktadır. Dolayısıyla 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:
2024-07-15 13:23:34 +03:00
ifade1 and ifade2 or ifade3
2024-08-12 17:16:10 +03:00
Bu ifade aslında aşağıdaki ile eşdeğerdir:
(ifade1 and ifade2) or ifade3
Burada or operatörüün sol tarafı önce yapılacaktır. Ancak or operatörünün solunda da and operatörü vardır. and operatörünün
de sol tarafı önce yapılacağına göre burada önce ifade1 yapılır. Eğer ifade1 False ise ifade2 yapılmaz ancal ifade3
yapılır. Eğer ifade1 True ise ifade2 yapılır. İfade2 de True ise ifade3 yapılmaz. Bu ifadeden yine ifade2 ya da ifade3'ün
sonucu elde edilecektir. Örneğin:
2024-07-15 13:23:34 +03:00
ifade1 or ifade2 and ifade3
2024-08-12 17:16:10 +03:00
Bu ifade aslında aşağıdakiyle eşdeğerdir:
2024-07-15 13:23:34 +03:00
ifade1 or (ifade2 and ifade3)
2024-08-12 17:16:10 +03:00
O halde burada önce ifade1 yapılır. ifade1 True ise başka bir şey yapılmaz. Tüm ifadenin sonucu ifade1'in sonucu olarak
elde edilir. Eğer ifade1 False ise bu durumda ifade2 yapılır. ifade2 de False ise ifade3 yapılmaz. Toplam sonuç ifade2'nin
sonucu olur. Eğer ifade1 False, ifade2 True ise ifade3 yapılır. Toplam sonuç ifade3'ün sonucu olur.
Özetle and ve or operatörleri aynı ifadede kullanıldığında her zaman soldaki operatörün sol tarafı önce yapılmaktadır.
Diğer taraflar duruma göre yapılacaktır. Örneğin:
>>> 10 and 2.3 or 7
2.3
>>> 10 and 0 or 20
20
>>> 10 or 20 and 30
10
>>> 0 or 20 and 0
0
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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. Örneğin:
>>> not 10
False
>>> not 0.2
False
>>> not 0
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = not 23.4
print(result) # False
result = not False
print(result) # True
result = not 0
print(result) # True
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
not operatörü öncelik tablosunda and ve or operatörlerinden daha yüksek önceliklidir ve kendi içerisinde sağdan sola
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
Örneğin:
>>> not not not not 10
True
>>> not not True
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = not not not not True
print(result) # True
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
>>> not 1 - 1
True
>>> not 3 * 2
False
Örneğin:
2024-07-15 13:23:34 +03:00
result = not a + b
işleminde ya da:
result = not a < b
2024-08-12 17:16:10 +03:00
işleminde Python'da önce toplama ve karşılaştırma oeratörleri yapılıp sonra not operetörü yapılmaktadır. Halbuki C, C++
gibi dillerde önce not işlemi yapılmaktadır. Bu yukarıdaki ifadenin C karşılığı C'de farklı bir biçimde sonuç elde
edilmesine yol açacaktır:
2024-07-15 13:23:34 +03:00
result = !a < b;
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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. Örneğin:
>>> 'ankara' or 0
'ankara'
>>> '' or 100
100
>>> '0' or 100
'0'
>>> ' ' or 100
' '
İçinde SPACE karakteri olan string'lerin de dolu string olarak ele alındığına dikkat ediniz. Boş string'ten kastettiğimiz
uzunluğu 0 olan string'lerdir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2024-08-12 17:16:10 +03:00
Bu işlemin sonucunda a is b ifadesi True değerini verecektir. Örneğin:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Yukarıda da belirttiğimiz gib i 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şağıdaki gibi bir ifade C, C++, Java ve C#'ta geçerli olduğu halde Python'da geçersizdir:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
a = (b = 10) + 20 # C, C++, Java ve C#'ta geçerli, Python'da geçersiz!
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'a 3.8 versiyonu ile birlikte (2019 Ekim) "Walrus operatörü" diye isimlendirilen değer üreten bir atama operatörü
de eklendi. Bu operatör sayesinde artık diğer dillerde yapılan faydalı birtakım işlemler Python'da da yapılabilir
hale getirilmiştir. Walrus operatörü := sembolü ile temsil edilmektedir. Walrus operatörü iki operandlı araek (binary
infix) bir operatördür. Örneğin:
a = (b := 10) + 5 # Python 3.8 ile birlikte geçerli
print(a, b) # 15 10
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = (b := 10) + 20
print(a, b) # 30 10
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da tasarımsal bakımında gereksiz bir biçimde Walrus operatörünün kullanılması istenmemiştir. By nedenle atama
operatörünün kullanılabileceği bir bağlamda Walrus operatörü kullanılırsa bu durum error oluşturmaktadır. Başka bir
deyişle walrus operatörü "atamadan elde edilen değerin kullanılması için" dile eklenmiştir. Atamadan elde edilen değerin
kullanılmadığı ifadelerdeki Walrus kullanımı error oluşturmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
a := 10
2024-08-12 17:16:10 +03:00
Burada walrus operatörüne hiç gerek yoktur. Bu nedenle bu kullanım error oluşturur. Ancak örneğin:
2024-07-15 13:23:34 +03:00
a = (b := 10) + 20
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
a := b := 10
2024-08-12 17:16:10 +03:00
Burada walrus operatörü gereksiz kullanılmıştır. Error oluşacaktır. Çünkü bu işlem atama operatörleriyle de yapılabilmektedir.
walrus operatörü paranteze alınırsa her zaman geçerli kabu edilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
a := 10 # geçersiz!
(a := 10) # geçerli
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Parantezler aslında "elde edilen değerin kullanılması" anlamına gelmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
Örneğin:
2024-08-12 17:16:10 +03:00
b := (a := 10) # error!
2024-07-15 13:23:34 +03:00
Fakat örneğin:
b = (a := 10) # geçerli
(b := (a := 10)) # geçerli
2024-08-12 17:16:10 +03:00
Örneğin:
2024-07-15 13:23:34 +03:00
print(a := 10) # geçerli parantez zaten vardır
2024-08-12 17:16:10 +03:00
Bu kod aşağıdaki ile eşdeğerdir:
2024-07-15 13:23:34 +03:00
a = 10
print(a)
2024-08-12 17:16:10 +03:00
Özetle atama operatörü ile yapabileceğimiz bir işlemi walrus operatörü ile yapmaya çalışırsak bu durum error oluşturur.
Eğer error oluşturmasın istiyorsak walrus operatörünü paranteze almalıyız. Fakat atama operatörü ile yapamadığımız bir
şeyi Walrus operatörüyle yapabiliyorsak bu durum error oluşturmaz. Örneğin:
2024-07-15 13:23:34 +03:00
print(a := 10)
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
a := 10 # error!
2024-08-12 17:16:10 +03:00
Biz bu işlemi atama operatörü ile yapabilirdik. Burada paranteze alınmadan walrus operatörünün kullanılması error oluşturur.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
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 sokulabilmektedir. Örneğin :
2024-07-15 13:23:34 +03:00
while s := input():
pass
2024-08-12 17:16:10 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
walrus opeeratörü öncelik tablosunda atama opeeatör ile aynı gruptadır:
2024-07-15 13:23:34 +03:00
() 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)
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
12. Ders 03/08/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da ++ ve -- biçiminde operatörler yoktur. ++ ve -- operatörleri C, C++, Java ve C# gibi dillerde vardır ve bu
dillerde programcılar bu operatörleri çokça kullanılmaktadır. Bu dillerdeki ++ operatörü değişkenin içerisindeki değeri
1 artıtmak için, -- operatörü ise değişkenin içerisindeki değeri 1 eksiltmek için kullanılmaktadır. Python'da bir değişkenin
gösterdiği nesne içerisindeki değeri 1 artırmak ya da eksiltmek için iki yol vardır. Birincisi bu artırmayı ya da eksiltmeyi
ıkça yapmaktır. Örneğin:
a = a + 1
b = b - 1
İkincisi ise işlemli atama operatörlerini kullanmaktır. Bu operatörleri izleyen paragraflarda ele alacağız. Örneğin
a += 1
b -= 1
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da +=, -=, *=, /=, //=, %= biçminde bir grup işlemli atama operatörü (augmented assignment statments) vardır.
Bu operatörler iki operand'lı araek operatörlerdir. op bir operatör belirtmek üzere:
2024-07-15 13:23:34 +03:00
a op= b
işlemi aşağıdakiyle eşdeğerdir:
a = a op b
2024-08-12 17:16:10 +03:00
Bu durumda örneğin 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:
2024-07-15 13:23:34 +03:00
a += 1
2024-08-12 17:16:10 +03:00
Benzer biçimde bir değişkeni 1 eksiltmek için en pratik yöntem de -= operatörünü kullanmaktır:
2024-07-15 13:23:34 +03:00
a -= 1
Tabii işlemli atama operatörleri temel türlerle kullanıldığında bu temel türler "değiştirilemez (immutable)" olduğu
2024-08-12 17:16:10 +03:00
için işlem yeni nesnelerin yaratılmasına yol açacaktır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = 10
>>> id(a)
140712253256776
>>> a += 1
>>> a
11
>>> id(a)
140712253256808
2024-08-12 17:16:10 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
İş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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Anımsanacağı gibi print fonksiyonu çıktıları imlecin (cursor) bulunduğu yerden itibaren ekrana (stdout dosyasına) yazdırıp
imleci yazdırdığı karakter sayısı kadar ilerletmektedir. print fonksiyonu değişken sayıda argüman alabilmektedir. Yani
biz print fonksiyonu 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 de '\n' karakterini ekrana (stdout dosyasına)
yazdırmaktadır. Yani print işleminden sonra imleç aşağı satırın başına geçmektedir. Örneğin:
a = 10
b = 20
c = 30
d = 40
print(a, b, c)
print(d)
Burada print önce a değerini ekrana yazdırır sonra bir SPACE boşluk bırakarak b değerini ekrana yazdırır, sonra da
yine bir SPACE boşluk bırakarak c ekrana yazdırır. İşini bitirdikten sonra '\n' karakterini de ekrana yazdırarak sonlanır.
Bu '\n' karakteri imlecin aşağı satırın başına geçmesini sağlamaktadır. Böylece akış diğer print çağrısına geldiğinde
artık imleç aşağı satırın başında olduğu için d değeri aşağıdaki satırın başına yazdırılacaktır. Yani yazdırılan değerler
şöyle görünecektir:
10 20 30
40
Örneğin:
a = 10
print('a =', a)
Burada print önce "a =" yazısını ekrana yazıdırır. Sonra bir SPACE boşluk bırakmak için SPACE karakterini ekrana yazdırır,
sonra da a'nın değerini ekrana yazdırır. Şöyle bir görüntü elde edilecektir:
a = 10
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
print('ali', 'veli', 'selami') # ali veli selami
a = 10; b = 20; c = 30
print(a, b, c) # 10 20 30
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Yukarıda da belirttiğimiz gibi 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ı da sağlayabilir. sep için girilen yazının tek karakter olması gerekmemektedir. Örneğin:
a = 10
b = 20
c = 30
print(a, b, c, sep=', ')
Burada artık değerler arasında SPACE yazdırılmayacak ", " yazısı yazdırılacaktır. Böylece aşağıdaki gibi bir görüntü
elde edilecektir:
10, 20, 30
sep parametresinin aynı çağırda birden fazla argüman varsa bir etki oluşturacağına dikkat ediniz. Eğer çağrıda tek
bir argüman kullanılıyorsa sep parametresi zaten hiç kullanılmayacaktır. Örneğin:
a = 10
print(a, sep='*')
Burada sep parametresinin girilmesine hiç gerek yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 10
b = 20
c = 30
print(a, b, c, sep='***') # 10***20***30
print('Ok')
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
sep parametresi için hiç argüman girilmemesiyle girilmemesiyle bunun sep=' ' biçiminde girilmesi arasında bir fark
olmadığına dikkat ediniz. Yani sep parametresinin default değerini ' ' olarak düşünebilirsiniz.
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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 yazdırmasını isteyebiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
print(10, 20, 30, sep='*', end='---')
2024-08-12 17:16:10 +03:00
print('ankara')
2024-07-15 13:23:34 +03:00
Burada ekranda şunları göreceğiz:
10*20*30---ankara
2024-08-12 17:16:10 +03:00
print çağrısında sep ile end kullanılırken bunların sırası farklı girilebilir. Ancak önce sep sonra end parametreleri
için argüman girmek daha anlaşılır bir yazım oluşturmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-08-12 17:16:10 +03:00
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.
2024-07-15 13:23:34 +03:00
Tür dönüştürme işleminin genel biçimi şöyledir:
<dönüştürülecek tür>(<ifade>)
Örneğin:
a = 13.2
b = int(a)
2024-08-12 17:16:10 +03:00
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ğerinden hareketle arzu
edilen türden yeni bir nesnenin yaratılmasını sağlamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
a = 10
b = float(a)
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Burada a'nın türü değişmemektedir. Ancak yeni bir float nesne yaratılacak ve bu float nesnenin içerisinde 10.0 biçiminde
bir değer bulunacaktır.
2024-07-15 13:23:34 +03:00
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.
2024-08-12 17:16:10 +03:00
Eğer bir ifade aynı türe dönüştürülüyormuş gibi yapılırsa ve o tür değiştirilemez bir tür ise (temel türlerin değiştirilemez)
olduğunu anımsayınız) yorumlayıcı yeni bir nesne yaratmayabilir. Bu konu yorumlayıcıların insiyatifinde bulunan "optimizasyon"
ile ilgidir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> a = 10
>>> b = int(a)
>>> id(a)
140708645475400
>>> id(b)
140708645475400
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Burada CPython yorumlayıcısının zaten int türünden olan değişkenin int türüne dönüştürülmesi sırasında yeni bir nesne
yaratmadığı görülmektedir. Yorumlayıcının yeni bir nesne yaratması ya da yaratmaması programın anlamı bağlamında
hiçbir değişikliğe yol açmayacaktır. İşte bu tür durumlarda yorumlayıcılar kodun daha etkin hale getirilmesi amacıyla
durumu istedikleri gibi ele alabilmektedir.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'daki tüm temel türler T(...) sentaksıyla birbirilerine dönüştürülebilmektedir. Python programcısının T1 türünün
T2 türüne dönüştürülmesi sonucunda nelerin olacağını bilmesi gerekir. Biz de izleyen paragraflarda hangi tür hangi türe
dönüştürüldüğünde ne olacağını tek tek ele alacağız.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
1) 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.
Bir noktalı sayının bu biçimde bir tamsayıya dönüştürülmesine İngilizce "truncation toward zero" denilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> a = 3.99
>>> b = int(a)
>>> b
3
>>> a = -3.99
>>> b = int(a)
>>> b
-3
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Buradaki işlemin "floor" ya da "ceil" işlemi olmadığına dikkat ediniz. Yukarıda da belirttiğimiz gibi bu biçimdeki
dönüştürmelere İngilizce "truncation toward zero" denilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
2) 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
2024-07-15 13:23:34 +03:00
yeni bir int nesne yaratılır ve yazı int bir nesne biçiminde ifade edilir. Ancak yazının içerisindeki karakterler int
2024-08-12 17:16:10 +03:00
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. Bir yazının başındaki boşluk
karakterlerine İngilizce "leading space", yazının sonundaki boşluk karakterlerine ise 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:
2024-07-15 13:23:34 +03:00
>>> a = '123'
>>> b = int(a)
>>> b
123
>>> a = '12.3'
>>> b = int(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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')
2024-08-12 17:16:10 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-08-12 17:16:10 +03:00
ikinci parametresiyle belirleyebiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2024-08-12 17:16:10 +03:00
Burada a yazısının belirttiği sayı 2'lik sistemde yorumlanacaktır. 16'lık, 8'lik ya da 2'lik sistemden yazı dönüştürürken
yazının başında taban belirten önekler de bulunabilir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = '0x1234'
>>> a = int(s, 16)
>>> a
4660
>>> s = '0o1234'
>>> a = int(s, 8)
>>> a
668
>>> s = '0b10101'
>>> a = int(s, 2)
>>> a
21
2024-08-12 17:16:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
3) bool bir değer int türüne dönüştürülürse eğer değer True ise 1, False ise 0 olarak dönüştürülür. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> a = True
>>> b = int(a)
>>> b
1
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
a = True
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
b = int(a)
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
print(b) # 1
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
a = False
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
b = int(a)
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
print(b) # 0
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
4) Bir complex türünden değer int türüne dönüştürülememektedir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> z = 3j+2
>>> a = int(z)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'complex'
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
Benzer biçimde None değeri de int türüne dönüştürülememktedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
O halde int türüne dönüştürme özetle şöyle yürütülmektedir:
2024-08-12 17:16:10 +03:00
- float bir değer int türüne dönüştürüldüğünde noktandan sonraki kısım atılır (truncation toward zero)
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
- str türü int türüne dönüştürüldüğünde eğer yazının içerisindeki karakterler int türü için anlamlıysa dönüştürme yapılır
2024-07-15 13:23:34 +03:00
değilse exception (ValueError) oluşur.
2024-08-12 17:16:10 +03:00
- bool bir değer int türüne dönüştürüldüğünde False için 0, True için 1 elde edilmektedir.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
- complex türü int türüne dnüştürülemez. Dönüştürülmeye çalışılırsa exception (TypeError) oluşur.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
- None değeri (NoneType türü) int türüne dönüştürülemez. Dönüştürülmeye çalışılırsa exception (TypeError) olulur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
5) 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 nedenle ç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ürüne
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 float sayı elde edilmektedir. Ancak yukarıda da belirtitğimiz gibi basamaksal
kayıplar exception'a (OverflowError) yol açmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float
2024-08-12 17:16:10 +03:00
Özetle int türünden float türüne dönüştürme şöyle yapılmaktadır:
- Eğer int değer float türü ile tam olarak ifade edilebiliyorsa herhangi bir kayıp olmadan sayı .0 biçiminde dönüştürülür.
- Eğer int değer float türüyle tam ifade edilemiyorsa ancak basamaksal bir kayıp oluşmuyorsa bu durumda mantis kaybıyla
dönüştürme yapılır. Dönüştürme sonucunda orijinal int değere en yakın o değerden büyük olan ya da o değerden küçük olan
float sayı değer elde edilir.
- Eğer int değer float türüne basamaksal kayıp olmadan dönüştürülemiyorsa bu durumda exception (OverflowException)
oluşmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
6) Bir string float türüne de dönüştürülebilir. Bu durumda string float türü 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 yani
string'in içerisinde float bir syaı için anlamlı olmayan karakterler varsa 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 CPython gerçekleştirimi bu durumda +inf ya da -inf değerlerini üretmektedir. Yine yazının
başındaki sonundaki boşluk karakterleri ("leading space" ve "trailing space") bir sorun oluşturmamaktadır. Örneğin:
>>> s = '123.456'
>>> f = float(s)
>>> print(f)
123.456
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = ' 123.45 '
b = float(a)
print(b)
a = '123.23ali'
b = float(a) # exception oluşur
print(b)
2024-08-12 17:16:10 +03:00
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)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
7) complex türünden bir değer de float türüne dönüştürülememektedir. Benzer biçimde None değeri de float türüne
dönüştürülemez. Bu dönüştürmelerde exception (TypeError) oluşmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
O halde float türüne dönüştürme özetle şöyle yürütülmektedir:
2024-08-12 17:16:10 +03:00
- int bir değer float türüne bilgi kaybı olmadan, mantis kaybıyla kayıp ile dönüştürülebilir. Ancak basamaksal bir kayıp
2024-07-15 13:23:34 +03:00
oluşacaksa dönüştürme exception (OverflowError) ile sonuçlanır.
2024-08-12 17:16:10 +03:00
- 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.
2024-07-15 13:23:34 +03:00
Aksi takdirde dönüştürme exception (ValueError) ile sonuçlanır.
2024-08-12 17:16:10 +03:00
- 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.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
- Complex türü float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur.
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
- None değeri (NoneType türü) float türüne dönüştürülemez. Dönüştürülmek istenirse exception (TypeError) oluşur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
8) int bir değer bool türüne dönüştürülürken eğer değer sıfır dışı bir değerse (sıfır dışı değer demekle 0'ın dışında
pozitif ya da negatif herhangi bir değer kastedilmektedir.) dönüştürme True olarak değer 0 ise dönüştürme False olarak
yapılmaktadır. float bir değer de bool türüne aynı biçimde dönüştürülmektedir. None değeri bool türüne her zaman False
olarak dönüştürülmektedir. Complex bir değer bool türüne dönüştürülürken complex sayının gerçek ya da sanal kısmı sıfır
değilse dönüştürme True olarak, her iki kısmı da sıfır ise dönüştürme False olarak yapılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> b = bool(123)
>>> b
True
>>> b = bool(0.01)
>>> b
True
>>> b = bool(None)
>>> b
False
>>> b = bool(1j+0)
>>> b
True
>>> b = bool(0j+0)
>>> b
False
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
9) 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:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> s = 'False'
>>> b = bool(s)
>>> b
True
>>> s = ''
>>> b = bool(s)
>>> b
False
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 'ankara'
b = bool(a)
print(b) # True
a = 'False'
b = bool(a)
print(b) # True
a = ''
b = bool(a)
print(b) # False
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
10) Ayrıca boş listeler, boş demetler, boş sözlükler, boş kümeler bool türüne False olarak, dolu listeler, dolu demetler,
dolu sözlükler ve dolu kümeler de True olarak dönüştürülürler. Listeler, demetler, sözlükler ve kümeler konusu izleyen
bölümlerde ele alınmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
11) 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:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> z = complex(10)
2024-07-15 13:23:34 +03:00
>>> z
(10+0j)
2024-08-12 17:16:10 +03:00
>>> z = complex(3.14)
>>> z
(3.14+0j)
>>> z = complex(True)
>>> z
(1+0j)
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
complex sayılar print edilirken her gerçek ve sanal kısımları tamsayı ise güzel bir görüntü 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
12) Bir string de complex türüne dönüştürülebilir. Bunun için yazının 'a+bj' biçiminde olması gerekir. Yazının başındaki
ve sonundaki boşluk karakterleri yine dikkate alınmamaktadır. Ancak yazının arasında boşluk karakteri bulunamaz. Ayrıca yazı
'aj+b' biçiminde de belirtilemektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
ValueError: complex() arg is a malformed string
2024-08-12 17:16:10 +03:00
None türü complex türüne dönüştürülemez.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
13) 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:
2024-07-15 13:23:34 +03:00
>>> 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)'
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
14) Temel türlere dönüştürme yaparken parantezlerin içleri boş bırakılabilir. Bu durumda int için 0, float için 0.0, complex
için 0+0j, bool için False ve str için '' elde edilmektedir. Örneğin:
>>> int()
0
>>> float()
0.0
>>> str()
''
>>> complex()
0j
>>> bool()
False
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Şimdi artık klavyeden (stdin dosyasından) belli bir tür için okumanın neden tür dönüştürmesi gibi yapıdığını anlayabiliriz.
input fonksiyonu her zaman string okuması yapmaktadır. Dolayısıyla okunan değerler T türüne T(input(....)) biçiminde
dönüştürülmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da iki int iki operand'lı aritmetik operatörlerle işleme sokarsak sonuç int türden çıkar. Benzer biçimde iki
float değeri de aritmetik operatörlerle işleme sokarsak sonuç float türden çıkacaktır. Ancak int bir değer ile float
bir değeri aritmetik işleme sokarsak sonuç yine float türünden elde edilmektedir. Yani:
int <op> int => int
float <op> float => float
int <op> float => float
float <op> int => float
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. Karşılaştırma operatörlerin de her zaman
bool türden değerler ürettiğine dikkat ediniz. Örneğin:
>>> a = 10
>>> b = 3.14
>>> c = a + b
>>> type(c)
<class 'float'>
>>> b = 10 > 2.3
>>> b
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 10
b = 3.14
c = a + b
print(c, type(c)) # 13.14 <class 'float'>
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
bool türü ile int türü aritmetik işleme sokulursa sonuç int türünden, bool türü ile float türü aritmetik 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. Başka bir deyişle aslında bool türü
artimetik işlemlere sokulurken yorumlayıcı tarafından otomatik olarak int türüne dönüştürülmektedir. Bu nedenle iki bool
türü de aritmetik işlemlere sokulduğunda sonuç int türünden elde edilmektedir. (/ operatörünün üretiiği değer her zaman
float türdendir.) Örneğin:
>>> a = True + False
>>> a
1
>>> type(a)
<class 'int'>
>>> a = True * 3.14
>>> a
3.14
>>> type(a)
<class 'float'>
>>> True / True
1.0
>>> z = 3j + 2
>>> a = z + 1.2
>>> a
(3.2+3j)
>>> type(a)
<class 'complex'>
bool türünün diğer türlerle artimetik işlemlere sokulabilmesi nedeniyle örneğin (a > 0) + 10 gibi ifadeler Python'da
(tıpkı C'de olduğu gibi) geçerlidir. Java ve C# gibi bazı dillerde bool türü hiçbir türle aritmetik işlemlere
sokulamamaktadır
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 10
b = True
c = a + b
print(c, type(c)) # 11 <class 'int'>
a = 12.34
c = a + b
print(c, type(c)) # 13.34 <class 'float'>
a = 3j+2
c = a + b
print(c, type(c)) # (3+3j) <class 'complex'>
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Yukarıda da belirttiğimiz gibi int bir değerle float bir değer işleme sokulduğunda sonuç float türden çıkacaktır ancak
sonuç üzerinde mantis kayıpları oluşabilir. Eğer oluşan kayıp basamaksal değilse bu bir hata olarak değerlendirilmez.
Gerçek değere en yakın gerçek değerden büyük olan ya da gerçek değerden küçük olan değerelde edilir. Ancak basamaksal
bir kayıp söz konusu olursa exception (OverflowError) oluşmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> result = 1234567890123456789 + 1.0
>>> result
1.2345678901234568e+18
2024-07-15 13:23:34 +03:00
>>> a = 1987239812739817239817239871293871928371987239817239817239871293871298371982739812379812739182739812739
817239817239871239871293871298371982379182739817239817239871298371928379182739817239817298379827349827349872349
87239847293847928374982734982734982734987239847293847928374982374982374982374982374982374982371234234234234
>>> b = a * 2.0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da iki str türünden değer toplama işlemine sokulabilir ancak diğer aritmetik işlemlere sokulamaz. İki str
değeri toplandığında yeni bir str nesnesi yaratılır. Bu yeni str nesnesinin içerisinde operand olan iki strin'in
birleşiminden oluşan string bulunacaktır. Yani iki str nesnesinin toplanması iki yazının birleştirilmesi (concatenate
edilmesi) anlamına gelmektedir. Java, C# gibi pek çok dilde de bu özellik benzer biçimde vardır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = 'ankara'
>>> k = 'istanbul'
>>> result = s + k
>>> result
'ankaraistanbul'
2024-08-12 17:16:10 +03:00
Java ve C# gibi bazı dillerde + operatörünün bir operand'ı string ise diğer operand'ı herhangi bir türden olabilmektedir.
Bu durumda bu dillerde string olmayan operand otomatik olarak string türüne dönüştürülüp yazılar birleştirilmektedir. Ancak
Python'da str türü ile diğer türler + operatörüyle işleme sokulamamaktadır. Bu tür durumlarda programcının diğer operand'ı
ıkça str türüne dönüştürmesi gerekir.
>>> s = 'Ankara-'
2024-07-15 13:23:34 +03:00
>>> a = 6
>>> b = s + a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
2024-08-12 17:16:10 +03:00
Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> s = 'Ankara-'
2024-07-15 13:23:34 +03:00
>>> a = 6
>>> b = s + str(a)
>>> b
2024-08-12 17:16:10 +03:00
'Ankara-6'
2024-07-15 13:23:34 +03:00
String'lerle ilgili diğer özellikler izleyen bölümlerde ele alınmaktadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da bir string int türdne bir değerle çarpılabilir. (Ancak int türden bir değerle diğer işlemlere sokulamaz.)
Bu işleme Python dünyasında "yineleme (repitition)" denilmektedir. Örneğin:
s = 'ali' * 3 # Python'da geçerli
Bu işlem Python'da geçerlidir. (Diğer pek çok programlama dilinde geçerli değildir.) Tabii çarpma işleminin değişme
özelliği olduğu için operand'ların yerleri de değiştirilebilmektedir. Örneğin:
s = 3 * 'ali' # Python'da geçerli
Bir string int bir değerle çarpıldığında aslında çarpılan değer kadar toplama yapılmaktadır. Yani örneğin.
s = 'ali' * 3
İşlemi aşağıdaki ile eşdeğerdir:
s = 'ali' + 'ali' + 'ali'
Örneğin:
>>> s = 'ali' * 3
>>> s
'alialiali'
>>> s = ' ' * 5 + 'Ankara' + ' ' * 5
>>> s
' Ankara '
>>> s = '-' * 10
>>> s
'----------'
Bir string negatif bir değerle çarpılırsa ya da 0 ile çarpılırsa boş string elde edilmektedir. Örneğin:
>>> s = 'ali' * -3
>>> s
''
>>> s = 'ali' * 0
>>> s
''
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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ı her ne kadar tekil bir tamlama gibi söyleyniyorsa da aslında çoğul bir anlam ifade etmektedir.
Python'da ismine "liste (list)", "demet (tuple)", "sözlük (dictionary)", "küme (set)" ve "string (str)" denilen built-in
biçimde bulunan veri yapıları vardır. Python'daki bu built-in veri yapıları doğrudan dilin sentaksıyla desteklenmiş
durumdadır. Bu da Python programlamayı oldukça kolaylaştırmaktadır. Biz de kursumuzun bu bölümünde Python'daki built-in
veri yapıları üzerinde duracağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da bazı sınıflara ilişkin nesneler "dolaşılabilir (iterable)" özellik göstermektedir. Bir nesnenin dolaşılabilir
olması dolaşıldıkça o nesneden birtakım değerlerin elde edilmesi anlamına gelmektedir. 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. Dolaşıldıkça da o tuttukları nesneleri bize
verirleer. Başka nesneleri tutan nesnelere Java ve C# gibi fillerde "collection", C++'da ise "container" denilmektedir.
Şimdiye kadar görmüş olduğumuz temel türlerden yalnızca str türü dolaşılabilir bir türdür. Dolayısıyla şimdiye kadar
gördüğümüz nesnelerden yalnızca str nesneleri dolaşılabilir nesnelerdir. Yukarıda da belirttiğimiz gibi dolaşılabilir
bir nesne dolaşıldığında birtakım değerler elde edilmektedir. Örneğin bir string nesnesi dolaşıldığında string'in içerisindeki
karakterler elde edilir. Örneğin:
s = 'ankara'
Burada s dolaşılabilir bir nesnedir. Biz bu s nesnesini dolaştığımızda sırasıyla 'a', 'n', 'k', 'a', 'r' ve 'a' string'lerini
elde ederiz. Özetle:
2024-07-15 13:23:34 +03:00
- Bir nesne dolaşılabilir ise o nesne dolaşıldığında bize nesneler vermektedir.
2024-08-12 17:16:10 +03:00
2024-07-15 13:23:34 +03:00
- 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.
2024-08-12 17:16:10 +03:00
Dolaşılabilir (iterable) nesnelere çok benzeyen ismine "dolaşım (iterator)" nesneleri de denilen nesneler vardır.
Dolaşım nesneleri (itreator) de aynı zamanda dolaşılabilir (itreable) 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. Başka bir deyişle dolaşılabilir nesneler tekrar tekrar dolaşılabilir.
Bu durumda her dolaşımda biz yeniden aynı değerleri elde ederiz. Ancak bir dolaşım nesnesi bir kez dolaşıldığında
artık biter. Biz o nesneyi bir daha dolaşmak istediğimizde artık bir şey elde edemeyiz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
14. Ders 10/08/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da en çok kullanılan veri yapısı liste (list) denilen veri yapısıdır. Listeler diğer programlama dillerindeki
"dizilere (arrays)" benzetilebilir. Listeler elemanlara sahiptir. Elemanların birer indeks numarası vardır. Elimizde
bir liste nesnesi varsa biz indeks belirterek onun belli bir elemanına erişebiliriz.
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 ayrılmış iafdeler girilirse bir liste nesnesi yaratılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
a = [10, 'ali', 2.3, True, 20]
2024-08-12 17:16:10 +03:00
Burada a değişkeni list türünden bir nesnenin adreini tutmaktadır. Örneğin:
>>> x = ['ali', 'veli', 123, True, 3.14]
>>> x
['ali', 'veli', 123, True, 3.14]
>>> type(x)
<class 'list'>
Köşeli parantezler içerisinde listeler için elemanlar girilirken herhangi birer ifade kullanılabilir. Ancak programcılar
genellikle liste elemanlarını sabit ifadeleri girerek yaratırlar. Örneğin:
>>> x = ['ali', 'veli', 123, True, 3.14]
>>> x
['ali', 'veli', 123, True, 3.14]
>>> type(x)
<class 'list'>
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
print(a) # [10, 20, 30, 40, 50]
print(type(a)) # <class 'list'>
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Listenin elemanları aynı türden olmak zorunda değildir. (Halbuki C, C++, Java ve C# gibi pek çok dilde diziler aynı
türden elemanlara sahip olmak zorundadır.) Örneğin:
a = [10, 3.14, 'ankara', True]
Tabii aslında genellikle liste elemanları aynı türden olur. Örneğin:
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [1, 3.14, 'ankara', False]
print(a) # [1, 3.14, 'ankara', False]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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ı indekstedir. 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 durumda önce köşeli parantezin içerisindeki ifadenin sayısal
değeri hesaplanır. Sonra o değerdeki indekse erişilir. Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> a[1 + 2]
40
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
val = a[2] + 100
print(val) # 130
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Köşeli parantez içerisindeki indeks belirten ifade int türden olmak zorundadır. Örneğin:
>>> a[1.2 + 1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers or slices, not float
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-08-12 17:16:10 +03:00
val = a[i + 2.] # exception! köşeli parantez içerisindeki ifade int türden olmak zorunda
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Bir listenin olmayan bir elemanına erişmeye çalışırsak IndexError isimli bir exception oluşur. Örneğin:
TypeError: list indices must be integers or slices, not float
>>> a = [10, 20, 30, 40, 50]
>>> a[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
val = a[100] # IndexError oluşur
2024-08-12 17:16:10 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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 int türden nesnenin adresini,
sonraki elemanı (yani a[1] elemanı) içerisinde "ali" yazısı bulunan str türünden bir adresini, son elemanı ise (yani
a[2] elemanı) içerisinde 12.3 değeri bulunan float türden nesnenin adresini tutmaktadır. Yani listeleri biz nesnelerin
adreslerinden oluşan bir dizi gibi düşünebiliriz.
>>> a = [10, 'ali', 12.3]
>>> id(a[0])
140710430112472
>>> id(a[1])
2525876807568
>>> id(a[2])
2525876929296
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Listeler "değiştirilebilir (mutable)" türlerdir. Bir türün değiştirilebilir olması demek o türden bir nesnenin içeriğinde
değişiklik yapılabilmesi demektir. Yani list türü değiştirilebilir olduğu için biz bir listenin elemanlarını atama yoluyla
değiştebiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
a = [10, 20, 30, 40, 50]
print(a[0]) # 10
a[0] = 'ali'
print(a[0]) # ali
print(a) # ['ali', 20, 30, 40, 50]
2024-08-12 17:16:10 +03:00
Listenin bir elemanına değer atadığımızda aslında o elemanın başka bir nesneyi göstermesini sağlamış oluruz. Örneğin:
a = [10, 20, 30]
a ---------> list_nesnesi
a[0] -----------------> 10 (int nesne)
a[1] -----------------> 20 (int nesne)
a[2] -----------------> 30 (int nesne)
a[1] = 'ankara'
a ---------> list_nesnesi
a[0] -----------------> 10 (int nesne)
a[1] -----------------> "ankara" (str nesnesi)
a[2] -----------------> 30 (int nesne)
Listelerin değiştirilebilir olması liste elemanlarının değiştirilebilir olması anlamına gelmektedir, bu elemanların
gösterdiği nesnelerin değiştirilebilir olduğu anlamına gelmemektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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ı
2024-08-12 17:16:10 +03:00
int bir değer olarak vermektedir. Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> len(a)
5
>>> b = ['ali', 'veli', 'selami']
>>> len(b)
3
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
n = len(a)
print(n) # 5
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Boş liste de söz konusu olabilir. Boş bir liste 0 eleman uzunluğundadır. Örneğin:
>>> a = []
>>> len(a)
0
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = []
n = len(a)
print(n) # 0
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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. Zaten dönüştürme bir nesne yaratma yoluyla yapılmaktadır. Biz tür dönüştürme
işlemlerini anlatırken parantezlerin içinin boş bırakılabileceğini de belirtmiştik. Örneğin int() ifadesinden içinde
0 olan bir int nesne, float() ifadesinden içinde 0 olan bir float nesne elde ederiz. bool() ifadesi ise bize içerisinde
False değeri olan bir nesne verir. İşte list() ifadesi de içi boş bir listeyi bize vermektedir. Yani aşağıdaki iki
ifade eşdeğerdir:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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 biz list fonksiyonuna bir string nesnesi verirsek list fonksiyonu o string'i dolaşıp o string'in karakterlerini elde
edecek ve o karakterlerden liste oluşturacaktır. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
liste oluşturulmuştur. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> s = 'ankara'
>>> a = list(s)
>>> a
['a', 'n', 'k', 'a', 'r', 'a']
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:
2024-07-15 13:23:34 +03:00
>>> s = 'ankara'
>>> a = list(s)
>>> a
['a', 'n', 'k', 'a', 'r', 'a']
>>> k = 123
>>> a = list(k)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
#------------------------------------------------------------------------------------------------------------------------
s = 'ankara'
a = list(s)
print(a) # ['a', 'n', 'k', 'a', 'r', 'a']
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
list sınıfının kendisi de 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:
2024-07-15 13:23:34 +03:00
a = [1, 2, 3, 4, 5]
b = list(a)
2024-08-12 17:16:10 +03:00
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. Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> b = list(a)
>>> a
[10, 20, 30, 40, 50]
>>> b
[10, 20, 30, 40, 50]
>>> id(a)
2525878408576
>>> id(b)
2525878486912
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [1, 2, 3, 4, 5]
b = list(a)
print(a, id(a))
print(b, id(b))
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Yukarıda da belirttiğimiz gibi listelerin değiştirilebilir olması demek liste elemanlarına başka adreslerin atanabilmesi
emektir. Örneğin:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir liste nesnesi dolaşıldığında listenin elemanları elde edilir demiştik. Tabii burada kastettiğimiz şey listenin
elemanlarındaki adreslerdir. Örneğin:
a = [10, 20, 30]
Burada biz bu a listesini dolaştığımızda içerisinde 10, 20, 30 olan int nesnelerin adreslerini elde ederiz. Default
durumda "listenin elemanları elde edilir" demekle "bu elemanları gösteren adresler elde edilir" demiş oluruz.
DOlayısıyla list(a) işlemi sonucunda aslında a listesindeki elemanların adreslerinden bir b listesi oluşturulmuş olur.
Örneğin:
>>> a = [10, 20, 30]
>>> b = list(a)
>>> id(a)
2525878486784
>>> id(b)
2525878487104
>>> id(a[0])
140710430112472
>>> id(b[0])
140710430112472
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da string'ler liste gibi saklanmamaktadır. Örneğin:
s = 'ali'
Burada s'in gösterdiği yerdeki str nesnesinin elemanları 'a', 'l', 'i' string'lerini tutan bir yapıda değildir.
Buradaki string doğrudan "ali" yazısının kensidini tutmaktadır. Dolayısıyla string'in bir karakterine eriştiğğimizde
Python o karakterden yeni bir str nesnesi yapıp onun adresini vermektedir.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir listenin bir elemanı başka bir liste olabilir. Örneğin:
a = [10, [20, 30, 40], 50]
2024-08-12 17:16:10 +03:00
Burada bu listenin 1'inci indisli elemanı başka bir list nesnesini göstermektedir. Buradaki organizasyonu şekilsel olarak
şöyle temsil edebiliriz:
a ------------> list_nesnesi
------------------> 10 (int)
------------------> list_nesnesi
-----------------> 20 (int)
-----------------> 30 (int)
-----------------> 40 (int)
------------------> 50 (int)
Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [10, [20, 30, 40], 50]
2024-08-12 17:16:10 +03:00
>>> a[0]
10
>>> a[1]
[20, 30, 40]
>>> a[2]
50
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
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]
>>> id(a[0])
140710430112472
>>> id(a[1])
2525878486784
>>> id(a[2])
140710430113752
>>> id(a[1][0])
140710430112792
>>> id(a[1][1])
140710430113112
>>> id(a[1][2])
140710430113432
a[i][k] ifadesinde iki [] operatörü vardır. Bu operatörler soldan sağa önceliklidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'da matrisel bir veri yapısı listenin içerisinde liste ile oluşturulmaktadır. Bunun başkaca bir yolu yoktur.
Örneğin biz 3x3'lük bir matris oluşturmak isteyelim:
a = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
2024-07-15 13:23:34 +03:00
Tabii matrisel bir listenin elemanları aynı uzunlukta listelerden oluşmak zorunda değildir. Örneğin:
2024-08-12 17:16:10 +03:00
a = [[10, 20, 30, 40], [50, 60], [70, 80, 90]]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
liste elemanlarına erişirken köşeli parantez içerisindeki indeks belirten 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. Örneğin 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:
2024-07-15 13:23:34 +03:00
a = [10, 20, 30, 40, 50]
2024-08-12 17:16:10 +03:00
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 de 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. Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> a[-1]
50
>>> a[-2]
40
>>> a[-3]
30
>>> a[-4]
20
>>> a[-5]
10
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Köşeli parantez içerisindeki negatif değer dizinin uzunluğundan 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:
2024-07-15 13:23:34 +03:00
a = [10, 20, 30, 40, 50]
2024-08-12 17:16:10 +03:00
Burada köşeli parantez içerisine yazabileceğimiz en küçü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. Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> a[-5]
10
>>> a[-6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> a[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
print(a[-10]) # exception oluşur! efektif indeks (yani gerçek indeks) -5
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Listenin elemanı liste olduğu durumda da negatif indeksler benzer biçimde kullanılabilir. Örneğin:
>>> a = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
>>> a[-2][-2]
50
>>> a[-2][2]
60
>>> a[-1][-1]
90
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-08-12 17:16:10 +03:00
bu durum daha okunabilir ve kısa yazımlara olanak sağlamaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Bir liste içindeki bir grup elemanı bir liste olarak elde edebiliriz. Buna Python'da "dilimleme (slicng)" denilmektedir.
Dilimleme işlemi köşeli parantezlerle yapılmaktadır. Dilimleme sentaksının genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
a[start:stop]
a[start:stop:step]
2024-08-12 17:16:10 +03:00
Görüldüğü gibi dilimlemede step kısmı hiç belirtilmeyebilir. Dilimleme ile "listenin start indeksli elemanı dahil olacak
biçimde ancakstop indeksli elemanı dahil olmayacak biçimde" listenin elemanlarından yeni bir liste oluştururuz. start,
stop ve step ifadeleri int türden olmak zorundadır. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> b = [2:7]
>>> b = a[2:7]
>>> b
[30, 40, 50, 60, 70]
>>> b = a[3:5]
>>> b
[40, 50]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
b = a[2:7]
print(b) # [30, 40, 50, 60, 70]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dilimleme işleminde start ya da stop değerleri liste uzunluğundan büyük olursa liste uzunluğuna çekilmektedir.. Yani
bu durum bir exception oluşturmamaktadır. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[3:100]
[40, 50, 60, 70, 80, 90, 100]
>>> a[8:11]
[90, 100]
>>> a[8:10]
[90, 100]
Dilimleme sırasında start ile stop aynı değerdeyse ya da start değri stop değerinden büyükse boş liste elde edilmektedir.
Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[7:8]
[80]
>>> a[7:7]
[]
>>> a[7:3]
[]
>>> a[70:3]
[]
>>> a[70:3]
[]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dilimlemede start ve stop ifadelerinde negatif indeksler kullanılabilir. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[-5:9]
[60, 70, 80, 90]
>>> a[-3:-1]
[80, 90]
>>> a[-1:10]
[100]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dilimlemede start ve stop değerleri negatif girildikten sonra efektif indeks hesaplandığında (yani bu değer len(a) ile
toplandığında) eğer sonuç negatif çıkıyorsa 0 olarak işleme sokulmaktadır. Bu durumda bir exeption oluşmamaktadır.
Örneğin biz start ya da stop için çok büyük negatif değerler kullanırsak aslında 0 kullanmış gibi oluruz:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[-100:3]
[10, 20, 30]
>>> a[-100:100]
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dilimlemede start değeri belirtilmezse start değerinin 0 olarak girildiği kabul edilmektedir. Eğer stop değeir belirtilmezse
stop değerinin de listenin uzunluğu biçiminde girildiği kabul edilmektedir. Örneğin a[:n] ifadesi ie a[0:n] ifadesi
eşdeğerdir. a[n:] ifadesi ile de a[n:len(a)] ifadesi eşdeğerdir. Başka bir deyişle dilimlemde start belirtilmezse bu
durum "baştan itibaren", stop belirtilmezse bu durum "geri kalan hepsi" anlamına gelmektedir. Tabii start da stop da
belirtilmeyebilir. Bu durumda listenin tüm elemanlardan liste oluşturulur. Yani a[:] ifadesi a[0:len(a)] eşdeğerdir.
Bir listeyi kopyalamak için bu yöntemi kullanabiliriz. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[:5]
[10, 20, 30, 40, 50]
>>> a[5:]
[60, 70, 80, 90, 100]
>>> a[-3:]
[80, 90, 100]
>>> a[: -1]
[10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[:]
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dilimlemede istenirse ikinci ':' atomunun sağına step değeri yerleştirilebilmektedir. step değeri atlama miktarını
belirtmektedir. step hiç belirtilmezse sanki 1 olarak belirtilmiş gibi kabul edilir. Tabii elde edilecek değerlere
hiçbir zaman stop dahil olmaz. Örneğin:
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Burada şöyle bir dilimleme yapmış olalım:
b = a[2:7:2]
Burada 2'indeksten itibaren (2'inci indeks dahil) 7'inci indekse kadar (7'indeks dahil değil) iki iki atlanarak
dilimleme yapılmaktadır:
10 20 30 40 50 60 70 80 90 100
0 1 2 3 4 5 6 7 8 9
Burada 2, 4 ve 6 indekslerindeki değerler elde edilecektir. Örneğin:
b = a[2:8:3]
Burada 2, 5 indekslerindeki değerler elde edilecektir.
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> b = a[2:7:2]
>>> b
[30, 50, 70]
>>> b = a[2:8:3]
>>> b
[30, 60]
Dilimlemede step değerinin kullanılması seyrektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
step değeri negatif de olabilir. Ancak bu durumda ilerleme yönü ters olur. İlerleme ters olduğu için de start indeksinin,
stop indeksinden daha büyük olması gerekir. Yine start indeksi dahil stop indeksi dahil değildir. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[7:2:-1]
[80, 70, 60, 50, 40]
Burada step değeri negatif olduğu için sağdan sola dilimleme yapılacaktır. Artık start indeks 7 (dahil) stop indeks 2
(dahil değil) biçimindedir. step negatif ise bu durumda start indeksinin boş bırakılması "listenin uzunluğı - 1" anlamına,
stop indeksinin boş bırakılması "efektif -1" anlamına (yani ilk eleman dahil olacak biçimde hepsi anlamına) gelmektedir.
Örneğin a[:2:-1] ifadesinde ilerleme yönü terstir. start indeksi belirtilmemiştir. 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, başka bir deyişle
stop indeks "-len(a) - 1" gibi ele alınmaktadır. İfadenin eşdeğeri a[4:-len(a)-1:-1] biçimindedir. Özetle step değeri
negatif ise start belirtilmediği durumda "son elemandan itibaren başa doğru", stop belirtilmediği durumda "ilk elemana
kadar (o da dahil)" dilimleme anlamı oluşmaktadır. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[:2:-1]
[100, 90, 80, 70, 60, 50, 40]
>>> a[4::-1]
[50, 40, 30, 20, 10]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Python'da bir listeyi tersyüz etmek için a[::-1] kalıbı sıkça kullanılmaktdır. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[::-1]
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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] = <dolaşılabilir nesne>
2024-08-12 17:16:10 +03:00
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.
(Ayrıca burada atama operatörü yerine Walrus operatörünü kullanamayız.) Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a[2:6] = [100, 200]
>>> a
[10, 20, 100, 200, 70, 80, 90, 100]
Burada önce a listesinin 2:6 elemanları silinip aşağıdaki durum elde edilmiştir:
[10, 20, 70, 80, 90, 100]
Sonra da 2'inci indeksten itibaren 100 ve 200 değerleri listeye insert edilmiştir. Böylece aşağıdaki gibi bir durum
oluşmuştur:
[10, 20, 100, 200, 70, 80, 90, 100]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
2024-08-12 17:16:10 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
15. Ders 11/08/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
String'lerin de dolaşılabilir nesneler olduğunu anımsayınız. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:6] = 'ankara'
>>> a
[10, 20, 'a', 'n', 'k', 'a', 'r', 'a', 70, 80, 90]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dolaşılabilir nesnede hiç eleman yoksa bu durum bir silme anlamına gelecektir. Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:6] = []
>>> a
[10, 20, 70, 80, 90]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
a[2:6] = []
print(a) # [10, 20, 70, 80, 90, 100]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Dilimleme yoluyla liste elemanları güncellenirken eğer step değeri belirtilirse bu durumda 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 dilimlemeden elde edilen eleman sayısı
ile aynı olması gerektiği dolaylı olarak belirtilmektedir. Ancak CPython gerçekleştirimi step değeri 1 olarak girildiğinde
sanki step değeri hiç belirtilmemiş gibi işlem yapmaktadır.) Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:8:2] = [1, 2, 3]
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:8:2] = [1, 2, 3, 4, 5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: attempt to assign sequence of size 5 to extended slice of size 3
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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]
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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]
2024-08-12 17:16:10 +03:00
Biz dilimleme yapmadan bir dolaşılabilir nesneyi belli bir indekse atadığımızda listenin o indeksine dolaşılabilir
nesnenin kendisi atanmaktadır. Halbuki dilimleme yoluyla atama yapıldığında dolaşılabilir nesnenin elemanları
dilimlenen elemanların yerlerine insert edilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Dilimleme yoluyla güncelleme yapılırken step değeri negatif ise yine dolaşılabilir nesnedeki eleman sayısı dilimlenen
2024-08-12 17:16:10 +03:00
eleman sayısı kadar olmak zorundadır. Ancak bu durumda elemanlar ters yönde insert edilecektir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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]
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Listeyi dilimlediğimizde biz yeni bir list nesnesi oluşturmuş oluruz. 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 yeni oluşturulan listenin elemanlarının dilimlenen listenin
dilimlenen elemanlarıyla aynı nesneleri göstermesi anlamına gelmektedir. Yani Sığ kopyalama 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. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> a = [10, 20, 30, 40, 50]
>>> b = a[2:4]
Burada b listesi e elemanlıdır. b listesinin 0'ınci indeksli elemanı aslında a listesinin 2'inci indeksli elemanı ile
aynı nesneyi, b listesinin 1'indeksli elemanı da a listesinin 3'üncü indekslei elemanı ile aynı nesyeyi gösterecektir.
Bu durumu aşağıdaki gibi ispatlayabiliriz:
2024-07-15 13:23:34 +03:00
>>> 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
2024-08-12 17:16:10 +03:00
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österidiği nesne ile de
b[1] elemanının gösteridiği nesnenin aynı nesneler olduğuna dikkat ediniz.
Eğer dilimleme işleminde asıl listenin elemanlarının gösterdiği yerdeki nesnelerin de kopyasından çıkarılıyor olsaydı
bu durumda "derien kopyalama (deep copy)" söz konusu olurdu. Ancak Python dilimlemesi sığ kopyalama yoluyla yapılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
a.foo()
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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(...)
2024-08-12 17:16:10 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
list sınıfının append isimli metodu çağrıldığı nesnesinin sonuna yeni bir eleman ekler. Örneğin:
2024-07-15 13:23:34 +03:00
a = [1, 2, 3, 4, 5] # [1, 2, 3, 4, 5]
print(a)
a.append('ankara') # [1, 2, 3, 4, 5, 'ankara']
print(a)
2024-08-12 17:16:10 +03:00
Örneğin:
>>> a = ['ali', 'veli', 'selami']
>>> a.append(100)
>>> a.append(200)
>>> a
['ali', 'veli', 'selami', 100, 200]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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. Örneğin:
>>> a.append([100, 200, 300])
>>> a
[10, 20, 30, 40, 50, [100, 200, 300]]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30]
print(a) # [10, 20, 30]
a.append([100, 200, 300])
print(a) # [10, 20, 30, [100, 200, 300]]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Bir listenin sonuna birden fazla eleman eklemek için extend metodu kullanılmaktadır. 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.Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> a.extend([100, 200, 300])
>>> a
[10, 20, 30, 40, 50, 100, 200, 300]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
str sınıfı dolaşılabilir bir sınıf olduğuna göre bir string'in karakterlerini extend metodu ile listeye ekleyebiliriz.
Örneğin:
>>> a = [10, 20, 30, 40, 50]
>>> a.extend('ankara')
>>> a
[10, 20, 30, 40, 50, 'a', 'n', 'k', 'a', 'r', 'a']
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30]
print(a) # [10, 20, 30]
a.extend('ankara')
print(a) # [10, 20, 30, 'a', 'n', 'k', 'a', 'r', 'a']
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Aşağıdaki örneğe dikkat ediniz:
>>> a = [10, 20, 30, 40, 50]
>>> a.extend([[100, 200, 300], [400, 500, 600]])
>>> a
[10, 20, 30, 40, 50, [100, 200, 300], [400, 500, 600]]
Buradaextend metoduna listeşler oluşan bir liste argüman olarak verilmiştir. Böyle bir durumda tabii ana nesne dolaşılmaktadır.
Örneğimizde bu nesne dolaşıldığında iki ayrı liste elde edilmektedir. Bu iki ayrı liste asıl listeye eklenmiştir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:
2024-07-15 13:23:34 +03:00
>>> a = [10, 2, 7, 8, 19, 41]
>>> result = a.index(19)
>>> result
4
>>> result = a.index(30)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 30 is not in list
2024-08-12 17:16:10 +03:00
İ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:
2024-07-15 13:23:34 +03:00
>>> 'ali' == 19
False
2024-08-12 17:16:10 +03:00
Dolayısıyla index metoduyla arama yapılırken liste içerisinde farklı türlerden nesneler varsa bunlar asla aradığımız değere
2024-07-15 13:23:34 +03:00
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
2024-08-12 17:16:10 +03:00
Eğer değer liste içerisinde birden fazla yerde varsa index metodu değerin listede ilk bulunduğu indeksi vermektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [1, 4, 7, 8, 4, 7]
>>> result = a.index(4)
>>> result
1
2024-08-12 17:16:10 +03:00
index metodu aslında iki argümanla ya da üç argümanla da çağrılabilir. Eğer metodu biz iki argümanla çağırıyorsak
birinci argüman aranacak değeri, ikinci argüman ise aramanın başlatılacağı indeksi belirtir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [10, 4, 5, 8, 9, 4, 8]
>>> result = a.index(4, 2)
>>> result
5
2024-08-12 17:16:10 +03:00
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 metodu
üç argümanla çağrılırsa üçüncü argüman aramanın bitirileceği indeksi belirtir. Ancak bu indeks aramaya dahil değildir.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [10, 4, 5, 8, 9, 4, 8]
>>> result = a.index(4, 2, 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 4 is not in list
2024-08-12 17:16:10 +03:00
Burada 4 değeri aranmak istenmiştir. Ancak arama 2'inci indeksten başlatılıp 5'inci indekse kadar devam ettirilmiştir.
Fakat 5'indeks aramaya dahil olmadığı için exception oluşmuştur. Tabii aramanın başlatılacağı ve bitirileceği indeks
negatif indeks olarak da belirtilebilir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-08-12 17:16:10 +03:00
>>> a = [10, 4, 5, 8, 9, 4, 8]
>>> result = a.index(8, -6, -1)
>>> result
3
>>> result = a.index(8, -6, 6)
>>> result
3
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [1, 3, 5, 'ali', 'veli', 7, 'veli']
result = a.index('veli')
print(result) # 4
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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ı olarak kullanılırsa belirtilen indeksteki eleman silinir. Yani metot argümanlı ya da argümansız
kullanılabilmektedir. Metot 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şur. index metodu negatif indeksleri kabul etmektedir. Bu durumda negatif indeks değerleri yine list uzunluğu ile
toplanıp efektif indeks elde edilmektedir. Yani örneğin a.pop(-2) işlemi son elemandan bir önceki elemanı siler. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
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
2024-08-12 17:16:10 +03:00
bizzat değeriyle belirtilmektedir. remove metodu parametresiyle belirtilen değeri liste içerisinde arar. Eğer onu 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.
2024-07-15 13:23:34 +03:00
Ö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 "<stdin>", line 1, in <module>
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) # []
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
list sınıfının 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" biçimde) yapılmaktadır. a bir liste belirtmek üzere a[::-1] ifadesi 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.
Bir işlemin "yerinde (in place)" yapılması metodun çağrıldığı nesne üzerinde işlemin uygulanması anlamına gelmektedir.
Örneğin:
>>> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a.reverse()
>>> a
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
Yukarıda da belirttiğimiz gibi 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
list sınıfının sort isimli metodu liste elemanlarını "in place" biçimde sıraya dizmektedir. Default durum küçükten
büyüğe sıraya dizmedir. sort metodu bize bir değer vermez. Bizzat elemanları nesne üzerinde sıraya dizer (in place işlem).
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-12 17:16:10 +03:00
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üyle karşılaştırılabilmektedir:
2024-07-15 13:23:34 +03:00
>>> 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!
#------------------------------------------------------------------------------------------------------------------------
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']
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
16. Ders 17/08/2024 - Cumartesi
2024-08-27 01:07:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
list sınıfının reverse metodunun yanı sıra ayrıca reversed isimli bir built-in 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şım nesnesi (iterator)
biçiminde verir. reversed fonksiyonu "in place" bir işlem yapmaz. reversed fonksiyonu bize bir liste değil bir folaşım
nesnesi vermektedir. Yani biz ters yüz edilmiş değerleri o dolaşım nesnesini dolaşarak elde edebiliriz. Dolaşın nesnelerinin
bir kez dolaşıldığında bittiğini, ancak dolaşım nesnelerinin dolaşılabilir nesneler gibi de kullanılabildiğini anımsayınız.
list fonksiyonu dolaşılabilir nesneyi alarak ondan bir liste oluşturabildiğine göre biz reversed fonksiyonundan elde
ettiğimiz dolaşım nesnesini list fonksiyonuna verirsek yine ters yüz edilmiş yeni bir liste elde edebiliriz:
2024-07-15 13:23:34 +03:00
>>> a = [1, 2, 3, 4, 5]
>>> result = reversed(a)
>>> type(result)
<class 'list_reverseiterator'>
>>> 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)
<class 'reversed'>
>>> 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']
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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, string'ler aynı zamanda tersten dolaşılabilir
biçimdedir. Bu nedenle biz reversed fonksiyonunu listeler ve string'lerle kullanabilmekteyiz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = list(reversed('ankara'))
print(s) # ['a', 'r', 'a', 'k', 'n', 'a']
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
2024-08-27 01:07:02 +03:00
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, yok ise False üretmektedir. Yani in operatörü bool bir değer üretmektedir.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [10, 4, 7, 'ali', 5]
>>> 5 in a
True
>>> 8 in a
False
>>> s = 'istanbul'
>>> 'i' in s
True
>>> 'k' in s
False
2024-08-27 01:07:02 +03:00
String'lerde in operatörü yazının ardışıl kısımlarını aramak için de kullanilebilmektedir. Örneğin:
>>> s = 'bugün hava çok sıcak'
>>> 'sıcak' in s
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Biz "bir liste içerisinde başka bir liste var mı" sorusu için de in operatörünü kullanabiliriz. in operatörü ==
karşılaştırması yapmaktadır. İki listenin bu biçimde karşılaştırılması ileride ele alınacaktır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [1, 2, [3, 4], 5, 6]
>>> [3, 4] in a
True
>>> [4, 3] in a
2024-08-27 01:07:02 +03:00
False
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
Burada "[4, 3] in a" ifadesinin False değer ürettiğine dikkat ediniz. Çünkü [4, 3] listesi [3, 4] listesine eşit
değildir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = [1, 2, 3, 4, 5]
>>> 5 in a
True
>>> 5 not in a
False
2024-08-27 01:07:02 +03:00
Tabii aslında in operatörü bool bir değer ürettiğine göre biz not in operatörü yerine mantıksal not operatörünü de
kullanabiliriz. Örneğin:
not 5 in a
ile aşağıdaki ifade eşdeğerdir
5 not in a
not in operatörünün not ve in operatörü değil ikisi birlikte başka bir operatör olduğuna dikkat ediniz. Bu tür
durumlarda not in operatörü daha iyi bir okunabilirlik sunmaktadır. in ve not in operatörleri öncelik tablosunda not
operatörünün yukarısında bulunmaktadır:
() soldan sağa
** sağdan sola
+ - sağdan sola
* / // % soldan sağa
+ - soldan sağa
< > <= >= == != soldan sağa
in not in soldan-sağa
not sağdan sola
and soldan sağa
or soldan sağa
= := , +=, -=, *=, ... sağdan sola
Bu durumda örneğin:
result = not 5 in a
İfadesinde işlem sıraları şöyle olacaktır:
İ1: 5 in a
İ2: not İ1
İ3: result = İ2
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
result = 30 not in a
print(result) # False
result = 15 not in a
print(result) # True
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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 seçilir. Örneğin del a[3] gibi. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [10, 20, 30, 40, 50]
>>> del a[2]
>>> a
[10, 20, 40, 50]
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = [10, 20, 30, 40, 50]
>>> del a[30]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
2024-08-27 01:07:02 +03:00
del deyiminde eleman erişim sentaksının kullanıldığına dikkat ediniz. Bu durumda örneğin listesnin son elemandan bir
önceki elemanını şöyle silebiliriz
>>> a = [10, 20, 30, 40, 50]
>>> del a[-2]
>>> a
[10, 20, 30, 50]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = [10, 20, 30, 40, 50, 60, 70]
>>> del a[1:6:2]
>>> a
[10, 30, 50, 70]
2024-08-27 01:07:02 +03:00
>>> a = ['ali', 3, 'veli', 45, 73, 'ali', 21, 16, 7]
>>> del a[:-2]
>>> a
[16, 7]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = [10, 20, 30, 40, 50, 60, 70]
>>> del a[2], a[3]
>>> a
[10, 20, 40, 60, 70]
2024-08-27 01:07:02 +03:00
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. Tabii bu tür silmeler biraz karıştırıcı olduğu için tercih edilmemektedir. Ancak aşağıdaki
gibi bir silme işlemi kafa karıştırıcı değildir:
del a[3], a[-1]
Burada 3'üncü indeksli eleman silinse de silinmese de a[-1] listenin son elemanı durumundadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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 ağı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.
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
İki list nesnesi toplanırken oluşturulan liste toplanan iki list nesnesinin tuttukları adreslerden oluştuğuna göre
list nesnesinin bir elemanı "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:
2024-07-15 13:23:34 +03:00
>>> 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
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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]
2024-08-27 01:07:02 +03:00
Burada belki de yorumlayıcı doğrudan [1, 2, 3, 4, 5] elemanlarına sahip bir liste oluşturup bu listenin adresini a'ya
atamıştır. Yani yorumlayıcı bu kodun adeta aşağıdaki gibi yazılmış olduğunu varsayabilir:
a = [1, 2, 3, 4, 5]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Anımsanacağı gibi a += b işlemi a = a + b işlemi aynı 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 elemanları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. Halbuki
a = a + b işleminde gerçekten a + b işlemi sonucunda yeni bir liste yaratılır ve bu yeni listenin adresi a'ya atanır.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Python dokümanlarına göre (Python Standard 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:
2024-07-15 13:23:34 +03:00
>>> 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']
2024-08-27 01:07:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
17. Ders 18/08/2024 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Bir liste int bir değerle çarpılabilir. (İki liste çarpılamaz, bölünemez ve çıkartılamaz. Ayrıca bir liste int değerle
toplanamaz ve int değere bölünemez.) Daha önceden de belirtitğimiz gibi 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 listelerdeki adreslerin kopyalandığını anımsayınız. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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]
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Yineleme iş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:
2024-07-15 13:23:34 +03:00
>>> a = [0] * 100
2024-08-27 01:07:02 +03:00
>>> 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2024-07-15 13:23:34 +03:00
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
[]
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Biz Python'da ne zaman bir köşeli parantez kullanarak liste oluştursak her zaman yeni bir liste yaratılmaktadır.
Örneğin:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> c = [1, 2, 3]
>>> id(a)
2557020292160
>>> id(b)
2557021639808
>>> id(c)
2557021642432
list sınıfı değiştirilebilir olduğu için yorumlayıcının tek bir list nesnesi yaratması biçiminde bir optimizasyon yapılmamaktadır.
Halbuki değiştirilemez türlerde yorumlayıcılar böylesi optimizasyonları yapabilmektedir. Örneğin:
>>> a = 10
>>> b = 10
>>> c = 10
>>> id(a)
140713427794648
>>> id(b)
140713427794648
>>> id(c)
140713427794648
Burada yorumlayıcı int türü değiştirilemez olduğu için 10 değerini tutan int nesneyi nesnesini gereksiz bir biçimde tekrar
tekrar yaratmamıştır. Ancak değiştirilebilir türler için böyle bir optimizasyon yapılamamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-08-27 01:07:02 +03:00
Bu örnekte b'nin elemanları oluşturulurken köşeli parantez kullanılmadığına dikkat ediniz. Çünkü her köşeli parantez
yeni bir list nesnesinin yaratılmasına yol açmaktadır. Aşağıdaki örneği inceleyiniz:
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
>>> a = [1, 2]
>>> b = [a, a, a]
>>> a
[1, 2]
2024-07-15 13:23:34 +03:00
>>> b
2024-08-27 01:07:02 +03:00
[[1, 2], [1, 2], [1, 2]]
>>> a[0] = 100
>>> b
[[100, 2], [100, 2], [100, 2]]
Burada b listesinin her elemanında a listesinin adresi vardır. Biz de a listesinin ilk elemanını değiştirdiğimizde sanki
b listesinin elemanları değişmiş gibi olmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki gibi boş bir listemiz olsun:
a = []
Şimdi biz aşağıdaki gibi bir liste oluşturalım:
b = [a, a, a]
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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]]
2024-08-27 01:07:02 +03:00
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.
2024-07-15 13:23:34 +03:00
>>> 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]]
2024-08-27 01:07:02 +03:00
Aşağıdaki örneğe dikkat ediniz:
b = [[], [], []]
Her köşeli parantez yeni bir list nesnesinin yaratılmasına yol açtığına göre burada b listesinin elemanları aslında
farklı listelerin adreslerini tutuyor durumdadır. Örneğin:
>>> id(b[0])
2557021639808
>>> id(b[1])
2557021642432
>>> id(b[2])
2557020292160
Bu durumda biz b[0].append(100) gibi bir işlem yaptığımızda bu 100 değeri yalnızca b listesinin ilk elemanında
görünecektir:
>>> b[0].append(100)
>>> b
[[100], [], []]
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki koda dikkat ediniz:
a = [[], [], []]
2024-08-27 01:07:02 +03:00
Burada her köşeli parantez yeni bir listenin yaratılmasına yol açacağı için a'nın elemanları farklı boş listeleri
gösterecektir. Ancak örneğin:
2024-07-15 13:23:34 +03:00
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]]
2024-08-27 01:07:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
Şimdi içerisinde 0 olan 10x10'luk bir matrisi pratik bir yolla oluşturmak isteyelim. Aşağıdaki gibi bir yööntem
istediğimizi tam olarak gerçekleştirmeyecektir:
>>> a = [[0, 0, 0, 0, 0, 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], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 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 burada istediğimiz şeyi yapmışız gibi bir durum oluşmuşsa da aslında burada a listesinin içerisinde
aynı listenin adresi vardır. Bu durumda bu listelerinin birinin üzerinde yapılan değişiklik diğerlerinin hepsinde
görülecektir. Örneğin:
>>> a[0][0] = 100
>>> a
[[100, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[100, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[100, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[100, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
Pekiyi a listesinin her elemanında farklı bir listenin olmasını sağlamanın pratik bir yolu var mıdır? İşte bu işlem
ancak bir döngü kullanarak ya da "liste içlemi (list comprehension)" kullanarak yapılabilir. Örneğin:
>>> a = []
>>> for _ in range(10):
... a.append([0, 0, 0, 0, 0, 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], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 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], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
Örneğin:
>>> a = [[0, 0, 0, 0, 0, 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], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 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], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
* 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]
2024-08-27 01:07:02 +03:00
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ğıdakiyle eşdeğerdir:
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
>>> 3 * 2 * a
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Demetler (tuples) listelere benzeyen önemli diğer bir veri yapısıdır. Bir demet tipik olarak normal parantezler kullanılarak
yaratılır. Örneğin:
2024-07-15 13:23:34 +03:00
t = (10, 20, 30)
2024-08-27 01:07:02 +03:00
Burada t nesnesi tuple isimli built-in sınıf türündendir. Python'da demetler tuple isimli sınıfla temsil edilmektedir.
("tuple" sözcüğü "tyupıl" gibi de "tapl" gibi de okunabilmektedir. Biz kursumuzda "tyupıl" biçiminde telaffuz edeceğiz.)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
t = (10, 20, 30)
print(t) # (10, 20, 30)
print(type(t)) # <class 'tuple'>
#------------------------------------------------------------------------------------------------------------------------
Bir demet tuple sınıfının tür fonksiyonu olan tuple fonksiyonu ile de yaratılabilir. tuple fonksiyonuna argüman girilmezse
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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)
2024-08-27 01:07:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
Demetler de dolaşılabilir nesnelerdir. Bir demet dolaşıldığında sırasıyla onun içerisindeki elemanlar elde edilmektedir.
Böylece biz örneğin bir demetten bir listeyi aşağıdaki gibi elde edebiliriz:
>>> t = (1, 2, 3, 4, 5)
>>> a = list(t)
>>> t
(1, 2, 3, 4, 5)
>>> a
[1, 2, 3, 4, 5]
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-08-27 01:07:02 +03:00
- Demetlere de len fonksiyonu uygulanabilir. Bu durumda demetteki eleman sayısı elde edilir.
2024-07-15 13:23:34 +03:00
- Demetler de dolaşılabilir (iterable) nesnelerdir.
2024-08-27 01:07:02 +03:00
Örneğin:
>>> t = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
>>> t
(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
>>> t[3]
40
>>> t[-2]
90
>>> t[2:5]
(30, 40, 50)
>>> t[:4]
(10, 20, 30, 40)
>>> len(t)
10
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
Boş bir demet içi boş bir parantezle oluşturulmaktadır. Örneğin:
>>> t = ()
>>> type(t)
<class 'tuple'>
>>> len(t)
0
2024-08-27 01:07:02 +03:00
Burada yorumlayıcı bu parantezleri öncelik parantezi olarak değerlendirmez. Çünkü () biçiminde bir öncelik parantezi
oluşturmak mümkün değildir. Ancak tek elemanlı demetleri oluştururken dikkat etmek gerekir. Çünkü (10) gibi bir sentaks
demet belirtmez. Buradaki parantezler öncelik parantezi anlamına gelir. Yani (10) ifadesi ile 10 ifadesi arasında bir
fark yoktur. (Biz her ifadeyi paranteze alabiliriz. İfadeyi paranteze aldık diye ona bir işlem uygulamak zorunda değiliz.)
Tek elemanlı demetleri oluştururken parantez içerisine ekstra bir ',' eklemek gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
t = (10, )
2024-08-27 01:07:02 +03:00
Aslında listelerde ve demetlerde de zaten son elemandan sonra bir virgül bırakılabilmektedir:
2024-07-15 13:23:34 +03:00
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 <class 'int'>
t = (10, )
print(t, type(t)) # (10,) <class 'tuple'>
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Listelerle demetler arasındaki en önemli farklılık (aslında tek farklılık, diğer farklılıklar bu farklılığın bir sonucudur)
2024-07-15 13:23:34 +03:00
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
2024-08-27 01:07:02 +03:00
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.)
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
tuple sınıfının da çeşitli metotları vardır. Ancak list sınıfında gördüğümüz 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.
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
Bu nedenle demetlerde listelerde bulunan aşağıdaki işlemler ve bu işlemleri gerçekleştiren metotlar yoktur:
2024-07-15 13:23:34 +03:00
- Sona eleman ekleme işlemi (append metodu) demetlerde yoktur.
2024-08-27 01:07:02 +03:00
- Sona dolaşılabilir nesnenin elemanlarını ekleme işlemi (extend metodu) demetlerde yoktur.
- Belli bir elemanın silinmesi işlemi (pop ve remove metotları) demetlerde yoktur.
- Tüm elemanların silinmesi işlemi (clear metodu) demetlerde yoktur.
- Araya eleman ekleme işlemi (insert işlemi) demetlerde yoktur.
- In-place sıraya dizme işlemi (sort metodu) demetlerde yoktur.
- In-place ters yüz etme işlemi (reverse metodu) demetlerde yoktur.
2024-07-15 13:23:34 +03:00
- 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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Bir grup bilgiden elde edilen onu temsil eden kısa bir bilgiye "hash" denilmektedir. Hash değerleri ç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 bilgi güvenliği ve şifreleme gibi bazı alanlarda da kullanılmaktadır.
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
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ürlerinin hash'lenebilir türlerdir. 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.
2024-07-15 13:23:34 +03:00
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:
2024-08-27 01:07:02 +03:00
>>> t = (1, 2, [3, 4], 5)
>>> hash(t)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
Çünkü bu demet bir liste elemanına sahiptir. Çünkü bir demetin hash'lenebilir olması için onun bütün elemanlarının
hash'lenebilir olması gerekir. Ancak bir liste hiçbir zaman hash'lenebilir değildir:
2024-07-15 13:23:34 +03:00
>>> t = (1, 2, [3, 4], 5)
>>> hash(t)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
2024-11-07 13:00:04 +03:00
Nesnelerden hash değerlerinin nasıl elde edildiği Python gerçekleştirimine bağlıdır. CPython tipik olarak bazı temel
türlerin hash değerlerini şöyle elde etmektedir:
- int bir nesnenin hash değeri ilgili int değerdir. Örneğin:
>>> a = 1234
>>> hash(a)
1234
- Eğer float bir nesnenin içerisinde noktalı kısmı 0 olan bir değer varsa, o nesnenin hash değeri o tamsayı değerdir.
Ancak float nesnenin içerisindeki değerin noktalı kısmı 0 değilse hash değeri farklı bir biçimde elde edilmektedir.
- bool bir nesnenin hash değeri eğer nesnede True değeri varsa 1, False değeri varsa 0 biçimindedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
2024-08-27 01:07:02 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
t = (1, [2, 3], 4)
print(t) # (1, [2, 3], 4)
t[1][1] = 100
print(t) # (1, [2, 100], 4)
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Aslında demetler oluşturulurken bazı zorunlu durumlar dışında parantezler kullanılmayabilir. Örneğin:
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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.
2024-07-15 13:23:34 +03:00
a = [10, (20, 30, 40), 50]
print(a)
t = 10, (30, 40, 50), 50
print(t)
2024-08-27 01:07:02 +03:00
Örneğin bir fonksiyona argüman olarak bir demet göndermek istiyorsak yine demet parantezini kullanmak zorundayız:
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
print(10, 20) # 10 20
print((10, 20)) # (10, 20)
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
t = ()
print(t)
t = tuple()
print(t)
Yani aşağıdaki gibi bir sentaks geçerli değildir:
t = ,
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Tek elemanlı bir demeti parantezsiz de oluşturabiliriz. Tabii bu durumda değerden sonra bir ',' atomu da gerekir.
Örneğin:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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. Örneğin:
>>> t = (1, 2, 3)
>>> k = t * 3
>>> k
(1, 2, 3, 1, 2, 3, 1, 2, 3)
Yineleme işleminde demet doğrudan yazılıyorsa bu durumda parantezler zorunludur. Örneğin:
t = 1, 2, 3 * 2
Burada (1, 2, 3) demeti 2 ile çarpılmamıştır. Elemanları (1, 2, 6) demet eld edilmiştir. Ancak örneğin:
t = (1, 2, 3) * 2
Burada demet 2 ile çarpılmaktadır. Dolayısıyla bu işlemden (1, 2, 3, 1, 2, 3) demeti elde edilecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = (10, 20, 30)
b = 3 * a # a * 3 de yazılabilirdi
print(b)
#------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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,
2024-08-27 01:07:02 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-08-27 01:07:02 +03:00
3) Demetler değiştirilebilir olmadığı için eğer onların elemanları hash'lenebilir (hashable) ise hash'lenebilir nesnelerdir.
Bu da onların sözlük gibi, küme gibi veri yapılarında kullanılabilmesine olanak sağlamaktadır. Halbuki listeler hiçbir durumda
hash'lenebilir değildir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Her ne kadar veri yapısı üzerinde değişlik yapmayacağımız durumda liste yerine demetlerin kullanılması daha uygunsa da
2024-08-27 01:07:02 +03:00
Python programcıları yine de bu tür durumlarda demet yerine listeleri 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 durumlar<da listeler yerine demetler hem etkinlik bakımından hem de
okunabilirlik bakımından daha uygun bir seçenektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
18. Ders - 07/09/2024-Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Dolaşılabilir bir nesnenin elemanları "açım (unpacking)" denilen bir sentaksla değişkenlere atanabilmektedir. Açım (unpacking)
2024-11-07 13:00:04 +03:00
işlemi Ruby, Python gibi dillerde eskiden beri vardı. Daha sonraları C# gibi, Swift gibi dillere ve hatta C++'a bile
eklendi. Açım işlemi eskiden yalnızca demetler üzerinde yapılabiliyordu. Ancak daha sonra dolaşılabilir her nesne açılabilir
hale getirildi. Dolayısıyla açım sentaksı da genelleştirildi. Açım için aşağıdaki üç sentaks tamamen eşdeğerdir yani aynı
anlama gelmektedir:
2024-07-15 13:23:34 +03:00
1) (x, y, z, ...) = <dolaşılabilir nesne>
2) x, y, z, ... = <dolaşılabilir nesne>
3) [x, y, z ...] = <dolaşılabilir nesne>
2024-11-07 13:00:04 +03:00
ım işleminde = operatörünün sağ tarafında "dolaşılabilir (iterable)" bir nesne bulunmak zorundadır. Açım sırasında bu
2024-07-15 13:23:34 +03:00
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'
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi açım işleminde sol taraftaki sentaksın bir önemi yoktur. Üç sentaks biçimi de tamamen
birbirleriyle eşdeğerdir. Tabii parantezsiz biçim daha az tuşa basım il oluşturulabildiği için tercih edilmektedir.
Örneğin:
x, y, z = [10, 20, 30]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
ı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ı
2024-11-07 13:00:04 +03:00
ile tamamen aynı olması gerekir. Aksi takdirde exception (ValueError) oluşmaktadır. Örneğin:
x, y = [10, 20, 30] # exception (ValueError)
x, y, z = [10, 20] # exception (ValueError)
x, y, z = 'ankara' # exception (ValueError)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
t = (10, 20, 30)
x, y = t # geçersiz! exception oluşur
x, y, z, k = t # geçersiz! exception oluşur
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
ılacak dolaşılabilir nesnedeki elemanlardan bazıları liste, demet gibi başka bir dolaşılabilir nesneler olabilir.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
ı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:
2024-07-15 13:23:34 +03:00
>>> 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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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. Örneğin:
a = [10, 20, 30]
x, y, z = a
Burada a[0] içerisindeki adres ile x içerisindeki adres aynıdır. Benzer biçimde a[1] içerisindeki adresle y içerisindeki
adres, a[2] içerisindeki adresle z içerisindeki adresler de aynıdır. Yukarıdaki unpack işleminin eşdeğerinin aşağıdaki
gibi olduğunu anımsayınız:
a = [10, 20, 30]
x = a[0]
y = a[1]
z = a[2]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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ı
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Python'da iki değişkenin değerinin yer değiştirilmesi (swap) açım (unpacking) işlemi ile kolay bir biçimde aşağıdaki gibi
yapılabilmektedir:
a, b = b, a
Burada atama operatörünün sağında bir demet vardır. Demetler dolaşılabilir nesnelerdir. Bu demetin ilk elemanı a değişkenine,
sonraki elemanı da b değişkenine atanacaktır. Bu da yer değiştirme anlamına gelmektedir. Python'da "pythonic biçimde"
yer değiştirmeyi (swap işlemini) bu biçimde yapabilirsiniz. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = 10
>>> b = 20
>>> a, b = b, a
>>> print(a, b)
20 10
2024-11-07 13:00:04 +03:00
Buradaki yer değiştirmede değerlerin bozulacağını sanabilirsiniz. Ancak işlemler aşağıdaki gibi yapılammaktadır:
a = b
b = a
Eğer işlemler böyle yapılıyro olsaydı iki değişkende de b'nin değeri olurdu. Biz eşitliğin sağ tarafonda b, a ifadesini
kullandığımızda asında bir demet yaratmış olmaktayız. Bu demetin ilk elemanı b'yi eikinci elemanı a göstermektedir.
Bundan sonra artık bu atamalar yapıldığında değerler bozulmayacaktır. Yani burada demetin kendisi zaten geçici nesne
işlevini görmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 10
b = 20
print(a, b) # 10 20
a, b = b, a
print(a, b) # 20 10
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
range isimli fonksiyon (aslında range bir sınıftır) bize range isimli bir sınıf türünden dolaşılabilir bir nesne 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:
2024-07-15 13:23:34 +03:00
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]
2024-11-07 13:00:04 +03:00
Yani tek argümanlı kullanımda 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
r = range(0, 1, 0.1) # exception (TypeError) oluşur
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Böyle range nesnesi yaratmak istersek step değeri float olduğu için exception (TypeError) oluşacaktır. Yuvarlama hataları
ile kararsız durumların oluşmaması için float türden 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:
2024-07-15 13:23:34 +03:00
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ı
2024-11-07 13:00:04 +03:00
b'den küçük olursa bu da dolaşılma dahil edilecektir. Bu durum da farklı uzunluklarda listelerin elde edilmesine yol
açabilecektir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Fakat örneğin yaygın kullanılan "NumPy" isimli kütüphanede arange isimli float parametreler alabilen 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:
2024-07-15 13:23:34 +03:00
>>> 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])
2024-11-07 13:00:04 +03:00
NumPy sayısal analiz, istatistik, matematik, veri bilimi ve makine öğrenmesinde çok yaygın kullanılan üçüncü parti bir
kütüphanedir. Python'un standart kütüphanesine dahil değildir. Aşağıdaki gibi install edilebilir:
2024-07-15 13:23:34 +03:00
pip install numpy
Anaconda dağıtımı default olarak NumPy kütüphanesini barındırmaktadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii range fonksiyonunun argümanları uygun biçimde girilmezse nesne dolaşıldığında hiç eleman elde edilmeyebilir.
2024-07-15 13:23:34 +03:00
Örneğin:
2024-11-07 13:00:04 +03:00
r = range(10, 5)
Burada start değeri 10, stop değeri 5 ve step değeri 1'dir. 10'dan 5'e 1 artırımlarla varılamaz. Dolayısıyla bu nesne
dolaşıldığında hiçbir değer elde edilemeyecektir. Örneğin:
>>> r = range(10, 5)
>>> a = list(r)
>>> a
[]
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:
2024-07-15 13:23:34 +03:00
>>> 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]
#------------------------------------------------------------------------------------------------------------------------
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 "<stdin>", line 1, in <module>
ValueError: range() arg 3 must not be zero
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ını
betimlemektedir. list ve tuple sınıfları da birer "sequence container" grubunundadır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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]
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
range nesnesinin belli bir elemanına [] operatörü ile erişebiliriz. Ancak range nesnesinin elemanları değiştirilememektedir.
2024-07-15 13:23:34 +03:00
Örneğin:
>>> r = range(100)
>>> r[23]
23
>>> r[23] = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Anımsanacağı gibi "dolaşılabilir (iterable)" nesneler her dolaşıldığında yeniden aynı değerleri veriyordu. Yani dolaşılabilir
nesneler bir kez dolaşıldığında bitmiyordu. Onları tekrar tekrar dolaşabiliyorduk. Oysa yine anımsanacağı gibi "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
nesnelerdi. Teknik olarak her dolaşım nesnesi dolaşılabilir bir nesne gibi de kullanılabilmekteydi. Özetle bir dolaşılabilir
nesne dolaşıldığında bitiyorsa o nesneye özel olarak "dolaşım (iterator)" nesnesi denilmektedir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
range nesnei "dolaşılabilir bir nesnedir, dolaşım nesnesi değildir". Dolayısıyla aynı range nesnesini her dolaştığımızda
2024-07-15 13:23:34 +03:00
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]
2024-11-07 13:00:04 +03:00
Oysa örneğin built-in reversed fonksiyonunun verdiği nesne bir dolaşım nesnesdir. Bu nesne bir kez dolaşıldığında bitmektedir.
Örneğin:
>>> a = [1, 2, 3, 4, 5]
>>> b = reversed(a)
>>> list(b)
[5, 4, 3, 2, 1]
>>> list(b)
[]
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
19. Ders - 08/09/2024-Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Bir küme "küme parantezleri içerisinde" eleman listesi girilerek oluşturulmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = {'ali', 'ankara', 100, 3.4}
>>> s
{'ali', 'ankara', 3.4, 100}
2024-11-07 13:00:04 +03:00
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
2024-07-15 13:23:34 +03:00
isimli bir sınıf türünden olduğunu görürüz. Örneğin:
>>> type(s)
<class 'set'>
#------------------------------------------------------------------------------------------------------------------------
s = {10, 'ali', 20, True, 30.2}
print(s, type(s)) # {True, 'ali', 20, 10, 30.2} <class 'set'>
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ının olmadığını anımsayınız.) 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 d bir önemi yoktur. Yani onun elemanları bizim girdiğimiz sırada yazdırılmak zorunda değildir.
2024-07-15 13:23:34 +03:00
Ö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 "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable
2024-11-07 13:00:04 +03:00
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. Bu nedenle
bir kümeyi yazdırırken elemanların bizim girdiğimiz sırada yazdırılması gerekmemektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümelerde [] operatörü kullanılamadığı için kümeler üzerinde dilimleme (slicing) işlemleri de yapılamamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'}
>>> s[2:5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümelerin eleman sayısı yine built-in len fonksiyonuyla elde edilebilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = {'ali', 'ankara', 'veli', 123}
>>> len(s)
4
#------------------------------------------------------------------------------------------------------------------------
s = {10, 'ali', 20, True, 30.2}
result = len(s)
print(result) # 5
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir kümeye zaten onda var olan bir elemanı eklemeye çalışmak exception oluşturmaz. Eleman ekleneceği zaman elaman zaten
varsa eklenmemektedir. Dolayısıyla aşağıdaki küme oluşturma işlemi tamamen geçerlidir:
2024-07-15 13:23:34 +03:00
a = {10, 20, 10, 30, 20}
2024-11-07 13:00:04 +03:00
Burada 10 elemanı ve 20 elemanı birden fazla kez kümenin içerisine yerleştirilmek istenmiştir. Bu durum bir exception
oluşturmaz. Ancak bu 10 ve 20'den küme içerisinde yalnızca bir tane bulunur. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = {1, 2, 1, 1, 3, 4, 1, 5, 2, 3}
>>> print(s)
{1, 2, 3, 4, 5}
2024-11-07 13:00:04 +03:00
Kümeler aynı elemanı tutamadığından dolayı "yinelenleri ortadan kaldırmak için" sıklıkla kullanılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
Sözlüklerle kümeler benzer sentakslarla oluşturulmaktadır. Ancak sözlüklere kümelerden daha fazla gereksinim duyulmaktadır.
2024-07-15 13:23:34 +03:00
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)
<class 'dict'>
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'set'>
>>> s
set()
>>> len(s)
0
#------------------------------------------------------------------------------------------------------------------------
a = {}
print(a, type(a)) # {} <class 'dict'>
s = set()
print(s, type(s)) # set() <class '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'}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümeler yinelenen elemanları atmak için iyi bir araç olabilmektedir.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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.
2024-11-07 13:00:04 +03:00
Genel olarak kümeler gibi veri yapıları arka planda "hash tabloları (hash tables)" ya da "ikili ağaç (binary trees)"
denilen algoritmik veri yapılarıyla gerçekleştirilmektedir. Örneğin CPython yorumlayıcısında kümeler "hash tabloları"
2024-07-15 13:23:34 +03:00
ile gerçekleştirilmiştir. Bu veri yapıları özellikle algoritmik aramalarda en çok tercih edilen veri yapılarındandır.
2024-11-07 13:00:04 +03:00
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. (Örneğin 1,000,000 elemandna oluşan bir listede bir elemanın olup olmadığını belirlemek için ortalama
500,000 karşılaştırmanın yapılması gerekir.)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = {'ankara', 10, 20.5, False}
result = 10 in s
print(result) # True
result = 10 not in s
print(result) # False
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
set nesnesinden hash tablosu oluşturabilmek için set elemanlarının "hashlenebilir (hashable)" olması gerekmektedir.
Temel türlerin hash'lenabilir olduğunu, listelerin hash'lenebilir olmadığını anımsayınız. Yine anımsayacağınız gibi
eğer bir demetin tüm elemanları hash'lenebilir ise demet de hashle'nebilir bir nesnedir. Bu durumda bir liste bir kümenin
elemanı yapılamamaktadır. Ayrıca 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 eklemeye çalışırsak bu durumda exception (TypeError)
oluşur. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m = {'ali', 12, {'veli', 'selami'}}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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ı
2024-11-07 13:00:04 +03:00
hash'lenebilir ise" hash'lenebilir. olmaktadır. Yani örneğin bir demetin elemanı bir liste ise artık o demet hash'lenebilir
2024-07-15 13:23:34 +03:00
olmaz:
>>> s = {1, (2, 3, [4, 5]), 6}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir elemanın bir kğmede olup olmadığına == karşılaştırmasıyla karar verilmektedir. Aşağıdaki eşitlik karşılaştırmalarının
geçerli ve True değeri verdiğine dikkat ediniz:
>>> 1 == True
True
>>> 1.0 == 1
True
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Dolayısıyla bool bir değer bir küme elemanı yapılırken eşitlik karşılaştırması yapılacağı için dikkatli olmak gerekir.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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'}
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümeler değiştirilebilir (mutable) türlerdir. Yani biz bir set nesnesi içerisindeki değeri kümeden çıkartabiliriz, yeni
bir değeri kümeye ekleyebiliriz.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
anlamında listeler için daha uygun olabilir. Ancak kümelerde "sona ekleme" diye bir kavram olmadığı için "append" ismi
yerine "add" ismi tercih edilmiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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
ekleme yapmaz ekleme yapar.) update metodu "dolaşılabilir bir nesneyi" parametre olarak alır. Nesneyi dolaşarak elde edilen
değerleri kümeye ekler. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
>>> s = {10, 20, 30}
>>> s.add(20)
>>> s
{10, 20, 30}
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> s = {'ali', 10, 'veli', 20}
>>> s.remove(10)
>>> s
{'veli', 20, 'ali'}
>>> s.remove('sacit')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
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()
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümelerde eleman silme işlemi listelere göre çok daha hızlı yapılmaktadır. Listelerde bir eleman silme işleminde listenin
elemanlarının kaydırılması (shrink edilmesi) 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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'}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
İ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:
2024-07-15 13:23:34 +03:00
c = a.intersection(b)
işlemi ile
c = a & b
2024-11-07 13:00:04 +03:00
işlemi aynı sonucu verecektir. Ancak 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. intersectioın metodunda parametre olarak verilen dolaşılabilir nesne dolaşılıp elde
edilen değerler sanki bir kümeymiş gibi kesişim işlemine sokulmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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'}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
word1 = input('Bir sözcük giriniz:')
word2 = input('Bir sözcük daha giriniz:')
s = set(word1)
result = s.intersection(word2)
print(result)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aynı örnek aşağıdaki gibi de yapılabilirdi.
#------------------------------------------------------------------------------------------------------------------------
word1 = input('Bir sözcük giriniz:')
word2 = input('Bir sözcük daha giriniz:')
result = set(word1) & set(word2)
print(result)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
İ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:
2024-07-15 13:23:34 +03:00
>>> 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'}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
İ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 olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'}
>>> k = {'ali', 'sacit', 'fatma', 'hüseyin', 'bora'}
>>> result = s - k
2024-11-07 13:00:04 +03:00
>>> result
{'selami', 'ayşe', 'veli'}
2024-07-15 13:23:34 +03:00
>>> 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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
20. Ders - 14/09/2024-Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
İ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.
2024-07-15 13:23:34 +03:00
Ö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)
2024-11-07 13:00:04 +03:00
Burada yine operatör versiyonlarının sağ tarafındaki operand set ya da frozenset türünden olmak zorundadır. Metot biçimlerinin
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.
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
Burada aslında a |= b işlemi ile a.update(b) işlemi aynıdır. Bu nedenle union_update isimli bir metot yoktur. Bu işlem
zaten update metoduyla yapılmaktadır.
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Bir kümenin diğer bir kümenin alt kümesi olup olmadığı "<=" operatörü ile ya da issubset metodu 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. Ü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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Öz alt küme ve öz üst küme işlemleri için metotlar yoktur. Bu işlemler yalnzıca '<' operatörü ve '>' operatörü ile
yapılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = {'ali', 'veli', 'selami', 'ayşe', 'fatma'}
>>> 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.
2024-11-07 13:00:04 +03:00
Nasıl tuple sınıfı list sınıfının değiştirilemez biçimi gibiyse frozenset sınıfı da set sınıfının değiştirilemez biçimi
gibidir. set sınıfı ile frozenset sınıfı arasındaki farklılıklar şunlardır:
2024-07-15 13:23:34 +03:00
1) frozenset sınıfı değiştirilebilir olmadığı için onun add gibi, update gibi, intersection_update gibi, remove gibi
2024-11-07 13:00:04 +03:00
metotları yoktur. Ancak değiştirme işlemi yapmayan metotları set sınıfi ile aynıdır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
3) frozenset elemanları eğer hash'lenebilir ise frozenset nesnesi de hash'lenebilir durumdadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir frozenset nesnesi küme parantezleriyle yaratılamaz. Ancak frozenset fonksiyonu ile yaratılabilir. Bu fonksiyon
diğer built-in veri yapılarında olduğu herhangi bir dolaşılabilir nesneyi parametre olarak alabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
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):
2024-07-15 13:23:34 +03:00
set & frozenset => set
frozenset & set => frozenset
frozenset & frozenset => frozenset
Örneğin:
>>> fs = frozenset(['ali', 'veli', 'selami', 'ayşe', 'fatma'])
>>> s = {'ali', 'sacit', 'veli'}
>>> 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}) <class 'frozenset'>
result = s & fs
print(result, type(result)) # {1} <class 'set'>
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi frozenset sınıfında &=, |=, ^=, -= gibi işlemlerde bir update yapılmamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> fs = frozenset(['ali', 'veli', 'selami'])
>>> id(fs)
1525529415904
>>> s = {'ali', 'ayşe', 'veli'}
>>> 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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümenin bir elemanı küme olamaz. Çünkü kümeler hash'lenebilir değildir. Ancak kümenin bir elemanı frozenset olabilir.
2024-07-15 13:23:34 +03:00
Çünkü frozenset hash'lenebilir biçimdedir. Örneğin:
>>> s = {1, 2, {3, 4}, 5}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
#------------------------------------------------------------------------------------------------------------------------
s = {10, frozenset((20, 30)), 40}
print(s) # {40, 10, frozenset({20, 30})}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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öntemler kullanılarak
gerçekleştirilmektedir. Programlama dillerinde sözlükler genellikle "hash tabloları (hash tables)", "dengelenmiş ikili
ağaçlar (balanced binary tree)" ve "sıralı diziler (sorted arrays)" biçiminde gerçekleştirilebilmektedir. CPython
gerçekleştiriminde sözlükler tıpkı kümelerde olduğu gibi "hash tabloları" yoluyla gerçekleştirilmiştir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Sözlükler yine küme parantezleri kullanılarak yaratılırlar. Ancak küme parantezlerinin içerisi "anahtar: değer"
çiftlerinden oluşturulmaktadır. Sözlük yaratma işleminin genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
{ anahtar: değer, anahtar: değer, anahtar: değer, ...}
2024-11-07 13:00:04 +03:00
Burada sözlüklerin küme partantezleri içerisinde anahtardan sonra ':' ile değer belirtilerek yaratıldığını görüyorsunuz.
2024-07-15 13:23:34 +03:00
Ö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)) # <class 'dict'>
#------------------------------------------------------------------------------------------------------------------------
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)
<class 'dict'>
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'}
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 list ve set türlerinden olamaz. Demetlerin ve frozenset nesnelerinin
eğer elemanları hash'lenebilir ise hash'lenebilir olduğunu anımsayınız. Örneğin:
2024-07-15 13:23:34 +03:00
>>> d = {'eskişehir': ['mihalıççık', 'sivrihisar', 'seyitgazi'], 'istanbul': ['şişli', 'pendik', 'ataşehir'],
'izmir': ['konak', 'gaziemir', 'karşıyaka']}
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Aşağıdaki örnekte sözlüğün anahtarının bir liste yapılamadığına dikkat ediniz.
2024-07-15 13:23:34 +03:00
>>> d = {[10, 20]: 100}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
2024-11-07 13:00:04 +03:00
Görüldüğü gibi biz sözlüğün anahtarını hash'lenebilir olmayan bir türden yapmak istersek eception (TypeError) oluşmaktadır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Demetler hash'lenebilir olabildiği için sözlük anahtarı yapılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> d = {(10, 20): 100}
>>> d
{(10, 20): 100}
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 hep aynı türden olması gerekmemektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
Bazen bir anahtar verildiğinde birden fazla değerin elde edilmesi istenebilir. Bu durumda değer bir liste ya da demet nesnesi
olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
d = {'eskişehir': ['alpu', 'mihalıççık', 'seyitgazi'], 'ankara': ['keçiören', 'polatlı', 'çankaya'],
'izmir': ['konak', 'buca', 'karşıyaka']}
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {'eskişehir': ['alpu', 'mihalıççık', 'seyitgazi'], 'ankara': ['keçiören', 'polatlı', 'çankaya'],
'izmir': ['konak', 'buca', 'karşıyaka']}
print(d)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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
sözlüğün değeri bir sözlük olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
>>> d = {'içanadolu': {'eskişehir': 26, 'konya': 42, 'ankara': 6}, 'ege': {'izmir': 35, 'aydın': 9},
'marmara': {'istanbul': 34, 'kocaeli': 41}}
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Listelerin, demetlerin, kümelerin ve sözlüklerin elemanları sabit olmak zorunda değildir. Elemanlar değişken olarak da
verilebilir. 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:
2024-07-15 13:23:34 +03:00
a = [10, 20, 30]
2024-11-07 13:00:04 +03:00
Böyle bir liste nesnesi yaratıldığında listenin elemanları 10, 20 ve 30 değerlerinin tutulduğu int türden nesnelerin adreslerini
2024-07-15 13:23:34 +03:00
tutmaktadır. Bu işlemin aşağıdakinden bir farkı yktur:
x = 10
y = 20
z = 30
a = [x, y, z]
2024-11-07 13:00:04 +03:00
Burada da liste elemanlarında yine 10, 20, 30 değerlerinin bulunduğu int türden nesnelerin adresleri tutulmaktadır. Yani
burada listenin ilk elemanında x değişkenin içerisindeki adres, sonraki elemanında y değişkeninin içerisindeki adres ve
sonraki elemanında da z değişkeninin içerisindeki adres bulunur. Örneğin:
2024-07-15 13:23:34 +03:00
x = 26
d = {x: 'eskişehir'}
2024-11-07 13:00:04 +03:00
Burada aslında anahtar x değişkenin içerisindeki adresteki nesnedir. Bunun aşağıdakinden bir farkı yoktur:
d = {26: 'eskişehir'}
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
deyim statüsündedir. Dolayısıyla aşağıdaki gibş bir işlem geçerli değildir:
2024-07-15 13:23:34 +03:00
>>> a = [x = 10 + 20, 20]
File "<stdin>", line 1
a = [x = 10 + 20, 20]
^^^^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
2024-11-07 13:00:04 +03:00
Tabii walrus opeartörü gerçekten atama işlemi yapıp değer üreten bir operatördür. Dolayısıyla aşağıdaki gibi bir işlem geçerlidir:
2024-07-15 13:23:34 +03:00
>>> a = [x := 10, 20]
>>> a
[10, 20]
>>> x
10
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yukarıda da belirtildiği gibi sözlükler dict isimli sınıfla temsil edilmiştir. Tıpkı diğer temel veri yapılarına ilişkin
sınıflarda olduğu gibi dict fonksiyonu ile de sözlük nesnelerini yaratabiliriz. Örneğin boş bir sözlük dict() biçiminde
yaratılabilir:
2024-07-15 13:23:34 +03:00
>>> d = dict()
>>> d
{}
Boş küme parantezleri de boş sözlük yaratmak için kullanılabilmektedir. Örneğin:
>>> d = {}
>>> d
{}
2024-11-07 13:00:04 +03:00
Boş küme parantezlerinin boş bir küme yaratmadığına boş bir sözlük yarattığına dikkat ediniz. Boş bir küme yaratmak için
2024-07-15 13:23:34 +03:00
mecburen set() çağrısı kullanılmaktadır:
>>> s = set()
>>> s
set()
#------------------------------------------------------------------------------------------------------------------------
d = dict()
print(d) # {}
d = {}
print(d) # {}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
dict fonksiyonuna biz iki elemanlı dolaşılabilir nesnelerden oluşan dolaşılabilir bir nesneyi argüman olarak verirsek,
2024-07-15 13:23:34 +03:00
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)]
>>> d = dict(a)
>>> d
{'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
2024-11-07 13:00:04 +03:00
Burada a nesnesi dolaşıldığında iki elemanlı dolaşılabilir nesneler elde edilecektir. İşte dict fonksiyonu bunlardan
sözlük yapmaktadır. Bu örnekte listenin elemanları birer demettir. Ancak önemli olan elemanların iki elemanlı dolaşılabilir
nesnelerden oluşmasıdır
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
>>> a = (['ali', 10], ['veli', 20], ('selami', 30), ['ayşe', 40], ('fatma', 50))
2024-07-15 13:23:34 +03:00
>>> 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:
2024-11-07 13:00:04 +03:00
>>> a = (['ali', 10], ['veli', 20], ['selami', 30], ('ayşe', 40), ['fatma', 50], 'ak', 'tk', 'xy', range(2))
2024-07-15 13:23:34 +03:00
>>> d = dict(a)
>>> d
2024-11-07 13:00:04 +03:00
{'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50, 'a': 'k', 't': 'k', 'x': 'y', 0: 1}
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> d = dict([('ali', 10), ('veli', 20, 30)])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
dict fonksiyonunda "değişken=değer" biçiminde argümanlar girdiğimizde dict fonksiyonu bize o değişkenlerin string halini
anahtar, '=' operatörünün sağındakileri de değer yaparak bir sözlük nesnesi oluşturur. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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}
2024-11-07 13:00:04 +03:00
Bu biçimde sözlük nesnesi oluşturma işlemi seyrek olarak kullanılmaktadır. Burada değişken ismi yerine başka bir şey
getirilemez. Örneğin aşağıdaki geçerli değildir:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
d = dict('ali'= 10, 'veli'=20, 'selami'=30) # geçersiz!
d = dict(10='ali', 20='veli', 30='selami') # geçersiz!
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir sözlük nesnesi başka bir sözlük nesnesinden hareketle de oluşturulabilmektedir. Bu bir çeşit kopyalama anlamına
gelir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
>>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
>>> k = dict(d)
>>> d
{'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
>>> k
{'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
Örneğin:
>>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
>>> val = d['ayşe']
>>> val
2024-11-07 13:00:04 +03:00
40
2024-07-15 13:23:34 +03:00
>>> val = d['fatma']
>>> val
50
>>> val = d['sacit']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi sözlüklerde anahtarların ve değerlerin türleri hep aynı olmak zorunda değildir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
dict sınıfının get isimli metodu da anahtar verildiğinde değerin elde edilmesi için kullanılır. Ancak get metodu 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 sözlükte bulunamazsa None değeri ile geri dönmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
Sözlüklerde anahtar verildiğinde değer elde edilmektedir, değer veilerek anahtar elde edilemez.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
varlığını sorgulamaktadır, değerlerini değil. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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.
2024-11-07 13:00:04 +03:00
Ancak bu anahtarların elde edilmesi Python 3.6'ya kadar herhangi bir sırada olabiliyordu. Fakat 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ürümlerinde
2024-07-15 13:23:34 +03:00
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)
>>> 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']
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Sözlükler değiştirilebilir (mutable) 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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ali': 40, 'selami': 50}
>>> d
{'ali': 40, 'veli': 20, 'selami': 50}
#------------------------------------------------------------------------------------------------------------------------
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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Sözlüğe yeni bir anahtar değer çifti eklemenin en basit yolu aşağıdaki gibi köşeli parantezli atama yapmaktır:
2024-07-15 13:23:34 +03:00
d[key] = value
2024-11-07 13:00:04 +03:00
Ancak burada eğer anahtar zaten sözlükte varsa anahtarın değeri değiştirilir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir sözlüğe tek hamlede birden fazla anahtar-değer çifti eklemek için update metodu kullanılmaktadır. update metoduna
argüman tıpkı dict fonksiyonunda olduğu gibi girilmelidir. Yani metoda dolaşılabilir bir nesne verilmelidir. Bu dolaılabilir
nesne dolaşıldığında iki elemanlı dolaşılabilir nesneler elde edilmelidir. Bu iki elemanlı dolaşılabilir nesnelerin ilk
elemanı anahtar ikinci elemanı değer olacak biçimde ekleme yapılır. Tabii zaten anahtar sözlükte bulunuyorsa onun yine
onların değerleri değiştirilecektir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
2024-11-07 13:00:04 +03:00
>>> d.update([('sacit', 100), ('mehmet', 200), ('sibel', 300)])
2024-07-15 13:23:34 +03:00
>>> 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}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi bir sözlük nesnesi dolaşıldığında anahtarlar her zaman Python'un 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:
2024-07-15 13:23:34 +03:00
>>> 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']
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 metodu iki argümanla da çağrılabilir.
Bu durumda anahtar bulunamzsa ikinci argümandaki değer geri döndürülmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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]
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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']
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
dict sınıfının keys ve values metotlarıyla elde nesneler "dolaşım (iterator)" nesneleri değil "dolaşılabilir (iterable)"
nesnelerdir. Yani bunlar bir kere dolaşıldığında bitmezler, tekrar tekrar dolaşılabilirler. Örneğin:
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.keys()
a = list(result)
print(a) # [10, 20, 30, 40, 50]
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'da çeşitli konularda karşımıza "view" nesnesi kavramı çıkabilmektedir. View nesnesi ana bir nesnenin bir bölümünü
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
keys ve values metotlarının bize verdiği dolaşılabilir nesneler aynı zamanda "view" nesneleridir. Yani biz bu metotlarla
dolaşılabilir nesneleri elde ettikten sonra ana nesne üzerinde ekleme, silme gibi işlemleri yaptığımızda daha önce elde
ettiğimiz view nesnesi son duruma ilşkin bir dolaşım sağlar. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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']
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
21. Ders 21/09/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
>>> 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'
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
Burada copy metodu ile çıkartılan kopya farklı bir sözlük nesnesidir. Ancak iki sözlük nesnesindeki değerlerin
aynı olduğu görülmektedir. Aynı durum anahtarlar için de geçerlidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'}
k = d.copy()
print(d, id(d))
print(k, id(k))
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ğerle sözlüğe ekler eklediği anahtarın değerini
bize verir. Yani setdefault anahtarı bulamazsa ikinci parametresiyle belirtilen değerle anahtarı eklemekte ve 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. Tabii bu durumda setdefault metodu da None değerler
geri dönecektir. setdefault anahtarı sözlükte bulursa ikinci argümanı hiç kullanmaz. Bu ikinci argüman "eğer anahtar
bulamazsa" kullanılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
çiftini silebiliriz. Bunu sağlamak için del deyiminde anahtar yine köşeli parantez içerisinde veribelirtilir. Zaten del
deyimi her zaman köşeli parantezle sanki elemana erişirmiş gibi kullanılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
del d[10]
2024-11-07 13:00:04 +03:00
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 pop metodunda olduğu gibi exception (KeyError) oluşturmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
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'}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tıpkı listeler gibi, demetler gibi string'ler de Python'da "sequence type" grubundadır. Yani adeta string'ler onları
oluşturan karakterden oluşan bir dizilim gibi düşünülebilir.
2024-07-15 13:23:34 +03:00
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)
<class 'str'>
2024-11-07 13:00:04 +03:00
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 (yani str nesnesi olarak) elde ederiz.
2024-07-15 13:23:34 +03:00
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 "<stdin>", line 1, in <module>
2024-11-07 13:00:04 +03:00
TypeError: 'str' object does not support item assignment
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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'
2024-11-07 13:00:04 +03:00
String'lerde dilimleme de tamamen listelerde ve demetlerde olduğu gibi yapılabilmektedir. Tabii bir string dilimlendiğinde
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
İ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:
2024-07-15 13:23:34 +03:00
>>> s = 'ankara'
>>> k = 'izmir'
>>> result = s + k
>>> result
'ankaraizmir'
2024-11-07 13:00:04 +03:00
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 böyle 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çıkça diğer operand'ı str türüne dönüştürmemiz gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = 10
>>> s = 'a = ' + a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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))
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Ş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 değiştirilmiş yeni bir yazı verirler. Zaten yaratılmış bir
string nesnesi üzerinde değişiklik yapmanın bir yolu yoktur. İzleyen paragraflarda sanki string nesnesi üzerinde değişiklik
yapılıyormuş gibi anlatımlar görürseniz şaşırmayınız. Biz konunun kolay anlatılması için bazen cümleleri sanki böyle
bir anlam ifade ediliyormuş gibi kullanabileceğiz. Bu tür anlatımlarda söylemek istediğimiz şey aslında ilgili metotların
değiştirilmiş yeni yazı ile geri döndüğüdür.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
>>> s = 'bu bir denemedir'
2024-07-15 13:23:34 +03:00
>>> k = s.capitalize()
>>> k
2024-11-07 13:00:04 +03:00
'Bu bir denemedir'
2024-07-15 13:23:34 +03:00
>>> s
2024-11-07 13:00:04 +03:00
'bu bir denemedir'
>>> s = '123 üç basanmaklı bir sayıdır'
>>> k = s.capitalize()
>>> k
'123 üç basanmaklı bir sayıdır'
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
str sınıfının title isimli metodu yazının tüm sözcüklerinin ilk harfi eğer küçük harfse büyük harf yapar (yani büyük
harf yapılmış yazıyla geri döner.) Bu metot da parametresizdir. Tabii bir sözcük küçük harf değilse ona dokunmaz.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 ortalanmış yazı bu uzunlukta olacaktı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 gelir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 + ':')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
str sınıfının find metodu bir yazı içerisinde bir yazıyı aramak ve yerini bulmak için kullanı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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
find metodu iki argümanlı da çağrılabilir. Bu durumda arama ikinci argümanla belirtilen indeksten başlatılır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = 'bugün hava çok güzel, evet evet hava çok güzel'
index = s.find('hava', 10)
print(index) # 32
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
find metodu üç argümanla da çağrılabilir. Bu durumda üçüncü argüman aramanın bitirileceği indeksi belirtir. Ancak bu
indeks aramaya dahil değildir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = 'bugün hava çok güzel, evet evet hava çok güzel'
index = s.find('z', 10, 20)
print(index) # 17
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 dururmda ikinci ve üçüncü argümanlar sanki aranacak kısmın başını ve sonunu belirtiyor
gibidir. (Bunu sanki önce dilimleme yapılıp bu dilimlemenin içerisinde rfind uygulanıyor gibi de düşünebilirsiniz.)
Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = 'adıyaman'
>>> pos = s.rfind('a', 1, 6)
>>> pos
4
>>> pos = s.rfind('a', 1)
>>> pos
6
>>> pos = s.rfind('a')
>>> pos
6
2024-11-07 13:00:04 +03:00
Aşağıdaki örnekte Windows'tai bir yol ifadesinin hedefindeki dosya ismi elde edilmiştir. Bu örnekte eğer yol ifadesinde
'\' karakteri yoksa rfind -1 ile geri döndüğünden tüm yol ifadesinin elde edileceğine dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
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 -1 ile geri dönmek yerine exception (ValueEroor) oluşturmalarıdır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = 'ankara'
>>> pos = s.index('a')
>>> pos
0
>>> pos = s.rindex('a')
>>> pos
5
>>> pos = s.rindex('x')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
2024-11-07 13:00:04 +03:00
Yine count metodu da iki tek arümanla, iki argümanla ya da üç argümanla kullanılabilmektedir. İkinci ve üçüncü
argümanlar yine aramanın yapılacağı başlanıç ve bitiş indekslerini belirtmektedir. Bitiş indeksi dahil değildir.
Örneğin:
>>> s = 'ankara'
>>> result = s.count('a', 2, 5)
>>> result
1
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = 'istanbul\'da iş buldum'
result = s.count('bul')
print(result) # 2
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Biz string nesnesi ile bir metot çağırmak için önce onu bir değişkene atamak zorunda değiliz. Doğrudan tırnaklarla
oluşturulmuş string nesneleri ile metotlar çağrılabilir. Örneğin:
>>> 'bugün hava çok güzel'.islower()
True
>>> 'bugün hava çok güzel'.count('a')
2
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
"""
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ı ayıraç 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 etmektedir. Yani bir
yazıyı tüm boşluklardan ayrıştırmak için split parametresiz kullanılabilir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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']
2024-11-07 13:00:04 +03:00
>>> s = ' ali veli \n\n\t \t selami \t '
2024-07-15 13:23:34 +03:00
>>> result = s.split()
>>> result
['ali', 'veli', 'selami']
>>>
2024-11-07 13:00:04 +03:00
join ile split metotlarının adeta ters işlemler yaptıklarına dikkat ediniz:
2024-07-15 13:23:34 +03:00
>>> 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']
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 (white
space) atar. Bu işlev pek çok programama dilinde "trim" isimli metotlarla yapılmaktadır. strip metodu parametreli kullanılırsa
strip edilecek karakteri de belirlememize olanak sağlar. Burada argüman birden fazla karakter biçiminde girilirse tüm bu
karakterler bireysel olarak strip karakterleri olarak ele alınmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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:
2024-11-07 13:00:04 +03:00
strip metodunu parametresiz çağırmakla boşluk karakteri parametresiyle çağırmak arasındaki farka dikkat ediniz:
>>> ' \t\t ankara \t\t '.strip()
'ankara'
>>> ' \t\t ankara \t\t '.strip(' ')
'\t\t ankara \t\t'
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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:
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kişinin adını soyadını okumak isteyelim. Bu ad ve soyadı veritabanına yazacak olalım. Burada yanlışlıkla girilmiş
baştaki ve sondaki boşluk karakterlerinin atılması uygundur. Pekiyi ya kullanıcı ad ile soyad arasında birden fazla
boşluk karakteri bırakmışsa onu tek boşluk karakterli hale nasıl getirebiliriz. Örneğin:
s = ' Kaan Aslan '
Biz burada 'Kaan Aslan' yazısını elde etmek isteyelim. Bunu nasıl yapabiliriz? Burada strip metodu aradaki boşluk
karakterlerini atamayacaktır. O halde en pratik jöntem önce boşluklardan split yapmak ve sonra tek boşlukla join
yapmak olabilir. Örneğin:
>>> s = ' Kaan Aslan '
>>> ' '.join(s.split())
'Kaan Aslan'
#------------------------------------------------------------------------------------------------------------------------
name = input('Adı soyadı:')
name = ' '.join(name.split())
print(name)
#------------------------------------------------------------------------------------------------------------------------
strip metodunun lstrip ve rstrip isimli biçimleri de vardır. lstrip (left strip) yalnızca sol taraftaki strip karakterlerini
atar, rstrip (right strip) ise yalnızca sağ taraftaki strip karakterlerini atar. (strip her iki taraftaki strip karakterlerini
atmaktadır).Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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:
2024-11-07 13:00:04 +03:00
>>> s = ' Kaan Aslan '
>>> s.lstrip()
'Kaan Aslan '
>>> s.rstrip()
' Kaan Aslan'
>>> s.lstrip().rstrip()
'Kaan Aslan'
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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:
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
str sınıfının partition isimli metodu yazı içerisinde 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. partition eğer parametresiyle
belirtilen yazıyı bulamazsa birinci elemanı tüm yazı olan, ikinci ve üçüncü elemanları boş string olan yine üçlü bir
demete geri dönmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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', '', '')
2024-11-07 13:00:04 +03:00
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şmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
'ali top at, ali ip atla'
s.replace('ali', )
Tabii istersek replace metodunu yazıdan belli kısımları atmak amacıyla da kullanabiliriz. Örneğin yazıdaki bütün
'a' karakterlerinin atılmak istendiğini varsayalım:
>>> s = 'ankara ankara güzel ankara seni görmek ister her bahtı kara'
>>> k = s.replace('a', '')
>>> k
'nkr nkr güzel nkr seni görmek ister her bhtı kr'
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
Bu durumda replace yalnızca burada belirtilen sayıda değiştirme yapar. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = 'istanbul istanbul güzel istanbul'
>>> k = s.replace('istanbul', 'ankara', 2)
>>> k
'ankara ankara güzel istanbul'
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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. Metodun geri dönüş değeri bool türündendir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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. Bu metot da bool değerle geri dönmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
str sınıfının upper metodu yazıdaki küçük harfleri büyük harflere lower metodu da büyük harfleri küçük harflere dönüştürür.
2024-07-15 13:23:34 +03:00
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'
2024-11-07 13:00:04 +03:00
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ılar için istediğimiz sonucun elde edilmesini engeller.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
22. Ders 21/09/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
İki string >, >=, <, <=, == ve != operatörleriyle karşılaştırılabilir. Karşılaştırma leksikografik olarak yapılmaktadır.
2024-11-07 13:00:04 +03:00
Leksikografik karşılaştırma sözlükteki sıraya göre karşılaştırma anlamına gelir. 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 aslında daha çok iki string'in eşit olması ya da eşit olmaması
biçiminde karşılaştırmalar kullanılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
Burada eğer karşılaştırma 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. Türkçeye özgü 'ç', 'ü', 'ö', 'ı', 'ğ', 'ş'
karakterleri ve bunların büyük harf karşılıkları ('I' hariç) UNICODE tabloda tüm İngilizce karakterlerden daha ileride
bulunmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = 'aysel'
k = 'ayçe'
result = s > k
print(result) # False 's'nin UNICODE numarası 'ç'nin UNICODE numarasından küçük
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii bir yazı diğer yazıyı baştan sona kapsıyorsa 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = 'ali'
k = 'aliye'
result = k > s
print(result) # True
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
ord fonksiyonuna biz tek karakterli bir string'i argüman olarak verebiliriz. Aksi takdirde exception (TypeError) oluşur.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> result = ord('ali')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 3 found
2024-11-07 13:00:04 +03:00
(Bize göre fonksiyonun 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.)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = input('Bir karakter giriniz:')
result = ord(s)
print(result)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
ord fonksiyonunun yaptığı şeyin tersi chr fonksiyonuyla yapılmaktadır. chr fonksiyonu bizden int bir değeri parametre
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Aşağıdaki gibi üç değişken olsun:
2024-07-15 13:23:34 +03:00
a = 10
b = 20
c = 30
2024-11-07 13:00:04 +03:00
Burada a, b, c'nin değerlerini bilmediğimizi varsayalım ve aşağıdaki gibi bir yazıyı ekrana basmak isteyelim:
2024-07-15 13:23:34 +03:00
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))
2024-11-07 13:00:04 +03:00
Bu tür yazımlara "formatlı yazım" denilmektedir. Formatlı yazım işlemleri çok sık karşımıza çıkmaktadır. Ancak görüldüğü
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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. Bu nedenle artık programcılar hemen her zaman string
enterpolasyonu ile formatlama yapmaktadır. Biz kursumuzda ağırlıklı olarak string enterpolasyonu ile formatlama
yapacağız. Ancak diğer iki yöntemi de göreceğiz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ı
(burada n bir sayı belirtmek zorundadır) 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:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
Format sentaksındaki sayıların peşi sıra gelme gibi bir zorunluluğu yoktur. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ştirilir. 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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
https://docs.python.org/3/library/string.html#formatspec
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
Burada formattaki ":0nd" sentaksı "n karakterlik alana başına 0 getirerek yazıyı oluştur" anlamına gelmektedir.
2024-07-15 13:23:34 +03:00
Örneğin biz değerleri değişik sayı sistemlerinde yazdırabiliriz:
x = 100
print('{:X}'.format(x)) # 64
2024-11-07 13:00:04 +03:00
Bazen birtakım değerlerin bıçakla kesilmiş gibi alt alta gelmesini isteyebiliriz. Örneğin:
a = 123
b = 6
c = 123456
print('{} {}'.format(a, a * a))
print('{} {}'.format(b, b * b))
print('{} {}'.format(c, c * c))
Bu prgram çalıştırıldığında aşağıdaki gib bir çıktı oluşacaktır:
123 15129
6 36
123456 15241383936
Fakat örneğin "{:<n}" formatı ile biz ilgili çıktı için n karakter yer ayrılıp yazının sola dayalı bir biçimde oluşturulmasını
sağlayabiliriz:
print('{:<20} {}'.format(a, a * a))
print('{:<20} {}'.format(b, b * b))
print('{:<20} {}'.format(c, c * c))
Burada şöyle çıktı elde edilmiştir:
123 15129
6 36
123456 15241383936
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Kavyeden önce bir sayı sonra da alan uzunluğunu alıp o sayıyı bu alan uzunlupuna sağa dayalı olarak hizalamak isteyelim.
Bu durumda bizim "{:>n}" gibi (n burada bir sayı belirtiyor) yazıyı oluşturmamız gerekir. Bu işlem aşağıdaki yapılabilir.
#------------------------------------------------------------------------------------------------------------------------
a = n = int(input('Sayı giriniz:'))
n = int(input('Sağa hizalanacak değeri giriniz:'))
fstr = '{:>' + str(n) + '}'
print(fstr.format(a))
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
format metodu yazı ieçrisindeki küme parantezlerini normal küme parantezi olarak değil yer tutucu olarak ele almaktadır.
Eğer gerçekten yazı içerisinde küme parantezi oluşturulacaksa "{{" ya da "}}" biçiminde iki küme parantezi kullanılmalıdır.
Örneğin klavyeden okunan sayıyı küme parantezleri içerisinde yazdırmak isteyelim. Bu işlem aiağıdaki gibi yapılamaz:
val = int(input('Sayı giriniz:'))
print('{{}}'.format(val))
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Bu işlemin şöyle yapılması gerekir:
val = int(input('Sayı giriniz:'))
print('{{{}}}'.format(val))
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'a 3.6 ile birlikte "string enterpolasyonu" denilen bir özellik de eklenmiştir. Aslında string enterpolasyonları
2024-11-07 13:00:04 +03:00
bazı programlama dillerinde uzun süredir bulunmaktaydı. Artık Python'un yanı sıra pek çok programlama diline bu özellik
eklenmiştir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
String enterpolasyonu bir string'e ona yapışık bir 'f' ya da 'F' öneki getirilerek oluşturulmaktadır. String enterpolasyonunda
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
değerini o anda hesaplayarak yer tutucu yerine yerleştirir ve yeni bir stringv oluşturur. Örneğin:
2024-07-15 13:23:34 +03:00
a = 10
b = 20
2024-11-07 13:00:04 +03:00
print(f'a = {a}, b = {b}') # yorumlayıcı buradaki string'i 'a = 10, b = 20' halinde dönüştürür.
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
ç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ıları bu tarz formatlamalar için her zaman string enterpolasyonlarını
tercih etmelidir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
print(f"a'nın karesi' = {a * a}, b'nin karesi = {b * b}")
print(f"a'nın karekökü ' = {math.sqrt(a)}, b'nin karekökü = {math.sqrt(b)}")
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
string enterpolasyonunda küme parantezlerinin içerisinde gerçek tırnaklar kullanılacaksa asıl string'in tırnağının bu
tırnaklarla karışması engellenmelidir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> s = f'{', '.join(a)}'
File "<stdin>", 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'
2024-11-07 13:00:04 +03:00
String enterpolasyonunda küme parantezleri içerisinde ters bölü karakteri kullanılamamaktadır. Yani aşağıdaki gibi bir
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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ılarak yapılabilir. Örneğin:
2024-07-15 13:23:34 +03:00
x = 10
print(f'{x * x}, {x * x}, {x * x}, {x * x}, {x * x}')
print('{0}, {0}, {0}, {0}, {0}'.format(x * x))
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
String enterpolasyonundaki format kuralları str sınıfının format metodundaki gibidir. Örneğin:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Python'un 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) kullanılabilmektedir. Tipik kullanılan format karakterleri şunlardır:
%d ---> int türünü 10'luk sistemde yazdırmak için
%x ---> int türünü 16'lık sistemde yazdırmak için
%f ---> float türünü 10'luk sistemde yazdırmak için
%s ---> string yazdırmak için
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:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = 10
>>> print('a = %d' % a)
a = 10
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
23. Ders 28/09/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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 şöyledir:
2024-07-15 13:23:34 +03:00
del <değişken listesi>
del a[ifade], ...
2024-11-07 13:00:04 +03:00
del deyimi değişkenleri de 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:
2024-07-15 13:23:34 +03:00
>>> a = 10
>>> print(a)
10
>>> del a
>>> print(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
2024-11-07 13:00:04 +03:00
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 (garbage collection)" mekanizması
yoluyla otomatik olarak sağlanmaktadır. Bir nesneyi gösteren hiçbir değişken kalmadıysa Python'un çöp toplama mekanizması
devreye girer ve o nesne silinir. Örneğin:
2024-07-15 13:23:34 +03:00
s = 'ankara'
s = 'izmir'
2024-11-07 13:00:04 +03:00
Burada s değişkeni önce "ankara" yazısının bulunduğu nesneyi gösterirken sonra "izmir" yazısının bulunduğu nesneyi gösterir
hale gelmiştir. İşte Python'un çö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:
2024-07-15 13:23:34 +03:00
s = 'ankara'
del s
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Anımsanacaüı gibi del deyimi ile köşeli parantez ile erişebildiğimiz veri yapılarının elemanları da silinebilmektedir.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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]
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
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]
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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]
2024-11-07 13:00:04 +03:00
Anımsanacağı gibi del deyimi ile dilimleme yapılarak da birden fazla eleman silinebilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 100]
>>> del a[2:6]
>>> a
[1, 2, 7, 8, 9, 100]
2024-11-07 13:00:04 +03:00
del deyimi ile sözlüklerden de elemanlar 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:
2024-07-15 13:23:34 +03:00
>>> 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.
2024-11-07 13:00:04 +03:00
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. (Yani biz Python'da "elma" ile "armutu" == ile
ile karşılaştırdığımızda her zaman False, != ile karşılaştırdığımızda her zaman True değerini elde ederiz.)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = 10
>>> b = 10.0
>>> a == b
True
>>> a = 1.0
>>> b = True
>>> a == b
True
2024-11-07 13:00:04 +03:00
İki string'i karşılaştırabileceğimizi görmüştük. Ancak bir string ile farklı türden bir değişken == operatörüyle
karşılaştırıldığında her zaman False değeri, != operatörüyle karşılaştırıldığında her zaman True değeri elde edilir.
Örneğin:
>>> s = 'ankara'
>>> s == 123
False
>>> s != 123
True
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20]
b = (10, 20)
result = a == b
print(result) # False
result = a != b
print(result) # True
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = [10, 20]
>>> b = (10, 20)
>>> a > b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'list' and 'tuple'
2024-11-07 13:00:04 +03:00
Özetle int, float ve bool dışındaki farklı türleri == ve != operatörleriyle karşılaştırabiliriz ancak >, <, >=, <=
2024-07-15 13:23:34 +03:00
operatörleriyle karşılaştıramayız.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> 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
2024-11-07 13:00:04 +03:00
string'lerde olduğu gibi "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:
2024-07-15 13:23:34 +03:00
>>> 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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii listeler ve demetler heterojen türlere sahip olabildiğine göre karşılıklı elemanların kullanılan operatöre göre
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:
2024-07-15 13:23:34 +03:00
>>> a = [10, 'ali', 20]
>>> b = [10, 20, 30]
>>> a > b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
2024-11-07 13:00:04 +03:00
özyinelemeli biçimde yapılır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
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
2024-11-07 13:00:04 +03:00
anahatar-değer çiftlerinin bire bir aynı olup olmadığına bakılmaktadır (yani yalnızca anahtarlara bakılmamaktadır).
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = {10, 'ali', 'veli'}
>>> b = {10, 'veli'}
>>> a > b
True
>>> b < a
True
>>> c = set()
>>> c < a
True
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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şında 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. (n hiçbir zaman 0 olamamaktadır.) Bu biçimdeki SPACE'lerin sayısı satırın girinti düzeyini vermektedir.
Örneğin:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Örneğin:
TAB TAB TAB SPACE
Bu satırın girinti düzeyi 25'tir. Ö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:
2024-07-15 13:23:34 +03:00
SPACE SPACE SPACE SPACE SPACE SPACE SPACE SPACE SPACE
SPACE TAB SPACE
2024-11-07 13:00:04 +03:00
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 dosyaya
yazmaktadır. Bu durumda yorumlayıcı zaten artık TAB karakterlerini görmez, çünkü dosyada yalnızca SPACE karakterleri
bulunmaktadır. Haliyle girinti düzeyi hesabı oldukça kolay hale gelöektedir. Tabii Python editörünüzün TAB yerine SPACE
basması yönünde bir zounluluk yoktur.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Eğer TAB yerine editörünüz n tane SPACE yerleştiriyorsa bu durumda alt alta gelen iki satırın girinti düzeyi her
zaman aynı olur. Eğer TAB yerine editörünüz n tane SPACE yerleştirmiyorsa bu durumda TAB ayarı 8 olduğunda da aynı
girinti düzeyine sahip olan iki satır alt alta gözükecektir. PEP 8'de Python editörlerinin TAB ayarlarının 8 yapılması
tavsiye edilmektedir. Ancak 8'lik TAB'lar programlama dünyasında pek kabul görmemiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
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.
Ancak bileşik deyimlerle diğer deyimler hiçbir zaman aynı satıra yazılamazlar. Örneğin if gibi for gibi deyimler Python'da
bileşik deyimler grubundandır. Python'da basit deyimlerin de bileşik deyimlerin de çeşitli biçimler vardır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:')
2024-11-07 13:00:04 +03:00
Bu iki deyim ifadesel deyimdir. Bir ifadeyi bir satıra yazdığımızda artık o ifadenin bir deyim heline geldiğine dikkat
ediniz. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
print(a + b)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Tabii ifadeler başka deyimlerin parçalarını da oluşturabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
if val % 2 == 0:
print('çift')
else:
print('tek')
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Burada if cümleesinin tamamı tek bir deyimdir. val % 2 == 0 bir ifadedir ancak burada deyim değildir. Bi ifade bağımsız
olarak bir satıra yazıldığında deyim olmaktadır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
x = 10
y = 20
z = x + y
print(z)
2024-11-07 13:00:04 +03:00
Burada ilk üç deyim atama deyimidir. Son deyim ise ifadesel deyimdir. Ancak bunların hepsi kategorik olarak basit deyim
statüsündedir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi biz basit deyimleri istersek tek satır üzerinde onların aralarına ';' getirerek de
yazabiliriz:
2024-07-15 13:23:34 +03:00
x = 10; y = 20; z = x + y; print(z)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
if (koşul) {
ifade1;
ifade2;
ifade3;
}
ifade4;
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
if koşul:
ifade1
ifade2
ifade3
ifade4
2024-11-07 13:00:04 +03:00
Burada ifade1, ifade2 ve ifade3 aynı girinti düzeyine sahip olduğu için bileşik deyimin parçalarını oluşturmaktadır. Yani
burada ifade1i ifade2 ve ifade3 if deyiminin "doğruysa kısmını" oluşturmaktadır. ifade4 ise if deyiminin dışındadır.
2024-07-15 13:23:34 +03:00
Bir bileşik deyimin içindeki deyimlerin aynı girinti düzeyine sahip olması gerekir. Örneğin:
if koşul:
ifade1;
ifade2
ifade3
ifade4
2024-11-07 13:00:04 +03:00
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 daha önceden de 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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Artık gerekli hazırlıkları tamamladık. Şimdi Python2un bileşik deyimlerini tek tek ele alıp açıklayacağız.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
if deyimi bir ifadenin doğru ya da yanlış olması durumuna göre farklı işlemlerin yapılmasını sağlayan en temel bileşik
deyimdir. Genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
if <ifade>: <suite>
2024-11-07 13:00:04 +03:00
[else: <suite>]
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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ından geçerlidir:
2024-07-15 13:23:34 +03:00
if ifade:
ifade1
ifade2
else:
ifade3
ifade4
2024-11-07 13:00:04 +03:00
else anahtar sözcüğü ile if anahtar sözüğünün aynı girinti düzeyine sahip olması gerekmektedir. Aşağıdaki if deyimi de
geçerlidir:
2024-07-15 13:23:34 +03:00
if ifade: ifade1; ifade
else:
ifade3
ifade4
Aşağıdaki if deyimi de geçerlidir:
if ifade:
2024-11-07 13:00:04 +03:00
ifade1; ifade2
2024-07-15 13:23:34 +03:00
else:
ifade3
ifade4
2024-11-07 13:00:04 +03:00
if deyimimn "doğruysa" ve "yanlışsa" kısmındaki suit'lerin aynı girinti düzeyine sahip olması gerekmez. örneğin:
2024-07-15 13:23:34 +03:00
if ifade:
2024-11-07 13:00:04 +03:00
ifade1
ifade2
2024-07-15 13:23:34 +03:00
else:
2024-11-07 13:00:04 +03:00
ifade3
ifade4
2024-07-15 13:23:34 +03:00
Bu yazım geçerlidir. Tabii böyle bir yazımda okunabilirlik bozuk olacaktır. Bu nedenle Python programcıları her ne kadar
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
if ifade:
ifade1
ifade2
else:
ifade3
ifade4
2024-11-07 13:00:04 +03:00
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. ':' atomunun boşluk bırakılmadan yapışık yazılması
tavsiye edilmektedir.
Suit "aynı satır üzerine yazılmış olan birden fazla basit deyim ya da farklı satı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:
2024-07-15 13:23:34 +03:00
if ifade: ifade1
ifade2
else: ifade3; ifade4
2024-11-07 13:00:04 +03:00
Çünkü if deyiminin doğruysa ksmındaki deyimler suite oluşturmamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
if ifade:
2024-11-07 13:00:04 +03:00
ifade1
ifade2
2024-07-15 13:23:34 +03:00
else:
ifade3
ifade4
ifade5
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
Örneğin:
if ifade: ifade1 else: ifade2
2024-11-07 13:00:04 +03:00
Bu if deyimi geçersizdir. else anahtar sözcüğü if ile aynı girinti düzeyinde yazılmak zorundadır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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 yalnı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:
2024-07-15 13:23:34 +03:00
x = 10
if x > 0:
print('ankara')
print('izmir')
else:
print('adana')
print('eskişehir')
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
x = int(input('Bir syaı giriniz:'))
if x % 2 == 0:
print('çift')
else:
print('tek')
print('program sonlanıyor')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Boş bir listenin, boş bir demetin ve boi bir string'in bool türüne False olarak, dolu bir listenin, dolu bir demetin ve
dolu bir string'in bool türüne True olarak dönüştürüldüğünü anımsayınız. Örneğin:
2024-07-15 13:23:34 +03:00
a = []
if a:
ifade1
ifade2
else:
ifade
ifade4
2024-11-07 13:00:04 +03:00
Bu if deyimi yanlışsa kısmından sapacaktır. Benzer biçimde sıfırdan farklı int ve float değerlerin bool türüne True olarak,
2024-07-15 13:23:34 +03:00
0 değerinin False olarak dönüştürüldüğünü de anımsayınız. Örneğin:
2024-11-07 13:00:04 +03:00
a = int(input('Bir değer giriniz:'))
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
Burada toplamda iki deyim vardır. if deyimi ve ifade3'ten oluşan basit deyim. Buradaki if deyimin else kısmı bulunmamaktadır.
ifade3 if deyiminin dışında olan başka bir basit deyimdir. Örneğin:
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
24. Ders 29/09/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
C, C++, Java ve C# gibi dillerde if anahtar sözcüğünden sonra if koşul ifadesinin parantezler içerisinde olması zorunludur.
2024-07-15 13:23:34 +03:00
Örneğin:
if (a > 0)
ifade1;
else
ifade2;
2024-11-07 13:00:04 +03:00
Pekiyi neden 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ın sağlanmak istenmesidir. Örneğin C'de bu parantezlerin gerekmediğini varsayalım:
2024-07-15 13:23:34 +03:00
if a > 10 b = 20;
2024-11-07 13:00:04 +03:00
Görüldüğü gibi koşul ifadesiyle if deyimin doğruysa ksımında ifade derleyici tarafından ayrıştırılamayacaktır. Parantezler
bu ayrıştırmayı sağlamaktadır:
2024-07-15 13:23:34 +03:00
if (a > 10) b = 20;
2024-11-07 13:00:04 +03:00
Artık her şey çok açıktır. Python'da zaten if deyiminin koşul ifadesinden sonra ':' atomu gelmek zorunda olduğu için
2024-07-15 13:23:34 +03:00
bu ayrıştırma parantezlere gereksinim duyulmadan da yapılabilmektedir. Örneğin:
if a > b: b = 20
2024-11-07 13:00:04 +03:00
Swift gibi Kotlin gibi yeni bazı dillerde küme parantezleri zorunlu tutulduğu için o dillerde de koşul ifadesinin
2024-07-15 13:23:34 +03:00
paranteze alınması gerekmemiştir. Örneğin:
if a > 10 {
2024-11-07 13:00:04 +03:00
b = 20;
2024-07-15 13:23:34 +03:00
}
2024-11-07 13:00:04 +03:00
Tabii Python'da biz istersek if deyiminin koşul ifadesinde de parantezleri kullanabiliriz. Ne de olsa parantezler
her ifadede kullanılabilir. Örneğin:
if (a > 10):
b = 20
deyimi Python'da sentaks olarak geçerlidir. Tabii gerekmeyen parantezlerin kullanılmasının da anlamı yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
Burada toplamda dışarıdan bakıldığında üç deyim vardır. Dıştaki if deyiminin doğruysa kısmında başka bir if deyimi vardır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
C/C++, Java ve C# gibi dillerde hizalamanın (girinti düzeyinin) 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:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örnekte üç sayıyı aynı satırda bir yazı olarak da girebiliriz:
#------------------------------------------------------------------------------------------------------------------------
s = input('Üç sayıyı yan yana giriniz:')
vals = s.split()
a = int(vals[0])
b = int(vals[1])
c = int(vals[2])
if a > b:
if a > c:
print(a)
else:
print(c)
else:
if b > c:
print(b)
else:
print(c)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
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çar. Örneğin:
2024-07-15 13:23:34 +03:00
if a == 1:
ifade1
if a == 2:
iafde2
if a == 3:
ifade3
2024-11-07 13:00:04 +03:00
Bu da benzer biçimde kötü bir tekniktir. Ayrık koşulların ayrı if'lerle değil else-if biçiminde organize edilmesi doğru
tekniktir. Örneğin:
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yukarıdaki örnekte görüldüğü gibi else-if merdivenlerinin 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 koşul ifadesiyle birlikte bulundurulur. elif
kısımlarından sonra son bir else kısmı da bulundurulabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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')
2024-11-07 13:00:04 +03:00
if, elif ve else anahtar sözcüklerinin yanı girinti düzeyine sahip olması gerektiğine dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
while döngüsünün genel biçimi şöyledir:
while <ifade>: <suite>
2024-11-07 13:00:04 +03:00
[else: <suite>]
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
Dolaysıyla aşağıdaki örnekte i değeri 0'a geldiğinde döngüden çıkılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
hemen anlayamayacaktır. Burada ayrıca 0'ın karesinin de ekrana yazdırılacağına dikkat ediniz. O halde Walrus'lu kalıp
gerçekten iyi işlev görmektedir:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
i = 0
while i < 10:
k = 0
while k < 10:
print(f'({i}, {k})')
k += 1
i += 1
#------------------------------------------------------------------------------------------------------------------------
Aşağıda da benzer bir iç içe döngü örneği verilmiştir. Bu örnekte klavyeden bir sayı istenmiştir. İlk satırı bir tane
*, ikinci satırı 2 tane *, ..., n'inci satıra n tane * bastırılmıştır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
n = int(input('Bir sayı giriniz:'))
i = 1
while i <= n:
k = 0
while k < i:
print('*', end='')
k += 1
print()
i += 1
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii yukarıdaki örneği aslında "yineleme (repitition)" ile çok daha kolay biçimde aşağıdaki gibi yapabilirdik.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
çözebilecek birden fazla 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
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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 (analysis of algorithm)" denilmektedir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da sonsuz döngü oluşturmak için while anahtar sözcüğünün yanındaki ifadeyi True yapabiliriz. Örneğin:
while True:
...
2024-11-07 13:00:04 +03:00
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
2025-02-02 21:34:10 +03:00
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 yazabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
bir durum oluşturur. Sonsuz döngüye giren bir programı sonlandırmak için IDE'lerde özel sonlandırma düğmeleri bulundurulmaktadır.
Eğer program komut satırından çalıştırılıyorsa sonlandırma "Ctrl+C" tuş kombinasyonuyla yapılabilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aslında while deyiminde isteğe bağlı olarak bir else kısım da bulundurulabilmektedir. Bu else kısım while döngüsünden
break dışında ya da exception dışında normal bir biçimde çıkılırken bir kez çalıştırılmaktadır. else anahtar sözcüğü
yine while anahtar sözcüğü ile aynı girinti düzeyine sahip olacak biçimde yazılmalıdır. Örneğin:
i = 0
while i < 5:
print(i)
i += 1
else:
print('ends', i)
Burada while döngüsünden koşul sağlanmaması nedeniyle çıkılırken en sonunda döngünün else kısmı da çalıştırılacaktır.
Dolayısıyla programı çalıştırdığınızda ekranda şunları göreceksiniz:
0
1
2
3
4
ends 5
while döngüsüne girilir girilmez koşul sağlanmıyor olsa da yine deyimin else kısmı çalıştırılmaktadır. Pekiyi yukarıdaki
döngünün aşağıdakinden farkı nedir?
i = 0
while i < 5:
print(i)
i += 1
print('ends', i)
İşte döngüden break deyimi ile ya da exception oluştuğundan dolayı çıkılırsa bu else kısım çalıştırılmamaktadır. Örneğin:
i = 0
while i < 5:
print(i)
val = input('break (y/n)?')
if val == 'y':
break
i += 1
else:
print('ends', i)
Burada klavyeden "y" girildiğinde programın akışı break deyimini görecektir. Böylece döngü kırılacaktır. Ancak else
kısım çalıştırılmayacaktır. Tabii while deyiminin else kısmı çok seyrek kullanılmaktadır. break deyimi ve exception
konusu ileride ele alınacaktır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
25. Ders 05/10/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
for döngülerinin genel biçimi şöyledir:
for <değişken> in <dolaşılabilir nesne>: <suite>
2024-11-07 13:00:04 +03:00
[else: <suite>]
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Python'da for döngüleri C/C++, Java ve C#'taki belli bir miktar yinelemeye yol açan tarzda for döngüleri gibi değildir.
Python'daki for döngüleri diğer bazı dillerdeki "foreach" döngülerine benzemektedir. Python'da for döngüleri aslında
dolaşılabilir nesneleri dolaşan bir döngüdür.
2024-07-15 13:23:34 +03:00
for döngüleri şöyle çalışır: Döngünün her yinelenmesinde dolaşılabilir nesnenin sıradaki elemanı döngü değişkenine
2025-02-02 21:34:10 +03:00
atanır. (Tabii bu aslında onun adresinin dögü değişkenine atandığı anlamına gelmektedir.) 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:
2024-07-15 13:23:34 +03:00
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ı
2024-11-07 13:00:04 +03:00
bittiğinde döngü de bitecektir. Tabii aslında döngü değişkenine atama bir adres atamasıdır. Yani yukarıdaki örnekte
aslında x'e sırasıyla 10, 20, 30, 40, 50 nesnelerinin adresleri atanmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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))
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Biz for döngüleri ile dolaşılabilir olan her nesneyi dolaşabiliriz. Örneğin:
s = 'ankara'
for c in s:
print(c)
Burada yazının karakterleri ekrana bastırılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
s = 'ankara'
for c in s:
print(c)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
for döngüleri dolaşılabilir nesneleri dolaşmak için kullanılan temel bir deyimdir. Biz daha önceden zaten list, tuple,
set, dict gibi sınıflar türünden nesneler yaratırken bu fonksiyonlara dolaşılabilir nesneler verdiğimizde bu fonksiyonlar
o nesneleri dolaşıp bize ilgili türden dolaşılabilir nesneler veriyordu. Yani bu fonksiyonlar dolaşımı kendileri yapıyordu.
Oysa for döngüleriyle biz doğrudan dolaşılabilir nesneleri dolaşabilmekteyiz.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Bir sözlük dolaşıldığında sözlüğün anahtarlarının elde edildiğini anımsayınız. Örneğin:
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
for x in d:
print(x)
Burada sözlüğün anahtarları elde edilip ekrana yazdırılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 58, 'veli': 87, 'selami': 59, 'ayşe': 98, 'fatma': 81}
for x in d:
print(x)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
dict sınıfının items metodunun dolaşılabilir bir nesne verdiğini, o nesne dolaşıldığında anahtar-değer çiftlerine
ilişkin demetlerin elde edildiğini anımsayınız. Örneğin:
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
for t in d.items():
print(t)
Burada sözlüğün anahtar-değer çiftleri ekrana yazdırılmaktadır. Program çalıştırıldığında ekranda şunları göreceksiniz:
('ali', 10)
('veli', 20)
('selami', 30)
('ayşe', 40)
('fatma', 50)
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
for t in d.items():
print(t)
#------------------------------------------------------------------------------------------------------------------------
Dolaşılabilir olmayan nesnelere for döngüsyle dolaşılamazlar. Örneğin:
2024-07-15 13:23:34 +03:00
a = 10
for x in a:
print(x)
2025-02-02 21:34:10 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Biz Python'da C, C++, Java ve C# gibi dillerdeki for döngülerini range fonksiyonu ile emüle edebiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
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) {
...
}
2024-11-07 13:00:04 +03:00
Örneğin:
for i in range(10):
print(i, end=' ')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
for i in range(10):
print(i, end=' ')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii diğer dillerdeki klasik for döngülerine benzer bir döngü while ile de oluşturabilir. Örneğin:
i = 0
while i < 10:
print(i)
i += 1
Buradaki kod işlevsel olarak aşağıdaki ile eşdeğerdir:
for i in range(10):
print(i)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Bir liste, demet ya da string'i dolaşmak isteyelim. for döngüsü ile iki alternatif yöntem kullanılabilir. Birincisi
indeks yoluyla dolaşma olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
a = [10, 20, 30, 40, 50]
for i in range(len(a)):
print(a[i], end=' ')
2025-02-02 21:34:10 +03:00
İkincisi onu doğrudan dolaşmak olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
Çünkü biz burada x'i değiştiriyoruz, a'da bir değişiklik yapmıyoruz. int türü değiştirilemez olduğu için burada aslında
x yeni yaratılan başka bir int nesneyi gösteriyor olmaktadır. Ancak örneğin:
2024-07-15 13:23:34 +03:00
for i in range(len(a)):
a[i] = a[i] ** 2
2024-11-07 13:00:04 +03:00
Burada gerçekten liste elemanları değiştirilmiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii dolaşılabilir bir nesne de dolaşılabilir nesnelerden oluşuyor olabilir. Örneğin bir demet listesi söz konusu o
labilir. Bu durumda biz listeyi dolaştığımızda demetleri elde ederiz:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a = [('ali', 100, 'eskişehir'), ('veli', 200, 'konya'), ('selami', 300, 'nevşehir'),
('ayşe', 400, 'sivas'), ('fatma', 700, 'istanbul')]
2024-07-15 13:23:34 +03:00
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:
2024-11-07 13:00:04 +03:00
a = [('ali', 100, 'eskişehir'), ('veli', 200, 'konya'), ('selami', 300, 'nevşehir'),
('ayşe', 400, 'sivas'), ('fatma', 700, 'istanbul')]
2024-07-15 13:23:34 +03:00
for t in a:
2024-11-07 13:00:04 +03:00
for x in t:
print(x, end=' ')
2024-07-15 13:23:34 +03:00
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()
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Tabii listenin elemanı bir liste ise listeler dolaşılabilir nesneler olduğu için biz döngü değişkeni yoluyla eleman
olan listelerde değişiklikler yapabiliriz. Örneğin:
a = [['ali', 100, 'eskişehir'], ['veli', 200, 'konya'], ['selami', 300, 'nevşehir'],
['ayşe', 400, 'sivas'], ['fatma', 700, 'istanbul']]
for x in a:
x.append(100)
Burada listenin her elemanındaki listelere ekleme yapılmıştır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
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öngüsünün içerisinde yapabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
a = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
for t in a:
x, y = t
2024-11-07 13:00:04 +03:00
print(x, y)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Aslında bu açım doğrudan for döngüsünün sentaksında da yapılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
for x, y in a:
2024-11-07 13:00:04 +03:00
print(x, y)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Yani biz dolaşılabilir bir nesneyi for ile dolaşırken eğer dolaşımdan elde ettiğimiz nesneler de dolaşılabilir ise
doğrudan ana nesneyi açım yaparak dolaşebiliriz. Tabii bu açım işleminde köşeli parantezler ya da normal parantezler de
kullanılabilir. Ancak gereksizdir. Örneğin:
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Python'da _ normal bir değişken ismi olarak kullanılabilmektedir. Ancak genel olarak programcılar _ ismini "ben bu
değişkenle ilgilenmiyorum, yalnızca bu değişkeni yer tutucu 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 döngü içerisindeki suit'te hiç kullanmayacak
olalım. Bu durumu vurgulamak için döngü değişkeni için _ ismini kullanabiliriz. Örneğin:
for _ in range(1000):
...
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
bir anahtar varsa biz onun değerini de elde edebiliriz. Pyton'da eskiden sözlük nesneleri dolaşıldığında anahtarların
hangi sırada dolaşılacağının bir garantisi yoktu. Ancak 3.7 ile birlikte artık dolaşımın anahtarların sözlüğe eklendiği
sırayla yapılması garanti edilmiştir.
#------------------------------------------------------------------------------------------------------------------------
d = {10: 'ali', 20: 'veli', 5: 'selami'}
d[100] = 'sacit'
d[300] = 'fehmi'
for x in reversed(d):
print(x)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
for key in d:
print(key, d[key])
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
dict sınıfının items isimli metodunun bize dolaşılabilir bir nesne verdiğini, o nesne dolaşıldığında da anahtar değer
çiftlerinden oluşan demetlerin elde edildiğini anımsayınız. O zaman biz sözlükteki anahtar değer çiftlerini şöyle de
elde edebiliriz:
2024-07-15 13:23:34 +03:00
for key, value in d:
2024-11-07 13:00:04 +03:00
print(key, value)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Küme nesnelerinin dolaşılabilir olduğunu belirtmiştik. Ancak bir küme nesnesi dolaşıldığında dolaşımın hangi sırada
yapılacağının bir garantisi yoktur. Örneğin:
s = {'ali', 'veli', 'selami', 'ayşe', 'fatma}
for name in s:
print(name)
Burada isimlerin hangi sırada ekran görüneceğinin bir garantisi yoktur.
#------------------------------------------------------------------------------------------------------------------------
s = {'ali', 'veli', 'selami', 'ayşe', 'fatma}
for name in s:
print(name)
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
for döngüsünde in anahtar sözcüğünün yanındaki ifadede walrus operatörü kullanılabilir. Ancak ifadenin parantez
içerisine alınması gerekmektedir. Örneğin:
for x in (b := a):
pass
Burada hem a b'ye atanmış hem de for döngüsüyle dolaşılmıştır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Dolaşılabilir bir nesneyi tersten dolaşabilir miyiz? Bunun yanıtı "o nesneye ilişkin sınıfı yazan kişi buna izin verdiyse
2025-02-02 21:34:10 +03:00
dolaşabiliriz" biçimindedir. Tabii str gibi, list gibi, tuple gibi nesneler ters indekslemeyle ya da ters çeviren
dilimleme ile tersten dolaşılabilirler. Bu türlere daha önceden de belirttiğimiz gibi "sequence type" denilmektedir.
2024-07-15 13:23:34 +03:00
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=' ')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tersten dolaşım daha önce görmüş olduğumuz built-in "reversed" fonksiyonu ile yapılabilmektedir. Anımsanacağı gibi
reversed fonksiyonu bize tersten dolaşılabilir bir nesne vermekteydi. O zaman tersten dolaşımı aşağıdaki gibi de
yapabiliriz:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a = [10, 20, 30, 40, 50]
for x in reversed(a):
print(x, end=' ')
#------------------------------------------------------------------------------------------------------------------------
a = [10, 20, 30, 40, 50]
2024-07-15 13:23:34 +03:00
for x in reversed(a):
print(x, end=' ')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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. Dolayısıyla biz bir
küme nesnesini reversed fonksiyonuna sokamayız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Pyton'da 3.7 versiyonundna önce sözlük nesnelerinin tıpkı küme nesnelerinde olduğu gibi hangi sırada dolaşılacağının
bir garantisi yoktu. Dolayısıyla eskiden sözlük nesneleri tersten dolaşılabilir değildi. Ancak 3.7 ile birlikte sözlük
nesnelerinin anahtarlarının onların eklenme sırasına göre dolaşılması garanti edilmiştir. Dolayısıyla 3.7 ile birlikte
artık sözlük nesneleri de tersten dolaşılabilir hale getirilmiştir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
for x in reversed(d):
print(x)
Burada anahtarlar ters sırada ekrana yazdırılmaktadır.
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
2024-07-15 13:23:34 +03:00
for x in reversed(d):
print(x)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Pekiyi for döngüsüyle sonsuz döngüler oluşturabilir miyiz? Bu işlem ancak sonsuz sayıda değer veren özel dolaşılabilir.
nesnelerle yapılabilmektedir. Örneğin itertools modülündeki count isimli fonksiyon bize 0'dan itibaren sonsuza kadar
ardışıl int değerler vermektedir. Biz de bu count ile bir sonsuz döngü etkisi yaratabiliriz. Örneğin:
for _ in count():
...
Tabii aslında sonsuz döngülerin for ile değil while oluşturulması daha uygundur. Örneğin:
while True:
...
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
for döngüsünün de isteğe bağlı olarak bir else kısmı olabilir. Döngünün else kısmı döngünün normal sonlanmasında bir
kez çalıştırılmaktadır. Ancak tıpkı while döngülerinde olduğu gibi eğer döngüden break deyimi ya da exception nedeniyle
çıkılırsa bu else kısım çalıştırılmamaktadır. Yine else anahtar sözcüğü for anahtar sözcüğü ile aynı girinti düzeyine
sahip olmalıdır. Örneğin:
for i in range(10):
print(i)
else:
print('ends')
2025-02-02 21:34:10 +03:00
Aşağıdaki örnekte döngü break deyimi ile kırıldığında else kısım çalıştırılmayacaktır:
2024-11-07 13:00:04 +03:00
for i in range(10):
print(i)
val = input('break (y/n)?')
if val == 'y':
break
else:
print('ends')
for döngüsünün de else kısmı çok seyrek kullanılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
break deyimi yalnızca döngüler içerisinde (yani while ve for döngüleri içerisinde) kullanılabilir. Genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
break
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
while True:
...
if koşul:
break
...
2024-11-07 13:00:04 +03:00
Örneğin:
while True:
val = float(input('Bir sayı giriniz:'))
if val == 0:
break
print(val * val)
Burada bir sonsuz döngü içerisinde girilen değerin karesi yazdırılmaktadır. Ancak lkavyeden 0 girildiğinde break ile
döngü deyimi sonlandırılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
while True:
val = float(input('Bir sayı giriniz:'))
if val == 0:
break
print(val * val)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Örneğin kullanıcıdan parola isteyelim. Eğer kullanıcı parolayı yanlış girmişse en fazla üç deneme hakkı olsun. Böyle
bir kod parçasını nasıl yazabiliriz? Burada ilk akla gelen yöntem en fazla 3 kez dönecek bir döngü oluşturmak ancak
parola doğru girilmişse döngüden erken bir biçimde çıkmaktır:
for i in range(3):
passwd = input('Enter password:')
if passwd == 'maviay':
print('ok')
break
else:
print('invalid password!')
if passwd != 'maviay':
print('try again later...')
Tabii aslında if deyiminin doğruysa kısmında break var ise o if deyiminin else kısmına gerek yoktur. Yani else kısmı
aslında if deyiminin dışına alınabilir. Örneğin:
for i in range(3):
passwd = input('Enter password:')
if passwd == 'maviay':
print('ok')
break
print('invalid password!')
Burada zaten if deyimi doğruysa döngü kırılacağı için akış aşağıya hiç geçmeyecektir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
İç içe döngülerde break yalnızca kendi döngüsünü sonlandırmaktadır. Örneğin:
for i in range(10):
for k in range(10):
print(f'({i}, {k})')
response = input('break? (y/n):')
if response == 'y':
break
print('program continues...')
Burada 'y' tuşuna basıldığında içteki döngü sonlanacak ancak dıştaki döngü sonlanmayacaktır. C, C++, C# gibi dillerde
tüm dönülerden tek hamlede çıkabilmek için goto deyimi kullanılmaktadır. Ancak Python'da goto deyimi yoktur. Dolayısıyla
iç döngüden çıktıktan sonra dış döngüde de yeniden break uygulanması gerekir. Örneğin:
for i in range(10):
for k in range(10):
print(f'({i}, {k})')
response = input('break? (y/n):')
if response == 'y':
break
if response == 'y':
break
print('program continues...')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
for i in range(10):
for k in range(10):
2024-11-07 13:00:04 +03:00
print(f'({i}, {k})')
response = input('break? (y/n):')
if response == 'y':
2024-07-15 13:23:34 +03:00
break
2024-11-07 13:00:04 +03:00
if response == 'y':
2024-07-15 13:23:34 +03:00
break
2024-11-07 13:00:04 +03:00
print('program continues...')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Döngüler içerisinde kullanılan diğer bir deyim de continue deyimidir. Deyimin genel biçimi şöyledir:
continue
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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. Örneğin:
for i in range(10):
if i % 2 == 0:
continue
print(i)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Burada çift sayılarda programın akışı continue deyimini göreceği için döngü başa saracatır. Dolayısıyla ekrana yalnızca
tek sayılar basılacaktır. Tabii biz bu örneği continue deyimininin çalışmasınııklamak için verdik. Yoksa amacımız
tek sayıları yazdırmak olsaydı onu şöyle de yapabilirdik:
for i in range(10):
if i % 2 == 1:
print(i)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
for i in range(10):
if i % 2 == 0:
continue
print(i)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte bir imleç çıkartılmakta ve bu imleç eşliğinde kullanıcının komut girmesi istenmektedir. Eğer kullanıcı
2025-02-02 21:34:10 +03:00
hiçbir komut girmeden ENTER tuşuna basarsa continue deyimi ile döngü başa sarılmaktadır. "quit" komutu girildiğinde
ise komut yorumlayıcı sonlandırılmaktadır.
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
while True:
cmd = input('CSD>').strip()
if cmd == 'quit':
break
if cmd == '':
continue
if cmd == 'dir':
print('dir command executes...')
elif cmd == 'del':
print('del command executes...')
elif cmd == 'cls':
print('cls command executes...')
else:
print(f'invalid command: {cmd}')
#------------------------------------------------------------------------------------------------------------------------
Aşağıda bir imleç eşliğinde dört işlem yapan bir program örneği verilmiştir. Bu programda kullanıcı imleçte "3 * 2"
gibi dört işlemden birini yazıp ENTER tuşuna basmaktadır. Program da işlemin sonucunu ekrana yazdırmaktadır. "quit"
ile programdan çıkılmaktadır. Ancak bu programda operatörler ile operand'lar arasında en az bir boşluk karakterinin
bulunması gerekir. Örneğin "3* 2" gibi bir komut geçerli değildir. Ayrıca programda operand'ların float sayılara
uygun olup olmadığı bizim henüz görmediğimiz exception mekanizmasıyla kontrol edilmiştir.
#------------------------------------------------------------------------------------------------------------------------
while True:
cmd = input('CSD>').strip()
if cmd == 'quit':
break
if cmd == '':
continue
terms = cmd.split()
if len(terms) != 3:
print('invalid command')
continue
try:
op1 = float(terms[0])
op2 = float(terms[2])
except:
print('invalid float number!')
continue
if terms[1] == '*':
result = op1 * op2
elif terms[1] == '/':
result = op1 / op2
elif terms[1] == '+':
result = op1 + op2
elif terms[1] == '-':
result = op1 + op2
else:
print('invalid operator!')
continue
print(result)
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
26. Ders 06/10/2024 - Pazar
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
...
...
...
...
...
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir sayının basamak sayısını bulmak isteyelim. Bunu nasıl yapabiliriz? Klasik çözüm sayıyı 0 olmayana kadar sürekli
tamsayılı biçimde (floordiv operatöryle) 10'a bölmek ve bir sayaçla bu işemin kaç kez yapıldığını hesaplamaktır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a = int(input('Bir sayı giriniz:'))
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
count = 0
while a:
count += 1
a //= 10
print(count)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
float olarak verir. float sayıyı noktadan kurtarmak için int dönüştürmesi yapılabilir. Örneğin:
import math
a = int(input('Bir sayı giriniz:'))
result = int(math.log10(a)) + 1
print(result)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
olabilir. Örneğin:
val = int(input('Bir sayı giriniz:'))
result = len(str(val))
print(result)
Tabii eğer sayı negatifse "-" sembolündne dolayı uzunluk 1 fazla çıkacaktır. Bu durumda önce mutlak değer alınabilir
ya da aşağıdaki gibi bir çıkartım terimi eklenebilir:
result = len(str(val)) - (val < 0)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
val = int(input('Bir sayı giriniz:'))
2024-11-07 13:00:04 +03:00
result = len(str(val)) - (val < 0)
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
Burada programcı for döngüsünün içerisinde bir şey yapmak istememiştir. Programcının belki de amacı akışı bir süre
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
operatörü kullanarak sağladığımızı düşünelim. Ancak bu durumda gerçekten de döngünün içerisinde yapacak bir şey kalmamaktadır.
O halde biz de pass deyimini sentaksın gereksinim duyduğu suite yerine yerleştirebiliriz. Örneğin:
import math
while (val := float(input('Pozitif bir sayı giriniz:'))) < 0:
pass
Burada while döngüsü klavyeden negatif değer girildiği sürece yinelenecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
import math
while (val := float(input('Pozitif bir sayı giriniz:'))) < 0:
2024-07-15 13:23:34 +03:00
pass
2024-11-07 13:00:04 +03:00
result = math.sqrt(val)
print(result)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
Ancak continue deyimi pass deyiminin yerini tutamamaktadır. Çünkü continue deyimi yalnıcaz döngülerde kullanılabilmektedir.
2024-07-15 13:23:34 +03:00
Halbuki pass deyimi normal bir deyimdir. Her yerde kullanılabilir. Örneğin ilerleyen zamanlarda fonksiyonlar konusunu
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da diğer bazı dillerdeki gibi bir switch deyimi yoktu. Ancak Python'un 3.10 versiyonuyla birlikte dile "match"
2024-11-07 13:00:04 +03:00
adı altında switch benzeri bir deyim eklenmiştir. Kursun yapıldığı sırada Python'un son versiyonu 3.12.7'dir. 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. Spyder IDE'sinde "durum çabuğunda (status bar)" kullanılan Python'un versiyon numarası
yazmaktadır. Komut satırında Python2un versiyon numarasını aşağıdaki gibi öğrenebilirsiniz:
python --version
2024-07-15 13:23:34 +03:00
Python'daki match deyimi diğer bazı dillerdeki switch deyimininden daha ayrıntılı ve daha yeteneklidir. match deyimi
2024-11-07 13:00:04 +03:00
"Structural Pattern Matching" başlığı altında PEP-634 dokümanında açıklanmıştır. PEP-636 dokğmanı da eğtici (tutorial)
biçimde hazırlanmıştır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
27. Ders 12/10/2024 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
match deyiminin genel biçimi şöyledir:
match <ifade>:
case <kalıp>:
<suit>
case <kalıp>:
<suit>
case <kalıp>:
<suit>
....
2024-11-07 13:00:04 +03:00
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 bir case bölümündeki kalıp uyuşursa ilk uyuşumun ilk sağlandığı case bölümündeki
2024-07-15 13:23:34 +03:00
suite'i çalıştırır.
2024-11-07 13:00:04 +03:00
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ı da 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'un 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. Örneğin:
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ş')
Burada eğer a ifadesi hangi değer eşitse o değeri ilişkin case bölüünün suit'i çalıştırılır.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
match deyiminde case bölümleri aynı girinti düzeyine sahip olmak zorundadır. Ancak farklı case bölümlerinin suit'lerinin
aynı girinti düzeyine sahip olması gerekmez.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
Python'un match deyiminde "default" bölüm yoktur. Ancak onunla aynı anlama gelen _ kalıbı vardır. Bu kalıp eğer yukarıdaki
2024-07-15 13:23:34 +03:00
kalıpların hiçbiri uyum sağlamazsa uyum sağlar. _ kalıbı case bölümlerinin sonuna yerleştirilmek zorundadır. (C, C++,
2024-11-07 13:00:04 +03:00
Java ve C# gibi dillerde default bölümün sonra bulundurulmak zorunda olmadığını anımsayınız.)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
kalıplar değişik biçimde oluşturulabilmektedir. case bölümlerinde kullanılan kalıplar şunlardır:
- as Kalıbı (As Pattern)
- Veya Kalıbı (Or Pattern)
- Sabit Kalıbı (Literal Pattern)
- Capture Kalıbı (Capture Pattern)
- Joker Kalıbı (Wildcard Pattern)
- Değer Kalıbı (Value Pattern)
- Grup Kalıbı (Group Pattern)
- Dizilim Kalıbı (Sequence Pattern)
- Sözlük Kalıbı (Mapping Pattern)
- Sınıf Kalıbı (Class Pattern)
Biz kurusumuzda bu kalıpları karışık sırada gözden geçireceğiz. Deyimin ayrıntılııklaması PEP 634'te öğretici
örnekler (tutorial) PEP 635'te verilmiştir.
match deyiminde önce yorumlayıcı match anahtar sözcüğünün yanındaki ifadenin değerini elde eder. Sonra sırasıyla
yukarıdan aşağıya doğru bu değerle uyuşan bir case bölümü var mı diye case bölümlerini gözden geçirir. Akış ilk
uyuşan case bölümüne aktarılmaktadır. O case bölümündeki suite çalıştırıldıktan sonra match deyimim çalışması biter.
Program sonraki deyimle çalışmaya devam eder. Python'da aynı ifadeye uyum sağlayan birden fazla case bölümünün
bulunabileceğine dikkat ediniz. Statik tür sistemine sahip programlama dillerinde genel olarak böyle bir söz konusu
değildir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
Sabit kalbında match anahtar sözcüğünün yanındaki ifadenin türü int ise case ifadelerinin de int türdne olması
gerekmektedir. match anahtar sözcüğünün yanındaki ifadenin türü float ise case bölümündeki sabit int ya da float
olabilir. Anımsanacağı gibi float değerlerin tam eşitliğinin karşılaştırılması sorunlu bir durumdur. Bu nedenle her
ne kadar yasak değilse de casr bölümlerinde float sabitlerin kullanılması iyi bir teknik değildir.
match anahtar sözcüğünün yanındaki ifade case bölümünde sabit kalıbı kullanılıyorsa == operatöryle karşılaştırılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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}')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Veya kalıbında kalıbı parantez içerisine alarak case bölümüne bir "as" cümleceği de ekleyebiliriz. Eklediğimiz bu as
cümleceğini bir değişken izlemelidir. Bu bu değişken hangi veya kalıbı uyuşum sağladıysa onun değerini barındırır.
Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
case ('del' | 'erase' | 'remove') as as_cmd:
print(f'{as_cmd} executes...')
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Burada "del", "erase" ya da "remove" komutlarından hangisi yazılmışsa as_cmd onu belirtecektir. 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ış olacağına dikkat ediniz. Bu nedenle buradaki
as değişkenini match deyimi dışında kullanırken dikkat ediniz.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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ğinin gereksiz olduğunu düşünebilirsiniz. Ancak as cümleceği daha geneldir ve diğer kalıplarda
da kullanılabilmektedir. Diğer kalıplarda as cümleciği faydalı durumlara yol açabilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
Burada "list" ve "tuple" türlerini dizilm türü olarak 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 ya da demet
olabilir. match anahtar sözcüğünün yanındaki ifade ile case anahtar sözcüğünün yanındaki ifadeninin aynı türden olması da
gerekmemektedir. Bu durumda dizilim kalıbında case anahtar sözcüğünün yanındaki ifade aşağıdaki biçimlerden birine ilişkin
olabilir:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
örnekle örtüşmemektedir. Dizilim kalıplarında as cümleceği match anahtar sözcüğünün yanındaki ifade hangi türden dizilim
olursa olsun her zaman bir listedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]}')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
case ['del', path]:
pass
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
['del', 'a.txt']
('del', 'b.txt')
['del', 123]
...
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
['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]}')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Dizilim kalıbında dizilimin yalnızca bir elemanı *'lı bir isimden oluşabilir. *'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. Dizilimde yalnızca tek bir *'lı eleman belirtilebilir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a = [10, 20, 30, 40, 50]
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi burada dizilim ne olursa olsun *'lı eleman her zaman liste olur. Yukarıda da belirttiğimiz
gibi 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:
2024-07-15 13:23:34 +03:00
a = [10, 20, 30, 40, 50]
match a:
case 10, *others, 50:
print(others) # [20, 30, 40]
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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]
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
"capture pattern" her şeye uyum sağladığı için case bölümlerinin sonuna yerleştirilmek zoundadır. Eğer "capture pattern"
case bölümlerinin sonuna yerleştirilmezse sentaks hatası oluşur. Ancak alt tireli case özel bir anlamdadır. Buradaki
alt tire bir değişken belirtmemektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Burada bir noktaya dikkat çekmek istiyoruz. Örneğin biz match anahtar sözcüğünün yaznına bir list nesnesi getirmiş olalım.
Burada dizilim kalbı oluşturabilmek için case anahtar sözcüğünün yanındaki liste ya da demet açıkça parantezlerle ya da
virgülle ayrılmış değerlerle belirtilmiş olması gerekir. Buraya eğer bu türlere ilişkin bir değişken getirilirse bu case
kalıbı artık dizilim kalıbı olmaz, capture kalıbı olur. Örneğin:
a = [1, 2, 3, 4, 5]
b = [1, 2, 3, 4, 5, 6]
match a:
case [1, 2, 3, *args]:
print('sequence pattern')
case b:
print('capture pattern')
Buarada "case b" kalıbındaki b bir liste nesnesi olsa da bu kalıp bir dizilim kalıbı değildir, capture kalıbıdır.
Dizilim kalbındaki listelerin ve demetlerin doğrudan parantezlerle ya da virgüllerle ayrılmış değerlerle belirtilmesi
gerekmektedir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Diğer bir case kalıbı da "sözlük kalıbı (mapping pattern)" denilen kalıptır. Bu kalıpta match yanındaki ifade bir sözlük
türündendir. Bu durumda case anahtar sözcüğünün yanında da küme parantezleriyle sözlük belirten bir ifade bulunmalı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ükte belirtilen elemanların uyuşumudur. Uyuşum hem anahtar
hem değer ile yapılmaktadır. Örneğin:
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
match d:
case {'ali': 10, 'fatma': 50}:
print('bir')
case {'sacit': 100, 'hüseyin': 200}:
print('iki')
Burada birinci case kalıbı uyum sağlayacaktır. Çünkü bu case kalbındaki sözlük ifadesind ebulunan anahtar-değer
çiftlerinin hepsi match anahtar sözcüğünün yanındaki sözlükte bulunmaktadır. Sözlükteki sıraların bir önemi yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Sözlük kalıbında değerde alt tire kullanılırsa değerin her zaman uyuştuğu kabul edilir. Ancak buradaki alt tire
bir değişken anlamına gelmemektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
kullanılabilmektedir, anahtarlarında kullanılamamaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
match d:
case {'ali': 10, 'veli': _}:
print('match edecek') # match edecek
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Aslında kalıptaki sözlüğün değer kısmında bir değişken ismi de belirtilebilir. Bu durumda anahtar uygunsa değer her zaman
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Burada 'veli' anahtarı uyuşursa bunun değeri de uyuşmuş kabul edilir. Dolayısıyla 'veli' anahtarının değeri x değişkenine
yerleştirilecektir. Ancak anahtar yerine bir değişken ismi getirilememektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
match d:
case {'ali': 10, 'veli': x}:
print(x) # 20
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ştirilmektedir. Ancak **'lı değişkenin bir tane olması ve sözlüğün sonunda
bulunması zorunludur. Örneğin:
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
match d:
case {'selami': 30, 'fatma': 50, **others}:
Burada d sözlğünün diğer elemanları bir sözlük nesnesine yerleştirilip o sözlük nesnesi de others elemanına atanacaktır.
Yani yukarıkdai case kalıbı uyuşum sağlayavak ve others değişkeninde {'ali': 10, 'veli': 20, 'ayşe': 40} sözlüğü
bulunacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
dikkat ediniz. Aslında *'lı ve **'lı değişkenler zaten Python'un ileride göreceğimiz başka konularında da karşımıza çıkacaktır.
2024-07-15 13:23:34 +03:00
match deyimi dile eklandiğinde zaten var olan *'lı ve **'lı sentakslar burada da kullanılmaya başlanmıştır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
match deyiminde case bölümlerinde "koruma (guard)" da oluşturulabilmektedir. case bölümlerinde if anahtar söcüğüyle
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
case <kalıp> if <koşul>:
...
Ö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ı
2024-11-07 13:00:04 +03:00
gerekmektedir. Tabii buradaki if cümleceği if deyimi anlamına gelmemektedir. Buradaki if cümleceğinde yalnızca
bir koşul belirtilebilir. Bu cümleceğin else kısmı olamaz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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...')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Koşul operatörü (conditional operator) üç operandlı (ternary) bir operatördür. Operatör if deyimine benzemekle birlikte
deüer üretmektedir. 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:
2024-07-15 13:23:34 +03:00
<ifade1> if <ifade2> else <ifade3>
2024-11-07 13:00:04 +03:00
Koşul operatörü şöyle çalışmaktadır: if anahtar sözcüğünün sağındaki ifade (ifade2) 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. Örneğin:
b = 100 if a % 2 == 0 else 200
Burada koşul operatöründen eğer a çift ise 100 değeri, tek ise 200 değeri elde edilecektir. Elde edilen bu değer de
b'ye atanmıştır Burada if anahtar sözcüünü if deyimiyle karıştırmayınız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
Ancak yukarıdaki ifade daha kısa ve daha özlü biçimde aynı işlemi yapmaktadır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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!
2024-11-07 13:00:04 +03:00
Koşul operatörü biile r koşula bağlı olarak elde edilen değerin ifadenin devamında kullanılması gerekir. Yukarıda gibi bir
işlem geçerli olsa da kötü bir tekniktir. Çünkü burada koşul operatörü if gibi kullanılmıştır. Buradan elde edilen değerden
faydalanılmamıştır.
Yukarıda da belirtitğimiz gibi 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:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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 ' ')
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
28. Ders 13/10/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
Burada val çift ise result değişkenine 100 değeri, val tek ise 200 + 300 değeri atanacaktır. Eğer buradaki + operatörü
2024-07-15 13:23:34 +03:00
koşul operatöründen ayrıştırılmak isteniyorsa parantezler kullanılmalıdır. Örneğin:
2024-11-07 13:00:04 +03:00
result = (100 if val % 2 == 0 else 200) + 300
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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örleri koşul operatörünün operand'ları olmaktan çıkartmak için parantezlerin kullanılması gerektiğine
dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Örneğin iki değerden büyük olanını bir değişkene atamak istediğimizde koşul operatörünü tercih edebiliriz:
maxval = a if a > b else b
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = int(input('Bir değer giriniz:'))
b = int(input('Bir değer giriniz:'))
result = a if a > b else b
print(result)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii yukarıdaki örnekte aslında biz büyük olan değeri doğrudan da print ile yazdırabilirdik:
print(a if a > b else b)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = int(input('Bir değer giriniz:'))
b = int(input('Bir değer giriniz:'))
print(a if a > b else b)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aslında bir grup değer içerisindeki en büyük ve en küçük değerler Python'un standart kütüphanesinde max ve min isimli
iki built-in fonksiyon ile de bulunabilir. Bu fonksiyonlar bir grup değeri argüman olarak alıp onların en büyük ya da
en küçük değerini vermektedir. Örneğin:
>>> max(32, 12, 5, 21)
32
>>> min(32, 12, 5, 21)
5
Bu fonksiyonlara biz dolaşılabilir bir nesne verirsek bu fonksiyonlar bu dolaşılabilir nesnedeki en büyük ya da en küçük
elemanları bulmaktadır. Örneğin:
>>> a = [3, 7, 21, 6, 14]
>>> max(a)
21
>>> min(a)
3
Biz bu fonksiyonları birden fazla argümanla çağırırsak bu fonksiyonlar argümanları karşılaştırmaktadır. Örneğin:
>>> a = [3, 7, 21, 6, 14]
>>> b = [4, 9, 12, 6]
>>> c = [3, 2, 17]
>>> max(a, b, c)
[4, 9, 12, 6]
>>> min(a, b, c)
[3, 2, 17]
Burada listelerin karşılaştırıldığına dikkat ediniz.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Koşul operatörü de iç içe kullanılabilir. İç içe kullanımda parantez kullanmak zorunlu değildir. Ancak parantezsiz
2024-11-07 13:00:04 +03:00
yazım kodu okuyanlar için kafa karıştırıcı olabilmektedir. Örneğin a, b, ve c değerlerinin en büyüğünü elde etmek isteyelim:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
result = a if a > c else c if a > b else b if b > c else c
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Burada koşul operatörünün doğruysa kısmında da yanlışsa kısmında da başka bir koşul operatörü vardır. İfadenin yazımı
geçerlidir. Ancak bu tğr durumlarda gerekmediği halde parantezleri kullanarak kodumuzun daha anlaşılabilir olmasını
sağlamalıyız:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
result = (a if a > c else c) if a > b else (b if b > c else c)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
29. Ders 19/10/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Ş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 <fonksiyon ismi> ([parametre listesi]): <suite>
2024-11-07 13:00:04 +03:00
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çermelidir. Örneğin:
2024-07-15 13:23:34 +03:00
def foo():
print('foo')
Burada fonksiyonun ismi "foo" biçimindedir. Fonksiyonun parametresi yoktur. Örneğin:
def bar(a, b):
print(a, b)
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Biz ö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. Özellikle "foo" ve "bar" isimleri son
yirmi yıldır bu amaçla çokça kullanılır olmuştur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
önce bir "fonksiyon nesnesi (function object)" oluşturur. 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:
2024-07-15 13:23:34 +03:00
def foo():
print('foo')
2024-11-07 13:00:04 +03:00
Bu tanımlamayı yorumlayıcı gördüğünde önce bir fonksiyon nesnesi oluşturup foo'nun kodlarını (yani suit deyimlerini) o
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
foo()
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> def foo():
... print('foo')
...
>>> id(foo)
4312805088
>>> type(foo)
<class 'function'>
>>> 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
2024-11-07 13:00:04 +03:00
Fonksiyonların da aslında böyle normal değişkenler gibi davranmasına ve atanabilmesine 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
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]
2024-11-07 13:00:04 +03:00
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. Örneğin:
def foo():
print('foo')
return 100
Bu fonksiyon çağrıldığında ekrana "foo" yazısı çıacak ve çağrımdan 100 değeri elde edilecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def foo():
print('foo')
return 100
a = foo()
print(a)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Fonksiyon çağırma operatörü tek operand'lı sonekk bir operatördür ve anımsanacağı gibi fonksiyon çağırma operatörü
öncelik tablosunun en yüksek düzeyinde bulunmaktadır. Örneğin:
2024-11-07 13:00:04 +03:00
result = foo() * 2
Burada önce fonksiyon çağrılır, geri dönüş değeri elde edilir. Bu değer 2 ile çarpılır ve result değişkenine atanır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
C, C++, Java ve C# gibi statik tür sistemine sahip olan programlama dillerinde 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. Fonksiyon içerisinde koşula bağlı olarak farklı return deyimleri
bulundurulabilir. Fonksiyon da farklı türden değerlerle geri dönebilir.
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
Tabii fonksiyonun bazı akışları return deyimini görürken bazı akışları görmeyebilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def foo():
print('foo')
result = foo()
print(result) # None
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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. Eğer return anahtar sözcüğünün yanına bir iafde yazılmazsa sanki None yazılmış gibi
etki oluşmaktadır. Yani başka bir deyişle:
2024-07-15 13:23:34 +03:00
return
kullanımı ile aşağıdaki kullanım eşdeğerdir:
return None
#------------------------------------------------------------------------------------------------------------------------
def foo():
print('foo')
return
result = foo()
print(result) # None
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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
listelere tercih edilmelidir. Örneğin:
2024-07-15 13:23:34 +03:00
def foo():
print('foo')
return 10, 20
2024-11-07 13:00:04 +03:00
Burada foo fonksiyonu bir demetle geri dönmektedir. Yani biz fonksiyonu çağırdığımızda bir demet elde ederiz:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
t = foo()
print(t[0], t[1])
Tabii geri döndürülen demet doğrudan açılabilir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a, b = foo()
print(a, b)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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!')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii fonksiyon bir listeyle, bir kümeyle ya da bir sözlükle de geri dönebilir. Aşağıdaki örnekte 0'dan klavyeden girilen
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Fonksiyonların dış dünyadan aldıkları değerlere "parametre (parameter)" denilmektedir. Fonksiyonlar parametrelere sahip
olabilirler. Bu durumda parametrelerin isimleri parametre parantezinin içerisinde ',' atomu ile ayrılarak belirtilmektedir.
Örneğin:
2024-07-15 13:23:34 +03:00
def foo(a, b):
pass
Burada a ve b foo fonksiyonunun parametreleridir. Parametre terimi yerine bazen "parametre değişkeni" terimi de kullanılabilmektedir.
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
def foo(a, b):
pass
foo(10 + 20, 30 + 40)
2024-11-07 13:00:04 +03:00
Burada önce 10 ile 20 toplanıp değeri 30 olan bir int nesne oluşturulur. Sonra 30 ile 40 toplanıp değeri değeri 70 olan bir
int nesne oluşturulur. Sonra bu int nesnelerin adresleri sırasıyla a ve b değişkenlerine atanır. Örneğin:
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Tabii aynı durum return işlemi için de geçerlidir. Yani return işleminde de aslında çağıran fonksiyona iletilen değer
return ifadesindeki nesnenin adresidir. Örneğin:
def foo():
a = 10
print(id(a))
return a
Biz burada foo fonksiyonunun geri dönüş değerini aşağıdaki gibi bir değişkene atamış olalım:
result = foo()
print(id(result))
Aslında burada result değişkenine return ifadesindeki nesnenin adresi atanmaktadır. Dolayısıyla program çalıştırıldığında
ekrna aynı id değerleri basılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
2024-11-07 13:00:04 +03:00
Buradaki a ile fonksiyonun parametre değişkeni olan a tamamen farklı iki değişkendir.
2024-07-15 13:23:34 +03:00
İ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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Fonksiyonların parametre değişkenleri onlar hangi türden argümanla çağrılmışsa o türden olurlar. Yani onların belli bir
türü yoktur. Daha önce de belirttiğimiz gibi statik tür sistemine sahip C, C++, Java, C# gibi dillerde parametre
2024-07-15 13:23:34 +03:00
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])
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Pekiyi biz neden fonksiyon yazmak isteriz? İşte fonksiyon oluşturmanın gerekçeleri şunlardır:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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şik projelerde kullanabiliriz. Örneğin Python'un standart kütüphanesindeki fonksiyonlar çok değişik projelerde çağrılarak
kullanılabilmekteir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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şıklaşmasına 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.
2024-07-15 13:23:34 +03:00
3) Karmaşık bir problem parçalarına ayrılarak daha kolay çözülebilir. Parçalarına ayırma işlemi genellikle fonksiyonlar
2024-11-07 13:00:04 +03:00
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
2024-07-15 13:23:34 +03:00
ç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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Fonksiyonlar listelerin, demetlerin sözlüklerin elemanları olabilir. Örneğin:
def add(a, b):
return a + b
def mul(a, b):
return a * b
def sub(a, b):
return a - b
def div(a, b):
return a / b
fs = [add, mul, sub, div]
for f in fs:
result = f(20, 10)
print(result)
Burada listenin elemanları fonksiyon nesnelerini (yani onların adreslerini) tutmaktadır. [] operatörü ile () operatörü
soldan-sağa aynı öncelik grubunda bulunmaktadır:
fs = [add, mul, sub, div]
for i in range(len(fs)):
result = fs[i](20, 10)
print(result)
fs[i](20, 10) ifadesine dikkat ediniz. Burada önce fs listesinin i'inci indeksli elemanına erişilecek sonra onun
belirttiği fonksiyon çağrılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir süre önceye kadar fonksiyon nesneleri hash'lenebilir değildi. Dolayısıyla sözlüklere anahtar yapılamıyordu. Sonra
hashable hale getirildi. Artık fonksiyon nesneleri sözlüklere anahtar yapılabilmektedir. Tabii daha çok fonksiyonların
sözlüklerde anahtar değil değer olarak kullanıldığını görürüz. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def dir_proc():
print('dir command')
def copy_proc():
print('copy command')
def del_proc():
print('del command')
cmd_dict = {'dir': dir_proc, 'copy': copy_proc, 'del': del_proc}
Burada komut yazıları anahtar onların çağrılması düşünülen fonksiyonlar da değer olarak sözlükte bulundurulmuştur.
#------------------------------------------------------------------------------------------------------------------------
def dir_proc():
print('dir command')
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def copy_proc():
print('copy command')
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def del_proc():
print('del command')
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
cmd_dict = {'dir': dir_proc, 'copy': copy_proc, 'del': del_proc}
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
while True:
cmd = input('CSD>').strip()
if cmd == 'quit':
break
if cmd not in cmd_dict:
print('invalid command!')
continue
cmd_dict[cmd]()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Aşağıdaki örnekte ikinci derece bir denklemin köklerini buluna bir fonksiyon yazılmıştır. Fonksiyon eğer kök yoksa None
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir algoritmanın düz mantık çözümüne İngilizce "brute force" çözüm de denilmektedir. Örneğin bir sayının asal olup
olmadığını test eden bir fonksiyon yazacak olalım. Fonksiyon parametre olarak bir sayı alsın. Eğer sayı asalsa True
değerine, asal değilse False değerine geri dönsün. Burada akla ilk gele düz mantık çözüm sayının 2'den itibaren herhangi
bir sayıya tam bölünüp bölünmediğinin kontrol edilmesidir:
def isprime(val):
for i in range(2, val - 1):
if val % i == 0:
return False
return True
Burada eğer sayı herhangi bir sayıya tam bölünüyorsa fonksiyon False ile geri döndürülmüştüri.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yukarıdaki fonksiyonu kullanarak 100'e kadar tüm asal sayıları aşağıdaki gibi yazdırabiliriz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
kadar kontrol yapılması yeterlidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
2024-11-07 13:00:04 +03:00
tarafından yapılmaktadır. PSF içerisinde çeşitli alt gruplar vardır. Dile ilişkin sentaks ve semantik yenilikler ve
2024-07-15 13:23:34 +03:00
standart kütüphaneye yapılacak eklemeler "PEP (Python Enhancement Proposals)" denilen dokümanlarla yürütülmektedir.
2024-11-07 13:00:04 +03:00
Bir kişi taslak biçiminde bir PEP dokümanı hazırlar. Önerisini oraya belli bir formatta yazar. Sonra bu öneri tartışılıp
2024-07-15 13:23:34 +03:00
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ı
2024-11-07 13:00:04 +03:00
Python diline ilişkin pek çok "gerekçeyi (rationale)" de barındırmaktadır. PEP dokümanlarına numaralar verilmiştir.
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
python'un tüm PEP dokümanlarına aşağıdaki bağlantıdan erişebilirsiniz:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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 grupları 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:
2024-07-15 13:23:34 +03:00
https://peps.python.org/pep-0008/
https://google.github.io/styleguide/pyguide.html
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
30. Ders 20/10/2024 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:
2024-11-07 13:00:04 +03:00
def foo(a, b, c=10):
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Bu çağrı aşağıdakiyle tamamen eşdeğer etkiye yol açacaktır.
2024-07-15 13:23:34 +03:00
foo(100, 200, 10)
2024-11-07 13:00:04 +03:00
Ancak default değer almış olan parametre için argüman girilirse artık o default değerin bir etkisi kalmaz. Örneğin:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
def foo(a, b, c=10, d=20):
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Yazım stili olarak PEP8 göre parametre ismi ile default değer arasında boşluk karakteri önermemektedir. Örneğin:
def foo(a, b=10, c=20):
pass
Siz PEP8'in tavsiyesine uyabilirsiniz ya da da uymayıp '=' atomunun iki yanına boşluk birakabilirsiniz:
def foo(a, b = 10, c = 20):
pass
Biz önceki kuraslarda PEP8'e uymayıp yukarıdaki gibi '=' atomunun iki yanına boşluk bırakıyorduk. Ancak bu kursta
PEP8 önerisini de kullanacağız.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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 isimli parametredir.
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ğerini 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def foo(a, b=10, c): # geçerli değil!
2024-07-15 13:23:34 +03:00
pass
Böyle bir fonksiyon tanımlaması geçerli değildir. Programcının fonksiyonu şöyle tanımlaması gerekirdi:
2024-11-07 13:00:04 +03:00
def foo(a, c, b=10):
2024-07-15 13:23:34 +03:00
pass
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
def banner(s, ch='-'):
print(ch * len(s))
print(s)
print(ch * len(s))
banner('ankara')
banner('ankara', '*')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def foo(a=[1, 2, 3]):
2024-07-15 13:23:34 +03:00
print(a, id(a))
2024-11-07 13:00:04 +03:00
Burada [1, 2, 3] elemanlarına sahip olan liste nesnesi yorumlayıcı foo fonksiyonunu gördüğünde 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:
2024-07-15 13:23:34 +03:00
foo()
foo()
foo()
Buradan elde edilen örnek bir çıktı şöyledir:
[1, 2, 3] 1560817328576
[1, 2, 3] 1560817328576
[1, 2, 3] 1560817328576
2024-11-07 13:00:04 +03:00
Aslında Python yorumlayıcıları parametre değişkenlerine verilen ilkdeğerleri (yani onların adreslerini) fonksiyon için oluşturdukları
fonksiyonun nesnesinin içerisinde saklamaktadır.
2024-07-15 13:23:34 +03:00
Benzer biçimde örneğin:
def foo():
print('foo')
return 10
2024-11-07 13:00:04 +03:00
def bar(a=foo() + 2):
2024-07-15 13:23:34 +03:00
pass
2024-11-07 13:00:04 +03:00
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. Yani program yukarıdaki kadar olsa bile foo fonksiyonu çağrılacaktır. Çünkü yorumlayıcı bar fonksiyonunun
tanımlamasını gördüğünde a parametre değişkenine verilecek ilkdeğeri daha bar çağrılmadan belirleyip fonksiyon nesnesinin
içerisinde saklamaktadır. Şimdi bar fonksiyonunu çağırmış olalım:
2024-07-15 13:23:34 +03:00
bar()
bar()
bar()
2024-11-07 13:00:04 +03:00
Artık foo fonksiyonu çağrılmayacaktır. Zaten bar fonksiyonun parametre değişkenine atanacak default değer bar fonksiyonu
görüldüğünde hesaplanıp saklanmıştır.
2024-07-15 13:23:34 +03:00
Bu tür durumlarda default argüman olarak "değiştirilebilir (mutable)" nesne kullanırken dikkat etmek gerekir. Örneğin:
2024-11-07 13:00:04 +03:00
def foo(a=[]):
a.append(10)
print(a)
foo()
foo()
foo()
Burada yorumlayıcı foo fonksiyonunu gördüğünde henüz fonksiyon çağrılmadan parametre değişkeni için listeyi yaratmıştır.
Dolayısıyla bu fonksiyon her çağrıldığında aslında aynı listeye elemen eklemektedir. Python programcıları bu mantıksal
yanılgıları sıkça yapmaktadır. Örneğin:
def foo(a=[]):
2024-07-15 13:23:34 +03:00
a.append(10)
return a
2024-11-07 13:00:04 +03:00
Burada foo fonksiyonu aslında hep aynı listeyi geri döndürmektedir. Aşağıdaki örneğe dikkat ediniz:
x = []
def foo(a=x):
a.append(10)
foo()
foo()
2024-07-15 13:23:34 +03:00
foo()
2024-11-07 13:00:04 +03:00
print(x) # [10 10 10]
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Burada a parametre değişkenine x listesinin adresi yorumlayıcı foo fonksiyonunu gördüğünde atanmaktadır. Dolayısıyla
fonksiyon her çağrıldığında x listesine ekleme yapılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
2024-11-07 13:00:04 +03:00
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
i = start
while i < stop:
print(i, end=' ')
i += step
print()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def disp_range(start, stop=None, step = 1):
if stop == None:
stop = start
start = 0
2024-11-07 13:00:04 +03:00
i = start
while i < stop:
print(i, end=' ')
i += step
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
Oysa Python'da böyle bir durum yoktur. Yukarıdaki kod parçasında foo fonksiyonunu çağırırsak artık ikinci fonksiyonu
çağırmış oluruz. Yani yukrıdaki işlem aslında aşağıdkai gibi bir işlemdir:
foo = 10
foo = 20
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:
2024-11-07 13:00:04 +03:00
def foo(a, b=a): # geçersiz!
2024-07-15 13:23:34 +03:00
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ü
2024-11-07 13:00:04 +03:00
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. Örneğin:
def banner(s, ch='-'):
print(ch * len(s))
print(s)
print(ch * len(s))
banner('ankara') # program normal çalışacak
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!
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def banner(s, ch='-'):
print(ch * len(s))
print(s)
print(ch * len(s))
2024-11-07 13:00:04 +03:00
banner('ankara') # program normal çalışacak
2024-07-15 13:23:34 +03:00
banner('ankara', 10) # program çökmeyecek ama istenildiği gibi çalışmayacaktır
2024-11-07 13:00:04 +03:00
banner(10) # burada exception oluşacak, çünkü len fonksiyonu int türüne uygulanamaz!
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
if isinstance(a, (int, float, complex)):
pass
2024-11-07 13:00:04 +03:00
Burada a değişkeni int, float ya da complex türündense isinstance True değerine geri dönecektir. Python 3.10 ile birlikte
birden fazla türün kontrolü artık '|' operatörü ile de yapılabilmektedir. Örneğin:
if isinstance(a, int|float|complex):
pass
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Python programcıları eğer fonksiyonun parametreleri üzerinde tür kontrolü yapıp onların uygun türlerden 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:
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
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 sorumluluğundadır. Yani eğer fonksiyon doğru türlerle çağrılmazsa
sonucuna onu çağıran programcı katlanacaktır.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
Bir fonksiyon çağrılırken arümanlara isim verilerek o argümanların hangi parametreler için girildiği belirtilebilmektedir.
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Pozisyonel argümanlarla isimli argümanlar fonksiyon çağrısında bir arada kullanılabilir. 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:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
İsimli argümana verilen değer aynı isimli bir değişken de olabilir. Fonksiyonun parametre değişkenleri yalnızca
fonksiyon içerisinde kullanıldığı için bu durum bir sorun oluşturmamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def foo(a):
print(a)
a = 10 # bu a ile parametre değişkeni olan a farklı değişkenler
foo(a=a) # geçerli
Burada foo(a=a) ifadesi biraz tuhaf gözükmekle birlikte geçerlidir. Bu durumda yorumlayıcı '=' atomunun solundaki
değişkenin çağrılan fonksiyonun fonksiyonun parametre değişken ismi olduğunu ona atanan a'nın ise normal değişken
olduğunu düşünecektir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
İsimli argümanlara iki nedenden dolayı gereksinim duyulmaktadır:
2024-11-07 13:00:04 +03:00
1) Çok sayıda default değer alan parametre değişkenlerinin bulunduğu bir fonksiyonda default değer almış bir parametre
değişkenine istenilen bir değeri pratik bir biçimde geçirebilmek için. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
kursumuzda bu yazım biçimini tercih edeceğiz. Parametre değişkenlerine default değer verirken bazı programcılar boşluk
bırakırken bazıları bırakmamaktadır. Python yazım stilini anlatan "PEP 8" dokümanlarında her iki durumda da '=' atomunun
iki yanında boşluk bırakılmamaktadır. Bu durum "Google Python Style Guide" dokümanında da aynı biçimdedir. Örneğin:
2024-07-15 13:23:34 +03:00
def disp(a, base=10):
pass
disp(100, base=16)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
def foo(a, b, *, c, d):
pass
2024-11-07 13:00:04 +03:00
Parametredeki '*' gerçek bir parametre değildir. Yani yukarıdaki foo fonksiyonunun 4 parametresi vardır. Buradaki '*'
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
için argümanların pozisyonel olarak girilmesi gerektiği" anlamına gelmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def foo(a, b, /, c=100, d=200):
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Bu ö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
2024-07-15 13:23:34 +03:00
pozisyonel argümanlarla, d ve e parametrelerinin isimli argümanlarla kullanılması zorunludur. Ancak c parametresi pozisyonel
2024-11-07 13:00:04 +03:00
ya da isimli kullanılabilir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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 de 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:
2024-07-15 13:23:34 +03:00
def sin(x, /):
pass
2024-11-07 13:00:04 +03:00
Parametre listesinde / karakterinin kullanılması özelliği Python 3.8 ile dile eklenmiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
31. Ders 26/10/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Fonksiyonların *'lı parametreleri olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
def foo(a, b, *c):
pass
2024-11-07 13:00:04 +03:00
Buradaki *'lı parametreyi önceki paragraflarda görmüş olduğumuz yalnızca *'dan oluşan parametre belirteciyle karıştırmayınız:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
def foo(a, b, *, c):
2024-07-15 13:23:34 +03:00
pass
2024-11-07 13:00:04 +03:00
Burada *'ın yanında bir değişken ismi yoktur. Dolayısıyla bu gerçek bir parametre değildir. Buradaki * bunun sağındaki
parametrelerin isimli argümanlarla çağrılacağını belirtir.
Bir fonksiyonun *'lı parametresi olacaksa yalnızca bir tane olmak zorundadır. Örneğin:
def foo(a, b, *c, *d): # error! *'lı parametreden yalnızca bir tane olabilir
pass
Fonksiyonların *'lı parametreleri genellikle parametre listesinin sonunda bulundurulur. Ancak böyle bir zorunluluk yoktur.
2024-07-15 13:23:34 +03:00
def foo(a, *b, c): # geçerli
pass
2024-11-07 13:00:04 +03:00
*'lı parametreler sıfır tane ya da daha fazla argümanla eşleşmektedir. Yani biz bir *'lı parametre için 0 tane ya da daha
fazla argüman girebiliri. Örneğin:
def foo(a, b, *c):
pass
foo(10, 20) # geçerli
foo(10, 20, 30) # geçerli
foo(10, 20, 30, 40, 50) # geçerli
*'lı parametrelerin isimlerine bu * dahil değildir. Buradaki * yalnızca bir belirteçtir. Örneğin:
def foo(a, b, *c):
pass
Burada parametre değişkeninin ismi c'dir, *c değildir.
Yorumlayıcı *'lı paramtre için girilen argümanların hepsini bir demete yerleştirip *'lı parametreye o demeti geçirmektedir.
Yani fonksiyonların *'lı parametreleri her zaman demet türündendir. Örneğin:
2024-07-15 13:23:34 +03:00
def foo(a, b, *c):
pass
foo(10, 20, 30, 40, 50)
2024-11-07 13:00:04 +03:00
Burada 10 değeri a parametresine, 20 değeri b parametresine aktarılacaktır. 30, 40 ve 50 değerleri bir demet haline getirilerek
bu demet c parametresine ktarılacaktır. Örneğin:
2024-07-15 13:23:34 +03:00
foo(10, 20)
2024-11-07 13:00:04 +03:00
Burada yine 10 değeri a parametresine, 20 değeri b parametresize aktarılacak, c paramatre değişkenine de içi boş bir
demet geçirelecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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 = ()
#------------------------------------------------------------------------------------------------------------------------
*'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ış
2024-11-07 13:00:04 +03:00
Burada 20 ve 30 c için girilmiş gibi olmaktadır. Dolayısıyla c parametre 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:
2024-07-15 13:23:34 +03:00
foo(10, 20, 30, 40, c=50) # geçerli
2024-11-07 13:00:04 +03:00
Burada 10 argümanı a ile, (20, 30, 40) argümanları b ile eşleşmektedir. 50 argümanı *'lı parametre ile eşleşmeyecektir.
2025-01-23 02:00:34 +03:00
Çünkü açıkça isimli bir biçimde belirtilmiştir. Dolayısıyla 50 değeri c argümanı ile eşleşecektir. Burada her parametre
2024-11-07 13:00:04 +03:00
değişkeni daha önce de belirttiğimiz gibi en az bir kez ve en fazla bir kez değer almış durumdadır. Örneğin:
2024-07-15 13:23:34 +03:00
def foo(a, *b, c = 100):
pass
foo(10, 20, 30) # geçerli, c de değer almış
2024-11-07 13:00:04 +03:00
Gördüğünüz gibi *'lı parametre, parametre listesinin sonunda değilse *'lı parametrenin sağındaki parametrelerin ya default
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
*'lı parametrenin her zaman demet belirttiğine dikkat ediniz. Demetler dolaşılabilir nesneler olduğuna göre biz *'lı
parametreyi for döngüsü içerisinde dolaşılbiliriz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
def myprint(*args, end='\n', sep=' '):
for i in range(len(args)):
if i != 0:
print(end=sep)
print(args[i], end='')
2024-07-15 13:23:34 +03:00
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()
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Anımsanacağı gibi Python'un 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önüyordu. Örneğin:
a = [34, 56, 21, 76, 18]
result = max(a)
print(result) # 76
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [34, 56, 21, 76, 18]
result = max(a)
print(result) # 76
result = min(a)
print(result) # 21
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Fakat min ve max fonksiyonlarına birden fazla argüman da girebiliyorduk. Bu durumda bu fonksiyonlar argümanların en büyük ve en
küçük değerlerini elde ediyordu. Örneğin:
result = max(89, 79, 34)
print(result) # 89
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
result = max(89, 79, 34)
print(result) # 89
result = min(4, 67, 1, 68)
print(result) # 1
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Görüldüğü gibi eğer biz min ve max fonksiyonlarına tek eleman girersek o argümanın dolaşılabilir bir nesne 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
elde ederler. 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ı parametre için 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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!
2024-11-07 13:00:04 +03:00
Fonksiyonun *'lı parametreleri default değer de alamaz. Örneğin:
2024-07-15 13:23:34 +03:00
def foo(a, b, *c = (10, 20, 30)): # error! *'lı parametrelere default değer veremeyiz!
pass
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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 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.
(Buradaki "kw" "key word" arguments sözcüklerindne kısaltılmıştır.)
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
def foo(a, *args, **kwargs):
print('a = {}, args = {}, kwargs = {}'.format(a, args, kwargs))
Biz bu fonksiyonu şöyle çağırmış olalım:
2024-11-07 13:00:04 +03:00
foo(10, 20, 30, 40, xx='ali', yy=100)
Bu durumda fonksiyona geçirilen argümanlar şöyle olacaktır:
a = 10, args = (20, 30, 40), kwargs = {'xx': 'ali', 'yy': 100}
2024-07-15 13:23:34 +03:00
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}
2024-11-07 13:00:04 +03:00
Yani biz *'lı ve **'lı parametre değişkenlerini isimli kullandığımızda yorumlayıcı bunları olmayan parametreler
gibi ele almaktadıdr.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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': {}}
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Pekiyi fonksiyonların **'lı parametreleri neden kullanılmaktadır? Bunun üç 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:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
foo(10, 20, r=20)
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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}')
2024-11-07 13:00:04 +03:00
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
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
**'lı parametrelerin diğer bir kullanım nedeni de parametrelerin dinamik bir biçimde oluşturulmasını sağlamaktır.
Örneğin bir fonksiyona biz belli şehirlere ve ilçelere ilişkin değerler geçmek isteyelim:
foo(eskisehir=150, ankara=200, izmir=900)
Burada aslında bu fonksiyona geçilecek değerler öneden belli değildir. Ancak **'lı parametre sayesinde geçilen bu
değerler elde edilip fonksiyon içerisinde kullanılabilir. Örneğin:
def foo(**kwargs):
for key in kwargs:
val = kwargs[key]
....
Tabii bu biimdeki fonksiyonlara biz bu tür argümanları bir yazı ya da sözlük olarak da geçirebiliriz. Örneğin:
foo({'eskisehir: 150, 'ankara': 200, 'izmir': 900});
Zaten **'lı parametre için yorumlayıcı buradaki işlemin aynısını yapmaktadır. Örneğin dict fonksiyonunun da **'lı
bir parametresi vardır. Bu sayede biz sanki isimli argüman giriyormuş gibi sözlük nesnelerini yaratabiliriz. Örneğin:
d = dict(eskiseşir=26, istanbul=34, izmir=35)
print(d) # {'eskiseşir': 26, 'istanbul': 34, 'izmir': 35}
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
**'lı parametre için girilen isimli argümanlardaki isimlerin değişken isimlendirme kurallarına uygun biçimde belirtilmiş
olması gerekmektedir. Örneğin:
def foo(**kwargs):
pass
Biz aşağıdaki gibi çağrıları yapamayız:
foo(100='ali', 200='veli') # geçersiz! 100 ve 200 değişken ismi belirtmez
foo('ali': 100, 'veli': 200) # geçersiz string'ler değiiken ismi olarak kullanılmazlar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Pekiyi biz bir fonksiyonda **'lı parametre 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".
2024-07-15 13:23:34 +03:00
**'lı parametreye örnek olarak matplotlib kütüphanesindeki plot, title ve text gibi fonksiyonlar verilebilir. plot fonksiyonu
2024-11-07 13:00:04 +03:00
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 geri kalan 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.
plot fonksiyonun parametrik yapısı şöyledir:
matplotlib.pyplot.plot(*args, scalex=True, scaley=True, data=None, **kwargs)
Fonksiyonları yazanlar genellikle önemli parametreleri parametre listesinde belirtip diğerlerini **kwargs ile
temsil etmektedir. Dokümanlara bakıldığında kwargs için onlarca parametrenin girilebileceği görülmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
**'lı parametre kullanımının üçüncü nedeni "forwarding (iletme)" yapmak içindir. Buna ilişkin örnek izleyen örneklerde
verilecektir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
32. Ders 27/10/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Fonksiyonları çağırırken argümanların önüne '*' atomu getirilebilir. Bu tür argümanlara "*'lı argümanlar" diyeceğiz.
2024-11-07 13:00:04 +03:00
*'lı argümanlar argüman listesinde birden fazla kez bulundurulabilir ve bunlar argüman listesinde herhangi bir yerde
bulunabilirler. Örneğin:
2024-07-15 13:23:34 +03:00
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,
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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ı argüman olarak girmiş fonksiyona yollar. Yani örneğin:
2024-07-15 13:23:34 +03:00
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')
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Burada 100 değeri a parametresine, 10 değeri b parametresine ve diğerleri de args parametresine demet olarak aktarılacaktır.
Örneğin:
2024-07-15 13:23:34 +03:00
def foo(*args):
pass
Aşağıdaki çağrısı da geçerlidir:
foo(*'ankara')
2024-11-07 13:00:04 +03:00
Örneğin:
print(*'ali', 100, 200, *range(3))
Bu çağrının eşdeğeri şöyledir:
print('a', 'l', 'i', 100, 200, 0, 1, 2)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
2024-11-07 13:00:04 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Ö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:
2024-07-15 13:23:34 +03:00
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=', ')
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
*'lı argüman sarma fonksiyon yazarken "iletme (forwarding)" amaçlı da kullanılabilmektedir. Örneğin biz myprint isimli
2024-11-07 13:00:04 +03:00
fonksiyon yazmak isteyelim. Ancak fonksiyonumuz aldığı parametreleri birtakım şeyler yaptoktann sonra print fonksiyonuna
hiç değiştirmeden yollayacak olsun (forward etsin) Bu işlem ancak argümanda * kullanıalrak yapılabilir.
2024-07-15 13:23:34 +03:00
def myprint(*args):
2024-11-07 13:00:04 +03:00
# ....
2024-07-15 13:23:34 +03:00
print(*args)
2024-11-07 13:00:04 +03:00
Burada biz myprint fonksiyonunu hangi argümanlarla çağırmışsak bu fonksiyon da asıl print fonksiyonunu aynı argümanlarla
çağırmış gibi olacaktır.
2024-07-15 13:23:34 +03:00
myprint(10, 20, 30, 40, 50)
2024-11-07 13:00:04 +03:00
Örneğin:
def foo(a, b, *args):
print(a, b, args)
def bar(a, b, *args):
print('araya giriyoruz')
foo(*args)
bar(10, 20, 30, 40, 50, 60)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
def myprint(*args):
print('araya giriyoruz')
print(*args)
myprint(10, 20, 30)
#------------------------------------------------------------------------------------------------------------------------
Yukarıdaki gibi iletimde bir kusur vardır. Bu iletimde isimli argümanlar iletilemeyecektir. myprint fonksiyonuna
yeniden bakınız:
def myprint(*args):
# ....
print(*args)
Ya biz myprint fonksiyonunu sep ve end parametrelerini kullanarak çağırmak istersek ne olur?
2024-07-15 13:23:34 +03:00
myprint(10, 20, 30, 40, 50, sep=', ', end='*')
2024-11-07 13:00:04 +03:00
Bu durumda myprint fonksiyonunda sep ve end parametreleri olmadığı için error oluşacaktır. Aşağıdaki gibi çözüm bu
örnekte çalışabilise de genel bir çözüm oluşturmaz:
2024-07-15 13:23:34 +03:00
def myprint(*args, sep=' ', end='\n'):
print(*args, sep=sep, end=end)
myprint(10, 20, 30, 40, 50, sep=', ')
2024-11-07 13:00:04 +03:00
Genel çözüm için izleyen bölümde ele alacağımız **'lı argümanlar kullanılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Sözlükler de dolaşılabilir nesneler belirttiğine göre *'lı argüman olarak kullanılabilirler. Örneğin:
d = {10: 'ali', 20: 'veli', 30: 'selami', 40: 'ayşe', 50: 'fatma'}
print(*d) # 10 20 30 40 50
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Kümeler de dolaşılabilir nesneler belirttiği için *'lı argümanlarla kullanılabilir. Ancak kümelerin dolaşımındaki
sıranın belirli olmadığını anımsayınız. Bu nedenle kümeler *'lı argüman kullanımına genellikle uygun değildir. Örneğin:
s = {'ali', 20, 'veli, 40, 5}
print(*s)
Burada değerlerin hangi sırada yazdırılacağının bir garantisi yoktur.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Fonksiyonların **'lı argümanları da olabilmektedir. Bu argümanlar da argüman listesinin herhangi bir yerinde ve birden
fazla kez bulundurulabilmektedir. **'lı argümanlar bir sözlük nesnesi olmak zorundadır. Yani ** atomunun yanında bir
sözlğk nesnesi olmalıdı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.
2024-07-15 13:23:34 +03:00
Yorumlayıcı argümanda ** gördüğünde argümana ilişkin sözlük nesnesini dolaşır. Anahtarları argümanların isimleri, değerleri
2024-11-07 13:00:04 +03:00
de o isimli argümanlara '=' ile verilmiş değerler kabul ederek fonksiyonu çağırır. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
**'lı argümanlar açıldıktan sonra (yani yerine yerleştirildikten sonra) yine toplamda tüm parametre değişkenlerine
bir kez ve yalnızca bir kez değer atanmış olmalıdır. Örneğin:
def foo(a, b, c, d, e, f):
print(f'a = {a}, b = {b}, c = {c}, d = {d}, e = {e}, f = {f}')
d = {'d': 100, 'c': 200, 'f': 300}
foo(10, 20, **d, e=400)
Buradaki foo çağrısı geçerlidir. Bu çağrının eşdeğeri şöyledir:
foo(10, 20, d=100, c=200, f=300, e=400)
Burada tüm kurallar sağlanmıştır. Toplamda parametre değişkenlerine yalnızca bir kez değer verilmiştir. Ayrıca
isimli argümanların sağında da isimli argümanlar bulunmaktadır. Örneğin:
d = {'d': 100, 'f': 200}
k = {'c': 300, 'e': 400}
foo(10, 20, **d, **k)
**'lı argümanlar argüman listesinde birden fazla kez kullanılabilir. Buradaki foo çağrısının eşdeğeri şöyledir:
foo(10, 20, d=100, f=200, c=300, e=400)
Burada yine tüm parametre değişkenleri bir kez ve yalnızca bir kez değer almış durumdadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
**'lı argümanlar sarma fonksiyonlarda "iletme (forwarding)" işlemlerinde de kullanılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
def myprint(*args, **kwargs):
print(*args, **kwargs) # perfect forwarding
myprint(10, 20, 30, sep=', ', end='*')
2024-11-07 13:00:04 +03:00
Burada myprint fonksiyonuna geçirilen tüm argümanlar print fonksiyonuna mükemmel biçimde iletilmektedir. Çünkü yorumlayıcı
ö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.
2024-07-15 13:23:34 +03:00
Aşağıdaki örnekte matplotlib kütüphanesindeki plot fonksiyonu myplot isimli bir fonksiyon tarafından sarmalanmış ve
parametreler plot fonksiyonuna ilketilmiştir.
2024-11-07 13:00:04 +03:00
İsimli argümanların sağında pozisyonel argümanlar bulunabilseydi biz mükemmel bir iletim yapamazdık.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import math
import matplotlib.pyplot as plt
2024-11-07 13:00:04 +03:00
def myplot(*args, **kwargs):
plt.plot(*args, **kwargs)
xs = []
ys = []
2024-07-15 13:23:34 +03:00
x = -6.
while x <= 6:
2024-11-07 13:00:04 +03:00
xs.append(x)
ys.append(math.sin(x))
2024-07-15 13:23:34 +03:00
x += 0.01
2024-11-07 13:00:04 +03:00
myplot(xs, ys, color='red', linewidth=4, linestyle='--')
plt.show()
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Fonksiyon çağırırken argümanların girilişi konusunda bazı ayrıntılar vardır. Bu ayrıntılarla birlikte sihai kıralı
aşağıda maddeler haline veriyoruz:
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
2) Ancak isimli argümanın sağında bir ya da birden fazla karışık sırada *'lı ve **'lı argüman bulunabilir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a = [10, 20, 30]
print(sep=', ', *a) # geçerli
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
3) Her zaman *'lı argümanlar **'lı argümanların solunda bulunmak zorundadır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
a = [10, 20, 30]
b = {'end': '*'}
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
print(sep=', ', *a, **b) # geçerli
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
4) Argüman parametre eşleşmesinde önce yalnızca isimsiz argümanlar (pozisyonel argümanlar) ve *'lı argümanlar dikkate alınır.
İsimli 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:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Burada önce isimsiz (pozisyonel) 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 yapılmıştır. Örneğin:
2024-07-15 13:23:34 +03:00
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:
2024-11-07 13:00:04 +03:00
d = {'d': 3000, 'e': 400}
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Burada argüman parametre eşleştirmesinde önce isimli 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:
2024-07-15 13:23:34 +03:00
t = (10, 20)
d = {'f': 100, 'e': 200}
foo(1, 2, *t, **d)
2024-11-07 13:00:04 +03:00
Burada da yukarıda belirttğimiz tüm kurallara uyulmuştur. Argüman parametre eşleştirmeleri de uygundur. O halde çağrı
geçerlidir. Örneğin:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir listenin demetin ya da kümenin elemanları 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]
Burada a nesnesi dolaşılacak elde edilen elemanlar b listesinin elemanları yapılacaktır. Dolayısıyla yukarıdaki işlem
aşağıdakiyle tamamen eşdeğerdir:
b = [10, 20, 1, 2, 3, 4, 5, 30, 40]
Tabii listeleri demetleri ve kmeleri oluştururken istediğimiz kadar çok *'lı eleman kullanabiliriz. Örneğin:
a = [10, *'ali', 20, *'veli']
Bu işlem aşağıdakiyle eşdeğerdir:
[10, 'a', 'l', 'i', 20, 'v', 'e', 'l', 'i']
#------------------------------------------------------------------------------------------------------------------------
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 durmmda **'ı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}
Burada k sözlüğünü oluşturmanın eşdeğeri şöyledir:
k = {'ali': 10, 'veli': 20, 'selami': 30, 'süleyman': 100, 'sacit': 200, '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}
#------------------------------------------------------------------------------------------------------------------------
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def add(*args):
total = 0
for x in args:
2024-11-07 13:00:04 +03:00
if isinstance(x, list|tuple):
total += sum(x)
2024-07-15 13:23:34 +03:00
else:
total += x
2024-11-07 13:00:04 +03:00
return total
result = add(1, 2, [3, 4, 5], 6, [7, 8], 9, 10) # 55
print(result)
#------------------------------------------------------------------------------------------------------------------------
Burada koşul operatörünü de kullanabilirdik.
#------------------------------------------------------------------------------------------------------------------------
def add(*args):
total = 0
for x in args:
total += sum(x) if isinstance(x, list|tuple) else x
2024-07-15 13:23:34 +03:00
return total
2024-11-07 13:00:04 +03:00
result = add(1, 2, [3, 4, 5], 6, [7, 8], 9, 10) # 55
2024-07-15 13:23:34 +03:00
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ı
2024-11-07 13:00:04 +03:00
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ğlanabilmektedir. Biz kursumuzda özyineleme
konusuna girmeyeceğiz. Ancak yukarıdaki fonksiyonu çok basit bir biçimde aşağıdaki gibi özyinelemeli hale getirebiliriz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def add(*args):
total = 0
for x in args:
2024-11-07 13:00:04 +03:00
if isinstance(x, list|tuple):
total += add(*x)
else:
total += x
2024-07-15 13:23:34 +03:00
return total
2024-11-07 13:00:04 +03:00
result = add(1, 2, [3, [4, [5]]], 6, [7, [8]], 9, 10) # 55
2024-07-15 13:23:34 +03:00
print(result)
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Built-in sum fonksiyonunun dolaşılabilir bir nesnenin elemanlarının toplamına geri döndüğünü anımsayınız. Aşağuıdaki gibi
bir çağrı geçerlidir:
result = sum([*(1, 2, 3, 4, 5), *(7, *(8, 9)), *[9, 2, *(3, 5, 6)]])
print(result)
Buaradaki sum çağrısının eşdeğeri şöyledir:
result = sum([1, 2, 3, 4, 5, 7, 8, 9, 9, 2, 3, 5, 6])
print(result)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
33. Ders 02/11/2024 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2024-11-07 13:00:04 +03:00
- 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 paralel biçimde çalışabiliyor olması gerekir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
İşte Python'da daha önce yazılmış olan ve başka dosyalarda bulunan fonksiyonların ve sınıfların kullanılabilmesi için
o dpsyaların import edilmesi gerekmektedir. Bu bölümde Python'da import işlemlerini ele alacağız.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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. Import işlemi import deyimi ile yapılmaktadır. import deyiminin genel biçimi
şöyledir:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
import <dosya_ismi> [as <isim>] [, <dosya ismi> [as <isim>], ...]
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Burada dosya ismi uzantı içermemelidir. Örneğin biz "utility.py" dosyasının içerisindekileri kullanmak isteyelim.
import işlemini şöyle yapabiliriz:
2024-07-15 13:23:34 +03:00
import utility
2024-11-07 13:00:04 +03:00
import işlemi yapılırken as anahtar sözcüğü ile modüle bir takma isim de verilebilir. Örneğin:
2024-07-15 13:23:34 +03:00
import utility as util
2024-11-07 13:00:04 +03:00
Artık burada modül içerisindeki öğeleri kullanırken modülün ismini değil as ile belirttiğimiz ismi kullanırız. as cümleciği
ile modül ismini başka bir isim biçiminde kullanmanın ana gerekçesi kısa bir yazım oluşturmaktadır. Örneğin:
import numpy as np
Burada her defasında numpy demek yerine np demek yazımı kısaltacaktır.
Aslında Python'un standart kütüphanesindeki öğeler de ".py" dosyalarının içerisindedir. Onları kullanmak için bizim o modülleri
2024-07-15 13:23:34 +03:00
import etmemiz gerekir. Örneğin:
import math
import statistics
2024-11-07 13:00:04 +03:00
Birden fazla modülün import edilmesi tek bir import deyimi ile de modül isimlerinin arasına ',' atomu getirilerek de yapılabilir.
2024-07-15 13:23:34 +03:00
Örneğin:
import math, statistics
2024-11-07 13:00:04 +03:00
Tabii birden fazla modülü tek bir import deyimi ile import ederken modüle as cümleciği ile takma isimler de verebiliriz.
2024-07-15 13:23:34 +03:00
Örneğin:
2024-11-07 13:00:04 +03:00
import math, statistics as st
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir modül import edildikten sonra o modülün içerisindeki değişkenler modül ismiyle ya da as ile belirttiğimiz isimle
"." operatörü ile niteliklendirilerek kullanılmak zorundadır. Örneğin:
2024-07-15 13:23:34 +03:00
import math
result = math.sqrt(10)
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi import deyimindeki as anahtar sözcüğü niteliklendirmede belirtilecek modül ismini değiştirmek
amacıyla kullanılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
import math as mt
result = mt.sqrt(10)
2024-11-07 13:00:04 +03:00
Yukarıda da belirttiğimiz gibi as ile takma isim verme genellikle uzun isimleri kısaltmak için as kullanılmaktadır.
Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
dolayısıyla da kullanmak için import işlemi gerekmeyen bir grup fonksiyona ve sınıfa "built-in" fonksiyonlar ve sınıflar
denilmektedir. print, input, max, min, type, id gibi fonksiyonlar built-in fonksiyonlardır. list, tuple, dict sınıflar
ise built-in sınıflardır.
2024-07-15 13:23:34 +03:00
Ö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.
2024-11-07 13:00:04 +03:00
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:
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir modül import edildiğinde yorumlayıcı modülün içindeki kodları çalıştırır. Sonra "module" isimli sınıf türünden bir
nesne yaratır. Modülün içerisindeki değişkenleri bu nesneye, nesnenin adresini de import deyiminde belirtilen değişkenin
içerisine yerleştirir. Yani modül isimleri aslında module isimli sınıf türünden nesnelerin adreslerini tutmaktadır.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> import math
>>> type(math)
<class 'module'>
>>> math.sqrt(10)
3.1622776601683795
>>> x = math
>>> x.sqrt(10)
3.1622776601683795
>>> type(x)
<class 'module'>
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir modül birden fazla kez import edilebilir. (Bu durum geçerli olsa da genellikle 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çerisindeki
kodlar çalıştırılmaz. Örneğin:
import utility as x
import utility as y
Burada modül iki kez import edilmiştir. Ancak modülün içerisindeki kodlar modül ilk kez import edildiğinde yalnızca
bir kez çalıştırılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Pekiyi bir modül yanlışlıkla kendisini import ederse ne olur? Örneğin:
# sample.py
import sample
print('sample')
2024-11-07 13:00:04 +03:00
Biz bu "sample.py" dosyasını çalıştırırsak ne olur? Burada dosya çalıştırıldığında import deyiminden dolayı yine "sample.py"
dosyası çalıştırılacaktır. Ancak o dosya çalıştırılırken yeniden import görülse bile bu ikinci kez import etme işlemi
olarak değerlendirileceği için dosyanın içi yeniden çalıştırılmayacaktır. import işleminden 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:
2024-07-15 13:23:34 +03:00
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)
2024-12-07 00:22:02 +03:00
Burada programcı standart modül olan math modülü yerine yanlışlıkla 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). Dolayısıyla burada bir sorun da ortaya çıkmayabilir. Ancak siz
2024-11-07 13:00:04 +03:00
kaynak dosyalarınıza Python'un standart modüllerinin ismini vermemelisiniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Modül dosyası yerel düzeyde de (yani bir fonksiyonun ya da metodun içerisinde 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:
2024-07-15 13:23:34 +03:00
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.
2024-11-07 13:00:04 +03:00
sys modülü Python standart kütüphanesinin içerisinde bulunan standart bir modüldür. path değişkeni de bu modülün içerisindeki
global bir değişkendir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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.
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
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ı
2024-11-07 13:00:04 +03:00
ç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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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ı bash kabuğu ile login olunduğunda bah programı "interaktive login shell" için "~/.bash_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:
2024-07-15 13:23:34 +03:00
export PYTHONPATH=/istenilen/dizinin/yol/ifadesi
2024-11-07 13:00:04 +03:00
UNIX/Linux ve macOS sistemlerinde PYTHONPATH çevre değişkenine birden fazla dizin eklemek için dizinler arasıbnda ':'
2024-07-15 13:23:34 +03:00
karakteri bulunmalıdır.
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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ı
2024-11-07 13:00:04 +03:00
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 arakod 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.
2024-07-15 13:23:34 +03:00
Pekiyi ya biz import işleminden sonra modül dosyasının kaynak kodu üzerinde değişiklik yaparsak ne olacaktır? İşte CPython
2024-11-07 13:00:04 +03:00
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.)
"__pycache__" dizini içerisindeki arakoda dosyalarının uzantılarının ".pyc" biçiminde olduğuna dikkat ediniz. Bu dosyalar
text dosyalar değildir. Dolayısıyla bunların içini görmeye çalışırsanız rastgele karakterler görürsünüz.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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. Bşka Python yorumlayıcısı tamamen farklı bir arakod sistemi kullanabilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Bir modüldeki belli değişkenleri modül ismiyle niteliklendirmeden doğrudan kullanabilmek için from import deyiminden
faydalanılmaktadır. from import deyiminin genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
from <modül_ismi> import <değişken_ismi> [as <değişken_ismi][, <değişken ismi...]
Örneğin:
from utility import add, multiply
Biz burada artık add ve multiply değişkenlerini utility.add ve utility.multiply biçiminde değil doğrudan add ve multiply
biçiminde kullanabiliriz. import edilen değişkenlere as ile başka isimler de verilebilmektedir. Örneğin:
from utility import add as a, multiply
2024-11-07 13:00:04 +03:00
Burada biz artık utility içerisindeki add değişkenini a ismiyle kullanabiliriz. Örneğimizdeki multiply isminde as
kullanılmadığına göre bu isim yine multiply biçiminde kullanılacaktır. Tabii multiply ismi için de as cümleciğini
bulundurabilirdik:
2024-07-15 13:23:34 +03:00
from utility import add as a, multiply as m
2024-11-07 13:00:04 +03:00
from import ile bir değişken import edildiğinde yine modüldeki kodların tamamı çalıştırılmaktadır. Tabii bu işlem toplamda
yine bir kez yapılır. from import deyiminden sonra biz modülün ismini kullanamayız. Çünkü burada modülün kendisi değil
modülün içerisindeki bazı değişkenler import edilmiş olmaktadır.
2024-07-15 13:23:34 +03:00
from utility import add
Burada biz utility ismini kullanamayız. Eğer utility ismini kullanacaksak ayrıca import uygulamalıyız:
import utility
Aşağıdaki from import işlemine bakınız:
from utility import add
Aslında bu işlemin eşdeğeri şöyledir:
import utility
add = utility.add
del utility
2024-11-07 13:00:04 +03:00
Bu eşdeğerlilikten şunu anlamalıyız: Biz sanki modülü önce import etmiş sonra da modül değişken ismini silmiş olmaktayız.
Eşdeğerlilikte modül import edilmiş gibi olduğuna göre modülün içerisindeki kodlar da yine çalıştırılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
from statistics import mean
from math import sqrt
result = mean([1, 2, 3, 4, 5, 6])
print(result)
result = sqrt(10)
print(result)
#------------------------------------------------------------------------------------------------------------------------
from import deyiminde verilen isim zaten varsa artık o değişkene yeni bir atama yapılacağına dikkat ediniz. Örneğin:
s = 10
from math import sqrt as s
print(s) # <built-in function sqrt>
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)
2024-11-07 13:00:04 +03:00
#------------------------------------------------------------------------------------------------------------------------
34. Ders 03/11/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
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ış olsun. Bu program asal sayılar üzerinde işlemler yapan fonksiyonları barındırsın. Ancak bu dosyanın
içerisinde aynı zamanda 1'den 100'e kadar asal sayıları yazdıran bir kod da buunuyor olsun:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
# utility.py
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
import math
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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 i in range(2, 100):
if isprime(i):
print(i, end=' ')
print()
Başka birisi utility.py içerisindeki asal sayı fonksiyonlarını kullana bir program yazmış olsun:
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
import utility
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
if utility.isprime(109):
print('asal')
else:
print('asal değil')
Buradaki sorun utility modülü import edildiğinde o dosyadaki tüm deyimler çalıştırılacağı için oradaki asal sayıları yazdıran
programın da çalışırılmasıdır. Oysa import işlemini yapan kişinin amacı o programı çalıştırmak değildir. Yalnızca oradaki
fonksiyonları kullanmaktır. İşte bunu sağlayabilmek için bir Python dosyasının normal mi çalıştırıldığının import deyiminden
dolayı mı çalıştırıldığının tespit edilebilmesi gerekir.
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 onun bağımsız bir program gibi de çalıştırılmasını istiyorsak programı aşağıdaki gibi
bir kontrol uygulayarak yazmalıyız:
2024-07-15 13:23:34 +03:00
if __name__ == '__main__':
pass
2024-11-07 13:00:04 +03:00
Burada eğer dosya bağımısz bir program gibi çalıştırılıyorsa __name__ değişkeninde "__main__ yazısı olacaktır. Dolayısıyla
if içerisindeki deyimler çalıştırılacaktır. Ancak dosya import işlemindne dolayı çalıştırılıyorsa __name__ içerisinde
"__main__" yazısı değil modül ismine ilişkin uyazı bulunacağı için if içerisindeki deyimler çalıştırılmayacaktır. Python
dosyalarında bu biçimdeki kontrollerle sıkça karşılaşılmaktadır. Yukarıdaki örnekte "utility.py" dosyası aşağdaıki
yazılırsa sözünü ettiğimiz sorun artık ortaya çıkmayacaktır:
2024-07-15 13:23:34 +03:00
import math
def isprime(val):
if val % 2 == 0:
return val == 2
2024-11-07 13:00:04 +03:00
for i in range(3, int(math.sqrt(val)) + 1, 2):
2024-07-15 13:23:34 +03:00
if val % i == 0:
2024-11-07 13:00:04 +03:00
return False
2024-07-15 13:23:34 +03:00
return True
if __name__ == '__main__':
2024-11-07 13:00:04 +03:00
for i in range(2, 100):
2024-07-15 13:23:34 +03:00
if isprime(i):
print(i, end=' ')
2024-11-07 13:00:04 +03:00
print()
Burada asal sayıları yazdıran kod ancak dosya bağımısz bir Python programı gibi çalıştırılırsa çalıştırılacaktır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
# utility.py
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
import math
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
if __name__ == '__main__':
for i in range(2, 100):
if isprime(i):
print(i, end=' ')
print()
# sample.py
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
import math
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
if __name__ == '__main__':
for i in range(2, 100):
if isprime(i):
print(i, end=' ')
print()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Rastgele sayı üretmimi (random number generation) 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.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
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. Bilgisayarlarda restgele sayılar tamamen sayısal işlemlerle
elde edilmektedir. Böyle elde edilmiş rastgele sayılara "sahte rasgele sayılar (pseudo random numbers)" denilmektedir.
ahte rassal sayı üretiminde bir "tohum değerden (seed)" başlanır. Bu tohum değer bir işleme sokulur. O işlem sonucunda
bir sayı elde edilir. O sayıda aynı işlemlere sokulur oaradan da bri sayı elde edilir. Böylece bir dizi rastgele sayı
elde edilmiş olur. Bu tohum değişmedikten sonra programın her çalıştırılmasında aynı rastgele sayılar elde edilir.
2024-07-15 13:23:34 +03:00
2024-11-07 13:00:04 +03:00
Aşağıda belli bir aralıkta rastgele sayı süreten randint isimli bir fonksiyon ytazılmıştır. Bu fonksiyon bir değişkene
bazı işlemler uygulayıp ondan rastgele bir değer elde etmektedir. Tohum değer seed isimli fonksiyonla değiştirilebilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
next_val = 1;
def randint(a, b):
global next_val
next_val = next_val * 1103515245 + 12345;
return next_val // 65536 % (b - a + 1) + a;
def seed(seed_val):
global next_val
next_val = seed_val
if __name__ == '__main__':
for _ in range(10):
val = randint(0, 1)
print(val, end=' ')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-11-07 13:00:04 +03:00
Python'da rastgele (rassal) sayılar random isimli standart modülüdeki fonksiyonlarla elde üretilmektedir. rondom modülündeki
random isimli fonksiyon parametresizdir. Her çağrıldığında [0,1) aralığında rastegele float bir sayı üretir. Eğer bir
aralıkta rastgele sayı üretilirken her sayının elde edilme olası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. Halk arasında "rastgele sayı" denildiğinde genellikle "düzgün dağılmış rastgele" sayılar
anlaşılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-11-07 13:00:04 +03:00
normal dağılıma istatistikte "standart normal dağılım" da denilmektedir. Örneğin IQ testleri ortalaması 100, standrat
sapması 15 olan normal dağılıma uyacak biçimde oluşturulmuştur. Yani rastgele bir kişinin zekası bu testlerle ölçüldüğünde
100'e yakın değerlerin elde edilme olasılığı uç değerlerin elde edilme olasılığından çok daha yüksektir. Aşağıda
böyle bir ortamda rastege seçilen 10 kişinin IQ puanlarına ilişkin bir örnek verilmiştir:
import random
for _ in range(10):
val = random.gauss(100, 15)
print(val, end=' ')
Aşağıda normal dağılmış rastgele sayılar bir listede toplanıp daha sonra matplotlib kütüphanesi ile bunların histogramı
çizilmiştir. Çizilen histogramın çan eğrisine benzediğine dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import random
2024-11-07 13:00:04 +03:00
vals = []
for _ in range(1000000):
val = random.gauss(100, 15)
vals.append(val)
import matplotlib.pyplot as plt
plt.hist(vals, bins=200)
plt.show()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
random modülündeki randint isimli fonksiyon [a, b] aralığında rastgele bir tamsayı (int türden sayı) üretir. Yani randint
2024-11-07 13:00:04 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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"
2024-11-07 13:00:04 +03:00
(random sampling)" denilmektedir.) Fonksiyon bize rastgele değerlerden oluşan bir liste vermektedir. Fonksiyonun geri
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
genellikle aşağıdaki gibi olmaktadır:
2024-07-15 13:23:34 +03:00
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)
2024-11-07 13:00:04 +03:00
Aklınıza şöyle bir çözümde gelebilir: Biz sayıları numbers isminde listede toplayalım. Sonra listeden choice fonksiyonuyla
2024-07-15 13:23:34 +03:00
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
2024-11-07 13:00:04 +03:00
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 olarak liste küçültme yöntemi uygulanmaktadır. Aşağıdaki kodda numbers
listesinden çekilen rastgele eleman columns listesine eklenmiştir. Ancak o eleman elemana son eleman ile yer değiştirilerek
liste küçültülmüştür. Bu daha bir çözümdür.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
35. Ders 09/11/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
2024-12-07 00:22:02 +03:00
counts parametresine belli bir değer girildiğinde artık sample fonksiyonun geri döndürdüğü liste aynı elemanlardan
oluşabilmektedir.
2024-07-15 13:23:34 +03:00
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)
2024-12-07 00:22:02 +03:00
İsimli kullanılmak zorunda olan k parametresi kaç elemanlık çekim yapılacağını belirtmektedir. Buradaki weights parametresi
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
random modülündeki randrange fonksiyonu parametrik kullanım bakımından range fonksiyonuna benzemektedir. Fonksiyon
parametreleriyle belirtilen range arasında rastgele bir sayı verir.
2024-07-15 13:23:34 +03:00
tamsayı üretmektedir. Örneğin:
result = random.randrange(0, 10, 2)
2024-12-07 00:22:02 +03:00
Burada aslında biz 0, 2, 4, 8 sayıları arasında rastgele bir sayı üretmiş oluruz. Fonksiyon aşağıdaki gibi yazılabilir:
def myrandrange(start, stop, step):
return random.choice(range(start, stop, step))
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import random
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)
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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. Bu örnekte önce kartları bir demet listesi biçiminde oluşturuyorduk:
colors = {'Sinek': 0, 'Maça': 1, 'Karo': 2 , 'Kupa': 3}
ctypes = {'2': 0, '3': 1, '4': 2, '5': 3, '6': 4, '7': 5, '8': 6, '9': 7,
'10': 8, 'Vale': 9, 'Kız': 10, 'Papaz': 11, 'As': 12}
def build_deck():
deck = []
for ctype in ctypes:
for color in colors:
deck.append((color, ctype))
return deck
Sonra kartları belli bir pozisyonden kesen fonksiyon yazdık:
def cut_deck(deck, pos):
deck[:pos], deck[pos:] = deck[pos:], deck[:pos]
Kartların dağıtılması için aşağıdaki gibi bir fonksiyon yazdık:
def deal_deck(deck):
return deck[0:NCARDS:4], deck[1:NCARDS:4], deck[2:NCARDS:4], deck[3:NCARDS:4]
Burada dağıtım tıpkı kart oyunlarındaki gibi yapımıştır. Burada kağıtların soldan (yani saat yönnde) dağıtıldığını
varsayıyoruz. player1 dağıtan kişinin solundaki kişiyi temsil etmektedir. Kağıtlar dağıtıldıktan sonra genellikle
oyuncular onları sıraya dizer. Burada oyuncuların elindeki kartlar bir demet listesi biçimindedir. Bu listenin doğrudan
sort edilmesi işimizi görmez. sort fonksiyonunun key parametresi bir fonksiyon almaktadır. Listenin iki elemanını
bu fonksiyona sokup fpnksiyo sonucundaki değeri referans alarak sıraya dizme yapmaktadır. O halde bizim bir kartı
sayıya dönüştüren bir fonksiyona ihtiyacımız olacaktır. Bu fonksiyonu aşağıdaki yazdık:
def comparer(card):
return ctypes[card[1]] * 4 + colors[card[0]]
Programın ana kısmı da şöyledir:
def main():
deck = build_deck()
random.shuffle(deck)
cut_deck(deck, 25)
player1, player2, player3, player4 = deal_deck(deck)
sort_deck(player1)
sort_deck(player2)
sort_deck(player3)
sort_deck(player4)
print(player1)
print('-' * 20)
print(player2)
print('-' * 20)
print(player3)
print('-' * 20)
print(player4)
main()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import random
2024-12-07 00:22:02 +03:00
NCARDS = 52
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
colors = {'Sinek': 0, 'Maça': 1, 'Karo': 2 , 'Kupa': 3}
ctypes = {'2': 0, '3': 1, '4': 2, '5': 3, '6': 4, '7': 5, '8': 6, '9': 7,
'10': 8, 'Vale': 9, 'Kız': 10, 'Papaz': 11, 'As': 12}
2024-07-15 13:23:34 +03:00
def build_deck():
2024-12-07 00:22:02 +03:00
deck = []
for ctype in ctypes:
for color in colors:
deck.append((color, ctype))
2024-07-15 13:23:34 +03:00
return deck
2024-12-07 00:22:02 +03:00
def cut_deck(deck, pos):
deck[:pos], deck[pos:] = deck[pos:], deck[:pos]
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
def deal_deck(deck):
return deck[0:NCARDS:4], deck[1:NCARDS:4], deck[2:NCARDS:4], deck[3:NCARDS:4]
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
def comparer(card):
return ctypes[card[1]] * 4 + colors[card[0]]
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
def sort_deck(deck):
deck.sort(key=comparer, reverse=True)
deck = build_deck()
random.shuffle(deck)
cut_deck(deck, 25)
player1, player2, player3, player4 = deal_deck(deck)
sort_deck(player1)
sort_deck(player2)
sort_deck(player3)
sort_deck(player4)
print(player1)
print('-' * 20)
print(player2)
print('-' * 20)
print(player3)
print('-' * 20)
print(player4)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Şimdi rastgele sayılar üreterek komik bir biçimde pi sayısını elde edelim. 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. [0, 1] aralığında
x ve y için iki rastgele sayı üreterek bu kare içerisinde rastgele noktalar elde ederiz. Kare içerisinde elde ettiğimiz
2024-07-15 13:23:34 +03:00
noktaların bazıları aynı zamanda bu daire diliminin de içerisinde olacaktır. Toplam nokta sayısı n tane olsun. Bu n tane
2024-12-07 00:22:02 +03:00
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 olmalıdır
2024-07-15 13:23:34 +03:00
n / k = 1 / (pi / 4)
Burada içler dışlar çarpımı ile pi değeri çekilirse şu sonuç bulunur:
pi = 4 * k / n
2024-12-07 00:22:02 +03:00
Tabii buradaki n değerini kadar artırırsak pi'ye o kadar daha çok yaklaşırız.
2024-07-15 13:23:34 +03:00
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:
2024-12-07 00:22:02 +03:00
k += 1
return 4 * k / n
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
ilk elemanları 0'dan başlayan indeks numarasından ikinci elemanları da bizim verdiğimiz dolaşılabilir nesnedeki elemanlardan
2024-07-15 13:23:34 +03:00
oluşur. Örneğin:
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
for t in enumerate(names)
print(t)
2024-12-07 00:22:02 +03:00
Buradan sırasıyla (0, 'ali'), (1, 'veli'), (2, 'selami'), (3, 'ayşe'), (4, 'fatma') biçiminde demetler elde edilecektir.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
enumerate fonksiyonu dolaşılabilir bir 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 bir parametresi daha vardır. Bu ikinci
parametreye default olarak 0 değeri verilmiştir. Bu parametre indeksin nereden başlatılacağını belirtir. Örneğin:
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Python'da biz bir değişkene ilk kez değer atadığımızda o değişkini yaratmış oluruz. Ancak her değişken programın her
yerinde kullanılamaz. 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:
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
a = 10
b = a
2024-12-07 00:22:02 +03:00
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 fakat tek
bir nesne vardır.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Yukarıda da belirttiğimiz gibi değişkenler onlara ilk kez değer atandığında yaratılmaktadı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:
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
Fonksiyonların parametre değişkenleri de yerel değişkenler gibi yalnızca ilişkin oldukları fonksiyona kullanılabilirler.
Farklı fonksiyonların aynı isimli parametre değişkenleri farklı değişkenlerdir, biribirine karışmazlar. Örneğin:
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
x = 10
def foo():
2024-12-07 00:22:02 +03:00
x = 20 # x yeni bir yerel değişken, global olan değil
print(x) # buradaki x yerel olan x
2024-07-15 13:23:34 +03:00
foo()
2024-12-07 00:22:02 +03:00
print(x) # buradaki x global x, zaten yerel x burada faaliyet göstermiyor, 10 çıkacak
Fonksiyonların parametre değişkenleri de o fonksiyonda yaratılmış yerel değişkenler gibi ele alınmaktadır. Yani bir
fonksiyonun global bir değişkenle aynı isimli bir parametre değişkeni olabilir. Fonksiyon içerisinde bu değişken kullanıldığında
global olan değil parametre değişkeni olan kullanılmış olur. Örneğin:
x = 10
def foo(x):
print(x) # burada global değişken değil paranetre değişkeni yazdırılıyor
x = 20 # burada global değişkene değiş parametre değişkenine değer atanıyor
foo(100)
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Bazen bir fonksiyonun global bir değişkeni değiştirmesi istenebilir. Bu durumda yorumlayıcıya bunun açıkça belirtilmesi
2024-07-15 13:23:34 +03:00
gerekir. Bu işlem global bildirimi ile yapılmaktadır. global bildiriminin genel biçimi şöyledir:
global <değişken listesi>
Örneğin:
global x
global y, z, k
2024-12-07 00:22:02 +03:00
global bildirimi Python'da aslında bir deyim statüsündedir. Bir fonksiyonun gerhangi bir yerine yerleştirilebilir.
Program akışının o noktayaya gelmesiyle etki gösterir. Örneğin:
2024-07-15 13:23:34 +03:00
a = 10
def foo():
global a
a = 20 # global olan a
print(x) # global olan a
foo()
print(a) # 20
2024-12-07 00:22:02 +03:00
Burada artık foo'nun içerisindeki a global olan a'dır. Dolayısıyla foo'nun içerisinde global olan a'ya 20 atanmıştır. global
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
değiştirilmeyecekse zaten global değişkenler doğrudan fonksiyonlar içerisinde kullanılabildiği için bu bildirimi yapmanın
bir anlamı kalmamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
a = 10
def foo():
2024-12-07 00:22:02 +03:00
global a # geçerl, ancak burada global bildirimine gerek yok
2024-07-15 13:23:34 +03:00
print(a) # global olan a
foo()
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
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. Ancak bu bildirim bu
değişkenin yaratılmasına yol açmamaktadır.
2024-07-15 13:23:34 +03:00
def foo():
global a
2024-12-07 00:22:02 +03:00
print(a) # error! henüz global değişken yaratılmadı
2024-07-15 13:23:34 +03:00
foo()
2024-12-07 00:22:02 +03:00
print(a) # error! henüz global değişken yaratılmadı
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Eğer global bildiriminden sonra ilgili değişkene fonksiyonda değer atanırsa gerçekten glovbal değişken de yaratılmış
olmaktadır. Yani global bildirimi global değişkenin yaratılmasına yol açmaz, ancak global bildiriminden sonra fonksiyon
içerisinde bu değişkene değer atanırsa global değişken yaratılmış olur. Örneğin:
2024-07-15 13:23:34 +03:00
def foo():
global a
2024-12-07 00:22:02 +03:00
a = 10 # geçerli, global değişken daha önce yaratılmamış olsa bile şimdi yaratılıyor
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
foo()
print(a) # geçerli 10 değeri ekrana yazdırılacak
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
36. Ders 10/11/2024 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
- 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:
2024-07-15 13:23:34 +03:00
def foo():
x = 10
# ...
2024-12-07 00:22:02 +03:00
print(x) # error!
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
- Fonksiyonların parametre değiş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:
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
ne zaman yaratılıp yok edilmektedir? Örneğin:
2024-07-15 13:23:34 +03:00
x = 10
x = 20
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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()
2024-12-07 00:22:02 +03:00
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 nesnesini
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.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Aşağıdaki örnekte döngünün her yinelenemesinde yeni bir int nesne yaratılır, ancak öncekiler çöp toplayıcı tarafından yok
edilir.
2024-07-15 13:23:34 +03:00
i = 0
for _ in range(100):
print(id(i)) # her defasınde değişik bir adres yazıdırılacaktır
i += 1
2024-12-07 00:22:02 +03:00
Python standart dokümanlarında çöp toplayıcının kullandığı yöntem ve algoritma açıklanmamıştır. Bu nedenle çöp toplayı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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Python'da çokça kullanılan "built-in" bir 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
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
olmak zorundadır. Örneğin:
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def foo(x):
return x * x
m = map(foo, a)
for val in m:
print(val, end=' ')
Burada biz map fonksiyonunun verdiği dolaşım nesnesini dolaştığımızda aslında liste ieçrisindeki sayıların karelerini
elde ederiz. Dolaşım nesnelerinin bir kere dolaşıldığında bittiğini anımsayınız. Programcılar genellikle ara bir
değişken kullanmadan map fonksiyonunun verdiği nesneyi doğrudan dolaşırlar. Örneğin:
for val in map(foo, a):
print(val, end=' ')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
birinci parametresine metot vereceksek metodu isimle değil <değişken>.<metot> biçiminde vermeliyiz. Örneğin:
2024-07-15 13:23:34 +03:00
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'
2024-12-07 00:22:02 +03:00
result = list(map(s.count, s))
print(result)
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Burada yazının her karakterinin yazıda kaç tane olduğuna ilişkin bir liste elde edilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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=' ')
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Aslında map fonksiyonu birden fazla dolaşılabilir nesne alabilmektedir. Bu durumda bu dolaşılabilir nesnelerin 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. Yine
map fonksiyonu bize fonksiyonun geri dönüş değerine ilişkin dolaşm nesnesi vermektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
def foo(*a):
return sum(a)
a = [2, 4, 6, 8]
b = [1, 3, 9, 5]
c = [3, 6, 9, 12]
result = list(map(foo, a, b, c))
print(result) # [6, 13, 24, 25]
#------------------------------------------------------------------------------------------------------------------------
Çok kullanılan built-in fonksiyonlardan biri de zip isimli fonksiyondur. Fonksiyonun 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)
Tabii yine genellile programcılar ara değişken kullanmadan doğrudan zip fonksyiyonunu çağırırlar. Örneğin:
for t in zip(a, b, c):
print(t)
#------------------------------------------------------------------------------------------------------------------------
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]
for t in zip(a, b, c):
print(t)
#------------------------------------------------------------------------------------------------------------------------
Tabii biz zip fonksiyonunun bize verdiği dolaşılabilir nesneyi dolaşırken açım işlemi (unpacking) de yapabiliriz.
Örneğin:
for x, y, z in zip(a, b, c):
pass
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
Sözlük yaratırken kulandığımız dict fonksiyonuna biz iki elemanlı dolaşılabilir nesnelerden oluşan dolaşılabilir nesne
verebiliyorduk. Bu durumda dict fonksiyonu ilk elemanı anahtar ikinci elemanı değer olan bir sözlük oluşturuyordu.
O halde biz zip fonksiyonundan faydalanarak paralel iki listenin karşılıklı elemanlarından sözlük yapabiliriz. Örneğin:
students = ['Ahmet', 'Ayşe', 'Mehmet', 'Fatma']
grades = [85, 90, 78, 92]
d = dict(zip(students, grades))
print(d) # {'Ahmet': 85, 'Ayşe': 90, 'Mehmet': 78, 'Fatma': 92}
#------------------------------------------------------------------------------------------------------------------------
students = ['Ahmet', 'Ayşe', 'Mehmet', 'Fatma']
grades = [85, 90, 78, 92]
d = dict(zip(students, grades))
print(d) # {'Ahmet': 85, 'Ayşe': 90, 'Mehmet': 78, 'Fatma': 92}
#------------------------------------------------------------------------------------------------------------------------
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) çağrısını yaptığımızda bu demetleri sanki zip fonksiyonun argümanları
yapmış gibi oluruz. Buradan bize verilen dolaşım nesnesi dolaşıldığında eski değerler elde edilecektir. Örneğin:
a = [1, 2, 3, 4, 5]
b = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
c = [1.1, 2.2, 3.3, 4.4, 5.5]
z = zip(a, b, c)
x, y, z = zip(*z)
print(x, y, z)
Burada aslında ikinci fonksiyonu şöyle çağrılmış gibi olacaktır:
x, y, z = zip((1, 'ali', 1.1), (2, 'veli', 2.2), (3, 'selami', 3.3), (4, 'ayşe', 4.4), (5, 'fatma', 5.5))
Bu durumda zip fonksiyonu aslında birinci elemanlardan, ikinci elemananlardan ve üçüncü elemanlardan oluşan demetleri
veren bir dolaşım nesnesi verecektir. Açım işleminde dolaşılabilir nesnenlerin kullanıldığını anımsayınız. Bu durumda
x, y ve z başlangıçtaki elemanlardan oluşan demet haline haline gelecektir.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
a = [1, 2, 3, 4, 5]
2024-12-07 00:22:02 +03:00
b = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
c = [1.1, 2.2, 3.3, 4.4, 5.5]
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
z = zip(a, b, c)
x, y, z = zip(*z)
print(x, y, z)
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki gibi bir soruyu geldiğimiz noktaya kadarki konuları kullanarak çözmeye çalışalım.
Elimizde aşağıdaki gibi üç liste olsun:
products = ['Elma', 'Portakal', 'Armut', 'Muz', 'Kiraz']
prices = [15, 10, 12, 20, 30]
stocks = [50, 30, 20, 60, 40]
Amacımız da şöyle bir sözlük sözlüğü oluşturmak olsun:
{'Elma': {'fiyat': 15, 'stok': 50}, 'Portakal': {'fiyat': 10, 'stok': 30}, 'Armut': {'fiyat': 12, 'stok': 20},
'Muz': {'fiyat': 20, 'stok': 60}, 'Kiraz': {'fiyat': 30, 'stok': 40}}
Soru aşağıdaki gibi çözülebilir.
#------------------------------------------------------------------------------------------------------------------------
products = ['Elma', 'Portakal', 'Armut', 'Muz', 'Kiraz']
prices = [15, 10, 12, 20, 30]
stocks = [50, 30, 20, 60, 40]
ps = []
for f, s in zip(prices, stocks):
ps.append(dict(fiyat=f, stok=s))
result = dict(zip(products, ps))
print(result)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'da iç içe (nested) fonksiyon tanımlamaları yapılabilmektedir. Örneğin:
def foo():
...
def bar():
...
...
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
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()
2024-12-07 00:22:02 +03:00
Burada bar ismi foo fonksiyonunun bir yerel değişkeni gibidir. Yani bar ismini ancak biz foo içerisinde kullanabiliriz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-07 00:22:02 +03:00
Örneğin 2'den parametresiyle belirtilen sayıya kadar asal sayıları bir listede toplayan 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. İşte 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
onun dışındak, fonksiyon içerisinde kullanmadıktan sonra onu tanımlamanın da bir anlamı yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
import math
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
def getprimes(n):
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
result = []
for i in range(2, n):
if isprime(i):
result.append(i)
return result
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
a = getprimes(100)
print(a)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
İç 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:
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Tabii Python'da isim araması fonksiyon çağrı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:
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
Burada 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ılıp ona atama yapılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
İç fonksiyonun da iç fonksiyonu olabilir. Bu durumda iç fonksiyon tüm dış fonksiyonların yerel değişkenlerini kullanabilir.
Örneğin:
def foo():
a = 10
def bar():
b = 20
def tar():
print(a, b) # 10, 20
tar()
bar()
foo()
Burada foo fonksiyonu bar fonksiyonunu, bar fonksiyonu da tar fonksiyonunu çağırmıştır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
İç fonksiyonun dış fonksiyonun yerel değişkenini değiştirebilmesi için nonlocal bildiriminin yapılması gerekir. nonlocal
bildiriminin genel biçimi şöyledir:
nonlocal <değişken listesi>;
Ö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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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. Daha dış bir fonksiyonda da bulunabilir. Tabii
global değişkenler nonlocal ile bildirilemezler.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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 iç fonksiyon çağrıldığında error oluşturur. Örneğin:
2024-07-15 13:23:34 +03:00
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()
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
37. Ders 16/11/2024 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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 (fonksiyon isimleri de birer
global değişkendir) birbirlerine karışmazlar. Aşağıdaki örnekte "sample.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 bu örnekte
"a.py" modülünde ve "b.py" modülünde foo isminde iki ayrı fonksiyon da bulundurulmuştur. Bu fonksiyonlar kendi modüllerindeki
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
util modülünde int türden bir x değişkeni olduğunu varsayalım. onu aşağıdaki gibi drom import deyimiyle import
ewtmiş olalım:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
from util import x
2024-07-15 13:23:34 +03:00
Bu işlemin tamamen eşdeğeri şöyledir:
2024-12-07 00:22:02 +03:00
import util
2024-07-15 13:23:34 +03:00
x = util.x
2024-12-07 00:22:02 +03:00
del util
2024-07-15 13:23:34 +03:00
Bu durumda:
2024-12-07 00:22:02 +03:00
from util import x
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
deyiminde x bu modüldeki bir değişkendir. Ancak bu modüldeki x değişkeni util modülündeki x değişkeni ile aynı nesneyi
int nesneyi göstermektedir. Biz bu işlemden sonra bu modüldeki x'i değiştirirsek int türü değiştirilemez bir tür olduğu için
util modülündeki x değişkeni eski nesneyi göstermeye devam edecektir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
from util import x
print(x) # util modülündeki x'in gösterdiği yerdeki nesnenin değerini yazdırıyor
2024-07-15 13:23:34 +03:00
x = 100
2024-12-07 00:22:02 +03:00
print(x) # x artık util modülündeki x'in gösterdiği nesneyi göstermiyor
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
import util
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
print(util.x) # util modülündeki x
2024-07-15 13:23:34 +03:00
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:
2024-12-07 00:22:02 +03:00
# util.py
2024-07-15 13:23:34 +03:00
x = [1, 2, 3, 4, 5]
# sample.py
2024-12-07 00:22:02 +03:00
from util import x
2024-07-15 13:23:34 +03:00
print(x) # [1, 2, 3, 4, 5]
x[0] = 100
2024-12-07 00:22:02 +03:00
import util
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
print(util.x) # [100, 2, 3, 4, 5]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Bir modül yerel düzeyde import edildiğinde her ne kadar modül değişkenini yalnızca o fonksiyonda kullanabiliyor olsa
da import işlemi kalıcı bir biçimde yapılmaktadır. Yani biz bu fonksiyonu birden fazla kez çağırdığımızda da toplamda
aslında bir kez import işlemi yağılmaktadır. Zaten modül nesnesi de toplamda yalnızca bir kez ve bir tane oluşturulmaktadır.
Aşağıdaki örnekte "sample.py" modülü içerisindeki foo fonksiyonunda "util.py" modülü import edilmiştir. Bu foo fonksiyonu
içersinde modül değişkeninin id'si yazdırıldığında hep aynı değer görülecektir. Modül dışardan import edildiğinde de
modül değişkeninin id'si aynı değere sahip olacaktır. Buradaki util modülünün içindeki deyimlerin toplamda yalnızca
bir kez çalıştırıldığına da dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
# sample.py
2024-07-15 13:23:34 +03:00
def foo():
2024-12-07 00:22:02 +03:00
import util
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
util.x = 200
print(id(util))
2024-07-15 13:23:34 +03:00
foo()
2024-12-07 00:22:02 +03:00
foo()
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
import util
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
print(id(util))
# util.py
print('utility')
2024-07-15 13:23:34 +03:00
x = 10
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Python'da globals isimli built-in fonksiyon o andaki o modüldeki 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
da yorumlayıcının global değişkenleri tutmak için kullandığı sözlüğü bize vermektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
içerisinde çağrılmışsa o fonksiyonun çağrılma noktasına kadar yaratılmış olan 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = 10
def foo():
b = 20
d = locals()
print(d) # {'b': 20}
print(list(d)) # ['b']
foo()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
İçteki fonksiyonun dıştaki fonksiyonun yerel değişkenlerini kullanabildiğini anımsayınız. Ancak locals fonksiyonu iç
bir fonksiyonda çağrıldığında yalnızca kendi fonksiyonunun yerel değişkenlerini verir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Daha önceden de belirttiğimiz gibi "fonksiyonel programlama 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
2024-07-15 13:23:34 +03:00
yazar gibi tek bir satırda pek çok şeyin yapılabildiği programlama modelidir. Artık klasik programlama dillerine de çeşitli
2024-12-07 00:22:02 +03:00
yoğunlukta fonksiyonel modeli destekleyebilecek özellikler eklenmiştir. İşte Python'da "içlemler (comprehensions)" konusu da
fonksiyonel programlama modelini desteklemek amacıyla dile eklenmiş olan bir özelliktir. Python'daki içlemlere benzer öğeler
bazı programlama dillerinde değişik biçimlerde bulunabilmektedir. Ancak C, C++, Java ve C# gibi yaygın kullanılan programlama
dillerinin çoğunda içlemlere benzer öğeler yoktur. Biz 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.)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
İç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)
2024-12-07 00:22:02 +03:00
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)"
2024-07-15 13:23:34 +03:00
denilmektedir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
İçlemlerin genel biçimleri birbirine çok benzemektedir. Liste içlemlerinin genel biçimi şöyledir:
[<ifade> for <değişken> in <dolaşılabilir nesne> [if koşul] ]
2024-12-07 00:22:02 +03:00
Burada en dışta bulunna kşeli parantezler liste oluştururken kullanılan köşeli parantezlerdir. Yani sentaka dahildir.
En çok karşılaşılan biçim köşeli poarantez içerisinde bir ifade ve onun yanında bir for döngüsünün bulunduğu biçimdir.
Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
x = [i * i for i in range(10)]
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
İçlemin çalışma mekanizması oldukça basittir. for döngüsünün her yinelenmesinden sonra for döngüsünün solundaki ifade
2024-07-15 13:23:34 +03:00
ç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)
2024-12-07 00:22:02 +03:00
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ıp 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:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
x = [ifade for i in iterable]
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
Tabii içlemler aslında hızdan ziyade kompakt bir görünüm sağladıkları için kullanılmaktadır. İçlemler bir ifade (expression)
2024-07-15 13:23:34 +03:00
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)]
2024-12-07 00:22:02 +03:00
print(a) # [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
2024-07-15 13:23:34 +03:00
Ö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])
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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. Örneğin:
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
x = list(map(len, ['ali', 'veli', 'selami', 'ayşe', 'fatma']))
print(x)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
names_len = [len(name) for name in names]
print(names_len)
2024-12-07 00:22:02 +03:00
names_len = list(map(len, names))
print(names_len)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
İçlemlerde istenirse for cümlesinin sağına if anahtar sözcüğü ile bir koşul ifadesi 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:
2024-07-15 13:23:34 +03:00
a = [i for i in range(10) if i % 2 == 0]
2024-12-07 00:22:02 +03:00
print(a) # [0, 2, 4, 6, 8]
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Burada koşul i çift ise koşul sağlanmaktadır. Dolayısıyla listede yalnızca çift sayılar bulunacaktır. O halde örneğin:
2024-07-15 13:23:34 +03:00
[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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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']
2024-12-07 00:22:02 +03:00
palindromes = [sentence for sentence in sentences if sentencelower() == sentence[::-1].lower()]
2024-07-15 13:23:34 +03:00
print(palindromes)
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte tam palindrom olmayan cümleler de elde edilmektedir.
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
sentences = ['anastas mum satsana', 'izmir', 'ey edip adanada pide ye', 'eskişehir', 'adamla çene çalmada', 'Arazi küçük iz arA']
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Yukarıdaki örnek aşağıdaki gibi de yapılabilirdi.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
Elimizde öğrencilere ilişkin bilgilerin bulunduğu aşağıdaki gibi bir liste olsun:
students = [
{'name': 'Alice', 'scores': [85, 92, 78], 'passed': True},
{'name': 'Bob', 'scores': [55, 60, 48], 'passed': False},
{'name': 'Charlie', 'scores': [95, 88, 91], 'passed': True},
{'name': 'David', 'scores': [44, 53, 50], 'passed': False},
{'name': 'Eve', 'scores': [91, 93, 87], 'passed': True},
{'name': 'Frank', 'scores': [72, 80, 79], 'passed': True}
]
Buradaki listenin sözlük nesnelerinden oluşturğuna dikkat ediniz. Şimdi bu sözlük listesi üzerinde bazı işlemleri
yapan liste içlemleri oluşturalım:
- Geçen öğrencilerin isimlerini içeren bir liste şöyle elde edilebilir:
passed_name = [student['name'] for student in students if student['passed']]
print(passed_name)
- Sadece dersten geçen (passed True olan) öğrencilerin ortalama puanlarını içeren bir listeyi şöyle oluşturabiliriz:
passed_mean = [statistics.mean(student['scores']) for student in students if student['passed']]
print(passed_mean)
- Geçen öğrencilerin en yüksek puanlarından bir listeyi şöyle oluşturabiliriz:
passed_max = [max(student['scores']) for student in students if student['passed']]
print(passed_max)
- Geçen öğrencilerin isimlerinden ve ortalamalarından oluşan bir sözlük şöyle elde edilebilir (sözlüğün anahtarları
isimlerden değerleri ise ortalamalardna oluşacak):
d = dict([(student['name'], statistics.mean(student['scores'])) for student in students if student['passed']] )
print(d)
- Herhangi bir öğrencinin en düşük puanı (scores listesinde) 50'nin altındaysa, o öğrenciyi "Risk altında" olarak,
değilse "Güvende" olarak işaretleyen bir liste oluşturmak isteyelim. Liste demetlerdne oluşsun. Demetlerin
birinci elemanı öğrencinin ismi, ikinci elemanı bu yazıdan oluşsun:
result = [(student['name'], 'Risk altında' if student['scores'][0] < 50 or student['scores'][1] < 50 or
student['scores'][2] < 50 else 'Güvende') for student in students]
print(result)
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
döngü baştan sona çalıştırılır. Yukarıdaki içlemin kod karşılığı şöyle ifade edilebilir:
2024-07-15 13:23:34 +03:00
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']
2024-12-07 00:22:02 +03:00
names_chars = [char for name in names for char in name]
2024-07-15 13:23:34 +03:00
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:
2024-12-07 00:22:02 +03:00
a = ['a', 'b', 'b']
2024-07-15 13:23:34 +03:00
b = [1, 2, 3]
2024-12-07 00:22:02 +03:00
a = [(x, y) for x in a for y in b]
print(a)
2024-07-15 13:23:34 +03:00
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]
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Aşağıdaki örnekte liste içerisindeki listelerdeki çift elemanlar elde elde edilmiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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]]
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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
"yineleme (repitition)" ile yaparsak arzu etmediğimiz bazı durumlar oluşabilir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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]]
2024-12-07 00:22:02 +03:00
Burada yineleme işlemi kopya yoluyla yapıldığı için aslında oluşturulan listedeki elemanların hepsi aynı listeyi
göstermektedir:
>>> for i in range(10):
... print(id(a[i]))
...
2071195472448
2071195472448
2071195472448
2071195472448
2071195472448
2071195472448
2071195472448
2071195472448
2071195472448
2071195472448
Yani burada aslında 5 elemanlı farklı 10 liste yoktur. Bir tane 5 elemanlı liste vardır. 10 elemanlı listenin her elemanı
aslında aynı listeyi göstermektedir. İşte bu durumda iç listenin bir elemanı değiştirildiğinde sanki tüm listelerin ilgili
elemanları değiştirilmiş gibi bir durum oluşacaktır. Örneğin biz bu liste listesindeki ilk listenin ilk elemanını değiştirecek
olalım:
2024-07-15 13:23:34 +03:00
>>> a[0][0] = 100
2024-12-07 00:22:02 +03:00
Biz aslında listedeki tüm listelerin ilk elemanlarını değiştirmiş olduk:
>>> 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]]
Gördüğünüz gibi yukarıda açıkladığımız nedenden dolayı tüm listelerin ilk elemanları değişmiş oldu.
Pekiyi biz 10 elemanlı listenin her elemanın farklı bir 5 elemanlı listeyi göstermesini nasıl sağlarız? Bunun bir yolu
tek tek append işlemi yapmaktır.
2024-07-15 13:23:34 +03:00
>>> 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]]
2024-12-07 00:22:02 +03:00
Python yorumlayıcısı programın akışı sırasında her köşeli parantez ifadesini gördüğünde yeni bir liste nesnesi yaratmaktadır.
Yani bir döngü içerisinde bir köşeli parantez ifadesi varsa döngünün her yinelenmesinde farklı bir liste nesnesi yaratılacaktır.
Tabii demetlerdeğiştirilemez olduğu için bir döngü içerisinde parantezlerle demet oluşturma ifadesi varsa yorumlayıcı
optimizasyon yaparak her defasında gereksiz bir biçimde farklı demet nesnelerini oluşturmayabilir. Örneğin:
>>> for _ in range(10):
... a.append((0, 0, 0, 0, 0))
...
>>> for i in range(10):
... print(id(a[i]))
...
2071198317552
2071198317552
2071198317552
2071198317552
2071198317552
2071198317552
2071198317552
2071198317552
2071198317552
2071198317552
Tabii aynı dırım string'ler ve diğer değiştirilemez teml türler için de geçerlidir.
Biz bir listenin içerisine her defasında farklı bir liste yerleştirmek istiyorsak bunun en pratik yolu liste içlemlerini
kullanmaktır. Çünkü liste içlemlerinin sentaks ifadesinde köşeli parantezler kullanılırsa içlem döngüsü her yinelendiğinde
yeni bir liste nesnesi yaratılacaktır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [[0, 0, 0, 0, 0] for _ in range(10)]
2024-12-07 00:22:02 +03:00
Burada içlem döngüsü her yinelendiğinde içi 0'larla dolu yeni bir 5 elemanlı liste nesnesi oluşturulacak ve bu listelerden
liste oluşturulacaktır. Böylece biz ana listenin içerisindeki bir listenin bir elemanını değiştirdiğimizde diğer ana listenin
içerisindeki diğer listeler bu işlemden etkilenmeyecektir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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]]
2024-12-07 00:22:02 +03:00
Tabii bu örnekte aslında içlem ifadesindeki listeyi yineleme (repition) yoluyla da oluşturabiliriz. İçlem döngüsü her
çalıştığında yeni bir yineleme yaılıp yeni bir liste elde edilecektir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [[0] * 5 for _ in range(10)]
>>> 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]]
2024-12-07 00:22:02 +03:00
Yorumlayıcının değiştirilemez nesneler için bizim lehimize optimizasyon yaptığına (bunu yapması zorunlu değil) diikat
ediniz. Örneğin:
a = [0, 0, 0, 0, 0]
Burada yorumlayıcı listenin her elemanına gereksiz bir biçimde içinde sıfır olan farklı int nesnenin adresini yerleştirmez.
Çünkü yapmasının bizim için hiçbir faydası yoktur. Örneğin:
>>> a = [0, 0, 0, 0, 0]
>>> for i in range(5):
... print(id(a[i]))
...
140723586529688
140723586529688
140723586529688
140723586529688
140723586529688
Tabii int türü değiştirilemez olduğu için biz bu listenin bir elemanını değiştirdiğimizde diğerleri zaten bu değişiklikten
etkilenmeyecektir. Örneğin:
>>> a[0] = 100
>>> a
[100, 0, 0, 0, 0]
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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]]
2024-12-07 00:22:02 +03:00
Burada aslında içlemle elde edilen liste dolaşılmaktadır.
Aşağıdaki örnekte bir 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']
Bu içlem ifadesi iki parça olarak aşağıdaki gibi de yazılabilirdi:
temp = [word[::-1] for word in text.split()]
a = [revword for revword in temp if revword[0] == 'n']
O halde burada önce yazı sözcüklere ayrılıp ters çevrilmiştir. Böylece sözcükleri ters çevrilmiş bir liste elde
edilmiştir. Sonra da bu listenin (Yani ters çevrilmiş sözcüklerin) ilk elemanı 'n' olan ters çevrilmiş sözcükler
elde edilmiştir. a listesi şöyle olacaktır:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
['nügub', 'nes', 'nittig']
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Daha önceden de belirttiğimiz gibi içlemler alternatiflerine göre daha hızlı liste oluşturmaktadır. Tabii bu durum
yorumlayıcıdan yorumlayıcıya değişebilmektedir. Bu nedenle eğer hesaplamalar sonucunda elde edilmesini listeyi
içlemle elde edebiliyorsanız içlemi tercih edebilirsiniz.
2024-07-15 13:23:34 +03:00
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 = []
2024-12-07 00:22:02 +03:00
for i in range(10_000_000):
2024-07-15 13:23:34 +03:00
a.append(str(i))
stop = time.time()
print(stop - start) # 3.7108449935913086
start = time.time()
2024-12-07 00:22:02 +03:00
a = [str(i) for i in range(10_000_000)]
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Aşağıdaki örnekte listenin elemanlarının ikili 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)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
küme içlemine göre daha yavaş bir çalışmaya neden olmaktadır. Yani örneğin:
a = [1, 2, 3, 4, 5]
s = {i + k for i in a for k in a}
bu kümeyi biz aşağıdaki gibi de elde edebilirdik:
s = set([i + k for i in a for k in a])
Ancak burada gereksiz bir biçimde önce liste nesnesi sonra küme nesnesi oluşturulmaktadır. Oysa küme içlemleri tek
hamlede küme nesnesi oluşturmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-07 00:22:02 +03:00
Ancak içlemin ifade kısmının "anahtar:değer" biçiminde bir sentaks içermesi gerekir. Tabii anahtar ve değer birer yine
ifade olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
{key: value for x in iterable}
2024-12-07 00:22:02 +03:00
Burada for döngüsünün her çalışmasında szlüğe yeni bir anahtar-değer çifti eklenecektir. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2024-12-07 00:22:02 +03:00
Aslında bu işlem aşağıdaki gibi sözlük içlemiyle kompakt bir biçimde de yapılabilir.:
result = {d[key]: key for key in d}
ya da örneğin:
result = {value: key for key, value in d.items()}
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
d = {'ali': 10, 'veli': 20, 'selami': 30, 'ayşe': 40, 'fatma': 50}
result = {value: key for key, value in d.items()}
print(result)
result = {d[key]: key for key in d}
print(result)
2024-12-07 00:22:02 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
İçlemler bir nesne oluşturduğuna göre onları ara bir değişkende saklamadan doğrudan işleme de sokabiliriz. Daha önce
de bunun çeşitli örneklerini yapmıştık. Örneğin:
2024-07-15 13:23:34 +03:00
a = [1, 2, 3, 4, 5]
for t in {x: str(x) for x in a}.items():
print(t)
2024-12-07 00:22:02 +03:00
Biz burada sözlük içlemi ile sözlük nesnesi elde ettikten sonra hemen nokta operatörü ile onun metodunu çağırdık.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-07 00:22:02 +03:00
Ancak demet içlemleri diye bir içlem türü yoktur. Aslında sentaks olarak demet içlemi gibi bir sentaks vardır. Ancak bu
sentaks tamamen farklı bir anlama gelmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
(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)
#------------------------------------------------------------------------------------------------------------------------
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!)
2024-12-07 00:22:02 +03:00
Eskiden bu değerleri veren Python'da standart fonksiyonlar yoktu. Ancak Python 3.8 ile birlikte math modülüne perm ve
comb fonksiyonları eklenmiştir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2024-12-07 00:22:02 +03:00
ve bize bir dolaşım nesnesi verir. İşte permutations fonksiyonunun bize verdiği dolaşım nesnesini dolaştığımızda bizim
birinci parametreyle verdiğimiz kümenin ikinci parametreyle belirtilen permütasyonlarını demetler halinde elde ederiz.
2024-07-15 13:23:34 +03:00
Ö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.
2024-12-07 00:22:02 +03:00
permutations fonksiyonun ikinci parametresi girilmezse sanki dolaşılabilir nesnenin uzunşuğu girilmiş gibi bir
etki söz konusu olur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import itertools
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma']
for t in itertools.permutations(names, 3):
print(t)
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
39. Ders 24/11/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Ş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.
2024-12-07 00:22:02 +03:00
Oyuncuların players isimli bir dolaşılabilir nesnede bulunduğunu düşünelim. O halde her oyuncunun sırasıyla kimlerle
oynayacağı aşağıdaki gibi belirlenebilir:
import itertools
players = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'nuri]
for player1, player2 in itertools.combinations(players, 2):
print(player1, '<--->', player2)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import itertools
2024-12-07 00:22:02 +03:00
players = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'nuri']
2024-07-15 13:23:34 +03:00
for player1, player2 in itertools.combinations(players, 2):
print(player1, '<--->', player2)
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir grup oyuncunun ya da takımın hepsinin birbirileriyle oynayarak puanlandığı sisteme "döner turnuva" ya da "lig usulü
turnuva" denilmektedir. Tabii bu tür turnuvalarda aslında kişiler ya da takımlar genellikle iki kez karşılaşırlar.
Örneğin böyle bir satranç turnuvasında iki oyuncu iki kez karşılaşmaktadır. Birinci karşılaşmada biri beyazla oynarken
ikinci karşılaşmada diğeri beyazla oynamaktadır. Futbolda da benzer biçimde ilk karşılaşmada bir takım kendi sahasında
oynarken ikinci karşıalşamda deplasmanda oynamaktadır. Ayrıca bu tür turnuvalarda karşılaşmalar seri bir biçimde yapılmaz
aynı oturumda tüm kişiler ya da takımlar aynı zamanda oynarlar. Örneğin "ali", "veli", "selami", "ayşe", "fatma" ve "nuri"
nin katıldığı satranç turnuvasında birinci gün maçları şöyle olabilir:
ali <-> veli
selami <-> ayşa
fatma <-> nuri
C(6, 2) = 15 olduğuna ya da her oyuncu diğer 5 oyuncuyla oynayacağına göre toplam 5 oturumda turnuvanın ilk yarısı
tamamlanacaktır. Kimin hangi oturumda kimle oynayacağının tablosuna furbolda "fikstür" de denilmektedir.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bilindiği gibi temel bellek birimi byte'tır. Bugünkü bilgisayar sistemlerinde adresleme byte esasına göre yapılmaktadır.
2024-12-07 00:22:02 +03:00
Programcılar çeşitli biçimlerde bir grup byte'ı saklamak durumunda kalabilirler. Bu nedenle programlamlama dillerinde
2024-07-15 13:23:34 +03:00
byte kavramını temsil eden türler bulundurulmuştur. İşte Python'da da byte kavramını temsil etmek için "bytes" isimli bir
2024-12-07 00:22:02 +03:00
tür bulunmaktadır.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Aslında byte kavramı 8 bitten oluşur. 8 bit de [0, 255] arasında bir sayı belirtir. Yani bir byte'lık bilgi aslında
0 ile 255 arasında bir sayı ile temsil edilebilmektedir. Ancak byte kavramını int türüyle temsil etmek verimsizdir.
Çünkü int türü sınırsız uzunlukta tamsayıları temsil etmek için düşünülmüştür.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Byte temel bellek birimi olarak kullanıldığından programlama 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ı
belirtir. Bir programdaki her şey (komutlar, yazılar, sayılar) hep byte yığınlarından oluşmaktadır.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
Bir bytes nesnesini pratik bir biçimde oluşturabilmek için tek tırnağa, iki tırnağa ya da üç tırnağa yapışık 'b' ya
da 'B' harfi kullanmak gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = b'ankara'
>>> type(b)
<class 'bytes'>
>>> b = B'izmir'
>>> type(b)
<class 'bytes'>
2024-12-07 00:22:02 +03:00
bytes türü her ne kadar byte kavramı için düşünülmüşse de Python'da sanki bir string gibi yaratılmaktadır. Ancak bytes
türü ile str türü tamamen farklı türlerdir. 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:
2024-07-15 13:23:34 +03:00
b = b'a'
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> b = b'ağrı'
File "<stdin>", line 1
b = b'ağrı'
^
SyntaxError: bytes can only contain ASCII literal characters.
2024-12-07 00:22:02 +03:00
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. Aslında 2'lik sistemde
4 bit bir hex sayı ile temsil edilir. Böylece 8 bit iki hex sayı ile temsil edilebilmektedir. Örneğin 0000 0000 biçimindeki
byte'ın hex karşılığı 00 biçimindedir. 1111 1111 biçiminde byte'ın da hex karşılığı FF biçimindedir. Örneğin 1100 0101
biçimindeki byte'ın hex karşılığı C5'tir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = b'\xFC\x17\x56'
>>> b
b'\xfc\x17V'
2024-12-07 00:22:02 +03:00
buradaki bytes nesnesinde 3 byte vardır. Birinci byte hex FC byte'ı, ikinci byte hex 17 byte'tı, üçüncü byte ise hex 56
byte'tıdır. Bir bytes nesnesi yazdırılırken sanki bir string gibi yazdırılmaktadır. Eğer byte değeri [0, 127] arasında ise
ve ilk 32 karakterin arasında değilse ona karşı gelen karakter yazdırılmakta diğer durumlarda \0xhh biçiminde yazdırılmaktadır.
(ASCII tablosunun ilk 32 karakterinin görüntülenemeyen (nonprintable) karakterlerden oluştuğunu anımsayınız.) Örneğin:
>>> b = b'\xFC\x17\x56'
>>> b
b'\xfc\x17V'
Burada b nesnesi yazdırıldığında çıkan yazıya dikkat ediniz. \xfc yazısı FC byte'ını belirtmektedir. \0x17 yazısı hex 17
byte'ını belirtmektedir. \x56 byte'ı [0, 127] arasında olduğu için onun sayısal değeri değeri değil ASCII karakter karşılı
olan 'V' karakteri yazdırılmıştır:
>>> ord('V')
86
>>> hex(86)
'0x56'
Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = b'\x168\x41\xFF'
2024-12-07 00:22:02 +03:00
burada aslında 4 byte'lık bir bilgi vardır. İlk byte hex 16 bayte'ıdır. İkinci byte '8' karakterinin ASCII sıra
numarasından oluşan byte'tır. Üçüncü byte hex 41 numaralı byte'tır bunun ASCII tablosundaki karakter karşılığı 'A'dır.
son byte ise hex FF numaralı byte'tır. Böylece biz b nesnesini yazdırmak istediğimizde aşağıdaki gibi bir yazı görürüz:
2024-07-15 13:23:34 +03:00
>>> 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
2024-12-07 00:22:02 +03:00
boş bir bytes nesnesi elde ederiz. Eğer bu fonksiyona biz int değerlerden oluşan 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:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
>>> a = [34, 56, 129, 227, 78]
2024-07-15 13:23:34 +03:00
>>> 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 "<stdin>", line 1, in <module>
ValueError: bytes must be in range(0, 256)
2024-12-07 00:22:02 +03:00
Dolaşılabilir nesnenin tüm elemanlarının int türden olması gerekmektedir. Örneğin:
>>> a = [34, 12., 129, 227, 78]
>>> b = bytes(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
"utf-8", "utf-16", "utf-32" gibi değişik encoding'leri vardır. Tabii bir string eğer mümkünse ASCII tablosuna göre ya da
ASCII tablosunun çeşitli code page'lerine göre de bytes nesnesine dönüştürülebilir.
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
bytes nesnesine dönüştürmektedir. Bu durumda bytes fonksiyonuna encoding belirten ikinci bir argümanın girilmesi gerekir.
2024-07-15 13:23:34 +03:00
Ö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.
2024-12-07 00:22:02 +03:00
Bu metot da encoding parametresi almaktadır. Ancak bu parametre girilmezse default durumda 'utf-8' encoding'i kullanılır.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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'
2024-12-07 00:22:02 +03:00
Encoding belirten parametrenin ismi "encoding" biçimindedir. Bazı programcılar okunabilirliği artırmak için parametrenin
ismini açıkça yazmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = bytes('ankara', encoding='utf-8')
>>> b
b'ankara'
>>> s = 'ankara'
>>> b = s.encode(encoding='utf-8')
>>> b
b'ankara'
2024-12-07 00:22:02 +03:00
bytes fonksiyonun birinci parametresine str nesnesi girildiğinde encoding belirtmenin zorunlu olduğuna dikkat ediniz. Ancak
str sınıfının encode metodunda encoding belirtilmezse default olarak 'utf-8' encoding kullanılmaktadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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. str fonksiyonun birinci parametresi bir bytes nesnesi
olarak girilirse ikinci parametrede encoding'in belirtilmesi gerekir. Fonksiyon bize str nesnesi verecektir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = b'\x61\x62\x63'
>>> s = str(b, encoding='ascii')
>>> s
'abc'
Örneğin:
2024-12-07 00:22:02 +03:00
>>> s = 'ağrı dağı'
>>> b = s.encode()
>>> b
b'a\xc4\x9fr\xc4\xb1 da\xc4\x9f\xc4\xb1'
>>> k = str(b, encoding='utf-8')
>>> k
'ağrı dağı'
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 alır. Ancak encoding belirtilmeyebilir. Bu durumda default
"utf-8" anlaşılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = b'\x61\x62\x63'
>>> s = b.decode(encoding='ascii')
>>> s
'abc'
2024-12-07 00:22:02 +03:00
Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
'ağrı dağı'
>>> s = 'ağrı dağı'
>>> b = s.encode()
>>> k = b.decode()
>>> k
'ağrı dağı
Ö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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
bytes nesnelerinin herhangi bir byte'ına yine [] operatörü ile erişilir. Ancak bu operatör bize int bir değer vermektedir.
Tbaii bu int değer 0 ile 255 arasında bişr sayı olabilir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> b = b'ankara'
>>> val = b[0]
>>> type(val)
<class 'int'>
>>> 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'
2024-12-07 00:22:02 +03:00
>>> b[0] = 110
2024-07-15 13:23:34 +03:00
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
2024-12-07 00:22:02 +03:00
bytes sınıfının str sınıfında olan pek çok metodu bulunmaktadır. Örneğin: center, split, strip, count, index, find gibi.
Tabii bu metotlar aslında seyrek kullanılmaktadır. Çünkü bytes nesnesindne amaç yazısal işlem yapmak değil byte kavramını
oluşturmaktadır.
2024-07-15 13:23:34 +03:00
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'
2024-12-07 00:22:02 +03:00
bytes sınıfının başka 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:
2024-07-15 13:23:34 +03:00
>>> 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.
2024-12-07 00:22:02 +03:00
Aralarındaki tek fark bytes türünün "değiştirilemez (immutable)", bytearray türünün ise "değiştirilebilir (mutable)"
olmasıdır. Bir bytearray nesnesi ancak bytearray fonksiyonu ile yaratılabilir. String gibi başına önekler getirilerek
yaratılmaz.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
bytearray fonksiyonunda biz parametre olarak int değerlerden dolaşılabilir bir nesne verirsek bu dolaşılabilir nesnenin
elemanlarından bytearray nesnesi oluşturulur. Tabii bu durumda yine dolaşılabilir nesnenin elemanlarının [0, 255] arasında
int değerler belirtmesi gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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')
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
>>> ba = bytearray([1, 2, 3, 4, 5])
2024-07-15 13:23:34 +03:00
>>> ba
2024-12-07 00:22:02 +03:00
bytearray(b'\x01\x02\x03\x04\x05')
>>> ba[0] = 200
2024-07-15 13:23:34 +03:00
>>> ba
2024-12-07 00:22:02 +03:00
bytearray(b'\xc8\x02\x03\x04\x05')')
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
bytearray türü üzerinde de dilimler yapılabilmektedir. Tabii bu dilimlemelerden bytearray nesneleri elde edilir.
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
bytes ve bytearray türleri len fonksiyonuna sokulabilir. Bu durumda nesne içerisindeki byte'ların sayısı elde edilmekedir.
Örneğin:
>>> b = bytes([1, 2, 3, 4, 5])
>>> len(b)
5
>>> ba = bytearray([1, 2, 3, 4, 5])
>>> len(ba)
5
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
bytes türü değiştirilemez olduğu için yorumlayıcı bu türle işlemlerde bazı optimizasyonlar yapabilmektedir. 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:
2024-07-15 13:23:34 +03:00
>>> d = {b'ali': 100}
>>> d
{b'ali': 100}
>>> d = {bytearray(b'ali'): 100}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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)"
2024-12-07 00:22:02 +03:00
uygulamak için gereken yapı taşlarıdır. NYPT'nin tek cümleyle 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
2024-07-15 13:23:34 +03:00
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 <isim>: <suite>
Ö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
2024-12-07 00:22:02 +03:00
ilk harfi büyük olur. Biz de kursumuzda 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ı harflendirmeyle ifade edeceğiz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Sınıflar aynı zamanda birer tür de belirtmektedir. Bu durumda biz oluşturduğumuz bir sınıf türünden nesneler yaratabiliriz.
B bir sınıf türünden nesne yaratmanın genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
sınıf_ismi([argüman_listesi])
2024-07-15 13:23:34 +03:00
Örneğin:
s = Sample()
2024-12-07 00:22:02 +03:00
Burada Sample türünden bir nesne yaratılmıştır, bu nesnenin adresi de s değişkenşne atanmıştır. s de artık Sample türünden
bir nesnenin adresini tutmaktadır.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
s = str()
t = tuple()
a = list()
2024-12-07 00:22:02 +03:00
Java ve C# gibi bazı programlama dillerinde sınıf türünden nesneler new anahtar sözcüğü (operatörü) kullanılarak
yaratılmaktadır. Örneğin:
Sample s = new Sample(); // Java ve C#'ta sınıf türünden nesne yaratımı böyle yapılıyor
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class Sample:
pass
s = Sample()
print(type(s)) # <class '__main__.Sample'>
#------------------------------------------------------------------------------------------------------------------------
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)"
2024-12-07 00:22:02 +03:00
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 özniteliği (instance attribute)" denilmektedir. Biz anlatımlarda
bazen "örnek özniteliği" bazen de "nesnenin özniteliği" diyeceğiz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Bir sınıf türünden birden fazla nesne (yani örnek) yaratabiliriz. Her yarattığımız nesne aslında farklı bir nesnedir.
Dolayısıyla bu nesnelerin id'leri de farklıdır. Örneğin:
2024-07-15 13:23:34 +03:00
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:
2024-12-07 00:22:02 +03:00
<değişken_ismi>.<öznitelik_ismi> = değer
2024-07-15 13:23:34 +03:00
Ö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.
2024-12-07 00:22:02 +03:00
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 de sırasıyla int bir nesne, int bir nesne ve str türünden bir nesne bulunmaktadır.
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
tutmaktadır. Bu nesne üç parçadan oluşmaktadır. Nesnenin parçalarına "örnek öznitelikleri (instance attribute)"
denilmektedir.
Burada bir noktaya daha dikkatiniz çekmek istiyoruz. Bir nesnenin bir özniteliğini oluşturduğumuzda bu öznitelik
değerin kendsini tutmamaktadır, onun adresini tutmaktadır. Örneğin:
>>> class Sample:
... pass
...
>>> s = Sample()
>>> s.a = 10
>>> s.b = 20
>>> s.c = 'ankara'
>>> id(s)
4304122256
>>> id(s.a)
4298523152
>>> id(s.b)
4298523472
>>> id(s.c)
4304260336
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
40. Ders 30/11/2024 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
Tabii NYPT'de aslında aynı sınıf türünden nesnelerin özniteliklerinin isim ve tür bakımından aynı fakat içerik bakımından
farklı olması beklenir. Yani aynı sınıf türünden nesnelerin farklı özniteliklere sahip olması NYPT bakımından uygun
değildir. Ancak Python'da istenirse böyle bir durum da oluşturulabilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Bir sınıf nesnesi için öznitelikler yaratıldıktan sonra bu öznitelikler istenildiği zaman <değişken_ismi>.<öznitelik_ismi>
sentaksıyla kullanılabilmektedir. Nesnenin özniteliklerini "nesnenin içerisindeki değişkenler" gibi düşünebilirsiniz.
2024-12-07 00:22:02 +03:00
Yukarıda belirttiğimiz gibi nesnenin öznitelikleri tüm değişkenler gibi aslında birer adres tutmaktadır. Asıl nesneler
2024-07-15 13:23:34 +03:00
o adreslerde bulunmaktadır. Örneğin:
2024-12-07 00:22:02 +03:00
class Sample:
pass
s = Sample()
s.a = 10
s.b = 'ankara'
Burada s nesnesinin içerisinde a ve b değişkenleri yaratılmıştır. Bu değişkenler sırasıyla int türünen nesnenin ve str türünden
nesnenin adreslerini tutmaktadır:
s ------> Sample Nesnesi
a -----------------> 10 (int nesne)
b -----------------> 'ankara' (str nesnesi)
2024-07-15 13:23:34 +03:00
>>> 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
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
zar fonksiyonunun Sample sinını ile aynı girinti düzeyine sahip olduğuna dikkat ediniz.
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
<değişken_ismi>.<metot_ismi>([argüman listesi])
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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)
2024-12-07 00:22:02 +03:00
Burada 10 değeri tar metodunun a parametresi için, 20 değeri ise b parametresi için girilmiştir.
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
class Sample:
pass
s = Sample()
s.a = 10
s.b = 20
k = s
print(k.a) # 10
print(k.b) # 20
2024-12-07 00:22:02 +03:00
Burada s ya da k değişkeninin kullanmanın hiçbir farkı yoktur. Çünkü bunlar aynı Sample nesnesini gösteriyor durumdadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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 parametresi metodun çağrılmasında kullanılan nesneyi gösteren bir değişkendir. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def foo(self, a, b):
pass
s = Sample()
s.foo(10, 20)
2024-12-07 00:22:02 +03:00
Burada s değişkeni (yani onun içindeki adres) self parametresine, 10 değeri (yani 10 değerinin bulunduğu nesnesinin adresi)
a parametre değişkenine ve 20 değeri de (yani 20 değerinin bulunduğu nesnesinin adresi) b parametre değişkenine atanmaktadır.
Burada self tamamen s ile aynı nesneyi gösterir duruma gelmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
de aynı nesneyi gösteriyor durumda olur. Nesneye hangi değişkenle erişildiğinin bir önemi yoktur. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
NYPT'te aslında bir sınıf ortak veriler (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:
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
üzerinde işlem yapma potansiyeline sahiptir. Başka bir deyişle bir sınıfın metotları aynı nesnenin öznitelikleri üzerinde
işlem yapan fonksiyonlar gibidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
içerisindeki adres) metodun birinci parametresi olan self parametresine atanmaktadır. Bu durumda self parametresi
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Yukarıdaki işlemin tersini de yapabiliriz. Yani metodun içerisinde nesenin özniteliklerini yaratılabiliriz. Yaratılan
bu öznitelikleri daha sonra kullanılabiliriz:
2024-07-15 13:23:34 +03:00
class Sample:
2024-12-07 00:22:02 +03:00
def set(self):
2024-07-15 13:23:34 +03:00
self.a = 10
self.b = 20
s = Sample()
2024-12-07 00:22:02 +03:00
s.set()
2024-07-15 13:23:34 +03:00
print(s.a, s.b) # 10 20
2024-12-07 00:22:02 +03:00
Burada set 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 özniteliklerine s yoluyla
da erişebiliriz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class Sample:
2024-12-07 00:22:02 +03:00
def set(self):
2024-07-15 13:23:34 +03:00
self.a = 10
self.b = 20
s = Sample()
2024-12-07 00:22:02 +03:00
s.set()
2024-07-15 13:23:34 +03:00
print(s.a, s.b) # 10 20
2024-12-07 00:22:02 +03:00
s
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Modüller konusunda da belirtmiştik, Python'da başında ve sonunda iki alt tire olan özel bazı isimler kullanılmaktadır.
Örneğin __init__, __add__ gibi. __xxx__ biçimindeki isimleri "dunderscore xxx" ya da "dunder xxx" biçiminde ifade
ettiğimizi söylemiştik. Yani örneğin biz "dunder init" demekle "__init__" ismini, "dunder add" demekle __add__ ismini
kastediyor olmaktayız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
nesnenin adresini self parametresine geçirerek __init__ metodunu çağırır, ondan sonra s değişkenine atamayı yapar.
Yani önce __init__ metodu çağrılıp sonra nesnenin adresi s değişkenine atanmaktadır.
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Yukarıda de belirtitğimiz gibi bir sınıf türünden nesne yaratılmak istendiğinde önce nesne yaratılır. Sonra sınıfın
otomatik bir biçimde __init__ metodu çağrılır. Ondan sonra nesnenin adresi değişkene atanır. Örneğin:
2024-07-15 13:23:34 +03:00
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ış
2024-12-07 00:22:02 +03:00
olan nesneyi belirtmektedir. İşte programcı tipik olarak yarattığı nesnenin nesnenin özniteliklerini __init__ metodu
içerisinde oluşturmaktadır. Böylece yaratılan her nesne aynı elemanlara yani özniteliklere sahip olur. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2024-12-07 00:22:02 +03:00
Burada s ve k değişkenlerinin gösterdiği yerdeki nesnelerin öznitelikleri __init__ tarafından yaratılmıştır. Böylece
s'in gösterdiği nesnenin içerisinde de k'nın gösterdiği nesnenin içerisinde de a ve b öznitelikleri oluşturulmuştur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-07 00:22:02 +03:00
Python'daki __init__ metodu bu dillerdeki "constructor" metotlarına benzemektedir. Ancak Python'duna __init__ metoduna
"constructor" değil "initializer" denilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
__init__ metodunun self dışında başka parametreleri de olabilir. Bu durumda nesne yaratılırken bu parametreler için de
2024-07-15 13:23:34 +03:00
argüman girilmelidir. Örneğin:
class Sample:
def __init__(self, a, b):
self.a = a
self.b = b
2024-12-07 00:22:02 +03:00
Burada nesneyi yaratırken parantezlerin içerisinde a ve b için iki argüman girmeliyiz. Örneğin:
2024-07-15 13:23:34 +03:00
s = Sample(10, 20)
2024-12-07 00:22:02 +03:00
Burada Sample(10, 20) ifadesi ile yeni yaratılan nesnenin adresi self parametresine, 10 nesnesinin adresi a parametresine,
2024-07-15 13:23:34 +03:00
20 nesnesinin adresi de b parametresine aktarılır. Bu parametreler de nesnenin özniteliklerine atanmıştır.
2024-12-07 00:22:02 +03:00
Python programcıları genellikle (ama her zaman değil) diğer metotlarda da kullanabilmek için __init__ metoduna geçirilen
parametreleri kullanrak nesnede öznitelikler oluşturmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Tabii biz nesnenin özniteliklerini başka bir metotlarda da yaratabiliriz. __init__ metodunun diğer metotlardan tek
farkı yorumlayıcı tarafından otomatik biçimde çağrılmasıdır. Aşağıdaki örnekte nesnenin a ve öznitelikleri __init__
metodunda değil set metodunda yaratılmıştır. Ancak bu set metodu yorumlayıcı tarafındna otomatik çağrılmadığı için
programcı tarafından çağrılmıştır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
İnsanları hayvanlardan ayıran en önemli özellik nedir? Pek çok kişi böyle bir soru sorulduğunda "düşünmek" biçiminde
yanıt vermektedir ve isnanın "düşünen bir hayvan" olduğunu belirtmektedir. Aslında böyle bir tanımlama yanlıştır.
Her ne kadar tam insanlar gibi olmasa da hayvanlar da düşünebilmektedir. İnsanı hayvandan ayıran asıl önemli özellik
insanların kavram oluştrabilmesidir. İnsanlar birbirine benzeyen nesneleri bir isimle sınıflandırmış ve bu isimleri
kullanarak daha üst düzey bir düşünce biçimi oluşturmuştur. Örneğin aslında doğada "ağaç" diye bir nesne yoktur.
Biz birbirine benzer birtakın nesnelere "ağaç" demekteyiz. Ağaç bir kavramdır, gerçek bir nesne değildir. Sokağımızdaki
ağaçlar bu ağaç kavramının örnekleridir. İşte insanlar evrim süreci içerisinde dış dünyadaki birbirine benzeyen nesnelere
ilişkin kavramlar oluşturmuş ve onların sembolik görüntülerini zihinlerinde oluşturabilmiştir. Bunun bir sonucu olarak
da diller ortaya çıkmıştır. Bir kedi için bir köpek için ağaç diye bir kavram yoktur. Onların dünyasında somut ağaçlar
vardır. Bir kedi ya da köpek ağacı görünce onunla ilgili işlemler yapabilir ancak bu ağaç algı planının dışına çıktığında
artık onun için yok olmuştur. Onun zihinsel temsilini yapamamaktadır.
Kavramlar gerçek dünyada yoktur. Gerçek dünyada bu kavramlara uygun örnekler (instances) vardır. Ağaç bizim kafamızda
uydurduğumuz bir şeydir. Gerçek dünyada ağaç diye bir şey yoktur. Somut birbirine benzer ama hepsi farklı olan nesneler
vardır. Biz bir mağazadaki aynı renkte iki sandalyenin aynı olduğunu varsayarız. Aslında onlar birbirilerinin aynısı
değildir. Tabii eğer insanlar bu biçimde kavram oluşturmasydı. Karmaşık dış dünyayı zihinlerinde temsil edemezlerdi.
Aslında kavramlar kısıtlı donanıma sahip insan beyninin gerçek dünyayı basit bir biçimde modellemek için oluşturdukları
mantıksal imgelemlerdir.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-07 00:22:02 +03:00
Bir proje NYPT'de modellenirken önce projedeki tüm kavramlar sınıflarla temsil edilir. Sonra bu sınıflar türünden gerçek
nesneler oluşturulur. Program bu nesneleri kullanarak yazılmaktadır. Örneğin biz hastane otomasyonunu NYPT'de modellemek
istersek önce hastane kavramını, doktor kavramını, hemşire kavramını bir sınıfla temsil ederiz. Sonra bu örneğin hastanemizde
5 tane doktor varsa 5 tane Doktor sınıfı türünden nesne yaratırız. Artık doktoru bir kavram olmaktan çıkartıp gerçek
doktor nesnelerini oluşturmuş oluruz. Benzer biçimde hemşireler için HEmşire sınıfı türünden nesneler oluştururz. Her hasta
için Hasta sınıfı türünden nesneler yaratırız. Programamızı bu nesneleri kullanarak ilgili seınıfların metotlarını çağuırarak
yazarız.
Yukarıda da belirttiğimiz gibi NYPT'de bizim ilgilendiğimiz her kavram bir sınıfla temsil edilmelidir. Örneğin tarih
bir kavramdır. İçerisinde gün, ay ve yıl bilgilerine içeren zamanda belli bir yer belirten bir kavramdır. Biz de
örneğin tarih bilgilerini Date isimli bir sınıfla temsil edebiliriz:
class Date:
def __init__(self, day, month, year):
self.day = day
self.month = month
self.year = year
def disp(self):
print(f'{self.day:02d}/{self.month:02d}/{self.year:04d}')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class Date:
def __init__(self, day, month, year):
self.day = day
self.month = month
self.year = year
def disp(self):
2024-12-07 00:22:02 +03:00
print(f'{self.day:02d}/{self.month:02d}/{self.year:04d}')
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
gerçek ve sanal kısımları __init__ metodunda nesnenin özniteliklerinde saklanmış disp metodunda bunlar ekrana
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Bir sınıf bir konuyla ilgili belli işlemleri yapmak amacıyla yazılır. Tipik olarak biz bir sınıf nesnesini yaratırken
2024-07-15 13:23:34 +03:00
__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
2024-12-07 00:22:02 +03:00
biz yalnızca birkaç bilgiyi nesnenin özniteliklerinde tutup disp metodunda onları ekrana yazdırdık. Oysa 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 tarihini veren, iki tarih arasındaki gün farkının elde edilmesini sağlayan metotlara sahip
olabilir. Örneğin biz yukarıdaki Point sınıfına başka faydalı metotlar da ekleyebiliriz. Sınıfın shift metodu nesnenin
tuttuğu noktayı öteleyebilir, disytance metodu iki nokta arasındaki uzaklığa geri dönebilir:
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def shift(self, deltax, deltay):
self.x += deltax
self.y += deltay
def distance(self, pt):
return math.sqrt((self.x - pt.x) ** 2 + (self.y - pt.y) ** 2)
def disp(self):
print(f'({self.x}, {self.y})')
Burada distance metoduna dikkat ediniz. Biz bu metodu şöyşe çağırabiliriz:
pt1 = Point(3, 2)
pt2 = Point(1, 4)
dist = pt1.distance(pt2)
Burada pt1 metodun self parametresine, pt2 de metodun pt parametresine aktarılacaktır. Dolayısıyla metot da pt1 ile
pt2 arasındaki uzaklığa geri dönecektir.
#------------------------------------------------------------------------------------------------------------------------
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def shift(self, deltax, deltay):
self.x += deltax
self.y += deltay
def distance(self, pt):
return math.sqrt((self.x - pt.x) ** 2 + (self.y - pt.y) ** 2)
def disp(self):
print(f'({self.x}, {self.y})')
pt1 = Point(3, 2)
pt1.disp()
pt1.shift(1, -1)
pt1.disp()
pt2 = Point(3, 5)
dist = pt1.distance(pt2)
print(dist)
#------------------------------------------------------------------------------------------------------------------------
41. Ders 01/12/2024 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Python'un 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, ...).
Örneğin:
import datetime
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
d = datetime.date(1920, 4, 23)
days = ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar']
print(days[d.weekday()]) # Cuma
Biz date nesnesinin içerisindeki tarih bileşenlerini nesnenin day, month ve year özniteliklerinden istersek geri
alabiliriz. Örneğin:
d = datetime.date(1920, 4, 23)
print(d.day, d.month, d.year)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()])
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Şimdi karmaşık sayıyı temsil eden Complex sınıfına add, sub ve multiply metotlarını ekleyelim. İki karmaşık sayı
toplanırken ve çıkartılırken gerçek kısımlar kendi aralarında, sanal kısımlar da kendi araların toplanıp çıkartılmaktadır.
Çarpma işleminde de iki gerçek sayının bileşenleri birbirleriyle çarpılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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):
2024-12-07 00:22:02 +03:00
result = Complex(0, 0)
result.real = self.real + z.real
result.imag = self.imag + z.imag
2024-07-15 13:23:34 +03:00
return result
def sub(self, z):
2024-12-07 00:22:02 +03:00
result = Complex(0, 0)
result.real = self.real - z.real
result.imag = self.imag - z.imag
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
return result
def multiply(self, z):
result = Complex(0, 0)
result.real = self.real * z.real - self.imag * z.imag
result.imag = self.real * z.imag + self.imag * z.real
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
return result
2024-07-15 13:23:34 +03:00
2024-12-07 00:22:02 +03:00
x = Complex(3, 2)
y = Complex(2, 1)
2024-07-15 13:23:34 +03:00
z = x.add(y)
z.disp()
z = x.sub(y)
z.disp()
2024-12-07 00:22:02 +03:00
z = x.multiply(y)
z.disp()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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ğı
2024-12-07 00:22:02 +03:00
anlamına gelir. Yani metotlar spesifik bir nesne üzerinde işlem yaparlar. Bu nedenle bir nesne ile çağrılırlar. Biz bir
metodu hangi nesneyle çağırırsak o metot aslında o nesnenin elemanları yani öznitelikleri üzerinde işlem yapar. Örneğin:
2024-07-15 13:23:34 +03:00
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.
2024-12-07 00:22:02 +03:00
O halde metotlar "bir kavrama ilişkin bir nesne (örnek) üzerinde" belli işlemleri yapan fonksiyonlardır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-07 00:22:02 +03:00
(constructor)" overload edilebilmektedir. Böylece nesne farklı biçimlerde yaratılabilmektedir. Halbuki Python'da sınıf
içerisinde yalnızca bir tane __init__ metodu bulunabilir. Farklı parametrelerle nesnenin yaratılması ancak default argümanlarla
yapılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Sınıfların metotları alternatif biçimde sınıf ismi ile de çağrılabilmektedir:
<sınıf_ismi>.<metot_ismi(değişken, ...)
Ancak bu çağrı biçiminde self parametresini açıkça argüman olarak geçirmek gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def foo(self):
2024-12-07 00:22:02 +03:00
pass
2024-07-15 13:23:34 +03:00
def bar(self, a):
2024-12-07 00:22:02 +03:00
pass
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2024-12-07 00:22:02 +03:00
Aslında sınıflar da Python'da deyim statüsündedir. Yorumlayıcı bir sınıf tanımlaması ile karşılaştığında onun içerisindeki
2024-07-15 13:23:34 +03:00
deyimleri de çalıştırmaktadır. Örneğin:
class Sample:
print('one')
def foo(self):
pass
print('two')
def bar(self):
pass
print('three')
2024-12-07 00:22:02 +03:00
Biz bu sınıf türünden hiçbir nesne yaratmasak bile yorumlayıcı yine de sınıfın içerisindeki kodları çalıştırmaktadır.
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
bir de "sınıf öznitelikleri (class attribute)" denilen bir kavram vardır. Biz "sınıf özniteliği" yerine bazen "sınıf
değişkenleri" de diyeceğiz. Pekiyi sınıf öznitelikleri nasıl yaratılmaktadır ve nasıl kullanılmaktadır?
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
yaratılabilir. Bunlara da "sınıfın öznitelikleri ya da sınıf değişkenleri" denilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
x = 10 # x global bir değişken
def foo():
y = 20 # y yerel bir değişken
class Sample:
2024-12-07 00:22:02 +03:00
z = 30 # sınıfın özniteliği (sınıf değişkeni)
2024-07-15 13:23:34 +03:00
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ı
2024-12-07 00:22:02 +03:00
içerisinden sınıf ismi ve nokta operatörü ile niteliklendirilerek kullanılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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ü
2024-12-07 00:22:02 +03:00
"operand olan değişkenin içerisindeki adresteki fonksiyon nesnesinin içerisinde kondu çalıştır" anlamına gelmektedir.
Örneğin:
2024-07-15 13:23:34 +03:00
foo()
Bu işlem aslında "foo değişkeninin içerisindeki adreste bulunan fonksiyon nesnesinin içerisindeki kodun çalıştırılacağı"
2024-12-07 00:22:02 +03:00
anlamına gelmektedir. Dolayısıyla anımsanacağı gibi Python'da "fonksiyonlar birinci sınıf vatandaştır". Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-12-07 00:22:02 +03:00
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:
2024-07-15 13:23:34 +03:00
Sample.x = 20 # geçerli
s = Sample()
Sample.foo(s) # geçerli
#------------------------------------------------------------------------------------------------------------------------
class Sample:
x = 10
def foo(self):
pass
print(type(Sample.x)) # <class 'int'>
print(type(Sample.foo)) # <class 'function'>
2024-12-07 00:22:02 +03:00
#------------------------------------------------------------------------------------------------------------------------
Mademki metot isimleri aslında fonksiyon nesnelerini gösteren birer sınıf değişkenidir. O halde bir metot ismini biz
başka bir sınıf değişkenine atayabiliriz. Bu durumda her iki sınıf değişkeni de aslında aynı fonksiyon nesnesini gösterir
durumda olur. Örneğin:
class Sample:
def foo(self):
print('foo')
x = foo
Burada foo da x de aslında Sample sınıfın bir değişkeni yani özniteliğidir. Dolayısıyla foo ismiyle yapabileceğimiz
her şeyi x ismiyle de yapabiliriz. Örneğin:
s = Sample()
s.foo()
s.x() # foo çağrılacak
Sample.foo(s)
Sample.x(s) # foo çağrılacak
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
42. Ders 07/12/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
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:
2024-07-15 13:23:34 +03:00
class Sample:
x = 10
def foo(self):
pass
s = Sample()
print(s.x) # geçerli ama yanlış anlaşılabilir!
2024-12-20 22:28:43 +03:00
print(Sample.x) # tavsiye edilen biçim
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
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:
2024-07-15 13:23:34 +03:00
class Sample:
def __init__(self):
self.x = 100
x = 10
s = Sample()
print(s.x) # 100
2024-12-20 22:28:43 +03:00
print(Sample.x) # 10
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Sınıfın öznitelikleri 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ılabilir. Oysa Python'da sınıf özniteliklerş bir metot
içerisinde doğrudan kullanılamaz. Sınıf öznitelikleri ya sınıf ismiyle ya da self parametresi yoluyla kullanılmak zorundadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Metotlar aslında sınıfın öznitelikleridir. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def foo(self):
2024-12-20 22:28:43 +03:00
pass
2024-07-15 13:23:34 +03:00
def bar(self):
2024-12-20 22:28:43 +03:00
pass
Burada aslında yorumlayıcı foo ve bar metotlarını gördüğünde birer fonksiyon nesnesi yaratır, bu fonksiyon nesnelerinin
adreslerini foo ve bar değişkenlerine yerleştirir. foo ve bar değişkenleri sınıfın içerisinde olduğu için birer
sınıf özniteliğidir. Örneğin:
2024-07-15 13:23:34 +03:00
s = Sample()
2024-12-20 22:28:43 +03:00
2024-07-15 13:23:34 +03:00
s.foo()
2024-12-20 22:28:43 +03:00
Sample.foo(s)
Biz sınıf özniteliklerini ilgili sınıf türünden değişkenlerle kullanabiliyorduk. İşte s.foo yapabilmemizin nedeni
budur. Tabii metot ile aynı isimli nesnede bir öznitelik oluşturulursa bu durumda artık biz metodu sınıf türünden
değişken ile çağıramayız. Örneğin:
s = Sample()
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
s.foo = 100
s.foo() # error! foo int türden, çağırma işlemi yapılamaz
Sample.foo(s) # geçerli
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Sınıfın bir metodu başka bir metodunu çağırabilir. Bu tür durumlarla sık karşılaşılmaktadır. Metotlar sınıf değişkeni
olduğuna göre ve sınıf değişkenleri metotlar içerisinde doğrudan kullanılamadığına göre bu çağrı ya sınıf ismiyle
ya da self parametresi yoluyla yapılabilir. Örneğin Sample sınıfının foo metodunun bar metodunu çağırmak istediğini
düşünelim. Bunu foo metodu içerisinde iki biçimde yapabiliriz:
self.bar()
Smaple.bar(self)
Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def foo(self):
2024-12-20 22:28:43 +03:00
self.bar()
2024-07-15 13:23:34 +03:00
Sample.bar(self)
2024-12-20 22:28:43 +03:00
2024-07-15 13:23:34 +03:00
def bar(self):
print('bar')
2024-12-20 22:28:43 +03:00
s = Sample()
s.foo()
Burada sınıfın foo metodu aynı sınıfın bar metodunu iki biçimde de çağırmıştır.
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Yukarıda da belirttiğimiza gibi C++, Java ve C# gibi dillerde sınıfın bir metodu (C++'ta sınıfın içerisindeki
fonksiyonlara "metot" değil "üye fonksiyon" denilmektedir) diğer metodunu doğrudan çağırabilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
class Sample:
def setval(self, x):
self.x = x
Sample.dispval(self) # geçerli
self.dispval() # geçerli
def dispval(self):
print(self.x)
2024-07-15 13:23:34 +03:00
s = Sample()
2024-12-20 22:28:43 +03:00
s.setval(10)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
2024-12-20 22:28:43 +03:00
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:
2024-07-15 13:23:34 +03:00
a = list('ankara')
2024-12-20 22:28:43 +03:00
Burada aslında list 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 (...) operatörü 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
Burada foo değişkeninin adresini bar değişkenine atamak isteyelim. Bunu aşağıdaki gibi yapamayız:
2024-07-15 13:23:34 +03:00
bar = foo
2024-12-20 22:28:43 +03:00
Çünkü foo Sample sınıfının bir özniteliğidir (yani Sample sınıfının bir sınıf değişkenidir). Mademki foo Sample sınıfının
bir özniteliğidir o halde ona sınıf ismiyle erişmemiz gerekir:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
f = Sample.foo
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Şimdi biz f değişkeni ile çağrı yaptığımızda foo metodu çağrılacaktır. Tabii bu durumda self parametresi için artık
bizim argüman girmemiz gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
s = Sample()
2024-12-20 22:28:43 +03:00
f(s)
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Tabii biz foo metodunu s.f() biçiminde çağıramayız. Çünkü s.bar() ifadesinde bar ismi önce s nesnesinin özniteliği olarak
2024-07-15 13:23:34 +03:00
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')
2024-12-20 22:28:43 +03:00
f = Sample.foo
2024-07-15 13:23:34 +03:00
s = Sample()
2024-12-20 22:28:43 +03:00
f(s)
#------------------------------------------------------------------------------------------------------------------------
Metotlar sınıfın özniteliği durumunda olduğuna göre biz bir metodun adresini sınıf içerisinde sınıfın başka bir değişkenine
atayabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
class Sample:
def foo(self):
print('foo')
f = foo
Burada artık foo da f de sınıfın bir özniteliğidir. Biz f değişkenini foo gibi de kullanabiliriz. Örneğin:
s = Sample()
s.foo() # geçerli
s.f() # geçerli
Sample.foo(s) # geçerli
Sample.f(s) # geçerli
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
class Sample:
def foo(self):
print('foo')
f = foo
s = Sample()
s.foo() # geçerli
s.f() # geçerli
Sample.foo(s) # geçerli
Sample.f(s) # geçerli
#------------------------------------------------------------------------------------------------------------------------
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 doğrudan (...) operatörü ile
kullanıldığında saklanmış olan sınıf nesnesi ile metot çağrılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def foo(self):
print('foo')
s = Sample()
2024-12-20 22:28:43 +03:00
f = s.foo
f()
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Burada f değişkeni bir metot nesnesini gösterir. Onun içerisinde de s ve foo birlikte bulunmaktadır. Dolayısıyla f
2024-07-15 13:23:34 +03:00
çağrıldığında sanki s.foo çağrılmış gibi olmaktadır.
#------------------------------------------------------------------------------------------------------------------------
class Sample:
def foo(self):
print('foo')
s = Sample()
2024-12-20 22:28:43 +03:00
f = s.foo
f()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
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)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
a = [1, 2, 3, 4, 5]
f = list.append
f(a, 10)
print(a)
f = a.append
f(20)
print(a)
#------------------------------------------------------------------------------------------------------------------------
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)) # <class 'type'>
s = Sample()
print(type(s)) # <class '__main__.Sample'>
Burada Sample değişkeninin türü type, s değişkeninin türü ise Sample biçimindedir.
#------------------------------------------------------------------------------------------------------------------------
class Sample:
pass
print(type(Sample)) # <class 'type'>
s = Sample()
print(type(s)) # <class '__main__.Sample'>
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
43. Ders 08/12/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Meta sözcüğü (Türkçeye "üst" biçiminde de çevrelimektedir) "bir kavramın kendisine yönelik onu tanımlayan kavramlar
için kullanılan" bir sözcüktür. Örneğin bir byte yığını bir veridir. Ancak o byte yığınının neresinin ne anlam ifade
ettiğine yönelik bir byte yığını varsa bu byte yığını meta veridir. Örneğin bir dilleri betimleyen dile "meta dil
(meta language)" denilmektedir. İşte Python'da da type sınıfı gibi sınıfları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 meta sınıfı da
değiştirilebilmektedir. Bu konu kurusumuz son bölümlerinde ele alınacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Biz bir değişkenin türünü type isimli built-in fonksiyon ile ekrana yazdırmıştık. Örneğin:
2024-12-20 22:28:43 +03:00
>>> a = 10
>>> type(a)
<class 'int'>
2024-07-15 13:23:34 +03:00
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:
2024-12-20 22:28:43 +03:00
>>> type(a)
<class 'int'>
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Burada biz aslında "int" isimli sınıfın bilgilerinin bulunduğu type nesnesini elde etmekteyiz. Yani burada aslında
type(a) ile int tamamen aynı anlamdadır:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
>>> a = 10
>>> type(a) is int
True
>>> id(type(a))
140722824288272
>>> id(int)
140722824288272
type türünden bir nesneyi print fonksiyonu ile yazdırdığımızda ya da komut satırında ismini yazıp ENTER tuşuna bastığımızda
o type nenesinin içerisinde hangi sınıf türünden bilgiler bulunuyorsa o sınıfn ismi ekrana yazdırılır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
>>> a = 10
>>> t = type(a)
>>> print(t)
<class 'int'>
Burada t aslında int sınıfının bilgilerini tutan type nesnesini göstermektedir. Biz bu t değişkenini yazdırmak istediğimizde
t değişkeninin gösterdiği yerdeki type nesnesi int isimli sınıfın bilgilerini tuttuğu için ekranda int yazısını görürüz.
Örneğin:
>>> class Sample:
... pass
...
>>> s = Sample()
>>> t = type(s)
>>> Sample is t
True
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Pekiyi type bir sınıf olduğuna göre onu betimleyen de bir nesne yok mu? İşte type sınıfının kendi bilgileri de type sınıfı
türünden bir nesnenin içerisinde saklanmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
>>> a = 10
>>> t = type(a)
>>> k = type(int)
>>> t
<class 'int'>
>>> k
<class 'type'>
Burada biz type(a) işemi yaptıımızda aslında int sınıfın bilgilerinin bulunduğu type nesnesinin adresini elde ederiz. type(int)
işlemini yaptığımızda type sınıfının bilgilerinin bulunduğu type nesnesinin adresini elde etmiş oluruz. Bu nedenle biz t'yi
yazdırdığımızda "<class 'int'>" yazısını k'yı yazdırdığımızda "<class 'type'>" görmekteyiz. Burada k değişkeni ile type değişkeni
aynı nesneyi göstermektedir:
>>> k is type
True
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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,
2024-12-20 22:28:43 +03:00
o halde biz bu type nesnesini kullanarak aynı sınıf türünden nesneler yaratabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
class Sample:
2024-07-15 13:23:34 +03:00
pass
s = Sample()
t = type(s)
k = t()
2024-12-20 22:28:43 +03:00
Biz burada t() işlemi ile aslında Sample sınıfı türünden nesne yaratmış olduk. Tabii Sample bir değişken olduğuna göre
Sample değişkenini başka bir değişkene de atayabiliriz:
2024-07-15 13:23:34 +03:00
class Sample:
pass
2024-12-20 22:28:43 +03:00
Mample = Sample
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Şimdi artık biz Mample() yaptığımızda Sample nesnesi yaratılacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
MAdemki sınıfların tüm bilgileri aslında type sınıfı türünden nesnelerin içerisinde bulunuyor o halde biz hiç class
bildirimi yapmadan da doğrudan type türündne nesne yaratarak bir sınıf oluşturabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
pass
Bu işlemin tamamen eşdeğeri şöyledir:
Sample = type('Sample', (), {})
2024-12-20 22:28:43 +03:00
type sınıfının __init__ metodunun birinci parametresi yaratılacak sınıfın ismini belirtir. İkinci parametresi sınıfın
taban sınıflarının type nesnelerini belirtmektedir. Buraya boş bir demet geçebiliriz. Üçüncü parametresi ise sınıfın
içerisindeki değişkenler yani sınıfın özniteliklerini belirtmektedir. Örneğin:
def foo(self):
print('foo')
Sample = type('Sample', (), {'x': 100, 'foo': foo})
s = Sample()
s.foo()
print(Sample.x)
Burada biz hiç class bildirimi yapmadan type sınıfı türündne nesne yaratarak bir sınıf oluşturmuş olduk. Sınıfımız
içerisinde bir tane x isimli öznitelik bir tane de foo isimli öznitelik vardır.
Bu örnekten de gördüğünüz gibi aslında metotlar sıradan bir fonksiyondur. Ancak bunların isimleri sınıf özniteliği
biçiminde oluşturulduğundan bunlara metot demekteyiz.
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Aslında bir sınıf türünden nesne type sınıfının __call__ metodu yoluyla yaratılmaktadır. Şöyle ki:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
bize vermektedir. Tabii bu durumu anlayabilmek için "operatör metotlrı konusunun" biliniyor olması gerekir. Biz operatör
metotlarını ileride başka bir bölümde ele alacağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Meta sınıflar (meta classes) kavramı Python'da biraz zor anlaşılan bir kavramdır. İleride bu konuya yeniden döneceğiz.
2024-12-20 22:28:43 +03:00
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ı türünden)
bir nesne yaratmış oluruz. Buradaki type sınıfına "meta sınıf" denilmektedir. Python'da default meta sınıf olan type
sınıfı değiştirilerek de nesneler yaratılabilir. 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şturulup o sınıfın bilgilerinin bu nesnenin içerisine
yerleştirmesi 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ı
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
her şey fonksiyonlarla yapılmaktadır. Bu teknikte elimizde sanki birbirlerinden bağımsız olan pek çok fonksiyon var gibidir.
Nesne yönelimli teknikte ise bir konuya ilişkin fonksiyonlar metot adı altında bir sınıfın içerisine yerleştirilir. Böylece
"çok fazla fonksiyon var" algısı yerine "şu sınıf bunu yapar, bu sınıf şunu yapar" biçiminde bir algısal kolaylık sağlanmış
olur. Örneğin bir kitaplıkta yüzlerce farklı kitap karışık bir biçimde bulunuyor olsun. Biz bu kitapları konulara ilişkin
raflara yerleştirirsek artık durum bize daha az karmaşık gözükür. Çünkü kitaplar birbirleriyle ilişki biçimde birbirlerinin
yakınlarında bulunmaktadır.
Tabii Python "multiparadigm" bir programlama dildir. Programcı isterse Python'un 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 yazabilir.
Ya da her iki tekniği bir arada da kullanabilir. Daha önceden de belirttiğimiz gibi Python'un standart kütüphanesinde
2024-07-15 13:23:34 +03:00
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.
2024-12-20 22:28:43 +03:00
Çü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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
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 konusun çok detaylarıayrıntısı yoktur. Bu nedenle Pyton'daki sınıfsal özellikleri C++, Java ve C# gibi dillerle
karşılaştırmamak gerekir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Built-in dir isimli fonksiyon bir sınıfın ya da bir nesnenin öznitekilklerini elde etmek için kullanılmaktadır. dir
2024-12-20 22:28:43 +03:00
fonksiyonu parametre olarak bir sınıf ismini (yani type türünden bir değişkeni) ya da bir sınıf türünden değişkeni
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
string'lerden oluşan bir liste biçiminde bize vermektedir. Python programcıları bir sınıfın elemanlarını hatırlamak
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
sınıfından gelmektedir. object sınıfı ileride ele alınacaktır.
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Bir modül import edildiğinde modüle ilişkin bütün bilgilerin module isimli bir sınıf türünden nesnenin içerisinde
tutulduğunu modül isminin de bu nesneyi gösterdiğini anımsayınız. İşte dir fonksiyonuna bir modül türünden değişkeni
verirsek fonksiyon bize o modülün içerisindeki elemanları bir liste biçiminde verir. Örneğin:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
>>> import math
>>> dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2',
'atanh', 'cbrt', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'exp2',
'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite',
'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter',
'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'sumprod', 'tan', 'tanh', 'tau', 'trunc',
'ulp']
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Bir proje NYPT bağlamında modellenirken proje içerisinde tüm kavramların sınıflarla temsil edildiğini ve projedeki
gerçek nesneler için bu sınıflar türünden nesneler yaratıldığını belirtmiştik. Ancak ptojedeki kavramlar birbirleriyle
ilişkili olabilmektedir. Örneğin bir hastane otomasyonunda Hastane ile Doktor arasında, Doktor ile Hasta arasında kavramsal
bir ilişki vardır. Bu kavramlar sınıflarla temsil edildiğine göre bu kavramları temsil eden sınıflar arasında da bir
ilişki olacaktır. Şimdi biz sınıflar arasındaki bu kavramsal ilişkiler üzerinde duracağız.
2024-07-15 13:23:34 +03:00
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)
2024-12-20 22:28:43 +03:00
3) Kalıtım İlişkisi (Inheritance)
2024-07-15 13:23:34 +03:00
4) Çağrışım İlişkisi (Association)
2024-12-20 22:28:43 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-20 22:28:43 +03:00
Ö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 insan
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Python'da içerme ilişkisi tipik olarak içeren sınıfın __init__ metodunda içerilen nesnenin yaratılarak içeren nesnenin
özniteliğinde tutulması yoluyla sağlanmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
class Motor:
pass
2024-12-20 22:28:43 +03:00
class Araba:
def __init__(self):
self.motor = Motor()
2024-07-15 13:23:34 +03:00
a = Araba()
2024-12-20 22:28:43 +03:00
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ılmış
olacaktır. Böylece araba nesnesi motor nesnesine sahip olmaktadır (has a ilişkisi). Henüz görmemiş olsak da "çöp toplayıcı
(garbage collector)" mekanizması Araba nesnesini yok ettiğinde motor nesnesi de yok olacaktır.
Bir satranç tahtasının kendisi Board isimli bir sınıfla temsil edilmiş olsun. Satranç tahtasında 64 tane kare vardır.
(Tahta 8x8'lik karelerden oluşan bir matris gibidir.) Satranç tahtası ile bu kareler arasındaki ilişki "içerme" ilişkisidir.
Bu içerme ilişkisi şöyle oluşturulabilir:
class Square:
pass
2024-07-15 13:23:34 +03:00
class Board:
def __init__(self):
self.squares = [[Square() for _ in range(8)] for _ in range(8)]
2024-12-20 22:28:43 +03:00
2024-07-15 13:23:34 +03:00
board = Board()
2024-12-20 22:28:43 +03:00
Burada Board sınıfı Square sınıfını içermektedir. Ancak 1 Board nesnesi 64 tane (8x8) Square nesnesini içerir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-20 22:28:43 +03:00
Örneğin Hospital sınıfı Doctor nesneleri tutan bir liste özniteliğine sahip olabilir. Hospital nesnesine Doctor eklemek
için ve bu nesneden Doctor silebilmek için add_doctor ve remove_doctor gibi metotlar bulunabilir:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Bir satranç tahtası uygulamasında tahtanın kendisi Board isimli sınıfla, kareler Square isimli sınıfla ve taşlar da
Figure isimli sınıfla temsil edilebilir. Board sınıfı ile Square sınıfı arasında içerme ilişkisi, Square sınıfı ile
Figure sınıfı arasında birleşme ilişkisi vardır.
Bir satranç taşının iki önemli özniteliği bulunmaktadır: Taşın cinsi ve taşın rengi. Bu durumda Figure sınıfı aşağıdaki
gibi olabilir:
class Figure:
def __init__(self, ftype, fcolor):
self.ftype = ftype
self.fcolor = fcolor
#...
Bir satranç karesinin de rengi vardır. Bir karede 0 tane ya da 1 tane taş olabilir. Bu durumda Square sınıfını
şöyle temsil edebiliriz:
class Square:
def __init__(self, scolor, figure=None):
self.scolor = scolor
self.figure = figure
def attach_figure(self, figure):
self.figure = figure
def detach_figure(self):
figure = self.figure
self.figure = None
return figure
#...
Tahtada 64 tane 8x8'lik kare bulunmaktadır:
class Board:
def __init__(self):
self.squares = [[Square('Black' if (col + row) % 2 == 0 else 'White') for col in range(8)]
for row in range(8)]
# ....
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
44. Ders 14/12/2024 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Sınıflar arasındaki diğer bir ilişki biçimi de "kalıtım (inheritance)" ilişkisidir. Buna türetme ilişkisi de denilmektedir.
Kalıtım ilişkisi türetme yoluyla sağlanmaktadır. Türetme mevcut bir sınıfa dokunmadan onu genişletme anlamına gelmektedir.
Burada asıl sınıfa (yani mevcut 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. Yani
biz türemiş sınıfı taban sınıf gibi de kullanabiliriz. Ancak türemiş sınıfın fazlalıklarından da faydalanabiliriz.
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 isimlerini alt alta yazacağız. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
B'nin hem de A'nın elemanlarını kullanabiliriz.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Bir sınıf birden fazla sınıfın taban sınıfı durumunda olabilir. Bu gayet normal bir durumdur. Örneğin:
2024-07-15 13:23:34 +03:00
A
B C
2024-12-20 22:28:43 +03:00
Burada B de C de A sınıfından türetilmiştir. B ile C arasında bir ilişki yoktur. Ancak A ikisinin de taban sınıfıdır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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,
2024-12-20 22:28:43 +03:00
bar ve zar metotlarının bulunduğunu kabul edelim. Burada foo ve bar ortak metotlardır ve tekrar etmektedi. İş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.
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
A (foo, bar)
2024-07-15 13:23:34 +03:00
B (tar) C (zar)
2024-12-20 22:28:43 +03:00
Sınıf kütüphanelerinde bir dizi türetmelerle bir türetme şeması oluşabilir. Bu tür türetme şemalarında türemiş sınıflar
taban sınıfların işlevlerine de sahip olurlar.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Türemiş sınıftan yeniden türetme yapılabilir. Bu durumda türemiş sını onun tüm taban sınıflarının işlevselliklerine
sahip olur. Örneğin:
A (foo, bar)
B (tar)
C (zar)
Burada A sıfınında foo ve bar metotlarının, B sınıfında tar metodunun ve C sınıfında da zar metodunun bulunduğunu
varsayalım. A sınıfı türünden bir nesne yaratmış olalım:
a = A()
Biz bu a nesnesi ile yalnızca foo ve bar metotlarını çağırabiliriz. Şimdi B sınıfı türünden bir nesne yaratalım:
b = B()
Biz bu b sınıfı türündne nesne ile foo, bar ve tar metotlarını çağırabiliriz. Şimdi de C sınıfı türünden
bir nesne yaratmış olalım:
c = C()
Burada biz c nesnesi ile artık foo, bar, tar ve zar metotlarını çağırabiliriz.
2024-07-15 13:23:34 +03:00
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.
2024-12-20 22:28:43 +03:00
Örneğin "işçi bir çalışandır". O zaman İşçi sınıfı Çalışan sınıfından türetilebilir. Ya da örneğin "kamyon bir taşıttır",
"otomobil bir taşıttır", "motosiklet bir taşıttır". O halde Kamyon sınıfı, Otomobil sınıfı ve Motosiklet sınıfı Taşıt
sınıfından türetilebilir:
Taşıt
Kamyon Otomobil Motosiklet
Burada tüm taşıtların ortak özellikleri Taşıt sınıfında bulundurulabilir. Kamyon bir taşıttır. Taşıtların olması
gereken özelliklerine sahiptir. Ancak kamyonun kamyon olmasından kaynaklanan ek özellikleri de vardır. Benzer biçimde
Otomobil de bir taşıttır, Motosiklet de bir taşıttır. Bunlar diğer taşıtların özelliklerini barındırırlar ancak kendilerine
özgü başka özelliklere de sahiptirler.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
ö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:
2024-07-15 13:23:34 +03:00
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.
2024-12-20 22:28:43 +03:00
Bazı programlama dillerinde çoklu türetme yoktur. Örneğin Java ve C# dillerinde bir sınıf yalnızca tek bir sınıftan
2024-07-15 13:23:34 +03:00
türetilebilir. Ancak C++, Python gibi bazı dillerde çoklu türetme bulunmaktadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Şimdi türetme işlemlerine bazı örnekler vermek istiyoruz:
2024-07-15 13:23:34 +03:00
- Bir personel takip programında iş yerinde çalışanlar görevlerine göre sınıflarla temsil ediliyor olsun. Tüm çalışanların
2024-12-20 22:28:43 +03:00
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 t
oplanabilir:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
uygulamalarında ekrandaki bağımsız kontrol edilebilen öğelere "pencere (window)" ya da "widget" denilmektedir. Her GUI
eleman bir penceredir. Dolayısıyla onların pencere olmasından kaynaklanan ortak özellikleri vardır. Örneğin pencere hangi
türden olursa olsun her penceren bir konuma, büyüklüğw, zemin renginw, bir yazıya ve başka çeşitli bilgilere sahiptir.
İşte GUI kütüphanelerinde çeşitli GUI elemanlar sınıflarla temsil edilmiştir. Ancak bu GUI elemanların ortak özellikleri
taban sınıflarda toplanmıştı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
ortaktır. İşte GUI kütüphanelerinde tipik olarak GUI elemanlara ilişkin sınıflar bir türetme şeması biçiminde biçiminde
oluşturulurlar. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
özellikleri ise en tepedeki Control sınıfında tutulmuştur. Tabii aslında bu kütüphanede yukarıdakinden çok daha fazla
sayıda GUI eleman ve dolayısıyla 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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ı
2024-12-20 22:28:43 +03:00
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.
2024-07-15 13:23:34 +03:00
Ç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.
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
45. Ders 15/12/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'da türetme sentaksının genel biçimi şöyledir:
2024-12-20 22:28:43 +03:00
class <türemiş_sınıf_ismi>(<taban_sınıf_listesi>):
2024-07-15 13:23:34 +03:00
pass
2024-12-20 22:28:43 +03:00
Genel biçimden de görüldüğü gibi sınıf tanımlamasında sınıf isminden sonra parantezler içeerisinde taban sınıflar belirtilmektedir.
2024-07-15 13:23:34 +03:00
Örneğin:
class A:
pass
class B(A):
pass
2024-12-20 22:28:43 +03:00
Burada B sınıfı A sınıfından türetülmiş durumdadır. Python çoklu türetmeyi desteklediği için bir sınıf birden fazla
sınıftan türetilebilir. Örneğin:
class A:
pass
class B:
pass
class C(A, B):
pass
Burada C sınıfı hem A sınıfından hem de B sınıfından türetilmiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-20 22:28:43 +03:00
Bu türetmeyi şöyle gösteribiliriz:
A
B C
class A:
def foo(self):
print('foo')
class B(A):
def bar(self):
print('tar')
class C(A):
def tar(self):
print('tar')
Burada biz B sınıfı türündne bir nesneyle A'nın ve B'nin metotlarını çağırabiliriz. C sınıfı türünden bir nesneyle de
C'nin ve A'nın metotlarını çağırabiliriz. Örneğinn:
b = B()
b.foo()
b.bar()
c = C()
c.foo()
c.tar()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class A:
def foo(self):
print('foo')
2024-12-20 22:28:43 +03:00
2024-07-15 13:23:34 +03:00
class B(A):
2024-12-20 22:28:43 +03:00
def bar(self):
2024-07-15 13:23:34 +03:00
print('tar')
class C(A):
2024-12-20 22:28:43 +03:00
def tar(self):
print('tar')
2024-07-15 13:23:34 +03:00
b = B()
2024-12-20 22:28:43 +03:00
2024-07-15 13:23:34 +03:00
b.foo()
b.bar()
c = C()
2024-12-20 22:28:43 +03:00
c.foo()
c.tar()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-12-20 22:28:43 +03:00
Oysa taban sınıfın __init__ metodunun içerisinde taban sınıf için gerekebilecek birtakım şeyler yapılmış olabilir. İşte
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__
metodunun çağrılmaması önemli sorunlara yol açabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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()
2024-12-20 22:28:43 +03:00
a.dispA() # sorun yok! nesnenin x özniteliği yaratılmış durumda
2024-07-15 13:23:34 +03:00
b = B()
2024-12-20 22:28:43 +03:00
b.dispA() # dikkat! nesnenin bir x örnek özniteliği yaratılmamış!
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
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ılır. Ancak türemiş sınıf nesnesi taban sınıf gibi de
2024-07-15 13:23:34 +03:00
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ı.
2024-12-20 22:28:43 +03:00
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
2024-07-15 13:23:34 +03:00
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.
2024-12-20 22:28:43 +03:00
Ancak çoklu türetmeler söz konusu olduğunda dikkat edilmesi gereken başka noktalar da vardır. İzleyen paragraflarda
MRO (Member Resolution Order) sırası hakkında bilgiler vereceğiz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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')
2024-12-20 22:28:43 +03:00
Burada hem A sınıfının hem de B sınıfının foo isimli metotları vardır. Biz b sınıfı türünden bir nesneyle foo metodunu
çağırırsak B sınıfının foo metodu, A sınıfı türünden bir nesneyle foo metodunu çağırırsak A sınıfının foo metodu çağrılacaktır.
Taban sınıfın türemiş sınıfı kullanamadığına, türemiş sınıfın taban sınıfı kullanabildiğine dikkat ediniz.
2024-07-15 13:23:34 +03:00
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!
2024-12-20 22:28:43 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Bazen programcı kasten taban sınıftaki metot ile aynı isimli metodu türemiş sınıfta yazmak isteyebilit. Böylece türemiş
sınıf türünden bir değişkenle bu metot çağrıldığında türemiş sınıfın metodu çağrılır. İşte türemiş sınıfın metodu içerisinde
de programcı taban 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:
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Yukarıda da belirtildiği gibi Python'da çoklu türetme vardır. Ancak Java ve C# gibi bazı dillerde çoklu 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:
2024-07-15 13:23:34 +03:00
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ı
2024-12-20 22:28:43 +03:00
gerekiyordu. İşte ç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 aşağıdaki gibi bir türetme söz konusu olsun:
A B
C
Burada C sınıfının __init__ metodunun hem A sınıfının hem de B sınıfının __init__ metotlarını çağırması gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Pekiyi çoklu türetmede isim araması hangi sıraya göre yapılmaktadır? Örneğin aşağıdaki gibi bir türetme söz konusu olsun:
2024-07-15 13:23:34 +03:00
A B
C
2024-12-20 22:28:43 +03:00
Burada C sınıfı A ve B'den çoklu türetilmiştir. Şimdi A ve B sınıflarında foo metodunun bulunduğunu ancak C sınıfında
foo metodunun bulunmadığını varsayalım. Nu duurmda C sınıfı türünden bir değişkenle foo metodu çağrıldığında hangi foo
metodu çağrılacaktır? Örneğin:
2024-07-15 13:23:34 +03:00
class A:
def foo(self):
print('A.foo')
class B:
def foo(self):
print('B.foo')
class C(A, B):
pass
c = C()
2024-12-20 22:28:43 +03:00
c.foo() # hangi foo metodu çağrılır?
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Python'da genel olarak bu tür durumlarda çoklu türetmede belirtilen sıra önemli olmaktadır. Yani yorumlayıcı türetrme
parantezinde belirtilen sırada taban sınıflarda arama yapar. Yukarıdaki örnekte A sınıfının foo metodu çağrılacaktır.
Ancak bu konunun bazı ayrıntıları vardır. İzleyen paragraflarda bu ayrıntılar üzerinde duracağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Türetme şemaları bazen oldukça karışık olabilmektedir. Özellikle baklava (dimaond) tarzı türetmeler kişilerin
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
Bu tür karmaşık türetme şemaalarında metotların aranma sırasına Python'da MRO (Member Resolution Order) denilmektedir. -
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Aşağıdaki gibi bir türetme şeması söz konusu olsun. Bu şemadaki bazı sınıflarda foo isimli metotların bulunuyor olduğunu
varsayalım:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
A (foo) B (foo)
C D (foo)
E
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Burada E sınıfı türünden bir değişkenle foo metodunun çağrıldığını düşünelim:
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
e = E()
e.foo()
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
Hangi sınıfın foo metodu çağrılacaktır? İşte 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 E, C, A, D, B sırasına göre yapılacaktır. Başka bir deyişle E 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 E
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ı.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2024-12-20 22:28:43 +03:00
Baklava (diamond) biçimindeki çoklu 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:
2024-07-15 13:23:34 +03:00
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.
2024-12-20 22:28:43 +03:00
Buradaki belirttiğimiz soruna ilişkin örnek aşağıda verilmiştir. Bu programı çalıştırarak A sınıfının __init__ metodunun
iki kez çağrıldığını gözlemleyiniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
göre kollarda aşağıdan yukarıya doğru arama yapılır. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
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
2024-07-15 13:23:34 +03:00
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.)
2024-12-20 22:28:43 +03:00
Bir sınıfın MRO sırası type sınıfının __mro__ örnek özniteliği ile elde edilmektedir. (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.
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2024-12-20 22:28:43 +03:00
(yani type nesnesini) veririz. Fonksiyon da bize yine MRO sırasını bir demet biçiminde verir. Örneğin:
2024-07-15 13:23:34 +03:00
import inspect
t = inspect.getmro(E)
print(t)
Tabii getmro fonksiyonu şöyle yazılmıştır:
def getmro(cls):
return cls.__mro__
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2024-12-20 22:28:43 +03:00
#------------------------------------------------------------------------------------------------------------------------
Genel olarak MRO sırası şu biçimdedir:
- Eğer çoklu türetme yoksa aşağıdan yukarıya doğrudur.
- Eğer çoklu türetme varsa soldan sağa taban sınıflarda aşağıdan yukarıya doğru bir kol bitirildikten sonra diğerine
geçilecek biçimdedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
kullanılarak nokta operatörü ile bir isim belirtilirdiğinde bu isim sırasıyla hangi sınıflarda aranacağına MRO sırası
2024-07-15 13:23:34 +03:00
denilmektedir. Tabii isim bulunursa arama devam etmez. Örneğin:
A
B C
D
E
e = E()
2024-12-20 22:28:43 +03:00
2024-07-15 13:23:34 +03:00
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
2024-12-20 22:28:43 +03:00
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 MRO sırası ise ş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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
46. Ders 21/12/2024 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Baklava biçimindeki türetme şemalarında tepedeki taban sınıf ortak olduğu için durum biraz ilginç hale gelmektedir.
Örneğin:
2024-07-15 13:23:34 +03:00
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__)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Pekiyi yukarıdaki türetme şemasında A da X sınıfından türetilseydi D sınıfının MRO sırası nasıl olurdu?
X
A
B C
D
İşte bu durumda D sınıfının MRO sırası D, B, C, A, X, object biçiminde olacaktır.
Biz burada MRO sırasına ilişkin sezgisel açıklamalar yaptık. Eğer tereddütte kalırsanız program içerisinde MRO sırasını
yukarıda bizim yaptığımız gibi kontrol edebilirsiniz.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
"Her şey bir çeşit nesne" olduğuna göre Python'da tüm sınıflar aslında doğrudan ya da dolaylı olarak object isimli bir
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
Burada Sample sınıfı object sınıfından türetilmiştir. Tabii biz bunu açıkça da belirtebilirdik:
2024-07-15 13:23:34 +03:00
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 '__main__.A'>,)
>>> class C:
... pass
...
>>> class D(C, B):
... pass
...
>>> D.__bases__
(<class '__main__.C'>, <class '__main__.B'>)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
bazı eklemeler sonrasında taban sınıftaki metodu da çağırmak isteyebilir. Daha önceden de bunun self parametresi ile
yapılamayacağını görmüştük:
2024-07-15 13:23:34 +03:00
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:
2025-01-23 02:00:34 +03:00
class A:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
super fonksiyonu built-in bir fonksiyondur. İki kullanım biçimi vardır:
2024-07-15 13:23:34 +03:00
super(sınıf_ismi, değişken_ismi)
super()
2025-01-23 02:00:34 +03:00
Yani super fonksiyonunu biz iki argümanla ya da argümansız çağırabiliriz. İki argümanla çağrım daha genel bir kullanım
sunmaktadır. super fonksiyonunun birinci parametresi bir sınıf ismini (yani Type sınıfı türünden bir değişkeni), 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 fonksiyonu
aramayı ikinci argümanda verdiğimiz değişkeninin ilişkin olduğu sınıfın MRO sırası dikkate alındığında birinci argümanda
verdiğimiz sınıftan sonra gelen snıftan başlatır. Örneğin:
2024-07-15 13:23:34 +03:00
d = D()
super(D, d).foo()
2025-01-23 02:00:34 +03:00
Burada aslında oluşan etki d.foo() gibi bir etkidir. Ancak bu etki oluşturulurken isim aramsı d değişkeninin iliş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ı söz konusu
olsun:
2024-07-15 13:23:34 +03:00
A
B
C
D
D sınıfı türünden bir nesne yaratılım:
d = D()
2025-01-23 02:00:34 +03:00
Burada d değişkeninin ilişkin olduğu sınıfın MRO sırası şöyledir: D, C, B, A, object. Şimdi şöyle bir çağrı yapmış olalım:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
B sınıfıdır. Yani eğer B'nin foo metodu varsa B'nin foo metodu yoksa A'nın foo metodu çağrılacaktır. A'da da foo metodu
yoksa exception oluşacaktır.
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
sınıfının foo metodunu nasıl çağırmalıyız. Anımsanacağı gibi yöntemlerden biri şöyle olabilir:
2024-07-15 13:23:34 +03:00
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ı
2025-01-23 02:00:34 +03:00
b değişkeninin ilişkin olduğu sınıfın MRO sırasında B sınıfından sonraki sınıftan yapılır. super fonksiyonun ikinci
parametresinin çağrılacak metotta kullanılacak nesneyi ve aynı zamanda da söz konusu MRO sırasına ilişkin olduğu Sınıfı
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Buradaki self D sınıfı türündendir. D sınıfının MRO sırası D, C, B, A biçimindedir. D sınıfı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 foo metotları için de benzer biçimdedir. Ekranda şunlar görülecektir:
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
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ı
2025-01-23 02:00:34 +03:00
dikkate aldığı için önemli bir işleve sahiptir. Özellikle baklava tarezı çoklu türetmelerde mecburen super fonksiyonundan
faydalanılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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()
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
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__')
2025-01-23 02:00:34 +03:00
Burada super(d, self).__init__() çağrısı yapıldığında self D sınıfı türündendir. 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:
2024-07-15 13:23:34 +03:00
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):
2025-01-23 02:00:34 +03:00
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
2024-07-15 13:23:34 +03:00
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__
2025-01-23 02:00:34 +03:00
metodu çağrılacaktır. Dolayısıyla ekranda şunlar görülecektir:
2024-07-15 13:23:34 +03:00
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__()
# ...
2025-01-23 02:00:34 +03:00
object sınıfın __init__ metodunun içinin boş olduğunu varsayabilirsiniz. Bu durumda programcının object sınıfının
__init__ metodunu çağırması gerekmez. Ancak çağırmasında da bir sakınca yoktur.
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
class Sample:
pass
s = Sample()
2025-01-23 02:00:34 +03:00
Burada Sample sınıfında __init__ metodu olmadığına göre object sınıfının __init__ metodu çağrılacaktır. object sınıfının
__init__ metodunun hiçbir şey yapmadığını varsayabilirsiniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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__')
2025-01-23 02:00:34 +03:00
class C(A, B):
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Çoklu türetmede diğer bir sorun da çoklu türetilmiş sınıfın __init__ metodunun parametrelerini MRO sırasına göre diğer
sınıfların __init__ metotlarına iletmesi durumunda ortaya çıkmaktadır. 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.
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
ismi olarak, ikinci parametresi ise 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:
2024-07-15 13:23:34 +03:00
super().__init__()
2025-01-23 02:00:34 +03:00
Bu çağrı aslında şununla eşdeğerdir:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
47. Ders 22/12/2024 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'da her ne kadar C++, Java ve C# gibi dillerde bulunan "public", "private", "protected" gibi 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.
2024-07-15 13:23:34 +03:00
Ö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
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
diğer metotlardan çağrılmaktadır. Tabii biz istersek gerçekten yine de onları dışarıdan çağırtabiliriz. Bunun için yorumlayıcı
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
istenmemektedir. Tabii yine dışarıdan erişim için bir gerçek bir engel yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
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.
2024-07-15 13:23:34 +03:00
Ö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
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
isimlere iki alt tire ile erişebiliriz. Ancak dışarıdan iki alt tire ile erişemeyiz. Dışarıdan ancak _sınıfismi__
2024-07-15 13:23:34 +03:00
öneki ile erişebiliriz.
2025-01-23 02:00:34 +03:00
İki altireli bir nesne özniteliği sınıfın içerisinde değil de dışarıda oluşturulmuşsa bu durumda erişim iki alt tireli
2024-07-15 13:23:34 +03:00
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!
2025-01-23 02:00:34 +03:00
İsimlerin önüne tek alt tire getirilmesine "Python Language Reference" 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 Reference" dokümanında (6.2.1)
2024-07-15 13:23:34 +03:00
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?
2025-01-23 02:00:34 +03:00
Bu durum tamamen bizim isteğimize bağlıdır. Eğer biz "dışarıdan erişmeme" tavsiyesini vurgulamak istiyorsak ik alt tire
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Başka bir deyişle dilin nesne yönelimli olabilmesi için "çokbiçimli mekanizmaya" sahip olması gerekir. Örneğin C++, Java,
C#, Python gibi dillerde çokbiçimli mekanizma bulunmaktadır. Dolayısıyla bu diller "nesne yönelimli" dillerdir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2025-01-23 02:00:34 +03:00
Yani kendine göre duymaktadır. Örneğin köpekler daha tiz sesleri duyabilirler. Eşeğin kulağı daha büyüktür. Benzer biçimde
göz de 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
ö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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
sahip olduğu için zaten doğuştan çokbiçimlidir. Python'da 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. Yani Python'da çokbiçimli mekanizma türetme yapılmadan da kuıllanılabilmektedir. Ancak çokbiçimli mekanizmaların
türetme yoluyla oluşturulması diğer dillerde de uygulanan bir yöntemdir.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Çokbiçimlilik NYPT'de "türden bağımsız kod paraçalarının (fonksiyonların ve metotların)" oluşturulmasına olanak sağlamaktadır.
Çokbiçimlilik sayesinde "birbirine benzeyen ancak farklı olan nesnelere sanki aynı nesneymiş muamalesi" yapılabilmektedir.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Top ile oynana bir oyun yazacak olalım. Oyunda çeşitli toplar bulunuyor olsun. NormalTop vurulduğunda normakl gitsin.
PatlakTop vurulduğunda sönümlenerek gitsin. ZıplayanTop vurulduğunda zıplaya zıplaya gitsin. Topların gitmesinin sınıfların
git isimli metotlarıyla sağlandığını varsayalım. Biz oyunun oynatılmasını sağlayan oynat isimli fonksiyona bu top nesnesini
parametre yoluyla geçirmiş olalım. Örneğin:
def oynat(top):
top.git()
#....
top.git()
#....
top.git()
#....
Burada oynat fonksiyonu belli bir topa dayalı olarak değil genel bir top kavramına dayalı olarak yazılmıştır. Biz bu
fonksiyona hangi top nesnesini geçrirsek oyunda o top nesnesi gidecektir. Çünkü topun gitmesi çokbiçimli bir eylemdir.
Yani her top gider ancak kendine göre farklı bir biçimde gider. Örneğin:
class NormalTop:
def git(self):
print('NormalTop gidiyor')
class PatlakTop:
def git(self):
print('PatlakTop gidiyor')
class ZıplayanTop:
def git(self):
print("ZıplayanTop gidiyor")
def oynat(top):
top.git()
# ....
top.git()
#...
top.git()
#...
top = NormalTop()
oynat(top)
top = PatlakTop()
oynat(top)
top = ZıplayanTop()
oynat(top)
Burada oynat fonksiyonu belli bir topu kullanacak biçimde değil git metodu olan bütün top nesnelerini kullanabilecek
biçimde yazılmıştır. Bu örnekte olduğu gibi çokbiçimlilik türden bağımsız kod parçalarını oluşturabilmek için kullanılmaktadır.
Bu tür çokbiçimli metotlara sahip sınıflarda ortak elemanların bulunma potansiyeli de çok fazla olduğu için genellikle
bu sınıflar ortak bir taban sınıftan türetilirler. Örneğin:
class Top:
pass
class NormalTop(Top):
def git(self):
print('NormalTop gidiyor')
class PatlakTop(Top):
def git(self):
print('PatlakTop gidiyor')
class ZıplayanTop(Top):
def git(self):
print("ZıplayanTop gidiyor")
Burada tüm topların ortak özellikleri taban Top sınıfında tutulabilir. Diğer sınıflar bu sınıftan türetilebilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Yukarıda da belirttiğimiz gibi çokbiçimlilikten elde edilecek en önemli fayda "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. İdeal durumda NYPT'de kodda hiç değişiklik yapılmaz. Her zaman "ekleme" yoluyla geliştirme yapılır.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Örneğin bir Tetris oyunu yazacak olalım. Bu oyunda çeşitli şekiller düşmektedir. Bu şekiller duruma göre sola, sağa
2024-07-15 13:23:34 +03:00
hareket ettirilmekte ve döndürülebilmektedir. Şeklin sola, sağa hareket ettrilmesi, düşmesi ve döndürülmesi "çokbiçimli
2025-01-23 02:00:34 +03:00
(polymorphic)" eylemlerdir. Yani bu eylemler bu şekil sınıflarının hepsinde vardır. Ancak her şekil kendine göre bu
2024-07-15 13:23:34 +03:00
eylemleri yerine getirmektedir.
2025-01-23 02:00:34 +03:00
Aşağıdaki örnekte böyle bir Tetris oyunu mantıksal olarak simüle edilmeye çalışılmıştır. Burada biz 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
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
bir byte nesnesi vermektedir. O halde hiç bekleme yapmadan tuş okumak için aşağıdaki gibi bir yol izlenebilir:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
daha zordur. Başkaları tarafından yazılmış olan "getch" modülü pip programıyla indirilip kurulabilir. Örneğin:
2024-07-15 13:23:34 +03:00
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('<<BarShape moves left>>')
def move_right(self):
print('<<BarShape moves right>>')
def rotate(self):
print('<<BarShape rotates>>')
class SquareShape(Shape):
def __init__(self):
super().__init__()
def move_down(self):
print('SquareShape moves down')
def move_left(self):
print('<<SquareShape moves left>>')
def move_right(self):
print('<<SquareShape moves right>>')
def rotate(self):
print('<<SquareShape rotates>>')
class ZShape(Shape):
def __init__(self):
super().__init__()
def move_down(self):
print('ZShape moves down')
def move_left(self):
print('<<ZShape moves left>>')
def move_right(self):
print('<<ZShape moves right>>')
def rotate(self):
print('<<ZShape rotates>>')
class LShape(Shape):
def __init__(self):
super().__init__()
def move_down(self):
print('LShape moves down')
def move_left(self):
print('<<LShape moves left>>')
def move_right(self):
print('<<LShape moves right>>')
def rotate(self):
print('<<LShape rotates>>')
class TShape(Shape):
def __init__(self):
super().__init__()
def move_down(self):
print('TShape moves down')
def move_left(self):
print('<<TShape moves left>>')
def move_right(self):
print('<<TShape moves right>>')
def rotate(self):
print('<<TShape rotates>>')
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()
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
48. Ders 4/01/2025 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
aynı anlamdadır. Programcı sınıfın __str__ metodunu bir str nesnesiyle geri döndürmelidir. Böylece str türüne dönüştümede
geri döndürülen bu yazı elde edilecektir.
2024-07-15 13:23:34 +03:00
Aslında print fonksiyonu yalnızca string'leri bastırmaktadır. Eğer print fonksiyonuna girdiğimiz argüman string değilse
2025-01-23 02:00:34 +03:00
print fonksiyonu onu str türüne dönüştürüp yazıyı ekrana basmaktadır. a string türünden olmayan bir türden olsun. Bu durumda:
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
print(a)
2024-07-15 13:23:34 +03:00
ile
2025-01-23 02:00:34 +03:00
print(str(a))
2024-07-15 13:23:34 +03:00
ya da
2025-01-23 02:00:34 +03:00
print(a.__str__())
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
göre 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. object sınıfının __str__ metodu da
nesnenin türünü ve bellek adresini (id bilgisini) bir yazı biçiminde vermektedir. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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
2025-01-23 02:00:34 +03:00
içerisindeki özniteliklere 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. Örneğin Date sınıfı Date nesnesinin tuttuğu
gün, ay, yıl değerini, Point sınıfı nesnenin tuttuğu x ve y değerlerini, Complex sınıfı nesnenin tuttuğu real ve imag
değerlerini yazı biçiminde geri döndürebilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python'un standart kütüphanesindeki sınıflarda genel olarak __str__ metodu zaten yazılmış durumdadır. Böylece biz
2025-01-23 02:00:34 +03:00
standart kütüphanedeki bir sınıf nesnesini print ile doğrudan yazdırırsak o nesneye ilişkin özet bilgileri görüntülemiş
oluruz. Örneğin:
import datetime
d = datetime.date(2024, 1, 4)
print(d) # 2024-01-04
Görüldüğü date sınıfını yazanlar tıpkı bizim yaptığımız gibi __str__ metodunda nesnenin tuttuğu gün, ay, bilgilerini
bir yazı biçiminde geri döndürmüşleridr.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
import datetime
d = datetime.date(2024, 1, 4)
print(d) # 2024-01-04
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
elde edilen yazılar yazdırılmaktadır. Tabii bu sınıflarda sınıflarda __str__ metotları bulunmaktadır. Örneğin:
>>> a = [1, 2, 3, 4, 5]
>>> a.__str__()
'[1, 2, 3, 4, 5]'
>>> d = {'ali': 10, 'veli': 20, 'selami': 40}
>>> d.__str__()
"{'ali': 10, 'veli': 20, 'selami': 40}"
Bir listenin print ile nasıl yazdırılabildiği hakkında fikir edinmeniz için aşağıdaki örneği veriyoruz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Programcı tarafından her yazılan sınıfta bir __str__ metodununun bulundurulması 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.
Yukarıda da belirttiğöimiz gibi Python'un standart kütüphanesindeki sınıflarda da hep bu __str__ metotları yazılmış durumdadır.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> import datetime
>>> d = datetime.date(2023, 10, 17)
>>> d.__str__()
'2023-10-17'
>>> str(d)
'2023-10-17'
>>> print(d)
2023-10-17
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
string ile geri dönmelidir. Pekiyi iki metot arasındaki farklılık nedir? İş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 verdiği
yazılar aynı da olabilir. Örneğin Python'un komut satırında, Spyder'ın komut satırında (IPython) bir değişkeni yazıp ENTER
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Aslında programcının bu iki metodu ayrı ayrı yazması da gerekmez. Çünkü:
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
1) Eğer sınıfın __repr__ metodu varsa fakat __str__ metodu yoksa hem str türüne dönüştürmede hem de komut satırında değişken
ismi yazılıp ENTER tuşuna basıldığında bu __repr__ metodunun geri döndürdüğü yazı kullanı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.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
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ığındaise __repr__ metodu çağrılmaktadır.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
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 object sınıfının __repr__ metodu çağrılır. Object sınıfının __repr__ metodu da tıpkı
__str__ metodu gibi nesnenin türünü ve onun bellek adresini bir yazı biçiminde geri döndürmektedir.
2024-07-15 13:23:34 +03:00
>>> class Sample:
... def __str__(self):
... return '__str__'
...
>>> s = Sample()
>>> print(s)
__str__
>>> s
<__main__.Sample object at 0x0000026954AC5850>
2025-01-23 02:00:34 +03:00
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'un
standart kütüphanesindeki sınıflarda genellikle bu metotlar ayrı ayrı yazılmıştır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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__()
2025-01-23 02:00:34 +03:00
Bazen programcı komut satırında değil de kendi programı içerisinde de __str__ yerine __repr__ metodunu kullanmak isteyebilir.
Örneğin:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Bu sırada str sınıfının __repr__ metodundan faydalanılmıştır. str sınıfının __repr__ metodu yazıyı ters bölü karakterlerini
de belirtirek yazdırmaktadır. İki metodun geri döndürdüğü yazılar arasındaki farka dikkat ediniz:
>>> s = 'ali\nveli'
>>> print(s)
ali
veli
>>> print(repr(s))
'ali\nveli'
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2025-01-23 02:00:34 +03:00
Bunun için standart kütüphanede bulunan hasattr isimli built-in fonksiyon kullanılmaktadır. hasattr fonksiyonu iki parametreye
sahiptir. Fonksiyonun birinci parametresi 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öner. 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 doğrudan ilgili sınıfın özniteliklerine bakar.
2024-07-15 13:23:34 +03:00
Ö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
2025-01-23 02:00:34 +03:00
hasattr fonksiyonu sınıfın taban sınıflarına da bakmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi Python'da değişken kavramı ile nesne kavramı farklı anlamlara geliyordu. Python'da değişkenler nesnelerin
2025-01-23 02:00:34 +03:00
adreslerini tutmaktadır. Biz Python'da değişken dediğimizde adres tutan varlıkları, nesne dediğimizde gerçek bilgileri
2024-07-15 13:23:34 +03:00
tutan varklıkları kastetmekteyiz. Örneğin:
s = 'ankara'
2025-01-23 02:00:34 +03:00
Burada s bir değişkendir. "ankara" yazısı str türünden bir nesnede tutulmaktadır. s'nin içerisinde o nesnenin adresi
2024-07-15 13:23:34 +03:00
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?
2025-01-23 02:00:34 +03:00
İşte programlama dillerinde "kullanılmayan nesnelerin otomatik biçimde silinmesini sağlayan mekanizmalara" çöp toplama
(garbage collection) mekanizması, 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.
2024-07-15 13:23:34 +03:00
Çö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
2025-01-23 02:00:34 +03:00
---> Bu noktaya dikkat!
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Burada çöp toplama mekanizması, içerisinde "ankara" yazısının bulundurğu str nesnesini silebilir. Ancak örneğin:
2024-07-15 13:23:34 +03:00
s = 'ankara'
k = s
s = 100
2025-01-23 02:00:34 +03:00
Burada çöp toplama mekanizması, içerisinde "ankara" yazısının bulunduğu str nesnesini silmememilidir. Çünkü o
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Python'da bir nesne yaratıldığında yorumlayıcı o nesnesnin kaç değişken tarafından refere edildiğini (yani gösterildiğini)
nesnenin içerisinde tutmaktadır. Buna nesnenin "referans sayacı (reference counter)" denilmektedir. Nesnenin referans sayacı
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
sayacını daha olması gerekenden daha fazla olarak verebilmektedir. Çünkü bu fonksiyon referans sayacı bulunacak nesnenin
adresini parametre yoluyla aldığı için fonksiyonun içerisinde nesnenin referans sayacı en azından 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 nesnenin referans sayacının sıfıra düşmesidir.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
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 float bir nesnenin de referans sayacı vardır.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Aşağıdaki programda bir nesnenin referans sayacı çeşitli aşamalarda yazdırılmıştır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
Buradaki yüksek değer kişiler tarafından şüpheyle karşılanabilmektedir. Ancak yukarıda da belirttiğimiz gibi CPython
2024-07-15 13:23:34 +03:00
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ı
2025-01-23 02:00:34 +03:00
ve dezavantajları vardır. Ö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ığı
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
kontrol ettiği çeşitli nesneler de olabilmektedir. Bazen nesne yaratılırken bir dosya yaratılır, nesne bu dosyayı kullanır.
2024-07-15 13:23:34 +03:00
İş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.
2025-01-23 02:00:34 +03:00
Python'un çö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.
__del__ metodu çağrılırken çöp toplayıcı mekanizma yok edilecke olan nesnenin adresini metodun self parametresine geçirmektedir.
Tabii programcılar genel olarak Python dünyasının içerisinde kaldıklarından dolayı bu metodu yazmaya pek gereksinim duymazlar.
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__ metodunda
2024-07-15 13:23:34 +03:00
yapmışsa bu tahsisatların otomatik geri bırakılmasını __del__ metodunda sağlayabilir. İşletim sistemi düzeyinde tahsis edilen
2025-01-23 02:00:34 +03:00
kaynaklara .NET ve Java dünyasında "unmanaged" kaynaklar denilmektedir. Aslında Python'un 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.
Python'da program bittiğinde hala birtakım nesnelerin referans sayaçları 0'dan büyükse onlar için __del__ metodunun çağrılıp
çağrılmayacağı konusunda bir belirlemede bulunulmamıştır. Ancak CPython yorumlayıcısı 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. Ancak programınızı Spyder'dan
çalıştırıyorsanız Spyder programları IPython içerisinde çalıştırdığı için programın çalışması bitse bile nesne IPython tarafından
tutulduğu için nesne için __del__ metodu çağrılmayabilir. Bu tür testleri Sypder'dan yapmayınız. Eğer Spyder'dan yapacaksanız
IPython konsolundan aşağıdaki gibi çalıştırmayı deneyebilirsiniz:
!python sample.py
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
IPython konsolunda "!" karakteri komutun kabuk üzerinden çalıştırılacağı anlamına gelmektedir.
2024-07-15 13:23:34 +03:00
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('üç')
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Aşağıdaki örnekte File isimli sınıfın __init__ metodunda içi boş bir dosya yaratılmıştır. Sınıfın write metodu bu dosyaya
2024-07-15 13:23:34 +03:00
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?
2025-01-23 02:00:34 +03:00
YANIT: __del__ metodu nesnenin referans sayacı 0'a düşer düşmez nesne yorumlayıcının çöp toplayıcı alt sistemi tarafından
2024-07-15 13:23:34 +03:00
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?
2025-01-23 02:00:34 +03:00
YANIT: __del__ metodu yorumlayı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. object sınıfının __del__ isminde bir metodu yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
çağrılacağı tam olarak bellidir. Ayrıca bazı durumlarda nesne referanslarının gizlice başka collection nesnelerde
2024-07-15 13:23:34 +03:00
tutulduğu durumlarda nesnenin tam olarak ne zaman silineceği yine kestirilememektedir. Program bittiğinde referans
2025-01-23 02:00:34 +03:00
sayacı 0'a düşmemiş olan nesneler için de __del__ metodunun çağrılıp çağrılmayacağı da aslında Python'da standart bir
biçimde belirlenmemiştir. Ancak CPython yorumlayıcısı program bitmeden önce yok edilmemiş nesneler için __del__ metodunu
çağırmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
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:
2025-01-23 02:00:34 +03:00
- __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.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
- Bir nesnenin başka bir nesneyi göstermesi, onun da onu göstermesi durumunda (döngüsel durum) çöp tolayıcının çalışması
programcının umduğu gibi olmayabilir.
2024-07-15 13:23:34 +03:00
- 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
2025-01-23 02:00:34 +03:00
__del__ metodunun olup olmadığını nasıl anlayabiliriz? Aslında en sağlam yol dökümantasyona bakmak olabilir. Ya da
2024-07-15 13:23:34 +03:00
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ı
2025-01-23 02:00:34 +03:00
bilemeyecek durumdaysak o zaman hasattr fonksiyonuyla bu kontrolü yapabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
class B(A):
def __del__(self):
...
base = super()
if hasattr(base, '__del__'):
base.__del__()
2025-01-23 02:00:34 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
"Operatör metotları" konusu nesne yönelimli dillerin çoğunda bulunmaktadır. Örneğin bu özellik C++'ta, C#'ta, Swift'te
2025-01-23 02:00:34 +03:00
vardır. Fakat örneğin Java'da bulunmamaktadır. Python da operatör metotlarını desteklemektedir. Operatör metotları aslında
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Operatör metotları özel dunder isimli metotlardır. Yani operatör metotlarının isimleri dilin kuralları içerisinde önceden
belirlenmiştir. Örneğin toplama işlemini yapan operatör metodu __add__, çıkartma işlemini yapan operatör metodu __sub__,
çarpma işlemini yapan operatör metodu __mul__ ismindedir. Programcı bu metotları yazarak ilgili operatörler kullanıldığında
kendi metotlarının çağrılmasını sağlayabilir. Örneğin Sample sınıfının __add__ isimli metodu bulunuyor olsun:
a = Sample()
b = Sample()
Biz burada a + b gibi bir işlem yaptığımızda aslında yorumlayıcı Sample sınıfının __add__ metodunu çağırmaktadır. Daha açık
bir deyişle:
result = a + b
işleminin eşdeğeri şöyledir:
result = a.__add__(b)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
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şdeğer metot
çağrısına dönüştürmektedir. Örneğin a bir sınıf türünden olmak üzere a + b ifadesi yukarıda da belirttiğimiz gibi tamamen
a.__add__(b) ile eşdeğerdir. Ya da örneğin a > b ifadesi de a.__gt__(b) ile eşdeğerdir. Böylece aslında biz bir sınıf
türünden değişkenleri 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.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Pekiyi örneğin __add__ metodunun parametrik yapısı nasıl olmalıdı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:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
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 sorumluluk tamamne programıya aittir. (Yani biz örneğin __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. )
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Operatör metotları operatör önceliklerini değiştirmez. Örneğin a, b ve 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ığı şöylşedir:
a.__add__(b.__mull__(c))
Görüldüğü gibi önce __mull__ metodu çağrılacak buradan elde edilen değer __add__ metoduna argüman olarak verilecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
farklı b türleri için farklı işlemler yapılablir. 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 gerçekleştirilir. Örneğin biz bir Complex
sayının hem bir Complex sayı ile hem de int ya da float türden bir sayı ile toplanabilmesini şöyle sağlayabiliriz:
class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, z):
if isinstance(z, int|float):
real = self.real + z
imag = self.imag
else:
real = self.real + z.real
imag = self.imag + z.imag
return Complex(real, imag)
def __str__(self):
return f'{self.real}+{self.imag}i'
Burada __add__ metodunda önce sağ taraf taraftaki operan'dın türüne bakılmış ona uygun bir işlem yapılmıştır.
#------------------------------------------------------------------------------------------------------------------------
49. Ders 05/01/2025 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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. Bu örnekte eğer ikinci operand Number, int ya da float türden değilse raise deyimiyle
exception oluşturduk. Exception konusu ileride ele alınmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Python'a 3.5 versiyonuyla birlikte @ operatörü de eklenmiştir. Bu operatöre "matris çarpımı (matmul)" operatörü de denilmektedir.
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
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) okunabilirlik sağlamak için kullanılmaktadır.
2024-07-15 13:23:34 +03:00
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])
2025-01-23 02:00:34 +03:00
for i in range(1, self.rowsize):
if len(matrix[i]) != self.colsize:
raise ValueError('invalid mati-rix')
def __add__(self, m):
if self.rowsize != m.rowsize or self.colsize != m.colsize:
raise ValueError('cannot add matricies that have diferent shapes')
result = []
for row in range(self.rowsize):
mrow = []
for col in range(self.colsize):
mrow.append(self.matrix[row][col] + m.matrix[row][col])
result.append(mrow)
return Matrix(result)
def __sub__(self, m):
if self.rowsize != m.rowsize or self.colsize != m.colsize:
raise ValueError('cannot add matricies that have diferent shapes')
result = []
for row in range(self.rowsize):
mrow = []
for col in range(self.colsize):
mrow.append(self.matrix[row][col] - m.matrix[row][col])
result.append(mrow)
return Matrix(result)
def __mul__(self, m):
if self.rowsize != m.rowsize or self.colsize != m.colsize:
raise ValueError('cannot add matricies that have diferent shapes')
result = []
for row in range(self.rowsize):
mrow = []
for col in range(self.colsize):
mrow.append(self.matrix[row][col] * m.matrix[row][col])
result.append(mrow)
return Matrix(result)
def __truediv__(self, m):
if self.rowsize != m.rowsize or self.colsize != m.colsize:
raise ValueError('cannot add matricies that have diferent shapes')
result = []
for row in range(self.rowsize):
mrow = []
for col in range(self.colsize):
mrow.append(self.matrix[row][col] / m.matrix[row][col])
result.append(mrow)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
return Matrix(result)
def __matmul__(self, m):
if self.colsize != m.rowsize:
raise ValueError('these two matrices canno multiply due to their size')
result = [[0] * m.colsize for _ in range(self.rowsize)]
2024-07-15 13:23:34 +03:00
for i in range(self.rowsize):
for j in range(self.colsize):
total = 0
for k in range(self.colsize):
2025-01-23 02:00:34 +03:00
total += self.matrix[i][k] * m.matrix[k][j]
result[i][j] = total
2024-07-15 13:23:34 +03:00
return Matrix(result)
def __repr__(self):
2025-01-23 02:00:34 +03:00
s = ''
for row in range(self.rowsize):
for col in range(self.colsize):
if col != 0:
2024-07-15 13:23:34 +03:00
s += ' '
2025-01-23 02:00:34 +03:00
s += str(self.matrix[row][col])
2024-07-15 13:23:34 +03:00
s += '\n'
2025-01-23 02:00:34 +03:00
2024-07-15 13:23:34 +03:00
return s
2025-01-23 02:00:34 +03:00
x = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
y = Matrix([[5, 7, 1], [2, 6, 4], [2, 3, 1]])
result = x @ y
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
print(x)
print(y)
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
2024-07-15 13:23:34 +03:00
def __gt__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val > number
return self.val > number.val
2024-07-15 13:23:34 +03:00
def __lt__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val < number
return self.val < number.val
2024-07-15 13:23:34 +03:00
def __ge__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val >= number
return self.val >= number.val
2024-07-15 13:23:34 +03:00
def __le__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val <= number
return self.val <= number.val
2024-07-15 13:23:34 +03:00
def __eq__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val == number
return self.val == number.val
2024-07-15 13:23:34 +03:00
def __ne__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val != number
return self.val != number.val
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
x = Number(10)
y = Number(10)
2024-07-15 13:23:34 +03:00
if x > y:
print('x > y')
elif x < y:
print('x < y')
2025-01-23 02:00:34 +03:00
elif x == y:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
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
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
bir operatör metodu yazabiliriz ancak kendi sınıfımızda 3.__add__(a) işlemini yapacak bir operaratör metodunu yazamayız. Çünkü
buradaka __add__ metodu int sınıfının metodu olmak zorundadır. Ö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.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
İşte iki operand'lı operatör metodunun ismi __op__ olmak üzere bunların __rop__ biçiminde başı "r" ile başlayan (reverse
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Programcının hem __op__ hem de __rop__ metotlarını ayrı ayrı yazmasına gerek yoktur. Bunlar aynı işi yapacağına göre
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Aşağıda daha önce yazmış olduğumuz Number sınıfının r'li operatör metotları eklenmiş biçimini de veriyoruz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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):
2025-01-23 02:00:34 +03:00
return self.val > number
return self.val > number.val
2024-07-15 13:23:34 +03:00
def __lt__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val < number
return self.val < number.val
2024-07-15 13:23:34 +03:00
def __ge__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val >= number
return self.val >= number.val
2024-07-15 13:23:34 +03:00
def __le__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val <= number
return self.val <= number.val
2024-07-15 13:23:34 +03:00
def __eq__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val == number
return self.val == number.val
2024-07-15 13:23:34 +03:00
def __ne__(self, number):
if isinstance(number, int|float):
2025-01-23 02:00:34 +03:00
return self.val != number
return self.val != number.val
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
__radd__ = __add__
__rsub__ = __sub__
__rmul__ = __mul__
__rtruediv__ = __truediv__
__rfloordiv__ = __floordiv__
__rgt__ = __gt__
__rlt__ = __lt__
__rge__ = __ge__
__rle__ = __le__
__req__ = __eq__
__rne__ = __ne__
x = Number(10)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
result = 2 + x
2024-07-15 13:23:34 +03:00
print(result)
2025-01-23 02:00:34 +03:00
result = x + 2
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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):
return self.val > number
return self.val > number.val
__rgt__ = __gt__
def __lt__(self, number):
if isinstance(number, int|float):
return self.val < number
return self.val < number.val
__rlt__ = __lt__
def __ge__(self, number):
if isinstance(number, int|float):
return self.val >= number
return self.val >= number.val
__rge__ = __ge__
def __le__(self, number):
if isinstance(number, int|float):
return self.val <= number
return self.val <= number.val
__rle__ = __le__
def __eq__(self, number):
if isinstance(number, int|float):
return self.val == number
return self.val == number.val
__req__ = __eq__
def __ne__(self, number):
if isinstance(number, int|float):
return self.val != number
return self.val != number.val
__rne__ = __ne__
x = Number(10)
result = 2 + x
print(result)
result = x + 2
#------------------------------------------------------------------------------------------------------------------------
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 metotları 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
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
a bir sınıf türünden değişken olmak üzere biz bu değişkeni a(...) biçiminde sanki fonksiyonmuş gibi fonksiyon çağırma
2024-07-15 13:23:34 +03:00
operatörü ile kullanabiliriz. Ancak bunun için ilgili sınıfın __call__ isimli bir operatör metodunun bulunuyor olması
gerekir. Yani:
a(...)
2025-01-23 02:00:34 +03:00
çağrısı aslında aşağıdaki çağrı ile tamamen eşdeğerdir:
2024-07-15 13:23:34 +03:00
a.__call__(...)
2025-01-23 02:00:34 +03:00
Bu nedenle Python'da fonksiyon çağırma operatörü ile çağrılabilen nesnelere genel olarak "callable" nesneler denilmektedir.
Bir fonksiyon "callable" bir nesnedir. __call__ metodu bulunan bir sınıf nesnesi de "callable" bir nesnedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Fonkisyon yerine sınıf nesnelerinin sanki fonksiyonmuş gibi kullanılması bazı durumlarda faydalar sağlamaktadır. Sınıf
2024-07-15 13:23:34 +03:00
nesneleri örnek özniteliklerinde bilgi tutabildiği için çağrılar arasında aynı değerlerin kullanılması mümkün olabilmektedir.
2025-01-23 02:00:34 +03:00
Fonksiyon yerine sınıf nesnelerinin kullanımıyla "callback" mekanizmasında yoğun olarak karşılaşılmaktadır. Bir foksiyon
2024-07-15 13:23:34 +03:00
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__()
2025-01-23 02:00:34 +03:00
len fonksiyonunun çokbiçimli (polymorpic) olduğuna dikkat ediniz. Yani biz pek çok nesnenin uzunluğunu len ile elde edebiliriz.
2024-07-15 13:23:34 +03:00
Ancak elde ettiğimiz değer o nesneye bağlıdır.
2025-01-23 02:00:34 +03:00
Biz de kendi sınıfımız türünden nesnelerin len fonksiyonuna sokulmasını istersek sınıfımız için __len__ metodunu yazmalıyız.
2024-07-15 13:23:34 +03:00
__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
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
50. Ders 11/01/2025 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
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 bulunur. 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. Örneğin:
class Number:
def __init__(self, val):
self.val = val
def __str__(self):
return str(self.val)
def __int__(self):
return int(self.val)
def __float__(self):
return float(self.val)
def __bool_(self):
return bool(self.val)
def __complex__(self):
return complex(self.val)
n = Number(123.4)
s = str(n)
print(s)
i = int(n)
print(i)
f = float(n)
print(f)
b = bool(n)
print(b)
c = complex(n)
print(c)
Tabii her sınıf için bu dönüştürmeleri yapan metotlar anlamlı olmayabilir. Örneğin rasyonel sayılarla işlem yapan
Rational isimli bir sınıf yazacak olsak bir rasyonel sayının float türüne dönüştürülmesi ve bool türüne dönüştürülmesi
anlamlıdır. Ancak diğer dönüştürülmesi o kadar anlamlı olmayabilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class Rational:
2025-01-23 02:00:34 +03:00
def __init__(self, a, b=1):
if b == 0:
raise ValueError('denominator od a rational number may not be zero!')
self.a = a
self.b = b
2024-07-15 13:23:34 +03:00
def __str__(self):
2025-01-23 02:00:34 +03:00
if self.b == 1:
return f'{self.a}'
if self.a == 0:
return '0'
return f'{self.a}/{self.b}'
2024-07-15 13:23:34 +03:00
def __float__(self):
2025-01-23 02:00:34 +03:00
return self.a / self.b
def __bool__(self):
return self.a != 0
r = Rational(3, 2)
2024-07-15 13:23:34 +03:00
print(r)
2025-01-23 02:00:34 +03:00
print(float(r))
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
if r:
print('non zero')
else:
print('zero')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Bir sınıf türünden değişken bool türüne dönüştürülürken eğer sınıfta bu dönüşüm için __bool__ metodu bulundurulmuşsa
bu metot çağrılır, bu metodun geri dönüş değeri dönüştürülmüş değer olarak elde edilir. Ancak sınıfta __bool__ metodu
bulundurulmamışsa bu durumda yorumlayıcı değişken içerisinde None olmayan bir değer bulunduğu için her zaman True
değerini üretecektir. Örneğin:
2024-07-15 13:23:34 +03:00
class Number:
def __init__(self, val):
self.val = val
x = Number(0)
result = bool(x)
print(result) # True
2025-01-23 02:00:34 +03:00
Eğer sınıfta __bool_ metodu yoksa değişkenin gösterdiği esnenin özniteliklerinde hangi değer 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:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
değişken atama operatörünün solunda da kullanılabilir. Bu durumda sınıfın __setitem__ metodu çağrılır. Başka bir
deyişle eğer biz bir 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.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
__getitem__ metodu self parametresinin yanı sıra indeks belirten bir parametreye daha sahiptir. __setitem__ ise self
2024-07-15 13:23:34 +03:00
parametresinin yanı sıra hem indeks belirten bir parametreye hem de atanacak değeri belirten bir parametreye sahiptir.
2025-01-23 02:00:34 +03:00
Bu iki metodun parametik yapıları şöyledir:
2024-07-15 13:23:34 +03:00
def __getitem__(self, index):
pass
def __setitem__(self, index, value):
pass
2025-01-23 02:00:34 +03:00
Kullanım sırasında köşeli parantez içerisindeki ifade metotların index parametresine aktarılır. __setitem__ metodunun
2024-07-15 13:23:34 +03:00
üçü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)
2025-01-23 02:00:34 +03:00
Tabii her sınıf için __getitem__ ve __setitem__ metotlarının bulundurulması anlamlı değildir. Birtakım değerleri
tutan ve değerler arasında öncelik sonralık ilişkisi bulunan sınıflar bu metotların bulundurulması anlamlıdır. Örneğin
Date sınıfındaki gün, ay ve yıl değerlerine [...] operatörü ile erişmek isteyebiliriz. Bunun için Date sınıfımız için
__getitem__ ve __setitem__ metotlarını yazabiliriz. Aşağıda böyle bir örnek verilmiştir.
#------------------------------------------------------------------------------------------------------------------------
class Date:
def __init__(self, day, month, year):
self.day = day
self.month = month
self.year = year
def __repr__(self):
return f'{self.day}/{self.month}/{self.year}'
def __getitem__(self, index):
match index:
case 0:
result = self.day
case 1:
result = self.month
case 2:
result = self.year
case _:
raise IndexError('invalid index')
return result
def __setitem__(self, index, value):
match index:
case 0:
self.day = value
case 1:
self.month = value
case 2:
self.year = value
case _:
raise IndexError('invalid index')
d = Date(11, 1, 2025)
print(d)
val = d[0] # d.__getitem__(0)
print(val)
val = d[1] # d.__getitem__(1)
print(val)
val = d[2] # d.__getitem__(2)
print(val)
d[1] = 7 # d.setitem__(2, 7)
print(d)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
yerleştirilmiştir. Sınıfın __getitem__ ve __setitem__ metotları index ile belirtilen sıradaki elemanı get ve set etmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
def __repr__(self):
return str(self.args)
2024-07-15 13:23:34 +03:00
def __len__(self):
return len(self.args)
2025-01-23 02:00:34 +03:00
2024-07-15 13:23:34 +03:00
s = Sample(10, 20, 30, 40, 50)
2025-01-23 02:00:34 +03:00
print(s)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
s[3] = 100
print(s)
print(s[1])
print(len(s))
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Python'daki listelerde ve demetlerde çok 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ünün kullanılması gerekir.
Örneğin:
2024-07-15 13:23:34 +03:00
>>> a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> a[2][1]
8
>>> a[2]
[7, 8, 9]
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a[2, 1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers or slices, not tuple
2025-01-23 02:00:34 +03:00
Ancak çok boyutlu diziler için bu tür kullanımların üçüncü parti bazı kütüphanelerde oluduğunu da görmekteyiz. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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:
2025-01-23 02:00:34 +03:00
val = s[(x, y, z)]
Bu da aşağıdakiyle eşdeğerdir:
val = s.__getitem__((x, y, z))
2024-07-15 13:23:34 +03:00
Benzer biçimde örneğin:
s[x, y, z] = val
2025-01-23 02:00:34 +03:00
işlemi aşağıdakiyle eşdeğerdir:
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
s[(x, y, z)] = val
Bu da aşağıdakiyle eşdeğerdir:
s.__setitem__((x, y, z), val)
2024-07-15 13:23:34 +03:00
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:
2025-01-23 02:00:34 +03:00
def __init__(self, matrix):
self.matrix = matrix
self.rowsize = len(matrix)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
self.colsize = len(matrix[0])
for i in range(1, self.rowsize):
if len(matrix[i]) != self.colsize:
raise ValueError('Matrix rows must be the same length')
def __getitem__(self, index) :
2024-07-15 13:23:34 +03:00
if isinstance(index, int):
2025-01-23 02:00:34 +03:00
return self.matrix[index]
2024-07-15 13:23:34 +03:00
if isinstance(index, tuple):
if len(index) != 2:
2025-01-23 02:00:34 +03:00
raise IndexError('invalid dimension in index')
if not isinstance(index[0], int) or not isinstance(index[1], int):
raise IndexError('Indexes must be int')
if index[0] >= self.rowsize or index[1] >= self.colsize:
raise IndexError('Index out of range')
return self.matrix[index[0]][index[1]]
def __setitem__(self, index, value) :
if isinstance(index, int):
if not isinstance(value, list|tuple):
raise ValueError('invalid assignment')
if len(value) != self.colsize:
raise ValueError(f'column size must be {self.colsize}')
self.matrix[index] = value
elif isinstance(index, tuple):
2024-07-15 13:23:34 +03:00
if len(index) != 2:
2025-01-23 02:00:34 +03:00
raise IndexError('invalid dimension in index')
if not isinstance(index[0], int) or not isinstance(index[1], int):
raise IndexError('Indexes must be int')
if index[0] >= self.rowsize or index[1] >= self.colsize:
raise IndexError('Index out of range')
self.matrix[index[0]][index[1]] = value
def __repr__(self):
s = ''
for r in range(self.rowsize):
for c in range(self.colsize):
if c != 0:
s += ' '
s += str(self.matrix[r][c])
s += '\n'
return s
m = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
result = m[2]
print(result)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
result = m[2, 2]
print(result)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
m[2, 2] = 100
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
print(m[2, 2])
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
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. Eğer 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. Eğer slice nesnesi üç argümanla yaratılmışsa argümanlar
sırasıyla start, stop ve step değerlerinde olur. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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__
2025-01-23 02:00:34 +03:00
metotlarına slice nesnesi geçirmektedir. Yani yorumlayıcı dilimleme sentaksını gördüğünde o dilimlemedeki öğelerden
bir slice nesnesi yaratıp o nesneyi __getitem__ ve __setitem__ metotlarına argüman olarak geçirmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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))
2025-01-23 02:00:34 +03:00
Dilimleme sentaksında boş bırakıl öğeeler için slice nesnesine None değeri geçirildiğine dikkat ediniz. Tabii aynı durum
__setitem__ metodu için de geçerlidir. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
s[:y:z] ---> slice(None, y, z)
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
fonksiyonu ile kontrol edip dilimleme mantığına uygun işlemleri yapabiliriz. Böylece sınıfımıza dilimleme dessteği
2024-07-15 13:23:34 +03:00
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]
"""
2025-01-23 02:00:34 +03:00
def __setitem__(self, index, value):
if isinstance(index, int):
self.args[index] = value
elif isinstance(index, slice):
self.args[index] = value
"""
2024-07-15 13:23:34 +03:00
def __setitem__(self, index, value):
self.args[index] = value
2025-01-23 02:00:34 +03:00
"""
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
s[2:4] = [100, 200, 300]
print(s)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Python'a 3'lü versiyonlarla birlikte "... (ellipsis)" biçiminde bir sabit de eklenmiştir. Önceleri bu sabit yalnızca
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> a = ...
>>> a
Ellipsis
>>> type(a)
<class 'ellipsis'>
>>> id(a)
140734384027912
>>> b = Ellipsis
>>> type(b)
<class 'ellipsis'>
>>> 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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Python çok modelli (multi-paradigm) bir programalama dilidir. Biz Python'u istersek sınıf konusuna girmeden tamamen
prosedürel bir biçimde kullanabiliriz. NYPT özellikle büyük projelerin mantıksal bakımdan daha kolay ele alınabilmesi
düşünülmüştür. Eğer kodlarımız çok uzun ve kapsamlı değilse python'un sınıfsal özelliklerini kullanmamıza gerek olmayabilir.
Tabii daha önceden de belirttiğimiz gibi Pythoon'un 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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
kullanılan veri elemanlarıdır. Ancak bazı fonksiyonlar belli bir 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:
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
O halde bizim şöyle bir özelliğe gereksinimimiz vardır: Bir metot hem bir sınıf içerisinde bulunsun hem de zaten kullanmayacağı
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
dillerinde bu gereksinimin sağlanabilmesi için "static metot" kavramı kullanılmaktadır. static metot demek self parametresi
olmayan dolayısıyla belli bir nesnenin özniteliklerini kullanmayan ancak konu bakımından sınıfla ilişkili olan metot demektir.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Python'da bir metodu static yapabilmek için metodun @staticmethod isimli bir dekoratörle dekore edilmesi gerekir.
2024-07-15 13:23:34 +03:00
Dekoratörler konusu izleyen bölümlerde ele alınmaktadır. Bir dekoratör bir fonksiyonu, bir metodu ya da bir sınıfı
2025-01-23 02:00:34 +03:00
dekore edebilir. Dekoratör kullanımının genel biçimi şöyledir:
2024-07-15 13:23:34 +03:00
@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
2025-01-23 02:00:34 +03:00
Artık burada isleap metodunun year parametresi birinci parametre olmasına karşın self anlamında değildir. Yani metot artuk
2024-07-15 13:23:34 +03:00
self parametresine sahip değildir.
2025-01-23 02:00:34 +03:00
Static metotlar nesnenin özniteliklerini kullanmadığına göre onların çağrılması için bir nesneye gereksinim de yoktur.
İşte static metotlar sınıf ismi ve metot ismi belirtilerek çağrılırlar. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
nesnenin özniteliklerini kullanıp kullanmadığına da bakabilirsiniz. Eğer metot bir nesnnein özniteliklerini kullanmıyorsa
2024-07-15 13:23:34 +03:00
static metot yapılabilir.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Örneğin Date sınıfına o anki tarih bilgini bize bir Date nesnesi olarak veren today isimli bir metot eklemek isteyelim.
2025-01-23 02:00:34 +03:00
Bu metot işletim sisteminden o günkü tarih bilgisini alıp onu bir Date nesnesi haline getirip bu Date nesnesini bize
verecektir. Söz konusu today metodu belli bir nesnenin özniteliklerini kullanmamaktadır. Bize yeni bir Date nesnesi
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)
Static metotların sınıf ismiyle kullanılabildiğine dikkat ediniz. Biz bu static metodu aşağıdaki gibi kullanabiliriz:
date = Date.today()
print(date)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
static metotların normal olarak sınıf ismiyle çağrılması gerekir. Çünkü onlar nesnenin veri özniteliklerini kullanmamaktadır.
Ancak ek bir özellik 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şkenden yalnızca sınıfın belirlenmesinde faydalanılmaktadır.
2024-07-15 13:23:34 +03:00
Ö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
2025-01-23 02:00:34 +03:00
isimlendirilir. Sınıf metotları @classmethod isimli dekoratörle dekore edilirler. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
@staticmethod
def foo():
pass
@classmethod
def bar(cls):
pass
def tar(self):
pass
2025-01-23 02:00:34 +03:00
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. Örneğin:
Sample.bar()
çağrısı yapıldığında cls parametresine Sample (yani Sample sınıfının type nesnesi) geçirilmektedir.
2024-07-15 13:23:34 +03:00
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) # <class '__main__.Sample'>
print(cls is Sample) # True
Sample.foo()
s = Sample()
s.foo()
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Static metotlar varken sınıf metotlarına neden gereksinim duyulduğunu merak edebilirsiniz. Sınıf metotlarına static
metotlardan daha seyrek gereksinim duyulmaktadır. Aslında static metotlar yerine sınıf metotlarının kullanılmasının
gerektiği durumlar vardır. Bir static metot türemiş sınıf sınıf ismiyle de çağrılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
class A:
@staticmethod
def foo():
pass
class B(A):
pass
A.foo() # geçerli
B.foo() # geçerli
2025-01-23 02:00:34 +03:00
Taban sınıfta bir sınıf metodu bulunuyor olabilir. Bu sınıf metodu türemiş sınıf ismiyle çağrılırsa metodun cls parametresine
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Böylece biz bu metodun hangi sınıf ismi ile çağrıldığını anlayıp bazı durumlarda bu bilgiden faydalanabiliriz. Örneğin:
class A:
@classmethod
def foo(cls):
print(cls)
class B(A):
pass
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
A.foo()
B.foo()
Burada A sııfındaki foo isimli sınıf metodu A ismiyle de B ismiyle de çağrılmıştır. Ancak foo metodunun birinci parametresine
metot hangi sınıf ismiyle çağrılmışsa o sınıfın type nesnesi geçirilmiştir. Böyle bir tespit static metotlarla yapılamaz.
Tabii bu biçimdeki bir tespitin gerektiği yerler son derece kısıtlıdır. Bu nednele siz özellikle gerekmedikçe sınıf
metodu yerine static metodu tercih etmelisiniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
Biz buaradaki foo metodunu normal olarak Sample türünden bir değişkenle çağrırız:
2024-07-15 13:23:34 +03:00
s = Sample()
2025-01-23 02:00:34 +03:00
2024-07-15 13:23:34 +03:00
s.foo(10, 20)
Burada s, metodun self parametresine, 10 metodun a parametresine 20 de metodun b parametresine aktarılacaktır.
2025-01-23 02:00:34 +03:00
Bu çağrının alternatifin olarak şöyle de yapılabileceğini belirtmiştik:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Python'da dekoratörler fonksiyonlara, 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.
2025-02-16 00:11:32 +03:00
Bir fonksiyonu, metodu ya da sınıfı dekora edebilmek için fonksiyonun, metodun 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:
2024-07-15 13:23:34 +03:00
@foo
def bar():
pass
Burada bar fonksiyonu dekore edilmiştir. Buradaki dekoratör foo'dur. Örneğin:
@foo
class Sample:
pass
2025-01-23 02:00:34 +03:00
Burada da Sample sınıfı dekore edilmiştir. Yine buradaki dekoratör foo'dur.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir dekorasyon yapılırken @ 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. Ya da bir sınıf
ismi de olabilir. (Sınıf isimlerinin de aslında çağrılabilen bir nesne gibi davrandığını anımsayınız. İlgili sınıf türünden
nesneler fonksiyon çağırma sentaksı ile yaratılmaktadır.)
Tabii buradaki çağrılabilen nesnenin bir parametrenin olması gerekmektedir. Eğer çağrılabilen (callable) nesne bir fonksiyon
nesnesi ise fonksiyonun bir parametresi olmalıdır. Eğer çağrılabilen nesne bir sınıf nesnesi ise sınıfın __call__ metodunun
self dışında bir parametresinin daha olması gerekir. Eğer çağrılabilen nesne bir sınıf ismi ise bu durumda sınıfın __init__
metodunun self dışında bir parametresinin olması gerekir. Örneğin:
2024-07-15 13:23:34 +03:00
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:
2025-01-23 02:00:34 +03:00
class Sample:
def __init_(self, f):
pass
@Sample
def bar():
pass
Burada Sample sınıf ismi bir dekoratördür. Sınıfın __init__ metodunun self dışında bir parametresi daha vardır. Örneğinİ
2024-07-15 13:23:34 +03:00
class Sample:
def __call_(self, f):
pass
2025-01-23 02:00:34 +03:00
s = Sample()
@s
2024-07-15 13:23:34 +03:00
def bar():
pass
2025-01-23 02:00:34 +03:00
Burada s bir dekoratördür. __call__ metodunun self parametresi dışında ekstra bir parametresi daha vardır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
51. Ders 12/01/2025 - Pazar
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki gibi dekore edilmiş bir fonksiyon olsun:
2024-07-15 13:23:34 +03:00
@foo
def bar():
pass
2025-01-23 02:00:34 +03:00
Bu dekorasyonun eşdeğeri tamamen şöyledir.
2024-07-15 13:23:34 +03:00
def bar():
pass
bar = foo(bar)
2025-01-23 02:00:34 +03:00
Yani @ atomunun yanındaki isim bir fonksiyondur (genel olarak calllable bir nesnedir). Dekore edilmiş fonksiyon bu
fonksiyona parametre yapılıp geri dönüş değeri 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:
2024-07-15 13:23:34 +03:00
@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):
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):
return foo
def tar():
pass
tar = bar(tar)
tar()
2025-01-23 02:00:34 +03:00
Dekoratör kalıbında programcı dekorasyon işleminden sonra dekore edilen fonksiyonu çağırmak istediğinde aslında başka
bir fonksiyon çağrılabilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
aslında başka bir fonksiyonu çağırırız, o fonksiyon da birşeyler yaptıktan sonra asıl çağırmak istediğimiz 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:
2024-07-15 13:23:34 +03:00
1) İç bir fonsiyon kullanılarak
2) __call__ metodu bulunan bir sınıf kullanılarak
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Birinci yöntemde dekoratör fonksiyonu iç bir fonksiyon ile geri döner. Böylece asıl fonksiyon çağrıldığında aslında iç
2024-07-15 13:23:34 +03:00
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')
2025-01-23 02:00:34 +03:00
return f()
2024-07-15 13:23:34 +03:00
return bar
@foo
def tar():
print('tar')
2025-01-23 02:00:34 +03:00
return 100
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
result = tar()
print(result)
2024-07-15 13:23:34 +03:00
print('---------')
2025-01-23 02:00:34 +03:00
result = tar()
print(result)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
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. Tüm işlevi
sağlamak için tekyapılan şey fonksiyonun yukarısına @foo dekoratörünün eklenmesidir. Burada ayrıca bar fonksiyonnun
orjinal fonksiyonun geri dönüş değeri ile geri döndüğüne dikkat ediniz. Böylece örneğimizde tar fonksiyonu çağrıldığında
yine tar fonksiyonun geri dönüş değeri elde edilecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def foo(f):
def bar():
print('araya giren kod')
2025-01-23 02:00:34 +03:00
return f()
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
fonksiyona aktarılmasıdır. Bu işlemi daha önce "yönlendirme (forwarding)" olarak da nitelemiştik. Örneğin:
2024-07-15 13:23:34 +03:00
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}')
2025-01-23 02:00:34 +03:00
Burada tar çağrılırken girilen argümanlar aslında bar fonksiyonuna girilmiş gibi olacaktır. Çünkü yukarıdaki dekorasyonun
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Bu 10, 20, 30 parametreleri bar fonksiyonunun args parametresine bir demet olarak aktarılacaktır. bar fonksiyonunun kwargs
2024-07-15 13:23:34 +03:00
parametresi boş sözlükten oluşacaktır. bar fonksiyonu içerisinde f(*args, **kwargs) çağrımı aslında tar fonksiyonunun
2025-01-23 02:00:34 +03:00
10, 20, 30 argümanlarıyla ve olmayan isimli argümanlarla çağrılması anlamına gelecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def foo(f):
def bar(*args, **kwargs):
print('araya giren kod')
2025-01-23 02:00:34 +03:00
return f(*args, **kwargs)
2024-07-15 13:23:34 +03:00
return bar
@foo
def tar(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
tar(10, 20, 30)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Araya girme işlemi yukarıda ikinci maddede de 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 daha esnek kullanım sınmaktadır. Bu nedenle
araya girme işlemi için daha çok bu yöntem tercih edilmekedir.
2024-07-15 13:23:34 +03:00
@foo
def bar(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
2025-01-23 02:00:34 +03:00
Burada foo'nun bir sınıf olduğunu düşünelim. Bu durumda yukarıdaki kodun eşdeğeri şöyle olur:
2024-07-15 13:23:34 +03:00
bar = foo(bar)
2025-01-23 02:00:34 +03:00
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.
2024-07-15 13:23:34 +03:00
class foo:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
print('araya girilen kod')
2025-01-23 02:00:34 +03:00
return self.f(*args, **kwargs)
2024-07-15 13:23:34 +03:00
@foo
def bar(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
bar(10, 20, 30)
2025-01-23 02:00:34 +03:00
foo sınıfının __init__ metodu içerisinde alınan fonksiyonun nesnenin özniteliğinde saklandığına dikkat ediniz. Sonra
bu fonksiyon __call__ metodunda çağrılmıştır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class foo:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
print('araya girilen kod')
2025-01-23 02:00:34 +03:00
return self.f(*args, **kwargs)
2024-07-15 13:23:34 +03:00
@foo
def bar(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
bar(10, 20, 30)
print('---------------')
bar(40, 50, 60)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Pekiyi dekoratörler araya girmeyi sağlıyorsa araya girmenin ne anlamı olabilir? İşte araya giren kod arka planda bizim
2024-07-15 13:23:34 +03:00
için bazı faydalı işlemleri yapıyor olabilir. Örneğin araya giren kod bir log oluşturabilir ya da faydalı başka şeyleri
2025-01-23 02:00:34 +03:00
yapıyor olabilir. Aşağıdaki örnekte counter isimli dekoratör sınıfı fonksiyon her çağrıldığında count sınıf değişkenini
2024-07-15 13:23:34 +03:00
(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
2025-01-23 02:00:34 +03:00
return self.f(*args, **kwargs)
2024-07-15 13:23:34 +03:00
@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
2025-01-23 02:00:34 +03:00
return self.f(*args, **kwargs)
2024-07-15 13:23:34 +03:00
@counter
def bar():
print('bar')
bar()
bar()
bar()
bar()
bar()
print(counter.count) # 5
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Tabii dekorasyonun @ sentaksıyla yapılması da zounlu değildir. Örneğin programcı kendisinin yazmadığı fonksiyonları
ve sınıfları dekore ederken mecburen @ sentaksı yerine açıkça fonksiyon çağırma sentaksını kullanır. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
Burada sqrt ismi aslında abs_decorator sınıfı türündne bir nesneyi belirtmektedir. Bu nesneyle fonksiyon çağrıldığında
sınıfın __call__ metodu çağrılacak ve oriinal fonksiyon (burada math.sqrt) pozitif parametreyle çağrılmış olacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
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)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Örneğin daha önce görmüş olduğumuz @staticmethod ve @classmethod dekoratörleri de aslında birer built-in b çağrılabilir
(callable) nesne belirtmektedr. Dolayısıyla biz bu dekoratörleri fonksiyon çağırma sentaksıyla da kullanabiliriz.
Bu dekoratörler CPython yorumlayıcısında built-in birer sınıf biçiminde oluşturulmuştur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class Sample:
@staticmethod
def foo():
print('foo')
def bar():
print('bar')
bar = staticmethod(bar)
Sample.foo()
Sample.bar()
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Görüldüğü gibi dekoratörler aslında arka planda birtakım işlemlerin pratik bir biçimde yapılması için bir araç oluşturmaktadır.
Örneğin bir dekoratör, fonksiyonun çalışma zamanını ölçerek geri dönüş değeri biçiminde bize verebilir:
2024-07-15 13:23:34 +03:00
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()
2025-01-23 02:00:34 +03:00
return result, stop - start
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
def sum_numbers(n):
2024-07-15 13:23:34 +03:00
total = 0
2025-01-23 02:00:34 +03:00
for i in range(1, n + 1):
2024-07-15 13:23:34 +03:00
total += i
return total
2025-01-23 02:00:34 +03:00
sum_numbers = profiler(sum_numbers)
result, t = sum_numbers(1_000_000_000)
print(result, t)
2024-07-15 13:23:34 +03:00
Burada herhangi bir fonksiyon profiler isimli dekoratörle dekore edildiğinde artık fonksiyon bir demete geri döner.
2025-01-23 02:00:34 +03:00
Demetin birinci elemanı fonksiyonun geri dönüş değerini, ikinci elemanı ise çalışma zamanını belirtmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
2025-01-23 02:00:34 +03:00
return result, stop - start
def sum_numbers(n):
2024-07-15 13:23:34 +03:00
total = 0
2025-01-23 02:00:34 +03:00
for i in range(1, n + 1):
2024-07-15 13:23:34 +03:00
total += i
return total
2025-01-23 02:00:34 +03:00
sum_numbers = profiler(sum_numbers)
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
result, t = sum_numbers(1_000_000_000)
print(result, t)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
Buradaki kodun eşdeğeri şöyledir:
2024-07-15 13:23:34 +03:00
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ığı
2025-01-23 02:00:34 +03:00
sınıfın aynısı ile geri döndüğüne dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2025-01-23 02:00:34 +03:00
Genellikle burada araya giren kodlar nesne üzerinde bazı öznitelikleri arka planda yaratarak nesneyi belli bir amaç
için kullanıma hazırlamaktadır.
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
Aşağıdaki örnekte Sample sınıfı türünden bir nesne yaratıldığında bar fonksiyonu çağrılmaktadır. Sample nesnesi ise bar
fonksiyonu tarafından yaratılmaktadır. Yani Sample(...) çağrısı aslında bar(...) çağrısı anlamına gelmektedir. Bu
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Tabii yukarıdaki örnekte dekoratör olarak iç fonksiyon kullanmak yerine bir sınıf da kullanılabilirdi. Yukarıda da
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
print('araya giren kod')
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Örneğin bir dekoratörün sınıf içerisine gizlice bir metot eklemesi, sınıf türünden nesnelerin içerisine öznitelikle
2024-07-15 13:23:34 +03:00
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()
2025-01-23 02:00:34 +03:00
Burda test fonksiyonun bir parametresinin olduğuna dikkat ediniz. Okunabilirliği artırmak için biz bu parametresi self
biçiminde isimlendirdik. Burada test fonksiyonu aslında Sample sınıfına bir metot gibi eklenmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
Burada ince fakat bazen önemli olabilecek bir ayrıntı üzerinde durmak istiyoruz. Aşağıdaki gibi bir dekorasyon söz konusu
olsun:
@foo
def bar():
pass
Biz (ve bizim gibi pek çok kaynak) kolay bir anlatım sağlayabilmek için bu dekorasyonun eşdeğerinin aşağıdaki gibi
olduğunu söylemiştik:
def bar():
pass
bar = foo(bar)
Aslında dekorasyınun tam eşdeğeri yukarıdaki gibi aşağıdaki gibidir:
def _bar_xxx():
pass
bar = foo(_bar_xxx)
Burada _bar_xxx bar fonksiyonunun atandığı geçici değişkeni belirtiyor. Yani aslında yorumlayıcı önce bar değişkenine
fonksiyon nesnesini atayıp sonra dekore etmemektedir. Bir geçici değişken yoluyla fonksiyon nesnesini dekoratöre
vermektedir. Benzer biçimde:
@foo
class bar:
pass
Bu dekorasyonun eşdeğeri de aslında tam olarak aşağıdaki gibi değildir:
class bar:
pass
bar = foo(bar)
Aşağıdaki gibidir:
class _bar_xxx:
pass
bar = foo(_bar_xxx)
Burada _bar_xxx sınıf için oluturulan type nesnesinin atandaığı geçici değişkeni belirtiyor. Yani aslında yorumlayıcı
önce bar değişkenini oluşturup onu foo dekoratörüne vermemektedir. Yorumlayıcı sınıfın type nesnesini oluşturup
onu geçici değişken yoluyla foo dekoratörüne vermektedir. İşte kolay anlaşılsın diye verilen eşdeğerlikle gerçek
eşdeğer durum arasındaki fark bazı durumlarda önemli olabilmektedir. İleride property'lerin anlatıldığı bölümde
bu konuda açıklamalar yapacağız.
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
52. Ders 18/01/2025 - Cumartes
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
"Python Uygulamaları" kursunda göreceğiz. Nispeten soyut olan bu konunun kişiler tarafından anlaşılması bazı konuları
gördükten ve bazı uygulamaları yaptıktan sonra daha kolay olacaktır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
2025-01-23 02:00:34 +03:00
Burada aslında foo nesnesi çağrılmış bunun geri dönüş değeri yeniden çağrılmıştır. Yani parametreli bir dekoratörün
çağrılması sonucunda yine bir çağrılabilir nesne elde edilmelidir. Elde edilen bu çağrılabilri nesne de dekoratör görevini
yapabiliyor olmalıdır. Buradaki işlemi adım adım yeniden çözümleyelim:
2024-07-15 13:23:34 +03:00
1) Burada önce foo nesnesi ile çağırma yapılmıştır:
temp1 = foo(a, b, c)
2025-01-23 02:00:34 +03:00
2) Bu çağrının sonucunda çağrılabilir bir nesne elde edilmelidir. Bu çağrılabilir nesneye bar argüman yapılarak çağrılmıştır:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Parametreli dekoratör yine iç fonksiyonlar yoluyla ya da sınıflar yoluyla gerçekleştirilebilir. İç fonksiyon yoluyla
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Bu çağrıdan wrapper1 fonksiyonu ile geri dönülmüştür. Dolaysıyla bu geri döndürülen fonksiyon bar argümanıyla çağrılmıştır.
2024-07-15 13:23:34 +03:00
Ş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()
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Aşağıdaki örnekte msg isimli parametrelş bşr dekoratör iç fonksiyonlar yoluyla yazılmıştır. msg fonksiyonu parametre
2025-01-23 02:00:34 +03:00
olarak bir yazı almaktadır. Asıl fonksiyon ne zaman çağrılsa önce bu yazı ekrana basılacaktır:
def msg(text):
def msg_decorator(f):
def msg_decorator_proc(*args, **kwargs):
print(text)
return f(*args, *kwargs)
return msg_decorator_proc
return msg_decorator
Burada msg fonksiyonunun msg_decorator fonksiyonuna geri döndüğüne, msg_decorator fonksiyonunun da msg_decorator_proc
fonksiyonuna geri döndüğüne dikkat ediniz. Dekorasyon aşağıdaki gibi yapılabilir:
@msg('foo called')
def foo():
print('foo')
Bu dekorasyon aşağıdaki ile eşdeğerdir:
foo = msg('foo called')(foo)
Şimdi foo fonksiyonu çağrıldığında aslında msg_decorator_proc fonksiyonu çağrılacaktır.
#------------------------------------------------------------------------------------------------------------------------
def msg(text):
def msg_decorator(f):
def msg_decorator_proc(*args, **kwargs):
print(text)
return f(*args, *kwargs)
return msg_decorator_proc
return msg_decorator
@msg('foo called')
def foo():
print('foo')
return
foo()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Parametreli dekoratörler de 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ıf olmalıdır.
Dolayısıyla elde edilen nesneyle çağrı uygulandığında aslında sınıfın __call__ metodu çağrılmış olur. 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ğırmış olacaktır. Örneğin:
2024-07-15 13:23:34 +03:00
@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
2025-01-23 02:00:34 +03:00
10, 20 ve 30 foo sınıfının __init__ metoduna parametre olarak aktarılacaktır. Sınıf bu parametreleri nesnenin özniteliklerinde
saklayabilir.
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
return self._decorator_proc
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
aslında yaratılmış olan foo nesnesi ile sınıfın _decorator_proc metodu çağrılacaktır. Böylece artık biz bar fonksiyonunu
çağırdığımızda bu fonksiyon kaydedilen bütün bilgilere self yoluyla erişebilecektir:
2024-07-15 13:23:34 +03:00
class foo:
def __init__(self, x, y, x):
self.x = X
self.y = y
self.z = z
def __call__(self, f):
self.f = f
2025-01-23 02:00:34 +03:00
return self._decorator_proc
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
def _decorator_proc(self, *args, **kwargs):
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
return self._decorator_proc
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
def _decorator_proc(self, *args, **kwargs):
2024-07-15 13:23:34 +03:00
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()
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
aşağıda daha önce iç fonksiyonlar yoluyla yapılmış olan msg dekoratörü bu kez sınıflar yoluyla yapılmıştır.
#------------------------------------------------------------------------------------------------------------------------
class msg:
def __init__(self, text):
self.text = text
def __call__(self, f):
self.f = f
return self._decorator_proc
def _decorator_proc(self, *args, **kwargs):
print(self.text)
return self.f(*args, **kwargs)
@msg('foo called')
def foo():
print('foo')
foo()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Dekoratör olarak sınıf kullanarak yukarıdaki log örneğini de aşağıdaki gibi yapabiliriz.
#------------------------------------------------------------------------------------------------------------------------
import datetime
class log:
def __init__(self, file):
self.file = file
def __call__(self, f):
self.f = f
2025-01-23 02:00:34 +03:00
return self.decorator_proc
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
def decorator_proc(self, *args, **kwargs):
2024-07-15 13:23:34 +03:00
now = datetime.datetime.now()
self.file.write(f'Call time: {now}' + '\n')
2025-01-23 02:00:34 +03:00
2024-07-15 13:23:34 +03:00
start = time.time()
retval = self.f(*args, **kwargs)
stop = time.time()
2025-01-23 02:00:34 +03:00
self.file.write(f'Execution time: {stop - start}' + '\n')
self.file.write('-----------------------------------\n')
2024-07-15 13:23:34 +03:00
return retval
2025-01-23 02:00:34 +03:00
f = open('log.txt', 'w')
@log(f)
def foo():
print('foo')
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
import time
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
foo()
time.sleep(1)
foo()
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
f.close()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Sonuç olarak biz bir fonksiyonun dekore edildiğini gördüğümüzde şunu düşünmeliyiz: "Bu fonksiyonu çağırdığımda aslında
2024-07-15 13:23:34 +03:00
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."
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Kursumuzun bu bölümünde Python'da exception işlemlerini ele alacağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
İngilizce "exception" sözcüğü "istisna" anlamına gelmektedir. Ancak bu sözcük yazılımda "programın çalışması sırasında
2024-07-15 13:23:34 +03:00
oluşan problemli durumları" anlatmak için kullanılmaktadır. Exception mekanizması genel olarak nesne yönelimli programlama
2025-01-23 02:00:34 +03:00
dillerinde bulunur. Prosedürel dillerin çoğunda bu mekanizma yoktur. Exception programın çalışma zamanına ilişkin
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Python'da exception'ların birer sınıf ismi vardır. Exception sınıfları XXXError biçiminde isimlendirilmiştir. (örneğin
TypeError, IndexError, ValueError gibi). Biz şimdiye kadar çeşitli konularda exception'larla lzaten karşılaşmıştık.
2024-07-15 13:23:34 +03:00
Örneğin:
>>> int('ali')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'ali'
>>> 10 + 'ali'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> a = [1, 2, 3]
>>> a[10]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
2025-01-23 02:00:34 +03:00
durum oluştuğunda programcı akışı başka bir biçimde devam ettirebilir ya da hatayı düzeltip programın normal 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.
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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: <suit>
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
except [<exception sınıf bildirimleri> [as <değişken_ismi>]]: <suit>
finally: <suit>
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
2025-01-23 02:00:34 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
try anahtar sözcüğünü, except cümleceğini, else anahtar sözcüğünü ve finally anahtar sözcüğünü 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. Örneğin "except bloğu" dediğimizde except anahtar sözcüğü ve bir suit anlaşılmalıdır.
2024-07-15 13:23:34 +03:00
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:
<suit>
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:
<suit>
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):
<suite>
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:
<suite>
5) except anahtar sözcüğünü bir şey izlemeyebilir. Örneğin:
except:
<suite>
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
2025-01-23 02:00:34 +03:00
try bloğunu except olmadan fibnally bloğu daizleyebilir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-01-23 02:00:34 +03:00
else bloğunun excpet bloklarından sonra ancak varsa finally bloğundan önce bulunduurlduğuna dikkat ediniz.s
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
53. Ders 19/01/2025 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
bulunyor olması gerekir. Aksi takdirde yine program çökecektir. Yani exception'ın yakalanması için akışın try bloğuna
girmesi ve o try bloğunun oluşan exception'ı yakalayabilecek bir except blıoğunun olması gerekir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
def tar():
print('tar başladı')
2025-01-23 02:00:34 +03:00
int('xxxxx') # ValueError oluşacak
2024-07-15 13:23:34 +03:00
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')
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
Python'da en çok karşılaşılan exception sınıfları şunlardı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.
2024-07-15 13:23:34 +03:00
Ö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
2025-01-23 02:00:34 +03:00
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şur. 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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 <exception sınıf nesnesi>
raise anahtar sözcüğünün yanında bir exception nesnesi bulunmalıdır. Bir problem ortaya çıktığında programcı bir exception
2025-01-23 02:00:34 +03:00
nesnesi oluşturur. Probleme ilişkin bazı bilgileri eğer gerekiyorsa o nesnenin örnek özniteliklerine yerleştirir ve o
nesneyle raise işlemi yapar. C++ gibi bazı programlama dillerinde bu tür işlemlerde herhangi türden nesneler kullanılabilmektedir.
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Daha önce sözünü ettiğimiz ValueError, TypeError, IndexError gibi exception sınıflarının __init__ metotları bizden
tipik olarak bir yazıyı parametre olarak almaktadır. Eğer exception yakalanmazsa program çökerken bu yazı da ekrana bastırılmaktadır.
2024-07-15 13:23:34 +03:00
Ö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')
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aslında ValueError, TypeError, IndexError gibi sınıfların __init__ metotları *'lı parametre almıştır. Dolayısıyla biz
bı sınıflar türünden nesneler yaratırken parantez içerisine istediğimiz kadar argüman girebiliriz. Bu argümanlar da
aslında herhangi türden olabilir. Yani bu nesneler yaratılırken biz onlara yazı vermek zorunda dğiliz. ÖrneğiN:
if a < 0:
raise ValueError('parametre negatif olamaz, a)
Verilen parametreler nesnenin args isimli demet özniteliğinde saklanmaktadır. Örneğin:
ve = ValueError(10, 20, 30)
print(ve.args) # 10, 20, 30
Bu sınıfların __str__ metotları (bu konudaki ayrıntılar izleyen paragraflarda ele alınmaktadır) aslında args özniteliğindeki
demeti yazı olarak vermektedir. Ancak eğer bu nesne yaratılırken tek bir argüman girilmişse bu durumda __str__ metotları
bu argümanı parantezler olmadan tek bir yazı biçiminde vermektedir. Örneğin:
ve = ValueError(10, 20, 30)
print(ve) # (10, 20, 30)
ve = ValueError(10)
print(ve) # 10
ve = ValueError('parametre negatif olamaz')
print(ve) # parametre negatif olamaz
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Ö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ı
2025-01-23 02:00:34 +03:00
olarak Exception isimli bir sınıftan türetilmiş durumdadır. Bu Exception sınıfı da BaseException isimli bir sınıftan
2024-07-15 13:23:34 +03:00
türetilmiştir. Yani Python'daki exception sınıf hiyerarşisi tipik olarak şöyledir:
2025-02-02 21:34:10 +03:00
BaseException
2025-01-23 02:00:34 +03:00
Exception
... ValueError TypeError AttributeError LookupError ....
KeyError IndexError
2024-07-15 13:23:34 +03:00
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')
...
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-01-23 02:00:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
BaseException sınıfının __str__ ve __repr__ metotları vardır. Bu metotlar eğer exception nesnesine tek argüman girilmişse o
2024-07-15 13:23:34 +03:00
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)
2025-01-23 02:00:34 +03:00
Görğldüğü gibi aslında args özniteliğ BaseException sınıfında yaratılmaktadır. __str__ ve __repr__ metotları da
taban sınıf olan BaseException sınıfından gelmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aşağıda Python'daki built-in exception sınıfları hiyerarşik biçimde gösterilmiştir:
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ExceptionGroup [BaseExceptionGroup]
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ ├── PythonFinalizationError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
├── BytesWarning
├── DeprecationWarning
├── EncodingWarning
├── FutureWarning
├── ImportWarning
├── PendingDeprecationWarning
├── ResourceWarning
├── RuntimeWarning
├── SyntaxWarning
├── UnicodeWarning
└── UserWarning
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
except bloklarında except anahtar sözcüğü ve exception sınıf isminden sonra as anahtar sözcüğü ile bir değişken de
belirtilebilir. Örneğin:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Böylece bir exception fırlatıldığında programcı o exception'ı yakalayarak oradan exception'ın argümanlarına erişebilir.
2024-07-15 13:23:34 +03:00
Ö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
2025-01-23 02:00:34 +03:00
İşte böylesi bir durumda hangi exception oluşursa (örneğimizde TypeError ya da ValueError) o exception nesnesi as ile
2024-07-15 13:23:34 +03:00
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ı
2025-01-23 02:00:34 +03:00
gerekir. Ancak "Python Standart Library Reference" 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üretilmesini tavsiye etmektedir. Tabii biz istersek
zaten var olan exception sınıflarından da türetme yapabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Pekiyi hazır pek çok built-in exception sınıfı varken programcının kendi exception sınıflarını oluşturmasına gerek var
mıdır? Exception'lar bir grup olarak yakalanacaksa böyle bir çabaya gerek olabilmektedir. Bazı kütüphanelerde bazı
2024-07-15 13:23:34 +03:00
konulara ilişkin exception'lar standart exception sınıflarıyla mantıksal olarak ifade edilemeyebilirler. Bu tür durumlarda
2025-01-23 02:00:34 +03:00
programcılar kendi exception sınıflarını yazabilmektedir. Gerçekten de standart kütüphanenin çeşitli modüllerinde o
2024-07-15 13:23:34 +03:00
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...')
#------------------------------------------------------------------------------------------------------------------------
2025-01-23 02:00:34 +03:00
53. Ders 31/10/2022 - Pazartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
exception'ları da yakalayabilmektedir. Örneğin biz NegativeError isimli exception sınıfını ValueError sınıfından
2024-07-15 13:23:34 +03:00
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.
2025-01-23 02:00:34 +03:00
Aşağıdaki örnekte klavye okuması sırasında oluşan hatalar doğrudan Exception sınıfı ile yakalanmıştır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-01-23 02:00:34 +03:00
taban sınıfa ilişkin except bloğu türemiş 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 ilişkin 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:
2024-07-15 13:23:34 +03:00
try:
do_something()
except ValueError as e:
pass
except Exception as e:
pass
2025-01-23 02:00:34 +03:00
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
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
try:
do_something()
except Exception as e:
pass
except ValueError as e:
pass
2025-02-02 21:34:10 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
54. Ders 25/01/2025 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-02 21:34:10 +03:00
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ış try bloklerının except blokları tarafından da yakalanamazsa program çöker.
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
parametresiz except blokları bulundurulacaksa tüm except bloklarının sonunda bulundurulmak zorundadır. Aksi takdirde
"sentaks hatası" ile programın çalıştırılması da başlamaz. Örneğin:
2024-07-15 13:23:34 +03:00
try:
foo()
except ValueError:
pass
except TypeError:
pass
except:
pass
2025-02-02 21:34:10 +03:00
Burada ValueError ve TypeError ayrı except bloklarıyla yakalanacaktı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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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')
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Pekiyi BaseException parametreli except bloğu parametresiz except bloğu gibi de zaten işlev görmüyor mu? Yani tüm
Exception'lar BaseException sınıfından türetildiğine göre BaseException parametreli (ya da Exception parametreli)
except bloğu ile parametresiz except bloğu arasında ne fark vardır. Yni örneğin:
2024-07-15 13:23:34 +03:00
try:
pass
2025-02-02 21:34:10 +03:00
except BaseException:
2024-07-15 13:23:34 +03:00
pass
2025-02-02 21:34:10 +03:00
ile aşağıdaki arasında kod arasında işlevsel bir farklılık var mıdır?
2024-07-15 13:23:34 +03:00
try:
pass
except:
pass
2025-02-02 21:34:10 +03:00
İşte aslında parametresiz except bloğu toplamda BaseException 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 BaseException ya da Exception parametreli except blokları tarafından
yakalanamazlar.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2025-02-02 21:34:10 +03:00
Aksi taktirde sentaks hatası oluşur. Örneğin:
2024-07-15 13:23:34 +03:00
try:
foo()
except ValueError:
pass
except TypeError:
pass
except:
pass
finally:
pass
2025-02-02 21:34:10 +03:00
Eception konusuna girişte try bloklarının tek başlarına bulunamadığını belirtmiştik. Geçerli blok dizilimleri şöyleydi:
2024-07-15 13:23:34 +03:00
- 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:
2025-02-02 21:34:10 +03:00
print('finally')
print('program ends...')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2025-02-02 21:34:10 +03:00
Ö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
2024-07-15 13:23:34 +03:00
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')
2025-02-02 21:34:10 +03:00
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ı:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
finally bloğu yine çalıştırılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
def foo():
...
<kaynak tahsisatı yapılıyor>
...
... <tahsis edilen kaynak kullanılıyor>
...
<kaynak geri bırakılıyor>
...
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:
...
<kaynak tahsisatı yapılıyor>
...
... <tahsis edilen kaynak kullanılıyor>
...
finally:
<kaynak geri bırakılıyor>
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
raise deyiminde raise anahtar sözcüğünün yanına ifade getirilmeyebilir. Örneğin:
except:
.....
raise
Bu biçimdeki raise işlemine "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.
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Python'un standart sys modülünde exc_info isimli exception mekainizması ile ilgili bir fonksiyon vardır. Bu fonksiyon
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
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 None olmaktadır. Örneğin:
def foo(a):
print('foo begins')
if a < 0:
raise ValueError('parameter must not be zero')
print('foo ends')
try:
foo(-2)
except:
exc_type, exc_obj, exc_trace = sys.exc_info()
print(exc_type, exc_obj, exc_trace)
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
Burada exception parametresiz except bloğu tarafından yakalanmıştır. Ancak exception yakalandıktan sonra exc_info
fonksiyonu ile exception bilgileri elde edilmiştir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import sys
2025-02-02 21:34:10 +03:00
def foo(a):
print('foo begins')
if a < 0:
raise ValueError('parameter must not be zero')
print('foo ends')
try:
foo(-2)
2024-07-15 13:23:34 +03:00
except:
exc_type, exc_obj, exc_trace = sys.exc_info()
2025-02-02 21:34:10 +03:00
print(exc_type, exc_obj, exc_trace)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
sys.exc_info fonksiyonundan elde edilen trace bilgisi bir bağlı liste biçimindedir. Bu bağlı liste bize exception'ın
2025-02-02 21:34:10 +03:00
oluştuğu noktaya kadarki fonksiyon akışlarını vermektedir. trace nesnelerinin tb_next öznitelikleri bir sonraki trace
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
Trace bilgilerinin dolaşımı tipik olarak aşağıdaki gibi bir döngüyle yapılabilir:
_, _, trace = sys.exc_info()
while trace:
print(trace.tb_lineno)
trace = trace.tb_next
Burada tg_next özniteliği ile sonraki trace nesnelerine geçiş yapılmıştır. Bağlı listenin son elemanına gelindiğinde
artık bu elemanı tb_next özniteliği None durumda olur. Yukarıdaki döngüde None görülene kadar ilerlenmiştir:
trace ---> trace ---> trace --> None
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Bu bölümde Python'da dosya işlemleri üzerinde duracağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
İç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.
2025-02-02 21:34:10 +03:00
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. Bu sistemlerde
Toplamda 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Bir dosyanın dizinler içerisindeki yerini belirten yazısal ifadelere "yol ifadesi (path)" denilmektedir. Yol ifadelerindeki
dizin geçişlerinde Microsoft sistemleri "\" karakterini, UNIX/Linux ve macOS sistemleri ise "/" karakterini kullanmaktadır.
Microsoft sistemleri programlama söz konusu olduğunda UNIX/Linux uyumunu korumak için dizin geçişlerinde "/" karakterini
de kabul etmektedir.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
"/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
2025-02-02 21:34:10 +03:00
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
2024-07-15 13:23:34 +03:00
ç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.
2025-02-02 21:34:10 +03:00
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 proseslerin çalışma dizilerini göreli yol ifadeleri biçiminde
tutmaktadır. 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 belirtirsek buradaki kök F sürücüsünün köküdür.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
Windows sistemlerinde sürücü içeren göreli yol ifadeleri de söz konusu olabilmektedir. Örneğin "C:a\b\test.txt" gibi.
2024-07-15 13:23:34 +03:00
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. "."
2025-02-02 21:34:10 +03:00
ifadesi "o andaki dizin" anlamına, ".." ifadesi ise o andaki dizinin üst dizini anlamına gelir. Örneğin:
2024-07-15 13:23:34 +03:00
"/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"
2025-02-02 21:34:10 +03:00
Ayrıca Windows, UNIX/Linux ve macOS sistemlerinde dizin geçişlerinde birden fazla \ ya da / karakteri kullanılabilmektedir.
Örneğin "/home//////kaan////Study/////test.txt" gibi bir yol ifadesi geçerlidir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Prosesin çalışma dizini Python Standart Kütüphanesinde nulunan "os" modülündeki getcwd fonksiyonu ile bir string
biçiminde elde edilebilir. Örneğin:
2024-07-15 13:23:34 +03:00
import os
cwd = os.getcwd()
print(cwd)
2025-02-02 21:34:10 +03:00
Pekiyi bir 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
2024-07-15 13:23:34 +03:00
dizin olur.
#------------------------------------------------------------------------------------------------------------------------
import os
cwd = os.getcwd()
print(cwd)
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
55. Ders 26/01/2025 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir program (yani proses) çalışırken onun çalışma dizini değiştirilebilmektedir. Prosesin çalışma dizinini değiştirmek
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
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çım sırasında programcı o dosya üzerinde hangi işlemleri yapacağını da belirtmektedir. Buna
dosyanın "açış modu" denilmektedir.
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
birinci parametresi açılacak dosyasının yol ifadesini, ikinci parametresi ise 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.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
Dosyanın açış modu bir yazı biçiminde belirtilmektedir. Açış modu aşağıdakilerden biri olabilir:
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
'r': Bu modda ancak var olan dosyalar açılabilir. Açılan dosyadan yalnızca okuma yapılabilir. Yani bu mod "olan dosyayı
yalnızca okuma amacıyla aç" anlamına gelmektedir. Dosya yoksa FileNotFoundError exception'ı oluşur. Açış modu hiç girilmezse
default durum 'r' kabul edilmektedir.
2024-07-15 13:23:34 +03:00
'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.
2025-02-02 21:34:10 +03:00
- "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ı ise 'w' modunda açmalıyız.
2024-07-15 13:23:34 +03:00
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ıı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
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
Okuma ve yazma işlemleri o offset'ten itibaren yapılır. Dosya açıldığında dosya göstericisi 0'ıncı offset'tedir. Yani
dosyanı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:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
Şimdi dosya göstericisi aşağıdaki durumda olsun:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
Şimdi dosya göstericisinin 6'ıncı offset'i gösterdeiğini düşünelim:
2024-07-15 13:23:34 +03:00
0 1 2 3 4 5 6
x x x x x x x
^
DG
2025-02-02 21:34:10 +03:00
Buradan 1 byte okumak isteyelim. Artık dosya göstericisi dosyanın son byte'ından sonraki byte'ı (yani aslında dosyada
olmayan byte'ı) gösteriyor durumda olur:
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
EOF durumundan okuma yapamayız. Ancak EOF durumunda açış modu da uygunsa dosyaya yazma yapabiliriz. Bu durumda yazılanlar
dosyaya eklenecektir. Örneğin yukarıdaki durumda 3 byte dosyaya yazmak isteyelim. Şöyle bir durum oluşacaktır:
2024-07-15 13:23:34 +03:00
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)
2025-02-02 21:34:10 +03:00
Bir dosyanın istenilen bir yerinden okuma yapmak için ya da istenilen bir yerine yazma yapmak için önce dosya göstericisinin
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
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" dosyası
onu bilmeyenlere tuhaf gelebilir. Ancak "html" dosyaları aslında yazı tutmaktadır ve dolayısıyla bu dosyalar text dosyalardır.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
f = open('text.txt', 'r') # text modda açılıyor
Fakat örneğin:
f = open('test.jpg', 'rb') # binary modda açılıyor
2025-02-02 21:34:10 +03:00
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
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
modda açılmışsa read metodu n byte okur ve bize onu bytes nesnesi olarak verir. Yukarıda da belirttiğimiz gibi read metodu
2024-07-15 13:23:34 +03:00
ile dosya göstericisinin gösterdiği yerden dosya sonuna kadar olan karakter ya da byte sayısından daha fazla okuma yapmak
2025-02-02 21:34:10 +03:00
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
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
fazla yer kaplayabilmektedir.) Örneğin "test.txt" dosyasının UTF-8 encoding'ine sahip olduğunu varsayalım ve içerisinde
2024-07-15 13:23:34 +03:00
"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()
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Tabii yukarıdaki kod walrus operatörüyle aşağıdaki gibi daha sade yazılabilirdi:
whie s = f.read(1024):
print(s, end='')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
SIZE = 1024
f = open(r'F:\Dropbox\Kurslar\Python\Doc\Python-Examples.txt', 'r')
while s := f.read(SIZE):
print(s, end='')
f.close()
#------------------------------------------------------------------------------------------------------------------------
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
ı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
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
f = open('test.txt', 'w')
2025-02-02 21:34:10 +03:00
f.write('this is a test')
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
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. Dosya nesneleri bir kez dolaşıldığında dosya göstericisi sona geldiği için biz bunu ikinci dolaşamayız.
2024-07-15 13:23:34 +03:00
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')
2025-02-02 21:34:10 +03:00
while s := f.readline():
2024-07-15 13:23:34 +03:00
print(s, end='')
f.close()
2025-02-02 21:34:10 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Bazen dosyanın herhangi bir yerinden okuma yapmak ya da herhangi bir yerine yazmak yapmak istenebilir. Bunun için
önce dosya göstericisinin istenilen yere konumlandırılması konumlnadırılması gerekir. Konumlandırma işlemi için seek
metodu kullanılmaktadır. seek metodunun iki parametresi vardır:
2024-07-15 13:23:34 +03:00
seek(offset, origin)
2025-02-02 21:34:10 +03:00
seek metodunun birinci parametresi offset belirtir. İkinci parametresi ise konumlandırmanın nereden itibaren yapılacağını
2024-07-15 13:23:34 +03:00
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()
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Dosyanın sonuna bir şeyler eklenmek isteniyorsa önce dosya göstericisi f.seek(0, 2) çağrısı ile EOF pozisyonuna
konumlandırılmalıdır. Anımsanacağı gibi EOF durumunda dosyaya yazma yapıldığında bu durum dosyaya ekleme anlamına
gelmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
önce zaten işletim sistemi dosya göstericisini atomik bir biçimde 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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()
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
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.
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
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 ve ISO 8859-9. ISO geç kalmış olsa da bu code page'leri zamanla standart hale
getirmiştir. ISO'nun ASCII code page'lerini tanımladığı standartlara 8859 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ı dillerin
2024-07-15 13:23:34 +03:00
karakter sayıları 256'dan fazladır. Örneğin Japonca ve Çince gibi uzak doğu dillerinde 4000'e yakın "kanji" denilen karakter
2025-02-02 21:34:10 +03:00
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 ile ifade edilmesi anlamlıydı. Ancak zamanla bellek miktarları
üstel bir biçimde artınca artık karakterlerin bir byte alanda tutulması konusu da sorgulanmaya başlandı. İşte bunun sonucu
olarak ismine UNICODE tablo denilen yeni bir karakter tablosu oluşturulmuştur. UNICODE ö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
2024-07-15 13:23:34 +03:00
bu tabloya yerleştirilmiştir.
2025-02-02 21:34:10 +03:00
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 ASCII Latin-1 code page'inin (ISO 8859-1) karakterleridir.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
"ağrı dağı"
bu yazı 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 2 = 13 byte yer kaplar.
2025-02-02 21:34:10 +03:00
Programcı üzerinde çalıştığı text dosyasının encoding'i bilmek zorundadır. Bir yazının farklı bir encoding ile okunması
tamamen yanlış karakterlerin elde edilmesine yol açabilmektedir. Ö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 dosların başında encoding için bir BOM (Byte of Order Marker) 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'lar UNICODE bir text dosyaların başında bulunmak da zorunda değildir.
(Yani UNICODE text bir dosyanın başında BOM marker bulunmak zorunda değildir.) 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-02 21:34:10 +03:00
fonksiyonuyla ya da Python 3.11 ile eklenen getencoding fonksiyonuyla elde edilebilmektedir. Örneğin kursun yapıldığı
Windows makinede default encoding "cp1254" biçimindedir. Linux sistemlerinde genellikle default encoding "utf-8"
olmaktadır.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
https://docs.python.org/3.11/library/codecs.html#standard-encodings
2025-02-02 21:34:10 +03:00
Binary modda açarken encoding parametresi kullanılmaz. Zaten binary dosyalarda yazı olmadığına göre encoding parametresi
de anlamsızdır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte her defasında klavyeden bir satırlık yazı okunup dosyaya yazdırılmıştır. Dosya "utf-8" encoding'i
kullanılarak açılmıştır.
#------------------------------------------------------------------------------------------------------------------------
f = None
try:
f = open('test.txt', 'w', encoding='utf-8')
while True:
s = input('Bir satır giriniz:')
if s == 'quit':
break
f.write(s + '\n')
finally:
if f: f.close()
#------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte bir dosya parça parça okunup başka bir dosyaya yazılarak kopyalama yapılmıştır. Tabii aslında read
metodu argümansız girildiğinde tüm dosyayı okuyabilmektedir. Ancak dosyaların parça parça okunması bellek kullanımı
ısından daha uygundur.
#------------------------------------------------------------------------------------------------------------------------
SIZE = 8192
spath = input('Kopyalanacak dosyanın yol ifadesini giriniz:')
dpath = input('Kopyanın yol ifadesini giriniz:')
fs = fd = None
try:
fs = open(spath, 'rb')
fd = open(dpath, 'wb')
while (buf := fs.read(SIZE)) != b'':
fd.write(buf)
finally:
if fs:
fs.close()
if fd:
fd.close()
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
56. Ders 01/02/2025 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Bu bölümde Python programlarında sıkça karşılaşılan with deyimini ele alacağız. 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ık. Örneğin:
2024-07-15 13:23:34 +03:00
f = None
try:
f = open(...)
...
2025-02-02 21:34:10 +03:00
... <burada bir exception oluşup akış başka bir try bloğunun except bloğuna aktarılmış olsun>
2024-07-15 13:23:34 +03:00
...
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 <ifade> [ as <değişken_ismi>]:
<suite>
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)
2025-02-02 21:34:10 +03:00
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ı
2024-07-15 13:23:34 +03:00
olmak zorunda değildir. Yani with deyimi şöyle de kullanılabilir:
with <ifade>:
<suite>
Örneğin:
f = open('test.txt')
with f:
s = f.read()
print(s)
2025-02-02 21:34:10 +03:00
with benzeri deyimler diğer nesne 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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
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ı exc_type, exc_value,
traceback biçiminde isimlendirirler. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def __enter__(self):
pass
def __exit__(self, xc_type, exc_value, traceback):
pass
with Sample() as s:
pass
2025-02-02 21:34:10 +03:00
with deyimi kabaca şöyle çalışmaktadır: Önce with anahtar sözcüğünün yanındaki ifade yapılır ve buradan bir sınıf nesnesi
elde edilir. 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.
2024-07-15 13:23:34 +03:00
Ö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
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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)
2025-02-02 21:34:10 +03:00
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ışlardı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.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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ı
2025-02-02 21:34:10 +03:00
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.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
Aslında with deyiminin çalışması biraz ayrıntılara sahiptir. Python Language Reference dokümanına göre:
2024-07-15 13:23:34 +03:00
with EXPRESSION as TARGET:
SUITE
deyiminin eşdeğeri şöyledir:
manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
2025-02-02 21:34:10 +03:00
value = enter(manager) # manager.__enter__()
2024-07-15 13:23:34 +03:00
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
2025-02-02 21:34:10 +03:00
if not exit(manager, *sys.exc_info()): # if not manager.__exit__(*sys.exc_info()):
2024-07-15 13:23:34 +03:00
raise
finally:
if not hit_except:
2025-02-02 21:34:10 +03:00
exit(manager, None, None, None) # manager.__exit__(None, None, None)
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
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 yakalanmış 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. None değeri de if deyiminde
False olarak ele alınmaktadır. Genellikle __exit__ fonksiyonu False ile (ya da None ile) geri döndürülmektedir.
2024-07-15 13:23:34 +03:00
with deyimi bazı potansiyel olanakları programcı kullanabilsin diye bu biçimde biraz karışık tasarlanmıştır. Ancak programcılar
2025-02-02 21:34:10 +03:00
ç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.
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
with anahtar sözcüğünün yanındaki ifadede exception oluşursa __enter__ ve __exit__ metodunun çağrılmayacağına dikkat ediniz.
Bu ifade semantik olarak with deyimi ile bağlantılı değildir. Örneğin:
2024-07-15 13:23:34 +03:00
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:
<suit>
2025-02-02 21:34:10 +03:00
Bu biçimde bir with deyimi geçerlidir. Birden fazla ifadenin bulunduğu with deyimleri "iç içe" gibi ele alınmaktadır.
2024-07-15 13:23:34 +03:00
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:
<suite>
2025-02-02 21:34:10 +03:00
Dolayısıyla örneğin:
2024-07-15 13:23:34 +03:00
with ifade1 as değişken1, ifade2 as değşken2, ifade3 as değişken3:
<suit>
Bu biçimdeki with deyiminde ifadelere ilişkin sınıfların __enter__ metotları soldan sağa çağrılacaktır. __exit__ metotları
2025-02-02 21:34:10 +03:00
da ters sırada sağdan sola çağrılacaktır. Ayrıca burada sağ taraftaki ifadelerde exception oluşrsa artık sol taraftaki
nesneler için __exit__ metodunun çağrılacağına dikkat ediniz. Örneğin:
try:
with open(spath, 'rb') as fs, open(dpath, 'wb') as fd:
while b := fs.read(SIZE):
fd.write(b)
except Exception as e:
print(e)
Burada her iki dosya da sırasıyla açılmış ve ters sırada kapatılmıştır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
SIZE = 8192
spath = input('Kopyalanacak dosyanın yol ifadesini giriniz:')
dpath = input('Kopyanın yol ifadesini giriniz:')
try:
with open(spath, 'rb') as fs, open(dpath, 'wb') as fd:
while b := fs.read(SIZE):
fd.write(b)
except Exception as e:
print(e)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Öncelikle "dolaşılabilir (iterable)" nesne ile "dolaşım (iterator)" nesnesi kavramları arasındaki farkı yeniden anımsatmak
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
döngüsü ile dolaşılabilmektedir. Python Language Reference iteratör nesnelerinin aynı zamanda dolaşılabilir (iterable),
nesne gibi de kullanılabileceğini belirtmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-02 21:34:10 +03:00
bu dolaşım nesnesiyle yapılmaktadır. Bu durumda iki sınıf söz konusudur:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
yoktur. Dolaşılabilir sınıf ile dolaşım sınıfları farklı olabilmektedir. Ancak çoğu kez bunlar aynı sınıf olurlar. 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:
2024-07-15 13:23:34 +03:00
class SampleIterator:
def __next__(self):
pass
class SampleIterable:
def __iter__(self):
si = SampleIterator()
return si
2025-02-02 21:34:10 +03:00
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.
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
Yukarıda da belirttiğimiz gibi 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. Yani dolaşım nesnelerinin aynı zamanda dolaşılabilir
bir nesne gibi de kullanılabilmesi 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:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Dolaşılabilir nesneler en genel olarak for döngüsüyle dolaşılmaktadır. Aşağıdaki gibi bir for döngüsü olsun:
2024-07-15 13:23:34 +03:00
for x in iterable:
<suite>
Bu döngünün tam eşdeğeri aslında şöyledir:
iterator = iterable.__iter__()
try:
while True:
x = iterator.__next__()
<suite>
except StopIteration:
pass
2025-02-02 21:34:10 +03:00
Buradaki eşdeğer kodun sözel açıklaması şöyle yapılabilir. Python yorumlayıcısı tarafından önce dolaşılabilir nesne ile
__iter__ metodu çağrılır ve ondan dolaşım nesnesi elde ederedilir. Sonra sürekli olarak bu dolaşım nesnesi ile dolaşım
sınıfının __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.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
Ş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
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Aşağıdaki örnekte bir sınıf __init__ metodu ile aldığı argümanları bir demet biçiminde nesnenin örnek özniteliğinde
saklamaktadır. Daha sonra her __next__ metodunda sırasuyla bunları vermektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-02 21:34:10 +03:00
return self.args[self.i - 1]
2024-07-15 13:23:34 +03:00
args = Args('ali', 'veli', 123, True)
for x in args:
print(x)
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
list gibi, tuple gibi sınıfların tür fonksiyonları (__init__ metotları) dolaşılabilir nesneyi parametre olarak alıp
bu nesneyi dolaşarak ilgili türden nesne oluşturmaktadır. Bu durumda yularıdaki Args sınıfı türünden nesneyi örneğin
biz list fonksiyonuna verirsek bu nesne dolaşılıp elde edilen değerlerdne list nesnesi oluşturulacaktır. Örneğin:
args = Args(10, 20, 30, 40, 50)
a = list(args)
print(a)
#------------------------------------------------------------------------------------------------------------------------
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 - 1]
args = Args('ali', 'veli', 123, True)
a = list(args)
print(a)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
İ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):
2025-02-02 21:34:10 +03:00
self.count = 0
2024-07-15 13:23:34 +03:00
return self
2025-02-02 21:34:10 +03:00
2024-07-15 13:23:34 +03:00
def __next__(self):
2025-02-02 21:34:10 +03:00
if self.count == self.n:
raise StopIteration
self.count += 1
2024-07-15 13:23:34 +03:00
return random.randint(self.beg, self.end)
2025-02-02 21:34:10 +03:00
ri = RandomIterable(0, 100, 10)
for x in ri:
print(x, end=' ')
print()
a = list(ri)
2024-07-15 13:23:34 +03:00
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 SqrtIterator:
2025-02-02 21:34:10 +03:00
def __init__(self, si):
2024-07-15 13:23:34 +03:00
self.i = 0
2025-02-02 21:34:10 +03:00
self.si = si
2024-07-15 13:23:34 +03:00
def __next__(self):
2025-02-02 21:34:10 +03:00
if self.i > self.si.n:
raise StopIteration
2024-07-15 13:23:34 +03:00
self.i += 1
return math.sqrt(self.i - 1)
def __iter__(self):
return self
2025-02-02 21:34:10 +03:00
class SqrtIterable:
def __init__(self, n):
self.n = n
def __iter__(self):
return SqrtIterator(self)
for x in SqrtIterable(100):
2024-07-15 13:23:34 +03:00
print(x)
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Aşağıdaki örnekte orijinal built-in range sınıfının myrange isminde bir benzeri yazılmıştır. Orijinal range nesnesi
len fonksiyonuna sokulabilmekte ve [] operatörü ile belli indeksteki değeri verebilmektedir. Biz de aşağıdaki örnekte
bu özellikleri korumak istedik.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import math
class myrange:
2025-02-02 21:34:10 +03:00
def __init__(self, start, stop=None, step=1):
if stop is not None:
2024-07-15 13:23:34 +03:00
self.start = start
self.stop = stop
2025-02-02 21:34:10 +03:00
else:
self.start = 0
self.stop = start
2024-07-15 13:23:34 +03:00
self.step = step
2025-02-02 21:34:10 +03:00
2024-07-15 13:23:34 +03:00
def __iter__(self):
self.i = self.start
return self
2025-02-02 21:34:10 +03:00
def __next__(self):
if self.step > 0:
if self.i >= self.stop:
raise StopIteration
else:
if self.i <= self.stop:
raise StopIteration
2024-07-15 13:23:34 +03:00
self.i += self.step
return self.i - self.step
2025-02-02 21:34:10 +03:00
def __len__(self):
math.ceil((self.stop - self.start) // 2)
2024-07-15 13:23:34 +03:00
def __getitem__(self, index):
2025-02-02 21:34:10 +03:00
if not isinstance(index, int|slice):
raise TypeError('range indices must be integers or slices, not float')
2024-07-15 13:23:34 +03:00
if isinstance(index, slice):
2025-02-02 21:34:10 +03:00
return myrange(index.start * self.step + self.start, index.stop * self.step + self.start, self.step if index.step is None else index.step * self.step)
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
val = self.start + index * self.step
if val >= self.stop:
raise IndexError('range object index out of range')
return val
2024-07-15 13:23:34 +03:00
def __repr__(self):
2025-02-02 21:34:10 +03:00
if self.step == 1:
return f'myrange({self.start}, {self.stop})'
2024-07-15 13:23:34 +03:00
return f'myrange({self.start}, {self.stop}, {self.step})'
2025-02-02 21:34:10 +03:00
mr = myrange(10, 20, 2)
2024-07-15 13:23:34 +03:00
for x in mr:
2025-02-02 21:34:10 +03:00
print(x, end=' ')
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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:
2025-02-02 21:34:10 +03:00
def __init__(self, iterable, start=0):
2024-07-15 13:23:34 +03:00
self.iterator = iterable.__iter__()
2025-02-02 21:34:10 +03:00
self.start = start
self.index = self.start
2024-07-15 13:23:34 +03:00
def __iter__(self):
return self
def __next__(self):
2025-02-02 21:34:10 +03:00
self.index += 1
return self.index - 1, self.iterator.__next__()
a = [10, 20, 30, 40, 50]
for i, x in myenumerate(a):
print(i, x)
2024-07-15 13:23:34 +03:00
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)
2025-02-02 21:34:10 +03:00
#------------------------------------------------------------------------------------------------------------------------
57. Ders 02/02/2025 - Pazar
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örnekte olduğu gibi bazen manuel bir biçimde __next__ metodunun çağrılması gerekebilir. Örneğin elimzde
2025-02-02 21:34:10 +03:00
dolaşılabilir bir nesne olsun. Biz bu nesneyi for döngüsü ile dolaşmak isteyelim. Ancak ilk elemanı da pas geçmek
isteyelim. Ya da elimizde yine bir dolaşılabilir nesne olsun. Biz bu nesnenin elemanlarını bir list nesnesine ilk
elemanı pas geçerek yerleştirmek isteyelim. Bu tür durumlarda iteratörün __next__ çağrılarak manuel bir biçimde
ilerletilmesi gerekir. Dolaşım nesnelerinin de dolaşılabilir nesneler gibi de davrandığını anımsayınız. Örneğin:
a = [10, 20, 30, 40, 50, 60]
iterator = a.__iter__()
iterator.__next__() # ilk elemanı pas geçmek için
Biz burada a listesine ilişkin iteratör'ü bir ilerlettik. Artık bu iteratörü for döngüsüne sokarsak ilk elemandan
sonraki elemanları elde ederiz.
for x in iterator:
print(x)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Ö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 manuel __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.)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import random
class RandomIterable:
2025-02-02 21:34:10 +03:00
def __init__(self, low, high, n):
self.low = low
self.high = high
2024-07-15 13:23:34 +03:00
self.n = n
def __iter__(self):
self.i = 0
return self
def __next__(self):
if self.i == self.n:
2025-02-02 21:34:10 +03:00
raise StopIteration
2024-07-15 13:23:34 +03:00
self.i += 1
2025-02-02 21:34:10 +03:00
return random.randint(self.low, self.high)
2024-07-15 13:23:34 +03:00
def mymax(iterable):
iterator = iterable.__iter__()
2025-02-02 21:34:10 +03:00
try:
maxval = iterator.__next__()
for x in iterator:
if x > maxval:
maxval = x
except StopIteration:
pass
2024-07-15 13:23:34 +03:00
return maxval
2025-02-02 21:34:10 +03:00
ri = RandomIterable(0, 100, 5)
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
maxval = mymax(ri)
print(maxval)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python standart kütüphanesinde iter ve next isimli iki built-in fonksiyon bulunmaktadır. Bu fonksiyonlar aslında parametresiyle
2025-02-02 21:34:10 +03:00
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 de next(iterator) çağrısı eşdeğerdir.
Yani bu fonksiyonların aşağıdaki gibi yazılmış olduğunu varsayabilirsiniz:
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
def iter(iterable):
return iterable.__iter__()
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
def next(iterator):
return iterator.__next__()
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
veren yeni bir dolaşım nesnesi verir. Yani kullanımı şöyledir:
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
dolaşılabilmesi için o sınıfı yazanların bunu sağlamış olmaları 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 iterable.__reversed__ çağrısı bazı ayrıntılar dışında eşdeğerdir. reversed
built-in fonksiyonunun şöyle yazılmış olduğunu varsayabilirsiniz:
2024-07-15 13:23:34 +03:00
def reversed(iterable):
return iterable.__reversed__()
2025-02-02 21:34:10 +03:00
Bu durumda bizim bir sınıf nesnesini reversed fonksiyonuyla kullanabilmemiz için sınıfın __reversed__ isimli metodunun
2024-07-15 13:23:34 +03:00
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.
2025-02-02 21:34:10 +03:00
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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import math
2025-02-02 21:34:10 +03:00
class ReverseSqrtIterator:
def __init__(self, n):
self.i = n
def __iter__(self):
return self
def __next__(self):
if self.i < 0:
raise StopIteration
self.i -= 1
return math.sqrt(self.i + 1)
2024-07-15 13:23:34 +03:00
class SqrtIterable:
def __init__(self, n):
self.n = n
def __iter__(self):
self.i = 0
return self
def __next__(self):
2025-02-02 21:34:10 +03:00
if self.i > self.n:
raise StopIteration
2024-07-15 13:23:34 +03:00
self.i += 1
return math.sqrt(self.i - 1)
def __reversed__(self):
return ReverseSqrtIterator(self.n)
2025-02-02 21:34:10 +03:00
for x in SqrtIterable(10):
2024-07-15 13:23:34 +03:00
print(x)
2025-02-02 21:34:10 +03:00
print()
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
for x in reversed(SqrtIterable(10)):
2024-07-15 13:23:34 +03:00
print(x)
2025-02-02 21:34:10 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Python'un temel veri yapılarını gerçekleştiren sınıflarından hangileri tersten dolaşılabilmektedir?
2024-07-15 13:23:34 +03:00
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
2025-02-02 21:34:10 +03:00
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
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
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):
2025-02-02 21:34:10 +03:00
return self.f(next(self.iterator))
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
a = [[10, 20, 30], [40, 50], ['ali', 'veli', 'selami', 'ayşe']]
for x in mymap(len, a):
print(x, end=' ')
2024-07-15 13:23:34 +03:00
a = [3, 6, 2, 8, 9]
def square(val):
return val * val
for x in mymap(square, a):
print(x, end=' ')
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
Python Standart Kütüphanesinde daha önce de bahsettiğimiz gibi filter isimli built-in bir fonksiyon bulunmaktadı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:
2024-07-15 13:23:34 +03:00
a = [34, 12, 15, 9, 26, 41]
2025-02-02 21:34:10 +03:00
def iseven(val):
2024-07-15 13:23:34 +03:00
return val % 2 == 0
2025-02-02 21:34:10 +03:00
for x in filter(iseven, a):
2024-07-15 13:23:34 +03:00
print(x, end=' ')
2025-02-02 21:34:10 +03:00
Burada a listesi içerisindeki çift olan elemanlar elde edilmektedir. Örneğin:
def isgreater20(x):
return x > 20
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
for x in filter(isgreater20, a):
print(x)
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
Burada a listesindeki 20'den büyük elemanlar elde edilmektedir. Örneğin:
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'ece', 'sinem']
def contains_a(name):
return 'a' in name
for x in filter(contains_a, names):
print(x)
Burada biz içerisinde 'a' karakteri geçen isimleri elde etmiş olduk.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
a = [12, 11, 24, 23, 18]
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
def iseven(x):
return x % 2 == 0
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
for x in filter(iseven, a):
print(x)
print('-' * 20)
def isgreater20(x):
return x > 20
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
for x in filter(isgreater20, a):
print(x)
print('-' * 20)
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
names = ['ali', 'veli', 'selami', 'ayşe', 'fatma', 'ece', 'sinem']
def contains_a(name):
return 'a' in name
for x in filter(contains_a, names):
print(x)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
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 self.f(val):
return val
Burada __next__ metodu her çağrıldığında iterable nesnedeki elemanlar verilen fonksiyona sokulmuş bu fonksiyon True
değerine geri dönmüşse __next__ metodu da bu elde ettiği değerle geri döndürülmüştür. Burada dolaşımın sonlanması
next(self.iterator) çağrısının oluşturduğu StopIteration yoluyla yapılmaktadır. Bizim ayrıca StopIteration uygulamamıza
gerek yoktur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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=' ')
#------------------------------------------------------------------------------------------------------------------------
2025-02-02 21:34:10 +03:00
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:
2024-07-15 13:23:34 +03:00
def get_sqrts(n):
2025-02-02 21:34:10 +03:00
return [math.sqrt(i) for i in range(n + 1)]
2024-07-15 13:23:34 +03:00
a = get_sqrts(100000)
for x in a:
print(x, end=' ')
2025-02-02 21:34:10 +03:00
İşte sonucu bir liste biçiminde veren fonksiyonlar sonucu baştan oluşturup bir listeye yerleştirmek zooundadı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ğerlerin bir listeyle geri döndürülmesi
aynı zamanda bu listenin baştan oluşturulması sırasında uzun beklemelere de 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 yapmak isteyebilir. Bu tür durumlarda ilgili
dolaşılabilir nesneden liste de oluşturulabilir.
Örneğin 1'den belli bir sayıya kadar asal sayılar üzerinde işlem yapacak olalım. Bunu bir liste biçiminde oluşturursak
listenin oluşturulması uzun zaman alabilir. Üstelik de bu asal sayılar gereksiz bir biçimde listede yer kaplayacaktır.
Ancak bu işlemi dolaşılabilir bir sınıfa yaptırırsak asal sayılar o anda kalınan yerden itibaren dolaış sırasında bulunup
verilecektir. Bir listenin belli bir biçimde doldurulduğunu ve sonra da o listenin elemanları üzerinde işlemler yaptığımızı
düşünelim. Eğer bu işlemleri yaparken döngüden break ile çıkarsak geri kalan elemanlar boşuna doldurulmuş olacaktır. Halbuki
aynı işlem dolaşılabilir bir sınıfla yapılırsa elemanlar talep edildiğinde verilecek ve bunlar boşuna yer kaplamayacaktır.
Örneğin diskimizdeki tüm dosyalar üzerinde işlemler yapmak isteyelim. Yani kökten dizinden 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 verimli biçimde yürütülür. İş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. Biz bu
nesneyi dolaştıkça dizin ağacında sıradaki dosyayı elde ederiz:
2024-07-15 13:23:34 +03:00
2025-02-02 21:34:10 +03:00
import os
for root, dirs, files in os.walk('f:\\'):
print(root)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import os
for root, dirs, files in os.walk('f:\\'):
print(root)
2025-02-16 00:11:32 +03:00
#------------------------------------------------------------------------------------------------------------------------
58. Ders 08/02/2025 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Bu bölümde Python'da "üretici fonksiyonların (generators)" kullanımları üzerinde duracağız.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Üretici fonksiyonlara (generators) özellikle yorumlayıcılarla çalışılan dillerde karşılaşılmaktadır. Ancak derleyici
2025-02-16 00:11:32 +03:00
temelli bazı dillerede de özellikle son yıllarda üretici fonksiyonlar özelliği eklenmiştir. Örneğin Ruby, C#, Kotlin,
Go gibi dillerde üretici fonksiyonlar bulunmaktadır.
2024-07-15 13:23:34 +03:00
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]
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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)) # <class 'function'>
g = foo()
print(type(g)) # <class 'generator'>
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
Ü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 geçici olarak 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:
2024-07-15 13:23:34 +03:00
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}')
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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}')
2025-02-16 00:11:32 +03:00
Bu durumda akış durdurulan yield deyiminden devam eder ve ilk yield deyimi görüldüğünde yeniden durdurulur. Ekranda şunlar
görünecektir:
2024-07-15 13:23:34 +03:00
two
next return value: 2
Şimdi bir adım daha dolaşalım. Ekranda şunları göreceksiniz:
three
next return value: 3
2025-02-16 00:11:32 +03:00
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.
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
Tabii biz üretici nesnesyi aşağıdaki gibi for dngüsüyle de dolaşabilirdik:
2024-07-15 13:23:34 +03:00
g = foo()
for x in g:
print(f'x: {x}')
2025-02-16 00:11:32 +03:00
Bu dolaşımda akış her yield deyiminde durduğunda o yield deyiminin yanındaki ifadenin değeri x'e atanacaktır. StopIteraton
oluştuğunda da for döngüsünden çıkılacaktır. Ekranda şunları göreceksiniz:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
def myrange(start, stop=None, step=1):
if step == 0:
raise ValueError('myrange() arg 3 must not be zero')
if stop is None:
2024-07-15 13:23:34 +03:00
stop = start
start = 0
2025-02-16 00:11:32 +03:00
while start < stop if step > 0 else start > stop:
yield start
start += step
2024-07-15 13:23:34 +03:00
for x in myrange(10, 20):
print(x, end=' ')
2025-02-16 00:11:32 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2'den itibaren parametresiyle belirtilen sayıya kadarki asal sayıları veren üretici fonksiyon aşağıdaki gibi yazılabilir.
#------------------------------------------------------------------------------------------------------------------------
import math
2025-02-16 00:11:32 +03:00
def prime_generator(n):
2024-07-15 13:23:34 +03:00
def isprime(val):
if val % 2 == 0:
return val == 2
2025-02-16 00:11:32 +03:00
stop_val = int(math.sqrt(val))
for i in range(3, stop_val + 1, 2):
2024-07-15 13:23:34 +03:00
if val % i == 0:
return False
return True
if n < 2:
raise ValueError('argument must greater or equal 2')
2025-02-16 00:11:32 +03:00
for x in range(2, n + 1):
if isprime(x):
yield x
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
for x in prime_generator(100):
2024-07-15 13:23:34 +03:00
print(x, end=' ')
2025-02-16 00:11:32 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
Daha önce biz built-in enumerate fonksiyonunu dolaşılabilir bir sınıf biçiminde yazmıştık. Şimdi de bu fonksiyonu
2024-07-15 13:23:34 +03:00
üretici fonksiyon olarak yazalım:
def myenumerate(iterable, start = 0):
2025-02-16 00:11:32 +03:00
index = start
2024-07-15 13:23:34 +03:00
for val in iterable:
2025-02-16 00:11:32 +03:00
yield index, val
index += 1
2024-07-15 13:23:34 +03:00
Aşağıda daha önce yazmış olduğumuz sınıf gerçekleştirimi ile üretici fonksiyon gerçekleştirimini birlikte veriyoruz.
#------------------------------------------------------------------------------------------------------------------------
class myenumerate1:
2025-02-16 00:11:32 +03:00
def __init__(self, iterable, index):
2024-07-15 13:23:34 +03:00
self.iterator = iterable.__iter__()
2025-02-16 00:11:32 +03:00
self.index = index
2024-07-15 13:23:34 +03:00
def __iter__(self):
return self
def __next__(self):
2025-02-16 00:11:32 +03:00
self.index += 1
return self.index - 1, self.iterator.__next__()
def myenumerate2(iterable, start=0):
index = start
for x in iterable:
yield index, x
index += 1
2024-07-15 13:23:34 +03:00
a = [10, 20, 30, 40, 50]
2025-02-16 00:11:32 +03:00
for index, val in myenumerate1(a, 10):
print(index, '=>', val)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
print('-' * 20)
for index, val in myenumerate2(a, 10):
print(index, '=>', val)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-16 00:11:32 +03:00
İş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:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
send metodu üretici nesnenin ilişkin olduğu "generator" isimli sınıfın bir metodudur. send metodunun kullanımına dikkat
ediniz:
2024-07-15 13:23:34 +03:00
g.send(ifade)
metoda verilen argüman akışın yield deyiminden devam ettirilmesi sırasında yield deyiminin üreteceği değeri belirtmektedir.
2025-02-16 00:11:32 +03:00
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 ettirilirken 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.
2024-07-15 13:23:34 +03:00
Ü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)
2025-02-16 00:11:32 +03:00
Üretici fonksiyon nesnesinin ilişkin olduğu sınıfta (generator sınıfı) bir __send__ metodu yoktur. Bu metot send ismindedir.
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
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.
Üretici nesnelerle send yapma çok karşılaşılan bir işlem değildir. Genellikle üretici fonksiyonun davranışını dışarıdan
değiştirmek amacıyla kullanılmaktadır. Aşağıdaki üretici fonksiyona dikkat ediniz:
def do_something():
while True:
val = yield
match val:
case 0:
break
case 1:
pass
case 2:
pass
case 3:
pass
Burada yield deyiminin sağına bir ifade yazılmamıştır. Bu durumda yield deyiminden None elde edilir Ancak burada
amaçlanan şey zaten üretici fonksiyonun bir şey vermesi değil bizim istediğimiz işlemi yapmasıdır. Biz burada
send ile ona değişik işlemleri yaptırabiliriz. Örneğin:
r = do_something()
next(r)
r.send(1)
r.send(2)
r.send(3)
...
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-16 00:11:32 +03:00
bulunmaktadır fakat başka bir anlama gelmektedir. Demet içlemi sentaksına Python'da "üretici ifadeler (generator
expresssions)" denilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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)
2025-02-16 00:11:32 +03:00
Bir üretici ifade aslında üretici bir fonksiyonun basit bir yazım biçimidir. Üretici ifade bize aslında bir üretici
nesne vermektedir. Yani:
2024-07-15 13:23:34 +03:00
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)
<class 'generator'>
>>> 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()
#------------------------------------------------------------------------------------------------------------------------
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.
2025-02-16 00:11:32 +03:00
Örneğin biz 0'dan 1000000'a kadar sayıların karelerinin ortalamasını hesaplamak isteyelim. Bunun için önce bu sayıları
liste biçiminde oluşturup sonra onları Python Standart Kütüphanesindeki mean fonksiyonuna sokabiliriz. Ancak bu durumda
sayılar için büyük bir liste oluşturulur:
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
import statistics
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
result = statistics.pstdev([i * i for i in range(1000000)])
print(result)
Oysa aynı şeyi benzer sentaksla üretici ifade ile yaparsak sayılar talep edildiğinde verileceği için onların baştan
biriktirilmesine gerek kalmayacaktır:
import statistics
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
result = statistics.mean((i * i for i in range(1000000)))
2024-07-15 13:23:34 +03:00
print(result)
2025-02-16 00:11:32 +03:00
Biz burada üretici ifadeyi doğrudan fonksiyon çağırma işleminde argüman olarak kullandık. Halbuki aynı işlemi üretici
2024-07-15 13:23:34 +03:00
fonksiyonlarla yapmak isteseydik daha zor olacaktı:
def some_name():
for i in range(10):
yield i * i
result = statistics.mean(some_name())
print(result)
2025-02-16 00:11:32 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
import statistics
result = statistics.mean((i * i for i in range(1000000)))
print(result)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Ü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
2025-02-16 00:11:32 +03:00
da bir argüman bulundurulmuyorsa dıştaki parantezler hiç kullanılmayabilir. Örneğin:
2024-07-15 13:23:34 +03:00
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}')
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
Pekiyi üretici ifadeler bize ne fayda sağlamaktadır? İşte yukarıda da belirttiğimiz gibi üretici ifadeler aslında
dolaşılabilir nesneleri oluşturmanın en kolay yollarından biridir. Dolaşılabilir nesneler içlemlerle de kolay bir biçimde
oluşturulabilmektedir. Ancak içlemler liste, küme ya da sözlük nesnesi yaratmaktadır. Dolayısıyla bazı durumlarda bu nesnelerin
yer kaplaması istenmeyebilir. Değerlerin talep edildiğinde oluşturulması gereksiz bellek tahsisatını elimine edebilir. Bazı
Yukarıda verdiğimiz örneği yeniden inceleyiniz:
2024-07-15 13:23:34 +03:00
result = statistics.mean([i * i for i in range(1000000)])
2025-02-16 00:11:32 +03:00
Burada ortalama elde edildikten sonra oluşturulan bu liste nesnesi zaten yok edilmektedir. Biz burada boşuna 1000000
elemandan oluşan bir liste yaratmış olduk. Aynı işlemi üretici ifadelerle daha etkin biçimde gerçekleştirebilirdşk:
2024-07-15 13:23:34 +03:00
result = statistics.mean((i * i for i in range(1000000)))
2025-02-16 00:11:32 +03:00
Artık burada 1000000 eleman gereksiz bir biçimde yer kaplamayacaktır. Değerler talep edildiğinde verilecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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))
2025-02-16 00:11:32 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bu bölümde pek çok programlama dilinde bulunan bazılarına sonradan eklenmiş olan "lambda ifadeleri" üzerinde duracağız.
#------------------------------------------------------------------------------------------------------------------------s
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Bir fonksiyonun bir ifade içerisinde tanımlanarak o ifade içerisinde kullanılmasına programlama dillerinde "lambda
2025-02-16 00:11:32 +03:00
ifadeleri (lambda expressions)" denilmektedir. Aslında lambda ifadeleri eskiden yalnızca 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:
2024-07-15 13:23:34 +03:00
lambda [parametre_listesi]: <ifade>
Ö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
2025-02-16 00:11:32 +03:00
parametrelerini belirtir. 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:
2024-07-15 13:23:34 +03:00
>>> f = lambda a: a * a
>>> type(f)
<class 'function'>
>>> 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
2025-02-16 00:11:32 +03:00
Tabii lambda ifadeleri deyim değil ifade belirtmektedir. Dolayısıyla başka ifadelerin içerisinde kullanılabilir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
def foo(val):
for x in map(lambda a: a * val, [1, 2, 3, 4, 5]):
print(x, end=' ')
2025-02-16 00:11:32 +03:00
print()
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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.
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
tek bir ifadenin kullanılıyor olması lambda ifadelerinin gücünü düşürebilmektedir. Ancak python'un sentaks yapısı
dikkate alındığında dile ayrıntılı bir lambda ifadesi yerleştirmenin zorluğu da anlaşılmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
59. Ders 09/02/2025 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-16 00:11:32 +03:00
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 __getattr__ metodundan döndürülen değerdir. Eğer nesne ile zaten var olan bir elemana erişiliyorsa
bu durumda __getattr__ çağrılmamaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
def __init__(self):
self.val = 10
2025-02-16 00:11:32 +03:00
2024-07-15 13:23:34 +03:00
def __getattr__(self, name):
print(name)
return 0
s = Sample()
x = s.val
print(x) # 10
2025-02-16 00:11:32 +03:00
Burada s.val erişiminde val özniteliği zaten vardır. Bu durumda __getattr__ hiç çağrılmayacaktır.
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
gibi kullanılmasını sağlayan bir mekanizmadır. Yani bu kavram sayesinde programcı nesnenin bir özniteliğine eriştiğinde
2024-07-15 13:23:34 +03:00
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.
2025-02-16 00:11:32 +03:00
2024-07-15 13:23:34 +03:00
Ö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
2025-02-16 00:11:32 +03:00
özniteliği sınıfta varmış gibi bir durum oluşturabiliriz:
2024-07-15 13:23:34 +03:00
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)
2025-02-16 00:11:32 +03:00
Bu örnekte nesne yoluyla aslında olmayan area ö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 öznitelik kullanılırsa AttributeError exception'ının oluşturulduğuna dikkat ediniz.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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)
2025-02-16 00:11:32 +03:00
Halbuki biz area örnek özniteliğine erişildiğinde bir metodun çağrılmasını sağlarsak 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Sınıfta olmayan elemanların kullanılması teması property dışında başka yerlerde de karşımıza çıkabilmektedir.
2025-02-16 00:11:32 +03:00
Örneğin biz __getattr__ sayesinde sınıf nesnesini yaratırken verdiğimiz isimli argümanları sanki onlar birer örnek
2024-07-15 13:23:34 +03:00
ö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
2025-02-16 00:11:32 +03:00
__init__ metodunda yaratılmış gibi bir etki oluşturulmuştur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
class Sample:
def __init__(self, **kwargs):
self.kwargs = kwargs
def __getattr__(self, name):
2025-02-16 00:11:32 +03:00
if (val := self.kwargs.get(name)) != None:
return val
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
da yine ç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 erişimden __getattribute__ metodunun geri dönüş
değeri elde edilmektedir.
Spyder'da çalışırken IPython nedneniyle nesnenin bazı özniteliklerine bizim dışımızda erişim de yapılabilmektedir.
Bu ortamda bir özniteliğe erişildiğinde __getattribute__ metodunun birden fazla kez çağrılabileceğini belirtmek
istiyoruz. Bu durum kafanızı Spyder'a özgüdür. Bu nedenle kafanızı karıştırmasın.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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
#------------------------------------------------------------------------------------------------------------------------
Sınıfın __getattribute__ metodunda sınıfın bir elemanına erişmeye çalışırsak yeniden __getattribute__ çağrılacağı için
2025-02-16 00:11:32 +03:00
özyinelemeli bir durum oluşacaktır. Programcı genellikle böyle br durumun oluşmasını istemez. Örneğin:
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
import math
class Circle:
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
2024-07-15 13:23:34 +03:00
def __getattribute__(self, name):
2025-02-16 00:11:32 +03:00
if name == 'area':
return math.pi * self.radius ** 2
raise AttributeError(f"Circle object has no attribute '{name}'")
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
c = Circle(10, 12, 5)
print(c.area) # AttributeError oluşacak!
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
Burada c.area erişiminde sınıfın __getattribute__ metodu çağrılacaktır. Bu metotun içinde de aynı nesne ile radius
özniteliğie erişildiği için yeniden __getattribute__ metodu çağrılır. İkinci çağrım AttributeError oluşmasına yol
açacaktır.
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
__getattribute__ metodu içerisinde özyineleme yapmadan nesnenin özniteliğine erişmenin tek yolu object sınıfının
2024-07-15 13:23:34 +03:00
__getattribute__ metodunu kullanmaktır. Eğer object sınıfının __getattribute__ metodu kullanılırsa artık ilgili sınıfın
2025-02-16 00:11:32 +03:00
__getattribute__ metodu çağrılmaz. object sınıfının __getattribute__ metodu aynı nesneyi kullandığına göre ilgili özniteliğe
erişebilecektir. Örneğin:
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
import math
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
class Circle:
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
def __getattribute__(self, name):
if name == 'area':
return math.pi * object.__getattribute__(self, 'radius') ** 2
raise AttributeError(f"Circle object has no attribute '{name}'")
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
c = Circle(10, 12, 5)
print(c.area)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
Burada object.__getattribute(self, 'area') çağrısı ile artık özyineleme yapmadan area isimli örnek özniteliğinin değerine
2024-07-15 13:23:34 +03:00
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
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
__getattr__ metotlarının yanı sıra Sınıfların __setattr__ isimli metotları da olabilmektedir. __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:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
nesnenin __dict__ elemanı yoluyla ya da object sınıfının __setattr__ metodu yoluyla yapılmalıdır. Örneğin biz daha önce
yazmış olduğumuz Circle sınıfındaki area property'sini read/write hale getirmeye çalışalım. Yani nesnenin area özniteliğine
atama yaptığımızda radius elemanı değişecek olsun. Bunu aşağıdaki gibi yapamayız:
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
class Circle:
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
2024-07-15 13:23:34 +03:00
def __getattr__(self, name):
2025-02-16 00:11:32 +03:00
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)
Burada iki kusur vardır. Birincisi __init__ metodu içerisinde ve __setattr__ metodu içerisinde self.radius erişimlerinde
yine __setattr__ metodu çağrılacaktır. Nesne yaratılırken __init__ içerisinde self.radius atamasında __setattr__ çağrılır.
Bu durumda öznitelik hiç yaratılmaz. Dolayısıyla AttributeError oluşur. __setattr__ kullanıldığında artık sınıf içerisinde
öznitelik atamalarının object sınıfının __setattr__ metoduyle yapılması gerekir. Böylece sınıftakş __setattr__ fonksiyonu
devreden çıkacaktır. İkinci kusur __setattr__ metodunda eğer atanan öznitelik area değilse bu atama etkisinin oluşturulmamış
olmasıdır. Kodun düzeltilmiş biçimi şöyledir:
import math
class Circle:
def __init__(self, x, y, radius):
self.x = x
self.y = y
object.__setattr__(self, 'radius', radius)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
def __getattr__(self, name):
if name == 'area':
return math.pi * self.radius ** 2
raise AttributeError(f"Circle object has no attribute '{name}'")
2024-07-15 13:23:34 +03:00
def __setattr__(self, name, value):
2025-02-16 00:11:32 +03:00
if name == 'area':
object.__setattr__(self, 'radius', math.sqrt(value / math.pi))
2024-07-15 13:23:34 +03:00
else:
object.__setattr__(self, name, value)
2025-02-16 00:11:32 +03:00
c = Circle(10, 12, 5)
print(c.area)
c.area = 100;
print(c.radius)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
c.x = 10
print(c.x)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
Burada da gördüğünüz gibi sınıf için __setattr__ metodu yazılmışsa artık örnek özniteliklerine atama işlemi object
sınıfının __setattr__ metodu ile yapılmalıdır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
#------------------------------------------------------------------------------------------------------------------------
Aslında __setattr__ metodunun yazıldığı durumda nesnenin bir özniteliğine eleman atamak için __dict__ ö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:
import math
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
class Circle:
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.__dict__['radius'] = radius
def __getattr__(self, name):
if name == 'area':
return math.pi * self.radius ** 2
raise AttributeError(f"Circle object has no attribute '{name}'")
2024-07-15 13:23:34 +03:00
def __setattr__(self, name, value):
2025-02-16 00:11:32 +03:00
if name == 'area':
self.__dict__['radius'] = math.sqrt(value / math.pi)
2024-07-15 13:23:34 +03:00
else:
self.__dict__[name] = value
2025-02-16 00:11:32 +03:00
c = Circle(10, 12, 5)
print(c.area)
c.area = 100;
print(c.radius)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
c.x = 10
print(c.x)
Yukarıda da belirttiğimiz gibi sınıfın __setattr__ metodu read/write property oluşturmak için kullanılabilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
import math
class Circle:
2025-02-16 00:11:32 +03:00
def __init__(self, x, y, radius):
2024-07-15 13:23:34 +03:00
self.x = x
self.y = y
2025-02-16 00:11:32 +03:00
self.__dict__['radius'] = radius
2024-07-15 13:23:34 +03:00
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':
2025-02-16 00:11:32 +03:00
self.__dict__['radius'] = math.sqrt(value / math.pi)
2024-07-15 13:23:34 +03:00
else:
self.__dict__[name] = value
2025-02-16 00:11:32 +03:00
c = Circle(10, 12, 5)
print(c.area)
c.area = 100;
print(c.radius)
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
c.x = 10
print(c.x)
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Daha önce de bahsettiğimiz gibi property'ler veri elemanı gibi kullanılan metotlardır. Biz yukarıda Circle sınıfı örneğinde
2025-02-16 00:11:32 +03:00
Circle sınıfının area isimli bir örnek özniteliğini kullandı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.
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
Ancak property kullanımının diğer bir amacı da (birinci amaçtan daha aşağı bir amaç değildir) "veri elemanlarının
(Python'daki nesne özniteliklerinin) gizlenmesi (data hiding)" prensibinin uygulanmasını sağlamaktır. Daha önce de
belirttiğimiz gibi 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.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
Veri elemanlarının gizlenmesi (data hiding) veri elemanlarının dışarıdan kullanımını engelleyip onlara kodlar yoluyla
2025-02-16 00:11:32 +03:00
erişimi mümkün hale getirmek anlamına gelmektedir. Python'da dışarıdan erişilmesini istemediğimiz özniteliklerin başına
'_' ve '__' ekliyorduk. Tabii bu ekleme erişimi engellemiyordu. Yalnızca dışarıya "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" bölüm
bulundurulmuştur.
2024-07-15 13:23:34 +03:00
NYPT'de sınıfın örnek öznitelikleri (veri elemanları) iç işleyişe ilişkin olma eğilimindedir. Özniteliklere dışarıdan
2025-02-16 00:11:32 +03:00
erişilmesinin engellenmesinin ve onlara kodlar yoluyla erişilmesine izin verilmesinin dört temel neden vardır:
2024-07-15 13:23:34 +03:00
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)
2025-02-16 00:11:32 +03:00
Burada tarih bilgisi nensnein day, month ve year özniteliklerinde tutulmuştur. Ancak programcı daha sonra fikrini değiştirip
bu tarih bilgisini örneğin tek bir özenitelikle "/dd/mm/yyyy" biçiminde bir yazı ile tutmak isteyebilir. İşte eğer programcı
bu örnek özniteliklerini değiştirirse onları kulalnmış olan programlar geçersiz kalır. Halbuki bu örnek özniteliklerine
doğrudan erişim engellenip onlara kodlarla erişim mümkün hale getirilirse bu durumda biz sınıfın bu örnek özniteliklerinde
değişiklik yapsak bile bu değişiklikten onları kullanmış olan kodların etkilenmesini engelleyebiliriz.
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
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.
2024-07-15 13:23:34 +03:00
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.
2025-02-16 00:11:32 +03:00
Python nesne yönelimlilik bakımından minimal biçimde tasarlanmıştır. Bu nedenle Python'daki sınıf özelliklerini
C++, Java ve C# gibi dillerdeki sınıf özellikleriyle karşılaştırmayınız. Python programcılarının çoğu veri elemanlarının
gizlenmesi (data hiding) için bir çaba sarf etmemektedir. Halbuki C++, Java ve C# gibi dillerde o dillerin programcıları
buna oldukça önem vermektedir. Bu tür özelliklerin Python'da gelişkin olmaması Python savunucuları tarafından "Python
for adults" esprisiyle açıklanmaktadır. Bu espride vurgulanan şey Python'da bu tür mekanizmalarla önlem almaya
gereksinim duyulmamış olmasıdır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
Propert'ler aslında yukarıda da örneklerini verdiğimiz gibi sınıfın __getattr__ ve __setattr__ metotları yoluyla
oluşturulabilmektedir. Ancak bu yöntem biraz zahmetlidir. Bu nedenle Python'da property oluşturulmasını kolaylaştırmak
için property isminde bir dekoratör sınıf bulundurulmuştur.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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:
2025-02-16 00:11:32 +03:00
def __init__(self, fget = None, fset = None, fdel = None, doc = None):
2024-07-15 13:23:34 +03:00
pass
2025-02-16 00:11:32 +03:00
Property kullanımı tipik olarak şöyle yapılmaktadır:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
örnek özniteliğine atanacak değeri belirtmektedir. set metodunun bir geri dönüş değeri yoktur.
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
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 özniteliğe
2024-07-15 13:23:34 +03:00
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:
2025-02-16 00:11:32 +03:00
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
2024-07-15 13:23:34 +03:00
def _get_area(self):
2025-02-16 00:11:32 +03:00
return math.pi * self.radius ** 2
2024-07-15 13:23:34 +03:00
def _set_area(self, value):
2025-02-16 00:11:32 +03:00
self.radius = math.sqrt(value / math.pi)
2024-07-15 13:23:34 +03:00
area = property(_get_area, _set_area)
2025-02-16 00:11:32 +03:00
def __repr__(self):
return f'center = ({self.x}, {self.y}), radius = {self.radius}'
c = Circle(10, 12, 5)
print(c.area)
c.area = 100
print(c.radius)
2024-07-15 13:23:34 +03:00
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.
2025-02-16 00:11:32 +03:00
Tabii sınıfa property eklendiğinde sınıfın örnek özniteliklerine artık bu property yoluyla erişilecektir. Bu durumda
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
property için delete metodunun yazılmasına seyrek bir biçimde gereksinim duyulmaktadır. Çünkü mantıksal olarak
özniteliğin delete edilmesi gibi bir istekle çok seyrek karşılaşılır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
property fonksiyonunun son parametresi olan doc ilgili property ile doküman yazısı elde edilmek istendiğinde verilecek
yazıyı belirtmektedir. Yani bu son parametre bir string olmalıdır. help gibi fonksiyonlarla yardım bilgisi alındığında
burada belirtilen yazı görüntülenir. Doküman yazıları ileride ayrı bir başlık içerisinde ele alınmaktadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
60. Ders 15/02/2025 - Cumartesi
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-16 00:11:32 +03:00
olabilmektedir. Ancak biz yukarıdaki setter işleminde property nesnesinin nasıl oluşturulduğu konusunda bir fikir vermek
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
Burada yorumlayıcı önce myproperty dekoratörünü görecektir. Bu dekoratörün eşdeğeri şöyledir:
2024-07-15 13:23:34 +03:00
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ı
2025-02-25 23:30:26 +03:00
düşünebilirsiniz. İşte dekoratörler konusunda da belirttiğimiz gibi 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:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
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.
2024-07-15 13:23:34 +03:00
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:
2025-02-25 23:30:26 +03:00
def __init__(self, fget = None, fset = None, fdel = None, doc = None):
2024-07-15 13:23:34 +03:00
self._fget = fget
self._fset = fset
self._fdel = fdel
2025-02-25 23:30:26 +03:00
self._doc = doc
2024-07-15 13:23:34 +03:00
def setter(self, fset):
self._fset = fset
return self
def deleter(self, fdel):
self._del = fdel
return self
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2025-02-16 00:11:32 +03:00
__new__ metodunun amacı tahsisat yapmak, __init__ ise amacı nesnenin özniteliklerini yaratmaktır.
2024-07-15 13:23:34 +03:00
2025-02-16 00:11:32 +03:00
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 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.
2024-07-15 13:23:34 +03:00
Programcılar sınıfları için __new__ metodunu genellikle yazmazlar. __new_ metodunun yazılmasına seyrek olarak gereksinim
2025-02-16 00:11:32 +03:00
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ığından 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:
2024-07-15 13:23:34 +03:00
@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.
2025-02-16 00:11:32 +03:00
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__ metodunu çağırmamız daha uygun olur. Yani __new__ metodu aslında aşağıdaki gibi yazılmalıdır:
2024-07-15 13:23:34 +03:00
@staticmethod
def __new__(cls):
# araya girme işlemi
return super().__new__(cls)
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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(...)
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
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:
2025-02-16 00:11:32 +03:00
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. Nesneyi yaratırken
2024-07-15 13:23:34 +03:00
kullandığımız tüm isimsiz ve isimli argümanlar bu __call__ metoduna geçirilmektedir.
2025-02-16 00:11:32 +03:00
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:
2024-07-15 13:23:34 +03:00
@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.
2025-02-16 00:11:32 +03:00
3) İlgili 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.
2024-07-15 13:23:34 +03:00
4) Nihayet __call__ metodu tahsis edilen nesnenin adresiyle geri dönmektedir.
Örneğin:
s = Sample(10, 20)
2025-02-16 00:11:32 +03:00
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.
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
türünden 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.
2024-07-15 13:23:34 +03:00
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.
2025-02-16 00:11:32 +03:00
Örneğin "singleton kalıbı" __new__ metodu kullanılarak oluşturulabilir. NYPT'de singleton kalıbı bir sınıf türünden
2024-07-15 13:23:34 +03:00
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
2025-02-16 00:11:32 +03:00
kalıbı bazı işlemlerin gerçekleştirilmesi için kullanılabilmektedir.
2024-07-15 13:23:34 +03:00
Aşağıda singleton kalıbına örnek verilmiştir.
#------------------------------------------------------------------------------------------------------------------------
class Sample:
@staticmethod
def __new__(cls, *args, **kwargs):
if Sample._singleton is None:
2025-02-16 00:11:32 +03:00
Sample._singleton = object.__new__(cls)
2024-07-15 13:23:34 +03:00
return Sample._singleton
_singleton = None
s = Sample()
print(s, id(s))
k = Sample()
print(k, id(k))
2025-02-16 00:11:32 +03:00
m = Sample()
print(m, id(m))
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
61. Ders 16/02/2025 - Pazar
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
Daha öce görmüş olduğumuz property isimli dekaratör bir sınıf olarak mevcut bilgiler kullanılarak yazılamaz. Bu tür
sınıfların yazılabilmesi için dile "betimleyici (descriptor)" denilen bir özellik eklenmiştir. Betimleyicilere çok
seyrek gereksinim duyulmaktadır. Bu nedenle bu konu genellikle Python programcıları tarafından bilinen bir konu
değildir
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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 az bir tanesinin
bulunuyor olması gerekir. Genellikle betimleyici sınıflarda __get__ ve __set__ metotları birlikte bulunurlar. Ancak bazı
sınıflarda yalnızca __get__ metodu bulunuyor olabilir. Betimleyici sınıflarda __delete__ bulunduurlmasına nadiren gereksinim
duyulmaktadır.
2024-07-15 13:23:34 +03:00
__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.
#------------------------------------------------------------------------------------------------------------------------
2025-02-16 00:11:32 +03:00
class myproperty:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
bakış açısıyla birden fazla Python dosyasından oluşan bir pakettir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
Python'da bir dizin'in bir paket olarak ele alınması için tek gereken şey o dizin'in içinde "__init__.py" isimli özel
bir dosyanın bulunuyor olmasıdır. Python 3.3'ten itibaren bu zorunluluk da ortadan kaldırılmıştır. Python 3.3'ten
itibaren her dizin bir paket olarak ele alınabilmektedir. 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 nasıl bir dosyayı import ediyorsak paketi de aynı biçimde import edebiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
import mypackage
Yine import işlemi sırasında as cümleciği de kullanılabilir. Örneğin:
import mypackage as mp
2025-02-25 23:30:26 +03:00
Bir paket import edildiğinde paketin içerisindeki "__init__.py" dosyası otomatik olarak çalıştırılmaktadır. Örneğin
mypackage isimli paketteki "__init__.py" dosyasının içeriği şöyle olsun:
2024-07-15 13:23:34 +03:00
# __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.
2025-02-25 23:30:26 +03:00
Şimdi mypackage dizini içerisine "__init__.py" dosyasının yanı sıra "a.py" ve "b.py" dosyalarını da yerleştirelim.
2024-07-15 13:23:34 +03:00
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)) # <class 'module'>
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
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 modul
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:
2024-07-15 13:23:34 +03:00
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(...)
2025-02-25 23:30:26 +03:00
Paketin içerisindeki bir dosyayı from import deyimiyle de import edebiliriz. Böylece bu ismi daha kolay kullanabiliriz.
Örneğin:
from mypackage import a
Burada yine mypackage paketindeki __init__.py dosyası çalıştırılacaktır. Tabii a.py import edildiğine göre onun da içi
çalıştırılacaktır. Ancak burada mypackage ismi artık kullanılmaz fakat a ismi kullanılabilir. Örneğin:
a.foo() # geçerli
mypackage.a.foo() # error! çünkü mypackage ismini kullanamayız
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
zaten import edilmektedir. Biz de aşağıdaki gibi işlemleri yapabilmekteyiz:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
# 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
2025-02-25 23:30:26 +03:00
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 tüm değişken isimleri sanki bu paketin
içerisindeymiş gibi bir etki oluşacaktır. Şimdi paketi aşağıdaki gibi kullanalım:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
dosyası içerisinde from import deyimleriyle aslında o paketin içerisindeki 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:
2024-07-15 13:23:34 +03:00
import pandas
s = pandas.Series([1, 2, 3, 4, 5])
print(s)
2025-02-25 23:30:26 +03:00
Aslında Series sınıfı pandas paketinin içerisindeki bir dosyanın içerisinde bulunmaktadır. Ancak pandas dizinindeki
__init__.py dosyasına yerleştirilmiş from import deyimlerisayesinde bu isimler paket isim alanına taşınmıştır.
2024-07-15 13:23:34 +03:00
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
# __init__.py
print('__init__.py')
import mypackage.a as a
Burada a ismi yine paket ismiyle kullanılmak zorundadır.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
İç 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.
2025-02-25 23:30:26 +03:00
Şimdi yukarıda oluşturmuş olduğumuz mypackage isimli paketin içerisinde util isimli bir paket daha oluşturalım. util
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
Şimdi bu alt paket içerisindeki "c.py" dosyasını import edecek olalım:
2024-07-15 13:23:34 +03:00
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.
2025-02-25 23:30:26 +03:00
Yani burada önce mypackage içerisindeki __init__.py dosyası, sonra "mypackage/util" içerisindeki __init__.py dosyası nihayet
2024-07-15 13:23:34 +03:00
"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
2025-02-25 23:30:26 +03:00
taşıyabiliriz. Örneğin mypackage paketinin __init__.py dosyası şöyle olsun:
2024-07-15 13:23:34 +03:00
print('__init__.py')
from mypackage.a import *
from mypackage.b import *
from mypackage.util.c import *
2025-02-25 23:30:26 +03:00
Burada "a.py", "b.py" ve "c.py" dosyalarının içerisindeki isimlerin hepsi sanki mypackage modülünün (paketinin) içerisindeymiş
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
Biz bir paketi "pip install" ile indirdiğimizde aslında bu paketin içindeki dosyalar paket isminde bir dizine çekilmektedir.
O dizin de alt dizinlerden yani alt paketlerdne oluşabilmektedir. Ancak paketi oluşturanlar kolat bir kullanım sunabilmek
için __init__.py dosyaları içerisinde from import deyimleri ile alt paket içerisindeki ve alt paket içerisindeki isimleri
asıl paketin isim alanına taşıyabilmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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
2025-02-25 23:30:26 +03:00
yalnızca modülün __all__ değişkeninde belirtilen değişkenler dışarıya import edilmektedir. Örneğin "x.py" isminde bir Python
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
Normal olarak biz bu modülü *'lı import ettiğimizde foo, bar, tar, a, b, c değişken isimlerinin hepsini dışarıdan doğrudan
kullanabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
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.
2025-02-25 23:30:26 +03:00
__all__ global değişkeni yalnızca *'lı "from import" deyiminde etkili olmaktadır. Modül normal olarak import edilirse
bu __all__ değişkeninde belirtilmeyen modül değişkenleri yine modül ismi ile niteliklendirilerek kullanılabilir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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)
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
62. Ders 22/02/2025 - Cumartesi
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
>>> import math
>>> math.floor.__doc__
'Return the floor of x as an Integral.\n\nThis is the largest integer <= x.'
2025-02-25 23:30:26 +03:00
Python'da built-in help isimli fonksiyon bir değişkenin ismini alıp ona ilişkin bilgileri ekrana (stdout dosyasın)
yazdırmaktadır. Bu yazdırma sırasında doküman yazıları da yazdırılmaktadır. Örneğin:
2024-07-15 13:23:34 +03:00
>>> 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.
2025-02-25 23:30:26 +03:00
help fonksiyonu yalnızca dokğman yazılarını değil birkaç kaynaktan toplayabildiği birtakım açıklama yazılarını da
yazdırmaktadır.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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__)
2025-02-25 23:30:26 +03:00
Bir sınıf için help fonksiyonu kullanılırsa hem sınıfın kendisine iliştirilen doküman yazısı hem de metotlarına ilişkin
dokğman yazıları görüntülenir.
2024-07-15 13:23:34 +03:00
Üç 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.
2025-02-25 23:30:26 +03:00
Yine help fonksiyonuyla modüle ilişkin bilgileri yazdırabiliriz. help fonksiyonu modülün doküman yazılarını da
görüntüleyecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
Bu bölümde gittikçe daha fazla kullanılır hale gelmekte olan "tür açıklamaları (type annotations / type hinting)" konusu
üzerinde duracağız. Tür açıklamaları konusu python'un 3'lü versiyonlarıyla birlikte eklenmiş olan bir özelliktir. Bu
özellik Python'un neredeyse her sürümünde genişletile genişletile bugünkü haline gelmiştir. Dolayısıyla kursumuzda
güncel son versiyondaki tür açıklamaları üzerinde duracağız.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Python dinamik tür sistemine sahip bir programlama dili olduğu için değişkenlerin, fonksiyon parametrelerinin, fonksiyonların
2025-02-25 23:30:26 +03:00
geri dönüş değerlerinin türleri programın çalışma zamanı sırasında 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.
2024-07-15 13:23:34 +03:00
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)
2025-02-25 23:30:26 +03:00
İş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.
2024-07-15 13:23:34 +03:00
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ş
2025-02-25 23:30:26 +03:00
olması gerekir. Bu nedenle bizim tür açıklamalarının nasıl oluşturulacağını bilmemiz gerekmektedir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
Tür açıklamalarının genel biçimi şöyledir:
<değişken_ismi>: <ifade> [= <ilkdeğer>]
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
ı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
2025-02-25 23:30:26 +03:00
Yukarıda da belirttiğimiz gibi ':' atomundan sonra tipik olarak bir tür ismi getirilmektedir. Ancak tasarımda daha
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
Global ya da yerel değişkenler için tür açıklaması yapılırken onun ilkdeğer verilerek yaratılması zorunlu değildir.
Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
ı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:
2024-07-15 13:23:34 +03:00
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)
2025-02-25 23:30:26 +03:00
Tabii biz tür açıklaması yaparken genellkle aynı zamanda ona değer atayarak aynı zamanda değişkeni yaratabiliriz.
Örneğin:
2024-07-15 13:23:34 +03:00
x: int = 10
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
Tür açıklamasındaki ifade içerisindeki tür isimlerinin daha önceden tanımlanmış olması gerekir. Örneğin:
int a: Sample
Burada tür açıklamasında a değişkeninn Sample türündne olması gerektiği belirtilmiştir. Eğer bu noktaya kadar Sample
türü henüz oluşturulmadıysa yorumlayıcı da bu işlemi error olarak değerlendirir. Tabii built-in türler hiçbir import
işlemi yapılmadan da tanımlanmış kabul edilmektedir. Örneğin:
int a: list
Burada list built-in bir tür olduğu için bir sorun ortaya çıkmayacaktır.
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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.
2025-02-25 23:30:26 +03:00
Bunun için "File/Settings/Plugins" sekmesine gelinir. Burada "mypy" seçilerek araç IDE'ye dahil edilir. Visual Studio
2024-07-15 13:23:34 +03:00
Code IDE'sine de bir plugin olarak mypy eklenebilmektedir. Maalesef henüz tür kontrolü yapmak için Spyder IDE'sine entegre
2025-02-25 23:30:26 +03:00
edilmiş bir araç yoktur. VSCode Editöründe de "mypy" bir plugin biçiminde bulunmaktadır.
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
UNIX/Linux ve macOS sistemlerinde son çalıştırılan programın exit kodu $? ile elde edilebilmektedir. Örneğin:
$ mypy sample.py
Success: no issues found in 1 source file
$ echo $?
0
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
statik analiz araçları herhangi bir hata rapor etmemektedir. Örneğin:
2024-07-15 13:23:34 +03:00
def square(a: float) -> float:
return a * a
result = square(10.2)
print(result)
2025-02-25 23:30:26 +03:00
Burada biz result değişkeni için bir tür açıklaması yapmadık. Ancak square fonksiyonunun geri dönüş değerinin float türden
olması gerektiğini belirttik. result değişkenine atama için mypy bir hata rapor etmeyecektir.
2024-07-15 13:23:34 +03:00
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)
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
Tür açıklamalarında kendi sınıflarımızı da kullanabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
class Sample:
pass
def foo(a: Sample):
print(a)
s = Sample()
foo(s)
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
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ı
2025-02-25 23:30:26 +03:00
çalıştırarak bir kontrol yapamadığı için her türlü ihlali de kontrol edememektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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ı
2025-02-25 23:30:26 +03:00
genellikle tespit edememektedir. Dolayısıyla burada mypy kodda bir ihlal olmadığına yönelik çıktı verecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
list, tuple, dict gibi türlerin köşeli parantezlerle tür açıklamalarında kullanılabilmesi Python 3.9 (Ekim 2020) 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 typing modü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:
2024-07-15 13:23:34 +03:00
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])
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
Benzer biçimde set ve tuple sınıfları için de tür açıklamaları kullanılabilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
Burada s int değerleri tutan bir küme türünden olmalıdır.
2024-07-15 13:23:34 +03:00
2025-02-25 23:30:26 +03:00
Demetlerde elemanların türleri sırasıyla tek tek belirtilir. Örneğin:
2024-07-15 13:23:34 +03:00
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,
2025-02-25 23:30:26 +03:00
o elemanın da int türden olduğu demet" açıklamasıdır.
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
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)
2025-02-25 23:30:26 +03:00
Yukarıda da belirttiğimiz gibi Python 3.9'un öncesinde bu işlem typing modülü içerisindeki Dict sınıfı ile yapılıyordu.
2024-07-15 13:23:34 +03:00
Ö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ı
2025-02-25 23:30:26 +03:00
durumlarda gereksizdir. Örneğin:
2024-07-15 13:23:34 +03:00
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,
2025-02-25 23:30:26 +03:00
üçüncü elemanı float türden ancak ikinci elemanı herhangi bir türden olabilir. Dolayısıyla aşağıdaki çağrılarda mypy
2024-07-15 13:23:34 +03:00
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:
2025-02-25 23:30:26 +03:00
return 'a must not be negative'
2024-07-15 13:23:34 +03:00
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}')
2025-02-25 23:30:26 +03:00
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 tanımlamasına dikkat ediniz:
2024-07-15 13:23:34 +03:00
def getroots(a: float, b: float, c: float) -> tuple[float, float]|None:
2025-02-25 23:30:26 +03:00
pass
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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ı
2025-02-25 23:30:26 +03:00
yapmıştık. Aynııklamayı Optional kullanarak Optional[tuple[float, float]] biçiminde de yapabilirdik.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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')
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
Bir değişkene bir fonksiyon gibi çağrılabilecek (callable) bir ifade atanacaksa tür açıklamasında typing modülündeki
2024-07-15 13:23:34 +03:00
Callable sınıfı kullanılmaktadır. Örneğin:
2025-02-25 23:30:26 +03:00
from typing import Callable
2024-07-15 13:23:34 +03:00
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ı
2025-02-25 23:30:26 +03:00
yapılabilmektedir. Bunun için Callable isminden sonra köşeli parantezler içerisinde "bir liste biçiminde parametre türleri,
2024-07-15 13:23:34 +03:00
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]
2025-02-25 23:30:26 +03:00
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.
2024-07-15 13:23:34 +03:00
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)
2025-02-25 23:30:26 +03:00
Daha önce de belirttiğimiz gibi Python 3.10 ile birlikte Union işlemi '|' operatörü ile de yapılır hale getirilmiştir.
2024-07-15 13:23:34 +03:00
Ö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
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
typing modülü içeisindeki cast fonksiyonu belli bir tür açıklamasına sahip bir değişkene başka türdne bir değerin
atanması için kullanılmaktadır. 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.
2024-07-15 13:23:34 +03:00
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)
2025-02-25 23:30:26 +03:00
cast fonksiyonu aynı zamanda atanan hedef değişkende bir tür açıklaması da oluşturmaktadır. Yukarıdaki örnekte artık
b de int türü olarak açıklanmıştı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.
2024-07-15 13:23:34 +03:00
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 <class 'float'>
Burada b str olarak açıklanmıştır ancak b içerisine float bir değer atanmıştır.
2025-02-25 23:30:26 +03:00
Pekiyi cast fonksiyonu tam olarak ne amaçla kullanılmaktadır? İşte bazen bri değişken belli bir türle açıklanmış
olabilir. Biz de ona başka bir türü atamak isteyebiliriz. Bu durumda mypy gibi araçların sorun bildirmemesi için
bu cast fonksiyonundan faydalanabiliriz. Örneğin:
def foo(a: str):
print(b)
Burada foo fonksiyonuna str türünden bir argüman geçirilmelidir. Eğer foo fonksiyonuna str türünden bir argüman
geçirilmezse mypy gibi araçlar sorun bildircektir. Örneğin bu fonksiyonu şöyle çağırmış olalım:
foo(123)
Burada mypy aşağıdaki gibi sorun bildirmektedir:
sample.py:7: error: Argument 1 to "foo" has incompatible type "int"; expected "str" [arg-type]
Found 1 error in 1 file (checked 1 source file)
Pekiyi biz hem bu atamayı yapmak isteyelim hem de bu mypy gibi araçların sorun bildirmemesini isteyelim. İşte bu
durumda cast fonksiyonundan faydalanırız:
foo(cast(str, 123))
Artık mypy bir sorun bildirmeyecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
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])
2025-02-25 23:30:26 +03:00
Çü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:
2024-07-15 13:23:34 +03:00
foo([1, 2, 3., 4, 5.0])
Örneğin:
a: Iterable[int|str]
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
a = [1, 2, 3, 4, 'ankara', 'izmit']
Örneğin:
a: Iterable[tuple[int, str]]
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
a = [(6, 'Anakara'), (26, 'Eskişehir'), (35, 'İzmir')]
#------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
from typing import Callable, Iterable
def foo(a: Iterable[tuple[Callable[[int, int], int], float]]):
pass
2025-02-25 23:30:26 +03:00
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. Örneğin:
def bar(a: int, b: int) -> int:
return 0
def tar(a: int, b: int) -> int:
return 0
a = [(bar, 3.4 ), (tar, 4.34)]
foo(a)
2024-07-15 13:23:34 +03:00
2025-02-25 23:30:26 +03:00
Buarad foo fonksiyonuna geçirilen argüman için mypy bir sorun bildirmeyecektir.
2024-07-15 13:23:34 +03:00
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
typing modülü içerisindeki Sequence sınıfı da string gibi liste gibi __getitem__, __len__ metotları bulunan "reversible
2025-02-25 23:30:26 +03:00
seqeunce" türlerini belirtmek için kullanılmaktadır. Anımsanacağı gibi tipik "seuqence" türleri list, range, tuple,
str" türleridir. dict türünün __getitem__ metodu olsa da dict türü bir "sequence türü değildir. Sequence ile açıklama
yaparken köşeli parantez içerisinde o türün tuttuğu nesnelerin türleri de belirtilebilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
from typing import Sequence
2025-02-25 23:30:26 +03:00
def foo(a: Sequence):
2024-07-15 13:23:34 +03:00
pass
2025-02-25 23:30:26 +03:00
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:
2024-07-15 13:23:34 +03:00
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
2025-02-25 23:30:26 +03:00
Burada a parametre değişkenine biz int elemanlardan oluşan bir "sequence türü" geçirebiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
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.
#------------------------------------------------------------------------------------------------------------------------
2025-02-25 23:30:26 +03:00
#------------------------------------------------------------------------------------------------------------------------
Python'daki tür açıklamaları için birkaç PEP dokğmanı oluşturulmuştur. Bu dokümanları da gözden geçirebilirsiniz:
https://peps.python.org/pep-0484/
https://peps.python.org/pep-0526/
https://peps.python.org/pep-0593/
#------------------------------------------------------------------------------------------------------------------------#------------------------------------------------------------------------------------------------------------------------