Python 2.x'te yerel olmayan anahtar kelime


117

Python 2.6'da bir kapatma uygulamaya çalışıyorum ve yerel olmayan bir değişkene erişmem gerekiyor ancak bu anahtar kelime python 2.x'de mevcut değil gibi görünüyor. Python'un bu sürümlerindeki kapanışlarda yerel olmayan değişkenlere nasıl erişilmelidir?

Yanıtlar:


125

İç işlevler yerel olmayan değişkenleri 2.x'te okuyabilir , onları yeniden bağlayamazlar . Bu can sıkıcı bir durum ama bunun üstesinden gelebilirsiniz. Sadece bir sözlük oluşturun ve verilerinizi burada öğeler olarak saklayın. İç fonksiyonlar yasak değildir mutasyona yerel olmayan değişkenler bakın nesneleri.

Wikipedia'daki örneği kullanmak için:

def outer():
    d = {'y' : 0}
    def inner():
        d['y'] += 1
        return d['y']
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

4
değeri sözlükten değiştirmek neden mümkün?
coelhudo

9
Eğer Çünkü @coelhudo yapabilirsiniz yerel olmayan değişkenleri değiştirin. Ancak yerel olmayan değişkenlere atama yapamazsınız. Örneğin, bu UnboundLocalError yükseltir: def inner(): print d; d = {'y': 1}. Burada print ddış okur , dböylece diç kapsamda yerel olmayan değişken oluşturur .
suzanshakya

21
Bu cevap için teşekkürler. Yine de terminolojiyi geliştirebileceğinizi düşünüyorum: "okuyabilir, değiştiremez" yerine, belki "referans verebilir, atayamaz". Yerel olmayan kapsamdaki bir nesnenin içeriğini değiştirebilirsiniz, ancak hangi nesneye başvurulacağını değiştiremezsiniz.
metamatt

3
Alternatif olarak, rastgele sınıf kullanabilir ve sözlük yerine nesneyi başlatabilirsiniz. Kodu değişmez değerlerle (sözlük anahtarları) doldurmadığından çok daha zarif ve okunaklı buluyorum, ayrıca yöntemlerden yararlanabilirsiniz.
Alois Mahdal

6
Python için "bind" veya "rebind" fiilini ve "değişken" yerine "isim" ismini kullanmanın en iyisi olduğunu düşünüyorum. Python 2.x'te bir nesneyi kapatabilirsiniz ancak adı orijinal kapsamda yeniden bağlayamazsınız. C gibi bir dilde, bir değişkenin bildirilmesi bir miktar depolama alanı ayırır (yığın üzerinde statik depolama veya geçici). Python'da ifade X = 1, adı Xbelirli bir nesneyle (bir intdeğerle 1) bağlar . X = 1; Y = Xiki adı aynı nesneye bağlar. Neyse, bazı nesnelerdir değişken ve onların değerlerini değiştirebilir.
steveha

37

Aşağıdaki çözüm, Elias Zamaria'nın yanıtından ilham almıştır , ancak bu yanıtın aksine, dış işlevin birden çok çağrısını doğru bir şekilde ele alır. "Değişken" inner.ygeçerli çağrı için yereldir outer. Sadece bu bir değişken değildir, çünkü bu yasaklanmıştır, ancak bir nesne niteliği (nesnenin innerkendisi işlevdir ). Bu çok çirkin (özniteliğin ancak innerişlev tanımlandıktan sonra oluşturulabileceğini unutmayın ) ancak etkili görünüyor.

def outer():
    def inner():
        inner.y += 1
        return inner.y
    inner.y = 0
    return inner

f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)

Çirkin olmamalı. İnner.y yerine external.y kullanın. Def interior'den önce external.y = 0 tanımlayabilirsiniz.
jgomo3

1
Tamam, yorumumun yanlış olduğunu görüyorum. Elias Zamaria, external.y çözümünü de uyguladı. Ama Nathaniel'in yorumladığı gibi [1], dikkatli olmalısın. Bu cevabın çözüm olarak tanıtılması gerektiğini düşünüyorum, ancak dış çözüm ve boşluklara dikkat edin. [1] stackoverflow.com/questions/3190706/…
jgomo3

Yerel olmayan değiştirilebilir durumu paylaşan birden fazla iç işlev istiyorsanız, bu çirkinleşir. Paylaşılan bir sayacı artıran ve azaltan dıştan dönen a inc()ve a deyin dec(). Ardından, mevcut sayaç değerini hangi işleve ekleyeceğinize ve diğer işlevlerden bu işleve başvuracağınıza karar vermelisiniz. Biraz tuhaf ve asimetrik görünüyor. Örneğin dec()bir satırda inc.value -= 1.
BlackJack

33

Bir sözlükten ziyade, yerel olmayan bir sınıf için daha az karmaşa vardır . @ ChrisB örneğini değiştirmek :

def outer():
    class context:
        y = 0
    def inner():
        context.y += 1
        return context.y
    return inner

Sonra

f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4

Her bir dış () çağrısı, bağlam adı verilen yeni ve farklı bir sınıf oluşturur (yalnızca yeni bir örnek değil). Bu nedenle, @ Nathaniel'in paylaşılan bağlam hakkında dikkatli olmasını önler .

g = outer()
assert g() == 1
assert g() == 2

assert f() == 5

Güzel! Bu gerçekten de sözlükten daha zarif ve okunabilir.
Vincenzo

Burada genel olarak yuvaları kullanmanızı tavsiye ederim. Sizi yazım hatalarından korur.
DerWeh

@DerWeh ilginç, bunu hiç duymadım . Herhangi bir sınıf için aynı şeyi söyleyebilir misiniz? Yazım hatalarından etkilenmekten nefret ediyorum.
Bob Stein

@BobStein Üzgünüm, ne demek istediğini gerçekten anlamıyorum. Ancak __slots__ = ()sınıfı kullanmak yerine bir nesne ekleyip yaratarak, örneğin context.z = 3bir AttributeError. Yuvaları tanımlamayan bir sınıftan miras almadıkları sürece tüm sınıflar için mümkündür.
DerWeh

@DerWeh Yazım hatalarını yakalamak için yuva kullandığımı hiç duymadım . Haklısın, bu sadece sınıf örneklerinde yardımcı olur, sınıf değişkenlerinde değil. Belki pyfiddle üzerine çalışan bir örnek oluşturmak istersiniz. En az iki ek hat gerektirecektir. Daha fazla dağınıklık olmadan bunu nasıl yapacağınızı hayal edemiyorum.
Bob Stein

14

Sanırım buradaki anahtar, "erişim" ile kastettiğin şey. Kapanış kapsamının dışındaki bir değişkeni okumakta herhangi bir sorun olmamalıdır, örneğin,

x = 3
def outer():
    def inner():
        print x
    inner()
outer()

beklendiği gibi çalışmalıdır (baskı 3). Ancak, x'in değerini geçersiz kılmak işe yaramaz, örneğin,

x = 3
def outer():
    def inner():
        x = 5
    inner()
outer()
print x

3. PEP-3104 anlayışıma göre , yerel olmayan anahtar kelimenin kapsaması budur. PEP'de belirtildiği gibi, aynı şeyi başarmak için bir sınıf kullanabilirsiniz (biraz dağınık):

class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
    def inner():
        ns.x = 5
    inner()
outer()
print ns.x

Bir sınıf oluşturmak ve onu örneklemek yerine, basitçe bir işlev yaratılabilir: def ns(): passardından ns.x = 3. Hoş değil ama gözüme biraz daha az çirkin geliyor.
davidchambers

2
Olumsuz oy için belirli bir neden var mı? Benimkinin en zarif çözüm olmadığını kabul ediyorum, ama işe yarıyor ...
ig0774

2
Peki ya class Namespace: x = 3?
Feuermurmel

3
Kapanışta bir referans yerine küresel bir referans oluşturduğundan, bu şekilde yerel olmayanı simüle ettiğini düşünmüyorum.
Carmelos

1
Ben @CarmeloS katılıyorum, kod son blok değil PEP-3104 Python 2. benzer bir şey başarmak için yol olarak önerdiklerini yapıyor nsbaşvurabileceğiniz neden olan küresel bir nesnedir ns.xmodül düzeyinde printen sonunda deyimi .
martineau

13

Python 2'de yerel olmayan değişkenleri uygulamanın başka bir yolu vardır, buradaki cevaplardan herhangi birinin herhangi bir nedenle istenmemesi durumunda:

def outer():
    outer.y = 0
    def inner():
        outer.y += 1
        return outer.y
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

Değişkenin atama ifadesinde işlevin adını kullanmak gereksizdir, ancak bana değişkeni bir sözlüğe koymaktan daha basit ve daha temiz görünüyor. Değer, tıpkı Chris B.'nin cevabında olduğu gibi, bir aramadan diğerine hatırlanır.


19
Lütfen dikkat: bu şekilde uygulandığında, bunu yaparsanız f = outer()ve daha sonra yaparsanız g = outer(), o zaman fsayacı sıfırlanacaktır. Bunun nedeni , her birinin kendi bağımsız değişkenine sahip olması yerine, her ikisinin de tek bir outer.y değişkeni paylaşmasıdır . Bu kod, Chris B'nin cevabından daha estetik görünse de, outerbirden fazla kez aramak istiyorsanız, sözcük kapsamını taklit etmenin tek yolu onun yöntemi gibi görünüyor .
Nathaniel

@Nathaniel: Bunu doğru anlayıp anlamadığıma bir bakayım. İçin atama outer.yişlev çağrısı (örnek) yerel bir şey içermeyen outer()isme bağlı işlev nesnesinin bir öznitelik, ama devrettikleri outeronun içinde kapatmakta kapsamı. Ve bu nedenle , bu kapsamda olduğu bilinmesi koşuluyla outer.y, herhangi bir başka isim yerine yazılı olarak da eşit derecede iyi bir şekilde kullanılabilir outer. Bu doğru mu?
Marc van Leeuwen

1
Düzeltme, "bu kapsamda bağlandıktan" sonra şunu söylemeliydim: türü niteliklerin ayarlanmasına izin veren bir nesneye (bir işlev veya herhangi bir sınıf örneği gibi). Ayrıca, bu kapsam aslında istediğimizden daha geniş olduğu için, bu son derece çirkin bir çözüm önermez miydi: outer.yadı kullanmak yerine inner.y(çünkü innerçağrının içinde bağlı outer()olduğundan, tam olarak istediğimiz kapsam budur), ancak içsel tanımdan inner.y = 0 sonraki ilklendirme (nesnenin niteliği yaratıldığında var olması gerektiği gibi), ama tabii ki daha önce ? return inner
Marc van Leeuwen

@MarcvanLeeuwen Yorumunuz Elias'ın inner.y ile çözümüne ilham vermiş gibi görünüyor. İyi düşündün.
Ankur Agarwal

Genel değişkenlere erişmek ve bunları güncellemek, muhtemelen çoğu insanın bir kapanışın yakalamalarını değiştirirken yapmaya çalıştığı şey değildir.
binki

12

İşte Alois Mahdal'ın başka bir cevapla ilgili bir yorumda yaptığı bir öneriden ilham alan bir şey :

class Nonlocal(object):
    """ Helper to implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


def outer():
    nl = Nonlocal(y=0)
    def inner():
        nl.y += 1
        return nl.y
    return inner

f = outer()
print(f(), f(), f()) # -> (1 2 3)

Güncelleme

Yakın zamanda buna dönüp baktıktan sonra, nasıl bir dekoratör gibi olduğu karşısında şaşkına döndüm - bunu bir başkası olarak uygulamanın onu daha genel ve kullanışlı hale getireceğini anladığında (bunu yapmak, okunabilirliğini bir dereceye kadar azalttığı tartışmalı bir şekilde).

# Implemented as a decorator.

class Nonlocal(object):
    """ Decorator class to help implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self._vars = kwargs

    def __call__(self, func):
        for k, v in self._vars.items():
            setattr(func, k, v)
        return func


@Nonlocal(y=0)
def outer():
    def inner():
        outer.y += 1
        return outer.y
    return inner


f = outer()
print(f(), f(), f()) # -> (1 2 3)

Her iki sürümün de hem Python 2 hem de 3'te çalıştığını unutmayın.


Bu, okunabilirlik ve sözcük kapsamını koruma açısından en iyisi gibi görünüyor.
Aaron S. Kurland

3

Python'un kapsam belirleme kurallarında bir siğil vardır - atama, bir değişkeni hemen kapsayan işlev kapsamına yerel yapar. Global bir değişken için, bunu şu şekilde çözersiniz:global anahtar kelime çözersiniz.

Çözüm, iki kapsam arasında paylaşılan, değişken değişkenler içeren, ancak kendisine atanmamış bir değişken aracılığıyla referans verilen bir nesneyi tanıtmaktır.

def outer(v):
    def inner(container = [v]):
        container[0] += 1
        return container[0]
    return inner

Bir alternatif, bazı kapsam korsanlığıdır:

def outer(v):
    def inner(varname = 'v', scope = locals()):
        scope[varname] += 1
        return scope[varname]
    return inner

Parametrenin adını almak için bazı hileler bulabilir outerve sonra onu değişken adı olarak geçirebilirsiniz, ancak isme güvenmeden outerbir Y birleştirici kullanmanız gerekir.


Her iki sürüm de bu özel durumda çalışır, ancak bunların yerini almaz nonlocal. locals()bir sözlük oluşturur outer()anda ler yerli inner()tanımlanmış ama Sözlük anlamına değişiyor değil değiştirmek viçinde outer(). Bu, kapalı bir değişken paylaşmak isteyen daha fazla iç işleviniz olduğunda artık çalışmayacaktır. A deyin inc()ve dec()bu paylaşılan bir sayacı artırır ve azaltır.
BlackJack

@BlackJack nonlocalbir python 3 özelliğidir.
Marcin

Evet biliyorum. Soru genelnonlocal olarak Python 2'de Python 3'lerin etkisinin nasıl elde edileceğiydi . Fikirleriniz genel durumu değil, yalnızca tek bir iç işlevi olanı kapsar . Bir örnek için bu öze bir bakın . Her iki iç işlevin de kendi kapsayıcıları vardır. Diğer cevapların zaten önerildiği gibi, dış fonksiyon kapsamında değiştirilebilir bir nesneye ihtiyacınız var.
BlackJack

@BlackJack Soru bu değil, ama girdiniz için teşekkürler.
Marcin

Evet o bir soru. Sayfanın üst kısmına bir göz atın. adinsa Python 2.6 sahiptir ve olmayan bir kapatılmasıyla yerel olmayan değişkenlere erişebilmesi gerekir nonlocalPython 3'te tanıtılan anahtar kelime
BlackJack

3

Bunu yapmanın başka bir yolu (çok ayrıntılı olmasına rağmen):

import ctypes

def outer():
    y = 0
    def inner():
        ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
        return y
    return inner

x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3

1
Aslında çözümü sözlük kullanarak tercih ediyorum ama bu harika :)
Ezer Fernandes

0

Yukarıdaki Martineau zarif çözümünü pratik ve biraz daha az zarif bir kullanım durumuna genişletmek:

class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
     nl = nonlocals( n=0, m=1 )
     def inner():
         nl.n += 1
     inner() # will increment nl.n

or...
    sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

def __init__(self, a_dict):
    self.__dict__.update(a_dict)

-3

Global bir değişken kullanın

def outer():
    global y # import1
    y = 0
    def inner():
        global y # import2 - requires import1
        y += 1
        return y
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

Şahsen ben global değişkenleri sevmiyorum. Ancak, teklifim https://stackoverflow.com/a/19877437/1083704 cevabına dayanıyor

def report():
        class Rank: 
            def __init__(self):
                report.ranks += 1
        rank = Rank()
report.ranks = 0
report()

Kullanıcının global bir değişken bildirmesi gerektiğinde ranks, her zaman report. Geliştirmem, işlev değişkenlerini kullanıcıdan başlatma ihtiyacını ortadan kaldırıyor.


Sözlük kullanmak çok daha iyidir. Örneğe içinde innerbaşvurabilir, ancak atayamazsınız, ancak anahtarlarını ve değerlerini değiştirebilirsiniz. Bu, global değişkenlerin kullanılmasını önler.
johannestaas
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.