Fonksiyonlar, kapsam ve modüller
C’de programı fonksiyonlara bölmeyi öğrendiniz. Nesne tabanlı programlamada da bu beceriye ihtiyacınız olacak, çünkü ileride yazacağımız sınıfların içindeki metotlar da birer fonksiyondur. Bu hafta Python’da parametreyi, dönüş değerini, kapsamı, varsayılan değeri, docstring’i ve modülleri kısaca toparlayacağız. Bir yandan da her şeyi tek büyük programa yazmak yerine işi küçük fonksiyonlara bölme alışkanlığını pekiştireceğ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 ne işe yaradığı
- 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 verilen listeyi mi değiştirmeli, yeni liste mi döndürmeli?
Bir listeye not ekleyen fonksiyonu iki biçimde yazabilirsiniz: verilen listeyi yerinde değiştirir ya da notu eklenmiş yeni bir liste döndürür. İkisi de çalışır, ama fonksiyonu çağıran kod için sonuç farklıdır: birinde eldeki liste değişir, ötekinde olduğu gibi kalır. Bu bölüm boyunca şunu soracağız: bu fonksiyon, onu çağıran koda ne söz veriyor?
Fonksiyon: adlandırılmış davranış
first ve second parametrelerdir; çağrıdaki 70 ve 90 argümanlardır.
Fonksiyonun adına bakan kişi, işin nasıl yapıldığından önce ne yapıldığını anlamalıdır. Bu yüzden calculate_average (ortalama hesapla), process ya da 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. Bu yüzden çağrının değeri None olur. None, Python’da “değer yok” anlamına gelen özel değerdir. get_square ise değeri çağırana verir ve başka hesaplarda kullanılabilir.
Varsayılan parametre ve anahtar kelime argümanı
limit=50 bir varsayılan değerdir: çağrıda limit verilmezse 50 kullanılır. Üçüncü çağrıdaki score=70 ve limit=60 ise anahtar kelime argümanıdır, yani değer parametrenin adıyla birlikte verilir. Birden çok isteğe bağlı parametre olduğunda hangi değerin hangi parametreye gittiği böylece çağrı satırından okunur.
Mutable varsayılan parametre tuzağı
Şu kod ilk bakışta sorunsuz görünür:
def add_item(item, items=[]):
items.append(item)
return itemsAma varsayılan [] her çağrıda yeniden oluşturulmaz. Fonksiyon tanımlanırken bir kez oluşturulur ve sonraki çağrılar aynı listeyi paylaşır. Sorunu önlemek için şu kalıp yaygın olarak kullanılır:
Burada None, “çağıran liste vermedi” anlamına gelen bir işarettir. Bu işi gören değere nöbetçi (sentinel) denir.
7. haftada sınıflarla çalışırken benzer bir sorunla karşılaşacağız. Onun nedeni de aynı: değiştirilebilir bir nesneyi farkında olmadan birden çok yerde ortak kullanmak.
Yerel kapsam, global ad ve UnboundLocalError
tax fonksiyonun içinde oluşturulduğu için yerel bir addır. C’deki yerel değişkenler gibi yalnızca o fonksiyonun içinde kullanılabilir. rate ise fonksiyonun dışında tanımlıdır ve fonksiyon onu dış kapsamdan (scope) okur.
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 fonksiyon çağrıldığında UnboundLocalError oluşur. Gövdede counta atama olduğu için Python countu baştan yerel sayar, ama print satırı çalıştığında bu yerel counta henüz değer verilmemiştir.
Dıştaki countu değiştirmek istiyorsanız bunu global ile açıkça söylemeniz gerekir:
count = 10
def increase():
global count
count += 1Ama her sorunu global ile çözerseniz hangi fonksiyonun hangi değişkeni değiştirdiğini izlemek zorlaşır. Bu derste mümkün olduğunca girdiyi parametreyle alıp sonucu return ile vereceğ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 parametresi, çağıranın verdiği listenin kendisine bağlanır. Python listeyi kendiliğinden kopyalamaz. append() bu ortak listeyi değiştirdiği için numbers da yeni içeriği gösterir. Bir fonksiyonun, dönüş değeri dışında dışarıda bıraktığı böyle bir değişikliğe yan etki (side effect) denir. Yan etki bazen istenir, bazen beklenmedik bir hataya yol açar. Hangisi olduğunu fonksiyonun sözleşmesi, yani ne alıp ne döndürdüğünü ve neyi değiştirdiğini anlatan tanımı açıkça söylemelidir.
Bu davranışı “Python her şeyi referansla geçirir” diye genellemek yanıltıcı olabilir. Bu derste şöyle düşüneceğiz: her ad bir nesneye bağlanır; fonksiyon çağrılınca parametre adı da verilen nesneye bağlanır. Değiştirilebilir bir nesne yerinde değiştirilirse, o nesneye bağlı bütün adlar değişikliği görür.
Çağıranın listesi değişmesin istiyorsak yeni bir liste döndürebiliriz:
Docstring: davranış sözleşmesini koda yazmak
Fonksiyonun adı her ayrıntıyı anlatmaz. Docstring, fonksiyon tanımının hemen altına üç tırnak içinde yazılan açıklama metnidir. Kısa bir docstring, fonksiyonun ne beklediğini, ne döndürdüğünü, varsa önemli yan etkilerini ve hangi durumda hata verdiğini yazar:
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 satırı, fiyat negatifse hatayı çağırana bildirir. raise, try ve except arasındaki ilişkiyi 5. haftada ayrıntılı olarak göreceğiz.
Fonksiyonları sorumluluğa göre ayırmak
Bir fonksiyon hem veri alır, hem doğrular, hem hesaplar, hem dosyaya yazar, hem de ekrana yazarsa onu test etmek ve değiştirmek zorlaşır.
Örneğin calculate_discount yalnızca indirimi hesaplar ve fiyatın negatif olmadığını denetler. Kullanıcıdan fiyatı alan kod başka bir fonksiyonda durabilir.
Modüller: kodu dosyalara ayırmak
Her Python dosyası bir modül olarak kullanılabilir: başka bir dosya onun içindeki fonksiyonları import ile alıp çağırabilir. Ö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))Bu sayfadaki kod hücrelerinde ayrı dosya oluşturmak her zaman kolay değil. Bu yüzden modül örneklerini bu hafta çoğunlukla çalıştırmadan okuyacağız. Siz de 1. haftada kurduğunuz Python ve VS Code ile kendi bilgisayarınızda iki dosya açıp deneyin.
__name__ == "__main__" ne işe yarar?
Bir dosya doğrudan çalıştırıldığında __name__ değeri "__main__" olur. Başka bir dosyadan import edildiğinde ise modülün adını alır (örneğin "pricing").
def main():
print("Program başladı")
if __name__ == "__main__":
main()Bu kalıpla fonksiyon tanımları bir yanda, yalnızca dosya doğrudan çalıştırıldığında işleyen kod öbür yanda durur. Dosyayı import eden başka bir dosya fonksiyonları alır, ama main() çalışmaz. 13. haftada birkaç dosyadan oluşan bir OOP programında bu kalıbı 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?
- Fonksiyonun listeyi değiştirdiği adından anlaşılıyor mu, yoksa bunu docstring’e de yazmak gerekir mi?
Alıştırma: Mutable varsayılanı düzelt
Aşağıdaki fonksiyonu, her çağrı kendi ayrı listesini döndürecek biçimde düzeltin:
def collect(value, values=[]):
values.append(value)
return valuesNone nöbetçisini kullanın ve buna neden gerek olduğunu bir cümleyle açıklayın.
Sıra sizde: 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 edin ve birkaç örnek veriyle çağırın. Argümanları hem sırasıyla (konumsal argüman) hem de parametre adıyla (anahtar kelime argümanı) verin. Ardından grades.py dosyasına, dosya doğrudan çalıştırıldığında birkaç örnek sonuç yazdıran bir main() fonksiyonu ekleyin.
Çalışır kod görevi: 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 kod tekrarını azaltır; üstelik bir işe ad verir.
returnsonucu çağırana verir;printyalnızca ekrana yazar.- Varsayılan parametreler ve anahtar kelime argümanları fonksiyonu çağırmayı kolaylaştırır. Varsayılan değer olarak liste gibi değiştirilebilir bir nesne vermeyin.
- Fonksiyon dıştaki bir adı okuyabilir, ama gövdede o ada atama yaparsa Python adı yerel sayar.
UnboundLocalErrorçoğu zaman bundan çıkar. - Fonksiyon çağrılınca parametre adı verilen nesneye bağlanır. Fonksiyon bu nesneyi yerinde değiştirirse çağıran da değişikliği görür.
- Docstring, fonksiyonun ne alıp ne döndürdüğünü ve neyi değiştirdiğini kodun hemen yanında anlatır.
- Modüller programı dosyalara böler; böylece hangi dosyanın hangi işi yaptığı belli olur.
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ı uygun durumda kullanır.
- Mutable varsayılan parametre tuzağını tanır ve
Nonenöbetçisiyle önler. - 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 fonksiyon tanımlarını ana akıştan neden ayırdığını açıklar.