四、模块与包&常用模块

2018-09-01 05:52:34来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

一、模块的使用

模块定义:一系列功能的集合体

分为三大类:1.自定义模块

      2.内置模块(比如 time,os,sys)

      3.第三方模块

模块的表现形式

  1.使用python编写的py文件

  2.已被编译为共享库或DLL的C或C++扩展

  3.把一系列模块组织到一起的文件夹(ps:文件夹下有一个__init__.py文件,该文件夹称为包)

  4.使用C编写并链接到python解释器的内置模块

为什么要用模块:

  1.可以拿来内置、第三方的模块,然后直接使用,这种拿来就用的方式,会极大的提升开发效率

  2.将程序中共用的一些功能组织到一个文件中,然后程序各部分组件可以重用该文件中的功能。

    优点是 减少代码冗余,增强程序的组织结构性与可维护性

怎么用模块:

  一个py文件就是一个模块,如果文件名为modules.py,模块名则为modules

  ps:模块的使用必须搞清楚谁是执行文件,谁是被导入的模块

1、模块的使用之import

#:coding:utf-8
#modules.py
print('from the modules.py')

money=1000

def read1():
    print('modules模块.read1',money)

def read2():
    print('modules模块.read2')
    read1()

def change():
    global money
    money=0
modules.py
import modules 

money=111111111111
print(modules.money)
print(modules.read1)
print(modules.read2)
print(modules.change)
modules.money=2222
print(money)###结果为money=111111111111

modules.read1()#modules模块.read1 2222   #这个更改成功modules.money=2222

def read1():#此处定义read1
    print('run.py中的read1')

modules.read1()#执行结果仍为modules模块.read1 2222

money=20000000
modules.change()#此处执行了modules中change函数
print(money)###此处为上述money=20000000
print(modules.money)##结果为0


import modules as spam ##可以as 起个别名
print(spam.money)  #依旧执行成功  结果为0
run.py

 

执行文件:run.py      被导入模块是:modules.py

首次导入模块会发生三件事: 

  1、创建一个模块modules.py的名称空间
  2、执行模块对应的文件modules.py,将产生的名字丢到模块的名称空间中
  3、在当前执行文件的名称空间中拿到一个名字modules,该名字就是指向模块modules.py的名称空间的
  import modules# modules=modules.py名称空间的内存地址

ps

  在当前执行文件中引用模块中的名字语法为:modules.名字,必须加上modules.作为前缀
  modules.名字相当于指名道姓地跟某一个名称空间要名字,根本不会与当前执行文件的名字冲突

 

2、模块的使用之  from   import

#modules.py
# print('from the modules.py')

__all__=['money','read1']

money=1000

def read1():
    print('spam模块.read1',money)

def read2():
    print('spam模块.read2')
    read1()

def change():
    global money
    money=0
spam.py
from spam import money,read1,read2,change
#
def read1():
    print('run.py.read1')

read1()      #  run.py.read1
read2()      #  spam模块.read2  ------------>   spam模块.read1 1000


from spam import money as my
print(my)   #1000

# ===========================================
from spam import * # * 会检索被导入模块中的__all__指定的名字,如果没有该变量那默认导入所有
print(money)  #1000
print(read1)  # <function read1 at 0x0000000001EB1C80>
# print(read2) #会报错
# print(change) #会报错
run.py

 

执行文件:run.py      被导入模块: spam.py

首次导入模块会发生三件事
  1、创建一个模块spam.py的名称空间
  2、执行模块对应的文件spam.py,将产生的名字丢到模块的名称空间中
  3、在当前执行文件的名称空间中拿到一个名字money,该名字就是指向模块spam.py的名称空间的那个money
  from spam import money,read1,read2,change

两种导入方式的对比
相同点:函数的作用域关系在定义阶段就规定死了,与调用位置无关

from import
优点:可以不用加前缀而直接引用名字,更简洁
缺点:容易与当前执行文件中的名字冲突

import
优点:指名道姓跟某一个名称空间要名字,肯定不会与当前名称空间中的名字冲突
缺点:必须加上前缀

 

3、循环导入的问题

# print('正在导入m1')
#
# x='m1.py'
#
# from m2 import y
#

print('正在导入m1')

def f1():
    from m2 import y,f2
    print(y)
    f2()

x='m1.py'
m1.py
# print('正在导入m2')
#
# y='m2'
#
# from m1.py import x


print('正在导入m2')

def f2():
    from m1 import x
    print(x)

y='m2.py'
m2.py
###   run.py
# 1、创建m1.py的名称空间
# 2、执行m1.py的代码,将产生的名字丢到m1.py的名称空间中
# 3、在当前执行文件的名称空间中拿到一个名字m1
import m1

m1.f1()

 

4、模块的搜索路径

模块搜索路径优先级:

  内存  --------> 内置的模块 --------> sys.path(环境变量)

#模块的查找顺序
1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
    ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看
2、如果没有,解释器则会查找同名的内建模块
3、如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。


#sys.path的初始化的值来自于:
The directory containing the input script (or the current directory when no file is specified).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
The installation-dependent default.

#需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。 

#在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。
1 >>> import sys
2 >>> sys.path.append('/a/b/c/d')
3 >>> sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索
注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理,

#首先制作归档文件:zip module.zip foo.py bar.py 
import sys
sys.path.append('module.zip')
import foo,bar

#也可以使用zip中目录结构的具体位置
sys.path.append('module.zip/lib/python')


#windows下的路径不加r开头,会语法错误
sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a')
 

#至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。

#需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。
详细的介绍

 

5、区分python文件的两种用途

一个python文件有两种用途:(模块与脚本)
  1、可以执行运行:__name__ == '__main__'
  2、可以被当做模块导入:__name__ == '模块名'

if __name__ == '__main__':

 

二、包的使用

1、什么是包

官网解释

Packages are a way of structuring Python’s module namespace by using “dotted module names”

包是一种通过使用‘.模块名’来组织python模块名称空间的方式

具体的:包就是一个包含有__init__.py文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来

需要强调的是:   

1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错   

2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块

2、为什么要用包

包的本质就是一个文件夹,  那么文件夹唯一的功能就是将文件组织起来 随着功能越写越多  ,我们无法将所以功能都放到一个文件中, 于是我们使用模块去组织功能, 而随着模块越来越多, 我们就需要用文件夹将模块文件组织起来,以此来提高程序的结构性和可维护性。

ps:注意事项

#1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

#2、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件

#3、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

 

|---示例
|     ||---dir1 #文件夹
|     |     |-----dir2 #文件夹
|     |            |---p1 #包文件夹
|     |               |||-------p2 #包文件夹
|     |               |||       ||----- __init__.py
|     |               |||       |------m3.py
|     |               |||
|     |               |||--- __init__.py
|     |               ||----m1.py     
|     |               |-----m2.py   
|     |
|     |------run.py  #执行文件
#   run.py

from dir1.dir2 import p1
p1.f1()
p1.f2()
p1.f3()

#======================================
#dir1文件夹 (dir2文件夹)
#
#dir2文件夹(p1包文件夹)
#======================================
                          #p1下的文件内容
#__init.py

from .m1 import f1
from .m2 import f2
from .p2.m3 import f3
#------------------------------------------
#m1.py

def f1():
    print('m1.f1')
#-------------------------------------------
#m2.py

def f2():
    print('m2.f2')
#-------------------------------------------
#p1包文件夹(p2包文件夹)
#=======================================
                         #p2下的文件内容
#__init__.py

#
#--------------------------------------------
#m3.py

def f3():
    print('m3.f3')
文件内容
#import p1
#1 创建p1的名称空间
#2 执行p1下的__init__.py文件的代码,将执行过程中产生的名字都丢到名称空间中
#3 在当前执行文件中拿到一个名字p1,p1指向__init__.py的名称空间
#包内模块的:绝对导入与相对导入
#    绝对导入:每次导入都是以最顶级包为起始开始查找的
#    相对导入:相对于当前所在的文件,.代表当前所在的文件,..代表上一级,...
#        强调:
#            相对导入只能在被导入的模块中使用
#           在执行文件里不能用.或者..的导入方式

#注意:
# 但凡在导入时带点的,点的左边必须是一个包

 

三、软件开发的目录规范

  

1、bin 文件夹  :一般放程序启动文件(start.py)

2、conf 文件夹:一些配置信息(例如路径等)

3、core 文件夹:程序的核心文件 与用户挂钩的

4、db 文件夹   :数据文件所在地

5、lib 文件夹   :公共模块文件(例如认证文件)

6、log 文件夹  :日志文件所在地 

7、Readme 文件   :程序的帮助文档

 

四、logging模块(日志)

一、日志的级别

CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不设置

二、默认级别为warning ,默认打印到终端

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 模块指定全局配置,针对所有logger有效,控制打印到文件

可在logging.basicConfig()函数中通过具体参数来更改logging模块默认行为,可用参数有
filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。

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:用户输出的消息  
logging.basicConfig()
#========使用
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

part2: 可以为logging模块指定模块级的配置,即所有logger的配置
固定套路

 

四、logging模块的Formatter ,Handler,Logger,Filte 对象

#logger:产生日志的对象

#Filter:过滤日志的对象

#Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端

#Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式
'''
critical=50
error =40
warning =30
info = 20
debug =10
'''


import logging

#1、logger对象:负责产生日志,然后交给Filter过滤,然后交给不同的Handler输出
logger=logging.getLogger(__file__)#日志名

#2、Filter对象:不常用,略

#3、Handler对象:接收logger传来的日志,然后控制输出
h1=logging.FileHandler('t1.log') #打印到文件
h2=logging.FileHandler('t2.log') #打印到文件
h3=logging.StreamHandler() #打印到终端

#4、Formatter对象:日志格式
formmater1=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

formmater2=logging.Formatter('%(asctime)s :  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

formmater3=logging.Formatter('%(name)s %(message)s',)


#5、为Handler对象绑定格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
h3.setFormatter(formmater3)

#6、将Handler添加给logger并设置日志级别
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(h3)
logger.setLevel(10)

#7、测试
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')
logging模块的固定套路

详细步骤:

1、配图

1、logger对象负责产生日志

logger1=logging.getLogger('xx日志')

2、filter过滤(基本用不到)

3、handler对象需要与logger对象绑定,用来接收logger对象传过来的日志,控制打印到不同的地方(文件、终端)

fh1=logging.FileHandler(filename='a1.log',encoding='utf-8')
fh2=logging.FileHandler(filename='a2.log',encoding='utf-8')

sh=logging.StreamHandler()# 往终端打印的

4、formmater对象需要与handler对象绑定,用于控制handler对象的日志格式

formmater1=logging.Formatter(
    fmt='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p'
)

#两种不同的日志格式

formmater2=logging.Formatter(
    fmt='%(asctime)s - %(levelname)s :  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p'
)
formmater格式

ps1:设置日志级别(统一设置):logger与handler两层关卡都放行,日志最终才放行

logger1.setLevel(10)  #logger对象设置

fh1.setLevel(10)  #文件1的handler设置级别
fh2.setLevel(20)  #文件2的handler设置级别
sh.setLevel(30)   #打印到终端的handler设置级别

ps2:建立logger对象与handler对象的绑定关系

logger1.addHandler(fh1)
logger1.addHandler(fh2)
logger1.addHandler(sh)

ps3:建立handler对象与formmater对象的绑定关系

fh1.setFormatter(formmater1)
fh2.setFormatter(formmater1)
sh.setFormatter(formmater2)

最终:使用logger1对象产生日志,打印到不同的位置

logger1.debug('蘑菇买彩票中奖30元')
logger1.warning('蘑菇花了1000元买彩票')

 

五、Logger与Handler的级别

 logger与handler两层关卡都放行,日志最终才放行

注意:logger是第一层关卡  ,然后才到handler

import logging


form=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

ch=logging.StreamHandler()

ch.setFormatter(form)
# ch.setLevel(10)
ch.setLevel(20)

l1=logging.getLogger('root')
# l1.setLevel(20)
l1.setLevel(10)
l1.addHandler(ch)

l1.debug('l1 debug')
示例

 

六、具体模块的使用方法

如果每次使用模块都得按照上述的步骤,那就太繁琐了。

所以logging模块有一个从字典中加载配置的方法

1.应用模板

"""
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()

logging配置文件
logging配置模板
"""
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()
使用
#1、有了上述方式我们的好处是:所有与logging模块有关的配置都写到字典中就可以了,更加清晰,方便管理


#2、我们需要解决的问题是:
    1、从字典加载配置:logging.config.dictConfig(settings.LOGGING_DIC)

    2、拿到logger对象来产生日志
    logger对象都是配置到字典的loggers 键对应的子字典中的
    按照我们对logging模块的理解,要想获取某个东西都是通过名字,也就是key来获取的
    于是我们要获取不同的logger对象就是
    logger=logging.getLogger('loggers子字典的key名')

    
    但问题是:如果我们想要不同logger名的logger对象都共用一段配置,那么肯定不能在loggers子字典中定义n个key   
 'loggers': {    
        'l1': {
            'handlers': ['default', 'console'],  #
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)传递
        },
        'l2: {
            'handlers': ['default', 'console' ], 
            'level': 'DEBUG',
            'propagate': False,  # 向上(更高level的logger)传递
        },
        'l3': {
            'handlers': ['default', 'console'],  #
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)传递
        },

}

    
#我们的解决方式是,定义一个空的key
    'loggers': {
        '': {
            'handlers': ['default', 'console'], 
            'level': 'DEBUG',
            'propagate': True, 
        },

}

这样我们再取logger对象时
logging.getLogger(__name__),不同的文件__name__不同,这保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置
log配置字典相关问题

2.示例

|----示例
|    |---bin
|    |    |----start.py  #启动文件
|    |
|    |---conf
|    |    |----settings.py   #配置文件(logging配置)
|    |
|    |---core
|    |    |----  src.py   #业务核心逻辑
|    |
|    |---lib
|    |    |---- common.py    #公共库
|    |
|    |---  log    #日志
import os

BASE_DIR=os.path.dirname(os.path.dirname(__file__))


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'


# log文件的全路径
fh1_logfile_path = r'%s\%s\%s' %(BASE_DIR,'log','b1.log')
fh2_logfile_path = r'%s\%s\%s' %(BASE_DIR,'log','b2.log')

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        #打印到终端的日志,使用的格式为 simple_format
        'ch': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        #打印到文件b1.log的日志,使用的格式为 standard_format
        'fh1': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': fh1_logfile_path,  # 日志文件
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
        #打印到文件b2.log的日志,使用的格式为 standard_format
        'fh2': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': fh2_logfile_path,  # 日志文件
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
    },
    'loggers': {
        '': {
            'handlers': ['ch', 'fh1','fh2'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}
settings配置
from conf import settings
import logging.config

def get_logger(name):
    logging.config.dictConfig(settings.LOGGING_DIC)
    logger1=logging.getLogger(name)
    return logger1
common.py
from lib import common

logger1=common.get_logger("交易日志")

def shopping():
    print('购物')

def pay():
    print('支付')
    logger1.info('蘑菇 支付了1元')

def transfer():
    print('转账')
    logger1.info('蘑菇给小火柴支付了200元')


def withdraw():
    print('提现')
    logger1.info('蘑菇提现200元')

func_dic={
    '1': shopping,
    '2': pay,
    '3': transfer,
    '4': withdraw
}

def run():
    while True:
        print("""
        0 退出
        1 购物
        2 支付
        3 转账
        4 提现
        """)
        choice=input('请输入您的操作: ').strip()
        if choice == '0':break
        if choice not in func_dic:
            print("输入的指令不存在,请重新输入")
            continue
        func_dic[choice]()
src.py
import sys
import os

BASE_DIR=os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR)

from core import src

if __name__ == '__main__':
    src.run()
start.py

 

五、re 模块(正则匹配)

 一、什么是正则

正则就是 用一些具体特殊含义的符号组合到一起(称为正则表达式)来描述字符或字符串的方法。或者说正则就是用来描述一类事物的规则。python中,它内嵌在python中,并通过re模块实现。

正则表达式被编译成一系列的字节码,然后由C编写的匹配引擎执行。

生活中处处是正则:

  比如我们描述:4条腿

    就会想到四条腿的动物或者桌子,椅子

  再描述:四条腿,活的

    就只有四条腿的动物这类

二、常用匹配模式(元字符)

#==========================re模块  正则表达式=============================
import re
#1、 \w匹配数字字母下划线
print(re.findall('\w','hello 123 - _ *()'))
#['h', 'e', 'l', 'l', 'o', '1', '2', '3', '_']
print(re.findall('mogu','hello mogu, mogu say hello'))#前面mogu代表完全匹配
#['mogu', 'mogu']

#2、 \W 上方的取反
print(re.findall('\W','hello 123 - _ *()'))
#[' ', ' ', '-', ' ', ' ', '*', '(', ')']

#3、 \s 匹配任意空白字符
print(re.findall('\s','666 \t jiayou'))
#[' ', '\t', ' ']
#    \S 上方取反

#4、 \d 匹配任意数字   \D 取反匹配任意数字
print(re.findall('\d','666 \t jia\nyou'))
#['6', '6', '6']

#5、 \n 只匹配换行符, \t 只匹配制表符
print(re.findall('\n','666 \t jia\nyou'))
print(re.findall('\t','666 \t jia\nyou'))
#['\n']
#['\t']

#6、 ^ 匹配字符串的开头   $ 匹配字符串的结尾
print(re.findall('^mogu','hello mogu, mogu say hello'))
print(re.findall('mogu$','hello mogu, mogu say hello mogu'))
# ^结果为空 只匹配开头
# $ ['mogu']  只匹配匹配末尾

#7、 . :点 代表匹配除了换行符以外的任意单个字符
print(re.findall('a.c','a-c a*c a2c a\nc aacacacacac a c a\tc',re.DOTALL))
# ['a-c', 'a*c', 'a2c', 'aac', 'a c', 'a\tc']
# re.DOTALL  可以让换行符也匹配出来

#8、 [] : 代表匹配指定范围的任意字符
print(re.findall('\d[-+*/]\d','1+2 3!3 qweasd 2+3 1/2 2-3 axcz hello'))
#['1+2', '2+3', '1/2', '2-3']
print(re.findall('a[0-9]c','a1c aAc a2c acc axc a23c a*c a9c'))
#['a1c', 'a2c', 'a9c']
print(re.findall('a[0-9][0-9]c','a1c aAc a2c acc axc a23c a*c a9c'))
#['a23c']
print(re.findall('a[a-z]c','a1c aAc a2c acc axc a23c a*c a9c'))
#['acc', 'axc']
print(re.findall('a[A-Z]c','a1c aAc a2c acc axc a23c a*c a9c'))
#['aAc']

#9、重复匹配
#9.1  ? : 代表左边那一个字符出现0次或者1次 可以用{0,1}
print(re.findall('ab?','b ab abb bbabbabbabba'))
#['ab', 'ab', 'ab', 'ab', 'ab', 'a']

#9.2  * :代表左边那一个字符出现0次或者无穷次,如果没有可以凑合,但如果>1个,有多少就必须拿多少
#   可以用{0,}
print(re.findall('ab*','b ab abb bbabbababba'))
#['ab', 'abb', 'abb', 'ab', 'abb', 'a']

#9.3  + :代表左边那一个字符出现1次或者无穷次,至少要有一个,但如果有>1个,有多少就必须拿多少
#   可以用{1,}
print(re.findall('ab+','b ab abb bbabbabbabba'))
#['ab', 'abb', 'abb', 'abb', 'abb']

#9.4 {n,m}:代表左边那一个字符出现n次到m次,至少要有n个,但如果有>n个,就拿<=m个
print(re.findall('ab{2,5}','b ab abb bbabbabbabba abbbbbbbb'))
#['abb', 'abb', 'abb', 'abb', 'abbbbb']

#9.5  .* : 匹配0个或无穷个任意字符,默认是贪婪匹配
print(re.findall('a.*c','a123c hello ab123  c321 mogu c'))
# ['a123c hello ab123  c321 mogu c'] 找离a最远的c

#9.6  .*? : 匹配0个或无穷个任意字符,非贪婪匹配
print(re.findall('a.*?c','a123c hello ab123  c321 mogu c'))
#['a123c', 'ab123  c']  找离a最近的c

#示例: 正则使用非贪婪匹配 ()括号的意思是  只取分组内的内容
print(re.findall('href="(.*?)"',''
    '<div class="div1"><a href="https://www.baidu.com">点我啊</a>'
    '</div><div class="div1"><a href="https://www.python.org">点我啊</a></div>'))
#[]
print(re.findall('a[1*-]b','a1b a*b a-b')) #[]内的都为普通字符了,且如果-没有被转意的话,应该放到[]的开头或结尾
print(re.findall('a[^1*-]b','a1b a*b a-b a=b')) #[]内的^代表的意思是取反,所以结果为['a=b']
print(re.findall('a[0-9]b','a1b a*b a-b a=b')) #[]内的^代表的意思是取反,所以结果为['a=b']
print(re.findall('a[a-z]b','a1b a*b a-b a=b aeb')) #[]内的^代表的意思是取反,所以结果为['a=b']
print(re.findall('a[a-zA-Z]b','a1b a*b a-b a=b aeb aEb')) #[]内的^代表的意思是取反,所以结果为['a=b']

#\# print(re.findall('a\\c','a\c')) #对于正则来说a\\c确实可以匹配到a\c,但是在python解释器读取a\\c时,会发生转义,然后交给re去执行,所以抛出异常
print(re.findall(r'a\\c','a\c')) #r代表告诉解释器使用rawstring,即原生字符串,把我们正则内的所有符号都当普通字符处理,不要转义
print(re.findall('a\\\\c','a\c')) #同上面的意思一样,和上面的结果一样都是['a\\c']

#():分组
print(re.findall('ab+','ababab123')) #['ab', 'ab', 'ab']
print(re.findall('(ab)+123','ababab123')) #['ab'],匹配到末尾的ab123中的ab
print(re.findall('(?:ab)+123','ababab123')) #findall的结果不是匹配的全部内容,而是组内的内容,?:可以让结果为匹配的全部内容
print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>'))#['http://www.baidu.com']
print(re.findall('href="(?:.*?)"','<a href="http://www.baidu.com">点击</a>'))#['href="http://www.baidu.com"']

#|
print(re.findall('compan(?:y|ies)','Too many companies have gone bankrupt, and the next one is my company'))
补充
import re
#1 findall
print(re.findall('e','mogu like read') )   #['e', 'e'],findall返回所有满足匹配条件的结果,放在列表里
#2 search
print(re.search('e','mogu like read').group())# e,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

#3 match
print(re.match('e','mogu like read'))    #None,同search,不过在字符串开始处进行匹配,完全可以用search+^代替match

#4 split
print(re.split('[ ]','mogu like read'))     #['mogu', 'like', 'read'],先按'空格'分割得到'mogu' 'like' 'read'

#5
print('===>',re.sub('m','M','mogu like read movie')) #===> Mogu like read Movie,不指定n,默认替换所有
print('===>',re.sub('m','M','mogu like read movie',1)) #===> Mogu like read movie
print('===>',re.sub('m','M','mogu like read movie',2)) #===> Mogu like read Movie
print('===>',re.sub('^(\w+)(.*?\s)(\w+)(.*?\s)(\w+)(.*?)$',r'\5\2\3\4\1','mogu like read movie')) #===> read like mogu

print('===>',re.subn('m','M','mogu like read movie')) #===> ('Mogu like read Movie', 2),结果带有总共替换的个数

#6
obj=re.compile('\d{2}')  #封装一种正则匹配模式

print(obj.search('abc123eeee').group()) #12
print(obj.findall('abc123eeee')) #['12'],重用了obj

#=======================================
print(re.findall(r'-?\d+\.?\d*',"1-12*(60+(-40.35/5)-(-4*3))")) #找出所有数字['1', '-12', '60', '-40.35', '5', '-4', '3']


#使用|,先匹配的先生效,|左边是匹配小数,而findall最终结果是查看分组,所有即使匹配成功小数也不会存入结果
#而不是小数时,就去匹配(-?\d+),匹配到的自然就是,非小数的数,在此处即整数
print(re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")) #找出所有整数['1', '-2', '60', '', '5', '-4', '3']
re模块的方法
#为何同样的表达式search与findall却有不同结果:
print(re.search('\(([\+\-\*\/]*\d+\.?\d*)+\)',"1-12*(60+(-40.35/5)-(-4*3))").group()) #(-40.35/5)
print(re.findall('\(([\+\-\*\/]*\d+\.?\d*)+\)',"1-12*(60+(-40.35/5)-(-4*3))")) #['/5', '*3']

#看这个例子:(\d)+相当于(\d)(\d)(\d)(\d)...,是一系列分组
print(re.search('(\d)+','123').group()) #group的作用是将所有组拼接到一起显示出来
print(re.findall('(\d)+','123')) #findall结果是组内的结果,且是最后一个组的结果
findall与search

总结:尽量精简,详细的如下

尽量使用泛匹配模式.*

尽量使用非贪婪模式:.*?

使用括号得到匹配目标:用group(n)去取得结果

有换行符就用re.S:修改模式

 

六、time与datetime模块

后续记录    常用模块 与 面向对象

未完待续。。。

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Python爬虫【解析库之beautifulsoup】

下一篇:Python爬虫 selenium