如何对可迭代元素使用glob

6 投票
4 回答
2765 浏览
提问于 2025-04-16 03:29

我有一个Python字典,这个字典里面包含了一些可迭代的东西,有的是列表,但大部分是其他字典。我想做一个类似于下面这种的操作:

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

也就是说,对于我这个可迭代对象中的每一个元素,查找所有键名以'.txt'结尾的元素,然后把它们的'name'项设置为'Woot'。

我考虑过创建一个字典的子类,并使用fnmatch模块。但是,我不太清楚实现这个目标的最佳方法是什么。

4 个回答

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'}

至于哪种方法是最好的?最好的方法就是能解决问题的方法。在你还没找到解决方案之前,不要急着去优化它!

2

最好的方法是创建一个字典的子类,并使用fnmatch模块。

  • 创建字典的子类:以面向对象的方式添加你想要的功能。
  • fnmatch模块:可以重复使用已有的功能。
5

我认为最好的办法就是不要这样做——'*'在字典中是一个完全有效的键,所以myiter['*']是有明确意义和用处的,改变这个用法肯定会引发问题。而且,如何在键不是字符串的情况下“通配”键,包括那些只用整数作为“键”(索引)的列表元素,这也是一个设计上的难题。

如果你非得这样做,我建议你通过子类化抽象基类 collections.MutableMapping来完全控制,并实现所需的方法(__len__, __iter__, __getitem__, __setitem__, __delitem__,为了更好的性能,还可以重写其他方法,比如__contains__,因为这个基类是基于其他方法实现的,但速度较慢),这些都可以基于一个包含的dict来实现。相较于其他建议,直接子类化dict会需要你重写很多方法,以避免在你重写的方法和未重写的方法中使用“包含通配符的键”时出现不一致的行为。

无论你是子类化collections.MutableMapping还是dict来创建你的Globbable类,你都必须做一个核心设计决定:当yourthing是一个Globbable时,yourthing[somekey]返回什么?

显然,当somekey是一个包含通配符的字符串时,它必须返回不同的类型,而不是其他任何东西。在后者的情况下,可以想象返回的就是那个条目的实际内容;但在前者的情况下,它不能仅仅返回另一个Globbable——否则,yourthing[somekey] = 'bah'在一般情况下会做什么呢?对于你单个的“简洁语法”示例,你希望它在yourthing的每个中设置一个somekey条目(这与宇宙中其他所有映射的行为有着巨大的语义差异;-)——但那样的话,你又怎么能在yourthing本身中设置一个条目呢?

让我们看看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及更高版本的语法,仅仅是为了字典推导——如果你使用的是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哲学中的“稀疏优于密集”这一说法,你可以至少在不需要经历我提到的各种噩梦的情况下,获得它们,以实现你理想中的“语法糖”。

撰写回答