假定你需要输出一句话,告诉Bob,程序发生了一个错误,已有两个变量:
>>> errno = 50159747054
>>> name = 'Bob'
你想要输出:
'Hey Bob, there is a 0xbadc0ffee error!'
1、最传统的%操作符(python2)
在Python中,有一个独特的内建操作可以使用%操作符来操作字符串。它可以让你很轻松的做基于位置的字符串格式化。
示例:
>>> 'Hello, %s' % name
"Hello, Bob"
使用 %s 格式说明符来告诉Python哪个位置应该替换成 name 的值,它代表了类型为字符串。
除了这个格式说明符,另外还有其他的格式操作符可供选择,让你可以控制输出的格式。 例如16进制数:
>>> '%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!'
个人观点:这是python2时代的字符串格式化方法,编码繁琐且不易阅读;需要传入的参数越多,犯错的概率越大;不够优雅。
2、规范的format方法(python3)
在Python3引入了一个新的字符串格式化的方法,并且随后支持了Python2.7。这个“新式”的字符串格式化方法摆脱了%操作符并且使得字符串格式化的语法更规范了。 现在时候通过调用字符串对象的.format() 方法进行格式化。
传统的%占位符被{}代替了:
>>> 'Hello, {}'.format(name)
'Hello, Bob'
当然也可以通过变量名来传参,还能通过:
后缀指定传入的参数类型:
>>> 'Hey {name}, there is a 0x{errno:x} error!'.format(name=name, errno=errno)
'Hey Bob, there is a 0xbadc0ffee error!'
注意,若在:
后接#
会输出传入字符的数据类型,而不用手动增加例如0x
的字符,仔细比对上下两个输入的差别:
>>> 'Hey {name}, there is a {errno:#x} error!'.format(name=name, errno=errno)
'Hey Bob, there is a 0xbadc0ffee error!'
个人观点: 相比%优雅了很多,如果可能请尽量使用format,但是可以看出编码还是比较繁琐。
3、优雅且强大的f方法/f-Strings(python3.6+)
在Python 3.6 中添加了一个新的字符串格式化方法,被称为字面量格式化字符串或者“f-strings”。这个新的方法让你能够在字符串常量中嵌入Python表达式。
示例:
>>> f'Hello, {name}!'
'Hello, Bob!'
你可以看到它在字符串前面放置了“f”作为前缀 - 因此被称为“f-strings”。这个新的格式化语法非常的强大。你可以使用任何Python表达式,甚至可以做内联计算:
>>> a = 5
>>> b = 10
>>> f'{a} plus {b} is {a + b} and not {2 * (a + b)}.'
'5 plus 10 is 15 and not 30.'
看看用f方法实现与前两节同样的效果:
>>> f"Hey {name}, there's a {errno:#x} error!"
"Hey Bob, there's a 0xbadc0ffee error!"
个人观点: 优雅且高效!强烈推荐!只要在3.6以上的版本,应尽量使用f方法格式化字符串!
4、特殊场景的选择——如何避免攻击?( 字符串模板——Python标准库)
在Python里还有另一个字符串格式化工具:模板字符串。它是一个更简单,也不太强大的方法,但是在某些情况下恰恰是你想要的。
看一下简单的欢迎词示例:
>>> from string import Template
>>> t = Template('Hey, $name!')
>>> t.substitute(name=name)
'Hey, Bob!'
同时这个模板方法不支持使用格式说明符,所以需要自己手动转换为想要的输出格式:
>>> templ_string = 'Hey $name, there is a $errno error!'
>>> Template(templ_string).substitute(name=name, errno=hex(errno))
'Hey Bob, there is a 0xbadc0ffee error!'
使用模板字符串的最佳的时机就是当你的程序需要处理由用户提供的输入内容时。模板字符串是最保险的选择,因为可以降低复杂性。
其他一些复杂的字符串格式化技巧的可能会给你的程序带来安全漏洞,例如,格式化字符串可以访问你程序里任意的变量。
这意味着,如果一个恶意用户可以提供一个格式化字符串,他们就有可能泄露安全密匙以及其他敏感的信息!
示例:
# This is our super secret key:
SECRET = 'this-is-a-secret'
class Error:
def __init__(self):
pass
# A malicious user can craft a format string that
# can read data from the global namespace:
user_input = '{error.__init__.__globals__[SECRET]}'
# This allows them to exfiltrate sensitive information,
# like the secret key:
err = Error()
user_input.format(error=err)
# output:
'this-is-a-secret'
如上,通过Error类的的构造函数__init__
,可以访问__globals__
字典获取全局变量SECRET
。
而Template可以关闭这个攻击载体,如下:
user_input = '${error.__init__.__globals__[SECRET]}'
Template(user_input).substitute(error=err)
# output
ValueError:
"Invalid placeholder in string: line 1, col 1"
总结
%s方法应该被抛弃,format方法是最基本的要求,如果运行3.6以上的环境,那推荐f方法,如果格式化字符串是由用户输入的,那应该考虑安全的Template方法。