集合Collections

Python进阶之Collections的理解和使用

Posted by Ted on December 21, 2016

Python附带一个模块包含了许多称为集合的容器数据类型。

  • defaultdict
  • OrderedDict
  • counter
  • deque
  • namedtuple
  • enum.Enum (outside of the module; Python 3.4+)

defaultdict

我个人使用defaultdict相当多。 与dict不同,defaultdict不需要检查一个键是否存在。 所以我们可以这样做:

from collections import defaultdict

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favourite_colours = defaultdict(list)

for name, colour in colours:
    favourite_colours[name].append(colour)

print(favourite_colours)

# output
# defaultdict(<type 'list'>,
#    {'Arham': ['Green'],
#     'Yasoob': ['Yellow', 'Red'],
#     'Ahmed': ['Silver'],
#     'Ali': ['Blue', 'Black']
# })

另外一个非常重要的用法是在字典中追加嵌套list。 如果一个键不在字典中,那么会产生一个KeyError。 defaultdict允许我们以一种聪明的方式绕开这个问题。 首先让我分享一个使用dict的例子,它引发了KeyError,然后我将使用defaultdict来分享一个解决方案。

问题:

some_dict = {}
some_dict['colours']['favourite'] = "yellow"
# Raises KeyError: 'colours'

解决方案:

import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
# Works fine

你可以打印 some_dictjson.dumps:

import json
print(json.dumps(some_dict))
# Output: {"colours": {"favourite": "yellow"}}

OrderedDict

OrderedDict保持其键值按照最初插入的顺序排序。 覆盖现有key的值不会改变该键值的位置。 但是,删除并重新插入键值会将该键值移到字典的末尾。

无顺序:

colours =  {"Red" : 198, "Green" : 170, "Blue" : 160}
for key, value in colours.items():
    print(key, value)
# Output:
#   Green 170
#   Blue 160
#   Red 198
# Entries are retrieved in an unpredictable order

有顺序

from collections import OrderedDict

colours = OrderedDict([("Red", 198), ("Green", 170), ("Blue", 160)])
for key, value in colours.items():
    print(key, value)
# Output:
#   Red 198
#   Green 170
#   Blue 160
# Insertion order is preserved

counter

counter计数器允许我们计算特定项目的出现次数。 例如,它可以用来计算个人最喜欢的颜色的数量:

from collections import Counter

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favs = Counter(name for name, colour in colours)
print(favs)
# Output: Counter({
#    'Yasoob': 2,
#    'Ali': 2,
#    'Arham': 1,
#    'Ahmed': 1
# })

同样可以用这个来计算一个文件中的最大相同行数

with open('filename', 'rb') as f:
    line_count = Counter(f)
print(line_count)

deque为您提供了一个双端队列,这意味着您可以从队列的任意一侧追加和删除元素。

from collections import deque

d = deque()
d.append('1')
d.append('2')
d.append('3')

print(len(d))
# Output: 3

print(d[0])
# Output: '1'

print(d[-1])
# Output: '3'

你可以在两端pop值:

d = deque(range(5))
print(len(d))
# Output: 5

d.popleft()
# Output: 0

d.pop()
# Output: 4

print(d)
# Output: deque([1, 2, 3])

你可以限制队列的最大长度。如果这样做当我们接收超出最大长度时,队里会在另外一端pop出。

d = deque(maxlen=30)

d = deque([1,2,3,4,5])
d.extendleft([0])
d.extend([6,7,8])
print(d)
# Output: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])

namedtuple

你可能已经熟悉元组了。 一个元组基本上是一个不可变的list,它允许你存储由逗号分隔的一系列值。 他们就像list,但有几个关键的区别。 主要的一点是,不像list,你不能重新分配元组中的项目。 为了访问元组中的值,可以使用如下的整数索引:

man = ('Ali', 30)
print(man[0])
# Output: Ali

那么现在是什么namedtuple? 他们把元组变成方便的容器来完成简单的任务。 使用namedtuples,您不必使用整数索引来访问元组的成员。 你可以把namedtuple看作字典,但不同于字典,它们是不可变的。

from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")

print(perry)
# Output: Animal(name='perry', age=31, type='cat')

print(perry.name)
# Output: 'perry'

现在你可以看到,我们可以通过使用一个名称来访问元组的成员。让我们再解析一下。 一个命名的元组有两个必需的参数。 它们是元组的名称和元组的field_names。 在上面的例子中,我们的元组名是’Animal’,而元组的field_names是’name’,’age’和’type’。 Namedtuple使你的元组自己生成文档。 快速浏览一下你的代码就可以很容易的理解到底发生了什么。 而且由于您不必使用整数索引来访问元组的成员,因此可以更容易地维护您的代码。 此外,由于namedtuple实例没有每个实例字典,所以它们是轻量级的,并且不需要比常规元组更多的内存。 这使得它们比字典更快。 但是,请记住,与元组一样,namedtuples中的属性是不可变的。

这意味着这是下面这是行不通的:

from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
perry.age = 42

# Output: Traceback (most recent call last):
#            File "", line 1, in
#         AttributeError: can't set attribute

你同样可以用index来访问:

from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
print(perry[0])
# Output: perry

你可以把namedtuple转化成字典

from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type="cat")
print(perry._asdict())
# Output: OrderedDict([('name', 'Perry'), ('age', 31), ...

enum.Enum(Python 3.4+)

from collections import namedtuple
from enum import Enum

class Species(Enum):
    cat = 1
    dog = 2
    horse = 3
    aardvark = 4
    butterfly = 5
    owl = 6
    platypus = 7
    dragon = 8
    unicorn = 9
    # The list goes on and on...

    # But we don't really care about age, so we can use an alias.
    kitten = 1
    puppy = 2

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type=Species.cat)
drogon = Animal(name="Drogon", age=4, type=Species.dragon)
tom = Animal(name="Tom", age=75, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.kitten)

# And now, some tests.
>>> charlie.type == tom.type
True
>>> charlie.type
<Species.cat: 1>

有三个方法去访问enumeration成员

Species(1)
Species['cat']
Species.cat