翻譯:《實用的 Python 編程》02_07_Objects
目錄 | 上一節 (2.6 列表推導式) | 下一節 (3 程序組織)
2.7 對象
本節介紹有關 Python 內部對象模型的更多詳細信息,并討論一些與內存管理,拷貝和類型檢查有關的問題。
賦值
Python 中的許多操作都與賦值或者存儲值有關。
a = value # Assignment to a variable
s[n] = value # Assignment to a list
s.append(value) # Appending to a list
d['key'] = value # Adding to a dictionary
警告:賦值操作永遠不是值拷貝。所有的賦值操作都是引用拷貝(如果你樂意,也可以說是指針拷貝)
賦值示例
考慮該代碼片段:
a = [1,2,3]
b = a
c = [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 = a
a = [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 b
True
>>>
is 操作符比較對象的標識值(一個整數)。標識值可以使用 id() 函數獲取。
>>> id(a)
3588944
>>> id(b)
3588944
>>>
注意:使用 == 檢查對象是否相等幾乎總是更好,is的結果通常會出乎意料:
>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>
淺拷貝
列表和字典自身具有用于拷貝的方法。
>>> a = [2,3,[100,101],4]
>>> b = list(a) # Make a copy
>>> a is b
False
這是一個新列表,但是列表中的項是共享的。
>>> 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) # abs
items[1].sqrt(2) # math
except 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)?
請花一些時間仔細思考你在練習中所做的事情。我們稍后會再次討論這些想法。
浙公網安備 33010602011771號