Fonksiyonlar, kapsam ve modüller
Nesne tabanlı programlama, iyi fonksiyon tasarımının yerine geçmez. Aksine sınıf metotları da fonksiyon mantığı üzerine kurulur. Bu hafta Python’da parametre, dönüş değeri, kapsam, varsayılan değer, docstring ve modül kullanımını hızlı biçimde toparlayacağız; aynı zamanda “tek büyük program” yerine sorumlulukları küçük parçalara ayırma alışkanlığını güçlendireceğiz.
Bu bölümün kapsamı
print()ilereturnfarkı ve örtükNone- varsayılan parametre ve anahtar kelime argümanı
- mutable varsayılan parametre tuzağı ve
Nonenöbetçisi - yerel ad ile dış kapsamdaki ad;
UnboundLocalError - değiştirilebilir parametrede yan etki
- docstring ile davranış sözleşmesi
__name__ == "__main__"kalıbının işlevi
- değişken sayıda argüman (
*args,**kwargs) - lambda ifadeleri ve closure
- paket yayımlama (
pip install, PyPI)
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Fonksiyon çağıranı değiştirmeli mi, yoksa sonuç mu üretmeli?
Aynı işi iki biçimde yazabilirsiniz: verilen listeyi yerinde değiştirmek ya da yeni bir sonuç döndürmek. İkisi de çalışır ama çağıran taraf için anlamları farklıdır. Bölümün ayırt edici sorusu budur: bu fonksiyon dışarıya ne vaat ediyor?
Fonksiyon: adlandırılmış davranış
first ve second parametrelerdir; çağrıdaki 70 ve 90 argümanlardır.
Fonksiyon adı, “nasıl yapılıyor?” ayrıntısından önce “ne yapıyor?” sorusuna cevap vermelidir. calculate_average bu açıdan process veya do_it gibi belirsiz adlardan daha açıklayıcıdır.
print() ile return aynı şey değildir
show_square ekrana yazar ama sonuç döndürmez; dolayısıyla çağrının değeri None olur. get_square ise değeri çağırana verir ve başka hesaplarda kullanılabilir.
Varsayılan parametre ve anahtar kelime argümanı
limit=50, çağıran başka değer vermediğinde kullanılacak varsayılandır. score=70 ve limit=60 ise anahtar kelime argümanlarıdır; özellikle birden çok isteğe bağlı parametre olduğunda çağrının niyetini görünür kılar.
Mutable varsayılan parametre tuzağı
Şu kod masum görünür:
def add_item(item, items=[]):
items.append(item)
return itemsAncak varsayılan [] nesnesi her çağrıda yeniden oluşturulmaz; aynı liste sonraki çağrılar arasında paylaşılabilir. Güvenli ve yaygın kalıp:
Bu sorun 7. haftadaki paylaşılan mutable sınıf niteliğiyle aynı temel fikre dayanır: istemeden ortak mutable nesne paylaşmak.
Yerel kapsam, global ad ve UnboundLocalError
tax fonksiyon içinde yereldir. rate dış kapsamdan okunmuştur.
Ancak fonksiyon içinde aynı ada atama yaparsanız Python bu adı o fonksiyonda yerel kabul eder:
count = 10
def increase():
print(count)
count = count + 1Bu çağrı UnboundLocalError üretir; çünkü count yerel kabul edilmiştir fakat ilk print anında henüz yerel değeri yoktur.
global ile dıştaki ada açıkça atama yapılabilir:
count = 10
def increase():
global count
count += 1Fakat bu mekanizmayı bir tasarım çözümü olarak sık kullanmak programı zor izlenir hâle getirir. Bu derste mümkün olduğunda girdiyi parametreyle alıp sonucu return ile vermeyi tercih edeceğiz.
Değiştirilebilir parametre ve yan etki
Aşağıdaki örneği çalıştırmadan önce numbers listesinin değişip değişmeyeceğini tahmin edin.
Çağrı sırasında scores parametre adı, çağıranın verdiği aynı liste nesnesine bağlanır. Python listeyi otomatik olarak kopyalamaz. append() bu ortak liste nesnesinin durumunu değiştirdiği için çağıran taraf da yeni içeriği görür. Bu davranış bazen istenen şeydir; bazen beklenmedik yan etkidir. Fonksiyonun sözleşmesi bunu açık kılmalıdır.
Bu davranışı “Python her şeyi referansla geçirir” biçiminde genellemek yanıltıcı olabilir. Bu derste daha güvenli zihinsel model şudur: adlar nesnelere bağlanır; fonksiyon çağrısında parametre adları da verilen nesnelere bağlanır. Mutable nesne yerinde değiştirilirse bu değişiklik aynı nesneyi gören yerlerden fark edilir.
Alternatif olarak yeni liste döndürebiliriz:
Docstring: davranış sözleşmesini koda yazmak
Fonksiyonun adı her ayrıntıyı anlatmaz. Kısa bir docstring, beklenen girdiyi, dönüş değerini ve önemli yan etki/hata koşullarını görünür kılar:
def calculate_discount(price, rate=0.10):
"""Pozitif fiyat için indirimli tutarı döndürür.
price negatifse ValueError üretir. Girdileri değiştirmez.
"""
if price < 0:
raise ValueError("Fiyat negatif olamaz")
return price * (1 - rate)Buradaki raise ifadesi bir hata koşulunu çağırana bildirir. 5. haftada raise, try ve except ilişkisini açık biçimde öğreneceğiz.
Fonksiyonları sorumluluğa göre ayırmak
Tek bir fonksiyonun aynı anda veri alma, doğrulama, hesaplama, dosyaya yazma ve çıktı üretme görevlerini üstlenmesi test etmeyi ve değiştirmeyi zorlaştırır.
Örneğin calculate_discount yalnızca hesaplama ve temel kuralı üstlenir. Kullanıcıdan veri alma ayrı yerde tutulabilir.
Modüller: kodu dosyalara ayırmak
Bir Python dosyası modül olarak kullanılabilir. Örneğin:
# pricing.py
def final_price(price, tax_rate):
return price * (1 + tax_rate)Başka dosyada:
from pricing import final_price
print(final_price(100, 0.20))Web sürümündeki tek hücrelerde ayrı dosya oluşturmak her zaman uygun değildir; bu nedenle modül örneklerini bu hafta çoğunlukla statik olarak okuyacağız. 1. haftada kurduğunuz yerel Python + VS Code ortamında iki dosyayla deneyin.
__name__ == "__main__" ne işe yarar?
Bir dosya doğrudan çalıştırıldığında __name__ değeri "__main__" olur. Modül olarak import edildiğinde modül adını alır.
def main():
print("Program başladı")
if __name__ == "__main__":
main()Bu desen, tanımlar ile doğrudan çalıştırılan örnek/ana akışı ayırmaya yardımcı olur. 13. haftada çok dosyalı OOP sisteminde yeniden kullanacağız.
Alıştırma — return ekle
Beklenen çıktı 0 ve 8’dir.
def clamp_zero(value):
if value < 0:
return 0
return valueAlıştırma — Yan etkiyi tahmin et
- Çıktıyı tahmin edin.
- Fonksiyonun çağıranın listesini değiştirmesini istemeseydik hangi yaklaşımı kullanabilirdik?
- Bu davranışın fonksiyon adından anlaşılması yeterli mi, yoksa docstring/sözleşme de gerekli mi?
Alıştırma — Mutable varsayılanı düzelt
Aşağıdaki fonksiyonu iki çağrının birbirinden bağımsız liste döndürmesini sağlayacak biçimde düzeltin:
def collect(value, values=[]):
values.append(value)
return valuesNone sentinel kalıbını kullanın ve neden gerekli olduğunu bir cümleyle açıklayın.
Küçük üretim görevi — Not yardımcı modülü
Yerel ortamınızda iki dosya oluşturun:
grades.py içinde:
average(scores)is_successful(score, limit=50)
fonksiyonlarını tanımlayın. Her fonksiyona kısa docstring ekleyin.
app.py içinde bu fonksiyonları import edip hem konumsal hem anahtar kelime argümanıyla birkaç örnek veri üzerinde kullanın. Ardından grades.py dosyasını doğrudan çalıştırdığınızda küçük bir demo gösteren main() fonksiyonu ekleyin.
Çalışır kod teslimi: py app.py / python3 app.py ile programı çalıştırın; en az üç assert ile average ve is_successful için normal ve sınır durumlarını doğrulayın.
Tek sayfa özet
- Fonksiyon, yalnızca kod tekrarını azaltan değil, sorumluluğu adlandıran bir yapıdır.
returnsonucu çağırana verir;printyalnızca çıktı üretir.- Varsayılan parametreler ve anahtar kelime argümanları arayüzü sadeleştirir; mutable varsayılanlardan kaçının.
- Kapsam kurallarında “okuma” ile “atama” aynı değildir;
UnboundLocalErrorbu farkı görünür kılabilir. - Fonksiyon çağrısında parametre adları verilen nesnelere bağlanır; mutable nesnelerde yerinde değişiklik ortak nesneyi etkileyebilir.
- Docstring, davranış sözleşmesini kodun yanında taşır.
- Modüller programı fiziksel dosyalara ayırırken sorumluluk sınırlarını da görünür hâle getirir.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
print()ilereturnfarkını gösterir ve değer döndürmeyen çağrınınNoneverdiğini bilir.- Varsayılan parametre ve anahtar kelime argümanını yerinde kullanır.
- Mutable varsayılan parametre tuzağını tanır ve
Nonenöbetçisiyle kaçınır. - Yerel ad ile dış kapsamdaki adı ayırır ve
UnboundLocalErrornedenini açıklar. - Fonksiyona verilen değiştirilebilir nesnenin yan etkiyle değişebileceğini gösterir.
- Docstring ile bir fonksiyonun davranış sözleşmesini yazar.
__name__ == "__main__"kalıbının tanım ile ana akışı neden ayırdığını açıklar.