学习路径
/
Python
Python
/
教学大纲
/
字符串格式化
字符

字符串格式化 属于 Python

1 个练习

关于 字符串格式化

Python 中的字符串格式化

字符串格式化是指把值转换成字符串,再插入到字符串模板中的过程。 Python 之禅 中说,在 Python 里做一件事应该只有一种 显而易见 的方法。 但一说到字符串格式化,事情就有点……不那么禅了。

令人意外的是,在 Python 里进行字符串格式化主要有 4 种方式,各自面向不同的场景。 这其中一部分源于 Python 悠久的历史,另一部分则出于国际化或输入清洗这类考虑。 我们会从字符串格式化工具箱里最新加入的方式讲起,再一路往回,讲到“旧式”或“printf()风格”的字符串格式化。

字面量字符串插值:f-string

Python 3.6 引入了 f-string(“formatted strings”的简称),也叫字面量字符串插值。在引号前加上 f(或 F)前缀,就能快速高效地把表达式和字符串求值(并格式化)为 str 类型(写法如下:f'{object}')。

f-string 可以用于所有类型的包围引号,例如单行('或")和多行('''或""")。 放进{}里的任何变量、表达式或其他类型,都会先被求值,再转换成 str,最后与花括号外的所有 str 拼接起来。

在下面这个例子里,我们把两个变量的值插入到一句话中(一个str和一个float):

>>> name = 'eighth'
>>> value = 1/8

# The f-string, using the two values.
# The .2f format code truncates, so the value displays as 0.12.
>>> f'An {name} is approximately {value:.2f}.'
'An eighth is approximately 0.12.'

可以求值的表达式几乎无所不包。 可求值的可能性很多,例如:str、数字、变量、算术表达式、条件表达式、内置类型、切片、函数、lambda、推导式,或者任何定义了 __str__ 或 __repr__ 方法的对象。

从简单到复杂,依次来看:

插入变量,这是f-string最简单的用法,就是把变量直接放进字符串里。

# Assigning a variable.
>>> name = "World"

# Inserting that variable.
>>> f'Hello, {name}!'
'Hello, World!'

{}里的表达式,花括号内可以求值任何合法的 Python 表达式。 注意,在单引号f-string里使用双引号(反过来也一样)可以避免使用转义序列:

# A dictionary of key:value pairs.
>>> waves = {'water': 1, 'light': 3, 'sound': 5}

# Inserting the whole dict.
>>> f'Wave ranks: {waves}'
"Wave ranks: {'water': 1, 'light': 3, 'sound': 5}"

# An expression can be evaluated inline:
>>> f"Tenfold the value of 'light' is {waves['light'] * 10}."
"Tenfold the value of 'light' is 30."

# A method call can also be evaluated inline:
>>> f'{"hello world!".title()} is a classic greeting.'
'Hello World! is a classic greeting.'

# An f-string can be nested inside another f-string:
>>> f"{f'hello world!'.title()} is a classic greeting."
'Hello World! is a classic greeting.'

输出格式化,可以用格式说明迷你语言来控制对齐、数字精度等等。 格式说明写在值之后,中间用:分隔。

# Right-align a value to ten characters, and round it to 3 decimal places.
>>> value = 1 / 7
>>> f'One seventh is {value:10.3f}.'
'One seventh is      0.143.'

# A format specification can be set using variables as well.
>>> padding = 10
>>> precision = 3
>>> f'One seventh is {value:{padding}.{precision}f}.'
'One seventh is      0.143.'

综合运用,把变量、表达式、函数调用和输出格式化结合起来:

>>> precision = 3
>>> f"{30e8 * 111_000:6.{precision}e}"
'3.330e+14'

>>> verb = 'meet'
>>> the_end = ['end', 'of', 'transmission']
>>> f'"Have a {"NICE".lower()} day, I will {verb} you after {30e8 * 111_000:6.{precision}e} light-years."{the_end}'
'"Have a nice day, I will meet you after 3.330e+14 light-years."[\'end\', \'of\', \'transmission\']'

# Did you notice the escaped single-quotes in the previous example?
# Using double quotes instead of single quotes for the f-string means the list's single-quoted strings print cleanly.
>>> f"Have a nice day. {the_end}"
"Have a nice day. ['end', 'of', 'transmission']"

有两个主要限制需要注意。 f-string 表达式不能为空。 另外,在 Python 3.12 之前,它们不能包含注释。

>>> f"An empty expression will error: {}"
SyntaxError: f-string: empty expression not allowed

>>> word = 'word'
>>> f"""A comment in a triple quoted f-string: {
    word # I chose a nice variable
}"""
'A comment in a triple quoted f-string: word'
Caution

字符串插值无法与用于国际化(I18N)和本地化(L10N)的 GNU gettext API 一起使用,因此在任何“字符串包装”的翻译场景中,建议用下面介绍的 string.Template 类或 str.format() 方法代替f-string。

另外还要记住,在f-string的括号{}里使用表达式,与使用 eval() 或 exec() 类似,所以并不十分安全,绝不能用于用户输入。

str.format() 方法

str.format() 方法会用作为实参传入的值替换字符串中的占位符。 占位符可以用名称({price})、数字({0} 或索引)标识,甚至可以是空的(位置)占位符{}。

例如:

# A named placeholder and a positional placeholder.
>>> 'My text: {placeholder_1} and {}.'.format(12, placeholder_1='named placeholder')
'My text: named placeholder and 12.'

和f-string一样,Python 的 str.format() 也支持一整套迷你语言格式说明符,可以用来对齐文本、截断浮点数等等。

完整的格式说明符模式是 {[<name>][!<conversion>][:<format_specifier>]}:

  • <name> 可以是具名占位符、数字,或者留空。
  • !<conversion> 是可选的,取值应为以下三种转换之一:!s 对应 str(),!r 对应 repr(),!a 对应 ascii()。 默认使用 str()。
  • :<format_specifier> 是可选的,用来控制值的显示方式。 关于可用选项的更多信息可以在这里找到。

下面是一个带变音符号字母的转换示例:

# Fills in the object at index zero, converted to a string.
>>> 'An e with an umlaut: {0!s}'.format('ë')
'An e with an umlaut: ë'


# Fills in the object at index zero, converted to a repr.
>>> 'An e with an umlaut (object representation): {0!r}'.format('ë')
"An e with an umlaut (object representation): 'ë'"


# Fills in the object at index zero, converted to ASCII.
>>> 'An e with an umlaut converted into ASCII: {0!a}'.format('ë')
"An e with an umlaut converted into ASCII: '\xeb'"


# Fills in the object in the first position,
# then fills in the object in the second position formatted as a repr.
>>> 'She said her name is not {} but {!r}.'.format('Chloe', 'Zoë')
"She said her name is not Chloe but 'Zoë'."

常见格式说明符的示例:

# Integer and binary/hex representations of the same number.
>>> my_num = 42
>>> f"{my_num} in binary is {my_num:b}. In hex, it is {my_num:x}"
"42 in binary is 101010. In hex, it is 2a"

# Alignment: left (<), right (>), and center (^) using up to ten characters total.
>>> f"[{"left":<10}] [{"right":>10}] [{"center":^10}]"
"[left      ] [     right] [  center  ]"

# Float precision and scientific notation (up to three decimal places).
>>> pi = 3.141592653589793
>>> f"Fixed: {pi:.3}  Scientific: {pi:.3e}"
"Fixed: 3.142  Scientific: 3.142e+00"

# Thousands separator and percentage.
>>> balance = 1000
>>> rate = 0.0225
>>> f"Balance: ${balance:,.0f}  Interest rate: {rate:.1%}"
"Balance: $1,000  Interest rate: 2.2%"

# Putting it all together:
>>> items = [("Widget", 1250, 9.991), ("Gadget", 37, 24.503), ("Doohickey", 4, 149.002)]
>>> header = f"{"Item":<12} {"Qty":>6} {"Price":>9}"
>>> print(header)
Item            Qty     Price
>>> for name, qty, price in items:
...     print(f"{name:<12} {qty:>6} {price:>9.2f}")
Widget         1250      9.99
Gadget           37     24.50
Doohickey         4    149.00

更多示例见这份文档末尾。

% 格式化,或 printf() 风格格式化

用%运算符进行格式化,是 Python 中最古老的字符串格式化方式。 它源自 C 语言,可以用位置实参来构建str。

这种方式已经被f-string和 str.format() 取代,所以%格式化又被称为_“旧式”。 在 Python 2 或遗留代码中仍然能看到它。 虽然这种方式在 Python 3.x 中仍然可用,但通常都会避免使用%格式化,因为它容易出错、效率较低、可用选项更少,而且占位符与实参不匹配时会抛出异常。 使用%运算符的方式与 printf() 类似,所以有时也被称为printf 格式化_。

# Assigning a variable.
>>> name = "Anna-conda"

# Building a string using %.
>>> "The snake's name is %s." % name
"The snake's name is Anna-conda."

在上面的例子里,% 运算符在运行时用变量 name 替换占位符 %s。 如果想在字符串里加入多个变量,就需要在%后面提供一个元组,每个占位符对应元组中的一个对象:

# Assigning variables.
>>> name = "Billy the Kid"
>>> fruit = "grapes"

# Building a string using %.
>>> "Surprisingly, %s's favorite snack was %s." %(name, fruit)
"Surprisingly, Billy the Kid's favorite snack was grapes."

模板字符串

string.Template()(不要和 Python 3.14 的 t-strings 混淆)是 string 模块里的一个类(不同于内置的 str 类型)。它属于 Python 标准库,但使用时需要先导入。 模板字符串支持基于$的替换,比这里提到的其他方式都更简单,功能也更弱。 不过,在处理复杂的国际化或清洗外部输入时,它能派上大用场。

对于不受信任的用户输入,string.Template 被认为更安全,因为它不会求值任意表达式,也不会访问对象属性,从而缓解格式字符串注入攻击。

>>> from string import Template

>>> name = "Anna-Conda"

# Creating a Template with placeholder text.
>>> template_string = Template("The snake called `$snake_name` has escaped!")

# Calling .substitute() to replace the placeholder with a value.
>>> template_string.substitute(snake_name=name)
'The snake called `Anna-Conda` has escaped!'

关于 string.Template 的更多信息,可以查阅 Python 的文档。

如何选择使用哪种格式化方式?

面对这么多选项和迷你语言,格式化 Python 字符串时到底该用哪一个呢? 这里有几条简单的准则:

  1. f-string 最新,也最易读。 如果你的代码不需要国际化,那么在 Python 3.6 及以上版本中,它应当作为首选。

  2. str.format() 用途广泛、功能强大,既能配合 gnu gettext,也兼容大多数 Python 版本。

  3. 如果你需要的是简单、安全,或者大量的国际化处理,那么 string.Template() 可以在处理用户输入时降低风险,也适合包装翻译字符串。

  4. 对于新代码,% 运算符通常被认为已过时,尽管它在现代 Python 中仍然可用。 它主要用于兼容旧的代码库。 相比其他方式,% 格式化更容易出错(功能也更少),还可能在显示非 ASCII 字符时出问题。 如果你打算使用它,请查阅你所用的 Python 发行版,了解具体的支持情况。

如果你想深入了解,all about formatting 和 Python String Formatting Best Practices 都是不错的起点。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开