重命名字典键

704

有没有一种方法可以重命名字典键,而不重新分配其值为新名称并删除旧名称的键;并且不需要迭代字典键/值?

在有序字典 OrderedDict 的情况下,保持该键的位置。


7
你所说的“不重新分配其值给新名称并删除旧名称键”的确切含义是什么?在我看来,这就是重命名键的定义,而下面所有的答案都重新分配了值并删除了旧键名称。你还没有接受一个答案,也许这些答案还没有达到你想要的效果? - abcd
11
你需要明确版本号。自Python 3.7版本起,语言规范现在保证字典遵循插入顺序。这也使得有序字典大多数已经过时(除非:a)你想要能够向后兼容2.x或3.6-的代码; b)你关注有序字典是否在Python 3.7中变得多余?中列出的问题)。而在3.6中,字典插入顺序由CPython实现保证(但不是语言规范)。 - smci
5
在Python 3.7中,字典按照插入顺序排序,但仍然不同于OrderedDict。当比较字典是否相等时,字典会忽略顺序,而OrderedDict在比较时考虑顺序。我知道你提供了一个解释链接,但我认为你的评论可能会误导那些没有阅读该链接的人。 - Flimm
2
@smci,我不同意你的观点,认为可以忽略dictOrderedDict之间的差异,并且OrderedDict现在已经过时。我认为,就这个问题而言,讨论这个观点也是离题的。你链接的那个问题是更好的讨论地点。有趣的是,你链接的那个问题上唯一得到赞同的答案也认为这些差异很重要,而且OrderedDict并不过时。如果你有不同的答案,请去那里发表并让社区投票。https://dev59.com/q1UL5IYBdhLWcg3wFErw - Flimm
3
这是一个重复的问题,原始提问者在 Stack Overflow 上的链接为 https://dev59.com/hW855IYBdhLWcg3wViot。 - betontalpfa
显示剩余3条评论
16个回答

1197

对于普通字典,您可以使用:

mydict[k_new] = mydict.pop(k_old)

这将把项目移动到字典的末尾,除非k_new已经存在,否则它将在原地覆盖该值。
对于Python 3.7+字典,如果您还想保留排序,则最简单的方法是重新构建一个全新的实例。例如,将键2重命名为'two':
>>> d = {0:0, 1:1, 2:2, 3:3}
>>> {"two" if k == 2 else k:v for k,v in d.items()}
{0: 0, 1: 1, 'two': 2, 3: 3}

同样适用于 OrderedDict,您不能使用字典推导语法,但可以使用生成器表达式:
OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())

修改密钥本身是不切实际的,因为密钥是hashable,这通常意味着它们是immutable并且无法被修改。

1
对于Python 3.5,根据我的测试,我认为dict.pop可用于基于OrderedDict的操作。 - Daniel
8
没错,OrderedDict 会保留插入顺序,这并不是该问题所问的内容。 - wim
当使用重建全新实例的方法(尽管只有单个键需要更改)时,对于非常庞大的字典(包含大量项),性能会如何? - Nerxis
1
如果在循环迭代字典时重命名键,此方法将生成“RuntimeError:dictionary keys changed during iteration”。重新构建容器也可以解决此问题。例如,在for key_old, value in list(d.items()):中的list()中。 - Bob Stein

38

通过检查newkey!=oldkey,你可以这样做:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]

7
这个方法运行很好,但是由于新的键默认会被添加到末尾,所以不会保持原始顺序(在Python3中)。 - szeitlin
8
@szeitlin,即使Python3.6+版本对字典进行了有序化的初始化,但您不应该依赖于字典的顺序。在先前的版本中,字典是无序的,这并不是一个真正的特性,而只是Py3.6+字典改变后的副作用。如果您关心顺序,请使用OrderedDict - Granitosaurus
4
谢谢@Granitosaurus,我并不需要你向我解释那个。我的评论重点并不在此。 - szeitlin
14
@szeitlin你的评论暗示着更改字典顺序的事实在某种程度上很重要,而实际上没有人应该依赖字典顺序,因此这个事实是完全无关紧要的。 - Granitosaurus
4
因此,上述有争议的评论在Python 3.6中是相关的,但从3.7开始,dicts are definitely ordered - mattdm

24

如果需要重新命名所有字典键:

target_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
new_keys = ['k4','k5','k6']

for key,n_key in zip(target_dict.keys(), new_keys):
    target_dict[n_key] = target_dict.pop(key)

3
target_dict.keys()保证与定义中的顺序相同吗?我认为Python字典是无序的,从字典键视图中得到的顺序是不可预测的。 - ttimasdf
自 Python 3.6 以来,这已经发生了变化,请参见 https://docs.python.org/3/library/collections.html#collections.OrderedDict - Sebastian
1
for循环可以简化target_dict = dict(zip(new_keys, list(target_dict.values())))请注意,这只是代码的一部分。 - vinodhraj
1
当你运行for key,n_key in zip(target_dict.keys(), new_keys): target_dict[n_key] = target_dict.pop(key)时,即使字典被修改了,你也会得到RuntimeError:dictionary keys changed during iteration的错误。 - GSA
1
当你运行for key,n_key in zip(target_dict.keys(), new_keys): target_dict[n_key] = target_dict.pop(key)时,你会得到RuntimeError: dictionary keys changed during iteration的错误,尽管对字典进行了更改。 - GSA

12
你可以使用 Raymond Hettinger 编写的 OrderedDict recipe 并修改它以添加一个 rename 方法,但这将具有 O(N) 的复杂度:
def rename(self,key,new_key):
    ind = self._keys.index(key)  #get the index of old key, O(N) operation
    self._keys[ind] = new_key    #replace old key with new key in self._keys
    self[new_key] = self[key]    #add the new key, this is added at the end of self._keys
    self._keys.pop(-1)           #pop the last item in self._keys

例子:

dic = OrderedDict((("a",1),("b",2),("c",3)))
print dic
dic.rename("a","foo")
dic.rename("b","bar")
dic["d"] = 5
dic.rename("d","spam")
for k,v in  dic.items():
    print k,v

输出:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5

1
@MERose 将Python文件添加到您的模块搜索路径中,并从中导入OrderedDict。但我建议:创建一个继承自OrderedDict的类,并添加一个rename方法。 - Ashwini Chaudhary
似乎OrderedDict已经被重写为使用双向链表,因此可能仍然有一种方法来实现这一点,但需要更多的技巧。 :-/ - szeitlin

11

在我之前有一些人提到了使用 .pop 技巧以一行代码的方式删除和创建一个键。

我个人认为更显式的实现更易读:

d = {'a': 1, 'b': 2}
v = d['b']
del d['b']
d['c'] = v

上面的代码返回{'a': 1, 'c': 2}


5
假设您想将键 k3 重命名为 k4:
temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')

3
无效,您的意思是...temp_dict['k4'] = temp_dict.pop('k3')吗? - DougR
1
这将返回一个 TypeError: 'dict' object is not callable。如果你想引用 k3 键的值,那么应该使用方括号而不是圆括号。然而,这只会向字典中添加一个新键,而不会重命名现有的键,正如所请求的那样。 - Mewtwo

4

其他回答都很好。但在Python3.6中,普通字典也具有顺序。因此,在正常情况下很难保持键的位置。

def rename(old_dict,old_name,new_name):
    new_dict = {}
    for key,value in zip(old_dict.keys(),old_dict.values()):
        new_key = key if key != old_name else new_name
        new_dict[new_key] = old_dict[key]
    return new_dict

4
在Python 3.6及以后版本中,我会选择以下一行代码。
test = {'a': 1, 'old': 2, 'c': 3}
old_k = 'old'
new_k = 'new'
new_v = 4  # optional

print(dict((new_k, new_v) if k == old_k else (k, v) for k, v in test.items()))

生产

的内容

{'a': 1, 'new': 4, 'c': 3}

值得注意的是,如果没有使用print语句,IPython控制台/Jupyter Notebook会按照它们自己的顺序呈现字典...


我认为这应该是被接受的答案。 - Mujtaba

3

如果有人想要一次性重命名所有键,并提供一个包含新名称的列表:

def rename_keys(dict_, new_keys):
    """
     new_keys: type List(), must match length of dict_
    """

    # dict_ = {oldK: value}
    # d1={oldK:newK,} maps old keys to the new ones:  
    d1 = dict( zip( list(dict_.keys()), new_keys) )

          # d1{oldK} == new_key 
    return {d1[oldK]: value for oldK, value in dict_.items()}

1
我正在使用@wim的答案,使用dict.pop()重命名键时,但我发现了一个陷阱。在循环遍历字典以更改键时,没有将旧键的列表与字典实例完全分开,导致循环新的更改后的键进入循环,并且会漏掉一些现有的键。
首先,我是这样做的:
for current_key in my_dict:
    new_key = current_key.replace(':','_')
    fixed_metadata[new_key] = fixed_metadata.pop(current_key)

我发现以这种方式循环字典时,即使是新的键(即我已更改的键),字典仍然能够找到它们!我需要将每个实例彻底分开,以避免在for循环中找到自己更改的键,并且为某些原因在循环中找不到某些键。
现在我正在进行以下操作:
current_keys = list(my_dict.keys())
for current_key in current_keys:
    and so on...

将my_dict.keys()转换为列表是必要的,以消除对正在更改的字典的引用。仅使用my_dict.keys()会让我与原始实例绑定在一起,并产生奇怪的副作用。

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