random 模塊
隨機:在某個范圍內取到每一個值的概率是相同的
import random # 隨機小數 print(random.random()) # 0-1之內的隨機小數 print(random.uniform(1, 5)) # 任意范圍之內的隨機小數 # 隨機整數 print(random.randint(1, 2)) # [1,2] 包含2在內的范圍內隨機取整數 print(random.randrange(1, 2)) # [1,2) 不包含2在內的范圍內隨機取整數 print(random.randrange(1, 10, 2)) # [1,10) 不包含10在內的范圍內隨機取奇數 # 隨機抽取 lst = [1, 2, 3, 'abc', ('wahaha', 'qqxing')] ret = random.choice(lst) # 隨機抽取一個值 print(ret) ret1 = random.sample(lst, 2) # 隨機抽取兩個值 print(ret1) # 打亂順序 在原列表的基礎上做亂序 lst = [1, 2, 3, 'abc', ('wahaha', 'qqxing')] random.shuffle(lst) print(lst)
練習:生成隨機驗證碼
# (1)4位數字的驗證碼 # 基礎版本 lis = '' for i in range(4): num = random.randint(0, 9) lis += str(num) print(lis) # 函數版本 def rand_code(n=4): lis = '' for i in range(n): num = random.randint(0, 9) lis += str(num) return lis print(rand_code(6)) # (2)6位 數字+字母 def rand_code(n): code = '' for i in range(n): rand_num = str(random.randint(0, 9)) rand_alph = chr(random.randint(97, 122)) rand_alph_upper = chr(random.randint(65, 90)) rand_num = random.choice([rand_num, rand_alph, rand_alph_upper]) code += rand_num return code ret = rand_code(4) print(ret) # (3)可控制驗證碼 數字 / 數字+字母 def rand_code(num, DefaultAlph=True): # 當DefaultAlph=True時生成字母+數字的驗證碼, 為False時生成純數字驗證碼 code = '' for i in range(num): rand_num = str(random.randint(0, 9)) if DefaultAlph: rand_alph = chr(random.randint(97, 122)) rand_alph_upper = chr(random.randint(65, 90)) rand_num = random.choice([rand_num, rand_alph, rand_alph_upper]) code += rand_num return code ret = rand_code(4, DefaultAlph=False) print(ret)
time 模塊
#常用方法 1.time.sleep(secs) (線程)推遲指定的時間運行。單位為秒。 2.time.time() 獲取當前時間戳
表示時間的三種方式
在python中,通常用這三種方式表示時間:時間戳、格式化的時間字符串、元組(struct_time)。
- 時間戳(timestamp) :通常來說,時間戳表示的是從1970年1月1日00:00:00開始按秒計算的偏移量。我們運行“type(time.time())”,返回的是float類型。
- 格式化的時間字符串(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 tm_year(年) 比如2018 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
認識Python中表示時間的幾種格式:
# 時間模塊 import time # 時間戳 print(time.time()) # 返回當前時間的時間戳 # 結果>>> 1543743462.3950245 # 時間字符串 print(time.strftime('%Y-%m-%d %X')) # 結果>>> 2018-12-02 17:39:58 print(time.strftime('%Y-%m-%d %H-%M-%S')) # 結果>>> 2018-12-02 17-39-58 # 時間元組:localtime將一個時間戳轉換為當前時區的struct_time print(time.localtime()) # 結果>>> time.struct_time(tm_year=2018, tm_mon=12, tm_mday=2, tm_hour=17, tm_min=43, tm_sec=44, tm_wday=6, tm_yday=336, tm_isdst=0)
小結:時間戳是計算機能夠識別的時間;時間字符串是人能夠看懂的時間;元組則是用來操作時間的。
time模塊相關方法:
time.localtime([secs]):將一個時間戳轉換為當前時區的struct_time;secs參數未提供,則以當前時間為准。 time.gmtime([secs]):和 localtime()類似;gmtime()方法是將一個時間戳轉換為UTC時區(0 時區)的struct_time。 time.time():返回當前時間戳 time.mktime(t):將一個time.struct_time轉為時間戳 time.sleep(secs):線程推遲指定的時間運行,單位為秒 time.asctime([t]):把一個表示時間的元組或者struct_time表示為這種形式:'Sun Dec 2 17:52:36 2018'。如果沒有參數,默認將time.localtime()作為參數傳入 time.ctime([t]):把一個時間戳(按秒計算的浮點數)轉為time.asctime()的形式。如果參數未給或者為None的時候,默認將time.time()作為參數,相當於time.asctime(time.localtime(secs)) time.strftime(format[, t]):把一個代表時間的元組或者struct_time(如由time.localtime()和time.gmtime()返回)轉為格式化的時間字符串,如果t未指定,默認傳入time.localtime() time.strptime(string[, format]):把一個格式化時間字符串轉化為struct_time。實際上它和strftime()是逆操作
幾種格式之間的轉化

# 時間戳——>結構化時間 # time.gmtime(時間戳) #UTC時間,與英國倫敦當地時間一致 # time.localtime(時間戳) #當地時間。例如我們現在在北京執行這個方法:與UTC時間相差8小時,UTC時間+8小時 = 北京時間 print(time.gmtime(1510000000)) #結果>>> time.struct_time(tm_year=2017, tm_mon=11, tm_mday=6, tm_hour=20, tm_min=26, tm_sec=40, tm_wday=0, tm_yday=310, tm_isdst=0) print(time.localtime()) #結果>>> time.struct_time(tm_year=2018, tm_mon=12, tm_mday=2, tm_hour=18, tm_min=4, tm_sec=23, tm_wday=6, tm_yday=336, tm_isdst=0) # 結構化時間——>時間戳 # time.mktime(結構化時間) time_tuple = time.localtime(1510000000) print(time.mktime(time_tuple)) #結果>>> 1510000000.0
# 結構化時間——>字符串時間 # time.strftime("格式定義","結構化時間") 結構化時間參數若不傳,則顯示當前時間 print(time.strftime("%Y-%m-%d %X")) #結果>>> 2018-12-02 18:07:47 print(time.strftime("%Y-%m-%d", time.localtime(1510000000))) #結果>>> 2017-11-07 # 字符串時間——>結構化時間 # time.strptime(時間字符串,字符串對應格式) print(time.strptime("2018-02-22", "%Y-%m-%d")) #結果>>> time.struct_time(tm_year=2018, tm_mon=2, tm_mday=22, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=53, tm_isdst=-1) print(time.strptime("2018/03/01", "%Y/%m/%d")) #結果>>> time.struct_time(tm_year=2018, tm_mon=3, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=60, tm_isdst=-1)

# 結構化時間 ——> %a %b %d %H:%M:%S %Y串 # time.asctime(結構化時間) 如果不傳參數,直接返回當前時間的格式化串 print(time.asctime(time.localtime(1510000000))) #結果>>> Tue Nov 7 04:26:40 2017 print(time.asctime()) #結果>>> Sun Dec 2 18:12:19 2018 # 時間戳 ——> %a %b %d %H:%M:%S %Y串 # time.ctime(時間戳) 如果不傳參數,直接返回當前時間的格式化串 print(time.ctime()) #結果>>> Sun Dec 2 18:13:14 2018 print(time.ctime(1510000000)) #結果>>> Tue Nov 7 04:26:40 2017
# 結構化時間 struct_time = time.localtime() struct_time = time.strptime('%s-%s-1'%(struct_time.tm_year,struct_time.tm_mon),'%Y-%m-%d') print(time.mktime(struct_time)) # 格式化時間 ret = time.strftime('%Y-%m-1') struct_time = time.strptime(ret,'%Y-%m-%d') print(time.mktime(struct_time))
datetime 模塊
相比於time模塊,datetime模塊的接口則更直觀,更容易調用
- datetime模塊定義了下面這幾個類:
- datetime.date:表示日期的類;常用的屬性有year, month, day;
- datetime.time:表示時間的類;常用的屬性有hour, minute, second, microsecond;
- datetime.datetime:表示日期時間。
- datetime.timedelta:表示時間間隔,即兩個時間點之間的長度。
- datetime.tzinfo:與時區有關的相關信息。
import datetime print(datetime.datetime.now()) # 現在的時間 # 只能調整的字段:weeks days hours minutes seconds print(datetime.datetime.now() + datetime.timedelta(weeks=3)) # 三周后 print(datetime.datetime.now() + datetime.timedelta(weeks=-3)) # 三周前 print(datetime.datetime.now() + datetime.timedelta(days=-3)) # 三天前 print(datetime.datetime.now() + datetime.timedelta(days=3)) # 三天后 print(datetime.datetime.now() + datetime.timedelta(hours=5)) # 5小時后 print(datetime.datetime.now() + datetime.timedelta(hours=-5)) # 5小時前 print(datetime.datetime.now() + datetime.timedelta(minutes=-15)) # 15分鍾前 print(datetime.datetime.now() + datetime.timedelta(minutes=15)) # 15分鍾后 print(datetime.datetime.now() + datetime.timedelta(seconds=-70)) # 70秒前 print(datetime.datetime.now() + datetime.timedelta(seconds=70)) # 70秒后 current_time = datetime.datetime.now() # 可直接調整到指定的 年 月 日 時 分 秒 等 print(current_time.replace(year=1977)) # 直接調整到1977年 print(current_time.replace(month=1)) # 直接調整到1月份 print(current_time.replace(year=1989,month=4,day=25)) # 1989-04-25 18:49:05.898601 # 將時間戳轉化成時間 print(datetime.date.fromtimestamp(1232132131)) # 2009-01-17
sys 模塊
sys模塊是與python解釋器交互的一個接口
sys.argv 命令行參數List,第一個元素是程序本身路徑,(類似shell中調用腳本后面傳入的$1,$2,$3) sys.exit(n) 退出程序,正常退出時exit(0),錯誤退出sys.exit(1) sys.version 獲取Python解釋程序的版本信息 sys.path 返回模塊的搜索路徑,初始化時使用PYTHONPATH環境變量的值 sys.platform 返回操作系統平台名稱
name = sys.argv[1] pwd = sys.argv[2] if name == 'xiaobai' and pwd == 'a123456': print('執行以下代碼') else: exit()
os 模塊
os 模塊是與操作系統交互的一個接口
#當前執行這個python文件的工作目錄相關的工作路徑
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.popen("bash command).read() 運行shell命令,獲取執行結果
os.environ 獲取系統環境變量
#path系列,和路徑相關
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的大小
注意: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)是創建時間(詳細信息參見平台的文檔)。
練習
import os, sys SIZE = 0 def countDirSize(path): global SIZE pathDirList = os.listdir(path) for fileName in pathDirList: newAbsPath = os.path.join(path, fileName) if os.path.isdir(newAbsPath): SIZE += os.path.getsize(newAbsPath) countDirSize(newAbsPath) else: SIZE += os.path.getsize(newAbsPath) def win(): path = input('請輸入需要統計的目錄>>> ') if os.path.exists(path): countDirSize(path) else: print("請輸入正確的路徑...") exit() return SIZE def linux(): path = sys.argv[1] if os.path.exists(path): countDirSize(path) else: print("請輸入正確的路徑...") exit() return SIZE if __name__ == '__main__': if os.name == "nt": print(win()) elif os.name == "posix": print(linux())
序列化模塊
前提:
試想一下,有沒有這種需求,想把一個字典或者列表直接存儲起來,等下次再用,但是這些數據類型是存儲在內存中的,程序結束后內存就被自動釋放了。想把數據永久的存儲下來,可以存儲在內存里面,可是文件中只能存儲字符串類型,那么這種怎么實現呢?比如打游戲過程中,打累了,停下來、關掉游戲,想過兩天再玩,兩天之后,游戲又從你上次停止的地方繼續運行,你上次的游戲進度肯定是存儲在硬盤上了,是以何種方式實現的呢?
可能你會想到強大的eval()內置函數,通過eval就能操作字符串。
stu = {'name':'xiaobai', 'age': 20}
ret = str(stu)
print(eval(ret), type(eval(ret)))
>>>
{'name': 'xiaobai', 'age': 20} <class 'dict'>
但是強大的eval()的安全性卻得不到保證,如果在文件中讀取出來的是一條類似‘rm /’之類的命令,后果就不堪設想
所以在三個場景中是不建議使用eval函數的:
1、用戶輸入的時候 2、文件讀入的時候 3、網絡傳輸的時候
所以這個時候用到:序列化
序列化:
將一個對象從內存中轉換為可存儲(字符串類型)或者可傳輸(bytes)類型的過程,就叫做序列化。在python中叫做pickling,通俗講:序列化就是將其他數據類型轉換為字符串/bytes類型的過程。
為什么要使用序列化:
(1)持久化數據類型
(2)跨平台進行交互。不同的編程語言都用協商好的序列化格式,那么便能打破平台/語言之間的限制,實現跨平台數據交互。
(3)使程序更具維護性。

json
json格式在各個語言之間都是通用的序列化格式。在json中,所有的字符串都必須是" "雙引號。

json的優點:
所有的數據類型都是各個語言通用的。在各個編程語言中都支持。
json的缺點:
1、json只是支持非常少的數據類型
2、對數據類型的約束十分嚴格
(1)字典中的key必須是字符串。
(2)json只支持列表,字典,數值,字符串,布爾值。
json模塊提供了四個功能:dumps、dump、loads、load
# dumps 與 loads import json # 序列化 dic = {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} str_dic = json.dumps(dic) # 序列化:將一個字典轉換為字符串 print(str_dic, type(str_dic)) #結果>>> {"name": "xiaobai", "age": 20, "sex": "nan", "2": 4} <class 'str'> # 注意:json轉換完的字符串類型的字典中的字符串是有""表示; 如果數字為key那么dump之后會強行轉換為字符串數據類型 # 反序列化 dic2 = json.loads(str_dic) # 將一個字符串格式的字典轉換成一個字典 print(dic2, type(dic2)) #結果>>> {'name': 'xiaobai', 'age': 20, 'sex': 'nan', '2': 4} <class 'dict'> # 注意:要用json的loads功能處理的字符串類型的字典中的字符串必須有""表示 # json是否支持元組,對元組做value的字典會把元組強制轉換為列表 dic = {'a': (1, 2, 3)} str_dic = json.dumps(dic) print(str_dic) # {"a": [1, 2, 3]} new_dic = json.loads(str_dic) print(new_dic) # {'a': [1, 2, 3]} # json是否支持元組做key? 不支持,會報錯 # dic = {(1, 2, 3): "a"} # str_dic = json.dumps(dic) # TypeError: keys must be a string # 處理嵌套的數據類型 list_dic = [1, ['a', 'b', 'c'], 2, {'k1': 'k2', 'k3': 'k4'}] str_dic = json.dumps(list_dic) print(str_dic, type(str_dic)) #結果:[1, ["a", "b", "c"], 2, {"k1": "k2", "k3": "k4"}] <class 'str'> new_dic = json.loads(str_dic) print(new_dic, type(new_dic)) #結果:[1, ['a', 'b', 'c'], 2, {'k1': 'k2', 'k3': 'k4'}] <class 'list'>
如果想把數據類型直接序列化到一個文件中,那么就要使用到dump和load方法
# dump 與 load # 系列化進文件 import json dic = {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} with open('dump_json', 'w') as f: json.dump(dic, f) # dump方法接收一個文件句柄,直接將字典轉換成json字符串寫入文件 with open('dump_json') as f: ret = json.load(f) # load方法接收一個文件句柄,直接將文件中的json字符串轉換成數據結構返回 print(type(ret), ret) # <class 'dict'> {'name': 'xiaobai', 'age': 20, 'sex': 'nan', '2': 4} # 能不能dump多個數據進入文件, dump可以多個數據進去,但是load不出來了,會報錯 dic = {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} dic2 = {'k1': 'v1', 'k2': 'v2'} with open('dump_json', 'w') as f: json.dump(dic, f) json.dump(dic2, f) # with open('dump_json') as f: # json.load(f) # json.decoder.JSONDecodeError # 如果非要使用json dump多個數據到文件里面,那么就要用到dumps dic = {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} dic2 = {'k1': 'v1', 'k2': 'v2'} with open('dump_json', 'w') as f: str_dic1 = json.dumps(dic) str_dic2 = json.dumps(dic2) f.write(str_dic1 + '\n') f.write(str_dic2 + '\n') with open('dump_json') as f: for line in f: ret = json.loads(line) print(ret)
寫入中文亂碼,需要使用ensure_ascii關鍵字參數
# 中文格式的 import json dic = {'中國':'北京', '美國':'華盛頓'} new_dic = json.dumps(dic) print(new_dic) # {"\u4e2d\u56fd": "\u5317\u4eac", "\u7f8e\u56fd": "\u534e\u76db\u987f"} new2_dic = json.dumps(dic, ensure_ascii=False) print(new2_dic) # {"中國": "北京", "美國": "華盛頓"} with open('dump_json', 'w') as f: json.dump(dic, f) # 寫入文件的內容:{"\u4e2d\u56fd": "\u5317\u4eac", "\u7f8e\u56fd": "\u534e\u76db\u987f"} with open('dump_json', 'w', encoding='utf-8') as f: json.dump(dic, f, ensure_ascii=False) # 寫入文件的內容:{"中國": "北京", "美國": "華盛頓"}
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格式對python數據類型的支持不是那么完美,如果只是在python程序之間交互,使用pickle模塊的支持性會更好。但是不足之處就是,pickle只是適用於python語言。

pickle的優點:
(1)pickle支持python中的幾乎所有數據類型
(2)pickle會把數據類型序列化為bytes類型
pickle的缺點:
(1)pickle只適用於python
pickle模塊提供了四個功能:dumps、dump(序列化,存)、loads(反序列化,讀)、load (不僅可以序列化字典,列表...可以把python中任意的數據類型序列化)
import pickle # dumps 與 loads dic = {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} b_dic = pickle.dumps(dic) print(type(b_dic)) # <class 'bytes'> d_dic = pickle.loads(b_dic) print(type(d_dic)) #<class 'dict'> # dump 與 load dic = {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} with open('pickle_dump', 'wb') as f: pickle.dump(dic, f) with open('pickle_dump', 'rb') as f: ret = pickle.load(f) print(ret) # {'name': 'xiaobai', 'age': 20, 'sex': 'nan', 2: 4} # 可以發現pickle和json用法其實是完全一樣,只是dump寫和讀的時候注意,因為pickle轉換為bytes類型,所以寫讀時候都要以wb 和rb的形式
hashlib模塊
算法介紹:
Python的hashlib提供了常見的摘要算法,如MD5,SHA1等等。
什么是摘要算法呢?摘要算法又稱哈希算法、散列算法。它通過一個函數,把任意長度的數據轉換為一個長度固定的數據串(通常用16進制的字符串表示)。
摘要算法就是通過摘要函數f()對任意長度的數據data計算出固定長度的摘要digest,目的是為了發現原始數據是否被人篡改過。
摘要算法之所以能指出數據是否被篡改過,就是因為摘要函數是一個單向函數,計算f(data)很容易,但通過digest反推data卻非常困難。而且,對原始數據做一個bit的修改,都會導致計算出的摘要完全不同。
hashlib介紹:
hashlib模塊是一個內部有摘要算法的模塊,而且內部可以給我們提供不止一種摘要算法。能夠把 一個 字符串 數據類型的變量,轉換成一個 定長的 密文的 字符串,字符串里的每一個字符都是一個十六進制數字
為什么需要hashlib?
對於同一個字符串,用相同的算法,相同的手段去進行摘要,獲得的值總是相同的
hashlib模塊的使用:
hashlib模塊提供了多種摘要算法:
md5算法: 定長32位 16進制(應用最廣發的摘要算法)
sha系統算法: 定長40位
sha算法要比MD5算法更加復雜,且sha N數值越大,算法越復雜,耗時越久,結果越長,但也更安全
import hashlib # hashlib模塊md5摘要算法的使用 name = "xiaobai" password = 'xiaobai123' # 1.首先,需要先實例化一個md5的對象,一個對象只加密一個數據 md5_obj = hashlib.md5() # 2. update()方法,把需要進行md5的對象放入 md5_obj.update(password.encode('utf-8')) # 3. 通過hexdigest(),得到摘要算法之后的密文 md5_password = md5_obj.hexdigest() print(md5_password) # 21b3a6792936ba9c2ecbcbe0da8ba961 print(len(md5_password)) # md5算法,定長32位 # hashlib模塊sha摘要算法的使用 # 操作和md5如出一轍,先創建對象,通過update加密,再通過hexdigest取值 name = "xiaobai" password = 'xiaobai123' sha_obj = hashlib.sha1() sha_obj.update(password.encode('utf-8')) sha_password = sha_obj.hexdigest() print(sha_password) # 6e96c5250d4d9c3b1ea9b5815d41aa0343a3c691 print(len(sha_password)) # sha1算法,定長40位
hashlib的應用
用戶登錄的驗證
name | password -------+-------- xiaobai|xiaobai123
有一個用戶小白,密碼為xiaobai123,如果密碼就這樣明文存儲,如果數據庫被黑,那么密碼就毫無保留的暴露給了黑客。所以這時候就需要用到摘要,在數據庫中,存儲密碼的摘要信息,每次登陸的時候,再做摘要信息的對比
name | password -------+-------- xiaobai|21b3a6792936ba9c2ecbcbe0da8ba961
所以每次登陸的時候,便需要進行密碼信息的摘要對比
import hashlib def get_md5_pwd(s): md5_obj = hashlib.md5() md5_obj.update(s.encode('utf-8')) ret = md5_obj.hexdigest() return ret username = input("username>>>: ").strip() password = input("password>>>: ").strip() with open('userinfo', encoding='utf-8') as f: for line in f: user, pwd = line.strip().split('|') if username == user and get_md5_pwd(password) == pwd: print("登錄成功") break else: print("登錄失敗")
通過摘要算法的手段,雖然密碼是用密文的形式存儲了,但是在現在的攻擊手段中,有一種叫做"撞庫"的手段,就是通過一個存儲着大量密碼與md5后的摘要對應的關系,再一一進行匹配,如果摘要信息一致,便能夠反推出密碼,因為同一種算法的同一個字符串,結果總是不變的。那么,有什么方法能夠防止撞庫?那就通過加鹽值得手段(1.固定鹽值 2.更好的方法:動態加鹽)
何為鹽值(salt),其實就是給原數據+一段指定的字符串,這樣得到的MD5值就會發生變化。只要顏值不被黑客知道,那么就很難反向推出原數據。
# 加鹽的md5算法,采用固定鹽值(鹽值:static) username = "xiaobai" password = "xiaobai123" md5_obj = hashlib.md5() md5_obj.update('static'.encode('utf-8')) # 加油 md5_obj.update(password.encode('utf-8')) ret = md5_obj.hexdigest() print(ret) # 動態加鹽,通過把用戶的唯一標識作為鹽值,例如每個用戶的用戶名都是唯一 username = "xiaobai" password = "xiaobai123" md5_obj = hashlib.md5() md5_obj.update(username.encode('utf-8')) # 動態加鹽 md5_obj.update(password.encode('utf-8')) ret = md5_obj.hexdigest() print(ret)
文件一致性的校驗
給一個文件中的所有內容進行摘要算法,得到一個md5結果。此時,我們可以體驗到md5摘要算法的神奇的地方,對於同一個字符串,不管把他拆開多少段,最終得到的md5值都是一樣。
# 同一個字符串,不管拆開多少段,最終的md5都是一樣的。 s = 'hello world' md5_obj = hashlib.md5() md5_obj.update(s.encode('utf-8')) ret = md5_obj.hexdigest() print(ret) # 5eb63bbbe01eeed093cb22bb8f5acdc3 md5_obj = hashlib.md5() md5_obj.update('hello '.encode('utf-8')) md5_obj.update('world'.encode('utf-8')) ret = md5_obj.hexdigest() print(ret) # 5eb63bbbe01eeed093cb22bb8f5acdc3
所以對文件進行一致性校驗
def get_file_md5(file_path): file_md5_obj = hashlib.md5() with open(file_path, encoding='utf-8') as f: for line in f: file_md5_obj.update(line.encode('utf-8')) ret = file_md5_obj.hexdigest() return ret
# 文件校驗, 兩個文件對比 import os, sys, hashlib def get_file_md5(file_path): file_md5_obj = hashlib.md5() with open(file_path, encoding='utf-8') as f: for line in f: file_md5_obj.update(line.encode('utf-8')) ret = file_md5_obj.hexdigest() return ret def file_Contrast(file_one_path, file_tow_path): file_one_md5 = get_file_md5(file_one_path) file_tow_md5 = get_file_md5(file_tow_path) if file_one_md5 == file_tow_md5: print("%s 與 %s 一致" % (file_one_path, file_tow_path)) else: print("兩個文件不一致") if __name__ == '__main__': if os.name == 'posix': if len(sys.argv) < 3: print("\033[1;36;40mUSAGE: python %s <file1> <file2>\033[0m" % sys.argv[0]) exit(-1) file_one_path = sys.argv[1] file_tow_path = sys.argv[2] if os.path.exists(file_one_path) and os.path.exists(file_tow_path): file_Contrast(file_one_path, file_tow_path) else: print("請輸入正確的路徑") elif os.name == 'nt': file_one_path = input('輸入需要對比的第一個文件路徑>>>: ') file_tow_path = input('輸入需要對比的第一個文件路徑>>>: ') if os.path.exists(file_one_path) and os.path.exists(file_tow_path): file_Contrast(file_one_path, file_tow_path) else: print("請輸入正確的路徑")
對視頻文件進行一致性校驗
一般是視頻格式的文件/網絡傳輸的文件,都是二進制的bytes類型。此時沒有行的概念,該怎么做?此時,可以設置一個buffer,每次都讀取相同長度的buffer.
#設置一個buffer,每次都通過f.read(buffer)讀取定長的數據。如果電腦配置比較高,調整相應的buffer即可 import os, sys, hashlib def get_vedio_md5(file_path, buffer=1024): file_size = os.path.getsize(file_path) md5_obj = hashlib.md5() with open(file_path, 'rb') as f: while file_size: content = f.read(buffer) md5_obj.update(content) file_size -= len(content) ret = md5_obj.hexdigest() return ret
logging模塊
logging模塊是用來操作日志的。
logging模塊分為兩種配置方式:(1)函數式簡單配置。(2)logger對象配置
函數式簡單配置
import logging logging.debug('debug message') logging.info('info message') logging.warning('warning message') logging.error('error message') logging.critical('critical')

默認情況下Python的logging模塊將日志打印到了標准輸出中,且只顯示了大於等於WARNING級別的日志,這說明默認的日志級別設置為WARNING(日志級別等級CRITICAL > ERROR > WARNING > INFO > DEBUG),默認的日志格式為日志級別:Logger名稱:用戶輸出消息。此時如果想改變顯示,就需要在logging.basicConfig()中,把level級別調低
import logging logging.basicConfig(level=logging.DEBUG) logging.debug('debug message') logging.info('info message') logging.warning('warning message') logging.error('error message') logging.critical('critical')

此時,如果不想日志默認輸出到標准輸出,想重定向輸出到指定的日志文件中,那么也可以通過修改logging.basicconfig()中filename屬性,指定重定向的文件。
import logging # 通過修改logging.basicConfig(filename=‘file_path’),進行輸出重定向 logging.basicConfig(level=logging.DEBUG, filename='loging.log') logging.debug('debug message') logging.info('info message') logging.warning('warning message') logging.error('error message') logging.critical('critical')

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用戶輸出的消息 # 一般常用的配置 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='loging.log', filemode='w')
總結:
通過logging模塊的簡單配置項可以完成對日志的基本操作,但是依然有幾點痛點:
(1)basicConifg不支持輸出中文
(2)日志的輸出只能夠文件/屏幕二選一,不能做到同時。
logger 對象配置
既然logging模塊自帶的basicConfig配置不能夠解決中文問題,那么只能通過自己創建對象的方式來更加靈活的操作,解決固有的痛點:1.不能支持中文 2.同時向文件和屏幕輸出內容
事前應該有幾個准備事項:
(1)先實例化一個日志對象
(2)創建一個控制文件輸出的文件操作符
(3)創建一個控制屏幕輸出的屏幕操作符
(4)指定日志輸出的格式(可以指定多個,文件輸出和屏幕輸出格式可以不同)
(5)文件操作符綁定一個日志格式
(6)屏幕操作符綁定一個日志格式
(7)日志對象綁定文件操作符以及屏幕操作符
import logging # (1) 創建一個log對象 logger = logging.getLogger() # (2) 創建一個控制文件輸出的文件操作符,encoding='utf-8‘’,解決中文問題 file_handler = logging.FileHandler('test.log', encoding='utf-8') # (3) 創建一個控制屏幕輸出的屏幕操作符 screen_handler = logging.StreamHandler() # (4) 設置日志輸出的格式 log_fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # (5) 給文件操作符綁定一個日志格式 file_handler.setFormatter(log_fmt) # (6) 給屏幕操作符綁定一個日志格式 screen_handler.setFormatter(log_fmt) # (7) 日志對象綁定文件操作符和屏幕操作符 logger.addHandler(file_handler) logger.addHandler(screen_handler) # (8) 設置日志輸出的級別 logger.setLevel(logging.DEBUG) # (9) 打印日志 logger.debug('這是debug的消息') logger.info('這是info的消息') logger.warning('這是warning的消息') logger.error('這是error的消息')
import logging # (1) 創建一個log對象 logger = logging.getLogger() # (2) 創建一個控制文件輸出的文件操作符,encoding='utf-8‘’,解決中文問題 file_handler = logging.FileHandler('test.log', encoding='utf-8') # (3) 創建一個控制屏幕輸出的屏幕操作符 screen_handler = logging.StreamHandler() # (4) 設置日志輸出的格式 log_fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # (5) 給文件操作符綁定一個日志格式 file_handler.setFormatter(log_fmt) # (6) 給屏幕操作符綁定一個日志格式 screen_handler.setFormatter(log_fmt) # (7) 日志對象綁定文件操作符和屏幕操作符 logger.addHandler(file_handler) logger.addHandler(screen_handler) # (8) 設置日志輸出的級別 logger.setLevel(logging.DEBUG) # (9) 打印日志 def login(): username = input("username>>>: ") password = input("password>>>: ") if username == "xiaobai" and password == "xiaobai123": logger.info("%s 登錄成功 " % username) print("登錄成功") else: logger.error("%s 登錄失敗,密碼:%s " % (username, password)) print("登錄失敗") login()
