Wie schreibe ich in Python 2 in eine Variable im übergeordneten Bereich?

77

Ich habe den folgenden Code in einer Funktion:

stored_blocks = {}
def replace_blocks(m):
    block = m.group(0)
    block_hash = sha1(block)
    stored_blocks[block_hash] = block
    return '{{{%s}}}' % block_hash

num_converted = 0
def convert_variables(m):
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)

Das Hinzufügen von Elementen stored_blocksfunktioniert einwandfrei, aber ich kann num_converteddie zweite Unterfunktion nicht erhöhen :

UnboundLocalError: Lokale Variable 'num_converted', auf die vor der Zuweisung verwiesen wird

Ich könnte verwenden, globalaber globale Variablen sind hässlich und ich brauche diese Variable wirklich nicht, um überhaupt global zu sein.

Ich bin also gespannt, wie ich in eine Variable im Bereich der übergeordneten Funktion schreiben kann. nonlocal num_convertedwürde wahrscheinlich den Job machen, aber ich brauche eine Lösung, die mit Python 2.x funktioniert.

DiebMaster
quelle
4
Entgegen der weit verbreiteten Meinung (nach dieser Art von Fragen zu urteilen) defist dies nicht das einzige Schlüsselwort, das einen Namespace definiert: Es gibt auch class.
Jochen Ritzel

Antworten:

87

Problem: Dies liegt daran, dass die Scoping-Regeln von Python dement sind. Das Vorhandensein des +=Zuweisungsoperators markiert das Ziel num_convertedals lokal für den Bereich der umschließenden Funktion, und in Python 2.x gibt es keine solide Möglichkeit, von dort aus auf nur eine Bereichsebene zuzugreifen. Nur das globalSchlüsselwort kann variable Referenzen aus dem aktuellen Bereich entfernen und bringt Sie direkt nach oben.

Fix: Verwandeln Sie sich num_convertedin ein Einzelelement-Array.

num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name
Marcelo Cantos
quelle
7
Können Sie erklären, warum das notwendig ist? Ich hätte erwartet, dass OP-Code funktioniert.
Björn Pollex
36
Weil Pythons Scoping-Regeln dement sind. Das Vorhandensein des +=Zuweisungsoperators markiert das Ziel num_convertedals lokal für den Bereich der umschließenden Funktion, und in Python 2.x gibt es keine solide Möglichkeit, von dort aus auf nur eine Bereichsebene zuzugreifen. Nur das globalSchlüsselwort kann variable Referenzen aus dem aktuellen Bereich entfernen und bringt Sie direkt nach oben.
Marcelo Cantos
6
Das ist nicht klug, das ist eigentlich ziemlich schlechter Code. Es gibt Klassen (siehe Kommentar unter der Frage). Diese Version verwendet eine globale Variable, die Sie immer vermeiden sollten. Nicht verwenden globalbedeutet nicht, dass Sie keine globale Variable haben.
Schlamar
15
@schlamar: Die fragliche Variable ist im wahrsten Sinne des Wortes nicht global. Der Anfangssatz des OP sieht vor, dass sich der gesamte von ihnen präsentierte Codeblock innerhalb einer Funktion befindet.
Marcelo Cantos
2
@schlamar: Das ist viel Gerüst für eine Variable. Wenn Sie mehr Klarheit benötigen, als meine Antwort liefert (wirklich?), Würde ich es vorziehen scope = {'num_converted':0} … scope['num_converted'] += 1.
Marcelo Cantos
29

(siehe unten für die bearbeitete Antwort)

Sie können etwas verwenden wie:

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted += 1
    return '<%%= %s %%>' % name

convert_variables.num_converted = 0

Auf diese Weise num_convertedfunktioniert es als C-ähnliche "statische" Variable der Methode convert_variable


(bearbeitet)

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
    return '<%%= %s %%>' % name

Auf diese Weise müssen Sie den Zähler im Hauptverfahren nicht initialisieren.

PabloG
quelle
3
Recht. Beachten Sie, dass Sie das Attribut nach dem Definieren der Funktion erstellen müssen , obwohl dies seltsam erscheint. convert_variables.num_converted
Marc van Leeuwen
@PabloG befriedigendste Antwort auf diese Frage, abgesehen von nicht lokal in 3.x; Die Verwendung des veränderlichen Typs [] ist eine kostengünstige Problemumgehung.
user2290820
9

Die Verwendung des globalSchlüsselworts ist in Ordnung. Wenn Sie schreiben:

num_converted = 0
def convert_variables(m):
    global num_converted
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

... num_converted wird keine "globale Variable" (dh sie wird an keiner anderen unerwarteten Stelle sichtbar), sondern bedeutet nur, dass sie im Inneren geändert werden kann convert_variables. Das scheint genau das zu sein, was Sie wollen.

Anders ausgedrückt, num_convertedist bereits eine globale Variable. Die global num_convertedSyntax sagt Python lediglich: "Erstellen Sie in dieser Funktion keine lokale num_convertedVariable, sondern verwenden Sie die vorhandene globale.

Emile
quelle
4
globalin 2.x funktioniert so ziemlich wie nonlocalin 3.x.
Daniel Roseman
2
"Anders ausgedrückt, num_converted ist bereits eine globale Variable" - mein Code wird in einer Funktion ausgeführt. Daher ist er derzeit nicht global.
ThiefMaster
2
Ah, ich habe mich nicht um den Teil "Innerhalb einer Funktion" gekümmert, sorry - in diesem Fall ist Marcelos Liste der Länge Eins möglicherweise eine bessere (aber hässliche) Lösung.
Emile
7

Was ist mit einer Klasseninstanz, um den Status zu halten? Sie instanziieren eine Klasse und übergeben Instanzmethoden an Subs, und diese Funktionen haben einen Verweis auf self ...

Seb
quelle
7
Klingt etwas übertrieben und wie eine Lösung, die von einem Java-Programmierer stammt. ; p
ThiefMaster
1
@ThiefMaster Warum ist das übertrieben? Wenn Sie auf den übergeordneten Bereich zugreifen möchten, sollten Sie eine Klasse in Python verwenden.
Schlamar
2
@schlamar, weil in jeder anderen vernünftigen Sprache mit erstklassigen Funktionen Unterstützung (JS, funktionale Sprachen) Verschlüsse einfach funktionieren.
Dzugaru
6

Ich habe ein paar Bemerkungen.

Erstens wird eine Anwendung für solche verschachtelten Funktionen beim Umgang mit unformatierten Rückrufen angezeigt, wie sie in Bibliotheken wie xml.parsers.expat verwendet werden. (Dass die Autoren der Bibliothek diesen Ansatz gewählt haben, mag zu beanstanden sein, aber ... es gibt dennoch Gründe, ihn zu verwenden.)

Zweitens: Innerhalb einer Klasse gibt es viel schönere Alternativen zum Array (num_converted [0]). Ich nehme an, das war es, worüber Sebastjan sprach.

class MainClass:
    _num_converted = 0
    def outer_method( self ):
        def convert_variables(m):
            name = m.group(1)
            self._num_converted += 1
            return '<%%= %s %%>' % name

Es ist immer noch seltsam genug, um einen Kommentar im Code zu verdienen ... Aber die Variable ist zumindest lokal für die Klasse.

Steve White
quelle
Hey, willkommen bei Stack Overflow - aber eine "Bemerkung" als Antwort zu posten, ist hier nicht wirklich etwas, was du tun kannst. Wir haben Kommentare dazu (Sie benötigen jedoch einige Vertreter, um sie zu veröffentlichen - aber veröffentlichen Sie keine Antworten, nur weil Sie noch nicht genügend Vertreter für einen Kommentar haben)
ThiefMaster
5
Hallo, du bist auch willkommen! Ich verstehe Ihre Bemerkung oder einige der von Ihnen verwendeten Begriffe nicht. Und ich bin ein vielbeschäftigter Mann, der nur versucht, hilfreich zu sein!
Steve White
Kein Problem - werfen Sie einen Blick auf stackoverflow.com/about . Obwohl es immer hilfreich ist, hilfreich zu sein, wird ein Kommentar, der veröffentlicht wird, irgendwann gelöscht, egal wie gut er ist.
ThiefMaster
0

Geändert von: https://stackoverflow.com/a/40690954/819544

Sie können das inspectModul nutzen, um auf das globale Diktat des aufrufenden Bereichs zuzugreifen und in dieses zu schreiben. Das bedeutet, dass dieser Trick sogar genutzt werden kann, um über eine in einem importierten Submodul definierte verschachtelte Funktion auf den aufrufenden Bereich zuzugreifen.

import inspect 

def get_globals(scope_level=0):
    return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]

num_converted = 0
def foobar():
    get_globals(0)['num_converted'] += 1

foobar()
print(num_converted) 
# 1

Spielen Sie scope_levelnach Bedarf mit dem Argument. Die Einstellung scope_level=1funktioniert für eine in einem Submodul definierte Funktion, scope_level=2für die in einem Dekorator in einem Submodul definierte innere Funktion usw.

NB: Nur weil Sie das können , heißt das nicht, dass Sie es sollten.

David Marx
quelle