Python 隐式行连接会给你挖什么坑?
Python 隐式行连接常被用于长行的分割,例如分割超长的字符串或者语句,而这个特性一不小心就会造成诡异的 bug。
两个例子 🌰
出题时间!
1 |
|
1 |
|
上面两个例子,看看能不能正确地判断出上面的结果是什么?(答案在文章最后面,可以放心地往下看)
隐式行连接
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20In [1]: a = '1234567890abcdefABCDEF'
In [2]: a
Out[2]: '1234567890abcdefABCDEF'
In [3]: a = ('1234567890''abcdefABCDEF')
In [4]: a
Out[4]: '1234567890abcdefABCDEF'
In [5]: a = ('1234567890' 'abcdefABCDEF')
In [6]: a
Out[6]: '1234567890abcdefABCDEF'
In [7]: a = ('1234567890'
...: 'abcdefABCDEF') # 分割长行,这样可读性会比较好
In [8]: a
Out[8]: '1234567890abcdefABCDEF'
过长的字符串先分割为多个短字符串,最后再外面套上括号即可。注意分割符只能是空
或者单/多个空白符
(空格或者换行之类的),比如你用逗号分割那就变成元组了对吧?
很简单吧?看起来似乎也没什么问题。。。
坑点解析
Python 的隐式行连接不仅仅是小括号才能使用,中括号和大括号也是可以使用的。但是仅仅是这一句话,你可能会以为这三个相等:
1
2
3
4
5In [25]: a = ('1234567890' 'abcdefABCDEF')
...: b = ['1234567890' 'abcdefABCDEF']
...: c = {'1234567890' 'abcdefABCDEF'}
...:
...: a == b == c
但是实际上,b 是个列表,c 是个字典,只有 a 是字符串。原因呢?如果你经常使用元组,你很快就能意识,在创建只有一个元素的元组时,需要在最后加个逗号,否则就变成这个元素本身了:
1
2
3
4
5In [32]: ('1')
Out[32]: '1'
In [33]: ('1',)
Out[33]: ('1',)
道理是一样的。扯远了扯远了,回到这个坑本身来吧。
所以如果你在定义一个字符串列表的时候,忘记在某个元素之后加括号,那么 Python 就会帮你拼接上去:
1
2
3
4
5
6In [34]: [
...: '1',
...: '2'
...: '3',
...: ]
Out[34]: ['1', '23']
你看,在大多数情况下,这个结果不是我们想要的吧?
你可能会想,这有什么呢?我一眼就可以看得出来少了个逗号。但是如果一个字符串列表比较大,元素又比较长,是很难直接发现少了个不起眼的逗号的。
第一个例子,这个例子我是在自己写的 CVE 爬虫里遇到过的。CVE 种类繁多,有些漏洞我不感兴趣,比如 Chrome、Safari 的漏洞,iOS 的漏洞、一些小众 CMS 的漏洞等等,我就写了个 list,只要在这个 list 里的就排除不做推送。当然这个 list 不是一次性就写好了,大概用了有一个月吧,过几天就会补几个关键字,慢慢的就有几百个元素了。然后有一天我发现一直在推送 iOS 的漏洞,找了半天没发现 bug,最后仔仔细细检查了大概 300 行的 list,终于发现问题所在。。。
第二个例子,是一个我负责的项目。简单来说,键 是路径,后面 值 中的列表是 关键字,逻辑是只要在这个路径中找到其中一个关键字,就视为匹配成功。然后我无意中发现一直没法匹配到 /.git/config
这个路径。这个例子如果没有经验的话,其实比第一个例子更难发现哪有问题:Python 把多行注释和 /.git/config
拼在了一起,所以根本没有 /.git/config
这个键。幸亏之前在 CVE 爬虫那踩过坑,定位并解决只花了 1 分钟左右。
答案
1 |
|
1 |
|
一些思考
Python 有很多很好使的特性,但是有一些实在是让人心情复杂。比如多行注释,别人家的语言一般都有特定的多行注释,比如 /**/
、<!---->
等,而 Python 使用三个连续的单引号 '''
或者三个连续的双引号 """
注释多行内容,实际上就是定义了一个字符串,只不过是没用上而已,照这个思路,单行注释还可以这样呢:"这是一个注释"
。你说有什么问题吧,似乎一下也说不上来;说没问题吧,就是感觉怪怪的,至少本文提到的问题就是多行注释引起的。
来呀快活呀