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

目錄 | 上一節 (2.1 數據類型) | 下一節 (2.3 格式化)

2.2 容器

本節討論列表(list),字典(dict)和集合(set)。

概述

通常,程式必須處理許多對象。

  • 股票的投資組合
  • 股票價格表

這裡有三種主要的選擇(譯註:數據結構)可以使用:

  • 列表。有序的數據。
  • 字典。無序的數據。
  • 集合。互異且無序的數據。

把列表當作容器

當數據順序很重要時,請使用列表。記住,列表可以存儲任何類型的對象。例如,包含元組的列表:

portfolio = [
    ('GOOG', 100, 490.1),
    ('IBM', 50, 91.3),
    ('CAT', 150, 83.44)
]

portfolio[0]            # ('GOOG', 100, 490.1)
portfolio[2]            # ('CAT', 150, 83.44)

列表構建

從零開始構建列表。

records = []  # Initial empty list

# Use .append() to add more items
records.append(('GOOG', 100, 490.10))
records.append(('IBM', 50, 91.3))
...

從文件讀取記錄的示例:

records = []  # Initial empty list

with open('Data/portfolio.csv', 'rt') as f:
    next(f) # Skip header
    for line in f:
        row = line.split(',')
        records.append((row[0], int(row[1]), float(row[2])))

把字典當作容器

如果要快速隨機查找(通過鍵名),那麼字典很有用。例如,股票價格字典:

prices = {
   'GOOG': 513.25,
   'CAT': 87.22,
   'IBM': 93.37,
   'MSFT': 44.12
}

以下是一些簡單的查找:

>>> prices['IBM']
93.37
>>> prices['GOOG']
513.25
>>>

字典構建

從零開始構建字典的示例:

prices = {} # Initial empty dict

# Insert new items
prices['GOOG'] = 513.25
prices['CAT'] = 87.22
prices['IBM'] = 93.37

從文件內容填充字典的示例:

prices = {} # Initial empty dict

with open('Data/prices.csv', 'rt') as f:
    for line in f:
        row = line.split(',')
        prices[row[0]] = float(row[1])

注意:如果是在 Data/prices.csv 文件上嘗試此操作,會發現幾乎可以正常工作——但是,在末尾有一個空行導致程式崩潰了。需要找出一些方法來修改程式碼以解決此問題(參見練習 2.6)。

字典查找

測試鍵是否存在:

if key in d:
    # YES
else:
    # NO

可以查找可能不存在的值,並在值不存在的情況下提供默認值。

name = d.get(key, default)

示例:

>>> prices.get('IBM', 0.0)
93.37
>>> prices.get('SCOX', 0.0)
0.0
>>>

組合鍵

在 Python 中,幾乎任何類型的值都可以用作字典的鍵。字典的鍵必須是不可變類型。例如,元組:

holidays = {
  (1, 1) : 'New Years',
  (3, 14) : 'Pi day',
  (9, 13) : "Programmer's day",
}

然後訪問:

>>> holidays[3, 14]
'Pi day'
>>>

列表,集合或者其它字典都不能用作字典的鍵,因為列表和字典(譯註:集合也是使用哈希技術實現的)是可變的。

集合

集合是互異且無序的數據。

tech_stocks = { 'IBM','AAPL','MSFT' }
# Alternative syntax
tech_stocks = set(['IBM', 'AAPL', 'MSFT'])

集合對於成員關係測試很有用。

>>> tech_stocks
set(['AAPL', 'IBM', 'MSFT'])
>>> 'IBM' in tech_stocks
True
>>> 'FB' in tech_stocks
False
>>>

集合對於消除重複也很有用。

names = ['IBM', 'AAPL', 'GOOG', 'IBM', 'GOOG', 'YHOO']

unique = set(names)
# unique = set(['IBM', 'AAPL','GOOG','YHOO'])

其它集合操作:

names.add('CAT')        # Add an item
names.remove('YHOO')    # Remove an item

s1 | s2                 # Set union
s1 & s2                 # Set intersection
s1 - s2                 # Set difference

練習

在這些練習中,你開始構建的程式是本課程剩餘部分使用的主要程式之一。請在 Work/report.py 文件中工作。

練習 2.4:包含元組的列表

Data/portfolio.csv 文件包含投資組合中的股票列表。在 練習 1.30 中,你編寫了一個讀取該文件並執行簡單計算的 portfolio_cost(filename) 函數。

程式碼看起來應該像下面這樣:

# pcost.py

import csv

def portfolio_cost(filename):
    '''Computes the total cost (shares*price) of a portfolio file'''
    total_cost = 0.0

    with open(filename, 'rt') as f:
        rows = csv.reader(f)
        headers = next(rows)
        for row in rows:
            nshares = int(row[1])
            price = float(row[2])
            total_cost += nshares * price
    return total_cost

請使用這些程式碼作為指導,創建一個新文件 report.py 。在 report.py 文件中,定義 read_portfolio(filename) 函數,該函數打開 Data/portfolio.csv 文件並將其讀入到包含元組的列表中。為此,你需要對上面的程式碼做一些小修改。

首先,創建一個最初設為空列表的變數,而不是定義 total_cost = 0。例如:

portfolio = []

接著,把每一行準確地存儲到元組中(就像在上次的練習中做的那樣),然後把元組追加到列表中,而不是合計總的費用。

for row in rows:
    holding = (row[0], int(row[1]), float(row[2]))
    portfolio.append(holding)

最後,返回得到的portfolio 列表。

請互動式地試驗函數(提醒,要執行此操作,首先需要在解釋器運行 report.py 程式)。

提示:當在終端執行文件的時候,請使用 -i 參數。

>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> portfolio
[('AA', 100, 32.2), ('IBM', 50, 91.1), ('CAT', 150, 83.44), ('MSFT', 200, 51.23),
    ('GE', 95, 40.37), ('MSFT', 50, 65.1), ('IBM', 100, 70.44)]
>>>
>>> portfolio[0]
('AA', 100, 32.2)
>>> portfolio[1]
('IBM', 50, 91.1)
>>> portfolio[1][1]
50
>>> total = 0.0
>>> for s in portfolio:
        total += s[1] * s[2]

>>> print(total)
44671.15
>>>

創建的包含元組的列表非常類似於二維(2-D)數組。例如,使用諸如 portfolio[row][column]rowcolumn 是整數)的查找來訪問特定的列和行。

也就是說,可以使用像下面這樣的語句重寫最後的 for 循環:

>>> total = 0.0
>>> for name, shares, price in portfolio:
            total += shares*price

>>> print(total)
44671.15
>>>

練習 2.5:包含字典的列表

使用字典(而不是元組)修改在練習 2.4 中編寫的函數來表示投資組合中的股票。在字典中,使用欄位名 “name”, “shares” 和 “price” 來表示輸入文件中的不同列。

以與練習 2.4 中相同的方式試驗這個新的函數。

>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> portfolio
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
    {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},
    {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},
    {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>> portfolio[0]
{'name': 'AA', 'shares': 100, 'price': 32.2}
>>> portfolio[1]
{'name': 'IBM', 'shares': 50, 'price': 91.1}
>>> portfolio[1]['shares']
50
>>> total = 0.0
>>> for s in portfolio:
        total += s['shares']*s['price']

>>> print(total)
44671.15
>>>

在這裡可以看到,每個條目的不同欄位是通過鍵名來訪問的,而不是數字類型的列號。這通常是首選方式,因為這樣得到的程式碼在以後易於閱讀。

查看大型的字典或者列表可能會很混亂。要使調試的輸出變得整潔,可以考慮使用 pprint() 函數。

>>> from pprint import pprint
>>> pprint(portfolio)
[{'name': 'AA', 'price': 32.2, 'shares': 100},
    {'name': 'IBM', 'price': 91.1, 'shares': 50},
    {'name': 'CAT', 'price': 83.44, 'shares': 150},
    {'name': 'MSFT', 'price': 51.23, 'shares': 200},
    {'name': 'GE', 'price': 40.37, 'shares': 95},
    {'name': 'MSFT', 'price': 65.1, 'shares': 50},
    {'name': 'IBM', 'price': 70.44, 'shares': 100}]
>>>

練習 2.6:把字典當作容器

在使用索引而不是數字查找某元素的地方,字典是一種用來跟蹤元素的很有用的方式。在 Python shell 中,嘗試使用字典:

>>> prices = { }
>>> prices['IBM'] = 92.45
>>> prices['MSFT'] = 45.12
>>> prices
... look at the result ...
>>> prices['IBM']
92.45
>>> prices['AAPL']
... look at the result ...
>>> 'AAPL' in prices
False
>>>

Data/prices.csv 文件包含一系列帶有股票價格的行,看起來像下面這樣:

"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
"C",3.72
...

編寫 read_prices(filename)函數將諸如此類的價格集合讀取到字典中,字典的鍵代表股票的名字,字典的值代表股票的價格。

為此,從空字典開始,並且像上面做的那樣開始插入值。但是,現在正在從從文件中讀取值。

我們將使用該數據結構快速查找給定名稱的股票的價格。

這部分需要一些小技巧。首先,確保像之前做的那樣使用 csv 模組——無需在這裡重複發明輪子。

>>> import csv
>>> f = open('Data/prices.csv', 'r')
>>> rows = csv.reader(f)
>>> for row in rows:
        print(row)


['AA', '9.22']
['AXP', '24.85']
...
[]
>>>

另外一個小麻煩是 Data/prices.csv 文件可能有一些空行在裡面。注意上面數據的最後一行是一個空列表——意味在那一行沒有數據。

這有可能導致你的程式因為異常而終止。酌情使用 tryexcept 語句捕獲這些異常。思考:使用 if 語句來防範錯誤的數據是否會更好?

編寫完 read_prices() 函數,請互動式地測試它並確保其正常工作:

>>> prices = read_prices('Data/prices.csv')
>>> prices['IBM']
106.28
>>> prices['MSFT']
20.89
>>>

練習 2.7:看看你是否可以退休

通過添加一些計算盈虧的語句到 report.py 程式,將所有的工作聯繫到一起。這些語句應該採用在練習 2.5 中存儲股票名稱的列表,以及在練習 2.6 中存儲股票價格的字典,並計算投資組合的當前值以及盈虧。

目錄 | 上一節 (2.1 數據類型) | 下一節 (2.3 格式化)

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