= lambda x: x*x f
Python Programlamaya Giriş 11 - İsimsiz Fonksiyonlar
Python Programlamaya Giriş yazı dizimizde Python fonksiyonlarının nasıl tanımlandığını, parametre eşleştirmenin ve varsayılan parametrelerin nasıl işlediğini gördük, çeşitli örnekler ve alıştırmalar işledik. Dizinin bütün yazılarına erişmek için Python Programlamaya Giriş kategorimize bakabilirsiniz. Bu dizideki yazılar ayrıca Jupyter defterleri halinde GitHub depomuzda da mevcut.
Fonksiyonların her zaman def
komutuyla tanımlanması gerekmez. Başka bir yol, lambda
komutunu kullanarak isimsiz (“anonymous” - belli bir isme bağlı olmayan) fonksiyonlar yaratmaktır. İsimsiz fonksiyonlar, def
komutuyla üretilen fonksiyonlara göre daha kısıtlıdırlar. Buna karşılık, bir fonksiyon nesnesi gereken yerlerde bazı kolaylıklar sağlarlar.
Fonksiyonların lambda
komutuyla üretilmesine basit bir örnek olarak, aşağıdaki ifadeyi ele alalım. Burada, aldığı parametrenin karesini döndüren bir fonksiyon yaratıyoruz ve buna f
ismini atıyoruz.
Bundan sonra f
alıştığımız şekilde bir fonksiyon olarak kullanılabilir.
1.2), f(4), f(f(4)) f(
(1.44, 16, 256)
Nitekim aynı f
fonksiyonunu def
ile de tanımlayabilirdik.
def f(x): return x*x
1.2), f(4), f(f(4)) f(
(1.44, 16, 256)
Python dinamik bir dildir. Bunun anlamı, verdiğimiz komutların anında işlenmesi, ve gerekli nesnelerin bellekte o anda yaratılıyor olmasıdır. Python’daki her şey gibi fonksiyonlar da (yazılım geliştirmedeki anlamıyla) birer nesnedir. Yukarıdaki lambda x: x*x
ifadesi bellekte bir fonksiyon nesnesi yaratır, ve bu nesne ile f
ismi birbirine bağlanır. Öz olarak bunun s = "merhaba"
komutundan farkı yok; burada da önce bellekte "merhaba"
değerini tutan bir dize nesnesi yaratılır, sonra bu nesne s
ismi ile eşleştirilir.
Elbette lambda
ile yaratılan fonksiyon nesnesi tek başına da kullanılabilir.
lambda x: x*x)(1.2) (
1.44
Ama böyle bir kullanımda fonksiyon her seferinde baştan oluşturulacağından programın verimi azalır.
İsimsiz fonksiyonlar, def
ile tanımladığımız genel amaçlı fonksiyonlardan daha kısıtlıdırlar. Genel amaçlı fonksiyonlarda Python’da tanımlı olan her türlü işlemi kullanabiliriz. İsimsiz fonksiyonlarda ise komut (“statement”) bulunamaz, yani mesela atama yapamayız veya if-else
, while
, for
yapılarını kullanamayız. Sadece bir ifade (“expression”, geriye bir değer veren bir işlem) bulunabilir.
O zaman isimsiz fonksiyonların faydası ne? Bunlar, özellikle fonksiyon alan fonksiyonlara verilen parametreler olarak kullanışlıdırlar.
İlk okuyuşta çok egzotik bir kullanım gibi görünebilir ama değil. İsimsiz fonksiyonların kullanılabildiği yerlere birkaç örnek verelim.
Örnek: Seriler
Önceki bir bölümde fonksiyon alan bir fonksiyon tanımlamıştık. Özel olarak, \(f\) reel sayı alıp reel sayı veren herhangi bir fonksiyon olmak üzere, \(f(a) + f(a+1) + f(a+2) +\cdots + f(b)\) toplamını hesaplayan bir fonksiyon yazdık. Bu fonksiyon sadece başlangıç ve bitiş değerleri olan a
ve b
’yi değil, aynı zamanda f
fonksiyonunu da parametre olarak alıyor. Böylece herhangi bir fonksiyon için bu toplamı hesaplayabiliyoruz.
Bu toplamı veren fonksiyonu şöyle tanımlayabiliriz:
def seritoplam(f, a, b):
= 0
toplam = a
x while x<=b:
+= f(x)
toplam += 1
x return toplam
Tanımladığımız seritoplam
fonksiyonu, matematiksel terimle bir “fonksiyonel”dir, yani fonksiyon alıp sayı veren bir fonksiyon. Bunu kullanarak, \(\sum_{x=a}^b 2^{-x}\) değerini hesaplayalım. Bu toplam için f
parametresi olarak \(2^{-x}\) olarak tanımlanmış bir fonksiyon verilmeli.
def g(x): return 2.0**(-x)
0, 10) seritoplam(g,
1.9990234375
Aynısını bir isimsiz fonksiyonla şöyle yapabiliriz:
lambda x: 2.0**(-x), 0, 10 ) seritoplam(
1.9990234375
Burada lambda
ifadesini doğrudan doğruya seritoplam
’a bir parametre olarak verdiğimize dikkat edin. Bunu doğrudan def
ile yapamayız, yani
seritoplam( def f(x): 2.0**(-x), 0, 10 )
yazmak hatalıdır, çünkü def
yapısı bir fonksiyon nesnesi döndürmez. Buna karşılık lambda
bir ifadedir (“expression”); konduğu yerde bir fonksiyon nesnesi yaratır.
seritoplam
fonksiyonunu birçok farklı fonksiyon parametresi vererek denemek istiyor olabilirsiniz. Mesela $ 1+ 1/2 + 1/3 + + 1/10$ toplamını bulalım:
lambda x: 1/x, 1, 10 ) seritoplam(
2.9289682539682538
Elbette deneyeceğiniz her türlü fonksiyonu def
ile tanımlayarak da aynı sonuca ulaşabilirsiniz. Ama lambda
kullanmak burada daha fazla kolaylık sağlıyor.
Örnek: Sıralama
Listelere kısaca değinmiştik. Bir listenin elemanlarını sıralamak için sorted
fonksiyonu kullanılabilir. Söz gelişi:
= [ 6.1, 2.3, -5.6, 8.5, 4.0, -1.2, -3.4, 7.8]
L sorted(L)
[-5.6, -3.4, -1.2, 2.3, 4.0, 6.1, 7.8, 8.5]
Bazen alışılageldik sırayla değil de, başka bir düzene göre sıralamak isteyebilirsiniz. sorted
fonksiyonunun key
parametresine bir fonksiyon verirseniz, bu fonksiyon bütün elemanlara tek tek uygulanır ve sıralama bu sonuçlara göre yapılır. Söz gelişi, yukarıdaki listeyi sayıların mutlak değerlerine göre sıralamak için key
parametresine abs
fonksiyonunu verebilirsiniz.
sorted(L, key=abs)
[-1.2, 2.3, -3.4, 4.0, -5.6, 6.1, 7.8, 8.5]
Gördüğünüz gibi burada da fonksiyon alan bir fonksiyonumuz var. Aynı sıralamayı bir isimsiz fonksiyonla şöyle elde ederdik.
sorted(L, key=lambda x: x if x>0 else -x)
[-1.2, 2.3, -3.4, 4.0, -5.6, 6.1, 7.8, 8.5]
Üçlü if-else yapısını başka bir bölümde daha ayrıntılı işlemiştik.
Başka bir örnek olarak, listelerden oluşan bir listeyi sıralamayı ele alalım. key
parametresini kullanmadan sorted
ilk elemana göre sıralama yapar.
= [ [1, "merhaba"], [6, "hello"], [-2, "guten tag"] ]
L sorted(L)
[[-2, 'guten tag'], [1, 'merhaba'], [6, 'hello']]
Eğer ikinci elemana göre sıralama yapmak istiyorsak, key
parametresine listenin ikinci elemanını (1 indeksli) veren bir fonksiyon koyarız.
sorted(L, key = lambda i: i[1])
[[-2, 'guten tag'], [6, 'hello'], [1, 'merhaba']]
Örnek: Sayısal integral hesaplama
Bir belirli integral \(\int_a^b f(x)\mathrm{d}x\) matematiksel olarak bir “fonksiyonel”dir: Bir fonksiyon alır ve bir sayı verir. Bir fonksiyonun integralini alırken de isimsiz fonksiyonlar kullanabiliriz.
Bir belirli integrali sayısal olarak hesaplamanın pek çok yöntemi vardır. Bunlara şimdilik hiç girmeden, SciPy paketi içindeki integral alma modülünü kullanalım ve \(\int_{-2}^4 6x^3 - 4x^2\, \mathrm{d}x\) integralini hesaplayalım.
import scipy.integrate
lambda x: 6*x**3 - 4*x**2, -2, 4) scipy.integrate.quad(
(264.00000000000006, 3.703317987014864e-12)
İlk sayı belirli integralin değeri, ikinci sayı ise tahmini hata miktarıdır.
Örnekler çoğaltılabilir: Bir fonksiyonun optimal noktalarını bulan bir fonksiyon, türev alan bir fonksiyon, veya bir fonksiyonun grafiğini çizen bir fonksiyon, isimsiz fonksiyonlar alarak daha kolay şekilde işlenebilirler. Ayrıca grafik arayüz oluştururken de isimsiz fonksiyonlar kolaylık sağlar.
İsimsiz fonksiyonların iyi ve kötü taraflarını şöyle özetleyebiliriz:
lambda
bir fonksiyon nesnesi döndürür,def
ise bir komuttur, bir değer döndürmez. Bu yüzdenlambda
ile tanımlanan isimsiz fonksiyonları başka ifadelerin içine, söz gelişi atamalara veya fonksiyon parametrelerine yerleştirebiliriz.- İsimsiz fonksiyonlarla karmaşık işlemler yapılamaz. Kısa, bir seferlik kullan-at fonksiyonlar tanımlamak için daha uygundurlar.
- İsimsiz fonksiyonlarla kodunuz daha okunaklı olabilir. Sıralama vb. işlemlerde
def
ile farklı fonksiyonlar tanımlamak isim alanının bir sürü tek kullanımlık fonksiyon ismiyle dolmasına sebep olur. Dahası, kaynak kodunuz uzunsa, fonksiyonun tanımlandığı yer ile kullanıldığı yer arasında kalan mesafe yüzünden, kodda ne yapıldığını anlamak için yukarı aşağı kaydırmak gerekebilir. Bu tür küçük işlerdelambda
ifadeleri kullanmak, fonksiyonun yapısını açıkça gösterdiği için kodun anlaşılırlığını artırır.
İsimsiz fonksiyonlarda parametre kullanımı kuralları isimli fonksiyonlardakilerle aynıdır. Örneğin birden fazla parametre alabilirler.
= lambda x, y: x+y
f 4,5) f(
9
"hello","world") f(
'helloworld'
Parametre paketleme / parametre çözme kuralları isimsiz fonksiyonlarda da aynen geçerlidir.
= lambda *p : sum(p)
g 0.25, 2, 13) g(
15.25
Parametrelere varsayılan değerler atanabilir.
= lambda x, n=2: x**n # üst alma. Varsayılan kuvvet 2.
h print(h(3)) # 3**2
print(h(3,5)) # 3**5
9
243