Register Now

Login


Lost Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Add question

You must login to ask question .

Login


Register Now

Merhaba, kayıt formu üzerinden kayıt olabilirsiniz. Fakat sosyal medya ile kayıt olmanızı önermekteyiz.

Python’da Fonksiyonel Programlamaya Giriş: [2] Lambda, Map, Filter, Reduce

Python’da Fonksiyonel Programlamaya Giriş: [2] Lambda, Map, Filter, Reduce

Önemli Not: Python 3 kullanıyorum. Bu not, serinin devamı için de geçerlidir. Python’un ikinci sürümünden sonra bu fonksiyonlarda özellikle performans vaadeden radikal değişimler oldu. Bunlara değinmeyeceğim ve üçüncü sürümü kullandığınızı varsayacağım. Özellikle hangi sürüm diye soracak olursanız, 3.5.1 iyidir.

Şimdi, geçen yazımızı “Döngülerden kaçınmalıyız, performansta düşüşlere sebep olabiliyor.” demiştik. Öyle, ama konunun biraz daha ayrıntısına inelim.

Performans O Kadar Önemli Mi?

Aynı çıktıyı aldığınız iki fonksiyon (adları da func) olsun ve bu iki fonksiyon A ve B olarak adlandırdığımız iki ayrı kütüphane veya dilde bulunsun. Fonksiyonun içeriğini, girdi olarak ne aldığını ve çıktı olarak ne verdiğini gözardı edelim, sadece aynı şeyi yaptıklarını biliyoruz. Bunun yerine odaklanacağımız şey dönüş süreleri:

Aralarındaki süre farkı sadece 0.06 saniye ve A, B’den daha önce yanıt veriyor. Şimdi, kendimize şu soruyu soralım:

Gerçekten A.func kullanmalı mıyım?

Bazen bir şeyler geliştirirken ya da yeni başlarken aklımızda bu tarz şeyler kalabiliyor? “Go/Rust, hızlı, neden Python kullanayım ki?” ya da “Django Flask’ı hız konusunda döver (?).” (sakin olun) gibi… Ama gerekli mi? Bu, ayrı bir konu. Ama sizi şu sözle bırakırsam sorunuz kısmen yanıtlanmış olacaktır, bunu başka bir güne bırakalım.

Donald Knuth

Donald Knuth

Sorun şu ki, programcılar (programmers) zamanlarının fazla büyük bir kısmını yanlış zamanda ve yanlış yerde verimliliği sağlamak için harcıyor. Gereksiz performans optimizasyonu oldukça kötü bir pratiktir (premature performance optimization is root of all evil).

Knuth, D. E. (2007:671, January). Computer programming as an art. In ACM Turing award lectures (p. 1974). ACM.

Dolayısıyla, örneğin, map fonksiyonunu kullanmanın list comprehension yönteminden daha hızlı olduğunu söylesem, hatta veriler ve bağlantılar da sunsam, (i) kimi zaman map fonksiyonunun daha yavaş olduğu bağlamlar olabileceğini ve (ii) fonksiyonel programlamayı performans vaadeden değil, kimi zaman uygun bir bakış açısı olarak algılamanızı isterim.

Ayrıca, yine belirtmem ya da hatırlatmam gerek, FP’de genel olarak döngülerden kaçınmanız gerekiyor. Bu fonksiyonlar size bu konuda yardımcı olacak.

Eğer, performans konusunda çok da merak ediyorsanız, şurada yüz bin sayıyı str türüne çevirmemiz gereken bir bağlamı (i) for döngüsüyle, (ii) sadece iterator döndüren map fonksiyonuyla ve (iii) iterator‘den sonra list‘e çeviren bir map fonksiyonuyla denedim ve saniyeleri aldım. Şurada da bir tartışmaya gözatabilirsiniz.


lambda

lambda, Python’da geçici veya tek satırda yazabileceğiniz fonksiyonlar oluşturabilmenizi sağlayan bir yöntemdir. Dediğim gibi, tek satırda yazılır.

lambda’da gördüğünüz üzere return yoktur. Zaten tek satırda olduğu için, kendisinden sonra gelen return değeridir. Örneğin, iki sayıyı çarpan bir lambda fonksiyonu yazalım:

Aslında bu, aşağıdaki fonksiyonla aynı:

Peki, iyi de, bir fonksiyon yazmak varken, neden lambda kullayım ki?

İyi bir soru. lambda’yı kullanmalısınız dediğim bir durum yok, kullanınca kızlar size teklif etmiyor. Ancak bir fonksiyonu;

  • tek seferliğine kullanmak istiyorsanız, daha teknik bir anlatımla, bellekte tutamayacak kadar önemsiz ama belli tek bir yerde kullanmanız gerekecek kadar önemliyse,
  • girdi olarak (high-order functions) kullanmanız gereken gereksiz bir fonksiyon varsa

kullanabilirsiniz.

Ayrıca, lambda size fonksiyonu verecektir, yani çağırmayacaktır. Yani;


map

map fonksiyonu, eğer FP ile ilgileniyorsanız, herhalde en sık kullanacağınız fonksiyondur. Bir listedeki her bir elemana verdiğiniz fonksiyonu uygulamanızı sağlar. Yani a diye bir listemiz ve ne olduğunu bilmediğimiz func diye bir fonksiyonumuz var, map ile şuna benzer bir şey yapıyoruz:

Format şu şekilde;

Örneğin bir int türünü str türüne şu şekilde çevirebiliyoruz:

Bu, bir fonksiyon aslında. Girdi olarak aldığı nesneyi, eğer mümkünse, str türüne çeviriyor. Bunu, map ile bir listeye şu şekilde uyguluyoruz:

Evet, gayet iyi. Şimdi sayılardan ilkine erişelim:

Bir dakika! Böyle bir şey beklemiyordum. Ne oluyor burada? Neden erişemiyorum?

Bunun sebebi map fonksiyonunun döndürdüğü şeyin bir generator olması. Bu, başka bir günün konusu, ama kısaca bahsetmek gerekirse;

  • map, verilen listeyi ve fonksiyonu alıyor.
  • Bir iteration yapılana kadar (yani teker teker çağırılana kadar) bunlar sadece metod olarak bellekte tutuluyor.
  • Her çağırılmada listeden sırayla eleman alınıyor.
  • O eleman, fonksiyona girdi olarak veriliyor.
  • Değer döndürülüyor.

Kafanız karıştı mı? Boşverin. Değerleri listeye çevirmek için şunu yapmanız gerekiyor.

“Yok, ben anlarım, sen gönder çorbayı.” diyorsanız, yukarıdaki işlemi boşvererek aşağıdaki gibi iki kere yapın, sonucu görün:

Basitçe, cursor gibi bir şeyimiz var ve artık listenin sonunda olduğu için daha fazla ilerleyemiyor. Dolayısıyla siz tekrar tekrar ne kadar çağırırsanız çağırın, boş döndürecektir.

Ayrıntısıyla, generator artık yield‘de son noktaya ulaştığı ve liste de StopIteration verdiği için daha fazla değer döndüremiyor.

Dolayısıyla map ile elde ettiğiniz değeri, hemen, tek seferde bir yerde saklamanız gerekiyor.

Sıkça sorulabileceğini düşündüğümüz sorular;

İyi de, bir değeri tek bir sefer döndürebilmesi, tutarsız değil mi?

Değil.

Neden böyle bir şey yapıyor ki?

Aslında, generator fonksiyonlar, bellek yönetimi için oldukça iyi. Tabi bu başka günün konusu. Başka bir yazıda generator fonksiyonlardan bahsedeceğiz.

Şimdi biraz daha açılalım ve gerçek dünya sorunlarına dönelim, bu kısım fazla terleme, deride kaşıntı, kızarma gibi yan etkiler yapabilir. Şöyle bir sınıfımız olsun ve örnekleyelim de:

Gördüğümüz üzere, contacts listemizin içerisinde çeşitli nesnelerimiz var. Şimdi ise öyle bir fonksiyon oluşturmalıyız ki, bir nesnenin sadece e-postasını almalıyız:

Yalnız, burada dikkat etmeniz gereken bir nokta var. Biz, “sadece bir nesnenin e-postasını döndüren” bir fonksiyon yazdık. Böyle olmalı. Yani amacımız;

Güzel, şimdi ise bunu map ile kullanalım ve listeye çevirelim.

Evet. Bu güzel oldu. Ama tabi, eğer isterseniz eposta_ver fonksiyonunu lambda ile de verebilirsiniz. Basit bir fonksiyon sonuçta, bu durumda şöyle yapabilirsiniz.

Bu yöntemi veritabanından çektiğiniz verilerde, CSV veya JSON dosyalarında, tablolarda kullanabilirsiniz.


filter

filter da map ile aynı mantıkta çalışır:

Ama filter fonksiyonunda amaç farklıdır, amacımız ayıklamaktır. Ancak filter fonksiyonuna verdiğimiz bizim fonksiyonumuz biraz özel bir fonksiyon. Fonksiyonumuzun, ne olursa olsun, mutlaka bool değer döndürmesi (True ya da False) gerekiyor. filter fonksiyonu, True döndüren fonksiyonun verisini verir, False olanınkini ise geçer. Yine, Contact sınıfımızla gidelim ve bir örnek yazalım (bundan sonra tek satırda yazabileceğim ve basit olan fonksiyonlar için lambda kullanacağım.):

Burada işaretli olan satırdaki lambda fonksiyonuna bakalım. Burada, bir nesnedeki soyadı değerinin “Biri” str türüne eşit olup olmadığını denetliyoruz. Eğer eşit ise, True döndürecek, değilse False. False döndürürse oluşturduğumuz listede olmayacak, True döndürürse olacak.

Yani, burada demek istediğimiz aslında “Listede soyadı “Biri” olanları al ve bir listeye dönüştür.”. Hepsi bu. Ayıklıyoruz yani.


reduce

reduce fonksiyonu, Python’un üçüncü sürümüyle beraber gömülü niteliğini yitirdi. Yani doğrudan kullanamıyorsunuz. Bunun yerine functools adlı bir paketin içerisinde yer almakta. Ayrıca belirtmeliyim ki reduce, map ve filter gibi generator döndürmüyor. Fonksiyonunuz ne döndürüyorsa onu döndürüyor. Bunlar bir yana, gömülü olmadığını ele alırsak reduce kullanmadan önce, şu şekilde import etmeniz sizin yararınızadır:

reduce fonksiyonuyla, listedeki ya da iterator niteliğine sahip bir nesnedeki/türdeki bütün elemanların belirli niteliklerini kullanıp onları tek bir değere indirirsiniz. Yani, bir listenizin olduğunu düşünün, içinde bir sürü int türü var, bütün sayıları toplamanız gerekiyor, yani sonunda tek bir değer alacaksınız: Tüm sayıların toplamı.

İşaretli satırdaki lambda fonksiyonumuza bakalım. İki değer almış: x ve y. Doğal olarak, reduce fonksiyonu, verilen bir listede ilerlerken bulunduğu değer ile bir sonrakini işler. Yani, reduce fonksiyonuna vereceğiniz fonksiyon, map ve filter fonksiyonlarına ziyade iki argümanlı olmak zorundadır.


map-filter-reduce Neye Benzer?

Olur da yukarıdakileri okudunuz ama aklınızda çok kalmadıysa, Tugdual Grall’ın Twitter’ında paylaştığı şu görsel, olayı özetler nitelikte (ne yazık ki sadece map ve reduce var).

Yazının bu kısmına kadar geldiğiniz için teşekkürler. Üçüncü yazıyı yazmam için bir şeyleri anlamam gerekiyor, dolayısıyla gelmesi belki bir ayı bulabilir. Bu yazıda değiştirilebilirlik sorununu (mutation problem) ele alacağım ve çözüm olarak Python’da kalıcı veri yapılarının (persistent data structure) hangi kütüphane altında kullanılabileceğinden söz edeceğim.

About Erdin ErayÇırak

Dilbilim öğrencisi, programlamaya meraklı, INTJ kişilik. Genelde web geliştirmeyle ilgilensem de kendi alanım sebebiyle doğal dil işleme ve bilgisayar dilbilimle de yakından ilgiliyim.

Follow Me

Comments ( 3 )

  1. Eline sağlık, çok faydalı yazı oldu.

  2. Çok açıklayıcı olmuş teşekkür ederim. Şunu merak ediyorum, bu lamba – map – filter fonksiyonları sadece python’a özel şeyler mi? yoksa her dilde var mı? Daha önce C# kullandım böyle ifadelere rastlamadım.

Leave a reply

Captcha Click on image to update the captcha .