Формат строки python suppress/silent keyerror/indexerror

Есть ли способ использовать python string.format, чтобы исключение не генерировалось при отсутствии индекса, вместо этого вставляется пустая строка.

result = "i am an {error} example string {error2}".format(hello=2,error2="success")

здесь, результат должен быть:

"i am an   example string success"

Прямо сейчас, python бросает keyerror и останавливает форматирование. Можно ли изменить это поведение?

Спасибо

Изменить:

Существует Template.safe_substitute (даже если он не содержит паттерн, а не вставляет пустую строку), но не может что-то подобное для string.format

Желаемое поведение будет похоже на замену строки в php.

class Formatter(string.Formatter):
  def get_value(self,key,args,kwargs):
    try:
        if hasattr(key,"__mod__"):
            return args[key]
        else:
            return kwargs[key]
    except:
        return ""

Это, по-видимому, обеспечивает желаемое поведение.

Ответы

Ответ 1

str.format() не ожидает объекта сопоставления. Попробуйте следующее:

from collections import defaultdict

d = defaultdict(str)
d['error2'] = "success"
s = "i am an {0[error]} example string {0[error2]}"
print s.format(d)

Вы делаете defaultdict с str() factory, который возвращает "". Затем вы делаете один ключ для defaultdict. В строке формата вы получаете доступ к ключам первого переданного объекта. Это имеет то преимущество, что вы можете передавать другие ключи и значения, если ваш defaultdict является первым аргументом format().

Также см. http://bugs.python.org/issue6081

Ответ 2

Официальное решение (Документы Python 3) для строк в сопоставлениях форматов заключается в создании подкласса класса dict и определении магического метода __missing__(). Этот метод вызывается всякий раз, когда отсутствует ключ, и вместо него используется форматирование строки:

class format_dict(dict):
    def __missing__(self, key):
        return "..."

d = format_dict({"foo": "name"})

print("My %(foo)s is %(bar)s" % d) # "My name is ..."

print("My {foo} is {bar}".format(**d)) # "My name is ..."

Изменить: вторая функция print() работает в Python 3.5.3, но не работает, например, в. 3.7.2: KeyError: 'bar' поднят, и я не смог найти способ его поймать.

После некоторых экспериментов я обнаружил разницу в поведении Python. В версии 3.5.3 это вызовы __getitem__(self, "foo"), которые выполняются успешно, и __getitem__(self, "bar"), которые не могут найти ключ "bar", поэтому он вызывает __missing__(self, "bar") для обработки отсутствующего ключа без выброса KeyError. В v3.7.2 __getattribute__(self, "keys") вызывается внутренне. Встроенный метод keys() используется для возврата итератора по ключам, который выдает "foo", __getitem__("foo") завершается успешно, затем итератор исчерпывается. Для {bar} из строки формата нет ключа "bar". __getitem__() и, следовательно, __missing_() не вызваны для разрешения ситуации. Вместо этого генерируется KeyError. Я не знаю, как можно это поймать, если вообще.

В Python 3. 2+ вместо этого следует использовать format_map() (см. также Отслеживание ошибок Python - выпуск 6081):

from collections import defaultdict

d = defaultdict(lambda: "...")
d.update({"foo": "name"})

print("My {foo} is {bar}".format_map(d)) # "My name is ..."

Если вы хотите сохранить заполнители, вы можете сделать следующее:

class Default(dict):
    def __missing__(self, key): 
        return key.join("{}")

d = Default({"foo": "name"})

print("My {foo} is {bar}".format_map(d)) # "My name is {bar}"

Как видите, format_map() действительно звонит __missing__().

Следующее представляется наиболее совместимым решением, поскольку оно также работает в более старых версиях Python, включая 2.x (я тестировал v2.7.15):

class Default(dict):
    def __missing__(self, key):
        return key.join("{}")

d = Default({"foo": "name"})

import string
print(string.Formatter().vformat("My {foo} is {bar}", (), d)) # "My name is {bar}"

Чтобы сохранить заполнители как есть, включая спецификацию формата (например, {bar:<15}), форматер должен быть разделен на подклассы:

import string

class Unformatted:
    def __init__(self, key):
        self.key = key
    def format(self, format_spec):
        return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")

class Formatter(string.Formatter):
    def vformat(self, format_string, args, kwargs):
        return super().vformat(format_string, args, kwargs)
    def get_value(self, key, args, kwargs):
        if isinstance(key, int):
            try:
                return args[key]
            except IndexError:
                return Unformatted(key)
        else:
            try:
                return kwargs[key]
            except KeyError:
                return Unformatted(key)

    def format_field(self, value, format_spec):
        if isinstance(value, Unformatted):
            return value.format(format_spec)
        else:
            return format(value, format_spec)

f = Formatter()
s1 = f.vformat("My {0} {1} {foo:<10} is {bar:<15}!", ["real"], {"foo": "name"})
s2 = f.vformat(s1, [None, "actual"], {"bar":"Geraldine"})
print(s1) # "My real {1} name       is {bar:<15}!"
print(s2) # "My real actual name       is Geraldine      !"

Обратите внимание, что индексы-заполнители не изменяются ({1} остается в строке без {0}), и для замены {1} необходимо передать массив с любым нечетным первым элементом и тем, что вы хотите замените оставшийся заполнитель вторым элементом (например, [None, "actual"]).

Вы также можете вызвать метод format() с позиционными и именованными аргументами:

s1 = f.format("My {0} {1} {foo:<10} is {bar:<15}!", "real", foo="name")
s2 = f.format(s1, None, "actual", bar="Geraldine")

Ответ 3

К сожалению, нет, нет такого способа делать по умолчанию. Однако вы можете предоставить ему defaultdict или object с переопределенным __getattr__ и использовать вот так:

class SafeFormat(object):
    def __init__(self, **kw):
        self.__dict = kw

    def __getattr__(self, name):
        if not name.startswith('__'):
            return self.__dict.get(name, '')

print "i am an {0.error} example string {0.error2}".format(SafeFormat(hello=2,error2="success"))
i am an  example string success

Ответ 4

Я сделал версию, которая работает аналогично методу Даниэля, но без доступа к атрибуту {0.x}.

import string    
class SafeFormat(object):
    def __init__(self, **kw):
        self.__dict = kw

    def __getitem__(self, name):
        return self.__dict.get(name, '{%s}' % name)



string.Formatter().vformat('{what} {man}', [], SafeFormat(man=2))

выводит

'{what} 2'