一.logging模塊
logging翻譯為日志記錄
那問題是什么是日志?
日志實際上是日記的一種,用於記錄某個時間點發生了什么事情,比如大學老師的教學日志,工作日志等
為什么要記錄日志?
在實際生活中記錄日志主要為了日后復查,
比如某個大學老師每天記錄自己講的什么內容,后面有學生某科成績優異獲獎了,校長想要獎勵對應的老師,但由於每個老師教的班級都很多,並不一定記得是誰教的,這時候就可以查看教學日志來獲取需要的信息了
再比如,工廠的生產日志,如果某個產品除了因為某個零件出現了故障,通過生成日志,可以找到與這個產品同批次的其他產品,進行返工,或是通過日志找到該零件的供應商,進行溝通解決!
程序中的日志
我們的程序開發完成后會被不同系統環境的用戶下載使用,期間可能就會出現問題,直接把錯誤信息展示給用戶看是沒有任何意義的,用戶看不懂也不會解決,那這時候就可以將用戶執行的所有操作,以及代碼運行的過程,記錄到日志中,程序員通過分析日志內容,可以快速的定位問題
綜上: 日志就是用來記錄發生的事件的
日志並不會立即產生作用,而是當程序出現了問題時在去分析日志文件提取有用信息
什么是logging模塊
logging模塊是python提供的用於記錄日志的模塊
為什么需要logging
我們完全可以自己打開文件然后,日志寫進去,但是這些操作重復且沒有任何技術含量,所以python幫我們進行了封裝,有了logging后我們在記錄日志時 只需要簡單的調用接口即可,非常方便!
日志級別
在開始記錄日志前還需要明確,日志的級別
隨着時間的推移,日志記錄會非常多,成千上萬行,如何快速找到需要的日志記錄這就成了問題
解決的方案就是 給日志划分級別
logging模塊將日志分為了五個級別,從高到低分別是:
1.info 常規信息
2.debug 調試信息
3.warning 警告信息
4.error 錯誤信息
5.cretical 嚴重錯誤
本質上他們使用數字來表示級別的,從高到低分別是10,20,30,40,50
logging模塊的使用
#1.導入模塊
import logging
#2.輸出日志
logging.info("info")
logging.debug("debug")
logging.warning("warning")
logging.error("error")
logging.critical("critical")
#輸出 WARNING:root:warning
#輸出 ERROR:root:error
#輸出 CRITICAL:root:critical
我們發現info 和 debug都沒有輸出,這是因為它們的級別不夠,
默認情況下:
logging的最低顯示級別為warning,對應的數值為30
日志被打印到了控制台
日志輸出格式為:級別 日志生成器名稱 日志消息
如何修改這寫默認的行為呢?,這就需要我們自己來進行配置
自定義配置
import logging
logging.basicConfig()
"""可用參數
filename:用指定的文件名創建FiledHandler(后邊會具體講解handler的概念),這樣日志會被存儲在指定的文件中。
filemode:文件打開方式,在指定了filename時使用這個參數,默認值為“a”還可指定為“w”。
format:指定handler使用的日志顯示格式。
datefmt:指定日期時間格式。
level:設置rootlogger(后邊會講解具體概念)的日志級別
"""
#案例:
logging.basicConfig(
filename="aaa.log",
filemode="at",
datefmt="%Y-%m-%d %H:%M:%S %p",
format="%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s",
level=10
)
格式化全部可用名稱
%(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:用戶輸出的消息
至此我們已經可以自己來配置一 寫基礎信息了,但是當我們想要將同一個日志輸出到不同位置時,這些基礎配置就無法實現了,
例如 有一個登錄注冊的功能 需要記錄日志,同時生成兩份 一份給程序員看,一份給老板看,作為程序員應該查看較為詳細的日志,二老板則應該簡單一些,因為他不需要關心程序的細節
要實現這樣的需要我們需要系統的了解loggin模塊
logging模塊的四個核心角色
1.Logger 日志生成器 產生日志
2.Filter 日志過濾器 過濾日志
3.Handler 日志處理器 對日志進行格式化,並輸出到指定位置(控制台或文件)
4.Formater 處理日志的格式
一條日志完整的生命周期
1.由logger 產生日志 -> 2.交給過濾器判斷是否被過濾 -> 3.將日志消息分發給綁定的所有處理器 -> 4處理器按照綁定的格式化對象輸出日志
其中 第一步 會先檢查日志級別 如果低於設置的級別則不執行
第二步 使用場景不多 需要使用面向對象的技術點 后續用到再講
第三步 也會檢查日志級別,如果得到的日志低於自身的日志級別則不輸出
生成器的級別應低於句柄否則給句柄設置級別是沒有意義的,
例如 handler設置為20 生成器設置為30
30以下的日志壓根不會產生
第四步 如果不指定格式則按照默認格式
logging各角色的使用(了解)
# 生成器
logger1 = logging.getLogger("日志對象1")
# 文件句柄
handler1 = logging.FileHandler("log1.log",encoding="utf-8")
handler2 = logging.FileHandler("log2.log",encoding="utf-8")
# 控制台句柄
handler3 = logging.StreamHandler()
# 格式化對象
fmt1 = logging.Formatter(
fmt="%(asctime)s - %(name)s - %(levelname)s: %(message)s",
datefmt="%m-%d %H:%M:%S %p")
fmt2 = logging.Formatter(
fmt="%(asctime)s - %(levelname)s : %(message)s",
datefmt="%Y/%m/%d %H:%M:%S")
# 綁定格式化對象與文件句柄
handler1.setFormatter(fmt1)
handler2.setFormatter(fmt2)
handler3.setFormatter(fmt1)
# 綁定生成器與文件句柄
logger1.addHandler(handler1)
logger1.addHandler(handler2)
logger1.addHandler(handler3)
# 設置日志級別
logger1.setLevel(10) #生成器日志級別
handler1.setLevel(20) #句柄日志級別
# 測試
logger1.debug("debug msessage")
logger1.info("info msessage")
logger1.warning("warning msessage")
logger1.critical("critical msessage")
到此我們已經可以實現上述的需求了,但是這並不是我們最終的實現方式,因為每次都要編寫這樣的代碼是非常痛苦的
logging的繼承(了解)
可以將一個日志指定為另一個日志的子日志 或子孫日志
當存在繼承關系時 子孫級日志收到日志時會將該日志向上傳遞
指定繼承關系:
import logging
log1 = logging.getLogger("mother")
log2 = logging.getLogger("mother.son")
log3 = logging.getLogger("mother.son.grandson")
# handler
fh = logging.FileHandler(filename="cc.log",encoding="utf-8")
# formatter
fm = logging.Formatter("%(asctime)s - %(name)s -%(filename)s - %(message)s")
# 綁定
log1.addHandler(fh)
log2.addHandler(fh)
log3.addHandler(fh)
# 綁定格式
fh.setFormatter(fm)
# 測試
# log1.error("測試")
# log2.error("測試")
log3.error("測試")
# 取消傳遞
log3.propagate = False
# 再次測試
log3.error("測試")
通過字典配置日志模塊(重點)
每次都要編寫代碼來配置非常麻煩 ,我們可以寫一個完整的配置保存起來,以便后續直接使用
import logging.config
logging.config.dictConfig(LOGGING_DIC)
logging.getLogger("aa").debug("測試")
LOGGING_DIC模板
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'
id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'
logfile_path = "配置文件路徑"
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_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', # 日志文件的編碼
},
},
'loggers': {
#logging.getLogger(__name__)拿到的logger配置
'aa': {
'handlers': ['default', 'console'], # 這里把上面定義的兩個handler都加上,即log數據既寫入文件又打印到屏幕
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)傳遞
},
},
}
補充:
getLogger參數就是對應字典中loggers的key , 如果沒有匹配的key 則返回系統默認的生成器,我們可以在字典中通過空的key來將一個生成器設置為默認的
'loggers': {
# 把key設置為空
'': {
'handlers': ['default', 'console'], # 這里把上面定義的兩個handler都加上,即log數據既寫入文件又打印到屏幕
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)傳遞
},
},
,往后在使用時可以這調用模塊提供的函數,來輸出日志
logging.info("測試信息!")
另外我們在第一次使用日志時並沒有指定生成器,但也可以使用,這是因為系統有默認的生成器名稱就叫root
最后來完成之前的需求:
有一個登錄注冊的功能 需要記錄日志,同時生成兩份 一份給程序員看,一份給老板看,作為程序員應該查看較為詳細的日志,二老板則應該簡單一些,因為他不需要關心程序的細節
# 程序員看的格式
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
'[%(levelname)s][%(message)s]' #其中name為getlogger指定的名字
logfile_path1 = "coder.log"
# 老板看的格式
simple_format = '[%(levelname)s][%(asctime)s]%(message)s'
logfile_path2 = "boss.log"
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
},
'filters': {},
'handlers': {
#打印到終端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
#打印到文件的日志,收集info及以上的日志
'std': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': logfile_path1, # 日志文件
'maxBytes': 1024*1024*5, # 日志大小 5M
'backupCount': 5, #日志文件最大個數
'encoding': 'utf-8', # 日志文件的編碼
},
'boss': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'simple',
'filename': logfile_path2, # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
'backupCount': 5, # 日志文件最大個數
'encoding': 'utf-8', # 日志文件的編碼
}
},
'loggers': {
#logging.getLogger(__name__)拿到的logger配置
'aa': {
'handlers': ['std', 'console',"boss"], # 這里把上面定義的handler都加上,即log數據會同時輸出到三個位置
'level': 'INFO',
'propagate': True, # 向上(更高level的logger)傳遞
},
},
}
二.os模塊
os翻譯過來就是操作系統,os模塊提供了與操作系統打交道需要用到的函數,
那我們什么時候需要與操作系統打交道呢?
在操作系統中,我們最最常用的操作就是,對文件及文件夾的操作, 所以 當你需要與操作文件時,就應該想到os模塊了
os提供一下函數
os.getcwd() 獲取當前工作目錄,即當前python腳本工作的目錄路徑
os.chdir("dirname") 改變當前腳本工作目錄;相當於shell下cd
os.curdir 返回當前目錄: ('.')
os.pardir 獲取當前目錄的父目錄字符串名:('..')
os.makedirs('dirname1/dirname2') 可生成多層遞歸目錄
os.removedirs('dirname1') 若目錄為空,則刪除,並遞歸到上一級目錄,如若也為空,則刪除,依此類推
os.mkdir('dirname') 生成單級目錄;相當於shell中mkdir dirname
os.rmdir('dirname') 刪除單級空目錄,若目錄不為空則無法刪除,報錯;相當於shell中rmdir dirname
os.listdir('dirname') 列出指定目錄下的所有文件和子目錄,包括隱藏文件,並以列表方式打印
os.remove() 刪除一個文件
os.rename("oldname","newname") 重命名文件/目錄
os.stat('path/filename') 獲取文件/目錄信息
os.sep 輸出操作系統特定的路徑分隔符,win下為"\\",Linux下為"/"
os.linesep 輸出當前平台使用的行終止符,win下為"\t\n",Linux下為"\n"
os.pathsep 輸出用於分割文件路徑的字符串 win下為;,Linux下為:
os.name 輸出字符串指示當前使用平台。win->'nt'; Linux->'posix'
os.system("bash command") 運行shell命令,直接顯示
os.environ 獲取系統環境變量
三.os.path模塊
該模塊用於處理路徑,我們知道python是一門跨平台的語言,二每種操作系統,文件路徑是截然不同的,為了使程序可以在不同平台生正確運行,python提供了該模塊,使用該模塊可以實現路徑在不同品台下的自動轉換,從而實現跨平台,
今后只要涉及到文件或文件夾路徑,就應該使用該模塊
提供的函數:
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的大小
normcase函數
在Linux和Mac平台上,該函數會原樣返回path,在windows平台上會將路徑中所有字符轉換為小寫,並將所有斜杠轉換為飯斜杠。
>>> os.path.normcase('c:/windows\\system32\\')
'c:\\windows\\system32\\'
normpath函數
規范化路徑,如..和/
>>> 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
四.subprocess
subprocess 稱之為子進程,進程是一個正在運行的程序
為什么要使用子進程,因為之前的os.system()函數無法獲取命令的執行結果,另一個問題是當我們啟動了某一其他進程時無法與這個子進程進行通訊,
當要在python程序中執行系統指令時 就應該使用subprocess 自動化運維經常會使用
#測試
res = os.system("python")
print(res)
# res結果為執行狀態
subprocess的使用
import subprocess
p = subprocess.Popen("ls",shell=True)
#shell=True 告訴系統這是一個指令 而不是某個文件名
#此時效果與sys.system()沒有任何區別,都是將結果輸出到控制台
# 那如何與這個進程交互數據呢,這需要用到三個參數
1.stdin 表示輸入交給子進程的數據
2.stdout 表示子進程返回的數據
3.stderr 表示子進程發送的錯誤信息
#這三個參數,的類型都是管道,(管道本質就是一個文件,可以進行讀寫操作),使用subprocess.PIPE來獲取一個管道
案例:
理解了三個參數的意義后讓我們來實現一個小功能
一個子進程執行tasklist命令獲取所有的任務信息,然后將結果交給另一個進程進行查找
另一個子進程執行findstr 查找某個任務信息
p1 = subprocess.Popen("tasklist",shell=True,stdout=subprocess.PIPE)
p2 = subprocess.Popen("findstr smss",shell=True,stdin=p1.stdout,stdout=subprocess.PIPE)
print(p2.stdout.read())
總結: subprocess 主要用於執行系統命令,對比sys.system 區別在於可以在進程間交換數據
五.sys 模塊
sys是system的縮寫,表示系統,但是要注意
sys指的是解釋器自身,而非操作系統
所以該模塊主要是處理與解釋器相關的操作的
提供的函數和屬性:
sys.argv 命令行參數List,第一個元素是程序本身路徑
sys.exit(n) 退出程序,正常退出時exit(0)
sys.version 獲取Python解釋程序的版本信息
sys.maxint 最大的Int值
sys.path 返回模塊的搜索路徑,初始化時使用PYTHONPATH環境變量的值
sys.platform 返回操作系統平台名稱
其中提供了有一個arg屬性用於接收從控制台傳入的參數,當你要開發一款命令行程序時,該屬性非常重要
案例:開發一款命令行界面的文件復制工具
思路:第一個參數是當前執行文件本身,第二個參數用於接收源文件,第三個參數用於接收目標文件
import sys
source_path = sys.argv[1]
target_path = sys.argv[2]
print(source_path)
print(target_path)
with open(source_path, "rb") as f1:
with open(target_path, "wb") as f2:
while True:
data = f1.read(1024)
if not data:
break
f2.write(data)
六.confiparser
confiparser,翻譯為配置解析,很顯然,他是用來解析配置文件的,
何為配置文件?
用於編寫程序的配置信息的文件
何為配置信息?
為了提高程序的擴展性,我們會把一些程序中需要用到的值交給用戶來確定,比如迅雷的下載目錄,同時下載數,qq的提示音等等,
作為配置信息的數據 應滿足兩個條件
1.數據的值不是固定的
2.可以由用戶來指定的
例如我們做一個登錄功能,為了方便使用我們可以將用戶的用戶名密碼寫到配置文件中,可以不需要每次都輸入
配置文件編寫格式
在使用該模塊前必須要先知道其配置文件的格式,由於讀寫文件的代碼是模塊封裝好的,所以必須按照固定的方式來邊編寫,才能被正常解析, 當然並不是只有python有配置文件,其他任何語言都有,但是格式是相同的!
格式:
配置文件中只允許出現兩種類型的數據
第一種 section 分區 方括號中是分區的名稱 例如:[ATM ]
第二種 option 選項 名稱 = 值
注意:
不能出現重復的分區名
同一個分區下不能有相同的選項名
值可以是任何類型 且字符串不需要加引號
confiparser的使用
讀取數據
import configparser
#獲取解析器對象
config=configparser.ConfigParser()
# 讀取某個配置文件
config.read('a.cfg')
#查看所有的分區
res=config.sections() #['section1', 'section2']
print(res)
#查看標題section1下所有key=value的key
options=config.options('section1')
print(options) #['k1', 'k2', 'user', 'age', 'is_admin', 'salary']
#查看標題section1下所有key=value的(key,value)格式
item_list=config.items('section1')
print(item_list) #[('k1', 'v1'), ('k2', 'v2'), ('user', 'egon'), ('age', '18'), ('is_admin', 'true'), ('salary', '31')]
#查看標題section1下user的值=>字符串格式
val=config.get('section1','user')
print(val) #egon
#由於使用前需要進行轉換,所以模塊封裝了轉換類型的功能,只需要調用對應的函數即可,如下:
val1=config.getint('section1','age')
val2=config.getboolean('section1','is_admin')
val3=config.getfloat('section1','salary')
#是否存在某選項
print(cfg.has_option("mysql","name"))
#是否存在某分區
print(cfg.has_section("db"))
添加,刪除,修改
import configparser
config=configparser.ConfigParser()
config.read('a.cfg',encoding='utf-8')
#刪除整個標題section2
config.remove_section('section2')
#刪除標題section1下的某個k1和k2
config.remove_option('section1','k1')
config.remove_option('section1','k2')
#判斷是否存在某個標題
print(config.has_section('section1'))
#判斷標題section1下是否有user
print(config.has_option('section1','user'))
#添加一個標題
config.add_section('jack')
#在標題egon下添加name=egon,age=18的配置
config.set('jack','name','egon') # 如果已存則覆蓋原來的值
#config.set('jack','age',18) #報錯,必須是字符串
#最后將修改的內容寫入文件,完成最終的修改
config.write(open('a.cfg','w'))
代碼創建生成文件
import configparser
config = configparser.ConfigParser()
config.add_section("setion1")
config.set("setion1","name","zhangsn")
with open("test.config","w") as f:
config.write(f)
總結configparser 用於解析配置文件,雖然可以修改和,創建,配置文件,但是並不常用,解析才是其核心功能!
七.shevle模塊
該模塊用於序列化python中的數據,但是序列化已經有pickle了為什么出現了shevle?
因為shevle更加簡單,封裝了文件的讀寫操作.load和dump操作,
只有一個open函數,返回類似字典的對象,可讀可寫;key必須為字符串,而值可以是python所支持的數據類型
完全可以將其看做是一個帶有持久存儲功能的字典來看待,操作方式與字典沒有任何區別
#保存數據
s = shelve.open("shv.shv")
s["name"] = "jack"
#取出數據
s = shelve.open("shv.shv")
print(s["name"])
#輸出 jack
#關閉資源
s.close
八.shutil
該模塊提供了更加豐富的文件操作功能,壓縮,解壓縮,獲取文件信息等
提供的功能:
shutil.copyfileobj 拷貝文件 提供兩個文件對象 長度表示緩沖區大小
shutil.copyfile(src, dst) 拷貝文件 提供兩個文件路徑
shutil.copymode() 拷貝文件權限 提供兩個文件路徑
shutil.copystat(src, dst) 拷貝文件狀態信息 最后訪問 最后修改 權限 提供兩個文件路徑
shutil.copy(src, dst) 拷貝文件和權限 提供兩個文件路徑
shutil.copy2(src, dst) 拷貝文件和狀態信息 提供兩個文件路徑
shutil.ignore_patterns("mp3","*.py")
shutil.copytree(src, dst, symlinks=False, ignore=None) 拷貝目錄
symlinks默認False將軟連接拷貝為硬鏈接 否則拷貝為軟連接
shutil.rmtree 刪除目錄 可以設置忽略文件
shutil.move(src, dst) 移動目錄和文件
壓縮與解壓縮測試
import shutil
#壓縮, 文件名 格式 需要壓縮的文件所在文件夾
shutil.make_archive("壓縮測試","zip",r"/Users/jerry/PycharmProjects/備課/常用模塊五期")
#解壓縮 #壓縮, 文件名 解壓后的文件存放目錄
shutil.unpack_archive("壓縮測試.zip",r"/Users/jerry/PycharmProjects/備課/常用模塊五期/
# #壓縮當前執行文件所在文件夾內容到當前目錄
# shutil.make_archive("test","zip")
#
# #壓縮root_dir指定路徑的文件到當前目錄
# shutil.make_archive("test","zip",root_dir=r"/Users/jerry/PycharmProjects/work/re模塊")
#
# #壓縮root_dir指定路徑的文件到base_name指定路徑
# shutil.make_archive("/Users/jerry/PycharmProjects/work/壓縮文件/test","zip",root_dir=r"/Users/jerry/PycharmProjects/work/re模塊")
# #壓縮root_dir指定路徑的文件到base_name指定的壓縮文件 壓縮文件僅包含re模塊下的的內容
# shutil.make_archive("test",
# "zip",
# root_dir=r"/Users/jerry/PycharmProjects/work",)
# 當指定base_dir時 則優先使用base_dir 與root_dir不同的是 壓縮文件不僅包含re模塊下的內容 還包括re模塊的完整文件夾層級
# # 解壓后得到Users ->jerry -> PycharmProject->work->re模塊
# shutil.make_archive("test",
# "zip",
# root_dir=r"/Users/jerry/PycharmProjects/work/re模塊",
# base_dir=r"/Users/jerry/PycharmProjects/work/re模塊")