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

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

都不是!

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

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

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

装饰器的妙用

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

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

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

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 函数并被调用。

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

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

但是我们现在的问题在于,每次都需要在单独调用一次 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
这样我们这段代码的注册过程是不是就显得更优雅了?
嗯,是的!实际上 Python 中有很多特性会帮助我们的代码更简洁,更优美。
接下来这个例子很可能帮我们减轻工作量。

聊聊 OrderedDict

dict 是我们经常使用的一种数据解构。但是在 Python 3.6 之前 dict 都是无序的,即我插入的顺序,和数据在 dict 中存放的顺序并无关联(笔者注:Python 3.6 dict 有序只是新版实现的顺带产物,Python 3.7 正式作为 feature 被固定下来)。
但是很多时候,比如在验签等场景,我们需要保证 dict 数据存放顺序,和我们插入顺序是一致的。那么我们该怎么办?
老板有需求下来了,我们肯定不能告诉老板这个需求没法做。那我们就自己实现一个 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 指针恒指向于数据中最后插入的节点。那么插入新的数据时,我们将新的数据插入到卫兵节点之后,从而达成维护插入顺序的目的。

在删除的时候,通过额外维护的一个字典找到待删除的 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 提供了相当多的功能去帮助我们完成日常的工作与学习任务。同时通过去深入地了解 Python 内部的一些功能实现,以便我们能更好地去学习一些知识。比如,上文提到的 OrderedDict 的实现,会让我们学到双头链表的一种非常典型的应用,与此同时,双头链表也会用于诸如 LRU 这样非常常用的数据解构的实现。所以,多去深入了解 Python 的方方面面,有助于我们整体能力的提升。

那么既然电脑上已经有了 Python,为何不将这些 tricks 用起来,让 Python 更 6 呢?安利这位来自德国的大咖,资深养蛇玩家 Dan Bader。

他的博客 dbader.org  每月有超过 20 万的浏览量,Real Python 视频累计浏览量超过百万。订阅他的邮件,每天都会准时收到他更新 Python 技巧,一日不差,是一个极其自律的人。

畅销书 Python Tricks 作者,影响全球 1 000 000 以上程序员的 PythonistaCafe 社区创始人,Real Python 培训机构总编,拥有近 20 年软件开发经验。巴德尔毕业于欧洲历史悠久的慕尼黑工业大学,该校以优异的科教质量闻名,截至 2018 年已经培养出 17 位诺贝尔奖得主。

他把自己对 Python 的理解成书,试图通过一些常用的小例子帮助更多开发者拥抱 Python。

精进Python不二之选

《深入理解Python特性》
Dan Bader 著
孙波翔 译

(扫一扫,新旧封面随机发货)
上市两个月获 Amazon 百余条五星评价,详解用好 Python 需要了解的最重要特性,与《流畅的Python》互为补充,Python 进阶必备。帮助 Python 开发人员挖掘这门语言及相关程序库的优秀特性,避免重复劳动,同时写出简洁、流畅、易读、易维护的代码。
彩蛋
看过书后,我发送了一封邮件给作者。提问他为什么会想把这些特性写成一本书?
他是这么说的:

When I started thinking about the book I'd been a lead developer at this software company in Vancouver, Canada for a while and we were doing lots of Python web development.

最开始有写书计划的时候,我在温哥华的一个软件公司担任开发团队的头儿,我们那会儿正在做大量有关 Python web 的开发工作。

I'd been a passionate user of Python for several years at that point and I always tried going out of my way to teach others at the company how to write better, cleaner, and more Pythonic code.

那时,我是一个狂热的 Python 用户,而且我不遗余力地想教会公司其他人如何编写更简洁易用,更 Pythonic 的代码。

I felt like there were already lots of good resources out there that taught people how to get started with Python, but there was a lack of material that showed the little "tricks" and idioms that are a sign of someone graduating from beginner level to intermediate and beyond.

我意识到已经有很多资源可以教会人们如何入门 Python,但是还是缺少一些能够帮助了解其特性和习惯用法的内容,这样的内容可以帮助一些初学者进阶到更高的水平。

I also wanted to make learning this stuff fun and engaging. I got so much joy from using Python in my day to day work, I wanted to get a chance to share that love and passion for Python as a programming language.

我还想把学习这些东西变得更有趣。在工作中使用 Python 使我得到了很多乐趣,我也想有机会分享对 Python 这门编程语言的热爱。

Writing Python Tricks was my outlet for that. My ideal reader is someone who really wants to make Python their own and learn how to write clean and Pythonic code. I want them to discover best practices and little-known tricks to round out their knowledge, and to fill up their "Python toolbox" one step at a time.

写这本书就是我对那份热爱的实现。我想写给那些想用好 Python 或想写出 Pythonic 代码的人。我希望他们发现这些 Python 的最佳实践和鲜为人知的技巧以完整他们的知识体系,并且一步步地填满他们的“Python工具箱”。

每一个大佬都有一个济世的梦想,他们善于分享,拥抱开源,并致力于建立良好的生态,不愧是养蛇专业户。

好了!不能再说了,我要去码代码了。回见!

文末畅聊

小伙伴都修炼了哪几门编程语言,平时工作或者学习中使用哪个比较多呢?可以说说你们相爱相杀的故事,或许它还有更多可爱之处等着你去发觉呢?精选留言选出 5 位获得赠书。活动截至 2019.10.15。

题图来源:Freepik.com

漫画制作:图晓轩

推荐阅读:
☟☟☟ 更多养蛇方法

后悔没早知道这些Python特性相关推荐

  1. 华为起诉最新进展,国内航司暂停运行有关客机,收买家庭不得继续抚养被解救儿童,脸书创始人后悔没早学微信,这就是今天的大新闻...

    今天是3月11日 农历二月初五 今天星期一 没带耳机线 感觉这样用手机 是没有灵魂的 下面是今天的大新闻 华为起诉最新进展 (环球网)多个来自美国的消息证实,美国的联邦法院已经就华为公司起诉美国政府一 ...

  2. 广州楼市:行情大秘密,买房者后悔没早知道

    我是广叔谈房,资深房产投资专家.你相见恨晚的买房导师,目前为提供买房最佳解决方案.广叔不像其他自媒体,遮遮掩掩的让你摸不清头脑!广叔属于实战派只说对你最有用的操作和建议. 关注"广叔谈房&q ...

  3. 985计算机硕士能进阿里吗,阿里员工:985硕士毕业,35岁才混到P7,后悔没早点入行!...

    很多到了一定年纪都会后悔自己当初做的决定,后悔自己当时没有进入其他行业,或者是后悔当初做的选择,我相信每个人都有过这样的经历,不同的选择的确会对现在的人生有很大的变化. 最近在职场论坛就看到一位阿里员 ...

  4. 误导孩子一生的20个坏习惯,真后悔没早看到

    当孩子还不理解真正意义上的对和错的时候,需要我们来告诉他.而我们自己呢?我们是否遵守了人生和道德的准则?是否在毫不自知地给孩子以错误的引导? 坏习惯1:不耐烦 工作繁忙压力大,回到家看到孩子又吵又闹, ...

  5. 超好用的在线PDF转Word网站,真后悔没早发现

    把PDF转成Word还是经常干的事情. 然而,下载一大堆软件来干这件简单的事情还是显得过于繁琐和复杂. 何况很多网上的软件安装常常出问题,在度娘里面搜索,然后下载又会经常遇到钓鱼网站,电脑" ...

  6. python自动化运维与开发岗位_新课 | 运维开发工程师必学的Python自动化运维课程,学完后悔没早点学!...

    原标题:新课 | 运维开发工程师必学的Python自动化运维课程,学完后悔没早点学! 马哥教育2017年Python自动化开发实战班,根据目前企业需求的Python开发人才进行了深度定制,加入了大量一 ...

  7. python版本越高越好吗-5个Python特性 越早知道越好的

    Python Python开发 Python语言 5个Python特性 越早知道越好的 Kirill Sharkovski 发布在 Unsplash 杂志上的照片 AI开发者按,Python 是近十年 ...

  8. 5 个越早知道越好的 Python 特性

    2019-12-29 18:04 导语:Python 有很多特性,初学者很难一开始就掌握所有的特性.现在我想要重点介绍其中五个最重要的特性. Kirill Sharkovski 发布在 Unsplas ...

  9. 5个Python特性 越早知道越好的

    Kirill Sharkovski 发布在 Unsplash 杂志上的照片 AI开发者按,Python 是近十年来兴起的编程语言,并且被证明是一种非常强大的语言.我用 Python 构建了很多应用程序 ...

最新文章

  1. springboot中使用mybatis
  2. 我对软件行业及大数据的理解
  3. hdu 5616 Jam's balance(简单dp)
  4. 卡尔曼滤波MATLAB代码实现
  5. SAP CDS entity 中使用 @readonly 进行访问控制
  6. Java中这7个方法,一不小心就用错了!
  7. oracle初始化序列值,如何修改序列(Sequence)的初始值(START WITH)
  8. [Chrome]查看内存使用命令
  9. c语言中英互译程序,c语言怎么翻译? 程序怎么运行?
  10. ubuntu安装nginx报错:Failed to start A high performance web server and a reverse proxy server
  11. 洛谷P3332 K大数查询
  12. nmos导通流向_MOS管类型-MOS管4种类型与工作原理解析
  13. python怎么打字母_如何用python打印字母表?
  14. 香蕉树上第五根芭蕉——机器学习算法PCA算法通俗易懂说明
  15. 访问局域网计算机切换用户,图解“访问局域网中的电脑时提示输入帐号和密码”...
  16. 麻省理工MIT计算机课程表
  17. 郭彦甫Matlab第五节笔记——进阶绘图
  18. 字符串短横线和驼峰形式的互相转换 Javascript
  19. (翻译)自我监督模式(Self-Monitoring)
  20. Ubuntu软件包下载在哪里,安装到哪里软件包备份删除旧的软件包

热门文章

  1. c语言 求sin近似值,用泰勒公式求sin(x)的近似值
  2. oracle 控制文件在哪里设置_从Oracle到PostgreSQL:最全控制文件
  3. sql实现random sample_web验证码的简单实现
  4. 组件php53 php55区别,PHP53、54、55、56各版本重要更新简述(上)
  5. solidworks模板_SolidWorks文件属性分类和创建方法,图纸自动属性的基础
  6. 大规模数据库的性能改善方法之一 数据分割
  7. 2017,公司必须换掉的六种人,别心软!
  8. RHEL\CentOS 7 下 MySQL 连接数被限制为214个
  9. 预写式日志(Write-Ahead Logging (WAL))
  10. 一个bug隐藏了另外一个bug,reloaddata,