Python’da Fonksiyonlar başlığı, dakikayı saate çeviren kod ve 90’dan 1.5’e dönüşüm akışını gösteren ders kapağı

Python’da Fonksiyonlar: Parametreler, return ve Tekrar Kullanılabilir Kod

Python’da def, parametreler, return ve değişken kapsamını örneklerle öğrenin. Fonksiyonlarla çalışan bir çalışma oturumu ve süre raporu programı hazırlayın.

PYTHON EĞİTİM SERİSİ · DERS 08 / 16

Tüm Python dersleri  ·  ← Önceki: Sözlükler ve Kümeler

Bir çalışma planı hazırladığımızı düşünelim. Her oturum için konu ve süre giriyoruz. Program bazen toplam dakikayı hesaplıyor, bazen oturumları listeliyor, bazen de boş bırakılan bir adı kontrol ediyor. İlk birkaç satırda her şey anlaşılır görünüyor. Yeni seçenekler ekledikçe aynı hesapların ve kontrollerin farklı yerlerde tekrarlandığını fark ediyoruz.

Bu tekrarları kopyalayıp çoğaltmak yerine belirli bir işi yapan, adı olan kod parçaları oluşturabiliriz. Gerektiğinde bu parçaya bilgi verir, sonucu alır ve programımızın başka yerinde kullanırız. Python’da bu düzeni kurmanın temel araçlarından biri fonksiyonlardır.

Önceki derslerde listeler ve demetlerle birden fazla değeri, sözlükler ve kümelerle ilişkili veya benzersiz bilgileri düzenledik. Bu derste ise verilerin üzerinde çalışan kodu düzenleyeceğiz. Amacımız her satırı ayrı bir fonksiyona taşımak değil; programın hangi parçasının ne yaptığını daha rahat okuyabilmek.

Örnekleri fonksiyonlar.py dosyasında ayrı ayrı deneyebilirsiniz. Her Python kutusu gerekli tanımları içerir. Çıktı kutuları kod değildir; bunları dosyaya eklemeyin. Bilerek hatalı olduğu belirtilen örneği de diğerlerinden ayrı çalıştırın.

Fonksiyon hangi sorunu çözer?

Bir dakikayı saate çevirmek için sayıyı 60’a böleriz. Bu işlemi bir yerde yapmak kolaydır. Ancak programın farklı bölümlerinde aynı hesabı kullanıyorsak, işlemin amacını her defasında yeniden anlamamız gerekir. Bir fonksiyon, hem bu hesaba anlamlı bir ad verir hem de uygulama biçimini tek yerde toplar.

Örneğin dakikayi_saate_cevir adını gördüğümüzde, ayrıntısını okumadan hangi dönüşümün yapıldığını tahmin edebiliriz. Sonucu ekrana yazdırabilir, başka bir hesapta kullanabilir veya bir sözlüğe kaydedebiliriz. Böylece hesaplama işiyle sonucu kullanma kararı birbirinden ayrılır.

Bu düzenin faydası yalnızca satır sayısını azaltmak değildir. Yanlış bir sonuç gördüğümüzde nereye bakacağımızı da daraltır. Süre dönüşümü hatalıysa bütün menüyü incelemek yerine bu işi yapan parçadan başlayabiliriz.

Yine de henüz anlaşılır olan üç satırı gereksiz katmanlara bölmek zorunda değiliz. Bir fonksiyon yazarken “Bu parçanın yaptığı işi tek cümlede anlatabiliyor muyum?” sorusu iyi bir başlangıçtır. Adını koymakta zorlandığımız bir parça, birbiriyle ilgisiz birkaç işi üstleniyor olabilir.

def ile ilk fonksiyonumuzu yazalım

Python’da kendi fonksiyonumuzu tanımlamak için def kullanırız. Ardından fonksiyon adı, parantezler ve iki nokta gelir. Fonksiyona ait satırlar bir alt satırda girintili yazılır. Bu yapının resmî açıklaması Python’un fonksiyon tanımları belgesinde yer alır.

def baslangic_mesaji():
    print("Çalışma planına hoş geldiniz.")
    print("Bugünkü ilk oturumu seçebilirsiniz.")


baslangic_mesaji()

Çıktı:

Çalışma planına hoş geldiniz.
Bugünkü ilk oturumu seçebilirsiniz.

İlk satır, Python’a bir fonksiyon tanımladığımızı söyler. Girintili iki satır onun gövdesidir. En alttaki baslangic_mesaji() ise fonksiyonu çağırır; yani hazırladığımız işi yapmasını ister.

Tanımlamakla çağırmak arasındaki fark önemlidir. Yalnızca def bölümünü yazarsanız gövdedeki mesajlar henüz ekrana basılmaz. Tanım yürütüldüğünde fonksiyon adı kullanılabilir hâle gelir; gövdeyi çalıştıran şey çağrıdır.

Örneklerimizde dört boşlukluk girinti kullanacağız. Dosyayı yukarıdan aşağıya okuduğumuzda da önce tanımları, ardından onları kullanan çağrıları göreceğiz. Bu sıralama, yeni başlayan biri için programın akışını takip etmeyi kolaylaştırır.

Parametre ve argüman arasındaki fark

Her oturumda aynı mesajı göstermek yerine kişinin adını kullanmak isteyebiliriz. Bunun için fonksiyona dışarıdan bilgi vermemiz gerekir. Tanımda bu bilginin alacağı isme parametre, çağrıda verdiğimiz gerçek değere ise argüman deriz. Terimler Python sözlüğünde açıklanır.

def calisma_mesaji(ad):
    print(f"{ad}, ilk oturum için hazırsınız.")


calisma_mesaji("Ece")
calisma_mesaji("Can")

Çıktı:

Ece, ilk oturum için hazırsınız.
Can, ilk oturum için hazırsınız.

Burada ad parametredir. "Ece" ve "Can" ise iki ayrı çağrının argümanlarıdır. Fonksiyonun gövdesini çoğaltmadan, aynı işi farklı bilgilerle yaptırmış olduk.

Çağırırken kullandığınız değişkenin adıyla parametrenin adı aynı olmak zorunda değildir. Elinizde katilimci = "Ece" adlı bir değişken varsa calisma_mesaji(katilimci) yazabilirsiniz. Fonksiyonun içinde o değere ad ismiyle ulaşılır.

Bu ayrım, program büyürken anlam kazanır. Dışarıdaki bir değişkenin adını değiştirmeniz, fonksiyonun yaptığı işi değiştirmek zorunda değildir. Önemli olan doğru değeri doğru parametreye vermektir.

Birden fazla parametre kullanmak

Bir fonksiyon tek bilgiyle sınırlı değildir. Çalışma oturumunun konusu ve süresi gibi birbiriyle ilişkili birkaç bilgiyi birlikte alabilir. İlk aşamada değerlerin tanımdaki sırayla eşleşmesine dikkat etmeliyiz.

def oturum_mesaji(konu, dakika):
    print(f"{konu} oturumu: {dakika} dakika")


oturum_mesaji("Python tekrarı", 35)
oturum_mesaji("Okuma", 20)

Çıktı:

Python tekrarı oturumu: 35 dakika
Okuma oturumu: 20 dakika

İlk argüman konu, ikinci argüman dakika parametresine bağlanır. Değerlerin yerini yanlışlıkla değiştirirsek program her zaman hata vermeyebilir; fakat anlamsız bir mesaj üretebilir. Bir kodun çalışmasıyla amaçladığımız bilgiyi doğru göstermesi aynı kontrol değildir.

Fonksiyonun alacağı bilgiler, onun göreviyle ilişkili olmalıdır. Bir süre mesajı hazırlayan fonksiyona parola, dosya yolu veya ilgisiz başka ayarlar vermemiz gerekmez. Parametre listesini okumak bile işin kapsamını anlamaya yardımcı olmalıdır.

Çok sayıda parametre eklemeye başladığınızda bir an durup sorumluluğu yeniden düşünün. Bu derste az sayıda, açık isimli parametrelerle çalışacağız. İleri kullanım biçimlerini öğrenmeden de faydalı programlar yazabiliriz.

return ile sonucu dışarıya vermek

Şimdi yalnızca mesaj gösteren değil, hesapladığı sonucu programın başka yerinde kullanmamızı sağlayan bir fonksiyon yazalım. Bunun için return kullanırız.

def dakikayi_saate_cevir(dakika):
    return dakika / 60


saat = dakikayi_saate_cevir(90)
print(saat)
print(saat + 0.5)

Çıktı:

1.5
2.0

dakikayi_saate_cevir(90) çağrısı 1.5 sonucunu üretir. Biz bu sonucu saat değişkenine atarız. Sonraki satırda üzerine yarım saat ekleyebiliriz; çünkü elimizde yalnızca ekrana basılmış bir görüntü değil, kullanılabilir bir sayı vardır.

return, bulunduğu fonksiyon çağrısını sonlandırır ve belirtilen değeri çağıran yere verir. Bu davranış Python’un return ifadesi belgesinde tanımlanır.

Bu örnekte fonksiyonun içinde input() bulunmaması da bilinçli bir tercihtir. Sürenin klavyeden mi, bir listeden mi yoksa başka bir hesaptan mı geldiğini bilmek zorunda değildir. Ona bir sayı veririz; dönüşümü yapıp sonucu alırız. Böylece aynı hesabı farklı yerlerde daha rahat kullanabiliriz.

print ile return neden aynı değildir?

print() bir bilgiyi çıktı olarak gösterir. Bir fonksiyonun hesapladığı değeri dışarıya vermek için ise return kullanırız. İkisini karıştırmak, özellikle sonuçları bir değişkende toplamak istediğimizde sorun çıkarır.

def toplam_yazdir(birinci, ikinci):
    print(birinci + ikinci)


sonuc = toplam_yazdir(12, 18)
print("Değişkende tutulan:", sonuc)

Çıktı:

30
Değişkende tutulan: None

Ekranda 30 görmemiz, sonuc değişkeninin de 30 olduğu anlamına gelmedi. Fonksiyon toplamı yazdırdı; fakat bu sayıyı döndürmedi. Gövdenin sonuna açık bir dönüş değeri olmadan gelindiğinde Python None döndürür.

Hesabı başka yerde kullanmak istiyorsak düzeni şöyle kurabiliriz:

def toplam_hesapla(birinci, ikinci):
    return birinci + ikinci


sonuc = toplam_hesapla(12, 18)
print("Toplam:", sonuc)

Çıktı:

Toplam: 30

Fonksiyon hem yazdırma hem değer döndürme işlemi yapabilir; ancak her zaman ikisini birden yapması gerekmez. Hesaplamayı ve görüntülemeyi ayırmak, aynı sonucu terminalde, bir raporda veya başka bir hesapta kullanmak istediğimizde esneklik sağlar. print() davranışı da yerleşik fonksiyonlar belgesinde açıklanır.

90 dakikayı 1.5 saate çeviren Python fonksiyonunun girdi ve dönüş akışı ile print ve return farkını gösteren infografik
Fonksiyonun dönüş değeri başka işlemlerde kullanılabilir; print() ekranda gösterir, return sonucu çağıran koda verir. Büyütmek için görsele tıklayın.

None bir hata mesajı değildir

None, Python’da bir değerin bulunmadığını belirtmek için kullanılabilen özel bir nesnedir. Ekranda bu kelimeyi gördüğünüzde programın mutlaka bozulduğunu düşünmeyin. Önce hangi fonksiyonun ne döndürmesini beklediğinize bakın.

Örneğin biraz önceki fonksiyon mesajını doğru yazdırdı; yalnızca hesap sonucunu dışarıya vermedi. Başka bir fonksiyonda ise None bilinçli olarak “uygun kayıt bulunamadı” anlamında kullanılabilir. Bu anlamı fonksiyonun tasarımı belirler.

def ilk_konuyu_bul(konular):
    if not konular:
        return None
    return konular[0]


print(ilk_konuyu_bul([]))
print(ilk_konuyu_bul(["Fonksiyonlar", "Döngüler"]))

Çıktı:

None
Fonksiyonlar

Boş listede ilk öğeye erişmeyi denemek yerine, sonucu olmadığını açıkça belirttik. Çağıran kod bu sonucu if sonuc is None: biçiminde kontrol edebilir. Böyle bir fonksiyon kullanırken “hangi durumda hangi tür değer gelir?” sorusunun cevabını bilmek gerekir.

None ile sıfırı veya boş metni gelişigüzel aynı kabul etmeyin. Bunlar farklı amaçlarla kullanılabilir. Python’un yerleşik sabitler belgesi, None için temel başvuru kaynağıdır.

Erken return ile koşulları sadeleştirmek

Bir fonksiyon, her çağrıda son satırına kadar ilerlemek zorunda değildir. İşi yapmasına engel olan bir durum varsa sonucu erken verebilir. Bu yaklaşım, iç içe koşulların gereksiz yere büyümesini azaltabilir.

def oturum_etiketi(konu):
    temiz_konu = konu.strip()
    if not temiz_konu:
        return "Konu belirtilmedi"
    return f"Çalışılacak konu: {temiz_konu}"


print(oturum_etiketi("   "))
print(oturum_etiketi("  Fonksiyonlar  "))

Çıktı:

Konu belirtilmedi
Çalışılacak konu: Fonksiyonlar

İlk çağrıda boşluklar temizlendikten sonra geriye içerik kalmıyor. Fonksiyon ilk return satırında bitiyor. Bu çağrı için aşağıdaki ikinci dönüş satırına ulaşılmıyor.

Burada bir döngüden çıkmıyoruz; o fonksiyon çağrısını tamamlıyoruz. Daha önce öğrendiğimiz break, içinde bulunduğu döngüyü sonlandırır. return ise bir fonksiyonun sonucunu verir ve o çağrıyı bitirir. Menü programlarımızda bu iki ihtiyacı ayrı yerlerde kullanacağız.

Erken dönüşü her satıra eklemek gerekmez. Önce geçersiz veya özel durumu ele alıp ardından asıl işi yapmak, bu örnekte akışı daha rahat okumamızı sağlıyor.

Fonksiyonları birbirinin sonucuyla kullanmak

Bir fonksiyonun döndürdüğü değer, başka bir fonksiyona verilebilir. Bu sayede büyük bir işi anlaşılır küçük adımlardan oluşturabiliriz. Önce süreyi hesaplamak, sonra o süre için mesaj üretmek buna basit bir örnektir.

def toplam_dakika(oturum_sayisi, oturum_suresi):
    return oturum_sayisi * oturum_suresi


def sure_mesaji(dakika):
    return f"Planlanan süre: {dakika} dakika"


sure = toplam_dakika(3, 25)
mesaj = sure_mesaji(sure)
print(mesaj)

Çıktı:

Planlanan süre: 75 dakika

Bu akışta sure_mesaji fonksiyonu sürenin nereden geldiğini bilmez. toplam_dakika da mesajın nasıl gösterileceğiyle ilgilenmez. Her birine daha sınırlı bir görev verdiğimiz için örneği okumak ve ayrı ayrı denemek kolaylaşır.

Çağrıları tek satırda iç içe de yazabiliriz; fakat başlangıçta ara sonuçlara isim vermek yararlıdır. Beklenmedik bir sonuç çıktığında hangi adımı inceleyeceğinizi daha rahat görürsünüz.

Kısalık, tek başına iyi kod ölçüsü değildir. Özellikle öğrenirken, önce ara değerlerin ne olduğunu anlayın. Ardından gerçekten daha okunabilir oluyorsa ifadeleri birleştirmeyi değerlendirin.

Varsayılan parametrelerle tekrarları azaltmak

Bazı bilgiler çoğu çağrıda aynı olabilir. Her seferinde bunları yazmak yerine parametreye varsayılan değer verebiliriz. Çağıran kod o bilgiyi belirtirse verilen değer, belirtmezse varsayılan kullanılır.

def kisa_mola_mesaji(ad, dakika=5):
    return f"{ad}: {dakika} dakika mola"


print(kisa_mola_mesaji("Ece"))
print(kisa_mola_mesaji("Can", 10))

Çıktı:

Ece: 5 dakika mola
Can: 10 dakika mola

ad hâlâ gerekli bilgidir. dakika ise bu tasarımda isteğe bağlıdır. Örneğimizde beş dakika, program için seçtiğimiz varsayılandır; bütün çalışma yöntemleri için önerilen sabit bir mola süresi değildir.

Normal parametreleri kullanırken gerekli parametreleri önce, varsayılanı olanları sonra yazmak anlaşılır bir başlangıçtır. Böylece çağrıda hangi bilgilerin mutlaka beklendiği görülebilir.

Varsayılan değerlerin değerlendirilmesiyle ilgili önemli bir ayrıntı da vardır: Bu değerler her çağrıda yeniden oluşturulmaz. Özellikle liste ve sözlük kullanırken bu davranış önem kazanır. Biraz sonra bunu ayrı bir örnekle inceleyeceğiz. Kuralların resmî açıklaması Python öğreticisindeki varsayılan değerler bölümünde bulunur.

Konumsal ve isimli argümanlar

Şimdiye kadar değerleri sırayla verdik. Bunlara konumsal argümanlar denir. Aynı değerleri, uygun parametrelerin isimlerini yazarak da verebiliriz. Özellikle birkaç sayısal seçenek içeren çağrılarda bu yöntem anlamı belirginleştirir.

def oturum_plani(konu, dakika, tekrar=1):
    return f"{konu}: toplam {dakika * tekrar} dakika"


print(oturum_plani("Python", 20, 3))
print(oturum_plani(konu="Python", dakika=20, tekrar=3))
print(oturum_plani("Python", tekrar=3, dakika=20))

Çıktı:

Python: toplam 60 dakika
Python: toplam 60 dakika
Python: toplam 60 dakika

Üç çağrı aynı sonucu veriyor. Sonuncusunda önce konuyu konumsal olarak verdik, ardından süre ve tekrar bilgisini isimleriyle belirttik. Kullanılan parametre adlarının tanımla eşleşmesi gerekir.

Aynı parametreye hem konumsal hem isimli değer vermek doğru değildir. Ayrıca normal çağrı düzeninde isimli argüman yazdıktan sonra araya sıradan konumsal argüman yerleştirmeyin. Çağrıyı sade ve tutarlı tutmak bu tür karışıklıkları azaltır.

Bu derste değişken sayıda argüman alan *args ve **kwargs yapılarıyla kapsamı büyütmeyeceğiz. Sabit sayıda parametreyi doğru kullanmak, birazdan yazacağımız program için yeterlidir.

Yerel değişkenler nerede yaşar?

Fonksiyonun içinde oluşturulan bir isim, normal durumda o fonksiyon çağrısının yerel alanındadır. Aynı adı dışarıda kullanıyor olmanız, içerideki atamanın dışarıdaki değişkeni otomatik değiştirdiği anlamına gelmez.

konu = "Genel tekrar"


def konu_mesaji():
    konu = "Fonksiyonlar"
    return f"İçeride: {konu}"


print(konu_mesaji())
print("Dışarıda:", konu)

Çıktı:

İçeride: Fonksiyonlar
Dışarıda: Genel tekrar

Fonksiyon içindeki konu, bu örnekte ayrı bir yerel isimdir. İki ismin yazılışı aynı olsa da atamalar aynı alanı kullanmıyor. Ad çözümleme kuralları Python’un yürütme modeli belgesinde açıklanır.

Başlangıçta dışarıdaki birçok değişkene gizlice bağlı fonksiyonlar yazmak yerine ihtiyaç duyulan değerleri parametrelerle almak daha izlenebilir bir akış sağlar. Sonuç gerekiyorsa onu da döndürün. Böylece fonksiyonun neye ihtiyaç duyduğu çağrıdan anlaşılır.

global kullanmak mümkün olsa da bu dersteki program için gerekli değildir. Bir değişkene ulaşamadığımız her durumda global eklemek yerine, o bilginin fonksiyona verilmesi gerekip gerekmediğini düşünelim.

Yerel isme dışarıdan erişmeye çalışırsak

Bir fonksiyonun içinde hesapladığımız sonucu dışarıda kullanmak istiyorsak onu geri vermeliyiz. Fonksiyonun içindeki değişkeni adıyla çağırarak dışarıdan alabileceğimizi düşünmek yaygın bir başlangıç hatasıdır.

Aşağıdaki örnek bilerek hatalıdır; ayrı çalıştırın:

def toplam_sure():
    dakika = 40
    return dakika


toplam_sure()
print(dakika)

Bu dosyada dışarıda dakika tanımlı olmadığı için NameError oluşur. Fonksiyon sonucu döndürdü; ancak biz bu sonucu bir değişkene atamadık. Doğru kullanım sure = toplam_sure() yazıp ardından sure üzerinden devam etmektir.

Buradaki ders, her hatanın ayrıntılı mesajını ezberlemek değildir. Verinin hangi yönde hareket ettiğini takip etmektir: Girdi parametrelerle içeri girer, ihtiyaç duyulan sonuç dönüş değeriyle dışarı çıkar.

Bir sonraki derste hata türlerini ve try-except yapısını ayrıca ele alacağız. Şimdilik bu örneğin neden başarısız olduğunu anlamak yeterlidir. Bilerek hatalı satırı ana çalışma dosyanızda bırakıp diğer örneklerin sonucunu beklemeyin.

Listeyi değiştirmek ile yeni değer atamak farklıdır

Parametre kullanmak, verilen nesnenin otomatik olarak kopyalandığı anlamına gelmez. Değiştirilebilir bir listeyi fonksiyona verdiğinizde, fonksiyon o liste üzerinde değişiklik yapabilir. Buna karşılık parametre ismine yeni bir liste atamak, dışarıdaki ismi yeniden bağlamaz.

def konu_ekle(konular, yeni_konu):
    konular.append(yeni_konu)


def yeni_liste_sec(konular):
    konular = ["Başka konu"]
    return konular


plan = ["Metinler"]
konu_ekle(plan, "Fonksiyonlar")
baska_plan = yeni_liste_sec(plan)
print(plan)
print(baska_plan)

Çıktı:

['Metinler', 'Fonksiyonlar']
['Başka konu']

İlk fonksiyon, verilen listenin içeriğini değiştirdi. İkinci fonksiyon ise kendi yerel ismini başka bir listeye bağladı; yeni listeyi almak için dönüş değerini kullandık. Nesneler ve isimler arasındaki ilişki Python’un veri modeli belgesinde açıklanır.

Bir fonksiyonun aldığı listeyi değiştirmesi mutlaka yanlış değildir. Birazdan oturum eklerken bunu bilinçli yapacağız. Önemli olan davranışın açık olmasıdır. Adı yalnızca “rapor hesapla” olan bir fonksiyonun beklenmedik biçimde listenizi boşaltması ise kullanımı zorlaştırır.

Varsayılan liste kullanırken oluşan sürpriz

Varsayılan parametreye boş liste yazmak ilk bakışta doğal görünebilir. Ancak o listenin her çağrıda yeniden oluştuğunu düşünürsek yanlış beklentiye gireriz. Varsayılan nesne, fonksiyon tanımlandığında oluşturulur ve sonraki çağrılarda tekrar kullanılabilir.

def not_biriktir(not_metni, notlar=[]):
    notlar.append(not_metni)
    return notlar


print(not_biriktir("Metinleri tekrar et"))
print(not_biriktir("Fonksiyonları dene"))

Çıktı:

['Metinleri tekrar et']
['Metinleri tekrar et', 'Fonksiyonları dene']

İkinci çağrıda ilk notun da gelmesi bu örneğin sürprizidir. Her çağrıda bağımsız bir liste istediğimizi varsayarsak, sonuç amacımızı karşılamaz. Bu bir sözdizimi hatası değildir; program çalışır, fakat paylaşılan varsayılan nesne nedeniyle beklemediğimiz birikim oluşur.

Bu noktada yalnızca ilk çıktıya bakmak yeterli olmaz. Aynı fonksiyonu ikinci kez çağırmak, davranışı ortaya çıkarır. Fonksiyonları kontrol ederken tek bir başarılı örnekle yetinmemek için küçük ama öğretici bir nedenimiz var.

Liste ve sözlük gibi değiştirilebilir varsayılanların paylaşılması, Python’un programlama soruları belgesinde de ele alınır.

Bağımsız liste gerektiğinde None kullanmak

Her bağımsız çağrıda yeni bir liste istiyorsak başlangıç değerini fonksiyonun içinde oluşturabiliriz. Bunun için None değerini “liste verilmedi” işareti olarak kullanmak anlaşılır bir yöntemdir.

def not_listesi_olustur(not_metni, notlar=None):
    if notlar is None:
        notlar = []
    notlar.append(not_metni)
    return notlar


print(not_listesi_olustur("Metinleri tekrar et"))
print(not_listesi_olustur("Fonksiyonları dene"))

ortak_notlar = []
not_listesi_olustur("Döngüleri çalış", ortak_notlar)
print(ortak_notlar)

Çıktı:

['Metinleri tekrar et']
['Fonksiyonları dene']
['Döngüleri çalış']

İlk iki çağrıda dışarıdan liste verilmediği için ayrı listeler oluşturduk. Son çağrıda ise listeyi özellikle verdik ve fonksiyon onu değiştirdi. Böylece bağımsız başlangıçla, mevcut liste üzerinde çalışmayı açıkça ayırdık.

Burada if notlar is None: yerine if not notlar: yazmamamızın nedeni de anlam farkıdır. Boş bir liste geçerli bir kullanıcı girdisi olabilir; yalnızca boş olduğu için onu başka bir nesneyle değiştirmek istemeyebiliriz.

Bu ayrıntı, küçük bir kod tercihinin fonksiyonun davranışını nasıl etkilediğini gösterir. Beklediğimiz ilişkiyi önce tarif edip ardından kodu ona göre yazmak, bir kalıbı ezberlemekten daha yararlıdır.

Birden fazla sonucu demetle döndürmek

Bir çalışma planını özetlerken hem oturum sayısına hem toplam süreye ihtiyaç duyabiliriz. Bunlar aynı hesaplamanın ilişkili sonuçlarıysa birlikte döndürülebilir. Python’da virgülle yazılan bu dönüş, bir demet oluşturur.

def sure_ozeti(sureler):
    adet = len(sureler)
    toplam = sum(sureler)
    return adet, toplam


oturum_adedi, toplam_dakika = sure_ozeti([25, 40, 15])
print("Oturum:", oturum_adedi)
print("Toplam dakika:", toplam_dakika)

Çıktı:

Oturum: 3
Toplam dakika: 80

Burada iki ayrı return çalıştırmıyoruz. Tek dönüşte iki öğeli bir demet veriyoruz; çağıran tarafta öğeleri iki değişkene açıyoruz. Değişken sayısıyla dönen öğe sayısının eşleşmesi gerekir.

sum() listedeki sayıları toplar. Boş liste için bu örnekte toplam sıfır olur; oturum sayısı da sıfırdır. Yerleşik fonksiyonların ayrıntıları Python’un sum belgesinde, demet oluşturma davranışı ise ifade listeleri bölümünde bulunur.

Çok sayıda isimsiz sonuç döndürmek okumayı zorlaştırabilir. Bizim örneğimizde “adet, toplam” sırası yeterince açık. Daha karmaşık raporlarda, önceki derste gördüğümüz anlamlı anahtarları olan bir sözlük de değerlendirilebilir.

İyi isim ve kısa açıklama, fonksiyonun kullanımını anlatır

islem() veya hesap() gibi isimler çok genel kalabilir. toplam_dakika_hesapla() gibi bir ad ise amacını daha açık söyler. Fonksiyon ve değişken isimlerinde küçük harf ve kelimeler arasında alt çizgi kullanımı, PEP 8 stil kılavuzunun önerisidir.

Gerektiğinde gövdenin ilk satırına kısa bir açıklama metni, yani docstring ekleyebiliriz. Açıklama, fonksiyonun ne beklediğini veya ne döndürdüğünü söylemelidir; kodun her satırını tekrar etmesi gerekmez.

def iki_oturum_toplami(ilk_dakika, ikinci_dakika):
    """İki oturumun dakika cinsinden toplamını döndürür."""
    return ilk_dakika + ikinci_dakika


print(iki_oturum_toplami(20, 35))

Çıktı:

55

Aynı zamanda print, list veya sum gibi yerleşik isimleri kendi değişkenlerimiz için kullanmamak iyi bir alışkanlıktır. Bu isimleri başka nesnelere bağlamak, daha sonra yerleşik işlevi çağırırken kafa karıştırabilir.

Fonksiyonumuzun aldığı listeyi değiştirip değiştirmediğini açıklamak da yararlıdır. Bir sonraki programda “ekle” ve “rapor” isimlerini bu beklentiyi görünür kılmak için kullanacağız.

Küçük proje: Çalışma oturumları ve süre raporu

Şimdi öğrendiklerimizi küçük bir terminal programında bir araya getirelim. Kullanıcı bir çalışma konusu ve dakika bilgisi eklesin. Program oturumları listeleyip toplam ve ortalama süreyi göstersin. Silme, dosyaya kaydetme veya takvim entegrasyonu eklemeyeceğiz; bu dersin odağı fonksiyonlar arasındaki görev paylaşımıdır.

Her kayıt, konu ve dakika alanları olan bir sözlük olacak. Kayıtları bir listede tutacağız. Aynı konuya birden fazla oturum eklemek mümkün; bunlar ayrı çalışma süreleri sayılacak. Program kapandığında bilgiler bellekte kaldığı için kaybolacak.

Bu örneğin giriş kuralı açıktır: Süre, baştaki ve sondaki boşluklar temizlendikten sonra en fazla üç ondalık rakamdan oluşmalı ve 1–240 arasında olmalıdır. 30.5, -20, boş metin ve 241 kabul edilmeyecek. isdecimal() kontrolü rakam karakterlerini sınar; davranışı Python’un metin türü belgesinde açıklanır.

def dakika_oku(metin):
    temiz = metin.strip()
    if len(temiz) > 3 or not temiz.isdecimal():
        return None
    dakika = int(temiz)
    if dakika < 1 or dakika > 240:
        return None
    return dakika


def oturum_ekle(plan, konu, dakika):
    temiz_konu = konu.strip()
    if not temiz_konu or dakika < 1 or dakika > 240:
        return False
    plan.append({"konu": temiz_konu, "dakika": dakika})
    return True


def ozet_hesapla(plan):
    toplam = 0
    for oturum in plan:
        toplam += oturum["dakika"]
    adet = len(plan)
    if adet == 0:
        return 0, 0, 0.0
    return adet, toplam, toplam / adet


def rapor_yazdir(plan):
    if not plan:
        print("Henüz çalışma oturumu yok.")
        return
    for sira, oturum in enumerate(plan, start=1):
        print(f"{sira}. {oturum['konu']} - {oturum['dakika']} dakika")
    adet, toplam, ortalama = ozet_hesapla(plan)
    print(f"Oturum: {adet} | Toplam: {toplam} dakika")
    print(f"Ortalama: {ortalama:.1f} dakika")


def ana_menu():
    plan = []
    while True:
        secim = input("1 Ekle | 2 Rapor | 0 Çıkış: ").strip()
        if secim == "0":
            print("Görüşmek üzere.")
            break
        elif secim == "1":
            konu = input("Çalışma konusu: ")
            dakika = dakika_oku(input("Süre (1-240 dakika): "))
            if dakika is None:
                print("1-240 arasında, en fazla üç rakam girin.")
            elif oturum_ekle(plan, konu, dakika):
                print("Oturum eklendi.")
            else:
                print("Konu boş bırakılamaz.")
        elif secim == "2":
            rapor_yazdir(plan)
        else:
            print("Geçerli bir seçim yapın.")


ana_menu()

Örneğin “Python tekrarı” için 25 dakika, “Okuma” için 40 dakika ekleyip 2 seçeneğiyle rapor istediğinizde rapor bölümü şöyle görünür:

1. Python tekrarı - 25 dakika
2. Okuma - 40 dakika
Oturum: 2 | Toplam: 65 dakika
Ortalama: 32.5 dakika

Programdaki fonksiyonlar nasıl iş birliği yapıyor?

ana_menu, kullanıcıyla konuşan ana akıştır. Plan listesini oluşturur ve hangi işlemin yapılacağını seçer. dakika_oku, metni uygun bir süreye dönüştürür; geçersiz girişte None döndürür. Bu sayede menü, dönüşümün ayrıntılarını kendi içinde tekrarlamaz.

oturum_ekle verilen listeyi bilinçli olarak değiştirir. Konuyu temizler, sınırları kontrol eder ve bir sözlük ekler. Döndürdüğü True veya False, menünün uygun mesajı seçmesini sağlar. Fonksiyonun süre parametresi sayı bekler; metni dönüştürme görevi önceki adımdadır.

ozet_hesapla ise listeyi değiştirmez. Sayı, toplam ve ortalamayı döndürür. Boş plan için ortalamayı bu uygulamada 0.0 olarak belirledik; rapor fonksiyonu zaten boş plan için ayrı bir mesaj gösterir. rapor_yazdir sonuçları kullanıcıya sunar; hesaplamayı yeniden yazmaz.

Fonksiyonlar arasında sürelerin birimini de tutarlı taşımalıyız. Listede dakika alanına saat cinsinden bir sayı yazarsak toplama işlemi yine çalışabilir; fakat rapor yanlış anlam taşır. Örneğin bir buçuk saat için bu programa 1.5 değil, 90 dakika kaydı gerekir. Bu yüzden parametre adları, yalnızca geçici etiketler değildir: Bir sonraki kod parçasının hangi bilgiyi beklediğini de anlatır. Sonucun sayısal olarak hesaplanabilmesi, doğru verinin kullanıldığını tek başına kanıtlamaz.

Buradaki fonksiyonlara ayırma kararı, her satırın ayrı yerde durması için alınmadı. Her parçanın farklı bir işi olduğu için yapıldı. Süre sınırını değiştireceğimizde hangi kontrolleri gözden geçireceğimizi, rapor metnini değiştireceğimizde nereye bakacağımızı daha kolay görebiliyoruz.

Çalıştırırken yalnızca başarılı girişi denemeyin

İlk olarak hiçbir kayıt eklemeden rapor isteyin. Ardından geçerli iki kayıt ekleyip toplamı kendiniz hesaplayın. Raporun açılması kadar, toplamın ve ortalamanın doğru olması da önemlidir. Gösterilen örnekte 25 ile 40’ın toplamı 65, iki oturumun ortalaması 32.5 olmalıdır.

Sonra boş konu, yalnızca boşluk içeren konu, sıfır dakika, eksi sayı, ondalıklı metin ve sınırın üstündeki süreyi deneyin. Geçersiz bir girişten sonra rapor alın. Hatalı kaydın listeye eklenmediğini bu yolla kontrol edebilirsiniz.

Sınır değerlerini de unutmayın: Bir dakika ve 240 dakika kabul edilir. 241 dakika kabul edilmez. Başına ve sonuna boşluk eklenmiş 30 girdisi temizlenerek işlenir. Aynı konu iki kez eklendiğinde iki ayrı kayıt görünmesi ise bu program için beklenen davranıştır.

Bu çalışma biçimi, kodu her değişiklikte baştan sona tahmin ederek okumaktan daha somuttur. Önce beklediğiniz sonucu söyleyin, sonra programın çıktısıyla karşılaştırın. Bir fonksiyonu değiştirdiğinizde, onun sonucunu kullanan diğer parçaları da yeniden deneyin.

Konuyu pekiştirmek için üç küçük alıştırma

İlk alıştırmada, bir süreyi dakika olarak alan ve “kısa”, “orta” veya “uzun” etiketi döndüren bir fonksiyon yazın. Sınırları kendiniz belirleyin ve hangi değerlerde değiştiğini açıklayın. Aynı fonksiyonun içinde hem klavyeden bilgi istemek hem de rapor yazdırmak zorunda değilsiniz.

İkinci alıştırmada, ozet_hesapla sonucunu bir demet yerine adet, toplam ve ortalama anahtarları olan sözlükle döndürün. Bu değişiklikten sonra rapor fonksiyonunu da uyarlayın. Yalnızca dönüş satırını değiştirmenin neden yeterli olmadığını gözlemleyin.

Üçüncü alıştırmada, planı değiştirmeden en uzun oturumun konusunu döndüren bir fonksiyon hazırlayın. Boş planda ne döneceğini baştan belirleyin. İki oturumun süresi eşitse ilkini mi sonuncusunu mu seçeceğinize karar verip bunu örnekle gösterin.

Bu alıştırmalarda yeni özellik sayısından çok, fonksiyonun görevinin açık olması önemlidir. Kodu yazmadan önce hangi girdiyi alacağını, listeyi değiştirip değiştirmeyeceğini ve hangi sonucu döndüreceğini birer cümleyle belirtin. Ardından boş, normal ve sınır durumlar için küçük denemeler yapın.

Bu dersten sonra hangi noktayı anlamış olmalısınız?

Artık bir kod parçasını adlandırıp çağırabiliyor, parametrelerle bilgi verebiliyor ve return ile sonucu alabiliyoruz. print() ile değer döndürmenin farkını, yerel isimleri ve liste değişikliklerinin dışarıya nasıl yansıyabildiğini de gördük.

Önemli olan her programı çok sayıda fonksiyona bölmek değildir. Bir işi neden ayırdığınızı ve o parçanın ne beklediğini açıklayabilmektir. Çalışma planı örneğinde bunu giriş doğrulama, kayıt ekleme, hesaplama ve görüntüleme görevlerini ayırarak uyguladık.

Bir sonraki dersimiz Python Hatalarını Anlamak olacak. Orada hata mesajlarını okumayı ve uygun yerlerde try-except kullanmayı ele alacağız. Şimdilik örneklerinizde sonuca ulaşan yolu takip edin; özellikle fonksiyon çağrılarının döndürdüğü değerleri küçük denemelerle inceleyin.

PratikTekno Bülteni

Yeni rehberleri kaçırmayın

Yeni teknoloji rehberleri ve pratik çözümler e-postanıza gelsin.