Help us understand the problem. What is going on with this article?

【Python2/3】書式文字列をパースする

More than 1 year has passed since last update.

Cのsprintfに相当する機能が、Pythonには今のところ3種類もあります。(f-stringは書式文字列を使わないので除外)

名称? 使い方
%-formatting fmt % values
str.format() fmt.format(*values) / fmt.format(**values)
Template strings string.Template(fmt).substitute(**values)

この記事では、上の表の values部分に要求されるタプルの要素数、または辞書のキーを調べる方法をまとめます。
正直、かなり使用頻度の低い知識だと思いますが、logging.Formatterを継承して自作のFormatterを作るときなどではもしかしたら役に立つかもしれません。

概要

  • %-formatting → formatを失敗させて例外を見る。
  • str.formatstring.Formatter().parser(fmt)
  • Template string → string.Template.pattern.finditer(fmt)

%-formatting (printf style formatting)

他の2つとは違ってパースするための専用機能が標準パッケージに見当たらないので自分で頑張ります。
今回は投げ返される例外をもとに解読する関数を作ってみました。fmt % valuesvalues の部分に要求されている型が、タプルならその長さを、辞書ならそのキーを返します。

def parse_printf_style_format(fmt):
    if not isinstance(fmt, (bytes, str)):
        raise TypeError('got ' + type(fmt).__name__)

    try:
        fmt % ()
    except TypeError as e:
        if e.args[0] == 'not enough arguments for format string':
            values = ()
        elif e.args[0] == 'format requires a mapping':
            values = {}
        else:
            raise
    else:
        return None

    if isinstance(values, tuple):
        while True:
            try:
                fmt % values
            except TypeError as e:
                if e.args[0] == 'not enough arguments for format string':
                    values += (0,)
                else:
                    raise ValueError('invalid format: ' + repr(fmt))
            else:
                return len(values)
    elif isinstance(values, dict):
        while True:
            try:
                fmt % values
            except TypeError as e:
                if e.args[0] == 'not enough arguments for format string':
                    raise ValueError('invalid format: ' + repr(fmt))
                else:
                    raise
            except KeyError as e:
                values[e.args[0]] = 0
            else:
                return tuple(values.keys())
    else:
        assert False

使用例

>>> parse_printf_style_format('%d %s %x')
3
>>> parse_printf_style_format('%(foo)s, %(bar)d')
('foo', 'bar')

str.format()

string.Formatter().parse(fmt) で一撃です。詳しくは公式ドキュメントを参照してください。

>>> import string
>>> fmt = '{foo:s}, {{bar:d}}, and {:f}'
>>> list(string.Formatter().parse(fmt))
[('', 'foo', 's', None),
 (', {', None, None, None),
 ('bar:d}', None, None, None),
 (', and ', '', 'f', None)]

Template strings

string.Template.pattern.finditer(fmt) で一撃です。
プレースホルダにマッチする正規表現がpattern属性に格納されています。

>>> import string
>>> fmt = '${this_is_braced} $$this_is_escaped $@this_is_invalid $this_is_named'
>>> print(string.Template.pattern.pattern)
    \$(?:
      (?P<escaped>\$) |   # Escape sequence of two delimiters
      (?P<named>[_a-z][_a-z0-9]*)      |   # delimiter and a Python identifier
      {(?P<braced>[_a-z][_a-z0-9]*)}   |   # delimiter and a braced identifier
      (?P<invalid>)              # Other ill-formed delimiter exprs
    )
>>> [match.groupdict() for match in string.Template.pattern.finditer(fmt)]
[{'braced': 'this_is_braced', 'escaped': None, 'invalid': None, 'named': None},
 {'braced': None, 'escaped': '$', 'invalid': None, 'named': None},
 {'braced': None, 'escaped': None, 'invalid': '', 'named': None},
 {'braced': None, 'escaped': None, 'invalid': None, 'named': 'this_is_named'}]

所感

%-fomattingの冷遇され方がひどい。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away