首页>学校动态>早知道这些Python特性

早知道这些Python特性

来源:沈阳兄弟连IT培训学校时间:2019/12/18 9:39:54

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

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

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

早知道这些Python特性

装饰器的妙用

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

def add(a: int, b: int) -> float:    return a + bdef sub(a: int, b: int) -> float:    return a - bdef mul(a: int, b: int) -> float:    return a * bdef div(a: int, b: int) -> float:    return a / b

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

可能我们反应是这样:

operator_map = {}def add(a: int, b: int) -> float:    return a + bdef sub(a: int, b: int) -> float:    return a - bdef mul(a: int, b: int) -> float:    return a * bdef div(a: int, b: int) -> float:    return a / boperator_map["+"] = addoperator_map["-"] = suboperator_map["*"] = muloperator_map["/"] = div

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

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

举个例子:

import typingdef 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 typingoperator_map = {}def add(a: int, b: int) -> float:    return a + bdef sub(a: int, b: int) -> float:    return a - bdef mul(a: int, b: int) -> float:    return a * bdef div(a: int, b: int) -> float:    return a / bdef register_operator(operator: str, func: typing.Callable) -> None:    operator_map[operator] = funcregister_operator("+", add)register_operator("-", sub)register_operator("*", mul)register_operator("/", div)

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

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

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

import functoolsimport typingimport timedef 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@executedef add(a: int, b: int) -> float:    return a + b

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

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

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

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

import typingoperator_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

这样我们这段代码的注册过程是不是就显得更优雅了?嗯,是的!实际上 Python 中有很多特性会帮助我们的代码更简洁,更优美。接下来这个例子很可能帮我们减轻工作量。

聊聊 OrderedDict

dict 是我们经常使用的一种数据解构。但是在 Python 3.6 之前 dict 都是无序的,即我插入的顺序,和数据在 dict 中存放的顺序并无关联(笔者注:Python 3.6 dict 有序只是新版实现的顺带产物,Python 3.7 正式作为 feature 被固定下来)。但是很多时候,比如在验签等场景,我们需要增加 dict 数据存放顺序,和我们插入顺序是一致的。那么我们该怎么办?老板有需求下来了,我们肯定不能告诉老板这个需求没法做。那我们就自己实现一个ordereddict 吧。于是,想了想,写了如下的代码:

import typingclass 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 插入的顺序。这段代码,看似完成了我们的需求,但是实则存在很大问题。大家可以猜猜问题在哪?

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

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

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

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

上一页 下一页

推荐课程更多>

立即申请体验课

关于我们 | 联系我们 | 沈阳兄弟连IT培训学校

版权所有:培训指南

  • 在线咨询
  • 电话咨询
  • 预约试听