如何将你的应用迁移到Python3的三个步骤


Posted in Python onDecember 22, 2019

Python 2.x 很快就要 失去官方支持 了,尽管如此,从 Python 2 迁移到 Python 3 却并没有想象中那么难。我在上周用了一个晚上的时间将一个 3D 渲染器的前端代码及其对应的 PySide 迁移到 Python 3,回想起来,尽管在迁移过程中无可避免地会遇到一些牵一发而动全身的修改,但整个过程相比起痛苦的重构来说简直是出奇地简单。

每个人都别无选择地有各种必须迁移的原因:或许是觉得已经拖延太久了,或许是依赖了某个在 Python 2 下不再维护的模块。但如果你仅仅是想通过做一些事情来对开源做贡献,那么把一个 Python 2 应用迁移到 Python 3 就是一个简单而又有意义的做法。

无论你从 Python 2 迁移到 Python 3 的原因是什么,这都是一项重要的任务。按照以下三个步骤,可以让你把任务完成得更加清晰。

1、使用 2to3

从几年前开始,Python 在你或许还不知道的情况下就已经自带了一个名叫 2to3 的脚本,它可以帮助你实现大部分代码从 Python 2 到 Python 3 的自动转换。

下面是一段使用 Python 2.6 编写的代码:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
mystring = u'abcdé' 
print ord(mystring[-1])

对其执行 2to3 脚本:

$ 2to3 example.py 
RefactoringTool: Refactored example.py 
--- example.py   (original) 
+++ example.py   (refactored) 
@@ -1,5 +1,5 @@ 
 #!/usr/bin/env python 
 # -*- coding: utf-8 -*- 
 
-mystring = u'abcdé' 
-print ord(mystring[-1]) 
+mystring = 'abcdé' 
+print(ord(mystring[-1])) 
RefactoringTool: Files that need to be modified: 
RefactoringTool: example.py

在默认情况下,2to3 只会对迁移到 Python 3 时必须作出修改的代码进行标示,在输出结果中显示的 Python 3 代码是直接可用的,但你可以在 2to3 加上 -w 或者 --write 参数,这样它就可以直接按照给出的方案修改你的 Python 2 代码文件了。

$ 2to3 -w example.py 
[...] 
RefactoringTool: Files that were modified: 
RefactoringTool: example.py

2to3 脚本不仅仅对单个文件有效,你还可以把它用于一个目录下的所有 Python 文件,同时它也会递归地对所有子目录下的 Python 文件都生效。

2、使用 Pylint 或 Pyflakes

有一些不良的代码在 Python 2 下运行是没有异常的,在 Python 3 下运行则会或多或少报出错误,这种情况并不鲜见。因为这些不良代码无法通过语法转换来修复,所以 2to3 对它们没有效果,但一旦使用 Python 3 来运行就会产生报错。

要找出这种问题,你需要使用 Pylint 、 Pyflakes (或 flake8 封装器)这类工具。其中我更喜欢 Pyflakes,它会忽略代码风格上的差异,在这一点上它和 Pylint 不同。尽管代码优美是 Python 的一大特点,但在代码迁移的层面上,“让代码功能保持一致”无疑比“让代码风格保持一致”重要得多。

以下是 Pyflakes 的输出样例:

$ pyflakes example/maths 
example/maths/enum.py:19: undefined name 'cmp' 
example/maths/enum.py:105: local variable 'e' is assigned to but never used 
example/maths/enum.py:109: undefined name 'basestring' 
example/maths/enum.py:208: undefined name 'EnumValueCompareError' 
example/maths/enum.py:208: local variable 'e' is assigned to but never used

上面这些由 Pyflakes 输出的内容清晰地给出了代码中需要修改的问题。相比之下,Pylint 会输出多达 143 行的内容,而且多数是诸如代码缩进这样无关紧要的问题。

值得注意的是第 19 行这个容易产生误导的错误。从输出来看你可能会以为 cmp 是一个在使用前未定义的变量,实际上 cmp 是 Python 2 的一个内置函数,而它在 Python 3 中被移除了。而且这段代码被放在了 try 语句块中,除非认真检查这段代码的输出值,否则这个问题很容易被忽略掉。

try: 
  result = cmp(self.index, other.index) 
except: 
  result = 42 
  
return result

在代码迁移过程中,你会发现很多原本在 Python 2 中能正常运行的函数都发生了变化,甚至直接在 Python 3 中被移除了。例如 PySide 的绑定方式发生了变化、importlib 取代了 imp 等等。这样的问题只能见到一个解决一个,而涉及到的功能需要重构还是直接放弃,则需要你自己权衡。但目前来说,大多数问题都是已知的,并且有 完善的文档记录 。所以难的不是修复问题,而是找到问题,从这个角度来说,使用 Pyflake 是很有必要的。

3、修复被破坏的 Python 2 代码

尽管 2to3 脚本能够帮助你把代码修改成兼容 Python 3 的形式,但对于一个完整的代码库,它就显得有点无能为力了,因为一些老旧的代码在 Python 3 中可能需要不同的结构来表示。在这样的情况下,只能人工进行修改。

例如以下代码在 Python 2.6 中可以正常运行:

class CLOCK_SPEED: 
    TICKS_PER_SECOND = 16 
    TICK_RATES = [int(i * TICKS_PER_SECOND) 
           for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)] 
class FPS: 
    STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND

类似 2to3 和 Pyflakes 这些自动化工具并不能发现其中的问题,但如果上述代码使用 Python 3 来运行,解释器会认为 CLOCK_SPEED.TICKS_PER_SECOND 是未被明确定义的。因此就需要把代码改成面向对象的结构:

class CLOCK_SPEED: 
    def TICKS_PER_SECOND(): 
        TICKS_PER_SECOND = 16 
        TICK_RATES = [int(i * TICKS_PER_SECOND) 
            for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)] 
        return TICKS_PER_SECOND 
class FPS: 
    STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND()

你也许会认为如果把 TICKS_PER_SECOND() 改写为一个构造函数(用 __init__ 函数设置默认值)能让代码看起来更加简洁,但这样就需要把这个方法的调用形式从 CLOCK_SPEED.TICKS_PER_SECOND() 改为 CLOCK_SPEED() 了,这样的改动或多或少会对整个库造成一些未知的影响。如果你对整个代码库的结构烂熟于心,那么你确实可以随心所欲地作出这样的修改。但我通常认为,只要我做出了修改,都可能会影响到其它代码中的至少三处地方,因此我更倾向于不使代码的结构发生改变。

坚持信念

如果你正在尝试将一个大项目从 Python 2 迁移到 Python 3,也许你会觉得这是一个漫长的过程。你可能会费尽心思也找不到一条有用的报错信息,这种情况下甚至会有将代码推倒重建的冲动。但从另一个角度想,代码原本在 Python 2 中就可以运行,要让它能在 Python 3 中继续运行,你需要做的只是对它稍加转换而已。

但只要你完成了迁移,你就得到了这个模块或者整个应用程序的 Python 3 版本,外加 Python 官方的长期支持。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
跟老齐学Python之再深点,更懂list
Sep 20 Python
Python中的map()函数和reduce()函数的用法
Apr 27 Python
使用Python读写文本文件及编写简单的文本编辑器
Mar 11 Python
Python读写docx文件的方法
May 08 Python
网红编程语言Python将纳入高考你怎么看?
Jun 07 Python
python批量修改文件夹及其子文件夹下的文件内容
Mar 15 Python
Python中的字符串切片(截取字符串)的详解
May 15 Python
解决Python中pandas读取*.csv文件出现编码问题
Jul 12 Python
pandas-resample按时间聚合实例
Dec 27 Python
python实现多进程按序号批量修改文件名的方法示例
Dec 30 Python
python如何进行矩阵运算
Jun 05 Python
python判断元素是否存在的实例方法
Sep 24 Python
使用python快速实现不同机器间文件夹共享方式
Dec 22 #Python
python FTP批量下载/删除/上传实例
Dec 22 #Python
python 实现保存最新的三份文件,其余的都删掉
Dec 22 #Python
python中的split()函数和os.path.split()函数使用详解
Dec 21 #Python
python 操作hive pyhs2方式
Dec 21 #Python
python 消费 kafka 数据教程
Dec 21 #Python
python kafka 多线程消费者&手动提交实例
Dec 21 #Python
You might like
上海牌131型七灯四波段四喇叭一级收音机
2021/03/02 无线电
PHP类中的魔术方法(Magic Method)简明总结
2014/07/08 PHP
Eclipse的PHP插件PHPEclipse安装和使用
2014/07/20 PHP
CodeIgniter配置之SESSION用法实例分析
2016/01/19 PHP
PHP Swoole异步MySQL客户端实现方法示例
2019/10/24 PHP
jquery 得到当前页面高度和宽度的两个函数
2010/02/21 Javascript
autoIMG 基于jquery的图片自适应插件代码
2011/03/12 Javascript
通过复制Table生成word和excel的javascript代码
2014/01/20 Javascript
JS 删除字符串最后一个字符的实现代码
2014/02/20 Javascript
jquery常用特效方法使用示例
2014/04/25 Javascript
AngularJS中取消对HTML片段转义的方法例子
2015/01/04 Javascript
javascript将异步校验表单改写为同步表单
2015/01/27 Javascript
ReactNative之键盘Keyboard的弹出与消失示例
2017/07/11 Javascript
Javascript中从学习bind到实现bind的过程
2018/01/05 Javascript
JS数组实现分类统计实例代码
2018/09/30 Javascript
JS实现商城秒杀倒计时功能(动态设置秒杀时间)
2019/12/12 Javascript
讲解Python中if语句的嵌套用法
2015/05/14 Python
基于Python log 的正确打开方式
2018/04/28 Python
使用pandas把某一列的字符值转换为数字的实例
2019/01/29 Python
Opencv实现抠图背景图替换功能
2019/05/21 Python
利用Python实现手机短信监控通知的方法
2019/07/22 Python
Python序列对象与String类型内置方法详解
2019/10/22 Python
Python实现平行坐标图的绘制(plotly)方式
2019/11/22 Python
python 爬取疫情数据的源码
2020/02/09 Python
使用HTML5在网页中嵌入音频和视频播放的基本方法
2016/02/22 HTML / CSS
加利福尼亚州威尼斯的女性奢侈品设计师服装和概念店:Mona Moore
2018/09/13 全球购物
警察思想汇报
2014/01/04 职场文书
公司活动邀请函
2014/01/24 职场文书
初中毕业生的自我评价
2014/03/03 职场文书
护士医德医风自我评价
2014/09/15 职场文书
司机岗位职责
2015/02/04 职场文书
2015年电厂工作总结范文
2015/05/13 职场文书
Python 处理表格进行成绩排序的操作代码
2021/07/26 Python
PHP遍历数组的6种方式总结
2021/11/17 PHP
详解SQL的窗口函数
2022/04/21 Oracle
详解如何使用Nginx解决跨域问题
2022/05/06 Servers