翻譯:《實用的 Python 編程》02_07_Objects

目錄 | 上一節 (2.6 列表推導式) | [下一節 (3 程序組織)]()python

2.7 對象

本節介紹有關 Python 內部對象模型的更多詳細信息,並討論一些與內存管理,拷貝和類型檢查有關的問題。git

賦值

Python 中的許多操做都與賦值或者存儲值有關。github

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

警告:賦值操做永遠不是值拷貝。全部的賦值操做都是引用拷貝(若是你樂意,也能夠說是指針拷貝)segmentfault

賦值示例

考慮該代碼片斷:app

a = [1,2,3]
b = a
c = [a,b]

如下是底層內存操做圖。在此示例中,只有一個列表對象 [1,2,3],可是有四個不一樣的引用指向它。
image
這意味着修改一個值會影響全部的引用。函數

>>> a.append(999)
>>> a
[1,2,3,999]
>>> b
[1,2,3,999]
>>> c
[[1,2,3,999], [1,2,3,999]]
>>>

請注意,原始列表中的更改是如何在其它地方顯示的。這是由於從未進行任何拷貝,全部的東西都指向同一個東西。ui

從新賦值

從新賦值永遠不會重寫以前的值所使用的內存。spa

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] 正在共享。這就是衆所皆知的淺拷貝。下面是圖示:
image

深拷貝

有時候,須要拷貝一個對象及其中所包含的全部對象,爲此,可使用 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)

請花一些時間仔細思考你在練習中所作的事情。咱們稍後會再次討論這些想法。

目錄 | 上一節 (2.6 列表推導式) | [下一節 (3 程序組織)]()

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索