我可以推迟对f字符串的评估吗?

时间:2021-11-04 21:45:06

I am using template strings to generate some files and I love conciseness of the new f-strings for this purpose.

我正在使用模板字符串来生成一些文件,为此,我喜欢新f字符串的简洁性。

Previously I would do 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:

现在我可以这样做:

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.

但是,有时在其他地方定义模板是有意义的 - 在代码中更高,或从文件或其他东西导入。这意味着模板是一个带有格式标签的静态字符串。必须在字符串上发生一些事情,告诉解释器将字符串解释为新的f字符串,但我不知道是否存在这样的事情。

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

有没有办法引入一个字符串并将其解释为一个f字符串,以避免使用.format(** locals())调用?

Ideal:

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

Desired Output:

The current name is foo
The current name is bar

Actual Output:

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

Edit:

Ideal 2:

template.txt contents are The current name is {name}. The magic_fstring_function is where the part I don't understand comes in.

template.txt的内容是当前名称是{name}。 magic_fstring_function是我不理解的部分所在。

template_a = magic_fstring_function(open('template.txt').read())
names = ["foo", "bar"]
for name in names:
    print (template_a)

Desired Output (without reading the file twice):

期望的输出(不读取文件两次):

The current name is foo
The current name is bar

Actual Output:

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

6 个解决方案

#1


6  

Here's a complete "Ideal 2".

这是一个完整的“理想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 head aches since we are not using eval.

它不是一个甚至不使用f字符串的f字符串。但它按要求提供。完全按照指定的语法。因为我们没有使用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.

它使用一个小类并实现__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

#2


9  

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.

f-string只是创建格式化字符串的简洁方式,用f替换.format(** names)。如果您不希望以这种方式立即评估字符串,请不要将其设为f字符串。将其保存为普通的字符串文字,然后在您想要执行插值时调用格式,就像您一直在做的那样。

Of course, there is an alternative with eval.

当然,还有eval的替代方案。

template.txt:

f'The current name is {name}'

f'当前名称是{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.

但是你所要做的就是用eval替换str.format,这肯定是不值得的。只需继续使用带有格式调用的常规字符串。

#3


3  

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.

是的,这正是为什么我们有文字替换字段和.format,所以我们可以随时通过调用格式替换字段。

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

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

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:

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

template_a = lambda: 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?

有没有办法引入一个字符串并将其解释为一个f字符串,以避免使用.format(** locals())调用?

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

除了一个函数(包括限制),nope,所以不妨坚持使用.format。

#4


3  

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

或者也许不使用f-strings,只需格式化:

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

#5


0  

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.

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

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.

这是一个延迟记录器的示例。这使用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)

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

这样做的好处是能够执行以下操作:log.fdebug(“{obj.dump()”)....而不会转储对象,除非启用调试。

#6


0  

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

一个使用f字符串的建议。在模板发生的逻辑层面进行评估并将其作为生成器传递。您可以使用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  

#1


6  

Here's a complete "Ideal 2".

这是一个完整的“理想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 head aches since we are not using eval.

它不是一个甚至不使用f字符串的f字符串。但它按要求提供。完全按照指定的语法。因为我们没有使用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.

它使用一个小类并实现__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

#2


9  

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.

f-string只是创建格式化字符串的简洁方式,用f替换.format(** names)。如果您不希望以这种方式立即评估字符串,请不要将其设为f字符串。将其保存为普通的字符串文字,然后在您想要执行插值时调用格式,就像您一直在做的那样。

Of course, there is an alternative with eval.

当然,还有eval的替代方案。

template.txt:

f'The current name is {name}'

f'当前名称是{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.

但是你所要做的就是用eval替换str.format,这肯定是不值得的。只需继续使用带有格式调用的常规字符串。

#3


3  

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.

是的,这正是为什么我们有文字替换字段和.format,所以我们可以随时通过调用格式替换字段。

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

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

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:

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

template_a = lambda: 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?

有没有办法引入一个字符串并将其解释为一个f字符串,以避免使用.format(** locals())调用?

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

除了一个函数(包括限制),nope,所以不妨坚持使用.format。

#4


3  

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

或者也许不使用f-strings,只需格式化:

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

#5


0  

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.

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

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.

这是一个延迟记录器的示例。这使用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)

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

这样做的好处是能够执行以下操作:log.fdebug(“{obj.dump()”)....而不会转储对象,除非启用调试。

#6


0  

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

一个使用f字符串的建议。在模板发生的逻辑层面进行评估并将其作为生成器传递。您可以使用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