如何在 Python 3 中使用集合模块

作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。

介绍

Python 3 有许多内置的数据结构,包括元组、字典和列表。数据结构为我们提供了一种组织和存储数据的方法。collections模块帮助我们有效地填充和操作数据结构。

在本教程中,我们将通过三个类collections模块,以帮助您与元组,字典和列表的工作。我们将使用namedtuples命名字段创建元组,defaultdict在字典中简明地分组信息,并deque有效地将元素添加到类似列表的对象的任一侧。

在本教程中,我们将主要处理在虚构的水族箱中添加或移除鱼类时需要修改的鱼类清单。

先决条件

为了充分利用本教程,建议您熟悉元组、字典和列表数据类型,包括它们的语法以及如何从中检索数据。您可以查看这些教程以获取必要的背景信息:

将命名字段添加到元组

Python 元组是不可变或不可更改的有序元素序列。元组经常用于表示列数据;例如,来自 CSV 文件的行或来自 SQL 数据库的行。水族馆可能会以一系列元组的形式跟踪其鱼类库存。

一个单独的鱼元组:

("Sammy", "shark", "tank-a")

这个元组由三个字符串元素组成。

虽然在某些方面很有用,但这个元组并没有清楚地表明它的每个字段代表什么。实际上,元素0是名称,元素1是物种,元素2是储罐。

鱼元组字段说明:

名称 物种 坦克
萨米 鲨鱼 坦克-a

这张表清楚地表明,元组的三个元素中的每一个都有明确的含义。

namedtuplecollections模块中,您可以为元组的每个元素添加显式名称,以在 Python 程序中明确这些含义。

让我们namedtuple用来生成一个类,它清楚地命名了鱼元组的每个元素:

from collections import namedtuple

Fish = namedtuple("Fish", ["name", "species", "tank"])

from collections import namedtuple使您的 Python 程序可以访问namedtuple工厂函数。namedtuple()函数调用返回绑定到该名称的类Fishnamedtuple()函数有两个参数:我们新类的所需名称"Fish"和命名元素列表["name", "species", "tank"]

我们可以使用Fish该类来表示之前的鱼元组:

sammy = Fish("Sammy", "shark", "tank-a")

print(sammy)

如果我们运行此代码,我们将看到以下输出:

Output
Fish(name='Sammy', species='shark', tank='tank-a')

sammy使用Fish实例化sammy是一个包含三个明确命名的元素的元组。

sammy的字段可以通过名称或传统的元组索引访问:

print(sammy.species)
print(sammy[1])

如果我们运行这两个print调用,我们将看到以下输出:

Output
shark shark

访问.species返回与访问sammyusing的第二个元素相同的值[1]

使用namedtuplefromcollections模块使您的程序更具可读性,同时保持元组的重要属性(它们是不可变的和有序的)。

此外,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 字典中收集数据通常很有用。defaultdictcollections模块可以帮助我们快速、简洁地组装词典中的信息。

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"]

如果我们运行此代码,我们将看到如下输出:

Output
Traceback (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)

运行此代码,我们将看到以下输出:

Output
defaultdict(<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习惯用法可以让您避免为每个坦克手动实例化一个空列表。

如果没有defaultdictfor循环体可能看起来更像这样:

没有 defaultdict 的更详细示例
...

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循环体必须始终检查给定的tankin是否存在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增长,在列表的开头插入一条鱼的时间会按比例增长,花费的时间也越来越长。

dequecollections模块中的(发音为“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)

运行此代码,我们将看到以下输出:

Output
deque(['Alice', 'Sammy', 'Jamie', 'Mary'])

我们可以deque使用预先存在的元素集合实例化 a ,在本例中是三个最喜欢的鱼名的列表。调用favorite_fish_dequeappendleft方法允许我们在集合的开头插入一个具有O(1)性能的项目。O(1)性能意味着favorite_fish_deque即使favorite_fish_deque有数千或数百万个元素项目添加到开头所花费的时间也不会增加

注意:尽管deque将条目添加到序列的开头比列表更有效,deque但并不比列表更有效地执行其所有操作。例如,访问a中的随机项dequeO(n)性能,但访问列表中的随机项有O(1)性能。使用deque时插入或从您的收藏两侧快速清除元素是很重要的。Python 的 wiki 上提供了时间性能的完整比较

结论

collections模块是 Python 标准库的强大组成部分,可让您简洁高效地处理数据。这个教程包括三个通过所提供的类的collections模块,包括namedtupledefaultdict,和deque

在这里,您可以使用collection模块的文档,详细了解其他可用的类和公用事业。要了解有关 Python 的更多信息,您可以阅读我们的如何在 Python 3 教程系列中编码

觉得文章有用?

点个广告表达一下你的爱意吧 !😁