January Star
  • Home
  • Categories
  • Tags
  • Archives

glob标准库源码学习

Contents

  • 简介
  • 代码解析
  • 代码吐槽
  • 总结

简介

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
comments powered by Disqus

Published

Aug 28, 2014

Category

Python

Tags

  • python 23
  • stdlibs 15

Contact

  • Powered by Pelican. Theme: Elegant by Talha Mansoor