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

JDA*_*ers 59 python string-interpolation python-3.x python-3.6 f-string

我正在使用模板字符串来生成一些文件,我喜欢为此目的的新f字符串的简洁性,以减少我之前的模板代码,如下所示:

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

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

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

但是,有时在其他地方定义模板是有意义的 - 在代码中更高,或从文件或其他东西导入.这意味着模板是一个带有格式标签的静态字符串.必须在字符串上发生一些事情,告诉解释器将字符串解释为新的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)
Run Code Online (Sandbox Code Playgroud)

...使用此期望的输出(不读取文件两次):

The current name is foo
The current name is bar
Run Code Online (Sandbox Code Playgroud)

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

The current name is {name}
The current name is {name}
Run Code Online (Sandbox Code Playgroud)

Pau*_*zer 13

这是一个完整的"理想2".

它不是一个甚至不使用f字符串的f字符串.但它按要求提供.完全按照指定的语法.因为我们没有使用eval,所以没有安全问题.

它使用一个小类和实现eval(),它通过print自动调用.为了逃避类的有限范围,我们使用__str__模块向上跳一帧并查看调用者可以访问的变量.

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
Run Code Online (Sandbox Code Playgroud)

  • @bli有趣,似乎是`str.format`的限制.我曾经认为f字符串只是`str.format(**locals(),**globals())的语法糖,但显然我错了. (4认同)
  • 我将接受这个作为答案,虽然我不认为我会在代码中实际使用它,因为它非常聪明.好吧也许永远不会:).也许python人可以用它来实现[PEP 501](https://www.python.org/dev/peps/pep-0501/).如果我的问题是"我应该如何处理这种情况",答案就是"继续使用.format()函数并等待PEP 501解决." 感谢你弄清楚如何做不应该做的事,@ PaulPanzer (3认同)
  • 当模板包含比简单变量名称更复杂的内容时,这将不起作用。例如:`template =“名称的开头是{name [:4]}”“(->`TypeError:字符串索引必须是整数`) (3认同)
  • 请不要在生产中使用它。“检查”是一个危险信号。 (2认同)
  • 我有两个问题,为什么要检查生产的“危险信号”,像这样的情况会是例外还是会有更可行的解决方法?为了减少内存使用,是否有人反对在这里使用“__slots__”? (2认同)

Tig*_*kT3 13

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
Run Code Online (Sandbox Code Playgroud)

不过,你已经成功地完成所有的替换str.formateval,这肯定是不值得的.只需通过format调用继续使用常规字符串.

  • 在您的代码片段中,我真的看不到任何优势。我的意思是,您总是可以只在`template.txt`文件中写入`当前名称为{name}`,然后使用`print(template_a.format(name = name))`(或`.format(** locals ())`)。该代码长了约10个字符,但是由于`eval`,它不会引起任何可能的安全性问题。 (2认同)
  • “ f字符串只是创建格式化字符串的一种更为简洁的方法,用。替换.format(** names)。” 不太完全-他们使用不同的语法。我没有最近可用的python3,但是例如,我相信f'{a + b}'可以工作,而'{a + b}'。format(a = a,b = b)会引发KeyError 。.format()在许多情况下可能都很好,但是它不是临时替代品。 (2认同)
  • @philh 我想我刚刚遇到了一个例子,其中 `.format` 不等同于一个 f 字符串,它可以支持你评论:`DNA = "TATTCGCGGAAAATTTTGA"; 片段 = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())`。尝试创建 `failed_fragment` 会导致 `TypeError: string indices must be integers`。 (2认同)

Jim*_*ard 9

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

是的,这就是为什么我们的文字带替换字段和.format,所以我们可以随时通过调用来替换字段format

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

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

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

打印出:

The current name is foo
The current name is bar
Run Code Online (Sandbox Code Playgroud)

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

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

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


msz*_*man 9

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

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

在没有名字的版本中:

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


kad*_*dee 9

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

def fstr(template):
    return eval(f"f'{template}'")
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

  • 这个答案的一个小扩展 - 您可以使用 `f'f"""{template}"""'` 来处理其中包含换行符的模板。在当前的实现中,如果您的模板具有换行符,您将得到类似“扫描字符串文字时语法错误:EOL”的信息。 (5认同)
  • @martineau它应该是python的一个功能,这样你就不需要使用eval...另外,f-string与eval()具有相同的风险,因为你可以将任何内容放在大括号中,包括恶意代码,所以如果这是如果有问题,请不要使用 f 字符串 (3认同)
  • 迄今为止最好的答案!当他们引入f字符串时,它们怎么不将这种简单的实现作为内置功能包括在内? (2认同)
  • 不,这失去了范围。唯一有效的原因是因为 `name` 是全局的。f-strings *应该*在评估中被推迟,但类 FString 需要通过查看调用者的局部变量和全局变量来创建对作用域参数的引用列表......然后在使用时评估字符串。 (2认同)
  • @user3204459:因为能够执行任意字符串本质上是一种安全隐患——这就是为什么通常不鼓励使用“eval()”。 (2认同)
  • @user3204459 这是不同的风险:eval 允许从任何不可靠的源(文件、命令行、Web 服务、表单字段等)的字符串运行任意代码。f 字符串包含的代码只能来自源代码本身。当然,它仍然会造成伤害(例如,如果您编写类似 f'result is { os.system( userSuppliedCommand ) }' 的内容),但我发现使用它比使用 eval 更容易编写更安全的代码。 (2认同)
  • 这正是我一直在寻找的,回避“fstr推迟”。一般来说,Eval似乎并不比使用fstring差,因为我猜它们都拥有相同的功能:f“{eval('print(42)” ')}" (2认同)

mar*_*eau 9

您想要的似乎被视为 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)
Run Code Online (Sandbox Code Playgroud)

输出:

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)
Run Code Online (Sandbox Code Playgroud)


use*_*459 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)
Run Code Online (Sandbox Code Playgroud)

这正是问题所要求的


Eri*_*sty 5

使用.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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 完全同意这个答案。这个用例是我在搜索这个问题时所想到的。 (2认同)
  • 这是正确的答案。一些计时: `%timeit log.finfo(f"{bar=}") 91.9 µs ± 7.45 µs 每个循环 %timeit log.info(f"{bar=}") 56.2 µs ± 630 ns 每个循环(平均值 ± 标准差) . 7 次运行的开发,每次 10000 次循环) log.setLevel(logging.CRITICAL) %timeit log.finfo("{bar=}") 575 ns ± 2.9 ns 每个循环 %timeit log.info(f"{bar= }") 每个循环 480 ns ± 9.37 ns %timeit log.finfo("") 每个循环 571 ns ± 2.66 ns %timeit log.info(f"") 每个循环 380 ns ± 0.92 ns %timeit log.info(" ") 每个循环 367 ns ± 1.65 ns(7 次运行的平均值 ± 标准差,每次 1000000 次循环) ` (2认同)

Den*_*nis 5

怎么样:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

s.format(foo='Bar')
> 'Hi, Bar!'
Run Code Online (Sandbox Code Playgroud)

  • 可以,只是没那么漂亮。`'2 * {x} = {y}'.format(x = 2, y = 2 * x)` 或 `'2 * {} = {}'.format(x, 2 * x)` (2认同)

小智 5

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

from fyeah import f

print(f("""'{'"all" the quotes'}'"""))
Run Code Online (Sandbox Code Playgroud)

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

  • f 字符串允许任意评估 python 代码
  • f 字符串不能在表达式中包含反斜杠(因为格式化字符串没有表达式,所以我想你可以说这没有区别,但它确实与原始 eval() 可以做的不同)
  • 格式化字符串中的 dict 查找不得加引号。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"))
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

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