賦值
Python 中的許多操作都與賦值或者儲存值有關。
a = value # Assignment to a variables[n] = value # Assignment to a lists.append(value) # Appending to a listd['key'] = value # Adding to a dictionary
警告:賦值操作永遠不是值複製。所有的賦值操作都是引用複製(如果你樂意,也可以說是指標複製)
賦值示例考慮該程式碼片段:
a = [1,2,3]b = ac = [a,b]
以下是底層記憶體操作圖。在此示例中,只有一個列表物件 [1,2,3],但是有四個不同的引用指向它。
這意味著修改一個值會影響所有的引用。
>>> a.append(999)>>> a[1,2,3,999]>>> b[1,2,3,999]>>> c[[1,2,3,999], [1,2,3,999]]>>>
請注意,原始列表中的更改是如何在其它地方顯示的。這是因為從未進行任何複製,所有的東西都指向同一個東西。
重新賦值重新賦值永遠不會重寫之前的值所使用的記憶體。
a = [1,2,3]b = aa = [4,5,6]print(a) # [4, 5, 6]print(b) # [1, 2, 3] Holds the original value
切記:變數是名稱,不是記憶體地址
風險如果你不知道這種(資料)共享(的方式),那麼在某些時候你會搬起石頭砸自己的腳。典型情景,你修改了一些資料,以為它是自己的私有複製,但是它卻意外地損破壞了程式其它部分的某些資料。
說明:這就是為什麼原始資料型別是不可變(只讀)的原因之一
標識值和引用使用 is 運算子檢查兩個值是否真的是相同的物件。
>>> a = [1,2,3]>>> b = a>>> a is bTrue>>>
is 運算子比較物件的標識值(一個整數)。標識值可以使用 id() 函式獲取。
>>> id(a)3588944>>> id(b)3588944>>>
注意:使用 == 檢查物件是否相等幾乎總是更好,is的結果通常會出乎意料:
>>> a = [1,2,3]>>> b = a>>> c = [1,2,3]>>> a is bTrue>>> a is cFalse>>> a == cTrue>>>
淺複製
列表和字典自身具有用於複製的方法。
>>> a = [2,3,[100,101],4]>>> b = list(a) # Make a copy>>> a is bFalse
這是一個新列表,但是列表中的項是共享的。
>>> a[2].append(102)>>> b[2][100,101,102]>>>>>> a[2] is b[2]True>>>
例如,內部列表 [100, 101, 102] 正在共享。這就是眾所皆知的淺複製。下面是圖示:
深複製有時候,需要複製一個物件及其中所包含的所有物件,為此,可以使用 copy 模組:
>>> a = [2,3,[100,101],4]>>> import copy>>> b = copy.deepcopy(a)>>> a[2].append(102)>>> b[2][100,101]>>> a[2] is b[2]False>>>
名稱,值,型別
變數名稱沒有型別,僅僅是一個名字。但是,值確實具有一個底層的型別。
>>> a = 42>>> b = 'Hello World'>>> type(a)<type 'int'>>>> type(b)<type 'str'>
type() 函式將告訴你這是什麼。型別名稱通常用作建立或將值轉換為該型別的函式。
型別檢查如何判斷物件是否為特定型別?
if isinstance(a, list): print('a is a list')
檢查是否是多種型別中的一種:
if isinstance(a, (list,tuple)): print('a is a list or tuple')
注意:不要過度使用型別檢查。這會導致過度的程式碼複雜性。通常,如果這樣做能夠阻止其他人在使用你的程式碼時犯常見錯誤,那麼就使用型別檢查。
一切皆物件數字,字串,列表,函式,異常,類,例項等都是物件。這意味著所有可以命名的物件都可以作為資料傳遞、放置到容器中,而沒有任何限制。沒有特殊的物件。有時,可以這樣說,所有的物件都是“一等物件”。
一個簡單的例子:
>>> import math>>> items = [abs, math, ValueError ]>>> items[<built-in function abs>, <module 'math' (builtin)>, <type 'exceptions.ValueError'>]>>> items[0](-45)45>>> items[1].sqrt(2)1.4142135623730951>>> try: x = int('not a number') except items[2]: print('Failed!')Failed!>>>
在這裡,items 是一個包含函式,模組和異常的列表。可以直接使用列表中的項代替原始名稱。
items[0](-45) # absitems[1].sqrt(2) # mathexcept items[2]: # ValueError
權利越大,責任越大。只是因為你可以做,但並意味這你應該這樣做。
練習在這組練習中,我們來看看來自一等物件的威力。
練習 2.24:一等資料在 Data/portfolio.csv 檔案中,我們把有組織的資料讀取為列,如下所示:
name,shares,price"AA",100,32.20"IBM",50,91.10...
在之前的程式碼中,我們使用 csv 模組讀取檔案,但是仍必須手動執行型別轉換。例如:
for row in rows: name = row[0] shares = int(row[1]) price = float(row[2])
也可以使用一些列表基本操作以更巧妙的方式來執行這種轉換。
建立一個包含轉換函式名稱的 Python 列表,這些函式用來把每一列轉換成適當的型別。
>>> types = [str, int, float]>>>
可以建立這樣的列表是因為在 Python 中一切皆一等物件。所以,如果想建立一個函式列表,也是可以的。列表中建立的項用於將值 x 轉換為給定的型別(如:str(x), int(x), float(x))。
現在,從上面檔案的資料中讀取一行:
>>> import csv>>> f = open('Data/portfolio.csv')>>> rows = csv.reader(f)>>> headers = next(rows)>>> row = next(rows)>>> row['AA', '100', '32.20']>>>
如前所述,該行不足以進行計算,因為型別是錯誤的。例如:
>>> row[1] * row[2]Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: can't multiply sequence by non-int of type 'str'>>>
但是,也許資料可以與在 types 中指定的型別配對。例如:
>>> types[1]<type 'int'>>>> row[1]'100'>>>
嘗試轉換其中一個值:
>>> types[1](row[1]) # Same as int(row[1])100>>>
嘗試轉換另一個值:
>>> types[2](row[2]) # Same as float(row[2])32.2>>>
嘗試使用轉換後的值進行計算:
>>> types[1](row[1])*types[2](row[2])3220.0000000000005>>>
使用 zip() 函式將欄位組合到一起,並且檢視結果:
>>> r = list(zip(types, row))>>> r[(<type 'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]>>>
注意看,這會將型別轉換函式名稱與值配對。例如,int 和 '100'配對。
如果要一個接一個地對所有值進行轉換,那麼合併後的列表很有用。請嘗試:
>>> converted = []>>> for func, val in zip(types, row): converted.append(func(val))...>>> converted['AA', 100, 32.2]>>> converted[1] * converted[2]3220.0000000000005>>>
確保你理解上述程式碼中所發生的事情。在迴圈中,func 變數是型別轉換函式(如str, int等 )之一且 val 變數是值('AA', '100')之一。表示式 func(val)轉換一個值(類似於型別轉換)。
上面的程式碼可以轉換為單個列表推導式。
>>> converted = [func(val) for func, val in zip(types, row)]>>> converted['AA', 100, 32.2]>>>
練習 2.25:建立字典還記得如果有一個鍵和值的序列,如何使用dict() 函式輕鬆地建立字典嗎?讓我們從列標題建立一個字典吧:
>>> headers['name', 'shares', 'price']>>> converted['AA', 100, 32.2]>>> dict(zip(headers, converted)){'price': 32.2, 'name': 'AA', 'shares': 100}>>>
當然,如果你精通列表推導式,則可以使用字典推導式一步完成整個轉換。
>>> { name: func(val) for name, func, val in zip(headers, types, row) }{'price': 32.2, 'name': 'AA', 'shares': 100}>>>
練習 2.26:全域性
使用本練習中的技術,可以編寫語句,輕鬆地將幾乎任何面向列的資料檔案中的欄位轉換為 Python 字典。
為了說明,假設你像下面這樣從不同的資料檔案讀取資料,如下所示:
>>> f = open('Data/dowstocks.csv')>>> rows = csv.reader(f)>>> headers = next(rows)>>> row = next(rows)>>> headers['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']>>> row['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']>>>
讓我們使用類似的技巧來轉換欄位:
>>> types = [str, float, str, str, float, float, float, float, int]>>> converted = [func(val) for func, val in zip(types, row)]>>> record = dict(zip(headers, converted))>>> record{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,'change': -0.18}>>> record['name']'AA'>>> record['price']39.48>>>
附加題:如何修改本示例以進一步解析 date 條目到元組中,如(6, 11, 2007)?