Ist es möglich, Variablen in Python zu ändern, die sich im äußeren, aber nicht im globalen Bereich befinden?

106

Gegeben folgenden Code:

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()

Denn der Code in der B()Funktionsvariablen bbefindet sich im äußeren Bereich, jedoch nicht im globalen Bereich. Ist es möglich, bVariablen innerhalb der B()Funktion zu ändern ? Sicher kann ich es von hier aus lesen und print(), aber wie kann ich es ändern?

grigoryvp
quelle
Sorry, natürlich 2.7 :). Für Python 3 wurden die Gültigkeitsbereichsregeln geändert.
Grigoryvp
Sie können so lange wie bveränderlich ist. Eine Zuweisung an bmaskiert den äußeren Bereich.
JimB
4
Es ist eine von Pythons Verlegenheiten, nonlocaldie nicht auf 2.x zurückportiert wurde. Es ist ein wesentlicher Bestandteil der Schließungsunterstützung.
Glenn Maynard

Antworten:

96

Python 3.x hat das nonlocalSchlüsselwort . Ich denke, das macht, was Sie wollen, aber ich bin nicht sicher, ob Sie Python 2 oder 3 ausführen.

Die nichtlokale Anweisung bewirkt, dass die aufgelisteten Bezeichner auf zuvor gebundene Variablen im nächsten umschließenden Bereich verweisen. Dies ist wichtig, da das Standardverhalten für die Bindung darin besteht, zuerst den lokalen Namespace zu durchsuchen. Die Anweisung ermöglicht es gekapseltem Code, Variablen außerhalb des lokalen Bereichs neben dem globalen (Modul-) Bereich neu zu binden.

Für Python 2 verwende ich normalerweise nur ein veränderbares Objekt (wie eine Liste oder ein Diktat) und mutiere den Wert, anstatt ihn neu zuzuweisen.

Beispiel:

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

foo()

Ausgänge:

[1, 1]
Adam Wagner
quelle
16
Ein guter Weg, dies zu tun, ist class nonlocal: passim äußeren Bereich. Dann nonlocal.xkann im inneren Bereich zugewiesen werden.
Kindall
1
Bis jetzt habe ich bereits zwei Python-Tipps, die einfach, aber sehr hilfreich sind: Ihre ist die zweite :) Danke @kindall!
Swdev
@kindall, das ist ein großartiger Hack - er unterscheidet sich minimal von der Python 3-Syntax und ist viel besser lesbar als das Weitergeben eines veränderlichen Objekts.
dimo414
2
@kindall sehr ordentlich danke Haufen :) wahrscheinlich brauchen einen anderen Namen, weil es die Vorwärtskompatibilität bricht. In Python 3 handelt es sich um einen Schlüsselwortkonflikt, der a verursacht SyntaxError. Vielleicht NonLocal?
Adam Terrey
oder, da es technisch eine Klasse ist Nonlocal? :-)
bitte
19

Sie können eine leere Klasse verwenden, um einen temporären Bereich zu speichern. Es ist wie das Veränderliche, aber etwas hübscher.

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()

Dies ergibt die folgende interaktive Ausgabe:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined
chrisk
quelle
Dies ist seltsam, dass die Klasse mit ihren Feldern in einer inneren Funktion "sichtbar" ist, Variablen jedoch nicht, es sei denn, Sie definieren die äußere Variable mit dem Schlüsselwort "nonlocal".
Celdor
12

Ich bin ein bisschen neu in Python, aber ich habe ein bisschen darüber gelesen. Ich glaube, das Beste, was Sie bekommen werden, ähnelt der Java-Problemumgehung, bei der Ihre äußere Variable in eine Liste eingeschlossen wird.

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

# The output is '2'

Bearbeiten: Ich denke, dies war wahrscheinlich vor Python 3 wahr. Sieht aus wie nonlocalist Ihre Antwort.

Mike Edwards
quelle
4

Nein, das kannst du zumindest auf diese Weise nicht.

Weil die "Set-Operation" im aktuellen Bereich einen neuen Namen erstellt, der den äußeren abdeckt.

Zchenah
quelle
"welche bedecken die äußere" Was meinst du? Das Definieren eines Objekts mit dem Namen b in einer verschachtelten Funktion hat keinen Einfluss auf ein Objekt mit demselben Namen im Weltraum dieser Funktion
eyquem
1
@eyquem, dh wo immer sich die Zuweisungsanweisung befindet, wird der Name im gesamten aktuellen Bereich eingeführt. Wenn der Beispielcode der Frage wie folgt lautet: def C (): print (b) b = 2, führt "b = 2" den Namen b in den gesamten C-Funktionsbereich ein. Wenn also (b) gedruckt wird, wird dies der Fall sein Versuchen Sie, b in den lokalen C-Funktionsbereich zu bekommen, aber nicht in den äußeren. Das lokale b wurde noch nicht initialisiert, daher tritt ein Fehler auf.
Zchenah
1

Für alle, die dies viel später betrachten, ist eine sicherere, aber schwerere Problemumgehung. Ohne dass Variablen als Parameter übergeben werden müssen.

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

Die kurze Antwort, die nur automatisch funktioniert

Ich habe eine Python-Bibliothek erstellt, um dieses spezielle Problem zu lösen. Es wird unter der Unlisence veröffentlicht, verwenden Sie es also, wie Sie möchten. Sie können es mit https://github.com/hirsimaki-markus/SEAPIE installieren pip install seapieoder die Homepage hier besuchen

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()

Ausgänge

2

Die Argumente haben folgende Bedeutung:

  • Das erste Argument ist der Ausführungsbereich. 0 würde lokal bedeuten B(), 1 bedeutet Eltern A()und 2 würde Großeltern <module>alias global bedeuten
  • Das zweite Argument ist eine Zeichenfolge oder ein Codeobjekt, das Sie im angegebenen Bereich ausführen möchten
  • Sie können es auch ohne Argumente für die interaktive Shell in Ihrem Programm aufrufen

Die lange Antwort

Das ist komplizierter. Seapie bearbeitet die Frames im Call Stack mit CPython API. CPython ist der De-facto-Standard, sodass sich die meisten Menschen keine Sorgen machen müssen.

Die magischen Wörter, an denen Sie wahrscheinlich interessiert sind, wenn Sie dies lesen, sind die folgenden:

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

Letzteres erzwingt, dass Aktualisierungen in den lokalen Bereich übertragen werden. Lokale Bereiche werden jedoch anders als der globale Bereich optimiert, sodass die Einführung neuer Objekte einige Probleme verursacht, wenn Sie versuchen, sie direkt aufzurufen, wenn sie in keiner Weise initialisiert werden. Ich werde einige Möglichkeiten kopieren, um diese Probleme von der Github-Seite zu umgehen

  • Assayn, importieren und definieren Sie Ihre Objekte im Voraus
  • Ordnen Sie Ihren Objekten zuvor einen Platzhalter zu
  • Ordnen Sie das Objekt im Hauptprogramm neu zu, um die Symboltabelle zu aktualisieren: x = local () ["x"]
  • Verwenden Sie exec () im Hauptprogramm, anstatt direkt aufzurufen, um eine Optimierung zu vermeiden. Anstatt x do: exec ("x") aufzurufen

Wenn Sie der exec()Meinung sind, dass die Verwendung nicht das ist, mit dem Sie arbeiten möchten, können Sie das Verhalten emulieren, indem Sie das wahre lokale Wörterbuch aktualisieren (nicht das von local () zurückgegebene). Ich werde ein Beispiel von https://faster-cpython.readthedocs.io/mutable.html kopieren

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()

Ausgabe:

hack!
Markus Hirsimäki
quelle
0

Ich denke nicht, dass du das machen willst. Funktionen, die Dinge in ihrem umschließenden Kontext verändern können, sind gefährlich, da dieser Kontext ohne Kenntnis der Funktion geschrieben werden kann.

Sie können es explizit machen, indem Sie entweder B zu einer öffentlichen Methode und C zu einer privaten Methode in einer Klasse machen (wahrscheinlich der beste Weg). oder indem Sie einen veränderlichen Typ wie eine Liste verwenden und diese explizit an C übergeben:

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

A()
Sideshow Bob
quelle
2
Wie können Sie eine Funktion schreiben, ohne die darin enthaltenen verschachtelten Funktionen zu kennen? Verschachtelte Funktionen und Abschlüsse sind ein wesentlicher Bestandteil der Funktion, in der sie enthalten sind.
Glenn Maynard
Sie müssen über die Schnittstelle der in Ihnen enthaltenen Funktionen Bescheid wissen, aber Sie sollten nicht wissen müssen, was in ihnen vor sich geht. Es ist auch nicht zu erwarten, dass Sie wissen, was in den von ihnen aufgerufenen Funktionen usw. vor sich geht! Wenn eine Funktion ein nicht globales oder nicht klassenbezogenes Mitglied ändert, sollte sie dies normalerweise über ihre Schnittstelle explizit machen, dh als Parameter verwenden.
Sideshow Bob
Python zwingt Sie natürlich nicht dazu, so gut zu sein, daher das nonlocalSchlüsselwort - aber es liegt an Ihnen, es mit großer Vorsicht zu verwenden.
Sideshow Bob
5
@ Bob: Ich habe nie festgestellt, dass die Verwendung solcher Verschlüsse überhaupt gefährlich ist, außer aufgrund von Sprachfehlern. Stellen Sie sich Einheimische als temporäre Klasse vor und lokale Funktionen als Methoden für die Klasse, und es ist nicht komplizierter. YMMV, denke ich.
Glenn Maynard
0

Sie können, aber Sie müssen die globale Anweisung verwenden (keine wirklich gute Lösung wie immer, wenn Sie globale Variablen verwenden, aber es funktioniert):

def A():
    global b
    b = 1

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

    B()
A()
Cédric Julien
quelle
Siehe meine Antwort, die den möglichen Nachteil dieser Lösung erklärt
eyquem
4
Die Verwendung einer globalen Variablen ist völlig anders.
Glenn Maynard
0

Ich weiß nicht, ob es ein Attribut einer Funktion gibt, das __dict__den Außenraum der Funktion angibt, wenn dieser Außenraum nicht der globale Raum ist == das Modul, was der Fall ist, wenn die Funktion eine verschachtelte Funktion ist. in Python 3.

Aber in Python 2 gibt es meines Wissens kein solches Attribut.

Die einzigen Möglichkeiten, das zu tun, was Sie wollen, sind:

1) Verwenden eines veränderlichen Objekts, wie von anderen gesagt

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()

Ergebnis

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

.

Kein

Die Lösung von Cédric Julien hat einen Nachteil:

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

Ergebnis

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

Das globale b nach der Ausführung von A()wurde geändert und kann möglicherweise nicht gewünscht werden

Dies ist nur dann der Fall, wenn sich im globalen Namespace ein Objekt mit der Kennung b befindet

eyquem
quelle