简介
glob 库提供了以下两个函数:
glob
输入 文件路径模式字符串(仅支持 Shell 通配符) 输出 和模式字符串匹配的真实文件名列表 iglob
输入 文件路径模式字符串(仅支持 Shell 通配符) 输出 和模式字符串匹配的真实文件名列表迭代器
代码解析
glob 库中最重要函数就是 iglob 函数了。 glob 函数其实也是调用 iglob 来实现的。
1 2 | def glob(pathname):
return list(iglob(pathname))
|
那么接下就主讲 iglob 这个函数。
首先我们分析一下传入的文件路径模式字符串有几种情况:
文件路径模式字符串中没有 Shell 通配符
文件路径模式字符串中有 Shell 通配符
这种情况还要再细分:
- dirname 没有 Shell 通配符,basename 有 Shell 通配符
- dirname 有 Shell 通配符,basename 没有 Shell 通配符
- dirname 有 Shell 通配符,basename 有 Shell 通配符
经过以上分析,我们为了函数的参数统一,可以将上面的几种情况,归纳如下:
- dirname 没有 Shell 通配符,basename 有 Shell 通配符
- dirname 有 Shell 通配符,basename 没有 Shell 通配符
- dirname 有 Shell 通配符,basename 有 Shell 通配符
- dirname 没有 Shell 通配符,basename 没有 Shell 通配符
根据以上的分析,我们就可以得出需要以下几个辅助函数:
has_magic 判断一个字符串是否有 Shell 通配符 glob0 处理 dirname 没有通配符,basename 也没有通配符的情况 glob1 处理 dirname 没有通配符,basename 有通配符的情况 glob2 处理 dirname 有通配符,basename 没有通配符的情况 glob3 处理 dirname 有通配符,basename 有通配符的情况
但是我们看一下 iglob 函数的源码,它只用到了 has_magic 、glob0 、 glob1 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | def iglob(pathname):
# 如果整个文件路径模式字符串中没有 Shell 通配符
# 直接判断该路径是否存在即可
if not has_magic(pathname):
if os.path.lexists(pathname):
yield pathname
return
# 如果有的话,就要按上面分析的四种情况考虑了
dirname, basename = os.path.split(pathname)
# 这里针对目录名没有的情况做了一下特殊处理
if not dirname:
for name in glob1(os.curdir, basename):
yield name
return
# 这里为什么要先判断 dirname != pathname?
# 源代码注释中说得很明白。
# pathname 为一个驱动器路径(就是 C 盘,D 盘 ...)
# 或者为一个 UNC 路径(自行百度 /Google)。那么 ,
# os.path.split 函数就会将之当作 dirname 返回。
# 这样会导致下面的代码无限递归,直到 Python 异常。
# `os.path.split()` returns the argument itself as a dirname if it is a
# drive or UNC path. Prevent an infinite recursion if a drive or UNC path
# contains magic characters (i.e. r'\\?\C:').
if dirname != pathname and has_magic(dirname):
dirs = iglob(dirname)
else:
dirs = [dirname]
if has_magic(basename):
glob_in_dir = glob1
else:
glob_in_dir = glob0
# 这里将 dirname 中有通配符的情况,
# 转换成没有通配符的情况
# 然后对每个 dirname 使用 glob1/glob0 进行处理
for dirname in dirs:
for name in glob_in_dir(dirname, basename):
yield os.path.join(dirname, name)
|
代码吐槽
还是按照我上面的解析思路来,添加两个辅助函数 glob2 和 glob3 ,这样 iglob 函数的逻辑就比较清楚了,完全按照我上面的解析思路来的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | def iglob(pathname):
dirname, basename = os.path.split(pathname)
if dirname != pathname and has_magic(dirname):
glob_func = glob3 if has_magic(basename) else glob2
else:
glob_func = glob1 if has_magic(basename) else glob0
for name in glob_func(dirname, basename):
yield name
def glob1(dirname, pattern):
if not dirname:
dirname = os.curdir
if isinstance(pattern, _unicode) and not isinstance(dirname, unicode):
dirname = unicode(dirname, sys.getfilesystemencoding() or
sys.getdefaultencoding())
try:
names = os.listdir(dirname)
except os.error:
return []
# . 号开头的文件在 unix 平台上为隐藏文件,
# 但是在正则表达式中它又可以匹配任意字符,
# 所以需要先单独过滤一下
if pattern[0] != '.':
names = filter(lambda x: x[0] != '.', names)
return fnmatch.filter(names, pattern)
def glob0(dirname, basename):
if basename == '':
# `os.path.split()` returns an empty basename for paths ending with a
# directory separator. 'q*x/' should match only directories.
if os.path.isdir(dirname):
return [basename]
else:
if os.path.lexists(os.path.join(dirname, basename)):
return [basename]
return []
def glob2(dirpattern, basename):
return [
os.path.join(dirname, name)
for dirname in iglob(dirpattern)
for name in glob0(dirname, basename)
]
def glob3(dirpattern, basepattern):
return [
os.path.join(dirname, name)
for dirname in iglob(dirpattern)
for name in glob1(dirname, basepattern)
]
|
总结
即然用到递归了,就要用得干净利落点。
comments powered by Disqus