前言

在学习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的更多相关文章

  1. C# 多线程的坑 之 代码变序

    英文好的,可跳过,直接打开底部的“参考“链接. 代码变序--reordering of memory operations 大概4年前,阅读了这篇文章后http://www.albahari.com/ ...

  2. 用AOP来让你的JS代码变得更有可维护性吧

    此文已由作者吴佳祥授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 好吧我承认这是篇任务. 最近看到个消息,ES2017已经定稿了,心想,我去,还完全没了解ES2016呢,ES ...

  3. 【如何让代码变“高级”(二)】-这样操作值得一波666(Java Stream)(这么有趣)

    [如何让代码变“高级”(二)]-这样操作值得一波666(Java Stream)(这么有趣) 开发中的代码 在开发中的代码是不是很常见这样的代码: 这样的? for循环取元素取值 List<Us ...

  4. Java8 Lambda表达式和流操作如何让你的代码变慢5倍

    原文出处:ImportNew 有许许多多关于 Java 8 中流效率的讨论,但根据 Alex Zhitnitsky 的测试结果显示:坚持使用传统的 Java 编程风格——iterator 和 for- ...

  5. Console命令详解,让调试js代码变得更简单

    Firebug是网页开发的利器,能够极大地提升工作效率. 但是,它不太容易上手.我曾经翻译过一篇<Firebug入门指南>,介绍了一些基本用法.今天,继续介绍它的高级用法. ======= ...

  6. HTML+CSS学习笔记 (13) - CSS代码缩写,占用更少的带宽

    标签:HTML+CSS 盒模型代码简写 还记得在讲盒模型时外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上右下左.具体应用在m ...

  7. 彩色网页变黑白色CSS代码变黑白色调!

    <style> html { -webkit-filter: grayscale(%); -moz-filter: grayscale(%); -ms-filter: grayscale( ...

  8. [转] Console命令详解,让调试js代码变得更简单

    http://www.cnblogs.com/see7di/archive/2011/11/21/2257442.html Firebug是网页开发的利器,能够极大地提升工作效率. 但是,它不太容易上 ...

  9. Firebug控制台详解,让调试js代码变得更简单

    http://www.open-open.com/lib/view/open1373120100347.html Firebug是网页开发的利器,能够极大地提升工作效率. Firebug控制台详解 控 ...

随机推荐

  1. ArcGIS for JS 离线部署

    本文以arcgis_js_v36_api为例,且安装的是IIS Web服务器 1.下载最新的ArcGIS for JS api 包,可在Esri中国社区或者Esri官网下载 2.下载后解压 3.将解压 ...

  2. 深入理解JVM(二)Java内存区域

    2.1 C.C++内存管理是由开发人员管理,而Java则交给了JVM进行自动管理 2.2 JVM运行时数据区:方法区.堆(运行时线程共享),虚拟机栈.本地方法栈.程序计数器(运行时线程隔离,私有) 1 ...

  3. 使用android访问SQLServer数据库

    1.SQL驱动 下载可以支持android的SQL驱动,下载地址http://sourceforge.net/projects/jtds/files/ 注意只能下载1.2.7版本.android不支持 ...

  4. 移动端300ms延迟解决的几种方法;

    方案一:禁用缩放 当HTML文档头部包含如下meta标签时: <meta name="viewport" content="user-scalable=no&quo ...

  5. 进度条(progress_bar)

    环境:linux.centos6.5 #include<stdio.h> #include<unistd.h> int main() { ]={'\0'}; char ch[] ...

  6. zookeeper配置文件共享中心

    最近频繁的系统上线,每次打包都要把配置文件替换为正式环境的配置文件,虽然说就是复制粘贴的事,架不住文件杂乱,而且多. 期初的想法是有没有办法将配置文件与系统隔离开来,这样在更新时候,就只需要更新代码部 ...

  7. ubuntu16.04下使用navicat连接docker mysql5.7.20

    摘要: 本文将介绍如何使用docker创建mysql容器,并使用navicat连接该mysql服务,最后提供一个navicat中文乱码问题的解决方案. docker的安装和使用在这里不再赘述,如果不是 ...

  8. VS2017离线安装与Oracle数据库开发环境搭建

    记得之前使用VS2015打开老的MVC4项目,不能右键创建控制器和添加视图,让我非常不习惯!找遍了网络无果,最后只能回到VS2013,但我就是不喜欢用旧的VS,这是不是病... 1.将VS2017离线 ...

  9. 机器翻译质量评测算法-BLEU

    机器翻译领域常使用BLEU对翻译质量进行测试评测.我们可以先看wiki上对BLEU的定义. BLEU (Bilingual Evaluation Understudy) is an algorithm ...

  10. LCA(最近公共祖先)——Tarjan

    什么是最近公共祖先? 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. ...