Python 隐式行连接会给你挖什么坑?

Python 隐式行连接常被用于长行的分割,例如分割超长的字符串或者语句,而这个特性一不小心就会造成诡异的 bug。

两个例子 🌰

出题时间!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
data = ['1111111111',
'2222222222',
'3333333333',
'4444444444',
'5555555555',
'6666666666',
'7777777777',
'8888888888',
'9999999999'
'0000000000',
'aaaaaaaaaa',
'bbbbbbbbbb',
'cccccccccc',
'dddddddddd',
'eeeeeeeeee',
'ffffffffff',
'AAAAAAAAAA',
'BBBBBBBBBB',
'CCCCCCCCCC',
'DDDDDDDDDD',
'EEEEEEEEEE',
'FFFFFFFFFF']

print('0000000000' in data)
print('AAAAAAAAAA' in data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
rules = {
'''
2019-11-01 09:55:32
todo: 新增关键字
'''

# git 泄露关键字
'/.git/config': ['[core]', '[remote]', '[branch]', '[repositoryformatversion]'],

# 其他
'/config': ['debug']
}

print(rules.get('/.git/config', None))

上面两个例子,看看能不能正确地判断出上面的结果是什么?(答案在文章最后面,可以放心地往下看)

隐式行连接

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [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
5
In [25]: a = ('1234567890' 'abcdefABCDEF')
...: b = ['1234567890' 'abcdefABCDEF']
...: c = {'1234567890' 'abcdefABCDEF'}
...:
...: a == b == c

但是实际上,b 是个列表,c 是个字典,只有 a 是字符串。原因呢?如果你经常使用元组,你很快就能意识,在创建只有一个元素的元组时,需要在最后加个逗号,否则就变成这个元素本身了:
1
2
3
4
5
In [32]: ('1')
Out[32]: '1'

In [33]: ('1',)
Out[33]: ('1',)

道理是一样的。扯远了扯远了,回到这个坑本身来吧。

所以如果你在定义一个字符串列表的时候,忘记在某个元素之后加括号,那么 Python 就会帮你拼接上去:

1
2
3
4
5
6
In [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
2
False
True
1
None

一些思考

Python 有很多很好使的特性,但是有一些实在是让人心情复杂。比如多行注释,别人家的语言一般都有特定的多行注释,比如 /**/<!----> 等,而 Python 使用三个连续的单引号 ''' 或者三个连续的双引号 """ 注释多行内容,实际上就是定义了一个字符串,只不过是没用上而已,照这个思路,单行注释还可以这样呢:"这是一个注释"。你说有什么问题吧,似乎一下也说不上来;说没问题吧,就是感觉怪怪的,至少本文提到的问题就是多行注释引起的。



来呀快活呀


Python 隐式行连接会给你挖什么坑?
https://www.tr0y.wang/2020/04/25/python隐式行连接的坑/
作者
Tr0y
发布于
2020年4月25日
更新于
2024年4月12日
许可协议