简学Python第四章__装饰器、迭代器、列表生成式
Python第四章__装饰器、迭代器
欢迎加入Linux_Python学习群
群号:478616847
目录:
-
列表生成式
-
生成器
-
迭代器
-
单层装饰器(无参)
-
多层装饰器(有参)
-
冒泡算法
-
代码开发规范
一、列表生成式(列表推导式)
列表生成式List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
首先来上个需求,我有一个列表 [1,2,3,4,5,6,7,8,9,10],现在有这么个需求,要里面的元素自乘,想想看要怎么实现
版本一,通过for循环,重新赋值(占内存空间)
1 a = [1,2,3,4,5,6,7,8,9,10] 2 b = [] 3 for i in a: 4 b.append(i*i) 5 a = b 6 print(a)
版本二,通过for循环修改原值(代码太多)
1 a = [1,2,3,4,5,6,7,8,9,10] 2 for indexs,i in enumerate(a): 3 a[indexs] *= i 4 print(a)
版本三,借助map和lambda(代码也不少)
1 a = [1,2,3,4,5,6,7,8,9,10] 2 a = map(lambda x:x*x,a) 3 print(list(a))
列表生成式(Python的高级特性),一句话即可实现上面的功能,在列表生成式中我们可以加if判断,也可以加多个for循环,多个for循环,我们就把它
想成for循环嵌套,并且实际结果也是for循环嵌套的结果
1 a = [i*i for i in range(1,11)] 2 print(a) 3 4 #加if判断 5 a = [i*i for i in range(1,11) if i%2 == 1 ] 6 print(a) 7 8 #多层for循环 9 a = [a+b+c for a in "abc" for b in "ABC" for c in "123"] 10 print(a) 11 12 13 #多层for循环解析 14 s = [] 15 for a in "abc" : 16 for b in "ABC": 17 for c in "123": 18 s.append(a+b+c) 19 print(s)
二、生成器
生成器也叫简单生成器,它是可以简单有效的创建出迭代器的工具,并且通过yield语句,当每次对有yield语句函数使用next()的时候
生成器会从yield停止的位置继续开始
生成器的特性:
1、生成器是用普通的函数语法定义的迭代器
2、通过next()语句调用,生成器函数将从yield语句处继续执行
3、节省空间,生成器只是指定的计算方式,在不调动的时候不会生成所有的值
4、通过生成器的这种可以再次从yield语句处再次执行的特性,我们可以完成协同程序(下面例子说明)
协同程序:协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。
第一个简单生成器
下面的代码主要是定义一个函数,这个函数接收一个数字的参数,然后通过list(range(num))生成一个列表。并for循环这个列表
每次循环都触发 yield i ,当第一次循环触发yield i 则程序会停止到这里并且产生一个值,等待下一步激活,那么通过__next__()和next()
就去到yield 返回的值,并且从停止的位置继续执行,知道再碰到yield,或者执行完成。
1 def Out_num(num): 2 for i in list(range(num)): 3 yield i 4 5 Builder = Out_num(5) 6 7 #返回的不是一个数字而是一个迭代器 8 print(Builder) 9 10 #取值方式 11 print("我是通过__next__()取值:",Builder.__next__()) 12 print("我是通过next()取值:",next(Builder)) 13 14 #for循环取值 15 print("我是通过for循环取值↓") 16 for i in Builder: 17 print(i)
要注意的一点是通过__next__()和next()取值时当生成器中没有值而你取值则程序会报错,通过for循环取值时不存在这个问题的!
1 >>> def Out_num(num): 2 ... for i in list(range(num)): 3 ... yield i 4 ... 5 >>> Builder = Out_num(3) 6 7 >>> Builder.__next__() 8 0 9 >>> Builder.__next__() 10 1 11 >>> Builder.__next__() 12 2 13 >>> Builder.__next__() 14 Traceback (most recent call last): 15 File "<stdin>", line 1, in <module> 16 StopIteration
协同程序与send()
我们知道了协同程序就是程序可以暂停或者挂起,并且可以从程序离开的地方继续或者重新开始
下面的例子是个吃包子与做包子的模拟程序,通过yield让consumer函数暂停,并执行做包子的操作,做完包子后通过send(),给yield赋值,
并且继续执行consumer函数中的代码,而且我们可以通过send()传进来的值判断做什么操作,就象例子中“梅菜肉馅的包子”一样,隔壁老王不吃而隔壁老李爱吃。
1 import time 2 def consumer(name): 3 print("%s 准备吃包子啦!" %name) 4 while True: 5 baozi = yield 6 if baozi == "牛肉" or baozi == "酸菜": 7 print("%s馅的包子来了,%s爱吃,被[%s]吃了!" %(baozi,name,name)) 8 elif baozi == "梅菜肉" and name == "隔壁老李": 9 print("%s馅的包子来了,%s爱吃,被[%s]吃了!" %(baozi,name,name)) 10 else: 11 print("%s馅的包子不好吃,%s不吃!" %(baozi,name)) 12 13 def producer(): 14 c = consumer('隔壁老王') 15 c2 = consumer('隔壁老李') 16 c.__next__() 17 c2.__next__() 18 print("来顾客了,大王开始做包子了!") 19 species = ["牛肉","素三鲜","酸菜","梅菜肉","韭菜"] 20 for i in species: 21 time.sleep(1) 22 print("---------------------分割线-------------------------") 23 print("做了%s馅的包子"%i) 24 c.send(i) 25 c2.send(i) 26 producer()
生成器表达式
在上面学习了列表生成式,和生成器,那么生成器表达式它和列表生成式的用法基本一致,其工作方式是每次处理一个对象,而不是一口气处理和构造整个数据结构,
也就是返回的是一个可迭代的对象,这样做的潜在优点是可以节省大量的内存,
语法:(expr for iter_var in iterable) 或 (expr for iter_var in iterable if cond_expr)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 #生成器表达式 5 c = (x for x in "ABCDEFG") 6 print(c) 7 #列表生成式 8 a = [i*i for i in range(1,11) if i%2 == 1 ] 9 print(a) 10 11 lists = ["user","pass","age","gender"] 12 s = {i:"" for i in lists } 13 print(s)
三、迭代器
什么是迭代器?一种是可以直接作用于for
循环的数据类型,另一种就是有一个 next() 方法的对象, 而不是通过索引来计数,也就说生成器生成的就是迭代器,
迭代器也有一些限制. 例如你不能向后移动,不能回到开始, 也不能复制一个迭代器,可以使用isinstance()
判断一个对象是否是可迭代:
1 from collections import Iterable 2 print(isinstance([],Iterable)) 3 print(isinstance({},Iterable)) 4 print(isinstance((),Iterable)) 5 print(isinstance("abcd",Iterable)) 6 #数字无法迭代 7 print(isinstance(100,Iterable))
生成器都是迭代器,但是虽然 list dict str可以迭代,但是它们不是迭代器,通过iter()函数可以把可迭代的对象变成迭代器
1 from collections import Iterator 2 print(isinstance([],Iterator)) 3 print(isinstance({},Iterator)) 4 print(isinstance((),Iterator)) 5 print(isinstance("abcd",Iterator)) 6 7 print(isinstance((x for x in range(10)),Iterator))
你可能会问,为什么list、dict、str等数据类型不是Iterator(迭代器)?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性
的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
四、单层装饰器(无参)
装饰器是函数式编程的重中之重!学好装饰器不仅能让你的代码看上去,B格更高,也能体现出技术含量,更能实现开发封闭原则,装饰器的背后主要动机源自
python 面向对象编程。所以装饰器是在函数调用之上的修饰。也就是说装饰器的作用是在不动函数源码的前提上给函数加功能,抽象的理解就是把函数装饰起
来,是函数执行前与执行后都能做不同的操作!
上故事有一家公司,准备面试新人,这个部门的老大亲自当面试官,并且呢,给面试者出了这么一道题
我们有这么一段代码,这段代码,这段代码假设是N个业务部门的业务的函数,这段代码的意思就是当我们调用上面的函数的时候,传入值给arg,
当arg的值等于f1或者f2那么对应的函数就返回ok
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 def f1(arg): 5 print('我是F1业务') 6 if arg == 'f1': 7 return 'ok' 8 9 def f2(arg): 10 print('我是F2业务') 11 if arg == 'f2': 12 return 'ok'
那么公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,
只需调用基础平台提供的功能即可。那么我们业务部门调用功能的时候只需要:
f1(值)
f2(值)
然而呢我发现了一个问题就是业务部调用基础平台的功能的时候没有验证这样不好,所以请各位面试者把验证功能加上,并且业务部门在调用功能的方式不能变
应聘者LowA:
他是这么做的,他说跟各个做基础功能的人协调,要求在自己业务的代码上加入验证模块,那么这样呢整个的基础平台就不需要更改,结果,老大直接请他走了
应聘者LowB:
这个LowB看到LowA直接被赶出去了,心里烦了低估,同样的题LowB把自己每个基础代码函数里面都加上了验证代码,老大一看,随便问了几句,也让他回去等消息了
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 def f1(arg): 5 #验证代码 6 #验证代码 7 print('我是F1业务') 8 if arg == 'f1': 9 return 'ok' 10 11 def f2(arg): 12 #验证代码 13 #验证代码 14 print('我是F2业务') 15 if arg == 'f2': 16 return 'ok'
应聘者LowC:
他看到了这道题,他把验证代码单独写成一个函数,然后在基础函数中调用这个验证函数,老大看见了LowC的实现方式,嘴角露出了一丝微笑,并且与LowC聊了个天
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 #验证函数 5 def verify(): 6 # 验证1 7 # 验证2 8 # 验证3 9 pass 10 11 def f1(arg): 12 verify() 13 print('我是F1业务') 14 if arg == 'f1': 15 return 'ok' 16 17 def f2(arg): 18 verify() 19 print('我是F2业务') 20 if arg == 'f2': 21 return 'ok'
老大说:
写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现的功能代码块
开放:对扩展开发
如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2的内部进行修改代码,老板就给了Low C一个实现方案并说什么时候看懂了什么时候来上班工资20K:
❤单层装饰器(无参)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 #装饰器函数 5 def func(main): 6 def wra(*args,**kwargs): 7 #验证代码 8 return main(*args,**kwargs) 9 return wra 10 11 #python语法糖 @func => f1 = func(f1) 12 @func 13 def f1(arg): 14 print('我是F1业务') 15 if arg == 'f1': 16 return 'ok' 17 18 def f2(arg): 19 print('我是F2业务') 20 if arg == 'f2': 21 return 'ok' 22 23 c = f1("f1") 24 print(c)
剖析 第一步:
首先@func 是python装饰器的语法糖,这个语句是放在被修饰函数的上方,当出现这个语句就要把它看成 f1 = func(f1)
公式 被装饰函数= 装饰器函数 (被装饰函数) 可以看出被装饰函数此时变成参数被装饰器函数传了进去,所以装饰器函数
(func)的参数main就是 被装饰函数 f1,所以main = f1,并且我们在第三章知道了函数不加括号是不执行的,所以main
就是函数 f1的内存地址,且不会执行。
第二步:
我们发现,装饰器函数func 就是在第三章学的闭包,所以当@func的时候 就是 f1 = func(f1) ,我们要知道函数加上括号就会执行
所以func就执行了,并且因为func是个闭包函数,所以把内部wra函数加载到内存,并返回,所以此时 f1 = wra,并且要注意的
是返回的是wra 没有加括号哦!
第三步:
当看到 c = f1("f1")的时候,因为python语法糖的原因所以f1就是 wra函数也就是图中红色标记箭头的地方,所以f1("f1")就是执行
了wra函数,并且把参数“f1”传了进去,第三章学过动态参数,所以被装饰的函数传入什么参数我们的wra都不需要改,然后看wra函
数内部做了个return main(*args,**kwargs),第一步的时候我们得出了结论 main就是f1函数,所以 main(*args,**kwargs)
,就是执行了f1函数并且把 c = f1("f1") 中的参数“f1”传到的真正的函数 f1里也就是蓝色框圈起来的内容,并且第三章我们也知道了,
传参的时候用 *args,**kwargs 就是把列表和字典分解(不理解请参考第三章解参)。
最后:main(*args,**kwargs) 就是执行了f1函数,当f1函数执行完成后,就return “ok”,所以wra中的 return实际返回的就是
f1函数返回的结果,这也就是c为什么等于“ok”当然就算f1函数没有返回值也无所谓,因为默认会返回None。到此无参装饰器剖析完毕
五、多层装饰器(有参)
咳咳,经过LowC的认真学习,他终于明白了无参装饰器的真意,于是成功的入职了公司,但是随着时间的推移,老大又找到了Low C说
这样我有个需求要你给我改改,我现在呢想在验证之后呢添加一个欢迎功能,这个功能呢,我们业务线的功能想要添加就添加先要不
添加就不添加,要记住封闭原则哦0.0……….
第二天Low C找到了老大说,大哥啊您晚上还是来我家教教我吧,真心的不知道啊0.0,,,于是老大就去了Low C的家里经过一场风云
(此处省略一万个字),最后老大提供了另外的参考代码:
❤多层装饰器(有参)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 #欢迎函数 5 def welcome(): 6 print("欢迎访问") 7 8 #装饰器函数 9 def fill(*func): 10 def single_func(main): 11 def wra(*args,**kwargs): 12 #验证代码 13 if len(func) !=0: 14 for i in func: 15 i() 16 return main(*args,**kwargs) 17 return wra 18 return single_func 19 20 @fill(welcome)#python语法糖 @fill(welcome) => @single_func => f1 = func(f1) 21 def f1(arg): 22 print('我是F1业务') 23 if arg == 'f1': 24 return '我是f1 ok' 25 26 @fill() 27 def f2(arg): 28 print('我是F2业务') 29 if arg == 'f2': 30 return '我是f2 ok' 31 32 c = f1("f1") 33 print(c) 34 print("------------分割线----------") 35 c2 = f2("f2") 36 print(c2)
剖析:第一步
其实有参装饰器,就是无参装饰器加了个闭包,这个闭包的参数接收一个函数,并且使用*func表示这个参数就表示可以传参也可以不传参
@fill(welcome) 我们知道函数加上括号就会执行,所以先执行了装饰器函数fill,并且把welcome函数传给了参数*func,然后我们把subgle_func
当成一个整体,那么这就是个闭包结构,所以当fill()执行后的到了 @fill(welcome) == @single_func *func == welcome这样我们在闭包
内部就可以调用 welcome这个函数!
第二步:
在第一步中得到了 @fill(welcome) == @single_func 那么这就变成了无参装饰器,那么就跟上面讲到的一样 @single_func => f1 = single_func(f1)
所以 f1 = wra函数 main = f1 函数
第三步:
当 f1("f1") 的时候就开始执行wra函数,在wra函数内部我们要先执行*func内的函数,毕竟欢迎信息在前面,所以先执行
并且我们同if判断可以判断使用者是否传入了函数,传入就执行,不传入就不执行,执行完成*func内的函数后就制成执行main
函数(main = f1函数),也就是执行了f1,到此需求实现。
六、冒泡算法
接下来学习一下冒泡排序,冒泡排序的主要思路就是从第一个元素开始,判断这个元素与每一个元素的大小关系,当符合判断关系
后就进行位置替换
1 def bubble(lists): 2 count = len(lists) 3 for i in range(0, count-1): 4 for j in range(i + 1, count): 5 if lists[i] > lists[j]: 6 lists[i], lists[j] = lists[j], lists[i] 7 print("第%s次排序"%(i+1),lists) 8 return lists 9 10 print([5,2,9,1,7,6,8,3,4]) 11 print(bubble([5,2,9,1,7,6,8,3,4]))
上面的例子中有两层for循环,最内层for循环的作用是比较第一个值与其它每个值的大小,如果大则替换位置,然后拿替换后的值接着、
跟剩下的值进行比较,同理如果大则替换位置,当这一次循环完成后就用第二个值与第二个值后面的值进行比较,如果大则替换位置,
直到只有最后一个值的时候排序就完成了,例子中的列表,让每个元素进行大小值的比较需要循环八次也就是最后是后面两个元素比较
就可以了。
七、代码开发规范
随着Python在国内的发展,使用python编程语言的公司越来越多,在使用python进行开发的时候,不仅仅只是编写代码时要注意规范
而且随着程序的复杂,在格式上也需要有规范,这样你的程序在别人眼中的可读性会大大增加,下面一起来看看编写代码时要注意的事情
缩进
在python代码中缩进的重要是毋庸置疑的,缩进错误会造成程序错误,并且好的缩进在代码可读性上也非常有用
注释
注释是程序非常重要的东西,不管是为了其他人阅读你的代码,还是为了你自己,因为时间久了很多你自己写的程序代码自己都会看不懂
这个时候要快速理解自己的代码,注释就尤为重要,所以你的每一个函数,和后面会学到的类,以及类中的方法,都要给他们加上注释
行的最大长度
每行代码或者输出内容如果过长在显示上和美观上都有较大的影响,所以当一行代码或者内容过长时建议使用 \ 进行换号,推荐将长度限
制在72字符。
软件目录结构
"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。目录规范,能更好的控制程序结构,让程序具有更高的可读性,关于如
何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构。在Stackoverflow的这个问题上,能看到大家对Python目录结
构的讨论,所以一个程序的目录设计我觉得应该有以下设计,假设你的项目名为foo,最后说一句,目录结构只是更好的让程序有更好的可读
性和易维护性,所以目录结构并非一尘不变,理解其中的意思即可。
作者:北京小远
出处:http://www.cnblogs.com/bj-xy/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。