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
38
Le second est meilleur je pense. Premièrement, on peut créer un contenu étrange. Deuxièmement, on fait penser "hé, quelque chose ne va pas ici", ce qui dans ce cas est une bonne chose.
ajouté l'auteur nelsonvarela, source
ajouté l'auteur dreftymac, source
Lequel voulez-vous lien, lien / lien, {james}, lien ?
ajouté l'auteur falsetru, source
J'ai mis à jour la réponse pour les deux cas.
ajouté l'auteur falsetru, source
python et également utiliser fo "> stackoverflow.com/questions/5466451/…
ajouté l'auteur Qlimax, source

9 Réponses

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

Pour lien, lien :

>>> 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 lien, lien :

>>> 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'
60
ajouté
J'utilise 2.7 .. merci! Je vais tester ça
ajouté l'auteur nelsonvarela, source
Génial! Accepté!
ajouté l'auteur nelsonvarela, source
Curieusement, dans python3 format donne le même résultat que format_map dans cet exemple.
ajouté l'auteur frnhr, source
Le voici: '{lien}, {james} {lien}'. Format (** SafeDict (lien = 'lien')) renvoie 'lien, {james} lien' . Notez le ** . En fait, je ne l'avais pas remarqué jusqu'à maintenant :) Python 3.4.3
ajouté l'auteur frnhr, source
@frnhr, pourriez-vous montrer des exemples concrets que vous avez essayés?
ajouté l'auteur falsetru, source
@frnhr, je vois. Merci pour votre avis.
ajouté l'auteur falsetru, source
@frnhr, que voulez-vous dire? '{lien}, {james} {lien}'. format (defaultdict (str, bond = 'lien')) me donne KeyError .
ajouté l'auteur falsetru, source

Vous pouvez utiliser une chaîne de modèle avec la méthode safe_substitute .

from string import Template

tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute({'bond': 'bond'})
17
ajouté

Vous pouvez suivre la recommandation dans PEP 3101 et dans 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:
            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 manière dont les erreurs de clé sont signalées en modifiant le texte dans self.default pour qu'il soit identique à 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
ajouté
Je pense que Formatter.get_value (clé, arguments, kwds) doit être , retourne string.Formatter.get_value (self, clé, arguments, kwds) dans votre code
ajouté l'auteur Grijesh Chauhan, source
Parmi les réponses fournies ici, je pense que c'est le meilleur en termes de portabilité/élégance +1
ajouté l'auteur Ajay, source
@GrijeshChauhan Je ne suis pas sûr… La fonction est appelée de manière récursive, le seul retour réel a lieu dans le dernier, terminal, appelez donc… En tout cas, je n'ai pas réussi à le faire fonctionner. J'ai fini avec un code plus simple, qui fait ce que je veux. class URLFormatter (string.Formatter): def __init __ (self, default = '{}'): self.default = default def get_value (self, key, args, kwds): retourne kwds.get (clé, self.default.format (clé))
ajouté l'auteur Stéphane, source

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 simple substitution en deux étapes (disons le nom du problème en premier, 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.

6
ajouté

falsetru's answer has a clever use of a defaulting dictionary with vformat(), and dawg's answer is perhaps more in-line with Python's documentation, but neither handle compound field names (e.g., with explicit conversion (!r) or format specs (:+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}' {"

Cela ne fonctionne pas 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}}. ) et effectue ensuite le vformat() normal.

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 (peut-être qu'une redéfinition de parse() aurait moins de kludges), mais devrait fonctionner pour davantage de chaînes de formatage.

5
ajouté

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
ajouté
Certains peuvent hésiter à utiliser _formatter_parser , mais pour moi, c’est l’approche la plus pythonique: simple, facile à comprendre, utilise des fonctionnalités immédiates, et si vous modifiez la deuxième ligne en d = dict ((x [1], '{' + str (x [1]) + '}') pour x dans l'action._formatter_parser ()) vous pouvez obtenir le lien , le format {james} bond aussi facilement que le format bond, bond .
ajouté l'auteur hlongmore, source

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.

format_partial() method uses the Formatter from string and ast to parse the format string and also find out whether the named parameter hash has all the values needed to partially evaluate the 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 will leave the unresolved portion of the format string, so subsequent calls can be used to resolve those parts as the data is available.

goodmami's and dawg's answers seem cleaner, but they both fail to capture the format mini-language completely as in {x:>{x}}; format_partial will have no problem resolving any format string that string.format() resolves:

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
ajouté
La façon dont le code parcourt les modèles de format remplaçant ceux résolus est un gros bug maintenant.
ajouté l'auteur topkara, source

Pour Python 3, en prenant la réponse approuvée, il s’agit d’une implémentation sympa et étroite de 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
ajouté
Malheureusement, cela ne gérera pas la casse de chaîne de "{a: <10}" .
ajouté l'auteur Marcel Wilson, source

Based on some of the other answers, I expanded the solutions. This will handle strings with formatting spec "{a:<10}".

J'ai constaté que certaines chaînes de la journalisation au sélénium entraînaient le dépassement de la limite de récursivité de vformat (et format_map). Je voulais également m'assurer de pouvoir manipuler des 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
ajouté