Подтвердить что ты не робот

"распаковка" прошедшего словаря в пространство имен функций в Python?

В работе, которую я делаю, у меня часто есть параметры, которые мне нужно группировать в подмножества для удобства:

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

Я делаю это, передавая несколько словарей. Большую часть времени я использую переданный словарь напрямую, т.е.:

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

В некоторых функциях мне нужно напрямую обращаться к переменным, и все становится громоздким; Мне действительно нужны эти переменные в локальном пространстве имен. Я хочу иметь возможность сделать что-то вроде:

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

но обновления словаря, которые возвращают locals(), не гарантируют фактическое обновление пространства имен.

Здесь очевидный ручной способ:

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}

Это приводит к трем повторениям списка параметров для каждой функции. Это можно автоматизировать с помощью декоратора:

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

Это приводит к трем повторениям для декоратора плюс два за каждую функцию, поэтому лучше, если у вас много функций.

Есть ли лучший способ? Может быть, что-то использует eval? Спасибо!

4b9b3361

Ответ 1

Вы всегда можете передать словарь в качестве аргумента функции. Например,

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

Ответ 2

Если вам нравится синтаксис d.variable лучше, чем d['variable'], вы можете поместить словарь в почти тривиальный объект "связки" , например это:

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

Это не совсем доставляет содержимое словаря в локальное пространство имен, но приближается, если вы используете короткие имена для объектов.

Ответ 3

Предполагая, что все ключи в вашем словаре квалифицируются как идентификаторы, Вы можете просто сделать это:

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

Ответ 4

Это то, что я использую в качестве обходного пути locals().update(d1):

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

Ответ 5

Это похоже на вашу идею декоратора, но она немного более общая, поскольку она позволяет передавать произвольное количество dicts на foo, и декоратор не должен ничего знать о ключах в dicts или порядок аргументов при вызове базовой foo функции.

#!/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

Ответ 6

Я не думаю, что вы можете получить больше удобства для распаковки dict в Python. Итак, здесь идет обязательное "если это больно, не делайте этого".

Доступ к сопоставлению элементов более громоздкий, чем доступ к атрибутам в Python, поэтому, возможно, вам следует передавать экземпляры пользовательских классов вместо dicts.

Ответ 7

Я думаю, что общая мудрость заключается в том, что "не используйте модуль inspect в производственном коде", и я в основном согласен с этим. Как таковой, я думаю, что это плохая идея сделать следующее в производственном коде. Но если вы работаете над python, поддерживающим фреймы (например, CPython), это должно работать:

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

Он просто захватывает фактический dict из кадра вызывающего, который при вызове внутри тела функции должен быть пространством имен функций. Я не знаю, есть ли или нет ситуация при использовании CPython, когда locals() не просто делает это в любом случае; предупреждение в документации может заключаться в том, чтобы сказать: "последствия изменения dict, возвращаемого locals(), зависят от реализации на основе python". Таким образом, хотя он работает для изменения этого dict в CPython, он не может быть в другой реализации.

ОБНОВЛЕНИЕ: Этот метод на самом деле не работает.

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

Как показано ddaa, в компиляции функций имеется более глубокая магия, которая учитывает локальные переменные. Таким образом, вы можете обновить dict, но вы не можете увидеть обновление с обычным поиском пространства имен.

Ответ 8

Здесь метод распаковки в одном вкладыше:

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

lambda args a и b - это ключи, которые я хочу распаковать в x и y в этом порядке. **_ следует игнорировать любые другие ключи в словаре, т.е. c.

Ответ 9

Я написал пакет Python с именем var_arguments для удобного объединения и развязывания аргументов, которые должны быть полезны здесь; он доступен в github.

Вместо того, чтобы писать, скажите:

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}

Вы можете написать:

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

Я написал решение, подобное этому, чтобы соответствовать тому, что вы, похоже, собираетесь делать: словари приходят и уходят в группы, и мы минимизируем количество раз, когда мы упоминаем имена ключей в словарях и/или вручную обращаемся к ним. В частности, нам нужно указать только x, y, a и b.

Как это работает, в основном, это то, что @use_dargs изменяет f2 так, что он принимает необязательный аргумент ключевого слова dargs, который, если он есть, должен предоставить список словарей (dargs = [d1, d2]). Ключ/значение пары в этих словарях добавляются к аргументам ключевого слова, которые в противном случае подают вызов функции, причем аргументы ключевого слова имеют наивысший приоритет, d2 имеют второй по высоте и d1 с наименьшим приоритетом. Соответственно, вы можете вызвать f2 различными способами и получить тот же результат:

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 предназначен для случая, когда у вас есть один словарь, который содержит старые значения для всех ключей, которые вас интересуют, и другого словаря, который содержит новые значения для всех этих ключей (а также, возможно, другие, которых вы не хотите). Например:

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)

Вот некоторые дополнительные трюки, чтобы удалить избыточность упоминания имен переменных несколько раз в теле функции, которое обрабатывает var_arguments. Во-первых, мы можем изменить:

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

в

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

Аналогично, мы можем изменить:

f(x=x,y=y)

в

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

В более общем плане, если у нас есть словарь xy с ключами x и y, и если наши локальные переменные включают a и b, мы можем изменить:

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

в

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