Kapsam belirleme kurallarının kısa tanımı?


472

Python kapsam belirleme kuralları tam olarak nedir ?

Bazı kod varsa:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

Nerede xbulunur? Bazı olası seçenekler aşağıdaki listeyi içerir:

  1. Ekteki kaynak dosyada
  2. Sınıf ad alanında
  3. İşlev tanımında
  4. For loop dizin değişkeninde
  5. For döngüsü içinde

Ayrıca, işlev spambaşka bir yere aktarıldığında yürütme sırasında bağlam vardır . Ve belki lambda fonksiyonları biraz farklı geçer?

Bir yerde basit bir referans veya algoritma olmalıdır. Ara Python programcıları için kafa karıştırıcı bir dünya.


2
Kapsam belirleme kuralları Python belgelerinde oldukça ters - fakat tamamen - tanımlanmıştır: docs.python.org/3/reference/… .
jefe2000

Yanıtlar:


420

Aslında, Python Kapsam çözünürlüğü için kısa bir kural, Learning Python, 3. Ed. . (Bu kurallar özniteliklere değil, değişken adlarına özgüdür. Nokta olmadan başvurursanız, bu kurallar geçerlidir.)

LEGB Kuralı

  • L ocal - Bir işlev ( defveya lambda) içinde herhangi bir şekilde atanan ve bu işlevde genel olarak bildirilmeyen adlar

  • E her ve tüm statik kapatma işlevleri (yerel kapsamında verilen isimleri - nclosing fonksiyonlu defveya lambdadış iç dan),

  • G lobal (module) - Bir modül dosyasının en üst düzeyinde atanan veya dosyanın içindeki bir globalifadeyi yürüterek adlardef

  • B : - (Python) uilt-İsimler yerleşik isimler modülünde preassigned open, range, SyntaxErrorvb

Yani,

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()

forDöngü kendi ad yoktur. LEGB düzeninde, kapsamlar

  • L: Local def spam(içinde code3, code4ve code5)
  • E: Herhangi bir çevreleme işlevi (tüm örnek başka birindeyse def)
  • G: Modülde x(in code1) global olarak beyan edilen var mı?
  • B: xPython'daki herhangi bir yerleşik .

xbulunabilir asla code2(bunu, görecekti Tahmin edebileceğiniz durumlarda bile Antti cevabını veya burada ).


45
Global erişime bir uyarı olarak - bir global değişkeni okumak, açık bir bildirimde bulunulmadan gerçekleşebilir, ancak global (var_name) bildirmeden yazılmak yerine yeni bir yerel örnek oluşturur.
Peter Gibson

12
Aslında @Peter, global(var_name)sözdizimsel olarak yanlış. Doğru sözdizimi global var_nameparantez olmadan olur . Yine de geçerli bir noktanız var.
martineau

Öyleyse, neden foo'nun "y" değişkeni aşağıdaki "bar" a görünmüyor: >>> def foo(x): ... y = x ... def bar(z): ... y = z ... bar(5) ... print x,y ... >>> foo(3) 3 3
Jonathan Mayer

3
@Jonathan: Çünkü her ybiri yazılıyor ve hiçbir global ybildirim yok - bakınız @ Peter'ın yorumu.
martineau

@LakshmanPrasad "E" ye düşüyor, ancak belirtilmesi gereken özel bir davranışı var: bir sınıf değişkeni, bu yüzden nesneler arasında "global". Buna atama olacak size ne yaptığınızı bilmiyorsanız ayıklama konulara beklenmedik ve sert yol açar.
Ctrl-C

157

Esasen, Python'da yeni bir kapsam sunan tek şey bir fonksiyon tanımıdır. Sınıflar, vücutta doğrudan tanımlanan herhangi bir şeyin sınıfın ad alanına yerleştirilmesi nedeniyle özel bir durumdur, ancak içerdikleri yöntemlerden (veya iç içe sınıflardan) doğrudan erişilemezler.

Örneğinizde, x'in aranacağı yalnızca 3 kapsam vardır:

  • spam'in kapsamı - code3 ve code5'te tanımlanan her şeyi içerir (kod4'ün yanı sıra döngü değişkeniniz)

  • Global kapsam - kod1'de tanımlanan her şeyi ve ayrıca Foo'yu (ve bundan sonra ne olursa olsun) içerir

  • Yerleşik ad alanı. Biraz özel bir durum - bu çeşitli Python yerleşik işlevlerini ve len () ve str () gibi türleri içerir. Genellikle bu herhangi bir kullanıcı kodu tarafından değiştirilmemelidir, bu nedenle standart işlevleri içermesini ve başka bir şey içermemesini bekle.

Daha fazla kapsam yalnızca resme iç içe bir işlev (veya lambda) eklediğinizde görünür. Bunlar beklediğiniz gibi davranacaktır. Yuvalanmış işlev, yerel kapsamdaki her şeye ve ayrıca kapatma işlevinin kapsamındaki her şeye erişebilir. Örneğin.

def foo():
    x=4
    def bar():
        print x  # Accesses x from foo's scope
    bar()  # Prints 4
    x=5
    bar()  # Prints 5

Kısıtlamalar:

Yerel işlevin değişkenleri dışındaki kapsamlardaki değişkenlere erişilebilir, ancak daha fazla sözdizimi olmadan yeni parametrelere geri yüklenemez. Bunun yerine atama , üst kapsamdaki değişkeni etkilemek yerine yeni bir yerel değişken oluşturur . Örneğin:

global_var1 = []
global_var2 = 1

def func():
    # This is OK: It's just accessing, not rebinding
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable
    global_var2 = 2 

    local1 = 4
    def embedded_func():
        # Again, this doen't affect func's local1 variable.  It creates a 
        # new local variable also called local1 instead.
        local1 = 5
        print local1

    embedded_func() # Prints 5
    print local1    # Prints 4

Bir işlev kapsamından global değişkenlerin bağlarını gerçekten değiştirmek için, değişkenin global anahtar kelimeyle global olduğunu belirtmeniz gerekir. Örneğin:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1

Şu anda aynı şeyi fonksiyon kapsamlarını kapsayan değişkenler için yapmanın bir yolu yoktur , ancak Python 3 yeni bir " nonlocal" anahtar kelimesini tanıtmaktadır .


111

Python3 zamanıyla ilgili tam bir cevap yoktu, bu yüzden burada bir cevap verdim. Burada açıklananların çoğu , Python 3 belgelerinin adlarının 4.2.2 çözümlenmesinde ayrıntılı olarak açıklanmıştır .

Diğer cevaplarda belirtildiği gibi, Yerel, Çevreleyen, Küresel ve Yerleşik için LEGB olmak üzere 4 temel kapsam vardır. Bunlara ek olarak, sınıf içinde tanımlanan yöntemler için kapalı bir kapsam içermeyen özel bir kapsam, sınıf gövdesi vardır; sınıf gövdesindeki tüm ödevler, değişkeni sınıf gövdesinde bağlı hale getirir.

Özellikle hiçbir blok deyimi, yanında defve classdeğişken bir kapsam oluşturun. Python 2'de bir liste kavraması değişken bir kapsam oluşturmaz, ancak Python 3'te liste kavramaları içindeki döngü değişkeni yeni bir kapsamda oluşturulur.

Sınıf bedeninin özelliklerini göstermek

x = 0
class X(object):
    y = x
    x = x + 1 # x is now a variable
    z = x

    def method(self):
        print(self.x) # -> 1
        print(x)      # -> 0, the global x
        print(y)      # -> NameError: global name 'y' is not defined

inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)

Böylece işlev gövdesinden farklı olarak, aynı ada sahip bir sınıf değişkeni elde etmek için değişkeni sınıf gövdesinde aynı ada yeniden atayabilirsiniz; bu isimdeki diğer aramalar sınıf değişkenine çözümlenir.


Python'a birçok yeni gelen için daha büyük sürprizlerden biri, bir fordöngünün değişken bir kapsam yaratmamasıdır. Python 2'de liste kavrayışları da bir kapsam oluşturmaz (jeneratörler ve diksiyon kavrayışları yapar!) Bunun yerine işlevdeki veya global kapsamdaki değeri sızdırırlar:

>>> [ i for i in range(5) ]
>>> i
4

Anlayışlar Python 2'de lambda ifadeleri içinde değiştirilebilir değişkenler oluşturmanın kurnaz (veya yapacaksanız korkunç) bir yol olarak kullanılabilir - lambda ifadesi, ifade gibi değişken bir kapsam oluşturur def, ancak lambda içinde hiçbir ifadeye izin verilmez. Python'da bir deyim olarak atama, lambda'da değişken atamalara izin verilmediği anlamına gelir, ancak liste kavraması bir ifadedir ...

Bu davranış Python 3'te giderilmiştir - anlama ifadeleri veya jeneratör kaçak değişkenleri yoktur.


Küresel, modülün kapsamı anlamına gelir; ana python modülü __main__; içe aktarılan tüm modüllere sys.modulesdeğişken üzerinden erişilebilir ; __main__birine erişmek için kullanabilirsiniz sys.modules['__main__'], veya import __main__; orada özniteliklere erişmek ve atamak tamamen kabul edilebilir; ana modülün küresel kapsamında değişkenler olarak görüneceklerdir.


Geçerli kapsamda (sınıf kapsamı hariç) bir ad atanmışsa, bu kapsama ait olduğu kabul edilir, aksi takdirde değişkene atanan herhangi bir kapalı kapsama ait olduğu kabul edilir (atanmamış olabilir henüz veya hiç değil) veya son olarak küresel kapsam. Değişken yerel olarak kabul edilir, ancak henüz ayarlanmamışsa veya silinmişse, değişken değerinin okunması UnboundLocalErrorbir alt sınıftır NameError.

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

Kapsam, global (modül kapsamı) değişkenini global anahtar kelimeyle açıkça değiştirmek istediğini bildirebilir:

x = 5
def foobar():
    global x
    print(x)
    x += 1

foobar() # -> 5
print(x) # -> 6

Bu, kapalı kapsamda gölgeli olsa bile mümkündür:

x = 5
y = 13
def make_closure():
    x = 42
    y = 911
    def func():
        global x # sees the global value
        print(x, y)
        x += 1

    return func

func = make_closure()
func()      # -> 5 911
print(x, y) # -> 6 13

Python 2'de, kapalı alandaki değeri değiştirmenin kolay bir yolu yoktur; genellikle bu, uzunluğu 1 olan bir liste gibi değiştirilebilir bir değere sahip olarak simüle edilir:

def make_closure():
    value = [0]
    def get_next_value():
        value[0] += 1
        return value[0]

    return get_next_value

get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2

Ancak python 3'te nonlocalkurtarmaya gelir:

def make_closure():
    value = 0
    def get_next_value():
        nonlocal value
        value += 1
        return value
    return get_next_value

get_next = make_closure() # identical behavior to the previous example.

nonlocalDokümantasyon söylüyor

Yerel olmayan bir deyimde listelenen adlar, genel bir deyimde listelenenlerden farklı olarak, kapalı bir kapsamda önceden var olan bağlamaları belirtmelidir (yeni bir bağlamanın oluşturulması gereken kapsam kesin olarak belirlenemez).

yani nonlocalher zaman adın bağlandığı en içteki dış küresel olmayan kapsamı ifade eder (yani for, withyan tümcedeki veya bir işlev parametresi olarak hedef değişken olarak kullanılanlar dahil ).


Geçerli kapsamda veya herhangi bir kapalı kapsamda yerel olarak kabul edilmeyen herhangi bir değişken genel bir değişkendir. Modül global sözlüğünde bir genel ad aranır; bulunamazsa, global yerleşik modülden aranır; modülün adı python 2'den python 3'e değiştirildi; python 2'de __builtin__ve python 3'te şimdi denir builtins. Yerleşik modülün bir özniteliğine atarsanız, daha sonra herhangi bir modül tarafından okunabilir genel değişken olarak görünür, ancak bu modül aynı ada sahip kendi genel değişkeniyle gölgelendirmezse.


Yerleşik modülü okumak da yararlı olabilir; Dosyanın bazı bölümlerinde python 3 stili yazdırma işlevini istediğinizi, ancak dosyanın diğer bölümlerinin yine de printifadeyi kullandığını varsayalım . Python 2.6-2.7'de Python 3'ü elde edebilirsinizprint işlevini :

import __builtin__

print3 = __builtin__.__dict__['print']

from __future__ import print_functionAslında almaz printPython 2'de fonksiyon hiçbir yerinde - yerine sadece için ayrıştırma kuralları devre dışı bırakır printtaşıma, cari modülünde açıklamada printbaşka değişken tanımlayıcı gibi ve böylece izin printfonksiyon yerleşikleri buradan takip edilebilir.


23

Python 2.x için kapsam belirleme kuralları diğer cevaplarda zaten belirtilmiştir. Ekleyeceğim tek şey, Python 3.0'da, yerel olmayan bir kapsam kavramının da ('yerel olmayan' anahtar kelime ile gösterilen) olmasıdır. Bu, doğrudan dış kapsamlara erişmenizi sağlar ve sözcüksel kapanışlar da dahil olmak üzere bazı düzgün numaralar yapma yeteneğini açar (değiştirilebilir nesneler içeren çirkin hack'ler olmadan).

EDIT: İşte bu konuda daha fazla bilgi ile PEP .


23

Biraz daha kapsamlı bir kapsam örneği:

from __future__ import print_function  # for python 2 support

x = 100
print("1. Global x:", x)
class Test(object):
    y = x
    print("2. Enclosed y:", y)
    x = x + 1
    print("3. Enclosed x:", x)

    def method(self):
        print("4. Enclosed self.x", self.x)
        print("5. Global x", x)
        try:
            print(y)
        except NameError as e:
            print("6.", e)

    def method_local_ref(self):
        try:
            print(x)
        except UnboundLocalError as e:
            print("7.", e)
        x = 200 # causing 7 because has same name
        print("8. Local x", x)

inst = Test()
inst.method()
inst.method_local_ref()

çıktı:

1. Global x: 100
2. Enclosed y: 100
3. Enclosed x: 101
4. Enclosed self.x 101
5. Global x 100
6. global name 'y' is not defined
7. local variable 'x' referenced before assignment
8. Local x 200

6
Bu harika bir cevap. Ancak, methodve arasındaki farkların method_local_refvurgulanması gerektiğini düşünüyorum. methodglobal değişkene erişebilir ve onu olduğu gibi yazdırabilir 5. Global x. Ancak method_local_refyapamaz çünkü daha sonra aynı ada sahip bir yerel değişken tanımlar. Bunu x = 200çizgiyi kaldırarak test edebilir ve farkı görebilirsiniz
kiril

@brianray: Z ne olacak?
Malik

@kiril Bununla ilgili bir not ekledim
brianray

@ MalikA.Rumi İlginç olmadığı için z'yi kaldırdım
brianray

Şaşırtıcı bir şekilde, bu, Python kapsamlarının tüm SO'da bulabileceğim tek açık açıklamasıdır. Sadece çok basit bir örnek kullanarak. Teşekkürler!
not2qubit

13

Python değişkenlerinizi - genellikle - üç ad alanı ile çözer.

Yürütme sırasında herhangi bir zamanda, ad alanlarına doğrudan erişilebilen en az üç iç içe kapsam vardır: ilk önce aranan en iç kapsam yerel adları içerir; en yakın çevreleme kapsamından başlayarak aranan herhangi bir çevreleme işlevinin ad alanları; daha sonra aranan orta kapsam, geçerli modülün global adlarını içerir; ve en dıştaki kapsam (son aranan) yerleşik adları içeren ad alanıdır.

İki işlev vardır: globalsve localsbunlar size bu ad alanlarının ikisini gösterir.

Ad alanları paketler, modüller, sınıflar, nesne yapımı ve işlevler tarafından oluşturulur. Başka ad alanı aroması yoktur.

Bu durumda, adlı bir işleve çağrı x yerel ad alanında veya genel ad alanında çözülmelidir.

Bu durumda yerel, yöntem işlevinin gövdesidir Foo.spam.

Küresel - iyi - küreseldir.

Kural, yöntem işlevleri (ve iç içe işlev tanımları) tarafından oluşturulan iç içe yerel alanları aramak, sonra genel olarak arama yapmaktır. Bu kadar.

Başka kapsam yok. forİfadesi (ve benzeri diğer bileşik ifadeler ifvetry ) yeni iç içe kapsamları oluşturmaz. Yalnızca tanımlar (paketler, modüller, işlevler, sınıflar ve nesne örnekleri.)

Sınıf tanımının içinde, adlar sınıf ad alanının bir parçasıdır. code2, örneğin, sınıf adıyla nitelenmelidir. Genellikle Foo.code2. Ancak,self.code2 Python nesneleri içeren sınıfa geri dönüş olarak baktığından da çalışır.

Bir nesnenin (sınıf örneği) örnek değişkenleri vardır. Bu adlar nesnenin ad alanındadır. Nesne tarafından nitelendirilmelidirler. ( variable.instance.)

Bir sınıf yönteminden, yerliler ve küreseller var. self.variableÖrneği ad alanı olarak seçmeyi söylüyorsunuz . Bunun selfher sınıf üyesi işlevi için bir argüman olduğunu ve yerel ad alanının bir parçası olduğunu göreceksiniz .

Bkz. Python Kapsam Kuralları , Python Kapsamı , Değişken Kapsam .


5
Bu güncel değil. 2,1'den (7 yıl önce) beri, iç içe işlevler yeni kapsamlar sunduğundan ikiden fazla kapsam vardır, bu nedenle bir işlev içindeki bir işlev yerel kapsamına, kapalı işlevler kapsamına ve global kapsama (yerleşik olarak) erişebilir.
Brian

Üzgünüm, artık durum böyle değil. Python has two namespaces available. Global and local-to-something.
Rizwan Kassim

9

X nerede bulunur?

x, tanımladığınız şekilde bulunamadı. :-) Orada koyarsanız, code1 (global) veya code3 (local) içinde bulunabilir.

code2 (sınıf üyeleri) aynı sınıftaki yöntemlerin içindeki kodlarda görünmez - genellikle kendiniz kullanarak onlara erişirsiniz. code4 / code5 (döngüler) code3 ile aynı kapsamda yaşar, bu nedenle orada x'e yazarsanız, code3'te tanımlanan x örneğini değiştirirsiniz, yeni bir x oluşturmazsınız.

Python statik olarak kapsamlıdır, bu nedenle başka bir işleve 'spam' iletirseniz, spam gelen modüldeki globallere (kod1'de tanımlanmıştır) ve kapsamları içeren diğerlerine (aşağıya bakın) erişmeye devam edecektir. code2 üyelerine yine kendinden erişilirdi.

lambda'nın def'den farkı yoktur. Bir işlevin içinde kullanılan bir lambda'nız varsa, bu, iç içe bir işlev tanımlamakla aynıdır. Python 2.2 ve sonrasında, içiçe kapsamlar mevcuttur. Bu durumda, herhangi bir işlev iç içe yerleştirme düzeyinde x'i bağlayabilirsiniz ve Python en içteki örneği alır:

x= 0
def fun1():
    x= 1
    def fun2():
        x= 2
        def fun3():
            return x
        return fun3()
    return fun2()
print fun1(), x

2 0

fun3, x örneğini fun2 ile ilişkili işlev kapsamı olan en yakın kapsamdan görür. Ancak fun1 ve global olarak tanımlanan diğer x örnekleri etkilenmez.

Nested_scope'dan önce - Python 2.1 öncesi ve 2.1'de özellikle bir gelecek-içe aktarma kullanarak özelliği istemediğiniz sürece - fun1 ve fun2'nin kapsamları fun3 tarafından görülemez, bu nedenle S.Lott'un cevabı tutar ve global x'i elde edersiniz :

0 0

1

Python'da,

değer atanan herhangi bir değişken, atamanın göründüğü blok için yereldir.

Geçerli kapsamda bir değişken bulunamazsa, lütfen LEGB sırasına bakın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.