简介
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 语言实现的。