分享一下如何编写高效且优雅的 Python 代码


Posted in Python onSeptember 07, 2017

本文部分提炼自书籍:《Effective Python》&《Python3 Cookbook》,但也做出了修改,并加上了作者自己的理解和运用中的最佳实践。

全文约 9956 字,读完可能需要 24 分钟。

Pythonic列表切割

list[start:end:step]

如果从列表开头开始切割,那么忽略 start 位的 0,例如list[:4]

如果一直切到列表尾部,则忽略 end 位的 0,例如list[3:]

切割列表时,即便 start 或者 end 索引跨界也不会有问题

列表切片不会改变原列表。索引都留空时,会生成一份原列表的拷贝

分享一下如何编写高效且优雅的 Python 代码

列表推导式

使用列表推导式来取代mapfilter

分享一下如何编写高效且优雅的 Python 代码

不要使用含有两个以上表达式的列表推导式

分享一下如何编写高效且优雅的 Python 代码

数据多时,列表推导式可能会消耗大量内存,此时建议使用生成器表达式

分享一下如何编写高效且优雅的 Python 代码

迭代

需要获取 index 时使用enumerate

enumerate可以接受第二个参数,作为迭代时加在index上的数值

分享一下如何编写高效且优雅的 Python 代码

zip同时遍历两个迭代器

分享一下如何编写高效且优雅的 Python 代码

zip遍历时返回一个元组

分享一下如何编写高效且优雅的 Python 代码

关于forwhile循环后的else

循环正常结束之后会调用else内的代码

循环里通过break跳出循环,则不会执行else

要遍历的序列为空时,立即执行else

分享一下如何编写高效且优雅的 Python 代码

反向迭代

对于普通的序列(列表),我们可以通过内置的reversed()函数进行反向迭代:

分享一下如何编写高效且优雅的 Python 代码

除此以外,还可以通过实现类里的__reversed__方法,将类进行反向迭代:

分享一下如何编写高效且优雅的 Python 代码

try/except/else/finally

如果try内没有发生异常,则调用else内的代码

else会在finally之前运行

最终一定会执行finally,可以在其中进行清理工作

函数使用装饰器

装饰器用于在不改变原函数代码的情况下修改已存在的函数。常见场景是增加一句调试,或者为已有的函数增加log监控

举个栗子:

分享一下如何编写高效且优雅的 Python 代码

除此以外,还可以编写接收参数的装饰器,其实就是在原本的装饰器上的外层又嵌套了一个函数:

分享一下如何编写高效且优雅的 Python 代码

但是像上面那样使用装饰器的话有一个问题:

分享一下如何编写高效且优雅的 Python 代码

也就是说原函数已经被装饰器里的new_fun函数替代掉了。调用经过装饰的函数,相当于调用一个新函数。查看原函数的参数、注释、甚至函数名的时候,只能看到装饰器的相关信息。为了解决这个问题,我们可以使用

Python 自带的functools.wraps方法。

functools.wraps是个很 hack 的方法,它本事作为一个装饰器,做用在装饰器内部将要返回的函数上。也就是说,它是装饰器的装饰器,并且以原函数为参数,作用是保留原函数的各种信息,使得我们之后查看被装饰了的原函数的信息时,可以保持跟原函数一模一样。

分享一下如何编写高效且优雅的 Python 代码

此外,有时候我们的装饰器里可能会干不止一个事情,此时应该把事件作为额外的函数分离出去。但是又因为它可能仅仅和该装饰器有关,所以此时可以构造一个装饰器类。原理很简单,主要就是编写类里的__call__方法,使类能够像函数一样的调用。

分享一下如何编写高效且优雅的 Python 代码

使用生成器

考虑使用生成器来改写直接返回列表的函数

分享一下如何编写高效且优雅的 Python 代码

用这种方法有几个小问题:

每次获取到符合条件的结果,都要调用append方法。但实际上我们的关注点根本不在这个方法,它只是我们达成目的的手段,实际上只需要index就好了

返回的result可以继续优化

数据都存在result里面,如果数据量很大的话,会比较占用内存

因此,使用生成器generator会更好。生成器是使用yield表达式的函数,调用生成器时,它不会真的执行,而是返回一个迭代器,每次在迭代器上调用内置的next函数时,迭代器会把生成器推进到下一个yield表达式:

分享一下如何编写高效且优雅的 Python 代码

获取到一个生成器以后,可以正常的遍历它:

分享一下如何编写高效且优雅的 Python 代码

如果你还是需要一个列表,那么可以将函数的调用结果作为参数,再调用list方法

分享一下如何编写高效且优雅的 Python 代码

可迭代对象

需要注意的是,普通的迭代器只能迭代一轮,一轮之后重复调用是无效的。解决这种问题的方法是,你可以定义一个可迭代的容器类

分享一下如何编写高效且优雅的 Python 代码

这样的话,将类的实例迭代重复多少次都没问题:

分享一下如何编写高效且优雅的 Python 代码

但要注意的是,仅仅是实现__iter__方法的迭代器,只能通过for循环来迭代;想要通过next方法迭代的话则需要使用iter方法:

分享一下如何编写高效且优雅的 Python 代码

使用位置参数

有时候,方法接收的参数数目可能不一定,比如定义一个求和的方法,至少要接收两个参数:

分享一下如何编写高效且优雅的 Python 代码

对于这种接收参数数目不一定,而且不在乎参数传入顺序的函数,则应该利用位置参数*args

分享一下如何编写高效且优雅的 Python 代码

但要注意的是,不定长度的参数args在传递给函数时,需要先转换成元组tuple。这意味着,如果你将一个生成器作为参数带入到函数中,生成器将会先遍历一遍,转换为元组。这可能会消耗大量内存:

分享一下如何编写高效且优雅的 Python 代码

使用关键字参数

关键字参数可提高代码可读性

可以通过关键字参数给函数提供默认值

便于扩充函数参数

定义只能使用关键字参数的函数

普通的方式,在调用时不会强制要求使用关键字参数

分享一下如何编写高效且优雅的 Python 代码

使用 Python3 中强制关键字参数的方式

分享一下如何编写高效且优雅的 Python 代码

使用 Python2 中强制关键字参数的方式

分享一下如何编写高效且优雅的 Python 代码

关于参数的默认值

算是老生常谈了:函数的默认值只会在程序加载模块并读取到该函数的定义时设置一次

也就是说,如果给某参数赋予动态的值(

比如[]或者{}),则如果之后在调用函数的时候给参数赋予了其他参数,则以后再调用这个函数的时候,之前定义的默认值将会改变,成为上一次调用时赋予的值:

分享一下如何编写高效且优雅的 Python 代码

因此,更推荐使用None作为默认参数,在函数内进行判断之后赋值:

分享一下如何编写高效且优雅的 Python 代码

__slots__

默认情况下,Python 用一个字典来保存一个对象的实例属性。这使得我们可以在运行的时候动态的给类的实例添加新的属性:

分享一下如何编写高效且优雅的 Python 代码

然而这个字典浪费了多余的空间 -— 很多时候我们不会创建那么多的属性。因此通过__slots__可以告诉 Python

不要使用字典而是固定集合来分配空间。

分享一下如何编写高效且优雅的 Python 代码

__call__

通过定义类中的__call__方法,可以使该类的实例能够像普通函数一样调用。

分享一下如何编写高效且优雅的 Python 代码

通过这种方式实现的好处是,可以通过类的属性来保存状态,而不必创建一个闭包或者全局变量。

@classmethod & @staticmethod

@classmethod@staticmethod很像,但他们的使用场景并不一样。

类内部普通的方法,都是以self作为第一个参数,代表着通过实例调用时,将实例的作用域传入方法内;

@classmethodcls作为第一个参数,代表将类本身的作用域传入。无论通过类来调用,还是通过类的实例调用,默认传入的第一个参数都将是类本身

@staticmethod不需要传入默认参数,类似于一个普通的函数

来通过实例了解它们的使用场景:

假设我们需要创建一个名为Date的类,用于储存 年/月/日 三个数据

分享一下如何编写高效且优雅的 Python 代码

上述代码创建了Date类,该类会在初始化时设置day/month/year属性,并且通过property设置了一个getter,可以在实例化之后,通过time获取存储的时间:

分享一下如何编写高效且优雅的 Python 代码

但如果我们想改变属性传入的方式呢?毕竟,在初始化时就要传入年/月/日三个属性还是很烦人的。能否找到一个方法,在不改变现有接口和方法的情况下,可以通过传入2016-11-09这样的字符串来创建一个Date实例?

你可能会想到这样的方法:

分享一下如何编写高效且优雅的 Python 代码

但不够好:

在类外额外多写了一个方法,每次还得格式化以后获取参数

这个方法也只跟Date类有关

没有解决传入参数过多的问题

此时就可以利用@classmethod,在类的内部新建一个格式化字符串,并返回类的实例的方法:

分享一下如何编写高效且优雅的 Python 代码

这样,我们就可以通过Date类来调用from_string方法创建实例,并且不侵略、修改旧的实例化方式:

分享一下如何编写高效且优雅的 Python 代码

好处:

@classmethod内,可以通过cls参数,获取到跟外部调用类时一样的便利

可以在其中进一步封装该方法,提高复用性

更加符合面向对象的编程方式

@staticmethod,因为其本身类似于普通的函数,所以可以把和这个类相关的 helper

方法作为@staticmethod,放在类里,然后直接通过类来调用这个方法。

分享一下如何编写高效且优雅的 Python 代码

将与日期相关的辅助类函数作为@staticmethod方法放在Date类内后,可以通过类来调用这些方法:

分享一下如何编写高效且优雅的 Python 代码

创建上下文管理器

上下文管理器,通俗的介绍就是:在代码块执行前,先进行准备工作;在代码块执行完成后,做收尾的处理工作。with语句常伴随上下文管理器一起出现,经典场景有:

分享一下如何编写高效且优雅的 Python 代码

通过with语句,代码完成了文件打开操作,并在调用结束,或者读取发生异常时自动关闭文件,即完成了文件读写之后的处理工作。如果不通过上下文管理器的话,则会是这样的代码:

分享一下如何编写高效且优雅的 Python 代码

比较繁琐吧?所以说使用上下文管理器的好处就是,通过调用我们预先设置好的回调,自动帮我们处理代码块开始执行和执行完毕时的工作。而通过自定义类的__enter____exit__方法,我们可以自定义一个上下文管理器。

分享一下如何编写高效且优雅的 Python 代码

然后可以以这样的方式进行调用:

分享一下如何编写高效且优雅的 Python 代码

在调用的时候:

with语句先暂存了ReadFile类的__exit__方法

然后调用ReadFile类的__enter__方法

__enter__方法打开文件,并将结果返回给with语句

上一步的结果被传递给file_read参数

with语句内对file_read参数进行操作,读取每一行

读取完成之后,with语句调用之前暂存的__exit__方法

__exit__方法关闭了文件

要注意的是,在__exit__方法内,我们关闭了文件,但最后返回True,所以错误不会被with语句抛出。否则with语句会抛出一个对应的错误。

Python 相关文章推荐
跟老齐学Python之开始真正编程
Sep 12 Python
在Python中使用lambda高效操作列表的教程
Apr 24 Python
python的else子句使用指南
Feb 27 Python
Python 基础知识之字符串处理
Jan 06 Python
Python中音频处理库pydub的使用教程
Jun 07 Python
详解appium+python 启动一个app步骤
Dec 20 Python
Python处理菜单消息操作示例【基于win32ui模块】
May 09 Python
基于Python3.6+splinter实现自动抢火车票
Sep 25 Python
对python numpy.array插入一行或一列的方法详解
Jan 29 Python
Python字符串的修改方法实例
Dec 19 Python
学会迭代器设计模式,帮你大幅提升python性能
Jan 03 Python
Python使用华为API为图像设置多个锚点标签
Apr 12 Python
python 函数传参之传值还是传引用的分析
Sep 07 #Python
windows下python之mysqldb模块安装方法
Sep 07 #Python
python 全局变量的import机制介绍
Sep 07 #Python
Python 多线程的实例详解
Sep 07 #Python
Python 闭包的使用方法
Sep 07 #Python
Python基于回溯法子集树模板解决选排问题示例
Sep 07 #Python
Python基于回溯法子集树模板解决全排列问题示例
Sep 07 #Python
You might like
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
2017/05/04 PHP
javascript 随机展示头像实现代码
2011/12/06 Javascript
jquery事件机制扩展插件 jquery鼠标右键事件。
2011/12/26 Javascript
javascript 拷贝节点cloneNode()使用介绍
2014/04/03 Javascript
JavaScript中用toString()方法返回时间为字符串
2015/06/12 Javascript
JQuery导航菜单选择特效
2016/04/11 Javascript
AngularJS过滤器filter用法分析
2016/12/11 Javascript
vue2.0实战之使用vue-cli搭建项目(2)
2017/03/27 Javascript
Angular2 组件通信的实例代码
2017/06/23 Javascript
老生常谈js中的MVC
2017/07/25 Javascript
React native ListView 增加顶部下拉刷新和底下点击刷新示例
2018/04/27 Javascript
一文快速了解JQuery中的AJAX
2019/05/31 jQuery
js实现掷骰子小游戏
2019/10/24 Javascript
js实现购物车商品数量加减
2020/09/21 Javascript
[28:57]EG vs VGJ.T 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/16 DOTA
python matlibplot绘制多条曲线图
2021/02/19 Python
Python使用sklearn库实现的各种分类算法简单应用小结
2019/07/04 Python
Django中的用户身份验证示例详解
2019/08/07 Python
Python3标准库之threading进程中管理并发操作方法
2020/03/30 Python
Keras 利用sklearn的ROC-AUC建立评价函数详解
2020/06/15 Python
让你相见恨晚的十个Python骚操作
2020/11/18 Python
纯CSS3实现自定义Tooltip边框涂鸦风格的教程
2014/11/05 HTML / CSS
美国Lolё官网:购买大胆而美丽的女性运动服装
2017/05/22 全球购物
英国假发网站:Hothair
2018/02/23 全球购物
Hoover胡佛官网:美国吸尘器和洗地机品牌
2019/01/09 全球购物
在线吉他课程,学习如何弹吉他:Fender Play
2019/02/28 全球购物
Delphi工程师笔试题
2013/09/21 面试题
国际贸易专业个人求职信范文分享
2013/12/14 职场文书
知识竞赛活动方案
2014/02/18 职场文书
冬季施工防火方案
2014/05/17 职场文书
医学专业自荐信
2014/06/14 职场文书
敬老院献爱心活动总结
2014/07/08 职场文书
2015年社区服务活动总结
2015/03/25 职场文书
2015社区爱国卫生工作总结
2015/04/21 职场文书
期中考试后的感想
2015/08/07 职场文书
党员反腐倡廉学习心得体会
2015/08/15 职场文书