私が持っているとしましょう:
action = '{bond}, {james} {bond}'.format(bond='bond', james='james')
このウィル出力:
'bond, james bond'
次にあります:
action = '{bond}, {james} {bond}'.format(bond='bond')
これは出力されます:
KeyError: 'james'
このエラーが発生するのを防ぐための回避策はありますか。
にとって 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'
にとって 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'
safe_substitute
メソッドで テンプレート文字列 を使用できます。
from string import Template
tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute({'bond': 'bond'})
少しばかげているとはいえ、シンプルで読みやすいこともできます。
'{bond}, {james} {bond}'.format(bond='bond', james='{james}')
この答えには予想されるキーの知識が必要であることは知っていますが、単純な2ステップの置換(問題名を最初に、次にループ内の問題インデックスを言う)を探していて、クラス全体または読み取り不可能なコードを作成するのは必要以上に複雑でした。
falsetru's answer はvformat()
でデフォルト辞書を巧妙に使用しており、 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()
を再定義するとクラッジが少なくなります)が、より多くのフォーマット文字列で機能するはずです。
書式文字列を部分的に埋める必要があることは、書式文字列を段階的に埋めるときによくある問題です。 SQLクエリ用。
format_partial()
メソッドは、Formatter
およびstring
のast
を使用してフォーマット文字列を解析し、名前付きパラメーターハッシュに必要なすべての値があるかどうかを調べます。形式を部分的に評価します。
_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'
_
古いスタイル形式の部分文字列は規則的であったため(ネストされたマーカーがないため)、文字列フォーマッターの代わりに正規表現を使用して、機能を古いスタイル形式の文字列に拡張するのはさらに簡単です。
他のいくつかの回答に基づいて、ソリューションを拡張しました。これは、フォーマット仕様"{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} }}}}
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'