Python變量與基本數據類型


前言

  好了,從本章開始將正式進入Python的學習階段。本章主要介紹的是Python變量與基本數據類型的認識,這些都是最基本的知識並且必須要牢靠掌握在心中。

img

注釋

  學習任何一門語言首要的就是學習它的注釋。注釋就是說你的腳本程序在運行過程中不會被解釋器解釋與執行的一部分,它的功能主要是給人閱讀方便代碼的后期維護。

  在Python中(Python2和Python3均可),主要有3種注釋方式,其中單行注釋1種。多行注釋2種:

  單行注釋: #

  多行注釋:"""'''

img

  我們可以看到。輸出結果只有一個 hello,world ,這說明在解釋過程中 # 部分 與 ''' 還有 """ 的所有內容並未讓解釋器解釋與執行。

變量

變量的定義與使用

  變量就是計算機用來記錄事物狀態變化過程的東西,這里最主要的一點就是變,它是允許變化的。

  首先,絕大部分的編程語言關於變量的定義都分為三部分:

變量定義三部分

img

  我們來看一看在Python中如何准確的為一個變量賦值(先定義變量,后使用變量)。

>>> name = "yunya"  # 記錄姓名
>>> age = 18      # 記錄年齡
>>> height = 1.92   # 記錄身高
>>>

  這個就是一個很簡單的賦值操作,但是對於底層來說它其實也做了很多的事情

  當Python解釋器解釋到有賦值操作時,會在內存空間(堆內存)中申請一塊地方存放該變量值,並且會在內存空間(棧內存)中申請一塊地方存放該變量名。再將變量名和變量值的內存地址做一個綁定(不是雙向唯一性,而是一對多。一個變量值可以被多個變量名引用,一個變量名只能綁定一個內存地址),這種綁定的關系也存在於棧內存,可以這么理解,變量名存儲的並非變量值,而是變量值的內存地址。

  這里請記住兩個概念,堆內存和棧內存。會在后面的垃圾回收機制中詳細介紹。如果難以理解可以嘗試看一下圖片:

img

  現在介紹一下Python變量賦值的一些基本操作。除開直接賦值。還有交叉賦值,間接賦值(鏈式賦值)等等,我們這里看一下間接賦值:

>>> x = 1
>>> y = x  # x 原本為 1, y通過x也綁定上了1的內存地址,這被稱為間接賦值

  如何使用一個定義好了的變量呢?方式也很簡單。

>>> name = "yunya"  #定義變量
>>> age = 18
>>> height = 1.92
>>>
>>>
>>> x = 1
>>> y = x
>>>
>>> print(name)  #直接引用
yunya
>>> print(age)
18
>>> print(height)
1.92
>>> print(x)
1
>>> print(y)    #雖然是間接賦值,但是這里還是直接引用,因為y綁定的就是變量值1的內存地址
1
>>>

  我們可以看到。當我們打印x的時候是1這個沒問題,因為賦值的時候就是賦值成了1.但是打印y的時候依舊是1是為什么呢?因為變量名存儲的是變量值的內存地址。所以y中當然也存儲的是變量1的內存地址。這個是沒問題的。畫一張圖演示一下:

img

  繼續來看下一個問題:

>>> y = 1
>>> x = y
>>> y = 2
>>> print(x)
1
>>>

  為什么y變成了2,打印x卻還是1呢?這也是一個常常令新手迷惑的地方,總有人認為這種情況是 x 指向 y 指向的內存地址。並隨着 y 的變化而變化 ,但是事實卻是 y 的變化並不會影響 x 的內存地址指向。(y第一次指向變量值1並且將y賦值給x,那么x間接也算是指向了變量值1的內存地址,第二次y指向的內存地址發生改變,而x並不會改變指向地址,依舊是指向1的內存地址)

img

  接下來看交叉賦值。

>>> x,y = 1,2
>>> print(x)
1
>>> print(y)
2
>>> x,y = y,x
>>> print(x)
2
>>> print(y)
1
>>>

  交叉賦值這個應該挺好理解,這里不做多的解釋。用文字描述一下就即可:

  房間x,房間y中有兩個人。他們同時換了兩個房間。

  那么關於Python變量的基本使用與介紹就講到這里,最后補充一個關於變量的刪除:

>>> x = 1
>>> del x
>>> print(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>>

  可以看到當我們使用 del 刪除 x 的時候實際上是解除了 變量名 x 與變量值 1 的綁定關系,而變量值 1 此時已經沒有了綁定關系則會被Python垃圾回收機制所回收用於釋放內存,如果使用未與變量值綁定的變量名 x 就會拋出異常。is not defined。關於Python垃圾回收機制接下來會講到,這里做一個了解即可。重點是del可作用於解除變量名與變量值之間的綁定關系(變量名和關系存在於棧內存,使用del會刪除它們)。除此之外我們也可以使用變量名與新的值綁定從而達到與舊值解綁的目的。

>>> x = 1
>>> print(x)
1
>>> x = 2  # 現在想訪問1這個變量值是不可能的,除非用新的變量名來指向變量值1.
>>> print(x)
2
>>>

  還是畫一個圖示:

img

img

變量命名的規范

  上面介紹了變量的使用方式與賦值,那么關於變量的命名方式其實是有一個規范的

  變量名應當見名知意

  變量名由數字,字母,下划線組成。並且開頭不能為數字。

  變量名不能使用Python中的關鍵字(關鍵字:是指某些具有特殊功能的單詞,如break

  變量命名錯誤示范:

$name = 'yunya'  #具有特殊字符
1year = 365      #數字開頭
*_size = 1024    #具有特殊字符
and = 123        #使用了關鍵字      

年級 = 3         #強烈不建議使用中文(占用內存大)    
(color) = 'red'  #雖然這種命名方式可行但是也極為不推薦

  變量命名正確方式:

name = 'yunya'
__age = 18      #Python中 雙下划線開頭的變量名一般有隱私的這種說法
page_1 = '首頁'

變量命名的風格

  下面介紹幾種常用的變量命名方式:

>>> YunyaAge = 18      #駝峰式  (駝峰式在Python中並不常用但是也並非完全不能使用,風格因人而異)
>>> yunya_name = "雲崖"    #小寫加下划線  (Python中更推薦使用的變量命名方式)
>>> myClass = "三年級二班"    #Camel標記法 (首字母是小寫的,接下來的字母都以大寫字符開頭。)
>>> sMyClass = "三年級一班"    #匈牙利類型標記法 (前面的小寫字母為變量類型)

  這里更加推薦使用匈牙利類標記法,因為Python是一種弱類型語言所以一直有一段話叫做擼碼一時爽維護火葬場。因為不能明確的知道變量的類型,這里也只是順便提一嘴,不必太在意。

變量具有的三大特征

  變量的三大特性我們通過函數就能夠知道。這里介紹與print()方法差不多的2個方法:

  id()

    - 返回該變量的內存地址id號(唯一標識)

  type()

    - 返回該變量的數據類型

  value

    - 使用變量名拿到與其綁定的變量值(注意這不是一個方法,value中文意思即為值)

   我們來看一下使用方式:

>>> name = "雲崖"  #定義變量
>>> age = 18
>>> print(id(name)) #打印變量指向的內存地址的唯一標示(id)  id()
1902025257776
>>> print(id(age))
140705586665664
>>> print(type(name)) #打印變量值的數據類型  type()
<class 'str'>
>>> print(type(age))
<class 'int'>
>>> print(name)  #打印變量值本身
雲崖
>>> print(age)
18
>>>

is == 的區別與小整數池

  關於Python基本數據類型這里先不做贅述下面很快會介紹的,我們來聊一聊 id() 方法,現在你應該已經知道了 id() 方法是獲取變量名指向的內存地址的唯一標識(id),那么請嘗試解釋下面的情況:

>>> # ------------- 請注意以下測試請到原生Python解釋器中執行,Pycharm中執行有所誤差 --------------
>>> x = "這里是變量x"
>>> x_copy = "這里是變量x"   #請注意 x 與 x_copy 中存儲的2個變量值都是一樣的
>>> print(id(x))
1928354538192
>>> print(id(x_copy))  #為什么他們變量值是一樣的但是存儲變量值的內存地址卻不一樣呢?
1928354538304
>>> y = 200
>>> y_copy = 200
>>> print(id(y))
140705586671488
>>> print(id(y_copy))  #同樣的 y 與 y_copy 存儲的變量值也都是一樣,並且他們變量值在堆內存中都是引用的同一塊內存,這是為什么呢?
140705586671488
>>>

  現在肯定是不明白的,做了同樣的事情。唯一不同的區別在於一個值好像是數字,另一個值好像是文本。但是他們的結果就是不一樣,別着急。還是先介紹2個方法。

  is(成員運算符)

    - 判斷兩個對象(也可以認為是值)是否來源於同一塊內存地址(也可以說是否引用同一塊內存),引用值是否相等

  ==(算術運算符)

    - 判斷兩個對象(也可以認為是指)是否形式值相等

  有了這兩個方法我們就能慢慢的介紹上面的情況:

>>> x = "雲崖是帥哥"
>>> y = "雲崖是帥哥"
>>> print(x == y)   # True,表明形式值相同
True
>>> print(x is y)   # False,表明內存地址id號不同,則引用值不同
False

  這是關於字符串類型的變量值,形式值相同的情況下引用值不同...

>>> i = 100
>>> j = 100
>>> print(i == j)  #形式值相同
True
>>> print(i is j)  #內存地址id號相同,即引用值相同
True
>>> n = 10000
>>> m = 10000
>>> print(n == m)  #形式值相同
True
>>> print(n is m)  #內存地址id號不同,即引用值不同
False
>>>

  關於整數類型的值,形式值相同的情況下引用值可能相同也有可能不同...

  是不是更懵逼了?不要着急。解密的時刻到了:

  Python解釋器會覺得有一些數值會經常被使用(-5到255之間),故當腳本程序運行前就將這些數字寫入堆內存中。當用戶定義變量且使用到其中的數值時會直接將該數值的內存地址引用到存在於棧內存的變量名上,這樣做極大節省了內存空間。

  如:一個程序中使用了 100000 次 1 這個數值。那么如果沒有Python的這個機制則會開辟出 *100000 個內存空間用來存放相同的數值。這么做顯然極大的浪費了內存。故Python的這種機制是十分高效且合理的,並且它的名字叫做小整數池(范圍:-5,255)。*

  那么為什么字符串沒有常用的呢、字符串類型(現在可以理解為文本,就隨便輸入的東西)實在是變化多端。比如1到10的數值如果用字符串的形式表現那就數不勝數,如一二三四五六七...甚至大寫的壹貳仨肆伍陸柒等等都是有可能的。所以Python並沒有針對字符串做一個常用池。

  注意:關於小整數池的范圍一定要注意這是Python原生解釋器的范圍,而使用Pycharm則會擴大這一范圍 !!!

常量

  既然有變量,那么對應的也有常量。常量用於存儲一些不允許被改變的值,比如PI,3.1415926535897... 再比如人眼睛的個數總是2個一樣。這些都可以使用常量來進行存儲,但是很遺憾Python自帶的數據類型中沒有常量這一說法。故在Python中有一個約定的章法,對於一些常量的值。在命名方式上會采取全部大寫的方式

  常見的一些常量:

>>> PI = 3.1415926535897     #圓周率
>>> NUM_OF_EYES = 2  #眼睛個數
>>> SEX = ""  #性別
>>>

數字(虛擬)

整數(int)

  整數在Python中叫做整形。Python3中沒有小整形長整形(Python2中如果數字后加上L代表長整型,Python3取消了這種數據類型。)之分,並且整形支持四則運算。如:

>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> x = 10
>>> y = 10
>>> type(x)    # 注:在交互式的環境下可以不使用print() 依舊能返回結果,但是對於初學者仍然建議使用它
<class 'int'>  # 查看類型
>>> type(y)
<class 'int'>
>>> x = x - 5
>>> y -= 5     # -= 與 - 自己體會一下
>>> x
5
>>> y
5
>>> x = x + 5
>>> y += 5
>>> x
10
>>> y
10
>>> x * 2     #乘法   這里沒做賦值操作。 x = x * 2 與 x *= 2效果是一樣的
20
>>> y * 2
20
>>> x
10
>>> y
10
>>> x ** 2    #2次方 冪運算  這里沒做賦值操作。 x = x ** 2 與 x **= 2 效果是一樣的
100
>>> y ** 3    #3次方 冪運算
1000
>>> x
10
>>> y
10
>>> x / 2   #除法(結果總是小數,精確求值)
5.0
>>> y // 2  #整除(結果總是整數-除數是浮點型小數除外,向下取整)
5
>>> x % 2   #求余運算。
0
>>> x % 3
1

  並且,整形允許與浮點型(小數)做四則運算。所得結果也必然是浮點型(小數):

>>> x = 100
>>> y = 10.5
>>> x + y
110.5
>>> x - y
89.5
>>> x * y
1050.0
>>> x / y
9.523809523809524
>>> x // y  #整除(除數為小數,拿到一個浮點型數據。且向下取整)
9.0
>>> x % 3.01.0

小數(float)

  小數被稱為浮點型,在Python中沒有所謂的單精度浮點型或者雙進度浮點型。浮點型的四則運算和整形基本差不多,這里不多舉例,不過需要注意的是浮點型與整形進行四則運算運算結果必然是浮點型。

>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> PI = 3.1415
>>> type(PI)
<class 'float'>
>>>

字符串(str)

字符串定義與使用

  字符串類型,其實上面已經使用到了。字符串類型其實就是若干個字符組成的集合,它可以包含任何內容。定義字符串也很簡單,Python中可以使用英文狀態下的 ' 單引號。" 雙引號,'''三個單引號或者"""三個雙引號來表示一組字符串,其中三個單引號或者三個雙引號可跨行。並且字符串在內存內部存入方式為順序存儲,即存入方式是有序的,只要是有序的存入方式就支持index操作。

>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> str1 = "ABC"
>>> print(type(str1))  # 在交互式的環境下可以不用使用print() 依舊能返回結果,但是對於初學者仍然建議使用它
<class 'str'>
>>> s1 = '我愛Python,Python使我快樂。注意:單引號不支持跨行操作'
>>> s2 = "我愛C語言,C語言是萬物基礎。注意:雙引號不支持跨行操作"
>>> s3 = '''我愛Java,除了Java沒有什么能讓我快樂
...     ,注意:三個單引號支持跨行操作'''
>>> s4 = """在21實際,Go語言將成為未來的希望。因此:
...     我愛Go語言!注意:三個雙引號支持跨行操作"""
>>> print(s1)
我愛Python,Python使我快樂。注意:單引號不支持跨行操作
>>> print(s2)
我愛C語言,C語言是萬物基礎。注意:雙引號不支持跨行操作
>>> print(s3)
我愛Java,除了Java沒有什么能讓我快樂
        ,注意:三個單引號支持跨行操作
>>> print(s4)
在21實際,Go語言將成為未來的希望。因此:
        我愛Go語言!注意:三個雙引號支持跨行操作
>>> s5 = "*"
>>> s5
'*'
>>> s5 += "^"
>>> s5
'*^'
>>> s5 *= 3
>>> s5
'*^*^*^'
>>> 

  盡管三個雙引號或者三個單引號十分強大。但是我們仍不建議你濫用它。因為他們還有另外的一層意義:注釋。

  有的時候我們需要在字符串內使用引號。可以有常用的5種方式:

>>> s1 = '請注意這里:"雙引號",看見沒有?牛逼不'                         #單引號中括雙引號
>>> print(s1)
請注意這里:"雙引號",看見沒有?牛逼不
>>> s2 = "現在是雙引號中括單'引號',看見沒?是不是很吊"                   #雙引號中括單引號
>>> print(s2)
現在是雙引號中括單'引號',看見沒?是不是很吊
>>> s3 = """在三引號中,單雙均可。可以隨意使用雙引或者單引:如''還有"""""
>>> print(s3)
"在三引號中,單雙均可。可以隨意使用雙引或者單引:如''還有"
>>> s4 = '使用反斜杠來轉義,比如:\'<---這個引號不具有特殊意義了'
>>> print(s4)
使用反斜杠來轉義,比如:'<---這個引號不具有特殊意義了
>>> s5 = r'單引號:\',雙引號:",三引號:""",\'''。這是因為開頭使用了r,將該字符串中所有字符全部取消特殊意義'       # \ 讓字符串中單引號不作為結束符
>>> print(s5)
單引號:\',雙引號:",三引號:""",\'。這是因為開頭使用了r,將該字符串中所有字符全部取消特殊意義
>>>

  如果想使用 \ 字符。那么就在對其做一次轉義:第一次 \ 具有意義,使用 \ 將 \ 失去意義

>>> print("顯示反斜杠:\\")
顯示反斜杠:\

索引-index介紹

  我們看一下需求:

>>> s1 = "ABCDEFGHIJKLMN"  #取出單一字符 C
>>>

  我們可以通過索引(index)來實現這個需求。

img

  一一對應的來取就好了。

>>> s1 = "ABCDEFGHIJKLMN"
>>> s1[2] #交互式環境下可以不使用print()
'C'
>>> s1[-12]
'C'
>>>

  索引不光可以用來取值。還可以用來賦值達到修改變量的目的,但是字符串str類型並不支持使用索引來修改變量(因為strint以及float都是看做一個整體。如"abc"和數字100),至於原因稍候會講到。

不可變類型-內部存儲方式

  我們嘗試為字符串使用索引達到修改某一字符的目的:

>>> s1 = "ABC"
>>> s1[0] = "a"  #我想將大寫的A替換成小寫的a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>

  可以看到它報了個異常,這個異常大概意思是說 str 對象 不支持使用索引分配值。

  這里要引出一個概念,可變類型與不可變類型,比如上面學到的整形與浮點型(小數)等均是不可變類型。(關於整形和浮點型它們甚至不支持index取單一值)

>>> x = 3.14
>>> x[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object is not subscriptable
>>> y = 1
>>> y[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not subscriptable
>>>

  不管整形和浮點型是什么樣的,這里就論字符串型,為什么字符串型不能使用索引修改單一字符呢?這還要從它的內部存儲原理說起。我們可以使用 + 對比整形來測試一下:

>>> x = 3
>>> id(x)
140705586665184
>>> x = x+3
>>> id(x)
140705586665280  #可以看到引用值出現了改變。這點能理解,畢竟小整數池中取整數不會開辟新的內存空間
>>> s1 = "ABC"
>>> id(s1)
2283017853488
>>> s1 = s1 + "DEF"
>>> id(s1)
2283018297392    #字符串為什么引用值也出現了改變?這么做豈不是多此一舉嗎?重新開辟內存空間?
>>>

  靈魂三連問,一個答案來解疑

  如果修改一個字符串的子串,比如添加或者刪除。因為字符串是連續的一塊內存存放,被看做為一個整體,修改其中某一個元素那么必定會導致后面的內存發生變化,鏈式反應滾起雪球需要處理的數據量很龐大,於是Python干脆不支持使用index為字符串進行修改(與整形同理)。但是如果和整形以及浮點型一樣用 + 作為拼接字符的話底層便會重新給你開辟新的連續的堆內存空間供變量值(str類型)存放。故str內部存儲方式為連續順序存儲。

img

列表

列表定義與使用

  我們的字符串可以使用index來取出數據,列表當然也可以。列表的定義是使用 [] ,它可以存取許多不同數據類型的變量值(被稱為列表中的元素)並且可以通過index來操縱這些變量值(元素)。所以列表就是一種可變的數據類型(列表內部也是采用順序存儲。但是與字符串的內部存儲方式有很大的不同)。

>>>  #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> li = [1,"A",3.1415]
>>> type(li)
<class 'list'>  # 列表類型為 list
>>> li[0] #通過 index來取出存儲的變量值
1
>>> li[1]
'A'
>>> li[2]
3.1415
>>> li[-1] #index倒序取值
3.1415
>>> li[-2]
'A'
>>> li[-3]
1
>>>

  列表是我們今后整個Python學習中使用最多的一種數據類型,因為它實在是太方便太好用了。關於列表的基本定義與使用就先介紹到這里,我們來看點進階的。

多維取值與列表的增刪改查

  上面說過,列表可以存取不同數據類型的變量值,當然列表中也能嵌套列表。我們看一下:

>>> li = ["A","B",[1,2,["C"]]]
>>> li += ["增加元素"]
>>> li
['A', 'B', [1, 2, ['C']], '增加元素']  #
>>> del li[-1]  #
>>> li
['A', 'B', [1, 2, ['C']]]
>>> li[0] = 'a'  #
>>> li[2][2][0]  #index正序取值
'C'
>>> li[-1][-1][-1] #index倒序取值
'C'
>>>
KeyboardInterrupt
>>> li[-1][2][0] #index正序與倒序交叉使用取值
'C'
>>> li
['a', 'B', [1, 2, ['C']]]
>>> # 列表支持 *= 可以自行測試一下

  多維列表的取值操作與index對於列表的增刪改查應該盡快熟練,這在日后非常常用。

可變類型-內部存儲方式

  我們嘗試為列表使用index索引賦值,達到增加或者修改列表中元素的目的。

>>> li = ["a","b","c"]
>>> li
['a', 'b', 'c']
>>> id(li)    #查看變量名li 綁定的 內存地址id
2210045040640
>>> li[0]="A"  #可以看到即使修改了其中的元素 變量名li 所綁定的內存地址id 還是沒有改變
>>> id(li)
2210045040640
>>> li
['A', 'b', 'c']
>>>

  我們嘗試使用 + 來試試看:

>>> li = ["a","b","c"]
>>> id(li)
2210045360896
>>> li += ["d","e","f"]
['a', 'b', 'c', 'd', 'e', 'f']
>>> id(li)
2210045360896   #可以看到即使使用 + 列表的變量綁定內存地址依舊沒有改變,每次的內存地址都一樣難道說列表申請的內存地址最夠大?才能夠存放這么多值?
>>>

  第三個試驗:

>>> li = ["a","b","c"]
>>> id(li)
2210045360960        # 變量名li 綁定的內存地址
>>> id(li[0])
2210044891248
>>> li[0] = "A"
>>> id(li)
2210045360960    # 可以看到其中元素已經改變了,但是其li 綁定的內存地址依舊沒有任何改變
>>> id(li[0])
2210045361456      # li中0號元素內存地址發生了變化
>>>

  其實到了這里,大家應該能明白了。列表的變量名綁定的是一個順序引用內存,該內存中標注了列表中各個index索引值與真正的變量值綁定的關系。如圖:

img

字典(dict)

字典的定義與使用

  字典是一種可變的數據類型,外部有一個 {} 大括號來包裹一組一組的元素。每一組元素用 : 表示 鍵(key) : 值(value), 每一組元素之間用逗號隔開。

  我們使用列表時候經常是會將一種類型的數據存放在同一個列表,比如:包含中國城市名的列表就應該很單純的存儲中國城市的名字,而不應該有其他的數據。但是對於字典來說它可以存儲更多其他的數據。

  字典中沒有index,但是依舊提供了 [] 的操作方法,唯一不同的是 [] 中填寫的不應該是元素的索引,而應該是引用元素值對應的key名稱。我們可以使用key來操縱其對應的value。這也變相說明了字典內部並不是通過順序方式存儲,而是以另一種無序存儲的方式(hash存儲都是無序存儲,也被稱為散列存儲)

>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> d1 = {
...     "name": "雲崖",
...     "age": 18,
...     "hobby": ["籃球", "足球", "乒乓球"],
...     "relation":{
...         "father":"大雲崖",
...         "son":"小雲崖",
...     }}
>>> d1["name"]
'雲崖'
>>> d1["age"]
18
>>> d1["hobby"]
['籃球', '足球', '乒乓球']
>>> d1["relation"]["father"]
'大雲崖'
>>>

鍵值對與字典的增刪改查

  字典中的存儲方式是鍵值對,那么為什么不使用index呢?這個涉及到底層存儲方式的不同。但是鍵值對的操作方式也是非常的便捷,並且它相較於列表的index來說查找數據更快。

  值得一提的是字典雖然是可變的數據類型,但是其鍵值對中的鍵(key)則必須是由不可變的數據類型構成。(字符串,整形,浮點型),因為不可變類型是可hash的而字典底層就是通過hash來存儲,這個等下會具體講。但是只要記住這一點就好了。

>>> d1 = {1:"整形,不可變類型"}
>>> d1
{1: '整形,不可變類型'}
>>> d2 = {1.1:"浮點型,不可變類型"}
>>> d2
{1.1: '浮點型,不可變類型'}
>>> d3 = {"str1":"字符串類型。不可變類型"}
>>> d3
{'str1': '字符串類型。不可變類型'}
>>> d4 = {[1,2,3],"可變類型,字典定義拋出異常"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> d5 = {{"k1":"v1"}:"如果使用字典作為鍵是會拋出異常的,字典本身就是屬於可變類型"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>

  基本增刪改查:

>>> d1 = {1:"整形,不可變類型"}
>>> d1
{1: '整形,不可變類型'}
>>> d2 = {1.1:"浮點型,不可變類型"}
>>> d2
{1.1: '浮點型,不可變類型'}
>>> d3 = {"str1":"字符串類型。不可變類型"}
>>> d3
{'str1': '字符串類型。不可變類型'}
>>> d4 = {[1,2,3],"可變類型,字典定義拋出異常"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> d5 = {{"k1":"v1"}:"如果使用字典作為鍵是會拋出異常的,字典本身就是屬於可變類型"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>

hash-內部存儲方式

  上面已經說過了。字典不使用index操作元素是因為底層原理的不同,那么我們來看一下關於字典內部它的底層是怎么實現的。

  字典對象的核心是散列表.散列表是一個稀疏數組(總是有空白元素的數組),數組的每個單元叫做bucket,每個bucket有兩部分:一個是鍵對象的引用,一個是值對象的引用.

  由於,所有bucket結構和大小一致,我么可以通過偏移量來指定bucket.

img

  將一個鍵值對放進字典的底層過程

>>> a={}
>>> a["name"] = "yunya"

  當將"name"="yunya"這個鍵值對放到字典對象a中,首先第一步要計算鍵"name"的散列表.Python可以使用hash()來計算.

>>> a = {}
>>> a["name"] = "yunya"
>>> bin(hash("name"))  #bin()轉換二進制 。 hash()查看可hash對象的hash值 --- 也可以理解為不可變類型絕對是可hash的
'0b11001101110010100000000000000001101100011111011011011001011010'
>>>

  由於數組長度為8,我們可以拿計算出的散列值,最右邊3位數作為偏移量,即"010",十進制是數字2,我們查看偏移量2,對應的bucket是否為空,如果為空,則將鍵值放進去,如果不為空,依次取右邊3位作為偏移量'011',十進制是數字3,再查看偏移3的bucket是否為空.直到棧為空的bucket將鍵值放進去

  根據鍵查找"鍵值對"的底層過程

  一個鍵值對是如果存儲到數組中的,根據鍵值對象取到值對象,理解起來就簡單了

      a["name"]

  當我們調用a["name"],就是根據鍵"name"查找"鍵值對",從而找到值"yunya"

  第一步,我們仍要計算"name"對象的散列值:

      bin(hash("name"))

>>> bin(hash("name"))  #bin()轉換二進制 。 hash()查看可hash對象的hash值 --- 也可以理解為不可變類型絕對是可hash的
'0b11001101110010100000000000000001101100011111011011011001011010'

  和存儲的底層流程算法一致,也是一次取散列值不同的數字。

  假設數組長度為8,我們可以拿計算出的散列值的最右邊3位數字作為偏移量,即"010",十進制是數字2,

  我們查看偏移量2,對應的bucket是否為空.如果為空則拋出KeyError的異常.

  如果不為空,則將這個bucket對象的鍵對象計算對應的散列值,和我們的散列值進行比較,

  如果相等,則將對應的"值對象"返回。

  如果不相等,則一次取其它幾位數字,重新計算偏移量,直到全部將散列值的便宜了.

用法總結:

  1. 鍵必須可散列數字,字符串,元組,都是可散列的
  2. 字典在內存中開銷巨大,典型的空間換時間.
  3. 鍵值查詢速度很快
  4. 往字典烈面添加新鍵可能導致擴容,導致散列表中的鍵的次序發生變化.因此,不要在遍歷字典的同時對字典的修改.

  自定義對象需要支持下面三點(新手忽視);

    - 支持hash()函數

    - 支持通過__eq__()方法尖刺相等性

    - 做a==b為真,則hash(a)==hash(b)也為真.

另外附上一篇我自己學習時做的筆記(新手玩家可忽略):

  hash 原理: 通過散列值計算來指定一塊特定的空間。如果這個空間被其他的數據占用了就會通過取位來重新計算。當找到一塊新的空間的時候就會把這個數據存放進去,當然hash 會提前聲明一塊連續的空間(這些空間不會被全部利用) 來方便存入數據。當剩余空間所剩只有一個特定的數的時候 又會去聲明一塊新的空間。所以: 通過hash來存放數據,內存占用量會比較高但是查詢速度是異常的快。

  ================================================

  hash查找:計算鍵的散列值(又稱hash函數) -> 使用散列值的一部分來定位散列表(稀松的含有空白的數組,分為2部分keyvalue)中的一個表元(存儲value的地方),那么就會出現下面幾種情況 :

    -> 表元為空 (代表要查找的值沒有寫入)

     ->拋出KeyError (對應上圖的處理辦法)

    -> 表元是其他值

     -> 使用散列值的另一部分來定位散列表中的另一行

     -> 表元是要查找的值 -> 返回表元里的值

  dictkeyset 中的值必須都是要可hash的對象。不可變對象,就是可hash的! 想要實現自定義類支持hash只需要在類中實現 hash 方法即可。

  dict的存儲順序和元素的添加順序是有關系的,先添加的元素就有可能先拿到本該屬於后者的一個空間。所以在這里有了collections 模塊下的:OrderedDict ( 有序字典 )

  添加數據,有可能改變已有數據的位置。因為在本身空間到了一定的值得時候會去重新開辟新空間,在運行數據遷徙算法的時候可能和上一次的有所誤差。所以我們盡量不要去期望采用哈希算法的類有一定的規律性 , setdict 的原理都是一樣的

  ==================================================

  內置類型查找性能最高的首先是set,它沒有映射關系又是 哈希表 (散列表) 。所以是最快的,其次是dict,因為有一層映射關系在所以相對於set會顯得比較慢但是這個時間差基本可以忽略不計。速度最慢的是listtuple,他們通過下標(索引)index來逐個進行查找,所以這樣的數據結構不適合查詢大量的數據。並且setdict的查找速度不會隨着被查找數據量的增大而增大。不管你有多少數據,我找到我需要的數據就只要那么幾個時間而已,這點也是listtuple做不到的。

元組(tuple)

元組定義與使用

  元組首先是一個不可變數據類型,可以作為字典的key。元組的定義主要是使用逗號將不同的元素進行分割,但是為了能夠一眼看到這是元組還會在元組兩邊加上括號。元組可使用index進行元素的查找(說明本身是順序存儲),但是無法通過index進行元素的修改以及刪除。因此常被存儲一些一旦被定義好了就不允許修改的變量(可以理解為常量)

>>>  #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> t1 = "北京","上海","天津","重慶",   #元組的定義是使用逗號
>>> type(t1)
<class 'tuple'>
>>> t2 = ("北京","上海","天津","重慶",)  #為了美觀和直接所以在2側加入括號
>>> type(t2)
<class 'tuple'>
>>> t1[0]
'北京'
>>> del t1[0]  #元組不支持 index 刪除
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion
>>> t1[0] = "舊金山"  #元組不支持 index 修改
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t1 += ("東京")  #元組不支持 +=
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "str") to tuple
>>> id(t1)  #查看原本的 內存地址 id
1711529097232
>>> t1 *= 3 #看起來 *= 好像是改變了元組內容。但是實際上是生成一個新的元組,因為內存地址 id不同了。代表開辟了新的內存空間
>>> t1
('北京', '上海', '天津', '重慶', '北京', '上海', '天津', '重慶', '北京', '上海', '天津', '重慶')
>>> id(t1)
1711528765904
>>>

元組使用注意這個坑

>>> t1 = (1,2,[3,4])  #元組套列表。這是非常sb的行為,本身元組具有不可變的特性非要去套上一個可變類型,這不是sb是什么
>>> t1[-1]
[3, 4]
>>> t1[-1][0] = ""
>>> t1
(1, 2, ['', 4])
>>> t2 = (1,2,{3:4}) #元組套字典,sb中的sb,對此已經無力吐槽
>>> t2[-1]
{3: 4}
>>> t2[-1][3] = ""
>>> t2
(1, 2, {3: ''})
>>>

集合(set)

集合的定義

  集合這種數據類型非常特殊,它和字典一樣本身是可變類型但是其存儲的元素必須是不可變類型。這就說明了集合的內部存儲也是使用了hash存儲,導致了它的存取速度非常之快且內部元素無序排列的特點,並且集合還有一個特性便是去重。使用集合只需要兩邊加上 {} ,中間每個元素用 號分隔開即可。

  集合常用於操縱數據而不是存儲數據,如:去重,求2個集合的交叉並等操作。

>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>> set1 = {1,1,1,"a","a","A"} #這里使用Python2.x版本,更能看出集合無序的特點
>>>type(set1)
>>><class 'set'>
>>> set1
set(['a', 1, 'A']) #可以看到集合改變了存入時的順序
>>> set[0] #集合不支持 index 操作
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'type' object has no attribute '__getitem__'
>>> set2 = {1,2,(3,3,4,5)} #集合支持存入 數字,字符串,元組
>>> set2
set([(3, 3, 4, 5), 1, 2])
>>> set3 = {1,2,[1,2,3]}  #集合不支持存入 list數據類型
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> set4 = {1,2,{1:2}}  #集合不支持存入 dict數據類型
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> set5 = {1,1,2,2,{1,1,2,2}}  # 當然集合也不支持存入集合。因為首先集合本身就是一種可變的數據類型
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>>

布爾(bool)

布爾的定義與作用

  布爾類型是不可變類型,它只有2個值,一個是True代表真,一個是Flase代表假。對應數字狀態即是 01,布爾值常被用於分支結構中。目前還沒有學習分支結構所以先不着急它的使用,只是了解一下即可。

  對於任何空的 :列表,元組,字典,集合,字符串,None,0 等等 它們的布爾值狀態都會是 False

  對於任何含有數據的 : 列表,元組,字典,集合,字符串,not None,任意非空的整形或者浮點型數據它們的狀態都會是True

>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型---------------------------- bool()方法返回對象的 bool狀態
>>>type(False)
<class 'bool'>
>>> bool(None)  #None代表為空,什么都沒有的意思
False
>>> bool("")  #空字符串
False
>>> bool([])  #空列表
False
>>> bool(())  #空元組
False
>>> bool({})  #空字典,注意!!!很多人認為這樣的定義是空集合,其實這是空字典。
False
>>> bool(set())  #空集合,由於{}被解釋器認為是空字典。故使用set()來定義一個空集合。
False
>>> bool(0)  #空整形(浮點型相同)
False
>>> #-----------------------------使用type()方法可查看當前對象(變量值)的數據類型----------------------------
>>>type(False)
<class 'bool'>
>>> bool(not None)  #not None代表不為空。
True
>>> bool("str")  #字符串
True
>>> bool(["list",])  #列表
True
>>> bool(("tuple",))  #元組
True
>>> bool({"dict":"dict_1"})  #字典
True
>>> bool({1,2,3,4})  #集合
True
>>> bool(1)  #空整形(浮點型相同)
True

  除此之外,bool類型的True和False還可以與整形或者浮點型進行四則運算(當True或False參與運算時,會從小整數池中取1或者0進行運算):

>>> True + 1
2
>>> True - 1
0
>>> True * 20
20
>>> True ** 20  #True 代表數字 1。所以冪運算不管是多少都是 1
1
>>> True / 20
0.05
>>> True // 20
0
>>> # 關於False的四則運算這里不做舉例,它和True的用法都是一樣的。


免責聲明!

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



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