web-dev-qa-db-fra.com

format de format python arguments nommés non utilisés

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:

  • si keyrror: ignore, laissez-le tranquille (mais analysez les autres)
  • compare la chaîne de format avec les arguments nommés disponibles, s'il manque, ajoutez
41
nelsonvarela

Si vous utilisez Python 3.2+, use peut utiliser str.format_map () .

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'

En Python 2.6/2.7

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'
64
falsetru

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'})
19
Martin Maillard

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+.

9
dawg

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.

7
Ioannis Filippidis

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.

6
goodmami

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
feqwix

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

1
topkara

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'
0
mattmc3

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} }}}}
0
Marcel Wilson