Строка в формате python неиспользуемые именованные аргументы

Скажем, у меня есть:

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

этот вывод wil:

'bond, james bond' 

Далее мы имеем:

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

это выведет:

KeyError: 'james'

Есть ли способ обхода этой ошибки, например:

  • if keyrror: игнорировать, оставлять его в покое (но разбирать другие)
  • сравнить строку формата с доступными именованными аргументами, если отсутствует, добавьте

Ответ 1

Если вы используете Python 3.2+, используйте 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'

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

Ответ 2

Вы можете использовать строку шаблона с помощью метода safe_substitute.

from string import Template

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

Ответ 3

Вы можете следовать рекомендациям в PEP 3101 и подклассе 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)

Теперь попробуйте:

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

Вы можете изменить способ пометки ключевых ошибок, изменив текст в self.default на то, что вы хотели бы показать для 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'

Код работает без изменений на Python 2.6, 2.7 и 3. 0+

Ответ 4

Можно также сделать простой и понятный, хотя и немного глупый:

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

Я знаю, что этот ответ требует знания ожидаемых ключей, но я искал простую двухшаговую подстановку (например, имя проблемы сначала, затем индекс проблемы в цикле), и создание целого класса или нечитаемого кода было более сложным, чем необходимо.

Ответ 5

falsetru answer имеет умное использование словаря по умолчанию с vformat(), а dawg answer - это возможно, больше в соответствии с документацией Python, но не обрабатывают имена составных полей (например, с явным преобразованием (!r) или спецификациями формата (:+10g).

Например, используя falsetru 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}' {"

И используя dawg 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}' {"

Ни один из них не работает хорошо во втором случае, потому что поиск значения (в get_value()) уже лишил спецификации форматирования. Вместо этого вы можете переопределить vformat() или parse(), чтобы эти спецификации были доступны. Мое решение ниже делает это путем переопределения vformat(), поэтому он выполняет поиск ключа и, если ключ отсутствует, ускользает от строки формата с двойными скобками (например, {{two!r}}), а затем выполняет обычный 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)

Здесь он в действии:

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

Это решение слишком хаки (возможно, переопределение parse() будет иметь меньше kludges), но должно работать для большего количества строк форматирования.

Ответ 6

Необходимость частичного заполнения строк формата является общей проблемой при постепенном заполнении строк формата, например. для SQL-запросов.

format_partial() использует Formatter из string и ast для анализа строки формата, а также выясняет, имеет ли именованный параметр hash все значения, необходимые для частичной оценки формата:

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 оставит неразрешенную часть строки формата, поэтому последующие вызовы могут использоваться для разрешения этих частей по мере доступности данных.

ответы goodmami и dawg кажутся более чистыми, но оба они не могут полностью фиксировать мини-язык формата, как в {x:>{x}}; format_partial не будет проблем с разрешением любой строки формата, которая 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'

Еще проще расширить функциональные возможности до строк старого стиля, используя регулярное выражение вместо форматирования строк, поскольку подстроки в стиле старого стиля были регулярными (т.е. не вложенными маркерами).

Ответ 7

Вот еще один способ сделать это с помощью 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

Ответ 8

Основываясь на некоторых других ответах, я расширил решения. Это будет обрабатывать строки со спецификацией форматирования "{a:<10}".

Я обнаружил, что некоторые строки в журнале selenium приводили к тому, что vformat (и format_map) достигали предела рекурсии. Я также хотел убедиться, что могу обрабатывать строки, в которых также есть пустые фигурные скобки.

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

выход:

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

Ответ 9

Для Python 3, принимая одобренный ответ, это хорошая, жесткая реализация 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'