如何推迟/推迟对F线的评估?

发布于 2025-01-19 12:08:09 字数 1271 浏览 0 评论 0原文

我正在使用模板字符串生成一些文件,并且我喜欢为此目的而使用的新 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}

另请参阅:如何将 f-string 与变量一起使用,而不是与字符串文字一起使用?

I am using template strings to generate some files and I love the conciseness of the new f-strings for this purpose, for reducing my previous template code from something like this:

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

Now I can do this, directly replacing variables:

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

However, sometimes it makes sense to have the template defined elsewhere — higher up in the code, or imported from a file or something. This means the template is a static string with formatting tags in it. Something would have to happen to the string to tell the interpreter to interpret the string as a new f-string, but I don't know if there is such a thing.

Is there any way to bring in a string and have it interpreted as an f-string to avoid using the .format(**locals()) call?

Ideally I want to be able to code like this... (where magic_fstring_function is where the part I don't understand comes in):

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)

...with this desired output (without reading the file twice):

The current name is foo
The current name is bar

...but the actual output I get is:

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

See also: How can I use f-string with a variable, not with a string literal?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

南风起 2025-01-26 12:08:09

将字符串评估为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

A concise way to have a string evaluated as an f-string (with its full capabilities) is using following function:

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

Then you can do:

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

And, in contrast to many other proposed solutions, you can also do:

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
心欲静而疯不止 2025-01-26 12:08:09

这是一个完整的“理想2”。

它不是F弦,甚至都不使用F-strings,而是根据要求进行的。符合指定的语法。没有安全性头痛,因为我们不使用eval()

它使用一个小型类并实现__ str __,该>由打印自动调用。为了逃脱类的有限范围,我们使用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

Here's a complete "Ideal 2".

It's not an f-string—it doesn't even use f-strings—but it does as requested. Syntax exactly as specified. No security headaches since we are not using eval().

It uses a little class and implements __str__ which is automatically called by print. To escape the limited scope of the class we use the inspect module to hop one frame up and see the variables the caller has access to.

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
寂寞陪衬 2025-01-26 12:08:09

这意味着模板是一个静态字符串,其中包含格式化标签

是的,这正是我们使用带有替换字段和 .format 的文字的原因,因此我们可以随时通过调用 format 来替换字段就可以了。

字符串必须发生一些事情才能告诉解释器将该字符串解释为新的 f 字符串

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

def template_a():
    return 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

This means the template is a static string with formatting tags in it

Yes, that's exactly why we have literals with replacement fields and .format, so we can replace the fields whenever we like by calling format on it.

Something would have to happen to the string to tell the interpreter to interpret the string as a new f-string

That's the prefix f/F. You could wrap it in a function and postpone the evaluation during call time but of course that incurs extra overhead:

def template_a():
    return f"The current name is {name}"

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

Which prints out:

The current name is foo
The current name is bar

but feels wrong and is limited by the fact that you can only peek at the global namespace in your replacements. Trying to use it in a situation which requires local names will fail miserably unless passed to the string as arguments (which totally beats the point).

Is there any way to bring in a string and have it interpreted as an f-string to avoid using the .format(**locals()) call?

Other than a function (limitations included), nope, so might as well stick with .format.

表情可笑 2025-01-26 12:08:09

怎么样:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

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

How about:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

s.format(foo='Bar')
> 'Hi, Bar!'
倾城花音 2025-01-26 12:08:09

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

这是延期记录器的示例。这使用了日志记录的正常序言,但然后添加新功能,仅当日志级别正确时,才能解释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()}”)。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

IMHO:这应该是f-strings的默认操作,但是现在为时已晚。 F弦弦评估可以具有巨大的意外副作用,并且以递延方式发生这种情况将改变程序执行。

为了使F-string适当推迟,Python需要某种明确切换行为的方法。也许使用字母“ G”? ;)

已经指出,如果字符串转换器中有一个错误,则延期记录不应崩溃。以上解决方案也可以执行此操作,最后将更改为 以外:,然后在其中粘贴log.exception

Using .format is not a correct answer to this question. Python f-strings are very different from str.format() templates ... they can contain code or other expensive operations - hence the need for deferral.

Here's an example of a deferred logger. This uses the normal preamble of logging.getLogger, but then adds new functions that interpret the f-string only if the log level is correct.

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)

This has the advantage of being able to do things like: log.fdebug("{obj.dump()}") .... without dumping the object unless debugging is enabled.

IMHO: This should have been the default operation of f-strings, however now it's too late. F-string evaluation can have massive and unintended side-effects, and having that happen in a deferred manner will change program execution.

In order to make f-strings properly deferred, python would need some way of explicitly switching behavior. Maybe use the letter 'g'? ;)

It has been pointed out that deferred logging shouldn't crash if there's a bug in the string converter. The above solution can do this as well, change the finally: to except:, and stick a log.exception in there.

一梦等七年七年为一梦 2025-01-26 12:08:09

F-string只是创建格式化字符串的一种更简洁的方法,用.format(**名称)f替换。如果您不希望以这样的方式对字符串进行评估,请不要使其成为F串。将其保存为普通字符串文字,然后在您要执行插值时,请在其稍后拨打格式

当然,有一个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.format替换为eval,这肯定不值得。只需继续使用格式调用常规字符串即可。

An f-string is simply a more concise way of creating a formatted string, replacing .format(**names) with f. If you don't want a string to be immediately evaluated in such a manner, don't make it an f-string. Save it as an ordinary string literal, and then call format on it later when you want to perform the interpolation, as you have been doing.

Of course, there is an alternative with eval.

template.txt:

f'The current name is {name}'

Code:

>>> 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

But then all you've managed to do is replace str.format with eval, which is surely not worth it. Just keep using regular strings with a format call.

梦中的蝴蝶 2025-01-26 12:08:09

您想要的似乎被视为 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

What you want appears to be being considered as a Python enhancement.

Meanwhile — from the linked discussion — the following seems like it would be a reasonable workaround that doesn't require using 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)

Output:

The current name, number is 'foo', 41
The current name, number is 'bar', 42
百思不得你姐 2025-01-26 12:08:09

或者也许不使用 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))

Or maybe do not use f-strings, just format:

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

In version without names:

fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
    print(fun(name))
两仪 2025-01-26 12:08:09

灵感来自 kadee的答案,可以使用以下来定义延迟的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)

这正是问题所要求的

inspired by the answer by kadee, the following can be used to define a deferred-f-string class.

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)

which is exactly what the question asked for

迷爱 2025-01-26 12:08:09

这些答案中的大多数有时会给你一些行为类似于 f 字符串的东西,但在某些情况下它们都会出错。
pypi 上有一个包 f-yeah 可以完成这一切,只需要花费两个额外的字符! (完全披露,我是作者)

from fyeah import f

print(f("""'{'"all" the quotes'}'"""))

f 字符串和格式调用之间有很多差异,这里是一个可能不完整的列表

  • f 字符串允许任意评估 python 代码
  • f 字符串不能在表达式中包含反斜杠(由于格式化字符串没有表达式,所以我想您可以说这没有区别,但它确实与原始 eval() 可以做的不同)
  • 格式化字符串中的字典查找不得被引用。 f 字符串中的 dict 查找可以被引用,因此也可以查找非字符串键
  • f 字符串具有 format() 没有的调试格式: f"The argument is {spam=}"
  • f 字符串表达式不能为空

使用 eval 的建议将为您提供完整的 f 字符串格式支持,但它们不适用于所有字符串类型。

def f_template(the_string):
    return eval(f"f'{the_string}'")

print(f_template('some "quoted" string'))
print(f_template("some 'quoted' string"))
some "quoted" string
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f_template
  File "<string>", line 1
    f'some 'quoted' string'
            ^
SyntaxError: invalid syntax

在某些情况下,此示例还会出现变量作用域错误。

Most of these answers will get you something that behaves sort of like f-strings some of the time, but they will all go wrong in some cases.
There is a package on pypi f-yeah that does all this, only costing you two extra characters! (full disclosure, I am the author)

from fyeah import f

print(f("""'{'"all" the quotes'}'"""))

There are a lot of differences between f-strings and format calls, here is a probably incomplete list

  • f-strings allow for arbitrary eval of python code
  • f-strings cannot contain a backslash in the expression (since formatted strings don't have an expression, so I suppose you could say this isn't a difference, but it does differ from what a raw eval() can do)
  • dict lookups in formatted strings must not be quoted. dict lookups in f-strings can be quoted, and so non-string keys can also be looked up
  • f-strings have a debug format that format() does not: f"The argument is {spam=}"
  • f-string expressions cannot be empty

The suggestions to use eval will get you full f-string format support, but they don't work on all string types.

def f_template(the_string):
    return eval(f"f'{the_string}'")

print(f_template('some "quoted" string'))
print(f_template("some 'quoted' string"))
some "quoted" string
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f_template
  File "<string>", line 1
    f'some 'quoted' string'
            ^
SyntaxError: invalid syntax

This example will also get variable scoping wrong in some cases.

舂唻埖巳落 2025-01-26 12:08:09

为此,我更喜欢在lambda功能中使用FSTRING,例如:

s = lambda x: f'this is your string template to embed {x} in it.'
n = ['a' , 'b' , 'c']
for i in n:
   print( s(i) )

to do that I prefer to use fstring inside a lambda function like:

s = lambda x: f'this is your string template to embed {x} in it.'
n = ['a' , 'b' , 'c']
for i in n:
   print( s(i) )
淡淡绿茶香 2025-01-26 12:08:09

关于使用str.format()的讨论很多,但是如前所述,它不允许在诸如算术或切片之类的F弦乐中允许的大多数表达式。使用eval()显然也有缺点。

我建议您研究一种模板语言,例如Jinja。对于我的用例,它运行良好。请参阅下面的示例,其中我已经用单个卷曲支撑覆盖了变量注释语法,以匹配F弦语法。我没有完全回顾F-strings和Jinja之间的差异。

from jinja2 import Environment, BaseLoader

a, b, c = 1, 2, "345"
templ = "{a or b}{c[1:]}"

env = Environment(loader=BaseLoader, variable_start_string="{", variable_end_string="}")
env.from_string(templ).render(**locals())

结果

'145'

There's a lot of talk about using str.format(), but as noted it doesn't allow most of the expressions that are allowed in f-strings such as arithmetic or slices. Using eval() obviously also has it's downsides.

I'd recommend looking into a templating language such as Jinja. For my use-case it works quite well. See the example below where I have overridden the variable annotation syntax with a single curly brace to match the f-string syntax. I didn't fully review the differences between f-strings and Jinja invoked like this.

from jinja2 import Environment, BaseLoader

a, b, c = 1, 2, "345"
templ = "{a or b}{c[1:]}"

env = Environment(loader=BaseLoader, variable_start_string="{", variable_end_string="}")
env.from_string(templ).render(**locals())

results in

'145'
梦里泪两行 2025-01-26 12:08:09

我发现这个问题很有趣,并写了我自己的 library 实现懒惰的F-strings。

安装它:

pip install fazy

并使用:

import f

number = 33
print(f('{number} kittens drink milk'))

此解决方案非常适合用于记录。阅读有关链接中文档中的功能和限制的更多信息。

I found this problem quite interesting and wrote my own library implementing lazy f-strings.

Install it:

pip install fazy

And use:

import f

number = 33
print(f('{number} kittens drink milk'))

This solution is well suited, for example, for logging. Read more about the features and limitations in the documentation at the link.

眼泪也成诗 2025-01-26 12:08:09

使用 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  

A suggestion that uses f-strings. Do your evaluation on the
logical level where the templating is occurring and pass it as a generator.
You can unwind it at whatever point you choose, using f-strings

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  
白日梦 2025-01-26 12:08:09

您可以使用.format样式替换,并明确定义替换的变量名称:

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

输出

The current name is foo
The current name is bar

You could use a .format styled replacement and explicitly define the replaced variable name:

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

Output

The current name is foo
The current name is bar
始终不够 2025-01-26 12:08:09

我遇到了这个问题,这与我要做的事情类似,唯一的区别是我需要提早评估的F弦乐的变量的某些,然后将其他F串变量推迟到以后确定。

因此,要从原始示例中借用,这就是我将其删除的方式:

from datetime import date
now = date.today()
names = ["foo", "bar"]

# double bracket allows `name` to be evaluated by the next format() call
template_a = f"The current name is {{name}} and the current date is {now}."

for name in names:
    print (template_a.format(name=name))

I came upon this question which was similar to something I was trying to do, the only difference is that I needed some of the variables for my f-string evaluated early and then defer other f-string variables to be determined later.

So to borrow from the original example, here's how I pulled it off:

from datetime import date
now = date.today()
names = ["foo", "bar"]

# double bracket allows `name` to be evaluated by the next format() call
template_a = f"The current name is {{name}} and the current date is {now}."

for name in names:
    print (template_a.format(name=name))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文