从字典中删除元素

有没有办法从 Python 的字典中删除项目?

另外,如何从字典中删除项目以返回副本(即不修改原始内容)?

答案

del语句删除一个元素:

del d[key]

但是,这会使现有字典发生变化,因此对于引用同一实例的其他任何人,字典的内容都会更改。要返回字典,请复制字典:

def removekey(d, key):
    r = dict(d)
    del r[key]
    return r

dict()构造函数进行浅表复制 。要进行深度复制,请参见copy模块


请注意,为每个 dict del / assignment / etc 复制一份。意味着您要从恒定时间变为线性时间,并且还要使用线性空间。对于小命令,这不是问题。但是,如果您打算复制大量大型字典,则可能需要不同的数据结构,例如 HAMT(如本答案所述 )。

pop使字典变形。

>>> lol = {"hello": "gdbye"}
 >>> lol.pop("hello")
     'gdbye'
 >>> lol
     {}

如果您想保留原件,则可以将其复印。

我认为您的解决方案是最好的方法。但是,如果您需要其他解决方案,则可以使用旧字典中的键来创建新字典,而无需包括指定的键,如下所示:

>>> a
{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> {i:a[i] for i in a if i!=0}
{1: 'one', 2: 'two', 3: 'three'}

您正在寻找del 语句 。如果您有一个名为 foo 的字典,其键名为 “bar”,则可以从 foo 中删除 “bar”,如下所示:

del foo['bar']

请注意,这将永久修改正在操作的词典。如果要保留原始词典,则必须事先创建一个副本:

>>> foo = {'bar': 'baz'}
>>> fu = dict(foo)
>>> del foo['bar']
>>> print foo
{}
>>> print fu
{'bar': 'baz'}

dict调用会进行浅表复制。如果要深拷贝,请使用copy.deepcopy

为方便起见,您可以使用以下方法复制和粘贴:

def minus_key(key, dictionary):
    shallow_copy = dict(dictionary)
    del shallow_copy[key]
    return shallow_copy

有很多不错的答案,但我想强调一件事。

您可以同时使用dict.pop()方法和更通用的del语句从字典中删除项目。它们都变异了原始词典,因此您需要进行复制(请参见下面的详细信息)。

如果您要提供给他们的密钥在字典中不存在,则它们两个都将引发KeyError

key_to_remove = "c"
d = {"a": 1, "b": 2}
del d[key_to_remove]  # Raises `KeyError: 'c'`

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove)  # Raises `KeyError: 'c'`

您必须注意以下事项:

通过捕获异常:

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    del d[key_to_remove]
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    d.pop(key_to_remove)
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

通过执行检查:

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    del d[key_to_remove]

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    d.pop(key_to_remove)

但是使用pop() ,还有一种更简洁的方法 - 提供默认的返回值:

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove, None)  # No `KeyError` here

除非您使用pop()来获取要删除的键的值,否则您可以提供任何内容,而不必提供None 。虽然它可能是使用delin检查快由于pop()是与自己的并发症引起的开销的功能。通常情况并非如此,因此具有默认值的pop()足够好。


对于主要问题,您必须复制字典,以保存原始字典,并在不删除密钥的情况下新建一个字典。

这里的其他一些人建议使用copy.deepcopy()进行完整(深层)副本,这可能是一个过大的杀伤力,使用copy.copy()dict.copy()进行 “正常”(浅层)副本可能就足够了。字典保留对对象的引用作为键的值。因此,当您从字典中删除键时,该引用将被删除,而不是被引用的对象。如果内存中没有其他引用,则垃圾回收器随后可以自动删除该对象本身。与浅拷贝相比,进行深拷贝需要更多的计算,因此,通过进行深拷贝,浪费内存以及为 GC 提供更多工作,它会降低代码性能,有时浅拷贝就足够了。

但是,如果您将可变对象作为字典值,并计划稍后在不带键的情况下在返回的字典中对其进行修改,则必须进行深拷贝。

使用浅拷贝:

def get_dict_wo_key(dictionary, key):
    """Returns a **shallow** copy of the dictionary without a key."""
    _dict = dictionary.copy()
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}

使用深拷贝:

from copy import deepcopy


def get_dict_wo_key(dictionary, key):
    """Returns a **deep** copy of the dictionary without a key."""
    _dict = deepcopy(dictionary)
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}
d = {1: 2, '2': 3, 5: 7}
del d[5]
print 'd = ', d

结果: d = {1: 2, '2': 3}

… 如何从字典中删除项目以返回副本(即不修改原始内容)?

dict是为此使用的错误数据结构。

当然,复制字典并从复制中弹出是可行的,使用理解力构建新字典也是如此,但是所有复制都需要时间 - 您已经用线性时间操作替换了恒定时间操作。所有这些活着的副本立刻占据了空间 - 每个副本的线性空间。

其他数据结构(例如哈希数组映射尝试 )也正是针对这种用例而设计的:添加或删除元素会以对数时间返回一个副本并将其大部分存储与原始共享1 个

当然也有一些缺点。性能是对数而不是常数(尽管基数较大,通常为 32-128)。而且,尽管您可以使非变异 API 与dict相同,但 “变异” API 显然是不同的。而且,最重要的是,Python 不附带 HAMT 电池。 2

pyrsistent库是用于 Python 的基于 HAMT 的字典替换(以及各种其他类型)的非常可靠的实现。它甚至有一个漂亮的Evolutioner API,用于尽可能平滑地将现有的变异代码移植到持久代码中。但是,如果您想明确地表示要返回副本而不是进行变异,则可以像这样使用它:

>>> from pyrsistent import m
>>> d1 = m(a=1, b=2)
>>> d2 = d1.set('c', 3)
>>> d3 = d1.remove('a')
>>> d1
pmap({'a': 1, 'b': 2})
>>> d2
pmap({'c': 3, 'a': 1, 'b': 2})
>>> d3
pmap({'b': 2})

d3 = d1.remove('a')正是问题所在。

如果您在pmap list嵌入了诸如dictlist类的可变数据结构,您仍然会遇到别名问题 - 您只能通过完全不变,嵌入pmappvector来解决此问题。


1. HAMT 在 Scala,Clojure 和 Haskell 等语言中也很流行,因为它们在无锁编程和软件事务存储中的表现非常好,但是在 Python 中它们都不重要。

2. 事实上, 一个 STDLIB HAMT,在执行使用contextvars较早撤消的 PEP 解释了原因。但这是库的隐藏实现细节,而不是公共集合类型。

只需调用 del d ['key']。

但是,在生产中,始终最好检查 d 中是否存在 “密钥”。

if 'key' in d:
    del d['key']

不,除了

def dictMinus(dct, val):
   copy = dct.copy()
   del copy[val]
   return copy

但是,通常仅创建略有变化的字典的副本可能不是一个好主意,因为这将导致相对较大的内存需求。通常最好记录旧字典(如果需要的话),然后对其进行修改。

# mutate/remove with a default
ret_val = body.pop('key', 5)
# no mutation with a default
ret_val = body.get('key', 5)