如何对可迭代元素进行glob

时间:2010-08-30 14:33:12

标签: python

我有一个包含iterables的python字典,其中一些是列表,但大多数是其他字典。我想做类似以下的glob-style赋值:

myiter['*']['*.txt']['name'] = 'Woot'

也就是说,对于myiter中的每个元素,使用以“.txt”结尾的键查​​找所有元素,然后将“name”项设置为“Woot”。

我考虑过对dict进行子类化并使用fnmatch模块。但是,我不清楚最好的实现这一目标的方式是什么。

4 个答案:

答案 0 :(得分:3)

我认为最好的方法是 - '*'是dict中完全有效的键,因此myiter['*']具有完美的定义意义和用处,颠覆这些肯定会导致问题。如何对字符串的键进行“全局”处理,包括列表而不是映射的元素中的排他性整数“键”(索引),这也是一个很大的设计问题。

如果您必须这样做,我建议您通过继承abstract base class collections.MutableMapping实施所需的方法来完全控制( __len____iter____getitem____setitem____delitem__以及为了获得更好的效果,还会覆盖其他内容,例如__contains__,其中ABC确实在其他人的基础上实现,但是慢慢地根据包含的dict实现。相反,根据其他建议对dict进行子类化将要求您覆盖大量方法,以避免在您覆盖的方法中使用“包含通配符的键”之间的不一致行为,以及在您不重写的方法中

无论是继承collections.MutableMapping还是dict,都要制作Globbable课程,您必须做出核心设计决定:yourthing[somekey] 返回什么< / em>当yourthingGlobbable时?

somekey是包含通配符的字符串时,它必须返回不同的类型,而不是其他任何类型。在后一种情况下,人们可以想象,实际上在那个条目上是什么;但在前者中,它不能只返回另一个Globbable - 否则,在一般情况下,yourthing[somekey] = 'bah' 会做什么?对于您的单个“光滑语法”示例,您希望它在somekey的每个中设置yourthing条目( HUGE 语义打破宇宙中每个其他映射的行为;-) - 但是,那你怎么设置yourthing本身的一个条目?!

让我们看看Python的Zen是否有任何关于你渴望的“光滑语法”的说法......:

>>> import this
    ...
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.

暂时考虑失去“光滑的语法”(以及它必然暗示的所有巨大的语义难题)的替代方案,以支持清晰和简单(在这里使用Python 2.7和更好的语法) ,仅用于字典理解 - 如果您遇到2.6或更早版本,请使用明确的dict(...)调用,例如:

def match(s, pat):
    try: return fnmatch.fnmatch(s, pat)
    except TypeError: return False

def sel(ds, pat):
    return [d[k] for d in ds for k in d if match(k, pat)]

def set(ds, k, v):
    for d in ds: d[k] = v

所以你的作业可能会变成

set(sel(sel([myiter], '*')), '*.txt'), 'name', 'Woot')

'*'的选择是多余的,如果全部,我只是省略它)。这是如此可怕,以至于值得我上面提到的问题的麻烦,以便使用

myiter['*']['*.txt']['name'] = 'Woot'

...?到目前为止,最清晰,表现最佳的方式仍然是更简单的

def match(k, v, pat):
    try:
      if fnmatch.fnmatch(k, pat):
        return isinstance(v, dict)
    except TypeError:
        return False

for k, v in myiter.items():
  if match(k, v, '*'):
    for sk, sv in v.items():
      if match(sk, sv, '*.txt'):
        sv['name'] = 'Woot'

但如果你绝对渴望简洁和紧凑,鄙视Python的禅宗“稀疏比密集更好”,你可以至少获得它们,而不是我提到的各种恶梦,以实现你的理想的“语法糖”。

答案 1 :(得分:2)

最好的方法是继承dict并使用fnmatch模块。

  • 子类dict:以面向对象的方式添加所需的功能。
  • fnmatch模块:重用现有功能。

答案 2 :(得分:2)

您可以使用fnmatch来匹配字典键,尽管您必须稍微妥协一下语法,特别是如果您想在嵌套字典上执行此操作。也许一个带有搜索方法的自定义字典类可以很好地返回通配符。

这是一个非常基本的示例,它带有一个警告,表明它不是递归的,不会处理嵌套的字典:

from fnmatch import fnmatch

class GlobDict(dict):
    def glob(self, match):
        """@match should be a glob style pattern match (e.g. '*.txt')"""
        return dict([(k,v) for k,v  in self.items() if fnmatch(k, match)])

# Start with a basic dict
basic_dict = {'file1.jpg':'image', 'file2.txt':'text', 'file3.mpg':'movie',
              'file4.txt':'text'}

# Create a GlobDict from it
glob_dict = GlobDict( **basic_dict )

# Then get glob-styl results!
globbed_results = glob_dict.glob('*.txt')
# => {'file4.txt': 'text', 'file2.txt': 'text'}

最佳的方式是什么?最好的方法是有效的方法。在创建解决方案之前不要尝试优化解决方案!

答案 3 :(得分:1)

遵循最小魔法原则,或许只是定义一个递归函数,而不是子类化dict

import fnmatch

def set_dict_with_pat(it,key_patterns,value):
    if len(key_patterns)>1:
        for key in it:
            if fnmatch.fnmatch(key,key_patterns[0]):
                set_dict_with_pat(it[key],key_patterns[1:],value)
    else:
        for key in it:
            if fnmatch.fnmatch(key,key_patterns[0]):
                it[key]=value

可以这样使用:

myiter=({'dir1':{'a.txt':{'name':'Roger'},'b.notxt':{'name':'Carl'}},'dir2':{'b.txt':{'name':'Sally'}}})
set_dict_with_pat(myiter,['*','*.txt','name'],'Woot')
print(myiter)
# {'dir2': {'b.txt': {'name': 'Woot'}}, 'dir1': {'b.notxt': {'name': 'Carl'}, 'a.txt': {'name': 'Woot'}}}