Python bir adı ararken sırasıyla dört kapsama bakar: önce fonksiyonun kendi yerel (local) adları, sonra varsa onu içeren fonksiyonların adları (enclosing), sonra modülün global adları, en son da print, len gibi yerleşik (built-in) adlar. Bu sıra baş harflerinden ötürü kısaca LEGB diye anılır. Bir fonksiyonun içinde bir ada atama yaptığınız anda o ad, fonksiyonun tamamında yerel sayılır. Global bir değişkeni fonksiyon içinden yeniden bağlamak için global, iç içe fonksiyonlarda dıştaki fonksiyonun değişkenini değiştirmek için nonlocal kullanılır.

Bu yazıda kapsam kurallarını resmî Python belgelerindeki (Tutorial'ın sınıflar bölümü, Language Reference'ın Execution model ve Simple statements bölümleri, Programming FAQ) tanımlara dayanarak anlatıyoruz: ad alanı nedir, UnboundLocalError neden çıkar, closure nasıl çalışır, döngüde oluşturulan lambda'lar neden aynı sonucu verir ve sınıf gövdesi neden özel bir durumdur. Bütün örnekler Python 3.15.2 ile çalıştırıldı. Fonksiyon temelleri için def ile fonksiyon tanımlama yazısına bakabilirsiniz.

Ad alanı ve kapsam: iki temel kavram

Tutorial'a göre bir ad alanı (namespace), adlardan nesnelere bir eşlemedir. Yerleşik adlar kümesi, bir modülün global adları ve bir fonksiyon çağrısının yerel adları birer ad alanıdır. Farklı ad alanlarındaki adlar arasında hiçbir ilişki yoktur: İki modül aynı adlı bir fonksiyon tanımlayabilir ve karışıklık olmaz, çünkü kullanan taraf modul.fonksiyon biçiminde yazar.

Ad alanlarının ömürleri farklıdır. Yerleşik adlar yorumlayıcı açılırken oluşur ve hiç silinmez. Bir modülün global ad alanı modül okunduğunda oluşur ve genellikle program bitene kadar yaşar; doğrudan çalıştırdığınız betik __main__ adlı bir modül sayılır. Bir fonksiyonun yerel ad alanı ise fonksiyon çağrıldığında oluşur, fonksiyon döndüğünde veya içinde yakalanmayan bir istisna yükseldiğinde ortadan kalkar; özyinelemeli her çağrının kendi yerel ad alanı vardır.

Kapsam (scope) ise bir ad alanının doğrudan, yani modul. gibi bir önek olmadan erişilebildiği program metni bölgesidir. Belgelerin vurguladığı nokta, kapsamların metne bakılarak, yani statik olarak belirlenmesidir: Bir fonksiyonun global kapsamı, fonksiyon nereden çağrılırsa çağrılsın, tanımlandığı modüldür.

LEGB: Python bir adı nerede arar?

LEGB: Python bir adı nerede arar? Local (yerel), Enclosing (çevreleyen), Global, Built-in (yerleşik)

Çalışma sırasında aynı anda erişilebilen iç içe kapsamlar şunlardır ve şu sırayla aranır:

SıraKapsamİçerdiği adlar
1Local (yerel)Çalışan fonksiyonun parametreleri ve içinde atanan adlar
2Enclosing (çevreleyen)İç içe fonksiyonlarda dıştaki fonksiyonların yerel adları, en yakından başlayarak
3GlobalModülün üst seviyesinde tanımlanan adlar
4Built-in (yerleşik)print, len, list, ValueError gibi builtins modülündeki adlar
python
mesaj = 'global'

def dis():
    ek = 'dış fonksiyon'
    def ic():
        yerel = 'iç fonksiyon'
        print(yerel, '|', ek, '|', mesaj, '|', len('abc'))
    ic()

dis()   # çıktı: iç fonksiyon | dış fonksiyon | global | 3

ic() içinde dört ad dört farklı kapsamdan bulundu: yerel kendi kapsamından, ek dıştaki fonksiyondan, mesaj modülden, len ise yerleşik adlardan. Ad hiçbir kapsamda yoksa NameError yükselir. Python'da if, for veya while blokları yeni bir kapsam oluşturmaz; Execution model belgesine göre kod blokları yalnızca modül, fonksiyon gövdesi ve sınıf tanımıdır. Bir if içinde atanan değişken, blok bittikten sonra da aynı fonksiyonda kullanılabilir.

Yerleşik bir adı gölgelemek

Arama sırası yerel ve globalden yerleşiğe doğru ilerlediği için, yerleşik bir adla aynı adı taşıyan bir değişken tanımlarsanız o kapsamda yerleşik ad görünmez olur. En sık görülen örnek bir değişkene list, str veya sum adını vermektir:

python
list = [1, 2]                # yerleşik list gölgelendi
try:
    list('abc')
except TypeError as e:
    print(e)                 # çıktı: 'list' object is not callable
del list                     # global adı sil, yerleşik yeniden görünür
print(list('abc'))           # çıktı: ['a', 'b', 'c']

Atama yapılan ad yereldir: UnboundLocalError

Kapsam kurallarının en şaşırtıcı sonucu şudur: Execution model belgesine göre bir kod bloğunun herhangi bir yerinde bir ada atama yapılıyorsa, o bloktaki bütün kullanımlar bu yerel ada atıf sayılır. Python'da değişken bildirimi olmadığı için yorumlayıcı yerel adları, bloğun tamamını tarayıp atamaları bularak belirler. Bu yüzden atamadan önce gelen bir okuma bile global değişkeni değil, henüz değeri olmayan yerel değişkeni okumaya çalışır. Programming FAQ'daki örneğin Türkçe uyarlaması:

python
sayac = 10

def oku():
    print(sayac)       # yalnızca okuma: global sayac kullanılır

oku()                  # çıktı: 10

def artir():
    print(sayac)       # sayac aşağıda atandığı için yerel sayılır
    sayac += 1

artir()
# hata: UnboundLocalError: cannot access local variable 'sayac' where it is not associated with a value

sayac += 1 bir atama olduğu için derleyici sayac'ı artir()'ın yerel değişkeni olarak işaretler; önceki print(sayac) değeri olmayan bu yerel değişkene ulaşmaya çalışır ve hata alır. UnboundLocalError, NameError'ın bir alt sınıfıdır. FAQ bu tasarımın gerekçesini de açıklar: Atanan değişkenler için global zorunluluğu istenmeyen yan etkilere karşı bir engel oluşturur; her okumada global istenseydi, her yerleşik fonksiyon ve içe aktarılan modül için de bu bildirimi yazmanız gerekirdi.

global: modül değişkenini fonksiyondan değiştirmek

global ad deyimi, o fonksiyon içindeki ad kullanımlarının modülün global adına gitmesini sağlar. Ad modülde yoksa, atama yapıldığında global ad alanında yeni bir değişken oluşur. Simple statements belgesine göre global deyimi ait olduğu kapsamın tamamına uygulanır ve adın o kapsamdaki ilk kullanımından önce yazılmalıdır.

python
sayac = 0

def artir():
    global sayac
    sayac += 1

artir()
artir()
print(sayac)          # çıktı: 2

def olustur():
    global yeni_ad
    yeni_ad = 'fonksiyon içinde oluştu'

olustur()
print(yeni_ad)        # çıktı: fonksiyon içinde oluştu

global deyimini yanlış yere koymak derleme aşamasında yakalanır. Python 3.15.2'nin verdiği mesajlar şunlardır: ad global'dan önce okunursa SyntaxError: name 'x' is used prior to global declaration, önce atanırsa SyntaxError: name 'x' is assigned to before global declaration, aynı ad hem parametre hem global yapılırsa SyntaxError: name 'a' is parameter and global. Modül seviyesinde bütün değişkenler zaten global olduğu için orada global yazmanın bir etkisi yoktur.

Değiştirmek ile yeniden bağlamak aynı şey değil

global yalnızca adı yeniden bağlamak (yeni bir nesneye atamak) için gerekir. Global bir listeye eleman eklemek veya bir sözlüğe anahtar yazmak, adı değiştirmez, aynı nesneyi yerinde değiştirir; bu yüzden global gerektirmez. Tutorial'ın ifadesiyle atamalar veriyi kopyalamaz, yalnızca adları nesnelere bağlar.

python
kayitlar = []

def ekle(x):
    kayitlar.append(x)     # nesneyi yerinde değiştirir, global gerekmez

def sifirla():
    kayitlar = []          # yeni bir YEREL ad oluşturur

ekle('a')
sifirla()
print(kayitlar)            # çıktı: ['a']

sifirla() global listeyi boşaltmadı; yalnızca kendi içinde aynı adlı yeni bir liste oluşturdu ve fonksiyon bitince bu liste kayboldu. Global listeyi gerçekten boşaltmak için kayitlar.clear() yazabilir ya da global kayitlar bildirip yeniden atayabilirsiniz.

nonlocal: dıştaki fonksiyonun değişkenini değiştirmek

nonlocal: dıştaki fonksiyonun değişkenini değiştirmek

İç içe fonksiyonlarda iç fonksiyon, dıştakinin değişkenlerini okuyabilir ama atama yaptığında yine kendi yerel değişkenini oluşturur. Dıştaki fonksiyonun değişkenini yeniden bağlamak için Python 3 ile gelen (PEP 3104) nonlocal deyimi kullanılır. Simple statements belgesine göre ad, çevreleyen fonksiyon kapsamlarından birinde önceden bağlanmış olmalıdır; birden fazla kapsamda varsa en yakını kullanılır. Ad hiçbir çevreleyen fonksiyonda yoksa SyntaxError: no binding for nonlocal 'x' found, modül seviyesinde kullanılırsa SyntaxError: nonlocal declaration not allowed at module level alırsınız.

python
def sayac_olustur():
    deger = 0
    def artir():
        nonlocal deger
        deger += 1
        return deger
    return artir

s = sayac_olustur()
print(s(), s(), s())                    # çıktı: 1 2 3
s2 = sayac_olustur()
print(s2())                             # çıktı: 1
print(s.__code__.co_freevars)           # çıktı: ('deger',)
print(s.__closure__[0].cell_contents)   # çıktı: 3

Her sayac_olustur() çağrısı kendi deger değişkenine sahip yeni bir artir fonksiyonu üretir; s ve s2 birbirini etkilemez. Bu yapıya closure denir: İç fonksiyon, dış fonksiyon bittikten sonra da kullandığı dış değişkenleri taşır. Data model belgesine göre bu bağlar fonksiyonun __closure__ özelliğindeki hücrelerde (cell) durur ve her hücrenin değeri cell_contents ile okunabilir. Global ve yerleşik adlar bu hücrelere dahil değildir.

Üç atama türünü yan yana görmek

Tutorial'daki scope_test örneği yerel, nonlocal ve global atamanın farkını tek seferde gösterir. Aşağıda aynı yapıyı Türkçe adlarla kurduk:

python
def kapsam_testi():
    def yerel_ata():
        deger = 'yerel'
    def nonlocal_ata():
        nonlocal deger
        deger = 'nonlocal'
    def global_ata():
        global deger
        deger = 'global'

    deger = 'test'
    yerel_ata()
    print('Yerel atamadan sonra:', deger)
    nonlocal_ata()
    print('nonlocal atamadan sonra:', deger)
    global_ata()
    print('global atamadan sonra:', deger)

kapsam_testi()
print('Modül kapsamında:', deger)
# çıktı: Yerel atamadan sonra: test
# çıktı: nonlocal atamadan sonra: nonlocal
# çıktı: global atamadan sonra: nonlocal
# çıktı: Modül kapsamında: global

Yerel atama dış fonksiyonun deger'ini etkilemedi. nonlocal ataması onu değiştirdi. global ataması ise fonksiyonun değişkenine dokunmadı, modül seviyesindeki deger'i oluşturdu; zaten öncesinde modülde böyle bir ad yoktu.

Serbest değişkenler çağrı anında okunur

Bir blokta kullanılıp orada tanımlanmayan değişkene serbest değişken (free variable) denir. Execution model belgesine göre serbest değişkenlerin çözümlenmesi derleme anında değil, çalışma anında yapılır. Yani fonksiyon, değişkenin tanım anındaki değerini değil, çağrıldığı andaki değerini görür:

python
i = 10
def f():
    print(i)
i = 42
f()        # çıktı: 42

Döngüde oluşturulan lambda tuzağı

Bu kuralın en bilinen sonucu, Programming FAQ'da da yer alan döngü içi lambda sorunudur. Bir döngüde fonksiyonlar oluşturup döngü değişkenini kullanırsanız, fonksiyonlar çağrıldığında döngü çoktan bitmiştir ve hepsi değişkenin son değerini görür. Çözüm, değeri varsayılan parametreyle yakalamaktır; varsayılan değerler tanım anında hesaplandığı için her fonksiyon o turdaki değeri saklar. FAQ, bu davranışın lambda'ya özgü olmadığını, normal fonksiyonlar için de geçerli olduğunu belirtir.

python
kareler = []
for x in range(5):
    kareler.append(lambda: x ** 2)
print(kareler[2](), kareler[4]())     # çıktı: 16 16
x = 8
print(kareler[2]())                   # çıktı: 64

kareler = []
for x in range(5):
    kareler.append(lambda n=x: n ** 2)
print(kareler[2](), kareler[4]())     # çıktı: 4 16

Sınıf gövdesi özel bir kapsamdır

Execution model belgesi sınıf tanımlarını ayrıca ele alır. Sınıf gövdesinde tanımlanan adların kapsamı yalnızca sınıf bloğuyla sınırlıdır; sınıfın metotlarına uzanmaz. Bir metodun içinden sınıf değişkenine ulaşmak için self.ad veya SinifAdi.ad yazmanız gerekir. Python 3.15.2'nin hata mesajı bu durumda doğru yazımı önerir:

python
class Vergi:
    oran = 20
    def hesapla(self, tutar):
        return tutar * oran / 100       # self.oran olmalı

print(Vergi().hesapla(100))
# hata: NameError: name 'oran' is not defined. Did you mean: 'self.oran'?

Aynı kural sınıf gövdesindeki comprehension'lar ve generator ifadeleri için de geçerlidir, çünkü onların da kendi kapsamları vardır. Belgelerdeki örneğe göre sınıf gövdesinde a = 42 tanımlayıp hemen altında [a + i for i in range(3)] yazmak NameError: name 'a' is not defined verir. Sınıflar ve self class ve nesne yazısında ayrıntılı anlatılıyor.

Döngü değişkeni sızar, comprehension değişkeni sızmaz

Bir for döngüsünün değişkeni, döngünün bulunduğu kapsamın değişkenidir ve döngü bittikten sonra son değeriyle kalır. List comprehension içindeki değişken ise comprehension'ın kendi kapsamındadır; dışarıdan görünmez ve aynı adlı bir dış değişkeni bozmaz.

python
for k in range(3):
    pass
print(k)                       # çıktı: 2

m = 'dokunulmadı'
kareler = [m * m for m in range(3)]
print(kareler, m)              # çıktı: [0, 1, 4] dokunulmadı

if True:
    blok_ici = 'if bloğu kapsam oluşturmaz'
print(blok_ici)                # çıktı: if bloğu kapsam oluşturmaz

Döngülerin bu ve benzeri ayrıntıları for ve while yazısında, comprehension yazımı list comprehension yazısında var.

Modüller arasında değişken paylaşmak

global yalnızca bir modülün kendi global ad alanını ilgilendirir; başka bir modülün değişkenini değiştirmez. Programming FAQ, tek bir program içinde modüller arasında bilgi paylaşmanın alışılmış yolunun ayrı bir yapılandırma modülü (örneğin config.py) oluşturmak olduğunu söyler: Her modül import config yazar ve değeri config.x = 1 biçiminde, modül niteliği olarak okur veya değiştirir. Her modülün yalnızca bir örneği olduğu için yapılan değişiklik her yerde görünür. from config import x yazarsanız ise kendi modülünüzde x adında ayrı bir bağ oluşur ve sonraki değişiklikleri görmezsiniz. İçe aktarma kuralları modül ve paket yazısında.

global ve nonlocal karşılaştırması

global ve nonlocal karşılaştırması: Hedef kapsam, Ad önceden var olmalı mı?, Modül seviyesinde, Tipik kullanım
globalnonlocal
Hedef kapsamModülün global ad alanıEn yakın çevreleyen fonksiyon
Ad önceden var olmalı mı?Hayır, atama yeni global oluştururEvet, yoksa SyntaxError
Modül seviyesindeEtkisiz ama yazılabilirSyntaxError
Tipik kullanımModül düzeyindeki bir ayarı veya sayacı güncellemekClosure içinde durum tutmak (sayaç, önbellek)
Yalnızca okumak için gerekli mi?HayırHayır

Sık yapılan hatalar

  • Global değişkeni fonksiyonda artırmak: sayac += 1 bir atamadır; UnboundLocalError alırsınız. Ya global bildirin ya da değeri parametreyle alıp return edin.
  • Her şey için global kullanmak: Fonksiyonların gizli yan etkileri olur ve test etmek zorlaşır; çoğu durumda parametre ve dönüş değeri daha temizdir.
  • Yerleşik adları gölgelemek: list, str, sum, id gibi adları değişken adı olarak kullanmayın.
  • Döngüde lambda veya iç fonksiyon oluşturup değişkenin donacağını sanmak: Değer çağrı anında okunur; varsayılan parametreyle yakalayın.
  • Metot içinde sınıf değişkenine çıplak adla ulaşmak: self.ad veya Sinif.ad yazın.
  • Global listeyi yeniden atayarak boşaltmaya çalışmak: liste = [] yerel ad oluşturur; liste.clear() kullanın.

Sık Sorulan Sorular

LEGB kuralı nedir?

Python'un bir adı arama sırasıdır: Local, Enclosing, Global, Built-in. Ad ilk bulunduğu kapsamdan alınır; hiçbirinde yoksa NameError yükselir.

UnboundLocalError neden çıkar?

Fonksiyonun herhangi bir yerinde bir ada atama yapıldığı için o ad yerel sayılır, ama siz onu atamadan önce okumaya çalışırsınız. Global değişkeni kastediyorsanız global bildirin; değilse değişkeni okumadan önce yerel olarak tanımlayın.

global ile nonlocal arasındaki fark nedir?

global modül seviyesindeki adı, nonlocal ise iç içe fonksiyonlarda en yakın dış fonksiyonun adını yeniden bağlar. nonlocal için adın dış fonksiyonda önceden tanımlı olması gerekir.

Python'da blok kapsamı var mı?

Hayır. if, for, while ve with blokları yeni bir kapsam oluşturmaz; kapsam oluşturan bloklar modül, fonksiyon gövdesi ve sınıf tanımıdır. Comprehension'ların ise kendi kapsamı vardır.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Python öğrenme rehberi · def ile fonksiyon tanımlama · lambda, map ve sorted · Decorator yazma · class ve nesne · Modül ve paket import · Sık hata mesajları