format Pythona ciąg nieużywanych nazwanych argumentów

Powiedzmy, że mam:

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

To wyjście wil:

'bond, james bond' 

Dalej mamy:

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

Wyświetli się:

KeyError: 'james'

Czy istnieje jakieś obejście, aby zapobiec wystąpieniu tego błędu, coś w stylu:

  • jeśli keyrror: ignoruj, zostaw go w spokoju (ale analizuj inne)
  • porównaj łańcuch formatowania z dostępnymi nazwanymi argumentami, jeśli ich nie ma, dodaj
Author: dreftymac, 2013-06-20

9 answers

Jeśli używasz Pythona 3.2+, użyj może użyć str.format_map () .

Dla bond, bond:

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

Dla bond, {james} bond:

>>> class SafeDict(dict):
...     def __missing__(self, key):
...         return '{' + key + '}'
...
>>> '{bond}, {james} {bond}'.format_map(SafeDict(bond='bond'))
'bond, {james} bond'

W Pythonie 2.6/2.7

Dla bond, bond:

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

Dla 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
Author: falsetru,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-20 14:20:39

Możesz użyć ciąg szablonów z metodą safe_substitute.

from string import Template

tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute({'bond': 'bond'})
 18
Author: Martin Maillard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-20 13:59:16

Możesz postępować zgodnie z zaleceniem w PEP 3101 i Formaterze podklasy:

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)

Teraz spróbuj:

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

Możesz zmienić sposób oznaczania błędów, zmieniając tekst w self.default na ten, który chcesz pokazać Dla 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'

Kod działa bez zmian w Pythonie 2.6, 2.7 i 3.0 +

 9
Author: dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-10 01:59:14

Można też zrobić proste i czytelne, choć nieco głupie:

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

Wiem, że ta odpowiedź wymaga znajomości oczekiwanych kluczy, ale szukałem prostego podstawienia dwuetapowego (najpierw nazwa problemu, potem indeks problemu w pętli) i tworzenie całej klasy lub nieczytelnego kodu było bardziej skomplikowane niż było to konieczne.

 7
Author: Ioannis Filippidis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-09 19:36:54

Odpowiedź Falsetru ma sprytne użycie domyślnego słownika z vformat(), a odpowiedź dawga jest prawdopodobnie bardziej zgodna z dokumentacją Pythona, ale ani nie obsługuje złożonych nazw pól (np. z jawną konwersją (!r), ani specyfikacji formatu (:+10g).

Na przykład, używając Safedictu 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}' {"

I używając myformatter dawga:

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

Nie działa dobrze w drugim przypadku, ponieważ wyszukiwanie wartości (w get_value()) zostało już usunięte ze specyfikacją formatowania. Zamiast tego możesz przedefiniować vformat() lub parse(), aby te specyfikacje były dostępne. Moje rozwiązanie poniżej robi to poprzez ponowne zdefiniowanie vformat(), więc wykonuje wyszukiwanie klucza i, jeśli brakuje klucza, ucieka łańcuch formatowania z podwójnymi klamrami (np. {{two!r}}), a następnie wykonuje normalną 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)

Oto w akcji:

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

To rozwiązanie jest trochę zbyt hakerskie( może redefinicja parse() miałaby mniej kludge ' ów), ale powinno działać na większą formatowanie struny.

 5
Author: goodmami,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:18

Oto inny sposób, aby to zrobić za pomocą 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
Author: feqwix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-01 19:50:52

Konieczność częściowego wypełnienia ciągów formatowych jest częstym problemem podczas stopniowego wypełniania ciągów formatowych, np. w przypadku zapytań SQL.

format_partial() Metoda wykorzystuje Formatter z string i ast do analizy ciągu znaków formatu, a także sprawdza, czy nazwany parametr hash ma wszystkie wartości potrzebne do częściowej oceny formatu:

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 pozostawia nierozwiązaną część ciągu formatu, więc kolejne wywołania mogą być używane do rozwiązywania tych części, ponieważ dane są dostępny.

Odpowiedzi Goodmamiego i dawga wydają się czystsze, ale obaj nie potrafią uchwycić mini-języka formatu, tak jak w {x:>{x}}; format_partial nie będzie problemu z rozwiązaniem dowolnego ciągu formatowego, który string.format() rozwiązuje:

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'

Jest jeszcze łatwiej rozszerzyć funkcjonalność na ciągi formatujące w starym stylu używając regex zamiast programu do formatowania łańcuchów, ponieważ podciągi formatujące w starym stylu były regularne (np. brak zagnieżdżonych znaczników).

 1
Author: topkara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-27 18:34:16

Dla Pythona 3, biorąc zatwierdzoną odpowiedź, jest to ładna, zwarta implementacja Pythoniczna:

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
Author: mattmc3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-20 17:58:52

Bazując na innych odpowiedziach, rozszerzyłem rozwiązania. Będzie to obsługiwać ciągi znaków ze specyfikacją formatowania "{a:<10}".

Odkryłem, że niektóre łańcuchy z logowania selenium powodowały, że vformat (i format_map) osiągały limit rekursji. Chciałem również upewnić się, że mogę obsługiwać struny tam, gdzie istnieją puste kręcone szelki.

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

Wyjście:

{"a": {"b": {"c": {"d" : {} Fooolery             & 'Fooolery' Fooolery ARRrrrrrg! ^BLUE^ {color.pink} {blah.atr} }}}}
 0
Author: Marcel Wilson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-20 18:52:15