一、协程函数
1 协程函数的定义
协程函数就是使用了yield表达式形成的生成器
实例:
def eater(name): print('%s eat food' %name) while True: food = yield print('done') g = eater('Yim') print(g) #执行结果: <generator object eater at 0x000000000227FEB8> #证明g现在是一个生成器函数
2 协程函数的赋值
第一阶段:next()初始化函数,让函数暂停在yield位置
第二阶段:send()给yield传值
next()和send()都是让函数在上次暂停的位置继续执行。next是让函数初始化,send会给yield赋值并触发下一次代码执行
实例:
def eater(name): print('%s start to eat food' %name) while True: food = yield print('%s eat %s' %(name,food)) e = eater('Yim') next(e) #初始化,等同于e.send(None) e.send('米饭') #给yield传值 e.send('大饼') #执行结果 Yim start to eat food Yim eat 米饭 Yim eat 大饼
def eater(name): print('%s start to eat food' %name) food_list = [] while True: food = yield food_list food_list.append(food) print('%s eat %s' %(name,food)) e = eater('Yim') next(e) #初始化 print(e.send('米饭')) #1、给yield传值 2、继续往下执行,直到再次碰到yield,然后暂停并且把yield后的返回值当做本次调用的返回值 print(e.send('大饼')) #执行结果 Yim start to eat food Yim eat 米饭 ['米饭'] Yim eat 大饼 ['米饭', '大饼']
3 用装饰器初始化协程函数
def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @init def eater(name): print('%s start to eat food' %name) food_list = [] while True: food = yield food_list food_list.append(food) print('%s eat %s' %(name,food)) e = eater('Yim') print(e.send('米饭')) print(e.send('大饼'))
二、 面向过程编程
面向过程的编程思想:流水线式的编程思想,在设计程序时,需要把整个流程设计出来
优点:
体系结构更清晰
简化程序的复杂度
缺点:
可扩展性差
应用场景:
Linux内核、git、httpd、shell脚本
实例:
# grep -rl 'error' /dir import os #初始化装饰器 def init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper #1、找到所有文件的绝对路径 --os.walk @init def search(target): while True: filepath=yield g=os.walk(filepath) for pardir,_,files in g: for file in files: #对最后一个元素进行遍历,这些都是文件 abspath=r'%s\%s' %(pardir,file) target.send(abspath) #2、打开文件 --open @init def opener(target): while True: abspath=yield # 接收search传递的路径 with open(abspath,'rb') as f: target.send((abspath,f)) # send多个用元组的方式,为了把文件的路径传递下去 #3、循环读取每一行内容 --for line in f @init def cat(target): while True: abspath,f=yield #(abspath,f) for line in f: res=target.send((abspath,line)) # 同时传递文件路径和每一行的内容 if res:break #4、过滤关键字 --if 'error' in line @init def grep(pattern,target): # # patter是过滤的参数 tag=False #为了去掉重复的文件路径,因为一个文件中存在多个error关键字 while True: abspath,line=yield tag tag=False if pattern in line: target.send(abspath) # 传递有相应内容的文件路径 tag=True #5、打印该行属于的文件名 @init def printer(): while True: abspath=yield print(abspath) g = search(opener(cat(grep('os'.encode('utf-8'), printer())))) g.send(r'F:\Python\Code')
三、函数的递归调用
在调用一个函数的过程中,直接或间接的调用了函数本身,就叫递归调用
执行有两个阶段:递推和回溯
递归的效率低,需要在进入下一次递归时保留当前的状态,解决方法是尾递归,即在函数的最后一步(而非最后一行)调用自己,但是python又没有尾递归,且对递归层级做了限制
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
#直接 def func(): print('from func') func() func() #间接 def foo(): print('from foo') bar() def bar(): print('from bar') foo() foo()
# salary(5)=salary(4)+300 # salary(4)=salary(3)+300 # salary(3)=salary(2)+300 # salary(2)=salary(1)+300 # salary(1)=100 # # salary(n)=salary(n-1)+300 n>1 # salary(1) =100 n=1 def salary(n): if n == 1: return 100 return salary(n-1)+300 print(salary(5))
四、模块
如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了。为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块。
模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是Python标准库的方法
1 import语句
import加载的模块分为四个通用类别:
- 使用Python编写的代码(.py文件)
- 已被编译为共享库或DLL的C或C++扩展
- 包好一组模块的包
- 使用C编写并链接到Python解释器的内置模块
import语法:
import module1[, module2[,... moduleN]
当解释器遇到import语句,如果模块在当前的搜索路径就会被导入,搜索路径是一个解释器会先进行搜索的所有目录的列表。如想要导入模块 support,需要把命令放在脚本的顶端:
support.py 文件代码为:
def print_func( par ): print ("Hello : ", par) return
test.py引入support模块:
import support #导入模块 support.print_func('Yim') #现在可以调用模块里包含的函数了 #执行结果 Hello : Yim
还可以给模块起一个别名:
import support as s1 s1.print_func('Yim')
一个模块只会被导入一次,不管你执行了多少次import。这样可以防止导入模块被一遍又一遍地执行(可以使用sys.modules查看验证):
import support import support import support support.print_func('Yim') #执行结果: Hello : Yim
2 from…import*语句
优点:使用源文件内的名字时无需加前缀,使用方便
缺点:容易与当前文件的名称空间内的名字混淆
把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
from modname import * #不推荐使用
下面只导入support模块里面的print_func函数:
from support import print_func #可以用逗号分隔,写上多个函数名 print_func('Yim') #直接执行print_func函数
也可以起别名:
from support import print_func as p1 p1('Yim')
可以使用__all__来控制*,修改support.py文件:
__all__ = [print_func] #可以在列表内添加多个函数名 def print_func( par ): print ("Hello : ", par) return
test.py文件如下:
from support import * print_func('Yim') #执行结果报错,NameError: name 'print_func' is not defined
3 模块的搜索路径
搜索路径是由一系列目录名组成的,Python解释器就依次从这些目录中去寻找所引入的模块
搜索路径是在Python编译或安装的时候确定的,安装新的库应该也会修改。搜索路径被存储在sys模块中的path变量
模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名
import sys print(sys.path) #执行结果: ['F:\\Python\\Code\\模块', 'F:\\Python', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']
在脚本中修改sys.path,引入一些不在搜索路径中的模块
import sys print(sys.path) sys.path.append('F:\\Python\\Code\\不常用模块') # sys.path.insert(0,'F:\\Python\\Code\\不常用模块') #也可以使用insert,排在前的目录会优先被搜索 print(sys.path) #执行结果 ['F:\\Python\\Code\\模块', 'F:\\Python\\2017-18s', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages'] ['F:\\Python\\Code\\模块', 'F:\\Python\\2017-18s', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages', 'F:\\Python\\Code\\不常用模块']
以下是官方文档:
#官网链接:https://docs.python.org/3/tutorial/modules.html#the-module-search-path
搜索路径:
当一个命名为spam的模块被导入时
解释器首先会从内建模块中寻找该名字
找不到,则去sys.path中找该名字
sys.path从以下位置初始化
1 执行文件所在的当前目录
2 PYTHONPATH(包含一系列目录名,与shell变量PATH语法一样)
3 依赖安装时默认指定的
注意:在支持软连接的文件系统中,执行脚本所在的目录是在软连接之后被计算的,换句话说,包含软连接的目录不会被添加到模块的搜索路径中
在初始化后,我们也可以在python程序中修改sys.path,执行文件所在的路径默认是sys.path的第一个目录,在所有标准库路径的前面。这意味着,当前目录是优先于标准库目录的,需要强调的是:我们自定义的模块名不要跟python标准库的模块名重复,除非你是故意的
4 __name__属性
一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。
if __name__ == '__main__': #文件当做脚本运行时,__name__ 等于'__main__' print('程序自身在运行') else: print('我来自另一模块') #执行结果 程序自身在运行
#交互式,导入模块 >>> import support 我来自另一模块
说明: 每个模块都有一个__name__属性,当其值是'__main__'时,表明该模块自身在运行,否则是被引入。
常用实例:
def func1(): print('from func1') def func2(): print('from func2') if __name__ == '__main__': func1() func2()
五、包
包是一种管理 Python 模块命名空间的形式,采用"点模块名称",比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B
无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,这都是关于包才有的导入语法
包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
强调:
- 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
- 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
有一个包结构如下:
glance/ #顶层包 ├── __init__.py #初始化 glance 包 ├── api │ ├── __init__.py │ ├── policy.py
导入包里的子模块:
import glance.api.policy
必须使用全名去访问:
glance.api.policy.func()
还有一种导入模块的方法是:
from glance.api import policy #从模块中导入一个指定的部分到当前命名空间中
它不需要那些冗长的前缀,所以可以这样使用:
policy.func()
还有一种就是直接导入一个函数或者变量:
from glance.api.policy import func
可以直接使用func()函数:
func()
1 导入包实例
包结构如下:
glance/ #顶层包 run.py # 执行文件 ├── __init__.py #初始化 glance 包 ├── api │ ├── __init__.py │ ├── policy.py
导入包:
#1、run.py用全名访问func(),内容如下: import glance glance.api.policy.func() # glance/__init__.py如下: import glance.api #也可以用from glance import api # glance/api/__init__.py如下: import glance.api.policy #也可以用from glance.api import policy #2、run.py直接访问func(),内容如下: import glance glance.func() # glance/__init__.py如下: from glance.api.policy import func #不能用import glance.api.policy.func # glance/api/__init__.py可以不用写
当使用from package import item这种形式的时候,对应的item既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。
import语法会首先把item当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,恭喜,一个:exc:ImportError 异常被抛出了。
反之,如果使用形如import item.subitem.subsubitem这种导入形式,除了最后一项,都必须是包,而最后一项则可以是模块或者是包,但是不可以是类,函数或者变量的名字。
注意事项:
- 关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则
- 对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)
- 对比import item 和from item import name的应用场景:如果我们想直接使用name那必须使用后者。
2 绝对导入和相对导入
以上面的例子是绝对导入的方式
绝对导入:以glance作为起始
相对导入:用.或者..的方式作为起始(只能在一个包中使用,不能用于不同目录内)
实例:
run.py直接访问func(),内容如下: import glance glance.func() # glance/__init__.py如下: from .api.policy import func #1、这样的好处是:就算包名glance被更改也不会有影响,只需改run.py内的import …2、..表示上级目录,可以导入上级目录中的其他模块
特别需要注意的是:
- 可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入
- 包的相对导入只能用from的形式
六、软件开发规范
bin:存放执行脚本
conf:存放配置文件
core:存放核心逻辑
db:存放数据库文件
lib:存放自定义的模块与包
log:存放日志
start.py内容:
import os import sys # print(os.path.abspath(__file__)) #获取当前文件的绝对路径,F:\soft\bin\test.py # print(os.path.dirname(os.path.abspath(__file__))) #获取当前文件所在的目录,F:\soft\bin # print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #获取当前文件所在的目录的上级目录,F:\soft BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) #将soft这个包的路径加到环境变量 from core import core …… #导入其他模块 if if __name__ == '__main__': …… #执行