January Star
  • Home
  • Categories
  • Tags
  • Archives

string标准库源码学习

Contents

  • 简介
  • 常量定义
  • maketrans
  • _multimap
  • _TemplateMetaclass
  • Template
  • strop
  • Fromatter

简介

string 库里面的大部分函数其实算是弃用了。

因为从 Python1.6 开始,那些函数都已经都已经在内置的 string 对象里实现了。

所以接下我所要讲解的代码就基本不包括这些函数了,何况它们也是比较简单好懂的。

常量定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
whitespace = ' \t\n\r\v\f'
lowercase = 'abcdefghijklmnopqrstuvwxyz'
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
letters = lowercase + uppercase
ascii_lowercase = lowercase
ascii_uppercase = uppercase
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + letters + punctuation + whitespace

代码中上来就是一系列常用的常量定义,和 ctypes.h 中的定义相同,这没有好看的。知道就行,以后需要用到直接用(比如生成随机字符串啊,和 random 配合使用)。

maketrans

 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
# 这里生成一个含有所有的 ASCII 字符的字符串(0~255)
# 这里是先生成一个列表,然后再将其转换成字符串 
# 但是到下面的 `maketrans` 函数使用时,又转换成列表 
# 不知道想干嘛 
l = map(chr, xrange(256))
_idmap = str('').join(l)
del l

_idmapL = None
def maketrans(fromstr, tostr):
    """maketrans(frm, to) -> string

    生成一个转换表,主要用来给 `translate` 函数使用 
    (translate 的第一个参数就要该函数的返回值)。

    当然它还有一个要求: `frm` 和 `to` 的长度要一样。

    注意:该转换表只能转换 ASCII
    """
    if len(fromstr) != len(tostr):
        raise ValueError, "maketrans arguments must have same length"
    global _idmapL
    if not _idmapL:
        _idmapL = list(_idmap)
    L = _idmapL[:]
    fromstr = map(ord, fromstr)
    for i in range(len(fromstr)):
        # 这里为了效率,用了个小技巧 
        # L 中每个元素的值正好是自己在 L 中的索引 
        L[fromstr[i]] = tostr[i]
    return ''.join(L)

上面的代码可能是为了效率考虑,采用点技巧(L 中每个元素的值正好是自己在 L 中的索引)。

那么我们用函数式编程的概念来如何实现呢?

maketrans 的最核心功能是什么呢?

替换,将即在 L 中也在 frm 中的元素替换成 to 中对应 index 位置的元素

除了这个核心功能外,剩下的就是对整个 L 进行迭代了,很自然会想到使用 map 功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
_ASCII_COUNT = 256
_IDMAP = [chr(n) for n in xrange(_ASCII_COUNT)]

def maketrans(fromstr, tostr):
    if len(fromstr) != len(tostr):
        raise ValueError, "maketrans arguments must have same length"

    frm_to = zip(fromstr, tostr)
    # 如果字符 C 在 fromstr 中,则替换为 tostr 中对应 index 的字符 
    # 否则返回字符 C 本身 
    sub_c = lambda c: next((t for f, t in frm_to if f == c), c)
    return ''.join((sub_c(c) for c in _IDMAP))

好了,刚才说到效率,那么两者效率相差多少呢?

1
2
3
4
5
6
7
In [53]: %time string.maketrans("abc", "efg")
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 16 µs

In [66]: %time maketrans("abc", "efg")
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 660 µs

是不是比较恐怖,40+ 倍的差距啊,所以说标准库的代码为了效率优化,还是下了功夫的。

_multimap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class _multimap:
    """ 一个辅助类,它是主要给下面的 `Template` 类使用的。

    将两个字典绑定在一起。

    如果在第一个字典中没有找到特定的 Key,则在第二个字典中查找。
    """
    def __init__(self, primary, secondary):
        self._primary = primary
        self._secondary = secondary

    def __getitem__(self, key):
        try:
            return self._primary[key]
        except KeyError:
            return self._secondary[key]

上面注释中也说了是 Template 的辅助类,那么它倒底在 Template 类中做了什么呢?

1
2
3
4
5
6
7
8
9
def substitute(self, *args, **kws):
     if len(args) > 1:
         raise TypeError('Too many positional arguments')
     if not args:
         mapping = kws
     elif kws:
         mapping = _multimap(kws, args[0])
     else:
         mapping = args[0]

上面是 Template 类中使用到 _multimap 类的代码,我们可以看到,_multimap 类中传入了两个参数: kws 和 args[0] 。

看到这里就应该明白了, Template 类为了提高易用性,无论你的 substitute 方法中传入的是一个字典、一个 / 多个关键字参数还是两者的结合,它都能给你想要的结果。

Note

第一个参数是 kws ,所以你在同时传入字典和一个 / 多个关键字参数时,如果两者包含同样字段的值,关键字参数的优先级是比较高的。

小心不要被坑喽。

1
2
3
4
In [38]: t = string.Template("${name}: hello")

In [39]: t.substitute({"name": "dict"}, name="key")
Out[39]: 'key: hello'

面向对象 SOLID 原则之一的 单一职责原则 ,这个类就是它的一个很好的体现。

_TemplateMetaclass

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class _TemplateMetaclass(type):
    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
      (?P<named>%(id)s)      |   # delimiter and a Python identifier
      {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
      (?P<invalid>)              # Other ill-formed delimiter exprs
    )
    """

    def __init__(cls, name, bases, dct):
        super(_TemplateMetaclass, cls).__init__(name, bases, dct)
        if 'pattern' in dct:
            pattern = cls.pattern
        else:
            pattern = _TemplateMetaclass.pattern % {
                'delim' : _re.escape(cls.delimiter),
                'id'    : cls.idpattern,
                }
        cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)

_TemplateMetaclass 是一个元类,它提供了一个核心功能:

通过它创建出来的类,都有一个 pattern 属性

该属性能够解析类似于 delimiter{idpattern} 或者 delimiteridpattern 的字符串。

而通过它创建出来的类,只需要定制 delimiter 和 idpattern 的具体值就可拥有以上功能。

当然,通过它创建出来的类,也可以自己提供 pattern (但最终使用时,会被元类自动换成 re 对象)。

Template

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class Template:
    """ 能够解析 $ 表达式的字符串模板类 """
    __metaclass__ = _TemplateMetaclass

    delimiter = '$'
    idpattern = r'[_a-z][_a-z0-9]*'

    def __init__(self, template):
        self.template = template

    # Search for $$, $identifier, ${identifier}, and any bare $'s

    def _invalid(self, mo):
        """ 如果解析到无效的占位符,就告诉调用者在几行几列出错 """
        i = mo.start('invalid')
        lines = self.template[:i].splitlines(True)
        if not lines:
            colno = 1
            lineno = 1
        else:
            colno = i - len(''.join(lines[:-1]))
            lineno = len(lines)
        raise ValueError('Invalid placeholder in string: line %d, col %d' %
                         (lineno, colno))

    def substitute(self, *args, **kws):
        if len(args) > 1:
            raise TypeError('Too many positional arguments')
        if not args:
            # 传入一个 / 多个关键字参数 
            mapping = kws
        elif kws:
            # 传入一个字典及一个 / 多个关键字参数 
            mapping = _multimap(kws, args[0])
        else:
            # 只传入一个字典 
            mapping = args[0]
        # Helper function for .sub()
        def convert(mo):
            # 为了效率,将最有可能的情况先进行判断 
            # 最有可能出现 `$XXXX` 或者 `${XXXX}` 这两种正常的情况 
            named = mo.group('named') or mo.group('braced')
            if named is not None:
                val = mapping[named]
                # We use this idiom instead of str() because the latter will
                # fail if val is a Unicode containing non-ASCII characters.
                return '%s' % (val,)
            # 其次是 `$$` 这种情况 
            if mo.group('escaped') is not None:
                return self.delimiter
            # 发现无效的占位符 
            if mo.group('invalid') is not None:
                self._invalid(mo)
            # 其它未知情况 
            raise ValueError('Unrecognized named group in pattern',
                             self.pattern)
        return self.pattern.sub(convert, self.template)

    def safe_substitute(self, *args, **kws):
        """ 该方法的功能基本和 `substitute` 差不多,
        只不过从参数中找不到可替换的字符串情况下,
        不会抛出异常,而是原样输出而已 
        """
        if len(args) > 1:
            raise TypeError('Too many positional arguments')
        if not args:
            mapping = kws
        elif kws:
            mapping = _multimap(kws, args[0])
        else:
            mapping = args[0]
        # Helper function for .sub()
        def convert(mo):
            named = mo.group('named')
            if named is not None:
                # 这里最好不用异常的方式来判断,Python 里处理异常的代价相当大 
                # 可以使用 get 方法,或者 in 来判断。
                try:
                    # We use this idiom instead of str() because the latter
                    # will fail if val is a Unicode containing non-ASCII
                    return '%s' % (mapping[named],)
                except KeyError:
                    return self.delimiter + named
            braced = mo.group('braced')
            if braced is not None:
                # 这里的问题同上所述,最好不要用异常方式 
                try:
                    return '%s' % (mapping[braced],)
                except KeyError:
                    return self.delimiter + '{' + braced + '}'
            if mo.group('escaped') is not None:
                return self.delimiter
            if mo.group('invalid') is not None:
                return self.delimiter
            raise ValueError('Unrecognized named group in pattern',
                             self.pattern)
        return self.pattern.sub(convert, self.template)

说实在的,这里为什么要用到元类来实现这个功能,我还真想不清楚,如果有人能够知道,麻烦告诉我一下。

Template 类其实就相当于一个轻量级的模板引擎了,你可以直接用它,也做一些简单的定制。

1
2
class MyTemplate(Template):
    delimiter = '#'

比如以上的 MyTemplate 就支持 #XXXX 、 #{XXX} 这种语法了。

strop

1
2
3
4
5
try:
    from strop import maketrans, lowercase, uppercase, whitespace
    letters = lowercase + uppercase
except ImportError:
    pass

string 库还会检查当前 Python 实现中是否有 strop 库,如果有,则加载该库中的部分函数。该库为一个内置库,一些字符串操作比 string 库快 100~1000 倍。

Fromatter

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class Formatter(object):
    def format(self, format_string, *args, **kwargs):
        # Fomatter 支持类似于 {0}, {name} 的语法 
        # 所以将参数直接以元组和字典处理,
        # 从格式化字符串里取出来的 0, name 就可以直接传给 args, kwargs 调用了。
        return self.vformat(format_string, args, kwargs)

    def vformat(self, format_string, args, kwargs):
        used_args = set()
        result = self._vformat(format_string, args, kwargs, used_args, 2)
        self.check_unused_args(used_args, args, kwargs)
        return result

    def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
        if recursion_depth < 0:
            raise ValueError('Max string recursion exceeded')
        result = []
        # "literal_text{field_name!conversion:format_spec}"
        for literal_text, field_name, format_spec, conversion in \
                self.parse(format_string):

            # output the literal text
            if literal_text:
                result.append(literal_text)

            # if there's a field, output it
            if field_name is not None:
                # this is some markup, find the object and do
                #  the formatting

                # given the field_name, find the object it references
                #  and the argument it came from
                obj, arg_used = self.get_field(field_name, args, kwargs)
                used_args.add(arg_used)

                # do any conversion on the resulting object
                obj = self.convert_field(obj, conversion)

                # expand the format spec, if needed
                format_spec = self._vformat(format_spec, args, kwargs,
                                            used_args, recursion_depth-1)

                # format the object and append to the result
                result.append(self.format_field(obj, format_spec))

        return ''.join(result)


    def get_value(self, key, args, kwargs):
        if isinstance(key, (int, long)):
            return args[key]
        else:
            return kwargs[key]


    def check_unused_args(self, used_args, args, kwargs):
        pass


    def format_field(self, value, format_spec):
        return format(value, format_spec)


    def convert_field(self, value, conversion):
        # 根据指定的转换方法对传入的值进行转换 
        # 在格式化字符串中的语法为 !r, !s
        # 例如: "{0!s}"、 "{name!r}"
        if conversion is None:
            return value
        elif conversion == 's':
            return str(value)
        elif conversion == 'r':
            return repr(value)
        raise ValueError("Unknown conversion specifier {0!s}".format(conversion))


    def parse(self, format_string):
        # 调用的是 C 语言实现的格式化字符串解析器 
        return format_string._formatter_parser()


    def get_field(self, field_name, args, kwargs):
        # 将格式类似于 "{person[name]}"、 "person.name" 或 "{person[0]}" 的字段进行拆分 
        first, rest = field_name._formatter_field_name_split()

        obj = self.get_value(first, args, kwargs)

        # loop through the rest of the field_name, doing
        #  getattr or getitem as needed
        for is_attr, i in rest:
            if is_attr:
                obj = getattr(obj, i)
            else:
                obj = obj[i]

        return obj, first

提供 Formatter 类的本意和上面的 Template 类是一样的,给需要用到该功能的用户进行自定义使用的。

我们正常的 Python 使用的字符串格式化操作,并不会调用到这里面的代码,它是直接调用 Python 的内置类型 string 的方法,是由 C 语言实现的。

Comments
comments powered by Disqus

Published

Sep 1, 2014

Category

Python

Tags

  • python 23
  • stdlibs 15

Contact

  • Powered by Pelican. Theme: Elegant by Talha Mansoor