【问题标题】:Filter dict to contain only certain keys?过滤字典以仅包含某些键?
【发布时间】:2011-03-26 02:37:10
【问题描述】:

我有一个dict 有一大堆条目。我只对其中的一小部分感兴趣。有没有一种简单的方法可以把所有其他的都剪掉?

【问题讨论】:

  • 说明什么类型的键(整数?字符串?日期?任意对象?)很有帮助,因此是否有一个简单的(字符串、正则表达式、列表成员资格或数字不等式)测试来检查哪些键进出。否则我们是否需要调用任意函数来确定这一点。
  • @smci 字符串键。不要以为我什至没有想到我可以使用其他任何东西;我已经在 J​​S 和 PHP 中编码这么久了......

标签: python dictionary


【解决方案1】:

构造一个新的字典:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

使用字典理解。

如果您使用缺少它们的版本(即 Python 2.6 和更早版本),请将其设为 dict((your_key, old_dict[your_key]) for ...)。还是一样的,只是丑了点。

请注意,这与 jnnnnn 的版本不同,对于任何大小的 old_dicts 都具有稳定的性能(仅取决于 your_keys 的数量)。无论是速度还是内存。由于这是一个生成器表达式,它一次处理一个项目,它不会查看 old_dict 的所有项目。

原地移除所有内容:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]

【讨论】:

  • "使用字典理解,如果你使用的版本缺少它们" == version
  • 如果 old_dict 中不存在文件管理器密钥之一,则引发 KeyError。我建议 {k:d[k] for k in filter if k in d}
  • @PeterGibson 是的,如果这是要求的一部分,您需要对此做一些事情。无论是静默删除键、添加默认值还是其他方式,都取决于您在做什么;有很多用例表明您的方法是错误的。还有很多 old_dict 中缺少的键表示其他地方的错误,在这种情况下,我非常喜欢错误而不是默默地错误结果。
  • @PeterGibson 它没有,字典查找是 O(1)。
  • nit:字典是哈希映射,所以正常情况是 O(1)。最坏(极不可能)的情况是 O(n),但取决于哈希冲突的可能性。你需要一个天文数字般大的字典,或者一个非常粗略的散列算法才能开始看到这是一个问题。 stackoverflow.com/a/1963514/1335793
【解决方案2】:

更优雅的dict理解:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}

【讨论】:

  • 赞成。我正在考虑添加与此类似的答案。只是出于好奇,为什么 {k:v for k,v in dict.items() ...} 而不是 {k:dict[k] for k in dict ...} 有性能差异吗?
  • 回答了我自己的问题。 {k:dict[k] for k in dict ...} 至少在 Python 2.7.6 中快了大约 20-25%,字典包含 26 个项目(timeit(..., setup="d = {chr(x+97):x+1 for x in range(26)}")),取决于有多少项目被过滤掉(过滤掉辅音键比过滤掉元音键更快,因为你正在查找更少的项目)。随着字典大小的增加,性能差异可能会变得不那么显着。
  • 如果您改用mydict.iteritems(),性能可能相同。 .items() 创建另一个列表。
【解决方案3】:

以下是 python 2.6 中的示例:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

过滤部分是if语句。

如果您只想选择非常多的键中的几个,则此方法比 delnan 的答案要慢。

【讨论】:

  • 我猜我可能会使用if key in ('x','y','z')。
  • 如果您已经知道想要哪些键,请使用 delnan 的答案。如果您需要使用 if 语句测试每个键,请使用 ransford 的答案。
  • 此解决方案还有一个优势。如果字典是从昂贵的函数调用返回的(即 a/old_dict 是函数调用),则此解决方案仅调用该函数一次。在命令式环境中,将函数返回的字典存储在变量中并不是什么大问题,但在函数式环境中(例如在 lambda 中),这是关键观察。
【解决方案4】:

您可以使用我的 funcy 库中的 project 函数来做到这一点:

from funcy import project
small_dict = project(big_dict, keys)

也可以看看select_keys。

【讨论】:

    【解决方案5】:

    代码 1:

    dict = { key: key * 10 for key in range(0, 100) }
    d1 = {}
    for key, value in dict.items():
        if key % 2 == 0:
            d1[key] = value
    

    代码 2:

    dict = { key: key * 10 for key in range(0, 100) }
    d2 = {key: value for key, value in dict.items() if key % 2 == 0}
    

    代码 3:

    dict = { key: key * 10 for key in range(0, 100) }
    d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}
    

    所有代码段的性能都是通过 timeit 使用 number=1000 来衡量的,并为每段代码收集 1000 次。

    对于python 3.6来说,三种方式的过滤dict键的性能几乎相同。对于 python 2.7 代码 3 稍快。

    【讨论】:

    • 只是好奇,你是用 Python 制作的吗?
    • ggplot2 in R - tidyverse的一部分
    • 最好不要使用dict,因为它是python内置的。
    【解决方案6】:

    这个线性 lambda 应该可以工作:

    dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])
    

    这是一个例子:

    my_dict = {"a":1,"b":2,"c":3,"d":4}
    wanted_keys = ("c","d")
    
    # run it
    In [10]: dictfilt(my_dict, wanted_keys)
    Out[10]: {'c': 3, 'd': 4}
    

    这是一个基本的列表理解,它遍历您的 dict 键(x 中的 i),如果键位于所需的键列表 (y) 中,则输出元组 (key,value) 对的列表。 dict() 将整个内容包装为 dict 对象输出。

    【讨论】:

    • 应该使用set 代替wanted_keys,否则看起来不错。
    • 如果我的原始字典包含列表而不是值,这会给我一个空白字典。有什么解决方法吗?
    • @Francesco,你能举个例子吗?如果我运行:dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')),它会按预期返回{'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}。
    • 我试过这个:dict={'0':[1,3], '1':[0,2,4], '2':[1,4]},结果是{},我认为这是一个空白字典。
    • 有一件事,“dict”是一个保留字,所以你不应该用它来命名一个dict。你试图拔出的钥匙是什么?如果我运行:foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2')),我会得到:{'0': [1, 3], '2': [1, 4]},这是预期的结果
    【解决方案7】:

    给定您的原始字典 orig 和您感兴趣的条目集 keys:

    filtered = dict(zip(keys, [orig[k] for k in keys]))
    

    这不如 delnan 的答案好,但应该适用于每个感兴趣的 Python 版本。但是,对于原始字典中存在的 keys 的每个元素来说,它都是脆弱的。

    【讨论】:

    • 嗯,这基本上是我的 dict 理解的“元组生成器版本”的急切版本。确实非常兼容,尽管在 2005 年春季的 2.4 中引入了生成器表达式 - 说真的,还有人在使用它吗?
    • 我不反对; 2.3 真的不应该再存在了。但是,作为对 2.3 使用情况的过时调查:moinmo.in/PollAboutRequiringPython24 短版:RHEL4、SLES9,随 OS X 10.4 一起提供
    【解决方案8】:

    基于 delnan 接受的答案。

    如果您想要的其中一个键不在 old_dict 中怎么办? delnan 解决方案将抛出一个您可以捕获的 KeyError 异常。如果这不是你需要的,也许你想:

    1. 只包含在 old_dict 和您的想要的_keys 中都存在的键。

      old_dict = {'name':"Foobar", 'baz':42}
      wanted_keys = ['name', 'age']
      new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
      
      >>> new_dict
      {'name': 'Foobar'}
      
    2. 为未在 old_dict 中设置的键设置默认值。

      default = None
      new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
      
      >>> new_dict
      {'age': None, 'name': 'Foobar'}
      

    【讨论】:

    • 你也可以{k: old_dict.get(k, default) for k in ...}
    【解决方案9】:

    这个函数可以解决问题:

    def include_keys(dictionary, keys):
        """Filters a dict by only including certain keys."""
        key_set = set(keys) & set(dictionary.keys())
        return {key: dictionary[key] for key in key_set}
    

    就像 delnan 的版本一样,这个使用字典理解并且对大型字典具有稳定的性能(仅取决于您允许的键数,而不是字典中的总键数)。

    就像 MyGGan 的版本一样,这个版本允许您的键列表包含字典中可能不存在的键。

    作为奖励,这是相反的,您可以通过排除原始中的某些键来创建字典:

    def exclude_keys(dictionary, keys):
        """Filters a dict by excluding certain keys."""
        key_set = set(dictionary.keys()) - set(keys)
        return {key: dictionary[key] for key in key_set}
    

    注意,和delnan的版本不同,操作没有到位,所以性能和字典的key数有关。不过这样做的好处是该函数不会修改提供的字典。

    编辑:添加了一个单独的函数,用于从字典中排除某些键。

    【讨论】:

    • 您应该允许 keys 以任何类型的可迭代,例如 set 接受的内容。
    • 啊,好电话,感谢您指出这一点。我会更新的。
    • 我想知道你是否最好使用两个功能。如果你问 10 个人“invert 是否暗示keys 参数被保留,或者keys 参数被拒绝?”,有多少人会同意?
    • 已更新。让我知道你的想法。
    • 如果输入字典有列表代替值,这似乎不起作用。在这种情况下,你会得到一个 void dict。有什么解决方法吗?
    【解决方案10】:

    另一种选择:

    content = dict(k1='foo', k2='nope', k3='bar')
    selection = ['k1', 'k3']
    filtered = filter(lambda i: i[0] in selection, content.items())
    

    但是你得到的是 list (Python 2) 或由 filter() 返回的迭代器 (Python 3),而不是 dict。

    【讨论】:

    • 将filtered 包裹在dict 中,然后您就可以取回字典了!
    【解决方案11】:

    如果我们想创建一个删除选定键的新字典,我们可以使用字典理解
    例如:

    d = {
    'a' : 1,
    'b' : 2,
    'c' : 3
    }
    x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
    y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
    # x is {'a': 1, 'b': 2}
    # y is {'a': 1, 'b': 2}
    

    【讨论】:

    • 整洁。仅适用于 Python 3。Python 2 显示“TypeError: unsupported operand type(s) for -: 'list' and 'set'”
    • 为 Python 2 添加了 set(d.keys())。这在我运行时有效。
    【解决方案12】:

    在我看来这是最简单的方法:

    d1 = {'a':1, 'b':2, 'c':3}
    d2 = {k:v for k,v in d1.items() if k in ['a','c']}
    

    我也喜欢这样做来解压值:

    a, c = {k:v for k,v in d1.items() if k in ['a','c']}.values()
    
    

    【讨论】:

    • 这种方法对于像我这样的新手来说是最容易解析和理解的。
    • 为了提高效率,我建议使用一组过滤键:if k in {'a','c'} 而不是 if k in ['a','c']。
    【解决方案13】:

    缩写:

    [s.pop(k) for k in list(s.keys()) if k not in keep]
    

    正如大多数答案所暗示的那样,为了保持简洁性,我们必须创建一个重复的对象,无论是 list 还是 dict。这个创建了一个丢弃的list,但删除了原始dict 中的键。

    【讨论】:

    【解决方案14】:

    你可以使用python-benedict,它是一个dict子类。

    安装:pip install python-benedict

    from benedict import benedict
    
    dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])
    

    它在 GitHub 上开源:https://github.com/fabiocaccamo/python-benedict


    免责声明:我是这个库的作者。

    【讨论】:

      【解决方案15】:

      这是在一个衬里中使用del 的另一种简单方法:

      for key in e_keys: del your_dict[key]
      

      e_keys 是要排除的键的列表。它会更新你的字典,而不是给你一个新的。

      如果你想要一个新的输出字典,那么在删除之前复制一个字典:

      new_dict = your_dict.copy()           #Making copy of dict
      
      for key in e_keys: del new_dict[key]
      

      【讨论】:

      • 如果您不需要创建副本(因此创建 dict 与 for key in e_keys: del your_dict[key]),您能否分析更快的方法?
      • @Mr_and_Mrs_D 不创建副本肯定会更快。大约快 15%
      • 事实证明 dict 理解确实较慢,但很大程度上取决于过滤键的大小 - 请参阅:stackoverflow.com/a/69973383/281545
      【解决方案16】:

      这是我的做法,支持像mongo查询这样的嵌套字段。

      使用方法:

      >>> obj = { "a":1, "b":{"c":2,"d":3}}
      >>> only(obj,["a","b.c"])
      {'a': 1, 'b': {'c': 2}}
      

      only函数:

      def only(object,keys):
          obj = {}
          for path in keys:
              paths = path.split(".")
              rec=''
              origin = object
              target = obj
              for key in paths:
                  rec += key
                  if key in target:
                      target = target[key]
                      origin = origin[key]
                      rec += '.'
                      continue
                  if key in origin:
                      if rec == path:
                          target[key] = origin[key]
                      else:
                          target[key] = {}
                      target = target[key]
                      origin = origin[key]
                      rec += '.'
                  else:
                      target[key] = None
                      break
          return obj
      

      【讨论】:

        【解决方案17】:

        我们也可以通过更优雅的dict理解来实现这一点:

        my_dict = {"a":1,"b":2,"c":3,"d":4}
        
        filtdict = {k: v for k, v in my_dict.items() if k.startswith('a')}
        print(filtdict)
        

        【讨论】:

          【解决方案18】:

          根据问题的标题,人们会期望在适当的位置过滤字典 - 一些答案建议了这样做的方法 - 仍然不清楚一种明显的方式是什么 - 我添加了一些时间:

          import random
          import timeit
          import collections
          
          repeat = 3
          numbers = 10000
          
          setup = ''
          def timer(statement, msg='', _setup=None):
              print(msg, min(
                  timeit.Timer(statement, setup=_setup or setup).repeat(
                      repeat, numbers)))
          
          timer('pass', 'Empty statement')
          
          dsize = 1000
          d = dict.fromkeys(range(dsize))
          keep_keys = set(random.sample(range(dsize), 500))
          drop_keys = set(random.sample(range(dsize), 500))
          
          def _time_filter_dict():
              """filter a dict"""
              global setup
              setup = r"""from __main__ import dsize, collections, drop_keys, \
          keep_keys, random"""
              timer('d = dict.fromkeys(range(dsize));'
                    'collections.deque((d.pop(k) for k in drop_keys), maxlen=0)',
                    "pop inplace - exhaust iterator")
              timer('d = dict.fromkeys(range(dsize));'
                    'drop_keys = [k for k in d if k not in keep_keys];'
                    'collections.deque('
                        '(d.pop(k) for k in list(d) if k not in keep_keys), maxlen=0)',
                    "pop inplace - exhaust iterator (drop_keys)")
              timer('d = dict.fromkeys(range(dsize));'
                    'list(d.pop(k) for k in drop_keys)',
                    "pop inplace - create list")
              timer('d = dict.fromkeys(range(dsize));'
                    'drop_keys = [k for k in d if k not in keep_keys];'
                    'list(d.pop(k) for k in drop_keys)',
                    "pop inplace - create list (drop_keys)")
              timer('d = dict.fromkeys(range(dsize))\n'
                    'for k in drop_keys: del d[k]', "del inplace")
              timer('d = dict.fromkeys(range(dsize));'
                    'drop_keys = [k for k in d if k not in keep_keys]\n'
                    'for k in drop_keys: del d[k]', "del inplace (drop_keys)")
              timer("""d = dict.fromkeys(range(dsize))
          {k:v for k,v in d.items() if k in keep_keys}""", "copy dict comprehension")
              timer("""keep_keys=random.sample(range(dsize), 5)
          d = dict.fromkeys(range(dsize))
          {k:v for k,v in d.items() if k in keep_keys}""",
                    "copy dict comprehension - small keep_keys")
          
          if __name__ == '__main__':
              _time_filter_dict()
          

          结果:

          Empty statement 8.375600000000427e-05
          pop inplace - exhaust iterator 1.046749841
          pop inplace - exhaust iterator (drop_keys) 1.830537424
          pop inplace - create list 1.1531293939999987
          pop inplace - create list (drop_keys) 1.4512304149999995
          del inplace 0.8008298079999996
          del inplace (drop_keys) 1.1573763689999979
          copy dict comprehension 1.1982901489999982
          copy dict comprehension - small keep_keys 1.4407784069999998
          

          因此,如果我们想就地更新,del 似乎是赢家 - dict 理解解决方案当然取决于正在创建的 dict 的大小,删除一半的键已经太慢了 - 所以如果你要避免创建新的 dict可以就地过滤。

          编辑以解决@mpen 的评论-我从keep_keys 计算了放置键(假设我们没有放置键)-我假设keep_keys/drop_keys 是此迭代的集合,或者需要很长时间。有了这些假设,del 仍然更快 - 但可以肯定的是:如果您有一个 (set, list, tuple) drop 键,请选择 del

          【讨论】:

          • drop_keys 不是一个公平的比较。问题更类似于keep_keys。我们知道我们想要哪些键,而不是我们不想要哪些键。
          • 感谢@mpen - 实际上,如果我们尝试计算drop_keys,这会减慢很多 pop/del 方法。将为此发布一些时间安排
          • 那里@mpen - 即使我计算drop_keys(我假设保持键是O(1)k in keep_keys的集合),似乎del也胜过dict理解。可能这意味着创建一个包含 500 个条目的字典比创建一个包含 500 个元素的列表要慢一些:P
          【解决方案19】:

          如果您提前知道否定集(又名not 键):

          v = {'a': 'foo', 'b': 'bar', 'command': 'fizz', 'host': 'buzz'  }
          args = {k: v[k] for k in v if k not in ["a", "b"]}
          args # {'command': 'fizz', 'host': 'buzz'}
          

          【讨论】:

            【解决方案20】:

            我们可以像这样简单地使用 lambda 函数:

            >>> dict_filter = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])
            >>> large_dict = {"a":1,"b":2,"c":3,"d":4}
            >>> new_dict_keys = ("c","d")
            >>> small_dict=dict_filter(large_dict, new_dict_keys)
            >>> print(small_dict)
            {'c': 3, 'd': 4}
            >>> 
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-12-10
              • 2022-08-19
              • 2011-10-13
              • 2023-04-09
              • 2019-11-21
              • 2022-10-14
              • 1970-01-01
              相关资源
              最近更新 更多