pyday05
一、生成器(yield用法)
1.yield两种用法:
yield的语句形式:
def foo(): print('first') yield 1 print('second') yield 2 print('third') yield 3 print('fourth') yield 4 print('fifth') g=foo() # 直接加()是不会触发执行迭代器函数的,而是获得生成器对象 print(g) for i in g: print(i) 运行结果: <generator object foo at 0x00000000026C2630> first second third fourth fifth
print(next(g)) #使用next()方法触发迭代器g的执行,进而触发函数的执行 print(next(g)) print(next(g)) print(next(g)) print(next(g))
next()方法碰到yield关键字会停止执行并挂起,直到下次使用next(),下次开始执行的地方就是挂起的地方。
yield的表达式用法:
def eater(name): print('%s ready to eat'%name) while True: food = yield print('%s eat %s'%(name,food)) g = eater('dylan') next(g) next(g) 运行结果: dylan ready to eat #第一次next() dylan eat None #第二次next(),由于food没有定义,所以为None
可以使用dec.send(var)方法给yield传参数,也同时相当于对生成器对象使用next():
def eater(name): print('%s ready to eat'%name) while True: food = yield print('%s eat %s'%(name,food)) g = eater('dylan') next(g) g.send('food1') g.send('food2') 运行结果: dylan ready to eat dylan eat food1 dylan eat food2
表达式形式的yield开始时必须先传一个None值[send(None)或next()都可以]进行初始化到达初始化位置,否则报错:
def eater(name): print('%s ready to eat'%name) while True: food = yield print('%s eat %s'%(name,food)) g = eater('dylan') # next(g) # g.send(None) g.send('food1') g.send('food2') 运行结果: Traceback (most recent call last): File "D:/python/py17/code/py17day05/lesson/yield_test01.py", line 20, in <module> g.send('food1') TypeError: can't send non-None value to a just-started generator
可以定义一个装饰器对yield进行初始化:
def send_none(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) #在装饰器里执行原函数的next() return res return wrapper @send_none # eater = send_none(eater) def eater(name): print('%s ready to eat'%name) while True: food = yield print('%s eat %s'%(name,food)) g = eater('dylan') # next(g) g.send('food1') g.send('food2') 运行结果: dylan ready to eat dylan eat food1 dylan eat food2
也可以利用yield进行值的返回:
def send_none(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @send_none # eater = send_none(eater) def eater(name): food_list = [] print('%s ready to eat'%name) while True: food = yield print('%s eat %s'%(name,food)) g = eater('dylan') # next(g) print(g.send('food1')) print(g.send('food2')) 运行结果: dylan ready to eat dylan eat food1 None #没有在yield后定义返回结果,所以返回为None dylan eat food2 None
def send_none(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @send_none # eater = send_none(eater) def eater(name): food_list = [] #定义一个空列表 print('%s ready to eat'%name) while True: food = yield food_list # 返回这个列表 food_list.append(food) # 将send的值加入列表 print('%s eat %s'%(name,food)) g = eater('dylan') # next(g) print(g.send('food1')) print(g.send('food2')) 运行结果: dylan ready to eat dylan eat food1 ['food1'] dylan eat food2 ['food1', 'food2']
2.表达式形式yield应用:
实现模拟gred -rl 'python' /root:
目录结构:

关于os.walk:
import os g = os.walk(r'D:\python\py17\code\py17day05\a') for i in g: print(i) 运行结果: ('D:\\python\\py17\\code\\py17day05\\a', ['b'], ['a', 'a1']) ('D:\\python\\py17\\code\\py17day05\\a\\b', ['c'], ['b', 'b1']) ('D:\\python\\py17\\code\\py17day05\\a\\b\\c', ['d'], ['c', 'c1']) ('D:\\python\\py17\\code\\py17day05\\a\\b\\c\\d', [], ['d', 'd1']) ''' 遍历结果表示了每一层级文件夹的详情, 第一个元素为文件夹路径, 第二个元素为当前路径包含的子文件夹, 第三个元素为当前路径下的文件 '''
import os def get_abs_path(walk_path): w = os.walk(walk_path) for path,_,files in w: for file in files: file_abs_path = r'%s\%s' % (path, file) print(file_abs_path) p = r'D:\python\py17\code\py17day05\a' get_abs_path(p) 运行结果: D:\python\py17\code\py17day05\a\a D:\python\py17\code\py17day05\a\a1 D:\python\py17\code\py17day05\a\b\b D:\python\py17\code\py17day05\a\b\b1 D:\python\py17\code\py17day05\a\b\c\c D:\python\py17\code\py17day05\a\b\c\c1 D:\python\py17\code\py17day05\a\b\c\d\d D:\python\py17\code\py17day05\a\b\c\d\d1
对于函数调用方式来说,根据检索的路径不同,每次都要重新调用一次get_abs_path函数,
使用yield表达式用法可以实现“一次调用,多次赋值”:
import os def send_none(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) res.send(None) # next(res) return res return wrapper @send_none def get_abs_path(): while True: walk_path = yield w = os.walk(walk_path) for path,_,files in w: for file in files: file_abs_path = r'%s\%s' % (path, file) print(file_abs_path) p = r'D:\python\py17\code\py17day05\a' g = get_abs_path() g.send(p) g.send(r'D:\python\py17\code\py17day05\lesson') 运行结果: D:\python\py17\code\py17day05\a\a D:\python\py17\code\py17day05\a\a1 D:\python\py17\code\py17day05\a\b\b D:\python\py17\code\py17day05\a\b\b1 D:\python\py17\code\py17day05\a\b\c\c D:\python\py17\code\py17day05\a\b\c\c1 D:\python\py17\code\py17day05\a\b\c\d\d D:\python\py17\code\py17day05\a\b\c\d\d1 D:\python\py17\code\py17day05\lesson\yield_test01.py D:\python\py17\code\py17day05\lesson\yield表达式用法.py D:\python\py17\code\py17day05\lesson\__init__.py
初步思路,因为已经实现获得目录下每一个文件和其子文件夹文件的绝对路径,接下来要将这些文件做打开操作,打开操作需要将绝对路径传入打开函数中,我们也通过send进行传值:
import os def send_none(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) res.send(None) # next(res) return res return wrapper @send_none def get_abs_path(open_file): while True: walk_path = yield w = os.walk(walk_path) for path,_,files in w: for file in files: file_abs_path = r'%s\%s' % (path, file) # print(file_abs_path) open_file.send(file_abs_path) # 将得到的绝对路径通过send方式传给open_file这个生成器函数 @send_none def open_file(): while True: file_abs_path = yield with open(file_abs_path,encoding='utf-8') as f: print(f) p = r'D:\python\py17\code\py17day05\a' g = get_abs_path(open_file()) # get_abs_path本身也是生成器函数,要通过send方法调用 g.send(p) 运行结果: <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\a' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\a1' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\b\\b' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\b\\b1' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\b\\c\\c' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\b\\c\\c1' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\b\\c\\d\\d' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='D:\\python\\py17\\code\\py17day05\\a\\b\\c\\d\\d1' mode='r' encoding='utf-8'>
接下来打开文件后,要遍历文件里的每一行,找到关键字是否存在于改行,存在关键字的文件,打印输出,传递参数同样使用send():
1 import os 2 3 def send_none(func): 4 ''' 5 装饰器函数:初始化表达式yield的生成器函数(send一个None) 6 ''' 7 def wrapper(*args,**kwargs): 8 res = func(*args,**kwargs) 9 res.send(None) # next(res) 10 return res 11 return wrapper 12 13 @send_none 14 def get_abs_path(open_file): 15 ''' 16 获取目录下所有文件的绝对路径函数,并send给open_file 17 ''' 18 while True: 19 walk_path = yield 20 w = os.walk(walk_path) 21 for path,_,files in w: 22 for file in files: 23 file_abs_path = r'%s\%s' % (path, file) 24 # print(file_abs_path) 25 open_file.send(file_abs_path) # 将得到的绝对路径通过send方式传给open_file这个生成器函数 26 27 @send_none 28 def open_file(match_line): 29 ''' 30 获得文件句柄函数,并将文件句柄和文件路径以元祖的形式send给match_line 31 ''' 32 while True: 33 file_abs_path = yield 34 with open(file_abs_path,encoding='utf-8') as f: 35 # print(f) 36 match_line.send((file_abs_path,f)) # send可以将多个参数打包成元祖传递给生成器函数 37 38 @send_none 39 def match_line(word): 40 ''' 41 遍历通过yield获取到的文件里的每一行,找到关键字是否存在于改行,存在关键字的文件,打印输出 42 (使用标签位位,避免重复打印) 43 ''' 44 while True: 45 file_abs_path,f = yield 46 Flag = False # 定义一个标志位 47 for line in f: 48 if Flag: # 如果标志位被更改为True则跳出循环(为了防止一个文件有多行关键字,print时存在重复) 49 break 50 if word in line : 51 Flag = True # 当匹配到关键字所在的行,将标志位改为Ture 52 print(file_abs_path) 53 54 walk_path = r'D:\python\py17\code\py17day05\a' 55 g = get_abs_path(open_file(match_line('python'))) # 获得get_abs_path()生成器对象 56 g.send(walk_path)
运行结果:
D:\python\py17\code\py17day05\a\a1
D:\python\py17\code\py17day05\a\b\c\c1
D:\python\py17\code\py17day05\a\b\c\d\d1
二、面向过程编程思想
面向过程的程序设计:是一种流水线式的编程思路,是机械式的
优点:程序的结构清晰,可以把复杂的问题简单
缺点:1 扩展性差
三、匿名函数,max,min,sorted,zip,map,reduce,filter
1.匿名函数lambda:
一般来说定义一个函数
def func(x,y,z=1): return x+y+z
匿名函数为:
lambda x,y,z=1:x+y+z
匿名就是没有名字,与函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放
(*引用计数:当一个值引用计数为0时,会被回收)
2.max,min,sorted,zip,map,reduce,filter结合lambda使用:
如果要获取一个字典value的最大值所对应的key,可以使用zip
salaries={ '网管':7000, 'CTO':100000000, '技术总监':50000, '程序员':25000 } res = zip(salaries.values(),salaries.keys()) print(max(res)) 运行结果: (100000000, 'CTO')
使用lambda结合max实现:
这里key是一个函数,用来选取参与比较的元素,
salaries={ '网管':7000, 'CTO':100000000, '技术总监':50000, '程序员':25000 } print(max(salaries,key=lambda x:salaries[x])) 运行结果: CTO
min同上
sorted结合lambda进行字典排序:
salaries={ '网管':7000, 'CTO':100000000, '技术总监':50000, '程序员':25000 } print(sorted(salaries,key=lambda x:salaries[x])) # 默认从小到大 print(sorted(salaries,key=lambda x:salaries[x],reverse=True)) # 从大到小 运行结果: ['网管', '程序员', '技术总监', 'CTO'] ['CTO', '技术总监', '程序员', '网管']
map(func,iter1,iter2)用法:
对iter中的元素依次执行func(item),见执行结果组成一个List返回:
res = map(lambda x:x*x*x,range(1,11)) print(res) print(list(res)) 运行结果: <map object at 0x000000000105C1D0> [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
另外map也支持多个iter,这就要求func也支持相应数量的参数输入:
res = map(lambda x,y:x+y,range(1,11),range(1,11)) print(list(res)) 运行结果: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
reduce(function, sequence, starting_value):
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用,例如可以用来对List求和:
from functools import reduce res = reduce(lambda x,y:x+y,range(1,11)) res1 = reduce(lambda x,y:x+y,range(1,11),20) print(res) print(res1) 运行结果: 55 #(注:1+2+3+4+5+6+7+8+9+10) 75 #(注:1+2+3+4+5+6+7+8+9+10+20)
filter(function, iterable):
对iterable中的item依次执行function(item),将执行结果为True的item组成一个List/String/Tuple(取决于iterable的类型)返回:
res = filter(lambda x:x%2==0,range(10)) print(res) print(list(res)) 运行结果: <filter object at 0x000000000073C1D0> [0, 2, 4, 6, 8]
四、递归与二分法
1.递归调用:
在函数调用过程中,直接或者间接调用了函数本身
def func1(): print('from func1') func1() func1() 运行结果: RecursionError: maximum recursion depth exceeded while calling a Python object
运行结果显示并不会无限的递归,使用sys.getrecursionlimit()可以得到默认层数:
import sys print(sys.getrecursionlimit()) 运行结果: 1000 # 默认递归层数为1000
# sys.setrecursionlimit()可以设置这个值
def age(n): if n == 1: return 18 return age(n-1) + 2 print(age(5)) 运行结果: 26

需要注意的几点:
1.必须要有一个明确的结束条件
2.每次递归,问题的规模要减少
2.二分法实现查找:
l = [1, 2, 10,33,53,71,73,75,77,85,101,201,202,999,11111] def match_num(num,seq): if seq == []: # 如果列表为空表示所找的值不存在 print('not exist!') return # 终止递归 middle_num_index = len(seq)//2 print(seq,seq[middle_num_index]) if seq[middle_num_index] == num: print('find it!') elif seq[middle_num_index] < num: seq = seq[middle_num_index + 1:] match_num(num,seq) elif seq[middle_num_index] > num: seq = seq[:middle_num_index] match_num(num,seq) 运行结果: [1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75 [1, 2, 10, 33, 53, 71, 73] 33 [1, 2, 10] 2 [10] 10 not exist!
五、模块
1.什么是模块:
一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
2.为什么使用模块:
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,
3.使用模块的两种方法:
import:
#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
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下 :
#test.py import spam #只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次'from the spam.py',当然其他的顶级代码也都被执行了,只不过没有显示效果. import spam import spam import spam ''' 执行结果: from the spam.py '''
我们可以从sys.module中找到当前已经加载的模块,sys.module是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突
#测试一:money与spam.money不冲突 #test.py import spam money=10 print(spam.money) ''' 执行结果: from the spam.py 1000 '''
#测试二:read1与spam.read1不冲突 #test.py import spam def read1(): print('========') spam.read1() ''' 执行结果: from the spam.py spam->read1->money 1000 '''
#测试三:执行spam.change()操作的全局变量money仍然是spam中的 #test.py import spam money=1 spam.change() print(money) ''' 执行结果: from the spam.py 1 '''
import首次导入模块spam时会做三件事:
1.为源文件(spam模块)创建新的名称空间,在spam中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
2.以新创建的名称空间为全局名称空间,执行模块中包含的代码(正如import spam时打印了一行)
3.创建名字spam来引用该命名空间:这个名字和变量名没什么区别,都是‘第一类的’,且使用 'spam.名字' 的方式可以访问spam.py文件中定义的名字,'spam.名字' 与test.py中的名字来自两个完全不同的地方。
一些补充:
模块起别名:相当于x=1,y=x
''' 为已经导入的模块起别名的方式对编写可扩展的代码很有用,假设有两个模块xmlreader.py和csvreader.py,它们都定义了函数read_data(filename):用来从文件中读取一些数据,但采用不同的输入格式。可以编写代码来选择性地挑选读取模块,例如: ''' if file_format == 'xml': import xmlreader as reader elif file_format == 'csv': import csvreader as reader data=reader.read_date(filename)
from ... import...:
对比import spam,会将源文件的名称空间'spam'带到当前名称空间中,使用时必须是spam.名字的方式而from 语句相当于import,也会创建新的名称空间,但是将spam中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了
#测试一:导入的函数read1,执行时仍然回到spam.py中寻找全局变量money #test.py from spam import read1 money=10 read1() ''' 执行结果: from the spam.py spam->read1->money 1000 '''
#测试二:导入的函数read2,执行时需要调用read1(),仍然回到spam.py中找read1() #test.py from spam import read2 def read1(): print('==========') read2() ''' 执行结果: from the spam.py spam->read2 calling read spam->read1->money 1000 '''
如果当前有重名read1或者read2,那么会有覆盖效果
#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了 #test.py from spam import read1 def read1(): print('==========') read1() ''' 执行结果: from the spam.py ========== '''
需要特别强调的一点是:python中的变量赋值不是一种存储操作,而只是一种绑定关系,如下:
from spam import money,read1 money=100 #将当前位置的名字money绑定到了100 print(money) #打印当前的名字 read1() #读取spam.py中的名字money,仍然为1000 ''' from the spam.py 100 spam->read1->money 1000 '''
同样也支持as和导入多行:
from spam import read1 as read from spam import read1,read2,money
关于 from spam import *: 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。
from spam import * #将模块spam中所有的名字都导入到当前名称空间 print(money) print(read1) print(read2) print(change) ''' 执行结果: from the spam.py 1000 <function read1 at 0x1012e8158> <function read2 at 0x1012e81e0> <function change at 0x1012e8268> '''
可以使用__all__来控制*(用来发布新版本)在spam.py中新增一行:
__all__=['money','read1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字
3.__name__:
__name__为全局变量
当模块当做脚本执行时:__name__ = __main__
print('from spam.py') money = 1000 def read1(): print('spam->read1->money',money) def read2(): print('spam->read2->money',money) read1() def change(): global money money = 0 print(__name__) ''' 运行结果: from spam.py __main__ '''
当模块导入时:__name__ = 模块名
import spam ''' 运行结果: from spam.py spam '''
当我们导入模块时,是为了使用模块里的功能,只需要加载模块里名字的定义,而不是立即执行其中的功能,而作为模块的开发者,有时候要直接使用模块做调试,这种需求我们可以通过判断__name__的值来实现:
money = 1000 def read1(): print('spam->read1->money',money) def read2(): print('spam->read2->money',money) read1() def change(): global money money = 0 if __name__ == '__main__': print('from spam.py') print('This is %s'%__name__) ''' 运行结果 from spam.py This is __main__ '''
4. 模块搜索路径:
我们执行以下程序:
运行后,执行完毕第一个import spam后,将目录里的spam.py删除,下一次依然可以正常导入spam模块。
import time import spam time.sleep(15) import spam 运行结果: from spam.py
再执行以下程序:
目录下新建一个sys.py文件,如下:
print('my sys.py')
之后在同级目录下运行test.py:
import sys print(sys) 运行结果: <module 'sys' (built-in)>
运行结果并没有打印之前由我们自己创建的同级目录下的sys.py。
以上两个例子可以判断,导入模块的查找路径方式,先在内存中查找是否存在之前导入的模块,然后再去寻找内置模块(如sys,os等),最后在依次查找sys.path所提供的路径(sys.path为一个列表,列表的第一个元素即当前路径,优先查找当前路径)
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
在第三步查找sys.path时,对于同级目录下的模块,我们可以直接使用import导入,对于import而言,只会查找当前test.py所在目录的相对路径下的内容,如果被导入模块与test.py不在同一级目录(在test.py的上级目录),我们只能将其加入sys.path下才可以使用模块。
同理from..import也一样,只会查找当前test.py所在目录的相对路径下的内容。
5.补充:
关于.pyc文件:
为了提高模块的加载速度,Python缓存编译的版本,每个模块在__pycache__目录的以module.version.pyc的形式命名,通常包含了python的版本号,如在CPython版本3.3,关于spam.py的编译版本将被缓存成__pycache__/spam.cpython-33.pyc,这种命名约定允许不同的版本,不同版本的Python编写模块共存。
六、包
有以下目录结构:
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
1 #文件内容 2 3 #policy.py 4 def get(): 5 print('from policy.py') 6 7 #versions.py 8 def create_resource(conf): 9 print('from version.py: ',conf) 10 11 #manage.py 12 def main(): 13 print('from manage.py') 14 15 #models.py 16 def register_models(engine): 17 print('from models.py: ',engine)
# glance下的 __init__.py : print('from glance __init__') # glance同级创建一个test.py: import glance 运行结果test.py: from glance __init__
我们想在glance同级的test.py里导入glance,结果运行了glance下的__init__.py,可见导入一个包就相当于导入(运行)了__init__.py,同时也产生了一个该__init__.py文件的名称空间。
# api下的 __init__.py : print('from api __init__')
import glance glance.api.policy.get() 运行结果: #由于该__init__.py下无api名字,所以报错 from glance __init__ Traceback (most recent call last): File "D:/python/py17/code/py17day05/test.py", line 50, in <module> glance.api.policy.get() AttributeError: module 'glance' has no attribute 'api'
要想导入执行policy,可以通过如下方式:
import glance.api.policy glance.api.policy.get() 运行结果: from glance __init__
from api __init__ # 导入包就相当于导入其下的__init__.py,所以打印了这行
from policy.py
import glance.api.policy.get glance.api.policy.get() 运行结果: Traceback (most recent call last): File "D:/python/py17/code/py17day05/test.py", line 47, in <module> import glance.api.policy.get ImportError: No module named 'glance.api.policy.get'; 'glance.api.policy' is not a package
#导入时,. 的左边必须是包名
总结如下:
1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2. 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
#glance下的__init__.py: print('from glance __init__') #api下__init__.py: print('from api __init__') import policy.py #glance同级的test.py from glance import api api.policy.get() 运行test.py结果: ImportError: No module named 'policy'
我们想通过glance下的test.py导入policy,首先在api的__init__.py下导入了policy.py,结果报错,将导入方式换成如下形式:
#api下的__init__.py: print('from api __init__') from glance.api import policy 运行test.py结果: from glance __init__ from api __init__ from policy.py
我们可以直接打印这两个文件下的sys.path来对比一下,发现结果是一样的:
# glance同级的test.py: import sys from glance import api print('test.py sys.path:%s'%sys.path) api.policy.get() # api下的__init__.py: import sys print('from api __init__.py') print('api init sys.path:%s'%sys.path) from glance.api import policy #运行test.py结果: from glance __init__.py from api __init__.py api init sys.path:['D:\\python\\python3\\s17day05code', 'D:\\python\\python3', 'D:\\python\\python\\monitor_test', 'C:\\Windows\\system32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages', 'C:\\Python34\\lib\\site-packages\\win32', 'C:\\Python34\\lib\\site-packages\\win32\\lib', 'C:\\Python34\\lib\\site-packages\\Pythonwin'] test.py sys.path:['D:\\python\\python3\\s17day05code', 'D:\\python\\python3', 'D:\\python\\python\\monitor_test', 'C:\\Windows\\system32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages', 'C:\\Python34\\lib\\site-packages\\win32', 'C:\\Python34\\lib\\site-packages\\win32\\lib', 'C:\\Python34\\lib\\site-packages\\Pythonwin'] from policy.py
可见导入模块判定路径(sys.path)的标准是以test.py(被执行文件)所在的路径(sys.path)来判断的:
因为执行文件时,sys.path已经加载到内存中了,内存中已经存在被执行文件的sys.path,执行文件过程中存在导入其它模块的过程,导入其它模块也要参考sys.path,因为内存中已经存在被执行文件的sys.path,所以导入的模块也以这个sys.path为准。
包的绝对导入:
如果policy想调用同级目录下的versions改如何导入:
#api下的policy.py: def get(): print('from policy.py') import versions versions.create_resource('conf') 运行policy.py结果: from version.py: conf
可见运行policy.py直接导入同级的versions是没问题的,但是policy.py一般来说是作为模块被人调用的,如果在glance的test.py执行则会报错:
# glance同级的test.py: from glance.pai import policy 运行test.py结果: ImportError: No module named 'versions'
如同之前的例子,执行文件是谁,路径就以谁的为准,所以:
#api下的policy: def get(): print('from policy.py') from glance.api import versions versions.create_resource('conf') #glance同级的test.py: from glance.api import policy 运行test.py结果: from glance __init__ from api __init__ from version.py: conf
这是如果单独运行policy.py则会报错,因为它在本身的路径下找不到glance,如果即想单独运行,又能够被调用,我们可以加一个if判断:
#api下的policy:
def get(): print('from policy.py') if __name__ == '__main__': # 当脚本运行时 import versions versions.create_resource('a.conf') else: # 被导入运行时 from glance.api import versions versions.create_resource('a.conf') 运行policy.py结果: from version.py: a.conf 运行test.py结果: from glance __init__ from api __init__ from version.py: a.conf
同样,policy也可以调用其它子包的模块,如:
#api下的policy.py: def get(): print('from policy.py') from glance.cmd import manage manage.main() # glance同级下的test.py: from glance.api import policy 运行glance同级下的test.py结果: from glance __init__ from api __init__ from manage.py
包的相对导入:
如果glance包名如果变更的话,调用其它子包模块都要更改?我们可以用相对导入的方式来避免,如:
#api下的policy.py: def get(): print('from policy.py') from ..cmd import manage # 相对导入 manage.main() # glance同级下的test.py: from glance.api import policy 运行glance同级下的test.py结果: from glance __init__ from api __init__ from manage.py
补充:
关于*和__all__的使用:
# api下的__init__.py: print('from api __init__') __all__=['policy','versions'] # 指定*导入的模块 #api下的policy.py: def get(): print('from policy.py') #api下的versions.py : def create_resource(conf): print('from version.py: ',conf) # glance 同级的test.py: from glance.api import * policy.get() versions.create_resource('conf') 运行test.py结果: from glance __init__ from api __init__ from policy.py from version.py: conf
注:__all__只能搭配*使用:
# glance 同级下的test.py: from glance import api policy.get() versions.create_resource('conf') # 运行test.py结果: from glance __init__ from api __init__ NameError: name 'policy' is not defined
七、正则表达式(re模块)

浙公网安备 33010602011771号