Python中的常用模块学习









【一】模块

【1】什么是模块

  • 在Python中,一个py文件就是一个模块,文件名为xxx.py模块名则是xxx,导入模块可以引用模块中已经写好的功能。

    • 如果把开发程序比喻成制造一台电脑
    • 编写模块就像是在制造电脑的零部件
    • 准备好零部件后,剩下的工作就是按照逻辑把它们组装到一起。
  • 将程序模块化会使得程序的组织结构清晰,维护起来更加方便。

    • 比起直接开发一个完整的程序,单独开发一个小的模块也会更加简单,并且程序中的模块与电脑中的零部件稍微不同的是:
      • 程序中的模块可以被重复使用。
    • 所以总结下来,使用模块既保证了代码的重用性,又增强了程序的结构性和可维护性。
    • 另外除了自定义模块外,我们还可以导入使用内置或第三方模块提供的现成功能,这种“拿来主义”极大地提高了程序员的开发效率。

【2】模块的优点

  • 极大地提高了程序员的开发效率。

【3】模块的来源

  1. 内置的:# python解释器自带的,直接拿来使用的
  2. 第三方的 # 别人写的,如果想用,就要先下载在使用
  3. 自定义的 # 我们自己写的

【4】模块的存在形式

  1. 我们自己写的py文件(一个py文件就是一个模块)
  2. 包:一系列py文件的集合(文件夹)

一个包里面会有一个__init__.py文件

【二】模块的使用

【1】import 语句

  • foo.py
x=1
def get():
    print(x)
def change():
    global x
    x=0
class Foo:
    def func(self):
       print('from the func')
  • 要想在另外一个 py 文件中引用foo.py中的功能
  • 需要使用 import foo
  • 首次导入模块会做三件事:
    • 执行源文件代码
    • 产生一个新的名称空间用于存放源文件执行过程中产生的名字
    • 在当前执行文件所在的名称空间中得到一个名字 foo,该名字指向新创建的模块名称空间
    • 若要引用模块名称空间中的名字,需要加上该前缀
import foo 			# 导入模块foo
a=foo.x 			# 引用模块foo中变量x的值赋值给当前名称空间中的名字a
foo.get() 			# 调用模块foo的get函数
foo.change() 		# 调用模块foo中的change函数
obj=foo.Foo() 		# 使用模块foo的类Foo来实例化,进一步可以执行obj.func()
  • 加上 foo. 作为前缀就相当于指名道姓地说明要引用foo名称空间中的名字

    • 所以肯定不会与当前执行文件所在名称空间中的名字相冲突
  • 并且若当前执行文件的名称空间中存在 x

    • 执行 foo.get()foo.change() 操作的都是源文件中的全局变量 x
  • 需要强调一点是,第一次导入模块已经将其加载到内存空间了,之后的重复导入会直接引用内存中已存在的模块,不会重复执行文件

    • 通过import sys
    • 打印sys.modules的值可以看到内存中已经加载的模块名。

1、在Python中模块也属于第一类对象

​ 可以进行赋值、以数据形式传递以及作为容器类型的元素等操作。

2、模块名应该遵循小写形式,标准库从python2过渡到python3做出了很多这类调整

​ 比如ConfigParser、Queue、SocketServer全更新为纯小写形式。

  • 用import语句导入多个模块

    • 可以写多行import语句
    import module1
    import module2
        ...
    import moduleN
    
  • 还可以在一行导入,用逗号分隔开不同的模块

    import module1,module2,...,moduleN
    

但其实第一种形式更为规范,可读性更强,推荐使用

​ 而且我们导入的模块中可能包含有python内置的模块、第三方的模块、自定义的模块

​ 为了便于明显地区分它们,我们通常在文件的开头导入模块

​ 并且分类导入

​ 一类模块的导入与另外一类的导入用空行隔开

​ 不同类别的导入顺序如下:

# 1. python内置模块
# 2. 第三方模块
# 3. 程序员自定义模块
  • 当然,我们也可以在函数内导入模块,对比在文件开头导入模块属于全局作用域,在函数内导入的模块则属于局部的作用域。

【2】 from ... import ... 语句

  • from...import...与import语句基本一致

    • 唯一不同的是:
      • 使用import foo导入模块后,引用模块中的名字都需要加上foo.作为前缀
      • 而使用from foo import x,get,change,Foo则可以在当前执行文件中直接引用模块foo中的名字
    from foo import x,get,change  # 将模块foo中的x和get导入到当前名称空间
    a=x 						  # 直接使用模块foo中的x赋值给a
    get() 						  # 直接执行foo中的get函数
    change()   					  # 即便是当前有重名的x,修改的仍然是源文件中的x
    
  • 无需加前缀的

    • 好处是使得我们的代码更加简洁
    • 坏处则是容易与当前名称空间中的名字冲突
  • 如果当前名称空间存在相同的名字

    • 则后定义的名字会覆盖之前定义的名字。
  • 另外from语句支持 from foo import *语法, * 代表将foo中所有的名字都导入到当前位置

from foo import * # 把foo中所有的名字都导入到当前执行文件的名称空间中,在当前位置直接可以使用这些名字

a=x
get()
change()
obj=Foo()
  • 如果我们需要引用模块中的名字过多的话,可以采用上述的导入形式来达到节省代码量的效果
  • 但是需要强调的一点是:
    • 只能在模块最顶层使用 * 的方式导入,在函数内则非法
    • 并且 * 的方式会带来一种副作用
      • 即我们无法搞清楚究竟从源文件中导入了哪些名字到当前位置
      • 这极有可能与当前位置的名字产生冲突。
    • 模块的编写者可以在自己的文件中定义__all__变量用来控制 * 代表的意思
# foo.py
__all__=['x','get'] # 该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字
x=1
def get():
    print(x)
def change():
    global x
    x=0
class Foo:
    def func(self):
       print('from the func')
  • 这样我们在另外一个文件中使用 * 导入时,就只能导入__all__定义的名字了
from foo import * # 此时的*只代表x和get

x #可用
get() #可用
change() #不可用
Foo() #不可用

【3】 导入文件的扩展用法

  • 我们还可以在当前位置为导入的模块起一个别名
import foo as f #为导入的模块foo在当前位置起别名f,以后再使用时就用这个别名f
f.x
f.get()
  • 还可以为导入的一个名字起别名
from foo import get as get_x
get_x()
  • 通常在被导入的名字过长时采用起别名的方式来精简代码
    • 另外为被导入的名字起别名可以很好地避免与当前名字发生冲突
  • 还有很重要的一点就是:
    • 可以保持调用方式的一致性
  • 例如
    • 我们有两个模块json和pickle同时实现了load方法
      • 作用是从一个打开的文件中解析出结构化的数据
    • 但解析的格式不同
      • 可以用下述代码有选择性地加载不同的模块
if data_format == 'json':
    import json as serialize #如果数据格式是json,那么导入json模块并命名为serialize
elif data_format == 'pickle':
    import pickle as serialize #如果数据格式是pickle,那么导入pickle模块并命名为serialize
    
data=serialize.load(fn) #最终调用的方式是一致的

【4】循环导入问题

  • 循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块
    • 而在另外一个模块中又返回来导入第一个模块中的名字
  • 由于第一个模块尚未加载完毕
    • 所以引用失败、抛出异常
  • 究其根源就是在python中
    • 同一个模块只会在第一次导入时执行其内部代码
    • 再次导入该模块时
    • 即便是该模块尚未完全加载完毕也不会去重复执行内部代码

(1)循环导入所引发的问题演示

  • m1.py
print('正在导入m1')
from m2 import y

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

y='m2'
  • run.py
import m1
  • 演示一

    • 执行run.py会抛出异常
    正在导入m1
    正在导入m2
    
    Traceback (most recent call last):
      File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in <module>
        import m1
      File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
        from m2 import y
      File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
        from m1 import x
    ImportError: cannot import name 'x'
    
    • 分析
      • 先执行run.py--->执行import m1
      • 开始导入m1并运行其内部代码--->打印内容"正在导入m1"
      • --->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”
      • --->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x
      • 然而x此时并没有存在于m1中,所以报错
  • 演示二:

    • 执行文件不等于导入文件,比如执行m1.py不等于导入了m1
    直接执行m1.py抛出异常
    正在导入m1
    正在导入m2
    正在导入m1
    Traceback (most recent call last):
      File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
        from m2 import y
      File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
        from m1 import x
      File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
        from m2 import y
    ImportError: cannot import name 'y'
    
    • 分析
      • 执行m1.py,打印“正在导入m1”,
      • 执行from m2 import y
        • 导入m2进而执行m2.py内部代码
        • --->打印"正在导入m2"
      • 执行from m1 import x
        • 此时m1是第一次被导入
        • 执行m1.py并不等于导入了m1
      • 于是开始导入m1并执行其内部代码
        • --->打印"正在导入m1"
      • 执行from m1 import y
        • 由于m1已经被导入过了
        • 所以无需继续导入而直接问m2要y
        • 然而y此时并没有存在于m2中所以报错

(2)循环引入产生的问题的解决方案

(2.1)方案一

  • 导入语句放到最后,保证在导入时,所有名字都已经加载过

  • m1.py

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

  • run.py
import m1
print(m1.x)
print(m1.y)

(2.2)方案二

  • 导入语句放到函数中,只有在调用函数时才会执行其内部代码

  • m1.py

print('正在导入m1')

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

x = 'm1'
  • m2.py
print('正在导入m2')

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

y = 'm2'
  • run.py
import m1

m1.f1()

注意:循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举,在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据,可以将共享的数据集中存放到某一个地方,然后进行导入

【5】搜索模块的路径与优先级

  • 模块其实分为四个通用类别,分别是:

    • 1、使用纯Python代码编写的py文件

    • 2、包含一系列模块的包

    • 3、使用C编写并链接到Python解释器中的内置模块

    • 4、使用C或C++编译的扩展模块

  • 在导入一个模块时

    • 如果该模块已加载到内存中,则直接引用
    • 否则会优先查找内置模块
      • 然后按照从左到右的顺序依次检索 sys.path 中定义的路径
    • 直到找模块对应的文件为止
      • 否则抛出异常。
  • sys.path 也被称为模块的搜索路径,它是一个列表类型

>>> sys.path
['',
'/Library/Frameworks/Python.framework/Versions/3.5/lib/python35.zip',
'/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5',
...,
'/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages'
  • 列表中的每个元素其实都可以当作一个目录来看:

    • 在列表中会发现有.zip或.egg结尾的文件
      • 二者是不同形式的压缩文件
  • 事实上Python确实支持从一个压缩文件中导入模块

    • 我们也只需要把它们都当成目录去看即可。
  • sys.path 中的第一个路径通常为空,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的

  • 而针对被导入的模块与执行文件在不同路径下的情况

    • 为了确保模块对应的源文件仍可以被找到
    • 需要将源文件 foo.py 所在的路径添加到 sys.path
    • 假设 foo.py 所在的路径为 /pythoner/projects/
    import sys
    sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)
    
    import foo #无论foo.py在何处,我们都可以导入它了
    
    • 作为模块foo.py的开发者
      • 可以在文件末尾基于__name__在不同应用场景下值的不同来控制文件执行不同的逻辑
    # foo.py
    ...
    if __name__ == '__main__':
        foo.py被当做脚本执行时运行的代码
    else:
        foo.py被当做模块导入时运行的代码
    

    通常我们会在if的子代码块中编写针对模块功能的测试代码

    ​ 这样foo.py在被当做脚本运行时,就会执行测试代码

    ​ 而被当做模块导入时则不用执行测试代码。

【6】模块编写规范

  • 我们在编写py文件时
    • 需要时刻提醒自己
      • 该文件既是给自己用的
      • 也有可能会被其他人使用
    • 因而代码的可读性与易维护性显得十分重要
    • 为此我们在编写一个模块时最好按照统一的规范去编写
#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。

"The module is used to..." # 模块的文档描述

import sys #导入模块

x=1 # 定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能

class Foo: # 定义类,并写好类的注释
    'Class Foo is used to...'
    pass

def test(): # 定义函数,并写好函数的注释
    'Function test is used to…'
    pass

if __name__ == '__main__': #主程序
    test() # 在被当做脚本执行时,执行此处的代码

【7】认识模块

(1)什么是模块

  • 常见的场景:
    • 一个模块就是一个包含了python定义和声明的文件
    • 文件名就是模块名字加上.py的后缀。
  • 但其实import加载的模块分为四个通用类别:
    • 1 使用python编写的代码(.py文件)
    • 2 已被编译为共享库或DLL的C或C++扩展
    • 3 包好一组模块的包
    • 4 使用C编写并链接到python解释器的内置模块

(2)模块的介绍与使用(见上文)

(3)模块的优点

  • 如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失
    • 因此我们通常将程序写到文件中以便永久保存下来
    • 需要时就通过python test.py方式去执行
    • 此时test.py被称为脚本script。
  • 随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。
    • 这时我们不仅仅可以把这些文件当做脚本去执行
    • 还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,

正则表达式 (re)

  • 一说规则我已经知道你很晕了

  • 首先你要知道的是

    • 谈到正则,就只和字符串相关了。
    • 在我给你提供的工具中,你输入的每一个字都是一个字符串。
  • 其次,如果在一个位置的一个值,不会出现什么变化,那么是不需要规则的。

    • 比如
      • 你要用"1"去匹配"1"
      • 或者用"2"去匹配"2"
      • 直接就可以匹配上。
    • 这连python的字符串操作都可以轻松做到。
    • 那么在之后我们更多要考虑的是在同一个位置上可以出现的字符的范围。

【一】正则模块知识点

【1】字符串

字符组 :

​ [字符组] 在同一个位置可能出现的各种字符组成了一个字符组

​ 在正则表达式中用[]表示

字符分为很多类

​ 比如数字、字母、标点等等。

假如你现在要求一个位置"只能出现一个数字"

​ 那么这个位置上的字符只能是0、1、2...9这10个数之一。

正则 待匹配字符 匹配结果 说明
[0123456789] 8 True 在一个字符组里枚举合法的所有字符,字符组里的任意一个字符和"待匹配字符"相同都视为可以匹配
[0123456789] a False 由于字符组中没有"a"字符,所以不能匹配
[0-9] 7 True 也可以用-表示范围,[0-9]就和[0123456789]是一个意思
[a-z] s True 同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示
[A-Z] B True [A-Z]就表示所有的大写字母
[0-9a-fA-F] e True 可以匹配数字,大小写形式的a~f,用来验证十六进制字符

【2】字符

元字符 匹配内容
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线
\s 匹配任意的空白符
\d 匹配数字
\n 匹配一个换行符
\t 匹配一个制表符
\b 匹配一个单词的结尾
^ 匹配字符串的开始
$ 匹配字符串的结尾
\W 匹配非字母或数字或下划线
\D 匹配非数字
\S 匹配非空白符
a|b 匹配字符a或字符b
() 匹配括号内的表达式,也表示一个组
[...] 匹配字符组中的字符
[^...] 匹配除了字符组中字符的所有字符

【3】量词

量词 用法说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

【4】. ^ $

正则 待匹配字符 匹配 结果 说明
海. 海燕海娇海东 海燕海娇海东 匹配所有"海."的字符
^海. 海燕海娇海东 海燕 只从开头匹配"海."
海.$ 海燕海娇海东 海东 只匹配结尾的"海.$"

【5】* + ? { }

正则 待匹配字符 匹配 结果 说明
李.? 李杰和李莲英和李二棍子 李杰 李莲 李二 ?表示重复零次或一次,即只匹配"李"后面一个任意字符
李.* 李杰和李莲英和李二棍子 李杰和李莲英和李二棍子 *表示重复零次或多次,即匹配"李"后面0或多个任意字符
李.+ 李杰和李莲英和李二棍子 李杰和李莲英和李二棍子 +表示重复一次或多次,即只匹配"李"后面1个或多个任意字符
李.{1,2} 李杰和李莲英和李二棍子 李杰和 李莲英 李二棍 {1,2}匹配1到2次任意字符
  • 注意:前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配

【6】 字符集[][^]

正则 待匹配字符 匹配 结果 说明
李[杰莲英二棍子]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子 表示匹配"李"字后面[杰莲英二棍子]的字符任意次
李[^和]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子 表示匹配一个不是"和"的字符任意次
[\d] 456bdha3 4 5 6 3 表示匹配任意一个数字,匹配到4个结果
[\d]+ 456bdha3 456 3 表示匹配任意个数字,匹配到2个结果

【7】分组 ()与 或 |[^]

  • 身份证号码是一个长度为15或18个字符的字符串
    • 如果是15位则全部🈶️数字组成,首位不能为0;
    • 如果是18位,则前17位全部是数字,末位可能是数字或x,
    • 下面我们尝试用正则来表示:
正则 待匹配字符 匹配 结果 说明
^[1-9]\d{13,16}[0-9x]$ 110101198001017032 110101198001017032 表示可以匹配一个正确的身份证号
^[1-9]\d{13,16}[0-9x]$ 1101011980010170 1101011980010170 表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字
^[1-9]\d{14}(\d{2}[0-9x])?$ 1101011980010170 False 现在不会匹配错误的身份证号了 ()表示分组,将\d{2}[0-9x]分成一组,就可以整体约束他们出现的次数为0-1次
`^([1-9]\d{16}[0-9x] [1-9]\d{14})$` 110105199812067023 110105199812067023

【8】转义符 \

  • 在正则表达式中,有很多有特殊意义的是元字符

    • 比如\n和\s等
    • 如果要在正则中匹配正常的"\n"而不是"换行符"就需要对""进行转义,变成'\'。
  • 在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。

  • 所以如果匹配一次"\n",字符串中要写成'\n',那么正则里就要写成"\\n",这样就太麻烦了。

  • 这个时候我们就用到了r'\n'这个概念,此时的正则是r'\n'就可以了。

正则 待匹配字符 匹配 结果 说明
\n \n False 因为在正则表达式中\是有特殊意义的字符,所以要匹配\n本身,用表达式\n无法匹配
\n \n True 转义\之后变成\,即可匹配
"\\n" '\n' True 如果在python中,字符串中的''也需要转义,所以每一个字符串''又需要转义一次
r'\n' r'\n' True 在字符串之前加r,让整个字符串不转义

【9】贪婪匹配

  • 贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配
正则 待匹配字符 匹配 结果 说明
<.*> <script>...<script> <script>...<script> 默认为贪婪匹配模式,会匹配尽量长的字符串
<.*?> r'\d' <script> <script> 加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串

几个常用的非贪婪匹配Pattern

*? 重复任意次,但尽可能少重复 +? 重复1次或更多次,但尽可能少重复 ?? 重复0次或1次,但尽可能少重复 {n,m}? 重复n到m次,但尽可能少重复 {n,}? 重复n次以上,但尽可能少重复

【10】.*?的用法

  • .
    • 是任意字符
  • *
    • 是取 0 至 无限长度
  • ?
    • 是非贪婪模式。
  • 合在一起就是取尽量少的任意字符,一般不会这么单独写
    • 他大多用在:.*?
      • 就是取前面任意长度的字符,直到一个x出现

【二】re模块的常用方法

import re

ret = re.findall('a', 'eva egon yuan')  # 返回所有满足匹配条件的结果,放在列表里
print(ret) #结果 : ['a', 'a']

ret = re.search('a', 'eva egon yuan').group()
print(ret) #结果 : 'a'
# 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
# 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

ret = re.match('a', 'abc').group()  # 同search,不过尽在字符串开始处进行匹配
print(ret)
#结果 : 'a'

ret = re.split('[ab]', 'abcd')  # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割
print(ret)  # ['', '', 'cd']

ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1)#将数字替换成'H',参数1表示只替换1个
print(ret) #evaHegon4yuan4

ret = re.subn('\d', 'H', 'eva3egon4yuan4')#将数字替换成'H',返回元组(替换的结果,替换了多少次)
print(ret)

obj = re.compile('\d{3}')  #将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字
ret = obj.search('abc123eeee') #正则表达式对象调用search,参数为待匹配的字符串
print(ret.group())  #结果 : 123

import re
ret = re.finditer('\d', 'ds3sy4784a')   #finditer返回一个存放匹配结果的迭代器
print(ret)  # <callable_iterator object at 0x10195f940>
print(next(ret).group())  #查看第一个结果
print(next(ret).group())  #查看第二个结果
print([i.group() for i in ret])  #查看剩余的左右结果

【三】正则方法之优先级查询

【1】findall的优先级查询:

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']

【2】split的优先级查询

ret=re.split("\d+","eva3egon4yuan")
print(ret) #结果 : ['eva', 'egon', 'yuan']

ret=re.split("(\d+)","eva3egon4yuan")
print(ret) #结果 : ['eva', '3', 'egon', '4', 'yuan']

#在匹配部分加上()之后所切出的结果是不同的,
#没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项,
#这个在某些需要保留匹配部分的使用过程是非常重要的。

【四】综合练习

【1】匹配标签

import re


ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")
#还可以在分组中利用?<name>的形式给分组起名字
#获取的匹配结果可以直接用group('名字')拿到对应的值
print(ret.group('tag_name'))  #结果 :h1
print(ret.group())  #结果 :<h1>hello</h1>

ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
#如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致
#获取的匹配结果可以直接用group(序号)拿到对应的值
print(ret.group(1))
print(ret.group())  #结果 :<h1>hello</h1>

【2】匹配整数

import re

ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) #['1', '2', '60', '40', '35', '5', '4', '3']
ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) #['1', '-2', '60', '', '5', '-4', '3']
ret.remove("")
print(ret) #['1', '-2', '60', '5', '-4', '3']

【3】数字匹配

1、 匹配一段文本中的每行的邮箱
      http://blog.csdn.net/make164492212/article/details/51656638

2、 匹配一段文本中的每行的时间字符串,比如:‘1990-07-12’;

   分别取出1年的12个月(^(0?[1-9]|1[0-2])$)、
   一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$

3、 匹配qq号。(腾讯QQ号从10000开始)  [1,9][0,9]{4,}

4、 匹配一个浮点数。       ^(-?\d+)(\.\d+)?$   或者  -?\d+\.?\d*

5、 匹配汉字。             ^[\u4e00-\u9fa5]{0,}$ 

6、 匹配出所有整数

【4】爬虫练习

import requests

import re
import json

def getPage(url):

    response=requests.get(url)
    return response.text

def parsePage(s):
    
    com=re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
                   '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S)

    ret=com.finditer(s)
    for i in ret:
        yield {
            "id":i.group("id"),
            "title":i.group("title"),
            "rating_num":i.group("rating_num"),
            "comment_num":i.group("comment_num"),
        }

def main(num):

    url='https://movie.douban.com/top250?start=%s&filter='%num
    response_html=getPage(url)
    ret=parsePage(response_html)
    print(ret)
    f=open("move_info7","a",encoding="utf8")

    for obj in ret:
        print(obj)
        data=json.dumps(obj,ensure_ascii=False)
        f.write(data+"\n")

if __name__ == '__main__':
    count=0
    for i in range(10):
        main(count)
        count+=25

【5】爬虫练习 - 简化版

import re
import json
from urllib.request import urlopen

def getPage(url):
    response = urlopen(url)
    return response.read().decode('utf-8')

def parsePage(s):
    com = re.compile(
        '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
        '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)

    ret = com.finditer(s)
    for i in ret:
        yield {
            "id": i.group("id"),
            "title": i.group("title"),
            "rating_num": i.group("rating_num"),
            "comment_num": i.group("comment_num"),
        }


def main(num):
    url = 'https://movie.douban.com/top250?start=%s&filter=' % num
    response_html = getPage(url)
    ret = parsePage(response_html)
    print(ret)
    f = open("move_info7", "a", encoding="utf8")

    for obj in ret:
        print(obj)
        data = str(obj)
        f.write(data + "\n")

count = 0
for i in range(10):
    main(count)
    count += 25

【五】作业练习

实现能计算类似 1 - 2 * ( (60-30 +(-40/5) * (9-25/3 + 7 /399/42998 +10 * 568/14 )) - (-43)/ (16-3*2) )等类似公式的计算器程序

在线测试工具 http://tool.chinaz.com/regex/

时间模块(time)

和时间有关系的我们就要用到时间模块。在使用模块之前,应该首先导入这个模块。

#常用方法
1.time.sleep(secs)
(线程)推迟指定的时间运行。单位为秒。
2.time.time()
获取当前时间戳

时间模块之time模块

  • 导入时间模块
import time
  • 生成时间戳
time.time()
# 1685671924.0515392
  • 时间字符串
time.strftime("%Y-%m-%d %X")
# 2023-06-02 10:11:30

time.strftime("%Y-%m-%d %H-%M-%S")
# 2023-06-02 10-11-47
  • 时间元组:
    • localtime将一个时间戳转换为当前时区的struct_time
import time

print(time.localtime())
# time.struct_time(tm_year=2023, tm_mon=6, tm_mday=2, tm_hour=10, tm_min=10, tm_sec=37, tm_wday=4, tm_yday=153, tm_isdst=0)

【一】表示时间的三种方式

  • 在Python中,通常有这三种方式来表示时间:

    • 时间戳

    • 元组(struct_time)

    • 格式化的时间字符串:

      • 格式化的时间字符串(Format String): ‘1999-12-06’

【二】Python中时间日期格式化符号

符号 含义
%y 两位数的年份表示 (00-99)
%Y 四位数的年份表示 (000-9999)
%m 月份 (01-12)
%d 月内中的一天 (0-31)
%H 24小时制小时数 (0-23)
%I 12小时制小时数 (01-12)
%M 分钟数 (00=59)
%S (00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天 (001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数 (00-53)星期天为星期的开始
%w 星期 (0-6),星期天为星期的开始
%W 一年中的星期数 (00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

【三】元组(struct_time)

  • struct_time 元组共有9个元素共九个元素:
    • (年,月,日,时,分,秒,一年中第几周,一年中第几天等)
索引(Index) 属性(Attribute) 值(Values)
0 (年) 比如2011
1 tm_mon(月) 1 - 12
2 tm_mday(日) 1 - 31
3 tm_hour(时) 0 - 23
4 tm_min(分) 0 - 59
5 tm_sec(秒) 0 - 60
6 tm_wday(weekday) 0 - 6(0表示周一)
7 tm_yday(一年中的第几天) 1 - 366
8 tm_isdst(是否是夏令时) 默认为0
import time

data_time = time.localtime()
print(data_time)
# time.struct_time(tm_year=2023, tm_mon=6, tm_mday=2, tm_hour=10, tm_min=10, tm_sec=37, tm_wday=4, tm_yday=153, tm_isdst=0)
# 可以通过直接 `.` 取每个值
print(data_time.tm_year)

# 可以通过索引取每个单独的值
print(data_time[0])

小结:时间戳是计算机能够识别的时间;时间字符串是人能够看懂的时间;元组则是用来操作时间的

  • 几钟时间格式之间的转化

【四】时间戳

  • 时间戳(timestamp) :
    • 通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。
  • 我们运行“type(time.time())”,返回的是float类型。

【1】time.gmtime(时间戳)

  • UTC时间,与英国伦敦当地时间一致

【2】time.localtime(时间戳)

  • 当地时间。
  • 例如我们现在在北京执行这个方法:
    • 与UTC时间相差8小时,UTC时间+8小时 = 北京时间
>>>time.gmtime(1500000000)
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=2, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)

>>>time.localtime(1500000000)
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=10, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)

【五】结构化时间

【1】time.mktime(结构化时间)-->时间戳

>>>time_tuple = time.localtime(1500000000)
>>>time.mktime(time_tuple)
1500000000.0

【2】time.strftime(结构化时间)-->字符串

(1)time.strftime("格式定义","结构化时间")

  • 结构化时间参数若不传,则显示当前时间
>>>time.strftime("%Y-%m-%d %X")
'2017-07-24 14:55:36'
>>>time.strftime("%Y-%m-%d",time.localtime(1500000000))
'2017-07-14'

(2)time.strptime(时间字符串,字符串对应格式)

  • 字符串时间-->结构化时间
>>>time.strptime("2017-03-16","%Y-%m-%d")
time.struct_time(tm_year=2017, tm_mon=3, tm_mday=16, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=75, tm_isdst=-1)

>>>time.strptime("07/24/2017","%m/%d/%Y")
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=24, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=205, tm_isdst=-1)

【3】time.asctime(结构化时间) --> %a %b %d %H:%M:%S %Y串

(1)time.asctime(结构化时间)

  • 如果不传参数,直接返回当前时间的格式化串
>>>time.asctime(time.localtime(1500000000))
'Fri Jul 14 10:40:00 2017'
>>>time.asctime()
'Mon Jul 24 15:18:33 2017'

(2)time.ctime(时间戳)

  • 时间戳 --> %a %b %d %H:%M:%S %Y串
  • 如果不传参数,直接返回当前时间的格式化串
>>>time.ctime()
'Mon Jul 24 15:19:07 2017'
>>>time.ctime(1500000000)
'Fri Jul 14 10:40:00 2017' 

时间模块之datetime模块

【一】导入模块

import datetime

【二】自定义日期

res = datetime.date(2019, 7, 15)
print(res)  # 2019-07-15

【三】获取本地时间

  • 年月日
now_date = datetime.date.today()
print(now_date) 
# 2023-06-02
  • 年月日时分秒
now_time = datetime.datetime.today()
print(now_time)
# 2023-06-02 19:16:56.447458
  • 无论是年月日,还是年月日时分秒对象

  • 都可以调用以下方法获取针对性的数据

    • 以datetime对象举例
    now_time = datetime.datetime.today()
    print(now_time.year)  
    # 获取年份2023
    print(now_time.month)  
    # 获取月份6
    print(now_time.day)  
    # 获取日2
    print(now_time.weekday())  
    # 4 获取星期(weekday星期是0-6) 0表示周一
    print(now_time.isoweekday())  
    # 5 获取星期(weekday星期是1-7) 1表示周一
    

【四】timedelta对象

【1】可以对时间进行运算操作

import datetime

(1)获得本地日期 年月日

tday = datetime.date.today()
print(tday)
# 2023-06-02

(2)定义操作时间

  • day=7
    • 也就是可以对另一个时间对象加7天或者减少7点
tdelta = datetime.timedelta(days=7)
print(tdelta)
# 7 days, 0:00:00

【2】打印今天的日期

tday = datetime.date.today()
print('今天的日期:{}'.format(tday))  
# 今天的日期:2023-06-02

【3】打印七天后的日期

tdelta = datetime.timedelta(days=7)
tday = datetime.date.today()
print('从今天向后推7天:{}'.format(tday + tdelta))  
# 从今天向后推7天:2023-06-09

【4】总结:日期对象与timedelta之间的关系

  • 日期对象 = 日期对象 +/- timedelta对象
  • timedelta对象 = 日期对象 +/- 日期对象

(1)定义日期对象/定义timedelta对象

now_date1 = datetime.date.today()
lta = datetime.timedelta(days=6)

(2)验证

now_date2 = now_date1 + lta  # 日期对象 = 日期对象 +/- timedelta对象
print(type(now_date2))  # <class 'datetime.date'>
lta2 = now_date1 - now_date2  # timedelta对象 = 日期对象 +/- 日期对象
print(type(lta2))  # <class 'datetime.timedelta'>

【五】练习

【1】计算举例今年过生日还有多少天

import datetime

birthday = datetime.date(2019, 12, 21)
now_date = datetime.date.today()
tday = datetime.date.today()
days = birthday - now_date
print('生日:{}'.format(birthday))
print('今天的日期:{}'.format(tday))
print('距离生日还有{}天'.format(days))

# 生日:2019-12-21
# 今天的日期:2023-06-02
# 距离生日还有-1259 days, 0:00:00天

【六】总结年月日时分秒及时区问题

import datetime

dt_today = datetime.datetime.today()
dt_now = datetime.datetime.now()
dt_utcnow = datetime.datetime.utcnow()  # UTC时间与我们的北京时间cha ju

print(dt_today)
# 2023-06-02 19:37:46.933068
print(dt_now)
# 2023-06-02 19:37:46.933068
print(dt_utcnow)
# 2023-06-02 11:37:46.933068

OS模块

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

【一】操作文件创建

  • 生成多层递归目录
os.makedirs('dirname1/dirname2')
  • 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.removedirs('dirname1')
  • 生成单级目录;相当于shell中mkdir dirname
os.mkdir('dirname')  
  • 删除单级空目录
    • 若目录不为空则无法删除,报错;
    • 相当于shell中rmdir dirname
os.rmdir('dirname')   
  • 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.listdir('dirname')  
  • 删除一个文件
os.remove() 
  • 重命名文件/目录
os.rename("oldname","newname")  
  • 获取文件/目录信息
os.stat('path/filename') 
  • 运行shell命令,直接显示
os.system("bash command") 
  • 运行shell命令,获取执行结果
os.popen("bash command).read() 
  • 获取当前工作目录,即当前python脚本工作的目录路径
os.getcwd() 
  • 改变当前脚本工作目录;相当于shell下cd
os.chdir("dirname") 

【二】操作文件路径

os.path

  • 返回path规范化的绝对路径
os.path.abspath(path)
  • 将path分割成目录和文件名二元组返回
os.path.split(path) 
  • 返回path的目录。
    • 其实就是 os.path.split(path) 的第一个元素
os.path.dirname(path) 
  • 返回path最后的文件名。
    • 如何 path以/或\结尾,那么就会返回空值。
    • os.path.split(path) 的第二个元素
os.path.basename(path) 
  • 如果path存在,返回True;
    • 如果path不存在,返回False
os.path.exists(path) 
  • 如果path是绝对路径,返回True
os.path.isabs(path)  
  • 如果path是一个存在的文件,返回True。
    • 否则返回False
os.path.isfile(path) 
  • 如果path是一个存在的目录,则返回True。
    • 否则返回False
os.path.isdir(path) 
  • 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.join(path1[, path2[, ...]])  
  • 返回path所指向的文件或者目录的最后访问时间
os.path.getatime(path)  
  • 返回path所指向的文件或者目录的最后修改时间
os.path.getmtime(path)  
  • 返回path的大小
os.path.getsize(path) 

【三】注意:os.stat('path/filename') 获取文件/目录信息 的结构说明

stat 结构:

st_mode: inode 保护模式
st_ino: inode 节点号。
st_dev: inode 驻留的设备。
st_nlink: inode 的链接数。
st_uid: 所有者的用户ID。
st_gid: 所有者的组ID。
st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。
st_atime: 上次访问的时间。
st_mtime: 最后一次修改的时间。
st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。

【四】os模块属性

  • os.sep 输出操作系统特定的路径分隔符

    • win下为 "\"
    • Linux下为 "/"
  • os.linesep 输出当前平台使用的行终止符

    • win下为 "\r\n"
    • Linux下为 "\n"
  • os.pathsep 输出用于分割文件路径的字符串

    • win下为 ;
    • Linux下为 :
  • os.name 输出字符串指示当前使用平台。

    • win-> 'nt'
    • Linux-> 'posix'

random模块

【一】导入模块

import random

【二】随机小数

  • 大于0且小于1之间的小数
random.random()      
#  0.7664338663654585
  • 大于1小于3的小数
random.uniform(1,3)
# 1.6270147180533838

【三】随机整数

  • 大于等于1且小于等于5之间的整数
random.randint(1,5)
  • 大于等于1且小于10之间的奇数
random.randrange(1,10,2)

【四】随机选择一个返回

  • 随机选择多个返回,返回的个数为函数的第二个参数
random.choice([1,'23',[4,5]])
# 1或者23或者[4,5]
  • 列表元素任意2个组合
random.sample([1,'23',[4,5]],2) 
# [[4, 5], '23']

【五】打乱列表顺序

  • 打乱次序
item=[1,3,5,7,9]
random.shuffle(item)
print(item)
# [5, 1, 3, 7, 9]

random.shuffle(item)
print(item)
# [5, 9, 7, 1, 3]

【六】练习

  • 生成随机验证码
import random


def v_code():
    code = ''
    for i in range(5):
        num = random.randint(0, 9)
        alf = chr(random.randint(65, 90))
        add = random.choice([num, alf])
        code = "".join([code, str(add)])

    return code


print(v_code())
# 713OI

random模块生成六位随机数字+大小写验证码

【一】代码

import random

'''生成六位随机 (数字 + 大小写) 验证码'''


def get_verify_code(n):
    code = ''
    for i in range(n):
        random_int = str(random.randint(0, 9))  # 0-9之间的整数
        random_upper = chr(random.randint(65, 90))  # A-Z之间的字母
        random_lower = chr(random.randint(97, 122))  # a-z之间的字母
        temp = random.choice([random_int, random_upper, random_lower])
        code += temp
    return code


if __name__ == "__main__":
    res = get_verify_code(6)
    print(res)

【二】引用方法

from verify_code import get_verify_code

# n 为获取到的随机验证码的位数
code_verify = get_verify_code(n)
# 打印生成的 随机验证码
print(code_verify)

序列化模块

什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化

【一】为什么要有序列化模块

  • 比如,我们在 python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?

    • 现在我们能想到的方法就是存在文件里
    • 然后另一个 python 程序再从文件里读出来。
  • 但是我们都知道

    • 对于文件来说是没有字典这个概念的
    • 所以我们只能将数据转换成字典放到文件中。
  • 你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了

    • 为什么我们还要学习序列化模块呢?
  • 没错序列化的过程就是从dic 变成str(dic)的过程。

  • 现在你可以通过str(dic)

    • 将一个名为dic的字典转换成一个字符串,
  • 但是你要怎么把一个字符串转换成字典呢?

    • 聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。
    • eval()函数十分强大
      • 但是eval是做什么的?
      • e官方demo解释为:
        • 将字符串str当成有效的表达式来求值并返回计算结果。
      • BUT!强大的函数有代价。安全性是其最大的缺点。
  • 想象一下

    • 如果我们从文件中读出的不是一个数据结构,
    • 而是一句"删除文件"类似的破坏性语句
    • 那么后果实在不堪设设想。
      而使用eval就要担这个风险。
  • 所以

    • 我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)

【二】序列化的目的

  • 1、以某种存储形式使自定义对象持久化
  • 2、将对象从一个地方传递到另一个地方。
  • 3、使程序更具维护性。

【三】序列化模块之JSON

导入模块

import json
  • Json 模块提供了四个功能:
    • dumps
    • dump
    • loads
    • load

【1】loads和dumps

  • 序列化
    • 将一个字典转换成一个字符串
import json

dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = json.dumps(dic)  
print(type(str_dic), str_dic)  
# <class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"}

注意

​ json转换完的字符串类型的字典中的字符串是由 "" 表示的

  • 反序列化
    • 将一个字符串格式的字典转换成一个字典
import json

dic2 = json.loads(str_dic)  
print(type(dic2),dic2)  
#<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}

注意

​ 要用 jsonloads 功能处理的字符串类型的字典中的字符串必须由 "" 表示

  • 也可以处理嵌套的数据类型
import json

list_dic = [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
str_dic = json.dumps(list_dic)
print(type(str_dic), str_dic)
# <class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}]
import json

list_dic = [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
str_dic = json.dumps(list_dic)
list_dic2 = json.loads(str_dic)
print(type(list_dic2), list_dic2)
# <class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]

【2】load和dump

  • dump方法
    • 接收一个文件句柄
    • 直接将字典 转换成 json字符串 写入文件
import json
f = open('json_file','w')
dic = {'k1':'v1','k2':'v2','k3':'v3'}
json.dump(dic,f)
f.close()
  • load方法
    • 接收一个文件句柄
    • 直接将文件中的 json字符串 转换成 数据结构返回
f = open('json_file')
dic2 = json.load(f)  
f.close()
print(type(dic2),dic2)

【四】ensure_ascii关键字参数

import json

f = open('file', 'w')
json.dump({'国籍': '中国'}, f)
ret = json.dumps({'国籍': '中国'})
f.write(ret + '\n')
json.dump({'国籍': '美国'}, f, ensure_ascii=False)
ret = json.dumps({'国籍': '美国'}, ensure_ascii=False)
f.write(ret + '\n')
f.close()

【五】其它参数说明

Serialize obj to a JSON formatted str.(字符串表示的json对象) 

Skipkeys:
	默认值是False
    	如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None)设置为False时,就会报TypeError的错误。
    	此时设置成True,则会跳过这类key 

ensure_ascii:
    当它为True的时候
    	所有非ASCII码字符显示为\uXXXX序列
        只需在dump时将ensure_ascii设置为False即可
        此时存入json的中文即可正常显示。

If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse). 
If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity). 

indent:
	应该是一个非负的整型
    	如果是0就是顶格分行显示
        如果为空就是一行最紧凑显示
        否则会换行且按照indent的数值显示前面的空白分行显示
    这样打印出来的json数据也叫pretty-printed json 

separators:
	分隔符
    	实际上是(item_separator, dict_separator)的一个元组
        默认的就是(‘,’,’:’);
        	这表示dictionary内keys之间用“,”隔开
            而KEY和value之间用“:”隔开。 

            
default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. 

sort_keys:
	将数据根据keys的值进行排序。 

To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.

【六】json 的格式化输出

import json

data = {'username': ['李华', '二愣子'], 'sex': 'male', 'age': 16}
json_dic2 = json.dumps(data, sort_keys=True, indent=2, separators=(',', ':'), ensure_ascii=False)
print(json_dic2)

# {
#   "age":16,
#   "sex":"male",
#   "username":[
#     "李华",
#     "二愣子"
#   ]
# }

pickle模块

json & pickle 模块 (用于序列化的两个模块)

  • json
    • 用于字符串 和 python数据类型间进行转换
  • pickle
    • 用于python特有的类型 和 python的数据类型间进行转换
  • pickle模块提供了四个功能:
    • dumps
    • dump(序列化,存)
    • loads(反序列化,读)
    • load (不仅可以序列化字典,列表...可以把python中任意的数据类型序列化

【一】方法演示

  • dumps
import pickle

dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)  # 一串二进制内容

# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'

  • loads
import pickle

dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
dic2 = pickle.loads(str_dic)
print(dic2)  # 字典

# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
  • dump
import pickle
import time

struct_time = time.localtime(1000000000)
print(struct_time)
# time.struct_time(tm_year=2001, tm_mon=9, tm_mday=9, tm_hour=9, tm_min=46, tm_sec=40, tm_wday=6, tm_yday=252, tm_isdst=0)
f = open('pickle_file', 'wb')
pickle.dump(struct_time, f)
f.close()
  • load
import pickle
import time

struct_time = time.localtime(1000000000)
print(struct_time)
# time.struct_time(tm_year=2001, tm_mon=9, tm_mday=9, tm_hour=9, tm_min=46, tm_sec=40, tm_wday=6, tm_yday=252, tm_isdst=0)

f = open('pickle_file', 'rb')
struct_time2 = pickle.load(f)
print(struct_time2.tm_year)

【二】优点

  • 这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?

  • 这里我们要说明一下

    • json是一种所有的语言都可以识别的数据结构。
  • 如果我们将一个字典或者序列化成了一个json存在文件里

    • 那么java代码或者js代码也可以拿来用。
  • 但是如果我们用pickle进行序列化

    • 其他语言就不能读懂这是什么了~
  • 所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块

  • 如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle

subprocess模块

【一】介绍

subprocess模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值。

简单理解就是:使用我们自己的电脑去链接别人的电脑 (socket模块)

【二】使用

# windows系统默认的编码格式是:gbk
import subprocess

"""
    1. 使用我们自己的电脑去链接别人的电脑 (socket模块)
"""
res = subprocess.Popen('tasklistaaa', shell=True,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE
                       )

print(res)  # <subprocess.Popen object at 0x000001ABB1970310>
# print(res.stdout.read().decode('gbk'))  # tasklist执行之后的正确结果返回
print(res.stderr.read().decode('gbk'))
  • subprocess模块首先推荐使用的是它的run方法,
  • 更高级的用法可以直接使用Popen接口。

【三】run() 方法

【1】语法:

subprocess.run( args, 
                *, 
                stdin=None, 
                input=None, 
                stdout=None, 
                stderr=None,
                capture_output=False, 
                shell=False, 
                cwd=None, 
                timeout=None, 
                check=False, 
                encoding=None, 
                errors=None, 
                text=None, 
                env=None, 
                universal_newlines=None )

可见run()函数有很多的参数,下面详细介绍几个重要的参数。

  • <1> args

    • 表示要执行的命令。
      • 必须是一个字符串,字符串参数列表。
  • <2> stdinstdoutstderr

    • 子进程的标准输入、标准输出和标准错误。
      • 其值可以是 subprocess.PIPE
        • subprocess.PIPE 表示为子进程创建新的管道。
      • subprocess.DEVNULL
        • subprocess.DEVNULL 表示使用 os.devnull
          • 默认使用的是 None,表示什么都不做。
      • 一个已经存在的文件描述符、
      • 已经打开的文件对象
      • 或者 None。
    • 另外
      • stderr 可以合并到 stdout 里一起输出。
  • <3> timeout:

    • 设置命令超时时间。
      • 如果命令执行时间超过timeout,
        • 子进程将被杀死,并弹出 TimeoutExpired 异常。
  • <4>check

    • 如果该参数设置为 True,并且进程退出状态码不是0
      • 则弹出 CalledProcessError 异常。
  • <5>encoding:

    • 如果指定了该参数,则 stdinstdoutstderr 可以接收字符串数据,并以该编码方式编码。
    • 否则只接收 bytes 类型的数据。
  • <6>shell:

    • 如果该参数为 True
      • 将通过操作系统的shell执行指定的命令。

【2】示例一

 import subprocess

subprocess.run(["ls", "-l", "/dev/null"])
# 该run()函数只传入了一个参数,就是args,而且该参数是以列表的形式传入的。
returncode: 执行完子进程状态,通常返回状态为0则表明它已经运行完毕,若值为负值 "-N",表明子进程被终。

【3】示例二

import subprocess


def runcmd(command):
    ret = subprocess.run(command,  # 子进程要执行的命令
                         shell=True,  # 执行的是shell的命令
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         encoding="utf-8",
                         timeout=1)


if ret.returncode == 0:  # returncode属性是run()函数返回结果的状态。
    print("success:", ret)
else:
    print("error:", ret)

runcmd(["dir", "/b"])  # 序列参数
runcmd("exit 1")  # 字符串参数

【四】Popen() 方法

Popen 是 subprocess的核心,子进程的创建和管理都靠它处理。

【1】语法

class subprocess.Popen( args,
                        bufsize=-1, 
                        executable=None, 
                        stdin=None, 
                        stdout=None, 
                        stderr=None, 
                        preexec_fn=None, 
                        close_fds=True,
                        shell=False, 
                        cwd=None, 
                        env=None, 
                        universal_newlines=False, 
                        startupinfo=None, 
                        creationflags=0,
                        restore_signals=True, 
                        start_new_session=False, 
                        pass_fds=(),
                        *, 
                        encoding=None, 
                        errors=None )

常用参数:

  • args:

    • shell命令,可以是字符串或者序列类型(如:list,元组)
  • bufsize:

    • 缓冲区大小。当创建标准流的管道对象时使用,
      • 默认是-1
      • 0代表不使用缓冲区
      • 1:表示行缓冲,仅当universal_newlines=True时可用,也就是文本模式
        • 正数:表示缓冲区大小
        • 负数:表示使用系统默认的缓冲区大小。
  • stdin, stdout, stderr:

    • 分别表示程序的标准输入、输出、错误句柄
  • preexec_fn:

    • 只在 Unix 平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
  • shell:

    • 如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
  • cwd:

    • 用于设置子进程的当前目录。
  • env:

    • 用于指定子进程的环境变量。
      • 如果 env = None,子进程的环境变量将从父进程中继承。
  • 创建一个子进程,然后执行一个简单的命令。

【2】示例

import time
import subprocess

p = subprocess.Popen('ls -l', shell=True)


# Popen的对象所具有的方法:
#       poll(): 检查进程是否终止,如果终止则返回 returncode,否则返回 None。
#       wait(timeout): 等待子进程终止。
#       communicate(input,timeout): 和子进程交互,发送和读取数据。
#       send_signal(singnal): 发送信号到子进程 。
#       terminate(): 停止子进程,也就是发送SIGTERM信号到子进程。
#       kill(): 杀死子进程。发送 SIGKILL 信号到子进程。

def f(command):
    # 创建一个子进程,并且执行
    subprocess = subprocess.Popen(command,
                                  shell=True,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  encoding="utf-8")
    # 这里是用wait()方法,等待子进程结束。
    subprocess.wait(2)
    if subprocess.poll() == 0:
        print(subprocess.communicate()[1])
    else:
        print("失败")


f("java -version")
f("exit 1")

hashlib模块

【一】算法介绍

  • Python的hashlib提供了常见的摘要算法

    • 如MD5
    • SHA1等等。
  • 什么是摘要算法呢?

    • 摘要算法又称哈希算法、散列算法。
    • 它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
  • 摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest

    • 目的是为了发现原始数据是否被人篡改过。
  • 摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数

    • 计算f(data)很容易,但通过digest反推data却非常困难。
    • 而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
  • 我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:

import hashlib

md5 = hashlib.md5()
md5.update(b'how to use md5 in python hashlib?')
print(md5.hexdigest())

# 计算结果如下:
# d26a53750bc40b38b65a520292f69306
  • 如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
import hashlib

md5 = hashlib.md5()
md5.update(b'how to use md5 in ')
md5.update(b'python hashlib?')
print(md5.hexdigest())

# 计算结果如下:
# d26a53750bc40b38b65a520292f69306
  • MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。
  • 另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
import hashlib

sha1 = hashlib.sha1()
sha1.update(b'how to use sha1 in ')
sha1.update(b'python hashlib?')
print(sha1.hexdigest())
# 2c76b57293ce30acef38d98f6046927161b46a44
  • SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。
  • 比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。

【二】摘要算法的应用

  • 任何允许用户登录的网站都会存储用户登录的用户名和口令。
  • 如何存储用户名和口令呢?
    • 方法是存到数据库表中:
name    | password
--------+----------
michael | 123456
bob     | abc999
alice   | alice2008
  • 如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。
  • 此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。
  • 正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:
username | password
---------+---------------------------------
michael  | e10adc3949ba59abbe56e057f20f883e
bob      | 878ef96e86145580c38c87f0410ad153
alice    | 99b1c2188db85afee403b1536010c2c9
  • 考虑这么个情况,很多用户喜欢用123456,888888,password这些简单的口令
  • 于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
  • 这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。
  • 对于用户来讲,当然不要使用过于简单的口令。
    • 但是,我们能否在程序设计上对简单口令加强保护呢?
  • 由于常用口令的MD5值很容易被计算出来
    • 所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5
    • 这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
hashlib.md5("salt".encode("utf8"))
  • 经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。
  • 但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。
    • 有没有办法让使用相同口令的用户存储不同的MD5呢?
  • 如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
  • 摘要算法在很多地方都有广泛的应用。
    • 要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

Day 29 29.2 MD5摘要算法 - Chimengmeng - 博客园 (cnblogs.com)

【三】MD5加密在验证登录中的应用

  • main.py
# -*-coding: Utf-8 -*-
# @File : login .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/3

import os
import hashlib
from verify_code import get_verify_code

# 声明数据库位置
file_path = 'Infos' + '\\' + 'user_pwd.txt'
if not os.path.exists(file_path):
    with open(file_path, 'a') as f:
        f.write('')


def encrypt_decrypt(data):
    # 转为二进制数据
    data = data.encode('utf-8')
    # 创建md5对象
    md5 = hashlib.md5()
    # md5进行加密
    md5.update(data)
    # 取出md5加密后的哈希值
    encrypt_result = md5.hexdigest()
    return encrypt_result


def write_read_data(data=None, cmd=0):
    if cmd == 0:
        with open(file_path, 'a+') as f:
            f.write(data)
    else:
        user_list = []
        user_info_data = []
        with open(file_path, 'r') as f:
            for line in f:
                user_data = {}
                line = line.strip().split('|')
                username, password, salt_code = line[0], line[1], line[2]
                user_data['username'] = username
                user_data['password'] = password
                user_data['salt_code'] = salt_code
                user_list.append(username)
                user_info_data.append(user_data)
        return [user_list, user_info_data]


def register(username, password):
    # 获得六位数的盐
    salt_code = get_verify_code(6)
    # 原始密码加盐
    password_str = password + salt_code
    # 加盐密码加密
    password_encrypted = encrypt_decrypt(password_str)
    # 拼接存储数据格式
    user_pwd_data = f'{username}|{password_encrypted}|{salt_code}\n'
    # 写入文件存储数据
    write_read_data(user_pwd_data, cmd=0)
    print(f'{username}注册成功,注册结束!')


def login():
    # 拿到用户名列表,用户名和密码及加盐后的列表
    user_list, user_data = write_read_data(data=None, cmd=1)
    username_input = input('校验Username:>>>')
    password_input = input('校验Password:>>>')
    # 判断用户名是否存在于用户名列表中
    # 存在则继续登录
    if username_input in user_list:
        # 循环所有用户名及信息
        for info in user_data:
            # 取用户名和加密后的密码
            username = info['username']
            password = info['password']
            # 取加盐后的密码
            salt_code = info['salt_code']
            # 当前密码加盐
            password_str = password_input + salt_code
            # 当前加盐密码加密
            password_encrypted = encrypt_decrypt(password_str)
            if username == username_input and password == password_encrypted:
                print('登陆成功!')
                return True
            else:
                print('用户名或密码错误,登陆失败!')
                main()
    else:
        print('用户名不存在,请注册')
        main()


def main():
    # 先校验用户名和密码是否存在
    username = input('Username:>>>')
    # 获取用户列表
    user_list = write_read_data(cmd=1)[0]
    # 不存在用户信息则进行注册
    if username not in user_list:
        print('当前用户未注册注册,注册操作开始!')
        # 注册函数
        password = input('Password:>>>')
        register(username, password)
        # 注册完成进行二次验证校验登陆
        main()
    else:
        password = input('Password:>>>')
        # 用户存在进行登陆校验
        print('进行登陆操作')
        # 拿到成功的结果
        res = login()
        # 成功则退出
        if res:
            print('欢迎使用')
            pass
        else:
            # 不成功二次校验
            login()


if __name__ == '__main__':
    main()
  • verify_code
# -*-coding: Utf-8 -*-
# @File : verify_code .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/3

import random

'''生成六位随机 (数字 + 大小写) 验证码'''


def get_verify_code(n):
    code = ''
    for i in range(n):
        random_int = str(random.randint(0, 9))  # 0-9之间的整数
        random_upper = chr(random.randint(65, 90))  # A-Z之间的字母
        random_lower = chr(random.randint(97, 122))  # a-z之间的字母
        temp = random.choice([random_int, random_upper, random_lower])
        code += temp
    return code


if __name__ == "__main__":
    res = get_verify_code(6)
    print(res)

Day 29 29.2 MD5摘要算法 - Chimengmeng - 博客园 (cnblogs.com)

logging模块

【一】函数式简单配置

import logging  
logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message')

默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为

WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG)

默认的日志格式为日志级别:Logger名称:用户输出消息。

【二】灵活配置日志级别,日志格式,输出位置:

import logging

file_handler = logging.FileHandler(filename='x1.log', mode='a', encoding='utf-8', )
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[file_handler, ],
    level=logging.ERROR
)

logging.error('你好')

【三】日志切割

import time
import logging
from logging import handlers

sh = logging.StreamHandler()
rh = handlers.RotatingFileHandler('myapp.log', maxBytes=1024, backupCount=5)
fh = handlers.TimedRotatingFileHandler(filename='x2.log', when='s', interval=5, encoding='utf-8')
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[fh, sh, rh],
    level=logging.ERROR
)

for i in range(1, 100000):
    time.sleep(1)
    logging.error('KeyboardInterrupt error %s' % str(i))

【四】配置参数

  • logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为
    • 可用参数有:
filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为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用户输出的消息

【五】logger对象配置

import logging

logger = logging.getLogger()
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log',encoding='utf-8') # 再创建一个handler,用于输出到控制台 ch = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setLevel(logging.DEBUG)fh.setFormatter(formatter) ch.setFormatter(formatter) 
logger.addHandler(fh) #logger对象可以添加多个fh和ch对象 logger.addHandler(ch) logger.debug('logger debug message') logger.info('logger info message') logger.warning('logger warning message') logger.error('logger error message') logger.critical('logger critical message')
  • logging库提供了多个组件:
    • Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口
      • Handler发送日志到适当的目的地
      • Filter提供了过滤日志信息的方法
      • Formatter指定日志显示格式。
    • 另外,可以通过:
      • logger.setLevel(logging.Debug)设置级别,
      • 当然,也可以通过fh.setLevel(logging.Debug)单对文件流设置某个级别。

【六】日志详细使用

【1】logger对象:负责产生日志

import logging

logger = logging.getLogger('转账记录')

【2】filter对象:负责过滤日志(直接忽略)


【3】handler对象:负责日志产生的位置

import logging

# 产生到文件的
hd1 = logging.FileHandler('a1.log',encoding='utf8')  
# 产生到文件的
hd2 = logging.FileHandler('a2.log',encoding='utf8')  
# 产生在终端的
hd3 = logging.StreamHandler()  

【4】formatter对象:负责日志的格式

import logging

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

【5】绑定handler对象

import logging

logger.addHandler(hd1)
logger.addHandler(hd2)
logger.addHandler(hd3)

【6】绑定formatter对象

import logging

hd1.setFormatter(fm1)
hd2.setFormatter(fm2)
hd3.setFormatter(fm1)

【7】设置日志等级

import logging

logger.setLevel(30)

【8】记录日志

import logging

logger.debug('写了半天 好累啊 好热啊')

【七】配置成字典格式

import logging
import logging.config

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

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

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

logfile_path = 'a3.log'
# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
        'test': {
            'format': test_format
        },
    },
    'filters': {},  # 过滤日志
    'handlers': {
        # 打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
        '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',
            'propagate': True,  # 向上(更高level的logger)传递
        },  # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
        'other': {
            'handlers': ['other', ],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}

# 使用配置字典
logging.config.dictConfig(LOGGING_DIC)  # 自动加载字典中的配置
logger1 = logging.getLogger('xxx')
logger1.debug('好好的 不要浮躁 努力就有收获')

【八】日志终极版

import logging

# 一:日志配置
logging.basicConfig(
    # 1、日志输出位置:1、终端 2、文件
    # filename='access.log', # 不指定,默认打印到终端

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

    # 3、时间格式
    datefmt='%Y-%m-%d %H:%M:%S %p',

    # 4、日志级别
    # critical => 50
    # error => 40
    # warning => 30
    # info => 20
    # debug => 10
    level=30,
)

# 二:输出日志
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')

'''
# 注意下面的root是默认的日志名字
WARNING:root:警告warn
ERROR:root:错误error
CRITICAL:root:严重critical
'''

【九】logging模块处理流程

分为几个模块:

  • logger: 最高层模块,用来输出log

    • logger.level来筛选log
    • logger.debug()/info()/warning()/error()等输出log
  • handler:

    • 经过logger过滤后log会分发给所有handler处理。
    • 每个handler有自己的level, formatter, 以及输出流
    • handler.level 过滤log
    • handler.formatter 决定log输出的样式
  • StreamHandler:

    • 常见的StreamHandler输出到标准流,FileHandler,输出到文件
  • level的排序:NOTSET < DEBUG < INFO < WARNING < ERROR

    • 只有大于level的才会被处理
  • 注意:

    • 一条log会经历两次过滤
      • 一次是logger.level
      • 一次是handler.level
    • 被前者过滤掉的log不会进入handler处理流程。
    • handler的level和logger的level没有什么必然关系。
posted @ 2023-06-05 09:35  Chimengmeng  阅读(81)  评论(0编辑  收藏  举报
/* */