Python'daki değişkeni dış, ancak global olmayan kapsamda değiştirmek mümkün müdür?


109

Aşağıdaki kod verildiğinde:

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

B()Fonksiyon değişkenindeki kod bdış kapsamdadır, ancak genel kapsamda değildir. bDeğişkeni B()fonksiyon içinden değiştirmek mümkün müdür ? Elbette buradan okuyabilirim ve print()nasıl değiştirebilirim?


Üzgünüm elbette 2.7 :). Python 3 için kapsam belirleme kuralları değişti.
grigoryvp

bDeğişken olduğu sürece yapabilirsiniz . Bir atama b, dış kapsamı maskeleyecektir.
JimB

4
Bu, Python'un nonlocal2.x'e geri yüklenmemiş utançlarından biridir . Kapatma desteğinin içsel bir parçasıdır.
Glenn Maynard

Yanıtlar:


99

Python 3.x nonlocalanahtar kelimeye sahiptir . Bunun istediğinizi yaptığını düşünüyorum, ancak python 2 veya 3'ü çalıştırdığınızdan emin değilim.

Yerel olmayan ifade, listelenen tanımlayıcıların en yakın kapsama alanındaki önceden bağlı değişkenlere başvurmasına neden olur. Bu önemlidir, çünkü bağlama için varsayılan davranış, önce yerel ad alanını aramaktır. Bu ifade, kapsüllenmiş kodun değişkenleri global (modül) kapsamın yanı sıra yerel kapsamın dışında yeniden bağlamasına izin verir.

Python 2 için, genellikle sadece değiştirilebilir bir nesne (bir liste veya dikte gibi) kullanıyorum ve yeniden atamak yerine değeri değiştiriyorum.

misal:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

Çıktılar:

[1, 1]

16
Bunu yapmanın güzel bir yolu class nonlocal: passdış kapsamdadır. Daha sonra nonlocal.xiç kapsamda atanabilir.
kindall

1
Şimdiye kadar, basit ama çok yardımcı olan iki python ipucum var: sizinki ikincisi :) Teşekkürler @kindall!
swdev

@kindall bu harika bir hack - Python 3 sözdiziminden minimum düzeyde farklı ve değişken bir nesnenin etrafından dolaşmaktan çok daha okunaklı.
dimo414

2
@kindall çok temiz teşekkürler yığınlar :) muhtemelen farklı bir isme ihtiyaç duyuyor çünkü ileriye dönük uyumluluğu bozuyor. Python 3'te bu bir anahtar kelime çakışmasıdır ve a SyntaxError. Belki NonLocal?
Adam Terrey

veya teknik olarak bir sınıf olduğu için Nonlocal? :-)
kindall

19

Geçici bir kapsamı tutmak için boş bir sınıf kullanabilirsiniz. Değişken gibi ama biraz daha güzel.

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

Bu, aşağıdaki etkileşimli çıktıyı verir:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

Dış değişkeni "yerel olmayan" anahtar sözcükle tanımlamadığınız sürece, alanlarıyla birlikte sınıfın bir iç işlevde "görünür" olması, ancak değişkenlerin görünmemesi gariptir.
Celdor

12

Python'da biraz yeniyim, ancak bunun hakkında biraz okudum. İnanıyorum ki elde edeceğiniz en iyi şey, dıştaki değişkeninizi bir listeye sarmak olan Java çalışma ortamına benzer.

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

Düzenleme: Sanırım bu muhtemelen Python 3'ten önce doğruydu. Görünüşe göre nonlocalcevabınız bu.


4

Hayır, yapamazsın, en azından bu şekilde.

Çünkü "set işlemi" mevcut kapsamda dışını kapsayan yeni bir isim yaratacaktır.


"dışını kapatan" Ne demek istiyorsun? İç içe geçmiş bir işlevde adı b olan bir nesnenin tanımlanmasının , bu işlevin dış uzayında aynı ada sahip bir nesne üzerinde hiçbir etkisi yoktur
eyquem

1
@eyquem, yani atama ifadesi nerede olursa olsun, adı tüm mevcut kapsamda tanıtacaktır. Sorunun örnek kodu gibi, eğer şöyle ise: def C (): print (b) b = 2, "b = 2" tüm C func kapsamına b adını tanıtacaktır, bu nedenle print (b) olduğunda, b'yi yerel C işlevi kapsamında almaya çalışın ancak dıştaki değil, yerel b henüz başlatılmadı, bu nedenle bir hata olacaktır.
zchenah

1

Buna daha sonra daha güvenli ama daha ağır bir çözümle bakan herkes için. Değişkenleri parametre olarak aktarmaya gerek kalmadan.

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

1

Otomatik olarak işe yarayacak kısa cevap

Bu özel sorunu çözmek için bir python kitaplığı oluşturdum. Özgürlük altında serbest bırakılır, bu yüzden onu dilediğiniz gibi kullanın. Bunu https://github.com/hirsimaki-markus/SEAPIE adresinden yükleyebilir pip install seapieveya ana sayfaya göz atabilirsiniz.

user@pc:home$ pip install seapie

from seapie import Seapie as seapie
def A():
    b = 1

    def B():
        seapie(1, "b=2")
        print(b)

    B()
A()

çıktılar

2

argümanların aşağıdaki anlamı vardır:

  • İlk argüman yürütme kapsamıdır. 0 yerel B()anlamına gelir, 1 ebeveyn anlamına gelir A()ve 2 büyük ebeveyn anlamına gelir<module> yani küresel
  • İkinci bağımsız değişken, verilen kapsamda yürütmek istediğiniz bir dize veya kod nesnesidir.
  • Programınızın içindeki etkileşimli kabuk için argümanlar olmadan da çağırabilirsiniz .

Uzun cevap

Bu daha karmaşık. Seapie, CPython api kullanarak çağrı yığınındaki çerçeveleri düzenleyerek çalışır. CPython fiili standarttır, bu nedenle çoğu insanın endişelenmesine gerek yoktur.

Bunları okuyorsanız muhtemelen ilgilendiğiniz sihirli kelimeler şunlardır:

frame = sys._getframe(1)          # 1 stands for previous frame
parent_locals = frame.f_locals    # true dictionary of parent locals
parent_globals = frame.f_globals  # true dictionary of parent globals

exec(codeblock, parent_globals, parent_locals)

ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only

İkincisi, güncellemeleri yerel kapsama geçmeye zorlayacaktır. Bununla birlikte yerel kapsamlar, genel kapsamdan farklı şekilde optimize edilmiştir, bu nedenle, herhangi bir şekilde başlatılmamışlarsa, onları doğrudan çağırmaya çalıştığınızda, yeni nesneleri devreye sokmanın bazı sorunları vardır. Github sayfasından bu sorunları aşmanın birkaç yolunu kopyalayacağım.

  • Nesnelerinizi önceden atayın, içe aktarın ve tanımlayın
  • Önceden nesnelerinize yer tutucu atamak
  • Sembol tablosunu güncellemek için ana programda nesneyi kendisine yeniden atayın: x = locals () ["x"]
  • Optimizasyonu önlemek için doğrudan çağırmak yerine ana programda exec () kullanın. X do: exec ("x") çağırmak yerine

Kullanmanın exec()gitmek isteyeceğiniz bir şey olmadığını düşünüyorsanız, gerçek yerel sözlüğü (locals () tarafından döndürülen sözlüğü değil) güncelleyerek davranışı taklit edebilirsiniz . Https://faster-cpython.readthedocs.io/mutable.html adresinden bir örnek kopyalayacağım.

import sys
import ctypes

def hack():
    # Get the frame object of the caller
    frame = sys._getframe(1)
    frame.f_locals['x'] = "hack!"
    # Force an update of locals array from locals dict
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                          ctypes.c_int(0))

def func():
    x = 1
    hack()
    print(x)

func()

Çıktı:

hack!

0

Yapman gerektiğini düşünmüyorum . Çevreleyen bağlamda şeyleri değiştirebilen işlevler tehlikelidir, çünkü bu bağlam, işlevin bilgisi olmadan yazılabilir.

B'yi bir genel yöntem ve C'yi bir sınıfta özel bir yöntem yaparak açık hale getirebilirsiniz (muhtemelen en iyi yol); veya liste gibi değiştirilebilir bir tür kullanarak ve bunu açıkça C'ye ileterek:

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()

2
Bir işlevi, içindeki yuvalanmış işlevleri bilmeden nasıl yazabilirsiniz? İç içe geçmiş işlevler ve kapanışlar, içinde bulundukları işlevin ayrılmaz bir parçasıdır.
Glenn Maynard

Sizinkinde bulunan işlevlerin arayüzünü bilmeniz gerekir, ancak bunların içinde neler olup bittiğini bilmeniz gerekmez. Ayrıca, fonksiyonlarda biteni bilmek beklenemez onlar , vs diyoruz! Bir işlev, genel olmayan veya sınıf üyesi olmayan bir üyeyi değiştirirse, genellikle bunu arabirimi aracılığıyla açık hale getirmelidir, yani bunu bir parametre olarak almalıdır.
Figüran Bob

Python sizi elbette o kadar iyi olmaya zorlamaz, dolayısıyla nonlocalanahtar kelime - ama onu büyük bir dikkatle kullanmak size kalmıştır.
Figüran Bob

5
@Bob: Dil tuhaflıkları dışında, böyle bir kapatma kullanmanın hiç tehlikeli olduğunu görmedim. Yerelleri geçici bir sınıf ve yerel işlevleri sınıfta yöntemler olarak düşünün ve bundan daha karmaşık değil. YMMV sanırım.
Glenn Maynard

0

Yapabilirsiniz, ancak global ifadeyi kullanmanız gerekecek (global değişkenleri kullanırken her zaman olduğu gibi gerçekten iyi bir çözüm değil, ama işe yarıyor):

def A():
    global b
    b = 1

    def B():
      global b
      print( b )
      b = 2

    B()
A()

Bu çözümün olası dezavantajını açıklayan cevabımı görün
eyquem

4
Global bir değişken kullanmak tamamen farklıdır.
Glenn Maynard

0

__dict__Bu dış uzay global uzay olmadığında fonksiyonun dış uzayını veren bir fonksiyonun bir özniteliği olup olmadığını bilmiyorum == modül, bu fonksiyon iç içe geçmiş bir fonksiyon olduğunda durum budur, Python 3'te.

Ancak Python 2'de bildiğim kadarıyla böyle bir özellik yok.

Yani istediğiniz şeyi yapmanın tek yolu:

1) başkalarının söylediği gibi değiştirilebilir bir nesne kullanmak

2)

def A() :
    b = 1
    print 'b before B() ==', b

    def B() :
        b = 10
        print 'b ==', b
        return b

    b = B()
    print 'b after B() ==', b

A()

sonuç

b before B() == 1
b == 10
b after B() == 10

.

Nota

Cédric Julien'in çözümünün bir dezavantajı var:

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

sonuç

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

Yürütme sonrası global bA() değiştirildi ve o kadar whished değil edilebilir

Bu, yalnızca global ad alanında b tanımlayıcısına sahip bir nesne varsa geçerlidir.

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.