Python 如何为iterable元素创建glob

Python 如何为iterable元素创建glob,python,Python,我有一个python字典,其中包含iterables,其中一些是列表,但大多数是其他字典。我想做类似于下面的glob风格的作业: myiter['*']['*.txt']['name'] = 'Woot' 也就是说,对于myiter中的每个元素,查找键以“.txt”结尾的所有元素,然后将它们的“name”项设置为“Woot” 我考虑过对dict进行分类并使用fnmatch模块。但是,我不清楚实现这一点的最佳方法是什么。最好的方法是将dict子类化并使用fnmatch模块 子类dict:以面向

我有一个python字典,其中包含iterables,其中一些是列表,但大多数是其他字典。我想做类似于下面的glob风格的作业:

myiter['*']['*.txt']['name'] = 'Woot'
也就是说,对于myiter中的每个元素,查找键以“.txt”结尾的所有元素,然后将它们的“name”项设置为“Woot”


我考虑过对dict进行分类并使用fnmatch模块。但是,我不清楚实现这一点的最佳方法是什么。

最好的方法是将dict子类化并使用fnmatch模块

  • 子类dict:以面向对象的方式添加所需的功能
  • 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'}

至于哪种方式最好?最好的方法是有效的方法。在创建解决方案之前,不要尝试对其进行优化

我认为最好的方法是而不是这样做--
'*'
是dict中一个完全有效的键,因此
myiter['*']
有一个非常明确的含义和用途,颠覆肯定会导致问题。如何在字符串的键上“glob”,包括列表而非映射元素中的独占整数“键”(索引),也是一个相当大的设计问题

如果您必须这样做,我建议您通过对
集合.MutableMapping进行子类化来实现完全控制,并实现所需的方法(
\uuuu len\uuuuuuu
\uuuuuu iter\uuuuuu
\uuuuuu getitem\uuuuuuu
\uuuu delitem\uuuuuuuu
,并且为了更好的性能,还覆盖其他的,例如
\uuuuu包含\uuuuuuu
,ABC是在其他的基础上实现的,但速度较慢)就包含的
dict
而言,按照其他建议,子类化
dict
将要求您重写大量方法,以避免在您确实重写的方法中使用“包含通配符的键”与在您不重写的方法中使用“包含通配符的键”之间出现不一致的行为

无论您是将
collections.MutableMapping
子类化,还是将
dict
子类化,为了使您的
Globbable
类成为一个类,您都必须做出一个核心设计决策:当
yourthing[somekey]
yourthing
时,
返回什么

somekey
是一个包含通配符的字符串时,与其他任何东西相比,它可能必须返回不同的类型。在后一种情况下,人们可以想象,该条目实际上是什么;但在前一种情况下,它不能只返回另一个
Globbable
——否则,
yourthing[somekey]会是什么='bah'
在一般情况下,你做什么?对于你单一的“灵活语法”示例,你希望它在
yourthing
的每个项目中设置一个
somekey
条目(一个与宇宙中所有其他映射行为的语义断裂;-)--但是,你怎么能在
你的东西本身设置一个条目呢

让我们看看Python的禅宗是否对您所向往的这种“流畅语法”有什么看法…:

>>> 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和更好的语法,只是为了理解dict——如果你坚持使用2.6或更早版本,请使用显式的
dict(…)
调用,而不是使用显式的
dict(…)
),例如:

所以你的任务可能会

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的禅宗“稀疏比密集好”,你至少可以在不做我提到的各种噩梦的情况下实现你理想的“语法糖”.

遵循最小魔法原则,也许只定义一个递归函数,而不是子类化
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'}}}

将导入fnmatch替换为from fnmatch import fnmatch将摆脱那些不方便的fnmatch.fnmatch。@Tony:谢谢你的建议。但是,这个答案()使我确信,
import module
是比
from module import function
更好的编码风格。
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'}}}