本文主要是介绍关于字符串格式化的惊人真相,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
关于字符串格式化的惊人真相
记住Python之禅,即“做某事有一个明显的方法?”当发现有四种主要的方式来完成Python中的字符串格式化时,你可能会感到困惑。
我将展示这四种字符串格式化方法的工作原理,以及它们各自的优点和缺点。同时,我还将提供我自己简单实用的“拇指规则”,来指导我在选择通用目的字符串格式化方法时的最佳实践。
好的,让我们直接进入主题,因为我们有很多内容要涵盖。为了有一个简单的实验示例尝试,我们假设需要使用以下变量(实际上是常量)来进行操作:
>>> errno = 50159747054
>>> name = 'Bob'
根据这些变量,我们希望生成一条包含以下错误消息的输出字符串:
'Hey Bob, there is a 0xbadc0ffee error!'
现在,那个错误确实可能让开发者在周一早上就心情糟糕!但我们今天聚在这里就是为了讨论字符串格式化的问题。所以让我们开始行动吧。
# 1—老式字符串格式化(Old Style String Formatting)
这是指在Python早期版本中,使用特定方法来格式化字符串的一种方式。随着新版本的推出,这种方式已经被现代、更简洁的字符串格式化方法所取代。在Python中,字符串具有一个独特的内置操作,可以通过%-操作符访问。这是一个快捷方式,让你能够非常简单地进行位置格式化。如果你曾经在C语言中使用类似于printf风格的函数,你会立刻明白这种行为是如何工作的。示例如下:
>>> 'Hello, %s' % name
'Hello, Bob'
我在这里使用%s
格式符,这是告诉Python在哪个位置替换name
这个值。这种行为被称为"老式"字符串格式化。简而言之,你在代码中指定一个占位符,然后提供一个实际的值来填充它,这就是"老式"字符串格式化的基本原理。
在"老式"字符串格式化中,除了%s
这样的占位符外,还有其他格式符可供选择,以控制输出的字符串。例如,可以将数字转换为十六进制表示,或者添加空白填充来生成结构良好、格式美观的表格和报告。
这里,我使用%x格式符将一个整型数值转成你一个字符串并以一个十六进制数字表示:
>>> '%x' % errno
'badc0ffee'
如果要在单个字符串中进行多个替换,"老式"的字符串格式化语法会稍有变化。由于%操作符只能接受一个参数,所以你需要将即要替换的部分包裹在一个元组中。例如:
>>> 'Hey %s, there is a 0x%x error!' % (name, errno)
'Hey Bob, there is a 0xbadc0ffee error!'
如果你将一个映射传递给%操作符,你还可以在格式字符串中通过变量名称引用替换的值。
>>> 'Hey %(name)s, there is a 0x%(errno)x error!' % {'name': name, 'errno': errno}
'Hey Bob, there is a 0xbadc0ffee error!'
这样使得你的格式字符串更易于维护,也更容易在未来进行修改。你不必担心值传递的顺序是否与格式字符串中引用值的顺序一致。当然,这种方法的缺点是需要额外打字。简而言之,这种通过映射在格式字符串中使用变量名进行替换的方法,虽然可能需要多输入一些字符,但能大大提高代码的可维护性和未来修改性。
我相信你一定在疑惑,为什么这种类似于printf的格式化行为被称为“老式”字符串格式化。嗯,让我来告诉你原因。首先,它在技术上已经被“新样式”的格式化所取代,后者是我们接下来要讨论的话题。尽管如此,“老式”格式化并未被正式淘汰。它仍然在Python最新版本中得到支持和使用。
# 2—“新式”字符串格式
在Python 3中,引入了一种新的字符串格式化方式,并且这种新风格的格式化后来也被移植到了Python 2.7版本。这种“新风格”的字符串格式化不再依赖%-operator
特殊语法,使得字符串格式化的语法更加常规和一致。现在的字符串格式化是通过调用一个format()
函数在字符串对象上进行处理实现的。
你可以使用format()函数实现简单的位置格式化,就像你可以用“旧式”格式化一样:
>>> 'Hello, {}'.format(name)
'Hello, Bob'
或者,你可以通过变量替换的名字,并在你想要的任何顺序中使用它们。这是一项相当强大的功能,因为它允许你在不改变传递给format()
函数参数的情况下重新排列显示的顺序。
>>> 'Hey {name}, there is a 0x{errno:x} error!'.format(name=name, errno=errno)
'Hey Bob, there is a 0xbadc0ffee error!'
这同样说明,格式化整数变量为十六进制字符串的语法已经发生了变化。现在我们需要通过在变量名后添加冒号:
来附加一个格式说明符。总的来说,格式字符串的语法变得更加强大,但并没有因此而增加简单使用场景的复杂性。在Python文档中深入阅读关于这种字符串格式化的小型语言的内容,你会发现这是一项值得的投入。
在Python 3中,这种“新风格”的字符串格式化被推荐使用,而不是传统的%-style
格式化。然而,从Python 3.6开始,有一个甚至更好的方法来格式化你的字符串。
# 3—字面量字符串插值(Python 3.6+)
在编程中,"字面量字符串插值"通常是指直接将变量的值插入到一个字符串中的过程。这种操作常用于输出动态内容或者格式化数据。在Python 3.6中,又引入了一种格式化字符串的新方法,称为“格式化字符串字面量”。这种新的字符串格式化方式允许你在字符串常量中嵌入Python表达式。
>>> f'Hello, {name}!'
'Hello, Bob!'
这种新的格式化语法确实强大,因为它允许你在字符串常量中嵌入任意的Python表达式。这甚至意味着你可以进行内联的算术运算,就像这样:
>>> a = 5
>>> b = 10
>>> f'Five plus ten is {a + b} and not {2 * (a + b)}'
'Five plus ten is 15 and not 30'
确实,在幕后,格式化字符串字面量(f-string
)是Python解析器的一个特性。它的工作原理是将f-string
转换为一系列的字符串常量和表达式。例如,"The total cost is ${sales} * ${price}"
这个格式化字符串,会被转换成 str(sales)) + str(price)
这样的表达式。最后,解析器会把这些表达式的值连接起来,形成最终的字符串。这种特性使得Python在处理复杂的文本格式时非常灵活和强大。
假设我们有一个greet()
函数,其中包含一个f-string
。以下是一个例子:
>>> def greet(name, question):
... message = f"Hello, {name}! How's it {question}?"
... return message>>> user_name = "Alice"
>>> greet(user_name, 'going') # 输出: Hello, Alice!How's it going?
在这个例子中,greet()
函数接收一个名字参数,并使用f-string
构造一句问候语。最后,我们调用greet()
并打印返回的结果。
当我们解构这个函数,查看背后发生的细节时,我们会发现函数中`f-string实际上被转换为了类似这样的结构:
>>> def greet(name, question): # 将 f-string转换为表达式
... message = f"Hello, " +name! + "! How's it " + question + "?"
... return message
真实实现的代码稍微快一些,因为它使用了BUILD_STRING
作为优化。但从功能角度来看,它们是完全相同的。
def greet(name, question):return (f"Hello, {name}! How's it {question}?")import dis
dis.dis(greet)
1 0 RESUME 02 2 LOAD_CONST 1 ('Hello, ')4 LOAD_FAST 0 (name)6 FORMAT_VALUE 08 LOAD_CONST 2 ("! How's it ")10 LOAD_FAST 1 (question)12 FORMAT_VALUE 014 LOAD_CONST 3 ('?')16 BUILD_STRING 518 RETURN_VALUE
字符串字面量还支持str.format()
方法中现有的格式字符串语法。这使得你可以解决我们在前两个部分讨论过的相同格式化问题。
>>> f'Hey {name}, there\'s a {errno:#x} error!'
"Hey Bob, there's a 0xbadc0ffee error!"
Python的新格式化字符串字面量类似于在ES2015中添加到JavaScript的模板字符串。如果你想了解更多关于格式化字符串字面量的信息,可以查阅官方的Python文档。
# 4—模版字符串
在Python中,除了我们之前提到的格式化字符串字面量(f-string
)外,还有一个用于字符串格式化的技术叫做模板字符串(Template Strings
)。模板字符串是一种更简单、功能相对弱但有时能满足特定需求的字符串构造方式。
>>> from string import Template
>>> t = Template("Hey, $name!")
>>> t.substitute(name=name)
'Hey, Bob!'
你看这里,我们需要从Python内置的字符串模块中的Template
类导入模板字符串。模板字符串并不是Python语言的核心特性,而是标准库中提供的一种辅助工具。另一个区别是,模板字符串不支持格式符,所以我们需要将整数错误号转换为十六进制字符串,才能让错误字符串示例正常工作。
>>> templ_string = 'Hey $name, there is a $error error!'
>>> Template(templ_string).substitute(
... name=name, error=hex(erron))
'Hey Bob, there is a 0xbadc0ffee error!'
模板字符串在你的Python程序中使用时,通常是在处理由用户程序生成的格式化字符串时。由于它们的简单性和减少复杂性,模板字符串是一个更安全的选择。与其他字符串格式化技术中的更复杂的格式化语言相比,这些可能引入你的程序中的安全漏洞。例如,如果用户提供的格式化字符串可以访问到你程序中的任意变量,那么恶意用户就可以泄露敏感信息了。
>>> SECRET = 'this-is-a-secret'
>>> class Error:
... def __init__(self):
... pass>>> err = Error()
>>> user_input = '{error.__init__.__globals__[SECRET]}'
>>> user_input.format(error=err)
'this-is-a-secret'
看到了吗?假设的攻击者能够通过访问格式字符串中的__globals__
字典来提取我们的秘密字符串。这听起来是不是很可怕呢?模板字符串实际上堵住了这个攻击路径,这就是为什么它们在处理用户输入生成的格式字符串时,会是一个更安全的选择的原因。
>>> user_input = '${error.__init__.__globals__[SECRET]}'
>>> Template(user_input).substitute(error=err)raise ValueError('Invalid placeholder in string: line %d, col %d' %
ValueError: Invalid placeholder in string: line 1, col 1
**经验总结:**如果您的格式字符串是用户自定义的,使用模板字符串以避免安全问题。否则,如果你在python3.6+上,使用字面量字符串插值;或者如果你不支持新风格字符串格式化,就使用旧式字符串格式化方法。
这篇关于关于字符串格式化的惊人真相的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!