Python--常用模塊


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’
    %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 當前時區的名稱
    %% %號本身
    python中時間日期格式化符號:
  • 元組(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()
sys.argv用法

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)是創建時間(詳細信息參見平台的文檔)。
stat 結構

練習

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)   # 寫入文件的內容:{"中國": "北京", "美國": "華盛頓"}
ensure_ascii關鍵字參數示例
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":[
    "李華",
    "二愣子"
  ]
}
'''
json的格式化輸出

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("請輸入正確的路徑")
綜合版兩個文件MD5對比

對視頻文件進行一致性校驗

一般是視頻格式的文件/網絡傳輸的文件,都是二進制的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')
basicConfig常用的參數及輸出格式

總結:

  通過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()
使用示例

將不同級別的logging 日志信息寫入到不同文件

https://www.cnblogs.com/yanjieli/p/10179626.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM