如何推迟/推迟对f弦的评估?


99

我正在使用模板字符串生成一些文件,为此我喜欢新的f字符串的简洁性,以减少类似以下内容的我以前的模板代码:

template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print (template_a.format(**locals()))

现在,我可以直接替换变量:

names = ["foo", "bar"]
for name in names:
    print (f"The current name is {name}")

但是,有时在其他地方定义模板是有意义的-在代码中较高的位置,或者从文件或其他内容中导入模板。这意味着模板是带有格式标记的静态字符串。字符串上必须发生一些事情,以告诉解释器将字符串解释为新的f字符串,但是我不知道是否有这种事情。

有什么方法可以引入字符串并将其解释为f字符串,从而避免使用.format(**locals())调用?

理想情况下,我希望能够像这样进行编码...(magic_fstring_function我不理解的部分在哪里出现):

template_a = f"The current name is {name}"
# OR [Ideal2] template_a = magic_fstring_function(open('template.txt').read())
names = ["foo", "bar"]
for name in names:
    print (template_a)

...具有所需的输出(无需两次读取文件):

The current name is foo
The current name is bar

...但是我得到的实际输出是:

The current name is {name}
The current name is {name}

5
您不能使用f字符串来执行此操作。一个f字符串不是数据,而且它肯定不是一个字符串; 这是代码。(与dis模块一起检查。)如果希望以后评估代码,请使用一个函数。
kindall

12
仅供参考,PEP 501提出了一个接近您的第一个理想的功能,但目前“推迟[f弦]的使用经验”。
jwodder

模板是静态字符串,但是f字符串不是字符串,而是代码对象,如@kindall所说。我认为f字符串在实例化时立即绑定到变量(在Python 3.6,7中),而不是在最终使用时绑定。因此,f-string可能比您的丑陋老人有用.format(**locals()),尽管在外观上更好。直到实施PEP-501。
smci

Guido救了我们,但PEP 498确实使它受挫PEP 501所描述的延期评估绝对应该被纳入核心f字符串实现中。现在,我们留下了少多特征的,极其缓慢之间的讨价还价str.format()方法,一方面支持推迟评估和更多的其他功能,速度极快F-字符串语法支持对其他递延评价。因此,我们仍然需要两者,而Python仍然没有标准的字符串格式化程序。插入xkcd标准模因。
塞西尔·库里

Answers:


26

这是完整的“理想2”。

它不是f字符串,它甚至不使用f字符串,但可以按要求进行操作。语法完全符合规定。没有使用安全性,因为我们没有使用eval()

它使用了一个小类并实现了__str__由print自动调用的类。为了逃避该类的有限范围,我们使用该inspect模块向上跳一帧并查看调用者可以访问的变量。

import inspect

class magic_fstring_function:
    def __init__(self, payload):
        self.payload = payload
    def __str__(self):
        vars = inspect.currentframe().f_back.f_globals.copy()
        vars.update(inspect.currentframe().f_back.f_locals)
        return self.payload.format(**vars)

template = "The current name is {name}"

template_a = magic_fstring_function(template)

# use it inside a function to demonstrate it gets the scoping right
def new_scope():
    names = ["foo", "bar"]
    for name in names:
        print(template_a)

new_scope()
# The current name is foo
# The current name is bar

13
我将接受这个作为答案,尽管由于极高的智能性,我认为我不会在代码中实际使用它。好吧,也许永远不会:)。也许python人们可以将其用于PEP 501的实现。如果我的问题是“我应该如何处理这种情况”,答案将是“只需继续使用.format()函数并等待PEP 501解决。” 感谢您弄清楚该怎么做,@ PaulPanzer
JDAnders

6
当模板包含比简单变量名称更复杂的内容时,这将不起作用。例如:template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers
bli

6
@bli有趣,似乎是的限制str.format。我曾经认为f字符串只是类似这样的语法糖,str.format(**locals(), **globals())但显然我错了。
Paul Panzer

4
请不要在生产中使用它。inspect是一个红旗。
亚历山大

1
我有两个问题,为什么在生产中检查“危险信号”会是一个例外,还是会有更多可行的解决方法?是否有一些反对使用__slots__here来减少内存使用量的问题?
Jab

21

这意味着模板是带有格式标记的静态字符串

是的,这就是为什么我们要使用带有替换字段和的文字.format,因此我们可以随时调用format它来替换这些字段。

字符串上必须发生一些事情,以告知解释器将字符串解释为新的f字符串

那是前缀f/F。您可以将其包装在一个函数中,并在调用期间推迟评估,但是当然会产生额外的开销:

template_a = lambda: f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print (template_a())

打印出:

The current name is foo
The current name is bar

但感觉不对,并受到以下事实的限制:您只能窥视替换中的全局名称空间。在需要本地名称的情况下尝试使用它会惨遭失败,除非将其作为参数传递给字符串(这完全是关键)。

有什么方法可以引入字符串并将其解释为f字符串,从而避免使用.format(**locals())调用?

除了功能(包括限制)外,不行,所以不妨坚持.format


有趣的是,我发布的片段完全一样。但是由于范围限制,我撤回了它。(尝试将for循环包装到函数中。)
Paul Panzer

@PaulPanzer您是否要编辑问题并重新添加?我不介意删除答案。对于OP的情况,这是一个可行的选择;对于所有情况,这不是可行的选择,因为它是偷偷摸摸的。
Dimitris Fasarakis Hilliard'2

1
不,没关系,保留它。我对新的解决方案更加满意。但是我可以看到您的观点,即如果您知道它的局限性,那么它是可行的。也许您可以在帖子中添加一些警告,以便没有人会因为使用错误而投篮?
Paul Panzer

16

一种将字符串评估为f字符串(具有完整功能)的简洁方法是使用以下函数:

def fstr(template):
    return eval(f"f'{template}'")

然后,您可以执行以下操作:

template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print(fstr(template_a))
# The current name is foo
# The current name is bar

并且,与许多其他建议的解决方案相比,您还可以执行以下操作:

template_b = "The current name is {name.upper() * 2}"
for name in names:
    print(fstr(template_b))
# The current name is FOOFOO
# The current name is BARBAR

4
迄今为止最好的答案!当他们引入f字符串时,它们如何不将这种简单的实现作为内置功能包括在内?
user3204459

1
不,那失去了范围。起作用的唯一原因是因为它name具有全球性。f字符串在评估中延迟,但FString类需要通过查看调用方的locals和globals来创建对范围参数的引用的列表...,然后在使用时评估字符串。
Erik Aronesty,

2
@ user3204459:因为能够执行任意字符串本质上是一种安全隐患-这eval()就是通常不鼓励使用的原因。
martineau

2
@martineau它应该是python的一个功能,因此您不需要使用eval ...另外,f字符串与eval()有相同的风险,因为您可以将任何内容放在大括号中,包括恶意代码,因此一个问题,那就不要使用f字符串
user3204459

2
这正是我一直在寻找的,为'fstr postpone做回避。Eval似乎并不比一般使用fstring差,因为我猜它们都具有相同的功能:f“ {eval('print(42) ')}“
user2692263,

12

一架F-string是简单的创建一个格式化字符串,更换了更为简洁的方式.format(**names)f。如果您不希望以这种方式立即评估字符串,请不要将其设置为f字符串。将其另存为普通字符串文字,然后format在以后要进行插值时再调用它,就像以前一样。

当然,可以使用替代eval

template.txt

f'当前名称为{name}'

码:

>>> template_a = open('template.txt').read()
>>> names = 'foo', 'bar'
>>> for name in names:
...     print(eval(template_a))
...
The current name is foo
The current name is bar

但后来你已经设法做的是替换str.formateval,这肯定是不值得的。只需继续使用常规字符串即可format


3
在您的代码片段中,我真的看不到任何优势。我的意思是,您始终可以只The current name is {name}template.txt文件中写入内容,然后使用print(template_a.format(name=name))(或.format(**locals()))。该代码长了约10个字符,但是由于,它没有引入任何可能的安全性问题eval
巴库里

@Bakuriu-是的;就像我说的那样,尽管eval确实允许我们编写f'{name}'并延迟name对它的求值,直到达到期望为止,但是不如formatOP已经在做的那样,简单地创建一个常规模板字符串然后调用它是次等的。
TigerhawkT3

4
“ f字符串只是创建格式化字符串的一种更简洁的方法,用。替换.format(** names)。” 不太完全-他们使用不同的语法。我没有最近可用的python3,但是例如,我相信f'{a + b}'可以工作,而'{a + b}'。format(a = a,b = b)会引发KeyError 。.format()在许多情况下可能都很好,但它不是临时替代品。
菲尔

2
@philh我想我刚刚遇到了一个.format不等同于f字符串的示例,它可以为您提供注释:DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())。尝试在中创建failed_fragment结果TypeError: string indices must be integers
bli

12

使用.format并不是此问题的正确答案。Python f字符串与str.format()模板非常不同...它们可以包含代码或其他昂贵的操作-因此需要推迟。

这是一个延迟记录器的示例。这使用了logging.getLogger的常规序言,但是随后添加了仅在日志级别正确的情况下解释f字符串的新函数。

log = logging.getLogger(__name__)

def __deferred_flog(log, fstr, level, *args):
    if log.isEnabledFor(level):
        import inspect
        frame = inspect.currentframe().f_back.f_back
        try:
            fstr = 'f"' + fstr + '"'
            log.log(level, eval(fstr, frame.f_globals, frame.f_locals))
        finally:
            del frame
log.fdebug = lambda fstr, *args: __deferred_flog(log, fstr, logging.DEBUG, *args)
log.finfo = lambda fstr, *args: __deferred_flog(log, fstr, logging.INFO, *args)

这样做的优点是能够执行以下操作: log.fdebug("{obj.dump()}")....除非启用调试,否则不转储对象。

恕我直言:这应该是f字符串的默认操作,但是现在为时已晚。F字符串评估可能会产生大量和意想不到的副作用,以延迟的方式发生会改变程序的执行。

为了适当延迟f字符串,python需要某种方式来显式切换行为。也许使用字母“ g”?;)


完全同意这个答案。这个用例是我在搜索此问题时所考虑的。
justhalf

这是正确的答案。一些时间: %timeit log.finfo(f"{bar=}") 91.9 µs ± 7.45 µs per loop %timeit log.info(f"{bar=}") 56.2 µs ± 630 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) log.setLevel(logging.CRITICAL) %timeit log.finfo("{bar=}") 575 ns ± 2.9 ns per loop %timeit log.info(f"{bar=}") 480 ns ± 9.37 ns per loop %timeit log.finfo("") 571 ns ± 2.66 ns per loop %timeit log.info(f"") 380 ns ± 0.92 ns per loop %timeit log.info("") 367 ns ± 1.65 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Jaleks

8

您想要的东西似乎被认为是Python增强功能

同时-从链接的讨论中-以下似乎是一个不需要使用的合理解决方法eval()

class FL:
    def __init__(self, func):
        self.func = func
    def __str__(self):
        return self.func()


template_a = FL(lambda: f"The current name, number is {name!r}, {number+1}")
names = "foo", "bar"
numbers = 40, 41
for name, number in zip(names, numbers):
    print(template_a)

输出:

The current name, number is 'foo', 41
The current name, number is 'bar', 42

7

kadee答案的启发,以下内容可用于定义deferred-f-string类。

class FStr:
    def __init__(self, s):
        self._s = s
    def __repr__(self):
        return eval(f"f'{self._s}'")

...

template_a = FStr('The current name is {name}')

names = ["foo", "bar"]
for name in names:
    print (template_a)

这正是这个问题要问的


4

或者也许不使用f字符串,只需格式化:

fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
    print(fun(name=name))

在没有名称的版本中:

fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
    print(fun(name))

这并非在所有情况下都有效。范例:fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA)。->TypeError: string indices must be integers
bli

但它在正常使用中也不起作用,请查看答案stackoverflow.com/questions/14072810/…–
msztolcman

2

怎么样:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

s.format(foo='Bar')
> 'Hi, Bar!'

0

使用f字符串的建议。在发生模板的逻辑级别上进行评估,并将其作为生成器传递。您可以使用f弦在任意位置解开它

In [46]: names = (i for i in ('The CIO, Reed', 'The homeless guy, Arnot', 'The security guard Spencer'))

In [47]: po = (f'Strangely, {next(names)} has a nice {i}' for i in (" nice house", " fast car", " big boat"))

In [48]: while True:  
...:     try:  
...:         print(next(po))  
...:     except StopIteration:  
...:         break  
...:       
Strangely, The CIO, Reed has a nice  nice house  
Strangely, The homeless guy, Arnot has a nice  fast car  
Strangely, The security guard Spencer has a nice  big boat  
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.