一 OS模块

os模块的主要功能:

  • 访问和操作系统相关信息

  • 操作目录及文件

  • 执行系统命令

  • 管理进程(到后面学习到并发编程再学这个)

OS模块
常用属性 os.environ:获取当前操作系统的环境变量
import os
if __name__ == '__main__':
print(os.environ) #environ({'PATH': '/Users/..
print(os.environ.get("USER")) # 获取当前系统用户的用户名 admin
环境变量     读取环境变量
# 如果有这个键,返回对应的值,如果没有,则返回 none。
print(os.environ.get("HOME")) #/Users/admin

# 也可以设置默认值,当键存在时返回对应的值,不存在时,返回默认值
print(os.environ.get("HOME", "/")) # 环境变量HOME不存在,返回 / #/Users/admin

# 也可以使用getenv来读取
print( os.getenv('变量名') ) #None
新增环境变量
# os.environ['变量名'] = '变量值'
os.environ["HOMEPATH"] = "/home/admin"

# 环境变量的键要按照变量的命名规范,值只能是string类型
os.environ.setdefault("HOMEPATH3", "/home/admin")

print(os.environ)
 更新环境变量
# 针对已经存在的环境变量,直接改值,就可以达到修改环境变量的效果,但是python程序执行结束以后,会重新恢复的。
os.environ["HOMEPATH3"] = "/home/xiaoming"
删除环境变量   
# del(os.environ['变量名'])

os.environ.setdefault("HOMEPATH", "/home/lll")
del os.environ['HOMEPATH']
print(os.environ)
 判断环境变量是否存在  
# '变量名' in os.environ   # 存在返回 True,不存在返回 False
# 一般都是先判断是否有该环境变量存在,然后再进行读取、修改或删除
# 注意,不要删除系统默认的环境变量
if os.getenv("HOMEPATH"):
del os.environ["HOMEPATH"]
 文件操作

 os.remove(path):删除文件,不能删除目录,文件不存在则报错

os.rename(src, dst):重命名文件,src源文件不存在,或dst新文件名已存在都会报错。

"""os.remove() 删除一个指定路径的文件""" !!!删除之前确定文件是否存在
if os.path.isfile(1.txt):
os.remove("1.txt")
"""os.rename() 文件重命名"""
os.rename("1.py", "2.py")
 
目录操作 

os.mkdir():新建空目录,目录存在,则报错。

os.listdir(path="."):列出指定目录下所有文件组成的列表,不指定path,则默认当前程序所在的工作目录

os.makedirs():创建多级目录,如果多级目录重叠则报错,部分重叠不会报错。mkdir -p 

"""os.mkdir() 新建空目录"""
os.mkdir("home")
# 如果要给目录创建文件,则可以使用之前的open内置函数
for i in range(10):
open(f"home/test_{i}.py", "w")

"""os.listdir() 列出当前程序的工作目录下的所有文件"""
# 列出当前程序的工作目录下所有文件
file_list = os.listdir()
print(file_list)

# 列出指定目录下所有文件
for file in os.listdir("home"):
print(file)

"""os.makedirs() 创建多级目录"""
if not os.path.isdir("2022/04/13")
os.makedirs("2022/04/13")#!!!创建目录之前必须判断目录是否存在
# # 重复创建一样报错,如果不希望报错,设置exist_ok的值为True
# os.makedirs("2021/05/01", exist_ok=True)
# 根据当前时间来创建
# from datetime import datetime
# date = datetime.now().strftime("%Y/%m/%d/%H/%M/%S")
# os.makedirs(date, exist_ok=True)
 
 路径操作

os.path.isdir():判断name是不是一个目录,name不是目录就返回false

os.path.isfile():判断name是不是一个文件,不存在返回false

os.path.dirname(filename):返回文件路径的目录部分 

os.path.basename(filename):返回文件路径的文件名部分

os.path.join(dirname,basename):将文件路径和文件名凑成完整文件路径

os.path.abspath(name):获得绝对路径

os.path.getatime():返回最近访问时间 浮点型

os.path.getmtime():返回上一次修改时间 浮点型

os.path.getctime():返回文件创建时间 浮点型

os.path.getsize():返回文件大小 字节单位

os.path.exists():判断文件或目录是否存在

 

""" os.path.isdir() 判断name是不是一个目录,name不是目录就返回false"""
print( os.path.isdir(home) ) # True,不要加"",否则是字符串
print( os.path.isdir(2.py) ) # False,不是目录,或不存在的文件都会返回False

""" os.path.isfile() 判断name是不是一个文件,不存在返回false"""
print( os.path.isfile("88.py")) # False, 不是文件,或不存在的文件都会返回False
print( os.path.isfile("home/2.py")) # True

""" os.path.dirname(filename) 返回文件路径的目录部分"""
# 返回当前文件路径
dir = os.path.dirname(__file__)
print(dir) #/Users/admin/PycharmProjects/demo

""" os.path.basename(filename) 返回文件路径的文件名部分"""
# 返回当前文件的文件名部分
dir = os.path.basename(__file__)
print(dir) #test.py

""" os.path.join(dirname,basename) 将文件路径和文件名凑成完整文件路径"""
dir = "/python/project"
file = "2.py"
ret = os.path.join(dir, file)
print(ret) # /python/project/2.py

""" os.path.abspath(path) 返回path的绝对路径"""
print( os.path.abspath("2.py") ) # /Users/admin/PycharmProjects/demo/2.py
print( os.path.abspath(".") ) #/Users/admin/PycharmProjects/demo/

""" os.path.getatime() 返回最近访问时间 浮点型"""
print( os.path.getatime("home/2.py") ) #1649860806.2789004

""" os.path.getmtime() 返回上一次修改时间 浮点型"""
print( os.path.getmtime("home/2.py") )

""" os.path.getctime() 返回文件创建时间 浮点型"""
print( os.path.getctime("home/2.py") )

""" os.path.getsize() 返回文件大小 字节单位"""
print( os.path.getsize("home/2.py") )

""" os.path.exists() 判断文件或目录是否存在"""
print( os.path.exists("home/2.py") ) #True
 
系统参数与命令执行 os.popen(command):创建一个命令管道对象,通往 cmd/shell终端。返回值是连接到管道的文件对象。 
# popen 执行终端命令
command = "ls -la"
# 如果要执行多条命令,务必要写在一块,一并执行用&&连接多条命令
ret = os.popen(command)
# print(ret) # 返回一个命令管道对象 <os._wrap_close object at 0x7f58802546a0>
# print(ret.read())
print(ret.readlines()) #返回一个列表
 

附:

1 目录操作的应用

import os
def rmdir(path):
    """
    递归删除多层非空目录文件
    :param path: 要删除的路径
    :return: None
    """
    if os.path.isfile(path):
        os.remove(path)
        return # 阻止代码继续往下执行
    elif os.path.isdir(path):
        # 切换工作目录
        os.chdir(path)
        for item in os.listdir():
            # 判断是否是文件
            if os.path.isfile(item):
                # 删除文件
                os.remove(item)
            else:
                # 删除多级目录
                rmdir(item)
        # 返回上一级目录
        os.chdir("..")
        # 把当前目录删除
        os.rmdir(path)
    else:
        print("路径有误!")

rmdir("home")

 二 shutil模块

shutil模块提供了一系列对文件和文件集合的高阶操作。 特别是提供了一些支持文件拷贝和删除、复制、移动的操作。

shutil模块
方法 描述 示例
copy(src,dst) 复制文件权限和内容

""" copy(src,dst) 复制文件权限和内容,src必须存在,dst不存在则自动创建 """
# shutil.copy("1.txt","2.txt")

copytree(src,dst) 复制文件夹里所有内容(递归复制)

""" copytree(src,dst) 递归文件夹里所有内容 """
# shutil.copytree("a","b")

rmtree(path) 递归删除文件或者目录

""" rmtree(path) 递归删除当前文件夹及其中所有内容"""
# shutil.rmtree("a")

move(src,dst) 移动文件或者文件夹

""" move(path1, paht2) 移动文件或者文件夹"""
# 注意paht2参数要在末尾加上斜杠,否则在移动到不存在目录以后就变成改名了
shutil.move("./home", "./demo2")

三 sys模块

sys模块主要是针对与Python解释器相关的变量和方法,不是针对主机操作系统的。

sys模块
属性/方法 描述 示例
sys.path 返回python解释器的搜索模块与包路径,默认使用环境变量PYTHONPATH的值
"""sys.path python导包路径列表。提供给python用于from和import导包时搜索文件的路径列表 """
# print(sys.path) #['/Users..
#
# # 临时增加一个导包路径,即可在后续操作中让python在新的路径中进行导包操作
# sys.path.insert(0, "/a/b")
# print(sys.path) #['/a/b', ..
sys.platform 返回操作系统平台名称,Linux系统是"linux", windows系统是"win32", max OS是"darwin"
""" sys.platform 返回操作系统平台名称 """#比os.name好用
print(sys.platform) # linux- linux, windows-win32, macos-darwin
print(os.name) #linux和macos-posix windows-nt
sys.modules 返回系统导入的模块字段,key是模块名,value是模块
""" sys.modules 返回系统导入的模块字段,key是模块名,value是模块 """
print(sys.modules) #{'sys': <module 'sys' (built-in)>,
# 返回所有python解释器已经导入的模块列表
print(sys.modules.keys()) #dict_keys(['sys', 'builtins',
print(sys.modules['builtins']) #<module 'builtins' (built-in)>
sys.version_info ‘final‘表示最终,也有‘candidate‘表示候选,表示版本级别,是否有后继的发行
""" sys.version_info ‘final‘表示最终,也有‘candidate‘表示候选,表示版本级别,是否有后继的发行 """
print(sys.version_info) #sys.version_info(major=3, minor=9, micro=9, releaselevel='final', serial=0)
#3.9.9:python解释程序的版本信息
sys.argv 命令行参数List,第一个元素是程序本身路径
""" 
sys.argv 打印调用python程序时,在终端下跟在python解释器命令后面所有的参数信息,
第一个元素是程序本身路径
"""
print(sys.argv) #['/Users/zm/PycharmProjects/demo/test.py']
python test.py student=xiaohong score=100
#['test.py','student=xiaohong','score=100']
sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0
""" sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0 """
print(sys.hexversion) # 50924016
sys.exit(n) 退出程序,其实就是exit()函数
""" sys.exit(n) 退出程序,正常退出时exit() """
sys.exit() # exit()
print("hello") # 因为上面代码已经退出了,所以此处不会执行

标准输入、标准输出以及标准错误输出(了解)

IO:I就是Input,译作输入,表示从外部设备输入到内存。O就是output,译作输出,表示从内存输出到外部设备。

而标准输入和标准输出就是用于IO操作的。在linux操作系统中,一切文件/目录/设备都是文件。因此所谓的标准输入、标准输出以及标准错误输出,其实就是linux系统下的3个默认文件,/dev/stdin、/dev/stdout、/dev/stderr。这3个文件并非普通文件,而是默认设备的链接文件。因此, 对三个文件的读写操作,本质上对系统默认输入和输出设备进行读写,系统默认的输入和输出设备默认一般是终端。当然,/dev/stdin、/dev/stdout、/dev/stderr仅仅是链接文件,并非真正的设备,他们之间的关系仅仅是关联。

属性/方法描述示例
sys.stdout 标准输出文件对象
""" sys.stdout 标准输出 """
print(sys.stdout)
# <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
sys.stdout.write("内容") 标准输出内容  
""" sys.stdout.write() 标准输出内容 """
sys.stdout.write("hello world")
sys.stdout.write("hello world\n")
#hello worldhello world
sys.stdout.writelines() 无换行输出  
""" sys.stdout.writelines() 无换行输出 """
sys.stdout.writelines(["hello", " world"]) # hello world
sys.stdin 标准输入文件对象  
""" sys.stdin 标准输入 """
print(sys.stdin)
# <_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>
sys.stdin.read() 输入一行  
""" sys.stdin.read() 输入一行 """
print("请输入您的姓名:", end='')
ret = sys.stdin.read() # 输入完成以后,回车,并使用ctrl+d结束输入
print(f"您刚才输入的是:{ret}")
# 请输入您的姓名:hello
#
# world
# ^D
# 您刚才输入的是:hello
#
#
# world
#
#
sys.stdin.readline() 从标准输入读一行,sys.stdout.write(“a”) 屏幕输出a
""" sys.stdin.readline() 从标准输入读一行"""
print("请输入您的姓名:", end="")
ret = sys.stdin.readline() # 输入完成以后,回车,表示结束输入
print(f"您刚才输入的是:{ret}")
# 请输入您的姓名:hi
# 您刚才输入的是:hi

# ret = input("请输入您的姓名:") # 内部本质上执行了 sys.stdin.readline().strip(’/n’)
# print(f"您刚才输入的是:{ret}")
sys.stderr 标准错误输出文件对象  
""" sys.stderr 错误输出 """
print(sys.stderr) # <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
sys.stderr.write("内容") 无换行输出错误  
sys.stderr.write('欢天喜地')#欢天喜地

四 文件压缩模块

  zipfile模块  tarfile模块
基本使用
'''基本使用'''
# 创建一个ZipFile对象, 表示一个zip文件
# ZipFile(路径包名,模式,压缩or打包,可选allowZip64)
'''zf = zipfile.ZipFile(file[, mode[, compression[, allowZip64]]])'''
# 参数:
# 1. file表示文件的路径或类文件对象(file-like object)
# 2. mode表示设置打开zip文件的访问权限模式,默认值为r
# r 表示读取已经存在的zip文件
# w 表示新建一个zip文档或覆盖一个已经存在的zip文档
# a 表示将数据追加到一个现存的zip文档中。
# 3. compression表示在写zip文档时使用的压缩方法
# zipfile.ZIP_STORED 只是存储模式,不会对文件进行压缩,这个是默认值
# zipfile.ZIP_DEFLATED 对文件进行zip算法压缩
# 4. allowZip64,如果要操作的zip文件大小超过2G,则必须设置allowZip64=True,否则报错。
'''基本使用'''
# 打开或创建一个tar文件
# tf = tarfile.open(name=None, mode='r')
# 参数:
# 1. name表示tar文件的路径
# 2. mode表示设置打开tar文件的访问权限模式,默认值为r
# r 打开和读取透明算法进行压缩的tar文件[可以使用这个打开读取, gzip, bzip2, lzma]
# r: 打开和读取没有进行压缩算法的tar文件
# r:gz 打开和读取使用gzip算法压缩的tar文件
# r:bz2 打开和读取使用bzip2算法压缩的tar文件
# r:xz 打开和读取使用lzma算法压缩的tar文件

# w 新建或覆盖一个已存在的未压缩的tar文件
# w:gz 新建或覆盖一个已存在的使用gzip算法压缩的tar文件
# w:bz2 新建或覆盖一个已存在的使用bzip2算法压缩的tar文件
# w:xz 新建或覆盖一个已存在的使用lzma算法压缩的tar文件

# a 将文件追加到一个现存的未压缩的tar文件中
 
压缩文件

# 1. zipfile.ZipFile()   写模式w打开或者新建压缩文件
'''压缩文件'''
# with zipfile.ZipFile("1.zip","w",zipfile.ZIP_DEFLATED)as zf:
# # 2. zf.write(路径,别名) 向压缩文件中添加文件内容
# zf.write("/Users/admin/PycharmProjects/demo/1.py", "1_new.py")
# zf.write("/Users/admin/PycharmProjects/demo/2.py", "2_new.py")
'''打包不压缩'''
# 打开或创建一个tar包
# with tarfile.open("1.tar","w") as tf:
# # tf.add(路径,别名)
# tf.add("/Users/admin/PycharmProjects/demo/1.py", "new_1.py")
# tf.add("/Users/admin/PycharmProjects/demo/2.py", "new_2.py")
'''打包并压缩'''
"""bz2, 使用bzip2算法压缩打包"""
# with tarfile.open("data.tar.bz2","w:bz2") as tf:
# tf.add("/Users/admin/PycharmProjects/demo/1.py", "new-1.py")
# tf.add("/Users/admin/PycharmProjects/demo/2.py", "new_2.py")
"""gz, 使用gzip算法压缩打包"""
# with tarfile.open("data.tar.gz","w:gz") as tf:
# tf.add("/Users/admin/PycharmProjects/demo/1.py", "new-1.py")
# tf.add("/Users/admin/PycharmProjects/demo/2.py", "new_2.py")
 
解压文件
'''解压文件'''
# with zipfile.ZipFile("1.zip","r") as zf: #读模式r打开压缩文件
# # zf.extractall(解压路径) 解压所有文件到某个路径下
# # zf.extract(包内指定文件,解压路径) 解压指定的某个文件到某个路径下
# zf.extractall("/Users/admin/PycharmProjects/demo/home")
'''解压文件'''
# with tarfile.open("1.tar", "r") as tf:
# # 解压所有文件到指定路径
# tf.extractall("/Users/admin/PycharmProjects/demo/home")
 
追压文件
'''追压文件'''
# 直接使用a模式,像上面压缩文件操作一样,直接通过write操作即可
# with zipfile.ZipFile("1.zip","a",zipfile.ZIP_DEFLATED) as zf:
# zf.write("/Users/admin/PycharmProjects/demo/goods.txt","3.txt")
 
"""追加打包"""
# 只能对之前使用w模式创建的未压缩的tar包进行追加!!看清楚后缀!!!
# with tarfile.open("1.tar", "a") as tf:
# tf.add("/Users/admin/PycharmProjects/demo/test.py", "new_test.py")
查看包内容 
'''查看包内容'''
# with zipfile.ZipFile("1.zip","r") as zf:
# data = zf.namelist()
# print(data) #['1_new.py', '2_new.py', '3.txt']
 
'''查看包内容'''
# with tarfile.open("1.tar","r") as tf:
# data = tf.getnames()
# print(data) #['new_1.py', 'new_2.py']

附:1 解决tarfile追加不压缩

import tarfile, os
"""追加压缩"""
# tarfile不支持直接追加压缩的,所以可以先解压,在打包压缩

# 1. 解压文件
path1 = "./backup/data.tar.bz2/"  # 临时目录
with tarfile.open("data.tar.bz2", "r") as tf:
    tf.extractall(path1)

# 2. 追加合并文件
# os.system("cp -a /home/moluo/Desktop/4.py  " + path1)
shutil.copy("/home/admin/Desktop/code/sysdemo.py", path1) # 复制文件
shutil.copytree("/home/admin/Desktop/code/demo2",  f"{path1}/demo2")  # 复制目录

# 3. 重新打包
list_file = os.listdir(path1)
# print(list_file)

# 4. 创建一个新的bz2模式的压缩包
with tarfile.open("data_new.tar.bz2","w:bz2") as tf:
    # 循环遍历文件夹当中的所有内容
    for file in list_file:
        # 拼接路径
        file_path = os.path.join(path1, file)
        # tf.add (路径,别名)
        tf.add(file_path, file)

# 5. 删除临时目录
shutil.rmtree("./backup")

2 基于生成器来提取tar大文件中指定内容[yield:占用一块内存空间]

import os
import tarfile

def tar_files(members):
    for tarinfo in members:
        if os.path.splitext(tarinfo.name)[1] == ".py": # 过滤条件
            yield tarinfo

with tarfile.open("data_new.tar.bz2", "r") as tf:
    tf.extractall("./new_data", members=tar_files(tf))#接收yield的返回值,存储到new_data路径

 五 日志处理

1. 说明:日志,是记录和跟踪程序运行过程中所发生的事件的文件内容。python内置了一个标准库模块logging用于记录程序运行过程的日志信息。开发人员可以提前在代码中调用日志功能来记录程序执行过程中是否会发生了某些日志事件,如文件被误删了,数据读取失败了,用户注册账号失败等。

开发中,一个事件的发生就会记录一条对应的日志信息,而一个日志信息通常需要包括事件相关的以下几个内容:

  • 事件发生时间-年月日时分秒

  • 事件发生位置-所在的文件路径或网址路径

  • 事件的严重程度–日志level级别

  • 事件内容-日志内容

上面这些都是一条日志记录中可能包含的字段信息,当然还可以包括一些其他信息(如进程ID、进程名称、线程ID、线程名称等)。日志格式就是用来定义一条日志记录中包含那些字段的,且日志格式通常都是可以自定义的。

logging模块默认定义了以下几个日志等级

日志等级 (level)等级 (从低到高)描述
loging.DEBUG 10 调试级别,在程序出现异常时,开发人员会使用DEBUG日志来进行问题诊断。
loging.INFO 20 运行级别,记录一些关键信息到日志中让开发人员确认程序是正常运行的。
loging.WARNING 30 警告级别,记录如某些函数被废弃了或服务器磁盘可用空间较低等情况。
loging.ERROR 40 错误级别,记录程序运行时导致程序运行终止的错误信息,如调用了未定义的函数等情况。
loging.CRITICAL 50 致命级别,记录让程序不能启动不能运行的危险错误信息,如语法错误等情况。

注意:

  1. 日志严重等级是从上到下依次升级的,即:DEBUG < INFO < WARNING(WARN) < ERROR < CRITICAL(FATAL)

  2. 当程序中指定一个日志级别后,程序会记录所有大于或等于指定级别的日志信息,小于该级别的日志将会被丢弃

2. 简单日志配置

import logging

# LOG_FORMAT = "{日志时间} {日志记录器名称} {日志等级名称} {程序路径}:{行号} {日志信息} "
LOG_FORMAT = "{asctime}{name} {levelname} {pathname}:{lineno} {message} "
# 配置输出时间的格式
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %A ' # %A周几

# 通过 logging.basicConfig 进行简单日志配置
logging.basicConfig(
    level=logging.INFO,      # 会输出到日志信息中的日志级别(大于或等于该级别)
    format=LOG_FORMAT,       # 日志格式
    datefmt = DATE_FORMAT ,  # 时间格式
    filename="demo.1.log",   # 记录日志到文件
    style="{"
)

# 经过上面配置好的简单日志,接着就可以直接使用日志
logging.debug("调试代码")
logging.info("记录程序是否正常运行")
logging.warning("记录程序运行过程中的警告信息")
logging.error("记录程序运行过程中的异常/错误信息")

'''
2022-04-17  21:16:33 Sunday root INFO /Users/zm/PycharmProjects/demo/test.py:187 记录程序是否正常运行 
2022-04-17  21:16:33 Sunday root WARNING /Users/zm/PycharmProjects/demo/test.py:188 记录程序运行过程中的警告信息 
2022-04-17  21:16:33 Sunday root ERROR /Users/zm/PycharmProjects/demo/test.py:189 记录程序运行过程中的异常/错误信息
'''

注:

2.1 fomat格式字符串参数

字段/属性名称使用格式描述
asctime %(asctime)s 将日志的时间构造成可读的形式,默认情况下是‘2016-02-08 12:00:00,123’精确到毫秒
name %(name)s 所使用的日志器名称,默认是’root',因为默认使用的是 rootLogger
filename %(filename)s 调用日志输出函数的模块的文件名; pathname的文件名部分,包含文件后缀
levelname %(levelname)s 日志的最终等级(被filter修改后的)
message %(message)s 日志信息, 日志记录的文本内容
lineno %(lineno)d 当前日志的行号, 调用日志输出函数的语句所在的代码行
pathname %(pathname)s 完整路径 ,调用日志输出函数的模块的完整路径名,可能没有
process %(process)s 当前进程, 进程ID。可能没有
thread %(thread)s 当前线程, 线程ID。可能没有
module %(module)s 调用日志输出函数的模块名, filename的名称部分,不包含后缀即不包含文件后缀的文件名

2.2 longging.basicConfig函数参数

参数名称描述
filename 指定日志输出目标文件的文件名(可以写文件名也可以写文件的完整的绝对路径,写文件名日志放执行文件目录下,写完整路径按照完整路径生成日志文件),指定该设置项后日志信心就不会被输出到控制台了
filemode 指定日志文件的打开模式,默认为’a'。需要注意的是,该选项要在filename指定时才有效
format 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
datefmt 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
level 指定日志器的日志级别
stream 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
style Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为'%'、'{‘和’$',默认为'%'
handlers Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。

3 详细日志配置

3.1 logging模块的四大组件:

组件名称 对应类名 功能描述 使用参数格式  组件关系
日志器 Logger 提供了应用程序可一直记录日志的对象

logging.getLogger([name])

(返回一个logger日志器对象,如果没有指定名字将返回root logger)

这些组件之间的关系描述:

  • 日志器(Logger)需要通过处理器(Handler)将日志信息输出到目标位置,如:文件、sys.stdout(标准输出)、网络、邮件等,

    不同的处理器(Handler)可以将日志输出到不同的位置。

  • 日志器(Logger)可以设置多个处理器(Handler)将一条日志记录同时输出到多个不同的位置。

  • 每个处理器(Handler)都可以设置自己的过滤器(Filter)实现日志过滤,从而只保留感兴趣的日志。

  • 每个处理器(Handler)都可以设置自己的格式器(Formatter)实现同一条日志以不同的格式输出到不同的地方。

简单说就是:日志器(Logger)是入口,真正干活儿的是处理器(Handler),处理器(handler)还可以通过过滤器(Filter)和格式器(Formatter)对要输出的日志内容做过滤和格式化等处理操作。

处理器 Handler

将logger创建的日志记录发送到合适的目的地进行输出的对象。

基于日志消息的等级将日志信息分发到预先指定的位置(文件、网络、邮件等)。Logger日志器对象可以通过addHandler()方法为自己添加0个/更多个handler处理器对象。

比如,一个应用程序可能想要实现以下几个日志需求:

  1. 把所有日志都发送到一个日志文件中;

  2. 把所有级别大于等于error的日志发送到stdout(标准输出,也就是终端);

  3. 把所有级别为critical的日志发送到一个email邮件地址或短信。这种场景就需要3个不同的handlers,每个handler负责发送一个特定严重级别的日志到一个特定的位置。

 

Handler.setLevel(lel):

# 指定被处理的信息级别,低于lel级别的信息将被忽略


Handler.setFormatter():

# 给这个handler选择一个格式器对象进行日志进行格式化输出


Handler.removeFilter(filt):

新增或删除一个filter过滤器对象

 

常用的Hadler处理器

过滤器 Filter 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录

源代码中filter的基本格式:

class logging.Filter(name='')
def filter(record):
"""日志过滤条件"""
pass

 

格式器 Formatter 决定日志记录的最终输出的字段格式

logging.Formatter(fmt=None, datefmt=None, style='%')

该构造方法接收3个可选参数:

fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值

datefmt:指定日期格式字符串,如果不指定该参数则默认使用”%Y-%m-%d %H:%M:%S"

style:Python 3.2新增的参数,可取值为 ‘%’, ‘{‘和 ‘$',如果不指定该参数则默认使用’%’

 

3.2 日志流处理流程

1、创建一个日志器对象logger
2、设置日志器logger的通用日志等级level
3、创建合适数量的Handler处理器对象(其中,如果日志输出到文件中,则要有日志的存储路径)
4、设置每个Handler处理器单独的日志等级level
5、创建合适数量的格式器formatter,对日志格式化
6、向Handler处理器中添加格式器对象formatter
7、将上面创建的Handler处理器添加到日志器logger中
8、最终在异常处理过程中,打印输出日志
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
示例:
import logging
def log(name):
    # 创建logger,如果参数name表示日志器对象名,name为空则返回root logger
    logger = logging.getLogger(name)
    # 务必设置一个初始化的日志等级
    logger.setLevel(logging.DEBUG)
    # 这里进行判断,如果logger.handlers列表为空则添加,否则多次调用log日志函数会重复添加
    if not logger.handlers:  #添加handler的时候一定要判断,不要重复添加造成重复打印日志的bug!
        # 创建handler
        fh = logging.FileHandler("test.log", encoding="utf-8")
        sh = logging.StreamHandler()
        # 单独设置logger日志等级
        fh.setLevel(logging.INFO)
        # 设置输出日志格式
        simple_formatter = logging.Formatter(
            fmt="{levelname} {asctime} {name} {module}:{lineno} {message}",
            datefmt="%Y/%m/%d %H:%M:%S",
            style="{"
        )
        verbose_formatter = logging.Formatter(
            fmt="{levelname} {asctime} {name} {pathname}:{lineno} {message}",
            datefmt="%Y/%m/%d %H:%M:%S",
            style="{"
        )

        # 为handler指定输出格式
        fh.setFormatter(verbose_formatter)
        sh.setFormatter(simple_formatter)
        # 为logger添加的日志处理器
        logger.addHandler(fh)
        logger.addHandler(sh)
    return logger  # 直接返回logger


logger = log("小明")
logger.debug("查错")
logger.info("提示")
logger.warning("警告")
logger.error("错误")
logger.critical("危险")

'''
DEBUG 2022/04/17 22:07:15 小明 test:230 查错
INFO 2022/04/17 22:07:15 小明 test:231 提示
WARNING 2022/04/17 22:07:15 小明 test:232 警告
ERROR 2022/04/17 22:07:15 小明 test:233 错误
CRITICAL 2022/04/17 22:07:15 小明 test:234 危险
''' 

3.3 日志配置模式:

通过字典、yaml、json格式对日志的配置信息进行统一配置,然后实现日志处理。最终效果与上面一样。

 六 异常处理基础

当Python解释器检测到运行的程序出现错误时,解释器就无法继续执行代码或者继续执行下去会导致未知的结果出现,python解释器就会输出一些错误的提示

1. 程序错误一般都会分为两种:

语法错误:代码没有按照python语法规则去编写,产生的错误。

异常错误:代码在语法正确的前提下,程序因为用户的使用或者开发者编写的逻辑混乱产生的错误信息。

python中就提供了容错语句抛出异常语句去解决产生的错误信息。

# 容错语句
try:
# 尝试执行缩进语句代码块,如果出错,则会代码停止跳到except中捕获错误
pass
except ...:
# 捕获异常语句,当有异常发生时,则会来到except进行判断,是否捕获了当前类型的错误。
# except后面直接跟着:冒号,则表示捕获的是 Exception这个错误,Exception是所有异常错误的顶级类型
pass
else:
# 当try语句块代码执行无误时,就会自动执行else语句块
pass
finally:
# 不过代码是否出错,这里最终都会被执行
pass

# 可以主动抛异常, 异常可以通过自定义的方式实现自己的提示错误(面向对象会涉及到)
raise 异常("错误提示")

示例1

import os
file = "1.zip"
try:  # 尝试执行缩进语句块的代码
    f = open(file, "r")
    content = f.read()
    # try语句块中一旦出现异常,则异常代码往下的代码就不会被执行,会自动跳到except里面
    return content #函数遇到return以后,就会结束不会往下继续执行了。!!但是这个规则遇到finally要让步
except FileNotFoundError:  # 捕获错误,一个try可以跟着1个或多个except语句
    print("文件读取错误!不存在的文件!")
except UnicodeDecodeError:
    ext = os.path.basename(file).split(".")[-1]
    print(f"文件读取错误!不能读取{ext}格式的文件!")
except: # 注意:一般都要在最后对前面的except没有捕获到的错误,进行最后一步的错误
    print("文件读取错误!未知错误!")
else:
    # 如果代码执行到任意一个except语句块,则不会执行else语句块
    print("文件正常读取完毕!")
finally:
    # 针对网络请求,特殊文件读写,就要在程序运行结果以后,不管是否发生了异常,都要关闭
    f.close()

示例2 抛出异常语句

num = input("请输入输入一个手机号码:")
if not num.isdecimal() or len(num) != 11:
    raise Exception("不是手机号码哦!")

示例3 查看异常信息

"""查看异常信息"""
try:
    1 / 0
except Exception as e:
    print(e)  #division by zero # 打印错误的提示内容,一般用于提示给用户,或者记录到日志中
    info = sys.exc_info() #返回当前异常信息
    print(info)
    # (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x7f4e01bef900>)
    print(info[-1].tb_lineno)  # 异常出现的行号
    # 11
    print(info[-1].tb_frame.f_code.co_filename)  # 异常出现的文件路径

2. 常见的内置异常

类名描述
Exception 未知异常,所有异常的总类型,一般用于主动抛出异常或者自定义异常
IndexError 索引超出序列的范围,操作列表,元组,类列表对象,类元组对象
KeyError 字典中查找一个不存在的关键字
NameError 尝试访问一个不存在的变量
IndentationError 缩进错误
AttributeError 尝试访问未知的对象属性
AssertionError 断言语句(assert)失败,用于单元测试(unit test)
GeneratorExit generator.close()方法被调用的时候
ImportError 导入模块失败的时候
NotImplementedError 尚未实现的方法
OSError 操作系统产生的异常(例如打开一个不存在的文件)
ReferenceError 弱引用(weak reference)试图访问一个已经被垃圾回收机制回收了的对象
RuntimeError 一般的运行时错误
SyntaxError Python的语法错误
TypeError 不同类型间的无效操作
UnicodeError Unicode相关的错误(ValueError的子类)
UnicodeEncodeError Unicode编码时的错误(UnicodeError的子类)
UnicodeDecodeError Unicode解码时的错误(UnicodeError的子类)
ValueError 传入无效的参数

 七 正则表达式

1. 定义:正则模式分2部分组成,分别是原子和元字符。元字符一般都会写在原子的后面(右边)

  • 原子,组成字符串或文本的基本元素,例如,数字、字母、文本符号、中文等多字节字符等都属于原子。

  • 元字符,就是对左边的原子的数量或类型进行补充描述的特殊代码。

2. 元字符

元字符 描述 示例
[] 匹配一个中括号中出现的任意原子
import re

"""re.findall(正则模式, 文本) 基于正则模式查找文本内容"""
"""[] 匹配一个中括号中出现的任意一个原子"""
ret = re.findall("[123]", "abc4d3ab2")
print(ret) # ['3', '2']
[^原子] 匹配一个没有在中括号出现的任意原子
"""[^] 匹配一个没有在中括号出现的任意原子"""
ret = re.findall('a[^ab]b','aab abb acb adbdaacb baacb')
print(ret) # ['acb', 'adb', 'acb', 'acb']
\ 转义字符,可以把原子转换特殊元字符,也可以把特殊元字符转成原子。
# 因为正则中[]表示任选其一的原子,而[]里面出现^表示对没在中括号里面的原子任选其一
# 那如果查找文本中的[],那么我们需要对中括号本身代表特殊作用要取消掉

"""\ 转义字符,可以把原子转换特殊元字符,也可以把特殊元字符转成原子"""
# 提取文本中的[]
ret = re.findall('\[b\]','[b]')
print(ret) # ['[b]']
# [0-9] 还可以使用\d表示
# ret = re.findall('\d','a1c3d3558')
# print(ret) # ['1', '3', '3', '5', '5', '8']
^ 叫开始边界符或开始锚点符,匹配一行的开头位置
txt = " hello, world"
ret = re.findall('^hello', txt)
print(ret) # []
$ 叫结束边界符或结束锚点符,匹配一行的结束位置  
ret = re.findall("d$", txt)
print(ret)#['d']
. 叫通配符、万能通配符或通配元字符,匹配1个除了换行符\n以外任何原子  
""" . 通配符,匹配1个除了换行符\n以外任何原子 """
txt = "123455 1234555 123 5"
ret = re.findall('123..5', txt)
# 文本中任意字符都算原子,当然空格也是
print(ret) # ['123455', '123455', '123 5']
* 叫星号贪婪符,指定左边原子出现0次或多次  
""" * 星号贪婪符, 匹配0个或者多个a """
ret = re.findall('a*b','abbzab abb aab')
print(ret) # ['ab', 'b', 'ab', 'ab', 'b', 'aab']
? 叫非贪婪符,指定左边原子出现0次或1次
"""  ? 非贪婪符,匹配0个或者1个原子 """
ret = re.findall('a?b','abbzab abb aab')
print(ret) # ['ab', 'b', 'ab', 'ab', 'b', 'ab']
+ 叫加号贪婪符,指定左边原子出现1次或多次  
""" + 加号贪婪符,匹配1个或者多个原子 """
ret = re.findall('a+b','abbzab abb aab')
print(ret) # ['ab', 'ab', 'ab', 'aab']
{n,m} 叫数量范围贪婪符,指定左边原子的数量范围,有{n},{n, }, {,m}, {n,m}四种写法,其中n与m必须是非负整数。  
""" {m,n} 范围贪婪符,匹配m个至n个原子
逗号左边的的非负整数必须<=右边的非负整数
{n} 匹配左边的n个原子
{n,} 匹配左边的n个以上原子
{n,m} 匹配左边的最少n个,最多m个以上原子
{,n} 匹配左边的最多n个原子

* 相当于 {0,}
+ 相当于 {1,}
? 相当于 {0,1}
"""
ret = re.findall('a{1,3}b','aaab ab aab abbb aaz aabb')
print(ret) # ['aaab', 'ab', 'aab', 'ab', 'aab']
| 指定原子或正则模式进行二选一或多选一  
""" | 指定原子或正则模式进行二选一或多选一 """
txt = "13512356782 17118856782 178560356782"
# 只匹配135或171的手机号和178
ret = re.findall("135\d{8}|171\d{8}|178",txt)
print(ret) # ['13512356782', '17118856782', '178']
() 对原子或正则模式进行捕获提取和分组划分整体操作  
""" () 对原子或正则模式进行捕获提取和分组操作 """
txt = "13512356782 17118856782 178560356782"
ret = re.findall("(135|171)\d{8}",txt)
print(ret) # ['135', '171']

# () 具备模式捕获的能力,也就是优先提取数据的能力,所以会导致\d{8}没有显示,所以要通过(?:) 取消模式捕获
txt = "13512356782 17118856782 178560356782"
ret = re.findall("(?:135|171)\d{8}",txt)
print(ret) # ['13512356782', '17118856782']

3. 特殊元字符

元字符 描述 示例
\d 匹配一个数字原子,等价于[0-9]
txt = "1alala2"
ret = re.findall("\d", txt)
print(ret) # ['1', '2']
\n 匹配一个换行符
""" \n 1个换行符原子 """
txt = """wo jiao nana.\nnihao,nana
ni zai nali"""
ret = re.findall(r"\nni", txt)
print(ret) # ['\nni', '\nni']
\s 匹配一个任何空白字符原子,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]
"""\s 匹配一个任何空白字符原子"""
txt = """wo jiao nana.\nnihao,nana
ni zai nali, nihao"""
ret = re.findall(r"\sni", txt)
print(ret) # ['\nni', '\nni', '\tni']
\w 匹配一个包括下划线的单词原子。等价于[A-Za-z0-9_]
"""\w 匹配一个包括下划线的单词原子 等价于[A-Za-z0-9_]。"""
txt = """hello world.\nI love China"""
ret = re.findall(r"\w+", txt)
print(ret) # ['hello', 'world', 'I', 'love', 'China']

4 re模块

python本身没有内置正则处理的,python中的正则就是一段字符串,我们需要使用python模块中提供的函数把字符串发送给正则引擎,正则引擎会把字符串转换成真正的正则表达式来处理文本内容。

re模块提供了一组正则处理函数,使我们可以在字符串中搜索匹配项:

函数描述示例
findall 按指定的正则模式查找文本中所有符合正则模式的匹配项,以列表格式返回结果。
txt = "His name is iron man, He's Amazing"
ret1 = re.findall("ma", txt)
ret2 = re.findall("hello", txt)
print(ret1) # ['ma', 'ma']
print(ret2) #[]
search 在字符串中任何位置查找首个符合正则模式的匹配项,存在则返回re.Match对象,不存在返回None  
txt = "我今年18岁,我的弟弟比我小13岁"
ret = re.search("\d", txt)
print(ret.group()) #1
print("获取首个匹配到的数字原子的下标范围:", ret.span()) #(3, 5)
print("获取首个匹配到的数字原子的开始下标:", ret.start()) #3
print("获取首个匹配到的数字原子的结束下标:", ret.end()) #5
ret = re.search("hello", txt)
print(ret) #None
match 判定字符串开始位置是否匹配正则模式的规则,匹配则返回re.Match对象,不匹配返回None  
ret = re.match("1[3-9]\d{9}","13928835900,这是我的手机号码")
print(ret.group()) #13928835900
ret = re.match("1[3-9]\d{9}","我的手机号码是13928835900")
print(ret) #None
ret = re.match("1[3-9]\d{9}$","13928835900,我的手机号码是")
print(ret) #None
ret = re.match("1[3-9]\d{9}$","13928835900") #None
split 按指定的正则模式来分割字符串,返回一个分割后的列表  
txt = "my name is mike"
ret = re.split("\s", txt)
print(ret) # ['my', 'name', 'is', 'mike']
sub 把字符串按指定的正则模式来查找符合正则模式的匹配项,并可以替换一个或多个匹配项成其他内容。  
txt = "my name is mike"
ret = re.sub("\s", "-", txt)
print(ret) # my-name-is-mike
ret = re.sub("\s", "-", txt, 2)
print(ret) # my-name-is mike #指定`count`参数来控制替换次数
"""模式的反向引用,模式的反向捕获"""
txt = "13512345678"
ret = re.sub(r"(\d{3})\d{4}(\d{4})", r"\1****\2", txt, count=1)
print(ret) # 135****5678

 5 正则进阶

5.1 匹配/模式分组

字符功能示例
a|b 匹配左右任意一个模式,a与b代表的是一段任意的正则模式
ret = re.search(r"(www)\.(baidu|tmall)\.(com)", "www.tmall.com")
if ret:
print(ret) #<re.Match object; span=(0, 13), match='www.tmall.com'>
print(ret.group()) #www.tmall.com # 没有提取小括号中的模式
print(ret.groups()) #('www', 'tmall', 'com') # 提取小括号中的模式,也叫模式捕获
print(ret.group(1))
txt = """<h1>这是web前端代码</h1> <a href="javascript:void(0);">更换城市</a>  <li><a href="javascript:void(0);">北京市</a></li>"""
ret = re.findall(r"<.*?>([^<]+?)</.*?>", txt) # ?可以让.+模式只激活0次或1次,达到多次匹配的效果
print(ret) # ['这是web前端代码', '更换城市', '北京市']
"""取消模式分组"""
# txt = "13512356782 17118856782 178560356782"
# ret = re.findall("(?:135|171)\d{8}",txt) # 匹配135或171的手机号
# print(ret) # ['13512356782', '17118856782']
(ab) 将括号中字符作为一个分组,表示一个整体,a与b代表的是一段任意的正则模式
\num 引用分组num匹配到的字符串,num是一个正整数,代表的是前一个参数里面的对应位置的小括号中的模式。  
"""模式的反向引用,模式的反向捕获"""
txt = "13512345678"
ret = re.sub(r"(\d{3})\d{4}(\d{4})", r"\1****\2", txt, count=1)
print(ret) # 135****5678

(?P<name>)

#大写P

分组起别名  
txt = """<h1>这是web前端代码</h1> <a href="javascript:void(0);">更换城市</a>  <li><a href="javascript:void(0);">北京市</a></li>"""
ret = re.search(r"<(.*?)>(?P<content>[^<]+?)</(?P<tag>.*?)>", txt) # ?可以让.+模式只激活0次或1次,达到多次匹配的效果
if ret:
print(ret.group()) #<h1>这是web前端代码</h1>
print(ret.groupdict()) # {'content': '这是web前端代码', 'tag': 'h1'}

5.2 贪婪模式

Python里关于数量词的元字符在匹配查找的时候,总是尝试尽可能多的匹配符合正则模式的字符内容,为贪婪匹配的行为。非贪婪模式则相反,总是尝试尽可能少的匹配符合正则模式的字符内容。

在"*","?","+","{m,n}"后面加上?,则可以是使贪婪变成非贪婪

txt = "My computer IP is 192-168-21-253"
ret = re.match(".+(\d+-\d+-\d+-\d+)",txt)
print(ret.group(1))  # '2-168-21-253'

ret=re.match(".+?(\d+-\d+-\d+-\d+)",txt)
print(ret.group(1))  # '192-168-21-253'

5.3 模式修正符

模式修正符,也叫正则修饰符,是给正则模式增强或增加功能的。

修正符re模块提供的变量描述示例
i re.I 使模式对大小写不敏感,也就是不区分大小写
"""I 修正符"""
# # 默认情况下,模式区分大小写,也就是对字母大小写敏感。
txt = "python,Python,PYTHON开源的类C语言"
ret = re.findall('python', txt)
print(ret) # ['python']
ret = re.findall('python', txt, re.I)
print(ret) # ['python', 'Python', 'PYTHON']
m re.M 使模式在多行文本中可以多个行头和行位,影响 ^ 和 $  
txt = '''python,
Python,
PYTHON开源的类C语言'''
ret = re.findall('python', txt, re.I+re.M)#在re.M的增强下,^不再是代表字符串文本的开头位置,而是代表了一行的开始
print(ret) # ['python', 'Python', 'PYTHON']
s re.S 让通配符. 可以代码所有的任意原子(包括换行符\n在内)  
"""S 修正符"""
# 让.变成真正的通配符,包含\n换行符
txt = """
<a href="http://www.baidu.com">百度</a>
<a href="http://www.tmall.com">天猫</a>
<a href="http://www.taobao.com">
淘宝
</a>
"""
ret = re.findall('<.*?>(.*?)</.*?>', txt, re.S)
print(ret) # ['百度', '天猫', '\n 淘宝\n']

5.4 正则在文本处理方面的应用

正则经常用于数据提取,爬虫数据抓取工作

 

posted on 2022-04-18 17:10  大明花花  阅读(199)  评论(0编辑  收藏  举报