健康茁壮成长的Python项目需要什么?

Python语言以简单易懂,上手难度低著称,遗憾的是,很多人保持着轻松上手后的水平,写了很多“面条式的”代码(我也写过很多)。一个项目如果想要写好,以可持续的方式发展,就不能和写练习的脚本一样,除了编程功底,一些保证项目质量的工具也必不可少。不然公司就像是个小作坊一样,以野路子的方式开发,浪费大量的人力,甚至把刚接触 Python 的新人带歪了。本文总结通常一个可持续交付的 Python 项目应该如何开发。这方便的知识也可以通过逛 Github,看一下一些经典的项目(例如 requests,django,sentry)是如何解决这些问题的。

1. 单元测试

王垠曾经说过:

我喜欢把编程比喻成开赛车,而测试就是放在路边用来防撞的轮胎护栏……

在没有测试的项目中写代码,就感觉在没有护栏的跑道上开车,心里很没有安全感。尤其是代码需要重构的时候。要么你要浪费时间每次都自己手动跑一下程序测试一下(测试也是自动化的一种),要么就要自信,相信自己的改动是绝对正确的。

Github 上面的代码就很方便了,有很多公开的服务可以使用,比如 travis,codecov.io 可以用。

私有项目就有点复杂了,需要自己搭建测试环境。之前用过 bitbucket,用我们自己开发的 CI badwolf 。这个是基于 docker 的,项目中有一个 Dockerfile,badwolf 负责以下一些工作:

  1. 对 PR 和 master 的代码在 Docker 中进行单元测试
  2. 对评论 “ci rebuild” 的时候重新从 Dockerfile build 一个image
  3. 3个人 approve PR 之后自动将 PR 合并

CI 要面对一个问题:测试环境和执行测试的命令如何分离?

Travis 是每次都会执行所有的命令,每次推送都会 install 依赖,运行测试。这样的好处是配置简单,配置环境和跑测试用一个文件就够了,清晰明了。缺点是每次都重新安装,速度慢,浪费资源。

badwolf 是只有 rebuild 的时候才会重新构建镜像,否则一直用同一个镜像来测试。这样的缺点是要制定 Dockerfile,并且要处理好环境,每次测试不能有副作用。优点是速度快。

现在我们用的是 Gitlab。Gitlab 有了 Runner 的概念,其实是更适合大团队一起。只要配置好了 shared runner,所有的项目都可以用,runner 跑完了一个去跑下一个,FIFO很公平,也节省了资源。缺点是,我觉得 Gitlab 是配置CI最复杂的,需要自己部署好 Docker 仓库,然后制定 .gitlab-ci.yml文件。

Gitlab其实比较灵活,你可以在Docker提供一个最基本的 Liunx 环境,在 yml 配置文件中写 install 的东西,每次 CI 都去安装。也可以将环境打包在后者,每次需要更新依赖就要重新 build image,但是 push 代码的时候速度快很多。个人偏爱后者。

2. 代码覆盖率

有了单元测试,如何说服同事保证写测试呢?靠价值观肯定是不行的。

对开源项目来说,有很棒的 codecov 可以用,能生成所有代码每一行跑的次数,覆盖率,changes 带来的覆盖率改变,这些都是最基本的功能了,还有什么 Github 机器人,pipeline(如果 PR 下降了覆盖率,或者新加的代码没有覆盖就不能 merge)更是酷炫。

但是对于私有项目来说,要么给钱,要么自己搞。badwolf 这种自己写的CI比较土,我们是直接在项目的最后打印出来 coverage.py 的输出结果,虽然土,但是有用。覆盖率的保证也需要我们的同事在对不好的 PR 下面留言: “testcase please!”

Gitlab 其实也好不了哪里去,除了打在 log 的最后,在它的 project settings 里面还有一个功能,写一个正则表达式,将覆盖率匹配出来显示。还是土了点,至少有点用吧。另外也可以用 coverage.py 生成的 html 报告推送到 pages 上面,这里有一篇教程,不过那样你又要去维护一个 pages 服务器(还要注意不对外网开放)。

3. 代码质量检查

PEP8,flake8,pylint 傻傻分不清楚?看这里吧

这种东西,一开始会挺痛苦的,后面对团队收益很多。

4. 保护master

组织任何人向 master push 代码。比较理想的情况是,develop 实时部署到 staging 情况,定期发布到 master。

5. issue追踪系统

类似 Github 的 issue,记录 bug 等未解决的问题,现在除了 bitbucket 丑的基本不能用,其余很多都不错的。

6. code review

对于开源项目或者团队合作来说,review 是非常重要的。

很多人觉得 review 太浪费时间了,业务这么紧,没空搞。但是我觉得这是去了解别人工作的一个好机会,并且互相学习,进步特别快。尤其是对一个刚毕业的同学来说,review几乎是快速成长的一条捷径。review由于合并速度,占用大家的时间等原因,慢是慢了一些。但是节省了很多潜在的时间,利远大于弊。

暂时想到这么多,里面每一个项目如果细开去说还有很多细节问题。甚至每一个点都够成立一个技术公司了。

一定要做正确的事情,不要被业务逼得去走歪路。

 

Git 在不同的项目使用不同的author

安装好 Git 的时候,每个人都会设置全局的账户:

公司的 Git 账户和个人的 Git 账户不一样,可以在项目中设置此项目的 Author :

这样私人账户每次都要设置,可以设置一条 Git 的 alias ,这样每次需要将这个仓库的 author 设置成私人账户,只要敲一下 git private 就可以了。

其实 git config --global 命令是修改的一个文件,$HOME/.gitconfig 例如我的文件如下:

同之前提到的 myrc 项目一样,gitconfig 这个文件我也是用 git 来追踪的,去一个新环境只要安装好这个项目就可以回到自己熟悉的 git 了。

 

OS X 多个 Python 优雅共存

首先解决了一个 Pyenv 多版本共存的问题,之前要么用 Python2.7 要么用最新版的 Python,现在怀疑是 Python3.5 导致的问题,所以要用 Pyenv 装一个多Python的环境。

在 OS X 上面安装 Pyenv 非常简单:

用 Pyenv 下载一个新的Python:

无奈公司的网络实在太差,卡在 Downloading 那一步很久都下载不完,只要自己挂代理下载下来然后从本地安装:

可以看到 Pyenv 里面已经新加了一个可执行的Python了(Pyenv挺有意思的,有兴趣的可以去看看它的原理):

这个使用 virualenv 创建 Python 的虚拟环境也可以用指定的 Python 版本:


好了,折腾这么一顿,实际上是想试一下今天遇到的一个诡异Bug, Python3.5 的 json 传进去 bytes 就挂了。

好吧,看来 Python3.6 之前确实不支持对 bytes 直接 load() 。

文档都写着的嘛……

PS: 今天发现 Python 官方网站有了交互式的 shell (也许很久就有了),是接入的 www.pythonanywhere.com 支持各种版本的 Python,很好用:

 

奇葩网站吐槽第四弹

这个周开始,爬虫的工作应该会告一段落了,《奇葩网站吐槽》系列全四弹也算截止了,enjoy!

前期回顾:

  1. 吐槽一些神奇的政府网站
  2. 奇葩网站吐槽第二弹
  3. 奇葩网站吐槽第三弹

本期内容:

20171207 这是12小时制还是24小时制?

20171213 遇到一个网站,带有很多参数,经过发送请求发现有些是固定的,是我要抓的页面定位的参数,有一些是改变的(很迷,甚至要通过不断向服务器发送一个时间戳保存session的生命),终于写好爬虫之后,过了几天发现这个爬虫没有工作!仔细观察发现,有些参数是按天变的……


这…… json请求有必要吗?而且这i18n……


20171225:第一次见可以带两个//的url……


20171228 老哥你这SEO不走心啊

网站介绍很朋克

keyword也很朋克


2018年1月5日更:大哥你这个“19月”可坑死我了……


2018年4月3日更新:今天发现一个网页返回的内容变了,不应该啊,打开一看,原来的 url 返回的内容变成了:

JS重定向到 index.html 首页?打开首页一看,首页原来是 HTTP 的,现在重定向到了 HTTPS (还是302状态码,槽点太多了)。

好嘛,用 JS 作 HTTPS 重定向的还是第一次见。


2018年4月10日更新:这网站日期最远的内容排在第一页,今天的内容排在最后一页。所谓“倒序排列”?

第一页只有年底的内容。

最后一页竟然是今天的内容。

 

使用brew升级Python之后修复virtualenv和pipsi

brew 升级和执行 brew cleanup 之后 Python virtualenv 的路径总是出问题,今天算是把问题找到了,但是却没找到好的解决办法…… 最终的解决方案是将所有的 virtualenv 删除重装。急着修复问题的可以不看中间的尝试直接拉到最下面。

以其中一个虚拟环境为例:

可以看到之前有一个 .Python 文件是软链接到 brew 安装的 Python3 的,以这次升级为例,3.6.4_4 这个版本已经不存在了,目前有的是 /usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/bin/python3.6 ,本打算将新的路径作为 .Python 的软连接:

但发现现在 pylint 是可以执行了,但是找不到依赖的包,于是又写了一个脚本,打算找到其所有的软连接,全都链接到最新的 3.6.5 版本中:

发现执行完之后还是找不到相关的包:

没办法,先重新建一下 virtualenv 吧,以前是这样处理的,应该也是最稳的方法了。

重新安装 pipsi 列表

我的 Python 命令行工具是用 pipsi 安装的,这个工具和 pip 的不同是: 它不会把模块装到系统 Python 而是每一个包新建一个 virtualenv ,然后在这个 virtualenv 中安装,然后链接 bin 的 Path。将所有 pipsi 安装的工具重装的命令如下:

pipsi list | grep -Eo ‘”(.*)”‘ | cut -c2- |rev | cut -c2- | rev | xargs -I {} — zsh -c ‘pipsi uninstall {} –yes ; pipsi install –python python3 {}|| pipsi install {}’

解释如下(按照颜色匹配):

  1. 打印出所有package,比如 “pylint”
  2. 砍掉第一个双引号,翻转,砍掉第一个双引号,再翻转(实际上就是去掉双引号)
  3. 将前面得到的干净包名传给下面的子命令,其中 {} 将被替换成包名
  4. 先卸载,再尝试用 python3 安装,如果失败改用 python2 安装

pipsi 的包就重建好了。virtualenv 里面的工作用的 env 自动重建了没什么意义,因为各个 env 的依赖都是根据项目的 requirements.txt 安装好的。还是用到什么再次重建什么吧。