25

我遇到了一个相当简单的问题,我无法想出一个优雅的解决方案。

我正在使用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 字典中,我会尝试优雅地失败。如果我 100% 准确地知道要查找哪些键,我只需填充所有键并完成它。

4

8 回答 8

28

您可以遵循PEP 3101中的建议并使用子类 Formatter:

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}.
于 2013-11-05T22:47:52.703 回答
14

这是一个使用的选项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]}and {0[adjective]},这表明我们应该分别对format()使用"name"and的第一个参数执行键查找"adjective"。通过使用defaultdict缺少的键将导致空字符串,而不是引发 KeyError。

于 2013-11-05T21:52:45.447 回答
0

作为记录:

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 .
于 2016-06-01T19:41:08.933 回答
0

想要添加一个非常简单的解决方案来替换所需的任何默认值。

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 #'
于 2017-07-19T21:26:18.183 回答
0

虽然子类化 aFormatter可能是“正确”的答案,但也可以通过捕获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}."

这可以很容易地专门做OP想要的:

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"
于 2019-07-05T02:43:36.407 回答
0

如果您仍在使用 Python 2,则可以使用defaultdictwithstring.Formatter来实现:

>>> format_string = '{foo:<2s}{bar:<3s}'
>>> data = {'bar': 'baz'}
>>> string.Formatter().vformat(format_string, (), defaultdict(str, data))
'  baz'
于 2019-08-02T04:42:15.190 回答
0

其他解决方案具有以下一个或多个特征,但不适用于我的需要: * 不支持复合/嵌套名称 * 需要 python 3 * 不起作用 * 不控制分层默认名称丢失钥匙的情况

所以这里有一个解决这些问题的解决方案。请注意,此解决方案仅适用于文本名称,例如“{key}”或“{nested[key]}”。不确定它是否会起作用,例如“{foo:<2s}”。

这也不处理数组,但可以很容易地扩展到这样做。您还可以替换您自己的函数来输出您想要的缺失值的任何占位符结果。

使用示例:

my_data = {
    'hi': 'there',
    'abc': {
        'def': 'ghi'
    },
    'level1': {
        'level2': {
            'level3': 'nested'
        }
    }
}

s = "{hi} there"
print FormatDict(my_data).format(s) # 'there there'

s = "{hi} there {abc[def]}"
print FormatDict(my_data).format(s) # 'there there ghi'

s = "{hix} there {abc[def]}"
print FormatDict(my_data).format(s) # '{hix} there ghi'

s = "{hix} there {abc[defx]}"
print FormatDict(my_data).format(s) # '{hix} there {abc[defx]}'

s = "{hi} there {level1[level2][level3]}"
print FormatDict(my_data).format(s) # 'there there nested'

s = "{hix} there {level1[level2][level3x]}"
print FormatDict(my_data).format(s) # '{hix} there {level1[level2][level3x]}'

这是代码:

import string

class FormatDict(dict):
    def set_parent(self, parent):
        self.parent = parent

    def __init__(self, *args, **kwargs):
        self.parent = None
        self.last_get = ''
        for arg in (args or []):
            if isinstance(arg, dict):
                for k in arg:
                    self.__setitem__(k, arg[k])
        for k in (kwargs or {}):
            self.__setitem__(k, kwargs[k])

    def __getitem__(self, k):
        self.last_get = k
        try:
            val = dict.__getitem__(self, k)
            return val
        except:
            ancestry = [k]
            x = self.parent
            while x:
                ancestry.append(x.last_get)
                x = x.parent
            ancestry.reverse()
            return '{' + ancestry[0] + ''.join(['[' + x + ']' for x in ancestry[1:]]) + '}'

    def __setitem__(self, k, v):
        if isinstance(v, dict):
            v = FormatDict(v)
            v.set_parent(self)
        dict.__setitem__(self, k, v)

    def format(self, s):
        return string.Formatter().vformat(s, (), self)
于 2020-04-09T21:49:10.113 回答
-1

避免关键错误的一种方法是包含在 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。

于 2013-11-05T21:51:13.017 回答