f-string 是 Python 中一种用于字符串插值的语法,它可以让我们在字符串中使用表达式。例如:
>>> name = "Alice"
>>> f"Hello, {name}!"
'Hello, Alice!'
遗留问题
f-string 最初在 PEP 498 中引入,但是当时并没有提供一个正式的语法规则,而且还有有一些限制。这使得 f-string 的解析不能直接在词法分析器(lexer)中完成,而需要额外的后期处理。这些限制包括:
- 不能在表达式中使用和 f-string 相同的引号字符,例如:
>>> f'Magic wand: { bag['wand'] }' ^ SyntaxError: invalid syntax
- 不能在表达式中使用反斜杠,因为这会导致转义序列的问题,例如:
>>> f'Magic wand { bag['wand'] } string' SyntaxError: f-string expression portion cannot include a backslash
- 不能在多行 f-string 中使用注释,例如:
>>> f'''A complex trick: { ... bag['bag'] # recursive bags! ... }''' SyntaxError: f-string expression part cannot include '#'
- 虽然当时提出 PEP 498 时并没有明确限制嵌套,但由于“引号不可复用”的缘故,使得我们无法任意嵌套表达式。这是可以编写的“最嵌套”的 f-string:
嵌套这一点在其他语言中是支持的,例如 Ruby 和 JavaScript:>>> f"""{f'''{f'{f"{1+1}"}'}'''}""" '2'
"# { "# {1+2}" }"
`${ `\${1+2}` }`
改动
从我们 Python 使用者的角度来说,这篇文章到这里应该已经非常明了了,这里稍微概括一下:
从 Python 3.12 开始,
- f-string 中可复用引号,且可嵌套;
- f-string 内可以是任意的 Python 表达式;
- 多行 f-string 支持注释。
如果我在这里结束的话,那属实是有点肤浅了(手动狗头)。
接下来带大家直观的感受一下这个改动在代码的词法解析(lexing)层面的不同~
要了解这一过程,我们需要使用内建的 tokenize
库。不必过度关注以下代码,理解结果即可:
import tokenize
from io import BytesIO
def tokenize_string(code):
tokens = []
code_bytes = code.encode('utf-8')
code_stream = BytesIO(code_bytes)
tokenize_generator = tokenize.tokenize(code_stream.readline)
for token in tokenize_generator:
token_type = tokenize.tok_name[token.type]
tokens.append((token.string, token_type))
return tokens
code_string = "..." # 填上要解析的代码
for token in tokenize_string(code_string):
token_string, token_type = token
print(token_type, '\t', repr(token_string))
让我们看看下面这个例子在 Python 3.12 和 <Python 3.12 中是怎么解析的:
f'some words {a+b:.3f} more words {c+d=} final words'
运行结果
< Python 3.12
ENCODING 'utf-8'
STRING "f'some words {a+b:.3f} more words {c+d=} final words'"
NEWLINE ''
ENDMARKER ''
Python 3.12
ENCODING 'utf-8'
FSTRING_START "f'"
FSTRING_MIDDLE 'some words '
OP '{'
NAME 'a'
OP '+'
NAME 'b'
OP ':'
FSTRING_MIDDLE '.3f'
OP '}'
FSTRING_MIDDLE ' more words '
OP '{'
NAME 'c'
OP '+'
NAME 'd'
OP '='
OP '}'
FSTRING_MIDDLE ' final words'
FSTRING_END "'"
NEWLINE '\n'
ENDMARKER ''
可以看到,在 Python 3.12 之前,f-string 是直接解析成 STRING
标记(token)的(这也造成了许多衍生问题,这里不细讲)。
Python 3.12 引入了 3 个新标记:
FSTIRNG_START
:包含 f-string 前缀(f
、F
、fr
)和开引号。FSTRING_MIDDLE
:包含 f-string 内的不是表达式、也不是{
或}
的文本。FSTRING_END
:包含关引号。
什么是标记?代码是怎么解析的?感兴趣的可以看看这篇文章:我用 Python 创造了一门自己的编程语言
大家怎么看这一次的改动?是让代码更灵活了呢,还是更加难读懂了呢?