python第五天
1 协程函数
1.1 协程函数理解
协程函数就是使用了yield表达式形式的生成器
def eater(name):
print("%s eat food" %name)
while True:
food = yield
print("done")
g = eater("gangdan")
print(g)
结果:
generator object eater at 0x00000000028DC048
这里就证明了g现在就是生成器函数
1. 2 协程函数赋值过程
用的是yield的表达式形式
要先运行next(),让函数初始化并停在yield,相当于初始化函数,然后再send() ,send会给yield传一个值
** next()和send() 都是让函数在上次暂停的位置继续运行,
next是让函数初始化
send在触发下一次代码的执行时,会给yield赋值
**
def eater(name): print('%s start to eat food' %name) food_list=[] while True: food=yield food_list print('%s get %s ,to start eat' %(name,food)) food_list.append(food) e=eater('钢蛋') # wrapper('') # print(e) print(next(e)) # 现在是运行函数,让函数初始化 print(e.send('包子')) # print(e.send('韭菜馅包子')) print(e.send('大蒜包子'))
这里的关键是:
要先运行next()函数
用装饰器函数把next()函数先运行一次:
def start(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) # next() next(res) # 这是关键 return res return wrapper @start # e = start(e) def eater(name): print('%s start to eat food' %name) food_list=[] while True: food=yield food_list print('%s get %s ,to start eat' %(name,food)) food_list.append(food) e=eater('钢蛋') # wrapper('钢蛋') print(e.send('包子')) print(e.send('韭菜馅包子')) print(e.send('大蒜包子'))
1.3 协程函数使用装饰器初始化
这里主要是为了防止忘记初始化next操作,在装饰器中添加
def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) # 在这里执行next return res return wrapper @init # eater=init(rater) def eater(name): print("%s eat food" %name) food_list=[] while True: food = yield food_list print("%s star to eat %s" %(name,food)) food_list.append(food) print("done") g = eater("gangdan") # 这里就不需要next print(g.send("1")) print(g.send("2")) print(g.send("3")) print(g.send("4"))
1.4 协程函数的应用
过滤一个文件下的子文件、字文件夹的内容中的相应的内容,在Linux中的命令就是 grep -rl 'python' /etc
使用了Python的包os 里面的walk(),能够把参数中的路径下的文件夹打开并返回一个元组
>>> import os >>> os.walk('D:\test') generator object walk at 0x0000000002ADB3B8 >>> os.walk('D:\\test') # 以后使用这种路径方式,win下 >>> os.walk(r'D:\test') # 使用r 是让字符串中的符号没有特殊意义,针对的是转义
>>> g=os.walk('D:\\test') >>> next(g) ('D:\\test', ['a', 'b'], ['test.txt'])
返回的是一个元组,第一个元素是文件的路径,第二个是文件夹,第三个是该路径下的文件
1.5 程序流程
- 找文件路径 --os.walk
- 然后打开文件 --open
- 读取文件的每一行内容 -- for line in f
- 过滤一行内容中是否有Python if 'python' in line
- 打印包含Python的文件路径
程序是从上往下执行的,1产生的路径作为参数给2,2产生的给3...
上面产生的结果是下面的输入参数
1 找文件的路径
g是一个生成器,就能够用next()执行,每次next就是运行一次,这里的运行结果是依次打开文件的路径
用循环打开:
import os # def serach(): g = os.walk('D:\\test') for i in g: print(i)
将查询出来的文件和路径进行拼接,拼接成绝对路径
import os # def serach(): g = os.walk('D:\\test') for i in g: # print(i) for j in i[-1]: # 对最后一个元素进行遍历,这些都是文件 file_path= '%s\\%s' %(i[0],j) print(file_path)
这样就把文件的所有的绝对路径找出来了
用函数实现:
def search(): while True: file_name = yield # 通过white循环能够循环接收 g = os.walk(file_name) # 这里就换成了参数 for i in g: for j in i[-1]: # 对最后一个元素进行遍历,这些都是文件 file_path= '%s\\%s' %(i[0],j) print(file_path) g=search() # g就是生成器函数 next(g) # 初始化 g.send('D:\\test') # 通过send传递的是路径
2 然后打开文件
写程序中,在这里遇到的问题是 with open(file_path) as f: AttributeError: enter,不明白是为什么,然后想到open可能是系统已经用了的,所以修改名字后执行成功。
@init # 初始化生成器 def opener(target): "打开文件,操作句柄" while True: file_path=yield # 接收search传递的路径 with open(file_path) as f: target.send((file_path,f)) # send多个用元组的方式,为了把文件的路径传递下去
3 读取文件的每一行内容
@init def cat(target): while True: file_path,f=yield for line in f: target.send((file_path,line)) # 同时传递文件路径和每一行的内容
4 过滤一行内容中是否有
@init def grep(pattern,target): # patter是过滤的参数 while True: file_path,line=yield if pattern in line: target.send(file_path) # 传递有相应内容的文件路径
5 打印包含Python的文件路径
@init def printer(): while True: file_path=yield print(file_path)
上面的是函数的定义阶段,下面是函数的执行阶段:
g=search(opener(cat(grep('python',printer())))) g.send('D:\\test')
target这个生成器:
opener(cat(grep('python',printer())))
对此的理解:
关于target还是有个点没有想明白,是target的传递的情况,我知道是从下面往上传递的。不明白的中怎么会这样接收,它的起始点在哪里
面向过程的编程思想:流水线式的编程思想,在设计程序时,需要把整个流程设计出来
优点:
1:体系结构更加清晰
2:简化程序的复杂度
缺点:
1:可扩展性极其的差,所以说面向过程的应用场景是:不需要经常变化的软件,如:linux内核,httpd,git等软件
2模块
1 什么是模块?
常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
但其实import加载的模块分为四个通用类别:
1 使用python编写的代码(.py文件)
2 已被编译为共享库或DLL的C或C++扩展
3 包好一组模块的包
4 使用C编写并链接到python解释器的内置模块
2 为何要使用模块?
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,
3.如何使用模块?
总结:首次导入模块spam时会做三件事:
1.为源文件(spam模块)创建新的名称空间,在spam中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
2.在新创建的命名空间中执行模块中包含的代码,见初始导入import spam
1 提示:导入模块时到底执行了什么? 2 3 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中的名字来自两个完全不同的地方。
示范用法一:
有两中sql模块mysql和oracle,根据用户的输入,选择不同的sql功能
#mysql.py def sqlparse(): print('from mysql sqlparse') #oracle.py def sqlparse(): print('from oracle sqlparse') #test.py db_type=input('>>: ') if db_type == 'mysql': import mysql as db elif db_type == 'oracle': import oracle as db db.sqlparse()
支持一次导入多个模块用逗号分隔,支持as别名
我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于'__main__'
当做模块导入:
__name__=
作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == '__main__':
模块搜索路径
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。
编译python文件
为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。python解释器会在__pycache__目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,spam.py模块会被缓存成__pycache__/spam.cpython-33.pyc。这种命名规范保证了编译后的结果多版本共存。
python -m spam.py
提示:
1.模块名区分大小写,foo.py与FOO.py代表的是两个模块
2.你可以使用-O或者-OO转换python命令来减少编译模块的大小
3包
1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2. 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
强调:
1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
注意事项
1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
3.对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
__init__.py文件
不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。
绝对导入和相对导入
我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入:以glance作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
特别需要注意的是:可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。
4软件开发规范

常用模块
logging模块
用于便捷记录日志且线程安全的模块
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
import logging logging.debug('调试debug') logging.info('消息info') logging.warning('警告warn') logging.error('错误error') logging.critical('严重critical') ''' WARNING:root:警告warn ERROR:root:错误error CRITICAL:root:严重critical '''
#======介绍 可在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有 filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。 filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。 format:指定handler使用的日志显示格式。 datefmt:指定日期时间格式。 level:设置rootlogger(后边会讲解具体概念)的日志级别 stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。 format参数中可能用到的格式化串: %(name)s Logger的名字 %(levelno)s 数字形式的日志级别 %(levelname)s 文本形式的日志级别 %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有 %(filename)s 调用日志输出函数的模块的文件名 %(module)s 调用日志输出函数的模块名 %(funcName)s 调用日志输出函数的函数名 %(lineno)d 调用日志输出函数的语句所在的代码行 %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示 %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数 %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 %(thread)d 线程ID。可能没有 %(threadName)s 线程名。可能没有 %(process)d 进程ID。可能没有 %(message)s用户输出的消息 #========使用 import logging logging.basicConfig(filename='access.log', format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S %p', level=10) logging.debug('调试debug') logging.info('消息info') logging.warning('警告warn') logging.error('错误error') logging.critical('严重critical') #========结果 access.log内容: 2017-07-28 20:32:17 PM - root - DEBUG -test: 调试debug 2017-07-28 20:32:17 PM - root - INFO -test: 消息info 2017-07-28 20:32:17 PM - root - WARNING -test: 警告warn 2017-07-28 20:32:17 PM - root - ERROR -test: 错误error 2017-07-28 20:32:17 PM - root - CRITICAL -test: 严重critical
import logging formatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S %p',) fh1=logging.FileHandler('test1.log') fh2=logging.FileHandler('test2.log') fh3=logging.FileHandler('test3.log') ch=logging.StreamHandler() fh1.setFormatter(formatter) #也可以是不同的formater fh2.setFormatter(formatter) fh3.setFormatter(formatter) ch.setFormatter(formatter) logger=logging.getLogger(__name__) logger.setLevel(40) logger.addHandler(fh1) logger.addHandler(fh2) logger.addHandler(fh3) logger.addHandler(ch) logger.debug('debug') logger.info('info') logger.warning('warning') logger.error('error') logger.critical('critical')
实际使用
""" 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 = os.path.dirname(os.path.abspath(__file__)) # log文件的目录 logfile_name = 'all2.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数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, } def load_my_logging_cfg(): logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置 logger = logging.getLogger(__name__) # 生成一个log实例 logger.info('It works!') # 记录该文件的运行状态 if __name__ == '__main__': load_my_logging_cfg() 复制代码
使用:
""" MyLogging Test """ import time import logging import my_logging # 导入自定义的logging配置 logger = logging.getLogger(__name__) # 生成logger实例 def demo(): logger.debug("start range... time:{}".format(time.time())) logger.info("中文测试开始。。。") for i in range(10): logger.debug("i:{}".format(i)) time.sleep(0.2) else: logger.debug("over range... time:{}".format(time.time())) logger.info("中文测试结束。。。") if __name__ == "__main__": my_logging.load_my_logging_cfg() # 在你程序文件的入口加载自定义logging配置 demo()
可在logging.basicConfig()函数中通过具体参数来更改logging模块默认行为,可用参数有
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。点击查看更详细
日志格式
|
%(name)s |
Logger的名字,并非用户名,详细查看 |
|
%(levelno)s |
数字形式的日志级别 |
|
%(levelname)s |
文本形式的日志级别 |
|
%(pathname)s |
调用日志输出函数的模块的完整路径名,可能没有 |
|
%(filename)s |
调用日志输出函数的模块的文件名 |
|
%(module)s |
调用日志输出函数的模块名 |
|
%(funcName)s |
调用日志输出函数的函数名 |
|
%(lineno)d |
调用日志输出函数的语句所在的代码行 |
|
%(created)f |
当前时间,用UNIX标准的表示时间的浮 点数表示 |
|
%(relativeCreated)d |
输出日志信息时的,自Logger创建以 来的毫秒数 |
|
%(asctime)s |
字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 |
|
%(thread)d |
线程ID。可能没有 |
|
%(threadName)s |
线程名。可能没有 |
|
%(process)d |
进程ID。可能没有 |
|
%(message)s |
用户输出的消息 |

浙公网安备 33010602011771号