从字典中删除一个元素。

2138

如何在Python中从字典中删除一个项目?

在不修改原始字典的情况下,如何获得另一个已删除该项目的字典?


参见如何从Python字典中删除键?用于特定问题的相关信息,包括删除可能不存在的项目(通过键)。


14
为什么需要一个返回字典的函数,而不是直接修改字典呢? - amillerrhodes
10
字典的 pop 方法会直接修改原字典,因此会改变从调用方传递给“辅助函数”的字典“引用”。因此,“辅助函数”不需要返回任何内容,因为调用方中原始字典的引用已经被修改。如果不需要返回值,请不要将 dict.pop() 的返回值赋值给任何变量。例如:对my_dict进行某些操作; my_dict.pop(my_key, None); 对my_dict进行更多操作 # 现在没有了my_key。如果需要,可以使用 deepcopy(my_dict) - Mark Mikofski
1
由于原始标题与细节不符,并特别排除了显然的解决方案“d.pop()”,因此我修正了标题,以提出在详细信息中指定的问题。 - smci
1
我们应该加上一个警告,询问你是否真的想要这样做,因为如果你在一个包含E个元素的字典上执行N次操作,你将使用O(N*E)的内存来进行所有深层复制。如果你只想要一个只读的(浅层复制)副本,请使用d.pop(key)。但是,如果有任何修改浅层副本的操作,就会出现众所周知的别名问题。如果你告诉我们更广泛的上下文,那么它就会很有帮助。(有没有其他东西改变了字典的值?你是否尝试对列表进行破坏性迭代?如果不是,那么你在做什么?) - smci
10
为什么需要返回一个字典的函数,而不直接修改字典呢? 可能是因为你想编写不会修改其参数的纯函数。 - Gene Callahan
19个回答

2474

del 语句 可以删除一个元素:

del d[key]

请注意,这会改变现有字典的内容,所以对于任何拥有相同引用的其他人来说,字典的内容都会发生变化。如果要返回一个新的字典,请复制该字典:

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

dict()构造函数生成一个浅拷贝。如果需要进行深度拷贝,请参阅copy模块


请注意,为每个字典del/赋值等制作副本意味着您从常数时间转换为线性时间,并且还使用线性空间。对于小型字典,这不是问题。但是,如果您计划创建大量大字典的副本,则可能需要其他数据结构,例如 HAMT(如在此答案中所述)。


21
关于字典的可变性这一点非常好 +1 - 虽然我想不起来有什么时候需要字典的副本,我一直依赖于“所有人”的副本都是相同的。很好的观点。 - tMC
38
如果你在循环过程中编辑字典,会引发错误:“RuntimeError: dictionary changed size during iteration”。请注意不要更改正在被迭代的对象。 - VertigoRay
25
pop方法呢?实际上它的作用也是一样的吧?它不是更符合Python风格吗?(因为它是字典的方法,而不是特殊保留字) - Serge
27
这个回答有一个弱点,可能会误导读者。读者可能会误解 dict(d) 可以给他们一个带有 'd' 的副本。但是这只是一个不完整的副本。当仅进行 del keys 操作时,这是可以的。但是,当您想要对嵌套字典进行其他操作时,在使用该复制方法修改 'r' 可能会影响原始字典 'd'。要获得真正的副本,您需要先 'import copy',然后 'r = copy.deepcopy(d)'。 - Zen
6
@GregHewgill,我知道这点,所以这不是一个严重的缺陷。但是既然你提到“要返回新的字典,请复制该字典:”,考虑到你拥有358K积分和143095次浏览记录,这可能会误导很多Python初学者。顺便说一下,我之前也被这篇文章误导过。 - Zen
显示剩余8条评论

510

pop方法会改变字典本身。

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

如果你想保留原件,只需复制它。


93
“del”可以,但我认为“pop”更符合Python的风格。 - ivanleoncz
3
@ivanleoncz 为什么? - kevr
6
pop 返回被“弹出”的值,这样您就可以为任何进一步的原因使用该值。如果不是更符合 Python 风格,我肯定会说那似乎更好 :). 它不是字典,但对于两者都是以相同方式工作的:https://github.com/ivanlmj/python-prototypes/blob/master/3.4/pop_list.py - ivanleoncz
30
@ivanleoncz 有另一个原因使得使用 pop 更好,可以为其提供一个默认值,当字典中不存在该键时将返回该默认值。这对于需要删除一些键但其中某些键可能不存在的情况非常有用;在这种情况下,del 将会抛出 KeyError 异常。 - itachi

117

我认为你的解决方案是最好的。但是如果你想要另一种解决方案,你可以创建一个新字典并使用旧字典的键,但不包括你指定的键,就像这样:

>>> 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'}

3
非常酷。我喜欢快速过滤字典的方法,而无需定义新函数。 - Joe J
8
对于不熟悉推导式的人,您也可以像这样做:{i:a[i] for i in a if i not in [0, 1, 2]},如果您想要删除多个元素。该语句的作用是创建一个字典,其中键为 a 中不在列表 [0, 1, 2] 中的元素 i,值为 a[i] - kmatheny
28
我认为更好的写法是{k:v for k,v in a.items() if k != 0} - rlbond
1
通过键删除项并在同一行返回新字典结果的最佳解决方案。例如,如果您需要使用已构建的不带单个项的字典作为 **kwargs,则可以使用以下代码:some_function(**{k:v for k,v in some_dict.items() if k != 'some_key'}) - Cole
1
最佳解决方案在这里。一行代码,不会改变原始字典。 - Andrew Winterbotham

110

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

您可以使用 dict.pop() 方法或更通用的del 语句 来从字典中删除项。它们都会改变原始字典,因此您需要复制它(请参阅下面的详细信息)。

如果传递给它们的键在字典中不存在,它们都会引发KeyError

key_to_remove = "c"
d = {"a": 1, "b": 2}
del d[key_to_remove]  # Raises `KeyError: 'c'`
and
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}

82

您需要的是 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

1
@pythonian29033,实际上,不是这样的。被接受的答案按预期工作 - 它返回一个没有一个键的字典。而这个答案的方法会改变原始字典;) 这有很大的区别。 - maxkoryukov
1
@arussell84,为什么在Python示例中经常使用>>>?是的,Python文档包含很多这样的内容。但是这种代码对于复制和粘贴来说并不方便。我感到困惑... - maxkoryukov
@maxkoryukov 是的!但是这个函数和这个答案完全相同,唯一的区别是这个答案在一个函数内部。而且你可能已经有一段时间没有用 Python 编程了,>>> 模仿了 Python 在 cli 模式下的监听符号。 - pythonian29033
2
@pythonian29033 关于 >>>。是的,它是REPL风格,但让我们坦白地说:只有一个人写了这个样本,而1000人阅读了。我认为,以易于复制和运行的方式编写示例将是很棒的。我不喜欢手动删除这些尖括号。或逐行复制..所以我不明白:为什么这些角度还在那里)))也许我不知道些什么? - maxkoryukov
3
我已经添加了一个可以复制/粘贴的函数,以方便您使用。 - arussell84
显示剩余3条评论

49
…如何从字典中删除一个项目以返回副本(即不修改原始内容)?
使用dict是错误的数据结构。
当然,复制字典并从副本中弹出也可以,使用推导式构建新字典也可以,但所有这些复制都需要时间,你用线性时间代替了常数时间操作。而且所有这些副本需要空间,每个副本需要线性空间。
其他数据结构,如hash array mapped tries,专门设计用于此类用例:添加或删除元素以在对数时间内返回副本,并与原始内容共享大部分存储空间。
当然,也有一些缺点。性能是对数级别而非常数级别(通常是32-128)。虽然可以使非变异API与dict相同,但“变异”API显然是不同的。最重要的是,Python中没有HATM电池。

pyrsistent库是一个基于HAMT的字典替代方案(以及其他类型)的Python实现。它甚至还有一个巧妙的evolver 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}}中嵌入了可变数据结构,如{{dict}}和{{list}},则仍会存在别名问题——您只能通过完全不可变来解决这个问题,嵌入{{pmap}}和{{pvector}}。


1. HAMTs在Scala、Clojure、Haskell等语言中也变得流行,因为它们与无锁编程和软件事务内存非常契合,但在Python中这些都不是很相关。

2. 实际上,stdlib中有一个HAMT,用于实现contextvars早期撤回的PEP解释了原因。但这是库的隐藏实现细节,而不是公共集合类型。


2
现在可以使用immutables包了。 - kdauria

26

使用del方法可以通过传递字典中的键来删除相应的值。

链接: del方法

del dictionary['key_to_del']

1
这是我认为最易读和高效的代码。 - Damien
我很惊讶地发现它甚至适用于del rule['key_to_del']['nestedMapProp'] - undefined

24
d = {1: 2, '2': 3, 5: 7}
del d[5]
print 'd = ', d

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


17

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

然而,在生产环境中,最好先检查 'key' 是否存在于 d 中。

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

8
在生产环境中,最好遵循EAFP的理念。只需在“try-except”块中删除键即可。至少,这将是一个原子操作;) (注:EAFP是Python编程语言中一种理念,意思是“易于请求,容易获得原谅”,即在代码中尝试执行操作,并在出现错误时处理异常,而不是在执行操作之前检查可能导致错误的情况。) - maxkoryukov
1
如果你想要简洁的方式,可以使用 d.pop('key', None),它是一行代码。但实际问题是要获取没有一个键的字典,而不是修改字典。因此,推导式 - 是这里的好选择;) - maxkoryukov

11

不,没有比这更好的方法。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接