【问题标题】:Can I have a dictionary with same-name keys?我可以有一个具有同名键的字典吗?
【发布时间】:2011-10-12 09:59:44
【问题描述】:

我需要一个字典,它可能对某些键具有相同的名称,并在这种情况下引用该键时返回一个值列表。

例如

print mydict['key']
[1,2,3,4,5,6]

【问题讨论】:

  • 不,但你可以使用 mydict = {'key':[1,2,3,4]}

标签: python dictionary key


【解决方案1】:

为了保持一致性,您应该将字典键映射到值列表(或集合),其中一些可以为空。有一个很好的成语:

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):
        if isinstance(value, (self.default_factory)): # self.default_factory is `set`
            super().__setitem__(key, value)
        else:
            self[key].append(value)

我没有测试过。

【讨论】:

  • 示例中的代码(Multidict)不起作用,我不确定您是否可以通过从 defaultdict 派生来使其工作。问题是 self[key] 调用 setitem 如果没有这样的项目(这是 defaultdict 在普通 dict 之上添加的内容)。并且无法区分谁在调用 setitem: user 或 defaultdict internals。
  • @Andrey 可以通过重写 missing 方法来区分谁在调用 setitem
【解决方案2】:

我对所有建议的解决方案都不满意,所以这是我的解决方案。这是针对 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: []})

代码

try:
    from collections.abc import MutableMapping
except ImportError:  # python < 3.3
    from collections import MutableMapping

class MultiDict(MutableMapping):
    @classmethod
    def fromkeys(cls, seq, value=None, *args, **kwargs):
        if value is None:
            v = []
        else:
            v = value

        return cls(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 self.__class__(self)


    def __repr__(self):
        return (
            self.__class__.__name__ + 
            "({{{body}}})".format(body=self._dict)
        )

一些冗长的解释

为简单起见,构造函数与dict 相同。传递给构造函数或直接分配给键的所有值必须是可迭代的。

我的MultiDict 的所有值都是列表,即使值只有一个。这是为了避免混淆。

我还添加了一个remove 方法来从MultiDict 中删除单个条目。此外,我添加了一个multiitems,它遍历字典的所有值的一对(键,值)。 multikeysmultivalues 相似。

替代品

您还可以使用 MultiDict 的 aiohttpWebOpWerkzeug 实现。

【讨论】:

    【解决方案3】:

    这是使用集合库中的 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)
    

    【讨论】:

      【解决方案4】:
      def toMultiDict(items):
          def insertMulti(d, kv):
              k, v = kv
              d.setdefault(k, []).append(v)
              return d
          return reduce(insertMulti, [{}] + items)
      

      应该创建一个从键到值列表的字典:

      In [28]: toMultiDict(zip([1,2,1], [4,5,6]))
      Out[28]: {1: [4, 6], 2: [5]}
      

      我无法将 insertMulti 放入 lambda,因为 lambda 需要再次返回 dict。

      【讨论】:

        【解决方案5】:

        你也可以试试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 查询字符串/发布数据,这些数据可以具有同名键。

        【讨论】:

          【解决方案6】:

          你可以使用:

          myDict = {'key': []}
          

          然后在运行时:

          if newKey in myDict:
              myDict[newKey].append(value)
          else:
              myDict[newKey] = [value]
          

          根据@Ben 的评论编辑:

          myDict = {}
          myDict.setdefault(newKey, []).append(value)
          

          【讨论】:

          • 今天更惯用 myDict.setdefault(newKey, []).append(value)
          猜你喜欢
          • 1970-01-01
          • 2021-10-07
          • 2015-08-19
          • 2021-09-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-27
          相关资源
          最近更新 更多