文章目录
- 前言
- 1. 起因
- 2. 调查
- 3. 高能
- 4. 释惑
前言
今天分享一件很诡异的事情,我写代码的时候遇到了不可见的字符!!!
1. 起因
今天在使用pipreqs
导出项目中所依赖的库时突然报错了:
pipreqs . --encoding=utf-8 --force
# 以下是报错信息
ERROR: Failed on file: ./build.py
Traceback (most recent call last):
File "/usr/local/bin/pipreqs", line 8, in <module>
sys.exit(main())
File "/usr/local/lib/python3.8/dist-packages/pipreqs/pipreqs.py", line 528, in main
init(args)
File "/usr/local/lib/python3.8/dist-packages/pipreqs/pipreqs.py", line 455, in init
candidates = get_all_imports(input_path,
File "/usr/local/lib/python3.8/dist-packages/pipreqs/pipreqs.py", line 131, in get_all_imports
raise exc
File "/usr/local/lib/python3.8/dist-packages/pipreqs/pipreqs.py", line 117, in get_all_imports
tree = ast.parse(contents)
File "/usr/lib/python3.8/ast.py", line 47, in parse
return compile(source, filename, mode, flags,
File "<unknown>", line 1
# -*- coding:utf-8 -*-
^
SyntaxError: invalid character in identifier
直接来了SyntaxError
,#
竟然是个无效字符,字符#
表示十分的无辜,当事人表示十分的震惊!!!这不是离天下之大谱,滑天下之大稽吗???这就一行代码注释,能够错到哪里去!
2. 调查
头一次遇到这种邪门的事情,我就查看了一下pipreqs
源码,代码很简单,我就摘取了报错的部分:
# pipreqs/pipreqs.py line 112
for file_name in files:
file_name = os.path.join(root, file_name)
with open(file_name, "r", encoding=encoding) as f:
contents = f.read()
try:
tree = ast.parse(contents) # 在这里报错了
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for subnode in node.names:
raw_imports.add(subnode.name)
elif isinstance(node, ast.ImportFrom):
raw_imports.add(node.module)
except Exception as exc:
...
意思也很好理解,pipreqs
读取当前工程下的所有python
文件,然后使用ast
库进行语法分析,获取python
文件所依赖的库名。既然这部分报错了,我就直接拿了出来,此时,我高度怀疑ast
存在重大bug
!
3. 高能
为了确认ast
在解析文件时存在bug,我对当前工程下的所有python
文件进行一一测试,然而,事情的发展却超出了我的预料:第二个文件(process_data.py)
竟然能够可以解析!
我看了下这个文件,开头也是一样的注释,然而却没有报错。难道是编码有问题?我打开了Pycharm
看了一下,也没有问题:
这也太诡异了吧,然后我又debug
了一下,看下文件的内容,确定也没有问题:
想不通了,于是问了下ChatGPT
:
给出了四个怀疑点,基本都是一一排除了,python 3.8
、文件为utf-8
编码,无语法错误,注释也没有什么问题。但有一个点却无法理解:不可见的特殊字符?
不可见?既然是个字符,即使不可见也得有位置吧。于是乎,最诡异的事情来了:
还真的有一个空字符,在第一个位置,这。。。空字符还能占个位置?
打印了一下ASCII
,发现其值竟然是65279
,空字符竟然有ASCII
值,顿时觉得这个问题不简单,难道还真的不可见?
4. 释惑
百度了一下发现,ASCII
值为65279
是因为文件采用UTF-8 BOM
编码导致的,这是Windows
环境下创建文件时默认的编码方式,对此还专门看了一下,还真是:
这在Pycharm
中也有这项设置,默认情况下在Pycharm
中创建新文件时会采用UTF-8 with NO BOM
编码,也就是常说的UTF-8
,而之所以ast
库有的能够正常解析文件有的却不可以,是可能有的文件不是在Pycharm
中创建的,导致了这种诡异的时间发生了。同时,我用二进制的方式读了一下文件,发现前三个字节是\xEF\xBB\xBF
,这也正是UTF-8 BOM
编码时自动添加的。
py_file = './build.py'
with open(py_file, 'r', encoding='utf-8') as f:
contents = f.read()
if ord(contents[0]) == 65279:
print('UTF-8 BOM')
with open(py_file, 'rb') as f:
contents = f.read(3)
if contents == b'\xEF\xBB\xBF':
print('UTF-8 BOM')
# UTF-8 BOM
# UTF-8 BOM
于是将文件编码由UTF-8 BOM
改为UTF-8
,问题就解决了!