第五章: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')
View Code

 三、模块

一、首先先了解什么是模块?

常见的场景:一个模块就是一个包含了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-import用法.py 文件内容

用法一:

  from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。

# from-import用法.py
from spam import *

print(money)
print(read1)
'''
from the spam.py
1000
<function read1 at 0x000000000290BF28>
'''
View Code

注意:当模块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 the spam.py文件内容
# from-import用法.py
from spam import *
print(money)
read1()
change()
'''
from the spam.py
0
spam->read1->money 0
from->spam->chage
'''
#fimport用法.py文件内容

总结:

  #优点:使用源文件内的名字时无需加前缀,使用方便

  #缺点:容易与当前文件的名称空间内的名字混淆 

模块搜索路径

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()
View Code

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()
View Code

总结:这样访问的好处:

  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')  #直接使用

 

posted @ 2017-07-30 22:45  ShengLeQi  阅读(422)  评论(0)    收藏  举报