如何将你的应用迁移到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中IPYTHON入门实例
May 11 Python
python实现将文本转换成语音的方法
May 28 Python
python XlsxWriter模块创建aexcel表格的实例讲解
May 03 Python
python使用代理ip访问网站的实例
May 07 Python
Python 文本文件内容批量抽取实例
Dec 10 Python
详解Python3 pickle模块用法
Sep 16 Python
Python matplotlib以日期为x轴作图代码实例
Nov 22 Python
Pandas实现DataFrame按行求百分数(比例数)
Dec 27 Python
Pytorch 实现冻结指定卷积层的参数
Jan 06 Python
python批量修改文件名的示例
Sep 27 Python
15个Pythonic的代码示例(值得收藏)
Oct 29 Python
Python编写万花尺图案实例
Jan 03 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
PHP缓冲区用法总结
2016/02/14 PHP
PHP数据库操作四:mongodb用法分析
2017/08/16 PHP
小程序微信退款功能实现方法详解【基于thinkPHP】
2019/05/05 PHP
PHP使用ActiveMQ实现消息队列的方法详解
2019/05/31 PHP
asp(javascript)全角半角转换代码 dbc2sbc
2009/08/06 Javascript
javascript 面向对象编程  function是方法(函数)
2009/09/17 Javascript
ASP中Sub和Function的区别说明
2020/08/30 Javascript
JavaScript实现拼音排序的方法
2012/11/20 Javascript
JS判定是否原生方法
2013/07/22 Javascript
JS远程获取网页源代码实例
2013/09/05 Javascript
javascript写的一个模拟阅读小说的程序
2014/04/04 Javascript
浅析javascript 定时器
2014/12/23 Javascript
详解JavaScript UTC时间转换方法
2016/01/07 Javascript
简单讲解AngularJS的Routing路由的定义与使用
2016/03/05 Javascript
vue-cli 自定义指令directive 添加验证滑块示例
2017/10/19 Javascript
js获取浏览器地址(获取第1个斜杠后的内容)
2019/09/03 Javascript
JavaScript RegExp 对象用法详解
2019/09/24 Javascript
基于axios 的responseType类型的设置方法
2019/10/29 Javascript
使用JavaScript实现贪吃蛇游戏
2020/09/29 Javascript
python2.6.6如何升级到python2.7.14
2018/04/08 Python
python3 flask实现文件上传功能
2020/03/20 Python
python实现简单名片管理系统
2018/11/30 Python
了解不常见但是实用的Python技巧
2019/05/23 Python
Python unittest生成测试报告过程解析
2020/09/08 Python
jupyter使用自动补全和切换默认浏览器的方法
2020/11/18 Python
Python3+Flask安装使用教程详解
2021/02/16 Python
巴西宠物商店:Cobasi
2019/04/19 全球购物
极简鞋类,赤脚的感觉:Lems Shoes
2019/08/06 全球购物
师范生实习个人的自我评价
2013/09/28 职场文书
社区食品安全实施方案
2014/03/28 职场文书
软件售后服务方案
2014/05/29 职场文书
党员个人对照检查材料思想汇报
2014/09/16 职场文书
医生学习党的群众路线教育实践活动心得体会
2014/11/03 职场文书
2015秋季运动会通讯稿
2015/07/18 职场文书
CocosCreator入门教程之网络通信
2021/04/16 Javascript
python实现的web监控系统
2021/04/27 Python