NumPy 将停止支持 Python 2,这里有一份给数据科学家的 Python 3 使用指导

Python 已经成为机器学习和数据科学的主要编程语言,同时 Python 2 和 Python 3 共存与 Python 的生态体系内。不过,在 2019 年底,NumPy 将停止支持 Python 2.7,2018 年后的新版本只支持 Python 3。

为了让数据科学家们快速上手 Python 3,该库介绍了一些 Python 3 的新功能,供数据工作者参考。

本文首发于 Github,原文链接请见文末,AI 研习社编译如下:

更好的 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

以前,开发者喜欢用字符串来连接(虽然简洁,但是很明显这是很不好的),而现在 pathlib 的代码则是简洁、安全并且有高可读性的。

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/

类型提示已经是 Python 的一部分

下图是 pycharm 中的类型提示案例:

Python 不再是仅用于编写一些小脚本的编程语言,如今的数据处理流程包括了不同的步骤并涉及到了不同的框架。

程序复杂度日益增长,类型提示功能的引入能够很好地缓解这样的状况,能够让机器帮助验证代码。

下面是个简单的例子,这些代码可以处理不同类型的数据(这就是我们喜欢的 Python 数据栈):

def repeat_each_entry(data):
    """ Each entry in the data is doubled
    <blah blah nobody reads the documentation till the end>
    """
    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

输入提示 —— 在运行时检查类型

默认情况下,函数注释不会影响你的代码,只是用于说明代码的意图。

不过,你可以在运行时使用类似 ... 这样的工具强制输入检测,这能有助于你的 debug(在很多情况下,输入提示不起作用)。

@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 包可以提供一个简单的修饰器来控制输入量的单位,并将输出转化为所需的单位。

# 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。

你也可以定义特定应用程序的修饰器,以相同的方式执行输入和输出的控制与转换。

矩阵与 @ 相乘

我们来实现一个最简单的 ML 模型 —— 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)

带有 @ 的代码在不同的深度学习框架之间更具有可读性并且更容易翻译:

同样的单层感知代码 X @ W + b[None, :] 可以在 numpy、cupy、pytorch、tensorflow 中运行。

与 ** Globbing

在 Python 2 里,递归文件的 globbing 并不容易,即使有 glob2 (https://github.com/miracle2k/python-glob2)模块克服了这点。从 3.5 版本开始,Python 支持递归 flag:

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)

一个更好的选择是在 Python 3 中使用 pathlib:

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

Print 现在是一个功能

是的,虽然代码里有些恼人的括号,但是这也有些优点:

  • 用于文件描述符的简单语法
print >>sys.stderr, "critical error"      # Python 2
print("critical error", file=sys.stderr)  # Python 3
  • 打印没有 str.jion 的 tab-aligned 表格
# Python 3
print(*array, sep='\t')
print(batch, epoch, loss, accuracy, time, sep='\t')
  • 哈希压制/重定向打印输出:
# 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 中最好将每个输出记录到一个单独的文件中(方便在你断线后跟踪),所以你现在可以覆盖(override)print 了,

在下面你可以看到一个临时覆盖 print 行为的 Context Manager:

@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)

数字间的下划线(千分位分隔符)

PEP-515(https://www.python.org/dev/peps/pep-0515/) 在数字文字中引入了下划线。在 Python3 里,下划线可用于整数、浮点数、和复数的可视化分隔。

# grouping decimal numbers by thousands
one_million = 1_000_000

# grouping hexadecimal addresses by words
addr = 0xCAFE_F00D

# grouping bits into nibbles in a binary literal
flags = 0b_0011_1111_0100_1110

# same, for string conversions
flags = int('0b_1111_0000', 2)

用于简单格式化的 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

在 Python 3.6 中,引入了f-strings aka 格式化的字符串文字:

# 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}')

「真除」和「整除」之间的区别

对于数据科学家来讲,这是一个非常方便的改变。

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

在 Python2 中,结果的类型取决于'time' 和 'distance' 是否储存为整数,在 Python3 中,结果在两种情况下都正确,因为结果是浮点型。

严格的 ordering

# 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]

Unicode for NLP

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

输出:

  • Python 2: 6\n��
  • Python 3: 2\n您好.

x = u'со'

x += 'co' # ok

x += 'со' # fail

输出结果在 Python2 失败,Python3 正常。

在 Python3 中,str 是 unicode 字符串,用于非英文文本的 NLP 更加方便。

保留字典和 **kwargs 的顺序

在默认情况下,CPython 3.6+ 中的 dicts 的行为类似 OrderedDict。这保留了 dict 理解的顺序(以及一些其他操作,比如在 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())
)

Iterable 拆包

# 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

更少的空间,更快的速度。实际上,类似的压缩可以通过 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()

Python2 的 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 的解析请移步:

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

更好的 IDE 和变量注释

在 Java,C#等语言编程里,最令人愉快的事情是 IDE 可以提出非常好的建议,因为在执行程序之前每个标识符的类型是已知的。

在 Python 里很难实现,但是注释会帮你:

  • 用清晰的形式写下你的期望
  • 从 IDE 里获取好的建议

多拆包

下面是你如何合并两个 dicts:

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

同样的方法也适用于列表、元组和集合(a、b、c 是任意 iterables):

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

函数也支持* args和** kwargs:

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 的编码风格(他很有可能刚从 C++ 或者 rust 跳转到 Python 开发上)。不幸的是,这不是编码风格的问题,因为你改变 SVC 中参数的顺序将打破这段代码。特别是,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 可靠性和灵活性

结论

Python2 和 Python3 共存了将近十年,现在我们应该转移到 Python3上,研究和产品开发代码转移到 Python3-Only 的代码库之后会更简洁、更安全、更易读。

Via:https://github.com/arogozhnikov/python3_with_pleasure

原文发布于微信公众号 - AI研习社(okweiwu)

原文发表时间:2018-02-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java架构师学习

十年Java”老兵“浅谈源码的七大设计模式

一个专业的程序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位置。他们总是通过定义大量的宏,来增强代码的清晰度和可读性,而又不增加编译后的代码长度和代码...

381120
来自专栏程序员互动联盟

【编程基础】写代码,你应该知道九类规则

网上有太多讲编码规范、编码习惯的文章,但我总是念的多,实际去认真阅读理解的少。或多或少的按照自己的思维去编写代码。这种习惯让我吃大亏,比如一个指针未赋值导致偶尔...

44250
来自专栏C/C++基础

C++ 模板元编程简介

模板元编程(Template Metaprogramming,TMP)是编写生成或操纵程序的程序,也是一种复杂且功能强大的编程范式(Programming Pa...

1.6K30
来自专栏光变

你所不知道的Java之HashCode

以下内容为作者辛苦原创,版权归作者所有,如转载演绎请在“光变”微信公众号留言申请,转载文章请在开始处显著标明出处。

14700
来自专栏Java爬坑系列

【JAVA零基础入门系列】Day11 Java中的类和对象

  今天要说的是Java中两个非常重要的概念——类和对象。   什么是类,什么又是对象呢?类是对特定集合的概括描述,比如,人,这个类,外在特征上,有名字,有年龄...

210100
来自专栏每周一脱topic

Effictive python学习总结连载(1)

python从读研开始就在用了,拿来做过web后台、安全分析、爬虫、测试框架等等,挺强大的。最近借放假和看书和整理的机会,系统的总结下。主要是2方面:一个是书或...

22220
来自专栏大史住在大前端

野生前端的数据结构基础练习(5)——散列

散列函数相关的应用非常广,例如webpack打包时在文件名中添加的哈希值,将给定信息转换为固定位数字符串的加密信息等都是散列的实际应用,感兴趣的读者可以自行搜索...

9320
来自专栏CDA数据分析师

教你一招:用 50 行 Python 代码制作一个计算器

简介 在这篇文章中,我将向大家演示怎样向一个通用计算器一样解析并计算一个四则运算表达式。当我们结束的时候,我们将得到一个可以处理诸如 1+2*-(-3+2)/5...

22870
来自专栏黑泽君的专栏

linux系统下,警告:warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration] 和 war

gets()函数的基本用法为: char *gets(char *s); 该函数的参数是一个字符数组,该函数的返回值也是一个字符数组。

56710
来自专栏编程

Python教学——第六天

今天我们要说说dict,在第四天里我们说到了tuple,list也知道了list比tuple好用多了,至少能添加删除还能修改里面的值 在Python里,我们知道...

20870

扫码关注云+社区

领取腾讯云代金券