列表、字典和集合介绍

Intermediate

This tutorial is from open-source community. Access the source code

简介

本节讨论列表、字典和集合。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 中级 级别的实验,完成率为 74%。获得了学习者 88% 的好评率。

概述

程序常常需要处理许多对象。

  • 股票投资组合
  • 股票价格表

有三种主要的选择可供使用。

  • 列表。有序数据。
  • 字典。无序数据。
  • 集合。唯一项的无序集合。

作为容器的列表

当数据顺序很重要时,请使用列表。请记住,列表可以容纳任何类型的对象。例如,一个元组列表。

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 = []  ## 初始为空列表

## 使用.append() 添加更多项
records.append(('GOOG', 100, 490.10))
records.append(('IBM', 50, 91.3))
...

从文件读取记录时的一个示例。

records = []  ## 初始为空列表

with open('portfolio.csv', 'rt') as f:
    next(f) ## 跳过标题行
    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 = {} ## 初始为空字典

## 插入新项
prices['GOOG'] = 513.25
prices['CAT'] = 87.22
prices['IBM'] = 93.37

从文件内容填充字典的示例。

prices = {} ## 初始为空字典

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

注意:如果你在 prices.csv 文件上尝试此操作,你会发现它几乎能正常工作——但文件末尾有一个空行,这会导致程序崩溃。你需要想办法修改代码来处理这个问题(见练习 2.6)。

字典查找

你可以测试键是否存在。

if key in d:
    ## 存在
else:
    ## 不存在

你可以查找一个可能不存在的值,并在其不存在时提供一个默认值。

name = d.get(key, default)

一个示例:

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

复合键

在 Python 中,几乎任何类型的值都可以用作字典的键。字典的键必须是不可变类型。例如,元组:

holidays = {
  (1, 1) : '新年',
  (3, 14) : '圆周率日',
  (9, 13) : "程序员节",
}

然后进行访问:

>>> holidays[3, 14]
'圆周率日'
>>>

列表、集合或另一个字典都不能用作字典的键,因为列表和字典是可变的。

集合

集合是无序的唯一元素的集合。

tech_stocks = { 'IBM','AAPL','MSFT' }
## 另一种语法
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'])

其他集合操作:

unique.add('CAT')        ## 添加一个元素
unique.remove('YHOO')    ## 移除一个元素

s1 = { 'a', 'b', 'c'}
s2 = { 'c', 'd' }
s1 | s2                 ## 集合并集 { 'a', 'b', 'c', 'd' }
s1 & s2                 ## 集合交集 { 'c' }
s1 - s2                 ## 集合差集 { 'a', 'b' }

在这些练习中,你将开始构建本课程剩余部分使用的一个主要程序。在 report.py 文件中完成你的工作。

练习 2.4:元组列表

文件 portfolio.csv 包含了一个投资组合中的股票列表。在练习 1.30 中,你编写了一个函数 portfolio_cost(filename),它读取这个文件并进行了一个简单的计算。

你的代码应该类似于以下这样:

## pcost.py

import csv

def portfolio_cost(filename):
    '''计算投资组合文件的总成本(股数 * 价格)'''
    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。在那个文件中,定义一个函数 read_portfolio(filename),它打开给定的投资组合文件并将其读入一个元组列表中。为此,你需要对上述代码做一些小的修改。

首先,不是定义 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('/home/labex/project/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
>>>

你创建的这个元组列表与二维数组非常相似。例如,你可以使用类似 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('/home/labex/project/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
... 查看结果...
>>> prices['IBM']
92.45
>>> prices['AAPL']
... 查看结果...
>>> 'AAPL' in prices
False
>>>

文件 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('/home/labex/project/prices.csv', 'r')
>>> rows = csv.reader(f)
>>> for row in rows:
        print(row)


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

另一个小麻烦是 prices.csv 文件中可能有一些空行。注意上面数据的最后一行是一个空列表 —— 这意味着该行没有数据。

这有可能导致你的程序因异常而崩溃。使用 tryexcept 语句来适当地捕获这种情况。思考一下:用 if 语句来防范错误数据会不会更好呢?

一旦你编写了 read_prices() 函数,通过交互式方式测试它,以确保它能正常工作:

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

练习 2.7:判断你是否可以退休

通过在你的 report.py 程序中添加一些额外的语句来计算收益/损失,将所有这些工作整合在一起。这些语句应该使用练习 2.5 中的股票列表和练习 2.6 中的价格字典,并计算投资组合的当前价值以及收益/损失。

总结

恭喜你!你已经完成了“容器”实验。你可以在 LabEx 中练习更多实验来提升你的技能。