在Python 2.7即将停止支持时,我们为你带来了一份python 3.x迁移指南


Posted in Python onJanuary 30, 2018

目前,Python 科学栈中的所有主要项目都同时支持 Python 3.x 和 Python 2.7,不过,这种情况很快即将结束。去年 11 月,Numpy 团队的一份声明引发了数据科学社区的关注: 这一科学计算库即将放弃对于 Python 2.7 的支持 ,全面转向 Python 3。Numpy 并不是唯一宣称即将放弃 Python 旧版本支持的工具,pandas 与 Jupyter notebook 等很多产品也在即将放弃支持的名单之中。对于数据科学开发者而言,如何将已有项目从 Python 2 转向 Python 3 成为了正在面临的重大问题。来自莫斯科大学的 Alex Rogozhnikov 博士为我们整理了一份代码迁移指南。

Python 3 功能简介

Python 是机器学习和其他科学领域中的主流语言,我们通常需要使用它处理大量的数据。Python 兼容多种深度学习框架,且具备很多优秀的工具来执行数据预处理和可视化。

但是,Python 2 和 Python 3 长期共存于 Python 生态系统中,很多数据科学家仍然使用 Python 2。2019 年底,Numpy 等很多科学计算工具都将停止支持 Python 2,而 2018 年后 Numpy 的所有新功能版本将只支持 Python 3。

为了使 Python 2 向 Python 3 的转换更加轻松,我收集了一些 Python 3 的功能,希望对大家有用。

使用 pathlib 更好地处理路径

pathlib 是 Python 3 的默认模块,帮助避免使用大量的 os.path.joins:

from pathlib import Path
dataset = 'wiki_images'
datasets_root = Path('/path/to/datasets/')
train_path = datasets_root / dataset / 'train'
test_path = datasets_root / dataset / 'test'
for image_path in train_path.iterdir():
 with image_path.open() as f: # note, open is a method of Path object
 # do something with an image

Python 2 总是试图使用字符串级联(准确,但不好),现在有了 pathlib,代码安全、准确、可读性强。

此外,pathlib.Path 具备大量方法,这样 Python 新用户就不用每个方法都去搜索了:

p.exists()
p.is_dir()
p.parts()
p.with_name('sibling.png') # only change the name, but keep the folder
p.with_suffix('.jpg') # only change the extension, but keep the folder and the name
p.chmod(mode)
p.rmdir()

pathlib 会节约大量时间,详见:

文档:https://docs.python.org/3/library/pathlib.html;

参考信息:https://pymotw.com/3/pathlib/。

类型提示(Type hinting)成为语言的一部分

PyCharm 中的类型提示示例:

Python 不只是适合脚本的语言,现在的数据流程还包括大量步骤,每一步都包括不同的框架(有时也包括不同的逻辑)。

类型提示被引入 Python,以帮助处理越来越复杂的项目,使机器可以更好地进行代码验证。而之前需要不同的模块使用自定义方式在文档字符串中指定类型(注意:PyCharm 可以将旧的文档字符串转换成新的类型提示)。

下列代码是一个简单示例,可以处理不同类型的数据(这就是我们喜欢 Python 数据栈之处)。

def repeat_each_entry(data):
 """ Each entry in the data is doubled 
 """
 index = numpy.repeat(numpy.arange(len(data)), 2)
 return data[index]

上述代码适用于 numpy.array(包括多维)、astropy.Table 和 astropy.Column、bcolz、cupy、mxnet.ndarray 等。
该代码同样可用于 pandas.Series,但是方式是错误的:

repeat_each_entry(pandas.Series(data=[0, 1, 2], index=[3, 4, 5])) # returns Series with Nones inside

这是一个两行代码。想象一下复杂系统的行为多么难预测,有时一个函数就可能导致错误的行为。明确了解哪些类型方法适合大型系统很有帮助,它会在函数未得到此类参数时给出提醒。

def repeat_each_entry(data: Union[numpy.ndarray, bcolz.carray]):

如果你有一个很棒的代码库,类型提示工具如 MyPy 可能成为集成流程中的一部分。不幸的是,提示没有强大到足以为 ndarrays/tensors 提供细粒度类型,但是或许我们很快就可以拥有这样的提示工具了,这将是 DS 的伟大功能。

类型提示 → 运行时的类型检查

默认情况下,函数注释不会影响代码的运行,不过它也只能帮你指出代码的意图。

但是,你可以在运行时中使用 enforce 等工具强制进行类型检查,这可以帮助你调试代码(很多情况下类型提示不起作用)。

@enforce.runtime_validation
def foo(text: str) -> None:
 print(text)
foo('Hi') # ok
foo(5) # fails
@enforce.runtime_validation
def any2(x: List[bool]) -> bool:
 return any(x)
any ([False, False, True, False]) # True
any2([False, False, True, False]) # True
any (['False']) # True
any2(['False']) # fails
any ([False, None, "", 0]) # False
any2([False, None, "", 0]) # fails

函数注释的其他用处

如前所述,注释不会影响代码执行,而且会提供一些元信息,你可以随意使用。

例如,计量单位是科学界的一个普遍难题,astropy 包提供一个简单的装饰器(Decorator)来控制输入量的计量单位,并将输出转换成所需单位。

# Python 3
from astropy import units as u
@u.quantity_input()
def frequency(speed: u.meter / u.s, wavelength: u.m) -> u.terahertz:
 return speed / wavelength
frequency(speed=300_000 * u.km / u.s, wavelength=555 * u.nm)
# output: 540.5405405405404 THz, frequency of green visible light

如果你拥有 Python 表格式科学数据(不必要太多),你应该尝试一下 astropy。你还可以定义针对某个应用的装饰器,用同样的方式来控制/转换输入和输出。

通过 @ 实现矩阵乘法

下面,我们实现一个最简单的机器学习模型,即带 L2 正则化的线性回归:

# l2-regularized linear regression: || AX - b ||^2 + alpha * ||x||^2 -> min
# Python 2
X = np.linalg.inv(np.dot(A.T, A) + alpha * np.eye(A.shape[1])).dot(A.T.dot(b))
# Python 3
X = np.linalg.inv(A.T @ A + alpha * np.eye(A.shape[1])) @ (A.T @ b)

下面 Python 3 带有 @ 作为矩阵乘法的符号更具有可读性,且更容易在深度学习框架中转译:因为一些如 X @ W + b[None, :] 的代码在 numpy、cupy、pytorch 和 tensorflow 等不同库下都表示单层感知机。

使用 ** 作为通配符

递归文件夹的通配符在 Python2 中并不是很方便,因此才存在定制的 glob2 模块来克服这个问题。递归 flag 在 Python 3.6 中得到了支持。

import glob
# Python 2
found_images = \
 glob.glob('/path/*.jpg') \
 + glob.glob('/path/*/*.jpg') \
 + glob.glob('/path/*/*/*.jpg') \
 + glob.glob('/path/*/*/*/*.jpg') \
 + glob.glob('/path/*/*/*/*/*.jpg')
# Python 3
found_images = glob.glob('/path/**/*.jpg', recursive=True)

python3 中更好的选择是使用 pathlib:

# Python 3
found_images = pathlib.Path('/path/').glob('**/*.jpg')

Print 在 Python3 中是函数

Python 3 中使用 Print 需要加上麻烦的圆括弧,但它还是有一些优点。

使用文件描述符的简单句法:

print >>sys.stderr, "critical error" # Python 2
print("critical error", file=sys.stderr) # Python 3

在不使用 str.join 下输出 tab-aligned 表格:

# Python 3
print(*array, sep='\t')
print(batch, epoch, loss, accuracy, time, sep='\t')

修改与重新定义 print 函数的输出:

# Python 3
_print = print # store the original print function
def print(*args, **kargs):
 pass # do something useful, e.g. store output to some file

在 Jupyter 中,非常好的一点是记录每一个输出到独立的文档,并在出现错误的时候追踪出现问题的文档,所以我们现在可以重写 print 函数了。

在下面的代码中,我们可以使用上下文管理器暂时重写 print 函数的行为:

@contextlib.contextmanager
def replace_print():
 import builtins
 _print = print # saving old print function
 # or use some other function here
 builtins.print = lambda *args, **kwargs: _print('new printing', *args, **kwargs)
 yield
 builtins.print = _print
with replace_print():
 <code here will invoke other print function>

上面并不是一个推荐的方法,因为它会引起系统的不稳定。

print 函数可以加入列表解析和其它语言构建结构。

# Python 3
result = process(x) if is_valid(x) else print('invalid item: ', x)

f-strings 可作为简单和可靠的格式化

默认的格式化系统提供了一些灵活性,且在数据实验中不是必须的。但这样的代码对于任何修改要么太冗长,要么就会变得很零碎。而代表性的数据科学需要以固定的格式迭代地输出一些日志信息,通常需要使用的代码如下:

# Python 2
print('{batch:3} {epoch:3} / {total_epochs:3} accuracy: {acc_mean:0.4f}±{acc_std:0.4f} time: {avg_time:3.2f}'.format(
 batch=batch, epoch=epoch, total_epochs=total_epochs,
 acc_mean=numpy.mean(accuracies), acc_std=numpy.std(accuracies),
 avg_time=time / len(data_batch)
))
# Python 2 (too error-prone during fast modifications, please avoid):
print('{:3} {:3} / {:3} accuracy: {:0.4f}±{:0.4f} time: {:3.2f}'.format(
 batch, epoch, total_epochs, numpy.mean(accuracies), numpy.std(accuracies),
 time / len(data_batch)
))

样本输出:

120 12 / 300 accuracy: 0.8180±0.4649 time: 56.60

f-strings 即格式化字符串在 Python 3.6 中被引入:

# Python 3.6+
print(f'{batch:3} {epoch:3} / {total_epochs:3} accuracy: {numpy.mean(accuracies):0.4f}±{numpy.std(accuracies):0.4f} time: {time / len(data_batch):3.2f}')

另外,写查询语句时非常方便:

query = f"INSERT INTO STATION VALUES (13, '{city}', '{state}', {latitude}, {longitude})"

「true division」和「integer division」之间的明显区别

对于数据科学来说这种改变带来了便利(但我相信对于系统编程来说不是)。

data = pandas.read_csv('timing.csv')
velocity = data['distance'] / data['time']

Python 2 中的结果依赖于『时间』和『距离』(例如,以米和秒为单位)是否被保存为整数。

在 Python 3 中,结果的表示都是精确的,因为除法的结果是浮点数。

另一个案例是整数除法,现在已经作为明确的运算:

n_gifts = money // gift_price # correct for int and float arguments

注意,该运算可以应用到内建类型和由数据包(例如,numpy 或 pandas)提供的自定义类型。

推荐:Python核心团队计划2020年停止支持Python2,NumPy宣布停止支持计划表

[Python核心团队计划在2020年停止支持Python 2。NumPy项目自2010年以来一直支持Python 2和Python 3,并且发现支持Python 2对我们有限的资源增加了负担;因此,我们最终计划

严格排序

# All these comparisons are illegal in Python 3
3 < '3'
2 < None
(3, 4) < (3, None)
(4, 5) < [4, 5]
# False in both Python 2 and Python 3
(4, 5) == [4, 5]
防止不同类型实例的偶然性的排序。
sorted([2, '1', 3]) # invalid for Python 3, in Python 2 returns [2, 3, '1']
在处理原始数据时帮助发现存在的问题。
旁注:对 None 的合适检查是(两个版本的 Python 都适用):
if a is not None:
 pass
if a: # WRONG check for None
 pass

自然语言处理的 Unicode

s = '您好'
print(len(s))
print(s[:2])

输出:

Python 2: 6\n��
Python 3: 2\n 您好.
x = u'со'
x += 'co' # ok
x += 'со' # fail

Python 2 在此失败了,而 Python 3 可以如期工作(因为我在字符串中使用了俄文字母)。

在 Python 3 中 strs 是 Unicode 字符串,对非英语文本的 NLP 处理更加方便。

还有其它有趣的方面,例如:

'a' < type < u'a' # Python 2: True
'a' < u'a'   # Python 2: False
from collections import Counter
Counter('Möbelstück')
Python 2: Counter({'\xc3': 2, 'b': 1, 'e': 1, 'c': 1, 'k': 1, 'M': 1, 'l': 1, 's': 1, 't': 1, '\xb6': 1, '\xbc': 1})
Python 3: Counter({'M': 1, 'ö': 1, 'b': 1, 'e': 1, 'l': 1, 's': 1, 't': 1, 'ü': 1, 'c': 1, 'k': 1})

这些在 Python 2 里也能正确地工作,但 Python 3 更为友好。

保留词典和**kwargs 的顺序

在 CPython 3.6+ 版本中,字典的默认行为类似于 OrderedDict(在 3.7+版本中已得到保证)。这在字典理解(和其他操作如 json 序列化/反序列化期间)保持顺序。

import json
x = {str(i):i for i in range(5)}
json.loads(json.dumps(x))
# Python 2
{u'1': 1, u'0': 0, u'3': 3, u'2': 2, u'4': 4}
# Python 3
{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}

它同样适用于**kwargs(在 Python 3.6+版本中):它们的顺序就像参数中显示的那样。当设计数据流程时,顺序至关重要,以前,我们必须以这样繁琐的方式来编写:

from torch import nn
# Python 2
model = nn.Sequential(OrderedDict([
   ('conv1', nn.Conv2d(1,20,5)),
   ('relu1', nn.ReLU()),
   ('conv2', nn.Conv2d(20,64,5)),
   ('relu2', nn.ReLU())
  ]))
# Python 3.6+, how it *can* be done, not supported right now in pytorch
model = nn.Sequential(
 conv1=nn.Conv2d(1,20,5),
 relu1=nn.ReLU(),
 conv2=nn.Conv2d(20,64,5),
 relu2=nn.ReLU())
)

注意到了吗?名称的唯一性也会被自动检查。

迭代地拆封

# handy when amount of additional stored info may vary between experiments, but the same code can be used in all cases
model_paramteres, optimizer_parameters, *other_params = load(checkpoint_name)
# picking two last values from a sequence
*prev, next_to_last, last = values_history
# This also works with any iterables, so if you have a function that yields e.g. qualities,
# below is a simple way to take only last two values from a list
*prev, next_to_last, last = iter_train(args)

默认的 pickle 引擎为数组提供更好的压缩

# Python 2
import cPickle as pickle
import numpy
print len(pickle.dumps(numpy.random.normal(size=[1000, 1000])))
# result: 23691675
# Python 3
import pickle
import numpy
len(pickle.dumps(numpy.random.normal(size=[1000, 1000])))
# result: 8000162

节省 3 倍空间,而且速度更快。实际上,类似的压缩(不过与速度无关)可以通过 protocol=2 参数来实现,但是用户通常会忽略这个选项(或者根本不知道)。

更安全的解析

labels = <initial_value>
predictions = [model.predict(data) for data, labels in dataset]
# labels are overwritten in Python 2
# labels are not affected by comprehension in Python 3

关于 super()

Python 2 的 super(...)是代码错误中的常见原因。

# Python 2
class MySubClass(MySuperClass):
 def __init__(self, name, **options):
  super(MySubClass, self).__init__(name='subclass', **options)
# Python 3
class MySubClass(MySuperClass):
 def __init__(self, name, **options):
  super().__init__(name='subclass', **options)

关于 super 和方法解析顺序的更多内容,参见 stackoverflow:

https://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods

更好的 IDE 会给出变量注释

在使用 Java、C# 等语言编程的过程中最令人享受的事情是 IDE 可以提供非常好的建议,因为在执行代码之前,所有标识符的类型都是已知的。

而在 Python 中这很难实现,但是注释可以帮助你:

以清晰的形式写下你的期望

从 IDE 获取良好的建议

这是一个带变量注释的 PyCharm 示例。即使你使用的函数不带注释(例如,由于向后兼容性),它也能工作。

多种拆封(unpacking)

在 Python3 中融合两个字典的代码示例:

x = dict(a=1, b=2)
y = dict(b=3, d=4)
# Python 3.5+
z = {**x, **y}
# z = {'a': 1, 'b': 3, 'd': 4}, note that value for `b` is taken from the latter dict.

可以在这个链接中查看 Python2 中的代码对比:https://stackoverflow.com/questions/38987/how-to-merge-two-

dictionaries-in-a-single-expression

aame 方法对于列表(list)、元组(tuple)和集合(set)都是有效的(a、b、c 是任意的可迭代对象):

[*a, *b, *c] # list, concatenating
(*a, *b, *c) # tuple, concatenating
{*a, *b, *c} # set, union

对于*args 和 **kwargs,函数也支持额外的 unpacking:

Python 3.5+
do_something(**{**default_settings, **custom_settings})
# Also possible, this code also checks there is no intersection between keys of dictionaries
do_something(**first_args, **second_args)

只带关键字参数的 API

我们考虑这个代码片段:

model = sklearn.svm.SVC(2, 'poly', 2, 4, 0.5)

很明显,代码的作者还没熟悉 Python 的代码风格(很可能刚从 cpp 和 rust 跳到 Python)。不幸的是,这不仅仅是个人偏好的问题,因为在 SVC 中改变参数的顺序(adding/deleting)会使得代码无效。特别是,sklearn 经常会重排序或重命名大量的算法参数以提供一致的 API。每次重构都可能使代码失效。

在 Python3,库的编写者可能需要使用*以明确地命名参数:

class SVC(BaseSVC):
 def __init__(self, *, C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, ... )

现在,用户需要明确规定参数 sklearn.svm.SVC(C=2, kernel='poly', degree=2, gamma=4, coef0=0.5) 的命名。

这种机制使得 API 同时具备了可靠性和灵活性。

小调:math 模块中的常量

# Python 3
math.inf # 'largest' number
math.nan # not a number
max_quality = -math.inf # no more magic initial values!
for model in trained_models:
 max_quality = max(max_quality, compute_quality(model, data)

小调:单精度整数类型

Python 2 提供了两个基本的整数类型,即 int(64 位符号整数)和用于长时间计算的 long(在 C++变的相当莫名其妙)。

Python 3 有一个单精度类型的 int,它包含了长时间的运算。
下面是查看值是否是整数的方法:

isinstance(x, numbers.Integral) # Python 2, the canonical way
isinstance(x, (long, int))  # Python 2
isinstance(x, int)    # Python 3, easier to remember

其他

Enums 有理论价值,但是 字符串输入已广泛应用在 python 数据栈中。 Enums 似乎不与 numpy 交互,并且不一定来自 pandas。

协同程序也非常有希望用于数据流程,但还没有出现大规模应用。

Python 3 有稳定的 ABI

Python 3 支持 unicode(因此ω = Δφ / Δt 也 okay),但你最好使用好的旧的 ASCII 名称
一些库比如 jupyterhub(jupyter in cloud)、django 和新版 ipython 只支持 Python 3,因此对你来讲没用的功能对于你可能只想使用一次的库很有用。

数据科学特有的代码迁移问题(以及如何解决它们)

停止对嵌套参数的支持:

map(lambda x, (y, z): x, z, dict.items())

然而,它依然完美适用于不同的理解:

{x:z for x, (y, z) in d.items()}

通常,理解在 Python 2 和 3 之间可以更好地「翻译」。

map(), .keys(), .values(), .items(), 等等返回迭代器,而不是列表。迭代器的主要问题有: 没有琐碎的分割和 无法迭代两次。 将结果转化为列表几乎可以解决所有问题。

遇到问题请参见 Python 问答:我如何移植到 Python 3?(https://eev.ee/blog/2016/07/31/python-faq-how-do-i-port-to-python-3/)

用 python 教机器学习和数据科学的主要问题

课程作者应该首先花时间解释什么是迭代器,为什么它不能像字符串那样被分片/级联/相乘/迭代两次(以及如何处理它)。
我相信大多数课程作者很高兴避开这些细节,但是现在几乎不可能。
结论

Python 2 与 Python 3 共存了近 10 年,时至今日,我们必须要说:是时候转向 Python 3 了。

研究和生产代码应该更短,更易读取,并且在迁移到 Python 3 代码库之后明显更加的安全。

现在大多数库同时支持 2.x 和 3.x 两个版本。但我们不应等到流行工具包开始停止支持 Python 2 才开始行动,提前享受新语言的功能吧。

Python 相关文章推荐
Python httplib,smtplib使用方法
Sep 06 Python
python创建一个最简单http webserver服务器的方法
May 08 Python
Python使用三种方法实现PCA算法
Dec 12 Python
Python元组常见操作示例
Feb 19 Python
python文本数据处理学习笔记详解
Jun 17 Python
python PIL和CV对 图片的读取,显示,裁剪,保存实现方法
Aug 07 Python
如何基于pythonnet调用halcon脚本
Jan 20 Python
Tensorflow矩阵运算实例(矩阵相乘,点乘,行/列累加)
Feb 05 Python
python实现简单的tcp 文件下载
Sep 16 Python
详解如何修改python中字典的键和值
Sep 29 Python
浅谈Selenium 控制浏览器的常用方法
Dec 04 Python
Python简易开发之制作计算器
Apr 28 Python
python使用Tkinter实现在线音乐播放器
Jan 30 #Python
Python字典及字典基本操作方法详解
Jan 30 #Python
Python操作MySQL数据库的三种方法总结
Jan 30 #Python
python3.5 tkinter实现页面跳转
Jan 30 #Python
python 连接各类主流数据库的实例代码
Jan 30 #Python
python操作oracle的完整教程分享
Jan 30 #Python
Python使用wxPython实现计算器
Jan 30 #Python
You might like
PHP计划任务之关闭浏览器后仍然继续执行的函数
2010/07/22 PHP
一个漂亮的php验证码类(分享)
2013/08/06 PHP
解析isset与is_null的区别
2013/08/09 PHP
在Win7 中为php扩展配置Xcache
2014/10/08 PHP
教你php如何实现验证码
2016/01/20 PHP
PHP连接数据库实现注册页面的增删改查操作
2016/03/27 PHP
php 数组处理函数extract详解及实例代码
2016/11/23 PHP
ThinkPHP3.2.3框架实现执行原生SQL语句的方法示例
2019/04/03 PHP
JQuery 操作select标签实现代码
2010/05/14 Javascript
jquery中的过滤操作详细解析
2013/12/02 Javascript
js动态往表格的td中添加图片并注册事件
2014/06/12 Javascript
扒一扒JavaScript 预解释
2015/01/28 Javascript
js识别uc浏览器的代码
2015/11/06 Javascript
JavaScript File分段上传
2016/03/10 Javascript
JavaScript的Ext JS框架中的GridPanel组件使用指南
2016/05/21 Javascript
浅析Javascript中双等号(==)隐性转换机制
2017/10/27 Javascript
Node.js搭建小程序后台服务
2018/01/03 Javascript
JS实现监控微信小程序的原理
2018/06/15 Javascript
vue中使用[provide/inject]实现页面reload的方法
2019/09/30 Javascript
使用vscode快速建立vue模板过程详解
2019/10/10 Javascript
[02:43]DOTA2英雄基础教程 圣堂刺客
2013/12/09 DOTA
python 创建弹出式菜单的实现代码
2017/07/11 Python
Python logging模块用法示例
2018/08/28 Python
python实现对服务器脚本敏感信息的加密解密功能
2019/08/13 Python
python从ftp获取文件并下载到本地
2020/12/05 Python
python uuid生成唯一id或str的最简单案例
2021/01/13 Python
Python 的 f-string 可以连接字符串与数字的原因解析
2021/02/20 Python
海滩咖啡馆:Beach Cafe
2018/02/02 全球购物
Booking.com西班牙:全球酒店预订
2018/03/30 全球购物
杭州联环马网络笔试题面试题
2013/08/04 面试题
个性发展自我评价
2014/02/11 职场文书
幼儿园保育员岗位职责
2014/04/13 职场文书
群众路线表态发言材料
2014/10/17 职场文书
毕业生班级鉴定评语
2015/01/04 职场文书
乌镇导游词
2015/02/02 职场文书
早恋主题班会
2015/08/14 职场文书