有没有类似f-string语法的可调用等价物?

6

每个人都喜欢Python 3.6的新f-strings:

In [33]: foo = {'blah': 'bang'}

In [34]: bar = 'blah'

In [35]: f'{foo[bar]}'
Out[35]: 'bang'

然而,虽然它们在功能上非常相似,但它们与str.format()的语义并不完全相同:

In [36]: '{foo[bar]}'.format(**locals())
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-36-b7ef5aead76c> in <module>()
----> 1 '{foo[bar]}'.format(**locals())

KeyError: 'bar'

特别地,str.format() 对getitem语法处理方式非常不同
In [39]: '{foo[blah]}'.format(**locals())
Out[39]: 'bang'

给定完整的Python表达式语法处理能力,f-strings非常棒,我喜欢它们。但是它们有一个问题:立即被评估了,然而使用str.format()可以将字符串与其格式保存为模板,在不同上下文中多次进行格式化。

那么,是否有一种等效的方法可以将字符串保存为模板,并在以后使用f-字符串语义进行评估?除了定义一个函数之外还有其他方法吗?是否有适用于f-字符串的等效方式类似于str.format()

更新:

因此,这里有一个假设接口示例:

In [40]: mystr = '{foo[bar]}'

In [41]: make_mine_fstring(mystr, foo=foo, bar=bar)
Out[41]: 'bang'

1
为什么需要除了定义函数之外的其他方法呢?那是最明显的方法。 - kindall
好的,假设我正在从文件中读取字符串。我该如何获得f-string语义? - David Eyk
这基本上就像从文件中读取任意可执行代码并执行它一样。在大多数情况下都是一个坏主意。 - kindall
1
@kindall 我想 Guido 在编写 Python 解释器之前应该考虑过这个问题。 ;) - David Eyk
2个回答

4
简短回答:不行。
关于这些f-strings,您可以阅读PEP-498,它清楚地定义了它们的目的和概念:这些字符串是就地评估的。结果是一个带有格式化内容的普通 str 。您不能将f-strings存储为模板,因为没有专门的f-string对象。
您特定的示例也在PEP-498的“f-string和str.format表达式之间的区别”部分中提到。
因此,无论您做什么,都要使用内联的就地f-strings或旧的 s.format()语法,具有不同的行为。
如果您想从文件中读取f-string并根据f-string的语法进行评估,则可以使用eval:
foo = {'blah': 'bang', 'bar': 'sorry'}
bar = 'blah'

tpl = '{foo[bar]}'
print(tpl)

print(tpl.format(**locals()))  # sorry
print(eval(f'f{tpl!r}'))  # bang

注意我们首先使用f-string,但将tpl转换为其自己的repr以进行即时评估。通常,对于简单类型,eval(repr(val))应返回val。但是,我们不仅仅是将repr(tpl)(或{tpl!r})放入其中,而是将常规字符串的repr转换为f-string,并进行评估。

我有一种感觉,答案会是<del>某些邪恶的东西</del><ins>一些评估函数</ins>。;) 太完美了,谢谢。 - David Eyk

1

这是我能想到的最接近的方法,但它仍然使用了eval(),而且由于作用域问题,它没有很好地封装在一个函数中:

def ffmt_bad(text):
    return eval('f' + repr(text))

它的功能类似于:
# this does works
a = 10
ffmt_bad('{a}')
# 10


# this does NOT work
def foo():
    def bar(a, b, text='{a}, {b}'):
        return ffmt_bad(text)
    return bar(1, 2)


foo()
# NameError: name 'a' is not defined

编辑 1

一种可能的解决方法是:

import inspect


def ffmt(text):
    frame = inspect.currentframe()
    _vars = vars()
    _vars.update(frame.f_back.f_vars)
    return eval('f' + repr(text))

这里有一个警告

如果没有参数,vars() 的行为类似于 locals()。请注意,locals 字典仅适用于读取,因为对 locals 字典的更新将被忽略。

尽管它在 CPython 3.6 和 CPython 3.7 中有效,但使用方式与以前相同,只是现在可以独立于调用位置工作。

a = 10
ffmt('{a}')
# 10


def foo():
    def bar(a, b, text='{a}, {b}'):
        return ffmt(text)
    return bar(1, 2)


foo()
# '1, 2'

编辑 2

另一个可能的解决方法,虽然承认不太方便,是:

def fize(text):
    return 'f' + repr(text)

eval(fize(...))

which works like this:

a = 10
eval(fize('{a}² = {a ** 2}'))
# '10² = 100'


def foo():
    def bar(a, b, text='{a}, {b}'):
        return eval(fize(text))
    return bar(1, 2)

# '1, 2'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接