问题 如何从python dict中检索密钥只是部分已知?


我有一个 dict 有字符串型键,其确切值我不知道(因为它们是在其他地方动态生成的)。但是,我知道我想要的密钥包含一个特定的子字符串,并且具有此子字符串的单个密钥肯定在dict中。

检索此密钥的值的最佳或“最pythonic”方法是什么?

我想到了两个策略,但两个都让我感到烦恼:

for k,v in some_dict.items():
    if 'substring' in k:
        value = v
        break

- 要么 -

value = [v for (k,v) in some_dict.items() if 'substring' in k][0]

第一种方法是笨重而有些丑陋,而第二种方法更清洁,但是进入列表理解的额外步骤( [0])让我烦恼。是否有更好的方式来表达第二个版本,或者更简洁的方式来编写第一个版本?


3830
2017-08-13 07:51


起源

与大多数其他语言相比,它们看起来都非常简洁。但我认为第一个更具可读性。 - Keith
为什么你知道子串?有没有更多信息可供您使用?你可以对字典进行某种解析/转换吗?你会在同一个dict上做很多类似的查找吗? - Karl Knechtel - away from home
你应该试试 k.startswith('substring') 要么 k.endswith('substring') 如果它在开头或结尾;他们可能会更快。 - agf
如果查找所有你拥有的 some_dict 因为它完全没用,列表会更好。如果你有一个你想匹配的子串列表,你的时间复杂度为O(N ** 2)。你需要一个关键的索引才能有效地做到这一点,像Sphinx这样的全文搜索引擎基本上都是这样做的。 - Jochen Ritzel
first method is bulky and somewhat ugly, while the second is cleaner 这里的小评论:第二个不是更清洁,它只是更少 \n 字符。有一些奇怪的信念,单线工作更快,更易读。他们不是。 - Jakub M.


答案:


可以选择使用第一个版本的性能属性编写第二个版本。

用一个 发电机表达 而不是列表理解:

value = next(v for (k,v) in some_dict.iteritems() if 'substring' in k)

括号内的表达式将返回一个迭代器,然后您将要求它提供下一个,即第一个元素。没有处理其他元素。


10
2017-08-13 08:05



绝对使用 iteritems 在Python 2上;否则这是我的首选方式。 - agf
@agf:感谢抓住小故障。运用 items() 实际上根本就没有任何意义。 - blubb
这是一个巧妙的伎俩。我很想写 first = next 在某个地方让它更清晰地阅读......尽管如此 first = next 线路本身就是一种WTF我想o_O - Karl Knechtel - away from home
@Karl让我笑得很开心。你不在家时有自己独立的帐户吗?为什么? - agf
@agf我不喜欢使用任何我需要登录的东西,而不是在我自己的电脑上。所以这只是一个临时ID,甚至没有注册。可能不值得担心,但你真的不知道....我也不完全确定我用的是什么密码,我认为这可能是一个随机生成的密码...当我回到家时应该检查我的笔记> _ < - Karl Knechtel - away from home


这个怎么样:

value = (v for (k,v) in some_dict.iteritems() if 'substring' in k).next()

它会在找到第一场比赛时立即停止。

但它仍然具有O(n)复杂度,其中n是键值对的数量。您需要类似后缀列表或后缀树的内容来加快搜索速度。


1
2017-08-13 08:05



这是我考虑过的,但我个人发现它比我提出的列表理解“更难看”。西蒙的答案解决了丑陋的问题,但你说这个解决方案比我提出的解决方案更好,至少在性能方面。 - CoreDumpError
免费 next 函数也可用于指定返回的默认值 .next() 呼吁迭代者加注 StopIteration。 - Karl Knechtel - away from home


如果有很多键但字符串很容易从子字符串重构,那么它可以更快地重建它。例如通常你知道密钥的开头但不知道附加的日期戳。 (因此,您可能只需要尝试365个日期,而不是迭代数百万个密钥)。 情况不太可能如此,但我认为无论如何我会建议它。 例如

>>> names={'bob_k':32,'james_r':443,'sarah_p':12}
>>> firstname='james' #you know the substring james because you have a list of firstnames
>>> for c in "abcdefghijklmnopqrstuvwxyz":
...     name="%s_%s"%(firstname,c)
...     if name in names:
...             print name
... 
james_r

1
2017-08-13 10:34



值得庆幸的是,我没有那么大的决定,这种策略是必要的,但它 是 一个我没想过的新想法。 +1 - CoreDumpError


class MyDict(dict):
    def __init__(self, *kwargs):
        dict.__init__(self, *kwargs)

    def __getitem__(self,x):
        return next(v for (k,v) in self.iteritems() if x in k)



# Defining several dicos ----------------------------------------------------    
some_dict = {'abc4589':4578,'abc7812':798,'kjuy45763':1002}

another_dict = {'boumboum14':'WSZE x478',
                'tagada4783':'ocean11',
                'maracuna102455':None}

still_another = {12:'jfg',45:'klsjgf'}



# Selecting the dicos whose __getitem__ method will be changed -------------       
name,obj = None,None
selected_dicos = [ (name,obj) for (name,obj) in globals().iteritems()
                   if type(obj)==dict
                   and all(type(x)==str for x in obj.iterkeys())]

print 'names of selected_dicos ==',[ name for (name,obj) in selected_dicos] 



# Transforming the selected dicos in instances of class MyDict -----------
for k,v in selected_dicos:
    globals()[k] = MyDict(v)



# Exemple of getting a value ---------------------------------------------      
print "some_dict['7812'] ==",some_dict['7812']

结果

names of selected_dicos == ['another_dict', 'some_dict']
some_dict['7812'] == 798

1
2017-08-13 10:05



在我的回答中,对于downvoter不满意的是什么?所以不应该在没有评论的情况下允许downvotes - eyquem
@Jakub Mikians谢谢你,但我想更多地了解我的回答是什么 - eyquem
我认为这是一个非常有趣的想法,但是你 是 比必要的更冗长。我很确定定义了 __init__ MyDict的函数是多余的,因为MyDict将使用dict __init__ 如果它没有它自己的一个。由于您的解决方案使用与Simon相同的基本答案,我认为对于想要更复杂的解决方案来解决这类问题的人来说,它是有价值的。 - CoreDumpError
这个答案基本上采用了简单的方法,有点可疑地将它包装在一个类中,然后显示了一个类的样板用法,其使用应该是不言自明的,使用通常被认为是相当深刻的魔法,以便转换dict in地点。哦,并且有一个列表理解,然后直接提供给for循环,在那里直接循环更简单,并且决定包装的决定是基于显式类型检查(一般不赞成)。简而言之,这可怕是非Pythonic。 - Karl Knechtel - away from home


我更喜欢第一个版本,虽然我会使用 some_dict.iteritems() (如果你使用的是Python 2)因为那时你不必事先建立所有项目的完整列表。相反,你会在完成后迭代dict并中断。

在Python 3上, some_dict.items(2) 已经导致字典视图,所以它已经是一个合适的迭代器。


0
2017-08-13 07:58