Python: Laden Sie Variablen in einem Diktat in den Namespace


72

Ich möchte eine Reihe lokaler Variablen verwenden, die in einer Funktion außerhalb der Funktion definiert sind. Ich übergebe also x=locals()den Rückgabewert.

Wie kann ich alle in diesem Wörterbuch definierten Variablen in den Namespace außerhalb der Funktion laden, sodass x['variable']ich anstelle des Zugriffs auf den Wert einfach verwenden kann variable?


10
Das klingt nach einer schrecklichen Idee.
John La Rooy

5
Weniger schrecklich als das Konzept von 'aus Modulimport *', da Sie vermutlich mehr Wissen darüber haben, was im Diktat steht.
Binary Phile

4
@ JohnLaRooy warum ist das eine schreckliche Idee?
Charlie Parker

Antworten:


99

Betrachten Sie die BunchAlternative:

class Bunch(object):
  def __init__(self, adict):
    self.__dict__.update(adict)

Wenn Sie also ein Wörterbuch haben dund auf seine Werte mit der Syntax x.fooanstatt mit dem ungeschickten zugreifen (lesen) möchten d['foo'], tun Sie dies einfach

x = Bunch(d)

Dies funktioniert sowohl innerhalb als auch außerhalb Funktionen - und es ist enorm sauberer und sicherer als Injektion din globals()! Erinnern Sie sich an die letzte Zeile aus dem Zen von Python ...:

>>> import this
The Zen of Python, by Tim Peters
   ...
Namespaces are one honking great idea -- let's do more of those!

14
Aber das sieht so elegant aus : globals().update(locals())! (Ich Kind, ich Kind)
Nathanismus

1
Ich verwende dieses globals (). Update (local ()) in einfachen Plotskripten, bei denen ich einen einfachen Unterschied habe, wenn ich das Modul interaktiv lade oder auf einmalige Weise über die Befehlszeile einführe. Ich nehme an, ich könnte einfach den if- Namen == ' main ' oben in die Datei setzen und dann keine Funktionen haben ... aber das scheint genauso unelagant. Auf jeden Fall wäre ich interessiert, wenn es einen besseren Weg gibt, den Wert innerhalb einer Funktion an das äußere Modul zu übergeben.
Mathtick

Ich liebe das, aber es ist nicht rekursiv. Irgendwelche Lösungen für verschachtelte Wörterbücher?
Nomen Nescio

@CleverGuy Sie können es rekursiv schreiben, solange Sie es nicht verwenden update: Stattdessen erhalten Sie k,vPaare mit adict.items()und if type(v) is dict: self.__dict__[k]=Bunch(v)und dann else: self.__dict__[k]=vfür alles andere. Sie können wie ein Namespace auf das Ergebnis zugreifen, einschließlich der Zuweisung neuer Werte zu den verschachtelten Bereichen.
Clay

53

Anstatt ein eigenes Objekt zu erstellen, können Sie Folgendes verwenden argparse.Namespace:

from argparse import Namespace
ns = Namespace(**mydict)

Um das Gegenteil zu tun:

mydict = vars(ns)

2
Endlich etwas sicheres, lesbares und bereits in der Standardbibliothek implementiertes. Ich frage mich, warum dies honking great ideaim argparseModul liegt.
Arthur Khazbs

2
Jetzt verfügbar alstypes.SimpleNamespace
PaulMcG

Dies ist eine super Lösung und verdient positive Stimmen. Siehe repl.it/repls/NavyFlusteredSearchengine, um zu sehen, wie einfach es ist
Mike Crowe

Es gibt eine subtile Einschränkung im "inversen" Teil: Wenn mydict = vars(ns)sowohl das neue Wörterbuch als auch der ursprüngliche Namespace ausgeführt werden, verweist dies auf dasselbe Objekt ( wenn Sie eines der beiden ändern, wird auch das andere geändert). Tun , mydict = vars(ns).copy()das zu vermeiden.
Aurelio Jargas

16

Dies ist ein absolut gültiger Fall, um Variablen in einem lokalen Bereich in einen anderen lokalen Bereich zu importieren, solange man weiß, was er / sie tut. Ich habe oft gesehen, dass solcher Code auf nützliche Weise verwendet wird. Ich muss nur vorsichtig sein, um den gemeinsamen globalen Raum nicht zu verschmutzen.

Sie können Folgendes tun:

adict = { 'x' : 'I am x', 'y' : ' I am y' }
locals().update(adict)
blah(x)
blah(y)

def func (x): Locals (). Update (x) print (pos) print (Locals ()) func ({"pos": 23}) ---------------- -------------------------------------------------- --------- NameError Traceback (letzter Aufruf zuletzt) ​​<ipython-input-211-4eae42dbc044> in <module> () 3 print (pos) 4 print (local ()) ----> 5 func ({"pos": 23}) <ipython-input-211-4eae42dbc044> in func (x) 1 def func (x): 2 Locals (). update (x) ----> 3 print (pos) 4 print (local ()) 5 func ({"pos": 23}) NameError: Name 'pos' ist nicht definiert
O.rka


5

Das Importieren von Variablen in einen lokalen Namespace ist ein gültiges Problem und wird häufig in Vorlagen-Frameworks verwendet.

Gibt alle lokalen Variablen einer Funktion zurück:

return locals()

Dann wie folgt importieren:

r = fce()
for key in r.keys():
   exec(key + " = r['" + key + "']")

2
Funktioniert perfekt, ist aber sehr langsam ... kein Problem für einfache Testprogramme.
snowcrash09

1

Die Bunch- Antwort ist in Ordnung, aber es fehlt an Rekursion und richtig __repr__und__eq__ Ordnung, Funktionen, um zu simulieren, was Sie bereits mit einem Diktat tun können. Der Schlüssel zur Rekursion liegt auch nicht nur in der Rekursion von Diktaten, sondern auch in Listen, sodass Diktate in Listen ebenfalls konvertiert werden.

Ich hoffe, dass diese beiden Optionen Ihre Anforderungen erfüllen (möglicherweise müssen Sie die Typprüfungen __elt()für komplexere Objekte anpassen ; diese wurden hauptsächlich bei JSON-Importen getestet, also sehr einfache Kerntypen).

  1. Der Bunch- Ansatz (gemäß vorheriger Antwort) - Objekt nimmt ein Diktat und konvertiert es rekursiv. repr(obj)gibt zurück Bunch({...}), das in ein äquivalentes Objekt neu interpretiert werden kann.
class Bunch(object):
    def __init__(self, adict):
        """Create a namespace object from a dict, recursively"""
        self.__dict__.update({k: self.__elt(v) for k, v in adict.items()})

    def __elt(self, elt):
        """Recurse into elt to create leaf namepace objects"""
        if type(elt) is dict:
            return type(self)(elt)
        if type(elt) in (list, tuple):
            return [self.__elt(i) for i in elt]
        return elt

    def __repr__(self):
        """Return repr(self)."""
        return "%s(%s)" % (type(self).__name__, repr(self.__dict__))

    def __eq__(self, other):
        return self.__dict__ == other.__dict__
  1. Der SimpleNamespace Ansatz - da types.SimpleNamespacebereits implementiert __repr__und __eq__alles, was Sie brauchen , ist eine rekursive zu implementieren __init__Methode:
import types
class RecursiveNamespace(types.SimpleNamespace):
    # def __init__(self, /, **kwargs):  # better, but Python 3.8+
    def __init__(self, **kwargs):
        """Create a SimpleNamespace recursively"""
        self.__dict__.update({k: self.__elt(v) for k, v in kwargs.items()})

    def __elt(self, elt):
        """Recurse into elt to create leaf namepace objects"""
        if type(elt) is dict:
            return type(self)(**elt)
        if type(elt) in (list, tuple):
            return [self.__elt(i) for i in elt]
        return elt

Die RecursiveNamespace- Klasse verwendet Schlüsselwortargumente, die natürlich aus einem de-referenzierten Diktat (ex **mydict) stammen können.


Lassen Sie uns sie jetzt auf die Probe stellen:

adict = {'foo': 'bar', 'baz': [{'aaa': 'bbb', 'ccc': 'ddd'}]}
a = Bunch(adict)
b = RecursiveNamespace(**adict)
print('a:', str(a))
print('b:', str(b))
print('a == b :', str(a == b))

Das Ergebnis ist:

a: Bunch({'foo': 'bar', 'baz': [Bunch({'aaa': 'bbb', 'ccc': 'ddd'})]})
b: RecursiveNamespace(baz=[RecursiveNamespace(aaa='bbb', ccc='ddd')], foo='bar')
a == b : True

Obwohl es sich um unterschiedliche Klassen handelt, da beide mit äquivalenten Namespaces initialisiert wurden und ihre __eq__Methode nur den Namespace ( self.__dict__) vergleicht, wird beim Vergleich der beiden Namespace-Objekte zurückgegebenTrue

Möglicherweise stellen Sie auch fest, dass ich die type(self)(...)Verwendung des Klassennamens anstelle der Verwendung des Klassennamens verwende. Dies hat zwei Vorteile: Erstens kann die Klasse umbenannt werden, ohne dass rekursive Aufrufe aktualisiert werden müssen, und zweitens, wenn die Klasse in eine Unterklasse unterteilt ist, wird eine Rekursion unter Verwendung des Unterklassennamens durchgeführt. Es ist auch der Name, der in __repr__( type(self).__name__) verwendet wird.


0

Verwendet das folgende Snippet (PY2), um einen rekursiven Namespace aus meinen dict (yaml) -Konfigurationen zu erstellen:

class NameSpace(object):
    def __setattr__(self, key, value):
        raise AttributeError('Please don\'t modify config dict')


def dump_to_namespace(ns, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            leaf_ns = NameSpace()
            ns.__dict__[k] = leaf_ns
            dump_to_namespace(leaf_ns, v)
        else:
            ns.__dict__[k] = v

config = NameSpace()
dump_to_namespace(config, config_dict)

-1

Es gibt immer diese Option, ich weiß nicht, dass es die beste Methode da draußen ist, aber sie funktioniert auf jeden Fall. Angenommen, Typ (x) = dikt

for key, val in x.items():  # unpack the keys from the dictionary to individual variables
    exec (key + '=val')

Dies funktioniert gut, bis der Praktikant Sie dabei erwischt und denkt, dass "exec" das Beste seit Python ist. Und nur für den Fall, dass Praktikanten dies lesen, tun Sie dies nicht. Dies beeinträchtigt die Lesbarkeit Ihres Codes, erschwert das Debuggen, ist unglaublich langsam und kann eine ganze Reihe von Problemen verursachen, die ich nicht einmal richtig vorhersehen und erklären kann.
Cedar

Wenn Sie diesen Weg gehen, aktualisieren Sie einfach die Einheimischen direkt. zB locals().update(x).
BrianHVB
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.