cadena de formato de Python argumentos con nombre no utilizados

Digamos que tengo:

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

esta salida será:

 'bond, james bond' 

A continuación tenemos:

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

esto dará como resultado:

 KeyError: 'james' 

¿Hay alguna solución para evitar que ocurra este error, algo como:

  • si keyrror: ignora, déjalo en paz (pero analiza a otros)
  • compare la cadena de formato con los argumentos con nombre disponibles, si falta, agregue

Si está usando Python 3.2+, use puede usar str.format_map () .

Para bond, bond

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

Para 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

Para bond, bond

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

Para 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' 

Podría usar una cadena de plantilla con el método safe_substitute .

 from string import Template tpl = Template('$bond, $james $bond') action = tpl.safe_substitute({'bond': 'bond'}) 

Puedes seguir la recomendación en PEP 3101 y subclase 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) 

Ahora inténtalo:

 >>> fmt=MyFormatter() >>> fmt.format("{bond}, {james} {bond}", bond='bond', james='james') 'bond, james bond' >>> fmt.format("{bond}, {james} {bond}", bond='bond') 'bond, {james} bond' 

Puede cambiar cómo se marcan los errores clave cambiando el texto en self.default a lo que le gustaría mostrar para 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' 

El código funciona sin cambios en Python 2.6, 2.7 y 3.0+

También se puede hacer lo simple y lo legible, aunque algo tonto:

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

Sé que esta respuesta requiere el conocimiento de las claves esperadas, pero estaba buscando una sustitución simple de dos pasos (primero el nombre del problema, luego el índice del problema dentro de un bucle) y crear una clase completa o un código ilegible era más complejo de lo necesario.

La respuesta de falsetru tiene un uso inteligente de un diccionario vformat() con vformat() , y la respuesta de dawg es quizás más en línea con la documentación de Python, pero ninguno maneja nombres de campos compuestos (por ejemplo, con conversión explícita ( !r ) o especificaciones de formato ( :+10g ).

Por ejemplo, utilizando 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}' {" 

Y usando 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}' {" 

Ninguno funciona bien en el segundo caso porque el valor de búsqueda (en get_value() ) ya ha eliminado las especificaciones de formato. En su lugar, puede redefinir vformat() o parse() para que estas especificaciones estén disponibles. Mi solución a continuación hace esto al redefinir vformat() para que realice la búsqueda de claves y, si falta la clave, escapa la cadena de formato con llaves dobles (por ejemplo, {{two!r}} ) y luego ejecuta el vformat() normal 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) 

Aquí está en acción:

 >>> 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" 

Esta solución es un poco demasiado intrincada (tal vez la redefinición de parse() tendría menos kludges), pero debería funcionar para más cadenas de formato.

Aquí hay otra forma de hacerlo usando 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 

La necesidad de completar parcialmente las cadenas de formato es un problema común cuando se rellenan progresivamente las cadenas de formato, por ejemplo, para consultas SQL.

format_partial() método format_partial() usa el Formatter desde la string y el ast para analizar la cadena del formato y también para averiguar si el hash del parámetro nombrado tiene todos los valores necesarios para evaluar parcialmente el formato:

 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 dejará la parte no resuelta de la cadena de formato, por lo que las llamadas subsiguientes se pueden usar para resolver esas partes cuando los datos estén disponibles.

Las respuestas de goodmami y dawg parecen más limpias, pero ambas fallan en capturar el formato en mini-lenguaje completamente como en {x:>{x}} ; format_partial no tendrá problemas para resolver cualquier cadena de formato que 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' 

Es incluso más fácil extender la funcionalidad a las cadenas de formato de estilo antiguo usando expresiones regulares en lugar del formateador de cadena, ya que las subcadenas de formato de estilo antiguo eran regulares (es decir, no hay marcadores nesteds).

Para Python 3, tomando la respuesta aprobada, esta es una implementación Pythonic agradable y ajustada:

 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ándome en algunas de las otras respuestas, amplié las soluciones. Esto manejará cadenas con la especificación de formato "{a:<10}" .

Descubrí que algunas cadenas del registro de selenium estaban causando que vformat (y format_map) alcanzaran un límite de recursión. También quería asegurarme de poder manejar cuerdas donde también existan llaves vacías.

 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)) 

salida:

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