在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编程-将Python程序转化为可执行程序[整理]
Apr 09 Python
python基础教程之字典操作详解
Mar 25 Python
Python基于pillow判断图片完整性的方法
Sep 18 Python
PyTorch 1.0 正式版已经发布了
Dec 13 Python
更新修改后的Python模块方法
Mar 03 Python
Python通过TensorFlow卷积神经网络实现猫狗识别
Mar 14 Python
tensorflow 保存模型和取出中间权重例子
Jan 24 Python
Python requests模块基础使用方法实例及高级应用(自动登陆,抓取网页源码)实例详解
Feb 14 Python
如何把python项目部署到linux服务器
Aug 26 Python
Django创建一个后台的基本步骤记录
Oct 02 Python
Python 处理表格进行成绩排序的操作代码
Jul 26 Python
Python初识逻辑与if语句及用法大全
Aug 07 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实现小写金额转换大写金额的代码(精确到分)
2012/01/10 PHP
实例介绍PHP的Reflection反射机制
2014/08/05 PHP
php获取网站百度快照日期的方法
2015/07/29 PHP
Symfony2实现在doctrine中内置数据的方法
2016/02/05 PHP
PHP延迟静态绑定的深入讲解
2018/04/02 PHP
浅谈PHP匿名函数和闭包
2019/03/08 PHP
浅谈php的TS和NTS的区别
2019/03/13 PHP
宝塔面板在NGINX环境中TP5.1如何运行?
2021/03/09 PHP
使用jQuery+HttpHandler+xml模拟一个三级联动的例子
2011/08/09 Javascript
js FLASH幻灯片字符串中有连接符&的处理方法
2012/03/01 Javascript
JavaScript 模式之工厂模式(Factory)应用介绍
2012/11/15 Javascript
javascript小数四舍五入多种方法实现
2012/12/23 Javascript
JS获取计算机mac地址以及IP的实现方法
2014/01/08 Javascript
jquery实现图片按比例缩放示例
2014/07/01 Javascript
angularjs中回车键触发某一事件的方法
2017/04/24 Javascript
vue-resouce设置请求头的三种方法
2017/09/12 Javascript
原生JavaScrpit中异步请求Ajax实现方法
2017/11/03 Javascript
Angular实现的自定义模糊查询、排序及三角箭头标注功能示例
2017/12/28 Javascript
学习JS中的DOM节点以及操作
2018/04/30 Javascript
JS实现点击发送验证码 xx秒后重新发送功能
2019/07/30 Javascript
Vue中强制组件重新渲染的正确方法
2021/01/03 Vue.js
[34:39]Secret vs VG 2018国际邀请赛淘汰赛BO3 第二场 8.23
2018/08/24 DOTA
python局部赋值的规则
2013/03/07 Python
python清除指定目录内所有文件中script的方法
2015/06/30 Python
Python http接口自动化测试框架实现方法示例
2018/12/06 Python
python GUI库图形界面开发之PyQt5控件数据拖曳Drag与Drop详细使用方法与实例
2020/02/27 Python
用python实现一个简单计算器(完整DEMO)
2020/10/14 Python
新加坡时尚网上购物:Zalora新加坡
2016/07/26 全球购物
Daisy London官网:英国最大的首饰集团IBB旗下
2019/02/28 全球购物
英国健身仓库:Bodybuilding Warehouse
2019/03/06 全球购物
历史学专业个人的自我评价
2013/10/13 职场文书
药物学专业学生的自我评价
2013/10/27 职场文书
大客户经理岗位职责
2015/04/09 职场文书
如何书写你的职业生涯规划书?
2019/06/27 职场文书
SpringRetry重试框架的具体使用
2021/07/25 Java/Android
Windows Server 2019 域控制器安装图文教程
2022/04/28 Servers