集合 — 容器数据类型 — Python 文档

来自菜鸟教程
Python/docs/3.7/library/collections
跳转至:导航、​搜索

collections — 容器数据类型

源代码: :source:`Lib/collections/__init__.py`



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

namedtuple() 用于创建具有命名字段的元组子类的工厂函数
deque 类似列表的容器,两端具有快速追加和弹出
ChainMap 用于创建多个映射的单个视图的类 dict 类
Counter 用于计算可散列对象的 dict 子类
OrderedDict 添加了记住订单条目的 dict 子类
defaultdict dict 子类调用工厂函数来提供缺失值
UserDict 围绕字典对象的包装器,以便于 dict 子类化
UserList 列表对象的包装器,以便于列表子类化
UserString 围绕字符串对象的包装器,以便于字符串子类化

ChainMap 对象

3.3 版中的新功能。


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

该类可用于模拟嵌套作用域,并可用于模板化。

class collections.ChainMap(*maps)

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

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

查找会连续搜索底层映射,直到找到一个键。 相比之下,写入、更新和删除仅对第一个映射进行操作。

ChainMap 通过引用合并了底层映射。 因此,如果底层映射之一得到更新,这些更改将反映在 ChainMap 中。

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

maps

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

new_child(m=None)

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

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

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

也可以看看


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'})

计数器对象

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

>>> # 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 类类似于其他语言中的 bag 或 multisets。

元素从 可迭代 计数或从另一个 映射 (或计数器)初始化:

>>> 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 版中的新功能。

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 最常见元素的列表及其从最常见到最少的计数。 如果省略 nNone,则 most_common() 返回计数器中的 all 元素。 具有相等计数的元素被任意排序:

>>> Counter('abracadabra').most_common(3)  
[('a', 5), ('r', 2), ('b', 2)]
subtract([iterable-or-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 版中的新功能。

通常的字典方法可用于 Counter 对象,除了两个对计数器的工作方式不同。

fromkeys(iterable)

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

update([iterable-or-mapping])

元素从 可迭代 计数或从另一个 映射 (或计数器)添加。 像 dict.update() 但增加计数而不是替换它们。 此外,iterable 应该是一个元素序列,而不是一个 (key, value) 对序列。

使用 Counter 对象的常见模式:

sum(c.values())                 # 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 = 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 中的包类

  • Multisets 的维基百科条目。

  • C++ multisets 教程示例。

  • 有关多重集及其用例的数学运算,请参阅克努斯,唐纳德。 计算机编程艺术第二卷,第 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 对象(使用 append()),数据来自 iterable。 如果未指定 iterable,则新的双端队列为空。

双端队列是栈和队列的泛化(名称发音为“deck”,是“双端队列”的缩写)。 双端队列支持线程安全、内存高效的追加和从双端队列的任一侧弹出,在任一方向上的 O(1) 性能大致相同。

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

如果未指定 maxlenNone,则双端队列可能会增长到任意长度。 否则,双端队列受限于指定的最大长度。 一旦有界长度的双端队列已满,当添加新项目时,从另一端丢弃相应数量的项目。 有界长度的双端队列提供类似于 Unix 中的 tail 过滤器的功能。 它们还可用于跟踪仅对最近的活动感兴趣的交易和其他数据池。

Deque 对象支持以下方法:

append(x)

x 添加到双端队列的右侧。

appendleft(x)

x 添加到双端队列的左侧。

clear()

从双端队列中删除所有元素,使其长度为 0。

copy()

创建双端队列的浅拷贝。

3.5 版中的新功能。

count(x)

计算等于 x 的双端队列元素的数量。

3.2 版中的新功能。

extend(iterable)

通过附加来自可迭代参数的元素来扩展双端队列的右侧。

extendleft(iterable)

通过附加来自 iterable 的元素来扩展双端队列的左侧。 请注意,一系列左追加会导致可迭代参数中元素的顺序颠倒。

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

返回 x 在双端队列中的位置(在索引 start 处或之后和索引 stop 之前)。 如果未找到,则返回第一个匹配项或引发 ValueError

3.5 版中的新功能。

insert(i, x)

x 插入双端队列中的位置 i

如果插入会导致有界双端队列增长超过 maxlen,则会引发 IndexError

3.5 版中的新功能。

pop()

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

popleft()

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

remove(value)

删除第一次出现的 。 如果未找到,则引发 ValueError

reverse()

原地反转双端队列的元素,然后返回 None

3.2 版中的新功能。

rotate(n=1)

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

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

Deque 对象还提供了一个只读属性:

maxlen

双端队列的最大大小或 None 如果无界。

3.1 版中的新功能。

除上述之外,双端队列支持迭代、酸洗、len(d)reversed(d)copy.copy(d)copy.deepcopy(d)in的成员资格测试] 运算符,以及下标引用,例如 d[-1]。 索引访问在两端都是 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 食谱

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

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

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

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

def moving_average(iterable, n=3):
    # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # http://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

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

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

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

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

要实现 deque 切片,请使用类似的方法应用 rotate() 将目标元素带到双端队列的左侧。 使用 popleft() 删除旧条目,使用 extend() 添加新条目,然后反向旋转。 通过对该方法的微小改动,很容易实现 Forth 风格的堆栈操作,例如 dupdropswapover、[ X152X]、rotroll


defaultdict 对象

class collections.defaultdict([default_factory[, ...]])

返回一个新的类似字典的对象。 defaultdict 是内置的 dict 类的子类。 它覆盖了一种方法并添加了一个可写的实例变量。 其余功能与 dict 类相同,此处未记录。

第一个参数提供 default_factory 属性的初始值; 默认为 None。 所有剩余的参数都被视为传递给 dict 构造函数,包括关键字参数。

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

__missing__(key)

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

如果 default_factory 不是 None,则不带参数调用它以提供给定 key 的默认值,该值被插入到 的字典中键,并返回。

如果调用 default_factory 引发异常,则此异常将保持不变地传播。

该方法在没有找到请求的key时由dict类的__getitem__()方法调用; 无论它返回或引发什么,然后由 __getitem__() 返回或引发。

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

defaultdict 对象支持以下实例变量:

default_factory

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

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

第一次遇到每个键时,它尚未在映射中; 因此使用 default_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 标识符都可用于字段名。 有效标识符由字母、数字和下划线组成,但不以数字或下划线开头,并且不能是 关键字 ,例如 classfor]returnglobalpassraise

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

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

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

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

3.1 版更改: 添加了对 重命名 的支持。

3.6 版更改: verboserename 参数变为 仅关键字参数

3.6 版更改: 增加了 模块 参数。

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()
OrderedDict([('x', 11), ('y', 22)])

在 3.1 版中更改: 返回 OrderedDict 而不是常规的 dict

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 版更改: 属性文档字符串变为可写。


可以通过使用 _replace() 自定义原型实例来实现默认值:

>>> Account = namedtuple('Account', 'owner balance transaction_count')
>>> default_account = Account('<owner name>', 0.0, 0)
>>> johns_account = default_account._replace(owner='John')
>>> janes_account = default_account._replace(owner='Jane')

也可以看看

  • 有关为命名元组添加类型提示的方法,请参阅 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 的相等运算检查匹配顺序。
  • OrderedDictpopitem() 方法具有不同的签名。 它接受一个可选参数来指定弹出哪个项目。
  • OrderedDict 有一个 move_to_end() 方法可以有效地将元素重新定位到端点。
  • 在 Python 3.8 之前, dict 缺少 __reversed__() 方法。
class collections.OrderedDict([items])

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

3.1 版中的新功能。

popitem(last=True)

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

move_to_end(key, last=True)

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

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

3.2 版中的新功能。

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

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

3.5 版更改: OrderedDict 的项目、键和值 views 现在支持使用 reversed() 进行反向迭代。


在 3.6 版更改:随着接受 PEP 468,保留传递给 OrderedDict 构造函数及其 [ X178X] 方法。


OrderedDict 例子和食谱

创建一个有序的字典变体很简单,它记住键 last 插入的顺序。 如果新条目覆盖现有条目,则更改原始插入位置并移至末尾:

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

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

OrderedDict 也可用于实现 functools.lru_cache() 的变体:

class LRU(OrderedDict):
    'Limit size, evicting the least recently looked-up key when full'

    def __init__(self, maxsize=128, *args, **kwds):
        self.maxsize = maxsize
        super().__init__(*args, **kwds)

    def __getitem__(self, key):
        value = super().__getitem__(key)
        self.move_to_end(key)
        return value

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        if len(self) > self.maxsize:
            oldest = next(iter(self))
            del self[oldest]

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

一个真正的 list 对象,用于存储 UserList 类的内容。

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

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


UserString 对象

UserString 充当字符串对象的包装器。 对此类的需求已部分被直接从 str 子类化的能力所取代; 但是,这个类更容易使用,因为底层字符串可以作为属性访问。

class collections.UserString(seq)

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

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

data

一个真正的 str 对象,用于存储 UserString 类的内容。

3.5 版本更改:新方法 __getnewargs____rmod__casefoldformat_mapisprintable 和 [ X104X]。