python实用技巧,学会这一招,减少工作量!

dict 是我们经常使用的一种数据解构。但是在 Python 3.6 之前 dict 都是无序的,即我插入的顺序,和数据在 dict 中存放的顺序并无关联(笔者注:Python 3.6 dict 有序只是新版实现的顺带产物,Python 3.7 正式作为 feature 被固定下来)。

但是很多时候,比如在验签等场景,我们需要保证 dict 数据存放顺序,和我们插入顺序是一致的。那么我们该怎么办?

老板有需求下来了,我们肯定不能告诉老板这个需求没法做。那我们就自己实现一个ordereddict 吧。于是,想了想,写了如下的代码:

我是一名python开发工程师,整理了一套python的学习资料,从基础的python脚本到web开发、爬虫、
数据分析、数据可视化、机器学习、面试真题等。想要的可以进群:688244617免费领取

import typing


class
 
OrderedDict
:
    
def
 __init__(self, *args, **kwargs):
        self._data = {}
        self._ordered_key = []

    
def
 __getitem__(self, key: typing.
Any
) -> typing.
Any
:
        
return
 self._data[key]

    
def
 __setitem__(self, key: typing.
Any
, value: typing.
Any
) -> 
None
:
        
if
 key 
not
 
in
 self._data:
            
return
        self._data[key] = value
        self._ordered_key.append(key)

    
def
 __delitem__(self, key: typing.
Any
):
        
del
 self._data[key]
        self._ordered_key.remove(key)

通过额外维护一个 list 来维护 key 插入的顺序。这段代码,看似完成了我们的需求,但是实则存在很大问题。大家可以猜猜问题在哪?

3,2,1!

揭晓答案,这段代码利用 list 来保证 key 的有序性,在删除的时候, list 的删除操作,是一个时间复杂度 O(n) 的操作。换句话说,我们的删除操作随着内部数据的增多,所需的删除时间也变得越长。这对于某些性能敏感的场景是无法接受的。

那要怎么办呢?事实上,Python 在很早之前就已经内置了有序字典,即很多人可能都用过的 collections.OrderedDict 。

在 OrderedDict 中, Python 维护了一个双向链表解构,来保证插入的有序性,如下图所示:

python实用技巧,学会这一招,减少工作量!_第1张图片

在最左侧维护一个卫兵节点,卫兵节点的 next 指针恒指向于数据中最后插入的节点。那么插入新的数据时,我们将新的数据插入到卫兵节点之后,从而达成维护插入顺序的目的。

在删除的时候,通过额外维护的一个字典找到待删除的 key 所对应的节点。这个操作是 O(1) 的复杂度,然后大家都知道,双向链表删除一个节点的时间复杂度也是 O(1) 。通过这样保证我们在即便有大量数据的情况下,也能保证相应的性能。

好了,我们按照这个思路来做一个最简单的实现:

import typing


class
 
Node
:
    
def
 __init__(self, key: typing.
Any
, value: typing.
Any
) -> 
None
:
        self.key = key
        self.value = value
        self.prev = 
None
        self.next = 
None


class
 
OrderedDict
:
    
def
 __init__(self, *args, **kwargs):
        self._data = {}
        self._head = 
Node
(
None
, 
None
)
        self._last = self._head

    
def
 __getitem__(self, key: typing.
Any
) -> typing.
Any
:
        
if
 key 
in
 self._data:
            
return
 self._data[key].value
        
raise
 
ValueError

    
def
 __setitem__(self, key: typing.
Any
, value: typing.
Any
) -> 
None
:
        
if
 key 
not
 
in
 self._data:
            
return
        value_node = 
Node
(key, value)
        next_node = self._head.next
        
if
 
not
 next_node:
            self._head.next = value_node
            value_node.prev = self._head
            self._last = value_node
        
else
:
            value_node.next = next_node
            next_node.prev = value_node
            value_node.prev = self._head
            self._head.next = value_node
        self._data[key] = value_node

    
def
 __delitem__(self, key: typing.
Any
):
        
if
 key 
not
 
in
 self._data:
            
return
        value_node = self._data[key]
        
if
 value_node == self._last:
            self._last = value_node.prev
            self._last.next = 
None
        
else
:
            prev_node = value_node.prev
            next_node = value_node.next
            prev_node.next = next_node
            next_node.prev = prev_node
        
del
 self._data[key]
        
del
 value_node

这只是一个 OrderedDict 的简化版,如果想完成一个完整的 OrderedDict 还有很多很多的 corner case 要去处理。不过现在,我们可以使用内置的数据结构去完成我们需求。怎么样,是不是有了一种幸福的感觉?

通过这个例子可以发现,深入地了解 Python 内部的一些功能实现,会更好地帮助我们完成日常的工作与学习任务。同时以便我们能更好地去学习一些知识。比如,OrderedDict 的实现,会让我们学到双头链表的一种非常典型的应用,与此同时,双头链表也会用于诸如 LRU 这样非常常用的数据解构的实现。所以,多去深入了解 Python 的方方面面,有助于我们整体能力的提升。

觉得文章还不错的话不妨收藏起来慢慢看,有任何建议或看法欢迎大家在评论区分享讨论!

你可能感兴趣的:(python实用技巧,学会这一招,减少工作量!)