相同环境下Python3 UTF-8解析失败而Python2正常的原因?
环境与代码说明
- 基础镜像:CentOS7
- Python版本:
python --version→ Python 2.7.5;python3 --version→ Python 3.6.8 - ENTRYPOINT脚本
run.sh:
#!/bin/bash locale >> locale.txt python3 /home/scripts/script.py >> output.txt while : do sleep 10000 done
- 执行脚本
script.py:
#!/bin/python path_to_file = '/home/file.json' print('starting') try: with open(path_to_file, "r") as file: data = file.read() print(data) except Exception as e: print(e) print('ending')
- 目标文件
/home/file.json内容:
"°"
现象对比
- Python3执行结果:
output.txt报错
starting 'ascii' codec can't decode byte 0xc2 in position 1: ordinal not in range(128) ending
- Python2执行结果:
output.txt正常输出
starting "°" ending
- Locale环境差异:
- ENTRYPOINT执行时
locale.txt显示:所有LC_*项均为POSIX,LANG为空 docker exec进入容器bash后执行locale:所有LC_*项均为en_US.UTF-8,此时运行python3 script.py可正常输出内容
- ENTRYPOINT执行时
疑问
已知Python2未配置字符集时默认用ASCII,Python3的open未指定encoding时也遵循系统配置,为何会出现上述差异?
核心差异在于Python2和Python3处理文件默认编码的逻辑不同,结合Docker ENTRYPOINT的环境特性导致了该现象:
1. Python2的文件读取逻辑
Python2中open()默认以字节流模式打开文件,读取到的内容是字节串(Python2的str等价于字节类型),不会自动执行编码解码操作。即使系统locale为POSIX(即ASCII),直接print字节串时,只是将原始字节写入输出目标(这里是output.txt),不会触发解码步骤,因此不会报错——你看到的"°"其实是文件的原始UTF-8字节直接被写入,没有经过解码。
2. Python3的文件读取逻辑
Python3中open()默认以文本模式打开文件,会自动将文件字节解码为字符串(str类型)。在Python3.6及更早版本中,解码使用的默认编码由系统locale设置决定:
- 当系统locale为
POSIX(对应ASCII编码)时,Python3会用ascii编码解码文件内容 - 你的
file.json中"°"的UTF-8编码字节是0xc2 0xb0,用ASCII解码必然触发错误
而通过docker exec进入容器bash时,会话会加载完整的镜像默认locale配置(en_US.UTF-8),此时Python3会以UTF-8作为默认编码,因此能正常解码文件。
3. Docker ENTRYPOINT的环境特性
Docker容器启动ENTRYPOINT时,默认不会加载完整的系统locale配置,环境变量LANG为空,LC_*均为POSIX;而docker exec启动的bash会话会初始化locale为镜像预设的en_US.UTF-8,这就是两种场景下locale差异的原因。
解决方法
要让Python3在ENTRYPOINT中正常运行,可采用以下方式:
- 在
open()中显式指定编码:open(path_to_file, "r", encoding="utf-8") - 在
run.sh中先设置locale环境变量:export LANG=en_US.UTF-8再执行Python脚本 - 构建镜像时配置默认locale环境变量
内容的提问来源于stack exchange,提问作者Jack Timothy

