collections — 容器数据类型

源代码: Lib/collections/__init__.py


此模块实现了专门的容器数据类型,为 Python 的通用内置容器 dictlistsettuple 提供了替代方案。

namedtuple()

用于创建具有命名字段的元组子类的工厂函数

deque

类似列表的容器,可以在两端快速添加和弹出元素

ChainMap

类似字典的类,用于创建多个映射的单个视图

Counter

用于计数 可哈希 对象的字典子类

OrderedDict

记住条目添加顺序的字典子类

defaultdict

调用工厂函数来提供缺失值的字典子类

UserDict

字典对象的包装器,用于更轻松地进行字典子类化

UserList

列表对象的包装器,用于更轻松地进行列表子类化

UserString

字符串对象的包装器,用于更轻松地进行字符串子类化

ChainMap 对象

3.3 版新增。

提供 ChainMap 类是为了快速链接多个映射,以便将它们视为单个单元。它通常比创建新字典并运行多个 update() 调用快得多。

该类可用于模拟嵌套作用域,在模板中很有用。

class collections.ChainMap(*maps)

ChainMap 将多个字典或其他映射组合在一起,以创建单个、可更新的视图。如果没有指定 *maps*,则提供一个空的字典,以便新链始终至少有一个映射。

底层映射存储在一个列表中。该列表是公共的,可以使用 *maps* 属性进行访问或更新。没有其他状态。

查找操作会依次搜索底层映射,直到找到键。相反,写入、更新和删除操作仅在第一个映射上进行。

ChainMap 通过引用合并底层映射。因此,如果其中一个底层映射被更新,这些更改将反映在 ChainMap 中。

支持所有常用的字典方法。此外,还有一个 *maps* 属性,一个用于创建新子上下文的方法,以及一个用于访问除第一个映射之外的所有映射的属性。

maps

用户可更新的映射列表。该列表按从先搜索到后搜索的顺序排列。它是唯一存储的状态,可以修改以更改搜索哪些映射。该列表应始终至少包含一个映射。

new_child(m=None, **kwargs)

返回一个新的 ChainMap,其中包含一个新映射,后跟当前实例中的所有映射。如果指定了 m,它将成为映射列表前面的新映射;如果没有指定,则使用空字典,因此调用 d.new_child() 等效于:ChainMap({}, *d.maps)。如果指定了任何关键字参数,它们将更新传递的映射或新的空字典。此方法用于创建子上下文,可以在不更改任何父映射中的值的情况下更新这些子上下文。

在 3.4 版更改: 添加了可选的 m 参数。

在 3.10 版更改: 添加了对关键字参数的支持。

parents

属性,返回一个新的 ChainMap,其中包含当前实例中除第一个映射之外的所有映射。这对于跳过搜索中的第一个映射很有用。用例类似于 nonlocal 关键字在 嵌套作用域 中的使用。用例也与内置 super() 函数的用例相似。对 d.parents 的引用等效于:ChainMap(*d.maps[1:])

请注意,ChainMap 的迭代顺序是通过从后到前扫描映射来确定的。

>>> baseline = {'music': 'bach', 'art': 'rembrandt'}
>>> adjustments = {'art': 'van gogh', 'opera': 'carmen'}
>>> list(ChainMap(adjustments, baseline))
['music', 'art', 'opera']

这提供了与从最后一个映射开始的一系列 dict.update() 调用相同的顺序。

>>> combined = baseline.copy()
>>> combined.update(adjustments)
>>> list(combined)
['music', 'art', 'opera']

3.9 版更改: 增加了对 ||= 运算符的支持,在 PEP 584 中有详细说明。

另请参阅

ChainMap 示例和方法

本节展示了使用链式映射的各种方法。

模拟 Python 内部查找链的示例

import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))

允许用户指定的命令行参数优先于环境变量,而环境变量又优先于默认值的示例

import os, argparse

defaults = {'color': 'red', 'user': 'guest'}

parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k: v for k, v in vars(namespace).items() if v is not None}

combined = ChainMap(command_line_args, os.environ, defaults)
print(combined['color'])
print(combined['user'])

使用 ChainMap 类模拟嵌套上下文的示例模式

c = ChainMap()        # Create root context
d = c.new_child()     # Create nested child context
e = c.new_child()     # Child of c, independent from d
e.maps[0]             # Current context dictionary -- like Python's locals()
e.maps[-1]            # Root context -- like Python's globals()
e.parents             # Enclosing context chain -- like Python's nonlocals

d['x'] = 1            # Set value in current context
d['x']                # Get first key in the chain of contexts
del d['x']            # Delete from current context
list(d)               # All nested values
k in d                # Check all nested values
len(d)                # Number of nested values
d.items()             # All nested items
dict(d)               # Flatten into a regular dictionary

ChainMap 类只对链中的第一个映射进行更新(写入和删除),而查找将搜索整个链。但是,如果需要深度写入和删除,则很容易创建一个子类来更新在链中更深处找到的键

class DeepChainMap(ChainMap):
    'Variant of ChainMap that allows direct updates to inner scopes'

    def __setitem__(self, key, value):
        for mapping in self.maps:
            if key in mapping:
                mapping[key] = value
                return
        self.maps[0][key] = value

    def __delitem__(self, key):
        for mapping in self.maps:
            if key in mapping:
                del mapping[key]
                return
        raise KeyError(key)

>>> d = DeepChainMap({'zebra': 'black'}, {'elephant': 'blue'}, {'lion': 'yellow'})
>>> d['lion'] = 'orange'         # update an existing key two levels down
>>> d['snake'] = 'red'           # new keys get added to the topmost dict
>>> del d['elephant']            # remove an existing key one level down
>>> d                            # display result
DeepChainMap({'zebra': 'black', 'snake': 'red'}, {}, {'lion': 'orange'})

Counter 对象

提供了一个计数器工具来支持方便快捷的计数。例如

>>> # Tally occurrences of words in a list
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
...     cnt[word] += 1
...
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})

>>> # Find the ten most common words in Hamlet
>>> import re
>>> words = re.findall(r'\w+', open('hamlet.txt').read().lower())
>>> Counter(words).most_common(10)
[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
 ('you', 554),  ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
class collections.Counter([iterable-or-mapping])

Counter 是一个 dict 子类,用于对 可哈希 对象进行计数。它是一个集合,其中元素存储为字典键,其计数存储为字典值。计数可以是任何整数值,包括零或负数。Counter 类类似于其他语言中的包或多重集。

元素从 iterable 中计数或从另一个 mapping(或计数器)初始化

>>> c = Counter()                           # a new, empty counter
>>> c = Counter('gallahad')                 # a new counter from an iterable
>>> c = Counter({'red': 4, 'blue': 2})      # a new counter from a mapping
>>> c = Counter(cats=4, dogs=8)             # a new counter from keyword args

Counter 对象具有字典接口,但它们返回缺失项的零计数,而不是引发 KeyError

>>> c = Counter(['eggs', 'ham'])
>>> c['bacon']                              # count of a missing element is zero
0

将计数设置为零不会从计数器中删除元素。使用 del 将其完全删除

>>> c['sausage'] = 0                        # counter entry with a zero count
>>> del c['sausage']                        # del actually removes the entry

3.1 版新增。

3.7 版更改: 作为 dict 的子类,Counter 继承了记住插入顺序的功能。对 Counter 对象的数学运算也保留顺序。结果的顺序取决于元素在左操作数中第一次出现的顺序,然后是右操作数中出现的顺序。

Counter 对象支持除所有字典可用的方法之外的其他方法

elements()

返回一个迭代器,迭代元素,每个元素重复其计数的次数。元素按首次遇到的顺序返回。如果元素的计数小于 1,则 elements() 将忽略它。

>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
most_common([n])

返回一个列表,其中包含 n 个最常见的元素及其计数,从最常见到最不常见。如果省略 n 或为 None,则 most_common() 返回计数器中的 所有 元素。具有相同计数的元素按首次遇到的顺序排序

>>> Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
subtract([iterable-or-mapping])

iterable 或另一个 mapping(或计数器)中减去元素。类似于 dict.update(),但减去计数而不是替换它们。输入和输出都可以为零或负数。

>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> d = Counter(a=1, b=2, c=3, d=4)
>>> c.subtract(d)
>>> c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})

3.2 版新增。

total()

计算计数的总和。

>>> c = Counter(a=10, b=5, c=0)
>>> c.total()
15

3.10 版新增。

常用的字典方法可用于 Counter 对象,但有两个方法对计数器的作用不同。

fromkeys(iterable)

此类方法未针对 Counter 对象实现。

update([iterable-or-mapping])

元素从 iterable 中计数或从另一个 mapping(或计数器)中添加。类似于 dict.update(),但添加计数而不是替换它们。此外,iterable 应该是元素序列,而不是 (key, value) 对的序列。

计数器支持用于相等性、子集和超集关系的丰富比较运算符:==!=<<=>>=。所有这些测试都将缺失元素视为计数为零,因此 Counter(a=1) == Counter(a=1, b=0) 返回 true。

3.10 版更改: 添加了丰富比较运算。

3.10 版更改: 在相等性测试中,缺失的元素将被视为计数为零。以前,Counter(a=3)Counter(a=3, b=0) 被认为是不同的。

使用 Counter 对象的常见模式

c.total()                       # total of all counts
c.clear()                       # reset all counts
list(c)                         # list unique elements
set(c)                          # convert to a set
dict(c)                         # convert to a regular dictionary
c.items()                       # convert to a list of (elem, cnt) pairs
Counter(dict(list_of_pairs))    # convert from a list of (elem, cnt) pairs
c.most_common()[:-n-1:-1]       # n least common elements
+c                              # remove zero and negative counts

提供了几种数学运算来组合 Counter 对象以生成多重集(计数大于零的计数器)。加法和减法通过对相应元素的计数进行加减来组合计数器。交集和并集返回相应计数的最小值和最大值。相等性和包含性比较相应的计数。每个操作都可以接受带符号计数的输入,但输出将排除计数为零或更小的结果。

>>> c = Counter(a=3, b=1)
>>> d = Counter(a=1, b=2)
>>> c + d                       # add two counters together:  c[x] + d[x]
Counter({'a': 4, 'b': 3})
>>> c - d                       # subtract (keeping only positive counts)
Counter({'a': 2})
>>> c & d                       # intersection:  min(c[x], d[x])
Counter({'a': 1, 'b': 1})
>>> c | d                       # union:  max(c[x], d[x])
Counter({'a': 3, 'b': 2})
>>> c == d                      # equality:  c[x] == d[x]
False
>>> c <= d                      # inclusion:  c[x] <= d[x]
False

一元加法和减法是添加空计数器或从空计数器中减去的快捷方式。

>>> c = Counter(a=2, b=-4)
>>> +c
Counter({'a': 2})
>>> -c
Counter({'b': 4})

3.3 版新增: 添加了对一元加法、一元减法和就地多重集操作的支持。

注意

计数器主要设计用于处理正整数以表示运行计数;但是,我们也注意不要不必要地排除需要其他类型或负值的用例。为了帮助处理这些用例,本节记录了最小范围和类型限制。

  • Counter 类本身是一个字典子类,对其键和值没有限制。这些值旨在表示计数的数字,但您*可以*在值字段中存储任何内容。

  • most_common() 方法只要求值是可排序的。

  • 对于就地操作,例如 c[key] += 1,值类型只需要支持加法和减法。因此,分数、浮点数和小数都可以使用,并且支持负值。对于允许输入和输出为负值和零值的 update()subtract() 也是如此。

  • 多重集方法仅设计用于具有正值的用例。输入可以是负数或零,但只创建具有正值的输出。没有类型限制,但值类型需要支持加法、减法和比较。

  • elements() 方法需要整数计数。它忽略零和负计数。

另请参阅

  • Smalltalk 中的 Bag 类

  • 多重集 的维基百科条目。

  • 包含示例的 C++ 多重集 教程。

  • 有关多重集的数学运算及其用例,请参阅*Knuth, Donald. 计算机程序设计艺术,卷 II,第 4.6.3 节,练习 19*。

  • 要枚举给定元素集上给定大小的所有不同多重集,请参阅 itertools.combinations_with_replacement()

    map(Counter, combinations_with_replacement('ABC', 2)) # --> AA AB AC BB BC CC
    

deque 对象

class collections.deque([iterable[, maxlen]])

返回一个新的 deque 对象,该对象使用来自 *iterable* 的数据从左到右初始化(使用 append())。如果未指定 *iterable*,则新的 deque 为空。

Deque 是栈和队列的泛化(名称发音为“deck”,是“双端队列”的缩写)。Deque 支持线程安全、内存高效的从 deque 两端的追加和弹出操作,并且在两个方向上的性能都接近 *O*(1)。

虽然 list 对象支持类似的操作,但它们针对快速固定长度操作进行了优化,并且对于 pop(0)insert(0, v) 操作会产生 *O*(*n*) 内存移动成本,这些操作会同时更改底层数据表示的大小和位置。

如果未指定 *maxlen* 或为 None,则 deque 的长度可以任意增长。否则,deque 的长度将限制为指定的最大长度。一旦有界长度的 deque 已满,当添加新项目时,将从另一端丢弃相应数量的项目。有界长度的 deque 提供的功能类似于 Unix 中的 tail 过滤器。它们还可用于跟踪事务和其他数据池,在这些事务和数据池中,只有最近的活动是重要的。

Deque 对象支持以下方法

append(x)

将 *x* 添加到 deque 的右侧。

appendleft(x)

将 *x* 添加到 deque 的左侧。

clear()

从 deque 中删除所有元素,使其长度为 0。

copy()

创建 deque 的浅拷贝。

3.5 版新增。

count(x)

计算 deque 中等于 *x* 的元素数量。

3.2 版新增。

extend(iterable)

通过追加可迭代参数中的元素来扩展 deque 的右侧。

extendleft(iterable)

通过追加 *iterable* 中的元素来扩展 deque 的左侧。请注意,一系列左追加操作会导致可迭代参数中元素的顺序反转。

index(x[, start[, stop]])

返回 deque 中 *x* 的位置(在索引 *start* 处或之后,以及在索引 *stop* 之前)。返回第一个匹配项,如果未找到,则引发 ValueError

3.5 版新增。

insert(i, x)

在位置 *i* 处将 *x* 插入 deque。

如果插入操作会导致有界 deque 的大小超过 *maxlen*,则会引发 IndexError

3.5 版新增。

pop()

从双端队列的右侧移除并返回一个元素。如果队列中没有元素,则引发 IndexError

pop()

从双端队列的左侧移除并返回一个元素。如果队列中没有元素,则引发 IndexError

popleft()

remove(value)

移除第一次出现的 value。如果没有找到,则引发 ValueError

reverse()

3.2 版新增。

反转双端队列中元素的顺序,并返回 None

rotate(n=1)

将双端队列向右旋转 n 步。如果 n 为负数,则向左旋转。

当双端队列不为空时,向右旋转一步等价于 d.appendleft(d.pop()),向左旋转一步等价于 d.append(d.popleft())

双端队列对象还提供了一个只读属性

maxlen

3.1 版新增。

双端队列的最大大小,如果无界则为 None

除了以上内容之外,双端队列还支持迭代、序列化、len(d)reversed(d)copy.copy(d)copy.deepcopy(d)、使用 in 运算符进行成员测试,以及使用下标引用(例如 d[0])访问第一个元素。索引访问在两端都是 O(1) 的时间复杂度,但在中间会降至 O(n)。如果需要快速随机访问,请改用列表。

从版本 3.5 开始,双端队列支持 __add__()__mul__()__imul__()

>>> from collections import deque
>>> d = deque('ghi')                 # make a new deque with three items
>>> for elem in d:                   # iterate over the deque's elements
...     print(elem.upper())
G
H
I

>>> d.append('j')                    # add a new entry to the right side
>>> d.appendleft('f')                # add a new entry to the left side
>>> d                                # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j'])

>>> d.pop()                          # return and remove the rightmost item
'j'
>>> d.popleft()                      # return and remove the leftmost item
'f'
>>> list(d)                          # list the contents of the deque
['g', 'h', 'i']
>>> d[0]                             # peek at leftmost item
'g'
>>> d[-1]                            # peek at rightmost item
'i'

>>> list(reversed(d))                # list the contents of a deque in reverse
['i', 'h', 'g']
>>> 'h' in d                         # search the deque
True
>>> d.extend('jkl')                  # add multiple elements at once
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> d.rotate(1)                      # right rotation
>>> d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1)                     # left rotation
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])

>>> deque(reversed(d))               # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear()                        # empty the deque
>>> d.pop()                          # cannot pop from an empty deque
Traceback (most recent call last):
    File "<pyshell#6>", line 1, in -toplevel-
        d.pop()
IndexError: pop from an empty deque

>>> d.extendleft('abc')              # extendleft() reverses the input order
>>> d
deque(['c', 'b', 'a'])

示例

deque 配方

本节展示了使用双端队列的各种方法。

def tail(filename, n=10):
    'Return the last n lines of a file'
    with open(filename) as f:
        return deque(f, n)

有界长度的双端队列提供了类似于 Unix 中 tail 过滤器的功能

def moving_average(iterable, n=3):
    # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # https://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable)
    d = deque(itertools.islice(it, n-1))
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

使用双端队列的另一种方法是通过向右添加元素和从左弹出元素来维护最近添加的元素序列

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    iterators = deque(map(iter, iterables))
    while iterators:
        try:
            while True:
                yield next(iterators[0])
                iterators.rotate(-1)
        except StopIteration:
            # Remove an exhausted iterator.
            iterators.popleft()

可以使用存储在 deque 中的输入迭代器来实现 循环调度器。值从位置 0 处的活动迭代器中产生。如果该迭代器已耗尽,则可以使用 popleft() 将其移除;否则,可以使用 rotate() 方法将其循环回到末尾

def delete_nth(d, n):
    d.rotate(-n)
    d.popleft()
    d.rotate(n)

rotate() 方法提供了一种实现 deque 切片和删除的方法。例如,del d[n] 的纯 Python 实现依赖于 rotate() 方法来定位要弹出的元素

要实现 deque 切片,请使用类似的方法,应用 rotate() 将目标元素移动到双端队列的左侧。使用 popleft() 移除旧条目,使用 extend() 添加新条目,然后反转旋转。通过对该方法进行少量修改,可以轻松实现 Forth 风格的堆栈操作,例如 dupdropswapoverpickrotroll

defaultdict 对象

class collections.defaultdict(default_factory=None, /[, ...])

返回一个新的类似字典的对象。 defaultdict 是内置 dict 类的子类。它重写了一个方法并添加了一个可写的实例变量。其余功能与 dict 类相同,此处不再赘述。

第一个参数为 default_factory 属性提供初始值;默认为 None。所有剩余参数的处理方式与传递给 dict 构造函数相同,包括关键字参数。

defaultdict 对象除了标准的 dict 操作外,还支持以下方法

__missing__(key)

如果 default_factory 属性为 None,则引发 KeyError 异常,并将 key 作为参数。

如果 default_factory 不为 None,则在不带参数的情况下调用它,为给定的 key 提供默认值,该值将插入到字典中对应 key 的位置,并返回该值。

当请求的键不存在时,dict 类的 __getitem__() 方法会调用此方法;无论它返回什么或引发什么异常,都会由 __getitem__() 返回或引发。

请注意,除了 __getitem__() 之外的任何操作都*不会*调用 __missing__()。这意味着 get() 会像普通字典一样返回 None 作为默认值,而不是使用 default_factory

defaultdict 对象支持以下实例变量

default_factory

__missing__() 方法使用此属性;它从构造函数的第一个参数初始化,如果存在的话,或者初始化为 None,如果不存在的话。

在 3.9 版更改: 添加了合并(|)和更新(|=)运算符,在 PEP 584 中指定。

defaultdict 示例

使用 list 作为 default_factory,可以轻松地将键值对序列分组到列表字典中

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

当每个键第一次遇到时,它不在映射中;因此,使用返回空 listdefault_factory 函数自动创建一个条目。list.append() 操作然后将值附加到新列表中。当再次遇到键时,查找将正常进行(返回该键的列表),并且 list.append() 操作将另一个值添加到列表中。这种技术比使用 dict.setdefault() 的等效技术更简单、更快

>>> d = {}
>>> for k, v in s:
...     d.setdefault(k, []).append(v)
...
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

default_factory 设置为 int 使 defaultdict 对计数很有用(就像其他语言中的包或多重集)

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> sorted(d.items())
[('i', 4), ('m', 1), ('p', 2), ('s', 4)]

当第一次遇到字母时,它在映射中丢失,因此 default_factory 函数调用 int() 来提供默认计数零。然后,递增操作会累加每个字母的计数。

始终返回零的函数 int() 只是常量函数的一个特例。创建常量函数的一种更快、更灵活的方法是使用 lambda 函数,它可以提供任何常量值(而不仅仅是零)

>>> def constant_factory(value):
...     return lambda: value
...
>>> d = defaultdict(constant_factory('<missing>'))
>>> d.update(name='John', action='ran')
>>> '%(name)s %(action)s to %(object)s' % d
'John ran to <missing>'

default_factory 设置为 set 使 defaultdict 对构建集合字典很有用

>>> s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
>>> d = defaultdict(set)
>>> for k, v in s:
...     d[k].add(v)
...
>>> sorted(d.items())
[('blue', {2, 4}), ('red', {1, 3})]

namedtuple() 具有命名字段的元组的工厂函数

命名元组为元组中的每个位置赋予含义,并允许更具可读性和自文档化的代码。它们可以在使用常规元组的任何地方使用,并且它们添加了通过名称而不是位置索引访问字段的功能。

collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)

返回一个名为 *typename* 的新元组子类。新子类用于创建类似元组的对象,这些对象可以通过属性查找访问字段,并且可以索引和迭代。子类的实例还有一个有用的文档字符串(包含 *typename* 和 *field_names*)和一个有用的 __repr__() 方法,该方法以 name=value 格式列出元组内容。

*field_names* 是一个字符串序列,例如 ['x', 'y']。或者,*field_names* 可以是一个字符串,每个字段名用空格和/或逗号分隔,例如 'x y''x, y'

任何有效的 Python 标识符都可以用作字段名,但以下划线开头的名称除外。有效的标识符由字母、数字和下划线组成,但不能以数字或下划线开头,也不能是 keyword,例如 *class*、*for*、*return*、*global*、*pass* 或 *raise*。

如果 rename 为真,则无效的字段名将自动替换为位置名称。例如,['abc', 'def', 'ghi', 'abc'] 会被转换为 ['abc', '_1', 'ghi', '_3'],从而消除关键字 def 和重复的字段名 abc

defaults 可以是 None 或一个默认值的 可迭代对象。由于具有默认值的字段必须位于任何没有默认值的字段之后,因此 defaults 将应用于最右边的参数。例如,如果字段名为 ['x', 'y', 'z'] 且默认值为 (1, 2),则 x 将是必需参数,y 将默认为 1z 将默认为 2

如果定义了 module,则命名元组的 __module__ 属性将设置为该值。

命名元组实例没有每个实例的字典,因此它们很轻量级,并且需要的内存不比常规元组多。

为了支持序列化,命名元组类应该赋值给一个与 typename 匹配的变量。

版本 3.1 中的变化: 添加了对 rename 的支持。

版本 3.6 中的变化: verboserename 参数变为 仅关键字参数

版本 3.6 中的变化: 添加了 module 参数。

版本 3.7 中的变化: 删除了 verbose 参数和 _source 属性。

版本 3.7 中的变化: 添加了 defaults 参数和 _field_defaults 属性。

>>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)

命名元组在为 csvsqlite3 模块返回的结果元组分配字段名时特别有用。

EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print(emp.name, emp.title)

import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
    print(emp.name, emp.title)

除了从元组继承的方法外,命名元组还支持三个附加方法和两个属性。为了防止与字段名冲突,方法和属性名以下划线开头。

classmethod somenamedtuple._make(iterable)

从现有序列或可迭代对象创建新实例的类方法。

>>> t = [11, 22]
>>> Point._make(t)
Point(x=11, y=22)
somenamedtuple._asdict()

返回一个新的 dict,它将字段名映射到它们对应的值。

>>> p = Point(x=11, y=22)
>>> p._asdict()
{'x': 11, 'y': 22}

版本 3.1 中的变化: 返回 OrderedDict 而不是常规的 dict

版本 3.8 中的变化: 返回常规的 dict 而不是 OrderedDict。从 Python 3.7 开始,常规字典保证是有序的。如果需要 OrderedDict 的额外功能,建议的补救措施是将结果转换为所需的类型:OrderedDict(nt._asdict())

somenamedtuple._replace(**kwargs)

返回命名元组的新实例,将指定的字段替换为新值。

>>> p = Point(x=11, y=22)
>>> p._replace(x=33)
Point(x=33, y=22)

>>> for partnum, record in inventory.items():
...     inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now())
somenamedtuple._fields

列出字段名的字符串元组。对于自省以及从现有命名元组创建新的命名元组类型很有用。

>>> p._fields            # view the field names
('x', 'y')

>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)
somenamedtuple._field_defaults

将字段名映射到默认值的字典。

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._field_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

要检索名称存储在字符串中的字段,请使用 getattr() 函数。

>>> getattr(p, 'x')
11

要将字典转换为命名元组,请使用双星号运算符(如 解包参数列表 中所述)。

>>> d = {'x': 11, 'y': 22}
>>> Point(**d)
Point(x=11, y=22)

由于命名元组是常规的 Python 类,因此可以使用子类轻松添加或更改功能。以下是如何添加计算字段和固定宽度打印格式:

>>> class Point(namedtuple('Point', ['x', 'y'])):
...     __slots__ = ()
...     @property
...     def hypot(self):
...         return (self.x ** 2 + self.y ** 2) ** 0.5
...     def __str__(self):
...         return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

>>> for p in Point(3, 4), Point(14, 5/7):
...     print(p)
Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

上面显示的子类将 __slots__ 设置为空元组。这有助于通过防止创建实例字典来保持较低的内存需求。

子类化对于添加新的存储字段没有用。相反,只需从 _fields 属性创建一个新的命名元组类型。

>>> Point3D = namedtuple('Point3D', Point._fields + ('z',))

可以通过直接赋值给 __doc__ 字段来自定义文档字符串。

>>> Book = namedtuple('Book', ['id', 'title', 'authors'])
>>> Book.__doc__ += ': Hardcover book in active collection'
>>> Book.id.__doc__ = '13-digit ISBN'
>>> Book.title.__doc__ = 'Title of first printing'
>>> Book.authors.__doc__ = 'List of authors sorted by last name'

版本 3.5 中的变化: 属性文档字符串变为可写的。

另请参阅

  • 有关为命名元组添加类型提示的方法,请参阅 typing.NamedTuple。它还提供了一种使用 class 关键字的优雅表示法。

    class Component(NamedTuple):
        part_number: int
        weight: float
        description: Optional[str] = None
    
  • 有关基于底层字典而不是元组的可变命名空间,请参阅 types.SimpleNamespace()

  • dataclasses 模块提供了一个装饰器和函数,用于自动向用户定义的类添加生成的特殊方法。

OrderedDict 对象

有序字典与常规字典类似,但具有一些与排序操作相关的额外功能。现在,内置的 dict 类能够记住插入顺序(此新行为在 Python 3.7 中得到保证),因此它们变得不那么重要了。

dict 的一些区别仍然存在

  • 常规的 dict 被设计为非常擅长映射操作。跟踪插入顺序是次要的。

  • OrderedDict 被设计为擅长重新排序操作。空间效率、迭代速度和更新操作的性能是次要的。

  • OrderedDict 算法可以比 dict 更好地处理频繁的重新排序操作。如下面的方法所示,这使得它适合于实现各种 LRU 缓存。

  • OrderedDict 的相等运算符检查匹配顺序。

    常规的 dict 可以使用 p == q and all(k1 == k2 for k1, k2 in zip(p, q)) 来模拟顺序敏感的相等性测试。

  • OrderedDictpopitem() 方法具有不同的签名。它接受一个可选参数来指定弹出哪个项目。

    常规的 dict 可以使用 d.popitem() 来模拟 OrderedDict 的 od.popitem(last=True),这保证弹出最右边的(最后一个)项目。

    常规的 dict 可以使用 (k := next(iter(d)), d.pop(k)) 来模拟 OrderedDict 的 od.popitem(last=False),如果存在,它将返回并删除最左边的(第一个)项目。

  • OrderedDict 有一个 move_to_end() 方法,可以有效地将元素重新定位到端点。

    常规的 dict 可以使用 d[k] = d.pop(k) 来模拟 OrderedDict 的 od.move_to_end(k, last=True),这会将键及其关联的值移动到最右边(最后一个)位置。

    常规的 dict 没有与 OrderedDict 的 od.move_to_end(k, last=False) 等效的有效方法,后者将键及其关联的值移动到最左边(第一个)位置。

  • 在 Python 3.8 之前,dict 缺少 __reversed__() 方法。

class collections.OrderedDict([items])

返回 dict 子类的实例,该子类具有专门用于重新排列字典顺序的方法。

3.1 版新增。

popitem(last=True)

有序字典的 popitem() 方法返回并删除一个 (key, value) 对。如果 *last* 为 true,则按 LIFO 顺序返回对;如果为 false,则按 FIFO 顺序返回对。

move_to_end(key, last=True)

将现有的 *key* 移动到有序字典的任一端。如果 *last* 为 true(默认值),则项目移动到右端;如果 *last* 为 false,则移动到开头。如果 *key* 不存在,则引发 KeyError

>>> d = OrderedDict.fromkeys('abcde')
>>> d.move_to_end('b')
>>> ''.join(d)
'acdeb'
>>> d.move_to_end('b', last=False)
>>> ''.join(d)
'bacde'

3.2 版新增。

除了常用的映射方法外,有序字典还支持使用 reversed() 进行反向迭代。

OrderedDict 对象之间的相等性测试对顺序敏感,并实现为 list(od1.items())==list(od2.items())OrderedDict 对象与其他 Mapping 对象之间的相等性测试与常规字典一样对顺序不敏感。这允许在使用常规字典的任何地方替换 OrderedDict 对象。

版本 3.5 中的变化: OrderedDict 的项目、键和值 视图 现在支持使用 reversed() 进行反向迭代。

版本 3.6 中的变化: 随着 PEP 468 的接受,传递给 OrderedDict 构造函数及其 update() 方法的关键字参数的顺序将保留。

版本 3.9 中的变化: 添加了合并 (|) 和更新 (|=) 运算符,在 PEP 584 中指定。

OrderedDict 示例和方法

创建一个有序字典变体很简单,它可以记住键 *最后* 插入的顺序。如果新条目覆盖现有条目,则原始插入位置将更改并移动到末尾

class LastUpdatedOrderedDict(OrderedDict):
    'Store items in the order the keys were last added'

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self.move_to_end(key)

OrderedDict 对于实现 functools.lru_cache() 的变体也很有用

from collections import OrderedDict
from time import time

class TimeBoundedLRU:
    "LRU Cache that invalidates and refreshes old entries."

    def __init__(self, func, maxsize=128, maxage=30):
        self.cache = OrderedDict()      # { args : (timestamp, result)}
        self.func = func
        self.maxsize = maxsize
        self.maxage = maxage

    def __call__(self, *args):
        if args in self.cache:
            self.cache.move_to_end(args)
            timestamp, result = self.cache[args]
            if time() - timestamp <= self.maxage:
                return result
        result = self.func(*args)
        self.cache[args] = time(), result
        if len(self.cache) > self.maxsize:
            self.cache.popitem(0)
        return result
class MultiHitLRUCache:
    """ LRU cache that defers caching a result until
        it has been requested multiple times.

        To avoid flushing the LRU cache with one-time requests,
        we don't cache until a request has been made more than once.

    """

    def __init__(self, func, maxsize=128, maxrequests=4096, cache_after=1):
        self.requests = OrderedDict()   # { uncached_key : request_count }
        self.cache = OrderedDict()      # { cached_key : function_result }
        self.func = func
        self.maxrequests = maxrequests  # max number of uncached requests
        self.maxsize = maxsize          # max number of stored return values
        self.cache_after = cache_after

    def __call__(self, *args):
        if args in self.cache:
            self.cache.move_to_end(args)
            return self.cache[args]
        result = self.func(*args)
        self.requests[args] = self.requests.get(args, 0) + 1
        if self.requests[args] <= self.cache_after:
            self.requests.move_to_end(args)
            if len(self.requests) > self.maxrequests:
                self.requests.popitem(0)
        else:
            self.requests.pop(args, None)
            self.cache[args] = result
            if len(self.cache) > self.maxsize:
                self.cache.popitem(0)
        return result

UserDict 对象

UserDict 类充当字典对象的包装器。由于可以直接从 dict 类继承,因此对该类的需求已部分被取代;但是,此类使用起来可能更容易,因为底层字典可以作为属性访问。

class collections.UserDict([initialdata])

模拟字典的类。实例的内容保存在一个常规字典中,可以通过 UserDict 实例的 data 属性访问该字典。如果提供了 initialdata,则使用其内容初始化 data;请注意,不会保留对 initialdata 的引用,从而允许将其用于其他用途。

除了支持映射的方法和操作外,UserDict 实例还提供以下属性

data

用于存储 UserDict 类内容的真实字典。

UserList 对象

此类充当列表对象的包装器。它是您自己的类似列表的类的有用基类,这些类可以从它们继承并覆盖现有方法或添加新方法。通过这种方式,可以向列表添加新行为。

由于可以直接从 list 类继承,因此对该类的需求已部分被取代;但是,此类使用起来可能更容易,因为底层列表可以作为属性访问。

class collections.UserList([list])

模拟列表的类。实例的内容保存在一个常规列表中,可以通过 UserList 实例的 data 属性访问该列表。实例的内容最初设置为 list 的副本,默认为空列表 []list 可以是任何可迭代对象,例如真实的 Python 列表或 UserList 对象。

除了支持可变序列的方法和操作外,UserList 实例还提供以下属性

data

用于存储 UserList 类内容的真实 list 对象。

**子类化要求:**UserList 的子类应提供一个可以不带参数或带一个参数调用的构造函数。返回新序列的列表操作会尝试创建实际实现类的实例。为此,它假定可以使用单个参数调用构造函数,该参数是用作数据源的序列对象。

如果派生类不希望遵守此要求,则需要覆盖此类支持的所有特殊方法;有关在这种情况下需要提供的方法的信息,请参阅相关资源。

UserString 对象

UserString 类充当字符串对象的包装器。由于可以直接从 str 类继承,因此对该类的需求已部分被取代;但是,此类使用起来可能更容易,因为底层字符串可以作为属性访问。

class collections.UserString(seq)

模拟字符串对象的类。实例的内容保存在一个常规字符串对象中,可以通过 UserString 实例的 data 属性访问该字符串对象。实例的内容最初设置为 seq 的副本。seq 参数可以是任何可以使用内置 str() 函数转换为字符串的对象。

除了支持字符串的方法和操作外,UserString 实例还提供以下属性

data

用于存储 UserString 类内容的真实 str 对象。

3.5 版更改: 新增方法 __getnewargs____rmod__casefoldformat_mapisprintablemaketrans