解构绑定字典内容


84

我正在尝试“分解”字典,并在其键之后将值与变量名称相关联。就像是

params = {'a':1,'b':2}
a,b = params.values()

但是由于字典没有排序,因此不能保证params.values()将返回的顺序为(a, b)。有没有很好的方法可以做到这一点?


3
懒?也许...但是我当然已经展示了最简单的例子。理想情况下,我想在params.items中使用x作为对象:eval('%s =%f'%x),但我想eval()不允许赋值。
hatmatrix

7
@JochenRitzel我敢肯定,ES6(JavaScript)的大多数用户都喜欢新的对象分解语法:let {a, b} = params。它提高了可读性,并且与您要谈论的Zen完全兼容。
安迪

8
@Andy我的恋爱对象解构的JS。从字典中提取某些键的一种干净,简单且易读的方法。我来到这里是希望在Python中找到类似的东西。
Rotareti '17

2
我也喜欢ES6对象解构,但是由于ES6的Map对象不支持解构的原因,恐怕它不能在Python中工作。键不仅是ES6 Map和Python dict中的字符串。同样,尽管我喜欢ES6中对象分解的“ pluck”风格,但分配风格并不简单。这里发生了什么? let {a: waffles} = params。即使您已经习惯了,也要花几秒钟才能弄清楚。
约翰·克里斯托弗·琼斯

1
@ naught101有时在取舍时会很有用,令人讨厌。对于用户:在Python中,任何对象都可以提供自己的str / repr方法。为使JSON序列化更容易,甚至可能对稍微复杂的关键对象(例如,命名为元组)执行此操作。现在您不知所措,为什么不能按名称破坏键。另外,为什么这对项目有用但对attrs不起作用?许多图书馆更喜欢attrs。对于实施者而言,此ES6功能使符号(可绑定的名称)和字符串混淆:在JavaScript中是合理的,但Python在起作用。另外,它看起来很难看。
约翰·克里斯托弗·琼斯

Answers:


10

如果您担心使用locals词典时会遇到问题,并且希望遵循最初的策略,可以使用python 2.7和3.1集合中的Ordered Dictionary.OrderedDicts可以按首次插入字典的顺序来恢复字典项


@Stephen您很幸运,因为OrderedDicts已从python 3.1移植到python 2.7
joaquin 2010年

我很期待...!以为那对我来说一直是Python的症结所在(订购的字典没有装满其他电池)
hatmatrix 2010年

4
当前,在3.5+中,所有字典都是有序的。现在还没有“保证”,这意味着它可能会改变。
Charles Merriam

3
保证从3.6+起。
naught101 '19

117
from operator import itemgetter

params = {'a': 1, 'b': 2}

a, b = itemgetter('a', 'b')(params)

除了详细的lambda函数或字典理解之外,还可以使用内置库。


9
这可能是公认的答案,因为这是最Python化的方法。您甚至可以attrgetter从适用于对象属性(obj.a)的同一标准库模块中扩展答案以使用。这与JavaScript的主要区别在于obj.a === obj["a"]
约翰·克里斯托弗·琼斯

如果字典上不存在该键,KeyError则会引发异常
Tasawar Hussain

2
但是您现在在销毁语句中输入了两次a和b
奥托

1
@JohnChristopherJones在我看来,这并不是很自然,使用现有的内容并不意味着它可以理解。我怀疑许多人会立即以实际代码理解。另一方面,建议a, b = [d[k] for k in ('a','b')]的方式更自然/可读(形式更常见)。这仍然是一个有趣的答案,但这不是最直接的解决方案。
cglacet

@cglacet我认为这取决于您必须阅读/实现多少次。如果您例行选择相同的3个键,则像get_id = itemgetter(KEYS)稍后使用这样的操作serial, code, ts = get_id(document)会更简单。诚然,您必须对高阶函数感到满意,但是Python通常对它们非常满意。例如,请参阅有关装饰器的文档,例如@contextmanager
约翰·克里斯托弗·琼斯

28

一种比Jochen的建议少重复的方法是使用助手功能。这使您可以灵活地以任何顺序列出变量名,并且仅解构dict中的子集:

pluck = lambda dict, *args: (dict[arg] for arg in args)

things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')

另外,可以代替键的排序和获取值来代替joaquin的OrderedDict。唯一要注意的是,您需要按字母顺序指定变量名,并分解dict中的所有内容:

sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)

4
这是一个小问题,但是如果您要为lambda变量分配a ,则最好将常规函数语法与一起使用def
亚瑟塔卡

投票否决,您不能像JS这样操作,它将是const {a,b} = {a:1,b:2}
PirateApp

1
你已经实现了itemgetteroperator标准库。:)
约翰·克里斯托弗·琼斯

16

Python仅能“分解”序列,而不能字典。因此,要编写所需的内容,必须将所需的条目映射到适当的顺序。就我自己而言,我能找到的最接近的匹配是(不是很性感):

a,b = [d[k] for k in ('a','b')]

这也适用于生成器:

a,b = (d[k] for k in ('a','b'))

这是一个完整的示例:

>>> d = dict(a=1,b=2,c=3)
>>> d
{'a': 1, 'c': 3, 'b': 2}
>>> a, b = [d[k] for k in ('a','b')]
>>> a
1
>>> b
2
>>> a, b = (d[k] for k in ('a','b'))
>>> a
1
>>> b
2


10

这是另一种方法,类似于JS中的解构分配如何工作:

params = {'b': 2, 'a': 1}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

我们所做的是将params字典解压缩为键值(使用**)(如Jochen的回答所示),然后我们将这些值取入了lambda签名并根据键名进行了分配-这是一个奖励-我们还可以获得什么是一本字典不能在lambda的签名,所以如果你有:

params = {'b': 2, 'a': 1, 'c': 3}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

应用lambda之后,其余变量现在将包含:{'c':3}

对于从字典中删除不需要的键很有用。

希望这可以帮助。


有趣的是,另一方面,我认为在功能上会更好。您可能会多次使用它,这样您就可以在上面加上名称。(当我说函数时,我指的不是lambda函数)。
cglacet

3

试试这个

d = {'a':'Apple', 'b':'Banana','c':'Carrot'}
a,b,c = [d[k] for k in ('a', 'b','c')]

结果:

a == 'Apple'
b == 'Banana'
c == 'Carrot'

3

警告1:如文档中所述,不能保证此方法可在所有Python实现上都可以使用:

CPython实现细节:此函数依赖于解释器中的Python堆栈框架支持,并不能保证在所有Python实现中都存在。如果在没有Python堆栈框架支持的实现中运行,则此函数返回None。

警告2:此函数的确会使代码更短,但可能与Python尽可能明确的哲学相矛盾。而且,尽管您可以创建一个类似的功能来使用属性而不是键,但是它并没有解决John Christopher Jones在评论中指出的问题。这只是一个演示,如果您确实愿意,您可以这样做!

def destructure(dict_):
    if not isinstance(dict_, dict):
        raise TypeError(f"{dict_} is not a dict")
    # the parent frame will contain the information about
    # the current line
    parent_frame = inspect.currentframe().f_back

    # so we extract that line (by default the code context
    # only contains the current line)
    (line,) = inspect.getframeinfo(parent_frame).code_context

    # "hello, key = destructure(my_dict)"
    # -> ("hello, key ", "=", " destructure(my_dict)")
    lvalues, _equals, _rvalue = line.strip().partition("=")

    # -> ["hello", "key"]
    keys = [s.strip() for s in lvalues.split(",") if s.strip()]

    if missing := [key for key in keys if key not in dict_]:
        raise KeyError(*missing)

    for key in keys:
        yield dict_[key]
In [5]: my_dict = {"hello": "world", "123": "456", "key": "value"}                                                                                                           

In [6]: hello, key = destructure(my_dict)                                                                                                                                    

In [7]: hello                                                                                                                                                                
Out[7]: 'world'

In [8]: key                                                                                                                                                                  
Out[8]: 'value'

该解决方案使您可以选择一些键,而不是全部,例如JavaScript。用户提供的字典也很安全


1

好吧,如果您想在课堂上学习这些,可以随时这样做:

class AttributeDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttributeDict, self).__init__(*args, **kwargs)
        self.__dict__.update(self)

d = AttributeDict(a=1, b=2)

真好 谢谢,但似乎是一种将调用语法从d ['a']更改为da的方法?也许添加隐式访问这些参数的方法...
hatmatrix 2010年

0

基于@ShawnFumo的答案,我想到了这个:

def destruct(dict): return (t[1] for t in sorted(dict.items()))

d = {'b': 'Banana', 'c': 'Carrot', 'a': 'Apple' }
a, b, c = destruct(d)

(请注意字典中的项目顺序)


0

由于字典可以保证在Python> = 3.7中保持其插入顺序,所以这意味着现在这样做是完全安全和惯用的:

params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)

输出:

1
2

2
大!自从我上任以来只花了9年的时间。:)
hatmatrix

1
问题是您无法执行b, a = params.values(),因为它使用的是订单而不是名称。
Corman

1
@ruohola这就是此解决方案的问题。这取决于名称的字典顺序,而不是名称本身,这就是我对此否决的原因。
Corman

1
如果它依赖于键的顺序,那么这将是一个糟糕的解决方案。itemgetter是执行此操作最有效的方法。这不适用于Python 3.6或更低版本,并且由于它依赖顺序,因此一开始可能会造成混淆。
科尔曼

-1

我不知道这是不是很好的风格,但是

locals().update(params)

会成功的 然后a,您有了,bparams字典中的任何内容都可以作为相应的局部变量使用。


2
请注意,如果用户以任何方式提供“ params”字典并且未正确过滤,则这可能是一个很大的安全问题。
Jacek Konieczny

9
要引用docs.python.org/library/functions.html#locals注意:不应修改此字典的内容;必须修改字典的内容。更改可能不会影响解释器使用的局部变量和自由变量的值。
Jochen Ritzel

4
吸取了教训。谢谢,伙计们。
约翰内斯·查拉
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.