Python’da değişken oluşturma ve değer atamayı istediğimiz değişken ismini yazarak ve eşitliğin karşısına değeri vererek yapabiliyoruz. Verdiğimiz değerin türünü ayrıca belirtmemize de gerek olmuyor. Peki oluşturduğumuz değişkenlerin kapsamı hakkında neler bilmemiz gerekiyor? Öncelikle yerel değişkenlere bakalım.
Yerel Değişkenler
Bir isim bir işlevin içinde tanımlanırsa, varsayılan olarak yalnızca işlev içinden erişiriz:
def foo():
a = 5
print(a) # ok
print(a) # NameError: name 'a' is not defined
Kontrol akışı yapılarının (koşul ifadeleri, döngüler vb.) kapsam üzerinde bir etkisi yoktur (except hariç), ancak henüz atanmamış bir değişkene erişmeye çalışmak hata verecektir:
ef foo():
if True:
a = 5
print(a) # ok
b = 3
def bar():
if False:
b = 5
print(b) # UnboundLocalError: local variable 'b' referenced before assignment
Yerel Olmayan Değişkenler için Kapsam
Python 3, nonlocal adında yeni bir anahtar kelime ile gelir. Yerel olmayan anahtar sözcüğü, iç kapsamdaki kapsamı geçersiz kılma imkanı verir (daha fazla bilgi için PEP 3104‘ü okuyabilirsiniz). Bu, en iyi şekilde bir kod örneği ile açıklanabilir. En yaygın örneklerden biri, artabilen bir fonksiyon oluşturmaktır:
def counter():
num = 0
def incrementer():
num += 1
return num
return incrementer
Bu kodu çalıştırmayı denerseniz, num değişkenine en içteki fonksiyonda atanmadan önce başvurulduğu için bir UnboundLocalError alırsınız. Karışıma nonlocal’i de ekleyelim:
def counter():
num = 0
def incrementer():
nonlocal num
num += 1
return num
return incrementer
c = counter()
c() # = 1
c() # = 2
c() # = 3
Temel olarak nonlocal, bir dış kapsamdaki değişkenlere atama yapmanıza izin verir, ancak küresel bir kapsama izin vermez. Yani sayaç fonksiyonumuzda nonlocal kullanamazsınız çünkü o zaman global bir kapsama atama yapmaya çalışacaktır. Bunu denediğinizde hemen bir SyntaxError alırsınız. Bunun yerine iç içe geçmiş bir fonksiyonda nonlocal kullanmanız gerekir.
(Burada sunulan işlevselliğin generatorler kullanılarak daha iyi uygulandığını unutmayın).
Global Değişkenlerde Kapsam
Python’da, işlevlerin içindeki değişkenler, yalnızca bir atama deyiminin sol tarafında veya başka bir değer atama olayında görünürlerse yerel olarak kabul edilirler; aksi takdirde böyle bir atama, küresel kapsama kadar çevreleyen işlevlerde aranır. Bu durum atama deyimi hiç çalıştırılmasa bile geçerlidir.
x = 'Hi'
def read_x():
print(x) # x'e sadece referans verilir, bu nedenle global olduğu varsayılır
read_x() # Hi yazdırır
def read_y():
print(y) # burada y'ye sadece referans veriliyor, bu nedenle global olduğu varsayılıyor
read_y() # NameError: global isim 'y' tanımlanmamış
def read_y():
y = 'Hey' # y bir atamada görünür, bu nedenle yereldir
print(y) # yerel y'yi bulacak
read_y() # prints Hey
def read_x_local_fail():
if False:
x = 'Hey' # x bir atamada görünür, bu nedenle yereldir
print(x) # atanmamış olan local z'yi arayacak ve bulunamayacaktır
read_x_local_fail() # UnboundLocalError: yerel değişken 'x' atamadan önce başvuruldu
Normalde, bir kapsam içindeki bir atama aynı isimdeki tüm dış değişkenleri gölgeleyecektir:
x = 'Merhaba'
def change_local_x():
x = 'Bye'
print(x)
change_local_x() # bye yazdırır
print(x) # merhaba yazdırır
Bir ismin global olarak bildirilmesi, kapsamın geri kalanı için isme yapılacak tüm atamaların modülün en üst seviyesinde gerçekleşeceği anlamına gelir:
x = 'Hi'
def change_global_x():
global x
x = 'Bye'
print(x)
change_global_x() # bye yazdırır
print(x) # bye yazdırır
Global anahtar sözcüğü, atamaların programın üst seviyesinde değil, modülün üst seviyesinde gerçekleşeceği anlamına gelir. Diğer modüller yine de modül içindeki değişkenlere normal -noktalı erişime – ihtiyaç duyacaktır.
Özetlemek gerekirse: bir x değişkeninin bir fonksiyon için yerel olup olmadığını bilmek için, fonksiyonun tamamını okumalısınız:
- global x’i bulduysanız, x global bir değişkendir
- nonlocal x’i bulduysanız, x çevreleyen bir işleve aittir ve ne yerel ne de küreseldir
- x = 5 veya for x in range(3) veya başka bir bağlayıcı bulduysanız, x yerel bir değişkendir
- Aksi takdirde x bazı çevreleyen kapsamlara (fonksiyon kapsamı, global kapsam veya yerleşikler) aittir
Yerel ve Global Kapsam
Yerel ve globalkapsam nedir?
Kodun bir noktasında erişilebilir olan tüm Python değişkenleri ya yerel kapsamdadır ya da küresel kapsamdadır.
Bunun açıklaması, yerel kapsamın geçerli işlevde tanımlanan tüm değişkenleri içermesi ve küresel kapsamın geçerli işlevin dışında tanımlanan değişkenleri içermesidir.
foo = 1 # global
def func():
bar = 2 # local
print(foo) # foo değişkenini global kapsamdan yazdırır
print(bar) # bar değişkenini yerel kapsamdan yazdırır
Hangi değişkenlerin hangi kapsamda olduğu incelenebilir. Yerleşik locals() ve globals() işlevleri tüm kapsamları sözlük olarak döndürür.
foo = 1
def func():
bar = 2
print(globals().keys()) # global kapsamdaki tüm değişken adlarını yazdırır
print(locals().keys()) # yerel kapsamdaki tüm değişken adlarını yazdırır
İsim çakışmalarında ne olur?
foo = 1
def func():
foo = 2 # yerel kapsamda yeni bir foo değişkeni oluşturur, global foo etkilenmez
print(foo) # 2 yazdırır
# foo global değişkeni değişmeden hala var:
print(globals()['foo']) # 1 yazdırır
print(locals()['foo']) # 2 yazdırır
Global bir değişkeni değiştirmek için global anahtar sözcüğünü kullanın:
foo = 1
def func():
global foo
foo = 2 # bu, yerel bir değişken oluşturmak yerine global foo'yu değiştirir
Kapsam, fonksiyonun tüm gövdesi için tanımlanır!
Bunun anlamı, bir değişkenin hiçbir zaman fonksiyonun bir yarısı için global ve sonrasında yerel olmayacağı ya da tam tersi olacağıdır.
foo = 1
def func():
# Bu fonksiyonun bir yerel değişkeni -foo- vardır, çünkü aşağıda tanımlanmıştır.
# Yani, foo bu noktadan itibaren yereldir. Global foo gizlidir.
print(foo) # yerel foo henüz başlatılmadığı için UnboundLocalError hatasını yükseltir
foo = 7
print(foo)
Aynı şekilde, tam tersi de:
foo = 1
def func():
# Bu fonksiyonda, foo başlangıçtan itibaren global bir değişkendir
foo = 7 # global foo değiştirildi
print(foo) # 7
print(globals()['foo']) # 7
global foo # bu fonksiyon içinde herhangi bir yerde olabilir
print(foo) # 7
Fonksiyonlar içinde fonksiyonlar
İşlevler içinde iç içe geçmiş birçok işlev düzeyi olabilir, ancak herhangi bir işlev içinde o işlev için yalnızca bir yerel kapsam ve genel kapsam vardır. Ara kapsamlar yoktur.
foo = 1
def f1():
bar = 1
def f2():
baz = 2
# burada, foo global bir değişkendir, baz ise yerel bir değişkendir
# bar her iki kapsamda da değil
print(locals().keys()) # ['baz']
print('bar' in locals()) # False
print('bar' in globals()) # False
def f3():
baz = 3
print(bar) # f1'deki bar referans alınır, böylece f3'ün (closure) yerel kapsamına girer
print(locals().keys()) # ['bar', 'baz']
print('bar' in locals()) # True
print('bar' in globals()) # False
def f4():
bar = 4 # bar'ı f1'in yerel kapsamından gizleyen yeni bir yerel bar
baz = 4
print(bar)
print(locals().keys()) # ['bar', 'baz']
print('bar' in locals()) # True
print('bar' in globals()) # False
global vs nonlocal
kapsam farkı (sadece Python 3)
Bu anahtar sözcüklerin her ikisi de geçerli fonksiyonlar için yerel olmayan değişkenlere yazma erişimi sağlamak için kullanılır.
global anahtar sözcüğü, bir ismin global bir değişken olarak ele alınması gerektiğini bildirir.
foo = 0 # global foo
def f1():
foo = 1 # f1 içinde yeni bir foo yerel
def f2():
foo = 2 # yeni bir foo local in f2
def f3():
foo = 3 # yeni bir foo local in f3
print(foo) # 3
foo = 30 # yalnızca f3'teki yerel foo'yu değiştirir
def f4():
global foo
print(foo) # 0
foo = 100 # global foo'yu değiştirir
Öte yandan, Python 3’te mevcut olan nonlocal, yerel bir değişkeni çevreleyen bir kapsamdan geçerli işlevin yerel kapsamına alır.
nonlocal ile ilgili Python belgelerinden:
Yerel olmayan deyim, listelenen tanımlayıcıların globaller hariç en yakın kapsama önceden bağlanmış değişkenlere başvurmasına neden olur.
def f1():
def f2():
foo = 2 # f2 içinde yeni bir foo yerel
def f3():
nonlocal foo # foo from f2, which is the nearest enclosing scope
print(foo) # 2
foo = 20 # f2'den foo'yu değiştirir!
Atama Gerçekleşmesi
x = 5
x += 7
for x in iterable: pass
Yukarıdaki ifadelerin her biri bir bağlama olayıdır – x, 5 ile gösterilen nesneye bağlanır. Bu ifade bir fonksiyonun içinde yer alırsa, x varsayılan olarak fonksiyon-yerel olacaktır.
İşlevler isimlere bakarken sınıf kapsamını atlar
Sınıflar tanımlama sırasında yerel bir kapsama sahiptir, ancak sınıf içindeki işlevler isimleri ararken bu kapsamı kullanmaz. Lambda‘lar fonksiyon olduğundan ve kavramalar fonksiyon kapsamı kullanılarak uygulandığından, bu durum bazı şaşırtıcı davranışlara yol açabilir.
a = 'global'
class Fred:
a = 'class' # sınıf kapsamı
b = (a for i in range(10)) # fonksiyon kapsamı
c = [a for i in range(10)] # işlev kapsamı
d = a # sınıf kapsamı
e = lambda: a # işlev kapsamı
f = lambda a=a: a # varsayılan bağımsız değişken sınıf kapsamını kullanır
staticmethod # veya @classmethod ya da normal örnek yöntemi
def g(): # işlev kapsamı
dönüş a
print(Fred.a) # sınıf
print(next(Fred.b)) # küresel
print(Fred.c[0]) # Python 2'de sınıf, Python 3'te global
print(Fred.d) # sınıf
print(Fred.e()) # global
print(Fred.f()) # sınıf
print(Fred.g()) # global
Bu kapsamın nasıl çalıştığını bilmeyen kullanıcılar b, c ve e’nin sınıf yazdırmasını bekleyebilir.
PEP 227’den:
Sınıf kapsamındaki adlara erişilemez. İsimler en içteki çevreleyen fonksiyon kapsamında çözümlenir. Bir sınıf tanımı iç içe geçmiş kapsamlar zincirinde yer alıyorsa, çözümleme işlemi sınıf tanımlarını atlar.
Python’un adlandırma ve bağlama ile ilgili belgelerinden:
Bir sınıf bloğunda tanımlanan adların kapsamı sınıf bloğuyla sınırlıdır; yöntemlerin kod bloklarına uzanmaz – bu, bir işlev kapsamı kullanılarak uygulandıkları için anlama ve üreteç ifadelerini içerir. Bu, aşağıdakilerin başarısız olacağı anlamına gelir:
class A:
a = 42
b = list(a + i for i in range(10))
Bu örnekte, Martijn Pieters tarafından verilen ve bu davranışın daha derinlemesine analizini içeren bu yanıttan referanslar kullanılmıştır.
del komutu ile kapsamdan kaldırma
Bu komutun birbiriyle ilişkili ancak farklı birkaç şekli vardır.
del v
Eğer v bir değişken ise, del v komutu değişkeni kapsamından çıkarır. Örneğin:
x = 5
print(x) # out: 5
del x
print(x) # NameError: 'f' ismi tanımlanmamış
del’in bir bağlama oluşumu olduğuna dikkat edin, bu da aksi açıkça belirtilmedikçe (yerel olmayan veya global kullanarak) del v’nin v’yi geçerli kapsam için yerel yapacağı anlamına gelir. Eğer v öğesini bir dış kapsamda silmek istiyorsanız, del v deyimiyle aynı kapsamda yerel olmayan v veya global v kullanın.
Aşağıdakilerin tümünde, bir komutun amacı varsayılan bir davranıştır ancak dil tarafından zorlanmaz. Bir sınıf bu niyeti geçersiz kılacak şekilde yazılmış olabilir.
del v.name
Bu komut v.__delattr__(name) çağrısını tetikler.
Amaç, öznitelik adını kullanılamaz hale getirmektir. Örneğin:
class A:
pass
a = A()
a.x = 7
print(a.x) # out: 7
del a.x
print(a.x) # hata: AttributeError: 'A' nesnesinin 'x' niteliği yok
del v[item]
Bu komut v.__delitem__(item) çağrısını tetikler.
Amaç, öğenin v nesnesi tarafından uygulanan eşlemeye ait olmamasıdır:
x = {'a': 1, 'b': 2}
del x['a']
print(x) # out: {'b': 2}
print(x['a']) # hata: KeyError: 'a'
del v[a:b]
Bu aslında v.__delslice__(a, b) çağrısıdır.
Amaç yukarıda anlatılana benzerdir, ancak dilimlerle – tek bir öğe yerine öğe aralıkları ile- amacımızı gerçekleştiririz. Örneğin:
x = [0, 1, 2, 3, 4]
del x[1:3]
print(x) # out: [0, 3, 4]