第22天 常用模块三

发布时间:2019-03-19 20:59:29编辑:auto阅读(1882)

    介绍的模块

    logging
    hashlib

     模块一:logging

    人生三问

    什么是日志
      对每天所发生的事情做的记录就是日志。
    为什么要用日志
      日志记录了我们程序每天发生了什么事情,这个对于我们程序的维护有很大的帮助。例如每天都会有同一个ip在尝试登陆我们的
    网站,如果我们没有日志就不会知道有这样一个现象发生,可能在多年之后程序就会被攻破,但是如果有了日志,我们就能即使的发
    现程序的异常,并及时的修复它。
    怎么使用日志?
    预备知识:日志级别
      1. debug  日常的调试信息 数字表示code为10
      2. info   常规信息  code为20
      3. warning  提醒信息  code为30
      4. error  错误信息  code为40
      5. critical  常规的错误信息  code为50
    系统默认的级别是30

     系统默认级别为warning,并打印到终端中:

    # 错误信息
    logging.debug('调试信息')
    logging.info('常规信息')
    logging.warning('提醒信息')
    logging.error('错误信息')
    logging.critical('重大错误信息')
    
    # 结果:
    # WARNING:root:提醒信息
    # ERROR:root:错误信息
    # CRITICAL:root:重大错误信息

    日志的基础配置信息

    import logging
    # 配置日志格式的时候无法通过basicConfig来配置编码方式
    format = '%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s'
    logging.basicConfig(
        filename='d.log', # 日志的输出的文件,不写默认是终端
        filemode='a', # 这个是打开文件的格式,默认是追加写入
        level=logging.DEBUG, # 日志的显示级别,默认是warning以上
        datefmt='%Y', # 显示日期的格式,和format配套使用
        format=format  # 显示日志的格式
        # stream:用指定的stream创建StreamHandler。
        # 可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。
        # 若同时列出了filename和stream两个参数,则stream参数会被忽略。
    )
    logging.debug('这是一个调试信息!')
    
    # 打开文件d.log,发现有乱码
    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用户输出的消息
    format参数中可能用到的格式化串

     

    日志系统的四个核心组件

    1. logger 日志生成器   
    2. filter 日志过滤器
    3. handler 日志处理器,主要是用来调用formatter控制打印输出
    4. formatter 日志格式定义

     

    创建一个自己的日志生成器:

    import logging
    # 1. 创建一个日志生成器
    my_logger = logging.getLogger('my_logger')
    # 1.1 设置日志生成器的级别
    my_logger.setLevel(logging.DEBUG)
    # 2. 创建一个日志处理器, 此处可以设置编码方式
    my_handler = logging.FileHandler('f.log', 'a', encoding='utf-8')
    # 3. 创建一个日志格式定义
    my_formatter = logging.Formatter(format)
    # 4. 关联生成器处理器
    my_logger.addHandler(my_handler)
    # 5. 关联处理器和格式定义
    my_handler.setFormatter(my_formatter)
    
    my_logger.debug('这是一个debug信息!')
    # 文件信息:
    2018-10-18 15:26:37,668 - my_logger - DEBUG -log模块练习:  这是一个debug信息!

     

    日志的继承:

    import logging
    # 创建三个日志生成器
    log1 = logging.getLogger('father')
    log2 = logging.getLogger('father.son')
    log3 = logging.getLogger('father.son.chiren')
    
    
    # 创建日志处理器
    log1_handler = logging.FileHandler('g.log', 'a', encoding='utf-8')
    # log2_handler = logging.FileHandler('g.log', 'a', encoding='utf-8')
    # log3_handler = logging.FileHandler('g.log', 'a', encoding='utf-8')
    
    # 创建一个formatter
    format1 = '%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s'
    log_format = logging.Formatter(format1)
    
    # 关联生成器,处理器和定义格式
    log1.addHandler(log1_handler)
    log2.addHandler(log1_handler)
    log3.addHandler(log1_handler)
    
    log1_handler.setFormatter(log_format)
    
    # 生成日志
    # log1.debug('father_log')  # 此时是创建了g.log文件,但是文件是空的,因为默认的级别是warning
    # log1.warning('father_log') # 此时文件内容:2018-10-18 18:15:09,940 - father - WARNING -log模块练习:  father_log
    
    # log2.warning('father.son.log')
    # 此时文件内容为:因为此时father继承了son的日志
    # 2018-10-18 18:15:09,940 - father - WARNING -log模块练习:  father_log
    # 2018-10-18 18:16:20,146 - father.son - WARNING -log模块练习:  father.son.log
    # 2018-10-18 18:16:20,146 - father.son - WARNING -log模块练习:  father.son.log
    
    log3.warning('father.son.chiren.log')
    # 此时文件内容又增加了三条,因为此时继承log3日志有两个父级
    # 2018-10-18 18:15:09,940 - father - WARNING -log模块练习:  father_log
    # 2018-10-18 18:16:20,146 - father.son - WARNING -log模块练习:  father.son.log
    # 2018-10-18 18:16:20,146 - father.son - WARNING -log模块练习:  father.son.log
    # 2018-10-18 18:18:32,956 - father.son.chiren - WARNING -log模块练习:  father.son.chiren.log
    # 2018-10-18 18:18:32,956 - father.son.chiren - WARNING -log模块练习:  father.son.chiren.log
    # 2018-10-18 18:18:32,956 - father.son.chiren - WARNING -log模块练习:  father.son.chiren.log

     

    练习: 创建一个日志生成器,让日志可以以三种不同的格式写入文件和打印到终端中

    import logging
    # 创建一个日志生成器
    logger = logging.getLogger(__file__)
    
    # 创建三个不同的处理器打印到终端和文件中
    logger_handler1 = logging.FileHandler('ceshi1.log', encoding='utf-8')
    logger_handler2 = logging.FileHandler('ceshi2.log', encoding='utf-8')  # 日志输出到两个文件中
    logger_handler3 = logging.StreamHandler()  # 打印到终端
    
    # 创建三种格式,格式对应
    logger_format1 = logging.Formatter(
        '%(asctime)s - %(name)s - %(funcName)s - %(module)s - %(message)s',
        datefmt='%Y-%m-%d %X %p'
    )
    logger_format2 = logging.Formatter(
        '%(asctime)s - %(name)s - %(funcName)s - %(module)s - %(lineno)s - %(message)s',
        datefmt='%Y-%m-%d %X %p'
    )
    logger_format3 = logging.Formatter('%(module)s - %(message)s ')
    
    # 处理器关联定义格式
    logger_handler1.setFormatter(logger_format1)
    logger_handler2.setFormatter(logger_format2)
    logger_handler3.setFormatter(logger_format3)
    
    # 设置日志级别
    logger.setLevel(logging.DEBUG)
    # 添加处理器
    logger.addHandler(logger_handler1)
    logger.addHandler(logger_handler2)
    logger.addHandler(logger_handler3)
    
    # 输出日志
    logger.debug('这是一个debug信息! ')
    
    
    # 输出信息:
    '''
    终端:log模块练习 - 这是一个debug信息! 
    ceshi1.log文件:2018-10-18 18:36:19 PM - H:/python_study/day22/log模块练习.py - <module> - log模块练习 - 这是一个debug信息! 
    ceshi2.log文件:2018-10-18 18:36:19 PM - H:/python_study/day22/log模块练习.py - <module> - log模块练习 - 95 - 这是一个debug信息! 
    
    '''
    测试代码

     

    案例分享:

    通过logging读出来日志配置文件并可以使用

    创建规范化目录的小工具:

    import os, sys
    
    dir_list = ['conf', 'core', 'bin', 'lib', 'db', 'log']
    
    def create_dir(parent_path, dir_list):
        '''
        在一个目录下面创建规范目录
        :param parent_path: 需要创建的文件夹, 是全局路径
        :param dir_list: 创建的规范目录列表
        :return: 创建成功返回True
        '''
        # 先规范化目录
        parent_path = os.path.normpath(parent_path)
        # 循环列表拼接目录并且创建
        for dir in dir_list:
            # 当前需要创建的目录路径
            current_dir = os.path.join(parent_path, dir)
            # 路径不存在则创建目录
            if not os.path.exists(current_dir):
                os.mkdir(current_dir)
    
    if os.path.isdir(sys.argv[1]):
        create_dir(sys.argv[1], dir_list)
    else:
        print('Usag: python current_exec_file parent_dir_path!')
    在一个目录下面创建规范目录

     

    import os, sys
    STANDARD_FORMAT = '%(asctime)s %(name)s %(funcName)s %(module)s %(message)s'
    SIMPLE_FORMAT = '%(asctime)s %(module)s %(message)s'
    COMPLETE_FORMAT = '%(asctime)s %(name)s %(funcName)s %(lineon)s %(module)s %(message)s'
    
    # 拼接日志文件存储路径
    LOGFILE_PATH = os.path.normpath(os.path.join(
        __file__,
        os.pardir,
        os.pardir,
        'log',
        'a.log'))
    
    print(LOGFILE_PATH)
    
    # 这个就是之前通过手工的方式创建的四个日志核心组件
    LOGGING_DIC = {
        # 日志字典的版本,这个自己设置的
        'version': 1,
        # 日志格式定义字段,通过一定格式的字符串进行创建
        'formatters': {
            # 定义了日志的表示格式标准格式,简单格式和完整格式
            'standard': {
                'format': STANDARD_FORMAT
            },
            # 简单格式
            'simple': {
                'format': SIMPLE_FORMAT
            },
            # 完整格式
            "complete":{
                "format": COMPLETE_FORMAT
            }
        },
        # 日志过滤器,暂时设置成空
        'filters': {},
        # 日志处理器
        'handlers': {
            # 定义了两种日志处理器
            # 把日志内容打印到终端
            'console': {
                'level': 'DEBUG',  # 日志级别
                'class': 'logging.StreamHandler',  # 日志流处理器
                'formatter': 'simple'   # 使用的打印格式是上面设置的simple
            },
            'default': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',   # 设置文件通过一定大小之后就换文件
                'formatter': 'standard',  # 写入文件的格式是上面定义的标准格式
                'filename': LOGFILE_PATH,  # 写入文件的路径
                'maxBytes': 1024 * 1024 * 5,  # 日志文件的最大大小为5M 超出后 换文件
                'backupCount': 5, # 最多留五个日志文件
                'encoding': 'utf-8', # 写入的时候编码方式
            },
        },
        # 日志生成器
        'loggers': {
            # 在getLogger的时候  如果指定的名称 不存在 或者不给名称 用的就是默认的
            # 在这里如果key为空 它就是默认的
            # 你可以自己定义生成器的名称 并且他们还能使用相同的默认配置
            '': {
                'handlers': ['default', 'console'],
                'level': 'DEBUG',
                'propagate': False,
            },
        },
    }
    logging文件的内容

     

    初始化操作: 创建规范目录,并创建logging配置文件

     

    文件内容:

    # star.py文件内容
    
    # 添加环境变量
    import sys, os
    
    BASE_DIR = os.path.normpath(os.path.join(
        __file__,
        os.pardir,
        os.pardir
    ))
    
    sys.path.append(BASE_DIR)
    
    # 导入核心逻辑代码程序并运行
    import core.atm
    import core.shop
    core.atm.login()
    core.shop.register()
    
    # atm.py文件内容
    def login():
        print('loggin')
    # shop.py文件内容
    def register():
        print('register')
    文件内容

    问题1:此时执行核心代码的login和register都是可以正常运行的,但是当他们执行的时候我想去往日志文件中写入日志,但是我又不想每次通过上面的方式手动的创建一个日志生成器,然后配置一些内容。说白了就是想把日志信息保存到配置文件中,每次想用的时候调用一下就可以了,因此写入配置文件settings的内容如下:

     日志字典的解析

    import os, sys
    STANDARD_FORMAT = '%(asctime)s %(name)s %(funcName)s %(module)s %(message)s'
    SIMPLE_FORMAT = '%(asctime)s %(module)s %(message)s'
    COMPLETE_FORMAT = '%(asctime)s %(name)s %(funcName)s %(lineon)s %(module)s %(message)s'
    
    # 拼接日志文件存储路径
    LOGFILE_PATH = os.path.normpath(os.path.join(
        __file__,
        os.pardir,
        os.pardir,
        'log',
        'a.log'))
    
    print(LOGFILE_PATH)
    
    # 这个就是之前通过手工的方式创建的四个日志核心组件
    LOGGING_DIC = {
        # 日志字典的版本,这个自己设置的
        'version': 1,
        # 日志格式定义字段,通过一定格式的字符串进行创建
        'formatters': {
            # 定义了日志的表示格式标准格式,简单格式和完整格式
            'standard': {
                'format': STANDARD_FORMAT
            },
            # 简单格式
            'simple': {
                'format': SIMPLE_FORMAT
            },
            # 完整格式
            "complete":{
                "format": COMPLETE_FORMAT
            }
        },
        # 日志过滤器,暂时设置成空
        'filters': {},
        # 日志处理器
        'handlers': {
            # 定义了两种日志处理器
            # 把日志内容打印到终端
            'console': {
                'level': 'DEBUG',  # 日志级别
                'class': 'logging.StreamHandler',  # 日志流处理器
                'formatter': 'simple'   # 使用的打印格式是上面设置的simple
            },
            'default': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',   # 设置文件通过一定大小之后就换文件
                'formatter': 'standard',  # 写入文件的格式是上面定义的标准格式
                'filename': LOGFILE_PATH,  # 写入文件的路径
                'maxBytes': 1024 * 1024 * 5,  # 日志文件的最大大小为5M 超出后 换文件
                'backupCount': 5, # 最多留五个日志文件
                'encoding': 'utf-8', # 写入的时候编码方式
            },
        },
        # 日志生成器
        'loggers': {
            # 在getLogger的时候  如果指定的名称 不存在 或者不给名称 用的就是默认的
            # 在这里如果key为空 它就是默认的
            # 你可以自己定义生成器的名称 并且他们还能使用相同的默认配置
            '': {
                'handlers': ['default', 'console'],
                'level': 'DEBUG',
                'propagate': False,
            },
        },
    }
    日志配置字典

    问题2:日志文件配置完成之后,首先我们应该考虑的时候日志生成器应该放在哪个文件里面,atm或者shop核心代码在运行的时候都是要进行记录日志的,因此我们应该把日志生成器函数写在一个公共组件里面以便于后续的调用,但是我们应该怎么去创建一个日志生成器呢?首先在common里面写入我们的日志生成器代码

    # 导入日志模块和配置文件
    import logging.config
    import conf.settings
    
    
    def create_logger():
        # 将配置文件导入到logging中
        logging.config.dictConfig(config=conf.settings.LOGGING_DIC)
        # 根据配置文件获得一个关联了日志处理器和格式的生成器
        logger = logging.getLogger('loggers')
        logger.debug('这是一个debug文件')

    配置atm.py文件为下:

    # 导入日志模块
    import lib.common
    
    def login():
        print('loggin')
        # 当函数执行完成之后写入日志
        lib.common.create_logger()

    执行start文件之后发现

    终端中输出:
    2018-10-18 19:54:25,557 common 这是一个debug文件
    log目录下创建了一个文件内容为:
    2018-10-18 19:54:25,557 loggers create_logger common 这是一个debug文件

    问题3:此时我们的日志模块基本上是已经创建完成了,但是还有一些小小的问题,就是我们希望的是可以在函数内自定义输入日志的内容,而不是由公共组件给我们定义输入的内容。也就是说我们的公共组件create_logger应该给我们返回一个日志生成器,然后在函数中获得此生成器,往文件中写入我们想要的内容。

    修改common.py文件如下:

    # 导入日志模块和配置文件
    import logging.config
    import conf.settings
    
    
    def create_logger():
        # 将配置文件导入到logging中
        logging.config.dictConfig(config=conf.settings.LOGGING_DIC)
        # 根据配置文件获得一个关联了日志处理器和格式的生成器
        return logging.getLogger('loggers')

    修改atm.py文件内容如下:

    # 导入日志模块
    import lib.common
    
    def login():
        print('loggin')
        # 当函数执行完成之后写入日志
        lib.common.create_logger().debug('今天我登陆了atm取款机!')

    重新执行start.py文件发现没有问题,至此我们通过配置文件创建日志格式的内容就算大功告成了!

    模块二:hashlib

     人生三问

    什么是hash
      hash是一种将任意长度的数据经过计算返回固定长度特征码的算法。 为什么要用hash
      1. 无论值多大,hash值都是一样长度的
      2. 同一个值hash值是一样的, 不同的值hash是不一样的
      3. 不会被反解
      基于上述特点,我们可以使用hash对数据进行完整性校验。
    怎么使用hashlib
      hashlib封装了一系列的哈希算法,我们可以通过hexdigest得到相应的的散列值

    注意的是:hash算法只能计算字节,也就是说传入的值必须是字节类型的。

    使用方法:

    # 把一段很长的数据update多次与一次update这段数据是一样的
    import hashlib
    # 一段很长的数据helloalvin
    m2 = hashlib.md5()
    m2.update('helloalvin'.encode('utf-8'))
    print(m2.hexdigest())  # 92a7e713c30abbb0319fa07da2a5c4af
    
    # 直接当参数传递进去也是一样的
    m3 = hashlib.md5('helloalvin'.encode('utf-8'))
    print(m3.hexdigest())  # 92a7e713c30abbb0319fa07da2a5c4af
    
    
    # 拆分开之后计算hash值
    m = hashlib.md5()
    print(m.hexdigest()) # d41d8cd98f00b204e9800998ecf8427e
    
    m.update('hello'.encode('utf-8')) # 5d41402abc4b2a76b9719d911017c592
    print(m.hexdigest())
    
    m.update('alvin'.encode('utf-8'))  # 92a7e713c30abbb0319fa07da2a5c4af
    print(m.hexdigest())

    问题:撞库

    撞库
      因为散列计算的其中一个特点是一样的值通过一样的方法得到的值一定是一样的,也就是说密码123的散列值一定是202cb962ac59075b964b07152d234b70
    这样子如果有人事先存储了值这一对值,当出现这个散列值的时候我们就会知道你使用的密码是123。
      但是库也是有限的,也是大部分经常出现的数据,因此我们只要把密码设置的相对复杂一点,反解是肯定反解不出来的。

    模拟撞库:

    # 模拟撞库
    import hashlib
    # 密码
    password = [
        'hello',
        'alexhello',
        'egonhello',
        'nihaohello',
        'hello,world',
    ]
    
    
    def create_password_dict(password):
        temp_dict = {}
        for password_item in password:
            # 计算密码的md5校验值
            hash = hashlib.md5(password_item.encode('utf-8'))
            temp_dict[hash.hexdigest()] = password_item
        # 返回一个密码与password的一个字典库
        print(temp_dict)
        return temp_dict
    
    def pojie_password(hash, password_dict):
        # 如果密码库中存在则返回密码
        if hash in password_dict:
            return password_dict[hash]
    
    
    # 得到密码库
    password_lib = create_password_dict(password)
    # 获得密码
    ps = pojie_password('5d41402abc4b2a76b9719d911017c592', password_lib)
    if ps:
        print('密码为===》%s' % ps)
    else:
        print('密码库中暂时还没有,请稍等!')
    撞库的实现

    另一个加密模块:hmac必须要加盐

    import hmac
    # 这个里面传入的值是秘钥
    h = hmac.new('miyao'.encode('utf-8'))
    # 这个是密码
    h.update('henchangdemima'.encode('utf-8'))
    print(h.hexdigest())   # c3ffabf0cf7eef648ba783c9673a54d1
    
    
    # 拆分密码
    # 密码不能改变,否则出来的结果不一样
    h1 = hmac.new('miyao'.encode('utf-8'))
    
    h1.update('henchang'.encode('utf-8'))
    h1.update('de'.encode('utf-8'))
    h1.update('mima'.encode('utf-8'))
    
    print(h1.hexdigest())  # c3ffabf0cf7eef648ba783c9673a54d1
    hmac

     

关键字