6

python进阶(26)collections标准库 - Silent丿丶黑羽

 1 year ago
source link: https://www.cnblogs.com/jiakecong/p/16050161.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

这个模块实现了特定目标的容器,以提供Python标准内建容器dict ,list ,set , 和tuple 的替代选择。
这个模块提供了以下几个函数

函数 作用
namedtuple() 创建命名元组子类的工厂函数
deque 类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop)
ChainMap 类似字典(dict)的容器类,将多个映射集合到一个视图里面
Counter 字典的子类,提供了可哈希对象的计数功能
OrderedDict 字典的子类,保存了他们被添加的顺序
defaultdict 字典的子类,提供了一个工厂函数,为字典查询提供一个默认值
UserDict 封装了字典对象,简化了字典子类化
UserList 封装了列表对象,简化了列表子类化
UserString 封装了字符串对象,简化了字符串子类化

namedtuple

namedtuple的由来

因为元组的局限性:不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义,所以引入namedtuple这个工厂函数,来构造一个带字段名的元组。namedtuple继承自tuple
命名元组赋予每个位置一个含义,提供可读性。它们可以用于任何普通元组,并添加了通过名字获取值的能力,通过索引值也是可以的。

namedtuple的格式

highlighter- Python
collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
  • typename: 返回一个新的元组子类,名为typename。这个新的子类用于创建类元组的对象,可以通过字段名来获取属性值,同样也可以通过索引和迭代获取值。
  • field_names: 像['x', 'y'] 一样的字符串序列。另外field_names可以是一个纯字符串,用空白或逗号分隔开元素名,比如 'x y' 或者 'x, y' 。
  • rename=False: 如果rename为true,无效字段名会自动转换成_+索引值,比如 ['abc', 'def', 'ghi', 'abc'] 转换成 ['abc', '_1', 'ghi', '_3'] , 消除关键词def和重复字段名abc。
  • default=None: defaults 可以为 None 或者是一个默认值的 iterable 。default默认值赋值跟我们平常的默认值相反,default默认值是从最右边开始,比如field_names中提供了3个字段['x', 'y', 'z'],default默认值设置为(1, 2),那么我们必须为x指定1个值,y默认值为1,z默认值为2
  • module=None: 如果 module 值有定义,命名元组的 module 属性值就被设置。

namedtuple声明以及实例化

我们首先创建一个User类,定义3个字段nameageheight,并给age设置默认值为18,给height设置了默认值180

python
User = namedtuple('User', ['name', 'age', 'height'], defaults=(18, 180))
print(User.__mro__)

我们查看结果

python
(<class '__main__.User'>, <class 'tuple'>, <class 'object'>)

可以看到我们声明的User类是继承于tuple,接下来我们创建实例

python
user1 = User(name='jkc')
user2 = User(name='jkc2', age=20, height=198)
print(user1)
print(user2)
print(user1.name)
print(user2.age)

运行结果为

python
User(name='jkc', age=18, height=180)
User(name='jkc2', age=20, height=198)
jkc
20

namedtuple的方法和属性

命名元组还支持三个额外的方法和两个属性。为了防止字段名冲突,方法和属性以下划线开始。
_make(iterable) 类方法从存在的序列或迭代实例创建一个新实例。

python
>>> t = ['jkc3', 25, 190]
>>> User._make(t)
User(name='jkc3', age=25, height=190)

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

python
>>> user4 = User(name='jkc4', age=28, height=200)
>>> user4._asdict()
{'name': 'jkc4', 'age': 28, 'height': 200}

_replace(**kwargs) 返回一个新的命名元组实例,并将指定域替换为新的值

python
>>> user5 = User(name='jkc5', age=30, height=210)
>>> user5._replace(age=18)
User(name='jkc5', age=30, height=210)

_fields 字符串元组列出了字段名。用于提醒和从现有元组创建一个新的命名元组类型

python
>>> user5._fields        
('name', 'age', 'height')

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

python
>>> User._field_defaults
{'name': 'jkc', 'age': 18, 'height': 180}

转换一个字典到命名元组,使用 ** 两星操作符

python
>>> d = {'name': 'jkc6', 'age': 18, 'height': 180}
>>> User(**d)
User(name='jkc6', age=18, height=180)

OrderedDict

有序字典就像常规字典一样,但有一些与排序操作相关的额外功能。由于内置的 dict 类获得了记住插入顺序的能力(在 Python 3.7 中保证了这种新行为),它们变得不那么重要了。

与dict类的区别

  • 常规的 dict 被设计为非常擅长映射操作。 跟踪插入顺序是次要的
  • OrderedDict 擅长重新排序操作。 空间效率、迭代速度和更新操作的性能是次要的。
  • 算法上, OrderedDict 可以比 dict 更好地处理频繁的重新排序操作。 这使其适用于跟踪最近的访问(例如在 LRU cache 中)。
  • 对于 OrderedDict ,相等操作检查匹配顺序。
  • OrderedDict 类的 popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。
  • OrderedDict 类有一个 move_to_end() 方法,可以有效地将元素移动到任一端。
  • Python 3.8之前, dict 缺少 __reversed__() 方法。

popitem(last=True)

有序字典的 popitem() 方法移除并返回一个 (key, value) 键值对。 如果 last 值为真,则按 LIFO 后进先出的顺序返回键值对,否则就按 FIFO 先进先出的顺序返回键值对。

python
from collections import OrderedDict


d = OrderedDict({'status': 200, 'message': 'success'})
print(f'原始的有序字典: {d}')
print('被删除的键值对是: ', d.popitem(last=True))  # 后进先出
print(f'被删除后的有序字典: {d}')

# 结果
原始的有序字典: OrderedDict([('status', 200), ('message', 'success')])
被删除的键值对是:  ('message', 'success')
被删除后的有序字典: OrderedDict([('status', 200)])
python
from collections import OrderedDict


d = OrderedDict({'status': 200, 'message': 'success'})
print(f'原始的有序字典: {d}')
print('被删除的键值对是: ', d.popitem(last=False))  # 先进先出
print(f'被删除后的有序字典: {d}')

# 结果
原始的有序字典: OrderedDict([('status', 200), ('message', 'success')])
被删除的键值对是:  ('status', 200)
被删除后的有序字典: OrderedDict([('message', 'success')])

move_to_end(key, last=True)

将现有 key 移动到有序字典的任一端。 如果 last 为真值(默认)则将元素移至末尾;如果 last 为假值则将元素移至开头。如果 key 不存在则会触发 KeyError:

python
d = OrderedDict({'status': 200, 'message': 'success'})
d.move_to_end('status', last=True)
print('移动后的字典: ', d)
d.move_to_end('status', last=False)
print('移动后的字典', d)

# 结果
移动后的字典:  OrderedDict([('message', 'success'), ('status', 200)])
移动后的字典:  OrderedDict([('status', 200), ('message', 'success')])

支持reversed

相对于通常的映射方法,有序字典还另外提供了逆序迭代的支持,通过 reversed()

python
d = OrderedDict({'status': 200, 'message': 'success'})
print({key: value for key, value in reversed(d.items())})

# 结果
{'message': 'success', 'status': 200}

相等测试敏感

OrderedDict 之间的相等测试是顺序敏感的

python
d1 = OrderedDict({'status': 200, 'message': 'success'})
d2 = OrderedDict({'message': 'success', 'status': 200})
d3 = {'status': 200, 'message': 'success'}
d4 = {'message': 'success', 'status': 200}
print('OrderedDict之间的比较结果: ', d1 == d2)
print('dict之间的比较结果: ', d3 == d4)
print('OrderedDict与dict的比较结果: ', d1 == d3 == d4)

# 结果
OrderedDict之间的比较结果:  False
dict之间的比较结果:  True
OrderedDict与dict的比较结果:  True

defaultdict

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

defaultdict的作用
我们看名字就知道defaultdict的作用是为字典提供一个默认的值,我们正常情况下访问一个字典的key,如果字典中没有这个key会报错

python
>>> dict1 = {}
>>> dict1['name']
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    dict1['name']
KeyError: 'name'
>>> 

此时我们就可以使用defaultdict,它包含一个名为 default_factory 的属性,构造时,第一个参数用于为该属性提供初始值,默认为 None

这个default_factory可以是listsetstr,也可以是自定义的函数,作用是当key不存在时,返回的是工厂函数的默认值,比如list对应[ ],str对应的是空字符串,set对应set( ),int对应0

python
dict1 = defaultdict(int)
dict2 = defaultdict(set)
dict3 = defaultdict(str)
dict4 = defaultdict(list)
print(dict1['name'])
print(dict2['name'])
print(dict3['name'])
print(dict4['name'])
highlighter- Bash
0
set()

[]

使用 list 作为 default_factory,我们可以很轻松地将(键-值对组成的)序列转换为(键-列表组成的)字典:

python
>>> from collections import defaultdict
>>> 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_factoryint,使 defaultdict 用于计数

highlighter- Python
>>> s = 'aiibiicii'
>>> d = defaultdict(int)
>>> for k in s:
	d[k] += 1

>>> sorted(d.items())
[('a', 1), ('b', 1), ('c', 1), ('i', 6)]

如果你需要自己定义一个返回值,你可以创建1个函数,设置自定义的返回值

python
def constant_factory(value):
    return lambda: value


d = defaultdict(constant_factory('success'))
d.update(status=200)
var = d['message']
print(sorted(d.items()))

# 输出
[('message', 'success'), ('status', 200)]

Counter对象

它一个计数器工具提供快速和方便的计数。
它是 dict 的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数。

元素从一个 iterable 被计数或从其他的 mapping (or counter)初始化:

highlighter- Bash
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

如果引用的键没有任何记录,就返回一个0,而不是弹出一个 KeyError

highlighter- Python
>>> c = Counter(['eggs', 'ham'])
>>> c['bacon']
0

作为 dict 的子类,Counter 继承了记住插入顺序的功能。 Counter 对象进行数学运算时同样会保持顺序。 结果会先按每个元素在运算符左边的出现时间排序,然后再按其在运算符右边的出现时间排序。

elements()

返回一个迭代器,其中每个元素将重复出现计数值所指定次。 元素会按首次出现的顺序返回。 如果一个元素的计数值小于一,elements() 将会忽略它。

highlighter- Python
>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']

most_common([n])

返回一个列表,其中包含 n 个最常见的元素及出现次数,按常见程度由高到低排序。 如果 n 被省略或为 Nonemost_common() 将返回计数器中的所有元素。 计数值相等的元素按首次出现的顺序排序:

highlighter- Python
>>> Counter('abracadabra').most_common()
[('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]
>>> Counter('abracadabra').most_common(2)
[('a', 5), ('b', 2)]

Counter对象一般有以下两种应用场景
1. 统计单词在列表中的出现次数

highlighter- Python
>>> count = Counter()
>>> list1 = ['red', 'blue', 'red', 'green', 'blue', 'blue']
>>> for word in list1:
	count[word] += 1

>>> count
Counter({'blue': 3, 'red': 2, 'green': 1})

count[word]因为没有在Counter对象中,所以默认情况下会给他赋值为0,因此可以统计出单词出现的次数

2. 找出文件中最常见的十个单词

highlighter- Python
>>> import re
>>> words = re.findall(r'\w+', open('log.txt').read().lower())
>>> Counter(words).most_common(10)
[('the', 1180), ('and', 822), ('to', 810), ('of', 799), ('i', 688),
 ('you', 510),  ('a', 508), ('my', 500), ('yes', 406), ('in', 318)]

deque([iterable[, maxlen]])

返回一个新的双向队列对象,从左到右初始化(用方法 append()) ,从 iterable (迭代对象) 数据创建。如果 iterable 没有指定,新队列为空。

Deque队列是由栈或者queue队列生成的。Deque 支持线程安全,内存高效添加(append)和弹出(pop),从两端都可以,两个方向的大概开销都是 O(1) 复杂度。

虽然 list 对象也支持类似操作,不过这里优化了定长操作和 pop(0)insert(0, v) 的开销。它们引起 O(n) 内存移动的操作,改变底层数据表达的大小和位置。

如果 maxlen 没有指定或者是 Nonedeques 可以增长到任意长度。否则,deque就限定到指定最大长度。一旦限定长度的deque满了,当新项加入时,同样数量的项就从另一端弹出。

deque的方法

双向队列(deque)对象支持很多方法,大部分方法list都有

方法名 作用
append(x) 添加 x 到右端
appendleft(x) 添加 x 到左端
clear() 移除所有元素,使其长度为0
copy() 创建一份浅拷贝
count(x) 计算 deque 中元素等于 x 的个数
extend(iterable) 扩展deque的右侧,通过添加iterable参数中的元素
extendleft(iterable) 扩展deque的左侧,通过添加iterable参数中的元素。注意,左添加时,在结果中iterable参数中的顺序将被反过来添加
index(x[, start[, stop]]) 返回 x 在 deque 中的位置(在索引 start 之后,索引 stop 之前)。 返回第一个匹配项,如果未找到则引发 ValueError
insert(i, x) 在位置 i 插入 x,如果插入会导致一个限长 deque 超出长度 maxlen 的话,就引发一个 IndexError。
pop() 移去并且返回一个元素,deque 最右侧的那一个。 如果没有元素的话,就引发一个 IndexError
popleft() 移去并且返回一个元素,deque 最左侧的那一个。 如果没有元素的话,就引发 IndexError
remove(value) 移除找到的第一个 value。 如果没有的话就引发 ValueError
reverse() 将deque逆序排列。返回 None 。
rotate(n=1) 向右循环移动 n 步。 如果 n 是负数,就向左循环。如果deque不是空的,向右循环移动一步就等价于 d.appendleft(d.pop()) , 向左循环一步就等价于 d.append(d.popleft())
maxlen Deque的最大尺寸,如果没有限定的话就是 None

deque 用法

① linux下查看最新日志的命令是:tail -n 2 test.log,deque也可以实现同样的功能

highlighter- Python
def tail(filename, n=10):
    with open(filename) as f:
        return deque(f, n)

② 维护一个近期添加元素的序列,通过从右边添加和从左边弹出

highlighter- Python
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

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK