#13 让代码变得Pythonic
前言
在学习Python的过程中,肯定听说过这么一个词:Pythonic,它的意思是让你的代码很Python!
一、列表生成式
前面有一节专门讲解了Python的列表,其灵活的使用方法一定让你陶醉其中。当然,也也知道怎么初始化一个列表,比如现在要生成 [0,1,2,3,4] 这样一个列表:
- In [1]: list(range(5))
- Out[1]: [0, 1, 2, 3, 4]
现在要将此列表的每个元素平方,要怎么办呢?
方法一:
- In [9]: a
- Out[9]: [0, 1, 2, 3, 4]
- In [10]: b = []
- In [11]: for i in a:
- ...: b.append(i**2)
- ...:
- In [12]: b
- Out[12]: [0, 1, 4, 9, 16]
- # 使用 for 循环遍历每一个元素,之后将结果保存在新的列表里
方法二:
- In [13]: a
- Out[13]: [0, 1, 2, 3, 4]
- In [14]: for index,i in enumerate(a):
- ...: a[index] **=2
- ...:
- In [15]: a
- Out[15]: [0, 1, 4, 9, 16]
- # 使用内置函数 enumerate() 将可迭代对象返回其索引和相应的值,这种方法直接改变原有列表的元素
方法三:
- In [15]: a
- Out[15]: [0, 1, 4, 9, 16]
- In [16]: func = lambda x:x**2
- In [18]: a = map(func,a)
- In [19]: a
- Out[19]: <map at 0x21bbb7a30b8>
- In [20]: for i in a:
- ...: print(i)
- ...:
- 0
- 1
- 16
- 81
- 256
- # 使用内置函数 map() 也可以实现,map(函数,可迭代对象),将可迭代对象的每一个元素传入函数并返回结果
方法四:使用更加Pythonic的方法:列表生成式
- In [22]: a = [i for i in range(5)]
- In [23]: a
- Out[23]: [0, 1, 2, 3, 4]
- # 可以看到生成一个列表就是如此简单
- In [24]: a = [i**2 for i in range(5)]
- In [25]: a
- Out[25]: [0, 1, 4, 9, 16]
- # 可以看到列表生成式很方便,很好用,这该死的无处安放的魅力啊
- In [26]: a = [i for i in range(10) if i%2 == 0]
- In [27]: a
- Out[27]: [0, 2, 4, 6, 8]
- # 列表生成式还可以加入 if 判断
- In [28]: [p + q for p in range(3) for q in range(5)]
- Out[28]: [0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6]
- # 使用两层循环实现全排列
- # 0+0 0+1 0+2 0+3 0+4 0+0 1+0 1+1 1+2 1+3 1+4 2+0 2+1 2+2 2+3 2+4
二、生成器
列表生成式很实用,但是有一个致命的缺点,就是不能创建大数据量的列表,数据量太大时会导致计算机内存不够用,同时,如果创建的大数据量列表被使用的元素很少的话,那么就会造成存储空间的大量浪费,那有没有一种方法,可以不提前生成列表,而是在使用列表的时候生成一个列表,换句话说就是:边循环边计算,这就是生成器—— generator。生成器在需要的时候才产生结果,不是立即产生结果,生成器效率高,节省CPU生成器只能遍历一次,是一个特殊的迭代器。
1.生成器表达式:类似于列表生成式,只不过将方括号 [] 改变为圆括号 ()
- In [29]: l = [i for i in range(8)]
- In [30]: l
- Out[30]: [0, 1, 2, 3, 4, 5, 6, 7]
- In [31]: g = (i for i in range(8))
- In [32]: g
- Out[32]: <generator object <genexpr> at 0x0000021BBBB16E08>
- # 可以看到 l 是列表,而 g 是一个generator
如何获得生成器的元素呢?使用next()方法可以获取一个元素:
- In [33]: next(g)
- Out[33]: 0
- In [34]: next(g)
- Out[34]: 1
- In [35]: next(g)
- Out[35]: 2
- In [36]: next(g)
- Out[36]: 3
- In [37]: next(g)
- Out[37]: 4
- In [38]: next(g)
- Out[38]: 5
- In [39]: next(g)
- Out[39]: 6
- In [40]: next(g)
- Out[40]: 7
- In [41]: next(g)
- ---------------------------------------------------------------------------
- StopIteration Traceback (most recent call last)
- <ipython-input-41-e734f8aca5ac> in <module>
- ----> 1 next(g)
- StopIteration:
- # 可以看到当生成器没有元素可以取的时候,会抛出StopIteration异常
可以看到上面的代码总司不停的手动使用next()获取下一个元素,很烦~,在Python中其实不经常使用next(),而是用for循环的方法迭代生成器:
- In [43]: g = (i for i in range(8))
- In [45]: for p in g:
- ...: print(p)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
创建一个生成器以后,基本上不会使用next()方法,而是使用for循环,迭代完成以后不会抛出StopIteration异常。
2.生成器函数:将函数返回时的关键字return改为yield。函数将每次返回一个结果,之后挂起,再次调用时,继续从挂起的位置执行
- In [46]: def print_num():
- ...: '''
- ...: print num to screen
- ...: '''
- ...: a = 'No.1'
- ...: b = 'No.2'
- ...: c = 'No.3'
- ...: print(a)
- ...: yield a
- ...: print(b)
- ...: yield b
- ...: print(c)
- ...: yield c
运行函数可以使用 next() ,当然也不常用,for循环才是generator的真爱:
- In [46]: def print_num():
- ...: '''
- ...: print num to screen
- ...: '''
- ...: a = 'No.1'
- ...: b = 'No.2'
- ...: c = 'No.3'
- ...: print(a)
- ...: yield a
- ...: print(b)
- ...: yield b
- ...: print(c)
- ...: yield c
- ...:
- In [52]: a = print_num()
- In [53]: next(a)
- No.1
- Out[53]: 'No.1'
- In [54]: next(a)
- No.2
- Out[54]: 'No.2'
- In [55]: next(a)
- No.3
- Out[55]: 'No.3'
- In [56]: next(a)
- ---------------------------------------------------------------------------
- StopIteration Traceback (most recent call last)
- <ipython-input-56-15841f3f11d4> in <module>
- ----> 1 next(a)
- StopIteration:
- # 菲波那切数列
- def Fib(max):
- n, a, b = 0, 0, 1
- while n < max:
- yield b
- a, b = b, a + b # 还记得这里的交换变量方法吗?
- n = n + 1
- return '到头了!!!!'
In [61]: f = Fib(10)
In [62]: for p in f:
...: print(p)
1
1
2
3
5
8
13
21
34
55
# for循环才是generator的真爱
三、迭代器
在Python中,list、string、tuple、dict都是可以使用for循环进行遍历的,现在又多了一类generator。这些可以使用for循环的对象称为可迭代对象。迭代器是用来帮助我们记录每次迭代的位置,而可迭代对象使用内置函数iter()是可以转换为迭代器的:
- In [63]: a = [1,2,3] # 创建一个新列表
- In [64]: print(a) # 可以看到a是列表
- [1, 2, 3]
- In [65]: i = iter(a) # 将其变为迭代器
- In [67]: print(i) # 可以看到i为迭代器
- <list_iterator object at 0x0000021BBCE00240>
获取迭代器中的元素可以使用内置函数next(),但不经常使用,经常使用的是for循环:
- In [68]: i
- Out[68]: <list_iterator at 0x21bbce00240>
- In [70]: for p in i:
- ...: print(p)
- 1
- 2
- 3
补充:对于列表、字符串、元组、字典等数据类型,在使用for循环时,在后台for语句对这些对象调用iter()函数,之后使用next()逐个访问每一个元素,直到遇到StopIteration异常,迭代结束。
在Python中,可以使用 isinstance() 判断一个对象是否为可迭代对象:
- In [71]: from collections import Iterable # 导入Iterable模块,之后会讲
- In [72]: isinstance([],Iterable)
- Out[72]: True
- In [73]: isinstance((),Iterable)
- Out[73]: True
- In [74]: isinstance({},Iterable)
- Out[74]: True
- In [75]: isinstance('',Iterable)
- Out[75]: True
四、装饰器
装饰器是什么呢?来举个例子就明白了:有一个长发飘飘的漂亮女明星,被邀出演尼姑,肯定不会把头发剃光了吧,怎么办呢,聪明的你一定想到戴个头套就行。是的,在Python中,长发飘飘的女明星就是源代码,头套就是装饰器。转时期的本质就是在不改变函数原有代码并且不改变原有函数的调用方式的基础上给函数加上新的功能,听起来很迷人,用起来一样有趣,让你的代码一下子就提高档次了。
1.过程No.1
现在有一个 tell_name() 函数:
- def tell_name():
- print('I am MinuteSheep')
要求记录它的执行时间,对原有函数改写,这样来实现:
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def tell_name():
- start_time = time.time()
- print('I am MinuteSheep')
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- end_time = time.time()
- print('执行时间为:',end_time - start_time)
- tell_name()
- # 运行结果:
I am MinuteSheep
执行时间为: 2.001427173614502
2.过程No.2
现在又100个函数需要计算其执行时间,总不能改写100个函数的源代码吧,怎么办呢?还记的高阶函数吗,可以讲函数当作变量传给函数:
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def tell_name():
- print('I am MinuteSheep')
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- def Time(func): # 使用高阶函数
- start_time = time.time()
- func()
- end_time = time.time()
- print('执行时间为:', end_time - start_time)
- Time(tell_name) # 调用方式发生改变
- # 运行结果:
I am MinuteSheep
执行时间为: 2.00026535987854
上面代码似乎实现了这个功能,也没有修改原函数的代码,但是却改变了它的调用方式,如果一个程序中有上百条调用,都要改的话还是很麻烦
3.过程No.3
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def tell_name():
- print('I am MinuteSheep')
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- def Time(func):
- def wrapper(): # 使用函数的嵌套
- start_time = time.time()
- func()
- end_time = time.time()
- print('执行时间为:', end_time - start_time)
- return wrapper
- tell_name = Time(tell_name) # 相当于 tell_name = wrapper
- tell_name() # 相当于执行 wrapper()
上面代码已经基本实现了这个功能,但是每次都要写两条调用语句才行,很烦
4.过程No.4
在Python中,为了克服上述问题,出现了一个叫做语法糖的语句,所以装饰器又叫做语法糖,在函数定义之前使用@语法糖可增加相应的功能
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def Time(func):
- def wrapper(): # 使用函数的嵌套
- start_time = time.time()
- func()
- end_time = time.time()
- print('执行时间为:', end_time - start_time)
- return wrapper
- @Time # 这就是装饰器,也叫语法糖
- def tell_name():
- print('I am MinuteSheep')
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- tell_name() # 相当于执行 wrapper()
- # 运行结果:
I am MinuteSheep
执行时间为: 2.000563621520996
上面代码实现了一个最简单的装饰器。
5.过程No.5
但是,又有新的问题出现了,如果被装饰函数有参数怎么办,这么办:
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def Time(func):
- def wrapper(name): # 使用函数的嵌套
- start_time = time.time()
- func(name)
- end_time = time.time()
- print('执行时间为:', end_time - start_time)
- return wrapper
- @Time
- def tell_name(name):
- print('I am',name)
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- tell_name('MS') # 相当于执行 wrapper('MS')
- # 运行结果:
I am MS
执行时间为: 2.0003795623779297
看起来不错
6.过程No.6
上面代码实现了装饰有一个参数函数的功能,但是,装饰器被应用与不同的函数,谁能知道这个函数有没有参数,有几个参数,为了实现通用性,这么办:
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def Time(func):
- def wrapper(*args, **kwargs): # 通过非固定参数实现各种参数的通用装饰器
- start_time = time.time()
- func(*args, **kwargs)
- end_time = time.time()
- print('执行时间为:', end_time - start_time)
- return wrapper
- @Time
- def tell_name(name):
- print('I am', name)
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- @Time
- def add(a, b):
- c = a + b
- print(c)
- tell_name('MS')
- add(5, 6)
- # 运行结果:
I am MS
执行时间为: 2.00108003616333
11
执行时间为: 0.0004711151123046875
7.过程No.7
上面的过程中装饰器没有参数,其实装饰器时可以带参数的:
- import time # 引入time模块,这是一个时间模块,以后会讲到
- def Time(num): # 使用两层嵌套实现带参数的装饰器
- def decorator(func):
- def wrapper(*args, **kwargs):
- if num == 1:
- start_time = time.time()
- func(*args, **kwargs)
- end_time = time.time()
- print('执行时间为:', end_time - start_time)
- elif num == 0:
- func(*args, **kwargs)
- print('不需要计算时间')
- return wrapper
- return decorator
- @Time(num=1)
- def tell_name(name):
- print('I am', name)
- time.sleep(2) # 为了体现执行时间,让程序等两秒
- @Time(num=0)
- def add(a, b):
- c = a + b
- print(c)
- tell_name('MS')
- add(5, 6)
- # 运行结果:
I am MS
执行时间为: 2.0000314712524414
11
不需要计算时间
8.过程No.8
一个函数可以使用多个装饰器,装饰器运行顺序从里到外:
- @a
- @b
- @c
- def func():
- pass
- # 先运行c,再运行b,最后运行a
以上就是装饰器99%的功能,还有一种叫做类装饰器,等记录完Python面向对象的知识后再补充,拜拜~
#13 让代码变得Pythonic的更多相关文章
- C# 多线程的坑 之 代码变序
英文好的,可跳过,直接打开底部的“参考“链接. 代码变序--reordering of memory operations 大概4年前,阅读了这篇文章后http://www.albahari.com/ ...
- 用AOP来让你的JS代码变得更有可维护性吧
此文已由作者吴佳祥授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 好吧我承认这是篇任务. 最近看到个消息,ES2017已经定稿了,心想,我去,还完全没了解ES2016呢,ES ...
- 【如何让代码变“高级”(二)】-这样操作值得一波666(Java Stream)(这么有趣)
[如何让代码变“高级”(二)]-这样操作值得一波666(Java Stream)(这么有趣) 开发中的代码 在开发中的代码是不是很常见这样的代码: 这样的? for循环取元素取值 List<Us ...
- Java8 Lambda表达式和流操作如何让你的代码变慢5倍
原文出处:ImportNew 有许许多多关于 Java 8 中流效率的讨论,但根据 Alex Zhitnitsky 的测试结果显示:坚持使用传统的 Java 编程风格——iterator 和 for- ...
- Console命令详解,让调试js代码变得更简单
Firebug是网页开发的利器,能够极大地提升工作效率. 但是,它不太容易上手.我曾经翻译过一篇<Firebug入门指南>,介绍了一些基本用法.今天,继续介绍它的高级用法. ======= ...
- HTML+CSS学习笔记 (13) - CSS代码缩写,占用更少的带宽
标签:HTML+CSS 盒模型代码简写 还记得在讲盒模型时外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上右下左.具体应用在m ...
- 彩色网页变黑白色CSS代码变黑白色调!
<style> html { -webkit-filter: grayscale(%); -moz-filter: grayscale(%); -ms-filter: grayscale( ...
- [转] Console命令详解,让调试js代码变得更简单
http://www.cnblogs.com/see7di/archive/2011/11/21/2257442.html Firebug是网页开发的利器,能够极大地提升工作效率. 但是,它不太容易上 ...
- Firebug控制台详解,让调试js代码变得更简单
http://www.open-open.com/lib/view/open1373120100347.html Firebug是网页开发的利器,能够极大地提升工作效率. Firebug控制台详解 控 ...
随机推荐
- 使用RabbitMq
1.配置环境 http://www.cnblogs.com/longlongogo/p/6479424.html 2.安装RabbitMQ https://www.cnblogs.com/longlo ...
- PHP-循环结构-数组(难)
今日目标: (1)循环结构 —— do..while.. —— 掌握 (2)循环结构 —— for —— 重点 (3)数组 —— 重点 1.PHP中的循环结构 —— do..while... do: ...
- MySQL skip-character-set-client-handshake导致的一个字符集问题
http://www.quweiji.com/mysql-skip-character-set-client-handshake%E5%AF%BC%E8%87%B4%E7%9A%84%E4%B8%80 ...
- 使用freemarker对模板进行渲染
最近项目中使用到了,对word模板进行编辑和渲染,所以使用到了模板引擎技术. 在项目中,我们前端使用的富文本编辑器,进行展示和保存(和word格式一致),后端采用了freemarker进行数据的渲染. ...
- 20155339 Exp9 Web安全基础
Exp9 Web安全基础 基础问题回答 (1)SQL注入攻击原理,如何防御 原理:它是利用现有应用程序,将恶意的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入恶意SQL语句得到 ...
- php上传图片预览,放大,裁剪
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 在.NET Core中三种实现“可插拔”AOP编程方式(附源码)
一看标题肯定会联想到使用动态编织的方式实现AOP编程,不过这不是作者本文讨论的重点. 本文讨论另外三种在netcore中可实现的方式,Filter(过滤器,严格意义上它算是AOP方式),Dynamic ...
- cobub razor 安装及使用
server端安装及配置 apache2 + Mysql5.7 + php7 + redis 参见:http://docs.cobub.com/pages/viewpage.action?pageId ...
- centos docker安装和使用
系统要求:centos7,内核3.10或更高一.配置yum源并安装 vim /etc/yum.repos.d/docker.repos [dockerrepo] name=Docker Resposi ...
- 用jQuery修改右键菜单
概述 以前在网上找过屏蔽右键菜单的代码,也找过屏蔽F12的代码,今天无意之中看到别人的右键菜单很有意思,我也想来搞一个. 思路 建立一个菜单并且隐藏起来. 用window.oncontextmenu屏 ...