Python format chaîne non utilisée arguments nommés
disons que j'ai:
action = '{bond}, {james} {bond}'.format(bond='bond', james='james')
ce sera de sortie:
'bond, james bond'
nous avons:
action = '{bond}, {james} {bond}'.format(bond='bond')
sortie:
KeyError: 'james'
y a-t-il une solution pour empêcher cette erreur de se produire, quelque chose comme:
- si keyrror: ignorer, de le laisser seul (mais il ne faut analyser d'autres)
- comparez la chaîne de format avec les arguments nommés disponibles, si elle est manquante, ajoutez
9 réponses
si vous utilisez Python 3.2+, utilisez 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'
En 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'
vous pourriez utiliser un modèle de chaîne de caractèressafe_substitute
méthode.
from string import Template
tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute({'bond': 'bond'})
Vous pouvez suivre la recommandation PEP 3101 et le format de la sous-classe:
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:
Formatter.get_value(key, args, kwds)
Maintenant, essaie:
>>> 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 changer la façon dont les erreurs clés sont signalées en changeant le texte dans self.default
pour ce que vous voulez 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 sur Python 2.6, 2.7, et 3.0+
on peut aussi faire le simple et lisible, bien que quelque peu stupide:
'{bond}, {james} {bond}'.format(bond='bond', james='{james}')
je sais que cette réponse nécessite la connaissance de l'clés, mais je cherchais une simple substitution en deux étapes (par exemple le nom du problème d'abord, 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.
réponse de falsetru utilise intelligemment un dictionnaire défaillant avec vformat()
et dawg de réponse est peut-être plus en ligne avec la documentation de Python, mais aucun des deux ne gère les noms de champs composés (par exemple, avec une conversion explicite (!r
) ou spécifications de format (:+10g
).
Par exemple, à l'aide de falsetru de 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}' {"
Et en utilisant dawg de 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}' {"
Ni bien travailler dans le second cas, parce que la valeur de recherche (dans get_value()
) a déjà dépouillé les spécifications de formatage. Au lieu de cela, vous pouvez redéfinir vformat()
ou parse()
ces spécifications sont donc disponibles. Ma solution ci-dessous n'est redéfini vformat()
ainsi il exécute la recherche de la clé et, si la clé est manquante, échappe à la chaîne de format avec des accolades doubles (par exemple {{two!r}}
) puis exécute la 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 hacky (peut-être redéfinir parse()
aurait moins de kludges), mais devrait fonctionner pour plus 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 exemple pour les requêtes SQL.
format_partial()
la méthode utilise le Formatter
string
et ast
pour analyser la chaîne de format et aussi découvrir si le paramètre nommé hash a 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 subséquents peuvent être utilisés pour résoudre ces parties lorsque les données sont disponibles.
les réponses de goodmami et dawg semblent plus propres, mais elles ne parviennent pas toutes les deux à capturer le format mini-langage complètement comme dans {x:>{x}}
;format_partial
n'auront aucun problème à résoudre n'importe quel format chaîne string.format()
décide:
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 formatteur de chaîne, car les substrats de format de style ancien étaient réguliers (c'est-à-dire réguliers). pas de marqueurs imbriqués).
Pour Python 3, en prenant le approuvés réponse, c'est une bonne, serrée, Pythonic mise en œuvre:
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'
basé sur certaines des autres réponses, j'ai élargi les solutions.
Cela permettra de gérer les chaînes avec des spécifications de formatage "{a:<10}"
.
j'ai trouvé que certaines chaînes de logging selenium causaient vformat (et format_map) à frapper une limite de récursion. Je voulais aussi m'assurer de pouvoir manier des cordes là où il y a des lacets vides.
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} }}}}