实际上,在日常的工作中,我们很多需求,无论是常见的、还是不常见的,Python 都为我们提供了一些独特的解决方案,既不需要自己造轮子,也不需要引入新的依赖(引入新的依赖势必会增加项目的复杂度)。
但是 Python 有太多功能和特性被我们忽略了,导致我们在遇到问题的时候,没法第一时间作出良好的决策。
所以,干脆来一起扫清这些被我们忽略的 Python 死角。
装饰器的妙用
我们经常会想完成一些注册&调用的功能,比如我们有四个函数:
def add(a: int, b: int) -> float:
return a + b
def sub(a: int, b: int) -> float:
return a - b
def mul(a: int, b: int) -> float:
return a * b
def div(a: int, b: int) -> float:
return a / b
现在我们想将这四个函数和 +、-、*、/ 四个操作符绑定,那么我们该怎么做?
可能我们第一反应是这样:
operator_map = {}
def add(a: int, b: int) -> float:
return a + b
def sub(a: int, b: int) -> float:
return a - b
def mul(a: int, b: int) -> float:
return a * b
def div(a: int, b: int) -> float:
return a / b
operator_map["+"] = add
operator_map["-"] = sub
operator_map["*"] = mul
operator_map["/"] = div
但这样写起来,有一个很大的问题就是太不美观了。因为直接对于 dict
的操作从实际上来讲可维护性是很差的,那么我们这个地方应该怎么做?
在改进这段代码之前,我们首先要明确 Python 中一个很重要的概念,即:函数/方法是:First Class Member 。用不精确的话来讲,就是函数/方法可以作为参数被传递、被使用。
举个例子:
import typing
def execute(func: typing.Callable, *args, **kwargs) -> typing.Any:
return func(*args, **kwargs)
def print_func(data: int) -> None:
print(data)
execute(print, 2)
大家可以看到我们将 print_func
这个函数作为参数传递给 execute 函数并被调用。
那么我们来改造下之前的代码:
import typing
operator_map = {}
def add(a: int, b: int) -> float:
return a + b
def sub(a: int, b: int) -> float:
return a - b
def mul(a: int, b: int) -> float:
return a * b
def div(a: int, b: int) -> float:
return a / b
def register_operator(operator: str, func: typing.Callable) -> None:
operator_map[operator] = func
register_operator("+", add)
register_operator("-", sub)
register_operator("*", mul)
register_operator("/", div)
好了,大家看看,目前整体代码的可读性以及可维护性是不是改了很多?
但是我们现在的问题在于,每次都需要在单独调用一次 register_operator 函数,这样也太烦了吧!要不要再改进一下?要得。我们可以用装饰器来改进一下。
首先,看一个最简单的装饰器例子:
import functools
import typing
import time
def execute(func: typing.Callable) -> typing.Callable:
@functools.wraps(func)
def wraps(*args, **kwargs) -> typing.Any:
start_time = time.time()
result = func(*args, **kwargs)
print("{}".format(time.time() - start_time))
return result
return wraps
@execute
def add(a: int, b: int) -> float:
return a + b
我们能看到这段函数的意义是计算函数的执行时间。那么这个原理是什么?
实际上装饰器是一个语法糖,具体可以参见 PEP318 Decorators for Functions and Methods。
简而言之,实际上是 Python 替我们做了一个替换过程。以上面的例子为例,这个替换过程就是 add=execute(add)
。
好了,我们就用这个知识点来改进下之前的代码:
import typing
operator_map = {}
def register_operator(operator: str) -> typing.Callable:
def wraps(func: typing.Callable) -> typing.Callable:
operator_map[operator] = func
return func
return wraps
@register_operator("+")
def add(a: int, b: int) -> float:
return a + b
@register_operator("-")
def sub(a: int, b: int) -> float:
return a - b
@register_operator("*")
def mul(a: int, b: int) -> float:
return a * b
@register_operator("/")
def div(a: int, b: int) -> float:
return a / b
聊聊 OrderedDict
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
维护了一个双向链表解构,来保证插入的有序性,如下图所示:
在最左侧维护一个卫兵节点,卫兵节点的 next 指针恒指向于数据中最后插入的节点。那么插入新的数据时,我们将新的数据插入到卫兵节点之后,从而达成维护插入顺序的目的。
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培训:http://www.baizhiedu.com/python2019