其实在我们的日常工作中,我们有很多需求,不管是常见的还是不常见的,Python为我们提供了一些独特的解决方法解决方法不需要自己造轮子,也不需要需要引入新的依赖(引入新的依赖必然会增加项目的复杂度)。但是Python有太多的功能和特性被我们忽略了,导致我们在遇到问题的时候无法在第一时间做出好的决策。那么,让我们清理一下我们忽略的这些Python死角吧。装饰器的妙用我们经常想完成一些注册&调用的功能,比如我们有四个功能:现在我们想把这四个功能绑定到+、-、*、/四个操作符上,那我们应该怎么做呢?可能我们的第一反应是这样的:但是这么写,有个大问题就是太不美观了。因为直接操作dict的可维护性其实是很差的,那么这里怎么办呢?在改进这段代码之前,首先要明确Python中一个非常重要的概念,即:函数/方法是:FirstClassMember。用不精确的术语来说,函数/方法可以作为参数传递和使用。例如:可以看到我们将print_func函数作为参数传递给execute函数并被调用。那我们来修改一下之前的代码:好了,我们来看一下,整体代码的可读性和可维护性是不是有了很大的变化?但是我们现在的问题是每次都需要单独调用register_operator函数,太烦人了!你想改进它吗?想。我们可以用装饰器来改善这一点。首先看一个最简单的装饰器例子:我们可以看出这个函数的意思是计算函数的执行时间。那么这个原理是什么?事实上,装饰器是一种语法糖。有关详细信息,请参阅PEP318DecoratorsforFunctionsandMethods。总之,Python其实是为我们做了一个替换的过程。以上面的例子为例,这个替换过程就是add=execute(add)。好吧,让我们利用这个知识点来改进之前的代码:这样是不是让我们代码的注册过程更加优雅了?嗯,是的!其实Python中有很多特性可以帮助我们的代码更简洁、更美观。下面的例子很可能会帮助我们减少工作量。再来说说OrderedDictdict是我们经常用到的一种数据解构。但是在Python3.6之前,dict都是无序的,也就是说我插入的顺序和数据在dict中的存储顺序没有关系(注:Python3.6dict顺序只是新版本的副产品,并且Python3.7是正式的一个特性是固定的)。但是在很多情况下,比如在签名验证等场景中,我们需要保证dict数据的存储顺序和我们的插入顺序是一致的。那我们该怎么办呢?老板有需求,我们千万不能跟老板说这个需求不能实现。那我们自己实现一个ordereddict吧。于是,思前想后,写了如下代码:通过维护一个额外的列表来维护key插入的顺序。这段代码看似满足了我们的需求,但实际上存在很大的问题。你能猜出问题出在哪里吗?3、2、1!为揭示答案,此代码使用列表来确保键的顺序。删除时,链表的删除操作是一个时间复杂度为O(n)的操作。也就是说,随着内部数据量的增加,我们的删除操作所需要的删除时间也会变长。这对于一些性能敏感的场景来说是不可接受的。那么该怎么办?其实Python很早就内置了有序字典,也就是可能很多人都用过的collections.OrderedDict。在OrderedDict中,Python维护了一个双向链表解构来保证插入的顺序,如下图所示:最左边维护了一个guard节点,guard节点的next指针始终指向最后插入的节点数据。那么在插入新数据的时候,我们将新数据插入到guard节点之后,从而达到保持插入顺序的目的。删除时,通过额外维护的字典找到要删除的key对应的节点。这个操作的复杂度是O(1),大家都知道在双向链表中删除节点的时间复杂度也是O(1)。这样就保证了即使在数据量很大的情况下,我们也能保证相应的性能。好吧,我们按照这个思路做一个最简单的实现:这只是OrderedDict的简化版。如果要完成一个完整的OrderedDict,还有很多cornercase需要处理。但是现在,我们可以使用内置的数据结构来满足我们的需求。怎么样,有没有幸福的感觉?随便聊聊通过今天的两个例子,我们发现Python提供了相当多的功能来帮助我们完成日常的工作和学习任务。同时,通过深入了解Python的一些内部函数,让我们更好的学习一些知识。比如上面提到的OrderedDict的实现,就可以让我们学习一个非常典型的双头链表的应用。同时双头链表也会被用于LRU等非常常用的数据解构的实现。因此,深入了解Python的各个方面,将有助于提升我们的综合能力。
