元組
Python元組的屬性:
- 任意對象的有序集合
- 通過偏移量存取
- 屬于“不可變序列”
- 固定長度、多樣性、任意嵌套
- 對象引用的數組
元組的常見方法:
| 運算 | 解釋 |
|---|---|
() |
空元組 |
T = (0,) |
單個元素的元組 |
T = (0, 'Ni', 1.2, 3) |
四個元素的元組 |
T = 0, 'Ni', 1.2, 3 |
還是四個元素的元組 |
T = ('Bob', ('dev', 'mgr')) |
嵌套元組 |
T = tuple('spam') |
可迭代對象的元素組成的元組 |
T[i] |
索引 |
T[i][j] |
索引的索引 |
T[i:j] |
分片 |
len(T) |
長度 |
T1 + T2 |
拼接 |
T * 3 |
重復 |
for x in T: print(x) |
迭代 |
'spam' in T |
成員關系測試 |
[x ** 2 for x in T] |
列表推導(好像不應該出現在這里?) |
namedtuple('Emp', ['name', 'jobs']) |
有名元組擴展類型 |
元組的實際應用
元組支持字符串和列表的一般序列操作,如拼接、重復、索引和分片。
>>> (1, 2) + (3, 4)
(1, 2, 3, 4)
>>> (1, 2) * 4
(1, 2, 1, 2, 1, 2, 1, 2)
>>> T = (1, 2, 3, 4)
>>> T[0]
1
>>> T[1:3]
(2, 3)
元組的特殊語法:逗號和圓括號
如果我們要得到元組而不是表達式的值,我們要在圓括號中加,。如果要構造單個元素的元組,就要在元素的后面加上,。
>>> (40) # 數字
40
>>> (40,) # 元組
(40,)
在不會引起二義性的情況下,Python允許構造元組時省略圓括號。要用到圓括號的情形:元組出現在一個函數調用中,或嵌套在一個更大的表達式內。
轉換、方法和不可變性
注意:上述的對元組的操作(拼接、重復等)會返回一個新的元組。元組不提供字符串、列表和字典中的方法。要對一個元組的元素排序,可以轉換成可變對象(如列表),或者使用新的內置函數(如sorted)。
>>> T = ('cc', 'aa', 'dd', 'bb')
>>> T = tuple(tmp)
>>> tmp = list(T)
>>> tmp.sort()
>>> sorted(T)
['aa', 'bb', 'cc', 'dd']
>>> T
('aa', 'bb', 'cc', 'dd')
list將元組轉換為列表,tuple將列表轉換為元組。
列表推導也可以用來轉換元組。
>>> T = (1, 2, 3, 4, 5)
>>> L = [x + 20 for x in T]
>>> L
[21, 22, 23, 24, 25]
列表的本質是不可變序列操作,列表推導甚至可以用在某些并非實際存儲的序列上,任何可迭代對象都可以。
元組有屬于自己的方法——index索引,count尋找元組中元素的數目。
>>> T = (1, 2, 3, 2, 4, 2)
>>> T.index(2) # 第一個2的索引
1
>>> T.index(2, 2) # 從偏移2開始,第一個2的索引
3
>>> T.count(2)
3
注意:元組的不可變性只適用于元組本身頂層而非其內容。我們可以修改元組內部的列表。
>>> T = (1, [2, 3], 4)
>>> T[1] = 'spam'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> T[1][0] = 'spam'
>>> T
(1, ['spam', 3], 4)
為什么有了列表還需要元組
“元組”的概念來自于數學,元組的不可變性提供了某種一致性,確保元組不會被另一個引用修改,類似于“常量”聲明。
重訪記錄:有名元組
我們可以實現一個同時提供序號和鍵兩種詢問方式的對象。例:namedtuple工具實現了一個增加了邏輯的元組擴展類型(在模塊collections被使用),能同時支持使用序號和屬性訪問組件。
>>> from collections import namedtuple
>>> Rec = namedtuple('Rec', ['name', 'age', 'jobs'])
>>> bob = Rec('Bob', age=40.5, jobs=['dev', 'mgr'])
>>> bob
Rec(name='Bob', age=40.5, jobs=['dev', 'mgr'])
>>> bob[0]
'Bob'
>>> bob[2]
['dev', 'mgr']
>>> bob.name
'Bob'
>>> bob.jobs
['dev', 'mgr']
>>>
可以將其轉換成一個字典,或者類似字典的結構:
>>> O = bob._asdict()
>>> O['name']
'Bob'
>>> O['jobs']
['dev', 'mgr']
>>> O
{'name': 'Bob', 'age': 40.5, 'jobs': ['dev', 'mgr']}
元組和有名元組都支持解包元組賦值:
>>> bob = Rec('Bob', 40.5, ['dev', 'mgr'])
>>> name, age, jobs = bob
>>> name
'Bob'
>>> jobs
['dev', 'mgr']
但是,解包元組賦值有時候不適用于字典,因為字典中的鍵值是沒有序號的。
>>> bob = {'name': 'Bob', 'age': 40.5, 'jobs': ['dev', 'mgr']}
>>> bob.values()
dict_values(['Bob', 40.5, ['dev', 'mgr']])
>>> job, name, age = bob.values()
>>> job
'Bob'
>>> name
40.5
文件
通過open,我們能夠創建一個Python文件對象作為到計算機上一個文件的鏈接。在調用open后,我們可以通過返回的文件對象的方法,在程序與相應文件之間來回傳遞串形式的數據。
文件對象與前面介紹的核心數據類型不同,它只支持與文件處理任務相關的方法。常見的文件操作見下表:
| 操作 | 解釋 |
|---|---|
output = open(r'C:\spam', 'w') |
創建輸出文件('w'代表寫入文件) |
input = open('data', 'r') |
創建輸入文件('r'代表從文件讀入) |
input = open('data') |
同上('r'是默認值) |
aString = input.read() |
把整個文件讀入字符串 |
aString = input.read() |
讀取接下啦的N個字符到一個字符串 |
aString = input.readline() |
讀取下一行(包括行末的'\n')到一個字符串 |
aList = input.readlines() |
讀取整個文件到一個字符串列表,列表中的元素是包括行末的'\n'的行 |
output.write(aString) |
把字符串寫入文件 |
output.writelines(aList) |
把列表內所有的字符串寫入文件 |
output.close() |
手動關閉(文件收集完成時會自動關閉) |
output.flush() |
把輸出緩沖區刷入硬盤中,但不關閉文件 |
anyFile.seek(N) |
把文件位置移動到偏移量N處以便進行下一個操作 |
for line in open('data'): use line |
文件迭代器逐行獲取 |
open('f.txt', encoding='latin-1') |
Python 3.X Unicode文件(str字符串) |
open('f.bin', 'rb') |
Python 3.X 字節碼文件(bytes字符串) |
codecs.open('f.txt', encoding='utf-8') |
Python 2.X Unicode文件(unicode字符串) |
open('f.bin', 'rb') |
Python 2.X 字節碼文件(str字符串) |
打開文件
打開文件時,程序調用內置函數open,第一個參數是外部文件名,第二個參數是文件的處理模式,返回文件對象,這個文件對象帶有傳輸數據的方法:
afile = open(filename, mode)
afile.method()
第一個參數是外部文件名,它可能有相對路徑前綴,如果沒有,則默認在腳本所運行的目錄下。

第二個參數是打開方式,'r'表示讀文件,'w'表示寫文件,'a'表示在文件內容追加內容并打開文件,'b'表示可以進行二進制處理,'+'表示同時支持輸入輸出。
第三個參數是可選的,控制輸出緩沖,0表示輸出無緩沖。
使用文件
基礎用法的提示:
- 文件迭代器最適合逐行讀取
- 寫入文件的內容必須是字符串,Python不會幫你轉換
- 文件是被緩沖的、可定位的,寫入的文本不會被立刻從內存轉移到硬盤,除非關閉文件或用
flush()方法 close是可選的,回收時自動關閉
文件的實際應用
首先為輸出打開一個文件,寫入兩行文本,關閉文件:
>>> afile = open('1.txt', 'w')
>>> myfile = open('myfile.txt', 'w')
>>> myfile.write('hello text file\n')
16
>>> myfile.write('goodbye text file\n')
18
>>> myfile.close()
在Python 3.X中,寫入字符串會返回字符數,但Python 2.X不會。
然后打開文件,逐行讀取。第三個readline()返回空字符串,表示文件已經到達末尾。
>>> myfile = open('myfile.txt')
>>> myfile.readline()
'hello text file\n'
>>> myfile.readline()
'goodbye text file\n'
>>> myfile.readline()
''
read方法把整個文件內容讀入到一個字符串中:
>>> open('myfile.txt').read()
'hello text file\ngoodbye text file\n'
如果要逐行掃描文件并對每一行操作,文件迭代器是最佳選擇:
>>> for line in open('myfile.txt'):
... print(line.upper(), end='')
...
HELLO TEXT FILE
GOODBYE TEXT FILE
這樣,open創建的臨時文件對象將自動在每次循環迭代時讀入并返回一行。優點:容易編寫、更高的內存使用效率、更快。
文本和二進制文件:一個簡要的故事
Python總是支持文本和二進制文件,其中:
-
文本文件把內容表示為常規的
str字符串,自動執行Unicode編碼和解碼,并且默認執行末行轉換; -
二進制文件把內容表示為一個特殊的
bytes字節串類型。
由于文本文件實現了Unicode編碼,因此不能以文本模式打開一個二進制文件,否則解碼會失敗。當我們讀取一個二進制文件時,會得到一個bytes對象(字節串)。
>>> open('myfile.txt', 'rb').read()
b'hello text file\r\ngoodbye text file\r\n'
此外,二進制文件不會對數據執行任何字符串轉換。
在文件中存儲Python對象:轉換
下面的例子把多種Python對象寫入一個文本文件的各行,必須把對象轉換成字符串。
>>> open('myfile.txt', 'r').read()
'hello text file\ngoodbye text file\n'
>>> X, Y, Z = 43, 44, 45
>>> S = 'Spam'
>>> D = {'a': 1, 'b': 2}
>>> L = [1, 2, 3]
>>>
>>> F = open('datafile.txt', 'w')
>>> F.write(S + '\n')
5
>>> F.write('%s,%s,%s\n' % (X, Y, Z))
9
>>> F.write(str(L) + '$' + str(D) + '\n')
27
>>> F.close()
創建文件后,我們可以讀取文件中的內容。注意,交互式(在交互模式下輸入對象)顯示給出直接的字節內容,而print操作解釋內嵌的換行符”:
>>> chars = open('datafile.txt').read()
>>> chars
"Spam\n43,44,45\n[1, 2, 3]${'a': 1, 'b': 2}\n"
>>> print(chars)
Spam
43,44,45
[1, 2, 3]${'a': 1, 'b': 2}
但是,問題是如何將文本文件中的字符串轉換成Python對象。
把第一行轉換為字符串:
>>> F = open('datafile.txt')
>>> line = F.readline()
>>> line
'Spam\n'
>>> line.rstrip()
'Spam'
把第二行轉換為數字序列。這里int函數可以忽略數字字符串旁邊的的空白:
>>> line = F.readline()
>>> parts = line.split(',')
>>> parts
['43', '44', '45\n']
>>> numbers = [int(P) for P in parts]
>>> numbers
[43, 44, 45]
轉換第三行時,我們可以運行eval函數,它把字符串參數當作可執行程序代碼:
>>> line = F.readline()
>>> line
"[1, 2, 3]${'a': 1, 'b': 2}\n"
>>> parts = line.split('$')
>>> parts
['[1, 2, 3]', "{'a': 1, 'b': 2}\n"]
>>> eval(parts[0])
[1, 2, 3]
>>> eval(parts[1])
{'a': 1, 'b': 2}
存儲Python原生對象:pickle
eval的缺陷是:它會執行任何表達式,包括刪除計算機中所有文件的表達式。如果要存儲Python的原生對象,就應該使用pickle。
pickle是一種能夠讓我們直接在文件中存儲幾乎任何Python對象的高級工具,而且不需要字符串轉換。
例:在文件中存儲字典:
>>> D = {'a': 1, 'b': 2}
>>> F = open('datafile.pkl', 'wb')
>>> import pickle
>>> pickle.dump(D, F)
>>> F.close()
想要取回字典時,再次使用pickle重建即可:
>>> F = open('datafile.pkl', 'rb')
>>> E = pickle.load(F)
>>> E
{'a': 1, 'b': 2}
實際上,pickle將字典轉化為一系列二進制的字節串,也能從字節串轉化為對象:
b'\x80\x04\x95\x11\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02u.'
shelve也可以實現類似功能,但這里不討論。
用JSON格式存儲Python對象
JSON是一種新興的數據交換格式,它與語言無關,也支持多種系統。它的可移植性在一些場景中會帶來巨大優勢。此外,由于JSON與Python中的字典和列表在語法上的相似性,使Python的json標準庫模塊能夠很容易地在Python對象與JSON之間來回轉換。
比如,Python的字典和JSON數據十分相似。我們創建一個字典,發現可以將字面量幾乎原封不動地傳給JSON文件:
>>> name = dict(first='Bob', last='Smith')
>>> rec = dict(name=name, job=['dev', 'mgr'], age=40.5)
>>> rec
{'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5}
>>> import json
>>> json.dumps(rec)
'{"name": {"first": "Bob", "last": "Smith"}, "job": ["dev", "mgr"], "age": 40.5}'
>>> S = json.dumps(rec)
>>> O = json.loads(S)
>>> O
{'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5}
json模塊可以使JSON表達式和Python對象互相轉換。從json文件讀取對象時,json模塊把文件地內容從JSON表示重建成Python對象。這里,dump把對象地字面量讀取到json文件中,load把json文件中的字面量加載到變量。注意,實際上的文件擴展名應該是.json而不是.txt。
>>> json.dump(rec, fp=open('testjson.txt', 'w'), indent=4)
>>> print(open('testjson.txt').read())
{
"name": {
"first": "Bob",
"last": "Smith"
},
"job": [
"dev",
"mgr"
],
"age": 40.5
}
>>> P = json.load(open('testjson.txt'))
>>> P
{'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5}
存儲打包二進制數據:struct
struct模塊能夠構造/解析打包二進制數據。
要生成一個打包二進制數據,可以用'wb'(寫入二進制)打開它,并將一個格式化字符串和幾個Python對象傳給struct。
>>> F = open('data.bin', 'wb')
>>> import struct
>>> data = struct.pack('>i4sh', 7, b'spam', 8)
>>> data
b'\x00\x00\x00\x07spam\x00\x08'
>>> F.write(data)
10
>>> F.close()
Python會創建一個我們通常寫入文件的二進制bytes數據字符串,主要由不可打印字符的十六進制轉義組成。
要把二進制文件解析成一般的Python對象,可以直接讀取字節串,并使用相同的格式字節串解壓即可。
>>> F = open('data.bin', 'rb')
>>> data = F.read()
>>> data
b'\x00\x00\x00\x07spam\x00\x08'
>>> values = struct.unpack('>i4sh', data)
>>> values
(7, b'spam', 8)
二進制文件是高級且底層的工具,因此這里不會介紹更多細節。
文件上下文處理器
文件上下文能夠讓我們把文件處理代碼包裝到一個邏輯層中,以確保在推出后一定會自動關閉文件,而不是在垃圾回收時自動關閉:
with open(r'C:\code\data.txt') as myfile:
for line in myfile:
...use line here...
之后學的try/finally也提供類似的功能。
其他文字工具
更多文件的方法查詢:dir()函數、help()函數、第13章。
Python工具集中有其他類似可用的文件工具:
- 標準流
os模塊中的描述文件- 套接字、管道和FiFO文件
- 通過鍵存取的文件
- Shell命令流
核心類型復習總結
- 按照分類,一些對象擁有共同的操作。如字符串、列表和元組擁有序列操作。
- 只有可變對象可以在原位置修改。可變對象有:列表、字典和集合。
- 文件只導出方法,因此可變性不適用于文件類型。
- “數字”包含:整數、浮點數、復數、小數和分數。
- 字符串包含:
str、Python 3.X中的bytes和Python 2.X中的unicode。 - 集合可以視為沒有值只有鍵的字典。
- 除了類型分類操作,所有類型都有可調用的方法。
請注意:運算符重載
在數值類型中,+表示將兩個數的值相加;在序列中,+表示拼接兩個序列。如果要設計新的類,我們可以定義加號(以及其他運算符)作用于這個類對象的含義。比如,定義類的加法可以用:def __add__(self, other):。
對象靈活性
一般來說:
- 列表、字典和元組可以包含任何種類的對象;
- 集合可包含任意的不可變類型對象;
- 列表、字典和元組可以任意嵌套;
- 列表、字典和集合可以動態地擴大和縮小。
Python的復合對象類型支持任意結構,因此它們非常適合表示程序中的復雜數據,可以嵌套任意多層。下面是一個嵌套的例子:
>>> L = ['abc', [(1, 2), ([3], 4)], 5]
>>> L[1]
[(1, 2), ([3], 4)]
>>> L[1][1]
([3], 4)
>>> L[1][1][0]
[3]
>>> L[1][1][0][0]
3
用圖表示,就是:

引用vs復制
第6章提到,賦值操作總是存儲對象的引用,而不是對象的副本。當涉及到較大的對象時,這種現象會變得微妙:
>>> X = [1, 2, 3]
>>> L = ['a', X, 'b']
>>> D = {'x': X, 'y': 2}
修改這三個引用的任意一個共享列表對象X,也會改變另外兩個引用的對象:
>>> X[1] = 'surprise'
>>> L
['a', [1, 'surprise', 3], 'b']
>>> D
{'x': [1, 'surprise', 3], 'y': 2}
引用是其他語言中指針的更高級模擬。雖然我們不能獲得引用本身,但是我們可以在不止一個地方存儲相同的引用。

這意味著,我們可以在程序范圍內任何地方傳遞大型對象而不用復制。如果要復制,需要明確要求:
- 沒有參數的分片表達式
L[:]可以復制序列; - 字典、集合和列表的
copy方法可以復制本身; - 內置函數也可以復制,如將
list方法應用于列表,dict方法應用于字典等等; copy標準庫模塊能夠在需要時創建完整副本。
列表和字典復制的例子:
>>> L = [1, 2, 3]
>>> D = {'a': 1, 'b': 2}
>>> # 將副本賦值給其他變量
>>> A = L[:]
>>> B = D.copy()
>>> # 由其他變量引發的改變將修改新產生的副本,而不是原有的對象
>>> A[1] = 'Ni'
>>> B['c'] = 'spam'
>>> L, D
([1, 2, 3], {'a': 1, 'b': 2})
>>> A, B
([1, 'Ni', 3], {'a': 1, 'b': 2, 'c': 'spam'})
但是,copy方法只能進行頂層復制。
>>> L0 = [1, 2, 3]
>>> L1 = ['a', L0, 'b']
>>> L2 = L1.copy()
>>> L2
['a', [1, 2, 3], 'b']
>>> L2[0] = 'c'
>>> L1, L2
(['a', [1, 2, 3], 'b'], ['c', [1, 2, 3], 'b'])
>>> L2[1][1] = 5
>>> L1, L2
(['a', [1, 5, 3], 'b'], ['c', [1, 5, 3], 'b'])
要進行深層復制(完整、獨立的復制),需要copy模塊中的deepcopy方法。
比較、等價性和真值
所有的Python對象都支持:測試等價性、相對大小等。比較復合對象時,會檢查所有的組件,直到得出結果為止。
這種比較也被稱為遞歸比較——對頂層對象的比較會被應用到每一個嵌套的下一次對象中,直到最底層的對象并得出結果。就核心類型而言,遞歸功能是默認實現的。
在比較列表對象時將自動比較所有內容,直到找到一個不匹配或完成比較:
>>> L1 = [1, 2, 3]
>>> L2 = [1, 2, 3]
>>> L1 == L2
True
>>> L1 is L2
False
這里展示了兩種測試等價性的方式,分別是:
==運算符測試值的等價性,Python遞歸地比較所有內嵌對象。is表達式測試對象的同一性,Python測試兩者是否為同一個對象(在存儲器中有相同地址)。
注意對短字符串的運行結果:
>>> S1 = 'spam'
>>> S2 = 'spam'
>>> S1 == S2
True
>>> S1 is S2
True
這是因為,Python會通過重復地理利用短字符串進行優化,因此S1和S2共享同一個'spam'。但這不適用于長字符串:
>>> S1 = 'A Longer String'
>>> S2 = 'A Longer String'
>>> S1 == S2
True
>>> S1 is S2
False
相對大小比較也能遞歸地應用于嵌套數據結構:
>>> L1 = [1, ('a', 3)]
>>> L2 = [1, ('a', 2)]
>>> L1 < L2, L1 == L2, L1 > L2
(False, False, True)
Python比較相對大小地規則是:
- 數字在轉換成必要的公共最高級類型后,比較數值地相對大小。
- 字符串按照字母字典順序比較(從左到右比較字符的ASCII碼大小)。
- 列表和元組從左到右對每個組件內容進行比較,直到末尾或發現區別。
- 集合是相等的,當且僅當它們含有相同的元素。集合的比較大小采取子集和超集的標準。
- 字典通過比較排序后的
(key, value)列表判斷是否相同。(僅Python 2.X) - 非數字不同類型(混合類型)的比較。(僅Python 2.X)
Python 2.X和3.X混合類型比較和排序
Python 2.X中的混合類型通過任意的順序的比較,但3.X不允許混合順序比較。
Python 2.X和3.X中的字典比較
在Python 2.X中,字典支持相對大小比較,就等效于比較排序的鍵/值列表,但是在Python 3.X中,字典之間的比較大小由于開銷太大,故被移除。
Python 3.X的替代方式是:要么編寫循環鍵比較值,要么手動比較排序的鍵/值列表(利用sorted方法):
>>> list(D1.items())
[('a', 1), ('b', 2)]
>>> list(D2.items())
[('a', 1), ('b', 3)]
>>> sorted(D1.items()) < sorted(D2.items())
True
>>> sorted(D1.items()) > sorted(D2.items())
False
Python中True和False的含義
在Python中,1表示真,0表示假。實際上,真和假是Python中每個對象的固有屬性,規則如下:
- 數字如果等于0則為假,反之則為真。
- 其他對象如果為空則為假,反之則為真。
一些是一些例子:
| 對象 | 值 |
|---|---|
"spam" |
True |
"" |
False |
[1, 2] |
True |
[] |
False |
{'a': 1} |
True |
{} |
False |
1 |
True |
0.0 |
False |
None |
False |
這樣做會使對象的檢查更加簡單,如檢查字符串是否為空可以簡化成if X:,也可以if X != '':。
None對象
None總是為假,這是Python中一種特殊數據類型的唯一值,作用是空占位符。
None不意味著“未定義”,而是某些內容。None是一個真正的對象,有一塊真實的內存。None是Python給定的一個內置名稱,也是函數的默認返回值。
bool類型
Python的布爾類型bool只是擴展了Python中的真假概念。這種設計只是為了讓真值更加顯式,程序更明確。
Python還提供了一個內置的函數bool,顯式地把對象轉換為對象的布爾值。
>>> bool(1)
True
>>> bool('spam')
True
>>> bool({})
False
Python的類型層次

類型和對象
即使是類型本身在Python中也是一種類型的對象:對象的類型本身,也屬于type類型的對象。
從Python 2.2開始,每個核心類型都有一個新內置名,用來支持通過面向對象編寫子類的類型定制:dict、list、tiple、str、int、float、complex、bytes、type、set等。
Python的其他類型
后面要學到的類型:函數、模塊和類。以及擴展:正則表達式對象、DBM文件、GUI組件、網絡套接字等等。它們(擴展)與核心類型的區別是:內置類型不需要導入模塊,但擴展類型需要用import導入模塊。
內置類型陷阱
賦值創建引用,而不是復制
可變對象的共享引用在你的程序中至關重要。
如果不想要這種共享,那么使用復制的手段來避免共享。
重復會增加層次深度
這里,當可變序列進行嵌套時,可能會使列表重復,或者列表中嵌套四個列表:
>>> L = [4, 5, 6]
>>> X = L * 4
>>> X
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6]
>>> Y = [L] * 4
>>> Y
[[4, 5, 6], [4, 5, 6], [4, 5, 6], [4, 5, 6]]
Y包含了指向原本L的列表的引用,因此出現了副作用:
>>> L[1] = 0
>>> X
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6]
>>> Y
[[4, 0, 6], [4, 0, 6], [4, 0, 6], [4, 0, 6]]
解決方法是:進行復制,這里用list解決:
>>> L = [4, 5, 6]
>>> Y = [list(L)] * 4
>>> Y
[[4, 5, 6], [4, 5, 6], [4, 5, 6], [4, 5, 6]]
>>> L[1] = 0
>>> Y
[[4, 5, 6], [4, 5, 6], [4, 5, 6], [4, 5, 6]]
但是,Y對應的列表對象所嵌套的引用仍然指向同一個對象。要避免這種現象,必須保證每一個嵌套有一個單獨的副本:
>>> Y[0][1] = 99
>>> Y
[[4, 99, 6], [4, 99, 6], [4, 99, 6], [4, 99, 6]]
>>> L = [4, 5, 6]
>>> Y = [list(L) for i in range(4)]
>>> Y
[[4, 5, 6], [4, 5, 6], [4, 5, 6], [4, 5, 6]]
>>> Y[0][1] = 99
>>> Y
[[4, 99, 6], [4, 5, 6], [4, 5, 6], [4, 5, 6]]
注意循環數據結構
如果一個復合對象包含指向自己的引用,就稱之為循環對象。Python在檢測到循環時,會打印出[...]。
>>> L = [1, 2, 3]
>>> L.append(L)
>>> L
[1, 2, 3, [...]]
經驗法則:除非真的需要,否則不要使用循環引用。
不可變類型不可以在原位置改變
如果要改變,需要通過分片、拼接等操作創建一份新的對象。
浙公網安備 33010602011771號