覆盖子类中的dict.update()方法以防止覆盖dict键

Dou*_* R. 15 python dictionary

今天早些时候,我读到了一个问题" 如果python dict理解覆盖了一个键就会引发错误 ",并决定试着回答一下.我自然发生的方法是为此子类化dict.但是,我坚持了我的答案,现在我痴迷于为自己解决这个问题.

笔记:

  • 不 - 我不打算将这个问题的答案作为另一个问题的答案.
  • 在这一点上,这对我来说纯粹是一种智力锻炼.作为一个实际问题,我几乎肯定会使用一个namedtuple或一个常规字典,只要我有这样的要求.

我的(不太正常)解决方案:

class DuplicateKeyError(KeyError):
    pass



class UniqueKeyDict(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)


    def __setitem__(self, key, value):
        if key in self:  # Validate key doesn't already exist.
            raise DuplicateKeyError('Key \'{}\' already exists with value \'{}\'.'.format(key, self[key]))
        super().__setitem__(key, value)


    def update(self, *args, **kwargs):
        if args:
            if len(args) > 1:
                raise TypeError('Update expected at most 1 arg.  Got {}.'.format(len(args)))
            else:
                try:
                    for k, v in args[0]:
                        self.__setitem__(k, v)
                except ValueError:
                    pass

        for k in kwargs:
            self.__setitem__(k, kwargs[k])
Run Code Online (Sandbox Code Playgroud)

我的测试和预期结果

>>> ukd = UniqueKeyDict((k, int(v)) for k, v in ('a1', 'b2', 'c3', 'd4'))  # Should succeed.
>>> ukd['e'] = 5  # Should succeed.
>>> print(ukd)
{'a': 1, 'b': 2, 'c': 3, d: 4, 'e': 5}
>>> ukd['a'] = 5  # Should fail.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __setitem__
__main__.DuplicateKeyError: Key 'a' already exists with value '1'.
>>> ukd.update({'a': 5})  # Should fail.
>>> ukd = UniqueKeyDict((k, v) for k, v in ('a1', 'b2', 'c3', 'd4', 'a5'))  # Should fail.
>>>
Run Code Online (Sandbox Code Playgroud)

我确定问题出在我的update()方法中,但我无法确定我做错了什么.

以下是我的update()方法的原始版本.在调用my_dict.update({k: v})dict中已有的键/值对时,此版本在重复项上按预期失败,但在创建原始dict时包含重复键时不会失败,因为将args转换为dict默认行为的结果字典,即覆盖重复的密钥.

def update(self, *args, **kwargs):
    for k, v in dict(*args, **kwargs).items():
        self.__setitem__(k, v)
Run Code Online (Sandbox Code Playgroud)

jon*_*rpe 7

请注意,根据文档:

  • dict.update接受一个other参数,“另一个字典对象或一个可迭代的键/值对”(我曾经collections.Mapping为此进行过测试)和“如果指定了关键字参数,则字典将使用这些键/值对进行更新”;和
  • dict()需要单个MappingIterable与可选**kwargs(与update接受...相同)。

这不是您实现的接口,这会导致一些问题。我会按如下方式实施:

from collections import Mapping


class DuplicateKeyError(KeyError):
    pass


class UniqueKeyDict(dict):

    def __init__(self, other=None, **kwargs):
        super().__init__()
        self.update(other, **kwargs)

    def __setitem__(self, key, value):
        if key in self:
            msg = 'key {!r} already exists with value {!r}'
            raise DuplicateKeyError(msg.format(key, self[key]))
        super().__setitem__(key, value)

    def update(self, other=None, **kwargs):
        if other is not None:
            for k, v in other.items() if isinstance(other, Mapping) else other:
                self[k] = v
        for k, v in kwargs.items():
            self[k] = v
Run Code Online (Sandbox Code Playgroud)

正在使用:

from collections import Mapping


class DuplicateKeyError(KeyError):
    pass


class UniqueKeyDict(dict):

    def __init__(self, other=None, **kwargs):
        super().__init__()
        self.update(other, **kwargs)

    def __setitem__(self, key, value):
        if key in self:
            msg = 'key {!r} already exists with value {!r}'
            raise DuplicateKeyError(msg.format(key, self[key]))
        super().__setitem__(key, value)

    def update(self, other=None, **kwargs):
        if other is not None:
            for k, v in other.items() if isinstance(other, Mapping) else other:
                self[k] = v
        for k, v in kwargs.items():
            self[k] = v
Run Code Online (Sandbox Code Playgroud)

和:

>>> ukd = UniqueKeyDict((k, v) for k, v in ('a1', 'b2', 'c3', 'd4'))
>>> ukd.update((k, v) for k, v in ('e5', 'f6'))  # single Iterable
>>> ukd.update({'h': 8}, g='7')  # single Mapping plus keyword args
>>> ukd
{'e': '5', 'f': '6', 'a': '1', 'd': '4', 'c': '3', 'h': 8, 'b': '2', 'g': '7'}
Run Code Online (Sandbox Code Playgroud)

如果你最终使用它,我会倾向于给它一个不同的__repr__以避免混淆!


sir*_*rfz 7

有趣的是,简单地覆盖__setitem__不足以改变updatein 的行为dict.我原以为dict在使用它__setitem__时会使用它的方法update.在所有情况下,我认为最好在collections.MutableMapping不触及的情况下实现所需的结果update:

import collections

class UniqueKeyDict(collections.MutableMapping, dict):

    def __init__(self, *args, **kwargs):
        self._dict = dict(*args, **kwargs)

    def __getitem__(self, key):
        return self._dict[key]

    def __setitem__(self, key, value):
        if key in self:
            raise DuplicateKeyError("Key '{}' already exists with value '{}'.".format(key, self[key]))
        self._dict[key] = value

    def __delitem__(self, key):
        del self._dict[key]

    def __iter__(self):
        return iter(self._dict)

    def __len__(self):
        return len(self._dict)
Run Code Online (Sandbox Code Playgroud)

编辑:dict作为基类包含以满足isinstance(x, dict)检查.