你好,欢迎光临我的博客!

我是一名使用Python的程序员,这是我的github。平时喜欢玩各种电子游戏,这是我的psn:CenterRight这个博客用来分享我的生活、心得以及有关计算机的技术内容。除特殊说明外均采用CC0创作协议(任何人可以自由地复制,修改,分发和演出——甚至用于商业目的,而不必署名)但是转载建议保留原文链接以让读者看到最新的版本。本站所有内容仅代表本人观点,与我的雇主无关,并且会永远保持独立性不受任何组织和公司的影响。如果我们有共同兴趣,或者你遇到了麻烦,可以通过右边的电子邮箱联系我。也欢迎留言和订阅!

Python老鸟想告诉新手的事情

最近reddit有个热帖:Senior Python Programmers, what tricks do you want to impart to us young guns? 很多python老鸟分享了自己的奇巧淫技(也不算特别淫),让我学到不少经验和技巧,觉得有必要整理一下。


尽量使用Python3.6,相比Python2.7,Python3.6有了很多提升,比如enums和fstring(实话说,就算只为了fstring我也要换到3.6)。

无比赞同,最近俺们公司的项目也都换到Python3.6了,俺之前写了个git-ext小工具,也随手升级到Python3.6了。

使用.open()和.close()的代码是“有味道”的,尽量使用with代码块(俺写的with文章)。

关于什么是“有味道的代码”,下面有人给出了回复:

有味道的代码指的是,当你去看这块代码的时候,看不出有什么错,但其实是有bug的……简单说就是欠优化的代码。wiki

每一个项目都使用virtualenv,不要在系统的Python上安装任何东西。这样可以保持产品环境独立、可复制。不使用virtualenv就像生活在垃圾场一样。

推荐pipsi,让每一个python工具使用一个独立的env,推荐virtualenv wapper,创建、使用、删除virtualenv从未如此简单。当然下面也有网友推荐了pipenv

使用csv模块来处理CSV文件…… 网友调侃此条说:就是有人喜欢手工处理CSV,“CSV而已,我用逗号分割就好了”成了他们最后的遗言。

不要嵌套生成器表达式,不然代码难以阅读(此条来自Google style guide)。

我觉得嵌套两层尚可。

如果你在循环的时候需要下标,使用enumerate(items)。

如果你用IDE编程,花点时间去学习它的features,特别是如何使用debugger,如何设置断点。

同意,Vim还是IDE,并不重要,重要的是去掌控你的武器。

multiprocessing,而不是threading。

不同意这点,要看情况,反对任何形式的教条主义。俺最近在翻译一本Python并行编程书,大家可以去看看。

用REPL开发特别方便,比如iPython和Jupyter。优点是可以方便地跳转和编辑运行,还有很多魔术方法。如果你经常用REPL的话,还可以试试ptpython和bpython。

理解解包,比如多重赋值: first, second, *_ = (1,2,3,4)。

避免使用 *args和**kwargs,这会让代码更难读,也会让代码自动补全无法工作。除非你确信自己需要这么做。下面有网友支持,即使你需要这样使用,也并不一定使用这两个名字。比如sum(*numbers)就比sum(*args)要好很多,虽然标准库里面用的*args。

这里提一下,Python3已经支持”keyword-only” argument了,如下:

def compare(a, b, *, case_insensitive=False, locale_aware=False)

在所有写代码的地方使用lint工具,例如pyflakes或pylint。它们会强迫你成为更好的程序员。

其实这一条争议比较大,有一规则个人认为比较死板,比如testcase的函数名应该尽量长并且表达出你要测试什么,因为这些test函数永远不会被程序员调用所以长一点也无所谓。另外80个字符也是比较烦。一开始用是好的,但是用了很长时间还需要lint来规范你就不太好了。手中无刀,心中有刀。(Vim用户推荐一下neomake,异步不阻塞)

当你有什么需求的时候,先check一下标准库有没有你想要的东西,如果没找到……再找一遍。还是没找到的话,就去第三方库找一找(有时候github的搜索框比Google还好用)。Python有比较好的生态,大多数痛点都已经有人做了。

itertools和fuctools是你的好朋友,两个模块对一些常见的问题提供了实用的工具。总之,你对标准库越熟悉,你处理问题的方法就会越清晰、越pythonic。

Understand the decorator. Become the decorator.

好吧这句话无法翻译出来意境,之前写过这篇文章,对理解装饰器帮助很大,可以参考一下。

不要称你自己是个Python程序员,不然如果换了一种语言,你就什么也不是了。称自己为“系统工程师”或“Python开发者”或“自动化专家”。

之前我总是不断打开命令行,载入自己写的模块,测试程序。后来我改变了开发模式,先写一个小的单元测试,然后以后开发只通过运行这个测试来调试。即使这个测试并没有真正assert,只有简单的打印。(欢迎加入TDD)

学习单元测试。当你发现你的代码太长,目的不明确的时候,就需要重构了。写出可测试的、清晰的代码。函数应该有明确的目的,入口和返回值。

经常重构代码。这是你让已经工作的代码变得更好的机会。

变量名字使用单词。Good:counter, probe, product; Bad: c, cnt, prb, prdct, obj

理解生成器。尽可能用它。

并不是所有的东西都必须是class。https://www.youtube.com/watch?v=o9pEzgHorH0

尽可能使用absolute import。

理解Python的线程,GIL和multiprocessing

熟悉asyncio。

永远不要用list(任何可变对象都不要用)作为参数默认值。 (foo=[])

It’s not about how complicated you can make it, but how simple you can make it.

That software development isn’t about tricks. It is about process, communication, and application of best practices.

import this. (Python的哲学

不要重复!如果同样的代码写了两次,就说明有更pythonic的方式,比如:

可以写成:

多调研,少写代码。

永远不要假设“问题出在哪”,要确定。

写小的函数。给每个函数一个docstring,确保代码的可读性。

Don’t use pygame, it’ll only break your heart.

阅读:https://docs.quantifiedcode.com/python-anti-patterns/

 

Vim使用相对行号进行一切操作

使用Vim的一点好处就是,可定制性非常高,如果遇到任何让自己感到不适的痛点,都可以通过配置甚至开发一款插件来解决。开始使用Vim一段时间之后,我发现一个非常“反人类”的地方:Vim的很多命令都是需要查行数的。比如“删除一个函数体”,你就要数数这个函数占了几行(比如5行),然后按5dd。如果是使用鼠标的话就不用数了,可以直接拖过去,但是鼠标效率太低,既然学了Vim就不要让手离开键盘了。我总结了一套方案,几乎所有的操作都可以不用数数来完成。

Vim支持两种行号,相对行号和绝对行号。相对行号是显示每一行距离光标所在行相隔多少行,绝对行号就是顺序显示每一行的序号。

set nu打开行号显示, set rnu打开相对行号显示(更多信息可以查看:help nu 和 :help rnu。效果如下:

打开行号显示

打开相对行号的显示

同时打开相对行号和绝对行号

可以看到,如果同时打开二者,就会在当前行显示绝对行号,其他显示距离当前行的距离。

使用一段时间我发现,在normal模式时,经常进行对多行的操作,这时候如果使用绝对行号,那么必然是要做减法,或者去数数来确定行的数量的。所以在normal模式应该使用相对行号。0对我们来说是没有任何作用的,所以当前行还是显示行号吧。然后一些常用的操作习惯都要相应换一下:

  • 删除n行。3dd是删除包括当前行一共3行,但是因为显示的是相对行号,我们要“删除直到目标行”的时候,要不自觉的+1,比如在上面最后一幅图,要删除直到“终端配色安装”的内容,就要按4dd。有没有办法不用计算,所见即所得呢?有的,d3j即可。command+直到目标行号+方向。例如删除直到上面“我对……”的内容,就是d4k
  • 其余的操作也是类似,例如移动到下面4行,可以用4gg,也可以用g4j。其他还有yank操作等。
  • 替换范围本来是使用绝对行号就可以,例如替换10-20行,就是:10,20s/xxx/yyy/g,但是由于我们normal模式显示了相对行号,那么就要根据相对行号操作了: :.+2,.+12s/xxx/yyy/g (假设当前在第8行)。.在Vim中表示当前行,要善于利用。由于我们显示的是相对行号,所以基本上和行号有关的操作都需要计算完成,用.加上/减去显示的相对行号(所见即所得)

在insert模式时,我们一般都是编辑操作,不会对多行进行判断,此时可能对绝对行号更关心(写到多少行了等)。所以可以监听一下事件,进入编辑模式时使用绝对行号,退出编辑模式进入normal模式时使用相对行号。设置如下:

set nu是因为我一般打开一个文件不一定是编辑目的,浏览的话绝对行号更方便。这样设置可以让第一次进入文件的时候使用绝对行号,进入编辑模式再退出到normal模式时再显示相对行号。

这样用了一段时间,感觉按下的键多了,但是脑子里需要的计算少了很多,还不容易出错,挺好的。

最后附上我的.vimrc,供参考使用,不推荐盲目复制。

 

HHKB开箱

心仪已久的HHKB终于趁着双十一下单了。水一篇开箱博客。

箱子真是小,快递给放到云柜里面去了。包装很好,没有多余的东西,只有键盘本地,一根两头可以插拔的线,以及说明书。说明书也写的简单明白。

我的上一个键盘用的HHKB Lite2,用了一年,觉得很顺手。现在换到HHKB Pro2 Type-s,感觉没有了方向键两头对称感觉更美观了,但是也更不方便了。比如Mac切换屏幕是Control+方向,现在要通过Fn组合实现方向的功能。

键盘手感比较像红轴,但是声音小很多(呼哧呼哧的声音),比Lite2的声音也是小很多。刚上手感觉手感不是很好(感觉还不如Lite2)需要适应一段时间(键帽弧度,手腕高度)等。用一天下来感觉挺好的了。静音电容,能感受到的就是不用按到底就打出来字了。但其实好像并没有什么卵用,本来我们打字是肯定会按到底的。

盒子很小

全家福(卖家送的廉价小物品,键盘托和键帽,拔键器)

其实想买的是黑色的键盘,白色的怕用久了会发黄。但是type-s版本没有黑色款,只有白色(坑啊)。BT版本倒是有,也看了一下,BT版本只比Pro2多了一个蓝牙功能,竟然要贵出800块钱(抢钱啊!)。网上也看大家说type-s的手感更好,于是最终入手了白色有刻。大不了过几年再买一套新的键帽呗。

尝试一下

带到办公室

和HHKB Lite2的对比

除了手感,在功能上和Lite2也有一些不同,比如多了对Mac的音量调节键和亮度调节(别看按键少了很多,其实组合起来比一个普通104键的键盘都多)。

另外我之前使用Lite2经常出现“键盘进入了另一个模式”的感觉,按什么东西都是大写的,好像shift被一直按住似的,不知道怎么调回来。每次遇到这种情况,只好拔掉USB重新插上…… 希望这个键盘没有问题……

 

终端使用技巧

我在github上有一个仓库myrc用来存放自己所有的.xxxrc文件。使用的时候将Linux对应的文件位置软连接到git仓库的路径。这样在新环境下只需要几步就可以配置出自己熟悉的样子。

我一直在不断更新这个仓库,比如.vimrc.tmux.conf,.zshrc等(在偷懒的路上越走越远)。本文分享一下最近的一些更新,不少可以每天给你的生命节省几秒。

  • 使用on-my-zsh的插件,这里有很多自动补全、alias、实用脚本等。之前使用oh-my-zsh只是用theme,简直浪费生命啊!
  • oh-my-zsh的git插件。这个插件非常变态,提供了非常多的alias,基本上所有用的到的git命令都被alias到了1-4个按键。鉴于我30%的命令都是git,这个alias不得不说是……给我续了很多秒。
  • z:这个命令可以让你在各种路径之间自由穿梭,比如在任何地方进入我的myrc项目,只要敲z rc就可以,这个插件会根据“最近最经常使用”的路径搜索一个最匹配的文件夹cd过去(曾经因为换电脑丢失了这个命令,非常痛心,后来一番搜索终于找回来了)。mac可以通过brew安装。
  • oh-my-zsh的sudo插件。按两下ESC键自动打出上一条命令,并且在开头加上sudo。比起按↑+CTRL-a两次+sudo,又省了几秒
  • oh-my-zsh的osx插件。有很多实用命令,比如man-preview git可以打开osx的preview.app查看git的man page。感觉pdf看起来要比终端的舒服一些。还有比如在Finder中打开当前终端的dir等。
  • 使用pipsi。Python开发肯定要用到virtualenv,如果要装一些命令行工具,就比较麻烦:装到virtualenv里面也不是,装到系统环境里面也不是。pipsi可以给每一个命令新建一个虚拟环境,让命令独立于开发的virtualenv和系统的python。管理依赖不再成为噩梦。比如安装我的git-ext命令: pipsi install git-ext。就会新建一个venv叫git-ext,然后将命令以及所有的依赖装到这里面。
  • vim-gh-line。这个前面介绍过,很好用。
  • vim-gist。将当前vim的buffer post到gist。同事之间交流代码片段比较实用。
  • 更多的vim插件可以参考我的vimrc,我组织的还是挺科学的,比较好读。也欢迎给我推荐vim插件,欢迎交流。
  • fpp。全名/PathPicker,接受一个输入(可以是git的输出,其他输出等)然后处理出一些文件路径供你选择,选中之后使用系统编辑器打开。比如git status | fpp会列出一个文件列表,选中一个编辑。(发现比较难解释,试过就知道了……
 

Python 实现class_property

Python的property可以让我们很方便地将一个object的函数当做一个属性来操作,可以赋值、读取。

但是不支持class,如果想使用这个特性,就要实例化对象。我去参考了celery实现的class_property,发现有一行特别难懂,花了很长时间琢磨,终于是看懂了。

celery的代码如下:

使用方法如下:

这段代码能work,而是没有任何问题。装饰在class内的一个函数上,可以将此函数的返回值作为一个class的属性,可以通过class和instance来访问,并且还支持setter。

首先,__init__()比较好理解,传入getter和setter,将其转换为classmethod然后绑定到内部的变量上,以便后来调用。

setter()方法也比较好理解,可以当做装饰器调用, 然后已有的self.__get和传入的setter重新初始化一个class_property,如果已经有getter的话,这里也不会对getter造成影响。参考下面的例子:

然后看__get____set__方法,很明显,这里使用的描述器。(关于描述器,我之前有一篇博文详细讲了描述器——理解Python对象的属性和描述器。)第一个if判断是为了确保用class_property装饰的属性通过instance也能访问到。

最后到了最让人不理解的部分了,如果要我实现的话,是直接返回getter就可以了:

return self.__get()

这样看起来也没问题:访问Logging类的时候,发现没有这个already_setup这个属性,然后试图访问描述器,描述器__get__()中返回self.__get()不正好是getter会返回的结果吗?

于是我将这行return改成了上面我的意思,结果报错:

classmethod为什么不是callable的呢?因为classmethod本来就不是callable的!在之前的文章中,我模拟了classmethod的实现:

它的原理是这样的:Class读一个属性的时候,如果Class.__dict__有该属性并且属性不是一个描述器,就返回该属性;如果该属性是一个描述器,就调用它的__get__方法。

举个例子说,下面的代码:

在调用的时候,先看Foo.__dict__里面的bar是一个描述器,那么调用就转换成:

Foo.__dict__['bar'].__get__(None, Foo)()

回到我们的class_property上来,拿最初的Logging举例。用了class_property装饰的already_setup使用起来(Logging.already_setup)就是:

Logging.__dict__['already_setup'].__get__(None, Logging)()

执行我们的class_property的__get__之后,也就是

Logging.__dict__['already_setup'].__get()

此时,橙色的部分拿到的是一个class_property处理之后的classmethod,而上面我们可以看到,classmethod并不是callable的,而是通过描述器协议,class在调用的时候会默认调用__get__,然后拿到一个和class绑定之后的func,这时候的func才是可以调用的。

celery里面原来写的return,调用链展开就是:

Logging.__dict__['already_setup'].__get.__get__(obj, type)()

其中橙色的部分依然是拿到了classmethod,即:

Logging.classmethod_get.__get__(obj, type)()

classmethod的__get__()返回的是一个unbound method,即

Logging.already_setup()

这样调用就没问题了。

所以说,这里手动调用__get__(obj, type)的原因是,描述器协议之能调用一次,如果返回的还是一个描述器,那么并不会继续调用__get__方法,需要手动调用。