模块

5. 模块

[TOC]

模块

模块是一系列功能的集合体,而是函数是某一个功能的集合体,因此,模块可以看成时一堆函数的集合体,因此一个py文件就可以看成是一个模块,文件名即模块名

模块的四种形式

  1. 自定义模块:自定义一个py文件,文件中写入自定义代码(一堆函数),则该文件即自定义模块
  2. 第三方模块:可以通过pip下载的已被编译为共享库或DLL的C或C++扩展的模块
  3. 内置模块:使用C编写的python解释器自带的模块,比如time、random等
  4. 包:把一系列模块组织到一起的文件夹(文件夹中有一个__init__.py的文件,该文件称之为包)

模块导入

  • import

    1. 开辟内存空间,内存空间命名为模块名
    2. 把模块(文件)中的所有代码读入名称空间,然后运行
    3. 在当前执行文件中拿到一个模块名,通过模块名.方法名使用模块中的方法
    • 优点:永不冲突
    • 缺点:每次要多输入几个字符
  • from···import

    1. 开辟内存空间,内存空间命名为模块名
    2. 把模块(文件)中的所有代码读入名称空间,然后运行
    3. 在当前执行文件的名称空间中拿到一个名字,该名字直接指向模块中对应的功能名,因此可以不加前缀直接使用
    • 优点:不用加前缀,代码更加精简

    • 缺点:容易与当前执行文件中名称空间中的名字冲突

      # 导入多个方法
      from time import sleep,time  # 不推荐
      

    同时导入所有方法

    from time import *

循环导入

在两个文件中分别到任意对方的变量,会导致循环导入

m1.py

from m2 import x
y = 10
print(x,y)

m2.py

from m1 import y
x = 20
print(x,y)
  • 原因:模块导入时,会在内存中试运行,试运行时由于变量在模块导入之后,导致m1文件无法找到x的变量,导入陷入循环导入(死循环)

  • 解决方法

    1. 方法1:先定义变量,再导入模块中的变量
    # m1.py
    y = 10
    from m2 import x
    print(x,y)
    
    # m2.py
    x = 20
    from m1 import y
    print(x,y)
    
    1. 方法2:利用函数不调用代码不会执行的特性,将导入模块封装
    # m1.py
    def f1():
        from m2 import y
        print('m1:', x, y)
    x = 10
    
    # m2.py
    def f2():
        from m1 import x
        print('m2:', x, y)
    y = 20
    

模块的搜索路径

导入模块时查找模块的顺序:

  1. 先从内存中已经导入的模块中查找
  2. 内置模块
  3. 环境变量sys.path中、

注意:导入模块时,搜索路径以执行文件为准

# m1.py
# import m2
from dir1 import m2
# run.py
from dir1 import m1

上述文件目录结构中,执行run.py文件导入m1文件后,m1.py文件中的导入以执行文件run.py所在路径为准,即模块搜索路径练习,此时m1.py文件中直接导入m2会报错,应该使用from dir1 import m2

__main__

  • 执行文件(当前运行文件)
  • 模块文件
# __main__一般用于模块文件中,用来测试,当作为模块文件时,内部的代码不会执行
if __name__ == '__main__':
    代码
  • __name__是每个文件独有的:
    • 当该文件为执行文件运行时,__name____main__
    • 当该文件作为模块文件时,__name__为文件名

  • 包本质就是模块,用来导入用的
  • 包是含有__init__的文件夹,导包就是导__init__
  • 导入包发生的三件事情:
    • 创建一个包的名称空间
    • 执行包的.py文件,将执行过程产生的名字存放于包名称空间中
    • 在当前执行文件中拿到一个名字aaa,该名字aaa指向包的名称空间

绝对导入

# aaa/.py
from aaa.m1 import func1
from aaa.m2 import func2

相对导入

  • .代表当前被导入文件所在的文件夹
  • ..代表当前被导入文件所在的文件夹的上一级
  • ...代表当前被导入文件所在的文件夹的上一级的上一级
# aaa/.py
from .m1 import func1
from .m2 import func2

软件开发的目录规范

ATM/
|-- core/
|   |-- src.py  # 业务核心逻辑代码
|
|-- api/
|   |-- api.py  # 接口文件
|
|-- db/
|   |-- db_handle.py  # 操作数据文件
|   |-- db.txt  # 存储数据文件
|
|-- lib/
|   |-- common.py  # 共享功能
|
|-- conf/
|   |-- settings.py  # 配置相关
|
|-- bin/
|   |-- run.py  # 程序的启动文件,一般放在项目的根目录下,因为在运行时会默认将运行文件所在的文件夹作为sys.path的第一个路径,这样就省去了处理环境变量的步骤
|
|-- log/
|   |-- log.log  # 日志文件
|
|-- requirements.txt # 存放软件依赖的外部Python包列表,详见https://pip.readthedocs.io/en/1.1/requirements.html
|-- README  # 项目说明文件

常用模块

time模块

提供三种不同类型且可以互相转化的时间

  • sleep
time.sleep(3)  # 代码暂停3s
  • 时间戳:表示从计算机元年1970年1月1日00:00:00开始到现在的秒数

    import time
    print(time.time())  # 1581236981.5428233
    
    # 时间戳转化为结构化时间
    time_stamp = time.time()
    print(time.localtime(time_stamp))
    
  • 格式化时间:普通的字符串格式的时间

    print(time.strftime('%Y-%m-%d %H:%M:%S'))  # 2020-02-09 16:31:07
    print(time.strftime('%Y-%m-%d %X'))  # 2020-02-09 16:31:26
    
    # 格式化时间转换成结构化时间
    format_time = time.strftime('%Y-%m-%d %H:%M:%S')
    print(time.strptime(format_time,'%Y-%m-%d %H:%M:%S'))
    
  • 结构化时间:struct_time元组共有9个元素共九个元素,分别为(年,月,日,时,分,秒,一年中第几周,一年中第几天,夏令时)

    print(time.localtime())  # 本地时区的struct_time
    # time.struct_time(tm_year=2020, tm_mon=2, tm_mday=9, tm_hour=16, tm_min=33, tm_sec=2, tm_wday=6, tm_yday=40, tm_isdst=0)
    print(time.gmtime())  # UTC时区的本地时区的struct_time
    
    # 结构化时间转化成时间戳
    struct_time = time.localtime(3600*24*365)
    print(time.mktime(struct_time))
    
    # 结构化时间转化成格式化时间
    struct_time = time.localtime(3600*24*365)
    print(time.strftime('%Y-%m-%d %H:%M:%S',struct_time))
    

datetime模块

可以看做是时间加减的模块

  1. datetime.now() 当前时间
  2. timedelta(整形)
import datetime

# 返回当前时间
now = datetime.datetime.now()
prtin(now)  # 2020-02-09 16:40:32.631637

# 当前时间+3天
print(now + datetime.timedelta(3))

# 加3周
print(now + datetime.timedelta(week = 3))  

# 加减3小时
print(now + datetime.timedelta(hours = 3))

# 替换时间
print(now.replace(year=1949, month=10, day=1, hour=10, minute=1, second=0, microsecond=0))

random模块

  1. random 随机数(<1)(不顾头不顾尾)
  2. randint 指定范围随机数(顾头顾尾)
  3. randrange 指定范围随机数(顾头不顾尾)
  4. uniform 指定范围随机数(不顾头不顾尾)
  5. shuffle 打乱顺序
  6. choice 随机选择
  7. seed 只随机一次
  8. sample 指定随机抽取多少个
import random

# (0,1)随机数(大于0且小于1)
print(random.random())

# [1,3]的随机数(大于等于1且小于等于3)
print(random.randit(1,3))

# [1,3)的随机数(大于等于1且小于3)
print(random.uniform(1, 3))

# 打乱顺序
lt = [1,2,3]
random.shuffle(lt)
print(lt)

# 随机选择一个
print(random.choice(lt))

# 只随机一次
random.seed(1) # 根据传入的数字进行类似梅森旋转算法得出随机数

import time
random.seed(time.time())  # 根据时间得出随机数,每一次都会一样

# 了解
print(random.sample([1,2,3,'a'],2)) # 在列表中随机抽取2个数

os模块

os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口,多用于文件处理

  • 对文件操作

    # 1. isfile:判断是否为文件
    import os
    res = os.path.isfile(r'D:\回顾.md')
    print(res)
    
    # 2. remove:删除文件
    os.remove(r'')
    
    # 3. rename:重命名文件
    os.rename(r'', r'')
    
  • 对文件夹操作

    # 1. isdir:判断是否为文件夹
    os.path.isdir()
    
    # 2. mkdir:创建文件夹
    os.mkdir(r'D:\test')
    
    # 3. rmdir:删除文件夹
    os.rmdir(r'D:\test')
    
    # 4. listdir:列出文件夹内所有文件
    res = os.listdir(r'D:\test')
    print(res)
    
  • 其他

    # 1. getcwd:执行文件所在的文件夹
    res = os.getcwd()
    print(res)
    
    # 2. __file__:当前文件所在的具体路径
    #    abspath:根据不同的操作系统该更换不同的\或/
    print('__file__:', __file__)
    res = os.path.abspath(__file__)  
    print(res)
    
    # 3. dirname:返回路径所在的文件夹路径
    res = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    print(res)
    
    # 4. join:拼接文件路径
    res = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img', 'test.jpg')
    print(res)
    
    # 5. exists:判断路径是否存在(文件或文件夹都适用) 
    res = os.path.exists(r'D:\模块.py')
    print(res)
    
  • system:在终端执行代码

    res = os.system('dir')
    print(res)
    
  • 方法列表

    方法 详解
    os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
    os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
    os.curdir 返回当前目录: ('.')
    os.pardir 获取当前目录的父目录字符串名:('..')
    os.makedirs('dirname1/dirname2') 可生成多层递归目录
    os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
    os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname
    os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
    os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
    os.remove() 删除一个文件
    os.rename("oldname","newname") 重命名文件/目录
    os.stat('path/filename') 获取文件/目录信息
    os.sep 输出操作系统特定的路径分隔符,win下为"",Linux下为"/"
    os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
    os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为:
    os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
    os.system("bash command") 运行shell命令,直接显示
    os.environ 获取系统环境变量
    os.path.abspath(path) 返回path规范化的绝对路径
    os.path.split(path) 将path分割成目录和文件名二元组返回
    os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
    os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
    os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
    os.path.isabs(path) 如果path是绝对路径,返回True
    os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
    os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
    os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
    os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间
    os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间

sys模块

sys模块负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境

import sys
# 1. argv:使用命令行式运行文件时,接受多余的参数
res,res1... = sys.argv  # 可以为多个

# 2. modules.keys:当前导入的模块中的所有方法 
print(sys.modules.keys())

# 3. 标准输出流:控制台白色字体打印
sys.stdout.write('123\n') # == print

# 4. 标准输入流:获取键盘输入信息,包括换行符
res  = sys.stdin.readline()
print(res)

# 5. 标准错误流:控制台红色字体打印  
sys.stderr.write('xyz\n')
方法 详解
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.modules.keys() 返回所有已经导入的模块列表
sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback当前处理的异常详细信息
sys.exit(n) 退出程序,正常退出时exit(0)
sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.maxunicode 最大的Unicode值
sys.modules 返回系统导入的模块字段,key是模块名,value是模块
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout 标准输出
sys.stdin 标准输入
sys.stderr 错误输出
sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息
sys.exec_prefix 返回平台独立的python文件安装的位置
sys.byteorder 本地字节规则的指示器,big-endian平台的值是'big',little-endian平台的值是'little'
sys.copyright 记录python版权相关的东西
sys.api_version 解释器的C的API版本

json和pickle模块

  • 序列化:把对象(变量)从内存中变成可存储或传输的过程称之为序列化,优点是持久保存状态和跨平台数据交互

  • json模块:json序列化并不是python独有的,在java等语言中也会涉及到,python中的json模块则可以完成序列化的处理,从而达到跨平台传输数据的目的

    • 注意:python3.5之前的版本只能反序列化字符串,python3.6之后可以反序列化bytes格式和字符串

      import json
      dic = [1, (1, 2)]
      

    1. dumps和loads是针对运行时内存中的传输转换 json串中没有单引号

    res = json.dumps(dic) # 内存中序列化
    dic1 = json.loads(res) # 内存中反序列化

    2. dump和load时针对存储在文件中时的传输转换

    序列化

    with open('test.json', 'w', encoding='utf8') as fw:

    json.dump(dic, fw)
    
    # 反序列化    
    with open('test.json', 'r', encoding='utf8') as fr:
        data = json.load(fr)
        print(type(data), data)
    
  • json数据类型和python数据类型对应关系表
    只能存数字,字符串,列表,字典,布尔值,None.不能存集合等

    Json类型 Python类型
    {} dict
    [] list
    "string" str
    520.13 int或float
    true/false True/False
    null None
  • pickle模块:pickle模块只能用于python,并且不同版本的python可能都不兼容,优点是支持python所有数据类型,包括对象,用法和json模块一样

    def func():  # 针对地址而言,只存了一个函数名
        print('func')
    
    with open('test.pkl','wb') as fw:
        pickle.dump(func,fw)
    

hashlib和hmac模块

  • hash算法
    hash是一种算法,该算法接受传入的内容,经过运算得到一串hash值
    hash的特点:

    1. 只要传入内容一样,就能得到同样的hash值,可用于非明文密码校验
    2. 不能有hash值反解成内容,保证了非明文密码的安全性
    3. 只要使用的hash算法不变,无论内容多大,达到的hash值长度是固定的,可用于对文本的hash处理
  • hashlib模块

    import hashlib
    m = hashlib.md5()  # 生成hash对象m
    m.update('hello'.encode('utf8'))
    print(m.hexdigest())  # 根据hello生成的密钥
    
  • hmac模块
    hash加密算法虽然看起来很厉害,但是他是存在一定缺陷的,即可以通过撞库可以反解,为了防止密码被撞库,我们可以使用python中的另一个hmac 模块,它内部对我们创建key和内容做过某种处理后再加密

    import hmac
    
    m = hmac.new(b'wick')  # 加的密钥
    m.update(b'hash123456')  # 本身密码生成的密钥
    
    # 最终存储的密钥是用户输入的密码生成的密钥加上内置密钥,更加难以破解
    

logging模块

日志总共分为以下五个级别,这个五个级别自下而上进行匹配 debug-->info-->warning-->error-->critical,默认最低级别为warning级别。

  • 打印日志

    import logging
    
    logging.debug('调试信息')
    logging.info('正常信息')
    logging.warning('警告信息')
    logging.error('报错信息')
    logging.critical('严重错误信息')
    
  • 日志写入文件

    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('调试信息')  # 10
    logging.info('正常信息')  # 20
    logging.warning('警告信息')  # 30
    logging.error('报错信息')  # 40
    logging.critical('严重错误信息')  # 50
    
    '''
    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参数中可能用到的格式化串:
    
    %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
    %(name)s Logger的名字
    %(levelname)s 文本形式的日志级别
    %(module)s 调用日志输出函数的模块名
    %(message)s用户输出的消息
    %(levelno)s 数字形式的日志级别
    %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
    %(filename)s 调用日志输出函数的模块的文件名
    %(funcName)s 调用日志输出函数的函数名
    %(lineno)d 调用日志输出函数的语句所在的代码行
    %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
    %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
    %(thread)d 线程ID。可能没有
    %(threadName)s 线程名。可能没有
    %(process)d 进程ID。可能没有
    '''
    
  • 日志对象
    logging模块包含四种角色:logger、Filter、Formatter对象、Handler

    1. logger:产生日志的对象
    2. Filter:过滤日志的对象
    3. Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式
    4. Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端
    '''
    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') # 打印到文件
    sm = 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)
sm.setFormatter(formmater3)

6、将Handler添加给logger并设置日志级别

logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(sm)

设置日志级别,可以在两个关卡进行设置logger与handler

logger是第一级过滤,然后才能到handler

logger.setLevel(30)
h1.setLevel(10)
h2.setLevel(10)
sm.setLevel(10)

7、测试

logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')

- **日志配置文件**
```python 
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()指定的名字;lineno为调用日志输出函数的语句所在的代码行
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.dirname(os.path.abspath(__file__)))  # log文件的目录,需要自定义文件路径 # atm
logfile_dir = os.path.join(logfile_dir, 'log')  # C:\Users\oldboy\Desktop\atm\log

logfile_name = 'log.log'  # log文件名,需要自定义路径名

# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):  # C:\Users\oldboy\Desktop\atm\log
    os.mkdir(logfile_dir)

# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)  # C:\Users\oldboy\Desktop\atm\log\log.log
# 定义日志路径 结束

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},  # filter可以不定义
    'handlers': {
        # 打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'INFO',
            '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配置。如果''设置为固定值logger1,则下次导入必须设置成logging.getLogger('logger1')
        '': {
            # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'handlers': ['default', 'console'],
            'level': 'DEBUG',
            'propagate': False,  # 向上(更高level的logger)传递
        },
    },
}

def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置
    logger = logging.getLogger(__name__)  # 生成一个log实例
    logger.info('It works!')  # 记录该文件的运行状态

    return logger

if __name__ == '__main__':
    load_my_logging_cfg()
  • 使用日志

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

- **Django日志配置文件**
```python 
# logging_config.py

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        # 打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 3,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        # 打印到文件的日志:收集错误及以上的日志
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        # 打印到文件的日志
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {
        # logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console', 'error'],
            'level': 'DEBUG',
            'propagate': True,
        },
        # logging.getLogger('collect')拿到的logger配置
        'collect': {
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}

# -----------
# 用法:拿到俩个logger

logger = logging.getLogger(__name__)  # 线上正常的日志
collect_logger = logging.getLogger("collect")  # 领导说,需要为领导们单独定制领导们看的日志

re模块

在字符串中筛选出符合某种特点的字符串

元字符

import re

# 1 ^ 以...开头
s = 'abc红花dabc'
print(re.findall('^ab',s))  # ['ab'] 按照\n分隔找元素

# 2 $ 以...结尾
print(re.findall('bc$',s))  # ['bc'] 根据\n判断结尾 

# 3 . 代表任意字符(不匹配换行符)
print(re.findall('abc.',s))  # ['abc红'] 

# 4 \d 数字
s = 'asd313ew'
print(re.findall('\d',s))  # ['3','1','3']

# 5 \w 数字/字母/下划线
s = 'af 1*-@ f_'
print(re.findall('\w',s))  # ['a','f','1','f','_']

# 6 \s 空格,\t,\n
s = 'sdfa 324_sa#$@'
print(re.findall('\s',s))  # [' ']

# 7 \D 非数字
s = 'a32s 3f '
print(re.findall('\D',s))  # ['a','s',' ,'f',' ']

# 8 \W 除了字母/数字/下划线
s = 'sk-#l@d_23 42ljk'
print(re.findall('\W', s))  # ['-','#','@',' ']

# 9 \S 非空
s = 'skld_23 42'
print(re.findall('\S', s))  # ['s', 'k', 'l', 'd', '_', '2', '3', '4', '2']

# 10 + 前面一个字符至少有1个
s = 'abcddddd abcd abc'
print(re.findall('abcd+', s))  # ['abcddddd', 'abcd']

# 11 ? 前面的一个字符0或者1个
print(re.findall('abcd?', s))  # ['abcd','abcd','abc']

# 12 * 前面的一个字符至少0个
s = 'abcdddddddddddddddddd abcd abc'
print(re.findall('abcd*', s))  # ['abcdddddddddddddddddd', 'abcd', 'abc']

# 13 [] 中括号内随便取一个都可以
s = 'abc bbc cbc dbc'
print(re.findall('[abc]bc', s))  # ['abc', 'bbc', 'cbc']

# 14 [^]: 中括号的都不可以
s = 'abc bbc cbc dbc'
print(re.findall('[^abc]bc', s))  # ['dbc']

# 15 |:或
s = 'abc bbc dbc'
print(re.findall('abc|bbc', s))  # ['abc','bbc']

# 16 {2} 前面的字符2个
s = 'abccabc abccc'
print(re.findall('abc{2}', s))  # ['abcc', 'abcc']
print(re.findall('abc{1,2}', s))  # ['abcc', 'abc', 'abcc'],前面的字符1或2个

正则表达式的贪婪模式和非贪婪模式

正则表达式本身是一种小型的、高度专业化的编程语言,它并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。

# 1. 贪婪模式:能匹配的最大部分,会一直查找到最后一个符合条件的字符
#     .* 任意字符 + 至少0个
s = 'abcdefgbbg'
print(re.findall('a.*g', s))  # 包括'a'和最后一个'g'之间的所有字符,['abcdefgbbg']

# 2.  非贪婪模式:匹配越少越好,会匹配到第一个符合条件的字符就停止
#     .*? 任意字符 + 至少0个 + 前一字符0或1个(控制进入非贪婪)
s = 'abcdefgbbg'
print(re.findall('a.*?g', s))  # 包括'a'和第一个'g'之间的所有字符['abcdefg']  

常用函数

import re

# 1. compile:Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象
s = 'abcd abcddd abc'
pattern = re.compile('abcd*')

print(pattern.findall(s))  # ['abcd', 'abcddd', 'abc']
print(re.findall(pattern, s))  # ['abcd', 'abcddd', 'abc']

# 2. match 只检测字符串开头位置是否匹配,匹配成功才会返回结果,否则返回None
s1 = 'abcabcabc'
print(re.match('abc', s1))  # <_sre.SRE_Match object; span=(0, 3), match='abc'>
print(re.match('abc', s1).group())  # abc,找不到会报错
print(re.match('abc', s1).span()) # (0, 3),返回索引

# 3. search 从字符串中查找,找到就不找了
s1 = 'abcabcabc'
print(re.search('bca', s1))  # <_sre.SRE_Match object; span=(1, 4), match='bca'>
print(re.search('bca', s1).group())  # bca,找不到会报错
print(re.search('bca', s1).span())  # (1, 4)

# 4. findall 匹配所有合规则的字符串,返回列表
print(re.findall("\d+\w\d+", "a2b3c4d5"))  # ['2b3', '4d5']

# 5. split 切割,返回列表
s = 'ab23423abcddd234234abcasdfjl'
print(re.split('\d+', s))  # ['ab', 'abcddd', 'abcasdfjl']

# 6. sub 替换
s = 'ab23423abcddd234234abcasdfjlasjdk'
print(re.sub('\d+', ' ', s))  # 'ab abcddd abcasdfjlasjdk'

# 7. subn 替换,替换次数
s = 'ab23423abcddd234234abcasdfjlasjdk'
print(re.subn('\d+', ' ', s))  # ('ab abcddd abcasdfjlasjdk',2)

正则匹配模式(修饰符)

import re

# 1. re.S 全局匹配,匹配换行符
s = '''
abc
abcabc*abc
'''
print(re.findall('abc.abc', s))  # ['abc*abc'],.默认不匹配换行
print(re.findall('abc.abc', s, re.S))  # ['abc\nabc', 'abc*abc']

# 2. re.I 不区分大小写
print(re.findall(r"A", "abc", re.I))  # a

# 3. re.M 多行匹配,让^匹配每行的开头,$匹配每行的结尾
s = '12 34\n56 78\n90'
print(re.findall('^\d+',s,re.M))  # ['12', '56', '90']

分组函数

import re
# 1. () 分组,只要括号里面的内容
s = 'abc abcd abcdd'
print(re.findall('a(.)c(d)', s))  # [('b', 'd'), ('b', 'd')]

# 2. ?P<name> 有名分组
''' 
  group()   获取匹配到的所有结果,不管有没有分组将匹配到的全部拿出来,有参取匹配到的第几个如2
  groups()   获取模型中匹配到的分组结果,只拿出匹配到的字符串中分组部分的结果
  groupdict()   获取模型中匹配到的分组结果,只拿出匹配到的字符串中分组部分定义了key的组结果
'''
s = 'abc abcd abcdd'
print(re.search('a(?P<name>.)c(?P<name2>d)', s).groupdict())  # {'name': 'b', 'name2': 'd'}
print(re.search('a(?P<name>.)c(?P<name2>d)', s).group())  # abcd
print(re.search('a(?P<name>.)c(?P<name2>d)', s).groups())  # ('b', 'd')

分组替换

import re
s = 'abc123abc123'  # c123a

# 1. 无名分组替换
print(re.sub('c(\d+)a', ' ', s))  # ab bc123

# 2. 有名分组替换 \g<name1>,只替换有名分组以外的内容
print(re.sub('c(?P<name1>\d+)a', 'A\g<name1>B', s))   # abA123Bbc123

typing模块

Python是一门弱类型的语言,不需要提前声明变量类型,会导致阅读代码时不知道参数类型,该模块可以规定参数类型和提供基础数据之外的数据类型

# 1. 作用
  '''
  - 类型检查,防止运行时出现参数和返回值类型不符合。
  - 作为开发文档附加说明,方便使用者调用时传入和返回参数类型
  - 该模块加入后并不会影响程序的运行,不会报正式的错误,只有提醒。
  注意:typing模块只有在python3.5以上的版本中才可以使用,pycharm目前支持typing检查
  '''

# 2. 使用     
from typing import Iterable,Iterator,Generator,Tuple
def func(x:int, y: Iterable)->Tuple:  # 传入x为int类型,y为可迭代对象,返回元组
    return (1,2,3,4)
func(10,'qwer')

traceback模块

获取详细的错误信息

import traceback
try:
    int('ss')
except Exception as e:
    print(traceback.format_exc())

exec模块

python的内置模块,可以把“字符串形式”的python代码,添加到全局名称空间或局部名称空间中

# 1. 对全局名称空间中的值进行修改
## 1. 文本形式的python代码
code = '''
global x
x=10
y=20
'''

## 2. 全局名称空间 {}
global_dict={'x':200}

## 3. 局部名称空间 {}
local_dict={}

exec(code,global_dict,local_dict)
print(global_dict)

# 2. 局部名称空间
## 1. 文本形式的python代码
code = '''
x=100
y=200
def func():
    pass
'''

## 2. 全局名称空间 {}
global_dict={'x':200}

## 3. 局部名称空间 {}
local_dict={}

exec(code,global_dict,local_dict)
print(local_dict)

subprocess模块

  1. 可以通过代码执行操作系统的终端命令
  2. 并返回终端执行命令的结果
import subprocess

# 1. 用法1
cmd = input('cmd>>>:')
obj = subprocess.popen(
    cmd,  # cmd命令
    Shell = True,
    stdout = subprocess.PIPE, # 返回正确结果
    stderr = subprocess.PIPE  # 返回错误结果
)

data1 = obj.stdout.read()
data2 = obj.stderr.read()
result = data1 + data2

print(result.decode('gbk'))

# 2, 用法2
import subprocess
res = subprocess.getoutput('ipconfig')
print(res)

paramiko模块

介绍

'''
1. paramiko模块
  - 可以通过用户名、密码和公钥私钥的方式连接操作服务器
  - ansible用来批量管理远程服务器,底层其实用的就是paramiko模块

2. 安装
  - pip install paramiko
'''

执行命令

# 1.  用户名和密码连接的方式
import paramiko
ssh = paramiko.SSHClient()  # 创建对象
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # 允许链接不在know_hosts文件的主机
ssh.connect(hostname='172.16.219.173',port=22,username='root',password='jason123')

# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls /')
"""
stdin用来输入额外的命令 
    yum install ansible  额外的命令-y
stdout命令的返回结果  正确
stderr命令的返回结果  错误
"""

res = stdout.read()  # 网络传输过来的二进制数据
print(res.decode('utf-8'))

ssh.close()  # 关闭链接


# 2. 公钥和私钥(先将公钥保存到服务器上)的连接方式
import paramiko
ssh = paramiko.SSHClient()  # 创建SSH对象
private_key = paramiko.RSAKey.from_private_key_file('a.txt')  # 读取本地私钥(文件读取)
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # 允许连接不在know_hosts文件中主机
ssh.connect(hostname='172.16.219.173', port=22, username='root', pkey=private_key)

# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls /')

# 获取命令结果
result = stdout.read()
print(result.decode('utf-8'))

# 关闭连接
ssh.close()

上传下载文件

# 1. 用户名和密码连接服务器
import paramiko
transport = paramiko.Transport(('172.16.219.173', 22))
transport.connect(username='root', password='jason123')
sftp = paramiko.SFTPClient.from_transport(transport)

# 上传文件
sftp.put("a.txt", '/data/b.txt')  # 注意上传文件到远程某个文件下 文件必须存在

# 下载文件
sftp.get('/data/b.txt', 'c.txt')  # 将远程文件下载到本地并重新命令
transport.close()


# 2. 公钥私钥连接服务器
import paramiko
private_key = paramiko.RSAKey.from_private_key_file('c.txt')
transport = paramiko.Transport(('172.16.219.173', 22))
transport.connect(username='root', pkey=private_key)
sftp = paramiko.SFTPClient.from_transport(transport)

# 将location.py 上传至服务器 /tmp/test.py
sftp.put('manage.py', '/data/temp.py')

# 将remove_path 下载到本地 local_path
sftp.get('remove_path', 'local_path')
transport.close()

基于with上下文的类的封装

import paramiko
class SSHProxy(object):
    def __init__(self, hostname, port, username, password):
        self.hostname = hostname
        self.port = port
        self.username = username
        self.password = password

    def open(self):  # 给对象赋值一个上传下载文件对象连接
        self.transport = paramiko.Transport((self.hostname, self.port))
        self.transport.connect(username=self.username, password=self.password)

    def command(self, cmd):  # 正常执行命令的连接  至此对象内容就既有执行命令的连接又有上传下载链接
        ssh = paramiko.SSHClient()
        ssh._transport = self.transport  # 由源码得知,ssh连接也是通过_transport,以此减少连接次数

        stdin, stdout, stderr = ssh.exec_command(cmd)
        result = stdout.read().decode('utf8')
        return result

    def upload(self, local_path, remote_path):
        sftp = paramiko.SFTPClient.from_transport(self.transport)
        sftp.put(local_path, remote_path)
        sftp.close()

    def close(self):
        self.transport.close()

    def __enter__(self):  # 对象执行with上下文会自动触发
        self.open()
        return self  # 这里发挥上面with语法内的as后面拿到的就是什么

    def __exit__(self, exc_type, exc_val, exc_tb):  # with执行结束自动触发
        self.close()


# 使用方法   
if __name__ == '__main__': 
    with SSHProxy('172.16.219.173',22,'root','jason123') as ssh:
        ssh.command()
        ssh.command()
        ssh.command()
        ssh.upload()

gitpython模块

基本使用

# 安装:pip install gitpython  
from git.repo import Repo
import os
local_path = os.path.join('jason', 'NB')

# 1. clone代码
Repo.clone_from('https://github.com/DominicJi/TeachTest.git', to_path=local_path, branch='master')

# 2. pull最新代码
repo = Repo(local_path)
repo.git.pull()

# 3. 获取所有分支
repo = Repo(local_path)

branches = repo.remote().refs
for item in branches:
    print(item.remote_head)

# 4. 获取所有版本
repo = Repo(local_path)

for tag in repo.tags:
    print(tag.name)

# 5. 获取所有commit    
repo = Repo(local_path)

# 将所有提交记录结果格式成json格式字符串 方便后续反序列化操作
commit_log = repo.git.log('--pretty={"commit":"%h","author":"%an","summary":"%s","date":"%cd"}', max_count=50,
                          date='format:%Y-%m-%d %H:%M')
log_list = commit_log.split("\n")
real_log_list = [eval(item) for item in log_list]
print(real_log_list)

# 6. 切换分支
repo = Repo(local_path)

before = repo.git.branch()
print(before)
repo.git.checkout('master')
after = repo.git.branch()
print(after)
repo.git.reset('--hard', '854ead2e82dc73b634cbd5afcf1414f5b30e94a8')

# 7. 打包代码
with open(os.path.join('jason', 'NB.tar'), 'wb') as fp:
    repo.archive(fp)

封装版本

import os
from git.repo import Repo
from git.repo.fun import is_git_dir
class GitRepository(object):
    """
    git仓库管理
    """

    def __init__(self, local_path, repo_url, branch='master'):
        self.local_path = local_path
        self.repo_url = repo_url
        self.repo = None
        self.initial(repo_url, branch)

    def initial(self, repo_url, branch):
        """
        初始化git仓库
        :param repo_url:
        :param branch:
        :return:
        """
        if not os.path.exists(self.local_path):
            os.makedirs(self.local_path)

        git_local_path = os.path.join(self.local_path, '.git')
        if not is_git_dir(git_local_path):
            self.repo = Repo.clone_from(repo_url, to_path=self.local_path, branch=branch)
        else:
            self.repo = Repo(self.local_path)

    def pull(self):
        """
        从线上拉最新代码
        :return:
        """
        self.repo.git.pull()

    def branches(self):
        """
        获取所有分支
        :return:
        """
        branches = self.repo.remote().refs
        return [item.remote_head for item in branches if item.remote_head not in ['HEAD', ]]

    def commits(self):
        """
        获取所有提交记录
        :return:
        """
        commit_log = self.repo.git.log('--pretty={"commit":"%h","author":"%an","summary":"%s","date":"%cd"}',
                                       max_count=50,
                                       date='format:%Y-%m-%d %H:%M')
        log_list = commit_log.split("\n")
        return [eval(item) for item in log_list]

    def tags(self):
        """
        获取所有tag
        :return:
        """
        return [tag.name for tag in self.repo.tags]

    def change_to_branch(self, branch):
        """
        切换分值
        :param branch:
        :return:
        """
        self.repo.git.checkout(branch)

    def change_to_commit(self, branch, commit):
        """
        切换commit
        :param branch:
        :param commit:
        :return:
        """
        self.change_to_branch(branch=branch)
        self.repo.git.reset('--hard', commit)

    def change_to_tag(self, tag):
        """
        切换tag
        :param tag:
        :return:
        """
        self.repo.git.checkout(tag)



if __name__ == '__main__':
    local_path = os.path.join('codes', 'luffycity')
    repo = GitRepository(local_path,remote_path)
    branch_list = repo.branches()
    print(branch_list)
    repo.change_to_branch('dev')
    repo.pull()

gojs插件

介绍

'''
1. 一个前端插件,可以通过代码动态的生成流程图
  - 官网:https://gojs.net/latest/index.html

2. 官网下载的js文件
  - go.js      正常要导入的文件
  - Figures.js   扩展图表(go.js自带的图表比较少,如果出现图标显示不出来的情况)
  - go-debug.js   会展示报错消息 类似于debug模式 线上肯定不会使用
  使用时导入go.js和Figures.js即可
'''

基本使用

先用div在页面上划定区域,之后所有的gojs图表渲染全部在该div内部进行

<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div>

<script src="go.js"></script>
<script>
  var $ = go.GraphObject.make;
  // 1. 创建图表
  var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图

  // 2. 创建一个节点,内容为jason
  var node = $(go.Node, $(go.TextBlock, {text: "jason"}));

  // 3. 将节点添加到图表中
  myDiagram.add(node)
</script>

流程图样式

'''
- TextBlock:创建文本
- Shape:图形
- Node:节点(文本与图形结合)
- Link:箭头
'''

TextBlock

text:文本内容

stroke:字体颜色

<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div>
<script src="go.js"></script>
<script>
    var $ = go.GraphObject.make;
    // 第一步:创建图表
    var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图

    var node1 = $(go.Node, $(go.TextBlock, {text: "jason"}));
    myDiagram.add(node1);
    var node2 = $(go.Node, $(go.TextBlock, {text: "jason", stroke: 'red'}));
    myDiagram.add(node2);
    var node3 = $(go.Node, $(go.TextBlock, {text: "jason", background: 'lightblue'}));
    myDiagram.add(node3);
</script>

Shape

figure:图形形状

fill:填充颜色

stroke:边框颜色

<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div>
<script src="go.js"></script>
<script src="Figures.js"></script>
<script>
    var $ = go.GraphObject.make;
    // 第一步:创建图表
    var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图

    var node1 = $(go.Node,
        $(go.Shape, {figure: "Ellipse", width: 40, height: 40})
    );
     myDiagram.add(node1);

     var node2 = $(go.Node,
        $(go.Shape, {figure: "RoundedRectangle", width: 40, height: 40, fill: 'green',stroke:'red'})
    );
    myDiagram.add(node2);

    var node3 = $(go.Node,
        $(go.Shape, {figure: "Rectangle", width: 40, height: 40, fill: null})
    );
    myDiagram.add(node3);

    var node5 = $(go.Node,
        $(go.Shape, {figure: "Club", width: 40, height: 40, fill: 'red'})
    );
    myDiagram.add(node5);
</script>

node(文本+图形)

<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div>
<script src="go.js"></script>
<script src="Figures.js"></script>
<script>
    var $ = go.GraphObject.make;
    // 第一步:创建图表
    var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图

    var node1 = $(go.Node,
         "Vertical",  // 垂直方向
        {
            background: 'yellow',
            padding: 8
        },
        $(go.Shape, {figure: "Ellipse", width: 40, height: 40,fill:null}),
        $(go.TextBlock, {text: "jason"})
    );
    myDiagram.add(node1);

    var node2 = $(go.Node,
        "Horizontal",  // 水平方向
        {
            background: 'white',
            padding: 5
        },
        $(go.Shape, {figure: "RoundedRectangle", width: 40, height: 40}),
        $(go.TextBlock, {text: "jason"})
    );
    myDiagram.add(node2);

    var node3 = $(go.Node,
        "Auto",  // 居中
        $(go.Shape, {figure: "Ellipse", width: 80, height: 80, background: 'green', fill: 'red'}),
        $(go.TextBlock, {text: "jason"})
    );
    myDiagram.add(node3);

</script>

link(连接箭头)

<div id="myDiagramDiv" style="width:500px; min-height:450px; background-color: #DAE4E4;"></div>
    <script src="go.js"></script>
    <script>
        var $ = go.GraphObject.make;

        var myDiagram = $(go.Diagram, "myDiagramDiv",
            {layout: $(go.TreeLayout, {angle: 0})}
        ); // 创建图表,用于在页面上画图

        var startNode = $(go.Node, "Auto",
            $(go.Shape, {figure: "Ellipse", width: 40, height: 40, fill: '#79C900', stroke: '#79C900'}),
            $(go.TextBlock, {text: '开始', stroke: 'white'})
        );
        myDiagram.add(startNode);

        var downloadNode = $(go.Node, "Auto",
            $(go.Shape, {figure: "RoundedRectangle", height: 40, fill: 'red', stroke: '#79C900'}),
            $(go.TextBlock, {text: '下载代码', stroke: 'white'})
        );
        myDiagram.add(downloadNode);

        var startToDownloadLink = $(go.Link,
            {fromNode:downloadNode, toNode:startNode,},
            $(go.Shape, {strokeWidth: 1}),
            $(go.Shape, {toArrow: "OpenTriangle", fill: null, strokeWidth: 1})
        );
        myDiagram.add(startToDownloadLink);
    </script>

数据动态绑定

<div id="diagramDiv" style="width:100%; min-height:450px; background-color: #DAE4E4;"></div>

    <script src="go.js"></script>
    <script>
        var $ = go.GraphObject.make;
        var diagram = $(go.Diagram, "diagramDiv",{
            layout: $(go.TreeLayout, {
                angle: 0,
                nodeSpacing: 20,
                layerSpacing: 70
            })
        });
        // 1. 生成一个节点模版
        diagram.nodeTemplate = $(go.Node, "Auto",
            $(go.Shape, {
                figure: "RoundedRectangle",
                fill: 'yellow',
                stroke: 'yellow'
            }, new go.Binding("figure", "figure"), new go.Binding("fill", "color"), new go.Binding("stroke", "color")),
            $(go.TextBlock, {margin: 8}, new go.Binding("text", "text"))
        );
        // 生成一个箭头模版
        diagram.linkTemplate = $(go.Link,
            {routing: go.Link.Orthogonal},
            $(go.Shape, {stroke: 'yellow'}, new go.Binding('stroke', 'link_color')),
            $(go.Shape, {toArrow: "OpenTriangle", stroke: 'yellow'}, new go.Binding('stroke', 'link_color'))
        );
        // 数据集合  以后替换ajax请求   注意使用key和parent来规定箭头的指向
        var nodeDataArray = [
            {key: "start", text: '开始', figure: 'Ellipse', color: "lightgreen"},
            {key: "download", parent: 'start', text: '下载代码', color: "lightgreen", link_text: '执行中...'},
            {key: "compile", parent: 'download', text: '本地编译', color: "lightgreen"},
            {key: "zip", parent: 'compile', text: '打包', color: "red", link_color: 'red'},
            {key: "c1", text: '服务器1', parent: "zip"},
            {key: "c11", text: '服务重启', parent: "c1"},
            {key: "c2", text: '服务器2', parent: "zip"},
            {key: "c21", text: '服务重启', parent: "c2"},
            {key: "c3", text: '服务器3', parent: "zip"},
            {key: "c31", text: '服务重启', parent: "c3"},
        ];
        diagram.model = new go.TreeModel(nodeDataArray);

        // 2. 动态控制节点颜色变化   后端给一个key值 即可查找图表并修改
        var node = diagram.model.findNodeDataForKey("zip");
        diagram.model.setDataProperty(node, "color", "lightgreen");
    </script>

去除gojs自带的水印

'''
需要修改js文件源码
1. 查找js文件中固定的字符串
  - 7eba17a4ca3b1a8346

2. 注释该字符串所在的一行代码,并添加一行新的代码  
  - /*a.yr=b.V[Ra("7eba17a4ca3b1a8346")][Ra("78a118b7")](b.V,Jk,4,4);*/a.yr=function(){return false};
  注意:版本不同,a.yr也可能是a.kr,保持一致修改后面的值即可
'''

Queue模块(队列)

'''
1. FIFO队列(Queue())
  - 遵循先进先出

2. LIFO队列(lifoQueue())
  - 遵循后进后出

3. 优先级队列(PriorityQueue())
  - 首先根据第一个参数判断ascii表的数值大小
  - 判断第二个参数中的汉字顺序
  - 再判断第二参数中数字--> 字符串数字 ---> 中文
  - 以此类推
'''

# 1. FIFO队列
import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

# 2. LIFO队列
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''

# 3. PriorityQueue()
import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

shutil模块

压缩文件

import shutil  # 压缩模块
package_path = shutil.make_archive(
                    base_name=os.path.join(package_folder,uid+'.zip'),
                    format='zip',  # tar
                    root_dir=upoad_folder_path
                )

channels模块(websocket)

并不是所有的后端框架默认都支持websocket,包括Django,因此需要安装第三方模块channels

websocet协议介绍

基于websocket协议

'''
1. 简介
  - http/https协议
    - http是不加密传输,https是加密传输
    - 都是基于请求响应的短连接
  - websocket协议
    - 加密传输
    - 浏览器与服务器全双工通信的长连接,建立连接后默认不断开
  websocket协议真正意义上实现了服务端实时推送信息

2. 原理(两个部分)
  - 握手环节
    1. 浏览器基于http协议访问服务端
      浏览器生成一个随机字符串,保存后给服务端也发送一份(请求头携带)

    2. 服务端、客户端字符串加密
          服务端获取字符串后,将字符串和magic string(固定的随机字符串,全球统一)拼接
          拼接后通过sha1/base64进行数据加密
            与此同时客户端也做相同的操作
            服务端将处理好的随机字符串再次发送给浏览器(响应头)
            浏览器会比对自己生成的随机字符串和服务端发送的随机字符串是否一致,如果一直说明支持websocket协议,如果不一致则会报错不支持

  - 收发数据   
    收发数据都是基于网络加密传输,使用二进制格式,以下为数据解密原理
      1. 读取第二个字节的后七位二进制数,转换成十进制后获得payload
      2. 根据payload做处理
        - 值为127,则继续读8个字节
        - 值为126,则继续读2个字节
        - 值<=125,则不再往后读取
    3. 在步骤2的基础上往后读4个字节的数据获得masking_key
        4. 根据masking_key计算出真实数据
  关键字:magic string、sha1/base64、127、126、125、payload、 masking-key     

3.  访问
  - 基于websocket的访问无法通过浏览器地址栏
  - 要通过利用js内置对象访问
    var ws = new WebSocket('ws://127.0.0.1:8000/chat/');

4. python三大主流web框架对websocket的支持
  - django:默认不支持,第三方模块:channels    
  - flask:默认不支持,第三方模块:geventwebsocket
  - tornado:默认就支持   
'''

代码验证

import socket
import hashlib
import base64

# 正常的socket代码
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止mac/linux在重启的时候 报端口被占用的错
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)


sock.bind(('127.0.0.1', 8080))
sock.listen(5)

conn, address = sock.accept()
data = conn.recv(1024)  # 获取客户端发送的消息

def get_headers(data):
    """
    将请求头格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding='utf-8')

    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(' ')) == 3:
                header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
        else:
            k, v = header_list[i].split(':', 1)
            header_dict[k] = v.strip()
    return header_dict

def get_data(info):
    """
    按照websocket解密规则针对不同的数字进行不同的解密处理
    :param info:
    :return:
    """
    payload_len = info[1] & 127
    if payload_len == 126:
        extend_payload_len = info[2:4]
        mask = info[4:8]
        decoded = info[8:]
    elif payload_len == 127:
        extend_payload_len = info[2:10]
        mask = info[10:14]
        decoded = info[14:]
    else:
        extend_payload_len = None
        mask = info[2:6]
        decoded = info[6:]

    bytes_list = bytearray()
    for i in range(len(decoded)):
        chunk = decoded[i] ^ mask[i % 4]
        bytes_list.append(chunk)
    body = str(bytes_list, encoding='utf-8')

    return body

header_dict = get_headers(data)  # 将一大堆请求头转换成字典数据  类似于wsgiref模块
client_random_string = header_dict['Sec-WebSocket-Key']  # 获取浏览器发送过来的随机字符串

magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'  # 全球共用的随机字符串 一个都不能写错
value = client_random_string + magic_string  # 拼接

ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())  # 加密处理

# 响应头
tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://127.0.0.1:8080\r\n\r\n"
response_str = tpl %ac.decode('utf-8')  # 处理到响应头中

# 将随机字符串给浏览器返回回去
conn.send(bytes(response_str, encoding='utf-8'))


# 收发数据
while True:
      data = conn.recv(1024)
      # print(data)

      value = get_data(data)

      print(value)

Django配置

安装channels模块

'''
安装命令:pip3 install channels==2.3
  - 最新版本的channels可能会将Django自动升级为最新版,因此不要下载最新版
  - 官网推荐使用python3.6解释器,该模块内部封装好了上面的握手、解密、加密过程  
'''

注册channels

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'channels'
]

创建py文件

# routing.py
# 固定代码
from channels.routing import ProtocolTypeRouter,URLRouter
from django.conf.urls import url
application = ProtocolTypeRouter({
    'websocket':URLRouter([
        # websocket相关的url与视图函数对应关系
        url(r'^chat/$',文件名.类名)
    ])
})

settings配置参数

# ASGI_APPLICATION = '项目名同名的文件名称.routing.py文件名.application变量名'
ASGI_APPLICATION = 's12_day01.routing.application'

上述三步配置完成后,再次启动django,就会即支持http协议又支持websocket协议,

  1. 关于http的url与视图函数对应关系还是在原来的urls.py中书写
  2. 关于websocket的url与视图函数对应关系则在routing.py中书写

使用方法

应用中新建py文件(consumers.py)

相当于views.py文件

'''
1. websocket_connect
  - 请求websocket链接时触发
  - 需要self.accept()接受客户端连接
  - self.scope:封装了前段所有信息的大字典(cookie、session)
    - self.scope['url_route']['kwargs'].get('task_id')   # 获取url中携带的有名分组参数
    - self.scope['url_route']['args'].get('task_id')    # 无名分组参数
  - async_to_sync(self.channel_layer.group_add)(群组名, self.channel_name)  
    将当前用户添加到群组中
    - 群组名必须是字符串
    - self.channel_name为固定写法,代表当前用户

2. websocket_receive
  - 前端浏览器发送信息时触发
  - self.send(text_data='你好') 发送信息必须指定text_data关键字
  - async_to_sync(self.channel_layer.group_send)(群组名, {'type': 'xxx.yyy','message': '你好'})
    为群组中的每个用户循环调用xxx_yyy方法发送信息
    - type参数:方法名,.分割代表_,xxx.yyy即自定义方法名为xxx_yyy
    - message参数:发送的信息
    注意:如果有多个group_send执行同一个方法,则所有的group_send为单线程执行,会等都执行完才会一次性发送给前端
      如果需要实时展示,可通过开始另外一个线程执行即可

3. websocket_disconnect
  - 断开websocket链接时触发
  -  async_to_sync(self.channel_layer.group_discard)(群组名, self.channel_name)  
    用户断开连接时,从群组中删除
  - raise StopConsumer

注意:以上方法每个链接状态均能使用,视情况而定  
'''

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
# 选用,为每个链接的客户端区分分组
from asgiref.sync import async_to_sync

class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        '''请求websocket链接时触发'''
        print(self.scope)  # 包含前端所有信息的大字典
        self.accept()  # 与客户端建立连接

        async_to_sync(self.channel_layer.group_add)('1', self.channel_name)  
        # 将当前用户添加到群组,第一个必须是字符串,第二是固定写法用户唯一标识



    def websocket_receive(self, message):
        '''前端浏览器发送信息时触发'''
        print(message)  # {'type': 'websocket.receive', 'text': '我是浏览器发送的信息'}
        self.send(text_data='你好')  # 给客户端发送信息

        async_to_sync(self.channel_layer.group_send)('1', {'type': 'xxx.yyy','message': '你好'})

    def xxx_yyy(self,event):
        message = event.get('message')  # 获取message中的数据
        self.send(json.dumps(message))

    def websocket_disconnect(self, message):
        '''断开websocket链接时触发'''
        print('断开链接')

        async_to_sync(self.channel_layer.group_discard)('1', self.channel_name)  
        # 用户断开连接后,从群组中删除

        raise StopConsumer

前端页面home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

<body>
<h1>聊天室:{{ name }}</h1>
<div>
    <input type="text" id="i1">
    <button onclick="SendMSG()">发送</button>
</div>

<script>
    var ws = new WebSocket('ws://127.0.0.1:8000/chat/');

    // 1. 握手环节成功后触发
    ws.onopen = function () {
        console.log('连接成功')
    }

    // 2. 发送数据
    function SendMSG() {
        ws.send($('#i1').val())
    }

    // 3, 服务端发送数据时触发
    ws.onmessage = function (args) {
        alert(args.data)
    }

    // 4. 浏览器断开链接时触发
    ws.onclose = function () {
        ws.close()
    }

</script>
</html>

pymysql模块

基本使用

'''
1. execute(sql,元组):执行sql语句,当有参数时,sql中%s不能加引号
2. fetchall():获取所有数据,返回的是列表套字典
3. fetchone():获取一条数据,返回的是字典
4. fetchmany(size):获取size条数据,返回的是列表套字典
5. commit:当sql中有参数时,必须要使用conn.commit()
6. executemany:新增多条数据
'''
import pymysql

conn = pymysql.connect(host='localhost',user='root',password='wickyo',database='test',charset='utf8')  # 连接数据库

# 创建一个游标对象cursor
# cursor = conn.cursor() # 默认返回的值是元组类型
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)  # 返回的是字典对象


# 1. 查
sql = 'select * from userinfo' 
cursor.execute(sql)

res = cursor.fetchall()  # 获取所有的数据,返回的是列表套字典
res = cursor.fetchone()  # 获取一条数据,返回的是字典类型
res = cursor.fetchmany(12)  # 获取12条数据,返回的是列表套字典
print(res)

# 2. 增
## (1)新增一条数据
# sql = "insert into user (name, password) values ('%s',  '%s')"%(user,pwd)
sql = "insert into user (name, password) values (%s,  %s)"
cursor.execute(sql, ('xxx', 'qwe'))  #!!!注意%s需要去掉引号,因为pymysql会自动为我们加上

## (2)新增多条数据 executemany
data = [
    ('zekai1', 'qwe'),
    ('zekai2', 'qwe1'),
    ('zekai3', 'qwe2'),
    ('zekai4', 'qwe3'),
]           
cursor.executemany(sql, data)
conn.commit() # 必须要加这一句
print(cursor.lastrowid)  # 打印最后一行id

# 3. 修
sql = "update user set name=%s where id=%s"
cursor.execute(sql, ('dgsahdsa', 2))
conn.commit()

# 4. 删
sql = "delete from user where id=%s"
cursor.execute(sql, ('dgsahdsa', 2))
conn.commit()

cursor.close()  # 关闭cursor对象
conn.close()  #关闭数据库里对象

execute()之SQL注入问题

'''
1. 利用mysql的注释符号的漏洞,避开密码验证
  #注释
        当输入的用户名为xxx' or 1=1 #时,下面的例子中sql会变成
            select * from user where name = xxx' or 1=1 # and password=%s,
        后面的内容会被注释掉,会导致sql注入问题

  -- 注释 (--注释后面必须至少有个空格)
      当输入的用户名为xxx' or 1=1 -- 时,下面的例子中sql会变成
            select * from user where name = xxx' or 1=1 -- and password=%s,
        后面的内容会被注释掉,会导致sql注入问题

2.  解决办法      
  - 将参数传给execute,利用模块本身进行一个校验
'''
import pymysql

user = input('请输入用户名:')
password = input('请输入密码:')

conn = pymysql.connect(host='localhost',user='root',password='wickyo',database=  'test',charset='utf8')

cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)

# sql1 = "select * from user where name='%s' and password='%s'" % (user, pwd)
# cursor.execute(sql1)
sql2 = 'select * from user where name = %s and password=%s'
#!!!注意%s需要去掉引号,因为pymysql会自动为我们加上
cursor.execute(sql2,(user,pwd))

res = cursor.fetchall()
print(res)

cursor.close()
conn.close()

if res:
    print('登陆成功')
else:
    print('登录失败')

posted on 2025-12-05 11:19  wickyo  阅读(0)  评论(0)    收藏  举报