《最值得收藏的python3語法彙總》之標準數據類型(超級完整版)

目錄

關於這個系列

該系列其它文章

概述

Number(數字)

Int

Float

Complex

Bool

String(字符串)

轉義字符

截取

分割

連接

替換

查找

格式化輸出

​​​​​​​其它常用操作

Bytes

字節序

字符編碼

Bytes和string之間的轉換

List(列表)

Tuple(元組)

​​​​​​​Dictionary(字典)

瞭解hash結構:

字典和列表的區別

深複製和淺複製

Set(集合)

可變數據類型和不可變數據類型

動態類型和靜態類型

數據類型轉換

數據類型轉換支持情況彙總表

轉換實例

轉換爲int

轉換爲float

轉換爲bool

轉換爲complex

轉換爲string

轉換爲bytes

轉換爲list

轉換爲tuple

轉換爲set

轉換爲dict

 


關於這個系列

《最值得收藏的python3語法彙總》,是我爲了準備公衆號“跟哥一起學python”上面視頻教程而寫的課件。整個課件將近200頁,10w字,幾乎囊括了python3所有的語法知識點。

你可以關注這個公衆號“跟哥一起學python”,獲取對應的視頻和實例源碼。

這是我和幾位老程序員一起維護的個人公衆號,全是原創性的乾貨編程類技術文章,歡迎關注。

 

該系列其它文章

《最值得收藏的python3語法彙總》之數據類型轉換

《最值得收藏的python3語法彙總》之函數機制

《最值得收藏的python3語法彙總》之運算符

《最值得收藏的python3語法彙總》之控制語句


概述

 

什麼是數據類型呢?前面我們提過,所謂的編程,就是控制一系列的數據去完成我們預設的邏輯或者功能。所以,編程語言首先要定義一系列對“數據”的處理規則。這些處理規則包括:如何存儲數據、數據的長度、數據的賦值、數據的讀取、數據的顯示、數據的比較等等。

不同類型的數據,它們的這些處理規則是不一樣的。比如:整數和小數在內存中的存儲方式肯定是不一樣的;小數有精度的操作,而字符串肯定是沒有的。

因此,編程語言需要對我們用到的所有數據進行分類,抽象出一些基本的類型,這就是編程語言定義的數據類型。

不同的編程語言所定義的數據類型其實大同小異,所以你只要理解了python的數據類型,其它編程語言的數據類型也基本都能搞明白。

 

Python3 中定義了七個標準的數據類型:

  1. Number(數字)
  2. String(字符串)
  3. Bytes(字節)
  4. List(列表)
  5. Tuple(元組)
  6. Set(集合)
  7. Dictionary(字典)

下面我們結合一些實例,依次學習這幾個標準類型。

 

Number(數字)

數學中的數字包括整數、小數,python中也對應定義了整型(int)和浮點型(float),另外還定義了複數類型(complex)和布爾型(bool)。

  • Int

​​​​​​​它定義了一個整數類型。在C語言中,有很多不同的整型,如下表:

 

不同的類型,有不同的存儲大小和取值範圍,超出這個範圍就會溢出報錯。Python明顯簡化了這一類型,所有的整數類型,不論正負不論大小,全都歸一爲int類型。Python2還保留了long類型,python3把long類型也去掉了。

我們可以使用不同的方法表示一個int類型的數據,如下所示:

# int 類型

int_1 = 100  # 10進制正數
int_2 = -69  # 10進制負數
int_3 = 0x77  # 16進制正數
int_4 = -0x24  # 16進制負數
int_5 = 0o70  # 8進制正數
int_6 = -0o70  # 8進制負數
int_7 = 0b10  # 2進制正數
int_8 = -0b10  # 2進制負數

上面提到了進制,我們先花幾分鐘瞭解一下“進制”的概念,看下面這張圖:

Python中使用0b開頭表示二進制(bin)、0o開頭表示八進制(oct)、0x開頭表示十六進制(hex)。我們平時用得比較多的,是十進制(dec)、十六進制、二進制,八進制用得比較少。十六進制由於是滿16進位,所以大於10的位用字母表示,10-15依次是a-f,不區分大小寫。

進制是數值的不同表達方式。不同進制之間可以相互轉換,參考下面的實例:


# file: ./6/6_1.py

# 進製表達和轉換
temp_value = 32904

print(" 十進制:{}\n 二進制:{}\n 八進制:{}\n 十六進制:{}\n"
      .format(temp_value, bin(temp_value), oct(temp_value), hex(temp_value)))

輸出爲:

十進制:32904

 二進制:0b1000000010001000

 八進制:0o100210

 十六進制:0x8088

 

  • Float

​​​​​​​它定義了一個小數類型。在編程語言中,我們習慣把小數叫做浮點數,所以float是浮點型。同樣,python簡化了浮點型的定義,不論數值大小以及精度,都歸一爲float類型。Python中有多種方式來表示一個浮點數,如下:

# float 類型

float_1 = 17.18  # 正浮點數
float_2 = -17.18  # 負浮點數
float_3 = 7.99E+3  # 科學計數正數
float_4 = -7.99E+3  # 科學計數負數
float_5 = 7.99E-3  # 科學計數
float_6 = 7.99E3  # 科學計數
float_7 = 198.  # 等同於198.0

 

  • Complex

​​​​​​​複數是由一個實數和一個虛數組合構成,表示爲:x+yj,其中 x 是實數部分,y 是虛數部分。實數和虛數都是float類型數據。虛數部分必須有後綴j或者J。


# file: ./5/5_2.py

# complex 類型
cpx_1 = 123.2+34.6j

print(cpx_1)
print('real: type %s, value %f' % (type(cpx_1.real), cpx_1.real))
print('imag: type %s, value %f' % (type(cpx_1.imag), cpx_1.imag))

輸出爲:

(123.2+34.6j)

real: type <class 'float'>, value 123.200000

imag: type <class 'float'>, value 34.600000

 

  • Bool

​​​​​​​布爾類型只有兩個值,True和False,它們對應值爲1和0。Python2中沒有真正的布爾類型,使用1和0替代。Python3中明確定義了關鍵詞True和False,注意大小寫。


# file: ./5/5_3.py

# bool 類型
bool_1 = True
bool_2 = False

print(bool_1)
print(bool_2)
print(int(bool_1))  # 強轉爲int類型
print(int(bool_2))  # 強轉爲int類型

輸出爲:

True

False

1

0

 

String(字符串)

 

Python的字符串用單引號或者雙引號括起來表示。

字符串由若干個有序字符組成,但是python中的字符串是不能改變的,也就是說我們不能通過索引去改變某個字符的值。比如:str[1]= ‘x’這樣的操作是不被允許的。

 

轉義字符

 

所謂轉義字符,就是在其前面增加右斜槓\後,它並不代表其原本的字符含義,而是轉義爲另外的含義。比如‘\n’轉義後表示一個換行符。

 

Python中用到的轉義字符如下表所示:

轉義字符

描述

\(在行尾時)

續行符

\\

反斜槓符號

\'

單引號

\"

雙引號

\a

響鈴

\b

退格(Backspace)

\e

轉義

\000

\n

換行

\v

縱向製表符

\t

橫向製表符

\r

回車

\f

換頁

\oyy

八進制數,yy代表的字符,例如:\o12代表換行

\xyy

十六進制數,yy代表的字符,例如:\x0a代表換行

\other

其它的字符以普通格式輸出

# 轉義字符
str1 = 'hello,\nworld!'
# 尾部的\是續行符,表示下一行也算作本行的內容。續行符後面不能再有任何字符,包括空格和註釋
str2 ='hello,\"world!\"' \
       'I am Tiger.'

print(str1)
print(str2)

輸出爲:

hello,

world!

hello,"world!"I am Tiger.

對於續行符要注意,它後面不能再有任何字符,包括空格和註釋,否則會報錯。

 

 

截取

 

字符串截取就是從字符串中獲取部分我們想到的字符,這是用得非常多的一個操作。

Python提供了非常靈活簡單的字符串截取方式。

 

字符串截取語法如下:

變量[頭下標:尾下標]

 

這裏的下標,就是指的某個字符在字符串中對應的索引值。支持雙向索引。我們還是以“hello,world!”這個字符串爲例,其索引如下圖所示:

 

正向索引,從0開始,往後遞增;反向索引,從-1開始,往前遞減。我們只需要明確要截取的子串對應的開始索引和結束索引,即可將其截取出來。

 

這裏需要注意一點,python的截取語法,是“前閉後開”的。其截取的子串包含“頭下標”對應的字符,但是卻不包含“尾下標”對應的字符。這點需要特別注意,容易出錯。

 

比如,我們要截取“hello”這個子串。[0:5]、[0:-7]、[-12:5]、[-12:-7],這幾種方式可以達到相同的效果。

 

下面列出了一些使用的例子,大家可以參考:

# 字符串截取
str3 = 'hello, world!'
sub_str1 = str3[0:5]  # 截取hello
sub_str2 = str3[-6:-1]  # 截取world
print(sub_str1)
print(sub_str2)
print(str3[5])  # 輸出第6個字符
print(str3[:5])  # 輸出第6個字符之前的所有字符
print(str3[5:])  # 輸出第6個字符及以後的所有字符
print(str3[-5:])  # 輸出倒數第5個字符及以後的所有字符

 

分割

 

有時候我們需要對一個字符串依照某種規則進行分割,得到若干個子串。比如,我們以逗號爲分割標識,將“hello,world!”分割爲兩個子串“hello”和“world!”。

 

Python提供了split方法實現字符串分割功能,其語法爲:

變量.split("分割標示符號"[分割次數])

 

# 字符串分割 split
sub_str_list =str3.split(',')
print(sub_str_list)

輸出爲:

['hello', ' world!']

 

Split會返回一個列表結構,這個結構裏面存儲了分割之後的所有子串。如果找不到分割標識符號,則返回的列表中只有一個元素,就是原始字符串。

 

看下面的例子,我們指定分割次數後會怎麼樣:

# 字符串分割 split
sub_str_list =str3.split(',')
print(str3.split('o'))
print(str3.split('o', 1))

輸出爲:

['hell', ', w', 'rld!']

['hell', ', world!']

 

如果我們不指定分割次數,會分割爲3個子串。如果我們指定只分割1次,則被分割成了2個子串。

 

Split只能滿足一些簡單固定的分割規則,對於比較複雜的規則,我們可以採用正則表達式,它的功能就非常強大了。後面我們會專門拿一個章節來講解正則表達式,這裏不展開了。

 

連接

 

我們通常使用下面兩種方式來實現字符串的連接:

1、通過加號+實現

2、通過join方法實現

語法: str.join(sequence)

sequence -- 要連接的元素序列,可以是元組或者列表。

 

str1.join([s1,s2, s3])

它的連接結果是:

s1-str1-s2-str1-s3

 

如下實例所示:

# 字符串連接
str3=’ hello, world!’
str4 = ' I am Tiger.'
print(str3 + str4 + " Glad to see you ! ")
print(''.join([str3,str4, " Glad to see you ! "]))
print('**'.join(["aa","bb", "cc"]))  #aa**bb**cc

 

輸出爲:

hello, world! I am Tiger. Glad to see you !

hello, world! I am Tiger. Glad to see you !

aa**bb**cc

這兩種方法實現的效果是一樣的,但是他們的實現邏輯卻有很大的區別。基於效率的考慮,如果連接的字符串超過2個,建議儘量採用join方法。

加號連接字符串,每連接一個字符串時,系統會分配一次內存,如果連接N個字符串,那麼需要分配N-1次內存,性能消耗較大。

而join方法,則是在一開始會計算列表中所有字符串的總長度並一次分配好內存,所以它的性能會高一些。

 

替換

Python使用replace()函數來實現字符串的替換,其語法爲:

str.replace(old, new[, max])

 

  1. old -- 將被替換的子字符串。
  2. new -- 新字符串,用於替換old子字符串。
  3. max -- 可選字符串, 替換不超過 max 次

執行成功後,返回替換後的新字符串。

 

比如下面的實例,我們將 “hello,world!”替換爲 “hello,python!”:

# 字符串替換

str5 = 'python'
print(str3.replace('world', str5))
print(str3.replace('l', 't'))  # 將l替換爲t,不限制替換次數
print(str3.replace('l', 't', 2))  # 將l替換爲t,限制最多替換2次

輸出爲:

hello, python!

hetto, wortd!

hetto, world!

 

同樣,我們也可以使用正則表達式來實現更加強大的字符串替換功能,正則表達式比較複雜,我們可以先往後放一放,繼續下面的學習。

 

查找

Python提供了多種方式來實現字符串的查找,下面我們結合實例分別介紹。

 

1、find()方法

語法 : str.find(sub_str, beg=0, end=len(string))

  • sub_str– 需要查找的子串
  • beg -- 開始索引,默認爲0。
  • end -- 結束索引,默認爲字符串的長度。

如果查找成功,則返回子串開始的索引值,如果失敗,則返回-1。


# 字符串查找

str6 = 'hello, python'
sub_str_find = 'll'
sub_str_not_find = 'world'
print(str6.find(sub_str_find))  # ‘ll’匹配到str6的起始索引是2,所以返回2
print(str6.find(sub_str_find, 3))  # 指定從str6的索引爲3開始,所以查找不到,返回-1
print(str6.find(sub_str_not_find))  # world字符串不在str6裏面,返回-1

 

2、index()方法

 

語法 : str.index(sub_str, beg=0, end=len(string))

  • sub_str– 需要查找的子串
  • beg -- 開始索引,默認爲0。
  • end -- 結束索引,默認爲字符串的長度。

 

它和find()方法是一樣的,只不過如果查找不到,find()方法返回-1,而index()會拋出一個異常(Exception)。關於python異常的處理,我們在後面章節會介紹,這裏不需要深究。


print(str6.index(sub_str_find, 3))  # 指定從str6的索引爲3開始,所以查找不到,拋出一個

異常

拋出異常:

Traceback (most recent call last):

  File "D:/跟我一起學python/練習/5/5_4.py", line 54, in <module>

    print(str6.index(sub_str_find, 3))  # 指定從str6的索引爲3開始,所以查找不到,拋出一個異常

ValueError: substring not found

 

正式的代碼裏面,我們應該對這個異常進行處理,如果不處理程序會報錯並退出。

 

3、rfind()方法和rindex()方法

它們的使用方式和find()、index()一樣,唯一的區別是它們是反向查找的(從右向左),返回的是子串最後一次出現的位置。

 

可以看看下面的實例:

str7 = 'hello, python, hello'

print(str7.find('ell'))  # 正向查找,返回第一次查找到的索引
print(str7.rfind('ell'))  # 反向查找,返回第一次查找到的索引

 

它的輸出爲:

1

16

反向查找時,返回的是,從右到左匹配到的第一個子串的首字符索引。

需要注意的是,字符串查找返回的都是正向索引的值。這點不要和字符串截取中,反向索引的負數值搞混淆了。

我們同樣可以使用正則表達式實現更加強大的字符串查找功能。

 

​​​​​​​格式化輸出

Python最常用的輸出方式是print(),語法如下。

print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

參數說明:

file: 輸出的目標文件流,缺省爲系統的標準輸出.
sep: 插入到不同value之間的字符,缺省是空格符.
end: 最後一個value之後的字符,缺省是\n換行.
flush: 強制刷新標記.

 

我們主要修改第一個參數value,它的表達方式是: “格式化字符串”% 參數列表

如果參數列表是多個,則用小括號括起來,“格式化字符串”% (參數1, 參數2, …)

“格式化字符串”由我們期望輸出的字符串和一系列格式化符號組成,下面是格式化符號列表:

   

描述

      %c

 格式化字符及其ASCII碼

      %s

 格式化字符串

      %d

 格式化整數

      %u

 格式化無符號整型

      %o

 格式化無符號八進制數

      %x

 格式化無符號十六進制數

      %X

 格式化無符號十六進制數(大寫)

      %f

 格式化浮點數字,可指定小數點後的精度

      %e

 用科學計數法格式化浮點數

      %E

 作用同%e,用科學計數法格式化浮點數

      %g

 %f和%e的簡寫

      %G

 %f 和 %E 的簡寫

      %p

 用十六進制數格式化變量的地址

 

相匹配的,python提供了一系列輔助指令:

符號

功能

*

定義寬度或者小數點精度

-

用做左對齊

+

在正數前面顯示加號( + )

<sp>

在正數前面顯示空格

#

在八進制數前面顯示零('0'),在十六進制前面顯示'0x'或者'0X'(取決於用的是'x'還是'X')

0

顯示的數字前面填充'0'而不是默認的空格

%

'%%'輸出一個單一的'%'

(var)

映射變量(字典參數)

m.n.

m 是顯示的最小總寬度,n 是小數點後的位數(如果可用的話)

 

下面是一個實例:

# 格式化輸出

fmt_1ist = [10.2, 99, 'hello']
print('this is example for fmt output \n %.4f, %#0x, %10s' % (fmt_1ist[0], fmt_1ist[1], fmt_1ist[2]))

 

格式化輸出本身是比較簡單的,大家只要掌握了基本方法,需要的時候查上面的表即可。

 

另外,上面的方式如果參數過多的話,代碼的可讀性會非常差。Python提供了str.format方法,相對看起來要簡單一些。這種方式也提供了很多格式控制符,大家可以自行百度,我們不一一列舉了。

# str.format

print('this is example for fmt output \n {:.4f}, 0x{:x}, {:>10}'.format(fmt_1ist[0], fmt_1ist[1], fmt_1ist[2]))

Python3.6之後又引入了一種新的格式化輸出方法:f-string。它比上面方式要簡單許多,這裏不做介紹,大家可以去百度。

 

​​​​​​​其它常用操作

字符串還有很多操作,我們不可能一一講解,大家可以在需要使用的時候去查詢相關的手冊。

 

 

功能

方法

str.strip()

刪除字符串兩邊的指定字符

str.lstrip()

刪除字符串左邊的指定字符

str.rstrip()

刪除字符串右邊的指定字符

in

是否包含指定字符串

len(str)

字符串長度

str.lower()

轉換爲小寫

str.upper()

轉換爲大寫

str.swapcase()

大小寫互換

str.capitalize()

首字母大寫

str.center()

將字符串放入中心位置可指定長度以及位置兩邊字符

str.count()

統計字符串中出現某個子串的次數

str * num

使用*來複制字符串,num表示複製次數

str.startswith(prefix[,start[,end]])

是否以prefix開頭

str.endswith(suffix[,start[,end]]) 

以suffix結尾

str.isalnum()                       

是否全是字母和數字,並至少有一個字符

str.isalpha()                      

是否全是字母,並至少有一個字符

str.isdigit()                      

是否全是數字,並至少有一個字符

str.isspace()                      

是否全是空白字符,並至少有一個字符

str.islower()                      

是否全是小寫

str.isupper()                      

是否全是大寫

str.istitle()                      

是否是首字母大寫的

str.partition()

分割,前中後三部分

str.splitlines()

根據換行執行分割

str.zfill()

返回指定長度的字符串,原字符串右對齊,前面填充0

 

 

 

Bytes

 

Bytes是python3新增的一個數據類型,用於表示一個字節串,它是一個有序的序列。

通常有兩種方式來構造一個bytes類型的對象:

1、通過bytes()函數構造

bytes_1 = bytes('hello', 'utf-8')
bytes_2 = bytes([1, 200, 80, 50])

2、通過b後面跟字符串的方式

bytes_3 = b'world'
bytes_4 = b'\x77\x6f\x72\x6c\x64'

我們在print一個bytes類型數據時,python會以/x的格式依次打印每個字節的值,以兩位16進制來顯示。但是python對於一些字符會直接字符編碼轉換,所以造成打印出來的結果看起來很混亂,比如:

​​​​​​​bytes_2 = bytes([1, 200, 80, 50])

print('bytes_2:', bytes_2)

輸出結果爲:

bytes_2: b'\x01\xc8P2'

最後兩個數值80、50,被轉換爲了字符P、2,看起來很混亂。

 

這時,我們可以寫一個簡單的方法,讓它不做這種轉換:

# bytes 按照16進制輸出,強制不ascii轉碼
def trans(s):
    return "b'%s'" % ''.join('\\x%.2x' % x for x in s)

bytes_2 = bytes([1, 200, 80, 50])

print('bytes_2:', trans(bytes_2))

輸出結果爲:

bytes_2: b'\x01\xc8\x50\x32'

這樣我們看到,bytes裏面包含了一個一個的字節。

因爲我們還沒有學函數的概念,所以大家只要知道在輸出的時候調用這個方法即可。

 

bytes類型,存儲的是一系列的字節,它並不關注這些字節具體表示什麼含義(字符、網絡數據、圖片、音視頻等)。Bytes並不約束你如果使用這些字節數據,你可以按照你自己的功能邏輯做任意的轉換。這個轉換邏輯,不是bytes數據類型的功能範疇。

 

比如:對於字符,通常我們需要對其做一個編碼轉換,將字節類型轉換爲有意義的字符串。這個轉換規則,就是字符編碼,緊接着下一小節我們會介紹字符編碼。

 

我們可以看到,bytes類型也是一種序列,所以它的大多數操作方法和String一致。

# 操作方法
print(bytes_3[0: 3])
print(bytes_1 + bytes_3)
print(b'h' in bytes_1)
print(bytes_1.split(b'l'))
print(bytes_1.find(b'll'))
print(bytes_1.replace(b'l', b't'))

輸出結果爲:

b'wor'

b'helloworld'

True

[b'he', b'', b'o']

2

b'hetto'

是不是和string類型高度一致? bytes類型和string類型的對比如下:

  • string的基本單位是字符,bytes的基本單位是字節;
  • 他們都是屬於一種序列,所以對於序列的操作方法,對他們基本都適用;
  • String和bytes都是不可變類型,不能對其元素進行修改。

 

注意,雖然bytes通常會和string一起使用,但是bytes並不只是給string用,它本質上是一個字節串。Bytes適合那種面向二進制流的存儲數據,比如圖片、視頻等多媒體,或者網絡通信等二進制報文流。

 

字節序

字節序,顧名思義就是字節存儲的順序。大家可能覺得奇怪,字節不都是“從左到右”依次存儲的嗎?怎麼會有字節序的問題?大家看看下面的例子:

#  author: Tiger,    wx ID:tiger-python

# file: ./6/6_2.py

# bytes 按照16進制輸出,強制不ascii轉碼
def trans(s):
    return "b'%s'" % ''.join('\\x%.2x' % x for x in s)


# 字節序
byte_1 = 'python'.encode('utf-8')
print(trans(byte_1))

print('Big endian: ', hex(int.from_bytes(byte_1, byteorder='big', signed=False)))
print('Little endian: ', hex(int.from_bytes(byte_1, byteorder='little', signed=False)))

輸出結果爲:

b'\x70\x79\x74\x68\x6f\x6e'

Big endian:  0x707974686f6e

Little endian:  0x6e6f68747970

上面的實例中,我們將bytes類型b’python’強制轉換爲int類型,在轉換過程中分別指定其字節序爲big和little。從打印結果可以看出,這兩種類型對應的輸出結果完全相反。它們對應的就是大端字節序(Big endian,BE)小端字節序(Little endian,LE)

比如我要存儲一個字節串:b’\x12\x34\x56\x78’:

大端字節序:從低地址到高地址,依次存儲數據字節;

小端字節序:相反,從高地址到低地址,依次存儲數據字節。因爲我們查看內存通常是由低位地址向高位地址看,所以大端字節序是更加符合我們的習慣的,而小端則相反。

爲什麼計算機會產生兩種不同的字節序呢?

因爲字節序是由CPU架構決定,而在計算機技術發展初期,CPU架構的兩大陣營X86和PowerPC分別採用了完全相反的兩種字節序,X86採用了LE,PowerPC採用了BE。所以,纔會導致我們現在需要面對字節序的問題。

 

我們可以下面的方法獲取當前cpu的字節序類型:

# 獲取當前cpu的字節序類型

import sys
print('endian of cur env:', sys.byteorder)

輸出爲:

endian of cur env: little

我使用的環境是X86的CPU,對應的是小端字節序。

 

如果你的程序只會在本地運行,不會涉及到跨主機(跨不同類型CPU)的操作,那麼你不需要關注字節序。反之,你需要特別關注字節序,因爲它容易出錯。

如果計算機A採用了BE架構的CPU,計算機B採用了LE架構的CPU。我們有一段程序,在計算機A發送一個bytes : b’\x12\x34\x56\x78’給計算機B,那麼計算機B解析出來的數據將是bytes : b’\x78\x56\x34\x12’,這就完全錯了。

 

在這種跨主機的數據傳輸中的字節序,我們通常稱之爲網絡字節序,網絡字節序和CPU無關,它是網絡通信協議定義的一套規範。幾乎所有的網絡字節序都採用了大端字節序BE。計算機將數據發送給網絡協議之前,需要統一轉換爲網絡字節序,同樣,接收端的計算機從網絡接收到數據後,也會統一將其由網絡字節序轉換爲本機字節序。這樣,我們就解決了跨主機的字節序問題。Python的網絡編程裏面,我們還會涉及到字節序,到時候我們可以回頭來看看。

 

字符編碼

Python2.x的亂碼問題一直被程序員所詬病,雖然Python3.X大體上已經解決了這個問題,但是作爲入門python的基礎,你還是需要把字符編碼問題搞得非常清楚,否則你會被各種“亂碼”搞崩潰。下面是引用的知乎上面一個回答,挺有意思:

 

  • 什麼是字符編碼呢?

我們知道計算機是使用01這樣的二進制串來存儲數據的,這裏的數據不僅僅是數字,而是所有數據,包括圖片、視頻、音頻、文字等等。所以,我們需要有一個規則,來定義這些數據和二進制之間如何轉換。

字符編碼,就是一套標準(事實上有若干套標準),根據這些標準,我們將字符和二進制雙向轉換。

最早的字符編碼標準,叫做ASCII碼。

 

  • ASCII碼

計算機技術起源於美國,所以最早的字符編碼標準ASCII(American Standard Code for Information Interchange)也是老美根據他們的語言體系制定的。在英語的世界裏面,26個小寫字母、26個大寫字母、若干個標點符號、一些控制符號等就可以表示所有字符。

下面就是全部的ASCII編碼,一共就只有256個符號的編碼定義:

 

第一列是字符對應的十進制數值,比如001d;

第二列是字符對應的十六進制數值,比如0Ah;

第三列是對應的字符;

0~31還定義了第四列,用於表示一些特殊的控制符,比如008d對應的是退格(Backspace),我們敲鍵盤的Backspace退格鍵,就會產生008d這個數值在計算機中傳遞。

最早 ASCII 只定義了128個字符編碼,包括96個顯示字符和32個控制符號,一共128個字符,只佔用了一個字節8bit位中的低7位。然而隨着計算機慢慢普及,一些西歐字符在字符集中無法被表示,於是就有了下面那種擴展ASCII表,將字符集擴展到了256個。

 

  • GB2312\GBK

ASCII碼標準的指定可沒有考慮中文的問題,於是當計算機在中國以及亞洲國家出現以後,中文編碼成了一個問題。爲了解決這個問題,我國制定了自己的字符編碼標準GB2312(GB-國標)。GB2312收錄的漢字基本覆蓋了我們使用的絕大多數場景,但是對於繁體字和少數民族文字無法處理。於是後來又制定了GBK標準(K-擴展),GBK在GB2312基礎上制定,它除了收錄27484個漢字以外,還收錄了藏文、蒙文、維吾爾文等少數民族文字。

GBK採用兩個字節來表示中文,同時它也兼容ASCII標準,當使用ASCII字符時可以自動轉換爲一個字節來表示。

 

  • Unicode

不同國家都有自己的語言和文字,如果大家都自己制定自己的字符編碼規範,那它們之間如何互通呢?爲了解決這個問題,科學家們發明一種統一的字符編碼-unicode(UniversalMultiple-OctetCodedCharacterSet,簡稱UCS),它幾乎囊括了人類所有語系字符的編碼。Unicode同樣也是兼容ascii碼的。

Unicode採用2字節編碼,這一標準的2字節形式叫做UCS-2。但是我們知道2字節最多隻能表示65535個字符編碼,這對於一些特殊語系來說是完全不夠的,比如中文字符就有幾萬個。所以,unicode還有一種4字節的形式,叫做UCS-4,它有足夠的空間可以定義人類所有的文字符號。

 

  • UTF-8

Unicode是一個字符集,它僅定義了不同符號對應的編碼值。但是這個字符該如何存儲呢?對於ascii碼,我們可以統一採用一個字節來存儲不同的字符。Unicode字符也可以這樣操作,但這樣會帶來一個存儲效率的問題。

比如字符“a”,它對應的編碼是61,一個字節就可以表示。如果我們採用4字節表示,那麼有3個字節就浪費掉了。在英語系國家,這種浪費就顯得非常嚴重,接近3/4的存儲性能損失,這是不能被接受的。

基於這個原因,出現了各種unicode的實現方法,最著名的是UTF-8。

UTF-8以一個字節(8比特位)爲一個編碼單位,並且它是變長的。對於不同的unicode字符,它會採用不同的字節長度來存儲。這樣就可以大大提升存儲性能。

 

比如漢字“中”,它的unicode表示和utf-8編碼如下:

 

除了UTF-8,還有UTF-16,同理,它是以16比特-2字節爲一個編碼單位。

 

字符亂碼的原因,基本都是字符編碼不一致導致的。Python2默認採用ascii碼,中文字符默認是不能轉換的,所以我們在python2代碼中如果涉及中文字符,必須在代碼文件一開始就進行字符編碼的聲明:

# -*-coding:utf-8-*-

Python2的字符編碼問題備受詬病。在python3中,默認編碼改成了utf-8,所以我們不聲明編碼類型,也是可以正常支持中文字符的。

字符編碼需要端到端保持一致,不管是存儲、計算、傳輸等環節都要設置一致,比如我們代碼處理時採用的字符編碼必須和數據庫存儲的編碼方式一致。否則很容易出現亂碼。

 

Bytes和string之間的轉換

Bytes和string之間的轉換,其本質就是一個字符編碼的過程。Python3提供了encode和decode方式來實現兩者之間的靈活轉換,其過程如下圖:

Encode是字符編碼過程,將字符轉換爲指定的編碼,並以bytes類型存儲;

Decode則相反,將bytes值轉換爲對應的字符。

語法如下:

str.encode(encoding='UTF-8',errors='strict')

str.decode(encoding='UTF-8',errors='strict')

 

encoding -- 要使用的編碼方式,如"UTF-8"。

errors -- 設置不同錯誤的處理方案。默認爲 'strict',意爲編碼錯誤引起一個UnicodeError。 其他可能得值有 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace' 以及通過 codecs.register_error() 註冊的任何值。

 

下面是一個編解碼的例子:

#  author: Tiger,    wx ID:tiger-python

# file: ./6/6_3.py

# bytes 按照16進制輸出,強制不ascii轉碼
def trans(s):
    return "b'%s'" % ''.join('\\x%.2x' % x for x in s)


# 字符編碼
encstr_1 = 'tiger-python'
encbytes_1 = encstr_1.encode('utf-8')
print(trans(encbytes_1))
print(encbytes_1.decode('utf-16'))  # 編碼方式不一致,造成亂碼
print(encbytes_1.decode('utf-8'))

輸出爲:

b'\x74\x69\x67\x65\x72\x2d\x70\x79\x74\x68\x6f\x6e'

楴敧⵲祰桴湯

tiger-python

如果編解碼的編碼方式不一致,則出現亂碼。

 

List(列表)

列表是python中非常常用的一個數據結構,它的語法如下:

[item1, item2, item3, …]

由中括號將所有列表元素括起來,不同的元素之間通過逗號分隔。

 

列表中的元素item,支持幾乎所有類型的數據,並且同一個列表中的所有元素可以是不同的數據類型。所以列表使用起來會非常靈活。用過C語言數組結構的同學應該知道,數組結構只能存儲同一類型的元素,比如整型數組、字符串數組等等。另外,C語言的數組結構一旦初始化之後,是不能動態擴容的。C語言也可以實現列表功能,但它不是C語言的標準數據類型。相比較起來,Python的數據類型要強大和靈活得多。

 

列表本質上是一種序列,前面我們學習的string字符串本質上也是一種序列,還有下一節的tuple元組也是序列。我們來看看序列都有一些什麼樣的共性呢?

  1. 序列具備索引,正向索引和反向索引,前面字符串截取時我們學習過。
  2. 序列都支持切片(分割、截取)。
  3. 序列具備一些通用的操作(加、乘、檢查成員)

 

通過下來的例子我們演示列表的常用操作:


#  author: Tiger,    wx ID:tiger-python
# file: ./5/5_5.py

"""
演示列表的操作
"""

# list 列表
list_1 = ['hello', 100, ['跟我一起學', 4]]  # 支持不同類型的item,可以嵌套list
list_2 = ['python', '!']

print(list_1[0:2])  # 截取的方式和字符串一致
print(list_1[0:-2])
print(list_1[0: 1])
print(list_1[0])

print(len(list_1))  # 獲取列表的長度

# 列表連接
list_3 = list_1 + list_2
print(list_3)

# 使用乘法讓列表重複n次
list_4 = list_2 * 3
print(list_4)

# 判斷一個元素是否存在於列表中
print('python' in list_2)  # True

# 判斷一個元素在列表中出現的次數
print(list_4.count('python'))

# 獲取列表中最大最小值,求和
list_5 = [1, 2, 4, 10, 90]
print(max(list_5))
print(min(list_5))
print(sum(list_5))

# 列表的增刪改操作
list_5.append(100)  # 在列表尾增加元素100
print(list_5)

list_5.insert(1, 'insert_obj')  # 把元素插入到索引爲1的位置
print(list_5)

list_6 = ['hello', 'python']
list_5.extend(list_6)  # 在列表後面追加另外一個列表

list_5[0] = 200  # 將索引爲0的元素修改爲200
print(list_5)

del list_5[0]  # 刪除索引爲0的元素
print(list_5)

list_5.pop(2)  # 移除索引爲2的元素,如果不填寫索引值,則默認移除列表最後一個元素
print(list_5)

list_5.remove('insert_obj')  # 移除一個元素,注意這裏指定的是元素的值。如果列表中有多個相同的值,則只移除第一個匹配項
print(list_5)

list_5.clear()  # 清空整個列表
print(list_5)

# 列表的排序操作
list_7 = [100, 99, 27, 198, 3]

list_7.reverse()  # 列表反向排列
print(list_7)

list_7.sort()  # 列表升序排列
print(list_7)

list_7.sort(reverse=True)  # 列表降序排列
print(list_7)

由於同一個列表可以支持不同的元素,所以某些列表操作會有一些限制,大家在使用時需要注意。比如一些數值操作,如sum(list),它就無法支持list中包含字符串的情況,因爲字符串沒法求和。一個比較特殊的操作,最大值max和最小值min,它們支持字符串的比較,那麼它們是按照什麼規則來比較大小的呢?我們通過一小段代碼測試一下。


#  author: Tiger,    wx ID:tiger-python
# file: ./5/5_6.py

# max\min 如何比較字符串列表
list_1 = ['a', 'b', 'cat', '跟我一起學python']

print(ord('a'), ord('b'), ord('c'), ord('跟'))

print(max(list_1))
print(min(list_1))

可以看出,對於字符串列表,是按照元素的首字符對應的ASCII編碼值來比較大小的(參考字符編碼章節)。如果同一個列表中混雜了數字和字符串,則無法比較,會拋出異常。

 

列表是python中用得最多的標準數據類型,後面我們在講循環語句時還會介紹如果對列表進行迭代操作。大家應該對列表操作勤加練習,熟能生巧。

 

Tuple(元組)

元組,也是一種序列結構,它和列表非常類似,但是它不能被改變。也就是說,我們不能對元組中的元素進行修改。元組的語法如下:

(item1, item2, item3, …)

由中括號將所有列表元素括起來,不同的元素之間通過逗號分隔。

元組的語法和列表是不是很像,唯一的差別就是它用的小括號,而列表用的中括號。其實不然,元組還有一些特殊的地方,比如下面兩個元組的定義:


# file: ./5/5_7.py

# 元組的操作
tuple_1 = (1, 2, 100, 'python')
tuple_2 = 'hello', 'python', 100  # python中,未加括號的序列都默認是元組
tuple_3 = (98,)  # 只包含一個元素的元組,需要加上逗號,否則小括號會被認爲是運算符

元組的操作和列表幾乎是一樣的,我們就不一一列出了,大家可以參考列表去練習。

下面我們主要通過實例來看看元組“不可改變”的特性。

# 元組的元素不可改變

tuple_4 = (1, 5, 10)
# tuple_4[0] = 3  # 會拋出異常

上面代碼運行時會拋出異常:

TypeError: 'tuple' object does not support item assignment

明確提示,元組對象不支持元素的改變。

 

但是,元組元素可以是可變數據類型,如下操作是合法的:

# 元組的元素可以是可變數據類型

tuple_5 = (100, 200, [300, 400])
tuple_5[2][0] = 500
print(tuple_5)

 

下圖解釋了這個過程:

 

我們所謂的元組不可改變,指的是元組本身不可改變。但如果它的元素指向的是一個可變類型,那麼這個元素指向的對象是可以改變的。因爲整個過程中,元組本身並沒有發生改變。

 

再看下面這個例子:

tuple_4 = (1, 5, 10)

tuple_4 = (100, 99)
print(tuple_4)

這個例子可以正常運行,並且輸出我們預期的正確結果。這說明tuple_4可以被改變嗎?還記得我們在前面講變量的哪些實例嗎,我們用同樣的方法把tuple_4這個變量對應的內存地址打印出來,看看它究竟發生了什麼變化?

tuple_4 = (1, 5, 10)

print(id(tuple_4))
tuple_4 = (100, 99)
print(tuple_4)
print(id(tuple_4))

輸出結果爲:

2812724308608

(100, 99)

2812723061120

我們可以看到,tuple_4在重新賦值後,它對應的內存改變了。這個行爲和數字、字符串是不是很像呢?沒錯,這一類數據類型我們稱爲“不可變數據類型”。

不可變數據類型對應的內存中的數據是不能被修改的,就像tuple_4一樣,它重新被賦值後,實際上是被指向了一塊新的內存。原來的那塊內存中的數據依然沒有改變。

 

​​​​​​​Dictionary(字典)

 

字典,也是Python中使用得比較廣泛的一種數據類型。

字典本質上是一種“鍵值對”(key-value)的集合。“鍵值對”這種數據的描述方式,更加符合我們對客觀世界的認識。客觀世界的數據,通常都存在一個名字-key,以及對應的值-value,使用“鍵值對”可以非常直觀簡便地表達這些數據。一些數據庫技術也是基於鍵值對的數據存儲方式,比如Redis、memcached。“鍵值對”在大數據領域也有廣泛應用。

字典的結構定義和JSON基本一致,兩者可以很方便的相互轉換。

 

字典的語法如下:

{key1: value1, key2: value2, key3: value3, …}

字典使用大括號{}括起來,每個key-value之間使用逗號分割。Key和value之間使用冒號:分割。

同一個字典裏面,key值需要保證唯一,不能重複,並且只能是不可變數據類型(數字、字符串、元組)。

Value不要求唯一,並且可以是任意數據類型。

 

瞭解hash結構:

字典是一種hash結構,我們有必要簡單瞭解一下hash結構。下圖是一個hash結構的示意(這不是python的真正實現):

 

hash是一種數據結構,準確說是一種數據的組織方式。我們在編程中,爲了便於數據的操作(增刪改查),尤其是性能考慮,會將數據對象以某種特別的方式組織起來,這就是數據結構。除了hash以爲,還有數組array、鏈表sll\dll、平衡二叉樹avl、紅黑樹rbt等。這些數據結構,是編程的算法範疇,不是編程語言的語法範疇,不同的編程語言都可以實現這些數據結構。我們不會重點去講這些算法的東西,但是作爲程序員,你需要理解這些常用的算法。如果有必要我後面可以專門製作一個系列來講算法。

上圖是我工作中最常見的hash結構。它定義了一個hash表(核心是一個定長的數組),用於存hash_key,由於它是數組,所以可以通過索引來查找,性能高。數據對象的key,通過一個hash函數映射到hash_key,這個映射關係可能是n:1,所以存在一個hash_key對應多個數據對象的情況,這就是所謂的hash衝突。爲了解決hash衝突,我們在每個hash_key下面掛了一個鏈表結構,這個就是hash桶(bucket)。Hash衝突越嚴重,bucket就會越深,從而導致查找性能就越低,所以hash函數是關鍵。衡量一個hash函數的好壞,是它能否將所有數據對象均衡的散列到不同的hash_key上面的,並且hash函數本身不能耗費太多性能。

 

Hash結構,是各種操作(增刪改查)性能都比較高比較均衡的一種結構,使用較廣泛。

 

字典和列表的區別

字典不是一種序列,它沒有索引,並且是無序的,這一點和列表存在本質區別,大家要注意。

實質上,字典在python的底層實現中,是一種hash索引結構。所以字典結構在查詢、新增、刪除、修改上面表現出更加優異的時間性能。

 

而列表在python的底層實現中,是一種動態數組結構。該數組元素不會直接保存列表元素的數據,而是存儲了列表元素的地址。地址的大小是固定的,所以它可以是一個數組結構。由於它是動態數組(支持resize內存空間),所以也可以支持擴容。同時,數組是連續存儲的,所以列表存在索引,是有序的。

下圖示意了字典和列表的區別,不同的python版本可能存在差異,但是大體類似。

 

列表採用了指針數組,它是一段連續內存,所以它可以像數組一樣去索引的。而字典採用了hash表結構,key值是通過hash函數去命中到一個hash index,所以它只能通過key值去查找。

真正的實現中,不會像我畫的這麼簡單。比如字典的hash表還需要考慮hash散列、hash衝突,但是這些細節並不影響我們理解字典和列表之間的本質區別。對於初學者,不建議大家現在去搞懂這些細節,可以留待後面再去深入理解。

 

下面我們通過一系列實例來熟悉字典的操作:


# file: ./5/5_8.py

# 字典
dict_1 = {'name': 'xiaowang', 'age': 20, 'city': 'Chengdu'}
print('name: %s, age: %d, city: %s' % (dict_1['name'], dict_1['age'], dict_1['city']))

dict_1['city'] = 'Beijing'
print('name: %s, age: %d, city: %s' % (dict_1['name'], dict_1['age'], dict_1['city']))

dict_2 = {}.fromkeys(('name', 'age', 'city'))  # 批量根據key值初始化一個字典
print(dict_2)

print(len(dict_1))

# 增加鍵值對
dict_1['sex'] = 'male'
print(dict_1)

# 刪除鍵值對
del dict_1['sex']
print(dict_1)

# 批量更新
dict_3 = {'name': 'xiaoliu', 'age': 18, 'city': 'Chengdu', 'sex': 'female'}
dict_1.update(dict_3)
print(dict_1)

# 判斷key是否在字典中存在
print('name' in dict_3)

# 獲取所有的key
print(list(dict_1.keys()))

# 獲取所有的value
print(list(dict_1.values()))

# 獲取所有的(鍵, 值) 元組列表
print(list(dict_1.items()))

# 清空字典
dict_1.clear()
print(dict_1)

 

深複製和淺複製

python提供了copy()方法,用於字典的淺複製(shallow copy)。與淺複製相對應的,還有深複製(deep copy)的概念。我們通過實例來理解一下二者的區別。淺複製和深複製的概念在很多編程語言中都涉及,大家應該理解並舉一反三。

# file: ./5/5_10.py

# 深複製和淺複製
dict_1 = {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 90, 'shuxue': 100}}
dict_2 = dict_1.copy()

print('dict_1:', dict_1)
print('dict_2:', dict_2)

# 修改value
print('modify 90->98'.center(64, '-'))
dict_1['score']['yuwen'] = 98
print('dict_1:', dict_1)
print('dict_2:', dict_2)

# 深拷貝
print('deep copy'.center(64, '-'))

import copy
dict_3 = copy.deepcopy(dict_1)
print('dict_1:', dict_1)
print('dict_3:', dict_3)
print('modify 98->99'.center(64, '-'))
dict_1['score']['yuwen'] = 99
print('dict_1:', dict_1)
print('dict_3:', dict_3)

輸出結果爲:

dict_1: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 90, 'shuxue': 100}}

dict_2: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 90, 'shuxue': 100}}

-------------------------modify 90->98--------------------------

dict_1: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 98, 'shuxue': 100}}

dict_2: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 98, 'shuxue': 100}}

---------------------------deep copy----------------------------

dict_1: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 98, 'shuxue': 100}}

dict_3: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 98, 'shuxue': 100}}

-------------------------modify 98->99--------------------------

dict_1: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 99, 'shuxue': 100}}

dict_3: {'name': 'xiaowang', 'age': 20, 'score': {'yuwen': 98, 'shuxue': 100}}

 

所謂淺複製,其實是複製了內存的地址,並沒有拷貝內存裏面存的數據。

而深複製,則是重新分配了一塊內存,並把數據拷貝進去。

所以,我們通過上面的實例可以看到,在淺複製的情況下,我們改變dict_1的值,dict_2依然會跟着變化,說明他們指向了同一塊內存空間。而深複製的情況下,則互不影響,說明他們指向的是不同的內存空間。

通過下面的圖,更加容易理解:

 

大家可以思考一下,爲什麼key爲‘name’和‘age’的值不受淺複製的影響呢?

 

Set(集合)

在python中,集合是一個無序的不可重複的元素組合,它是不是和前面介紹的字典的key值很像呢?集合的語法如下:

{item1, item2, item3,…} 或者 set(iterable)

集合中的元素必須保證唯一,不可重複。

對於空集合,必須使用set()來創建,而不是使用{},因爲這對應的是一個空字典。

Set的參數,是一個可迭代對象,後續我們在迭代器章節會詳細介紹,目前我們學習到的序列類型結構,如字符串、元組、列表,都是可迭代的。參數中不能包含重複元素,否則自動覆蓋。比如:


# file: ./5/5_9.py

# 集合 Set
set_1 = set('hello')
print(set_1)

輸出結果爲:

{'l', 'e', 'o', 'h'}

 

我們可以看到,它的輸出是無序的,並且重複的元素‘l’算作一個元素,是不可重複的。

集合的這種特性很適合用來對字典的key賦值:

dict_1 = {}.fromkeys(set_1)  # 批量根據key值初始化一個字典

print(dict_1)

輸出結果爲:

{'h': None, 'l': None, 'e': None, 'o': None}

 

可以把集合理解爲沒有value只有key的字典。

 

集合中的元素必須是可hash的,也就是說元素是不可變的,這一點也和字典的key一樣。比如列表就不能作爲集合中的元素。

 

集合分爲可變集合和不可變集合,使用frozenset()創建的是不可變集合,其它都是可變集合。不可變集合不支持增加、刪除等操作。

 

下面通過實例來熟悉集合的一些操作:


# file: ./5/5_9.py

# 集合 Set
set_1 = set('hello')
print(set_1)
print(len(set_1))

dict_1 = {}.fromkeys(set_1)  # 批量根據key值初始化一個字典
print(dict_1)

set_2 = frozenset('hello')  # 不可變集合
print(set_2)

# 新增元素
set_1.add('t')
print(set_1)
set_1.update('k', 'p')  # 批量添加多個
print(set_1)

# 刪除元素
set_1.remove('t')
print(set_1)

# 判斷元素是否存在
print('t' in set_1)
print('t' not in set_1)

# 清空元素
set_1.clear()
print(set_1)

# 集合操作
set_3 = set('hello')
set_4 = set('world')

print(set_3 - set_4)  # 補集
print(set_3 | set_4)  # 並集
print(set_3 & set_4)  # 交集
print(set_3 ^ set_4)  # 對稱補集, 等同於 (set_3 - set_4) | (set_4 - set_3)

set_5 = {1, 2}
set_6 = {1, 2, 3}
print(set_5 < set_6)  # 子集判斷
print(set_5 > set_6)  # 超集判斷

集合提供了類似數學裏面集合概念的一些操作,比如並集、交集、子集、超集等,這在某些特殊場景下使用起來會非常方便。

 

 

可變數據類型和不可變數據類型

 

前面我們的學習中其實已經涉及到可變數據類型和不可變數據類型的概念了,這一節我們總結一下。

python的數據類型分爲mutable(可變) 和 immutable (不可變):

  1. mutable包括list、dictionary、set
  2. immutable包括 number、string、bytes、tuple、frozenset

 

這裏的可變與不可變,指的是該對象對應內存中的那塊數據(value)是否允許改變。

如果是不可變類型數據,系統會重新分配一個對象,並將該對象的引用(可以理解爲內存地址\指針)重新賦給變量。而可變類型則可以直接修改,不會生成新的對象。

 

在函數傳參時,兩者也存在差別。如果入參是不可變類型,那麼是傳值方式,如果入參是可變類型,那麼傳入的是對象的引用。這裏我們要使用到函數的概念,因爲還沒有講函數,所以大家先通過下面的一個實例記住這個知識點,後面學習函數的時候我們會再次提及。

#  author: Tiger,    wx ID:tiger-python

# file: ./5/5_16.py

# 可變數據類型和不可變數據類型
# 函數入參區別
def incr_num(x):
    x = x + 1

def pop_list(list_1=[]):
    list_1.pop()


test_x = 100
test_list = [100, 200, 300]

incr_num(test_x)  # 不可變類型,傳的是值
pop_list(test_list)  # 可變類型,傳的是引用(地址)

print(test_x)
print(test_list)

# 不可變類型,需要return一個新的對象
def incr_num_2(x):
    y = x + 1
    return y


test_y = incr_num_2(test_x)
print(test_y)

通過上面實例可以看出,對於函數入參是可變類型的情況,它傳遞的是變量的值-值傳遞,函數裏面處理的變量已經是指向了一個新的對象空間,所以在函數裏面對它進行改變不會影響函數外面的變量。值傳遞的方式,需要return來返回結果。

而對於函數入參是不可變類型的情況,它傳遞的是變量指向對象空間的地址。函數裏面對這個地址進行操作,同樣會影響外部的變量。這種方式,不需要return結果。

 

動態類型和靜態類型

編程語言從代碼到能夠運行通常需要經過編譯和運行兩個階段,Python雖然是解釋性語言,也不例外。源碼.py通過編譯,生成字節碼文件.pyc。.pyc是一系列指令,這些指令通過python虛擬機PVM來執行。

 

我們根據檢查變量數據類型的時機,將編程語言分爲動態類型語言靜態類型語言

靜態類型:在編譯階段檢查變量的數據類型,比如C、Java等。對於這種類型的語言,我們需要在代碼中定義變量的數據類型,不管是顯式的聲明還是隱式的定義。靜態類型語言的變量,一旦定義了數據類型,在運行時是不能動態改變的。比如C語言,它的典型用法如下:

void main()

{

    int score1 = 0;  // 變量score1在使用之前需要聲明爲int類型,否則編譯報錯

    int score2 = 50;

   

    score1 = score2 + 10;

                // ......

}

動態類型:在運行階段才檢查變量的數據類型。Python就是一種動態類型語言,它的變量不需要聲明數據類型,只有在運行時才動態確定其類型。通過上一節的幾個小實驗我們其實可以看出來,python的變量本質上是一個內存地址,本身沒有類型,它的數據類型由其指向的對象來決定。比如下面的例子,在python中是可以正常運行的:



# 此時,tmp是一個整數類型
tmp = 100
print(type(tmp))

# 此時,tmp是一個字符串類型,類型可以動態改變
tmp = "hello, world!"
print(type(tmp))

它的輸出是:

<class 'int'>

<class 'str'>

 

這種動態類型的特性,讓python使用起來會更加簡單,代碼會更加簡潔。但是它也會帶來一些負面影響,比如無法在編譯階段發現問題,同時動態類型會帶來運行時的一部分性能損耗。兩者各有利弊。我們在使用python編程時,也建議隨時搞清楚變量對應的數據類型,這樣可以規避一些隱藏的問題。

 

數據類型轉換

數據類型轉換,指的是通過某種方法,將一個數據由原來的類型轉換爲另外一個類型。比如,我們將字符串“123”轉換爲數字123,這就是一種數據類型的轉換。

Python支持各種標準數據類型之間的轉換,但並不是任意數據都可以轉換的,所有的轉換要符合“常理”,邏輯上應該是成立的。比如,你不應該試圖將一個complex類型轉換爲int,因爲python也不知該怎麼轉換。

 

數據類型轉換支持情況彙總表

下面我整理了python3數據類型之間轉換的支持情況(這應該是最全的了):

 

Int

Float

Bool

Complex

String

Bytes

List

Tuple

Set

Dict

Int

-

Y

Y

N

Y

Y

N

N

N

N

Float

Y

-

Y

N

Y

Y

N

N

N

N

Bool

Y

Y

-

Y

Y

Y

Y

Y

Y

Y

Complex

Y

Y

Y

-

Y

N

N

N

N

N

String

Y

Y

Y

Y

-

Y

Y

Y

Y

Y

Bytes(只考慮直接轉換)

Y

N

Y

N

Y

-

Y

Y

Y

N

List

N

N

N

N

Y

Y

-

Y

Y

Y

Tuple

N

N

N

N

Y

Y

Y

-

Y

Y

Set

N

N

N

N

Y

Y

Y

Y

-

Y

Dict

N

N

N

N

Y

N

Y

Y

Y

-

下面列舉了各種類型之間的轉換及實例:

轉換實例

  • 轉換爲int

print(int(1.2))  # float -> int
print(int('123'))  # string -> int
print(int(b'456'))  # bytes -> int
print('0x%x' % (int.from_bytes(b'456', byteorder='little', signed=True)))
print(int(True))  # bool -> int

 

  • 轉換爲float

print(float('1.2'))  # string->float
print(float(b'3.4'))  # bytes -> float
print(float(123))  # int->float
print(float(False))  # bool->float

 

  • 轉換爲bool

所有類型都可以轉換爲bool型

print(bool(1))  # int->bool
print(bool(0.0))  # float->bool
print(bool(0 + 0j))  # complex->bool
print(bool(''))  # string->bool, 空字符串爲False,其它都是True
print(bool(b'hello'))  # bytes->bool, 空爲False,其它都是True
print(bool.from_bytes(b'\x00', byteorder='little'))  # bytes->bool
print(bool([]))  # list->bool, 空爲False,其它都是True
print(bool(()))  # tuple->bool, 空爲False,其它都是True
print(bool({}))  # dict->bool, 空爲False,其它都是True
print(bool(set()))  # set->bool, 空爲False,其它都是True


 

  • 轉換爲complex

print(complex(100))  # int->complex
print(complex(1.2))  # float->complex
print(complex(True))  # bool->complex
print(complex('1.2+2.3j'))  # string->complex

 

  • 轉換爲string

所有基本類型都可以轉換爲string

print(b'hello'.decode('utf-8'))  # bytes->string
print(str(1))  # int->string
print(str(1.2))  # float->string
print(str(True))  # bool->string
print(str(1.2 + 2.3j))  # complex->string其它都是True
print(str(['hello', 100]))  # list->string
print(str(('hello', 100)))  # tuple->string
print(str({'name': 'xiaowang', 'age': 20}))  # dict->string
print(str({'name', 'age'}))  # set->string

 

  • 轉換爲bytes

因爲所有類型都可以轉換爲string,而string可以轉換爲bytes,所以所有類型都可以間接轉換爲bytes。
下面我們只討論直接轉換爲bytes的類型

print('bytes'.center(30, '*'))
print(b'\x64')  # int轉bytes
print(int.to_bytes(100, byteorder='big', signed=True, length=2))  # int轉bytes
print(bool.to_bytes(True, byteorder='big', signed=True, length=2))  # bool轉bytes
print('hello'.encode(encoding='utf-8'))  # string轉bytes
print(bytes([1, 200, 80, 50]))  # list轉bytes
print(bytes((1, 200, 80, 50)))  # tuple轉bytes
print(bytes({1, 200, 80, 50}))  # set轉bytes

 

  • 轉換爲list

print(list("hello"))  # string->list
print(list(b'hello'))  # bytes->list
print(list((100, 200, 300)))  # tuple->list
print(list({'name', 'age'}))  # set->list
print(list({'name': 'xiaowang', 'age': 20}))  # dict->list, 只取key值

 

  • 轉換爲tuple

print(tuple("hello"))  # string->tuple
print(tuple(b"hello"))  # bytes->tuple
print(tuple([100, 200, 300]))  # list->tuple
print(tuple({'name', 'age'}))  # set->tuple
print(tuple({'name': 'xiaowang', 'age': 20}))  # dict->tuple, 只取key值

 

  • 轉換爲set

print(set("hello"))  # string->set
print(set(b"hello"))  # bytes->set
print(set([100, 200, 300]))  # list->set
# print(set([100, 200, [300, 400]]))  # list->set, list中包含可變數據類型,報異常
print(set(('name', 'age')))  # tuple->set
# print(set(('name', 'age', [])))  # tuple->set,包含可變數據類型,報異常
print(set({'name': 'xiaowang', 'age': 20}))  # dict->set, 只取key值

 

  • 轉換爲dict

轉換爲dict的方法略微複雜一些
 

1、string->dict

方式一、使用json轉換,字符串格式需要嚴格按照json格式來

user_str = '{"name": "xiaowang", "city": "Chengdu", "age": 28}'
import json
print(json.loads(user_str))  

方式二、使用eval函數轉換,eval有安全隱患,不建議使用

print(eval(user_str))  

方式三、 使用ast.literal_eval

import ast
print(ast.literal_eval(user_str))  

 

2、list->dict

 

方式一、需要用到zip

user_keys = ['name', 'city', 'age']
user_values = ['xiaowang', 'Chengdu', 28]
print(dict(zip(user_keys, user_values)))  

 

方式二、二維列表

user_info = [
    ["name", "xiaowang"],
    ["city", "Chengdu"],
    ["age", 28]
    ]
print(dict(user_info))  


set->dict tuple->dict的方式和list->dict一樣


 

關於這個系列

《最值得收藏的python3語法彙總》,是我爲了準備公衆號“跟哥一起學python”上面視頻教程而寫的課件。整個課件將近200頁,10w字,幾乎囊括了python3所有的語法知識點。

你可以關注這個公衆號“跟哥一起學python”,獲取對應的視頻和實例源碼。

這是我和幾位老程序員一起維護的個人公衆號,全是原創性的乾貨編程類技術文章,歡迎關注。

 

該系列其它文章

《最值得收藏的python3語法彙總》之數據類型轉換

《最值得收藏的python3語法彙總》之函數機制

《最值得收藏的python3語法彙總》之運算符

《最值得收藏的python3語法彙總》之控制語句

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章