今天早些时候,我读到了问题“Raise error if python dict comprehension overwrites a key”,并决定尝试一下答案。我自然而然地想到的方法是将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])
>>> 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.
>>>
我确定问题出在我的update()
方法中,但我无法确定我做错了什么。
以下是我的update()
方法的原始版本。对于已经在dict中的键/值对调用my_dict.update({k: v})
时,此版本在重复项上失败,但在创建原始dict时包含重复键时不会失败,因为将args转换为a dict
导致字典的默认行为,即覆盖重复的密钥。
def update(self, *args, **kwargs):
for k, v in dict(*args, **kwargs).items():
self.__setitem__(k, v)
答案 0 :(得分:7)
有趣的是,简单地覆盖__setitem__
并不足以改变update
中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)
编辑:包含dict
作为基类以满足isinstance(x, dict)
检查。
答案 1 :(得分:4)
我不确定这是问题所在但我只是注意到您将args
方法中的update
视为对的列表:
for k, v in args[0]
当你实际提供字典时:
ukd.update({'a': 5})
你试过这个:
try:
for k, v in args[0].iteritems():
self.__setitem__(k, v)
except ValueError:
pass
编辑:可能这个错误没有引起注意,因为你是except
ValueError
,这就是将字典视为对的列表会引起的。
答案 2 :(得分:4)
请注意,根据文档:
dict.update
只使用一个other
参数,“另一个字典对象或一对键/值对的迭代”(我使用了collections.Mapping
来对此进行测试)和“如果指定了关键字参数,则使用这些键/值对更新字典”;和dict()
只需一个Mapping
或Iterable
以及可选的**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
使用中:
>>> UniqueKeyDict((k, v) for k, v in ('a1', 'b2', 'c3', 'd4'))
{'c': '3', 'd': '4', 'a': '1', 'b': '2'}
>>> UniqueKeyDict((k, v) for k, v in ('a1', 'b2', 'c3', 'a4'))
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
UniqueKeyDict((k, v) for k, v in ('a1', 'b2', 'c3', 'a4'))
File "<pyshell#7>", line 5, in __init__
self.update(other, **kwargs)
File "<pyshell#7>", line 15, in update
self[k] = v
File "<pyshell#7>", line 10, in __setitem__
raise DuplicateKeyError(msg.format(key, self[key]))
DuplicateKeyError: "key 'a' already exists with value '1'"
和
>>> 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'}
如果你最终使用了这个,我会倾向于给它一个不同的__repr__
来避免混淆!
答案 3 :(得分:2)
我能够通过以下代码实现目标:
class UniqueKeyDict(dict):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def __setitem__(self, key, value):
if self.has_key(key):
raise DuplicateKeyError("%s is already in dict" % key)
dict.__setitem__(self, key, value)
def update(self, *args, **kwargs):
for d in list(args) + [kwargs]:
for k,v in d.iteritems():
self[k]=v
答案 4 :(得分:1)
为什么不使用setdefault在MultiKeyDict的启发下做一些事情呢?这使得更新方法成为覆盖当前存储值的一种方式,我知道d [k] = v == d.update({k,v})的意图。在我的应用程序中,覆盖是有用的。因此,在将此标记为未回答OP问题之前,请考虑此答案可能对其他人有用。
class DuplicateKeyError(KeyError):
"""File exception rasised by UniqueKeyDict"""
def __init__(self, key, value):
msg = 'key {!r} already exists with value {!r}'.format(key, value)
super(DuplicateKeyError, self).__init__(msg)
class UniqueKeyDict(dict):
"""Subclass of dict that raises a DuplicateKeyError exception"""
def __setitem__(self, key, value):
if key in self:
raise DuplicateKeyError(key, self[key])
self.setdefault(key, value)
class MultiKeyDict(dict):
"""Subclass of dict that supports multiple values per key"""
def __setitem__(self, key, value):
self.setdefault(key, []).append(value)
相当新的python如此火焰,可能值得...
答案 5 :(得分:1)
这个有趣的问题有点老了,已经有了一些可靠的答案(我最喜欢的是来自 sirfz 的那个)。不过,我想再提出一个建议。您可以使用 dict
-wrapper UserDict。如果我没记错的话,这应该可以完成您正在寻找的工作:
from collections import UserDict
class DuplicateKeyError(KeyError):
pass
class UniqueKeyDict(UserDict):
def __setitem__(self, key, value):
if key in self:
raise DuplicateKeyError(f"Key '{key}' already exists with value '{self[key]}'")
self.data[key] = value
与 collections.abc.MutableMapping
的用法一样,update
方法被隐式修改。但相比之下,您只必须(重新)定义 __setitem__
方法。由于您的修改相当小,因此使用 UserDict
对我来说似乎是一种合适的方法。
此类的实例不是dict
的实例,而是collections.abc.Mapping
的实例,应该用于测试dict
-likeness。