解构绑定字典内容
- 2025-03-18 08:55:00
- admin 原创
- 56
问题描述:
我正在尝试“解构”一本字典,并将值与其键后的变量名称关联起来。例如
params = {'a':1,'b':2}
a,b = params.values()
但是由于字典是无序的,因此无法保证params.values()
将按 的顺序返回值(a, b)
。 有没有什么好的方法可以做到这一点?
解决方案 1:
from operator import itemgetter
params = {'a': 1, 'b': 2}
a, b = itemgetter('a', 'b')(params)
除了复杂的 lambda 函数或字典理解之外,还不如使用内置库。
解决方案 2:
怎么没有人发布最简单的方法?
params = {'a':1,'b':2}
a, b = params['a'], params['b']
解决方案 3:
有一种比 Jochen 的建议更少重复的方法,那就是使用辅助函数。这可以灵活地以任何顺序列出变量名,并且只解构字典中的一部分内容:
pluck = lambda dict, *args: (dict.get(arg, -1) for arg in args)
things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')
另外,除了 Joaquin 的 OrderedDict 之外,您还可以对键进行排序并获取值。唯一的问题是您需要按字母顺序指定变量名称并解构字典中的所有内容:
sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))
things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)
解决方案 4:
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
解决方案 5:
以下是另一种与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}
对于从字典中省略不需要的键很有用。
如果你不想保留rest
,你可以这样做:
a, b = (lambda a, b, **_): (a, b))(**params)
解决方案 6:
也许你真的想做这样的事?
def some_func(a, b):
print a,b
params = {'a':1,'b':2}
some_func(**params) # equiv to some_func(a=1, b=2)
解决方案 7:
如果你担心使用本地字典时出现的问题,并且更愿意遵循你原来的策略,那么 Python 2.7 和 3.1 collections.OrderedDicts中的有序字典可以让你按照字典项首次插入的顺序恢复它们
解决方案 8:
使用 Python 3.10,您可以执行以下操作:
d = {"a": 1, "b": 2}
match d:
case {"a": a, "b": b}:
print(f"A is {a} and b is {b}")
但它增加了两个额外的缩进级别,并且您仍然必须重复键名。
解决方案 9:
滥用导入系统
该from ... import
语句允许我们解构和绑定对象的属性名称。当然,它只适用于sys.modules
字典中的对象,因此可以使用如下技巧:
import sys, types
mydict = {'a':1,'b':2}
sys.modules["mydict"] = types.SimpleNamespace(**mydict)
from mydict import a, b
一个更严重的黑客攻击是编写一个上下文管理器来加载和卸载模块:
with obj_as_module(mydict, "mydict_module"):
from mydict_module import a, b
通过将__getattr__
模块的方法直接指向__getitem__
字典的方法,上下文管理器也可以避免使用SimpleNamespace(**mydict)
。
请参阅这个答案以了解该想法的实现和一些扩展。
sys.modules
你也可以暂时用感兴趣的字典替换整个字典,而import a, b
不用from
。
解决方案 10:
警告 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 中一样。对于用户提供的字典来说,这也是安全的
解决方案 11:
好吧,如果你想要在课堂上实现这些,你可以这样做:
class AttributeDict(dict):
def __init__(self, *args, **kwargs):
super(AttributeDict, self).__init__(*args, **kwargs)
self.__dict__.update(self)
d = AttributeDict(a=1, b=2)
解决方案 12:
寻找其他答案,因为这不会满足字典中意外的顺序。将很快用正确的版本更新它。
试试这个
data = {'a':'Apple', 'b':'Banana','c':'Carrot'}
keys = data.keys()
a,b,c = [data[k] for k in keys]
结果:
a == 'Apple'
b == 'Banana'
c == 'Carrot'
解决方案 13:
我不知道这是否是一种好的风格,但是
locals().update(params)
就可以了。然后你就有了a
,b
以及你的字典中的所有内容,params
它们可以作为相应的局部变量。
解决方案 14:
这是一个老话题,但我发现这是一个有用的方法:
data = {'a':'Apple', 'b':'Banana','c':'Carrot'}
for key in data.keys():
locals()[key] = data[key]
此方法循环遍历字典中的每个键并将变量设置为该名称,然后将关联键的值分配给这个新变量。
测试:
print(a)
print(b)
print(c)
输出
Apple
Banana
Carrot
解决方案 15:
dict
在 Python 中,一种简单易行的析构方法:
params = {"a": 1, "b": 2}
a, b = [params[key] for key in ("a", "b")]
print(a, b)
# Output:
# 1 2
解决方案 16:
不,目前在 Python 中没有很好的方法可以做到这一点。
为了在我的书中称得上“好”,解决方案必须避免在作业中重复字典键。
插图:
from operator import itemgetter
params = {'a_very_long_name': 1, 'b': 2}
# Not nice solution, we still have to spell out 'a_very_long_name' twice
a_very_long_name, b = itemgetter('a_very_long_name', 'b')(params)
相反,用 Python 编写此代码的最易读的方法是:
params = {'a_very_long_name': 1, 'b': 2}
a_very_long_name = params['a_very_long_name']
b = params['b']
在 Javascript 中,对象解构有一个特定的语法:
const params = {a_very_long_name: 1, b: 2};
const {a_very_long_name, b} = params;
如果您有 Javascript 背景,您可能会想在 Python 中寻找相同的功能。但 Python 不是 Javascript,它没有此功能。这并不会让 Python 低劣,这只是一个不同的设计决策,处理它的最佳方法是接受它,而不是试图复制编写代码的“Javascript 方式”。
解决方案 17:
最简单、最好的方法是
client_code, password, login_type = (
body.get("client_code"),
body.get("password"),
body.get("login_type"),
)
.get() 的优点在于它不会引发错误,这通常发生在 body['password'] 的情况下,默认情况下 .get() 将返回 None
解决方案 18:
我过去在 Python 代码中使用过的最接近的模式是:
def do_the_work(a, b):
console.log(f'{a}, {b}')
do_the_work(**{'a': 'hello', 'b': 'world'})
解决方案 19:
以下答案将提供真正的解构语法 - 只要您的代码如下所示:
请注意我是如何故意弄乱字典的顺序的——它对此很稳健!
D = {"b":2, "a":1, "c":3}
c, b, a = destructure(D)
print(a, b, c) #Prints "1 2 3"
该函数本身通过分析调用解构函数的行的源代码来工作。
它包含在我的 rp 库中(pip install rp
)
def destructure(d: dict) -> tuple:
"""
Extracts values from a dictionary based on the variable names in the
assignment expression in the calling line of code.
Mimics Javascript's destructuring assignment feature.
The main purpose of this function is to make your code just a little shorter and less redundant.
It can make your code less redundant, but relies on being able to find
you source code - an assumption which doesnt always hold (for example,
in ptpython or the default python repl. Jupyter and rp work fine though.)
Parameters
----------
d : dict
The dictionary from which to extract values.
Returns
-------
tuple or value
A tuple of extracted values, or a single value if only one is extracted.
Examples
--------
d = {'x': 1, 'y': 2, 'z': 3}
# Destructuring into multiple variables
>>> x, y = destructure(d)
>>> print(x, y)
1 2
# Destructuring into a single variable
>>> z = destructure(d)
>>> print(z)
3
# Useful for getting kwargs out
def make_color(**kwargs):
red,green,blue = destructure(kwargs)
Pitfalls
--------
# Variables on the left-hand side must match keys in the dictionary.
>>> a, b = destructure(d)
KeyError: 'Key not found in the provided dictionary.'
# The function must be used within an assignment operation.
>>> destructure(d)
ValueError: 'Destructuring must be used within an assignment operation.'
# The function doesn't support nested destructuring.
>>> d = {'p': {'q': 4}}
>>> p.q = destructure(d)
AttributeError: 'tuple' object has no attribute 'q'
# Multi-line assignments are not supported
>>> a, \n ... b = destructure(d)
TypeError: 'Cannot unpack non-iterable int object.'
"""
import inspect
import ast
# Get the source code of the line that called this function
frame = inspect.currentframe().f_back
info = inspect.getframeinfo(frame)
code = info.code_context[0].strip()
# Use the ast module to parse the source code into a syntax tree
tree = ast.parse(code)
try:
# Find the Assign node (i.e., the assignment operation)
assign_node = next(node for node in ast.walk(tree) if isinstance(node, ast.Assign))
# Check if there are multiple assignment targets
if isinstance(assign_node.targets[0], ast.Tuple):
# Extract the variable names from the left-hand side of the assignment
var_names = [target.id for target in assign_node.targets[0].elts]
else: # Single target
var_names = [assign_node.targets[0].id]
except StopIteration:
raise Error("Destructuring must be used within an assignment operation.")
# Use the variable names as keys to get the corresponding values from the dictionary
values = tuple(d[name] for name in var_names)
# Return single value instead of a tuple if there is only one value
if len(values) == 1:
return values[0]
return values
请注意以下注意事项:
因为它每次调用时都会读取源代码,所以它比手动访问字典要慢!此外,由于它读取源代码,如果在运行时修改源代码,则可能会引起问题。
上述函数的赋值也必须在同一行,因此以下操作不起作用:
D = {"b":2, "a":1, "c":3}
(
c,
b,
a,
) = destructure(D)
如果有人有改进此解决方案的建议,请说!
解决方案 20:
由于在 Python >= 3.7 中字典保证保持其插入顺序,这意味着现在这样做是完全安全且惯用的:
params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)
输出:
1 2
扫码咨询,免费领取项目管理大礼包!