it-swarm.com.de

ein übergebenen Wörterbuch in den Namensraum der Funktion in Python "entpacken"?

In meiner Arbeit habe ich oft Parameter, die ich zur Vereinfachung in Teilmengen gruppieren muss:

d1 = {'x':1,'y':2}
d2 = {'a':3,'b':4}

Ich mache dies, indem ich mehrere Wörterbücher übergebe. Meistens verwende ich das übergebene Wörterbuch direkt, d.h.

def f(d1,d2):
    for k in d1:
        blah( d1[k] )

In einigen Funktionen muss ich direkt auf die Variablen zugreifen, und die Dinge werden umständlich. Ich möchte diese Variablen wirklich im lokalen Namensraum. Ich möchte etwas können wie:

def f(d1,d2)
    locals().update(d1)
    blah(x)
    blah(y)    

die Aktualisierungen des Wörterbuchs, die von localals () zurückgegeben werden, können den Namespace jedoch nicht wirklich aktualisieren.

Hier ist der offensichtliche manuelle Weg:

def f(d1,d2):
    x,y,a,b = d1['x'],d1['y'],d2['a'],d2['b']
    blah(x)
    return {'x':x,'y':y}, {'a':a,'b':b}

Dies führt zu drei Wiederholungen der Parameterliste pro Funktion. Dies kann mit einem Dekorateur automatisiert werden:

def unpack_and_repack(f):
    def f_new(d1, d2):
        x,y,a,b = f(d1['x'],d1['y'],d2['a'],d3['b'])
        return {'x':x,'y':y}, {'a':a,'b':b}
    return f_new
@unpack
def f(x,y,a,b):
    blah(x)
    blah(y)
    return x,y,a,b

Dies führt zu drei Wiederholungen für den Dekorateur plus zwei pro Funktion. Es ist also besser, wenn Sie viele Funktionen haben.

Gibt es einen besseren Weg? Vielleicht etwas mit eval? Vielen Dank!

27
Andrew Wagner

Sie können ein Wörterbuch immer als Argument an eine Funktion übergeben. Zum Beispiel, 

dict = {'a':1, 'b':2}
def myFunc(a=0, b=0, c=0):
    print(a,b,c)
myFunc(**dict)
28
Bear

Wenn Sie die d.variable-Syntax besser als d['variable'] mögen, können Sie das Wörterbuch in ein fast triviales "Bündel" -Objekt wie folgt umschließen:

class Bunch:
    def __init__(self, **kw):
        self.__dict__.update(kw)

Wörterbuchinhalte werden nicht genau in den lokalen Namespace eingefügt. Sie kommen jedoch näher, wenn Sie kurze Namen für die Objekte verwenden. 

10

Angenommen, alle Schlüssel in Ihrem Wörterbuch können als Bezeichner qualifiziert werden.

adict = { 'x' : 'I am x', 'y' : ' I am y' }
for key in  adict.keys():
  exec(key + " = adict['" + key + "']")
blah(x)
blah(y)
7
Thava

Dies ähnelt Ihrer Dekorateuridee, ist jedoch etwas allgemeiner, da Sie eine beliebige Anzahl von Diktaten an foo übergeben können, und der Dekorateur muss weder etwas über die Schlüssel in den Diktaten noch über die Reihenfolge wissen Argumente beim Aufruf der zugrunde liegenden foo-Funktion.

#!/usr/bin/env python
d1 = {'x':1,'y':2}
d2 = {'a':3,'b':4}

def unpack_dicts(f):
    def f_new(*dicts):
        new_dict={}
        for d in dicts:
            new_dict.update(d)
        return f(**new_dict)
    return f_new

@unpack_dicts
def foo(x,y,a,b):
    print x,y,a,b

foo(d1,d2)
# 1 2 3 4
4
unutbu

Dies ist, was ich als locals().update(d1) Workaround verwende:

def f(d1,d2)
    exec ','.join(d1) + ', = d1.values()'
    blah(x)
    blah(y)
4
Thomas Holder

Hier ist eine Auspackmethode in einem Einzeiler:

x,y = (lambda a,b,**_: (a,b))(**{'a':'x', 'b':'y', 'c': 'z'})

die Lambda-Argumente a und b sind die Schlüssel, die ich in dieser Reihenfolge in x und y entpacken möchte. **_ ist dazu da, andere Schlüssel im Wörterbuch zu ignorieren, d. h. c.

3
Doody P

Ich glaube, die gängige Meinung ist: "Verwenden Sie das inspect-Modul nicht im Produktionscode", und ich stimme dem meistens zu. Daher halte ich es für eine schlechte Idee, im Produktionscode Folgendes zu tun. Wenn Sie jedoch an einem Python arbeiten, der Frames unterstützt (wie CPython), ist dies sollte:

>>> def framelocals():
...    return inspect.currentframe(1).f_locals
... 
>>> def foo(ns):
...    framelocals().update(ns)
...    print locals()
... 
>>> foo({'bar': 17})
{'ns': {'bar': 17}, 'bar': 17}

Es holt sich einfach das eigentliche dict aus dem Rahmen des Aufrufers, der bei Aufruf innerhalb eines Funktionskörpers den Namensraum der Funktion darstellen sollte. Ich weiß nicht, ob es eine Situation gibt oder nicht gibt, wenn CPython verwendet wird, wenn locals() dies sowieso nicht einfach tut. Die Warnung in der Dokumentation könnte lauten: "Die Auswirkungen der Änderung des von locals() zurückgegebenen dict sind von der Implementierung der Python abhängig". Während es funktioniert, um dict in CPython zu ändern, ist es in einer anderen Implementierung möglicherweise nicht möglich. 

UPDATE: Diese Methode funktioniert eigentlich nicht. 

>>> def flup(ns):
...    framelocals().update(ns)
...    print locals()
...    print bar
... 
>>> flup({'bar': 17})
{'ns': {'bar': 17}, 'bar': 17}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in flup
NameError: global name 'bar' is not defined

Wie ddaa vorgeschlagen, gibt es etwas tiefere Magie in der Funktionskompilierung, die die lokalen Variablen zur Kenntnis nimmt. Sie können also dict aktualisieren, das Update kann jedoch nicht mit einem normalen lokalen Namespace-Lookup angezeigt werden.

2
Matt Anderson

Ich glaube nicht, dass Sie mehr Komfort für das Auspacken von Diktieren in Python erhalten können. Also, hier kommt die obligatorische Antwort: "Wenn das weh tut, tu das nicht".

Der Zuordnungselementzugriff IS ist umständlicher als der Attributzugriff in Python. Daher sollten Sie Instanzen von benutzerdefinierten Klassen anstelle von Diktaten übergeben.

2
ddaa

Ich habe ein Python-Paket namens var_arguments geschrieben, um Argumente bequem zu bündeln und zu entflechten, die hier nützlich sein sollten. es ist verfügbar auf github .

Anstelle des Schreibens sagen Sie:

def f(d1,d2):
    x,y,a,b = d1['x'],d1['y'],d2['a'],d2['b']
    y=x+a
    return {'x':x,'y':y}, {'a':a,'b':b}

Sie könnten schreiben:

from var_arguments import recon_dict, use_dargs

def f(d1,d2):
  r=f2(dargs=[d1,d2])
  return recon_dict(d1,r), recon_dict(d2,r)

@use_dargs
def f2(x,y,a,b):
  y=x+a
  return locals()

Ich habe die Lösung so geschrieben, dass sie Ihren Wünschen entspricht: Die Wörterbücher werden in Gruppen ankommen und wieder verlassen, und wir minimieren die Anzahl, in der wir die Namen der Schlüssel in den Wörterbüchern erwähnen und/oder manuell darauf zugreifen. Im Einzelnen müssen wir x, y, a und b nur einmal auf diese Weise erwähnen.

Im Grunde funktioniert das so, dass @use_dargs f2 so modifiziert, dass es ein optionales dargs-Schlüsselwortargument akzeptiert, das, falls vorhanden, eine Liste von Wörterbüchern (dargs = [d1, d2]) bereitstellen sollte. Die Schlüssel/Wert-Paare in diesen Wörterbüchern werden zu den Schlüsselwortargumenten hinzugefügt, die andernfalls für den Aufruf der Funktion geliefert werden, wobei die Schlüsselwortargumente die höchste Priorität haben, d2 die zweithöchste Priorität und D1 die niedrigste Priorität hat. Dementsprechend können Sie f2 auf verschiedene Arten aufrufen und dasselbe Ergebnis erzielen:

f2(1,2,3,4)
f2(1,a=3,dargs=[dict(y=2,b=4)])
f2(dargs=[dict(x=1,y=2),dict(a=3,b=4)])

recon_dict ist für den Fall, dass Sie ein Wörterbuch mit alten Werten für alle Schlüssel, an denen Sie interessiert sind, und ein anderes Wörterbuch mit dennew-Werten für alle diese Schlüssel (und möglicherweise auch andere) enthält du willst nicht Zum Beispiel:

old_d=dict(a=8,b=9) # I want these keys, but they're old values
xyab=dict(x=1,y=2,a=3,b=4) # These are the new values, but I don't want all of them
new_d=recon_dict(old_d,xyab)
assert new_d==dict(a=3,b=4)

Hier einige zusätzliche Tricks, um die Redundanz der mehrfachen Erwähnung von Variablennamen innerhalb eines Funktionskörpers, der von var_arguments behandelt wird, zu entfernen . Zunächst können wir Folgendes ändern:

{'x':x,'y':y}

in:

ddict('x,y',locals())

Gleichermaßen können wir ändern:

f(x=x,y=y)

in:

dcall(f,'x,y',locals())

Allgemeiner gesagt, wenn wir ein Wörterbuch xy mit den Schlüsseln x und y haben und unsere lokalen Variablen a und b enthalten, können wir Folgendes ändern:

f(x=xy['x'],y=xy['y'],a=a,b=b)

in:

ldcall(f,'x,y,a,b',[locals(),xy])
0
Marc Coram

Sie können sorcery verwenden:

from sorcery import unpack_dict

x, y = unpack_dict(d1)
0
Alex Hall