Оставшиеся значения пустые, если не переданы в str.format
Я столкнулся с довольно простой проблемой, из-за которой я не могу придумать элегантное решение.
Я создаю строку, используя str.format
в функции, которая передается в dict
подстановок для использования в формате. Я хочу создать строку и форматировать ее со значениями, если они переданы, и оставить их пустыми в противном случае.
Пример
kwargs = {"name": "mark"}
"My name is {name} and I'm really {adjective}.".format(**kwargs)
должен возвращать
"My name is mark and I'm really ."
вместо того, чтобы бросать KeyError
(что будет, если мы ничего не сделаем).
Смутно, я даже не могу придумать неэлегантное решение этой проблемы. Думаю, я мог бы решить это, просто не используя str.format
, но я предпочел бы использовать встроенный (что в основном делает то, что я хочу), если это возможно.
Примечание. Я заранее не знаю, какие ключи будут использоваться. Я пытаюсь потерпеть неудачу изящно, если кто-то включает ключ, но не помещает его в kwargs dict. Если бы я знал с 100% точностью, какие ключи будут подняты, я бы просто заполнил их все и сделаю с ним.
Ответы
Ответ 1
Вы можете следовать рекомендациям в PEP 3101 и использовать подклассификатор форматирования:
import string
class BlankFormatter(string.Formatter):
def __init__(self, default=''):
self.default=default
def get_value(self, key, args, kwds):
if isinstance(key, str):
return kwds.get(key, self.default)
else:
return string.Formatter.get_value(key, args, kwds)
kwargs = {"name": "mark", "adj": "mad"}
fmt=BlankFormatter()
print fmt.format("My name is {name} and I'm really {adj}.", **kwargs)
# My name is mark and I'm really mad.
print fmt.format("My name is {name} and I'm really {adjective}.", **kwargs)
# My name is mark and I'm really .
Начиная с Python 3.2, вы можете использовать .format_map в качестве альтернативы:
class Default(dict):
def __missing__(self, key):
return '{'+key+'}'
kwargs = {"name": "mark"}
print("My name is {name} and I'm really {adjective}.".format_map(Default(kwargs)))
который печатает:
My name is mark and I'm really {adjective}.
Ответ 2
Вот один из вариантов, который использует collections.defaultdict
:
>>> from collections import defaultdict
>>> kwargs = {"name": "mark"}
>>> template = "My name is {0[name]} and I'm really {0[adjective]}."
>>> template.format(defaultdict(str, kwargs))
"My name is mark and I'm really ."
Обратите внимание, что мы не используем **
, чтобы больше распаковать словарь в аргументы ключевых слов, а спецификатор формата использует {0[name]}
и {0[adjective]}
, что указывает, что мы должны выполнить ключевой поиск по первому аргументу format()
используя "name"
и "adjective"
соответственно. Используя defaultdict
, отсутствующий ключ приведет к пустой строке вместо повышения KeyError.
Ответ 3
Вы можете обновить словарь по умолчанию с помощью списка ключевых слов:
kwargs = {"name": "mark"}
defaults = {"name": "", "adjective": ""}
defaults.update(kwargs)
print "My name is {name} and I'm really {adjective}.".format(**defaults)
Ответ 4
Для записи:
s = "My name is {name} and I'm really {adjective}."
kwargs = dict((x[1], '') for x in s._formatter_parser())
# Now we have: `kwargs = {'name':'', 'adjective':''}`.
kwargs.update(name='mark')
print s.format(**kwargs) # My name is mark and I'm really .
Ответ 5
Требуется добавить довольно простое решение для замены любых значений по умолчанию.
import string
class SafeDict(dict):
def __init__(self, missing='#', empty='', *args, **kwargs):
super(SafeDict, self).__init__(*args, **kwargs)
self.missing = missing
self.empty = empty
def __getitem__(self, item):
return super(SafeDict, self).__getitem__(item) or self.empty
def __missing__(self, key):
return self.missing
values = SafeDict(a=None, c=1})
string.Formatter().vformat('{a} {c} {d}', (), values)
# ' 1 #'
Ответ 6
Хотя подклассификация Formatter
, вероятно, является "правильным" ответом, можно также следовать строгой вене KeyError
прощения, а не разрешения, KeyError
. Преимущество этого подхода заключается в том, что он легко гибок: в частности, легко иметь значения "по умолчанию", которые не являются статичными (то есть, возможно, пустой константой), но могут зависеть от имени ключа, например, здесь:
def f(s, **kwargs):
"""Replaces missing keys with a pattern."""
RET = "{{{}}}"
try:
return s.format(**kwargs)
except KeyError as e:
keyname = e.args[0]
return f(s, **{ keyname: RET.format(keyname) }, **kwargs)
который будет работать следующим образом:
In [1]: f("My name is {name} and I'm really {adjective}.", **{"name": "Mark"})
Out[1]: "My name is Mark and I'm really {adjective}."
Это может быть легко специализировано, чтобы сделать то, что хотел ОП:
def f_blank(s, **kwargs):
"""Replaces missing keys with a blank."""
try:
return s.format(**kwargs)
except KeyError as e:
keyname = e.args[0]
return f(s, **{ keyname: "" }, **kwargs)
Мне было немного веселее с этой идеей: https://gist.github.com/jlumbroso/57951c06a233c788e00d0fc309a93f91
# (not a real import! just saying importing the code from the Gist)
from gist.57951c06a233c788e00d0fc309a93f91 import _make_f
# Define replacement f"..." compatible with Python 2 and 3
_f = _make_f(globals=lambda: globals(), locals=lambda: locals())
# Use:
s = "Test"
var = 1
assert _f("{s} {var}") == "Test 1"
# Inside a non-global scope, you may have to provide locals
def test():
l_s = "Test"
l_var = 1
assert _f("{l_s} {l_var} / {s} {var}") == "{l_s} {l_var} / Test 1"
assert _f("{l_s} {l_var} / {s} {var}", **locals()) == "Test 1 / Test 1"
Ответ 7
Способ избежать ключевой ошибки состоит в том, чтобы включить в dict, но оставить его пустым:
kwargs = {"name": "mark", "adjective": ""}
"My name is {name} and I'm really {adjective}.".format(**kwargs)
Аргументы ключевого слова ожидают, что они будут ключом в kwargs. Другой способ сделать это - позиционные аргументы:
"My name is {0} and I'm really {1}.".format("mark")
Печать: "Меня зовут Марк, и я на самом деле". Пока
"My name is {0} and I'm really {1}.".format("mark","black")
Печать "Меня зовут Марк, и я действительно черный".
Кроме того, вы можете поймать ValueError.