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
40
demandé sur dreftymac 2013-06-20 17:50:18

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'
61
répondu falsetru 2013-06-20 14:20:39

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'})
18
répondu Martin Maillard 2013-06-20 13:59:16

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+

9
répondu dawg 2015-11-10 01:59:14

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.

7
répondu Ioannis Filippidis 2016-02-09 19:36:54

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.

5
répondu goodmami 2017-05-23 11:47:18

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
2
répondu feqwix 2016-06-01 19:50:52

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

1
répondu topkara 2016-02-27 18:34:16

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'
0
répondu mattmc3 2017-04-20 17:58:52

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} }}}}
0
répondu Marcel Wilson 2018-07-20 18:52:15