Python常用模块大全

time模块

时间戳

  学习时间相关的模块前,了解三个概念。时间戳(毫秒为单位),结构化时间,字符串形式的时间:

img

方法大全

time模块常用操作方法 
直接获取时间格式时间 
time.time() 从Unix诞生日开始算,距离至今经历多少秒(1970.1.1 8:00开始计算)
直接获取结构化格式时间 
time.localtime() 显示本地时间(中国为东八区,上海时间)
time.gmtime() 显示世界UTC时间(与本地时间差8h)
直接获取字符串格式时间 
time.strftime() 显示一个按照指定格式格式化好的本地时间
time.asctime() 显示一个格式化好的本地时间,该格式时间为Linux系统采用
time.ctime() 显示一个格式化好的世界时间,该格式时间为Linux系统采用
转换方法 
time.mktime() 放入一个结构化时间,可以转换为时间戳形式
time.strftime()

放入一个结构化时间,可以按照指定格式转换为字符串形式

可选参数格式 (%Y-%m-%d %H:%M:%S` 或者 %Y-%m-%d %X ),注意可以加入%p代表时间,上午或者下午

time.strptime()

放入一个字符串时间,可以按照指定格式转换为结构化时间

参数默认为:%a %b %d %H:%M:%S %Y(代表可以直接支持time.ctime()或者time.asctime()的转换,详情看示例)

time.localtime() 放入一个时间戳时间,可以按照指定格式转换为本地的结构化时间
time.gmtime() 放入一个时间戳时间,可以按照指定格式转换为世界的结构化时间
其他方法 
time.sleep() 线程推迟指定的时间运行,单位为秒

import time
# ---- 时间戳格式 ----
print(time.time()) # 1590424571.3390846

# ---- 结构化格式 ----
print(time.localtime()) # 如果未指定参数seconds,则默认放入time.time()
print(time.gmtime()) # 如果未指定参数seconds,则默认放入time.time()
"""
执行结果:
time.struct_time(tm_year=2020, tm_mon=5, tm_mday=26, tm_hour=0, tm_min=36, tm_sec=11, tm_wday=1, tm_yday=147, tm_isdst=0)
time.struct_time(tm_year=2020, tm_mon=5, tm_mday=25, tm_hour=16, tm_min=36, tm_sec=11, tm_wday=0, tm_yday=146, tm_isdst=0)
"""

# ---- 结构化时间其他玩法 ----
print(time.localtime().tm_year)  # 只拿到年份 2020

# ---- 字符串格式 ----
print(time.asctime())
print(time.ctime())
print(time.strftime("%Y-%m-%d %H:%M:%S")) # 相当于 : %Y-%m-%d %X
"""
执行结果:
Tue May 26 00:36:11 2020
Tue May 26 00:36:11 2020
2020-05-26 00:36:11
"""

# ==============转换相关=======================

# --- 结构化时间转时间戳 ---
print(time.mktime(time.localtime())) # 1590424571.0
# --- 结构化时间转字符串 ---
print(time.strftime("%Y-%m-%d %X",time.gmtime())) # 如不指定time.strftime()的参数。默认为localtime() Tue May 26 00:36:11 2020

# --- 字符串格式转结构化 ---
print(time.strptime(time.ctime())) # 默认转换格式是%a %b %d %H:%M:%S %Y。刚好可以放ctime或者asctime,因为ctime和asctime支持该种格式。

res = time.strftime("%Y-%m-%d %X")
print(time.strptime(res,"%Y-%m-%d %X")) # 如果放入的是其他格式的字符串时间,则需要按原定格式转回,相当于strftime的逆操作。
"""
执行结果:
time.struct_time(tm_year=2020, tm_mon=5, tm_mday=26, tm_hour=0, tm_min=36, tm_sec=11, tm_wday=1, tm_yday=147, tm_isdst=-1)
time.struct_time(tm_year=2020, tm_mon=5, tm_mday=26, tm_hour=0, tm_min=36, tm_sec=11, tm_wday=1, tm_yday=147, tm_isdst=-1)
"""

# --- 时间戳转本地结构化 ---
print(time.localtime(333333))
# time.struct_time(tm_year=1970, tm_mon=1, tm_mday=5, tm_hour=4, tm_min=35, tm_sec=33, tm_wday=0, tm_yday=5, tm_isdst=0)

# --- 时间戳转UTC时间结构化 ---
print(time.gmtime(333333))
# time.struct_time(tm_year=1970, tm_mon=1, tm_mday=4, tm_hour=20, tm_min=35, tm_sec=33, tm_wday=6, tm_yday=4, tm_isdst=0)

# --- 时间戳转固定的UTC时间字符串格式 ---
print(time.ctime(00))
# print(time.asctime(1)) 注意。无法时间戳转本地字符串格式时间
# Thu Jan  1 08:00:00 1970

# --- 时间戳转本地时间字符串格式 --- 重点!
t = time.time()
struct_lo = time.localtime(t) # 先转为本地结构化时间
res = time.strftime("%Y-%m-%d %X",struct_lo) # 将本地结构化时间转为字符串格式时间
print(res) # 2020-05-26 00:36:11

# --- 时间戳转UTC时间字符串格式 --- 重点!
t = time.time()
struct_gm = time.gmtime(t)
res = time.strftime("%Y-%m-%d %X",struct_gm)
print(res) # 2020-05-25 16:36:11

# --- 字符串格式转时间戳 --- 重点!
str_time = "1998-01-26 00:00:10"
struct_lo = time.strptime(str_time,"%Y-%m-%d %X") # 先转为本地结构化时间
res = time.mktime(struct_lo) # 将本地结构化时间转为时间戳格式
print(res) # 885744010.0

# 总结 #

"""
字符串与时间戳的转换

字符串 ----> 结构化时间(指定是UTC时间还是本地时间) ---> 时间戳

"""
time模块操作演示及时间转换
import time
to_day_time = time.time()
sum_7day_time = to_day_time+7*86400 #七天后的时间 = 天数 乘以 每天的秒数总和
sub_3day_time = to_day_time-3*86400 #三天前的时间

str_to_day_time = time.strftime("%Y-%m-%d %X",time.localtime(to_day_time))
str_sum_7day_time = time.strftime("%Y-%m-%d %X",time.localtime(sum_7day_time))
str_sub_3day_time = time.strftime("%Y-%m-%d %X",time.localtime(sub_3day_time))

print(str_to_day_time) # 2020-05-26 00:57:33 当前时间
print(str_sum_7day_time) # 2020-06-02 00:57:33 七天后的时间
print(str_sub_3day_time) # 2020-05-23 00:57:33 三天前的时间
time模块中实现时间的加减

扩展:结构化时间详解

import time
print(time.localtime())
"""
本地时间参数详解:

time.struct_time(tm_year=2020, tm_mon=5, tm_mday=26, tm_hour=0, tm_min=47, tm_sec=0, tm_wday=1, tm_yday=147, tm_isdst=0)

tm_year     :   年
tm_mon      :   月
tm_mday     :   日
tm_ hour    :   时
tm_min      :   分
tm_sec      :   秒
tm_wday     :   星期几(从0开始计算,星期一是0,星期二是1,以此类推)
tm_yday     :   该年份中第多少天
tm_isdst    :   夏令营时间
"""

datetime模块

方法大全

  该模块主要提供直接获取字符串格式的方法(非asctime的格式),并且对于时间加减上比time模块更加灵活。

datetime模块常用操作方法 
直接获取字符串格式时间 
datetime.now() 返回一个固定格式的本地字符串格式化时间。%Y-%m-%d %X
datetime.utcnow() 返回一个固定格式的UTC字符串格式化时间
时间转换 
date.fromtimestamp() 放入一个时间戳格式的时间,直接转换为字符串格式时间。(如:time.time())
时间加减 
datetime.timedelta() 为一个字符串格式的时间加减时间
时间替换 
datetime对象.replace() 替换一个字符串格式的时间中某部分
import time
import datetime # Ps:与time模块不同的是,datetime获取的是一个datetime对象,而并非Python基本数据类型

# --- 字符串格式 ---
print(datetime.datetime.now()) # 获取本地时间 2020-05-26 13:55:35.758277
print(datetime.datetime.utcnow()) # 获取UTC时间(世界标准时间) 2020-05-26 05:55:35.758277

# --- 时间转换:时间戳转字符串格式 ---
print(datetime.date.fromtimestamp(1111)) # 1970-01-01

# --- 时间加减 --- Ps: 时间加减中不支持年份加减。可用天数365代替

# days: float  天
# seconds: float  秒
# microseconds: float  微秒
# milliseconds: float  毫秒
# minutes: float  分钟
# hours: float    小时
# weeks: float    周

to_day_time = datetime.datetime.now() # 获取当前时间的字符串格式
sum_3day_time = to_day_time + datetime.timedelta(+3) # 当前时间加三天
sub_3day_time = to_day_time + datetime.timedelta(-3) # 当前时间减三天
sum_7hours_time = to_day_time + datetime.timedelta(hours= +7) # 当前时间加7小时
sub_3hours_time = to_day_time + datetime.timedelta(hours= -3) # 当前时间减7小时

print(to_day_time)  # 2020-05-26 13:55:35.758277
print(sum_3day_time) # 2020-05-29 13:55:35.758277
print(sub_3day_time) # 2020-05-23 13:55:35.758277
print(sum_7hours_time) # 2020-05-26 20:55:35.758277
print(sub_3hours_time) # 2020-05-26 10:55:35.758277

# --- 时间替换---
str_lo_time = datetime.datetime.now()
print("替换前 ---> ",str_lo_time) # 替换前 --->  2020-05-26 13:55:35.758277
new_time = str_lo_time.replace(year=2012)
print("替换后 ---> ",new_time) # 替换后 --->  2012-05-26 13:55:35.758277
datetime模块操作演示

random模块

方法大全

  random模块可用于生成随机数

random模块常用操作方法 
生成1个整数 
random.randint(1,3) 生成1,2,3中随机一个整数(顾头顾尾)
random.randrange(1,3) 生成1,2中随机一个整数(顾头不顾尾)
生成1个小数 
random.random() 大于0且小于1的小数
random.uniform(1,3) 大于1且小于3的小数
列表随机取元素 
random.choice([1,"a","b"]) 随机取一个元素
random.sample([1,"a","b"],2) 随机取两个元素,由参数指定
随机打乱顺序 
random.shuffle([1,2,3,4,5]) 随机打乱顺序
import random

print(random.random())  # (0,1)----float    大于0且小于1之间的小数 0.25697226355807967

print(random.randint(1, 3))  # [1,3]    大于等于1且小于等于3之间的整数 3

print(random.randrange(1, 3))  # [1,3)    大于等于1且小于3之间的整数 2

print(random.choice([1, '23', [4, 5]]))  # 1或者23或者[4,5] 结果是:23

print(random.sample([1, '23', [4, 5]], 2))  # 列表元素任意2个组合 [[4, 5], 1]

print(random.uniform(1, 3))  # 大于1小于3的小数,如2.3551911687118054

item = [1, 3, 5, 7, 9]
random.shuffle(item)  # 打乱item的顺序,相当于"洗牌"
print(item)  # [1, 5, 3, 9, 7]
random模块操作演示

生成随机验证码

生成随机验证码需要用到两个内置函数:

  chr() ---> 接收一个整数。根据ASCII转换为对应的字符

  ord() ---> 接收一个字符,根据ASCII转换为对应的整数

def captcha(digits:int)->str:
    import random
    res = ''
    for i in range(digits):
        s1 = chr(random.randint(65, 90)) # ASCII中 65 - 90 是 A-Z
        s2 = str(random.randint(0, 9)) # 0 到 9 的数字
        res += random.choice([s1, s2]) # 随机取出一个数字或者一个字符
    return res
code = captcha(6)
print(code) # 7F1096 6H7652 520N7N

os模块

方法大全

  os模块是与操作系统交互的一个接口

os模块常用操作方法 
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname") 改变当前脚本工作目录;相当于shellcd
os.curdir 返回当前目录: ('.')
os.pardir 获取当前目录的父目录字符串名:('..')
os.makedirs('dirname1/dirname2') 可生成多层递归目录。当目录存在时,抛出异常
os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推(目录不为空则不删除)
os.mkdir('dirname') 生成单级目录;相当于shellmkdir dirname
os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shellrmdir 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所指向的文件或者目录的最后修改时间
os.path.getsize(path) 返回path的大小
import os
# === 工作目录相关 ===
print(os.getcwd()) # C:\Users\Administrator\PycharmProjects\learn
os.chdir("PerfectProject") # 相当于 cd 命令
print(os.getcwd()) # C:\Users\Administrator\PycharmProjects\learn\PerfectProject
print(os.curdir) # 就是 . ,它的作用是返回当前目录,可与os.chdir搭配使用
print(os.pardir) # 就是 .. 它的作用是返回上级目录。可与os.chdir搭配使用

# === 文件与目录相关 ===

# -- 创建与删除 --
"""os.makedirs(r'a/b/c') # 创建三个目录。当目录已存在时抛出异常。
os.removedirs(r'a/b/c') # 递归删除刚刚创建好的三个目录,相当于rm -r
os.mkdir("test") # 创建test目录
os.rmdir("test") # 删除test目录
os.remove('README.txt') # 删除单个文件"""

# -- 浏览与修改 --
print(os.listdir(".")) # 打印当前目录下的所有内容(包含隐藏文件,以list返回)['api', 'bin', 'conf', 'core', 'db', 'lib', 'log', 'README.txt', 'requirements.txt']
# os.rename("requirements.txt","Req.txt") # 改名 第一个参数是旧名,第二个参数是新名
print(os.stat("Req.txt")) # 获取文件或者目录的信息 os.stat_result(st_mode=33206, st_ino=844424930256166, st_dev=442394905, st_nlink=1, st_uid=0, st_gid=0, st_size=1209, st_atime=1590476737, st_mtime=1590162165, st_ctime=1590159649)

# === 操作系统相关 ===
print(os.sep) # \ 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
print(os.linesep) # 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
print(os.pathsep) # ; 输出用于分割文件路径的字符串 win下为;,Linux下为:
print(os.name) # nt 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command")  # 运行sell命令,在Windows平台上会产生乱码:'ll' �����ڲ����ⲿ���Ҳ���ǿ����еij������������ļ���
print(os.environ)   # 全局环境变量字典(有用处)

# === 路径相关 ===

# -- 操作与获取路径 --
print(os.path.abspath(".")) # 返回指定目录或文件的绝对路径 C:\Users\Administrator\PycharmProjects\learn\PerfectProject
print(os.path.split(__file__)) # 返回二分元组形式。[0]是目录路径,[1]是文件名。 ('C:/Users/Administrator/PycharmProjects/learn', 'test.py')
print(os.path.dirname(__file__)) # 二分元组的第一部分 C:/Users/Administrator/PycharmProjects/learn
print(os.path.basename(__file__)) # 二分元组的第二部分 test.py
print(os.path.join(r"one","two","1.txt")) # 路径拼接。按平台的路径分隔符拼接  one\two\1.txt

# -- 判断路径 --
print(os.path.exists("bin")) # 一个路径是否存在,返回一个布尔值 True
print(os.path.isabs("a/b")) # 一个路径是否是绝对路径,返回一个布尔值 False
print(os.path.isfile("bin/run.py")) # 文件是否存在,返回一个布尔值 True
print(os.path.isdir("conf")) # 目录是否存在,返回一个布尔值 True

# -- 获取权限信息 --
print(os.path.getmtime(__file__)) # 返回path所指向的文件或者目录的最后修改时间 1590477809.9701936
print(os.path.getsize(__file__)) # 返回path大小 3248
os模块操作演示

项目路径处理的三种方式

在Linux和Mac平台上,该函数会原样返回path,在windows平台上会将路径中所有字符转换为小写,并将所有斜杠转换为饭斜杠。
>>> os.path.normcase('c:/windows\\system32\\')   
'c:\\windows\\system32\\'   
   

规范化路径,如..和/
>>> os.path.normpath('c://windows\\System32\\../Temp/')   
'c:\\windows\\Temp'   

>>> a='/Users/jieli/test1/\\\a1/\\\\aa.py/../..'
>>> print(os.path.normpath(a))
/Users/jieli/test1
# os 路径处理

# 方式一:不推荐使用 openstack中使用的路径处理方式
import os
import os, sys

print(os.path.abspath(__file__))
possible_topdir = os.path.normpath(os.path.join(
    __file__,
    os.pardir,  # 上一级,相当于手动输入".."
    os.pardir,
))
print(possible_topdir)

# 方式二:推荐使用 Django中使用的路径处理方式
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
print(BASE_DIR)

# 方式三,暂时不推荐使用 Python3.5之后提供pathlib模块
from pathlib import Path
root = Path(__file__)
res = root.parent.parent # 取上层的上层
print(res)

# pathlib补充 -- pathlib 的 路径拼接 : 使用符号 / ,符号左边为Path对象,右边为str类型
print(Path("one/two") / r"a/b/c") # one\two\a\b\c  注意:会按照当前平台的路径分隔符进行拼接 

其他补充

  1.为何os.systemshell命令在windows平台上使用会出现乱码?

  涉及到字符编码问题。如果在Pycharm中使用os.system拿到的结果会按照Pycharm规定的UTF-8进行解码,而Windows平台是使用GBK格式进行的字符编码。所以会出现乱码,但是我们在Windows终端环境下执行该命令则不会出现乱码,因为Windows自带的终端环境对字符的解码是按照自身的GBK进行解码的。

img

  2.os.environ有什么作用?

  这玩意儿就是个存储环境变量的字典,当我们使用该字典存储一些可能全项目都会用到的变量时就会产生作用。

  如:我在test2.py文件中存储了一个变量,该变量可能被其他文件中的Python代码使用,就可以用os.environ来进行变量的传递。

  另外要注意,该字典的key规定必须是str类型。否则会抛出异常。

# === test1.py ===
 
  import os
  import test2 # 导入test2时。test2已将变量存入该字典中
  name = os.environ.get("name")
  print(name) # Yunya test1.py中的Python代码也可以使用test2.py中的变量。但并不是通过导入的方式。
  # os.environ[1] = "1" # 抛出异常
# === test2.py ===<br>
  import os
  os.environ["name"] = "Yunya" 

sys模块

方法大全

sys模块常用操作方法 
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0)
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值(Python3已废除,Python2为2147483647)
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
import sys
print(sys.argv) # 该方法主要是用脚本调用式方式执行.py文件时才有用 ['C:/Users/Administrator/PycharmProjects/learn/test2.py']

print(sys.version) # 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
# print(sys.path)
print(sys.platform) # win32

while 1:
    print("1")
    print("2")
    print("3")
    sys.exit() # 终止当前.py文件的运行 

进度条演示

# 知识储备

for i in range(10):
    import time
    time.sleep(0.1)
    print("\r {}".format("#"*i),flush=True,end="") # \r 代表回到首位。end = ""代表不换号
# 基于sys.stdout的原生版。可用try与except使程序更加健硕

import sys
import time


if sys.argv[1] == "get":
    print("正在下载{0}的资源".format(sys.argv[2]))
    print("程序正在下载..\n")
    for i in range(20):
        sys.stdout.write("#") # 写入#好
        time.sleep(0.1) # 模拟数据传输的延迟
        sys.stdout.flush() # 不断刷新
    print("程序下载完成")

elif sys.argv[1] == "put":
    print("正在上传资源至:",sys.argv[2])
    for i in range(20):
        sys.stdout.write("#") # 写入#好
        time.sleep(0.1) # 模拟数据传输的延迟
        sys.stdout.flush() # 不断刷新
    print("程序上传完成")
else:
    print("请指定正确的参数:\n1.put or get\n2.url")
原生版简易进度条演示
# print与\r的应用(优化版) Ps:print是基于sys.stdout来做的,可用try与except使程序更加健硕

import sys
import time
import random

def progress(percent,width=50):
    if percent >= 1: # 代表已经全部接收完成了
        percent=1
    show_str=("[%%-%ds]" %width) %(int(width*percent)*"#")
    print("\r%s %d%%" %(show_str,int(100*percent)),file=sys.stdout,flush=True,end="")

if sys.argv[1] == "get":
    print("正在下载{0}的资源".format(sys.argv[2]))
    print("程序正在下载..\n")
    data_size = random.randint(1024,102400)
    recv_size = 0
    while recv_size < data_size:
        time.sleep(0.1) # 模拟数据的传输延迟
        recv_size += 1024 # 每次收1024的数据

        percent = recv_size / data_size # 接收的比例,不能用 // 因为会得出 0
        progress(percent,width=100) # 宽度控制为 100,传入当前的接收比例

elif sys.argv[1] == "put":
    print("正在上传资源至:",sys.argv[2])
    data_size = random.randint(1024,102400)
    send_size = 0
    while send_size < data_size:
        time.sleep(0.1)  # 模拟数据传输的延迟
        send_size += 1024 # 每次发送1024的数据

        percent = send_size / data_size  # 接收的比例,不能用 // 因为会得出 0
        progress(percent, width=100)  # 宽度控制为 100,传入当前的接收比例
else:
    print("请指定正确的参数:\n1.put or get\n2.url")
优化版

shutil模块

方法大全

  shutil模块提供对文件,文件夹,压缩包的处理

shutil模块常用操作方法 
shutil.copyfileobj(fsrc, fdst[, length]) 将文件内容拷贝到另一个文件中
shutil.copyfile(src, dst) 拷贝文件
shutil.copymode(src, dst) 仅拷贝权限。内容、组、用户均不变
shutil.copystat(src, dst) 仅拷贝状态的信息,包括:mode,bits, atime, mtime, flags
shutil.copy(src, dst) 拷贝文件和权限
shutil.copy2(src, dst) 拷贝文件和状态信息
以下两种方法是一起使用的 

shutil.ignore_patterns(*patterns)

shutil.copytree(src, dst, symlinks=False, ignore=None)

递归的去拷贝文件夹
还可用于拷贝软连接
 
shutil.rmtree(path[, ignore_errors[, onerror]]) 递归的去删除文件
shutil.move(src, dst) 递归的去移动文件,它类似mv命令,其实就是重命名。
shutil.make_archive(base_name, format,...) 创建压缩包并返回文件路径,例如:ziptar

  shutil.copyfileobj(fsrc, fdst[, length])

  将文件内容拷贝到另一个文件中

shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))

  shutil.copyfile(src, dst)

  拷贝文件

shutil.copyfile('f1.log', 'f2.log') #目标文件无需存在

  shutil.copymode(src, dst)

  仅拷贝权限。内容、组、用户均不变

shutil.copymode('f1.log', 'f2.log') #目标文件必须存在

  shutil.copystat(src, dst)

  仅拷贝状态的信息,包括:mode bits, atime, mtime, flags

shutil.copystat('f1.log', 'f2.log') #目标文件必须存在

  shutil.copy(src, dst)

  拷贝文件和权限

shutil.copy('f1.log', 'f2.log')

  shutil.copy2(src, dst)

  拷贝文件和状态信息

shutil.copy2('f1.log', 'f2.log')

  shutil.ignore_patterns(*patterns)

  shutil.copytree(src, dst, symlinks=False, ignore=None)

  递归的去拷贝文件夹

shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) #目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除 
import shutil

shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))

'''
通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件
'''

拷贝软连接
软连接拷贝

  shutil.rmtree(path[, ignore_errors[, onerror]])

  递归的去删除文件

shutil.rmtree('folder1')

  shutil.move(src, dst)

  递归的去移动文件,它类似mv命令,其实就是重命名。

shutil.move('folder1', 'folder3')

  shutil.make_archive(base_name, format,...)

  创建压缩包并返回文件路径,例如:zip、tar

  创建压缩包并返回文件路径,例如:zip、tar

shutil.make_archive()参数大全 
base_name

压缩包的文件名,也可以是压缩包的路径。

只是文件名时,则保存至当前目录,否则保存至指定路径,

data_bak =>保存至当前路径,如:/tmp/data_bak =>保存至/tmp/

format 压缩包种类,“zip”, “tar”, “bztar”,“gztar
root_dir 要压缩的文件夹路径(默认当前目录)
owner 用户,默认当前用户
group 组,默认当前组
logger 用于记录日志,通常是logging.Logger对象
#将 /data 下的文件打包放置当前程序目录
import shutil
ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data')


#将 /data下的文件打包放置 /tmp/目录
import shutil
ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data')

  shutil 对压缩包的处理是调用 ZipFileTarFile 两个模块来进行的,详细:

import zipfile

# 压缩
z = zipfile.ZipFile('laxi.zip', 'w')
z.write('a.log')
z.write('data.data')
z.close()

# 解压
z = zipfile.ZipFile('laxi.zip', 'r')
z.extractall(path='.')
z.close()
import tarfile

# 压缩
>>> t=tarfile.open('/tmp/egon.tar','w')
>>> t.add('/test1/a.py',arcname='a.bak')
>>> t.add('/test1/b.py',arcname='b.bak')
>>> t.close()


# 解压
>>> t=tarfile.open('/tmp/egon.tar','r')
>>> t.extractall('/egon')
>>> t.close()

json与pickle

eval与exec函数

  evalexec 都可以在字符串中检查Python语法,并且提取Python的数据类型。

  不同的是 eval 会接收返回值,而 exec 不会接收返回值。

def func(bifname):
    print(bifname+"正在执行func函数...")
    return "执行完毕,返回结果"

eval_res = eval("func('eval')")
exec_res = exec("func('exec')")

print(eval_res) # 执行完毕,返回结果
print(exec_res) # None

序列化相关知识

  之前我们学习过用eval内置方法可以将一个字符串转成python对象,不过,eval方法是有局限性的,对于普通的数据类型,json.loads()eval都能用,但遇到特殊类型的时候,eval就不管用了,所以eval的重点还是通常用来执行一个字符串表达式,并返回表达式的值。

import json

x="[null,true,false,1]"
print(eval(x)) #报错,无法解析null类型,而json就可以
print(json.loads(x)) 

什么是序列化?

  我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serializationmarshallingflattening等等,都是一个意思。

为什么要序列化?

  1:持久保存状态

  需知一个软件/程序的执行就在处理一系列状态的变化,在编程语言中,'状态'会以各种各样有结构的数据类型(也可简单的理解为变量)的形式被保存在内存中。

内存是无法永久保存数据的,当程序运行了一段时间,我们断电或者重启程序,内存中关于这个程序的之前一段时间的数据(有结构)都被清空了。

在断电或重启程序之前将程序当前内存中所有的数据都保存下来(保存到文件中),以便于下次程序执行能够从文件中载入之前的数据,然后继续执行,这就是序列化。

具体的来说,你玩使命召唤闯到了第13关,你保存游戏状态,关机走人,下次再玩,还能从上次的位置开始继续闯关。或如,虚拟机状态的挂起等。

  2:跨平台数据交互

  序列化之后,不仅可以把序列化后的内容写入磁盘,还可以通过网络传输到别的机器上,如果收发的双方约定好实用一种序列化的格式,那么便打破了平台/语言差异化带来的限制,实现了跨平台数据交互。

  反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling

json模块基本使用

  json是非常重要的一个模块。它可以支持任意语言之间数据类型 的转换,且json本身是通过JavaScript中提取出来的,所以操作十分便捷。对于提取字符串中数据结构的操作,我们之前使用过eval来提取。但是eval局限性比较高,并且json也能够去完成这项工作。

  在学习json后,就不要再使用eval来提取数据类型。

img

json序列化与反序列化的流程

img

json模块常用操作方法 
json.dumps() 将数据序列化为json类型的字符串
json.loads() json类型字符串反序列化为Python语言所对应的数据类型
json.dump() 将数据类型序列化成json字符串(写入文件简化操作)
json.load() json字符串反序列化为Python对应的数据类型(读取文件简化操作)

  Ps:json.dumps()将对象转换为json字符串的过程

    1.修改引号(json中没有单引号)  

    2.json包装

json.dumps()

  将数据序列化为json类型的字符串

import json
dic = {'name':'Yunya'}
res = json.dumps(dic)
print(res,type(res)) # {"name": "Yunya"} <class 'str'>
"""
Ps:可以看到,定义字典时用的是单引号,序列化完毕后用的是双引号了。
"""# ==== 文件保存 ====

with open(file="test.txt",mode="w",encoding="utf-8") as f:
    f.write(res)

"""
文件中的内容:{"name": "Yunya"}
"""

json.loads()

  将json类型字符串反序列化为Python语言所对应的数据类型

import json
with open(file="test.txt",mode="r",encoding="utf-8") as f:
    json_data = f.read()

res = json.loads(json_data)
print(json_data) # {"name": "Yunya"}
print(res) # {'name': 'Yunya'}

  json.dump()json.load()都是直接可以写入一个文件或者读取某个文件。在文件操作的存取中比较常用。所以使用json如果不是将数据保存到文件中,那么我们并不推荐使用这种方法。

json.dump()

  将数据序列化为json类型的字符串(写入文件简化操作)

import json
dic = {'name': 'Yunya'}
with open(file="test.txt",mode="w",encoding="utf-8") as f:
    json.dump(dic,f) # 简化操作

json.load()

  将json类型字符串反序列化为Python语言所对应的数据类型(读取文件简化操作)

import json
dic = {'name': 'Yunya'}
with open(file="test.txt",mode="r",encoding="utf-8") as f:
    res = json.load(f) # 直接反序列化出结果
print(res) # {'name': 'Yunya'}

pickle模块基本使用

  picklejson使用方法全部一致,区别在于pickle序列化后的类型是bytes类型,而json序列化后的类型是字符串类型。由于要考虑到多语言的兼容性问题,json模块并不支持Python除基本数据类型之外的类型。如:set类型,函数类型,类等等。但是pickle由于只支持Python使用,所以有了更强的对Python序列化对象的支持。pickle可以序列化函数,类等等,但是并不推荐这么做,因为保存的只有一个内存地址。另外,由于pickle的局限性太强所以更推荐使用json进行序列化操作。

pickle序列化与反序列化的流程

img

pickle模块常用操作方法 
pickle.dumps() 将Python的数据序列化为bytes类型
json.loads() bytes类型字符串反序列化为Python语言所对应的数据类型
json.dump() 将Python的数据序列化成bytes类型(写入文件简化操作)
json.load() bytes字符串反序列化为Python对应的数据类型(读取文件简化操作)
import pickle
dic = {'name':'Yunya'}
res1 = pickle.dumps(dic)
print(res1,type(res1))
# b'\x80\x04\x95\x13\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x04name\x94\x8c\x05Yunya\x94s.' <class 'bytes'>
def func():
    print("function func...")

res2 = pickle.dumps(func) # pickle 支持序列化Python非基本数据类型,这是json做不到的
print(res2,type(res2))
# b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04func\x94\x93\x94.' <class 'bytes'>
# ===== 文件操作 =====

with open(file="test",mode="wb") as f:
    pickle.dump(func,f) # 将func函数写入文件中
print("写入文件成功...")

with open(file="test",mode="rb") as f:
    res3 = pickle.load(f)
print(res3) # <function func at 0x0000029C2E207160>

扩展:json相关补充

import json
#dct="{'1':111}"#json 不认单引号
#dct=str({"1":111})#报错,因为生成的数据还是单引号:{'one': 1}

dct='{"1":"111"}'
print(json.loads(dct))

#conclusion:
#        无论数据是怎样创建的,只要满足json格式,就可以json.loads出来,不一定非要dumps的数据才能loads
json使用注意事项
"""
 If ``ensure_ascii`` is false, then the return value can contain non-ASCII
    characters if they appear in strings contained in ``obj``. Otherwise, all
    such characters are escaped in JSON strings.

ensure_ascii如果为false,则返回值可以包含非ASCII字符(如果它们出现在obj中的字符串中)。
否则,所有此类字符都将以JSON字符串转义。

注意:该参数默认为True
"""

import json
dic = {'name':'云崖先生'}
res1 = json.dumps(dic)
print(res1) #{"name": "\u4e91\u5d16\u5148\u751f"}

res2 = json.dumps(dic,ensure_ascii=False) # 改为False
print(res2) #{"name": "云崖先生"}
如何让json字符串中显示中文
# 在python解释器2.7与3.6之后都可以json.loads(bytes类型),但唯独3.5不可以
>>> import json
>>> json.loads(b'{"a":111}')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/linhaifeng/anaconda3/lib/python3.5/json/__init__.py", line 312, in loads
    s.__class__.__name__))
TypeError: the JSON object must be str, not 'bytes'
python3.6与2.7之前不可反序列化bytes类型
# 一.什么是猴子补丁?
      猴子补丁的核心就是用自己的代码替换所用模块的源代码,详细地如下
  1,这个词原来为Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。
  2,还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch。


# 二. 猴子补丁的功能(一切皆对象)
  1.拥有在模块运行时替换的功能, 例如: 一个函数对象赋值给另外一个函数对象(把函数原本的执行的功能给替换了)
class Monkey:
    def hello(self):
        print('hello')

    def world(self):
        print('world')


def other_func():
    print("from other_func")



monkey = Monkey()
monkey.hello = monkey.world
monkey.hello()
monkey.world = other_func
monkey.world()

# 三.monkey patch的应用场景
如果我们的程序中已经基于json模块编写了大量代码了,发现有一个模块ujson比它性能更高,
但用法一样,我们肯定不会想所有的代码都换成ujson.dumps或者ujson.loads,那我们可能
会想到这么做
import ujson as json,但是这么做的需要每个文件都重新导入一下,维护成本依然很高
此时我们就可以用到猴子补丁了
只需要在入口处加上
, 只需要在入口加上:

import json
import ujson

def monkey_patch_json():
    json.__name__ = 'ujson'
    json.dumps = ujson.dumps
    json.loads = ujson.loads

monkey_patch_json() # 之所以在入口处加,是因为模块在导入一次后,后续的导入便直接引用第一次的成果

#其实这种场景也比较多, 比如我们引用团队通用库里的一个模块, 又想丰富模块的功能, 除了继承之外也可以考虑用Monkey
Patch.采用猴子补丁之后,如果发现ujson不符合预期,那也可以快速撤掉补丁。个人感觉Monkey
Patch带了便利的同时也有搞乱源代码的风险!
猴子补丁与ujson介绍
import json
import datetime

res = datetime.datetime.now()  # 得到的是datetime对象
print(type(res))  # <class 'datetime.datetime'>

# json无法对其进行序列化,此时我们需要对json功能做出一些改进。
# json.loads(res)

# ========== 扩展json方法开始 =========== 思路:将非基本数据类型转换为基本数据类型再做序列化

import json as default_json
from json.encoder import JSONEncoder


class JsonEncoder(JSONEncoder):
    """该类是逻辑处理类"""

    def default(self, o): # 此时的o就是Json中穿过来的serialize_obj
        if isinstance(o, datetime.datetime):  # 判断对象o是否是否属于datetime类型
            return str(o)  # 转换为str类型,str类型可以被序列化。
        return JSONEncoder.default(self, o)  # 将对象o转换为json对象后返回。


class Json(object):
    """该类是自定义的接口,用来给用户提供非基本数据类型序列化的操作"""

    @staticmethod
    def dumps(serialize_obj, ensure_ascii=True):  # 接收一个序列化对象
        return default_json.dumps(serialize_obj, ensure_ascii=ensure_ascii,
                                  cls=JsonEncoder)  # 去 JsonEncoder中执行default方法


str_datatime = Json.dumps(res)  # 注意,我们用的是自己写的Json来序列化。
print(str_datatime)  # "2020-05-27 15:22:00.099375",此时的str_datatime已经被序列化了

print(json.loads(str_datatime))  # 2020-05-27 15:22:00.099375
如何用json序列化非Python基本数据类型

扩展:pickle相关补充

# coding:utf-8
import pickle

with open('a.pkl',mode='wb') as f:
    # 一:在python3中执行的序列化操作如何兼容python2
    # python2不支持protocol>2,默认python3中protocol=4
    # 所以在python3中dump操作应该指定protocol=2
    pickle.dump('你好啊',f,protocol=2)

with open('a.pkl', mode='rb') as f:
    # 二:python2中反序列化才能正常使用
    res=pickle.load(f)
    print(res)
python2与python3的pickle兼容性问题

shelve模块

方法大全

  shelve.open() 文件句柄相当于一个字典。可以对他增加一个数据类型的值

  shelve模块比pickle模块简单,只有一个open()函数,返回类似字典的对象,可读可写;key必须为字符串,而值可以是python所支持的数据类型。

import shelve

with shelve.open(r"test.txt") as f: # 将文件句柄看做一个大字典
    f["Yunya"] = {"age":18,"sex":"male"}
    f["Xiaohua"] = {"age":19,"sex":"Fmale"}

with shelve.open(r"test.txt") as f:
    yunyamsg = f.get("Yunya") # 通过键值对的方式取出就好。f本身就是个字典


print(yunyamsg) #{'age': 18, 'sex': 'male'}

"""
Ps:生成了三个文件

test.txt.bak

'Yunya', (0, 41)
'Xiaohua', (512, 42)

test.txt.bat
乱码。估计是用二进制存储的

test.txt.dir
'Yunya', (0, 41)
'Xiaohua', (512, 42)
"""
shelve模块操作演示

xml模块

xml基本知识与方法大全

  xmljson的功能目的大部分都差不多,但是由于xml诞生时间很早。所以json无法完全取代xml,可以说json虽然是未来的大势所趋,但是xml也是必须要了解的一个模块。

  xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单,不过,古时候,在json还没诞生的黑暗年代,大家只能选择用xml呀,至今很多传统公司如金融行业的很多系统的接口还主要是xml

img

  在学习xml.etree.ElementTree模块来进行操纵xml文档之前,首先要明白一个文档树的概念。

img

XML模块常用操作方法 
节点属性相关 
tag string对象,表示数据代表的种类,当为节点时代表节点名称。
attrib dictionary对象,表示附有的属性,可以理解为节点的属性。
text string对象,表示节点的内容。
tail string对象,表示element闭合之后的尾迹。
节点方法相关 
findall(self, path, namespaces=None) 获取所有的子节点
iter(self, tag=None) 在当前节点的子孙中根据节点名称寻找所有指定的节点,并返回一个迭代器(可以被for循环)
get(self, key, default=None) 获取当前节点的属性值
set(self, key, value) 为当前节点设置属性值
append(self,subelement) 为当前节点内部增加一个子节点
extent(self,elements) 为当前节点扩展多个子节点
remove(self, subelement) 在当前节点的子节点中删除某个节点
makeelement(self,tag,attrib) 创建一个新的节点(不要调用该方法,应该使用Subelement工厂函数来创建标签)
copy(self) 拷贝节点
insert(self, index, subelement) 在当前节点的子节点中插入某个节点,即:为当前节点创建子节点,然后插入指定位置
getchildren(self) 获取所有的子节点(废弃)
find(self, path, namespaces=None) 获取第一个寻找到的子节点
findtext(self, path, default=None, namespaces=None) 获取第一个寻找到的子节点的内容
iterfind(self, path, namespaces=None) 获取所有指定的节点,并创建一个迭代器(可以被for循环)
clear(self) 清空节点
keys(self) 获取当前节点的所有属性的 key
items(self) 获取当前节点的所有属性值,每个属性都是一个键值对
itertext(self) 在当前节点的子孙中根据节点名称寻找所有指定的节点的内容,并返回一个迭代器(可以被for循环)
文档树相关 Ps:创建xml文档时用到的方法 
Et.Element.__init__(self,tag,attrib={},**extra 创建主标签,其中tag必须为位置传参。(一个文档树只应该有一个主标签)
ET.Subelement(parent,tag,attrib,**extra) 创建一个新的标签,parent代表父级,tag代表标签名,attrib代表属性。parenttag必须使用位置传参
ET.ElementTree.__init__(element=None, file=None) 创建文档树,file可指定直接写入文件,element代表根标签名。并且会返回一个文档树对象
ET.dump(elem) 调用sys下的stdout方法。(默认将标签打印至屏幕)
文档树对象.write() 将文档树写入文件中
文档树对象.write()参数介绍 
self 无需填写
file_or_filename 文件名或路径
encoding=None 字符编码
ml_declaration=None 一般设置为True
default_namespace=None 命名空间,默认即可
method=None, *, 默认即可
short_empty_elements=True 默认即可,空的标签排序

对xml文档的删改查

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank updated="yes">5</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank updated="yes">69</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>
XML数据

1.导入ET

  在Python标准库中,ElementTree有两种实现方式:一种是纯Python的实现xml.etree.ElementTree,另一种是速度更快一点的xml.etree.cElementTree。如果不确定环境中是否有cElementTree,可以使用如下的方式导入:

try:
    import xml.etree.cElementTree as ET # 速度快
except ImportError:
    import xml.etree.ElementTree as ET
导入ET

2.解析xml

  载入数据后获取根节点。通过遍历根节点来获得字节点的信息。

# === 1.导入ET ===
try:
    import xml.etree.cElementTree as ET # 速度快
except ImportError:
    import xml.etree.ElementTree as ET

# === 2.创建文档树对象与获取根节点 ===

tree = ET.ElementTree(file='test.xml') # 导入文件,拿到文档树对象(文档树对象可用write方法)
root = tree.getroot() # 获取根节点

# === 3.1 获取国家标签 ===

for child in root:
    print(child.tag) # 打印国家标签的名字
    print(child.attrib) # 打印国家标签的属性
    # print(child.text) # 打印国家标签的文本内容

""" Ps: 文本内容为空
country
{'name': 'Liechtenstein'}
country
{'name': 'Singapore'}
country
{'name': 'Panama'}
"""

# === 3.2 我们也可以使用iter方法来查看特定的节点 ===
for node in root.iter("country"): # 循环所有的国家标签。注意,iter方法是查找所有的子孙代标签,并非只针对子代
    print("="*20)
    print(node.tag)
    print("="*20)
    for child in node: # 循环当前拿到国家级标签下的所有标签,并打印其标签名与文本内容
        print(child.tag,child.text)
"""
====================
country
====================
rank 2
year 2008
gdppc 141100
neighbor None
neighbor None
====================
country
====================
rank 5
year 2011
gdppc 59900
neighbor None
====================
country
====================
rank 69
year 2011
gdppc 13600
neighbor None
neighbor None
"""
解析XML示例

3.修改xml

  xml.etree.ElementTree模块修改无法直接修改原值,通过采用重新赋值的方式修改。

# === 1.导入ET ===
try:
    import xml.etree.cElementTree as ET # 速度快
except ImportError:
    import xml.etree.ElementTree as ET

# === 2.创建文档树对象与获取根节点 ===

tree = ET.ElementTree(file='test.xml') # 导入文件,拿到文档树对象(文档树对象可用write方法)
root = tree.getroot() # 获取根节点

# === 3 获取与修改国家标签名字 ===

for node in root.findall("country"): # 拿到所有的root子级标签中名为country的标签,对其循环。(不包含孙级)
    node.tag = "nation" # 将国家标签的标签名改为 nation

print(root.findall("country"))  # [] 可以看到,已经查找不到country标签,代表修改标签名成功了
print(root.findall("nation")) # [<Element 'nation' at 0x000001E5D08EDC70>, <Element 'nation' at 0x000001E5D0B4A130>, <Element 'nation' at 0x000001E5D0B4A2C0>]

# === 4 获取所有year标签的文本内容并对其进行修改 ===

res = list(map(lambda x:x.text,root.iter("year")))
print(res) #  查看未修改前的year文本内容列表 ['2008', '2011', '2011']

for node in root.iter("year"): # 拿到所有root子孙级标签中名为year的标签,对其循环。
    node.text = "2020"

res = list(map(lambda x:x.text,root.iter("year")))
print(res) #  查看修改后的year文本内容列表 ['2020', '2020', '2020']

# === 5 为每个国家增添新的标签,并且删除其下的year标签 ===

for node in root.findall("nation"):
    new_ele = ET.SubElement(node,"test",attrib={"test":"True"}).text="新添加的标签" # 创建新标签,parent代表父亲。tag代表标签名,attrib代表属性. 注意:parent和tag必须是使用位置传参
    # node.append(new_ele) 由于SubElement中已经设置了parent,故不用append添加。
    for ele in node.findall("year"):
        node.remove(ele)
        
# === 6 打印根节点,写入修改后的文档树对象 ===

ET.dump(root) # 打印root节点。
tree.write(file_or_filename="test2.xml",encoding='utf-8',xml_declaration=True) # 将文档树对象写入新文件。

"""
<data>
    <nation name="Liechtenstein">
        <rank updated="yes">2</rank>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E" />
        <neighbor name="Switzerland" direction="W" />
    <test test="True">新添加的标签</test></nation>
    <nation name="Singapore">
        <rank updated="yes">5</rank>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N" />
    <test test="True">新添加的标签</test></nation>
    <nation name="Panama">
        <rank updated="yes">69</rank>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W" />
        <neighbor name="Colombia" direction="E" />
    <test test="True">新添加的标签</test></nation>
</data>

Process finished with exit code 0
"""
修改XML示例

创建xml文档与格式化

# === 1.导入ET ===
from xml.dom import minidom

try:
    import xml.etree.cElementTree as ET  # 速度快
except ImportError:
    import xml.etree.ElementTree as ET


# === 2.添加缩进 === Ps:由于原生保存的XML时默认无缩进,如果想要设置缩进的话, 需要修改保存方式

def prettify(elem):
    """将节点转换成字符串,并添加缩进。
    """
    rough_string = ET.tostring(elem, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent="\t")


# === 3.创建主标签 ===

root = ET.Element("data")

# === 4.创建子标签 ===

one = ET.SubElement(root, "son", attrib={"name": "son_1"})

two = ET.SubElement(root, "son", attrib={"name": "son_2"})

three = ET.SubElement(root, "son", attrib={"name": "son_3"})

# === 5.创建孙级标签 ===

g_one = ET.SubElement(one, "grandson", attrib={"name": "grandson_1"})
g_one.text = "one的儿子,data的孙子" # 设置文本属性

g_two = ET.SubElement(two, "grandson", attrib={"name": "grandson_2"})
g_two.text = "two的儿子,data的孙子"

g_three = ET.SubElement(three, "grandson", attrib={"name": "grandson_3"})
g_three.text = "three的儿子,data的孙子"

# === 6.生成文档树 ===


# ==== 如果直接进行写入,那么是没有缩进的。 ====
# ET.dump(root) # 打印根节点,此时是未有缩进的
# et = ET.ElementTree(element=root)
# et.write("new_xml.xml",encoding="utf-8",xml_declaration=True)

# ==== 先将根节点做格式化,再写入文件 ====

raw_str = prettify(root)  # 将根节点进行格式化
print(raw_str)  # 打印根节点,

with open(file="new_xml.xml", mode="wt", encoding='utf-8') as f:
    f.write(raw_str)
创建xml文档与格式化

扩展:xml命名空间问题

  详细介绍,猛击这里

from xml.etree import ElementTree as ET

ET.register_namespace('com',"http://www.company.com") #some name

# build a tree structure
root = ET.Element("{http://www.company.com}STUFF")
body = ET.SubElement(root, "{http://www.company.com}MORE_STUFF", attrib={"{http://www.company.com}hhh": "123"})
body.text = "STUFF EVERYWHERE!"

# wrap it in an ElementTree instance, and save as XML
tree = ET.ElementTree(root)

tree.write("page.xml",
           xml_declaration=True,
           encoding='utf-8',
           method="xml")
命名空间问题

hashlib模块

基础知识与方法大全

  作用:通过hash算法来生成hash值。hashlib模块是Python3中独有的,

  在Python2中则为md5模块和sha模块。Python3中的hashlib提供的算法有:sha1,sha224,sha256,sha384,sha512,md5等等。

    特点1:只要传入的内容一样,获得的hash值也必然一样。

    特点2:不能由hash值反解成内容。

    特点3:只要使用hash的算法不变,无论校验的内容有多大,得到的hash长度都是固定的。

  应用场景:网络传输密码,验证文件完整性等等。

  注意:md5算法的使用方法很简单,但是如今却相对于sha256来说并没有那么安全。md5可以通过撞库手段来进行解密, sha256作为md5的加强版现在也慢慢的开始流行起来,在今后的实际开发过程中推荐使用sha256。因为md5已经被人研究透了。

hashlib模块常用操作方法 
hashlib.algorithms_guaranteed 列出所有平台支持的算法
hashlib.algorithms_available 返回 python 可用的算法,可用于 new 函数构造 hash 对象
hash.digest_size hash函数返回结果的大小
hash.block_size 块大小
hash.name 所使用的 hash 函数的名字
hash.copy() 返回 hash 对象的拷贝
hash.hexdigest() 返回经过 hash 函数,映射后的十六进制的字符串
hash.digest() 返回经过 hash 函数,映射后的二进制的字节串
import hashlib

# === 使用md5得出的hash值 ===

m = hashlib.md5()  # <--- 此处可填写 m = hashlib.md5("helloYunya")
m.update("hello".encode("utf-8"))  # 注意:必须是二进制类型
m.update("Yunya".encode("utf-8"))
res = m.hexdigest()
print(res)  # 7a8c8e846b9cdb345bf7005c335c7389

# === 使用SHA256得出的hash值 ===

m = hashlib.sha256()  # <--- 此处可填写 m = hashlib.sha256("helloYunya")
m.update("hello".encode("utf-8"))
m.update("Yunya".encode("utf-8"))
res = m.hexdigest()
print(res)  # 69bd8de772d4f50882288eaac0a3a22230b3adf8e14c88ff40cfe84af805a59e

# Ps: 可以看出,可以不断的对一个hash对象进行填值。它的加密结果也会不断的发生变化。
md5与sha256的基本使用
import time
import hashlib

# === 模拟网络传输密码 ===
send_res = hashlib.sha256("YY123456".encode("UTF-8")).hexdigest() # 假设用户输入的是 YY123456


# == 模拟网络传输延迟 ===
time.sleep(0.5) # 注意,在网络传输的过程中该密码很可能被黑客截取。使用sha256加密后传输让黑客不知道真实的密码。(不可反解)

# === 服务端存储的密码 ===

password = "YY123456" # 服务端数据库中存储了用户密码,由于sha256得到的hash值不可反解,所以只能通过加密后比对的方式
server_res = hashlib.sha256(password.encode("UTF-8")).hexdigest()

if server_res == send_res:
    print("密码正确..")
else:
    print("密码错误..")
模拟密码传输验证
import time
import hashlib

# ==== 为何要有文件校验 ====
"""
server端    --------->     client端
                |
                |
        可能被黑客窃取,修改下载文件

解决方案:
    在发送文件的时候要让用户知道我们文件本身的hash校验值
    用户下载完成后将得出的结果与我们的hash校验值做对比
    如果一致则文件没有被篡改过
    如果不一致则文件已被篡改过
"""

# ======= 模拟sever端生成文件hash校验值的两种方式  =======

# 方式1:文件所有内容hash校验一遍。安全系数最高,速度最慢。

res = ""
m =  hashlib.sha256()

f = open(file="1.txt",mode="rb")
while 1:
    temp = f.read(1024)
    # === 更新hash值 ===
    m.update(temp) # 由于temp本身就是字节类型。故不用encode
    if not len(temp):
        f.close()
        hash_res = m.hexdigest() # 读取完毕后生成hash字符串
        break

print(hash_res) # 48dd13d8629b4a15f791dec773cab271895187a11683a3d19d4877a8c256cb70

# 方式2:文件指定指针点来更新hash值,安全系数小幅度降低,但速度大幅度提升。(迅雷等下载软件均采用此种方式),前提是要让用户知道我们seek()的文件指针点在哪里.

m =  hashlib.sha256()
f = open(file="1.txt",mode="rb")

# === 获取最开始的点 ===
f.seek(20,0)
temp = f.read(10)
m.update(temp)

# === 获取中间部位的点 ===
f.seek(20,1)
temp = f.read(10)
m.update(temp)

# === 获取尾部的点 ===
f.seek(-20,2)
temp = f.read(10)
m.update(temp)

# 最后得到的结果是每个点后面十个字节所组成的hash字符串,Ps:指针点越多安全系数越高
hash_res = m.hexdigest()
print(hash_res) # daffa21b2be95802d2beeb1f66ce5feb61195e31074120a605421563f775e360
生成文件hash校验值的两种方式

撞库

  虽然说使用上述算法生成的hash值不能被反解,但是我们可以通过撞库来实现破解。

  撞库是指由一个庞大的数据库记载了各种各样字符串经过某种算法生成的hash值,它与原本未加密的字符串有一种映射关系,理论上来说只要这个数据库无限大。那么生成的hash值都能在这个库中找到对应的hash值。

img

  我们将该加密后的hash值放到某一个解密的网站上。成功解密出了原字符串,可以看到,只要库中的对应关系够多数据库够庞大。是能够对生成的hash值进行解密的。

img

加盐

  为了防止被撞库破解,我们可以使用加盐的手段来对加密的字符串进行二次处理。

import hashlib

salt = "salt" # <-- 盐
m = hashlib.md5()
m.update("hello,world".encode("utf-8"))
m.update(salt.encode("utf-8")) # <-- 加盐 Ps:盐可以掺在任何地方
hash_res = m.hexdigest() # <-- 拿到掺了盐的结果

# === 服务端必须也有有盐,在收到带盐的hash值后跟数据库中存储的数据做对比 ===
# 取出数据
# hash加密
# 掺盐
# 对比结果
加盐
"""
hmac模块的使用与hashlib大同小异。但是在某些方面会比hashlib更优秀。
"""
import hmac

h1 = hmac.new("hello".encode("utf-8"), digestmod='md5')  # 最后面指定加密方式
h1.update("world".encode("utf-8"))
print(h1.hexdigest()) # 0e2564b7e100f034341ea477c23f283b
hmac模块的使用

configparser模块

读取配置文件

  configparser能够解析配置文件。本身十分便利,可以极大的减少对配置文件解析的代码量。因此相对来说较为重要,可以在使用的时候查阅一下相关的资料,关于configparser的结构有点类似于shelve模块,可以看作本身是一个非常大的字典,通过往字典中添加键值对的方式来更新配置文件。将文件中的块看作字典中的键值对,就很简单。

  注意:configparser模块在Python2的版本中是以驼峰式进行命名:ConfigParser

configparser模块读取配置文件的相关方法 
ConfigParser() 创建文档对象
文档对象.read(filenames, encoding=None) 读取配置文件
sections(self) 拿到所有的标题
options(self, section) 指定一个标题名,拿到其下所有的key
items(self, section=_UNSET, raw=False, vars=None) 指定一个标题名,拿到其下所有的key
get(self, section, option) 指定标题名与key,拿到具体的valuestr类型
getint(self, section, option) 指定标题名与key,拿到具体的valueint类型
getboolean(self, section, option) 指定标题名与key,拿到具体的valuebool类型,Ps:内部会json做转换
getfloat(self, section, option) 指定标题名与key,拿到具体的valuefloat类型,保留小数点后一位

img

# 配置文件除开.ini后缀,还经常以cfg作为后缀
; 该种文件的注释方式有两种。分别是 # 与 ;

[regulator]
user_name : Yunya
age = 21
sex = male
is_admin = true
salary = 20

[path]
# 后面加上 $true 是自己定制的一个规则。如果为 $true 则与 BASE_DIR 做拼接。得到完整的路径
RUN_LOG_FILE = log/run.log $true
ERROR_LOG_FILE = log/error.log $true
图中配置文件数据
import configparser

config_obj = configparser.ConfigParser() # 创建文档对象
config_obj.read("conf.ini",encoding="utf-8") # 文档对象读入配置文件内容

# === 查看所有标题 ===

sections = config_obj.sections()
print(sections) # ['regulator', 'path']

# === 查看标题regulator下所有的键 ===

options = config_obj.options("regulator")
print(options) # ['user_name', 'age', 'sex', 'is_admin', 'salary']

# === 查看标题regulator下所有的键值对 ===

item_list = config_obj.items("regulator")
print(item_list) # [('user_name', 'Yunya'), ('age', '21'), ('sex', 'male'), ('is_admin', 'true'), ('salary', '20')]

# === 查看标题regulator下的user_name的值 === Ps: get()拿到的是str类型

user_name = config_obj.get("regulator","user_name")
print(user_name) # Yunya

# === 查看标题regulator下的age的值 === Ps: getint()拿到的是int类型

age = config_obj.getint("regulator","age")
print(age) # 21

# === 查看标题regulator下的用户类型是否为管理员 === Ps: getboolean()拿到的是bool类型。并且其内部会采取json格式来进行数据类型转换

res = config_obj.getboolean("regulator","is_admin")
print(res) # True

# === 查看标题regulator下的salary的值 === Ps: getfloat()拿到的是float类型,保留小数点后一位。

salary = config_obj.getfloat("regulator","salary")
print(salary) # 20.0

创建配置文件

  对于配置文件来说,它的创建实际上不多,基本都是手动创建即可。

  而使用configparser模块创建配置文件的过程十分简单。

  介绍一个新方法:

    文档对象.write(文件句柄) ---> 放入文件句柄,将文档对象写入该文件句柄的文件中。

import configparser

# === 关于创建配置文件一定要将整个文档对象当成一个空的大字典 ===

# === 第一步:创建空字典 ===

config_obj = configparser.ConfigParser() # 创建文档对象,相当于创建一个空字典。

# === 第二步:为空字典中添加小字典 ===

config_obj["DEFAULT"] = {
    "ServerAliveInterval":"45",
    "Compression":"Yes",
    "CompressionLevel":"9",
}

"""
相当于:
{
"DEFAULT":{
    "ServerAliveInterval":"45",
    "Compression":"Yes",
    "CompressionLevel":"9",
}

}
"""
#

# === 第三步,我们也可以继续为DEFAULT小字典添加键值对 ===

config_obj["DEFAULT"]["ForwarDX11"] = "Yes"

# === 第四步:我们也可以再创建一个小字典,通过不同的方式来添加键值对 ===

config_obj["TopSecret.Server.com"] = {}
topsecret = config_obj["TopSecret.Server.com"] # 拿到空字典
topsecret["Host Port"] = "50022"
topsecret["ForwarDX11"] = "No"

# === 第五步:写入文件 ===

with open(file="new_conf.ini",mode="wt",encoding="utf-8") as f:
    config_obj.write(f) # 这使用config对象的write方法将文件句柄写入

"""
新建的文件内容如下:

[DEFAULT]
serveraliveinterval = 45
compression = Yes
compressionlevel = 9
forwardx11 = Yes

[TopSecret.Server.com]
host port = 50022
forwardx11 = No
"""

修改配置文件

configparser模块修改配置文件的相关方法 
add_section(self, section) 向文档对象中增加一个键
set(self, section, option, value=None) 设置一个键中的子健的值/也可用于新增一个子健
remove_section(self, section) 删除文档对象中的一个键
remove_options(self, section, option) 删除文档对象中的一个子键值对
has_section(self, section) 判断文档对象中的一个键是否存在
has_options(self, section, option) 判断文档对象中的一个子键值对是否存在

  注意!特殊的DEFAULT键:

  示例中有详细介绍...

import configparser

# === 增删改 === Ps:增删改都是建立在查的基础上,这里用的文件就是上面一章节中刚刚新建的那个文件。

config_obj = configparser.ConfigParser()  # 创建文档对象。
config_obj.read("new_conf.ini", encoding="utf-8")  # 为文档对象读取内容

# === 查看所有标题 ===

sections = config_obj.sections()
print(sections)  # ['TopSecret.Server.com']  Ps:默认的DEFAULT不会显示在其中

# === 查看某一标题是否在文档对象中 ===

print("DEFAULT" in config_obj)  # True
print(config_obj.has_section("DEFAULT"))  # False
# Ps:可以看到使用in是能看到DEFALUT这个块的,但是使用文档对象的方法是看不见的。

# === 查看某一个option是否在某一个块中(块就是我们说的标题,也可以看做是一个小字典) ===

print(config_obj.has_option("TopSecret.Server.com", "host port"))  # True

# === 打印 TopSecret.Server.com 中的键值对 ===

items_list = config_obj.items("TopSecret.Server.com")
print(items_list)
# [('serveraliveinterval', '45'), ('compression', 'Yes'), ('compressionlevel', '9'), ('forwardx11', 'No'), ('host port', '50022')]
# Ps: DEFALUT中的键值对也会在其他的一些options中出现。如果其他的options没有的键值对在DEFALUT中有,则添加。否则保持其原本默认值

# === 循环遍历 TopSecret.Server.com 的键 ===

for key in config_obj["TopSecret.Server.com"]:
    print(key)

"""
host port
forwardx11
serveraliveinterval
compression
compressionlevel

Ps: 当一个conf.ini中存在DEFALUT的块时,该块中的属性则变得非常特殊。通过上面两个小案例已经发现了这种现象,
这样的做法在于能够去设置一些很多块都需要的公有属性。如果想改变这种做法,则将其改名即可。

"""

# === 增删改 ===

config_obj.add_section("other")  # 新增一个键,对应文件中就相当于一个块
config_obj.set("other", "name", "Yunya")  # 对other这个小字典新增一对键值对。 --> other = {"name":"Yunya"}。也可使用该方法添加新的一组option
config_obj.remove_section("TopSecret.Server.com")  # 删除一个键,对应文件中就是删除一个块。
config_obj.remove_option("DEFAULT", "forwardx11")  # 删除一个键中的子键值对,对应文件中就是删除一个块中的选项。

# 写入文件
f = open(file="new_conf.ini",mode="wt",encoding="utf-8")
config_obj.write(f)
f.close() # 关闭系统文件调度资源

"""
修改后的文件内容如下:

[DEFAULT]
serveraliveinterval = 45
compression = Yes
compressionlevel = 9

[other]
name = Yunya
"""

subprocess模块

基本介绍

  我们之前使用os.system()的时候可以运行一些shell命令,但是很遗憾不能使用字符编码对其进行解码,这使得我们在Windows环境下的Pycharm平台上运行该命令会产生乱码问题。

  这个时候就需要介绍到我们的subprocess模块了。

  subprocess模块最早在Python2.4版本引入,用来生成子进程,并且可以通过管道链接他们的输入/输出/错误,以及获得他们的返回值。可以当作命令解释器来用,十分方便。

  subproess替换了多个旧模块和函数:

被subproess替换的旧模块与个别方法 
os.system 该方法被替换
os.spawn* 该模块被替换
os.popen* 该方法被替换
popen2.* 该模块被替换
commands.* 该模块被替换

  运行python的时候,我们都是在创建并运行一个进程,linux中一个进程可以fork一个子进程,并让这个子进程exec另外一个程序。在python中,我们通过标准库中的subprocess包来fork一个子进程,并且运行一个外部的程序。subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

最常用的操作

subprocess模块常用操作方法 
subprocess.Popen(...) 可用于执行复杂的系统命令
stdout.read() 拿出执行成功的命令结果
stderr.read() 拿出执行失败的命令结果
stdin.write() 标准写入
subprocess.Popen(...)参数详解 
args shell命令,可以是字符串或者序列类型(如:list,元组)
bufsize 指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
stdin,stdout,stderr 分别表示程序的标准输入、输出、错误句柄 <-----重点
preexec_fn 只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
close_sfs

在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。

所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout,   stderr)。

shell 同上      <-----重点
cwd 用于设置子进程的当前目录     <-----重点
env 用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
universal_newlines 不同系统的换行符不同,True -> 同意使用 \n
startupinfo与createionflags

只在windows下有效。将被传递给底层的CreateProcess()函数,

用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等

import subprocess

res = subprocess.Popen("dir",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # encoding如不指定,默认返回bytes类型。
print(res.stdout.read().decode("gbk"))  # 打印命令执行成功后的结果
print(res.stderr.read().decode("gbk"))  # 打印命令执行失败后的结果

# 更推荐在下面解码。由于我们是在Windows平台上使用,故使用gbk进行解码。为什么要在下面解码呢?因为得到的这个内容我们可能要用于网络传输。

res.stdout.close() # 关闭系统资源
res.stderr.close()
import subprocess

msg ="""
#coding:utf-8
print "Hello,Python2.x"
print u"我是云崖"
    """

# 1.首先进入了Python2.x的解释器,然后开始写入msg的内容,被管道拿到执行结果。
res = subprocess.Popen("python2",shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

res.stdin.write(msg.encode("utf-8"))
res.stdin.close()

print(res.stdout.read().decode("utf-8"))
print(res.stderr.read().decode("utf-8"))

res.stdout.close()
res.stderr.close()

"""
Hello,Python2.x
我是云崖
"""
subprocess的stdin作用示范

其他操作演示

subprocess模块其他操作方法 
subprocess.run() 运行多条命令并将结果打印到屏幕上
subprocess.call() 执行命令,返回命令执行的结果内容与状态。0代表成功,1代表失败
subprocess.check_call() 执行命令,返回命令执行的结果内容与状态。0代表成功,失败则抛出异常
subprocess.getstatusoutput() 接受字符串形式的命令,返回一个元组形式的结果,第一个元素是命令执行状态,第二个为执行结果内容
subprocess.getoutput() 接受字符串形式的命令,返回执行结果
subprocess.check_output() 执行命令,返回执行的结果,而不是打印

  subprocess.run( )

  运行多条命令并将结果打印到屏幕上

img

  subprocess.call( )

  执行命令,返回命令执行的结果内容与状态。0代表成功,1代表失败

img

  subprocess.check_call( )

  执行命令,返回命令执行的结果内容与状态。0代表成功,失败则抛出异常

img

  subprocess.getstatusoutput( )

  接受字符串形式的命令,返回一个元组形式的结果,第一个元素是命令执行状态,第二个为执行结果内容

img

  subprocess.getoutput( )

  接受字符串形式的命令,返回执行结果

img

  subprocess.check_output( )

  执行命令,返回执行的结果,而不是打印

img

logging模块

初识logging模块

  logging模块是用来做日志相关的操作。

  logging信息一共分为五个级别。分别是debug(10)info(20)warning(30)error(40)critical(50),越低的等级越能看见更多的日志信息。这些信息都有一个默认值,可以通过相关参数进行修改。

import logging

print(logging.NOTSET) # 0级,忽略,基本不会使用它。
logging.debug("debug ... 调试的信息") # 10 级
logging.info("info ... 普通的信息") # 20 级
logging.warning("warning ... 警告的信息") # 30 级
logging.error("error ... 错误的信息") # 40 级
logging.critical("critical  ... 危机/致命/严重的错误信息") # 50 级

# ==== 执行结果 ==== Ps:root为默认的用户,也是最高级别的用户。可以看到默认级别是30,也就只打印3条信息

"""
0
WARNING:root:warning ... 警告的信息
ERROR:root:error ... 错误的信息
CRITICAL:root:critical  ... 危机/致命/严重的错误信息
"""

  显然,我们的这些日志信息全部打印在了屏幕上,并且日志打印出的内容也十分有限。故我们不会直接用这种方式来进行对日志记录的操作。

basicConfig傻瓜式配置

import logging

# 一:日志配置(只是列举了一些常用的参数)

logging.basicConfig(
    # 1、日志输出位置与日志文件编辑模式:1、终端 2、文件

    # filename="access.log", # 不指定,默认打印到终端
    # filemode="a", # 默认对文件的格式为a,也可指定为w(极度不推荐)

    # 2、日志格式
    format="%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s",

    # 3、asctime中时间格式的设定, %p 为显示 AM 和 PM (AM:上午,PM:下午),如不指定则采取默认格式 如:2020-05-31 22:37:46,442
    datefmt="%Y-%m-%d %H:%M:%S %p",

    # 4、日志级别的设置
    # critical => 50
    # error => 40
    # warning => 30
    # info => 20
    # debug => 10
    level=10, # 这里既可以使用数字,也可以使用 logging.debug
)

# 二:输出日志

logging.debug("debug ... 调试的信息")
logging.info("info ... 普通的信息")
logging.warning("warning ... 警告的信息")
logging.error("error ... 错误的信息")
logging.critical("critical ... 危机/致命/严重的错误信息")

# ==== 执行结果 ==== 

"""
2020-05-31 22:45:08 PM - root - DEBUG -logging模块学习:  debug ... 调试的信息
2020-05-31 22:45:08 PM - root - INFO -logging模块学习:  info ... 普通的信息
2020-05-31 22:45:08 PM - root - WARNING -logging模块学习:  warning ... 警告的信息
2020-05-31 22:45:08 PM - root - ERROR -logging模块学习:  error ... 错误的信息
2020-05-31 22:45:08 PM - root - CRITICAL -logging模块学习:  critical ... 危机/致命/严重的错误信息
"""

# 可以看见,我们的日志只能要么向屏幕上打印,要么写入文件中。这很明显不是我们所需要的,有没有一种方法可以既向文件中写入又向屏幕上打印呢?很遗憾,傻瓜式的basicConfig办不到。
============ basicConfig函数参数大全 ============

filename    --->    用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode    --->    文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format      --->    指定handler使用的日志显示格式。
datefmt     --->    指定日期时间格式。
level       --->    设置rootlogger的日志级别(注意,默认的用户是root)
stream      --->    用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。


============ 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     --->    用户输出的消息
basicConfig函数参数大全

手动配置

import logging

# === 第一步:创建logger对象 ===

logger = logging.getLogger() # 创建looger对象/如不指定用户默认使用root用户

# === 第二步:设置logger实例对象的默认级别,如不设置默认为warning级别。30

logger.setLevel(10) # 可用用数字,也可以用logging.DEBUG来进行设置

# === 第三步:创建用于写入文件的对象(可指定文件名)以及创建用于向屏幕打印日志的对象 ===

fh = logging.FileHandler(filename="access.log",encoding="utf-8") # 如不指定字符编码,按照平台指定。Windows -> GBK , Linux,MacOS -> UTF-8
ch = logging.StreamHandler()

# === 第四步:设置日志打印格式 === Ps:可以看到我为文件和屏幕设置了不同的两种格式

fh_formatter = logging.Formatter("[%(asctime)s]-[%(threadName)s-%(thread)d]-[task_id-%(name)s][%(filename)s-%(lineno)d]" \
                  "[%(levelname)s]-[%(message)s]")

ch_formatter = logging.Formatter("[%(levelname)s]-[%(asctime)s]-[%(filename)s-%(lineno)d]-%(message)s")

fh.setFormatter(fh_formatter)
ch.setFormatter(ch_formatter)

# === 第五步:为logger实例对象添加接口 ===

logger.addHandler(fh)
logger.addHandler(ch)

# === 第六步:测试,logger实例对象日志记录信息 ===

logger.debug("debug ... 调试的信息")
logger.info("info ... 普通的信息")
logger.warning("warning ... 警告的信息")
logger.error("error ... 错误的信息")
logger.critical("critical ... 危机/致命/严重的错误信息")

# ==== 执行结果 ====

"""
文件中:
[2020-05-31 23:20:02,678]-[MainThread-11248]-[task_id-root][logging模块学习.py-33][DEBUG]-[debug ... 调试的信息]
[2020-05-31 23:20:02,679]-[MainThread-11248]-[task_id-root][logging模块学习.py-34][INFO]-[info ... 普通的信息]
[2020-05-31 23:20:02,679]-[MainThread-11248]-[task_id-root][logging模块学习.py-35][WARNING]-[warning ... 警告的信息]
[2020-05-31 23:20:02,679]-[MainThread-11248]-[task_id-root][logging模块学习.py-36][ERROR]-[error ... 错误的信息]
[2020-05-31 23:20:02,679]-[MainThread-11248]-[task_id-root][logging模块学习.py-37][CRITICAL]-[critical ... 危机/致命/严重的错误信息]
"""

"""
屏幕终端:
[DEBUG]-[2020-05-31 23:20:02,678]-[logging模块学习.py-33]-debug ... 调试的信息
[INFO]-[2020-05-31 23:20:02,679]-[logging模块学习.py-34]-info ... 普通的信息
[WARNING]-[2020-05-31 23:20:02,679]-[logging模块学习.py-35]-warning ... 警告的信息
[ERROR]-[2020-05-31 23:20:02,679]-[logging模块学习.py-36]-error ... 错误的信息
[CRITICAL]-[2020-05-31 23:20:02,679]-[logging模块学习.py-37]-critical ... 危机/致命/严重的错误信息
"""

  注意:如果要使用手动配置的话,请将它做成函数,但是我仍然不推荐使用这种方式进行使用,因为它无法规避多用户一些方面的操作。强烈推荐使用配置文件的方式进行使用!

手动配置的两个坑

  上面的手动配置还存在一些坑。我们在这里将举例出来并加以完善,在此之前引入一个用户的概念。

  我们知道,在创建logging的实例对象前,如果不指定用户则默认为root用户,那么这里引入一个用户的概念。

img

  代码表示如下图:

import logging

root = logging.getLogger() # --> 默认不指定为创建root用户
Yunya = logging.getLogger("Yunya") # ---> 相当于创建了一个root的子用户 root_Yunya
Ken = logging.getLogger("Ken") # ---> 相当于创建了一个root的子用户 root_Ken
other = logging.getLogger("Yunya.other") # ---> 相当于创建了一个Yunya的子用户 root_Yunya_other 。注意,使用 . 来进行分割

  来么基于这个概念,坑就来了。我们先来看一个示例:

import logging

# === 第一步:创建logger对象 ===

root = logging.getLogger() # 创建looger对象/如不指定用户默认使用root用户
Yunya = logging.getLogger("Yunya") # 相当于创建了一个root的子用户 root_Yunya

# === 第二步:设置logger实例对象的默认级别,如不设置默认为warning级别。30

root.setLevel(30) # 注意!root应该打印3条信息
Yunya.setLevel(10) # 注意!Yunya应该打印5条信息

# === 第三步:创建用于写入文件的对象(可指定文件名)以及创建用于向屏幕打印日志的对象 ===

fh = logging.FileHandler(filename="access.log",encoding="utf-8") # 如不指定字符编码,按照平台指定。Windows -> GBK , Linux,MacOS -> UTF-8
ch = logging.StreamHandler()

# === 第四步:设置日志打印格式 === Ps:可以看到我为文件和屏幕设置了不同的两种格式

fh_formatter = logging.Formatter("[%(asctime)s]-[%(threadName)s-%(thread)d]-[task_id-%(name)s][%(filename)s-%(lineno)d]" \
                  "[%(levelname)s]-[%(message)s]")

ch_formatter = logging.Formatter("[%(levelname)s]-[%(asctime)s]-[%(filename)s-%(lineno)d]-%(message)s")

fh.setFormatter(fh_formatter)
ch.setFormatter(ch_formatter)

# === 第五步:为logger实例对象添加接口 ===

root.addHandler(fh)
root.addHandler(ch)

Yunya.addHandler(fh)
Yunya.addHandler(ch)

# === 第六步:测试,logger实例对象日志记录信息 ===

root.debug("debug ... 调试的信息")
root.info("info ... 普通的信息")
root.warning("warning ... 警告的信息")
root.error("error ... 错误的信息")
root.critical("critical ... 危机/致命/严重的错误信息")

Yunya.debug("debug ... 调试的信息")
Yunya.info("info ... 普通的信息")
Yunya.warning("warning ... 警告的信息")
Yunya.error("error ... 错误的信息")
Yunya.critical("critical ... 危机/致命/严重的错误信息")


# ==== 执行结果 ==== Ps:root显示正常,Yunya却打印了10条信息。这是为何???

"""
文件中:
[2020-05-31 23:41:06,431]-[MainThread-12952]-[task_id-root][logging模块学习.py-40][WARNING]-[warning ... 警告的信息]
[2020-05-31 23:41:06,432]-[MainThread-12952]-[task_id-root][logging模块学习.py-41][ERROR]-[error ... 错误的信息]
[2020-05-31 23:41:06,432]-[MainThread-12952]-[task_id-root][logging模块学习.py-42][CRITICAL]-[critical ... 危机/致命/严重的错误信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-44][DEBUG]-[debug ... 调试的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-44][DEBUG]-[debug ... 调试的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-45][INFO]-[info ... 普通的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-45][INFO]-[info ... 普通的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-46][WARNING]-[warning ... 警告的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-46][WARNING]-[warning ... 警告的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-47][ERROR]-[error ... 错误的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-47][ERROR]-[error ... 错误的信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-48][CRITICAL]-[critical ... 危机/致命/严重的错误信息]
[2020-05-31 23:41:06,433]-[MainThread-12952]-[task_id-Yunya][logging模块学习.py-48][CRITICAL]-[critical ... 危机/致命/严重的错误信息]
"""

"""
屏幕终端:
[WARNING]-[2020-05-31 23:41:06,431]-[logging模块学习.py-40]-warning ... 警告的信息
[ERROR]-[2020-05-31 23:41:06,432]-[logging模块学习.py-41]-error ... 错误的信息
[CRITICAL]-[2020-05-31 23:41:06,432]-[logging模块学习.py-42]-critical ... 危机/致命/严重的错误信息
[DEBUG]-[2020-05-31 23:41:06,433]-[logging模块学习.py-44]-debug ... 调试的信息
[DEBUG]-[2020-05-31 23:41:06,433]-[logging模块学习.py-44]-debug ... 调试的信息
[INFO]-[2020-05-31 23:41:06,433]-[logging模块学习.py-45]-info ... 普通的信息
[INFO]-[2020-05-31 23:41:06,433]-[logging模块学习.py-45]-info ... 普通的信息
[WARNING]-[2020-05-31 23:41:06,433]-[logging模块学习.py-46]-warning ... 警告的信息
[WARNING]-[2020-05-31 23:41:06,433]-[logging模块学习.py-46]-warning ... 警告的信息
[ERROR]-[2020-05-31 23:41:06,433]-[logging模块学习.py-47]-error ... 错误的信息
[ERROR]-[2020-05-31 23:41:06,433]-[logging模块学习.py-47]-error ... 错误的信息
[CRITICAL]-[2020-05-31 23:41:06,433]-[logging模块学习.py-48]-critical ... 危机/致命/严重的错误信息
[CRITICAL]-[2020-05-31 23:41:06,433]-[logging模块学习.py-48]-critical ... 危机/致命/严重的错误信息
"""
多用户继承示例(坑1)

  原因如下:

img

  解决办法:关闭父级的工作

import logging

# === 第一步:创建logger对象 ===

root = logging.getLogger("") # 创建looger对象/如不指定用户默认使用root用户
Yunya = logging.getLogger("Yunya") # 相当于创建了一个root的子用户 root_Yunya

# === 第二步:设置logger实例对象的默认级别,如不设置默认为warning级别。30

root.setLevel(30) # 注意!root应该打印3条信息
Yunya.setLevel(10) # 注意!Yunya应该打印5条信息

# === 第三步:创建用于写入文件的对象(可指定文件名)以及创建用于向屏幕打印日志的对象 ===

fh = logging.FileHandler(filename="access.log",encoding="utf-8") # 如不指定字符编码,按照平台指定。Windows -> GBK , Linux,MacOS -> UTF-8
ch = logging.StreamHandler()

# === 第四步:设置日志打印格式 === Ps:可以看到我为文件和屏幕设置了不同的两种格式

fh_formatter = logging.Formatter("[%(asctime)s]-[%(threadName)s-%(thread)d]-[task_id-%(name)s][%(filename)s-%(lineno)d]" \
                  "[%(levelname)s]-[%(message)s]")

ch_formatter = logging.Formatter("[%(levelname)s]-[%(asctime)s]-[%(filename)s-%(lineno)d]-%(message)s")

fh.setFormatter(fh_formatter)
ch.setFormatter(ch_formatter)

# === 第五步:为logger实例对象添加接口 ===

# root.addHandler(fh) # 关闭接口
# root.addHandler(ch)

Yunya.addHandler(fh)
Yunya.addHandler(ch)

# === 第六步:测试,logger实例对象日志记录信息 ===

root.debug("debug ... 调试的信息")
root.info("info ... 普通的信息")
root.warning("warning ... 警告的信息")
root.error("error ... 错误的信息")
root.critical("critical ... 危机/致命/严重的错误信息")

Yunya.debug("debug ... 调试的信息")
Yunya.info("info ... 普通的信息")
Yunya.warning("warning ... 警告的信息")
Yunya.error("error ... 错误的信息")
Yunya.critical("critical ... 危机/致命/严重的错误信息")


# ==== 执行结果 ==== 

"""
文件中:Ps:文件不会再存有root用户的日志信息
[2020-06-01 00:47:16,128]-[MainThread-480]-[task_id-Yunya][logging模块学习.py-44][DEBUG]-[debug ... 调试的信息]
[2020-06-01 00:47:16,128]-[MainThread-480]-[task_id-Yunya][logging模块学习.py-45][INFO]-[info ... 普通的信息]
[2020-06-01 00:47:16,128]-[MainThread-480]-[task_id-Yunya][logging模块学习.py-46][WARNING]-[warning ... 警告的信息]
[2020-06-01 00:47:16,128]-[MainThread-480]-[task_id-Yunya][logging模块学习.py-47][ERROR]-[error ... 错误的信息]
[2020-06-01 00:47:16,128]-[MainThread-480]-[task_id-Yunya][logging模块学习.py-48][CRITICAL]-[critical ... 危机/致命/严重的错误信息]
"""

"""
屏幕终端:
warning ... 警告的信息
error ... 错误的信息
critical ... 危机/致命/严重的错误信息
[DEBUG]-[2020-06-01 00:47:16,128]-[logging模块学习.py-44]-debug ... 调试的信息
[INFO]-[2020-06-01 00:47:16,128]-[logging模块学习.py-45]-info ... 普通的信息
[WARNING]-[2020-06-01 00:47:16,128]-[logging模块学习.py-46]-warning ... 警告的信息
[ERROR]-[2020-06-01 00:47:16,128]-[logging模块学习.py-47]-error ... 错误的信息
[CRITICAL]-[2020-06-01 00:47:16,128]-[logging模块学习.py-48]-critical ... 危机/致命/严重的错误信息
"""
多用户继承问题解决方案,详细在第五步

  除此之外还有一个坑:解决办法:不要重复命名

import logging
Yunya1 =logging.getLogger("Yunya") # 注意他们的名字都是一样的
Yunya2 =logging.getLogger("Yunya")

Yunya1.setLevel(10)
Yunya1.setLevel(30) # 后者设置覆盖前者

ch = logging.StreamHandler()

Yunya1.addHandler(ch)
Yunya2.addHandler(ch)

Yunya1.debug("debug ... 调试的信息 Yunya1")
Yunya1.info("info ... 普通的信息 Yunya1")
Yunya1.warning("warning ... 警告的信息 Yunya1")
Yunya1.error("error ... 错误的信息 Yunya1")
Yunya1.critical("critical ... 危机/致命/严重的错误信息 Yunya1")

Yunya2.debug("debug ... 调试的信息 Yunya2")
Yunya2.info("info ... 普通的信息 Yunya2")
Yunya2.warning("warning ... 警告的信息 Yunya2")
Yunya2.error("error ... 错误的信息 Yunya2")
Yunya2.critical("critical ... 危机/致命/严重的错误信息 Yunya2")

# ==== 执行结果 ==== Ps:Yunya1应该是打印五条信息的为何只打印3条?
"""
warning ... 警告的信息 Yunya1
error ... 错误的信息 Yunya1
critical ... 危机/致命/严重的错误信息 Yunya1
warning ... 警告的信息 Yunya2
error ... 错误的信息 Yunya2
critical ... 危机/致命/严重的错误信息 Yunya2
"""
重复命名带来的设置覆盖问题

 img

接口设置与filter

import logging

root = logging.getLogger()
root.setLevel(10) # 设置第一层级别过滤

ch = logging.StreamHandler()
ch.setLevel(30) # 设置第二层级别过滤

root.addHandler(ch)

root.debug("debug ... 调试的信息")
root.info("info ... 普通的信息")
root.warning("warning ... 警告的信息")
root.error("error ... 错误的信息")
root.critical("critical ... 危机/致命/严重的错误信息")

# ==== 执行结果 ==== Ps:为什么明明设置了级别是10,却只打印了3条信息?原因:第一次级别过滤:root的level为10,打印5条信息,第二次级别过滤:接口的level为30,打印3条信息。

"""
warning ... 警告的信息
error ... 错误的信息
critical ... 危机/致命/严重的错误信息
"""

  Filter的作用(了解)

  限制只有满足过滤规则的日志才会输出。   

  比如我们定义了filter = logging.Filter('a.b.c'),并将这个Filter添加到了一个Handler上,则使用该Handler的Logger中只有名字带a.b.c前缀的Logger才能输出其日志。

    filter = logging.Filter('mylogger')

    logger.addFilter(filter)

  这是只对logger这个对象进行筛选

  如果想对所有的对象进行筛选,则:

    filter = logging.Filter('mylogger')

    fh.addFilter(filter)

    ch.addFilter(filter)

  这样,所有添加fh或者ch的logger对象都会进行筛选。

使用配置文件进行配置(推荐)

# === 注意,该文件应该放在settings.py文件中 ,此处导入的os模块就是为了做路径拼接使用 ===

import os

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"

test_format = "%(asctime)s] %(message)s"

# 3、日志配置字典
LOGGING_DIC = { # 注意,该配置字典下的key,比如formatters等同一级别的key名均不可改。而其孙代如 fortmatters 中的 test 这一级别的key名均可更改
    "version": 1, # 默认即可
    "disable_existing_loggers": False, # 默认即可
    "formatters": { # 格式化相关
        "standard": { # 自定义规则,标准的格式输出。该级别key名可更改
            "format": standard_format # 格式化,该级别key名不可更改
        },
        "simple": { # 自定义规则,简单的格式输出,该级别key名可更改
            "format": simple_format
        },
        "test": { # 自定义规则,测试用于的输出,该级别key名可更改
            "format": test_format
        },
    },
    "filters": {},
    "handlers": { # 接口相关,控制处理相关
        #打印到终端的日志
        "console": {
            "level": "DEBUG", #设置第二层级别过滤。(详情可查看上一小节)
            "class": "logging.StreamHandler",  # 打印到屏幕
            "formatter": "simple" # 向终端打印采取的日志格式
        },
        #打印到文件的日志,收集info及以上的日志
        "default": {
            "level": "DEBUG", # 设置第二层级别过滤。(详情可查看上一小节)
            "class": "logging.handlers.RotatingFileHandler",  # 保存到文件,日志轮转
            "formatter": "standard",
            # 可以定制日志文件路径
            # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
            # LOG_PATH = os.path.join(BASE_DIR,"a1.log")
            "filename": "a1.log",  # 日志文件
            "maxBytes": 1024*1024*5,  # 日志大小 5M 。当一个文件中的日志大于5M时开启日志轮转,将老文件改为a1.log1,以此类推
            "backupCount": 5, # 最多可存有5个日志轮转文件,如超过5个删除最老的日志用于创建新日志。
            "encoding": "utf-8",  # 日志文件的编码,再也不用担心中文log乱码了
        },
        "other": {
            "level": "DEBUG",
            "class": "logging.FileHandler",  # 保存到文件,注意。这里没有日志轮转
            "formatter": "test",
            "filename": "a2.log", # 日志文件,可指定路径
            "encoding": "utf-8",
        },
    },
    "loggers": {
        #logging.getLogger(__name__)拿到的logger配置
        "": { # 当用户不存在时,将采用这里面的配置。
            "handlers": ["default", "console"],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            "level": "DEBUG", # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)。通常这两个位置的级别过滤都设置为一样的。
            "propagate": False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递,也是手动配置中坑1的解决办法
        },
        "专门的采集": {
            "handlers": ["other"], #采用other的配置
            "level": "DEBUG",
            "propagate": False,
        },
    },
}
# ==== 模块的导入 ====

from logging import config as log_conf # 为了避免命名冲突,这里as了一下
from logging import getLogger
from settings import LOGGING_DIC # 导入配置文件

# ==== 加载配置文件 ====

log_conf.dictConfig(LOGGING_DIC)

# ==== 使用配置文件生成LOGGER对象 ====

logger =getLogger("用户操作") # 由于LOGGING_DIC中不存在该key,则默认使用 "",此外注意日志的命名应该尽量见名知意

# ==== 测试打印 ====

logger.debug("debug ... 调试的信息")
logger.info("info ... 普通的信息")
logger.warning("warning ... 警告的信息")
logger.error("error ... 错误的信息")
logger.critical("critical ... 危机/致命/严重的错误信息")

# ==== 执行结果 ====

"""
文件中:
[2020-06-01 14:08:44,666][MainThread:7708][task_id:用户操作][logging模块学习.py:15][DEBUG][debug ... 调试的信息]
[2020-06-01 14:08:44,666][MainThread:7708][task_id:用户操作][logging模块学习.py:16][INFO][info ... 普通的信息]
[2020-06-01 14:08:44,666][MainThread:7708][task_id:用户操作][logging模块学习.py:17][WARNING][warning ... 警告的信息]
[2020-06-01 14:08:44,666][MainThread:7708][task_id:用户操作][logging模块学习.py:18][ERROR][error ... 错误的信息]
[2020-06-01 14:08:44,666][MainThread:7708][task_id:用户操作][logging模块学习.py:19][CRITICAL][critical ... 危机/致命/严重的错误信息]
"""

"""
屏幕终端:
[DEBUG][2020-06-01 14:08:44,666][logging模块学习.py:15]debug ... 调试的信息
[INFO][2020-06-01 14:08:44,666][logging模块学习.py:16]info ... 普通的信息
[WARNING][2020-06-01 14:08:44,666][logging模块学习.py:17]warning ... 警告的信息
[ERROR][2020-06-01 14:08:44,666][logging模块学习.py:18]error ... 错误的信息
[CRITICAL][2020-06-01 14:08:44,666][logging模块学习.py:19]critical ... 危机/致命/严重的错误信息
"""

执行流程 :

  1.加载配置字典

  2.当实例化对象后,通过配置字典中不同的配置生成日志文件

  3.当实例化对象使用日志操作时,通过配置字典中的格式化以及过滤级别进行显示

  配置字典的使用十分简单,只需要将上面的第一部分代码拷贝到setting.py中即可。注意执行流程,它会在实例化的时候将配置字典中所有的打印到文件中的日志文件进行生成,此外还需要注意日志轮转文件的大小及其数量限制。

re模块

re模块简介及操作方法

  正则表达式其本身就是一种小型的,高度专业化的编程语言。在Python中,它被内嵌在了re模块里面,正则表达式模式被编译成一系列的字节码,然后由用C编写的匹配引擎执行。

re模块常用操作方法 
正则表达式通用方法
. 通配符

代表匹配除\n后的任意字符。

如果想使用.匹配\n,可指定第三个参数为re.Sre.DOTALL

^ 开始符

代表被匹配的字符串必须以某个子串开头,只检测开头。

不论成功或者失败都会返回

$ 结束符

代表被匹配的字符串必须以某个子串结尾,只检测结尾。

不论成功或者失败都会返回

* 重复符

代表可以取0到无穷位

(默认贪婪取值,可通过?取消贪婪模式)

+ 重复符

代表可以取1到无穷位

(默认贪婪取值,可通过?取消贪婪模式)

? 重复符

代表可以取0到1位

(默认贪婪取值,可通过?取消贪婪模式)

{n} 重复符 精确匹配n个前面的表达式
{n,m} 重复符

代表匹配nm次由前面的正则表达式定义的片段 

(默认贪婪取值,可通过?取消贪婪模式)

[] 字符集

其本身代表或的作用 [ab]代表a或者b

在字符集中上面的方法均失去原本含义。但 - ^ \ 可以在字符集中使用

[-]

字符集中的 - 号代表可以取从多少到多少区间的值。

ASCII码排序,比如 [a-z 0-9 A-Z]就是取全部的英文字母和数字

[^] 字符集中的 ^ 号代表 非 的作用。比如[^0-9]就是说这一位数并非数字
[\] 转义符

除开在字符集中使用还可以在外部使用,

它可以使所有具有特殊意义的字符失去特殊意义。

并且还可以为特定的字符指定意义。

\d 匹配任何十进制数,它相当于在字符集中使用[0-9]
\D 匹配任何非十进制数,它相当于在字符集中使用[^0-9]
\s 匹配任何空白字符,它相当于在字符集中使用[\t\n\r\f\v]
\S 匹配任何非空白字符,它相当于在字符集中使用[^\t\n\r\f\v]
\w 匹配任何字母数字下划线字符,它相当于在字符集中使用[a-z A-Z 0-9]
\W 匹配任何非字母数字下划线字符,它相当于在字符集中使用[a-z A-Z 0-9]
\b 匹配一个特殊字符边界,比如空格,&.#等(不常用)
\A 匹配字符串开始(不常用)
\Z 匹配字符串结束,如果存在换行则只匹配换行前的字符(不常用)
\z 匹配字符串结束(不常用)
\G 匹配最后匹配完成的位置(不常用)
\n 匹配一个换行符(不常用)
\t 匹配一个制表符(不常用)
\f 匹配一个分页符(不常用)
\ 转义字符

在Python解释器会解释一遍然后又会到re模块中解释一遍,所有有的时候会出现特殊情况。

为了避免这种情况我们更应该在匹配规则前使用r将规则转换为原生字符串

| 管道符

相当于 或 请注意与字符集里的区别。

|符将前后分为两段,左右看做一个整体,而[]中的或仅仅代表从众多选项中取出一个

() 分组符

将多个元素字符看出一个元素字符进行匹配。

这里需要主要如果进行分组后会优先返回分组时规则里所定的内容而并非筛选到的内容。

这是由re默认优先级导致的,使用(?: )可以取消优先级,另外也可以通过(?P<组名>)来实行效率分组

re模块中提供的方法 
findall() 将所有匹配到的结果返回至一个列表中
finditer()

将所有匹配到的结果返回至迭代器中,可通过__next__()next(),以及for循环进行取值,

极大节省内存,适用于大数据操作

search() 将第一次匹配到的结果返回至一个对象中,可通过group()取值
match() search()基础上添加了一个^,使之只能在开头匹配。其他同样
group() search()方法和match()方法的对象进行取值操作
split() 对一个字符串进行分割,算法导致可能会出现令人意外的情况
sub() 对一个字符串的字串进行替换操作。最少需要三个参数,以字符串方式返回
subn() 对一个字符串的字串进行替换操作。最少需要三个参数,以元组方式返回,并且会提示完成了几次匹配结果
compile() 可以将一个变量赋于指定规则,达到简化重复操作的目的
import re

# ==== . 通配符 ====  代表匹配除\n后的任意字符。如果想使用.匹配\n,可指定第三个参数为re.S或re.DOTALL。

print(re.findall(r".","aBc123_*()-=\t\n"))
# ['a', 'B', 'c', '1', '2', '3', '_', '*', '(', ')', '-', '=', '\t']
print(re.findall(r".","aBc123_*()-=\t\n",re.DOTALL))
# ['a', 'B', 'c', '1', '2', '3', '_', '*', '(', ')', '-', '=', '\t', '\n']
print(re.findall(r".","aBc123_*()-=\t\n",re.S))
# ['a', 'B', 'c', '1', '2', '3', '_', '*', '(', ')', '-', '=', '\t', '\n']

# ==== ^ 开始符 ====   代表被匹配的字符串必须以某个子串开头,只检测开头。不论成功或者失败都会返回

print(re.findall(r"^hello......","hello,wrold,hello,\nworld")) # 开头hello匹配成功之后还会向后匹配6个除了\n的其他任意字符
# ['hello,wrold'] Ps:可以看到,拿到了第一个结果就即使返回了。不会在继续向后匹配。
print(re.findall(r"^hello......","omg,wrold,hello,\nworld"))
# []

# ==== $ 结束符 ====  代表被匹配的字符串必须以某个子串结尾,只检测结尾,不论成功或者失败都会返回

print(re.findall(r"......world$","hello,world")) # 结尾的world匹配成功之后还会向前匹配6个除了\n的其他任意字符
# ['hello,world'] Ps:可以看到,拿到了第一个结果就即使返回了。不会在继续向前匹配。
print(re.findall(r"......world$","hello,Python"))
# []

# ==== * 重复符 ==== 代表可以取0到无穷位 (默认贪婪取值,可通过?取消贪婪模式)

print(re.findall(r"omg*","omomgomggomggg,god")) # 第一位是o,第二位是m,第三位到无穷位可以有g也可以没g
# ['om', 'omg', 'omgg', 'omggg']
print(re.findall(r"omg*?","omomgomggomggg,god")) # 第一位是o,第二位是m,即使第三位乃至后面无穷位是g也不取(因为*代表是0-无穷,所有取消贪婪取最小的0)
# ['om', 'om', 'om', 'om']

# ==== + 重复符 ==== 代表可以取1到无穷位 (默认贪婪取值,可通过?取消贪婪模式)

print(re.findall(r"omg+","omomgomggomggg,god")) # 第一位是o,第二位是m,第三位是g,第四位到无穷位可以有g也可以没g
# ['omg', 'omgg', 'omggg']
print(re.findall(r"omg+?","omomgomggomggg,god")) # 第一位是o,第二位是m,第三位是g,即使第四位乃至后面无穷位是g也不取
# ['omg', 'omg', 'omg']

# ==== ? 重复符 ==== 代表可以取0到1位 (默认贪婪取值,可通过?取消贪婪模式)

print(re.findall(r"omg?","omomgomggomggg,god")) # 第一位是o,第二位是m,第三位可以有g也可以没g
# ['om', 'omg', 'omg', 'omg']
print(re.findall(r"omg??","omomgomggomggg,god")) # 第一位是o,第二位是m,即使第三位是g也不取
# ['om', 'om', 'om', 'om']

# ==== {n} ==== 精确匹配n个前面的表达式

print(re.findall(r"omg{3}","omomgomggomggg,god")) # 第一位是o,第二位是m,第三位是第四位第五位必须都是g
# ['omggg']

# ==== {n,m} 重复符 ==== 代表匹配n到m次由前面的正则表达式定义的片段

print(re.findall(r"omg{1,3}","omomgomggomggg,god")) # 第一位是o,第二位是m,第三位是g,第四位到第五位可以有g也可以没g
# ['omg', 'omgg', 'omggg']

# ==== [] 字符集 ==== 其本身代表或的作用 [ab]代表a或者b。在字符集中上面的方法均失去原本含义。但 - ^ \ 可以在字符集中使用

print(re.findall(r"[nc]ba","nnbacbazbba")) # 取出nba或cba
# ['nba', 'cba']

# ==== [-] ==== 字符集中的 - 号代表可以取从多少到多少区间的值,ASCII码排序,比如 [a-z0-9A-Z]就是取全部的英文字母和数字

print(re.findall(r"[0-9a-zA-Z]c","1c2c3c\tczccc c"))
# ['1c', '2c', '3c', 'zc', 'cc']

# 注意:如果想取 - 则将 - 指定在最前
print(re.findall(r"[-0-9a-zA-Z]c","1c2c3c\tczccc c -c"))
# ['1c', '2c', '3c', 'zc', 'cc', '-c']

# ==== [^] ==== 字符集中的 ^ 号代表 非 的作用。比如[^0-9]就是说这一位数并非数字

print(re.findall(r".[^0-9]","c1c2cacbc\t")) # 取两位数,第二位不能是数字
#['1c', '2c', 'ac', 'bc']

# ==== [\] 转义符 ==== 除开在字符集中使用还可以在外部使用,它可以使所有具有特殊意义的字符失去特殊意义。并且还可以为特定的字符指定意义。

# 我想匹配 \ d 或者 w
print(re.findall(r"[\dw]","abcdefg1234\d\w"))
# ['1', '2', '3', '4', 'w'] 匹配失败
print(re.findall(r"[\\dw]","abcdefg1234\d\w"))
# ['d', '\\', 'd', '\\', 'w'] 匹配成功,但是结果是\\ ,因为re模块是在Python解释器之上。r为原始字符串,一个\在Python解释器看来是普通的斜杠
# 但是传入到re那一层就是 \d的意思。 所以我们需要再加上一个 \ ,让re模块收到的就是一个普通的 \ 。

# ==== \d 与 \D ====

print(re.findall(r"\d+","abc123e4f56g")) # 代表匹配任意数字组合  + 代表最少一位
# ['123', '4', '56']
print(re.findall(r"\D+","abc123e4f56g")) # 代表匹配任意非数字组合  + 代表最少一位
# ['abc', 'e', 'f', 'g']

# ==== \s 和 \S ====

print(re.findall(r"\s+","abc_()*&^%$#@! \n\t\r\b 123")) # 代表匹配任意特殊字符组合  + 代表最少一位
# [' \n\t\r', ' ']
print(re.findall(r"\S+","abc_()*&^%$#@! \n\t\r\b 123")) # 代表匹配任意非特殊字符组合  + 代表最少一位
# ['abc_()*&^%$#@!', '\x08', '123']

# ==== \w 和 \W ====

print(re.findall(r"\w+","abc_()*&^%$#@! \n\t\r\b 123")) # 代表匹配任意特殊字符组合  + 代表最少一位
# ['abc_', '123']
print(re.findall(r"\W+","abc_()*&^%$#@! \n\t\r\b 123")) # 代表匹配任意非特殊字符组合  + 代表最少一位
# ['()*&^%$#@! \n\t\r\x08 ']

# ==== \ 转义字符 ====

print(re.findall("www.baidu.com","wwwfbaidufcomwww.baidu.com")) # 未转义,未加r
# ['wwwfbaidufcom', 'www.baidu.com']
print(re.findall("www\.baidu\.com","www.google.comwww.baidu.comwww.biying.com")) # 转义,未加r
# ['www.baidu.com']
print(re.findall(r"www.baidu.com","www.google.comwww.baidu.comwww.biying.com")) # 直接加r,不推荐使用
# ['www.baidu.com']
print(re.findall(r"www\.baidu\.com","www.google.comwww.baidu.comwww.biying.com")) # 转义+加r,推荐使用
# ['www.baidu.com']

# ==== | 管道符 ====  相当于 或 请注意与字符集里的区别。| 符将前后分为两段,左右看做一个整体,而[]中的或仅仅代表从众多选项中取出一个。

print(re.findall(r"cba|nba","cccnnbabbcba")) # 左右看做一个整体,代步取cba或者nba
# ['nba', 'cba']
print(re.findall(r"[cbanba)]","cccnnbabbcba")) # 代步取c , b ,a , n 中的一个
# ['c', 'c', 'c', 'n', 'n', 'b', 'a', 'b', 'b', 'c', 'b', 'a']
# ==== () 分组 ==== 将组内看做一个整体

# - 普通分组 -
print(re.findall(r"([^\d\s\b\W_]+)","Yunya123(+)#(&)__@"))
# ['Yunya']

# -有名分组- 需要使用search方法或match()方法与group才能拿到某一具体的组
print(re.search(r"(?P<name>\w+)","Yunya123(+)#(&)__@").group("name"))
#Yunya123

# - 取消默认优先级 - (如果使用分组的话,那么会优先返回组内的内容。而不是匹配成功的内容)
print(re.findall(r"(abc)+","abcabcabc")) # 结果错误
# ['abc']
print(re.findall(r"(?:abc)+","abcabcabc")) # ?: 取消默认优先级,结果正确
# ['abcabcabc']

# ==== findall() ====  将所有匹配到的结果返回至一个列表中

print(re.findall(r"Y.{3}[aA]","YunyaYufajfYunyaYUNYA"))
# ['Yunya', 'Yunya', 'YUNYA']

# ==== finditer() ====   将所有匹配到的结果返回至迭代器中,可通过__next__( ),next( ),以及for循环进行取值,极大节省内存,适用于大数据操作。

res = re.finditer(r"Y.{3}[aA]","YunyaYufajfYunyaYUNYA")
print(res.__next__()) # <re.Match object; span=(0, 5), match='Yunya'>
print(res.__next__()) # <re.Match object; span=(11, 16), match='Yunya'>
print(res.__next__()) # <re.Match object; span=(16, 21), match='YUNYA'>

# ==== search() ====   将第一次匹配到的结果返回至一个对象中,可通过group()取值

print(re.search(r"Y.{3}[aA]","YunyaYufajfYunyaYUNYA").group()) # 如果求的是一个分组,那么在group中加入组名即可。
# Yunya

# ==== match() ====   在search()基础上添加了一个^,使之只能在开头匹配。其他同样

print(re.match(r"Y.{3}[aA]","YunyaYufajfYunyaYUNYA").group()) # 如果求的是一个分组,那么在group中加入组名即可。
# Yunya

# ==== split() ====   对一个字符串进行分割,算法导致可能会出现令人意外的情况

print(re.split(r" ","hello abc def")) # 按空格切分
# ['hello', 'abc', 'def']
print(re.split(r" |\|","hello abc|def")) # 按空格或 | 分
# ['hello', 'abc', 'def']
print(re.split(r"[ |]","hello abc|def")) # 按空格或 | 分
# ['hello', 'abc', 'def']

# 算法导致切分看不懂:
print(re.split(r"[ab]","asdabcd"))
# 第一次按a来分:['', 'sd', 'bcd']
# 第二次按b来分: ['', 'sd', '', 'cd'] 按b的分法由于是空。故前进一位
print(re.split(r"[ab]","abc"))
# ['', '', 'c']

# ==== sub() ====   对一个字符串的字串进行替换操作。最少需要三个参数,以字符串方式返回

print(re.sub(r"\d+","替换了","a1b2c3d4"))
# a替换了b替换了c替换了d替换了
print(re.sub(r"\d+","替换了","a1b2c3d4",2)) # 指定替换几次
# a替换了b替换了c3d4

# ==== subn() ==== 对一个字符串的字串进行替换操作。最少需要三个参数,以元祖方式返回,并且会提示完成了几次匹配结果

print(re.subn(r"\d+","替换了","a1b2c3d4"))
# ('a替换了b替换了c替换了d替换了', 4)
print(re.subn(r"\d+","替换了","a1b2c3d4",2)) # 指定替换几次
# ('a替换了b替换了c3d4', 2)

# ==== compile() ==== 可以将一个变量赋于指定规则,达到简化重复操作的目的
# 只匹配 133,135,138 开头的 11 位手机号
Chinese_phone = re.compile(r"^13[358]\d{8}$") #指定规则

print(Chinese_phone.findall(r"13837823899"))
# ['13837823899']
print(Chinese_phone.findall(r"13505214269"))
# ['13505214269']
print(Chinese_phone.findall(r"13338231369"))
# ['13338231369']
import 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的作用是将所有组拼接到一起显示出来 123
print(re.findall('(\d)+','123')) #findall结果是组内的结果,且是最后一个组的结果 ['3']
补充search与findall对分组使用的区别
IP:
^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
手机号:
^1[3|4|5|8][0-9]\d{8}$
邮箱:
[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+
通用正则表达式
import re
ret=re.findall('www.(baidu|oldboy).com','www.oldboy.com')
print(ret)#['oldboy']     这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可
 
ret=re.findall('www.(?:baidu|oldboy).com','www.oldboy.com')
print(ret)#['www.oldboy.com']
findall与分组使用注意事项

计算器练习

武大神原文出处

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
该计算器思路:
    1、递归寻找表达式中只含有 数字和运算符的表达式,并计算结果
    2、由于整数计算会忽略小数,所有的数字都认为是浮点型操作,以此来保留小数
使用技术:
    1、正则表达式
    2、递归
 
执行流程如下:
******************** 请计算表达式: 1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) ) ********************
before: ['1-2*((60-30+(-40.0/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
-40.0/5=-8.0
after: ['1-2*((60-30+-8.0*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
========== 上一次计算结束 ==========
before: ['1-2*((60-30+-8.0*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
9-2*5/3+7/3*99/4*2998+10*568/14=173545.880953
after: ['1-2*((60-30+-8.0*173545.880953)-(-4*3)/(16-3*2))']
========== 上一次计算结束 ==========
before: ['1-2*((60-30+-8.0*173545.880953)-(-4*3)/(16-3*2))']
60-30+-8.0*173545.880953=-1388337.04762
after: ['1-2*(-1388337.04762-(-4*3)/(16-3*2))']
========== 上一次计算结束 ==========
before: ['1-2*(-1388337.04762-(-4*3)/(16-3*2))']
-4*3=-12.0
after: ['1-2*(-1388337.04762--12.0/(16-3*2))']
========== 上一次计算结束 ==========
before: ['1-2*(-1388337.04762--12.0/(16-3*2))']
16-3*2=10.0
after: ['1-2*(-1388337.04762--12.0/10.0)']
========== 上一次计算结束 ==========
before: ['1-2*(-1388337.04762--12.0/10.0)']
-1388337.04762--12.0/10.0=-1388335.84762
after: ['1-2*-1388335.84762']
========== 上一次计算结束 ==========
我的计算结果: 2776672.69524
"""
 
 
import re
 
 
def compute_mul_div(arg):
    """ 操作乘除
    :param expression:表达式
    :return:计算结果
    """
 
    val = arg[0]
    mch = re.search('\d+\.*\d*[\*\/]+[\+\-]?\d+\.*\d*', val)
    if not mch:
        return
    content = re.search('\d+\.*\d*[\*\/]+[\+\-]?\d+\.*\d*', val).group()
 
    if len(content.split('*'))>1:
        n1, n2 = content.split('*')
        value = float(n1) * float(n2)
    else:
        n1, n2 = content.split('/')
        value = float(n1) / float(n2)
 
    before, after = re.split('\d+\.*\d*[\*\/]+[\+\-]?\d+\.*\d*', val, 1)
    new_str = "%s%s%s" % (before,value,after)
    arg[0] = new_str
    compute_mul_div(arg)
 
 
def compute_add_sub(arg):
    """ 操作加减
    :param expression:表达式
    :return:计算结果
    """
    while True:
        if arg[0].__contains__('+-') or arg[0].__contains__("++") or arg[0].__contains__('-+') or arg[0].__contains__("--"):
            arg[0] = arg[0].replace('+-','-')
            arg[0] = arg[0].replace('++','+')
            arg[0] = arg[0].replace('-+','-')
            arg[0] = arg[0].replace('--','+')
        else:
            break
 
    if arg[0].startswith('-'):
        arg[1] += 1
        arg[0] = arg[0].replace('-','&')
        arg[0] = arg[0].replace('+','-')
        arg[0] = arg[0].replace('&','+')
        arg[0] = arg[0][1:]
    val = arg[0]
    mch = re.search('\d+\.*\d*[\+\-]{1}\d+\.*\d*', val)
    if not mch:
        return
    content = re.search('\d+\.*\d*[\+\-]{1}\d+\.*\d*', val).group()
    if len(content.split('+'))>1:
        n1, n2 = content.split('+')
        value = float(n1) + float(n2)
    else:
        n1, n2 = content.split('-')
        value = float(n1) - float(n2)
 
    before, after = re.split('\d+\.*\d*[\+\-]{1}\d+\.*\d*', val, 1)
    new_str = "%s%s%s" % (before,value,after)
    arg[0] = new_str
    compute_add_sub(arg)
 
 
def compute(expression):
    """ 操作加减乘除
    :param expression:表达式
    :return:计算结果
    """
    inp = [expression,0]
 
    # 处理表达式中的乘除
    compute_mul_div(inp)
 
    # 处理
    compute_add_sub(inp)
    if divmod(inp[1],2)[1] == 1:
        result = float(inp[0])
        result = result * -1
    else:
        result = float(inp[0])
    return result
 
 
def exec_bracket(expression):
    """ 递归处理括号,并计算
    :param expression: 表达式
    :return:最终计算结果
    """
    # 如果表达式中已经没有括号,则直接调用负责计算的函数,将表达式结果返回,如:2*1-82+444
    if not re.search('\(([\+\-\*\/]*\d+\.*\d*){2,}\)', expression):
        final = compute(expression)
        return final
    # 获取 第一个 只含有 数字/小数 和 操作符 的括号
    # 如:
    #    ['1-2*((60-30+(-40.0/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
    #    找出:(-40.0/5)
    content = re.search('\(([\+\-\*\/]*\d+\.*\d*){2,}\)', expression).group()
 
    # 分割表达式,即:
    # 将['1-2*((60-30+(-40.0/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
    # 分割更三部分:['1-2*((60-30+(    (-40.0/5)      *(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
    before, nothing, after = re.split('\(([\+\-\*\/]*\d+\.*\d*){2,}\)', expression, 1)
 
    print 'before:',expression
    content = content[1:len(content)-1]
 
    # 计算,提取的表示 (-40.0/5),并活的结果,即:-40.0/5=-8.0
    ret = compute(content)
 
    print '%s=%s' %( content, ret)
 
    # 将执行结果拼接,['1-2*((60-30+(      -8.0     *(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
    expression = "%s%s%s" %(before, ret, after)
    print 'after:',expression
    print "="*10,'上一次计算结束',"="*10
 
    # 循环继续下次括号处理操作,本次携带者的是已被处理后的表达式,即:
    # ['1-2*((60-30+   -8.0  *(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))']
 
    # 如此周而复始的操作,直到表达式中不再含有括号
    return exec_bracket(expression)
 
 
 
# 使用 __name__ 的目的:
#   只有执行 python index.py 时,以下代码才执行
#   如果其他人导入该模块,以下代码不执行
if __name__ == "__main__":
    #print '*'*20,"请计算表达式:", "1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )" ,'*'*20
    #inpp = '1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) ) '
    inpp = "1-2*-30/-12*(-20+200*-3/-200*-300-100)"
    #inpp = "1-5*980.0"
    inpp = re.sub('\s*','',inpp)
    # 表达式保存在列表中
    result = exec_bracket(inpp)
    print result

importlib

   importlib是一款内置模块,能够使用字符串来导入模块,但是py文件是最小单位。

   无法拿到里面的类或者函数。

实用案例

   下面有一个obatin的包,并且有4个py文件。

   我们来模拟一个多发爬虫的场景。

-- test
	-- obation
		__init__.py
		cnblog.py
		csdn.py
		zhihu.py
	start.py
	settings.py

   settings.py配置如下

GETURL = [
    "obtain.cnblog.Cnblog",
    "obtain.csdn.Csdn",
    "obtain.zhihu.Zhihu",
]

   cnblog\csdn\zhihu这三个文件夹下都是基本相同的,都有一个get()入口方法。

class Zhihu:
    def get(self):
        print("获取知乎数据")

   再来看一下__init__.py中的代码,这里利用importlib就可以做成可插拔式的设计。

   我们想要运行那个py文件下的功能,就直接在settings.py中进行设置即可。

import importlib
import settings

def run():  # 定义run,统一管理
    for path_str in settings.GETURL:
        module_path,cls_name = path_str.rsplit(".",maxsplit=1)
        # "obtain.cnblog.Cnblog" 拿到模块路径,以及类名
        module = importlib.import_module(module_path) # 导入模块
        cls = getattr(module,cls_name) # 拿到真实的类
        obj = cls() # 生成实例对象
        obj.get() # 调用通用入口函数get

   最后是start.py文件。

import obtain

if __name__ == "__main__":
    obtain.run()
posted @ 2020-05-27 16:02  云崖先生  阅读(1168)  评论(0编辑  收藏  举报