第五章:Python初识协程+模块和包+logging模块的使用
导航:
协程函数
面向过程编程
模块
包
logging模块的使用
一、协程函数
协程好抽象的概念:=》让我们了解什么是协程?
我们把一个线程中的一个个函数叫做子程序,那么子程序在执行过程中可以中断去执行别的子程序;别的子程序也可以中断回来继续执行之前的子程序,这就是协程。=》也就是说同一线程下的一段代码<1>执行着执行着就可以中断,然后跳去执行另一段代码,当再次回来执行代码块<1>的时候,接着从之前中断的地方开始执行。
比较专业的理解是:
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的优缺点:
>协程的优点:
(1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
(2)无需原子操作锁定及同步的开销
(3)方便切换控制流,简化编程模型
(4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
>协程的缺点:
(1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
(2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
Python中如何实现协程
#! /usr/bin/env python # -*- coding:utf-8 -*- # Author: "ShengLeQi"# Date: 2017/7/30 def init(func): def wrapper(*args,**kwargs): fun_g=func(*args,**kwargs) next(fun_g) return fun_g return wrapper @init def consumer(name): food_list=[] while True: food=yield food_list food_list.append(food) print('消费了{}'.format(food)) # print(food_list) def producer(): sheng_g = consumer('ShengLeQi') while True: food=input('>:').strip() if not food:continue print(sheng_g.send(food)) producer()
二、面向过程
面向过程:核心是过程二字,过程即解决问题的步骤,基于面向过程去设计程序就像是在设计
# 一条工业流水线,是一种机械式的思维方式
#优点:程序结构清晰,可以把复杂的问题简单化,流程化
#缺点:可扩展性差,一条流线只是用来解决一个问题
#应用场景:linux内核,git,httpd,shell脚本
模仿linux命令:#grep -rl 'error' /dir/
#! /usr/bin/env python # -*- coding:utf-8 -*- # Author: "ShengLeQi"# Date: 2017/7/30 # #grep -rl 'error' /dir/ #迭代器初始化的装饰器 作用:不需要迭代器每次都做一次初始化,可以直接使用 import os def init(func): def wrapper(*args,**kwargs): fun_g=func(*args,**kwargs) next(fun_g) return fun_g return wrapper #第一阶段:找到所有文件的绝对路径 @init def search(target): while True: file_path=yield g=os.walk(file_path) #内置函数walk()返回三个值:dirpath, dirnames, filenames 这里使用2个。 for dirpath,_,filenames in g: for file in filenames: abspath = r'%s\%s' % (dirpath, file) target.send(abspath) # print(abspath) # #第二阶段:打开文件 @init def opener(target): while True: abspath=yield with open(abspath,'rb') as read_f: #因为windows是gbk格式,所以使用二进制形式打开 target.send((abspath,read_f)) #连个小括号是里面一个是指的一个元组(里面2个值) #第三阶段:循环读出每一行内容 @init def cat_f(target): while True: abspath,f = yield for line in f: res=target.send((abspath,line)) if res: break #第四阶段:过滤 @init def grep(patten,target): tag = False #tag作用当patten in line所在的行的时候tag返回True,不需要再当前文件往下读内容,直接读取下一个文件。 while True: abspath,line=yield tag tag = False if patten in line: target.send(abspath) tag=True #第五阶段:打印该行属于的文件名 @init def printer(): while True: abs_path=yield print(abs_path) g = search(opener(cat_f(grep('path'.encode('utf-8'),printer())))) g.send(r'C:\Users\think\PycharmProjects\Python18\day05')
三、模块
一、首先先了解什么是模块?
常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
但其实import加载的模块分为四个通用类别:
1 使用python编写的代码(.py文件)
2 已被编译为共享库或DLL的C或C++扩展
3 包好一组模块的包
4 使用C编写并链接到python解释器的内置模块
二、为什么需要使用模块:
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,
三、如何使用模块()
1、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 spam
导入模块干了哪些事:
1 执行源文件
2 以一个源文件的全局名称空间
3 在当前位置拿到一个模块名,指向2创建的名称空间
执行文件:sys.path是执行文件的路径。
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下
#test.py import spam # 只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次'from the spam.py',当然其他的顶级代码也都被执行了,只不过没有显示效果 import spam import spam
注意:
1、我们可以从sys.module中找到当前已经加载的模块,sys.module是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
如下代码即可:
import sys sys_m=sys.modules # print(sys_m) for i in sys_m: print(i,sys_m[i])
2、每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突。
总结:首次导入模块spam时会做三件事:
1.为源文件(spam模块)创建新的名称空间,在spam中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
2.在新创建的命名空间中执行模块中包含的代码。
提示:导入模块时到底执行了什么? In fact function definitions are also ‘statements’ that are ‘executed’; the execution of a module-level function definition enters the function name in the module’s global symbol table. 4 事实上函数定义也是“被执行”的语句,模块级别函数定义的执行将函数名放入模块全局名称空间表,用globals()可以查看
3.创建名字spam来引用该命名空间
1 这个名字和变量名没什么区别,都是‘第一类的’,且使用spam.名字的方式可以访问spam.py文件中定义的名字,spam.名字与test.py中的名字来自两个完全不同的地方。
别名功能=》为模块起别名:
import spam spam.read1() 两者相同: import spam as s s.read1()
2、from...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
import spam #调用spam问价里面的read1 spam.read1() import spam as s #取别名的方法 s.read1() #调用spam问价里面的read1 from spam import money,read1,read2,change #导入spam里面的money,read1,read2,change名称, money=0 print(money) #因为 执行文件已经定义money,所有应该输出执行文件定义的值 print(read1) #打印出函数的read1的内存位置 read1() #直接调用spam的read1函数 def read1():print('ok') read1() #因为 执行文件已经定义read1,所有执行执行文件的函数read1 read2() #还是调用spam的函数read2 money=10 change() print(money) from spam import money as m print(m) ''' #执行的结果: from the spam.py spam->read1->money 1000 spam->read1->money 1000 0 <function read1 at 0x0000000001E9BF28> spam->read1->money 1000 ok spam->read2 calling read spam->read1->money 1000 10 0 '''
用法一:
from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。
# from-import用法.py from spam import * print(money) print(read1) ''' from the spam.py 1000 <function read1 at 0x000000000290BF28> '''
注意:当模块spam中的变量前面加了下划线‘_’,使用from spam import *导入时,就无效(不起作用),‘*’只是对前面没有下划线开头的变量名起作用。
用法二:
使用__all__=['money','x'] #对from spam import * 有用
#print('from the spam.py') __all__=['money','x'] #对from spam import * 有用 # _money=1000 #对from spam import * 有用 money=0 x=1 def read1(): print('spam->read1->money',money) def read2(): print('spam->read2 calling read') read1() def change(): global money money=0
# from-import用法.py from spam import * print(money) read1() change() ''' from the spam.py 0 spam->read1->money 0 from->spam->chage '''
总结:
#优点:使用源文件内的名字时无需加前缀,使用方便
#缺点:容易与当前文件的名称空间内的名字混淆
模块搜索路径
python解释器在启动时会自动加载一些模块,可以使用sys.modules查看
在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
模块只在第一次导入时才会执行,之后的导入都是直接引用内存已经存在的结果
import sys print('spam' in sys.modules) #存放的是已经加载到内的模块 import spam print('spam' in sys.modules) # import spam # import spam
注意:
1、当import spam第一次导入的时候已经存到内存之中,后面无论导入多少次,都不会导入。
2、自定义模块名字不能和内置模块一样。
有模块可以重新导入(仅仅是测试的时候是使用):
import importlib importlib.reload(spam)
使用importlib这个可以重新导入模块(只能测试环境使用)不能使用到生产环境中。(一般不用!)
四、包:
包:特殊形式的模块,也是被导入使用,就等价于=>创建一个目录和__init__.py
Packages are a way of structuring Python’s module namespace by using “dotted module names”
包是一种通过使用‘.模块名’来组织python模块名称空间的方式。
1、 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2、 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3、 import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件.
强调:
1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
导入一个文件夹:给文件夹定制一个文件就叫__init__.py。实际上导入的是__init__.py。
绝对导入和相对导入
例1:
目录结构 ├── glance │ ├── api │ │ ├── __init__.py │ │ ├── policy.py │ │ └── versions.py │ ├── cmd │ │ ├── __init__.py │ │ └── manage.py │ ├── db │ │ ├── __init__.py │ │ └── models.py │ └── __init__.py # glance下的__init__ └── run.py
#文件内容 #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)
=》绝对导入
让run.py访问get、create_resource、main、register_models函数
# glance下的__init__内容 from glance.api.policy import get from glance.api.versions import create_resource from glance.cmd.manage import main from glance.db.models import register_models
#run.py的内容 import glance glance.get() glance.create_resource('ShengLeQi') glance.main() glance.register_models('--')
=》相对导入
让run.py访问get、create_resource、main、register_models函数
# glance下的__init__内容 from .api.policy import get from .api.versions import create_resource from .cmd.manage import main from .db.models import register_models
#run.py的内容 import glance glance.get() glance.create_resource('ShengLeQi') glance.main() glance.register_models('--')
以下是不同情况的导入方式:
1、当glance变成了glance_v1
#run.py的内容 import glance_v1 glance_v1.get() glance_v1.create_resource('ShengLeQi') glance_v1.main() glance_v1.register_models('--')
2、当包内部相互访问的时候:policy.py访问models.py时候如何导入:
#policy.py文件内容 #绝对导入 # from glance.db.models import register_models #相对导入 from ..db.models import register_models def get(): print('from glance->api->policy.py') register_models('SHENG')
3、当glance整理包都移到了其他目录下面。
import sys sys.path.append(r'C:\Users\think\PycharmProjects\Python18\day05\练习_1\aaa') #append追加其他目录的到sys.path路径下面 import glance glance.get()
例2:
目录结构: ├── aaa │ ├── __init__.py │ ├── m1.py │ └── m2.py └── run.py
#m1.py文件内容 def fun1(): print('aaa=>m1->fun1') #m2.py文件内容 def fun2(): print('aaa=>m2->fun2')
练习:
1、想要run.py访问m1.py里面的fun和m2.py里面的fun:
需要在如何导入包:
#_init__.py文件内容 print( '==>aabb') from aaa import m1 #关键
#run.py内容 #_init__.py文件内容 print( '==>aabb') from aaa import m1 #关键
2、想要run.py访问m1.py、m2.py、m3.py、m4.py
目录结构: ├── aaa │ ├── bbb │ │ ├── __init__.py #=》这个是bbb下面的__init__.py │ │ ├── m3.py │ │ └── m4.py │ ├── __init__.py #=》这个是aaa下面的__init__.py │ ├── m1.py │ └── m2.py └── run.py
#m1.py文件内容 def fun1(): print('aaa=>m1->fun1') #m2.py文件内容 def fun2(): print('aaa=>m2->fun2') #m3py文件内容 def func3(): print('aaa->bbb->m3->func3') #m4.py文件内容 def func4(): print('aaa->bbb->m4->func4')
a、相对麻烦的访问方式访问,使用路径比较冗长
# aaa下面的__init__.py from aaa import m1 from aaa import m2 from aaa.bbb import m3 from aaa.bbb import m4 #bbb下面的__init__.py from aaa.bbb import m3 from aaa.bbb import m4 #run.py文件访问方式: import aaa aaa.m1.fun1() aaa.m2.fun2() aaa.bbb.m3.func3() aaa.bbb.m4.func4()
b、相对比较简短的访问函数的方式,简单明了
# aaa下面的__init__.py from aaa.m1 import fun1 from aaa.m2 import fun2 from aaa.bbb.m3 import func3 from aaa.bbb.m4 import func4 #bbb下面的__init__.py from aaa.bbb import m3 from aaa.bbb import m4 #run.py文件访问方式: import aaa aaa.fun1() aaa.fun2() aaa.func3() aaa.func4()
总结:这样访问的好处:
1、方便开发者的使用,完全不需要了解里面的包的目录结构
2、模块的开发者给使用者提供的便利
3、维护方便、使用方便
五、 logging模块的使用
""" logging配置 """ import os import logging.config # 定义三种日志输出格式 开始 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' # 定义日志输出格式 结束 logfile_dir = r'%s\log' %os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # log文件的目录 logfile_name = 'all.log' # log文件名 # 如果不存在定义的日志目录就创建一个 if not os.path.isdir(logfile_dir): os.mkdir(logfile_dir) # log文件的全路径 logfile_path = os.path.join(logfile_dir, logfile_name) # log配置字典 LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { # 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'handlers': ['default'], # 使用输入all的文件中 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, } def load_my_logging_cfg(): logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置 logger = logging.getLogger(__name__) # 生成一个log实例 # logger.info('start load_my_logging_cfg !') # 记录该文件的运行状态 if __name__ == '__main__': load_my_logging_cfg()
导入直接可以使用:
import os, logging # import conf from conf import my_log_settings logger = logging.getLogger(__name__) # 生成logger实例 my_log_settings.load_my_logging_cfg() logger.debug('from shopping -- my_log') #直接使用

浙公网安备 33010602011771号