字符串插值是将变量值替换为字符串中占位符的过程。这是 Python 中的一项强大功能,使您能够通过在运行时将变量的值嵌入或替换到字符串中来创建动态字符串。
Python支持多种格式化字符串和执行字符串插值的方式,使得格式字符串更容易维护,也更容易动态修改。
使用 % 运算符的 Python 字符串插值
The % 操作员允许您使用格式说明符,例如%s
对于字符串,%d
对于整数,以及%f
对于浮点数。
name = "Alice"
age = 25
height = 1.68
print("Hello, my name is %s, I'm %d years old and I'm %.2f meters tall." % (name, age, height))
Output:
Hello, my name is Alice, I'm 25 years old and I'm 1.68 meters tall.
在本例中,我们使用了%s
, %d
, and %.2f
分别用于字符串、整数和小数点后两位数字的浮点数的格式说明符。
然而,使用该方法有一些限制%
操作员:
- 随着变量数量或复杂性的增加,它可能更容易出错并且更难以维护。
- 存在元组大小与字符串中占位符数量不匹配的风险。
str.format()
Python 引入了str.format()
Python 3 中的方法,它提供了一种格式化字符串的新方法。
此方法使用占位符,用大括号表示{}
,您可以在其中放置变量名称。
name = "Bob"
age = 45
print("Hello, my name is {} and I'm {} years old.".format(name, age))
Output:
Hello, my name is Bob and I'm 45 years old.
在此,论据format
函数放在花括号内{}
按照它们通过的顺序。这种文字字符串插值方法比使用%
操作员。
位置参数
With str.format()
,您可以使用位置参数,它允许重新排列显示顺序而不更改传递的参数。
name = "Carol"
age = 50
print("Hello, I'm {1} years old and my name is {0}.".format(name, age))
Output:
Hello, I'm 50 years old and my name is Carol.
大括号内的数字表示参数的位置。这是一个非常强大的功能,因为它可以重新排列显示顺序而不更改传递的参数。
命名参数
您还可以按名称引用变量替换,并在format
method.
print("Hello, I'm {age} years old and my name is {name}.".format(name="David", age=55))
Output:
Hello, I'm 55 years old and my name is David.
通过在大括号内使用变量名称,可以更轻松地理解哪个变量在何处被替换,特别是当代码中要替换的字符串和数据相距很远时。
它通过名称进行替换并使用命名参数format
方法清晰且可维护。
格式规范迷你语言
Python 还提供了一种迷你语言来指定替换字段的格式。当您需要控制字段的宽度、数据的对齐方式、小数位数等时,这尤其方便。
import math
print("The value of pi is approximately {0:.3f}.".format(math.pi))
Output:
The value of pi is approximately 3.142.
Here, {0:.3f}
表示我们要将第一个参数格式化为小数点后 3 位的浮点数。
模板字符串
The string
Python 中的模块提供了另一种使用 Template 类执行字符串插值的方法。
from string import Template
t = Template('Hello, my name is $name and I am $age years old.')
s = t.substitute(name='Emma', age=35)
print(s)
Output:
Hello, my name is Emma and I am 35 years old.
在这里,我们首先导入Template
类来自string
模块。然后,我们使用包含占位符变量的字符串创建一个模板,其前缀为$
symbol.
最后,我们调用substitute
模板对象上的方法并传递变量映射来替换占位符。
此方法功能较弱,但对于简单替换或当格式字符串由用户提供时更加用户友好。
F 弦
从 Python 3.6 开始,引入了一种新的字符串格式化机制,称为 f-strings(格式化字符串文字)。
这种新的字符串格式化机制使 Python 字符串插值更加可读和简洁。
name = "Frank"
age = 60
print(f"Hello, my name is {name} and I'm {age} years old.")
Output:
Hello, my name is Frank and I'm 60 years old.
在此代码中,f 字符串是一个文字字符串,以“f”为前缀,其中包含大括号内的表达式。
表达式在运行时计算,并将它们的值插入到字符串中。
嵌入表达式
f 字符串的优点之一是能够在字符串文字中嵌入任意 Python 表达式。
name = "Gary"
age = 65
print(f"{name} will be {age + 5} years old in five years.")
Output:
Gary will be 70 years old in five years.
在这里,我们在 f 字符串内执行算术运算。表达方式age + 5
在运行时评估并将其结果插入到字符串中。
格式说明符
F 字符串支持与str.format()
method.
import math
print(f"The value of pi is approximately {math.pi:.3f}.")
Output:
The value of pi is approximately 3.142.
在此示例中,大括号内的 Python 表达式为math.pi
, and :.3f
是格式说明符,表示我们要将结果格式化为小数点后 3 位的浮点数。
内联算术和函数调用
您甚至可以使用 f 字符串进行内联算术运算、函数调用等。
x = 10
y = 20
print(f"The sum of {x} and {y} is {x + y}.")
print(f"The square root of {x} is {math.sqrt(x):.2f}.")
Output:
The sum of 10 and 20 is 30.
The square root of 10 is 3.16.
在这里,我们直接在字符串内执行算术运算和函数调用。这些操作和调用的结果在运行时评估和插入。
Lambda 和 f 弦
您还可以在 f 字符串中使用 lambda 函数。
x = 10
y = 20
print(f"The maximum of {x} and {y} is {(lambda a, b: a if a > b else b)(x, y)}.")
Output:
The maximum of 10 and 20 is 20.
在此示例中,我们在 f 字符串的大括号内定义了一个 lambda 函数,用于计算两个数字的最大值。我们立即调用这个函数x
and y
作为参数。
多行 f 字符串
F 字符串也可以跨越多行。当您想要创建在单个字符串中包含多个替换的复杂字符串时,这会很有用。
x = 10
y = 20
z = 30
multi_line_f_string = (
f"The value of x is {x}."
f"The value of y is {y}."
f"The sum of x and y is {x + y}."
f"The value of z is {z}."
f"The sum of x, y, and z is {x + y + z}."
)
print(multi_line_f_string)
Output:
The value of x is 10.The value of y is 20.The sum of x and y is 30.The value of z is 30.The sum of x, y, and z is 60.
在这里,我们通过将整个 f 字符串括在括号内来创建多行 f 字符串。
每一行都是一个单独的 f 字符串,它们全部连接在一起形成一个长字符串。
动态格式化
动态格式化允许您在运行时确定输出字符串的格式。
您可以使用 f 字符串来完成此操作:
for align in ['<', '^', '>']:
print(f"{'hello':{align}10}")
Output:
hello
hello
hello
在此示例中,我们使用带有动态格式说明符的 f 字符串。
在花括号内{}
, align
是一个变量,它是格式说明符的一部分,其值随着循环的每次迭代而变化。
The 10
下列的{align}
是格式化字符串的字段宽度。取决于值align
,字符串“hello”与字段的左侧、中心或右侧对齐。
F 字符串注入攻击以及如何避免它们
如果您不小心在 f 字符串中计算哪些表达式,则可能会发生 F 字符串注入。
如果 f 字符串的任何部分是根据用户输入或外部源构造的,则存在攻击者注入恶意代码的风险。
这可能导致任意代码执行、内存泄漏,甚至崩溃。
import sys
user_input = 'sys.exit()'
dangerous_f_string = f'Hello, {eval(user_input)}!'
Here, eval(user_input)
会导致Python执行sys.exit()
,你的程序就会退出。
为了避免这种情况:
切勿从不受信任的输入构造格式字符串:这是防止格式字符串攻击的最直接的方法。始终确保用于构造格式字符串的任何数据都不是来自不受信任的或外部来源。
将模板字符串用于用户提供的格式字符串: The string.Template
Python中的类提供了一种更安全的方式来实现用户定义的格式字符串。
from string import Template
t = Template('Hello, ${name}!')
user_input = 'sys.exit()'
print(t.safe_substitute(name=user_input))
性能评估(f-string 更快)
我们来进行一个简单的性能评估,来比较Python中四种不同字符串插值方法的速度。
我们将使用timeit
模块来测量每种方法所花费的时间。
import timeit
name = "Henry"
age = 70
profession = "doctor"
hobby = "gardening"
location = "New York"
# Using % operator
time_percent_op = timeit.timeit("'Hello, my name is %s. I am %s years old, a %s from %s, and I love %s.' % (name, age, profession, location, hobby)", globals=globals())
print("% Operator: ", time_percent_op)
# Using str.format()
time_str_format = timeit.timeit("'Hello, my name is {}. I am {} years old, a {} from {}, and I love {}.'.format(name, age, profession, location, hobby)", globals=globals())
print("str.format(): ", time_str_format)
# Using Template strings
time_template_str = timeit.timeit("Template('Hello, my name is $name. I am $age years old, a $profession from $location, and I love $hobby.').substitute(name=name, age=age, profession=profession, location=location, hobby=hobby)", setup="from string import Template", globals=globals())
print("Template strings: ", time_template_str)
# Using f-strings
time_f_strings = timeit.timeit("f'Hello, my name is {name}. I am {age} years old, a {profession} from {location}, and I love {hobby}. '", globals=globals())
print("f-strings: ", time_f_strings)
Output:
% Operator: 1.0545442999864463
str.format(): 1.1527161999838427
Template strings: 9.305830199999036
f-strings: 0.5057788999984041
由于其编译时表达式求值,f 字符串往往比其他方法表现得更好。
然而,对于小字符串来说差异并不显着,还应该考虑可读性和安全性等其他因素。
将字符串插值与正则表达式结合使用
使用正则表达式时,字符串插值非常有用。让我们用一个例子来证明这一点:
import re
pattern = "fox"
text = "The quick brown fox jumps over the lazy dog"
regex = re.compile(fr"\b{pattern}\b") # Using f-string
matches = regex.findall(text)
print(matches)
Output:
['fox']
在此代码中,我们创建一个与单词“fox”匹配的正则表达式。 f 弦fr"\b{pattern}\b"
允许我们动态插入变量的值pattern
到正则表达式中。
Summary
总而言之,这里有一个简单的表格,其中包括 Python 中字符串插值的每种方法的优点和缺点:
Method |
Strengths |
Weaknesses |
`%` operator |
Simple and straightforward for basic usage, familiar to C programmers |
Less readable with multiple substitutions, type specification is mandatory |
`str.format()` |
Improved readability, positional and keyword substitutions, versatile formatting options |
More verbose compared to f-strings. |
Template Strings |
User-friendly syntax, ideal for simple substitutions or user-supplied format strings, and secured against injection attacks. |
Limited functionality, no support for complex expressions or custom formatting, slower execution time |
f-strings |
Concise and readable, supports inline expressions and complex formatting, faster execution time |
Only available in Python 3.6 and above, potential security risk if format string is constructed at runtime |
请记住,最合适的方法通常取决于您的具体用例。根据上下文,无论是格式的复杂性、性能考虑还是您正在使用的 Python 版本,这些方法中的每一种都可能更适合。