Оставшиеся значения пустые, если не переданы в 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.