Python'da Değişken Kapsamı: local, global ve nonlocal Nasıl Çalışır
Python'da bir değişken adının hangi kapsamda çözüldüğünü, global ve nonlocal bildirimlerini ve UnboundLocalError'ın nedenini örneklerle öğrenirsiniz.
Bir bakışta
- Ad, LEGB sırasıyla aranır. Önce yerel, sonra çevreleyen fonksiyon, global ve yerleşik kapsam kontrol edilir.
- Atama adı yerel yapar. Fonksiyonda herhangi bir yerde atanan ad, tüm gövdede yerel kabul edilir.
- UnboundLocalError bu kuraldan doğar. Atamadan önce okuma, henüz değeri olmayan yerel adı okumaya çalışır.
- global modül düzeyine, nonlocal çevreleyen fonksiyona bakar. İkisi de yalnız yeniden atama gerektiğinde şarttır.
- En iyi çözüm parametre ve return kullanmaktır. Kapsama bağımlılığı azalan fonksiyon test edilebilir olur.
Kapsam nedir, Python isimleri nasıl çözer
Kapsam (scope), bir değişken adının kodun hangi bölümünde geçerli olduğunu belirler. Python'da bir ad kullandığınızda yorumlayıcı onun hangi değere bağlı olduğunu bulmak için belirli bir sırayla farklı kapsamlara bakar. Bu sıra bozuk anlaşıldığında, "değişken tanımlı ama hata veriyor" türü şaşırtıcı hatalar ortaya çıkar.
Python belgelerine göre bir ad kullanıldığında, en yakın kapsayan kapsamda çözülür. Pratikte bu sıra, İngilizce baş harflerle LEGB olarak hatırlanır: önce fonksiyonun kendi yerel (Local) kapsamına bakılır, sonra varsa onu çevreleyen fonksiyonların (Enclosing) kapsamına, ardından modül düzeyindeki global (Global) kapsama, en sonunda da Python'un yerleşik adlarının (Builtins) bulunduğu kapsama. İlk bulunan ad kullanılır.
Bir ad, bir kapsamda bağlandığında (atama, for döngüsü, import, fonksiyon parametresi gibi) o kapsama aittir. Fonksiyon içinde bağlanan ad yerel, modül düzeyinde bağlanan ad globaldir. Python'da ayrıca bildirim yoktur; hangi adların yerel olduğunu yorumlayıcı, fonksiyon gövdesinin tamamını tarayıp bağlama işlemlerini bularak belirler.
Bu kuralları bilmek, ad çakışmalarını ve beklenmedik hataları açıklamanın anahtarıdır. Fonksiyonların nasıl tanımlandığı ve çağrıldığı ayrı bir konudur; burada yalnız adların nasıl çözüldüğü ele alınır.
Ad, en içten en dışa doğru sırayla aranır
LEGB arama sırası
- LocalÇalışan fonksiyonun kendi adları.
- EnclosingÇevreleyen fonksiyonların adları.
- GlobalModül düzeyindeki adlar.
- Builtinsprint, len gibi yerleşik adlar.
Yerel ve global değişkenler
Fonksiyon içinde değer atanan bir ad yerel olur ve fonksiyon bittiğinde kaybolur. Dışarıdaki aynı adlı bir değişkene dokunmaz:
mesaj = "genel"
def goster(): mesaj = "yerel" print(mesaj)
goster() print(mesaj)
Bu kod önce "yerel", sonra "genel" yazdırır; çünkü fonksiyondaki atama yeni bir yerel ad üretmiştir. Global ad ise fonksiyon içinde yalnız okunuyorsa, herhangi bir ek şey gerekmeden görülür:
def oku(): print(mesaj)
Burada fonksiyonda mesaj için bir atama yoktur, bu yüzden arama global kapsama kadar uzanır ve "genel" bulunur. Python belgelerine göre kapsam çözümlemesi çalışma zamanında yapılır: global değişkenin değeri fonksiyon çağrıldığı andaki haliyle okunur.
Sınıf gövdesindeki adlar bir istisnadır. Sınıf içinde tanımlanan bir ad, o sınıfın metotlarından doğrudan görünmez; belgeler, sınıf gövdesinin ad alanının metotlara kapsayan kapsam olarak uzanmadığını belirtir. Metotlarda sınıf özniteliklerine self ya da sınıf adı üzerinden erişilir.
Genel kural: bir fonksiyon, ihtiyaç duyduğu değeri parametre olarak almalı ve sonucu return ile vermelidir. Global duruma bağımlı fonksiyonlar test edilmesi ve anlaşılması zor kod üretir.
UnboundLocalError: atama adı yerel yapar
En sık karşılaşılan kapsam hatası şu örnektir:
oran = "yuksek"
def degistir(): print(oran) oran = "dusuk"
degistir()
Beklenti, önce global değerin yazdırılması olabilir. Oysa bu kod UnboundLocalError verir. Sebep Python'un kuralındadır: Python belgelerine göre bir ad bir blokta herhangi bir yerde bağlanıyorsa, o bloktaki tüm kullanımlar o yerel ada ait kabul edilir. Fonksiyonda oran = "dusuk" ataması olduğu için oran, fonksiyonun tamamında yereldir; print(oran) satırı ise henüz değer atanmadan okumaya çalışır.
Hata, atamayı yazdığınız satırdan önceki bir okumada ortaya çıkar. Atamayı fonksiyonun en altına koymuş olsanız da etkisi tüm gövdeye yayılır. Bu durumda mesaj "yerel değişken atanmadan önce başvuruldu" anlamına gelir.
Çözüm niyetinize bağlıdır. Yerel bir değişken istiyorsanız, onu kullanmadan önce atayın ya da farklı bir ad seçin. Globalin kendisini değiştirmek istiyorsanız global bildirimi kullanmalısınız. UnboundLocalError, NameError'ın bir alt sınıfıdır; hiçbir kapsamda bulunamayan bir ad ise düz NameError üretir.
Aynı sorun, parametre kullanmayan ve döngüde değişkeni güncelleyen kodlarda da çıkar. Hatanın mesajını dikkatle okuyun: hangi adın hangi satırda sorun yarattığını gösterir.
global ile global değişkeni değiştirmek
Fonksiyon içinden global bir adı değiştirmek istiyorsanız, fonksiyonda önce global bildirimi yapmanız gerekir:
durum = "kapali"
def ac(): global durum durum = "acik"
ac() print(durum)
Python belgelerine göre global bildirimi, listelenen adların bu blokta modülün üst düzey ad alanındaki bağlamalara işaret ettiğini söyler. Bildirim, o ad kullanılmadan önce yazılmalıdır; adın bu blokta kullanılmasından sonra yazılan global bildirimi hata verir.
Burada önemli bir ayrım vardır: değişkeni yeniden atamak ile nesneyi değiştirmek farklı şeylerdir. Global bir liste varsa, fonksiyon içinde liste.append(deger) demek adı yeniden bağlamaz, mevcut nesneyi değiştirir; bu nedenle global bildirimi gerektirmez. Ancak liste = [] gibi yeni bir nesneye atama yapıyorsanız, ad yeniden bağlanıyordur ve global gerekir.
Global bildirimi çalışır, ama çok kullanıldığında kodu izlemesi zor hale getirir. Birçok fonksiyonun aynı globali değiştirdiği bir programda, değerin nerede değiştiğini bulmak güçleşir. Daha sağlıklı yollar, değeri parametre ve dönüş değeriyle taşımak ya da durumu bir sınıfta tutmaktır.
nonlocal ile iç içe fonksiyonlar
Bir fonksiyonun içinde başka bir fonksiyon tanımladığınızda, iç fonksiyon dış fonksiyonun değişkenlerini okuyabilir. Ancak onları yeniden atamak için nonlocal bildirimi gerekir:
def kayit_tut(): son = None
def guncelle(deger): nonlocal son son = deger
def oku(): return son
return guncelle, oku
Python belgelerine göre nonlocal, listelenen adların en yakın çevreleyen fonksiyon kapsamındaki daha önce bağlanmış değişkenlere işaret etmesini sağlar. Ad hiçbir çevreleyen fonksiyonda yoksa derleme zamanında SyntaxError oluşur. nonlocal modül düzeyindeki adlara ulaşmaz; bunun için global gerekir.
Bu yapıya kapanış (closure) denir ve durum tutan küçük fonksiyonlar, geri çağırmalar ve dekoratörler için kullanışlıdır. Yukarıdaki örnekte iki iç fonksiyon aynı son değişkenini paylaşır; biri günceller, diğeri okur. Dış fonksiyon geri döndükten sonra bile bu değişken iç fonksiyonlar tarafından yaşatılır.
Eğer iç fonksiyon dış değişkeni yalnız okuyorsa nonlocal gerekmez. İç fonksiyonda değişkene atama yapıyorsanız ve nonlocal yazmazsanız, Python bunu yeni bir yerel değişken olarak yorumlar ve dış değişken değişmez; bu da sessiz bir hataya yol açabilir.
Kapsam hatalarını önlemek
En sağlam yaklaşım, kapsama bağımlılığı azaltmaktır. Fonksiyona ihtiyacı olan veriyi parametre olarak verin, sonucu return ile alın. Bu, fonksiyonu tek başına anlaşılır ve test edilebilir yapar ve global/nonlocal ihtiyacını büyük ölçüde ortadan kaldırır.
Ad seçiminde yerel adları global adlarla çakıştırmayın. Python'un yerleşik adlarını (list, sum, max, input gibi) değişken adı olarak kullanmak, builtins kapsamındaki adı gölgeler ve sonradan o işlevi çağırmak istediğinizde garip hatalar üretir. Bunlardan kaçının.
Döngü ve karşılaştırma yapılarında değişkenin sızması da kafa karıştırabilir. for döngüsünün hedef değişkeni, döngü bitince de erişilebilir kalır, çünkü for hedefi de bir bağlama işlemidir. Fonksiyon içinde küçük bir döngü yazıyorsanız bu değişken fonksiyonun yereli olur.
Kapsam hatasıyla karşılaştığınızda şu sırayı izleyin: hata mesajını okuyun, adın hangi satırda kullanıldığına bakın, aynı fonksiyonda o ada herhangi bir yerde atama olup olmadığını arayın, ardından hangi kapsamda olması gerektiğine karar verin. Çoğu zaman sorun, bir atamanın adı sessizce yerel yapmasıdır.
Büyük projelerde modül düzeyindeki değişkenleri sabitlerle sınırlayın ve bu sabitleri büyük harfle adlandırarak, değiştirilmemesi gerektiğini gösterin.
Sık yapılan hatalar
İlk hata, fonksiyonda bir adı hem okuyup hem sonradan atamaktır; UnboundLocalError buradan doğar. İkinci hata, global değeri değiştirmek için global bildirimi yazmamaktır: fonksiyonda yapılan atama yeni bir yerel ad üretir ve global değişmez, bu da sessiz bir mantık hatasıdır.
Üçüncü hata, global bildirimini adı kullandıktan sonra yazmaktır. Dördüncü hata, nonlocal ile global'i karıştırmaktır: biri çevreleyen fonksiyonlara, diğeri modül düzeyine bakar. Beşinci hata, yerleşik adları değişken adı yapmaktır.
Altıncı hata, değişken değiştirmek ile nesne değiştirmeyi karıştırmaktır: liste.append global gerektirmez ama liste = [] gerektirir. Yedinci hata, sınıf gövdesindeki adların metotlarda doğrudan görüneceğini varsaymaktır.
Son olarak, çok sayıda globalle çalışan bir betiği düzenli parçalara bölmeden büyütmek, kapsam sorunlarının sürekli geri dönmesine yol açar. Fonksiyonlara parametre ve dönüş değeri kazandırmak, çoğu kapsam sorununu kökünden çözer.
Bu beceriyi Mindset programlarında nerede çalışırsınız
Python programlama ve veri bilimi programlarındaki fonksiyonlar bölümü, parametre, dönüş değeri ve kapsamı temel konular olarak işler. Kapsam kurallarını bilmek, betiklerden gerçek uygulamalara geçerken hata ayıklamayı ve temiz kod yazmayı kolaylaştırır.
İlgili Mindset programı
Python ile Programlama Eğitimi
Sık sorulan sorular
Python'da local ve global değişken farkı nedir?
Fonksiyonun içinde bağlanan (atanan) değişken yereldir ve fonksiyon bitince kaybolur. Modül düzeyinde bağlanan değişken globaldir ve fonksiyonlarda okunabilir. Fonksiyondan global değişkeni değiştirmek için fonksiyonda global bildirimi yapmak gerekir.
UnboundLocalError neden oluşur?
Python belgelerine göre bir ad bir blokta herhangi bir yerde bağlanıyorsa, o bloktaki tüm kullanımlar yerel ada ait kabul edilir. Fonksiyonda atamadan önce adı okursanız, henüz değer almamış yerel adı okumaya çalışırsınız ve bu hata çıkar. Atamayı kaldırın ya da global bildirimi kullanın.
global ve nonlocal arasındaki fark nedir?
global, adı modülün üst düzey ad alanına bağlar. nonlocal ise en yakın çevreleyen fonksiyonun değişkenine bağlar ve çevreleyen fonksiyonda bulunmayan bir ad için SyntaxError verir. İkisi de yalnız yeniden atama gerektiğinde şarttır.
Global listeye global yazmadan eleman ekleyebilir miyim?
Evet. append gibi yöntemler mevcut nesneyi değiştirir, adı yeniden bağlamaz, bu yüzden global bildirimi gerekmez. Ancak aynı adla yeni bir liste atarsanız ad yeniden bağlanmış olur ve global gerekir.
Global değişken kullanmak kötü mü?
Okunabilir sabitler için sorun değildir. Ancak birçok fonksiyonun değiştirdiği global durum, kodun izlenmesini ve test edilmesini zorlaştırır. Parametre ve dönüş değeriyle veri taşımak ya da durumu bir sınıfta tutmak daha sağlıklıdır.