day05_20170530_函数(三)/模块与包
上节课补充:
一、yield的表达式方式
协程函数(生成器:yield关键字的另外一种用法:表达式法)。
#模拟人去餐厅吃饭的过程 def eater(name): print('%s ready to eat' %name) while True: food = yield#food为变量名,每次都会拿到一个值,这个值由yield传给他的,一旦传给他,在打印的额就是谁在吃什么东西的一个过程 print('%s start to eat %s' %(name,food)) g = eater('alex') print(g)#<generator object eater at 0x007EBBD0>返回生成器,因为有yield,只是yield是一种表达式方式。 next(g)#既然是生成器你的形式,那么next一次就会触发一次执行。此时停在food = yield next(g)#再次执行,那么会接着food = yield继续执行,结果输出alex start to eat None #以上代码发现food值取不到,这就是我要讲的yield的另一种表达方式形式:food = yield 该怎么用呢? g.send('西红柿')#g.send()#g.send跟next的效果是一样的,都是从上次执行函数暂停的位置继续执行。除此之外可以传值。send给yield传值,yield传给food
如果上述代码,我们把next注释掉,直接第一次就开始用send,执行报错如图:
Traceback (most recent call last): File "D:/python2017/20170423/s17_zc/day05/上节课复习.py", line 14, in <module> g.send('西红柿') TypeError: can't send non-None value to a just-started generator
提示告诉我们开始的生成器必须传一个none作为初始状况。所以如果我们不用next,我们也可以用 g.sent(None).总结:表达式形式的生成器在最初的时候必须先传一个空,让他到达一个初始的位置!以后才能给他send值!
对于上面的例子,我们一开始拿到的是没有初始化的生成器,需要我们手动进行初始化!我可以不以做一个装饰器,使得拿到的直接就是一个已经初始化过的生成器
def deco(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) next(res) return res return wrapper @deco def eater(name): print('%s ready to eat' %name) while True: food = yield#food为变量名,每次都会拿到一个值,这个值由yield传给他的,一旦传给他,在打印的额就是谁在吃什么东西的一个过程 print('%s start to eat %s' %(name,food)) g = eater('alex') g.send('西红柿')
现在追加完成这样的效果:每次执行一个send操作,都返回一下现在菜单总共上了多少道菜,我们可以用列表。
def deco(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) next(res) return res return wrapper @deco def eater(name): print('%s ready to eat' %name) food_list=[] while True: food = yield food_list food_list.append(food) print('%s start to eat %s' %(name,food)) g = eater('alex') print(g.send('西红柿')) 输出结果: alex ready to eat alex start to eat 西红柿 ['西红柿']
总结一下表达式形式的yield功能(x = yield):g.send('1111'),先把1111传给yield,由yield赋值给x,然后在往下执行,直到再次碰到yield,然后把yield后的返回值返回。
来讲一下yield表达式的应用:用携程函数模拟:grep -rl 'python' /root 找到root目录下面所有包含Python这一行内容的文件名是哪些!
在模拟之前先讲一下os的用法:
首先创建一个目录结构如下:
如a下面的文件一层一层遍历出来:
import os g = os.walk(r'D:\python2017\20170423\s17_zc\day05\a')#因为window平台目录是\\,所以前面加上r,r就是告诉Python解释器识别这个字符串的时候用原生字符串去识别 print(g)#输出结果<generator object walk at 0x0057BBA0> 这是一个生成器,那我们可以for循环 for i in g: print(i) #os.walk实现一层一层向下找文件 输出结果: <generator object walk at 0x0062BBA0> ('D:\\python2017\\20170423\\s17_zc\\day05\\a', ['b'], ['a.txt', 'a1.txt']) ('D:\\python2017\\20170423\\s17_zc\\day05\\a\\b', ['c'], ['b.txt']) ('D:\\python2017\\20170423\\s17_zc\\day05\\a\\b\\c', ['d'], ['c.txt']) ('D:\\python2017\\20170423\\s17_zc\\day05\\a\\b\\c\\d', [], ['d.txt'])
取出所有的文件名,见代码:
import os g = os.walk(r'D:\python2017\20170423\s17_zc\day05\a')#因为window平台目录是\\,所以前面加上r,r就是告诉Python解释器识别这个字符串的时候用原生字符串去识别 print(g)#输出结果<generator object walk at 0x0057BBA0> 这是一个生成器,那我们可以for循环 for par_dir,_,files in g: for file in files: file_abs_path=r'%s\%s' %(par_dir,file) print(file_abs_path) 输出结果: <generator object walk at 0x005BBBA0> D:\python2017\20170423\s17_zc\day05\a\a.txt D:\python2017\20170423\s17_zc\day05\a\a1.txt D:\python2017\20170423\s17_zc\day05\a\b\b.txt D:\python2017\20170423\s17_zc\day05\a\b\c\c.txt D:\python2017\20170423\s17_zc\day05\a\b\c\d\d.txt
拿到所有的文件名,如下代码也可以实现:
import os def search(search_path): g = os.walk(search_path) for par_dir,_,files in g: for file in files: file_abs_path=r'%s\%s' %(par_dir,file) print(file_abs_path) search(r'D:\python2017\20170423\s17_zc\day05\a')
但是,不能每次都search里面传参数,我们可以用send不断的传值,我们可以用yield表达式的形式完成:
import os def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @init def search(): while True:#这样就可以不听的send了 search_path = yield g = os.walk(search_path) for par_dir,_,files in g: for file in files: file_abs_path=r'%s\%s' %(par_dir,file) print(file_abs_path) g = search() x=r'D:\python2017\20170423\s17_zc\day05\a' g.send(x)
显示文件的功能完成了,我们开始进行下面的功能:接收上一个功能的结果,然后挨个打开,怎么从一个函数外部源源不断的传过来值呢?
import os def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @init def search(target): while True:#这样就可以不听的send了 search_path = yield g = os.walk(search_path) for par_dir,_,files in g: for file in files: file_abs_path=r'%s\%s' %(par_dir,file) #print(file_abs_path) target.send(file_abs_path) #接收上一个功能的结果,然后挨个打开,怎么从一个函数外部源源不断的传过来值呢? @init#初始化 def opener(): while True: file_abs_path = yield print('openner func==>',file_abs_path) with open(file_abs_path,encoding='utf-8') as f: pass g = search(opener())#search执行得到的是一个生成器,所以要想有执行结果,得传值 g.send(r'D:\python2017\20170423\s17_zc\day05\a')
值传过来之后,如何不停的接受呢?下面是完整的代码:
import os def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @init def search(target): while True:#这样就可以不听的send了 search_path = yield g = os.walk(search_path) for par_dir,_,files in g: for file in files: file_abs_path=r'%s\%s' %(par_dir,file) #print(file_abs_path) target.send(file_abs_path) #接收上一个功能的结果,然后挨个打开,怎么从一个函数外部源源不断的传过来值呢? @init#初始化 def opener(target): while True: file_abs_path = yield #print('openner func==>',file_abs_path) with open(file_abs_path,encoding='utf-8') as f: target.send((file_abs_path,f)) @init #不断的接收上面功能传过来的文件对象,并进行遍历: def cat(target): while True: file_abs_path,f = yield #(file_abs_path,f) for line in f :#遍历文件内容 target.send((file_abs_path,line)) @init #进行过滤 def grep(target,pattern): while True: file_abs_path,line = yield if pattern in line: target.send(file_abs_path) @init def printer(): while True: file_abs_path=yield print(file_abs_path) #如上,整套流程完毕,那么怎么调用呢? x = r'D:\python2017\20170423\s17_zc\day05\a' g = search(opener(cat(grep(printer(),'python')))) print(g)#输出结果:<generator object search at 0x007AACF0>这只是个生成器 g.send(x) 输出结果: <generator object search at 0x0036ACF0> D:\python2017\20170423\s17_zc\day05\a\a.txt D:\python2017\20170423\s17_zc\day05\a\b\b.txt D:\python2017\20170423\s17_zc\day05\a\b\c\d\d.txt
这种变成思路称之为面向过程的编程思路:这是一种流水线式的编程思想,生产线的特点是每个阶段都有自己负责的内容,各个阶段之间有个衔接,上一个跌段的结果交给下一个阶段执行。如果把函数传进去的参数当做是他吃进去的东西,那函数的返回值就是拉出来的结果,这样用函数去写面向过程的东西就变成这样的流水线。这种流水线的编程思想有什么好处么?流水线非常清晰,可以把复杂的东西变得非常的简单,一个环节套一个环节,所以如果让你基于面向过程去写程序的话,那脑子里面应该设计成一条流水线,分各个环节,直到不能再细分为止。坏处就是一个流水线只是在做这一件事情,可扩展性非常差。
总结:面向过程的程序设计:是一种流水线式的编程思路,是机械式。优点:程序的结构清晰,可以把复杂的问题简单缺点:扩展性差应用场景:Linux内核,git,httpd都是面向过程中比较好的案例。
问题:上面的这个例子,如果一个文件中有三行含Python的,那打印出几个文件名称呢?
输出结果:
<generator object search at 0x006BBCF0>
D:\python2017\20170423\s17_zc\day05\a\a.txt
D:\python2017\20170423\s17_zc\day05\a\b\b.txt
D:\python2017\20170423\s17_zc\day05\a\b\b.txt
D:\python2017\20170423\s17_zc\day05\a\b\b.txt
D:\python2017\20170423\s17_zc\day05\a\b\c\d\d.txt
那代码怎么修正呢?
import os def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @init def search(target): while True:#这样就可以不听的send了 search_path = yield g = os.walk(search_path) for par_dir,_,files in g: for file in files: file_abs_path=r'%s\%s' %(par_dir,file) #print(file_abs_path) target.send(file_abs_path) #接收上一个功能的结果,然后挨个打开,怎么从一个函数外部源源不断的传过来值呢? @init#初始化 def opener(target): while True: file_abs_path = yield #print('openner func==>',file_abs_path) with open(file_abs_path,encoding='utf-8') as f: target.send((file_abs_path,f)) @init #不断的接收上面功能传过来的文件对象,并进行遍历: def cat(target): while True: file_abs_path,f = yield #(file_abs_path,f) for line in f :#遍历文件内容 tag = target.send((file_abs_path,line)) if tag: break @init #进行过滤 def grep(target,pattern): tag = False while True: file_abs_path,line = yield tag tag = False if pattern in line: tag = True target.send(file_abs_path) @init def printer(): while True: file_abs_path=yield print(file_abs_path) #如上,整套流程完毕,那么怎么调用呢? x = r'D:\python2017\20170423\s17_zc\day05\a' g = search(opener(cat(grep(printer(),'python')))) print(g)#输出结果:<generator object search at 0x007AACF0>这只是个生成器 g.send(x)
二、匿名函数
有名函数,看下面的例子:
#有名函数: def func(x,y): return x + y func(1,2)
匿名函数,看下面的例子:
#匿名函数,用在不需要名字的场景: f = lambda x,y:x+y print(f)#<function <lambda> at 0x00851150>输出的func的内存地址 #lambda 的好处:取代上面简单的函数 print(f(1,2))#调用
分析前请看下面的图片解析:
匿名函数,定义完了没人用,引用计数为0 ,是垃圾,直接就回收掉了。那他的应用场景何在?就跟下面要讲的函数有关系了!
#max,min,zip的用法 #定义一个薪资的字典 salaries = { 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } #找出薪资最高的人! #print(max(salaries))#为何输出yuanhao呢?max默认比较的是key的最大值!首字母一个一个去比较 #怎样求value最大值呢? #print(max(salaries.values()))#这样就只输出了最大的那个值,也不是那个人 res = zip(salaries.values(),salaries.keys()) #print(res)#<zip object at 0x004E5468> zip的一个对象 #print(list(res))#看结果可以用for也可以用list print(max(res))#需要把print(list(res))注释掉,否则报ValueError: max() arg is an empty sequence,原因res是迭代器,遍历一次,就不会再回去取值了,把他注释掉,就没有任何迭代了,就可以取值了
输出结果:
(100000000, 'alex')
但是上面的zip实现的代码还是没有完美的表达我想要的结果,我只想取薪资最高者的一个名字,用max函数优化:
#定义一个薪资的字典 salaries = { 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } def func(k): return salaries[k] print(max(salaries,key=func))#要比较的是salaries,而key表示你要按照什么字段进行比价薪资的大小
上面的按个函数功能非常简单,他的功能就是一个return,而且用了一次就结束了,针对这种情况,什么可以派上用场?匿名函数的用途:
salaries = { 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } def func(k): return salaries[k] #print(max(salaries,key=func))#要比较的是salaries,而key表示你要按照什么字段进行比价薪资的大小 print(max(salaries,key=lambda k:salaries[k])) print(min(salaries,key=lambda k:salaries[k]))
现在我要排序这个字典,看下代码:
salaries = { 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } print(sorted(salaries))#sorted默认的排序结果是从小到大 print(sorted(salaries,key=lambda x:salaries[x]))#升序 print(sorted(salaries,key=lambda x:salaries[x],reverse=True))#降序排序
普及另外一种编程思想:函数式编程思想-->他有很多特性,在Python里面支持不了!1)执行一个函数,不要对外部产生任何的修改。2)没有循环的概念,他所有的循环都用递归去做,函数式编程里面的递归一定要做成伪递归的方式,但是Python里面不支持伪递归的方式。要研究函数是编程语言,跟Python是没有关系的。面向过程编程思想和函数式编程思想跟语言无关!
global关键字的功能:在执行的过程中对外部的状态就行修改!
x = 1000 def f1(): global x#如果global x 不加这句,那么最终的x的结果就是1000 x = 0 f1() print(x)
高阶函数:如果函数传入的参数指向的是一个函数,或者他的返回值是一个函数,这个函数就称为高阶函数!
#如下面例子-----这是函数式编程思想里面的特性,Python不是一门编程式语言,但是可以用它的一些好的特性!
def func(f): return f res = func(max)#参数是一个内置的max print(res)#返回的是max的内存地址
讲一下map reduce filter
map:
实现场景1):给列表中的每一个元素加上sb后缀
l = ['alex','wupeiqi','yuanhao'] res = map(lambda x:x+'_sb',l) print(res)#返回map的对象<map object at 0x00339770>,发现有__next__方法,他是一个迭代器 print(list(res)) 输出结果: <map object at 0x00839770> ['alex_sb', 'wupeiqi_sb', 'yuanhao_sb']
实现场景2)已知num=(2,4,9,10),现在要求得到一个新的列表,里面的值是平方的形式!
nums = (2,4,9,18) res1=map(lambda x:x**2,nums)#每次迭代的结果有个参数传过来 print(list(res1)) print(nums)#发现原来的数据没有被修改
输出结果:[4, 16, 81, 324](2, 4, 9, 18)
reduce:
实现场景:求列表各个元素的和
#Python3里面reduce放在一个模块里面了 from functools import reduce l = [1,2,3,4,5] print(reduce(lambda x,y:x+y,l)) print(reduce(lambda x,y:x+y,l,10))#如果有初始值10,那么就是初始值和迭代的结果相加 输出结果: 15 25
filter:
实现场景:把下面列表中SB结尾的元素筛选出来
l = ['alex_sb','wupeiqi_sb','yuanhao_sb','egon'] res = filter(lambda x:x.endswith('sb'),l) print(res) print(list(res))
本节课内容
三、递归调用
递归调用:在函数调用过程中,直接或间接地调用了函数本身,这就是函数的递归调用
递归的形式:
#!/usr/bin/python # -*- coding:utf-8 -*- def f1(): print('from f1') f1() f1()
还有另外一种形式,但是用的很少:
def f1(): print('f1') f2() def f2(): f1() f1()
递归,抛开技术,我们来举个例子: 比如问路:我问A天安门怎么走,A去问B,在A问B的时候,我在等待,我怎么等呢?我在内存中维护我的状态,A问B,B也不知道,B去问C,那么A在B问C的过程中也是在等待,在内存中维护他的状态,这一连串的等待都需要开辟内存空间,大家都要在这等着,直到C问到了天安门在哪,然后C告诉B,B在告诉A,A在告诉我,那如果允许让你出现无限递归,那么会出现什么状况呢?会一直占着内存,直到内存消耗完毕,那Python是不允许你这样做的。你需要导入一个sys.getrecursionlimit()模块,他规定你递归可以递归到多少层,默认是1000层,他是可以让你设置这个值的!
import sys print(sys.getrecursionlimit())
自定义递归多少层:改了是没用的,他只是增加了你程序的风险,这个要取决于你内存的大小。
import sys print(sys.getrecursionlimit()) print(sys.setrecursionlimit(100000)) print(sys.getrecursionlimit())
使用场景: 问A多大,A说比B大2岁,B说比C大2岁,C说比D大2岁,D为18岁
分析:
age(5) = age(4)+2 age(4) = age(3)+2 age(3) = age(2)+2 age(2) = age(1)+2 age(1) = 18 age(n) = age(n-1)+2 n>1 age(n) = 18 n=1
代码实现:
def age(n): if n == 1: return 18 return age(n-1) +2 print(age(5))
分析下代码过程:
分析完之后,我们来总结一下递归:
1)必须有一个明确的结束条件(把地推给结束掉,才能回溯)
2)每次进入更深一层递归时,问题规模相比上次递归都应有所减少(递归的目的:这一层解决不了问题,就要下一层,知道解决问题)
3)递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
讲一下递归的应用:
二分法:比如一个序列,里面有一万个值,让你判断某个值是否在这个序列里面,你会for循环一个一个比对,若这个值在第九千个位置,那前面的遍历都浪费了,可以采取二分法。二分法的前提是这个列表里面的元素必须是从小到大的排列,我们可以把这个列表切为两半,中间的值比左边的值大,比右面的值小,那我最开始就去比较中间的这个值,如果我要找的那个值比中间这个值大,那证明这个值在列表的右边,我们可以用切片的方式把列表切开只保留右边的一部分,然后我在进行找中间值,直到最终找到那个值!
用递归的方式,把这个二分法写出来:
l = [1,2,10,33,53,71,73,75,77,85,101,201,202,999,11111] def search(find_num,seq): if len(seq) == 0: print('not exist') return #这个if是判断如果要查的值不在列表里面,就打印not exist,并返回递归 mid_index=len(seq)//2#根据中间的索引取中间的值,注意:如果列表只有两个值,索引取1,也就是右边的那个值 mid_num = seq[mid_index] print(seq,mid_num) if find_num > mid_num: #in the right seq = seq[mid_index+1:] search(find_num,seq) elif find_num < mid_num: #in the left seq = seq[:mid_index] search(find_num,seq) else: print('find it') search(77,l) search(72,l) 输出结果: [1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75 [77, 85, 101, 201, 202, 999, 11111] 201 [77, 85, 101] 85 [77] 77 find it [1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75 [1, 2, 10, 33, 53, 71, 73] 33 [53, 71, 73] 71 [73] 73 not exist
四、模块
什么是模块?一个模块就是一个包含了Python定义和声明的文件,文件名就是模块名字加上.py的后缀。
为何要使用模块?如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,
如何使用模块? 第一种方式:可以直接把py文件当做一个脚本,直接运行。 第二种方式:当做一个模块导入进来。
import:
新建目录:模块与包 在该目录建spam.py,代码如下:
#spam.py print('from the spam.py') money=1000 def read1(): print('spam->read1->money',money) def read2(): print('spam->read2 calling read') read1() def change(): global money money=0
再在该目录建test.py,代码如下:
import spam#import这个关键字跟def,class等一样,就是在定义一个名字 #执行一下,输出from the spam.py,说明导入一个模块,就会执行里面的代码 #取出导入模块里面的变量名称,比如money money = 10#即使本文件定义了money,但是最终输出的money的值还是导入模块中定义的money print(spam.money)#spam.的形式 print(spam.read1)#拿到read1的内存地址,就可以执行了 spam.read1() def read1(): print('from test.py') spam.read2()#调用的是spam.py里面的read2函数里面的read1函数,而不是上面定义的read1()函数 spam.change() print(money)#调的是原文件的money,不是导入模块的
import导入模块干的事:
1、产生新的名称空间
2、以新建的名称空间为全局名称空间,执行文件的代码
3、拿到一个模块名spam,指向spam.py产生的名称空间
有时候你导入的模块的名称非常长,你可以给他取一个别名如下:
import spam as x print(x.money)
在一行你可以导入多个模块,如:import spam,os,sys用逗号分隔,但是不鼓励这么做,最好还是分行导入,这样更明确一点。
from ...import...
from spam import money print(money) 输出结果: from the spam.py 1000
这个就不用输入(模块名.)的形式了 !直接就可以使用。
import导入模块干的事:
1、产生新的名称空间
2、以新建的名称空间为全局名称空间,执行文件的代码
3、但是不会拿到一个模块名spam,直接拿到是spam.py产生的名称空间中的名字
这种导入方式的优点:方便,不用加前缀。缺点:特别容易和当前文件里面的名称空间冲突
拿到spam.py里面的函数怎么操作呢?
from spam import money,read1 print(money) read1() 输出结果: from the spam.py 1000 spam->read1->money 1000
如下代码返回的结果是什么呢?
from spam import money,read1 money = 10 print(money) 输出结果: from the spam.py 10
返回是当前文件的money,money在最开始导入的时候绑定到了一个名字,再次定义的时候,money被重新绑定了。
如下代码返回的是什么结果?
from spam import money,read1,read2,change def read1(): print('=============from test.py read1') read2() 输出结果: from the spam.py spam->read2 calling read spam->read1->money 1000
跟导入方式无关,这个函数来自于哪个文件,他执行的时候全都以那个环境变量为准。
思考这样的一个问题:如果我的spam文件里面有三十多个函数,那我在导入的时候能全部都写一遍导入么?导入模块是一种方式,那如果不想加前缀,又不想一个一个导入怎么办呢?直接写:from spam import * 有一个控制导入*的内置的行为变量:在源文件的任意位置定义一个__all__=['money','read1'] 列表里面一定是字符串类型的。这就是证明from spam import *只导入了all里面定义的两个名称空间。
from...import也支持as,给导入的内容取个别名
from spam import read1 as read#给read1取的别名
from...import也支持导入多行
from spam import (read1, read2, money)
之前给大家讲过,一个Python文件有两种用途,一种用途是直接当脚本运行,还有一种方式是在另一个文件中当做模块导入。
有一个内置变量叫__name__,通过他的值,我可以判断当前文件是哪种用途。
#spam.py当做脚本执行,__name__ == '__main__'
#spam.py当做模块导入,__name__ = 模块名
print('当前文件的用途是:',__name__)
输出结果: 当前文件的用途是: __main__
当前文件的用途是: spam
五、模块搜索路径
导入模块跟路径有什么关系?导入模块是要执行那个文件,是文件就要有路径,跟文件有关系,找不到就会出现问题,那么我们来讲一下模块搜索路径。
我导入一个模块,他怎么找呢?先从内存里面开始找,内存没有在去内置里面找,内置没有去sys.path里面找,
我们来演示一下怎么从内存里面找的?存在spam.py,test2.py 我们在test2.py里面输入下面代码:导入了spam和time ,我们执行test2.py之后,睡10s的期间删除spam.py ,发现输出结果并没有报错,说明内存里面已经有spam.py这个文件了。
#!/usr/bin/python # -*- coding:utf-8 -*- import time import spam time.sleep(10) import spam as aa print(aa.money) 输出结果: from the spam.py 当前文件的用途是: spam 1000
模块搜索顺序:内存--->内置----->sys.path
sys.path指的是什么呢?
import sys print(sys.path) 输出结果: ['D:\\python2017\\20170423\\s17_zc\\day05\\模块与包',
'D:\\python2017\\20170423\\s17_zc',
'D:\\tools\\python\\python35.zip',
'D:\\tools\\python\\DLLs',
'D:\\tools\\python\\lib',
'D:\\tools\\python',
'D:\\tools\\python\\lib\\site-packages']
问题:在pycharm中输入spam.之后,显示的方法不是spam.py里面的内容,如图:这是为什么呢?
这是pycharm给返回来的,spam.py的路径不在环境变量里面。如果想显示spam.py里面的内容,怎么操作呢?spam.py在目录《模块与包》下面,所以右键:
如图:发现spam也没有错误提示了,实际上就做了一个加环境变量的操作。
注意:如果导入的模块在其他目录下,而不在引入该模块的文件所在的目录下,我们需要把他的目录添加到sys.path中,如下操作:
这样,执行test2.py,就可以输出正确结果了!append表示把该目录添加到了环境变量的最后一行,如果想要提高速度可以执行:
sys.path.insert(0,r'D:\python2017\20170423\s17_zc\day05\dir1')
注意:我们导入spam.py文件的时候生成了一个文件,我们可以通过命令来看到:
这个文件夹里面显示的是:
spam.cpython-35.pyc这个就是字节码文件,什么时候会产生这个文件呢?只有文件被导入的时候会产生。产生了以后下一次在导入的时候就不用再解释了直接读这个字节码,提升了导入速度。
六、包
包是一种通过使用‘.模块名’来组织Python模块名称空间的方式。
1.无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2.包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3.import导入文件时,产生名称空间中的名字来源于文件,import包,产生的名称孔家安的名字同样来源于文件,即包下的__init__.py。导入包本质就是在导入该文件。
首先按照下面的目录结构建好包:
glance/ #Top-level package |--__init__.py #Initialize the glance package |-- api #Subpackage for api | |--__init__.py | |-- policy.py | |-- versions.py |-- cmd #Subpackage for cmd | |--__init__.py | |-- manage.py |-- db #Subpackage for db |--__init__.py |-- models.py
建好的目录如图:
glance与test.py在同级目录,glance对于test.py来说就是一个模块,只不过这个模块里面还有很多小的模块。那test.py
在导入glance的时候,先在内存里面找,然后在内置空间找,都不是,就在sys.path里面找,在当前路径可以找到。
建好之后,下面为各个文件里面的内容:
#文件内容 #policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py:',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py:',engine)
在test.py里面执行如下:
import glance #导入glance这个包,实际上使用的就是这个包下面的__init__.py #如何验证呢? 在glance下的__init__.py编辑:print('from glance.__init__.py'),执行之后输出的结果:from glance.__init__.py
在test.py里面执行如下:
import glance glance.api.policy.get() #报错:AttributeError: module 'glance' has no attribute 'api' #说明:import glance,看起来导入的是glance,实际上导入的是他下面的__init__.py,也就是说glance.调用的名字都是在找__init__.py里面的名字,而__init__.py里面并没有api,没有所以报这个错误!
在test.py里面执行如下:
import glance.api.policy#导入具体的模块 print("========")#判断输出的from glance.__init__.py是上一句执行的结果 glance.api.policy.get()#输出from policy.py
注意:执行import glance.api.policy的时候,因为导入的api是包,glance也是包,所以也会执行glance和api下面的__init__.py
在test.py里面执行如下:
import glance.api.policy.get glance.api.policy.get() #报错:ImportError: No module named 'glance.api.policy.get'; 'glance.api.policy' is not a package #说明:点的使用是包独有的,点的左面必须是包。policy前面的点的前面是包,但是,get前面的点的前面不是包名,故报错。
考虑这个问题:
在glance下面的__init__.py编辑:import api,在test.py 中编辑:import glance ,执行test.py结果报错:ImportError: No module named 'api' 这是什么原因呢?
说明:按照流程:执行test.py----->导入glance,就要找glance,就跟test.py这个执行文件在同一路径下,找到之后,发现他是一个包,就开始执行他下面的__init__.py(执行的过程中发现他又import api)----->导api就要找api,问题来了,去哪里找呢?api用的是一个相对路径,到底是相对于init所在的路径,还是执行文件test.py的相对路径呢? 你执行哪个文件,那个sys.path就是执行文件的sys.path的。__init__.py里面的import api ,api要在test.py所在的路径里面找,test.py在包这个路径下,所以找不到。怎么解决呢?
在test.py里面执行如下:
import glance#触发glance下面__init.py__的运行 glance.policy.get() glance.models.register_models('mysql')
glance下面的__init__.py的内容:
from glance.api import policy,versions #from glance import api.versions #语法错误 from glance.cmd import manage from glance.db import models
这样,test.py就正常执行输出:
from policy.py from models.py: mysql
注意:导入包的时候实际在导入他下面的__init__.py文件。
思考这样的问题:
如果我test.py中执行如下:
import glance glance.get() glance.register_models('mysql')
那我怎么修改glance下面的__init__.py文件呢?如下:
from glance.api.policy import get from glance.db.models import register_models
下面有这样的一个需求:比如说在policy.py里面,我的包内部还引用了我包内部的其他模块,就是包内部的互相引用!现在要在policy.py里面用到api包下面的versions.py里面的create_resource功能。怎么操作?
policy.py编辑如下:加上main方法,这样可以直接在本文件右键执行(内部测试时候用main)
if __name__ == '__main__': import versions versions.create_resource('a.conf')
输出结果:from version.py: a.conf
在包内部使用导入操作,应该尽量避免使用import,因为import在导入的时候就是以当前路径为准去找,所以在包内部要想导入其他模块的话,应该用from...import
policy.py编辑如下:
from glance.api import versions versions.create_resource('a.conf')
这样需要通过执行test.py来调:
import glance.api.policy
如果是想包内调用跨包的功能怎么操作呢?现在要在policy.py里面用db包下面的models.py里面的register_models功能。怎么操作?
policy.py编辑如下:
from glance.db import models models.register_models('mysql')
通过执行test.py来调:
import glance.api.policy
包内部去导入自己包的其他模块有两种方式:包的绝对导入和包的相对导入!
包的绝对导入:用from...import的导入方式,from后面跟包的顶头比如glance,称为包的绝对导入。从包的头开始导,这样就一定能找到导入的内容。这种的导入的缺点:如果你的路径有变更,那么已经导入过的因路径不对,会报错。所以引入包的相对导入!实现下面需求:
现在要在policy.py里面用db包下面的models.py里面的register_models功能。怎么操作?
policy.py编辑如下:
from ..db import models models.register_models('mysql')
执行test.py如下:
import glance.api.policy
优点:即便路径发生变化,里面的代码也无需改变!如果多层目录,就可以不断的加点(.)。
疑点:关于模块的导入,有import * 的用法,那关于包的的导入,有这种用法么?也有!
语法为:from glance.api import *
与* 对应的是 __all__ ,配置在 __init__.py里面!下面简例:
glance/api下的__init__.py代码如下:
__all__=['x'] x = 1 y = 2
test.py代码如下:
from glance.api import * print(x) 输出结果: 1
七、re模块的用法
正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。或者说:正则就是用来描述一类事物的规则。(在Python中)他内嵌在Python中,并通过re模块实现。正则表达式模式被编译成一系列的字节码,然后由C编写的匹配引擎运行。
生活中处处都是正则: 比如我们描述:4条腿 你可能会想到的是四条腿的动物或者桌子,椅子等 继续精确描述:4条腿,活的 就只剩下四条腿的动物这一类了
是
为了消除你对正则表达式的恐惧,我们提供了这个地址:http://tool.oschina.net/regex/#
输入左侧的内容,比如匹配邮箱:
用Python实现,把生成的正则粘过来:
#!/usr/bin/python # -*- coding:utf-8 -*- import re s = ''' http://www.baidu.com 1011010101 egon@oldboyedu.com 你好 21213 010-3134 egon@163.com ''' res = re.findall(r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?",s)#因为Python对/有自己的认识,所以加个 r ,把单引号改成双引号 print(res)
下面来介绍下正则:
1)\w:匹配字母数字及下划线
import re print(re.findall('\w','as213df_*|')) #输出结果:['a', 's', '2', '1', '3', 'd', 'f', '_'] #没有输出* |
2)\W:匹配非字母数字下划线
import re print(re.findall('\W','as213df_*|')) #输出结果:['*', '|']
注意:下面执行代码:
import re print(re.findall('a\wb','a_b a3b aEb a*b')) 输出结果:['a_b', 'a3b', 'aEb']
3)\s:匹配任意空白字符,等价于[\t\n\r\f]
import re print(re.findall('\s','a b\nc\td')) 输出结果: [' ', '\n', '\t']
4)\S:匹配任意非空字符
import re print(re.findall('\S','a b\nc\td')) 输出结果: ['a', 'b', 'c', 'd']
5)\d:匹配任意数字,等价于[0-9]
import re print(re.findall('\d','a123bcdef')) 输出结果: ['1', '2', '3']
6)\D:匹配任意非数字
import re print(re.findall('\D','a123bcdef')) 输出结果: ['a', 'b', 'c', 'd', 'e', 'f']
7)\n:匹配一个换行符
import re print(re.findall('\n','a123\nbcdef')) 输出结果: ['\n']
8)\t:匹配一个制表符
import re print(re.findall('\t','a123\tbcdef')) 输出结果: ['\t']
9)^:匹配字符串的开头
import re print(re.findall('^h','hello egon hao123')) print(re.findall('^h','ello egon hao123'))#没有就为空 输出结果: ['h'] []
10)$:匹配字符串的结尾
import re print(re.findall('3$','ello egon hao123')) print(re.findall('3$','ello egon hao123ww')) 输出结果: ['3'] []
11).:匹配任意字符,除了换行符
import re print(re.findall('a.c','abc a1c a*c a|c abd aed a\nc'))#换行符匹配不到 输出结果: ['abc', 'a1c', 'a*c', 'a|c']
#如果想让点也匹配到换行符,该怎么办?
import re
print(re.findall('a.c','abc a1c a*c a|c abd aed a\nc',re.S))#这样就可以输出换行符了
12)[...]:跟点差不多,匹配一个字符,但是他可以指定这个字符是谁
import re print(re.findall('a[12]c','abc a1c a*c a|c abd aed a\nc')) 输出结果: ['a1c']
13)-:有开头有结尾表示范围,从...到...
import re print(re.findall('a[0-9]c','abc a1c a*c a|c abd aed a\nc')) 输出结果: ['a1c']
还可以表示匹配 -
import re print(re.findall('a[0-9a-zA-Z*-]c','a1c abc a*c a-c aEc')) 输出结果: ['a1c', 'abc', 'a*c', 'a-c', 'aEc']
14)[ ]:如果里面加上^,表示取反
import re print(re.findall('a[^0-9]c','a1c abc a*c a-c aEc')) 输出结果: ['abc', 'a*c', 'a-c', 'aEc']
15)* + ? {n,m} 这些统一代表重复匹配
1)*:匹配0个或多个的表达式 import re print(re.findall('ab*','a')) print(re.findall('ab*','abbbbbb')) print(re.findall('ab*','bbbbbb')) 输出结果: ['a'] ['abbbbbb'] [] 2)+:表示+左侧的字符出现一次或者无穷次,至少一次 import re print(re.findall('ab+','a')) print(re.findall('ab+','abbbbb')) print(re.findall('ab+','bbbb')) 输出记结果: [] ['abbbbb'] [] #注意:下面的这两种取值情况: print(re.findall('ab[123]','abbbbb123')) print(re.findall('ab[123]','ab1 ab2 ab3 abc1')) #下面相当于 ab[123] ab1+ ab2+ ab3+ print(re.findall('ab[123]+','ab1111 ab2 ab3 abc1 ab122')) 输出结果: [] ['ab1', 'ab2', 'ab3'] ['ab1111', 'ab2', 'ab3','ab122'] 3){}:指定左侧字符出现的次数 print(re.findall('ab{3}','ab1 abbbbbbbbb2 abbbb3 ab4 ab122')) 输出结果: ['abbb', 'abbb'] #也可以指定出现次数的范围 print(re.findall('ab{3,4}','ab1 abbb123 abbbb123 abbbbbt')) 输出结果: ['abbb', 'abbbb', 'abbbb'] #指定a后面的字符至少出现多少次 print(re.findall('ab{3,}','ab1 abbb123 abbbb123 abbbbbt')) 4)?:匹配0次或者1次,也就是左侧的字符出现0次或者1次 print(re.findall('ab?c','ac abc aec a1c'))#可以匹配ac ,abc 输出结果: ['ac', 'abc'] 5) .* 贪婪匹配 匹配任意长度的任意字符 print(re.findall('a.*c','ac abc aec a1c'))#输出结果:['ac abc aec a1c'] 6) .*? 非贪婪匹配 print(re.findall('a.*?c','ac abc aec a1c a'))#输出结果:['ac', 'abc', 'aec', 'a1c'] print(re.findall('a.*?c','ac abc aec a1c a\nc',re.S))#注意 :.不包含\n 输出结果:['ac', 'abc', 'aec', 'a1c', 'a\nc']
16)a|b:匹配a或b 左边成功了就不会执行右边 ,左边不成功才会执行右边
import re print(re.findall('compan(?:y|ies)','Too many companies have gone bankrupt,next company is my company')) 输出结果: ['companies', 'company', 'company']
17)()分组
#()分组 print(re.findall('ab+123','ababab123')) print(re.findall('(ab)+123','ababab123'))#['ab'],匹配到末尾的ab123中的ab print(re.findall('(?:ab)+123','ababab123'))#findall的结果不是匹配的全部内容,而是组内的内容,?:可以让结果为匹配的全部内容 输出结果: ['ab123'] ['ab'] ['ababab123']
18) \ 转义字符
import re print(re.findall('a\\c','a\c'))#对于正则来说a\\c确实可以匹配到a\c,但是在Python解释器读取a\\c时,会发生转义,然后交给re去执行,所以跑出异常 print(re.findall(r'a\\c','a\c'))#r代表告诉解释器使用rawstring,即原生字符串,把我们正则内的所有符号都当普通字符处理,不要转义 print(re.findall('a\\\\c','a\c'))#同上面的意思一样,和上面的结果一样都是['a\\c']
----------------本节over