如何愉快地迁移到 Python 3
引言
如今Python成为机器学习和大量使用数据操作的科学领域的主流语言;它拥有各种深度学习框架和完善的数据处理和可视化工具。但是,Python生态系统在Python2和Python3中共存,而Python2仍在数据科学家中使用。到2019年底,也将停止支持Python2。至于numpy,2018年9月之后任何新功能版本都将只支持Python3。同样的还包括pandas,matplotlib,ipython,jupyternotebookandjupyterlab。所以迁移到python3刻不容缓,当然不止是这些,还有些新特性让我们跟随后面到文章一一进行了解。
使用pathlib处理更好的路径
pathlib是Python3中的一个默认模块,可以帮助你避免使用大量的os.path.join。
frompathlibimportPath dataset='wiki_images' datasets_root=Path('/path/to/datasets/') #Navigatinginsideadirectorytree,use:/ train_path=datasets_root/dataset/'train' test_path=datasets_root/dataset/'test' forimage_pathintrain_path.iterdir(): withimage_path.open()asf:#note,openisamethodofPathobject #dosomethingwithanimage
不要用字符串链接的形式拼接路径,根据操作系统的不同会出现错误,我们可以使用/结合pathlib来拼接路径,非常的安全、方便和高可读性。
pathlib还有很多属性,具体的可以参考pathlib的官方文档,下面列举几个:
frompathlibimportPath a=Path("/data") b="test" c=a/b print(c) print(c.exists())#路径是否存在 print(c.is_dir())#判断是否为文件夹 print(c.parts)#分离路径 print(c.with_name('sibling.png'))#只修改拓展名,不会修改源文件 print(c.with_suffix('.jpg'))#只修改拓展名,不会修改源文件 c.chmod(777)#修改目录权限 c.rmdir()#删除目录
类型提示现在是语言的一部分
一个在Pycharm使用Typing的例子:
引入类型提示是为了帮助解决程序日益复杂的问题,IDE可以识别参数的类型进而给用户提示。
关于Tying的具体用法,可以看我之前写的:python类型检测最终指南--Typing的使用
运行时类型提示类型检查
除了之前文章提到mypy模块继续类型检查以外,还可以使用enforce模块进行检查,通过pip安装即可,使用示例如下:
importenforce @enforce.runtime_validation deffoo(text:str)->None: print(text) foo('Hi')#ok foo(5)#fails
输出
Hi Traceback(mostrecentcalllast): File"/Users/chennan/pythonproject/dataanalysis/e.py",line10,infoo(5)#fails File"/Users/chennan/Desktop/2019/env/lib/python3.6/site-packages/enforce/decorators.py",line104,inuniversal _args,_kwargs,_=enforcer.validate_inputs(parameters) File"/Users/chennan/Desktop/2019/env/lib/python3.6/site-packages/enforce/enforcers.py",line86,invalidate_inputs raiseRuntimeTypeError(exception_text) enforce.exceptions.RuntimeTypeError: Thefollowingruntimetypeerrorswereencountered: Argument'text'wasnotoftype .Actualtypewasint.
使用@表示矩阵的乘法
下面我们实现一个最简单的ML模型——l2正则化线性回归(又称岭回归)
#l2-regularizedlinearregression:||AX-y||^2+alpha*||x||^2->min #Python2 X=np.linalg.inv(np.dot(A.T,A)+alpha*np.eye(A.shape[1])).dot(A.T.dot(y)) #Python3 X=np.linalg.inv(A.T@A+alpha*np.eye(A.shape[1]))@(A.T@y)
使用@符号,整个代码变得更可读和方便移植到其他科学计算相关的库,如numpy,cupy,pytorch,tensorflow等。
**通配符的使用
在Python2中,递归查找文件不是件容易的事情,即使是使用glob库,但是从Python3.5开始,可以通过**通配符简单的实现。
importglob #Python2 found_images=( glob.glob('/path/*.jpg') +glob.glob('/path/*/*.jpg') +glob.glob('/path/*/*/*.jpg') +glob.glob('/path/*/*/*/*.jpg') +glob.glob('/path/*/*/*/*/*.jpg')) #Python3 found_images=glob.glob('/path/**/*.jpg',recursive=True)
更好的路径写法是上面提到的pathlib,我们可以把代码进一步改写成如下形式。
#Python3 importpathlib importglob found_images=pathlib.Path('/path/').glob('**/*.jpg')
Print函数
虽然Python3的print加了一对括号,但是这并不影响它的优点。
使用文件描述符的形式将文件写入
print>>sys.stderr,"criticalerror"#Python2 print("criticalerror",file=sys.stderr)#Python3
不使用str.join拼接字符串
#Python3 print(*array,sep='') print(batch,epoch,loss,accuracy,time,sep='')
重新定义print方法的行为
既然Python3中的print是一个函数,我们就可以对其进行改写。
#Python3 _print=print#storetheoriginalprintfunction defprint(*args,**kargs): pass#dosomethinguseful,e.g.storeoutputtosomefile
注意:在Jupyter中,最好将每个输出记录到一个单独的文件中(跟踪断开连接后发生的情况),这样就可以覆盖print了。
@contextlib.contextmanager defreplace_print(): importbuiltins _print=print#savingoldprintfunction #orusesomeotherfunctionhere builtins.print=lambda*args,**kwargs:_print('newprinting',*args,**kwargs) yield builtins.print=_print withreplace_print():
虽然上面这段代码也能达到重写print函数的目的,但是不推荐使用。
print可以参与列表理解和其他语言构造
#Python3 result=process(x)ifis_valid(x)elseprint('invaliditem:',x)
数字文字中的下划线(千位分隔符)
在PEP-515中引入了在数字中加入下划线。在Python3中,下划线可用于整数,浮点和复数,这个下划线起到一个分组的作用
#groupingdecimalnumbersbythousands one_million=1_000_000 #groupinghexadecimaladdressesbywords addr=0xCAFE_F00D #groupingbitsintonibblesinabinaryliteral flags=0b_0011_1111_0100_1110 #same,forstringconversions flags=int('0b_1111_0000',2)
也就是说10000,你可以写成10_000这种形式。
简单可看的字符串格式化f-string
Python2提供的字符串格式化系统还是不够好,太冗长麻烦,通常我们会写这样一段代码来输出日志信息:
#Python2 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) ) #Python2(tooerror-proneduringfastmodifications,pleaseavoid): 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.4649time:56.60
在Python3.6中引入了f-string(格式化字符串)
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}')
关于f-string的用法可以看我在b站的视频[https://www.bilibili.com/video/av31608754]
'/'和'//'在数学运算中有着明显的区别
对于数据科学来说,这无疑是一个方便的改变
data=pandas.read_csv('timing.csv') velocity=data['distance']/data['time']
Python2中的结果取决于“时间”和“距离”(例如,以米和秒为单位)是否存储为整数。在python3中,这两种情况下的结果都是正确的,因为除法的结果是浮点数。
另一个例子是floor除法,它现在是一个显式操作
n_gifts=money//gift_price#correctforintandfloatarguments nutshell >>>fromoperatorimporttruediv,floordiv >>>truediv.__doc__,floordiv.__doc__ ('truediv(a,b)--Sameasa/b.','floordiv(a,b)--Sameasa//b.') >>>(3/2),(3//2),(3.0//2.0) (1.5,1,1.0)
值得注意的是,这种规则既适用于内置类型,也适用于数据包提供的自定义类型(例如numpy或pandas)。
严格的顺序
下面的这些比较方式在Python3中都属于合法的。
3<'3' 2对于下面这种不管是2还是3都是不合法的
(4,5)==[4,5]
如果对不同的类型进行排序
sorted([2,'1',3])
虽然上面的写法在Python2中会得到结果[2,3,'1'],但是在Python3中上面的写法是不被允许的。
检查对象为None的合理方案
ifaisnotNone: pass ifa:#WRONGcheckforNone pass NLPUnicode问题 s='您好' print(len(s)) print(s[:2])输出内容
Python2:6 Python3:2您好.
还有下面的运算
x=u'со' x+='co'#ok x+='со'#failPython2失败了,Python3正常工作(因为我在字符串中使用了俄文字母)。
在Python3中,字符串都是unicode编码,所以对于非英语文本处理起来更方便。
一些其他操作
'a'再比如
fromcollectionsimportCounter Counter('Möbelstück')在Python2中
Counter({'Ã':2,'b':1,'e':1,'c':1,'k':1,'M':1,'l':1,'s':1,'t':1,'¶':1,'¼':1})
在Python3中
Counter({'M':1,'ö':1,'b':1,'e':1,'l':1,'s':1,'t':1,'ü':1,'c':1,'k':1})
虽然可以在Python2中正确地处理这些结果,但是在Python3中看起来结果更加友好。
保留了字典和**kwargs的顺序
在CPython3.6+中,默认情况下,dict的行为类似于OrderedDict,都会自动排序(这在Python3.7+中得到保证)。同时在字典生成式(以及其他操作,例如在json序列化/反序列化期间)都保留了顺序。
importjson x={str(i):iforiinrange(5)} json.loads(json.dumps(x)) #Python2 {u'1':1,u'0':0,u'3':3,u'2':2,u'4':4} #Python3 {'0':0,'1':1,'2':2,'3':3,'4':4}这同样适用于**kwargs(在Python3.6+中),它们的顺序与参数中出现的顺序相同。当涉及到数据管道时,顺序是至关重要的,以前我们必须以一种繁琐的方式编写它
fromtorchimportnn #Python2 model=nn.Sequential(OrderedDict([ ('conv1',nn.Conv2d(1,20,5)), ('relu1',nn.ReLU()), ('conv2',nn.Conv2d(20,64,5)), ('relu2',nn.ReLU()) ]))而在Python3.6以后你可以这么操作
#Python3.6+,howit*can*bedone,notsupportedrightnowinpytorch model=nn.Sequential( conv1=nn.Conv2d(1,20,5), relu1=nn.ReLU(), conv2=nn.Conv2d(20,64,5), relu2=nn.ReLU()) )可迭代对象拆包
类似于元组和列表的拆包,具体看下面的代码例子。
#handywhenamountofadditionalstoredinfomayvarybetweenexperiments,butthesamecodecanbeusedinallcases model_paramteres,optimizer_parameters,*other_params=load(checkpoint_name) #pickingtwolastvaluesfromasequence *prev,next_to_last,last=values_history #Thisalsoworkswithanyiterables,soifyouhaveafunctionthatyieldse.g.qualities, #belowisasimplewaytotakeonlylasttwovaluesfromalist *prev,next_to_last,last=iter_train(args)
提供了更高性能的pickle
Python2
importcPickleaspickle importnumpy printlen(pickle.dumps(numpy.random.normal(size=[1000,1000]))) #result:23691675 Python3 importpickle importnumpy len(pickle.dumps(numpy.random.normal(size=[1000,1000]))) #result:8000162
空间少了三倍。而且要快得多。实际上,使用protocol=2参数可以实现类似的压缩(但不是速度),但是开发人员通常忽略
这个选项(或者根本不知道)。
注意:pickle不安全(并且不能完全转移),所以不要unpickle从不受信任或未经身份验证的来源收到的数据。
更安全的列表推导
labels=predictions=[model.predict(data)fordata,labelsindataset] #labelsareoverwritteninPython2 #labelsarenotaffectedbycomprehensioninPython 更简易的super()
在python2中super相关的代码是经常容易写错的。
#Python2 classMySubClass(MySuperClass): def__init__(self,name,**options): super(MySubClass,self).__init__(name='subclass',**options) #Python3 classMySubClass(MySuperClass): def__init__(self,name,**options): super().__init__(name='subclass',**options)这一点Python3得到了很大的优化,新的super()可以不再传递参数。
同时在调用顺序上也不一样。
IDE能够给出更好的提示
使用Java、c#等语言进行编程最有趣的地方是IDE可以提供很好的建议,因为在执行程序之前,每个标识符的类型都是已知的。
在python中这很难实现,但是注释会帮助你
这是一个带有变量注释的PyCharm提示示例。即使在使用的函数没有注释的情况下(例如,由于向后兼容性),也可以使用这种方法。
Multipleunpacking
如何合并两个字典
x=dict(a=1,b=2) y=dict(b=3,d=4) #Python3.5+ z={**x,**y} #z={'a':1,'b':3,'d':4},notethatvaluefor`b`istakenfromthelatterdict.
我在b站同样发布了相关的视频[https://www.bilibili.com/video/av50376841]
同样的方法也适用于列表、元组和集合(a、b、c是任何迭代器)[*a,*b,*c]#list,concatenating (*a,*b,*c)#tuple,concatenating {*a,*b,*c}#set,union函数还支持*arg和**kwarg的多重解包
#Python3.5+ do_something(**{**default_settings,**custom_settings}) #Alsopossible,thiscodealsochecksthereisnointersectionbetweenkeysofdictionaries do_something(**first_args,**second_args) DataclassesPython3.7引入了Dataclass类,它适合存储数据对象。数据对象是什么?下面列出这种对象类型的几项特征,虽然不全面:
它们存储数据并表示某种数据类型,例如:数字。对于熟悉ORM的朋友来说),数据模型实例就是一个数据对象。它代表了一种特定的实体。它所具有的属性定义或表示了该实体。
它们可以与同一类型的其他对象进行比较。例如:大于、小于或等于。
当然还有更多的特性,下面的这个例子可以很好的替代namedtuple的功能。
dataclass装饰器实现了
@dataclass classPerson: name:str age:int @dataclass classCoder(Person): preferred_language:str='Python3'几个魔法函数方法的功能(__init__,__repr__,__le__,__eq__)
关于数据类有以下几个特性:
数据类可以是可变的,也可以是不可变的
支持字段的默认值
可被其他类继承
数据类可以定义新的方法并覆盖现有的方法
初始化后处理(例如验证一致性)
更多内容可以参考官方文档。
自定义对模块属性的访问在Python中,可以用getattr和dir控制任何对象的属性访问和提示。因为python3.7,你也可以对模块这样做。
一个自然的例子是实现张量库的随机子模块,这通常是跳过初始化和传递随机状态对象的快捷方式。numpy的实现如下:
#nprandom.py importnumpy __random_state=numpy.random.RandomState() def__getattr__(name): returngetattr(__random_state,name) def__dir__(): returndir(__random_state) defseed(seed): __random_state=numpy.random.RandomState(seed=seed)也可以这样混合不同对象/子模块的功能。与pytorch和cupy中的技巧相比。
除此之外,还可以做以下事情:
使用它来延迟加载子模块。例如,导入tensorflow时会导入所有子模块(和依赖项)。需要大约150兆内存。
在应用编程接口中使用此选项进行折旧
在子模块之间引入运行时路由
内置的断点
在python3.7中可以直接使用breakpoint给代码打断点
#Python3.7+,notallIDEssupportthisatthemoment foo() breakpoint() bar()在python3.7以前我们可以通过importpdb的pdb.set_trace()实现相同的功能。
对于远程调试,可尝试将breakpoint()与web-pdb结合使用.
Math模块中的常数
#Python3 math.inf#Infinitefloat math.nan#notanumber max_quality=-math.inf#nomoremagicinitialvalues! formodelintrained_models: max_quality=max(max_quality,compute_quality(model,data))
整数类型只有int
Python2提供了两种基本的整数类型,一种是int(64位有符号整数)一种是long,使用起来非常容易混乱,而在python3中只提供了int类型这一种。
isinstance(x,numbers.Integral)#Python2,thecanonicalway isinstance(x,(long,int))#Python2 isinstance(x,int)#Python3,easiertoremember在python3中同样的也可以应用于其他整数类型,如numpy.int32、numpy.int64,但其他类型不适用。
总结
以上所述是小编给大家介绍的愉快地迁移到Python3,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!