Ответ 1
Вы всегда можете передать словарь в качестве аргумента функции. Например,
dict = {'a':1, 'b':2}
def myFunc(a=0, b=0, c=0):
print(a,b,c)
myFunc(**dict)
В работе, которую я делаю, у меня часто есть параметры, которые мне нужно группировать в подмножества для удобства:
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? Спасибо!
Вы всегда можете передать словарь в качестве аргумента функции. Например,
dict = {'a':1, 'b':2}
def myFunc(a=0, b=0, c=0):
print(a,b,c)
myFunc(**dict)
Если вам нравится синтаксис d.variable
лучше, чем d['variable']
, вы можете поместить словарь в почти тривиальный объект "связки" , например это:
class Bunch:
def __init__(self, **kw):
self.__dict__.update(kw)
Это не совсем доставляет содержимое словаря в локальное пространство имен, но приближается, если вы используете короткие имена для объектов.
Предполагая, что все ключи в вашем словаре квалифицируются как идентификаторы, Вы можете просто сделать это:
adict = { 'x' : 'I am x', 'y' : ' I am y' }
for key in adict.keys():
exec(key + " = adict['" + key + "']")
blah(x)
blah(y)
Это то, что я использую в качестве обходного пути locals().update(d1)
:
def f(d1,d2)
exec ','.join(d1) + ', = d1.values()'
blah(x)
blah(y)
Это похоже на вашу идею декоратора, но она немного более общая, поскольку она позволяет передавать произвольное количество 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
Я не думаю, что вы можете получить больше удобства для распаковки dict в Python. Итак, здесь идет обязательное "если это больно, не делайте этого".
Доступ к сопоставлению элементов более громоздкий, чем доступ к атрибутам в Python, поэтому, возможно, вам следует передавать экземпляры пользовательских классов вместо dicts.
Я думаю, что общая мудрость заключается в том, что "не используйте модуль 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
, но вы не можете увидеть обновление с обычным поиском пространства имен.
Здесь метод распаковки в одном вкладыше:
x,y = (lambda a,b,**_: (a,b))(**{'a':'x', 'b':'y', 'c': 'z'})
lambda args a
и b
- это ключи, которые я хочу распаковать в x
и y
в этом порядке. **_
следует игнорировать любые другие ключи в словаре, т.е. c
.
Я написал пакет 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])