【问题标题】:Rename a dictionary key重命名字典键
【发布时间】:2013-05-04 17:34:26
【问题描述】:

有没有办法重命名字典键,而无需将其值重新分配给新名称并删除旧名称键;并且不遍历dict键/值?

在 OrderedDict 的情况下,执行相同操作,同时保持该键的位置。

【问题讨论】:

  • “不将其值重新分配给新名称并删除旧名称键”到底是什么意思?我看到它的方式是重命名键的定义,下面的所有答案都重新分配值并删除旧键名。你还没有接受答案,所以也许这些还没有完成你想要的?
  • 您确实需要指定版本号。 As of Python 3.7, the language spec now guarantees that dicts follow insertion order。这也使得 OrderedDict 基本上已经过时(除非 a)您想要也向后移植到 2.x 或 3.6 的代码,或者 b)您关心Will OrderedDict become redundant in Python 3.7? 中列出的问题)。回到 3.6,字典插入顺序由 CPython 实现(但不是语言规范)保证。
  • @smci 在 Python 3.7 中,dicts 遵循插入顺序,但与 OrderedDict 仍然不同。 dicts 在比较它们是否相等时会忽略顺序,而OrderedDict 在比较时会考虑顺序。我知道您链接到可以解释它的内容,但我认为您的评论可能会误导那些可能没有阅读该链接的人。
  • @smci 我不同意你的观点,即dictOrderedDict 之间的差异可以被掩盖,OrderedDict 现在已经过时了。我认为辩论这个观点也是这个问题的主题。你链接到的问题是一个更好的地方来谈论它。有趣的是,关于您链接到的问题的唯一赞成答案同意差异很重要并且OrderedDict 并没有过时。如果您有不同的答案,请在此处发布并让社区投票。 stackoverflow.com/q/50872498/247696
  • 这能回答你的问题吗? Change the name of a key in dictionary

标签: python


【解决方案1】:

结合了上面线程中的一些答案,并提出了下面的解决方案。虽然它很简单,但它可以用作构建块,从字典中进行更复杂的键更新。

test_dict = {'a': 1, 'b': 2, 'c': 3}
print(test_dict)
# {'a': 1, 'b': 2, 'c': 3}
prefix = 'up'
def dict_key_update(json_file):    
    new_keys = []
    old_keys = []
    for i,(key,value) in enumerate(json_file.items()):
        old_keys.append(key)
        new_keys.append(str(prefix) + key) # i have updated by adding a prefix to the 
        # key
    for old_key, new_key in zip(old_keys,new_keys):
        print('old {}, new {}'.format(old_key, new_key))
        if new_key!=old_key:  
           json_file[new_key] = json_file.pop(old_key)
     return json_file

test_dict = dict_key_update(test_dict)
print(test_dict)
# {'upa': 1, 'upb': 2, 'upc': 3}

【讨论】:

    【解决方案2】:

    对于常规字典,您可以使用:

    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 也是如此,您不能使用 dict 理解语法,但可以使用生成器表达式:

    OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())
    

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

    【讨论】:

    • 对于 python 3.5,根据我的测试,我认为 dict.pop 对于 OrderedDict 是可行的。
    • 不,OrderedDict 将保留插入顺序,这不是问题所问的。
    • 当使用重建一个全新实例的方法时(尽管只需要更改一个键),对于非常大的字典(很多项)会有什么性能?
    【解决方案3】:

    我想出了这个不会改变原始字典的函数。此功能也支持字典列表。

    import functools
    from typing import Union, Dict, List
    
    
    def rename_dict_keys(
        data: Union[Dict, List[Dict]], old_key: str, new_key: str
    ):
        """
        This function renames dictionary keys
    
        :param data:
        :param old_key:
        :param new_key:
        :return: Union[Dict, List[Dict]]
        """
        if isinstance(data, dict):
            res = {k: v for k, v in data.items() if k != old_key}
            try:
                res[new_key] = data[old_key]
            except KeyError:
                raise KeyError(
                    "cannot rename key as old key '%s' is not present in data"
                    % old_key
                )
            return res
        elif isinstance(data, list):
            return list(
                map(
                    functools.partial(
                        rename_dict_keys, old_key=old_key, new_key=new_key
                    ),
                    data,
                )
            )
        raise ValueError("expected type List[Dict] or Dict got '%s' for data" % type(data))
    

    【讨论】:

      【解决方案4】:

      假设您要将密钥 k3 重命名为 k4:

      temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
      temp_dict['k4']= temp_dict.pop('k3')
      

      【讨论】:

      • 不起作用,你的意思是... temp_dict['k4'] = temp_dict.pop('k3') ?
      • 这将返回一个TypeError: 'dict' object is not callable 如果使用temp_dict('k3') 您试图引用k3 键的值,那么您应该使用方括号而不是圆括号。但是,这只会向字典中添加一个新键,而不会根据要求重命名现有键。
      【解决方案5】:

      如果重命名所有字典键:

      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)
      

      【讨论】:

      • target_dict.keys() 是否保证与定义中的顺序相同?我认为 Python dict 是无序的,并且 dict 的键视图的顺序是不可预测的
      • 这在 Python 3.6 之后发生了变化,请参阅 docs.python.org/3/library/…
      【解决方案6】:

      @helloswift123 我喜欢你的功能。这是在一次调用中重命名多个键的修改:

      def rename(d, keymap):
          """
          :param d: old dict
          :type d: dict
          :param keymap: [{:keys from-keys :values to-keys} keymap]
          :returns: new dict
          :rtype: dict
          """
          new_dict = {}
          for key, value in zip(d.keys(), d.values()):
              new_key = keymap.get(key, key)
              new_dict[new_key] = d[key]
          return new_dict
      

      【讨论】:

        【解决方案7】:

        在 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 笔记本会按照他们选择的顺序显示字典...

        【讨论】:

          【解决方案8】:

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

          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()}
          

          【讨论】:

            【解决方案9】:

            我在上面使用@wim 的答案,在重命名键时使用 dict.pop(),但我发现了一个问题。循环遍历 dict 以更改键,而不将旧键列表与 dict 实例完全分开,导致将新的、更改的键循环到循环中,并丢失一些现有的键。

            首先,我是这样做的:

            for current_key in my_dict:
                new_key = current_key.replace(':','_')
                fixed_metadata[new_key] = fixed_metadata.pop(current_key)
            

            我发现以这种方式在字典中循环,即使在不应该的情况下,字典也会继续查找键,即新键,我更改的键!我需要将实例完全彼此分开,以 (a) 避免在 for 循环中找到我自己更改的键,以及 (b) 找到一些由于某种原因在循环中未找到的键。

            我现在正在这样做:

            current_keys = list(my_dict.keys())
            for current_key in current_keys:
                and so on...
            

            将 my_dict.keys() 转换为列表对于摆脱对不断变化的 dict 的引用是必要的。仅使用 my_dict.keys() 就让我与原始实例绑定在一起,但会产生奇怪的副作用。

            【讨论】:

              【解决方案10】:

              其他答案还不错。但是在python3.6中,普通的dict也有顺序。所以在正常情况下很难保持key的位置。

              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
              

              【讨论】:

                【解决方案11】:

                在我之前的几个人提到了.pop 在单行中删除和创建密钥的技巧。

                我个人认为更明确的实现更具可读性:

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

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

                【讨论】:

                  【解决方案12】:

                  使用检查newkey!=oldkey,您可以这样做:

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

                  【讨论】:

                  • 这很好用,但不保持原始顺序,因为默认情况下(在 python3 中)最后添加了新键。
                  • @szeitlin 你不应该依赖dict 顺序,即使python3.6+以有序形式初始化它,以前的版本也没有,它不是一个真正的特性,只是py3的后遗症.6+ dicts 发生了变化。如果您关心订单,请使用OrderedDict
                  • 谢谢@Granitosaurus,我不需要你向我解释。这不是我评论的重点。
                  • @szeitlin 您的评论暗示它会以某种方式、形状或形式改变字典顺序这一事实,而实际上没有人应该依赖字典顺序,因此这一事实完全无关
                  • 因此,上述争论的 cmets 在 Python 3.6 中是相关的,但从 3.7+ 开始,dicts are definitely ordered
                  【解决方案13】:

                  您可以使用 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
                  

                  【讨论】:

                  • @MERose 在模块搜索路径中的某处添加 Python 文件并从中导入 OrderedDict。但我建议:创建一个继承自 OrderedDict 的类,并为其添加一个 rename 方法。
                  • 似乎 OrderedDict 已经被重写为使用双向链表,所以可能还有一种方法可以做到这一点,但它需要更多的杂技。 :-/
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-01-17
                  • 2021-10-08
                  • 2016-09-23
                  • 2015-06-10
                  • 2021-04-22
                  相关资源
                  最近更新 更多