关于字符串格式化的惊人真相

2024-05-13 08:36

本文主要是介绍关于字符串格式化的惊人真相,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于字符串格式化的惊人真相

记住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+上,使用字面量字符串插值;或者如果你不支持新风格字符串格式化,就使用旧式字符串格式化方法。

这篇关于关于字符串格式化的惊人真相的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/985200

相关文章

2390.从字符串中移除星号

给你一个包含若干星号 * 的字符串 s 。 在一步操作中,你可以: 选中 s 中的一个星号。 移除星号左侧最近的那个非星号字符,并移除该星号自身。 返回移除 所有 星号之后的字符串。 注意: 生成的输入保证总是可以执行题面中描述的操作。 可以证明结果字符串是唯一的。 示例 1: 输入:s = “leet**cod*e” 输出:“lecoe” 解释:从左到右执行移除操作: 距离第 1 个

Python 字符串占位

在Python中,可以使用字符串的格式化方法来实现字符串的占位。常见的方法有百分号操作符 % 以及 str.format() 方法 百分号操作符 % name = "张三"age = 20message = "我叫%s,今年%d岁。" % (name, age)print(message) # 我叫张三,今年20岁。 str.format() 方法 name = "张三"age

剑指offer(C++)--左旋转字符串

题目 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它! class Solution {public:string LeftRotateStri

PAT-1039 到底买不买(20)(字符串的使用)

题目描述 小红想买些珠子做一串自己喜欢的珠串。卖珠子的摊主有很多串五颜六色的珠串,但是不肯把任何一串拆散了卖。于是小红要你帮忙判断一下,某串珠子里是否包含了全部自己想要的珠子?如果是,那么告诉她有多少多余的珠子;如果不是,那么告诉她缺了多少珠子。为方便起见,我们用[0-9]、[a-z]、[A-Z]范围内的字符来表示颜色。例如,YrR8RrY是小红想做的珠串;那么ppRYYGrrYBR2258可以

js小题:通过字符串执行同名变量怎么做

在JavaScript中,你不能直接使用一个字符串来直接引用一个变量,因为JavaScript是一种静态类型语言(尽管它的类型在运行时可以变化),变量的名字在编译时就被确定了。但是,有几种方法可以实现类似的功能: 使用对象(或Map)来存储变量: 你可以使用一个对象来存储你的变量,然后使用字符串作为键来访问这些变量。 let myVars = { 'var1': 'Hello', 'var

Java格式化日期的三种方式

1)借助DateFormat类: public String toString(Date d) { SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); return sdf.format(d); } 2)使用String.format()方法。 String.format()的用法类似于C语

linux匹配Nginx日志,某个字符开头和结尾的字符串

匹配 os=1 开头, &ip结尾的字符串 cat 2018-06-07.log | egrep -o ‘os=1.*.&ip’ 存入日志。然后使用submit 前面和后面的值去掉,剩下就是需要的字符串。 cat 2018-06-07.log | egrep -o ‘os=1.*.&ip’ >log.log

算法训练营第六十七天 | 卡码网110 字符串接龙、卡码网105 有向图的完全可达性、卡码网106 岛屿的周长

卡码网110 字符串接龙 这题一开始用的邻接表+dfs,不幸超时 #include <iostream>#include <list>#include <string>#include <vector>using namespace std;int minLen = 501;bool count(string a, string b) {int num = 0;for (int i

【JavaSE ⑧】P219 ~ 225 Date类‘’DateFormat类转化Date和字符串;Calendar类获得日历中某值,修改日历,日历转日期

目录 日期时间类1 Date类概述常用方法 2DateFormat类构造方法格式规则常用方法parse方法format方法 3 Calendar类概念获取方式常用方法get/set方法add方法getTime方法 ● 练习1.判断Date不同参数构造的输出2. 用日期时间相关的API,计算一个人已经出生了多少天。3. 获取Calendar对象,输出日历当前年,月,日4. 把日历转换为日期

python - 变量和字符串

一.变量 变量名就像我们现实社会的名字,把一个值赋值给一个名字时,Ta会存储在内存中,称之为变量(variable),在大多数语言中,都把这种行为称为“给变量赋值”或“把值存储在变量中”。 •不过Python与大多数其他计算机语言的做法稍有不同,Ta并不是把值存储在变量中,而更像是把名字贴在值的上边。 •所以有些Python程序员会说“Python”没有“变量”,只有“名字”。 >>> t