我需要一个字典,它可能对某些键具有相同的名称,并在这种情况下引用键时返回值列表。
例如
print mydict['key']
[1,2,3,4,5,6]
为了保持一致性,您应该使用字典映射键来列出(或设置)值,其中一些可以为空。这有一个很好的习语:
from collections import defaultdict
d = defaultdict(set)
d["key"].add(...)
(defaultdict
就像一个普通的字典,但如果缺少一个键,它将调用您在实例化时传入的参数并将结果用作默认值。因此,如果您要求,这将自动创建一组空值一个尚未出现的钥匙。)
如果您需要对象看起来更像字典(即通过d["key"] = ...
设置值),您可以执行以下操作。但这可能是一个坏主意,因为它违反了普通的Python语法,并且很可能会在以后回来并咬你。特别是如果其他人必须维护您的代码。
class Multidict(defaultdict):
def __init__(self):
super(Multidict, self).__init__(set)
def __setitem__(self, key, value):
self[key].add(value)
我没有测试过这个。
你也可以试试paste.util.multidict.MultiDict
$ easy_install Paste
然后:
from paste.util.multidict import MultiDict
d = MultiDict()
d.add('a', 1)
d.add('a', 2)
d.add('b', 3)
d.mixed()
>>> {'a': [1, 2], 'b': 3}
d.getall('a')
>>> [1, 2]
d.getall('b')
>>> [3]
像Pylons这样的Web框架正在使用此库来处理HTTP查询字符串/发布数据,这些数据可以具有相同的名称密钥。
您可以使用:
myDict = {'key': []}
然后在运行时:
if newKey in myDict:
myDict[newKey].append(value)
else:
myDict[newKey] = [value]
根据@ Ben的评论编辑:
myDict = {}
myDict.setdefault(newKey, []).append(value)
我对所有提议的解决方案都不满意,所以这是我的解决方案。这适用于Python 3.代码如下。
(代码如下)
>>> a = MultiDict({0: [0]})
>>> a
MultiDict({0: [0]})
>>> a[0] = (1, 7)
>>> a
MultiDict({0: [1, 7]})
>>> a.add(0, 2)
>>> a
MultiDict({0: [1, 7, 2]})
>>> a.add(1, 2)
>>> a
MultiDict({0: [1, 7, 2], 1: [2]})
>>> a.getfirst(0)
1
>>> a.getfirst(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 61, in getfirst
File "<stdin>", line 17, in __getitem__
KeyError: 3
>>> len(a)
2
>>> tuple(a.items())
((0, [1, 7, 2]), (1, [2]))
>>> tuple(a.values())
([1, 7, 2], [2])
>>> a.get(0)
[1, 7, 2]
>>> tuple(a.multiitems())
((0, 1), (0, 7), (0, 2), (1, 2))
>>> tuple(a.multikeys())
(0, 0, 0, 1)
>>> tuple(a.multivalues())
(1, 7, 2, 2)
>>> a.remove(0, 1)
>>> a
MultiDict({0: [7, 2], 1: [2]})
>>> a.remove(3, 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 53, in remove
File "<stdin>", line 17, in __getitem__
KeyError: 3
>>> a.remove(0, 5)
Traceback (most recent call last):
File "<stdin>", line 53, in remove
ValueError: list.remove(x): x not in list
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 56, in remove
ValueError: No element with value 5 for key 0
>>> b = MultiDict({0: [7, 2], 1: [2]})
>>> b == a
True
>>> c = MultiDict(a)
>>> c
MultiDict({0: [7, 2], 1: [2]})
>>> d = MultiDict({0: 0})
Traceback (most recent call last):
File "<stdin>", line 30, in __init__
TypeError: 'int' object is not iterable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 33, in __init__
TypeError: Values must be iterables, found 'int' for key 0
>>> a.pop(0)
[7, 2]
>>> a
MultiDict({1: [2]})
>>> c.popitem()
(0, [7, 2])
>>> c.setdefault(0, [1])
[1]
>>> c
MultiDict({0: [1], 1: [2]})
>>> c.setdefault(0, [2])
[1]
>>> c
MultiDict({0: [1], 1: [2]})
>>> c.setdefault(3)
[]
>>> c
MultiDict({0: [1], 1: [2], 3: []})
>>> c.getfirst(3)
Traceback (most recent call last):
File "<stdin>", line 61, in getfirst
IndexError: list index out of range
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 63, in getfirst
IndexError: No values in key 3
>>> c.clear()
>>> c
MultiDict({})
>>> c.update(b)
>>> c
MultiDict({0: [7, 2], 1: [2]})
>>> d = c.copy()
>>> d == c
True
>>> id(d) == id(c)
False
>>> MultiDict.fromkeys((0, 1), [5])
MultiDict({0: [5], 1: [5]})
>>> MultiDict.fromkeys((0, 1))
MultiDict({0: [], 1: []})
from collections.abc import MutableMapping
class MultiDict(MutableMapping):
@classmethod
def fromkeys(cls, seq, value=None, *args, **kwargs):
if value is None:
v = []
else:
v = value
return MultiDict(dict.fromkeys(seq, v, *args, **kwargs))
def __setitem__(self, k, v):
self._dict[k] = list(v)
def __getitem__(self, k):
return self._dict[k]
def __iter__(self):
for k in self._dict:
yield k
def __init__(self, *args, **kwargs):
self._dict = dict(*args, **kwargs)
for k, v in self._dict.items():
try:
self._dict[k] = list(v)
except TypeError:
err_str = "Values must be iterables, found '{t}' for key {k}"
raise TypeError(err_str.format(k=k, t=type(v).__name__))
def __delitem__(self, k):
del self._dict[k]
def __len__(self):
return len(self._dict)
def add(self, k, v):
if not k in self:
self[k] = []
self[k].append(v)
def remove(self, k, v):
try:
self[k].remove(v)
except ValueError:
err_str = "No element with value {v} for key {k}"
raise ValueError(err_str.format(v=v, k=k))
def getfirst(self, k):
try:
res = self[k][0]
except IndexError:
raise IndexError("No values in key {k}".format(k=k))
return self[k][0]
def multiitems(self):
for k, v in self.items():
for vv in v:
yield (k, vv)
def multikeys(self):
for k, v in self.items():
for vv in v:
yield k
def multivalues(self):
for v in self.values():
for vv in v:
yield vv
def setdefault(self, k, default=None):
if default is None:
def_val = []
else:
def_val = default
if k not in self:
self[k] = def_val
return self[k]
def copy(self):
return MultiDict(self)
def __repr__(self):
body_str = ""
for k, v in self.items():
body_str += "{k}: {v}, ".format(k=repr(k), v=repr(v))
if body_str:
body_str_true = body_str[:-2]
else:
body_str_true = body_str
return "MultiDict({{{body}}})".format(body=body_str_true)
为简单起见,构造函数与dict
相同。传递给构造函数或直接分配给键的所有值必须是可迭代的。
我的MultiDict
的所有值都是列表,即使值只有一个。这是为了避免混淆。
我还添加了一个remove
方法来删除MultiDict
中的单个条目。此外,我添加了一个multiitems
,它在字典的所有值上覆盖了这对(key,value)。 multikeys
和multivalues
是相似的。
def toMultiDict(items):
def insertMulti(d, kv):
k, v = kv
d.setdefault(k, []).append(v)
return d
return reduce(insertMulti, [{}] + items)
应该从键创建一个dict到值列表:
In [28]: toMultiDict(zip([1,2,1], [4,5,6]))
Out[28]: {1: [4, 6], 2: [5]}
我无法将insertMulti放入lambda中,因为lambda需要再次返回dict。
这是使用集合库中的defaultdict对象的理想位置
from collections import defaultdict
mydict = defaultdict(set)
mydict['key'] += set([1,2,3,4])
mydict['key'] += set([4,5,6])
print(mydict['key'])
返回[1,2,3,4,5,6]
在引用未被隐式分配的键的情况下,返回空集。
print(mydict['bad_key'])
返回[]
在标准库的dict上使用setdefault在分配值时需要对语法进行重大更改,并且可能会变得相当混乱。我从来没有使用过Multidict,但它看起来也像是在分配方式上发生了重大变化。使用此方法,您只需假设字典中可能已存在与此键关联的值,并在分配键值时使用“+ =”运算符稍微修改赋值运算符。
仅供参考 - 我非常喜欢使用NoneType作为默认值,这会导致无效键的任何访问都返回None。这在大多数情况下都适用,包括迭代和json转储,但是对于您的特定需要,默认值应该是set类型,除非您要启用密钥中存储的重复值。然后使用列表。事实上,只要你有一个同质字典,默认应该是那种类型。
mydict = defaultdict(lambda: None)