­

萬惡之源 – 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()