编译: Python开发者 -冲动老少年 英文:Alex Rogozhnikov
http://python.jobbole.com/89031/
为数据科学家准备的 Python 3 特性指南
Python 已经成为机器学习和一些需处理大量数据的科学领域的主流语言。它支持了许多深度学习框架和其他已确立下来的数据处理和可视化的工具集。
然而,Python 生态系统还处于 Python 2 和 Python 3 并存的状态,且 Python 2 仍然被数据科学家们所使用。从 2019 年底开始,系统工具包将会停止对 Python 2 的支持。对于 numpy,2018 年之后任何更新将只支持 Python 3。
为了让大家能够顺利过渡,我收集了一系列 Python 3 特性,希望对大家有用。
图片来源于Dario Bertini post (toptal)
使用 pathlib 更好的对路径进行处理
frompathlibimportPath
dataset='wiki_images'
datasets_root=Path('/path/to/datasets/')
train_path=datasets_root/dataset/'train'
test_path=datasets_root/dataset/'test'
forimage_pathintrain_path.iterdir():
withimage_path.open()asf:# note, open is a method of Path object
# do something with an image
在之前的版本中总是不可避免的使用字符串连接(简洁但明显可读性很差),如今使用 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 会节省你大量的时间,具体用法请参考文档和说明。
类型提示现在是 Python 的一部分啦
Pycharm 中类型提示示例:
Python 已不再是一个小型的脚本语言了,如今的数据处理流程包含许多步骤,每步涉及不同的构架(而且有时会涉及不同的逻辑)
引入类型提示功能有助于处理日渐复杂的程序,因此机器就可以帮助实现代码验证。而以前是不同的模块需使用自定义的方式在文档字符串(doctrings)中指定类型(提示:pycharm 能够将旧的 doctrings 转换为新的 Type hinting)。
下图是一个简单的例子,这段代码对不同类型的数据均有效(这正是我们喜欢 Python 数据栈的原因)。
def repeat_each_entry(data):
""" Each entry in the data is doubled
"""
index=numpy.repeat(numpy.arange(len(data)),2)
returndata[index]
这段代码样例适用于 numpy.array(含多维数组)、astropy.Table 及 astropy.Column、bcolz、cupy、mxnet.ndarray 等。
这段代码可用于 pandas.Series,但方式不对:
repeat_each_entry(pandas.Series(data=[,1,2],index=[3,4,5]))# returns Series with Nones inside
这还仅是两行代码。想象一下一个复杂系统的行为将是多么的难以预测,仅因一个函数就有可能行为失常。在大型系统中,明确各类方法的期望类型是非常有帮助的,这样会在函数未得到期望的参数类型时给出警告。
def repeat_each_entry(data:Union[numpy.ndarray,bcolz.carray]):
如果你有重要的代码库, MyPy 这样的提示工具很可能成为持续集成途径的一部分。由 Daniel Pyrathon 发起的名为“让类型提示生效”的在线教程可为您提供一个很好的介绍。
旁注:不幸的是,类型提示功能还未强大到能为 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:
returnany(x)
any([False,False,True,False])# True
any2([False,False,True,False])# True
any(['False'])# True
any2(['False'])# fails
any([False,None,"",])# False
any2([False,None,"",])# fails
函数注释的其他惯例
如前所述,函数注释不会影响代码的执行,但是它可提供一些供你随意使用的元信息(译者注:关于信息的信息)。
例如,计量单位是科学领域的一个常见问题,astropy 包能够提供一种简单装饰器来控制输入量的单位并将输出量转换成所需单位。
# Python 3
from astropy import unitsasu
@u.quantity_input()
def frequency(speed:u.meter/u.s,wavelength:u.m)->u.terahertz:
returnspeed/wavelength
frequency(speed=300_000*u.km/u.s,wavelength=555*u.nm)
如果你正使用 Python 处理表格式科学数据(数据量很大),你应该试一试 astropy。
你也可以定义你的专用装饰器以同样的方法对输入量和输出量进行控制或转换。
使用 @ 进行矩阵乘积
我们来执行一个简单的机器学习模型,带 L2 正则化的线性回归(也称脊回归):
# l2-regularized linear regression: || AX - b ||^2 + alpha * ||x||^2 -> min
# Python 2
# Python 3
使用 @ 的代码更可读也更容易在各深度学习架构间转译:一个单层感知器可以在 numpy、cupy、pytorch、tensorflow(和其他操作张量的框架)下运行相同的代码 X @ W + b[None, :] 实现。
使用 ** 作通配符
递归文件夹的通配符在 python 2 中实现起来并不简单,实际上我们要自定义 glob2 模块来克服这个问题。而从 Python 3.6 以后将支持遍历标志:
importglob
# 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(-1 导入!):
# 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.join 输出制表符:
# 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函数了。
下面你可以看到名为contextmanager的装饰器暂时重写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():
这种方法并不推荐,因为此时有可能出现些小问题。
print 函数可参与列表理解和其他语言构建。
# Python 3
result=process(x)ifis_valid(x)elseprint('invalid item: ',x)
数值中的下划线(千位分隔符)
PEP-515 在数值中引入下划线。在 Python 3 中,下划线可用于整数、浮点数、复数的位数进行分组,增强可视性。
# 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(' / accuracy: ± time: '.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)
))
样本输出:
12012/300accuracy:0.8180±0.4649time:56.60
f-strings全称为格式化字符串,引入到了Python 3.6:
# Python 3.6+
print(f' / accuracy: ± time: ')
同时,写查询或者进行代码分段时也非常便利:
query=f"INSERT INTO STATION VALUES (13, , , , )"
重点:别忘了转义字符以防 SQL 注入攻击。
‘真实除法’与‘整数除法’的明确区别
这对于数据科学而言是非常便利的改变(但我相信对于系统编程而言却不是)
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)提供的自定义类型。
严格排序
# All these comparisons are illegal in Python 3
(3,4)
(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 均需要)
ifaisnotNone:
pass
ifa:# WRONG check for None
pass
自然语言处理(NLP)中的统一编码标准(Unicode)
s='您好'
print(len(s))
print(s[:2])
输出:
Python 2:
Python 3:.
x=u'со'
x+='co'# ok
x+='со'# fail
Python 2 失效而 Python 3 如期输出(因为我在字符串中使用了俄文字母)
在 Python 3 中 strs 是 unicode 字符串,这更方便处理非英语文本的 NPL。
还有其他好玩的例子,比如:
'a'
'a'
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(这在 Python 3.6+ 版本中已被保证)。这样可在理解字典(及其他操作,例如:json 序列化或反序列化)时保持了顺序。
importjson
x={str(i):iforiinrange(5)}
json.loads(json.dumps(x))
# Python 2
{u'1':1,u'0':,u'3':3,u'2':2,u'4':4}
# Python 3
{'0':,'1':1,'2':2,'3':3,'4':4}
这同样适用于 **kwargs(在 Python 3.6+ 中),即按照 **kwargs 在参数中出现的顺序来保存。在涉及到数据流这个顺序是至关重要的,而以前我们不得不用一种麻烦的方法来实现。
from torch importnn
# 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 cPickleaspickle
import numpy
# Python 3
import pickle
import numpy
# result: 8000162
节省三倍空间,并且更快速。实际上 protocol=2 参数可以实现相同的压缩(但是速度不行),但是使用者基本上都会忽视这个选项(或者根本没有意识到)。
更安全的解析
labels=
predictions=[model.predict(data)fordata,labelsindataset]
# labels are overwritten in Python 2
# labels are not affected by comprehension in Python 3
超级简单的 super 函数
在 Python 2 中,super(…) 是代码里常见的错误源。
# Python 2
classMySubClass(MySuperClass):
def __init__(self,name,**options):
super(MySubClass,self).__init__(name='subclass',**options)
# Python 3
classMySubClass(MySuperClass):
def __init__(self,name,**options):
super().__init__(name='subclass',**options)
领取专属 10元无门槛券
私享最新 技术干货