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



编辑:

我创建了一个模块来提供此功能。它可能不是那么好,但可以在这里获得。

原始问题

我需要能够解析格式字符串(由字符串格式规范迷你语言指定)。我正在从事的一个项目大量使用parse模块来"取消格式化"字符串。该模块允许创建自定义格式代码/公式。我的目的是以与现有字符串格式规范迷你语言一致的方式自动解析某些类型的格式字符串。

澄清一下:通过"格式化字符串",我指的是使用format函数和formatstr对象方法时使用的字符串,例如:

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

我看了一下 cpython 字符串模块,在我看来,#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__()方法解析。例如,对于字符串对象,该方法在 C 中作为unicode__format__函数实现。

许多格式在对象类型之间共享,处理它的代码也是如此。formatter_unicode.c文件处理大多数格式字符串分析。在此文件中,parse_internal_render_format_spec()函数执行大部分分析。

不幸的是,此函数不会向 Python 代码公开。此外,它被声明为static,因此您也无法从外部访问它(例如,通过ctypes包装器)。您唯一的选择是重新实现它,或者使用从函数中删除的static关键字重新编译 Python 源代码,然后通过共享库访问它。

对于遇到这个问题需要这样做的人,这里有一个我想出的正则表达式来匹配我所谓的格式字符串(这个 PyCon 2017 演讲对于我能够如此迅速地提出这个非常宝贵!

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

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

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

编辑:

我几乎明白了。诀窍是将组标记添加到正则表达式(即括号),以便您以后可以访问它们。这似乎效果很好:

r=r'([sS]?[<>=^])?([+- ])?([#])?([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'(([sS])?([<>=^]))?([+- ])?([#])?([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'(([sS])?([<>=^]))?([+- ])?([#])?([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'

最新更新