可信網站值得做嗎seo推廣專員工作內容
一、序列
序列(sequence)是一組有順序的值的集合,是計算機科學中的一個強大且基本的抽象概念。序列并不是特定內置類型或抽象數據表示的實例,而是一個包含不同類型數據間共享行為的集合。也就是說,序列有很多種類,但它們都具有共同的行為。特別是:
-
長度(Length):序列的長度是有限的,空序列的長度為 0。
-
元素選擇(Element selection):序列中的每個元素都對應一個小于序列長度的非負整數作為其索引,第一個元素的索引從 0 開始。
Python 包含幾種內置的序列數據類型,其中最重要的是列表(list)。
1.1、列表
列表(list)是一個可以有任意長度的序列。列表有大量的內置行為,以及用于表達這些行為的特定語法。我們已經見過列表字面量(list literal),它的計算結果是一個?list
?實例,以及一個計算結果為列表中元素值的元素選擇表達式。list
?內置的?len
?函數返回序列的長度。如下,digits
?是一個包含四個元素的列表,索引為 3 的元素是 8。
>>> digits = [1, 8, 2, 8]
>>> len(digits)
4
>>> digits[3]
8
此外,多個列表間可以相加,并且列表可以乘以整數。對于序列來說,加法和乘法并不是作用在內部元素上的,而是對序列自身進行組合和復制。也就是說,operator
?模塊中的?add
?函數(和?+
?運算符)會生成一個為傳入列表串聯的新列表。operator
?中的?mul
?函數(和?*
?運算符)可接收原列表和整數 k 來返回一個內容為原列表內容 k 次重復的新列表。
>>> [2, 7] + digits * 2
[2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
?
任何值都可以包含在一個列表中,包括另一個列表。在嵌套列表中可以應用多次元素選擇,以選擇深度嵌套的元素。
>>> pairs = [[10, 20], [30, 40]]
>>> pairs[1]
[30, 40]
>>> pairs[1][0]
30
1.2、序列遍歷
在許多情況下,我們希望依次遍歷序列的元素并根據元素值執(zhí)行一些計算。這種情況十分常見,所以 Python 提供了一個額外的控制語句來處理序列的數據:for
?循環(huán)語句。
考慮統(tǒng)計一個值在序列中出現了多少次的問題。我們可以使用?while
?循環(huán)實現一個函數。
>>> def count(s, value):"""統(tǒng)計在序列 s 中出現了多少次值為 value 的元素"""total, index = 0, 0while index < len(s):if s[index] == value:total = total + 1index = index + 1return total
>>> count(digits, 8)
2
Python 的?for
?循環(huán)可以通過直接遍歷元素值來簡化函數,相比?while
?循環(huán)無需引入變量名?index
。
>>> def count(s, value):"""統(tǒng)計在序列 s 中出現了多少次值為 value 的元素"""total = 0for elem in s:if elem == value:total = total + 1return total
>>> count(digits, 8)
2
一個?for
?循環(huán)語句由如下格式的單個子句組成:
for <name> in <expression>:<suite>
for
?循環(huán)語句按以下過程執(zhí)行:
- 執(zhí)行頭部(header)中的?
<expression>
,它必須產生一個可迭代(iterable)的值 - 對該可迭代值中的每個元素,按順序:
- 將當前幀的?
<name>
?綁定到該元素值 - 執(zhí)行?
<suite>
- 將當前幀的?
此執(zhí)行過程中使用了可迭代值。列表是序列的一種,而序列是可迭代值,它們中的元素按其順序進行迭代。Python 還包括其它可迭代類型,但我們現在將重點介紹序列。
這個計算過程中的一個重要結果是:執(zhí)行?for
?語句后,<name>
?將綁定到序列的最后一個元素。所以?for
?循環(huán)引入了另一種可以通過語句更新環(huán)境的方法。
序列解包(Sequence unpacking):程序中的一個常見情況是序列的元素也是序列,但所有內部序列的長度是固定相同的。for
?循環(huán)可以在頭部的?<name>
?中包含多個名稱,來將每個元素序列“解包”到各自的元素中。
例如,我們可能有一個包含以列表為元素的?pairs
,其中所有內部列表都只包含 2 個元素。
>>> pairs = [[1, 2], [2, 2], [2, 3], [4, 4]]
此時我們希望找到有多少第一元素和第二元素相同的內部元素對,下面的?for
?循環(huán)在頭部中包括兩個名稱,將?x
?和?y
?分別綁定到每對中的第一個元素和第二個元素。
>>> same_count = 0
>>> for x, y in pairs:if x == y:same_count = same_count + 1
>>> same_count
2
這種將多個名稱綁定到固定長度序列中的多個值的模式稱為序列解包(sequence unpacking),這與賦值語句中將多個名稱綁定到多個值的模式類似。
范圍(Ranges):range
?是 Python 中的另一種內置序列類型,用于表示整數范圍。范圍是用?range
?創(chuàng)建的,它有兩個整數參數:起始值和結束值加一。(其實可以有三個參數,第三個參數為步長,感興趣可以自行搜索)
>>> range(1, 10) # 包括 1,但不包括 10
range(1, 10)
將?range
?的返回結果傳入?list
?構造函數,可以構造出一個包含該?range
?對象中所有值的列表,從而簡單的查看范圍中包含的內容。
>>> list(range(5, 8))
[5, 6, 7]
如果只給出一個參數,參數將作為雙參數中的“結束值加一”,獲得從 0 到結束值的范圍。(其實單參數就相當于默認了起始值從 0 開始)
>>> list(range(4))
[0, 1, 2, 3]
范圍通常出現在?for
?循環(huán)頭部中的表達式,以指定?<suite>
?應執(zhí)行的次數。一個慣用的使用方式是:如果?<name>
?沒有在?<suite>
?中被使用到,則用下劃線字符 "_" 作為?<name>
。
>>> for _ in range(3):print('Go Bears!')Go Bears!
Go Bears!
Go Bears!
對解釋器而言,這個下劃線只是環(huán)境中的另一個名稱,但對程序員具有約定俗成的含義,表示該名稱不會出現在任何未來的表達式中。
1.3、序列處理
序列是復合數據的一種常見形式,常見到整個程序都可能圍繞著這個單一的抽象來組織。具有序列作為輸入輸出的模塊化組件可以混用和匹配以實現數據處理。將序列處理流程中的所有操作鏈接在一起可以定義復雜組件,其中每個操作都是簡單和集中的。
列表推導式(List Comprehensions):許多序列操作可以通過對序列中的每個元素使用一個固定表達式進行計算,并將結果值保存在結果序列中。在 Python 中,列表推導式是執(zhí)行此類計算的表達式。
>>> odds = [1, 3, 5, 7, 9]
>>> [x+1 for x in odds]
[2, 4, 6, 8, 10]
上面的?for
?關鍵字并不是?for
?循環(huán)的一部分,而是列表推導式的一部分,因為它被包含在方括號里。子表達式?x+1
?通過綁定到?odds
?中每個元素的變量?x
?進行求值,并將結果值收集到列表中。
另一個常見的序列操作是選取原序列中滿足某些條件的值。列表推導式也可以表達這種模式,例如選擇?odds
?中所有可以整除 25 的元素。
>>> [x for x in odds if 25 % x == 0]
[1, 5]
?
列表推導式的一般形式是:
[<map expression> for <name> in <sequence expression> if <filter expression>]
為了計算列表推導式,Python 首先執(zhí)行?<sequence expression>
,它必須返回一個可迭代值。然后將每個元素值按順序綁定到?<name>
,再執(zhí)行?<filter expression>
,如果結果為真值,則計算?<map expression>
,<map expression>
?的結果將被收集到結果列表中。
聚合(Aggregation):序列處理中的第三種常見模式是將序列中的所有值聚合為一個值。內置函數?sum
、min
?和?max
?都是聚合函數的示例。
通過組合對每個元素進行計算、選擇元素子集和聚合元素的模式,我們就可以使用序列處理的方法解決問題。
完美數是等于其約數之和的正整數。n
?的約數指的是小于?n
?且可以整除?n
?的正整數。可以使用列表推導式來列出?n
?的所有約數。
>>> def divisors(n):return [1] + [x for x in range(2, n) if n % x == 0]>>> divisors(4)
[1, 2]
>>> divisors(12)
[1, 2, 3, 4, 6]
通過?divisors
,我們可以使用另一個列表推導式來計算 1 到 1000 的所有完美數。(1 通常也被認為是一個完美數,盡管它不符合我們對約數的定義。)
>>> [n for n in range(1, 1000) if sum(divisors(n)) == n]
[1, 6, 28, 496]
我們可以重用定義的?divisors
?來解決另一個問題:在給定面積的情況下計算具有整數邊長的矩形的最小周長。矩形的面積等于它的高乘以它的寬,因此給定面積和高度,我們可以計算出寬度。
使用?assert
?可以規(guī)定寬度和高度都能整除面積,以確保邊長是整數。
>>> def width(area, height):assert area % height == 0return area // height
矩形的周長是其邊長之和,由此我們可以定義?perimeter
。
>>> def perimeter(width, height):return 2 * width + 2 * height
對于邊長為整數的矩形來說,高度必是面積的約數,所以我們可以考慮所有可能的高度來計算最小周長。
>>> def minimum_perimeter(area):heights = divisors(area)perimeters = [perimeter(width(area, h), h) for h in heights]return min(perimeters)>>> area = 80
>>> width(area, 5)
16
>>> perimeter(16, 5)
42
>>> perimeter(10, 8)
36
>>> minimum_perimeter(area)
36
>>> [minimum_perimeter(n) for n in range(1, 10)]
[4, 6, 8, 8, 12, 10, 16, 12, 12]
高階函數(Higher-Order Functions):序列處理中常見的模式可以使用高階函數來表示。首先可以將對序列中每個元素進行表達式求值表示為將某個函數應用于序列中每個元素。
>>> def apply_to_all(map_fn, s):return [map_fn(x) for x in s]
僅選擇滿足表達式條件的元素也可以通過對每個元素應用函數來表示。
>>> def keep_if(filter_fn, s):return [x for x in s if filter_fn(x)]
最后,許多形式的聚合都可以被表示為:將雙參數函數重復應用到?reduced
?值,并依次對每個元素應用。
>>> def reduce(reduce_fn, s, initial):reduced = initialfor x in s:reduced = reduce_fn(reduced, x)return reduced
例如,reduce
?可用于將序列內的所有元素相乘。使用?mul
?作為?reduce_fn
,1 作為初始值,reduce
?可用于將序列內的數字相乘。
>>> reduce(mul, [2, 4, 8], 1)
64
同樣也可以用這些高階函數來尋找完美數。
>>> def divisors_of(n):divides_n = lambda x: n % x == 0return [1] + keep_if(divides_n, range(2, n))>>> divisors_of(12)
[1, 2, 3, 4, 6]
>>> from operator import add
>>> def sum_of_divisors(n):return reduce(add, divisors_of(n), 0)>>> def perfect(n):return sum_of_divisors(n) == n>>> keep_if(perfect, range(1, 1000))
[1, 6, 28, 496]
約定俗成的名字(Conventional Names):在計算機科學中,apply_to_all
?更常用的名稱是?map
,而?keep_if
?更常用的名稱是?filter
。Python 中內置的?map
?和?filter
?是以上函數的不以列表為返回值的泛化形式,這些函數在第 4 章中介紹。上面的定義等效于將內置?map
?和?filter
?函數的結果傳入?list
?構造函數。
>>> apply_to_all = lambda map_fn, s: list(map(map_fn, s))
>>> keep_if = lambda filter_fn, s: list(filter(filter_fn, s))
reduce
?函數內置于 Python 標準庫的?functools
?模塊中。在此版本中,initial
?參數是可選的。
>>> from functools import reduce
>>> from operator import mul
>>> def product(s):return reduce(mul, s)>>> product([1, 2, 3, 4, 5])
120
在 Python 程序中,更常見的是直接使用列表推導式而不是高階函數,但這兩種序列處理方法都被廣泛使用。
1.4、序列抽象
成員資格(Membership):可用于測試某個值在序列中的成員資格。Python 有兩個運算符?in
?和?not in
,它們的計算結果為 True 或 False,取決于元素是否出現在序列中。
>>> digits
[1, 8, 2, 8]
>>> 2 in digits
True
>>> 1828 not in digits
True
切片(Slicing):一個切片是原始序列的任意一段連續(xù)范圍,由一對整數指定。和?range
?構造函數一樣,第一個整數表示起始索引,第二個整數是結束索引加一。(譯者注:和前面的?range
?一樣,其實還有第三個參數代表步長,最經典的例子是使用?s[::-1]
?得到?s
?的逆序排列,具體可以自行搜索)
在 Python 中,序列切片的表達方式類似于元素選擇,都使用方括號,方括號中的冒號用于分隔起始索引和結束索引。
如果起始索引或結束索引被省略則默認為極值:當起始索引被省略,則起始索引為 0;當結束索引被省略,則結束索引為序列長度,即取到序列最后一位。
>>> digits[0:2]
[1, 8]
>>> digits[1:]
[8, 2, 8]
1.5、字符串?
Python 中文本值的內置數據類型稱為字符串(string),對應構造函數?str
。在 Python 中,表示、表達和操作字符串的細節(jié)有很多。
字符串字面量(string literals)可以表示任意文本,使用時將內容用單引號或雙引號括起來。
>>> 'I am string!'
'I am string!'
>>> "I've got an apostrophe"
"I've got an apostrophe"
>>> '您好'
'您好'
我們已經在代碼中看到過字符串,比如文檔字符串(docstring)、print
?的調用中,以及?assert
?語句中的錯誤消息。
字符串同樣滿足我們在本節(jié)開頭介紹的序列的兩個基本條件:它們具有長度且支持元素選擇。字符串中的元素是只有一個字符的字符串。字符可以是字母表中的任何單個字母、標點符號或其他符號。
與其他編程語言不同,Python 沒有單獨的字符類型,任何文本都是字符串。表示單個字符的字符串的長度為 1。
>>> city = 'Berkeley'
>>> len(city)
8
>>> city[3]
'k'
與列表一樣,字符串也可以通過加法和乘法進行組合。
>>> 'Berkeley' + ', CA'
'Berkeley, CA'
>>> 'Shabu ' * 2
'Shabu Shabu '
成員資格(Membership):字符串的行為與 Python 中的其他序列類型有所不同。字符串抽象不符合我們對列表和范圍描述的完整序列抽象。具體來說,成員運算符?in
?應用于字符串時的行為與應用于序列時完全不同,它匹配的是子字符串而不是元素。(如果字符串的行為和列表的一樣,則應該匹配字符串的元素,即單個字符,但實際上匹配的是任意子字符串)
>>> 'here' in "Where's Waldo?"
True
多行字面量(Multiline Literals):字符串可以不限于一行??缭蕉嘈械淖址置媪靠梢杂萌匾柪ㄆ?#xff0c;我們已經在文檔字符串中廣泛使用了這種三重引號。
>>> """The Zen of Python
claims, Readability counts.
Read more: import this."""
'The Zen of Python\nclaims, "Readability counts."\nRead more: import this.'
在上面的打印結果中,\n
(讀作“反斜杠 n”)是一個表示換行的單個元素。盡管它顯示為兩個字符(反斜杠和 "n" ),但為了便于計算長度和元素選擇,它被視為單個字符。
字符串強制轉換(String Coercion):通過以對象值作為參數調用?str
?的構造函數,可以從 Python 中的任何對象創(chuàng)建字符串。字符串的這一特性在用構造各種類型對象的描述性字符串時非常有用。
>>> str(2) + ' is an element of ' + str(digits)
'2 is an element of [1, 8, 2, 8]'
二、數據抽象
2.1、示例:有理數
有理數是整數的比值,并且有理數是實數的一個重要子類。?1/3
?或?17/29
?等有理數通常寫為:
<分子>/<分母>
其中?<分子>
?和?<分母>
?都是整數值的占位符,這兩個部分能夠準確表示有理數的值。實際上的整數除以會產生?float
?近似值,失去整數的精確精度。
>>> 1/3
0.3333333333333333
>>> 1/3 == 0.333333333333333300000 # 整數除法得到近似值
True
但是,我們可以通過將分子和分母組合在一起來創(chuàng)建有理數的精確表示。
通過使用函數抽象,我們可以在實現程序的某些部分之前開始高效地編程。我們首先假設已經存在了一個從分子和分母構造有理數的方法,再假設有方法得到一個給定有理數的分子和分母。進一步假設得到以下三個函數:
rational(n, d)
?返回分子為?n
、分母為?d
?的有理數numer(x)
?返回有理數?x
?的分子denom(x)
?返回有理數?x
?的分母
我們在這里使用了一個強大的程序設計策略:一廂情愿(wishful thinking)。即使我們還沒有想好有理數是如何表示的,或者函數?numer
、denom
?和?rational
?應該如何實現。但是如果我們確實定義了這三個函數,我們就可以進行加法、乘法、打印和測試有理數是否相等:
>>> def add_rationals(x, y):nx, dx = numer(x), denom(x)ny, dy = numer(y), denom(y)return rational(nx * dy + ny * dx, dx * dy)>>> def mul_rationals(x, y):return rational(numer(x) * numer(y), denom(x) * denom(y))>>> def print_rational(x):print(numer(x), '/', denom(x))>>> def rationals_are_equal(x, y):return numer(x) * denom(y) == numer(y) * denom(x)
現在我們有了選擇器函數?numer
?和?denom
?以及構造函數?rational
?定義的有理數運算,但還沒有定義這些函數。我們需要某種方法將分子和分母粘合在一起形成一個復合值。
2.2、對
為了使我們能夠實現具體的數據抽象,Python 提供了一個名為?list
?列表的復合結構,可以通過將表達式放在以逗號分隔的方括號內來構造。這樣的表達式稱為列表字面量。
>>> [10, 20]
[10, 20]
可以通過兩種方式訪問 ?? 列表元素。第一種方法是通過我們熟悉的多重賦值方法,它將列表解構為單個元素并將每個元素與不同的名稱綁定。
>>> pair = [10, 20]
>>> pair
[10, 20]
>>> x, y = pair
>>> x
10
>>> y
20
訪問列表中元素的第二種方法是通過元素選擇運算符,也使用方括號表示。與列表字面量不同,直接跟在另一個表達式之后的方括號表達式不會計算為?list
?值,而是從前面表達式的值中選擇一個元素。
>>> pair[0]
10
>>> pair[1]
20
Python 中的列表(以及大多數其他編程語言中的序列)是從 0 開始索引的,這意味著索引 0 選擇第一個元素,索引 1 選擇第二個元素,以此類推。對于這種索引約定的一種直覺是,索引表示元素距列表開頭的偏移量。
元素選擇運算符的等效函數稱為?getitem
?,它也使用 0 索引位置從列表中選擇元素。
>>> from operator import getitem
>>> getitem(pair, 0)
10
>>> getitem(pair, 1)
20
雙元素列表并不是 Python 中表示對的唯一方法。將兩個值捆綁在一起成為一個值的任何方式都可以被認為是一對。列表是一種常用的方法,它也可以包含兩個以上的元素,我們將在本章后面進行探討。
代表有理數:我們現在可以將有理數表示為兩個整數的對:一個分子和一個分母。
>>> def rational(n, d):return [n, d]>>> def numer(x):return x[0]>>> def denom(x):return x[1]
連同之前定義的算術運算,我們可以使用我們定義的函數來操作有理數。
>>> half = rational(1, 2)
>>> print_rational(half)
1 / 2
>>> third = rational(1, 3)
>>> print_rational(mul_rationals(half, third))
1 / 6
>>> print_rational(add_rationals(third, third))
6 / 9
如上面的示例所示,我們的有理數實現不會將有理數簡化為最小項??梢酝ㄟ^更改?rational
?的實現來彌補這個缺陷。如果我們有一個計算兩個整數的最大公分母的函數,我們可以用它在構造對之前將分子和分母減少到最低項。與許多有用的工具一樣,這樣的功能已經存在于 Python 庫中。
>>> from fractions import gcd # python3.10變from math import gcd
>>> def rational(n, d):g = gcd(n, d)return (n//g, d//g)
//
?表示整數除法,它會將除法結果的小數部分向下舍入。因為我們知道?g
?會將?n
?和?d
?均分,所以在這種情況下整數除法是精確的。這個修改后的?rational
?實現會確保有理數以最小項表示。
>>> print_rational(add_rationals(third, third))
2 / 3
這種改進是通過更改構造函數而不更改任何實現實際算術運算的函數來實現的。
2.3、抽象屏障
在繼續(xù)更多復合數據和數據抽象的示例之前,讓我們考慮一下有理數示例引發(fā)的一些問題。我們根據構造函數?rational
?和選擇器函數?numer
?和?denom
?來定義操作。一般來說,數據抽象的基本思想是確定一組基本操作,根據這些操作可以表達對某種值的所有操作,然后僅使用這些操作來操作數據。通過以這種方式限制操作的使用,在不改變程序行為的情況下改變抽象數據的表示會容易得多。
對于有理數,程序的不同部分使用不同的操作來處理有理數,如此表中所述。
該程序的一部分... | 把有理數當作... | 僅使用... |
---|---|---|
使用有理數進行計算 | 整個數據值 | add_rational, mul_rational, rationals_are_equal, print_rational |
創(chuàng)建有理數或操作有理數 | 分子和分母 | rational, numer, denom |
為有理數實現選擇器和構造器 | 二元列表 | 列表字面量和元素選擇 |
在上面的每一層中,最后一列中的函數會強制實施抽象屏障(abstraction barrier)。這些功能會由更高層次調用,并使用較低層次的抽象實現。
當程序中有一部分本可以使用更高級別函數但卻使用了低級函數時,就會違反抽象屏障。例如,計算有理數平方的函數最好用?mul_rational
?實現,它不對有理數的實現做任何假設。
>>> def square_rational(x):return mul_rational(x, x)
直接引用分子和分母會違反一個抽象屏障。
>>> def square_rational_violating_once(x):return rational(numer(x) * numer(x), denom(x) * denom(x))
假設有理數會表示為雙元素列表將違反兩個抽象屏障。
>>> def square_rational_violating_twice(x):return [x[0] * x[0], x[1] * x[1]]
抽象屏障使程序更易于維護和修改。依賴于特定表示的函數越少,想要更改該表示時所需的更改就越少。計算有理數平方的所有這些實現都具有正確的行為,但只有第一個函數對未來的更改是健壯的。即使我們修改了有理數的表示,square_rational
?函數也不需要更新。相比之下,當選擇器函數或構造函數簽名發(fā)生變化后,square_rational_violating_once
?就需要更改,而只要有理數的實現發(fā)生變化,square_rational_violating_twice
?就需要更新。
2.4、數據的屬性
抽象屏障塑造了我們思考數據的方式。有理數的表示不限于任何特定的實現(例如二元素列表);它就是由?rational
?返回的值,然后可以傳遞給?numer
?和?denom
?。此外,構造器和選擇器之間必須保持適當的關系。也就是說,如果我們從整數?n
?和?d
?構造一個有理數?x
?,那么?numer(x)/denom(x)
?應該等于?n/d
?。
通常,我們可以使用選擇器和構造器的集合以及一些行為條件來表達抽象數據。只要滿足行為條件(比如上面的除法屬性),選擇器和構造器就構成了一種數據的有效表示。抽象屏障下的實現細節(jié)可能會改變,但只要行為沒有改變,那么數據抽象就仍然有效,并且使用該數據抽象編寫的任何程序都將保持正確。
這種觀點可以廣泛應用,包括我們用來實現有理數的對。我們從來沒有真正談論什么是一對,只是語言提供了創(chuàng)建和操作二元列表的方法。我們需要實現一對的行為是它將兩個值粘合在一起。作為一種行為條件,
- 如果一對?
p
?由值?x
?和?y
?構成,則?select(p, 0)
?返回?x
,?select(p, 1)
?返回?y
我們實際上并不一定需要?list
?類型來創(chuàng)建對,作為替代,我們可以用兩個函數?pair
?和?select
?來實現這個描述以及一個二元列表。
>>> def pair(x, y):"""Return a function that represents a pair."""def get(index):if index == 0:return xelif index == 1:return yreturn get>>> def select(p, i):"""Return the element at index i of pair p."""return p(i)
通過這個實現,我們可以創(chuàng)建和操作對。
>>> p = pair(20, 14)
>>> select(p, 0)
20
>>> select(p, 1)
14
這種高階函數的使用完全不符合我們對數據應該是什么的直覺概念。但盡管如此,這些函數足以在我們的程序中表示對,也足以表示復合數據。
這種表示對的函數表示的重點并不是 Python 實際上以這種方式工作(出于效率原因,列表更直接地實現),而是它可以以這種方式工作。函數表示雖然晦澀難懂,但卻是表示對的一個完全合適的方法,因為它滿足了表示對需要滿足的唯一條件。數據抽象的實踐使我們能夠輕松地在表示之間切換。
2.5、字典
字典(Dictionary)是 Python 的內置類型,用來存儲和操作帶有映射關系的數據。一個字典包含一組鍵值對(key-value pairs),其中鍵和值都是對象。字典的主要目的是抽象一組基于鍵值對的數據,在字典中,數據的存取都是基于帶有描述性信息的鍵而不是連續(xù)遞增的數字。
字典的 key 一般都是字符串(String),因為我們習慣用字符串來表示某個事物的名稱。下面這個字典字面量表示了一組羅馬數字:
>>> numerals = {'I': 1.0, 'V': 5, 'X': 10}
在字典元素中查找某個 key 對應的 value,與我們之前在列表中使用的操作符相同:
>>> numerals['X']
10
在字典中,一個 key 只能對應一個 value。無論是向字典中增加新的鍵值對,還是修改某個 key 值對應的 value,都可以使用賦值語句實現:
>>> numerals['I'] = 1
>>> numerals['L'] = 50
>>> numerals
{'I': 1, 'X': 10, 'L': 50, 'V': 5}
注意上面的打印輸出,'L' 并沒有被插入到字典的末尾。字典是無序的。當我們打印一個字典的時候,鍵值對會以某種順序被渲染在頁面上,但作為 Python 語言的使用者,我們無法預測這個順序是什么樣的。如果我們多運行幾次這個程序,字典輸出的順序可能會有所變化。
Python 3.7 及以上版本的字典順序會確保為插入順序,此行為是自 3.6 版開始的 CPython 實現細節(jié),字典會保留插入時的順序,對鍵的更新也不會影響順序,刪除后再次添加的鍵將被插入到末尾
字典類型也提供了一系列遍歷字典內容的方法。keys
、values
?和?items
?方法都返回一個可以被遍歷的值。
>>> sum(numerals.values())
66
利用?dictionary
?構造方法,我們可以將一個由鍵值對組成的列表轉化為一個字典對象。
>>> dict([(3, 9), (4, 16), (5, 25)])
{3: 9, 4: 16, 5: 25}
但是字典類型也有一些限制:
- 字典的 key 不可以是可變數據,也不能包含可變數據
- 一個 key 只能對應一個 value
第一個限制是由于字典在 Python 內部的實現機制導致的。字典類型具體的實現機制不在這里展開。簡單來說,假設是 key 值告訴 Python 應該去內存中的什么位置找對應的鍵值對,如果 key 值本身發(fā)生了變化,那鍵值對在內存中的位置信息也就丟失了。比如,元組可以被用來做字典的 key 值,但是列表就不可以。
第二個限制是因為字典本身被設計為根據 key 去查找 value,只有 key 和 value 的綁定關系是唯一確定的,我們才能夠找到對應的數據。
字典中一個很有用的方法是?get
,它返回指定 key 在字典中對應的 value;如果該 key 在字典中不存在,則返回默認值。get
?方法接收兩個參數,一個 key,一個默認值。
>>> numerals.get('A', 0)
0
>>> numerals.get('V', 0)
5
與列表類似,字典也有推導式語法。其中,key 和 value 使用冒號分隔。字典推導式會創(chuàng)建一個新的字典對象。
>>> {x: x*x for x in range(3,6)}
{3: 9, 4: 16, 5: 25}
鍵不可以是列表、字典或語言中任何可變類型。