我能否将Python 3.6的格式化字符串字面量(f-string)导入旧版本的3.x、2.x Python?

61
新的Python 3.6 f-strings对于字符串的可用性来说似乎是一个巨大的飞跃,我很想投入并全心全意地采用它们用于可能在旧解释器上运行的新项目。虽然2.7和3.3-3.5支持会很好,但至少我想在Python 3.5代码库中使用它们。我该如何导入3.6的格式化字符串字面值以供旧解释器使用?
我知道像f"Foo is {age} {units} old"这样的格式化字符串字面值不是破坏性的更改,因此不会包含在from __future__ import ...中。但是这个改变(据我所知)没有被回溯到较早版本,我需要确保我使用f-strings编写的任何新代码仅在Python 3.6+上运行,这对于许多项目来说是无法接受的。
8个回答

49

future-fstrings 将 f-strings 带到 Python 2.7 脚本中。(根据文档,我假设还支持 3.3-3.5 版本。)

一旦通过 pip install future-fstrings 安装它,您需要在代码的顶部放置一个特殊的行。那行是:

# -*- coding: future_fstrings -*-

然后你可以在代码中使用格式化字符串字面值(f-strings):

# -*- coding: future_fstrings -*-
var = 'f-string'
print(f'hello world, this is an {var}')

2
很不幸,这会破坏emacs:Warning (mule): Invalid coding system future_fstrings' is specified`。 - Muposat
3
如果你删除 -*- 位,Python仍然能够识别它。 - Muposat
我只是用它来为我进行自动代码转换:安装后,运行future-fstrings-show main.py。然后我复制/粘贴代码。这在jupyter中无法“开箱即用”时很有效(不确定是否有意)。但是,如果你只想要自动代码转换,那么你可能可以自己动手做。 - Ying Zhang
不需要在文件开头添加编码字符串,您可以尝试在 Python 脚本开头添加以下代码:import future_fstrings future_fstrings.register() - lytseeker

18

不幸的是,如果你想使用它,你必须要求 Python 3.6+ 版本,与矩阵乘法运算符 @Python 3.5+ 或者 yield from (Python 3.4+,我想) 相同。

这些对代码的解释方式进行了更改,因此在旧版本中导入时会引发 SyntaxErrors。 这意味着你需要将它们放在旧版本 Python 中没有导入它们的地方,或者通过 evalexec 进行保护(我不建议使用后两种方法!)。

所以,是的,你说得对,如果你想支持多个 Python 版本,就不能轻松地使用它们。


有没有任何理由不使用下面回答中提到的`future-fstrings'包?我很感激这个选项可能在你回答这个问题时还没有可用。 - Siwel
4
@Siwel 这是一种非常聪明的方法来进行后移植。我认为没有任何严重的理由不使用它。但是使用自定义编码可能会与某些IDE冲突。我认为这并不违反 PEP 263 定义Python源代码编码,但它并不是“编码行”设定的真正用例。不过,这是一种非常聪明的方法,我将在将来肯定测试它。 - MSeifert
1
如果传递了locals()变量字典,format_map可以成为一个有用的解决方法...我自己没有测试过,但应该可以工作。 - arod

14

这是我使用的:

text = "Foo is {age} {units} old".format(**locals())

它解压(**)由locals()返回的字典,其中包含您所有的本地变量作为一个字典{variable_name: value}

注意: 这对于在外部作用域中声明的变量将不起作用,除非您使用nonlocal将其导入到本地作用域中(仅适用于Python 3.0+)。

您还可以使用

text.format(**locals(),**globals())

在字符串中包含全局变量。


5
这个方法可行,但我认为这种“hack”应该尽量避免使用! - Næreen
如果字符串由外部来源提供,则这是有风险的。 - maazza

7

在解析 f 前缀时,解释器会创建 f-strings - 这个特性单独就足以导致不兼容。

你最接近的选择是使用关键字格式化,例如

'Foo is {age} {units} old'.format(age=age, units=units)

如果不再需要兼容性,就可以更轻松地重构它。


2
如果使用此方法,并假设年龄和单位已经是变量,那么在Python2.7中更好的写法可能是'Foo is {age} {units} old'.format(age=age, units=units),这样在转移到Python3.6时可以快速更新为f'Foo is {age} {units} old' - petiepooo
'Foo is {age} {units} old'.format(**locals(), **globals()) 这个怎么样? - Mad Physicist
1
@MadPhysicist,虽然可行但被认为是不好的形式,因为在格式调用中包含了所有可能需要的参数。 - Gringo Suave
@GringoSuave。同意,但这就是一个实际 f-string 的评估方式。 - Mad Physicist
@MadPhysicist,不是真的。这是一个明确的设计目标。在编译时,字符串被解析成字符串和表达式部分,并且表达式部分是正常的Py代码。因此,仅引用命名的变量,而不是整个命名空间。这有些理论,但是非常明确。 - Gringo Suave

4

我刚刚为f-string编写了一个回溯编译器,名为f2format。就像您的要求一样,您可以在Python 3.6中编写f-string文字,并将其编译成兼容版本供最终用户运行,就像JavaScript的Babel一样。

f2format提供了一个智能但不完美的回溯编译器解决方案。它将使用str.format方法替换f-string文字,同时保持源代码的原始布局。您只需使用

f2format /path/to/the/file_or_directory

即可原地重写所有Python文件。例如,

var = f'foo{(1+2)*3:>5}bar{"a", "b"!r}boo'

将被转换为

var = ('foo{:>5}bar{!r}boo').format(((1+2)*3), ("a", "b"))

字符串拼接、转换、格式规范、多行和Unicode都被正确处理。此外,f2format会在出现任何语法违规时存档原始文件。


我希望它是开源的,因为我不想让一个随机程序运行在我的源代码上 :( - Walter
2
@Walter 是的,它是在 Apache License 2.0 下的开源项目;只需查看 repo 即可 :) - Jarry Shaw

3

我之前一直在使用'str'.format(**locals()),但是后来觉得每个语句都需要额外的代码有点繁琐,于是写了这个。

def f(string):
    """
    Poor man's f-string for older python versions
    """
    import inspect

    frame = inspect.currentframe().f_back

    v = dict(**frame.f_globals)
    v.update(**frame.f_locals)

    return string.format(string, **v)

# Example
GLOBAL = 123


def main():
    foo = 'foo'
    bar = 'bar'

    print(f('{foo} != {bar} - global is {GLOBAL}'))


if __name__ == '__main__':
    main()

我一直在寻找一种自己定制的方式。这就是它。我缺少的那一部分是inspect模块。 - ibonyun

0

使用dict()来保存名称-值对

  • 除了在本主题中提到的方法(如format(**locals())),开发人员可以创建一个或多个Python字典来保存名称-值对。

  • 对于有经验的Python开发人员来说,这是一种显而易见的方法,但很少有讨论明确列举这个选项,可能是因为它是如此显而易见。

  • 相对于不加选择地使用locals(),这种方法可以说是更有优势的。它明确地使用一个或多个字典作为命名空间与您的格式化字符串一起使用。

  • Python 3还允许解包多个字典(例如,.format(**dict1,**dict2,**dict3)...这在Python 2.7中不起作用)

  ## 初始化字典
  ddvars = dict()
## 分配固定的值 ddvars ['firname'] = 'Huomer' ddvars ['lasname'] = 'Huimpson' ddvars ['age'] = 33 pass
## 分配计算出来的值 ddvars ['comname'] = '{firname} {lasname}'.format(**ddvars) ddvars ['reprself'] = repr (ddvars) ddvars ['nextage'] = ddvars ['age'] + 1 pass
## 创建并显示一个示例消息 mymessage = ''' 你好 {firname} {lasname}! 今天你 {age} 岁了。 在你下一个生日时,你将 {nextage} 岁了!   '''.format (**ddvars)
print(mymessage)

0

使用 simpleeval 的一种不太优秀的解决方案

import re
import simpleeval
test='_someString'
lst = ['_456']

s = '123123{lst[0]}{test}'

def template__format(template, context=None):
    if context is None:
        frame = inspect.currentframe()
        context = frame.f_back.f_locals        
        del frame
    ptn =  '([^{]?){([^}]+)}'
    class counter():
        i = -1

    def count(m):
        counter.i += 1
        return m.expand('\\1{%d}'%counter.i)

    template = re.sub(ptn,string=s, repl= count)
    exprs = [x[1] for x in re.findall(ptn,s)]
    vals = map(simpleeval.SimpleEval(names=context).eval,exprs)
    res = template.format(*vals)
    return res

print (template__format(s))


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