萬惡之源 – Python基礎知識補充
- 2020 年 1 月 19 日
- 筆記
編碼轉換
編碼回顧:
1. ASCII : 最早的編碼. ⾥⾯有英⽂⼤寫字⺟, ⼩寫字⺟, 數字, ⼀些特殊字元.
沒有中⽂, 8個01程式碼, 8個bit, 1個byte
2. GBK: 中⽂國標碼, ⾥⾯包含了ASCII編碼和中⽂常⽤編碼. 16個bit, 2個byte
3. UNICODE: 萬國碼, ⾥⾯包含了全世界所有國家⽂字的編碼. 32個bit, 4個byte, 包含了 ASCII
4. UTF-8: 可變⻓度的萬國碼. 是unicode的⼀種實現. 最⼩字元佔8位
1.英⽂: 8bit 1byte
2.歐洲⽂字:16bit 2byte
3.中⽂:24bit 3byte
綜上, 除了ASCII碼以外, 其他資訊不能直接轉換.
在python3的記憶體中. 在程式運⾏階段. 使⽤的是unicode編碼.
因為unicode是萬國碼. 什麼內容都可以進⾏顯⽰. 那麼在數據傳輸和存儲的時候由於unicode比較浪費空間和資源.
需要把 unicode轉存成UTF-8或者GBK進⾏存儲. 怎麼轉換呢.
在python中可以把⽂字資訊進⾏編碼. 編碼之後的內容就可以進⾏傳輸了.
編碼之後的數據是bytes類型的數據.其實啊. 還是原來的 數據只是經過編碼之後表現形式發⽣了改變⽽已.
bytes的表現形式:
1. 英⽂ b'alex' 英⽂的表現形式和字元串沒什麼兩樣
2. 中⽂ b'xe4xb8xad' 這是⼀個漢字的UTF-8的bytes表現形式
s = "alex" print(s.encode("utf-8")) # 將字元串編碼成UTF-8 print(s.encode("GBK")) # 將字元串編碼成GBK 結果: b'alex' b'alex' s = "中" print(s.encode("UTF-8")) # 中⽂編碼成UTF-8 print(s.encode("GBK")) # 中⽂編碼成GBK 結果: b'xe4xb8xad' b'xd6xd0'
記住: 英⽂編碼之後的結果和源字元串⼀致. 中⽂編碼之後的結果根據編碼的不同. 編碼結果也不同.
我們能看到. ⼀個中⽂的UTF-8編碼是3個位元組. ⼀個GBK的中⽂編碼是2個位元組.
編碼之後的類型就是bytes類型. 在⽹絡傳輸和存儲的時候我們python是保存和存儲的bytes 類型.
那麼在對⽅接收的時候. 也是接收的bytes類型的數據. 我們可以使⽤decode()來進⾏解 碼操作.
把bytes類型的數據還原回我們熟悉的字元串:
s = "我叫李嘉誠" print(s.encode("utf-8")) # b'xe6x88x91xe5x8fxabxe6x9dx8exe5x98x89xe8xafx9a' print(b'xe6x88x91xe5x8fxabxe6x9dx8exe5x98x89xe8xafx9a'.decod e("utf-8")) # 解碼
編碼和解碼的時候都需要制定編碼格式.
s = "我是⽂字" bs = s.encode("GBK") # 我們這樣可以獲取到GBK的⽂字 # 把GBK轉換成UTF-8 # ⾸先要把GBK轉換成unicode. 也就是需要解碼 s = bs.decode("GBK") # 解碼 # 然後需要進⾏重新編碼成UTF-8 bss = s.encode("UTF-8") # 重新編碼 print(bss)
基礎補充
我們補充給幾個數據類型的操作
lst = [1,2,3,4,5,6] for i in lst: lst.append(7) # 這樣寫法就會一直持續添加7 print(lst) print(lst)
列表: 循環刪除列表中的每⼀個元素
li = [11, 22, 33, 44] for e in li: li.remove(e) print(li) 結果: [22, 44]
分析原因: for的運⾏過程. 會有⼀個指針來記錄當前循環的元素是哪⼀個, ⼀開始這個指針指向第0 個.
然後獲取到第0個元素. 緊接著刪除第0個. 這個時候. 原來是第⼀個的元素會⾃動的變成 第0個.
然後指針向後移動⼀次, 指向1元素. 這時原來的1已經變成了0, 也就不會被刪除了.
⽤pop刪除試試看:
li = [11, 22, 33, 44] for i in range(0, len(li)): del li[i] print(li) 結果: 報錯 # i= 0, 1, 2 刪除的時候li[0] 被刪除之後. 後⾯⼀個就變成了第0個. # 以此類推. 當i = 2的時候. list中只有⼀個元素. 但是這個時候刪除的是第2個 肯定報錯啊
經過分析發現. 循環刪除都不⾏. 不論是⽤del還是⽤remove. 都不能實現. 那麼pop呢?
for el in li: li.pop() # pop也不⾏ print(li) 結果: [11, 22]
只有這樣才是可以的:
for i in range(0, len(li)): # 循環len(li)次, 然後從後往前刪除 li.pop() print(li)
或者. ⽤另⼀個列表來記錄你要刪除的內容. 然後循環刪除
li = [11, 22, 33, 44] del_li = [] for e in li: del_li.append(e) for e in del_li: li.remove(e) print(li)
注意: 由於刪除元素會導致元素的索引改變, 所以容易出現問題. 盡量不要再循環中直接去刪 除元素. 可以把要刪除的元素添加到另⼀個集合中然後再批量刪除.
dict中的fromkey(),可以幫我們通過list來創建⼀個dict
dic = dict.fromkeys(["jay", "JJ"], ["周杰倫", "麻花藤"]) print(dic) 結果: {'jay': ['周杰倫', '麻花藤'], 'JJ': ['周杰倫', '麻花藤']}
程式碼中只是更改了jay那個列表. 但是由於jay和JJ⽤的是同⼀個列表. 所以. 前⾯那個改了. 後面那個也會跟著改
dict中的元素在迭代過程中是不允許進⾏刪除的
dic = {'k1': 'alex', 'k2': 'wusir', 's1': '⾦⽼板'} # 刪除key中帶有'k'的元素 for k in dic: if 'k' in k: del dic[k] # dictionary changed size during iteration, 在循環迭 代的時候不允許進⾏刪除操作 print(dic)
那怎麼辦呢? 把要刪除的元素暫時先保存在⼀個list中, 然後循環list, 再刪除
dic = {'k1': 'alex', 'k2': 'wusir', 's1': '⾦⽼板'} dic_del_list = [] # 刪除key中帶有'k'的元素 for k in dic: if 'k' in k: dic_del_list.append(k) for el in dic_del_list: del dic[el] print(dic)
類型轉換:
元組 => 列表 list(tuple)
列表 => 元組 tuple(list)
list=>str str.join(list)
str=>list str.split()
轉換成False的數據:
0,'',None,[],(),{},set() ==> False
深淺拷貝
lst1 = ["⾦⽑獅王", "紫衫⻰王", "⽩眉鷹王", "⻘翼蝠王"] lst2 = lst1 print(lst1) print(lst2) lst1.append("楊逍") print(lst1) print(lst2) 結果: ['⾦⽑獅王', '紫衫⻰王', '⽩眉鷹王', '⻘翼蝠王', '楊逍'] ['⾦⽑獅王', '紫衫⻰王', '⽩眉鷹王', '⻘翼蝠王', '楊逍'] dic1 = {"id": 123, "name": "謝遜"} dic2 = dic1 print(dic1) print(dic2) dic1['name'] = "范瑤" print(dic1) print(dic2) 結果: {'id': 123, 'name': '謝遜'} {'id': 123, 'name': '謝遜'} {'id': 123, 'name': '范瑤'} {'id': 123, 'name': '范瑤'}
對於list, set, dict來說, 直接賦值. 其實是把記憶體地址交給變數. 並不是複製⼀份內容. 所以. lst1的記憶體指向和lst2是⼀樣的. lst1改變了, lst2也發⽣了改變
淺拷⻉
lst1 = ["何炅", "杜海濤","周渝⺠"] lst2 = lst1.copy() lst1.append("李嘉誠") print(lst1) print(lst2) print(id(lst1), id(lst2)) 結果: 兩個lst完全不⼀樣. 記憶體地址和內容也不⼀樣. 發現實現了記憶體的拷⻉ lst1 = ["何炅", "杜海濤","周渝⺠", ["麻花藤", "⻢芸", "周筆暢"]] lst2 = lst1.copy() lst1[3].append("⽆敵是多磨寂寞") print(lst1) print(lst2) print(id(lst1[3]), id(lst2[3])) 結果: ['何炅', '杜海濤', '周渝⺠', ['麻花藤', '⻢芸', '周筆暢', '⽆敵是多磨寂寞']] ['何炅', '杜海濤', '周渝⺠', ['麻花藤', '⻢芸', '周筆暢', '⽆敵是多磨寂寞']] 4417248328 4417248328
淺拷⻉. 只會拷⻉第⼀層. 第⼆層的內容不會拷⻉. 所以被稱為淺拷⻉
深拷⻉
import copy lst1 = ["何炅", "杜海濤","周渝⺠", ["麻花藤", "⻢芸", "周筆暢"]] lst2 = copy.deepcopy(lst1) lst1[3].append("⽆敵是多磨寂寞") print(lst1) print(lst2) print(id(lst1[3]), id(lst2[3])) 結果: ['何炅', '杜海濤', '周渝⺠', ['麻花藤', '⻢芸', '周筆暢', '⽆敵是多磨寂寞']] ['何炅', '杜海濤', '周渝⺠', ['麻花藤', '⻢芸', '周筆暢']] 4447221448 4447233800
都不⼀樣了.
深度拷貝. 把元素內部的元素完全進行拷貝複製. 不會產⽣⼀個改變另⼀個跟著 改變的問題 補充⼀個知識點:
最後我們來看⼀個⾯試題:
a = [1, 2] a[1] = a print(a[1])
id is ==
在Python中,id是什麼?id是記憶體地址,比如你利用id()內置函數去查詢一個數據的記憶體地址:
name = 'meet' s_id = id(name) # 通過內置方法獲取name變數對應的值在記憶體中的編號 print(s_id) # 2055782908568 這就是name在記憶體中的編號
那麼 is 是什麼? == 又是什麼?
== 是比較的兩邊的數值是否相等,而 is 是比較的兩邊的記憶體地址是否相等。 如果記憶體地址相等,那麼這兩邊其實是指向同一個記憶體地址。

可以說如果記憶體地址相同,那麼值肯定相同,但是如果值相同,記憶體地址不一定相同,如圖:

這就很神奇了,剛剛還不是一個記憶體地址呢,現在怎麼又是一個記憶體地址了,其中神奇之處就是我們的小數據池
小數據池,也稱為小整數快取機制,或者稱為駐留機制等等. 那麼到底什麼是小數據池?他有什麼作用呢?
程式碼塊(了解)
接下來我們學習下小數據池,在學小數據池之前我們來看下程式碼塊
根據提示我們從官方文檔找到了這樣的說法: A Python program is constructed from code blocks. A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition. Each command typed interactively is a block. A script file (a file given as standard input to the interpreter or specified as a command line argument to the interpreter) is a code block. A script command (a command specified on the interpreter command line with the 『-c『 option) is a code block. The string argument passed to the built-in functions eval() and exec() is a code block. A code block is executed in an execution frame. A frame contains some administrative information (used for debugging) and determines where and how execution continues after the code block』s execution has completed.
上面的主要意思是:
Python程式是由程式碼塊構造的。塊是一個python程式的文本,他是作為一個單元執行的。
程式碼塊:一個模組,一個函數,一個類,一個文件等都是一個程式碼塊。
而作為交互方式輸入的每個命令都是一個程式碼塊。
什麼叫交互方式?就是咱們在cmd中進入Python解釋器裡面,每一行程式碼都是一個程式碼塊,例如:

而對於一個文件中的兩個函數,也分別是兩個不同的程式碼塊:

OK,那麼現在我們了解了程式碼塊,這和小數據池有什麼關係呢?且聽下面分析。
程式碼塊的快取機制
Python在執行同一個程式碼塊的初始化對象的命令時,會檢查是否其值是否已經存在,如果存在,會將其重用。換句話說:執行同一個程式碼塊時,遇到初始化對象的命令時,他會將初始化的這個變數與值存儲在一個字典中,在遇到新的變數時,會先在字典中查詢記錄,如果有同樣的記錄那麼它會重複使用這個字典中的之前的這個值。所以在你給出的例子中,文件執行時(同一個程式碼塊)會把i1、i2兩個變數指向同一個對象,滿足快取機制則他們在記憶體中只存在一個,即:id相同。
程式碼塊的快取機制的適用範圍: int(float),str,bool
int(float):任何數字在同一程式碼塊下都會復用。
bool:True和False在字典中會以1,0方式存在,並且復用。
str:幾乎所有的字元串都會符合快取機制,具體規定如下(了解即可!):
1,非乘法得到的字元串都滿足程式碼塊的快取機制:
s1 = '寶元@!#*ewq' s2 = '寶元@!#*ewq' print(s1 is s2) # True
2,乘法得到的字元串分兩種情況:
2.1 乘數小於等於1的時候,任何字元串滿足程式碼塊的快取機制:
s1 = '好嗨啊,感覺自己身體要到了.932023756QQ932023756'*1 s2 = '好嗨啊,感覺自己身體要到了.932023756QQ932023756'*1 print(s1 is s2)
2.2 乘數>=2時:僅含大小寫字母,數字,下劃線,總長度<=20,滿足程式碼塊的快取機制:
s1 = 'old_' * 5 s2 = 'old_' * 5 print(s1 is s2) # True
優點:能夠提高一些字元串,整數處理人物在時間和空間上的性能;需要值相同的字元串,整數的時候,直接從『字典』中取出復用,避免頻繁的創建和銷毀,提升效率,節約記憶體。
小數據池(了解)
小數據池,也稱為小整數快取機制,或者稱為駐留機制等等,部落客認為,只要你在網上查到的這些名字其實說的都是一個意思,叫什麼因人而異。
那麼到底什麼是小數據池?他有什麼作用呢?
大前提:小數據池也是只針對 int(float),str,bool
小數據池是針對不同程式碼塊之間的快取機制!!!
官方對於整數,字元串的小數據池是這麼說的:
對於整數,Python官方文檔中這麼說: The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. 對於字元串: Incomputer science, string interning is a method of storing only onecopy of each distinct string value, which must be immutable. Interning strings makes some stringprocessing tasks more time- or space-efficient at the cost of requiring moretime when the string is created or interned. The distinct values are stored ina string intern pool. –引自維基百科
來,我給你們翻譯並匯總一下,這個表達的意思就是:
Python自動將-5~256的整數進行了快取,當你將這些整數賦值給變數時,並不會重新創建對象,而是使用已經創建好的快取對象。
python會將一定規則的字元串在字元串駐留池中,創建一份,當你將這些字元串賦值給變數時,並不會重新創建對象, 而是使用在字元串駐留池中創建好的對象。
其實,無論是快取還是字元串駐留池,都是python做的一個優化,就是將~5-256的整數,和一定規則的字元串,放在一個『池』(容器,或者字典)中,無論程式中那些變數指向這些範圍內的整數或者字元串,那麼他直接在這個『池』中引用,言外之意,就是記憶體中之創建一個。
優點:能夠提高一些字元串,整數處理人物在時間和空間上的性能;需要值相同的字元串,整數的時候,直接從『池』里拿來用,避免頻繁的創建和銷毀,提升效率,節約記憶體。
int:那麼大家都知道對於整數來說,小數據池的範圍是-5~256 ,如果多個變數都是指向同一個(在這個範圍內的)數字,他們在記憶體中指向的都是一個記憶體地址。

那麼對於字元串的規定呢?
str:字元串要從下面這幾個大方向討論(了解即可!):
1,字元串的長度為0或者1,默認都採用了駐留機制(小數據池)。


2,字元串的長度>1,且只含有大小寫字母,數字,下劃線時,才會默認駐留。

3,用乘法得到的字元串,分兩種情況。
3.1 乘數小於等於1時:
僅含大小寫字母,數字,下劃線,默認駐留。

含其他字元,長度<=1,默認駐留。

3.2 乘數>=2時:
僅含大小寫字母,數字,下劃線,總長度<=20,默認駐留。

4,指定駐留。
from sys import intern a = intern('hello!@'*20) b = intern('hello!@'*20) print(a is b) #指定駐留是你可以指定任意的字元串加入到小數據池中,讓其只在記憶體中創建一個對象,多個變數都是指向這一個字元串。
滿足以上字元串的規則時,就符合小數據池的概念。
bool值就是True,False,無論你創建多少個變數指向True,False,那麼他在記憶體中只存在一個。
看一下用了小數據池(駐留機制)的效率有多高:
顯而易見,節省大量記憶體在字元串比較時,非駐留比較效率o(n),駐留時比較效率o(1)。

小結
如果在同一程式碼塊下,則採用同一程式碼塊下的換快取機制。
如果是不同程式碼塊,則採用小數據池的駐留機制。
# pycharm 通過運行文件的方式執行下列程式碼: 這是在同一個文件下也就是同一程式碼塊下,採用同一程式碼塊下的快取機制。 i1 = 1000 i2 = 1000 print(i1 is i2) # 結果為True 因為程式碼塊下的快取機制適用於所有數字
通過交互方式中執行下面程式碼: # 這是不同程式碼塊下,則採用小數據池的駐留機制。 >>> i1 = 1000 >>> i2 = 1000 >>> print(i1 is i2) False # 不同程式碼塊下的小數據池駐留機制 數字的範圍只是-5~256.
更多驗證:
# 雖然在同一個文件中,但是函數本身就是程式碼塊,所以這是在兩個不同的程式碼塊下,不滿足小數據池(駐存機制),則指向兩個不同的地址。 def func(): i1 = 1000 print(id(i1)) # 2288555806672 def func2(): i1 = 1000 print(id(i1)) # 2288557317392 func() func2()