PEP 290 – 代码迁移和现代化
- 作者:
- Raymond Hettinger <python at rcn.com>
- 状态:
- 活跃
- 类型:
- 信息性
- 创建:
- 2002年6月6日
- 修订历史:
摘要
本 PEP 收集了在安装较新版本的 Python 时更新 Python 应用程序的程序和想法。
迁移提示突出了可能存在的兼容性问题,并提出了查找和解决这些差异的建议。现代化程序展示了如何更新旧代码以利用新的语言特性。
基本原理
此程序存储库用作已知迁移问题和解决这些问题的程序的目录或检查清单。
迁移问题可能由于多种原因而出现。根据 PEP 4 中的指南,一些过时的特性正在逐渐弃用。此外,一些代码依赖于未记录的行为,这些行为在不同版本之间可能会发生变化。某些代码可能依赖于随后被证明是错误的行为,并且当修复该错误时,该行为会发生变化。
当 Python 的新版本添加了允许提高清晰度或比以前更高的性能的功能时,就会出现现代化选项。
新条目指南
拥有提交访问权限的开发人员可以直接更新此 PEP。其他人可以将其想法发送给开发人员以供可能纳入。
虽然一致的格式使存储库更易于使用,但请随时添加或删除部分以提高清晰度。
可以提供 Grep 模式作为工具来帮助维护人员找到可能需要更新的代码。但是,不建议完全自动化的搜索/替换样式正则表达式。相反,应单独评估每个代码片段。
禁忌部分是新条目的最重要部分。它列出了不应应用更新的已知情况。
迁移问题
比较运算符不是生成 0 或 1 的快捷方式
在 Python 2.3 之前,比较运算符返回 0 或 1 而不是 True 或 False。某些代码可能已将其用作在不适合使用其布尔对应项的位置生成零或一的快捷方式。例如
def identity(m=1):
"""Create and m-by-m identity matrix"""
return [[i==j for i in range(m)] for j in range(m)]
在 Python 2.2 中,对 identity(2) 的调用将产生
[[1, 0], [0, 1]]
在 Python 2.3 中,相同的调用将产生
[[True, False], [False, True]]
由于布尔值是整数的子类,因此矩阵将继续正常计算,但不会按预期打印。列表推导式应更改为
return [[int(i==j) for i in range(m)] for j in range(m)]
在将数据存储到可能期望数字而不是 True 或 False 的其他应用程序中使用时,存在类似的担忧。
现代化程序
程序按能够利用现代化的 Python 版本进行分组。
Python 2.4 或更高版本
在列表开头插入和弹出元素
Python 的列表在实现时旨在通过在右侧追加和弹出元素来获得最佳性能。使用 pop(0)
或 insert(0, x)
会触发整个列表的 O(n) 数据移动。为了帮助解决此需求,Python 2.4 引入了一个新的容器 collections.deque()
,它在左右两侧都具有高效的追加和弹出操作(权衡是 getitem/setitem 访问速度慢得多)。新的容器特别有助于实现数据队列
模式
c = list(data) --> c = collections.deque(data)
c.pop(0) --> c.popleft()
c.insert(0, x) --> c.appendleft()
定位
grep pop(0 or
grep insert(0
简化自定义排序
在 Python 2.4 中,列表的 sort
方法和新的 sorted
内置函数都接受一个 key
函数来计算排序键。与应用于每个比较的 cmp
函数不同,键函数仅对每个记录应用一次。它比 cmp 快得多,并且通常更易于阅读,同时使用更少的代码。键函数还保持排序的稳定性(具有相同键的记录按其原始顺序排列。
使用比较函数的原始代码
names.sort(lambda x,y: cmp(x.lower(), y.lower()))
使用显式装饰的备选原始代码
tempnames = [(n.lower(), n) for n in names]
tempnames.sort()
names = [original for decorated, original in tempnames]
使用键函数的修订代码
names.sort(key=str.lower) # case-insensitive sort
定位:grep sort *.py
替换常见的 Lambda 用法
在 Python 2.4 中,operator
模块获得了两个新函数 itemgetter() 和 attrgetter(),它们可以替换常见的 lambda
关键字用法。新函数运行速度更快,并且被一些人认为可以提高可读性。
模式
lambda r: r[2] --> itemgetter(2)
lambda r: r.myattr --> attrgetter('myattr')
典型上下文
sort(studentrecords, key=attrgetter('gpa')) # set a sort field
map(attrgetter('lastname'), studentrecords) # extract a field
定位:grep lambda *.py
简化的反向迭代
Python 2.4 引入了用于反向迭代的 reversed
内置函数。现有的反向迭代方法存在冗长、性能问题(速度和内存消耗)和/或缺乏清晰度等问题。首选样式是向前表达序列,将 reversed
应用于结果,然后遍历生成的快速、内存友好的迭代器。
使用半开区间表达的原始代码
for i in range(n-1, -1, -1):
print seqn[i]
分多个步骤反转的备选原始代码
rseqn = list(seqn)
rseqn.reverse()
for value in rseqn:
print value
使用扩展切片的备选原始代码
for value in seqn[::-1]:
print value
使用 reversed
函数的修订代码
for value in reversed(seqn):
print value
Python 2.3 或更高版本
测试字符串成员资格
在 Python 2.3 中,对于 string2 in string1
,string2
的长度限制被解除;它现在可以是任何长度的字符串。在搜索子字符串时,如果不在乎子字符串在原始字符串中的位置,使用 in
运算符可以使含义清晰。
模式
string1.find(string2) >= 0 --> string2 in string1
string1.find(string2) != -1 --> string2 in string1
用直接函数调用替换 apply()
在 Python 2.3 中,apply() 被标记为待弃用,因为它被 Python 1.6 在函数调用中引入的 * 和 ** 所取代。使用直接函数调用始终比 apply() 快一点,因为它节省了对内置函数的查找。现在,由于使用了 warnings 模块,apply() 甚至更慢。
模式
apply(f, args, kwds) --> f(*args, **kwds)
注意:apply() 在 Python 2.3.3 中已删除待弃用标记,因为它会给需要维护与 Python 版本(回溯到 1.5.2)兼容的代码的人带来困扰,在该版本中没有 apply() 的替代方案。但是,该函数仍然已弃用。
Python 2.2 或更高版本
测试字典成员资格
对于测试字典成员资格,请使用“in”关键字而不是“has_key()”方法。结果更短且更易读。样式与列表成员资格测试保持一致。结果略快,因为 has_key
需要属性搜索并使用相对昂贵的函数调用。
模式
if d.has_key(k): --> if k in d:
禁忌
- 某些类似字典的对象可能未定义
__contains__()
方法if dictlike.has_key(k)
定位:grep has_key
遍历字典
使用新的 iter
方法遍历字典。 iter
方法速度更快,因为它们不必创建包含所有键、值或项的完整副本的新列表对象。根据需要仅选择键、值或项(键/值对)可以节省创建临时对象引用的时间,并且在项的情况下,可以节省键的第二次哈希查找。
模式
for key in d.keys(): --> for key in d:
for value in d.values(): --> for value in d.itervalues():
for key, value in d.items():
--> for key, value in d.iteritems():
禁忌
- 如果需要列表,请不要更改返回类型
def getids(): return d.keys()
- 某些类似字典的对象可能未定义
iter
方法for k in dictlike.keys():
- 迭代器不支持切片、排序或其他操作
k = d.keys(); j = k[:]
- 字典迭代器禁止修改字典
for k in d.keys(): del[k]
stat
方法
用新的 os.stat
属性和方法替换 stat
常量或索引。 os.stat
属性和方法与顺序无关,并且不需要导入 stat
模块。
模式
os.stat("foo")[stat.ST_MTIME] --> os.stat("foo").st_mtime
os.stat("foo")[stat.ST_MTIME] --> os.path.getmtime("foo")
定位:grep os.stat
或 grep stat.S
减少对 types
模块的依赖
types
模块将来可能会被弃用。改用内置构造函数。它们可能会略快。
模式
isinstance(v, types.IntType) --> isinstance(v, int)
isinstance(s, types.StringTypes) --> isinstance(s, basestring)
完全使用此技术需要 Python 2.3 或更高版本(basestring
在 Python 2.3 中引入),但 Python 2.2 对于大多数用途来说已经足够了。
定位:grep types *.py | grep import
避免与 __builtins__
模块冲突的变量名
在 Python 2.2 中,为 dict
和 file
添加了新的内置类型。脚本应避免分配掩盖这些类型的变量名。相同的建议也适用于 list
等现有内置函数。
模式
file = open('myfile.txt') --> f = open('myfile.txt')
dict = obj.__dict__ --> d = obj.__dict__
定位:grep 'file ' *.py
Python 2.1 或更高版本
whrandom
模块已弃用
所有与随机相关的函数都已在一个地方收集,即 random
模块。
模式
import whrandom --> import random
查找位置:grep whrandom
Python 2.0 或更高版本
字符串方法
string 模块未来可能会被弃用。请使用字符串方法替代。字符串方法也更快。
模式
import string ; string.method(s, ...) --> s.method(...)
c in string.whitespace --> c.isspace()
查找位置:grep string *.py | grep import
startswith
和 endswith
字符串方法
请使用这些字符串方法代替切片。这样无需创建切片,并且避免了错误计数的风险。
模式
"foobar"[:3] == "foo" --> "foobar".startswith("foo")
"foobar"[-3:] == "bar" --> "foobar".endswith("bar")
atexit
模块
atexit 模块支持在程序终止时执行多个函数。此外,它还支持带参数的函数。不幸的是,其实现与 sys.exitfunc 属性冲突,后者仅支持单个退出函数。依赖 sys.exitfunc 的代码可能会干扰其他选择使用更新且更通用的 atexit 模块的模块(包括库模块)。
模式
sys.exitfunc = myfunc --> atexit.register(myfunc)
Python 1.5 或更高版本
基于类的异常
字符串异常已弃用,因此请从 Exception
基类派生。与已弃用的字符串异常不同,类异常都从另一个异常或 Exception
基类派生。这允许对异常进行有意义的分组。它还允许“except Exception
”子句捕获所有异常。
模式
NewError = 'NewError' --> class NewError(Exception): pass
查找位置:使用 PyChecker。
所有 Python 版本
测试 None
由于只有一个 None
对象,因此可以使用身份测试来测试相等性。身份测试比相等性测试略快。此外,某些对象类型可能会重载比较,因此相等性测试可能会慢得多。
模式
if v == None --> if v is None:
if v != None --> if v is not None:
查找位置:grep '== None'
或 grep '!= None'
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0290.rst