Disons que j'ai:
action = '{bond}, {james} {bond}'.format(bond='bond', james='james')
cette sortie wil:
'bond, james bond'
Ensuite nous avons:
action = '{bond}, {james} {bond}'.format(bond='bond')
ceci produira:
KeyError: 'james'
Existe-t-il une solution permettant d'éviter cette erreur, par exemple:
Pour bond, bond
:
>>> from collections import defaultdict
>>> '{bond}, {james} {bond}'.format_map(defaultdict(str, bond='bond'))
'bond, bond'
Pour bond, {james} bond
:
>>> class SafeDict(dict):
... def __missing__(self, key):
... return '{' + key + '}'
...
>>> '{bond}, {james} {bond}'.format_map(SafeDict(bond='bond'))
'bond, {james} bond'
Pour bond, bond
:
>>> from collections import defaultdict
>>> import string
>>> string.Formatter().vformat('{bond}, {james} {bond}', (), defaultdict(str, bond='bond'))
'bond, bond'
Pour 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'
Vous pouvez utiliser un template string avec la méthode safe_substitute
.
from string import Template
tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute({'bond': 'bond'})
Vous pouvez suivre la recommandation dans PEP 3101 et la sous-classe Formatter:
from __future__ import print_function
import string
class MyFormatter(string.Formatter):
def __init__(self, default='{{{0}}}'):
self.default=default
def get_value(self, key, args, kwds):
if isinstance(key, str):
return kwds.get(key, self.default.format(key))
else:
return string.Formatter.get_value(key, args, kwds)
Maintenant l'essayer:
>>> fmt=MyFormatter()
>>> fmt.format("{bond}, {james} {bond}", bond='bond', james='james')
'bond, james bond'
>>> fmt.format("{bond}, {james} {bond}", bond='bond')
'bond, {james} bond'
Vous pouvez modifier la façon dont les erreurs de clé sont signalées en modifiant le texte dans self.default
par ce que vous souhaitez afficher pour KeyErrors:
>>> fmt=MyFormatter('">>{{{0}}} KeyError<<"')
>>> fmt.format("{bond}, {james} {bond}", bond='bond', james='james')
'bond, james bond'
>>> fmt.format("{bond}, {james} {bond}", bond='bond')
'bond, ">>{james} KeyError<<" bond'
Le code fonctionne sans changement sous Python 2.6, 2.7 et 3.0+.
On peut aussi faire le simple et lisible, quoique un peu idiot:
'{bond}, {james} {bond}'.format(bond='bond', james='{james}')
Je sais que cette réponse nécessite la connaissance des clés attendues, Mais je recherchais une substitution simple en deux étapes (disons d'abord le nom du problème, puis l'index du problème dans une boucle) et la création d'une classe entière ou d'un code illisible était plus complexe que nécessaire.
La réponse de falsetru utilise intelligemment un dictionnaire par défaut avec vformat()
et la réponse de dawg est peut-être plus en ligne avec la documentation de Python, mais ne gère pas les noms de champs composés (par exemple, avec conversion explicite (!r
) ou spécifications de format (:+10g
).
Par exemple, en utilisant SafeDict de falsetru:
>>> 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}' {"
Et en utilisant MyFormatter de dawg:
>>> 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}' {"
Aucune ne fonctionne bien dans le second cas car la recherche de valeur (dans get_value()
) a déjà supprimé les spécifications de formatage. Au lieu de cela, vous pouvez redéfinir vformat()
ou parse()
pour que ces spécifications soient disponibles. Pour ce faire, ma solution ci-dessous redéfinit vformat()
de sorte qu'elle effectue la recherche de clé et, si la clé est manquante, échappe à la chaîne de format avec des doubles accolades (par exemple {{two!r}}
), puis effectue la procédure normale 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)
Voici en action:
>>> 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"
Cette solution est un peu trop compliquée (redéfinir parse()
aurait peut-être moins de kludges), mais devrait fonctionner pour davantage de chaînes de formatage.
Voici une autre façon de le faire en utilisant python27:
action = '{bond}, {james} {bond}'
d = dict((x[1], '') for x in action._formatter_parser())
# Now we have: `d = {'james': '', 'bond': ''}`.
d.update(bond='bond')
print action.format(**d) # bond, bond
Le besoin de remplir partiellement les chaînes de format est un problème courant lors du remplissage progressif des chaînes de format, par ex. pour les requêtes SQL.
La méthode format_partial()
utilise les variables Formatter
de string
et ast
pour analyser la chaîne de formatage et déterminer si le paramètre nommé hash contient toutes les valeurs nécessaires pour évaluer partiellement le format:
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
laissera la partie non résolue de la chaîne de format, de sorte que les appels suivants puissent être utilisés pour résoudre ces parties lorsque les données sont disponibles.
les réponses de goodmami et de dawg semblent plus claires, mais elles ne parviennent pas toutes deux à capturer le format mini-langage complètement comme dans {x:>{x}}
; format_partial
n'aura aucun problème à résoudre les chaînes de format que string.format()
résoudra:
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'
Il est encore plus facile d'étendre la fonctionnalité aux chaînes de format de style ancien en utilisant regex au lieu du formateur de chaîne, car les sous-chaînes de format de style étaient régulières (c'est-à-dire sans marqueurs imbriqués).
Pour Python 3, en prenant la réponse approuvée, il s'agit d'une implémentation de Nice, étroite, 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'
Sur la base de certaines des autres réponses, j’ai développé les solutions .Cela traitera les chaînes avec la spécification de formatage "{a:<10}"
.
J'ai constaté que certaines chaînes de la journalisation Selenium provoquaient vformat (et format_map) d'atteindre une limite de récursivité. Je voulais aussi m'assurer de pouvoir gérer les chaînes où des accolades vides existent également.
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))
sortie:
{"a": {"b": {"c": {"d" : {} Fooolery & 'Fooolery' Fooolery ARRrrrrrg! ^BLUE^ {color.pink} {blah.atr} }}}}