访问cpython字符串格式规范迷你语言解析器

Access the cpython string format specification mini language parser

编辑:

我创建了一个模块来提供此功能。它可能不是那么好,但可以得到 here.

原问题

我需要能够解析格式字符串(由 string format specification mini language). A project I'm working on makes heavy use of the parse 模块指定,用于“取消格式化”字符串。该模块允许创建自定义格式 codes/formulas。我的目的是自动以某种与现有字符串格式规范迷你语言一致的方式解析某些类型的格式字符串。

澄清一下:“格式化字符串”是指使用 format 函数和 str 对象的 format 方法时使用的那些字符串,例如:

'{x!s: >5s}'.format('foo') # the format string is ' >5s'

我查看了 cpython string module,第 166 行在我看来好像是在说格式字符串的解析是在 _string 模块中处理的。

# The overall parser is implemented in _string.formatter_parser.

这发生在这一行 (# 278):

return _string.formatter_parser(format_string)

我对 cPython 代码库很不熟悉,也不是 C 程序员,我找不到 _string 模块。我想知道它是否在C语言级别实现...?

主要问题:格式规范解析实现是否暴露在某处以供使用?我怎样才能得到它,所以我不必自己写?我希望得到这样的输出:

>>> parse_spec(' >5.2f')
{'fill': ' ', 'align': '>', 'sign': None, '#': None, '0': None, 'width': 5, ',': None, 'precision': 2, 'type': 'f'}

编辑

请注意,评论说,尽管它的名字,_string.formatter_parser 并不符合我的要求。

# returns an iterable that contains tuples of the form:
# (literal_text, field_name, format_spec, conversion)
# literal_text can be zero length
# field_name can be None, in which case there's no
#  object to format and output
# if field_name is not None, it is looked up, formatted
#  with format_spec and conversion and then used
def parse(self, format_string):
    return _string.formatter_parser(format_string)

格式规范是针对每个对象的;它由 __format__() method of an object. For example, for string objects, that method is implemented in C as the unicode__format__ function.

解析

很多格式在对象类型之间是共享的,处理它的代码也是如此。 formatter_unicode.c file handles most format-string parsing. Within this file, the parse_internal_render_format_spec() function 完成大部分解析。

遗憾的是,此函数未公开给 Python 代码。此外,它被声明为 static,因此您也无法从外部访问它(例如,通过 ctypes wrapper)。您唯一的选择是要么重新实现它,要么重新编译您的 Python 源代码并从函数中删除 static 关键字,然后通过共享库访问它。

对于遇到这个问题需要这样做的任何其他人,这是我想出的一个正则表达式来匹配我正在调用的格式字符串(这个 PyCon 2017 talk 对我来说非常宝贵这么快就想出来了!):

r=r'([\s\S]?[<>=\^])?[\+\- ]?[#]?[0]?\d*[,]?(\.\d*)?[sbcdoxXneEfFgGn%]?'
import re
c=re.compile(r)

这应该匹配字符串格式规范迷你语言指定的任何有效字符串。我做了一些有限的测试,它似乎有效。

现在我需要了解如何解析我需要的所有数据。当我弄清楚如何做到这一点时会更新。

编辑:

我快搞定了。诀窍是将组标记添加到正则表达式(即括号),以便您稍后可以访问它们。这似乎运作良好:

r=r'([\s\S]?[<>=\^])?([\+\- ])?([#])?([0])?(\d)*([,])?(\.\d*)?([sbcdoxXneEfFgGn%])?'

from collections import namedtuple as nt
FormatSpec = nt('FormatSpec', 'fill_align sign alt zero_padding width comma precision type')

import re
spec = FormatSpec(*re.search(r,'x>5.2f').group(1,2,3,4,5,6,7,8))

这导致:

FormatSpec(fill_align='x>', sign=None, alt=None, zero_padding=None, width='5', comma=None, precision='.2', type='f')

我想弄清楚如何分别访问填充和对齐字符,以及如何去掉 precision 部分中的小数点标记,但这是一个好的开始。

编辑:

只需添加额外的括号即可创建和访问嵌套组;他们按照遇到的顺序分配了一个组号:

r=r'(([\s\S])?([<>=\^]))?([\+\- ])?([#])?([0])?(\d)*([,])?((\.)(\d)*)?([sbcdoxXneEfFgGn%])?'

from collections import namedtuple as nt
FormatSpec = nt('FormatSpec', 'fill align sign alt zero_padding width comma precision type')

import re
spec = FormatSpec(*re.search(r,'x>5.2f').group(2,3,4,5,6,7,8,11,12)) # skip groups not interested in

这就是我想要的结果:

FormatSpec(fill='x', align='>', sign=None, alt=None, zero_padding=None, width='5', comma=None, precision='2', type='f')

编辑:

FormatSpec 元组中 包含 小数字符(单独)似乎更好,因为格式规范可以直接重构:

r=r'(([\s\S])?([<>=\^]))?([\+\- ])?([#])?([0])?(\d)*([,])?((\.)(\d)*)?([sbcdoxXneEfFgGn%])?'

from collections import namedtuple as nt
FormatSpec = nt('FormatSpec', 'fill align sign alt zero_padding width comma decimal precision type')

import re
spec = FormatSpec(*re.fullmatch(r,'x>5.2f').group(2,3,4,5,6,7,8,10,11,12)) # skip groups not interested in

此外,我已更改为 r.fullmatch 方法(而不是 searchmatch),因此模式必须完全匹配。

现在我们可以这样做来重建提供的格式规范:

''.join(s for s in spec if s is not None)
# 'x>5.2f'