這是一份完整的python基礎知識補充
萬惡之源 - Python基礎知識補充
閱讀目錄
- 編碼轉換
- 基礎補充
- 深淺拷貝
- id is ==
- 程式碼塊(瞭解)
- 小資料池(瞭解)
- 小結
回到頂部
編碼轉換
學習Python中有不明白推薦加入交流群
號:960410445
群裡有志同道合的小夥伴,互幫互助,
群裡有不錯的視訊學習教程和PDF!
編碼回顧:
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'\xe4\xb8\xad' 這是⼀個漢字的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'\xe4\xb8\xad'
b'\xd6\xd0'
記住: 英⽂編碼之後的結果和源字串⼀致. 中⽂編碼之後的結果根據編碼的不同. 編碼結果也不同.
我們能看到. ⼀箇中⽂的UTF-8編碼是3個位元組. ⼀個GBK的中⽂編碼是2個位元組.
編碼之後的型別就是bytes型別. 在⽹絡傳輸和儲存的時候我們python是儲存和儲存的bytes 型別.
那麼在對⽅接收的時候. 也是接收的bytes型別的資料. 我們可以使⽤decode()來進⾏解 碼操作.
把bytes型別的資料還原回我們熟悉的字串:
s = "我叫李嘉誠" print(s.encode("utf-8")) # b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a' print(b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a'.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('[email protected]'*20) b = intern('[email protected]'*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()