【问题标题】:Best practice to recursively update a nested dictionary?递归更新嵌套字典的最佳实践?
【发布时间】:2016-05-21 01:24:00
【问题描述】:

我正在 python 中创建一个嵌套字典,其中包含存储在 Amazon S3 存储桶中的文件的条目。因此,如果在我的存储桶中,我有几个这样的文件:

mys3bucket/subdir/world.txt

mys3bucket/subdir/hello.txt

mys3bucket/foobar.txt

我想用python制作一个这种格式的字典:

dict = { 'subdir' : { 'world.txt' : 'file', 'hello.txt' : 'file' }, 'foobar.txt'  :'file' } 

值('file')在这种情况下没有意义,但它们可以替换为文件的大小或其他东西(对于这个问题,这无关紧要)。关键是字典必须嵌套,因为子目录,显然嵌套的级别取决于特定树的深度。我已经编写了一个已经这样做的工作实现:

#!/usr/bin/python
import httplib
from re import compile as recomp

pattern = recomp("<Key>(.*?)<\/Key>")

def main(bucketname='elasticmapreduce'):
    url = bucketname + '.s3.amazonaws.com'
    HTTPconnection = httplib.HTTPConnection(url)
    HTTPconnection.request("GET", "/")
    response = HTTPconnection.getresponse()
    content = response.read()
    fileslist = pattern.findall(content)

    filesdict = {}

  def intoDict(path,mydict):
      if len(path) == 1:
          mydict[path[0]] = 'file'
         return mydict
      else:
          name = path.pop(0)
      if name in mydict:
          mydict[name] = intoDict(path,mydict[name])
      else:
          mydict[name] = intoDict(path,{})
      return mydict

  for line in fileslist:
      splitline = line.split('/')
      if splitline[-1] != '':
          filesdict = intoDict(splitline,filesdict)

      return filesdict

默认存储桶名称只是设置为我发现能够测试代码的公共存储桶。

使用正则表达式的原因是,当您查询存储桶时,S3 会返回一个 XML 格式的文本,因此正则表达式只是从中提取文件路径。

我很好奇我的实施效率。正如您在 for 循环中看到的那样,我每次都将整个字典传递给 intoDict() 函数,并在它返回时重新编写它。 intoDict() 函数是递归/自引用的,这就是嵌套的产生方式。解释发生了什么有点困难,但我想你可以看到。我花了一段时间才找到这个解决方案,因为起初我试图使用dictionary.update() 来更新for 循环内的字典,但它不能正常工作。

我想知道是否有经验丰富的嵌套字典和/或递归函数的人可以评论这是否是实现我正在尝试的正确方法,或者是否可以做得更好。

【问题讨论】:

    标签: python dictionary recursion amazon-s3 hashmap


    【解决方案1】:

    有几件事可以改进。您可以使用迭代来找到插入的正确路径,而不是使用递归。您还应该考虑在此处删除列表的突变:name = path.pop(0)。如果 filesdict 的值正如您所确定的那样已经存在,您也不需要在每个级别上分配它们。

    以下是使用defaultdict 的示例,说明如何将上述内容付诸实践:

    from collections import defaultdict
    
    # Create dict that automatically assigns empty dict to a key that doesn't exist
    dd = lambda: defaultdict(dd)
    filesdict = dd()
    
    for line in fileslist:
        path = line.split('/')
        if path[-1] != '':
            d = filesdict
    
            # Iterate to location where file is to be added without mutating path
            for i in range(len(path) - 1):
                # If d[path[i]] doesn't exist empty dict is automatically created here
                d = d[path[i]]
            d[path[-1]] = 'file'
    

    您也可以考虑使用re.finditer 而不是re.findall,因为您会逐个处理文件。

    【讨论】:

    • 谢谢@niemmi,这真的很酷!你在这里向我介绍了几个新概念。我已将代码更改为在没有defaultdict 的情况下工作,所以我添加了:if path[i] not in d: d[path[i]] = {} 我不明白的事情:当我们进行分配时d = filesdict 我猜 Python 在两个字典之间创建了某种动态关联?抱歉,如果这是一个愚蠢的问题。
    • @KirillVourlakidis 根本不是一个愚蠢的问题。我不确定动态关联是什么意思,但它只是对同一对象的两个引用。现在,在d = d[path[i]] 行执行之后,d 改为引用子dict。处理下一条路径时,我们需要再次从根目录开始,因此我们需要在那里保持引用 (filesdict)。
    • 引用同一个对象是有道理的!在我的脑海中,我有点想象 Python 创建整个字典的副本并将它们链接在一起,如果一个被更改,它会自动更改另一个,因此我的“动态关联”评论。正如你所看到的,我仍然对整个面向对象的事情有所了解。
    猜你喜欢
    • 2020-10-21
    • 2014-12-20
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 2011-02-13
    • 2013-10-02
    • 2011-07-12
    相关资源
    最近更新 更多