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

时间:2017-06-14 17:53:12

标签: python string-formatting cpython

编辑:

我已经创建了一个模块(作为我的pypi parmatter包的一部分)来提供此功能。它还没有被推到包中,但可以获得here

原始问题

我需要能够解析格式字符串(由string format specification mini language指定)。我正在开展的一个项目大量使用parse模块来进行"取消格式化"字符串。该模块允许创建自定义格式代码/公式。我的意图是以与现有字符串格式规范迷你语言稍微一致的方式自动解析某些格式字符串。

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

'{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)

2 个答案:

答案 0 :(得分:3)

格式规范特定于每个对象;它由对象的__format__() method解析。例如,对于字符串对象,该方法在C中实现为unicode__format__ function

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

不幸的是,这个函数没有暴露给Python代码。此外,它被声明为static,因此您无法在外部访问它(例如,通过ctypes wrapper)。您唯一的选择是重新实现它,或者从函数中删除static关键字重新编译Python源代码,然后通过共享库访问它。

答案 1 :(得分:0)

对于其他任何遇到此问题的人来说,这是一个正则表达式,我提出来匹配我调用格式字符串的内容(这个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'