#13 让代码变得Pythonic 
在学习Python的过程中,肯定听说过这么一个词:Pythonic,它的意思是让你的代码很Python!
 
一、列表生成式 
前面有一节专门讲解了Python的列表,其灵活的使用方法一定让你陶醉其中。当然,也也知道怎么初始化一个列表,比如现在要生成 [0,1,2,3,4] 这样一个列表:
1 2 In [1 ]: list(range(5 )) Out[1 ]: [0 , 1 , 2 , 3 , 4 ] 
 
现在要将此列表的每个元素平方,要怎么办呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ] 
 
1 2 3 4 5 6 7 8 9 10 11 12 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 ] 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 
 
1 2 3 4 5 6 7 8 In [22 ]: a = [i for  i in  range(5 )] In [23 ]: a Out[23 ]: [0 , 1 , 2 , 3 , 4 ] 
 
1 2 3 4 5 6 In [24 ]: a = [i\*\*2  for  i in  range(5 )] In [25 ]: a Out[25 ]: [0 , 1 , 4 , 9 , 16 ] 
 
1 2 3 4 5 6 In [26 ]: a = [i for  i in  range(10 ) if  i%2  == 0 ] In [27 ]: a Out[27 ]: [0 , 2 , 4 , 6 , 8 ] 
 
1 2 3 4 5 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 ] 
 
二、生成器 
列表生成式很实用,但是有一个致命的缺点,就是不能创建大数据量的列表,数据量太大时会导致计算机内存不够用,同时,如果创建的大数据量列表被使用的元素很少的话,那么就会造成存储空间的大量浪费,那有没有一种方法,可以不提前生成列表,而是在使用列表的时候生成一个列表,换句话说就是:边循环边计算,这就是生成器—— generator。生成器在需要的时候才产生结果,不是立即产生结果,生成器效率高,节省CPU生成器只能遍历一次,是一个特殊的迭代器。
生成器表达式 
类似于列表生成式,只不过将方括号 [] 改变为圆括号 ()
1 2 3 4 5 6 7 8 9 10 11 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 > 
 
如何获得生成器的元素呢?使用next()方法可以获取一个元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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: 
 
可以看到上面的代码总司不停的手动使用next()获取下一个元素,很烦~,在Python中其实不经常使用next(),而是用for循环的方法迭代生成器:
1 2 3 4 5 6 7 8 9 10 11 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异常。
生成器函数 
将函数返回时的关键字return改为yield。函数将每次返回一个结果,之后挂起,再次调用时,继续从挂起的位置执行
1 2 3 4 5 6 7 8 9 10 11 12 13 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 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 运行函数可以使用 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 -15841 f3f11d4> in  <module> ----> 1 next(a) StopIteration: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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       
 
三、迭代器 
在Python中,list、string、tuple、dict都是可以使用for循环进行遍历的,现在又多了一类generator。这些可以使用for循环的对象称为可迭代对象。迭代器是用来帮助我们记录每次迭代的位置,而可迭代对象使用内置函数iter()是可以转换为迭代器的:
1 2 3 4 5 6 7 8 9 In [63 ]: a = [1 ,2 ,3 ]       In [64 ]: print(a)          [1 , 2 , 3 ]   In [65 ]: i = iter(a)       In [67 ]: print(i)          <list_iterator object at 0x0000021BBCE00240 > 
 
获取迭代器中的元素可以使用内置函数next(),但不经常使用,经常使用的是for循环:
1 2 3 4 5 6 7 8 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() 判断一个对象是否为可迭代对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 In [71 ]: from  collections import  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中,长发飘飘的女明星就是源代码,头套就是装饰器。转时期的本质就是在不改变函数原有代码并且不改变原有函数的调用方式的基础上给函数加上新的功能,听起来很迷人,用起来一样有趣,让你的代码一下子就提高档次了。
过程No.1 
现在有一个 tell_name() 函数:
1 2 def  tell_name () :    print('I am MinuteSheep' ) 
 
要求记录它的执行时间,对原有函数改写,这样来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 import  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  
 
过程No.2 
现在又100个函数需要计算其执行时间,总不能改写100个函数的源代码吧,怎么办呢?还记的高阶函数吗,可以将函数当作变量传给函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import  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  
上面代码似乎实现了这个功能,也没有修改原函数的代码,但是却改变了它的调用方式,如果一个程序中有上百条调用,都要改的话还是很麻烦
过程No.3 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import  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()                             
 
上面代码已经基本实现了这个功能,但是每次都要写两条调用语句才行,很烦
过程No.4 
在Python中,为了克服上述问题,出现了一个叫做语法糖的语句,所以装饰器又叫做语法糖,在函数定义之前使用@语法糖可增加相应的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  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()     I am MinuteSheep   执行时间为: 2.000563621520996  
 
上面代码实现了一个最简单的装饰器。
过程No.5 
但是,又有新的问题出现了,如果被装饰函数有参数怎么办,这么办:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  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' )     I am MS   执行时间为: 2.0003795623779297  
 
看起来不错
过程No.6 
上面代码实现了装饰有一个参数函数的功能,但是,装饰器被应用与不同的函数,谁能知道这个函数有没有参数,有几个参数,为了实现通用性,这么办:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import  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  
 
过程No.7 
上面的过程中装饰器没有参数,其实装饰器时可以带参数的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import  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    不需要计算时间 
 
过程No.8 
一个函数可以使用多个装饰器,装饰器运行顺序从里到外:
1 2 3 4 5 6 7 @a @b  @c   def  func () :    pass  
 
以上就是装饰器99%的功能,还有一种叫做类装饰器,等记录完Python面向对象的知识后再补充,拜拜~