Python'da Özyineleme: Temel Durum, Küçültme ve Derinlik Sınırı
Beceri rehberi

Python'da Özyineleme: Temel Durum, Küçültme ve Derinlik Sınırı

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 5 dk okuma

Özyinelemeli fonksiyon kendi kendini çağırarak problemi küçültür. Rehber temel durum ve küçültme adımını, iç içe veri örneğini, derinlik sınırını ve tekrar eden hesabı önlemeyi anlatır.

Bir bakışta

  1. Özyineleme üç parçadan oluşur. Temel durum, küçültme ve birleştirme; biri eksikse fonksiyon sonlanmaz ya da yanlış sonuç verir.
  2. İç içe yapılarda doğal çözümdür. Klasör ağacı, kategori ağacı ve iç içe listeler derinlik bilinmediğinde özyinelemeyle sade çözülür.
  3. Python özyineleme derinliğini sınırlar. Sınır aşılınca RecursionError oluşur; sınırı yükseltmek çökme riski taşır.
  4. Düz tekrar için döngü daha güvenlidir. Her özyinelemeli çözüm döngüyle de yazılabilir; okunurluk belirler.
  5. Tekrar eden alt hesap yavaşlık yaratır. Önbellek kullanmak ya da döngüye geçmek sorunu giderir.

Özyineleme nedir ve ne zaman akla gelir

Özyinelemeli (recursive) fonksiyon, kendi kendini çağıran fonksiyondur. Fikir, büyük bir problemi aynı türden daha küçük bir probleme indirgemek ve bu indirgeme sonunda doğrudan cevaplanabilen bir noktaya ulaşmaktır. Matematikteki faktöriyel buna klasik örnektir: bir sayının faktöriyeli, o sayı ile bir eksiğinin faktöriyelinin çarpımıdır; en küçük değerde ise sonuç doğrudan bilinir.

Gerçek işlerde özyineleme, yapısı kendini tekrar eden verilerde doğal bir çözümdür. İç içe geçmiş listeleri düzleştirmek, bir klasör ağacındaki dosyaları gezmek, bir organizasyon şemasında bir yöneticiye bağlı herkesi bulmak, JSON gibi iç içe yapıları dolaşmak bunlardandır. Bu yapıların her biri aynı türden alt parçalar içerir; fonksiyon bir parçayı işler, alt parçalar için kendini çağırır.

Her özyinelemeli çözüm döngüyle de yazılabilir. Seçim, hangisinin problemi daha doğal anlattığına bağlıdır: yapı ağaç gibiyse özyineleme, düz bir tekrar söz konusuysa döngü genellikle daha sade ve daha güvenlidir.

Şekil 1

Her özyinelemeli çözüm üç parçadan oluşur

Üçü de yerinde olmazsa fonksiyon sonlanmaz ya da yanlış sonuç verir

  1. Temel durumDoğrudan cevaplanan, kendini çağırmayan en küçük durum
  2. KüçültmeHer çağrıda problemi temel duruma yaklaştıran adım
  3. BirleştirmeAlt çağrıdan dönen sonucu üst çağrıda işleyip döndürme
Kaynak: sayfa içeriğiMindset Institute

İlk örnek: faktöriyel

Faktöriyel en çok verilen örnektir; çünkü özyinelemenin üç parçasını açıkça gösterir. Aşağıdaki kodda BIR, tam sayı bir değerini taşıyan bir sabittir.

def faktoriyel(sayi): if sayi <= BIR: return BIR return sayi * faktoriyel(sayi - BIR)

Fonksiyonun ilk satırı temel durumu tanımlar: sayı bire eşit ya da küçükse sonuç doğrudan bir'dir. Bu satır olmazsa fonksiyon kendi kendini sonsuza dek çağırır. İkinci satır küçültme ve birleştirmeyi yapar: sayıyı bir azaltarak kendini çağırır, dönen sonucu sayıyla çarpar.

Çağrılar yığına eklenir. faktoriyel çağrıldığında fonksiyon bir kez daha çağırır, o bir kez daha çağırır ve temel duruma ulaşana kadar bu böyle sürer. Temel durumda dönen değer geriye doğru yığılmış çağrılara tek tek iletilir ve her biri kendi çarpımını yaparak sonucu üst çağrıya verir. Bu geri dönüş zinciri, özyinelemeyi izlerken akılda tutulması gereken kısımdır.

Özyinelemeyi anlamanın pratik yolu, küçük bir girdiyle çağrıları kağıt üzerinde adım adım yazmaktır. Hangi çağrının hangisini beklediğini görmek, fikri kavramak için en hızlı yoldur.

Şekil 2

Çağrılar iç içe birikir, sonuçlar geriye dönerek birleşir

Faktöriyel çağrısında yığının işleyişi

  1. Dış çağrıBüyük sayıyla başlar, alt çağrının sonucunu bekler
  2. Ara çağrılarHer biri bir küçük sayıyla çağrılır ve beklemeye geçer
  3. Temel durumDoğrudan değer döner; geri dönüş zinciri buradan başlar
Kaynak: sayfa içeriğiMindset Institute

İş örneği: iç içe yapıyı dolaşmak

Özyinelemenin gerçek gücü, derinliği önceden bilinmeyen yapılarda ortaya çıkar. Örneğin bir veri kaynağından iç içe listeler gelir: bazı öğeler tek değer, bazıları kendi içinde başka listeler barındırır. Döngüyle bunu yazmak, derinlik bilinmediğinden zordur; özyinelemeyle sade bir çözümdür:

def duzlestir(oge): if not isinstance(oge, list): return [oge] sonuc = [] for alt in oge: sonuc.extend(duzlestir(alt)) return sonuc

Temel durum: öğe liste değilse tek elemanlı bir liste olarak döndürülür. Aksi halde listenin her öğesi için fonksiyon kendini çağırır ve sonuçlar birleştirilir. Liste ne kadar derin olursa olsun kod aynı kalır.

Aynı kalıp bir klasör ağacı için de geçerlidir: bir klasörün içindeki her öğe dosyaysa işle, klasörse aynı işlevi onun için çağır. Organizasyon şeması, ürün kategori ağacı ve iç içe sözlük ya da JSON yapıları da bu desene girer. Ortak nokta, parçanın kendisinin de aynı türden olmasıdır.

Bir ayrıntı: özyinelemeli çözümü yazarken önce temel durumu, sonra küçültmeyi, en son birleştirmeyi yazın. Bu sıra, sonsuz döngüye düşme riskini baştan ortadan kaldırır.

Derinlik sınırı ve RecursionError

Python her çağrı için bellekte yer ayırdığından özyineleme derinliğine bir sınır koyar. Python belgelerine göre bu sınır, yorumlayıcı yığınının azami derinliğidir ve sonsuz özyinelemenin C yığınını taşırıp Python'u çökertmesini önler. Sınıra ulaşıldığında RecursionError oluşur.

Sınırı sys.getrecursionlimit() ile görür, sys.setrecursionlimit() ile değiştirebilirsiniz. Ancak belgeler, olabilecek en yüksek sınırın platforma bağlı olduğunu ve fazla yüksek bir sınırın çökmeye yol açabileceğini açıkça uyarır; bu yüzden değiştirmek dikkat gerektirir. Pratikte sınırı yükseltmek yerine çözümü yeniden düşünmek daha sağlıklıdır.

İki yol vardır. Birincisi, yinelemeyi döngüye çevirmek: toplama, sayma ya da düz bir tekrar söz konusuysa döngü hem hızlı hem güvenlidir. İkincisi, yapıyı açık bir yığın (liste) ile kendiniz yönetmek: ağaç dolaşımı derin olabiliyorsa, yapılacakları bir listede tutup while ile işlemek, çağrı yığınına bağımlılığı kaldırır.

Derinliğin çok büyüyebileceği veriyle çalışıyorsanız ilk baştan döngü ya da açık yığın düşünün.

Tekrar eden hesap, önbellek ve yaygın tuzaklar

Özyinelemenin gizli maliyeti, aynı alt problemin tekrar tekrar çözülmesidir. Fibonacci dizisi bunun klasik örneğidir: her çağrı iki alt çağrı yapar ve aynı değerler defalarca hesaplanır. Çağrı sayısı girdiyle çok hızlı büyür ve fonksiyon küçük sayılarda bile yavaşlar.

Çözüm, sonuçları saklamaktır. Python'un standart kütüphanesindeki functools modülünde bu iş için hazır bir önbellek dekoratörü vardır; bir fonksiyonu sarmalayıp aynı argümanla çağrıldığında önceki sonucu döndürür. Böylece özyinelemeli yapıyı koruyup tekrarlı hesabı ortadan kaldırabilirsiniz. Bunun işe yaraması için fonksiyonun aynı girdiye her zaman aynı sonucu vermesi gerekir.

Sık yapılan hatalar:

  • Temel durumu unutmak ya da yanlış yazmak: fonksiyon sonlanmaz ve RecursionError ile durur.
  • Problemi küçültmeyi unutmak: aynı argümanla kendini çağıran fonksiyon asla temel duruma ulaşmaz.
  • Dönen değeri kullanmamak: alt çağrının sonucunu return etmeden bırakmak, üst çağrıda None sonucuna yol açar.
  • Değiştirilebilir varsayılan argümanı çağrılar arasında paylaşmak: ara sonuç listesini varsayılan değer yapmak, çağrılar arasında birikime neden olur.
  • Gereksiz özyinelemeyi seçmek: basit bir toplama ya da sayma için döngü daha okunaklıdır.

Hata ayıklarken fonksiyonun başına geçici bir yazdırma satırı ekleyip her çağrıda girdiyi ve girinti düzeyini göstermek, çağrı zincirini görünür kılar.

Bu beceriyi Mindset programlarında nerede çalışırsınız

Python ile Programlama Eğitimi Fonksiyonlar modülünü, Data Science Bootcamp Fonksiyonlar ve Veri Yapıları modüllerini içerir; özyineleme bu iki başlığın birleştiği, iç içe verilerle çalışırken en çok karşılaşılan tekniktir. Hiyerarşik verileri, kategori ağaçlarını ya da iç içe JSON yapılarını işleyen bir analist için özyinelemeyi tanımak, çözümü ilk denemede doğru kurmayı kolaylaştırır.

İlgili Mindset programı

Python ile Programlama Eğitimi

Sık sorulanlar

Sık sorulan sorular

Özyineleme ile döngü arasındaki fark nedir?

İkisi de tekrar sağlar. Döngü aynı kodu sırayla çalıştırır; özyineleme ise problemi daha küçük aynı türden bir probleme indirger. Veri ağaç gibi iç içeyse özyineleme daha doğal, düz bir tekrar varsa döngü daha sade ve çoğu zaman daha güvenlidir. Her özyinelemeli çözüm döngüyle de yazılabilir.

RecursionError neden oluşur, nasıl çözülür?

Çağrı derinliği Python'un yorumlayıcı yığın sınırını aştığında oluşur. En sık neden, temel durumun eksik ya da hatalı olması ve problemin küçültülmemesidir. Önce bu ikisini kontrol edin. Veri gerçekten derinse sınırı yükseltmek yerine döngüye ya da açık yığına geçmek daha güvenlidir.

Özyinelemeli fonksiyon her zaman yavaş mıdır?

Hayır, ama aynı alt problemi tekrar tekrar çözüyorsa yavaşlar. Fibonacci bunun tipik örneğidir. Sonuçları saklayan bir önbellek kullanmak ya da çözümü döngüyle yazmak bu sorunu giderir. Yapı ağaç gibiyse özyineleme, her düğümü bir kez gezdiği için sorunsuz çalışır.

Temel durum tam olarak nedir?

Fonksiyonun kendini çağırmadan doğrudan cevap verdiği en küçük durumdur. Faktöriyelde en küçük sayı, listeyi düzleştirmede liste olmayan öğe, klasör gezintisinde dosya olması gibi. Temel durum olmadan özyineleme sona ermez; ayrıca her çağrının problemi bu duruma yaklaştırdığından emin olmalısınız.

Python'da özyineleme derinliği neden sınırlıdır?

Python belgelerine göre sınır, sonsuz özyinelemenin C yığınını taşırıp yorumlayıcıyı çökertmesini önler. Her çağrı bellekte yer kapladığı için sınırsız derinlik güvenli değildir. Sınır değiştirilebilir ama belgelerin uyardığı gibi aşırı yüksek bir değer çökmeye yol açabilir.

Python Özyinelemeli (Recursive) Fonksiyon Nasıl Yazılır?