内容目录:
- 列表生成式、迭代器&生成器
- 装饰器
- 模块初始
- 常用模块
1.列表生成式,迭代器&生成器
列表生成式
需求:列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求把列表里的每个值加1
你可能会想到2种方式 :
1 >>> a 2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 3 >>> b = [] 4 >>> for i in a:b.append(i+1) 5 ... 6 >>> b 7 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 8 >>> a = b 9 >>> a 10 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
法 1
1 >>> a 2 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 3 >>> a = map(lambda x:x+1, a) 4 >>> a 5 <map object at 0x101d2c630> 6 >>> for i in a:print(i) 7 ... 8 3 9 5 10 7 11 9 12 11
法 2
其实还有一种写法,如下
1 >>> a = [i+1 for i in range(10)] 2 >>> a 3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
装逼版
这就叫做列表生成
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
1 2 3 4 5 6 |
|
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()
函数获得generator的下一个返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
我们讲过,generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1 2 3 4 5 6 7 |
|
注意,赋值语句:
1 |
|
相当于:
1 2 3 |
|
但不必显式写出临时变量t就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
def fib(max): n,a,b = 0,0,1 while n < max: #print(b) yield b a,b = b,a+b n += 1 return ‘done‘
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
>>> f = fib(6) >>> f <generator object fib at 0x104feaaa0>
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
data = fib(10) print(data) print(data.__next__()) print(data.__next__()) print("干点别的事") print(data.__next__()) print(data.__next__()) print(data.__next__()) print(data.__next__()) print(data.__next__()) #输出 <generator object fib at 0x101be02b0> 1 1 干点别的事 2 3 5 8 13
在上面fib
的例子,我们在循环过程中不断调用yield
,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
>>> for n in fib(6): ... print(n) ... 1 1 2 3 5 8
但是用for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
关于如何捕获错误,后面的错误处理还会详细讲解。
还可通过yield实现在单线程的情况下实现并发运算的效果
1 import time 2 def consumer(name): 3 print("%s 准备吃包子啦!" %name) 4 while True: 5 baozi = yield 6 7 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) 8 9 10 def producer(name): 11 c = consumer(‘A‘) 12 c2 = consumer(‘B‘) 13 c.__next__() 14 c2.__next__() 15 print("老子开始准备做包子啦!") 16 for i in range(10): 17 time.sleep(1) 18 print("做了2个包子!") 19 c.send(i) 20 c2.send(i) 21 22 producer("alex")
迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象:
1 2 3 4 5 6 7 8 9 10 11 |
|
而生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
*可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象:
1 2 3 4 5 6 7 8 9 |
|
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
1 2 3 4 |
|
你可能会问,为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python的for
循环本质上就是通过不断调用next()
函数实现的,例如:
1 2 |
|
实际上完全等价于:
1 # 首先获得Iterator对象: 2 it = iter([1, 2, 3, 4, 5]) 3 # 循环: 4 while True: 5 try: 6 # 获得下一个值: 7 x = next(it) 8 except StopIteration: 9 # 遇到StopIteration就退出循环 10 break
2.装饰器
你是一家视频网站的后端开发工程师,你们网站有以下几个版块
1 2 3 4 5 6 7 8 9 10 11 |
|
视频刚上线初期,为了吸引用户,你们采取了免费政策,所有视频免费观看,迅速吸引了一大批用户,免费一段时间后,每天巨大的带宽费用公司承受不了了,所以准备对比较受欢迎的几个版块收费,你拿到这个需求后,想了想,想收费得先让其进行用户认证,认证通过后,再判定这个用户是否是VIP付费会员就可以了,是VIP就让看,不是VIP就不让看就行了呗。 你觉得这个需求很是简单,因为要对多个版块进行认证,那应该把认证功能提取出来单独写个模块,然后每个版块里调用 就可以了,与是轻轻的就实现了下面的功能 。
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 39 40 41 |
|
此时你信心满满的把这个代码提交给你的TEAM LEADER审核,没成想,没过5分钟,代码就被打回来了, TEAM LEADER给你反馈是,我现在有很多模块需要加认证模块,你的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现的功能代码块
- 开放:对扩展开发
这个原则你还是第一次听说,我擦,再次感受了自己这个野生程序员与正规军的差距,BUT ANYWAY,老大要求的这个怎么实现呢?如何在不改原有功能代码的情况下加上认证功能呢?
高阶函数,就是把一个函数当做一个参数传给另外一个函数,我只需要写个认证方法,每次调用 需要验证的功能 时,直接 把这个功能 的函数名当做一个参数 传给 我的验证模块不就行了么
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 39 40 41 42 43 44 |
|
你很开心,终于实现了老板的要求,不改变原功能代码的前提下,给功能加上了验证
你改变了调用方式呀, 想一想,现在没每个需要认证的模块,都必须调用你的login()方法,并把自己的函数名传给你,人家之前可不是这么调用 的, 试想,如果 有100个模块需要认证,那这100个模块都得更改调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式 才能加上认证
但问题是,如何即不改变原功能代码,又不改变原有调用方式,还能加上认证呢?
学过匿名函数没有?
1 2 3 4 |
|
上面这两种写法是不是代表 同样的意思?
我给lambda x:x+1 起了个名字叫plus2,是不是相当于def plus2(x) ?
给函数赋值变量名就像def func_name 是一样的效果,如下面的plus(n)函数,你调用时可以用plus名,还可以再起个其它名字,如
1 2 3 |
|
之前写的下面这段调用 认证的代码
1 2 3 4 5 |
|
你之所改变了调用方式,是因为用户每次调用时需要执行login(henan),类似的。其实稍一改就可以了呀
1 2 3 |
|
这样你,其它人调用henan时,其实相当于调用了login(henan), 通过login里的验证后,就会自动调用henan功能。
那用户调用时,应该是下面这个样子
1 2 3 4 5 6 |
|
但问题在于,还不等用户调用 ,你的america = login(america)就会先自己把america执行了
想实现一开始你写的america = login(america)不触发你函数的执行,只需要在这个login里面再定义一层函数,第一次调用america = login(america)只调用到外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义 的函数里了,login只返回 里层函数的函数名,这样下次再执行america()时, 就会调用里层函数啦。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
这是开发中一个常用的玩法,叫语法糖,官方名称“装饰器”,其实上面的写法,还可以更简单
可以把下面代码去掉
1 |
|
只在你要装饰的函数上面加上下面代码
1 2 3 4 5 6 7 8 9 10 11 12 |
|
效果是一样的。
给你的“河南专区”版块 加了个参数,然后,结果 出错了。
怎么传个参数就不行了呢?
你调用henan时,其实是相当于调用的login,你的henan第一次调用时henan = login(henan), login就返回了inner的内存地址,第2次用户自己调用henan("3p"),实际上相当于调用的时inner,但你的inner定义时并没有设置参数,但你给他传了个参数,所以自然就报错了
最终,你终于搞定了所有需求,完全遵循开放-封闭原则,最终代码如下 。
第二2天早上,产品经理又提了新的需求,要允许用户选择用qq\weibo\weixin认证,此时的你,已深谙装饰器各种装逼技巧,轻松的就实现了新的需求。
1 user_status = False 2 def login(authtype): 3 def outer(func): 4 def inter(): 5 if authtype == ‘qq‘: 6 _username = ‘yang‘ 7 _password = ‘abc123‘ 8 global user_status 9 10 if user_status == False: 11 username = input("username: ") 12 password = input("password: ") 13 14 if username == _username and password == _password: 15 print("欢迎你,尊敬的VIP 。 ") 16 user_status = True 17 else: 18 print("用户名或密码错误。 ") 19 if user_status == True: 20 func() 21 else: 22 print("仅支持QQ。 ") 23 return inter 24 return outer 25 def home(): 26 print("---首页---") 27 @login(‘qq‘) 28 def amercia(): 29 print("---欧美专区---") 30 31 def japan(): 32 print("---日韩专区---") 33 @login(‘weixin‘) 34 def henan(): 35 print("---河南专区---") 36 37 38 home() 39 amercia() 40 japan() 41 henan()
3.模块初始
模块,用一砣代码实现了某个功能的代码集合。
类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和代码间的耦合。而对于一个复杂的功能来,可能需要多个函数才能完成(函数又可以在不同的.py文件中),n个 .py 文件组成的代码集合就称为模块。
如:os 是系统相关的模块;file是文件操作相关的模块
模块分为三种:
- 自定义模块
- 内置标准模块(又称标准库)
- 开源模块 (https://pypi.python.org/pypi)
如何在py文件中引入自定义模块?
import os
from os import time
通过:
1 for i in sys.path: 2 print(i)
我们可以得到模块路径。
在pycharm中自动帮我们把pychram的路径加进去了。
但是在windows 系统执行的时候是不会把pychram的路径加进去的。
这时候我们需要手工添加进去:
1 import sys 2 import os 3 BaseDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 # #__file__是取文件的相对路径 5 # os.path.abspath()是取文件的绝对路径 6 # os.path.dirname()是取文件的上级路径 7 sys.path.append(BaseDir)
python2中
目录里没有__init__,那就只是一个目录,目录是不可以被导入的。
有__init__,那这个目录就变成了包 = "package"
no matter py2 or py3 , only the package can be imported
howere, in py 3 ,the directory will also be treated as package,
__init__.pi in py3 is not mandatory.
4.常用模块
提供对操作系统进行调用的接口:
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 |
|