web-dev-qa-db-ja.com

pythonフォーマット文字列未使用の名前付き引数

私が持っているとしましょう:

action = '{bond}, {james} {bond}'.format(bond='bond', james='james')

このウィル出力:

'bond, james bond' 

次にあります:

 action = '{bond}, {james} {bond}'.format(bond='bond')

これは出力されます:

KeyError: 'james'

このエラーが発生するのを防ぐための回避策はありますか。

  • keyrrorの場合:無視し、そのままにします(ただし、他の構文解析は行います)
  • フォーマット文字列を利用可能な名前付き引数と比較し、欠落している場合は追加します
45
nelsonvarela

Python 3.2+を使用している場合は、 str.format_map() を使用できます。

にとって bond, bond

>>> from collections import defaultdict
>>> '{bond}, {james} {bond}'.format_map(defaultdict(str, bond='bond'))
'bond,  bond'

にとって bond, {james} bond

>>> class SafeDict(dict):
...     def __missing__(self, key):
...         return '{' + key + '}'
...
>>> '{bond}, {james} {bond}'.format_map(SafeDict(bond='bond'))
'bond, {james} bond'

In Python 2.6/2.7

にとって bond, bond

>>> from collections import defaultdict
>>> import string
>>> string.Formatter().vformat('{bond}, {james} {bond}', (), defaultdict(str, bond='bond'))
'bond,  bond'

にとって bond, {james} bond

>>> from collections import defaultdict
>>> import string
>>>
>>> class SafeDict(dict):
...     def __missing__(self, key):
...         return '{' + key + '}'
...
>>> string.Formatter().vformat('{bond}, {james} {bond}', (), SafeDict(bond='bond'))
'bond, {james} bond'
74
falsetru

safe_substituteメソッドで テンプレート文字列 を使用できます。

from string import Template

tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute({'bond': 'bond'})
21
Martin Maillard

少しばかげているとはいえ、シンプルで読みやすいこともできます。

'{bond}, {james} {bond}'.format(bond='bond', james='{james}')

この答えには予想されるキーの知識が必要であることは知っていますが、単純な2ステップの置換(問題名を最初に、次にループ内の問題インデックスを言う)を探していて、クラス全体または読み取り不可能なコードを作成するのは必要以上に複雑でした。

8

falsetru's answervformat()でデフォルト辞書を巧妙に使用しており、 dawg's answer はおそらくPythonのドキュメントとよりインラインですが、どちらも処理しませんフィールド名(たとえば、明示的な変換(_!r_)またはフォーマット仕様(_:+10g_)。

たとえば、falsetruのSafeDictを使用します。

_>>> string.Formatter().vformat('{one} {one:x} {one:10f} {two!r} {two[0]}', (), SafeDict(one=215, two=['James', 'Bond']))
"215 d7 215.000000 ['James', 'Bond'] James"
>>> string.Formatter().vformat('{one} {one:x} {one:10f} {two!r} {two[0]}', (), SafeDict(one=215))
"215 d7 215.000000 '{two}' {"
_

そして、dawgのMyFormatterを使用します。

_>>> MyFormatter().format('{one} {one:x} {one:10f} {two!r} {two[0]}', one=215, two=['James', 'Bond'])
"215 d7 215.000000 ['James', 'Bond'] James"
>>> MyFormatter().format('{one} {one:x} {one:10f} {two!r} {two[0]}', one=215)
"215 d7 215.000000 '{two}' {"
_

後者の場合、値のルックアップ(get_value()内)がフォーマット仕様をすでに削除しているため、どちらもうまくいきません。代わりに、vformat()またはparse()を再定義して、これらの仕様を利用できるようにします。以下の私のソリューションはvformat()を再定義することでこれを行い、キー検索を実行し、キーが見つからない場合は、二重中括弧でフォーマット文字列をエスケープし(例_{{two!r}}_)、その後通常のvformat()

_class SafeFormatter(string.Formatter):
    def vformat(self, format_string, args, kwargs):
        args_len = len(args)  # for checking IndexError
        tokens = []
        for (lit, name, spec, conv) in self.parse(format_string):
            # re-escape braces that parse() unescaped
            lit = lit.replace('{', '{{').replace('}', '}}')
            # only lit is non-None at the end of the string
            if name is None:
                tokens.append(lit)
            else:
                # but conv and spec are None if unused
                conv = '!' + conv if conv else ''
                spec = ':' + spec if spec else ''
                # name includes indexing ([blah]) and attributes (.blah)
                # so get just the first part
                fp = name.split('[')[0].split('.')[0]
                # treat as normal if fp is empty (an implicit
                # positional arg), a digit (an explicit positional
                # arg) or if it is in kwargs
                if not fp or fp.isdigit() or fp in kwargs:
                    tokens.extend([lit, '{', name, conv, spec, '}'])
                # otherwise escape the braces
                else:
                    tokens.extend([lit, '{{', name, conv, spec, '}}'])
        format_string = ''.join(tokens)  # put the string back together
        # finally call the default formatter
        return string.Formatter.vformat(self, format_string, args, kwargs)
_

これが実際の動作です:

_>>> SafeFormatter().format('{one} {one:x} {one:10f} {two!r} {two[0]}', one=215, two=['James', 'Bond'])
"215 d7 215.000000 ['James', 'Bond'] James"
>>> SafeFormatter().format('{one} {one:x} {one:10f} {two!r} {two[0]}', one=215)
'215 d7 215.000000 {two!r} {two[0]}'
>>> SafeFormatter().format('{one} {one:x} {one:10f} {two!r} {two[0]}')
'{one} {one:x} {one:10f} {two!r} {two[0]}'
>>> SafeFormatter().format('{one} {one:x} {one:10f} {two!r} {two[0]}', two=['James', 'Bond'])
"{one} {one:x} {one:10f} ['James', 'Bond'] James"
_

この解決策は少しハックすぎます(多分parse()を再定義するとクラッジが少なくなります)が、より多くのフォーマット文字列で機能するはずです。

6
goodmami

書式文字列を部分的に埋める必要があることは、書式文字列を段階的に埋めるときによくある問題です。 SQLクエリ用。

format_partial()メソッドは、Formatterおよびstringastを使用してフォーマット文字列を解析し、名前付きパラメーターハッシュに必要なすべての値があるかどうかを調べます。形式を部分的に評価します。

_import ast
from collections import defaultdict
from itertools import chain, ifilter, imap
from operator import itemgetter
import re
from string import Formatter

def format_partial(fstr, **kwargs):
    def can_resolve(expr, **kwargs):
        walk = chain.from_iterable(imap(ast.iter_fields, ast.walk(ast.parse(expr))))
        return all(v in kwargs for k,v in ifilter(lambda (k,v): k=='id', walk))

    ostr = fstr
    fmtr = Formatter()
    dd = defaultdict(int)
    fmtr.get_field = lambda field_name, args, kwargs: (dd[field_name],field_name)
    fmtr.check_unused_args = lambda used_args, args, kwargs: all(v in dd for v in used_args)
    for t in ifilter(itemgetter(1), Formatter().parse(fstr)):
        f = '{'+t[1]+(':'+t[2] if t[2] else '')+'}'
        dd = defaultdict(int)
        fmtr.format(f,**kwargs)
        if all(can_resolve(e,**kwargs) for e in dd):
            ostr = re.sub(re.escape(f),Formatter().format(f, **kwargs),ostr,count=1)
    return ostr
_

_format_partial_は、フォーマット文字列の未解決部分を残すため、後続の呼び出しを使用して、データが利用可能なときにこれらの部分を解決できます。

goodmamiとdawgの答えはすっきりしているように見えますが、どちらも_{x:>{x}}_;のようにフォーマットのミニ言語を完全にキャプチャできません。 _format_partial_は、string.format()が解決するフォーマット文字列を解決しても問題ありません。

_from datetime import date
format_partial('{x} {} {y[1]:x} {x:>{x}} {z.year}', **{'x':30, 'y':[1,2], 'z':date.today()})

'30 {} 2                             30 2016'
_

古いスタイル形式の部分文字列は規則的であったため(ネストされたマーカーがないため)、文字列フォーマッターの代わりに正規表現を使用して、機能を古いスタイル形式の文字列に拡張するのはさらに簡単です。

2
topkara

他のいくつかの回答に基づいて、ソリューションを拡張しました。これは、フォーマット仕様"{a:<10}"

Seleniumロギングの一部の文字列により、vformat(およびformat_map)が再帰制限に達することがわかりました。また、空の中括弧が存在する文字列も処理できるようにしたかったのです。

def partialformat(s: str, recursionlimit: int = 10, **kwargs):
    """
    vformat does the acutal work of formatting strings. _vformat is the 
    internal call to vformat and has the ability to alter the recursion 
    limit of how many embedded curly braces to handle. But for some reason 
    vformat does not.  vformat also sets the limit to 2!   

    The 2nd argument of _vformat 'args' allows us to pass in a string which 
    contains an empty curly brace set and ignore them.
    """

    class FormatPlaceholder:
        def __init__(self, key):
            self.key = key

        def __format__(self, spec):
            result = self.key
            if spec:
                result += ":" + spec
            return "{" + result + "}"

    class FormatDict(dict):
        def __missing__(self, key):
            return FormatPlaceholder(key)

    class PartialFormatter(string.Formatter):
        def get_field(self, field_name, args, kwargs):
            try:
                obj, first = super(PartialFormatter, self).get_field(field_name, args, kwargs)
            except (IndexError, KeyError, AttributeError):
                first, rest = formatter_field_name_split(field_name)
                obj = '{' + field_name + '}'

                # loop through the rest of the field_name, doing
                #  getattr or getitem as needed
                for is_attr, i in rest:
                    if is_attr:
                        try:
                            obj = getattr(obj, i)
                        except AttributeError as exc:
                            pass
                    else:
                        obj = obj[i]

            return obj, first

    fmttr = string.Formatter()
    fs, _ = fmttr._vformat(s, ("{}",), FormatDict(**kwargs), set(), recursionlimit)
    return fs

class ColorObj(object):
    blue = "^BLUE^"
s = '{"a": {"b": {"c": {"d" : {} {foo:<12} & {foo!r} {arg} {color.blue:<10} {color.pink} {blah.atr} }}}}'
print(partialformat(s, foo="Fooolery", arg="ARRrrrrrg!", color=ColorObj))

出力:

{"a": {"b": {"c": {"d" : {} Fooolery             & 'Fooolery' Fooolery ARRrrrrrg! ^BLUE^ {color.pink} {blah.atr} }}}}
1
Marcel Wilson

Python 3、承認された答えを取得するため、これはニースでタイトなPythonic実装です。

def safeformat(str, **kwargs):
    class SafeDict(dict):
        def __missing__(self, key):
            return '{' + key + '}'
    replacements = SafeDict(**kwargs)
    return str.format_map(replacements)

# In [1]: safeformat("a: {a}, b: {b}, c: {c}", a="A", c="C", d="D")
# Out[1]: 'a: A, b: {b}, c: C'
0
mattmc3