python基础——14(shelve/shutil/random/logging模块/标准流)

时间:2022-08-30 05:42:45

一、标准流

1.1、标准输入流

res = sys.stdin.read(3)
可以设置读取的字节数
print(res)
res = sys.stdin.readline()
print(res)

1.2、标准输出流

import sys
sys.stdout.write('123')  # 相当于print('123', end='')
sys.stdout.write('123\n')  # == print()

1.3、标准错误流

sys.stderr.write('错误信息1')
sys.stderr.write('错误信息2')
sys.stderr.write('错误信息3\n')  # 自己加换行

二、random模块

2.1、随机数random

(0, 1):random.random()

[1, 10]:random.randint(1, 10)

[1, 10):random.randrange(1, 10)
(1, 10):random.uniform(1, 10)  # 在random的基础上,增加了可以指定区间的功能

单例集合随机选择1个:random.choice(item)  # 参数可以是字符串,元组,列表

单例集合随机选择n个:random.sample(item, n)  # 前面是序列,后面是长度

洗牌单列集合:random.shuffle(item),打乱列表的顺序

import random
for i in range(10):
    print(random.random())
    print(random.randint(1, 10))
    print(random.randrange(1, 10))
    print(random.uniform(1, 10))
    print('-------------------------------------')

print(random.choice('abc'))
print(random.sample({1, 3, 5, 7, 9}, 3))
ls = [1, 2, 3, 4, 5]
random.shuffle(ls)
print(ls)

2.2、生成随机验证码

import random

# 第一种
def random_code(count):
    code = ''
    for i in range(count):
        num = random.choice([1,2,3])
        if num == 1:  # 该位为数字
            code += str(random.randint(0, 9))
        elif num == 2:  # 该位为大写字母
            code += chr(random.randint(65, 90))
        else:  # 该位为小写字母
            code += chr(random.randint(97, 122))
    return code
print(random_code(6))  # 生成6位数的验证码

# 第二种
def random_code1(count):
    source = 'ABCDEFabcdef0123456789'   # 把所有字母写出来
    code_list = random.sample(source,count)
    return ''.join(code_list)
print(random_code1(6))  # 生成6位数的验证码

三、shelve

shelve是将内存数据持久化的模块,可以持久化任何一种pickle支持的python数据格式。

它将序列化文件操作dump和load进行了封装。

直接通过字典存取:

import shelve

# 打开文件
s_dic = shelve.open('target.txt')

# 序列化::存
s_dic['key1'] = [1,2,3,4,5]
s_dic['key2'] = {'name':'Bob','age':18}
s_dic['key3'] = 'abc'

# 文件释放
s_dic.close()

# 序列化::取
s_dic = shelve.open('target.txt',writeback=True)
print(s_dic['key1'])
s_dic['key1'][2] = 30
print(s_dic['key1'])  # writeback=True,直接将更改同步到了文件,默认False

print(s_dic['key2'])
s_dic['key2']['age'] = 300
print(s_dic['key2'])

print(s_dic['key3'])
s_dic['key3'] = 'def'
print(s_dic['key3'])
# 记得释放
s_dic.close()

四、shutil模块

它是一个高级的文件夹、文件、压缩包处理模块

4.1、基于文件路径的复制

shutil.copyfile('a.py','b.py')  # 同目录下复制改名
shutil.copyfile('a.txt',r'./b/c.txt')  # 复制到其他目录下并改名

4.2、基于流的文件复制

with open('a.py','rb') as r,open('c.py','wb') as w:
    shutil.copyfileobj(r,w)

4.3、递归删除目标目录

shutil.rmtree(r'D:\fullstack_s4\day18\代码\part1\part0')
shutil.rmtree(r'G:\脱产7期\day18\shutil与random模块\b\a')

4.4、文件移动

shutil.move('a/aa.py', 'b/bb.py')  # 移动并改名
shutil.move('../a.txt',r'G:\脱产7期\day18\shutil与random模块')  # 移动至其他目录不改名

4.5、文件夹压缩

shutil.make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
                 dry_run=0, owner=None, group=None, logger=None)
#创建压缩包并返回文件路径
base_name:压缩包的文件名,也可以是压缩包的路径,只是文件名时,则保存在当前目录
format:压缩包种类,“zip,tar”
root_dir:要压缩的文件夹路径(默认当前)
owner:用户,默认当前用户
group:组,默认当前组
logger:用于记录日志,通常是logging.logger对象'''

shutil.make_archive(压缩包的文件名, '格式', 压缩的目标路径)
shutil.make_archive('bbb', 'zip', r'D:\fullstack_s4\day18\代码\part1\b')
shutil.make_archive('b','zip','b')

4.6、解压缩

shutil.unpack_archive(要解压的文件, 解压的路径, 格式)
shutil.unpack_archive(r'D:\fullstack_s4\day18\代码\part1\bb.tar.gz', 'a/bbb', 'gztar')
shutil.unpack_archive(r'b.zip', r'G:\脱产7期\day18\shutil与random模块\b\a', 'zip')

4.7、其他的特性

f1 = open('本节笔记','r',encoding='utf-8')
f2 = open('笔记2','w',encoding='utf-8')
shutil.copyfileobj(f1,f2) #将文件内容拷贝到另一个文件中,可以部分内容
shutil.copyfile('笔记2','笔记3') #拷贝文件,直接拷贝
shutil.copymode('笔记2','笔记3') # 仅拷贝权限,内容,用户,组不变
shutil.copystat('笔记2','笔记3')  #拷贝状态的信息,包括inode,bits,atime,mtime,flags
shutil.copy('笔记2','笔记3')  #拷贝文件和权限
shutil.copy2('笔记2','笔记3')  #拷贝文件和状态信息
shutil.copytree()  #递归的拷贝文件
shutil.rmtree()  #递归的删除目录
shutil.move()  #递归的移动文件
f1.close()
f2.close()

4.8、也可以导入zipfile模块,单独对某个文件压缩

import zipfile

# 压缩
z = zipfile.ZipFile("day2.zip","w")  # 设置压缩包名
z.write("b/c.txt")  #  一次写入要打包的所有文件
z.write("a.txt")
z.close()

# 解压
z = zipfile.ZipFile("day2.zip","r")  # 解压包名
z.extractall()
z.close()

五、logging模块

5.1、什么是logging模块?

logging模块是python提供的用于记录日志的模块

5.2、为什么用logging模块?

我们完全可以自己打开文件然后写入日志,但是这些操作过于重复并且没有技术含量,所以python帮我们进行了封装,有了logging后我们在记录日志时,只需要简单的调用接口即可,非常方便。

5.3、日志级别

在开始记录日志前,还需要明确日志的级别。

随着时间的推移,日志记录会达到成千上万行之多,如何快速找到需要的日志记录就成了重中之重。

解决的方案就是给日志划分级别。

logging将日志划分了5个级别,分别是:

info        常规信息

debug    调试信息

warning  警告信息

error       错误信息

critical(fatal)  严重错误

本质上他们是使用数字来表示级别的,源码如下:

CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

5.4、logging模块的使用

1、导入模块

import  logging

2、输出日志

logging.info("info")
logging.debug("debug")
logging.warning("warning")
logging.error("error")
logging.critical("critical")

输出 WARNING:root:warning
输出 ERROR:root:error
输出 CRITICAL:root:critical

我们发现info和debug都没有输出,这是因为它们的级别不够。

默认情况下:

logging的最低显示级别为warning,对应数值为30

日志被打印到了控制台

日志输出格式为:级别    日志生成器名称    日志消息

如何修改写默认的行为呢?这就需要我们来进行配置。

5.5、自定义配置

import  logging

logging.basicConfig()

可用参数:

filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别 

案例:

logging.basicConfig中,文件和控制台打印不能同时出现,若要输出到控制台,就不能写filename,而是stream=True。

import logging
import sys
# 对日志格式化,输出到文件
handler1 = logging.FileHandler('owen.log',encoding='utf-8')
# 输出到控制台
handler2 = logging.StreamHandler()

logging.basicConfig(
    filename="aaa.log",
    filemode="at",
    level=logging.DEBUG,
    # stream=sys.stdout,  # 设置输出的流,默认错误流
    datefmt="%Y-%m-%d %H:%M:%S %p",
    format='%(asctime)s - [%(levelname)s] : %(message)s',  # 设置输出格式
    handlers=[handler1,handler2]  # 设置输出源
)

# 打印级别是人为规定的
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.fatal('fatal')  # 致命错误
logging.critical('critical')  # 致命错误,同fatal

格式化的全部可用名称:

%(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:用户输出的消息

至此我们已经可以自己来配置——写基础信息了,但是当我们想要将同一个日志输出到不同位置时,这些基础配置就无法实现了,例如 有一个登录注册的功能 需要记录日志,同时生成两份 一份给程序员看,一份给老板看,作为程序员应该查看较为详细的日志,二老板则应该简单一些,因为他不需要关心程序的细节要实现这样的需要我们需要系统的了解loggin模块

python基础——14(shelve/shutil/random/logging模块/标准流)

logging模块的四个核心角色:

1.Logger   日志生成器 产生各种级别日志
logger1 = logging.getLogger('日志名')
    日志名用来标识日志与什么业务有关

2.Filter    日志过滤器  过滤日志
了解即可

3.Handler 日志处理器  输出日志到指定位置(控制台或文件),可以有多个
fh1 = logging.FileHandler('a1.log', encoding='utf-8')
fh2 = logging.FileHandler('a2.log', encoding='utf-8')
ch = logging.StreamHandler()  --终端打印

4.Formatter 处理日志的格式
formatter = logging.Formatter(
fmt = '%(asctime)s - [%(levelname)s] : %(message)s',
datefmt = "%m-%d %H:%M:%S %p"
)

在设置完四个核心模块后,还要进行以下步骤:

5、绑定handler与logger对象
logger1.addHandler(fh1)
logger1.addHandler(fh2)
logger1.addHandler(ch)

6、绑定handler与formatter对象
fh1.setFormatter(formatter1)
fh1.setFormatter(formatter1)
ch.setFormatter(formatter2)

7、设置日志级别:有logger和handler两层关卡,必须都放行,最终日志才会输出,通常他俩的级别要一致
logger1.setLevel(10)
fh1.setLevel(10)
fh2.setLevel(10)
ch.setLevel(10)

8、设置字符编码,其实在前面确定handler对象的时候就设置好了编码格式

9、使用logger对象产生日志
logger1.info('用户shj给用户wk充值500元成功')

一条日志完整的生命周期

由logger产生日志--》交给过滤器判断是否被过滤--》将日志消息分发给绑定的所有处理器--》处理器按照绑定的格式化对象输出日志

其中,第一步会先检查日志级别,如果低于设置的级别则不执行。

第二步,使用场景不多,需要用到面向对象的点

第三步,也会检查日志级别,如果得到的日志低于自身的级别则不输出

生成器的级别应低于句柄,否则给句柄设置级别是没有意义的

例如:handler设置为20,生成器设置为30.     30以下的日志根本不会产生

第四步,如果不指定格式则按照默认格式

logging各角色的使用:

# 生成器
logger1 = logging.getLogger("日志对象1")

# 文件句柄
handler1 = logging.FileHandler("log1.log",encoding="utf-8")
handler2 = logging.FileHandler("log2.log",encoding="utf-8")

# 控制台句柄
handler3 = logging.StreamHandler()

# 格式化对象
fmt1 = logging.Formatter(
    fmt="%(asctime)s - %(name)s - %(levelname)s:  %(message)s",
    datefmt="%m-%d %H:%M:%S %p")
fmt2 = logging.Formatter(
    fmt="%(asctime)s - %(levelname)s :  %(message)s",
    datefmt="%Y/%m/%d %H:%M:%S")

# 绑定格式化对象与文件句柄
handler1.setFormatter(fmt1)
handler2.setFormatter(fmt2)
handler3.setFormatter(fmt1)

# 绑定生成器与文件句柄
logger1.addHandler(handler1)
logger1.addHandler(handler2)
logger1.addHandler(handler3)

# 设置日志级别
logger1.setLevel(10)    #生成器日志级别
handler1.setLevel(20)   #句柄日志级别

# 测试
logger1.debug("debug msessage")
logger1.info("info msessage")
logger1.warning("warning msessage")
logger1.critical("critical msessage")

到此我们已经可以实现上述的需求了,但是这并不是我们最终的实现方式,因为每次都要编写这样的代码是非常痛苦的

logging的继承(了解)

可以将一个日志指定为另一个日志的子日志 或子孙日志

当存在继承关系时 子孙级日志收到日志时会将该日志向上传递

指定继承关系:

```python
import  logging

log1 = logging.getLogger("mother")
log2 = logging.getLogger("mother.son")
log3 = logging.getLogger("mother.son.grandson")

# handler
fh = logging.FileHandler(filename="cc.log",encoding="utf-8")
# formatter
fm = logging.Formatter("%(asctime)s - %(name)s -%(filename)s - %(message)s")

# 绑定
log1.addHandler(fh)
log2.addHandler(fh)
log3.addHandler(fh)
# 绑定格式
fh.setFormatter(fm)
# 测试
# log1.error("测试")
# log2.error("测试")
log3.error("测试")
# 取消传递
log3.propagate = False
# 再次测试
log3.error("测试")

5.6、通过字典配置日志模块(重要)

每次都要编写代码配置非常麻烦,在此我们写一个完整的保存起来,方便日后使用。

1、先把项目路径添加到环境变量

# 先把项目路径添加到环境变量
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR)

2、字典配置

import os

BASE_PATH = os.path.dirname(os.path.dirname(__file__))
DB_PATH = os.path.join(BASE_PATH, 'db')

"""
logging配置
"""

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.join(BASE_PATH, 'log')  # log文件的目录

logfile_name = 'atm_shop_log.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()

补充:

getLogger参数就是对应字典中loggers的key , 如果没有匹配的key 则返回系统默认的生成器,我们可以在字典中通过空的key来将一个生成器设置为默认的

另外我们在第一次使用日志时并没有指定生成器,但也可以使用,这是因为系统有默认的生成器名称就叫root。

3、加载日志配置文件

# 加载配置文件
from conf.settings import LOGGING_DIC
import logging.config
logging.config.dictConfig(LOGGING_DIC)

def get(name):
    return logging.getLogger(name)

最后有一个需求:

有一个登录注册的功能 需要记录日志,同时生成两份 一份给程序员看,一份给老板看,作为程序员应该查看较为详细的日志,二老板则应该简单一些,因为他不需要关心程序的细节

程序员看的格式

standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字
logfile_path1 = "coder.log"

老板看的格式

simple_format = '[%(levelname)s][%(asctime)s]%(message)s'
logfile_path2 = "boss.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及以上的日志
        'std': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path1,  # 日志文件
            'maxBytes': 1024*1024*5,  # 日志大小 5M
            'backupCount': 5, #日志文件最大个数
            'encoding': 'utf-8',  # 日志文件的编码
        },
        'boss': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'simple',
            'filename': logfile_path2,  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,  # 日志文件最大个数
            'encoding': 'utf-8',  # 日志文件的编码
        }
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        'aa': {
            'handlers': ['std', 'console',"boss"],  # 这里把上面定义的handler都加上,即log数据会同时输出到三个位置
            'level': 'INFO',
            'propagate': True,  # 向上(更高level的logger)传递
        },
    },
}