后悔没早知道这些Python特性

写 Python 也好几年时间了。讲道理,在工作中大家肯定遇到过这样的场景:

这个故事告诉我们什么?先造轮子再去 GitHub?还是提高下 GitHub 搜索技巧?

很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。

很多已经做案例的人,却不知道如何去学习更加高深的知识。

那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!

QQ群:127341871

都不是!

实际上,在日常的工作中,我们很多需求,无论是常见的、还是不常见的,Python 都为我们提供了一些独特的解决方案,既不需要自己造轮子,也不需要引入新的依赖(引入新的依赖势必会增加项目的复杂度)。

但是 Python 有太多功能和特性被我们忽略了,导致我们在遇到问题的时候,没法第一时间作出良好的决策。

所以,干脆来一起扫清这些被我们忽略的 Python 死角。

装饰器的妙用

我们经常会想完成一些注册&调用的功能,比如我们有四个函数:

 
  1. def add(a: int, b: int) -> float:

  2. return a + b

  3.  

  4.  

  5. def sub(a: int, b: int) -> float:

  6. return a - b

  7.  

  8.  

  9. def mul(a: int, b: int) -> float:

  10. return a * b

  11.  

  12.  

  13. def div(a: int, b: int) -> float:

  14. return a / b

 
  1.  

现在我们想将这四个函数和 +、-、*、/ 四个操作符绑定,那么我们该怎么做?

可能我们第一反应是这样:

 
  1. operator_map = {}

  2.  

  3.  

  4. def add(a: int, b: int) -> float:

  5. return a + b

  6.  

  7.  

  8. def sub(a: int, b: int) -> float:

  9. return a - b

  10.  

  11.  

  12. def mul(a: int, b: int) -> float:

  13. return a * b

  14.  

  15.  

  16. def div(a: int, b: int) -> float:

  17. return a / b

  18.  

  19.  

  20. operator_map["+"] = add

  21.  

  22. operator_map["-"] = sub

  23.  

  24. operator_map["*"] = mul

  25.  

  26. operator_map["/"] = div

但这样写起来,有一个很大的问题就是太不美观了。因为直接对于 dict 的操作从实际上来讲可维护性是很差的,那么我们这个地方应该怎么做?

在改进这段代码之前,我们首先要明确 Python 中一个很重要的概念,即:函数/方法是:First Class Member 。用不精确的话来讲,就是函数/方法可以作为参数被传递、被使用。

举个例子:

 
  1. import typing

  2.  

  3.  

  4. def execute(func: typing.Callable, *args, **kwargs) -> typing.Any:

  5. return func(*args, **kwargs)

  6.  

  7.  

  8. def print_func(data: int) -> None:

  9. print(data)

  10.  

  11.  

  12. execute(print, 2)

大家可以看到我们将 print_func 这个函数作为参数传递给 execute 函数并被调用。

那么我们来改造下之前的代码:

 
  1. import typing

  2.  

  3. operator_map = {}

  4.  

  5.  

  6. def add(a: int, b: int) -> float:

  7. return a + b

  8.  

  9.  

  10. def sub(a: int, b: int) -> float:

  11. return a - b

  12.  

  13.  

  14. def mul(a: int, b: int) -> float:

  15. return a * b

  16.  

  17.  

  18. def div(a: int, b: int) -> float:

  19. return a / b

  20.  

  21.  

  22. def register_operator(operator: str, func: typing.Callable) -> None:

  23. operator_map[operator] = func

  24.  

  25.  

  26. register_operator("+", add)

  27. register_operator("-", sub)

  28. register_operator("*", mul)

  29. register_operator("/", div)

 
  1.  

好了,大家看看,目前整体代码的可读性以及可维护性是不是改了很多?

但是我们现在的问题在于,每次都需要在单独调用一次 register_operator 函数,这样也太烦了吧!要不要再改进一下?要得。我们可以用装饰器来改进一下。

首先,看一个最简单的装饰器例子:

 
  1. import functools

  2. import typing

  3. import time

  4.  

  5.  

  6. def execute(func: typing.Callable) -> typing.Callable:

  7. @functools.wraps(func)

  8. def wraps(*args, **kwargs) -> typing.Any:

  9. start_time = time.time()

  10. result = func(*args, **kwargs)

  11. print("{}".format(time.time() - start_time))

  12. return result

  13.  

  14. return wraps

  15.  

  16.  

  17. @execute

  18. def add(a: int, b: int) -> float:

  19. return a + b

我们能看到这段函数的意义是计算函数的执行时间。那么这个原理是什么?

实际上装饰器是一个语法糖,具体可以参见 PEP318 Decorators for Functions and Methods。

简而言之,实际上是 Python 替我们做了一个替换过程。以上面的例子为例,这个替换过程就是 add=execute(add) 。

好了,我们就用这个知识点来改进下之前的代码:

 
  1. import typing

  2.  

  3. operator_map = {}

  4.  

  5.  

  6. def register_operator(operator: str) -> typing.Callable:

  7. def wraps(func: typing.Callable) -> typing.Callable:

  8. operator_map[operator] = func

  9. return func

  10.  

  11. return wraps

  12.  

  13.  

  14. @register_operator("+")

  15. def add(a: int, b: int) -> float:

  16. return a + b

  17.  

  18.  

  19. @register_operator("-")

  20. def sub(a: int, b: int) -> float:

  21. return a - b

  22.  

  23.  

  24. @register_operator("*")

  25. def mul(a: int, b: int) -> float:

  26. return a * b

  27.  

  28.  

  29. @register_operator("/")

  30. def div(a: int, b: int) -> float:

  31. return a / b

这样我们这段代码的注册过程是不是就显得更优雅了?

嗯,是的!实际上 Python 中有很多特性会帮助我们的代码更简洁,更优美。

接下来这个例子很可能帮我们减轻工作量。

聊聊 OrderedDict

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

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

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

 
  1. import typing

  2.  

  3.  

  4. class OrderedDict:

  5. def __init__(self, *args, **kwargs):

  6. self._data = {}

  7. self._ordered_key = []

  8.  

  9. def __getitem__(self, key: typing.Any) -> typing.Any:

  10. return self._data[key]

  11.  

  12. def __setitem__(self, key: typing.Any, value: typing.Any) -> None:

  13. if key not in self._data:

  14. return

  15. self._data[key] = value

  16. self._ordered_key.append(key)

  17.  

  18. def __delitem__(self, key: typing.Any):

  19. del self._data[key]

  20. self._ordered_key.remove(key)

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

3,2,1!

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

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

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

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

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

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

 
  1. import typing

  2.  

  3.  

  4. class Node:

  5. def __init__(self, key: typing.Any, value: typing.Any) -> None:

  6. self.key = key

  7. self.value = value

  8. self.prev = None

  9. self.next = None

  10.  

  11.  

  12. class OrderedDict:

  13. def __init__(self, *args, **kwargs):

  14. self._data = {}

  15. self._head = Node(None, None)

  16. self._last = self._head

  17.  

  18. def __getitem__(self, key: typing.Any) -> typing.Any:

  19. if key in self._data:

  20. return self._data[key].value

  21. raise ValueError

  22.  

  23. def __setitem__(self, key: typing.Any, value: typing.Any) -> None:

  24. if key not in self._data:

  25. return

  26. value_node = Node(key, value)

  27. next_node = self._head.next

  28. if not next_node:

  29. self._head.next = value_node

  30. value_node.prev = self._head

  31. self._last = value_node

  32. else:

  33. value_node.next = next_node

  34. next_node.prev = value_node

  35. value_node.prev = self._head

  36. self._head.next = value_node

  37. self._data[key] = value_node

  38.  

  39. def __delitem__(self, key: typing.Any):

  40. if key not in self._data:

  41. return

  42. value_node = self._data[key]

  43. if value_node == self._last:

  44. self._last = value_node.prev

  45. self._last.next = None

  46. else:

  47. prev_node = value_node.prev

  48. next_node = value_node.next

  49. prev_node.next = next_node

  50. next_node.prev = prev_node

  51. del self._data[key]

  52. del value_node

(此段代码,如有错乱,烦请将浏览字体调小几号)

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

随意聊聊

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值