作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。
介绍
Python 3 有许多内置的数据结构,包括元组、字典和列表。数据结构为我们提供了一种组织和存储数据的方法。该collections
模块帮助我们有效地填充和操作数据结构。
在本教程中,我们将通过三个类的collections
模块,以帮助您与元组,字典和列表的工作。我们将使用namedtuples
命名字段创建元组,defaultdict
在字典中简明地分组信息,并deque
有效地将元素添加到类似列表的对象的任一侧。
在本教程中,我们将主要处理在虚构的水族箱中添加或移除鱼类时需要修改的鱼类清单。
先决条件
为了充分利用本教程,建议您熟悉元组、字典和列表数据类型,包括它们的语法以及如何从中检索数据。您可以查看这些教程以获取必要的背景信息:
将命名字段添加到元组
Python 元组是不可变或不可更改的有序元素序列。元组经常用于表示列数据;例如,来自 CSV 文件的行或来自 SQL 数据库的行。水族馆可能会以一系列元组的形式跟踪其鱼类库存。
一个单独的鱼元组:
("Sammy", "shark", "tank-a")
这个元组由三个字符串元素组成。
虽然在某些方面很有用,但这个元组并没有清楚地表明它的每个字段代表什么。实际上,元素0
是名称,元素1
是物种,元素2
是储罐。
鱼元组字段说明:
名称 | 物种 | 坦克 |
---|---|---|
萨米 | 鲨鱼 | 坦克-a |
这张表清楚地表明,元组的三个元素中的每一个都有明确的含义。
namedtuple
从collections
模块中,您可以为元组的每个元素添加显式名称,以在 Python 程序中明确这些含义。
让我们namedtuple
用来生成一个类,它清楚地命名了鱼元组的每个元素:
from collections import namedtuple
Fish = namedtuple("Fish", ["name", "species", "tank"])
from collections import namedtuple
使您的 Python 程序可以访问namedtuple
工厂函数。该namedtuple()
函数调用返回绑定到该名称的类Fish
。该namedtuple()
函数有两个参数:我们新类的所需名称"Fish"
和命名元素列表["name", "species", "tank"]
。
我们可以使用Fish
该类来表示之前的鱼元组:
sammy = Fish("Sammy", "shark", "tank-a")
print(sammy)
如果我们运行此代码,我们将看到以下输出:
OutputFish(name='Sammy', species='shark', tank='tank-a')
sammy
使用Fish
类实例化。sammy
是一个包含三个明确命名的元素的元组。
sammy
的字段可以通过名称或传统的元组索引访问:
print(sammy.species)
print(sammy[1])
如果我们运行这两个print
调用,我们将看到以下输出:
Outputshark
shark
访问.species
返回与访问sammy
using的第二个元素相同的值[1]
。
使用namedtuple
fromcollections
模块使您的程序更具可读性,同时保持元组的重要属性(它们是不可变的和有序的)。
此外,namedtuple
工厂函数为Fish
.
用._asdict()
一个实例转换为词典:
print(sammy._asdict())
如果我们运行print
,您将看到如下输出:
Output{'name': 'Sammy', 'species': 'shark', 'tank': 'tank-a'}
调用.asdict()
onsammy
返回一个字典,将三个字段名称中的每一个映射到它们对应的值。
3.8 之前的 Python 版本可能会以稍微不同的方式输出此行。例如,您可能会看到OrderedDict
此处显示的而不是普通字典。
注意:在 Python 中,带有前导下划线的方法通常被认为是“私有的”。但是,由namedtuple
(如_asdict()
、._make()
、 ._replace()
等)提供的其他方法是 public。
在字典中收集数据
在 Python 字典中收集数据通常很有用。defaultdict
从collections
模块可以帮助我们快速、简洁地组装词典中的信息。
defaultdict
从不引发KeyError
. 如果键不存在,defaultdict
只需插入并返回一个占位符值:
from collections import defaultdict
my_defaultdict = defaultdict(list)
print(my_defaultdict["missing"])
如果我们运行此代码,我们将看到如下输出:
Output[]
defaultdict
插入并返回一个占位符值而不是抛出一个KeyError
. 在这种情况下,我们将占位符值指定为列表。
相比之下,常规词典会KeyError
在缺少键时抛出一个:
my_regular_dict = {}
my_regular_dict["missing"]
如果我们运行此代码,我们将看到如下输出:
OutputTraceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'missing'
当我们尝试访问不存在的键时,常规字典my_regular_dict
会引发 a KeyError
。
defaultdict
行为与常规字典不同。不是KeyError
在丢失的键上引发 a ,而是defaultdict
调用不带参数的占位符值来创建新对象。在这种情况下list()
创建一个空列表。
继续我们虚构的水族馆示例,假设我们有一个代表水族馆库存的鱼元组列表:
fish_inventory = [
("Sammy", "shark", "tank-a"),
("Jamie", "cuttlefish", "tank-b"),
("Mary", "squid", "tank-a"),
]
水族馆中存在三种鱼——它们的名称、种类和储水箱都在这三个元组中注明。
我们的目标是按坦克组织我们的库存——我们想知道每个坦克中的鱼列表。换句话说,我们希望映射的字典"tank-a"
来["Sammy", "Mary"]
和"tank-b"
到["Jamie"]
。
我们可以使用defaultdict
按坦克对鱼进行分组:
from collections import defaultdict
fish_inventory = [
("Sammy", "shark", "tank-a"),
("Jamie", "cuttlefish", "tank-b"),
("Mary", "squid", "tank-a"),
]
fish_names_by_tank = defaultdict(list)
for name, species, tank in fish_inventory:
fish_names_by_tank[tank].append(name)
print(fish_names_by_tank)
运行此代码,我们将看到以下输出:
Outputdefaultdict(<class 'list'>, {'tank-a': ['Sammy', 'Mary'], 'tank-b': ['Jamie']})
fish_names_by_tank
被声明为 a defaultdict
,默认为插入list()
而不是抛出 a KeyError
。由于这保证了每个键fish_names_by_tank
都指向 a list
,我们可以自由调用.append()
以将名称添加到每个坦克的列表中。
defaultdict
在这里可以帮助您,因为它减少了意外的机会KeyErrors
。减少意外KeyErrors
意味着您的程序可以编写得更清晰,行数更少。更具体地说,这个defaultdict
习惯用法可以让您避免为每个坦克手动实例化一个空列表。
如果没有defaultdict
,for
循环体可能看起来更像这样:
...
fish_names_by_tank = {}
for name, species, tank in fish_inventory:
if tank not in fish_names_by_tank:
fish_names_by_tank[tank] = []
fish_names_by_tank[tank].append(name)
仅使用常规字典(而不是 a defaultdict
)意味着for
循环体必须始终检查给定的tank
in是否存在fish_names_by_tank
。只有在我们验证tank
已经存在于 中fish_names_by_tank
,或者刚刚用 a 初始化之后[]
,我们才能附加鱼名。
defaultdict
在填写字典时可以帮助减少样板代码,因为它永远不会引发KeyError
.
使用 deque 有效地将元素添加到集合的任一侧
Python 列表是可变的或可变的、有序的元素序列。Python 可以在恒定时间内追加到列表(列表的长度对追加所需的时间没有影响),但在列表的开头插入可能会更慢——随着列表变大,所需的时间也会增加。
就大 O 表示法而言,附加到列表是一个常数时间O(1)
操作。相比之下,在列表的开头插入会降低O(n)
性能。
注意:软件工程师通常使用称为“大 O”符号的东西来衡量过程的性能。当输入的大小对执行过程所需的时间没有影响时,它被称为以恒定时间运行或O(1)
(“Big O of 1”)。正如您在上面了解到的,Python 可以附加到具有恒定时间性能的列表,也称为O(1)
.
有时,输入的大小直接影响运行过程所需的时间。例如,在 Python 列表的开头插入,列表中的元素越多,运行速度就越慢。Big O 表示法使用字母n
来表示输入的大小。这意味着将项目添加到 Python 列表的开头以“线性时间”或O(n)
(“n 的大 O”)运行。
一般来说,O(1)
程序比O(n)
程序更快。
我们可以在 Python 列表的开头插入:
favorite_fish_list = ["Sammy", "Jamie", "Mary"]
# O(n) performance
favorite_fish_list.insert(0, "Alice")
print(favorite_fish_list)
如果我们运行以下命令,我们将看到如下输出:
Output['Alice', 'Sammy', 'Jamie', 'Mary']
.insert(index, object)
list 上的方法允许我们"Alice"
在favorite_fish_list
. 值得注意的是,在列表的开头插入具有O(n)
性能。随着长度的favorite_fish_list
增长,在列表的开头插入一条鱼的时间会按比例增长,花费的时间也越来越长。
deque
collections
模块中的(发音为“deck”)是一个类似列表的对象,它允许我们在具有恒定时间 ( O(1)
) 性能的序列的开头或结尾插入项目。
在 a 的开头插入一个项目deque
:
from collections import deque
favorite_fish_deque = deque(["Sammy", "Jamie", "Mary"])
# O(1) performance
favorite_fish_deque.appendleft("Alice")
print(favorite_fish_deque)
运行此代码,我们将看到以下输出:
Outputdeque(['Alice', 'Sammy', 'Jamie', 'Mary'])
我们可以deque
使用预先存在的元素集合实例化 a ,在本例中是三个最喜欢的鱼名的列表。调用favorite_fish_deque
的appendleft
方法允许我们在集合的开头插入一个具有O(1)
性能的项目。O(1)
性能意味着favorite_fish_deque
即使favorite_fish_deque
有数千或数百万个元素,将项目添加到开头所花费的时间也不会增加。
注意:尽管deque
将条目添加到序列的开头比列表更有效,deque
但并不比列表更有效地执行其所有操作。例如,访问a中的随机项deque
有O(n)
性能,但访问列表中的随机项有O(1)
性能。使用deque
时插入或从您的收藏两侧快速清除元素是很重要的。Python 的 wiki 上提供了时间性能的完整比较。
结论
该collections
模块是 Python 标准库的强大组成部分,可让您简洁高效地处理数据。这个教程包括三个通过所提供的类的collections
模块,包括namedtuple
,defaultdict
,和deque
。
在这里,您可以使用该collection
模块的文档,详细了解其他可用的类和公用事业。要了解有关 Python 的更多信息,您可以阅读我们的如何在 Python 3 教程系列中编码。