同时兼容Python2和Python3的字符串判断语句

文章信息

作者:史晨 Charles Shih
创建时间:2018年08月24日

问题描述

我之前写过一个测试脚本,解释器用的是Python2.7,在RHEL7上用起来是没有问题的。但是最近要在RHEL8上进行这个测试,RHEL8采用的Python解释器是Python3,因此我的脚本不能直接运行在RHEL8上。

现在的现实情况如下:
1. RHEL7默认只安装了Python2的解释器;
2. RHEL8默认只安装了Python3的解释器;
3. 脚本需要同时兼容Python2和Python3解释器,因为没必要维护两份代码;
4. 测试框架尽量保持简洁,尽量少改动被测试系统的软件部署,因此不会为RHEL7安装Python3也不会为RHEL8安装Python2;

出问题的语句最开始是:

if not isinstance(params['backend'], (unicode, str)):

经过2to3(官方的代码升级工具)转换后变成了:

if not isinstance(params['backend'], str):

这是因为Python3当中没有Unicode这种类型。因此,如果2to3不把unicode优化掉的话,代码在Python3中就会直接报错。

但是经过转换的语句在Python2.7解释器下,当params['backend']为Unicode时,就会出现误判(其实Unicode是可接受的),影响脚本的正确执行。

原理分析

因为Python3解释器对字符串的处理方式进行了调整(这是个典型的坑,Google上相关的文章很多,下面我也推荐了几篇)。

简单来说,它们的对应关系如下:

Python2 Python3
str bytes
unicode str

于是根据这个规则,原始的代码应该改为:

if not isinstance(params['backend'], (str, bytes)):

然而这样的语句在Python2中还是无法正确判断Unicode字符串(虽然Python2.7解释器支持bytes类型,但是它等同于str类型)。

关于unicode_literals

对于Python3字符串这个坑,官方有一些解决方案,比如:

# still running on Python 2.7
from __future__ import unicode_literals

这个语句可以让Python2.7解释器像Python3解释器一样的工作,但这并不能解决我的问题。因为我的需求是提供同时兼容两种解释器的代码。

关于-*- coding: utf-8 -*-

有些时候我们在Python代码的开头,添加下面的语句,似乎与这个问题有一定的联系:

# -*- coding: utf-8 -*-

然而事实上,这个操作只是为了告诉Python解释器,这个代码文件里用到的所有字符串都是以utf-8编码的。这对于需要在代码中包含中文字符是很有帮助的,但对于提供同时兼容两种解释器的代码这件事来说,是一点儿忙也帮不上的。

解决办法

其实我们不妨换一个思路,不用去管一个字符串是unicode还是str亦或是bytes,它是什么都好,只要它还是个字符串就行。这就是我解决问题的思路,按照这个思路我把代码改成了:

if type(params['backend']) not in (type(u''), type(b'')):

这样就写出了同时兼容Python2和Python3解释器的代码。

相关代码

https://github.com/SCHEN2015/virt-perf-scripts/commit/66e3945edb56b9ea81482ef5a0b9cf9d2b3ec0e9#diff-6cab55a9902115ac841e798becf8317f

推荐阅读

  1. 关于python2中的unicode和str以及python3中的str和bytes
  2. 廖雪峰的官方网站:Python 2.7教程 - 模块 - 使用future
  3. Python同时兼容python2和python3的8个技巧分享

你可能感兴趣的:(问题笔记)