Формат строки 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'