一? 序列化模塊
什么叫序列化——將原本的字典、列表等內(nèi)容轉(zhuǎn)換成一個(gè)字符串的過(guò)程就叫做序列化。
比如,我們?cè)趐ython代碼中計(jì)算的一個(gè)數(shù)據(jù)需要給另外一段程序使用,那我們?cè)趺唇o?
現(xiàn)在我們能想到的方法就是存在文件里,然后另一個(gè)python程序再?gòu)奈募镒x出來(lái)。
但是我們都知道,對(duì)于文件來(lái)說(shuō)是沒(méi)有字典這個(gè)概念的,所以我們只能將數(shù)據(jù)轉(zhuǎn)換成字典放到文件中。
你一定會(huì)問(wèn),將字典轉(zhuǎn)換成一個(gè)字符串很簡(jiǎn)單,就是str(dic)就可以辦到了,為什么我們還要學(xué)習(xí)序列化模塊呢?
沒(méi)錯(cuò)序列化的過(guò)程就是從dic 變成str(dic)的過(guò)程。現(xiàn)在你可以通過(guò)str(dic),將一個(gè)名為dic的字典轉(zhuǎn)換成一個(gè)字符串,
但是你要怎么把一個(gè)字符串轉(zhuǎn)換成字典呢?
聰明的你肯定想到了eval(),如果我們將一個(gè)字符串類型的字典str_dic傳給eval,就會(huì)得到一個(gè)返回的字典類型了。
eval()函數(shù)十分強(qiáng)大,但是eval是做什么的?e官方demo解釋為:將字符串str當(dāng)成有效的表達(dá)式來(lái)求值并返回計(jì)算結(jié)果。
BUT!強(qiáng)大的函數(shù)有代價(jià)。安全性是其最大的缺點(diǎn)。
想象一下,如果我們從文件中讀出的不是一個(gè)數(shù)據(jù)結(jié)構(gòu),而是一句"刪除文件"類似的破壞性語(yǔ)句,那么后果實(shí)在不堪設(shè)設(shè)想。
而使用eval就要擔(dān)這個(gè)風(fēng)險(xiǎn)。
所以,我們并不推薦用eval方法來(lái)進(jìn)行反序列化操作(將str轉(zhuǎn)換成python中的數(shù)據(jù)結(jié)構(gòu))

序列化的目的
1、以某種存儲(chǔ)形式使自定義對(duì)象持久化;
2、將對(duì)象從一個(gè)地方傳遞到另一個(gè)地方。
3、使程序更具維護(hù)性。

Python可序列化的數(shù)據(jù)類型:
| Python | JSON |
| dict? ? ? ? ? |? ? object? ? |
| list, tuple? |? ? array? ? |
| str? ? ? ? ? ? |? ? string? ? |
| int, float? ? |? ? number? |
| True? ? ? ? ? |? ? ? true? ? |
| False? ? ? ? ? |? ? ? false? ? |
| None? ? ? ? ? |? ? ? null? ? |

1.1 json模塊
Json模塊提供了四個(gè)功能:dumps、dump、loads、load
import json
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = json.dumps(dic)? #序列化:將一個(gè)字典轉(zhuǎn)換成一個(gè)字符串
print(type(str_dic),str_dic)? #<class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"}
#注意,json轉(zhuǎn)換完的字符串類型的字典中的字符串是由""表示的
dic2 = json.loads(str_dic)? #反序列化:將一個(gè)字符串格式的字典轉(zhuǎn)換成一個(gè)字典
#注意,要用json的loads功能處理的字符串類型的字典中的字符串必須由""表示
print(type(dic2),dic2)? #<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}]
str_dic = json.dumps(list_dic) #也可以處理嵌套的數(shù)據(jù)類型
print(type(str_dic),str_dic) #<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}]
list_dic2 = json.loads(str_dic)
print(type(list_dic2),list_dic2) #<class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
loads和dumps
dumps loads

import json
f = open('json_file','w')
dic = {'k1':'v1','k2':'v2','k3':'v3'}
json.dump(dic,f)? #dump方法接收一個(gè)文件句柄,直接將字典轉(zhuǎn)換成json字符串寫(xiě)入文件
f.close()
f = open('json_file')
dic2 = json.load(f)? #load方法接收一個(gè)文件句柄,直接將文件中的json字符串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)返回
f.close()
print(type(dic2),dic2)
其它參數(shù)說(shuō)明:
Serialize obj to a JSON formatted str.(字符串表示的json對(duì)象)
Skipkeys:默認(rèn)值是False,如果dict的keys內(nèi)的數(shù)據(jù)不是python的基本類型(str,unicode,int,long,float,bool,None),設(shè)置為False時(shí),就會(huì)報(bào)TypeError的錯(cuò)誤。此時(shí)設(shè)置成True,則會(huì)跳過(guò)這類key
ensure_ascii:,當(dāng)它為True的時(shí)候,所有非ASCII碼字符顯示為\uXXXX序列,只需在dump時(shí)將ensure_ascii設(shè)置為False即可,此時(shí)存入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:應(yīng)該是一個(gè)非負(fù)的整型,如果是0就是頂格分行顯示,如果為空就是一行最緊湊顯示,否則會(huì)換行且按照indent的數(shù)值顯示前面的空白分行顯示,這樣打印出來(lái)的json數(shù)據(jù)也叫pretty-printed json
separators:分隔符,實(shí)際上是(item_separator, dict_separator)的一個(gè)元組,默認(rèn)的就是(‘,’,’:’);這表示dictionary內(nèi)keys之間用“,”隔開(kāi),而KEY和value之間用“:”隔開(kāi)。
default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError.
sort_keys:將數(shù)據(jù)根據(jù)keys的值進(jìn)行排序。
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.
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)

1.2 pickle模塊
用于序列化的兩個(gè)模塊
json,用于字符串 和 python數(shù)據(jù)類型間進(jìn)行轉(zhuǎn)換
pickle,用于python特有的類型 和 python的數(shù)據(jù)類型間進(jìn)行轉(zhuǎn)換
pickle模塊提供了四個(gè)功能:dumps、dump(序列化,存)、loads(反序列化,讀)、load ?(不僅可以序列化字典,列表...可以把python中任意的數(shù)據(jù)類型序列化)
pickle
import pickle
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)? #一串二進(jìn)制內(nèi)容
dic2 = pickle.loads(str_dic)
print(dic2)? ? #字典
import time
struct_time? = time.localtime(1000000000)
print(struct_time)
f = open('pickle_file','wb')
pickle.dump(struct_time,f)
f.close()
f = open('pickle_file','rb')
struct_time2 = pickle.load(f)
print(struct_time2.tm_year)

這時(shí)候機(jī)智的你又要說(shuō)了,既然pickle如此強(qiáng)大,為什么還要學(xué)json呢?
這里我們要說(shuō)明一下,json是一種所有的語(yǔ)言都可以識(shí)別的數(shù)據(jù)結(jié)構(gòu)。
如果我們將一個(gè)字典或者序列化成了一個(gè)json存在文件里,那么java代碼或者js代碼也可以拿來(lái)用。
但是如果我們用pickle進(jìn)行序列化,其他語(yǔ)言就不能讀懂這是什么了~
所以,如果你序列化的內(nèi)容是列表或者字典,我們非常推薦你使用json模塊
但如果出于某種原因你不得不序列化其他的數(shù)據(jù)類型,而未來(lái)你還會(huì)用python對(duì)這個(gè)數(shù)據(jù)進(jìn)行反序列化的話,那么就可以使用pickle

二? hashlib模塊
算法介紹
Python的hashlib提供了常見(jiàn)的摘要算法,如MD5,SHA1等等。
什么是摘要算法呢?摘要算法又稱哈希算法、散列算法。它通過(guò)一個(gè)函數(shù),把任意長(zhǎng)度的數(shù)據(jù)轉(zhuǎn)換為一個(gè)長(zhǎng)度固定的數(shù)據(jù)串(通常用16進(jìn)制的字符串表示)。
摘要算法就是通過(guò)摘要函數(shù)f()對(duì)任意長(zhǎng)度的數(shù)據(jù)data計(jì)算出固定長(zhǎng)度的摘要digest,目的是為了發(fā)現(xiàn)原始數(shù)據(jù)是否被人篡改過(guò)。
摘要算法之所以能指出數(shù)據(jù)是否被篡改過(guò),就是因?yàn)檎瘮?shù)是一個(gè)單向函數(shù),計(jì)算f(data)很容易,但通過(guò)digest反推data卻非常困難。而且,對(duì)原始數(shù)據(jù)做一個(gè)bit的修改,都會(huì)導(dǎo)致計(jì)算出的摘要完全不同。
我們以常見(jiàn)的摘要算法MD5為例,計(jì)算出一個(gè)字符串的MD5值:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?')
print md5.hexdigest()
計(jì)算結(jié)果如下:
d26a53750bc40b38b65a520292f69306
如果數(shù)據(jù)量很大,可以分塊多次調(diào)用update(),最后計(jì)算的結(jié)果是一樣的:
md5 = hashlib.md5()
md5.update('how to use md5 in ')
md5.update('python hashlib?')
print md5.hexdigest()
MD5是最常見(jiàn)的摘要算法,速度很快,生成結(jié)果是固定的128 bit字節(jié),通常用一個(gè)32位的16進(jìn)制字符串表示。另一種常見(jiàn)的摘要算法是SHA1,調(diào)用SHA1和調(diào)用MD5完全類似:
import hashlib
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in ')
sha1.update('python hashlib?')
print sha1.hexdigest()

SHA1的結(jié)果是160 bit字節(jié),通常用一個(gè)40位的16進(jìn)制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不過(guò)越安全的算法越慢,而且摘要長(zhǎng)度更長(zhǎng)。
摘要算法應(yīng)用
任何允許用戶登錄的網(wǎng)站都會(huì)存儲(chǔ)用戶登錄的用戶名和口令。如何存儲(chǔ)用戶名和口令呢?方法是存到數(shù)據(jù)庫(kù)表中:
name | password
--------+----------
michael | 123456
bob? ? | abc999
alice? | alice2008

如果以明文保存用戶口令,如果數(shù)據(jù)庫(kù)泄露,所有用戶的口令就落入黑客的手里。此外,網(wǎng)站運(yùn)維人員是可以訪問(wèn)數(shù)據(jù)庫(kù)的,也就是能獲取到所有用戶的口令。正確的保存口令的方式是不存儲(chǔ)用戶的明文口令,而是存儲(chǔ)用戶口令的摘要,比如MD5:
username | password
---------+---------------------------------
michael? | e10adc3949ba59abbe56e057f20f883e
bob? ? ? | 878ef96e86145580c38c87f0410ad153
alice? ? | 99b1c2188db85afee403b1536010c2c9
考慮這么個(gè)情況,很多用戶喜歡用123456,888888,password這些簡(jiǎn)單的口令,于是,黑客可以事先計(jì)算出這些常用口令的MD5值,得到一個(gè)反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
這樣,無(wú)需破解,只需要對(duì)比數(shù)據(jù)庫(kù)的MD5,黑客就獲得了使用常用口令的用戶賬號(hào)。
對(duì)于用戶來(lái)講,當(dāng)然不要使用過(guò)于簡(jiǎn)單的口令。但是,我們能否在程序設(shè)計(jì)上對(duì)簡(jiǎn)單口令加強(qiáng)保護(hù)呢?
由于常用口令的MD5值很容易被計(jì)算出來(lái),所以,要確保存儲(chǔ)的用戶口令不是那些已經(jīng)被計(jì)算出來(lái)的常用口令的MD5,這一方法通過(guò)對(duì)原始口令加一個(gè)復(fù)雜字符串來(lái)實(shí)現(xiàn),俗稱“加鹽”:
hashlib.md5("salt".encode("utf8"))
經(jīng)過(guò)Salt處理的MD5口令,只要Salt不被黑客知道,即使用戶輸入簡(jiǎn)單口令,也很難通過(guò)MD5反推明文口令。
但是如果有兩個(gè)用戶都使用了相同的簡(jiǎn)單口令比如123456,在數(shù)據(jù)庫(kù)中,將存儲(chǔ)兩條相同的MD5值,這說(shuō)明這兩個(gè)用戶的口令是一樣的。有沒(méi)有辦法讓使用相同口令的用戶存儲(chǔ)不同的MD5呢?
如果假定用戶無(wú)法修改登錄名,就可以通過(guò)把登錄名作為Salt的一部分來(lái)計(jì)算MD5,從而實(shí)現(xiàn)相同口令的用戶也存儲(chǔ)不同的MD5。
摘要算法在很多地方都有廣泛的應(yīng)用。要注意摘要算法不是加密算法,不能用于加密(因?yàn)闊o(wú)法通過(guò)摘要反推明文),只能用于防篡改,但是它的單向計(jì)算特性決定了可以在不存儲(chǔ)明文口令的情況下驗(yàn)證用戶口令。

三? import的使用
import 模塊 先要怎么樣?
import tbjx 執(zhí)行一次tbjx這個(gè)模塊里面的所有代碼,
第一次引用tbjx這個(gè)模塊,會(huì)將這個(gè)模塊里面的所有代碼加載到內(nèi)存,只要你的程序沒(méi)有結(jié)束,接下來(lái)你在
引用多少次,它會(huì)先從內(nèi)存中尋找有沒(méi)有此模塊,如果已經(jīng)加載到內(nèi)存,就不再重復(fù)加載.
第一次導(dǎo)入模塊執(zhí)行三件事 ***
1. 在內(nèi)存中創(chuàng)建一個(gè)以tbjx命名的名稱空間.
2. 執(zhí)行此名稱空間所有的可執(zhí)行代碼(將tbjx.py文件中所有的變量與值的對(duì)應(yīng)關(guān)系加載到這
個(gè)名稱空間).
3. 通過(guò)tbjx. 的方式引用模塊里面的代碼.
import tbjx
print(tbjx.name)
tbjx.read1()
被導(dǎo)入模塊有獨(dú)立的名稱空間 ***
import tbjx
# name = 'alex'
# print(name)
# print(tbjx.name)
#
# def read1():
# print(666)
# tbjx.read1()
#
# name = '日天'
# tbjx.change()
# print(name) # 日天
# print(tbjx.name) # barry
為模塊起別名 **
1 簡(jiǎn)單,便捷.
# import hfjksdahdsafkd as sm
# print(sm.name)
2,有利于代碼的簡(jiǎn)化.
# 原始寫(xiě)法
# result = input('請(qǐng)輸入')
# if result == 'mysql':
# import mysql1
# mysql1.mysql()
# elif result == 'oracle':
# import oracle1
# oracle1.oracle()
# list.index()
# str.index()
# tuple.index()
# 起別名
# result = input('請(qǐng)輸入')
# if result == 'mysql':
# import mysql1 as sm
# elif result == 'oracle':
# import oracle1 as sm
# ''' 后面還有很多'''
# sm.db() # 統(tǒng)一接口,歸一化思想
導(dǎo)入多個(gè)模塊
import time, os, sys # 這樣寫(xiě)不好
# 應(yīng)該向以下這種寫(xiě)法:
import time
import os
import sys
from ... import ...
from ... import ...的使用
# from tbjx import name
# from tbjx import read1
# from tbjx import read2
# print(name)
# print(globals())
# read1()
from ... import ... 與import對(duì)比 ***
1. from.. import 用起來(lái)更方便
from tbjx import name
print(name)
2. from...import 容易與本文件的名字產(chǎn)生沖突.
# 1, 容易產(chǎn)生沖突,后者將前者覆蓋
# name = 'alex'
# from tbjx import name
# print(name)
3. 當(dāng)前位置直接使用read1和read2,執(zhí)行時(shí),仍然以tbjx.py文件全局名稱空間 ***
# from tbjx import read1
#
# def read1():
# print(666)
# name = '大壯'
# read1()
# print(globals())
from tbjx import change
name = 'Alex'
print(name) # 'Alex'
change() # 'barry'
from tbjx import name
print(name)
一行導(dǎo)入多個(gè)
from tbjx import name,read1,read2 # 這樣不好
from tbjx import name
from tbjx import read1
from ... import *
模塊循環(huán)導(dǎo)入的問(wèn)題
py文件的兩種功能py文件的兩個(gè)功能:
1. 自己用 腳本
2. 被別人引用 模塊使用
# print('from the tbjx.py')
__all__ = ['name', 'read1'] # 配合*使用
name = '太白金星'
def read1():
print('tbjx模塊:', name)
def read2():
print('tbjx模塊')
read1()
def change():
global name
name = 'barry'
print(name)
# print(__name__)
# 當(dāng)tbjx.py做腳本: __name__ == __main__ 返回True
# 當(dāng)tbjx.py做模塊被別人引用時(shí): __name__ == tbjx
# __name__ 根據(jù)文件的扮演的角色(腳本,模塊)不同而得到不同的結(jié)果
#1, 模塊需要調(diào)試時(shí),加上 if __name__ == '__main__':
# import time
# change() # 測(cè)試代碼
# if __name__ == '__main__':
# change()
# 2, 作為項(xiàng)目的啟動(dòng)文件需要用

模塊的搜索路徑
# import sm
import abc
# python 解釋器會(huì)自動(dòng)將一些內(nèi)置內(nèi)容(內(nèi)置函數(shù),內(nèi)置模塊等等)加載到內(nèi)存中
import sys
# print(sys.modules) # 內(nèi)置內(nèi)容(內(nèi)置函數(shù),內(nèi)置模塊等等)
import time
# print(sys.path)
#['D:\\python_22\\day17', 'C:\\Python\\Python36\\python36.zip',
'C:\\Python\\Python36\\DLLs', 'C:\\Python\\Python36\\lib',
'C:\\Python\\Python36', 'C:\\Python\\Python36\\lib\\site-packages']# 'D:\\python_22\\day17' 路徑是當(dāng)前執(zhí)行文件的相對(duì)路徑
# import tbjx
# 我就想找到dz 內(nèi)存沒(méi)有,內(nèi)置中,這兩個(gè)你左右不了,sys.path你可以操作.
import sys
sys.path.append(r'D:\python_22\day16')
# sys.path 會(huì)自動(dòng)將你的 當(dāng)前目錄的路徑加載到列表中.
import dz
# 如果你想要引用你自定義的模塊:
# 要不你就將這個(gè)模塊放到當(dāng)前目錄下面,要不你就手動(dòng)添加到sys.path
import sm
1. 它會(huì)先從內(nèi)存中尋找有沒(méi)有已經(jīng)存在的以sm命名的名稱空間.
2. 它會(huì)從內(nèi)置的模塊中找. time,sys,os,等等.
3. 他從sys.path中尋找.
