首页 > 代码库 > python 自动化之路 day 05
python 自动化之路 day 05
内容目录:
- 列表生成式、迭代器&生成器
- 装饰器
- 模块初始
- 常用模块
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 >>> 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
其实还有一种写法,如下
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 = [x * x for x in range ( 10 )] >>> L [ 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 ] >>> g = (x * x for x in range ( 10 )) >>> g <generator object <genexpr> at 0x1022ef630 > |
创建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
|
>>> next (g) 0 >>> next (g) 1 >>> next (g) 4 >>> next (g) 9 >>> next (g) 16 >>> next (g) 25 >>> next (g) 36 >>> next (g) 49 >>> next (g) 64 >>> next (g) 81 >>> next (g) Traceback (most recent call last): File "<stdin>" , line 1 , in <module> StopIteration |
我们讲过,generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> g = (x * x for x in range ( 10 )) >>> for n in g: ... print (n) ... 0 1 4 9 16 25 36 49 64 81 |
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1
2
3
4
5
6
7
|
def fib( max ): n, a, b = 0 , 0 , 1 while n < max : print (b) a, b = b, a + b n = n + 1 return ‘done‘ |
注意,赋值语句:
1
|
a, b = b, a + b |
相当于:
1
2
3
|
t = (b, a + b) # t是一个tuple a = t[ 0 ] b = t[ 1 ] |
但不必显式写出临时变量t就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:
1
2
3
4
5
6
7
8
9
10
11
12
|
>>> fib( 10 ) 1 1 2 3 5 8 13 21 34 55 done |
仔细观察,可以看出,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 = http://www.mamicode.com/fib(10)"干点别的事") 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
|
>>> g = fib( 6 ) >>> while True : ... try : ... x = next (g) ... print ( ‘g:‘ , x) ... except StopIteration as e: ... print ( ‘Generator return value:‘ , e.value) ... break ... g: 1 g: 1 g: 2 g: 3 g: 5 g: 8 Generator return value: done |
关于如何捕获错误,后面的错误处理还会详细讲解。
还可通过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
|
>>> from collections import Iterable >>> isinstance ([], Iterable) True >>> isinstance ({}, Iterable) True >>> isinstance ( ‘abc‘ , Iterable) True >>> isinstance ((x for x in range ( 10 )), Iterable) True >>> isinstance ( 100 , Iterable) False |
而生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
*可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象:
1
2
3
4
5
6
7
8
9
|
>>> from collections import Iterator >>> isinstance ((x for x in range ( 10 )), Iterator) True >>> isinstance ([], Iterator) False >>> isinstance ({}, Iterator) False >>> isinstance ( ‘abc‘ , Iterator) False |
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
1
2
3
4
|
>>> isinstance ( iter ([]), Iterator) True >>> isinstance ( iter ( ‘abc‘ ), Iterator) True |
你可能会问,为什么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
|
for x in [ 1 , 2 , 3 , 4 , 5 ]: pass |
实际上完全等价于:
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
|
def home(): print ( "---首页----" ) def america(): print ( "----动作专区----" ) def japan(): print ( "----武侠专区----" ) def henan(): print ( "----魔幻专区----" ) |
视频刚上线初期,为了吸引用户,你们采取了免费政策,所有视频免费观看,迅速吸引了一大批用户,免费一段时间后,每天巨大的带宽费用公司承受不了了,所以准备对比较受欢迎的几个版块收费,你拿到这个需求后,想了想,想收费得先让其进行用户认证,认证通过后,再判定这个用户是否是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
|
#_*_coding:utf-8_*_ user_status = False #用户登录了就把这个改成True def login(): _username = "alex" #假装这是DB里存的用户信息 _password = "abc!23" #假装这是DB里存的用户信息 global user_status if user_status = = False : username = input ( "user:" ) password = input ( "pasword:" ) if username = = _username and password = = _password: print ( "welcome login...." ) user_status = True else : print ( "wrong username or password!" ) else : print ( "用户已登录,验证通过..." ) def home(): print ( "---首页----" ) def america(): login() #执行前加上验证 print ( "----欧美专区----" ) def japan(): print ( "----日韩专区----" ) def henan(): login() #执行前加上验证 print ( "----河南专区----" ) home() america() henan() |
此时你信心满满的把这个代码提交给你的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
|
#_*_coding:utf-8_*_ user_status = False #用户登录了就把这个改成True def login(func): #把要执行的模块从这里传进来 _username = "alex" #假装这是DB里存的用户信息 _password = "abc!23" #假装这是DB里存的用户信息 global user_status if user_status = = False : username = input ( "user:" ) password = input ( "pasword:" ) if username = = _username and password = = _password: print ( "welcome login...." ) user_status = True else : print ( "wrong username or password!" ) if user_status = = True : func() # 看这里看这里,只要验证通过了,就调用相应功能 def home(): print ( "---首页----" ) def america(): #login() #执行前加上验证 print ( "----欧美专区----" ) def japan(): print ( "----日韩专区----" ) def henan(): #login() #执行前加上验证 print ( "----河南专区----" ) home() login(america) #需要验证就调用 login,把需要验证的功能 当做一个参数传给login # home() # america() login(henan) |
你很开心,终于实现了老板的要求,不改变原功能代码的前提下,给功能加上了验证
你改变了调用方式呀, 想一想,现在没每个需要认证的模块,都必须调用你的login()方法,并把自己的函数名传给你,人家之前可不是这么调用 的, 试想,如果 有100个模块需要认证,那这100个模块都得更改调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式 才能加上认证
但问题是,如何即不改变原功能代码,又不改变原有调用方式,还能加上认证呢?
学过匿名函数没有?
1
2
3
4
|
def plus(n): return n + 1 plus2 = lambda x:x + 1 |
上面这两种写法是不是代表 同样的意思?
我给lambda x:x+1 起了个名字叫plus2,是不是相当于def plus2(x) ?
给函数赋值变量名就像def func_name 是一样的效果,如下面的plus(n)函数,你调用时可以用plus名,还可以再起个其它名字,如
1
2
3
|
calc = plus calc(n) |
之前写的下面这段调用 认证的代码
1
2
3
4
5
|
home() login(america) #需要验证就调用 login,把需要验证的功能 当做一个参数传给login # home() # america() login(henan) |
你之所改变了调用方式,是因为用户每次调用时需要执行login(henan),类似的。其实稍一改就可以了呀
1
2
3
|
home() america = login(america) henan = login(henan) |
这样你,其它人调用henan时,其实相当于调用了login(henan), 通过login里的验证后,就会自动调用henan功能。
那用户调用时,应该是下面这个样子
1
2
3
4
5
6
|
home() america = login(america) #你在这里相当于把america这个函数替换了 henan = login(henan) #那用户调用时依然写 america() |
但问题在于,还不等用户调用 ,你的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
|
def login(func): #把要执行的模块从这里传进来 def inner(): #再定义一层函数 _username = "alex" #假装这是DB里存的用户信息 _password = "abc!23" #假装这是DB里存的用户信息 global user_status if user_status = = False : username = input ( "user:" ) password = input ( "pasword:" ) if username = = _username and password = = _password: print ( "welcome login...." ) user_status = True else : print ( "wrong username or password!" ) if user_status = = True : func() # 看这里看这里,只要验证通过了,就调用相应功能 return inner #用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数 |
这是开发中一个常用的玩法,叫语法糖,官方名称“装饰器”,其实上面的写法,还可以更简单
可以把下面代码去掉
1
|
america = login(america) #你在这里相当于把america这个函数替换了 |
只在你要装饰的函数上面加上下面代码
1
2
3
4
5
6
7
8
9
10
11
12
|
@login def america(): #login() #执行前加上验证 print ( "----欧美专区----" ) def japan(): print ( "----日韩专区----" ) @login def henan(): #login() #执行前加上验证 print ( "----河南专区----" ) |
效果是一样的。
给你的“河南专区”版块 加了个参数,然后,结果 出错了。
怎么传个参数就不行了呢?
你调用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)
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
|
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir( "dirname" ) 改变当前脚本工作目录;相当于shell下cd os.curdir 返回当前目录: ( ‘.‘ ) os.pardir 获取当前目录的父目录字符串名:( ‘..‘ ) os.makedirs( ‘dirname1/dirname2‘ ) 可生成多层递归目录 os.removedirs( ‘dirname1‘ ) 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 os.mkdir( ‘dirname‘ ) 生成单级目录;相当于shell中mkdir dirname os.rmdir( ‘dirname‘ ) 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname os.listdir( ‘dirname‘ ) 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 os.remove() 删除一个文件 os.rename( "oldname" , "newname" ) 重命名文件 / 目录 os.stat( ‘path/filename‘ ) 获取文件 / 目录信息 os.sep 输出操作系统特定的路径分隔符,win下为 "\\",Linux下为" / " os.linesep 输出当前平台使用的行终止符,win下为 "\t\n" ,Linux下为 "\n" os.pathsep 输出用于分割文件路径的字符串 os.name 输出字符串指示当前使用平台。win - > ‘nt‘ ; Linux - > ‘posix‘ os.system( "bash command" ) 运行shell命令,直接显示 os.environ 获取系统环境变量 os.path.abspath(path) 返回path规范化的绝对路径 os.path.split(path) 将path分割成目录和文件名二元组返回 os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素 os.path.exists(path) 如果path存在,返回 True ;如果path不存在,返回 False os.path.isabs(path) 如果path是绝对路径,返回 True os.path.isfile(path) 如果path是一个存在的文件,返回 True 。否则返回 False os.path.isdir(path) 如果path是一个存在的目录,则返回 True 。否则返回 False os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间 os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间 |
python 自动化之路 day 05