【标准库】collections中的deque和defaultdict

        python的成功除了语言本身的设计非常受喜欢外,还有一个更大的原因是其开源库的繁荣,尤其是机器学习相关的库,但除此以外,python官方也开发了很多标准库,这些库有些是为了给我们提供原生语言不支持的功能,而有的是为了给我们提供一些高频函数和类的实现

        今天要介绍的是collections库,从英文名就能看出来其是有关容器的库,这个库从底层给我们提供了一些原生python中很难甚至是不可能实现的容器,也用原生python写了一些常见容器

try:
    from _collections import deque
except ImportError:
    pass
else:
    _collections_abc.MutableSequence.register(deque)

try:
    from _collections import defaultdict
except ImportError:
    pass

        我们可以在collections库的开头看到这几行代码,deque和defaultdict就是从python底层实现的新容器,也就是用C语言写的,今天主要介绍它们俩及常用场景

from collections import deque

# deque可以接受一个可迭代对象,并生成对应的deque容器
dq = deque(i for i in range(10))

        上述代码是通过生成器表达式创建了一个0-9的deque容器,deque翻译过来叫双端队列,它有内置类型list的append和pop方法,但仔细观察,我们可以发现,deque的pop方法没有可传参数,也就是说deque的pop只可以弹出尾端的元素,deque还有appendleft和popleft两个方法,表示在左侧增加和删除元素

        刚接触python的人会认为deque是list的一个子集,list也可以通过pop来删除第一个元素,通过insert来插入元素到表头,但实际上可以说两者基本没什么关系,deque是官方提供的高效队列实现,是对容器的扩展,为什么说是高效呢?

from time import time
from collections import deque

T = 10 ** 5
dq = deque(i for i in range(T))
ls = list(i for i in range(T))
s = time()
for _ in range(T):
    dq.popleft()    # 0.00398707389831543
e1 = time()
print(e1 - s)
for _ in range(T):
    ls.pop(0)
print(time() - e1)  # 9.261945247650146

        可以看到,list.pop(0)来模拟deque的popleft效率会低很多,这是因为list的pop(0)时间复杂度实际上是O(n),python会重新拷贝一遍这个list,而deque不会,因此为了模拟队列先进先出的特性,使用deque远远比list好

        有人可能非要根据index来删除deque的某个元素,实际上这也是可以的,使用关键字del即可

from collections import deque

dq = deque(i for i in range(10))

del dq[5]    # 删除index==5的元素
print(dq)    # deque([0, 1, 2, 3, 4, 6, 7, 8, 9])

        deque还有一个list没有的方法rotate,就是将元素向右移动,传入负数可以向左移动,其实该方法的实现也非常简单,感兴趣可以自己实现一下

from collections import deque

dq = deque(i for i in range(10))

dq.rotate(3)
print(dq)    # deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6])
dq.rotate(-5)
print(dq)    # deque([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])

        另一个C实现的容器时defaultdict,翻译过来叫默认值字典,其接受一个工厂函数,还有可选的第二个参数,但这个参数如果要使用需要熟悉泛型编程,没错,python也支持泛型编程,什么是泛型?泛型就是在API的传参和返回过程中,不规定特定类型,而是根据用户的输入动态地改变

from typing import TypeVar

_T = TypeVar('_T')


def add(x: _T, y: _T) -> _T:
    return x + y


print(add(1, 2))    # 3
print(add("a", "b")) # ab
print(add([1, 2], [3, 4]))    # [1,2,3,4]

print(add(1, 1.1))    # IDE会标注类型错误,但可以正常输出2.1

        TypeVar就是泛型,该add函数接受一个x和y,要求x和y必须是同类型的,并且返回同类型的值,但具体x和y是int还是str,并不关心,它们相同即可,否则IDE会提醒用户类型错误,但是注意,即使用户不遵守,只要这两个类型实际上可以正确相加,那解释器不会报错,而是正常执行,这是因为python的type hint对实际运行没有任何影响,只是提高程序的可读性和可维护性的工具

from collections import defaultdict

dc1 = defaultdict(list)
dc1['a'].append('a')    # 会创建一个新的键值对,'a': ['a']

dc2 = dict()
dc2['a'].append('a')    # KeyError: 'a'

        defaultdict又叫永不报KeyError的dict,在执行defaultdict的__getitem__方法时,若不存在该键,触发KeyError后,会根据工厂函数创建一个新的键值对,dc1那行代码可以成立也说明这个创建的值是会立即被返回的,然后我们就可以向自动创建的空list中添加'a'了

from collections import defaultdict

dc1 = defaultdict(list, {'a': [1, 2, 3]})
dc1['a'].append('a')
print(dc1)    # defaultdict(<class 'list'>, {'a': [1, 2, 3, 'a']})

dc1 = defaultdict(list, {'a': 1})    # IDE会标注类型错误
print(dc1['a'])    # 1

        我们可以很清晰地知道第二个参数是干什么的了,就是指定在某个key下,默认按照该参数指定的实例来创建,那为什么说涉及到泛型呢,因为在这里第二个参数的value必须是工厂函数的实例,否则会产生意想不到的行为,但还是那句话,你如果实在不遵守,只要你的操作实际上不会出错,那么就不会出错

        最后再说一下什么是工厂函数,工厂函数简单来说就是生产实例的函数,list就是工厂函数,因为在调用list()的时候,实际上返回了list的实例,因此任何类本身严格来说都是工厂函数,本质上真正的工厂函数是类中的__new__魔术方法,它真实地创建了这个实例

class H:

    def __new__(cls, *args, **kwargs):
        # 重写该方法,让它返回None
        return    


h = H()    # 并不会创建H的实例
print(h)    # None

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值