Tag Archives: python

2021年Python工具链

1. Python虚拟环境:Poetry

一个类似Pipenv的Python虚拟环境和依赖管理的工具,据称改善了一些Pipenv的问题。对我而言,主要看重了Poetry可以对Python库打包的功能。毕竟对我而言书写 setup.py 并不是一件很让人愉快的事情。

2. 代码静态扫描:Flake8

Flake8使用起来非常简单,不用这么配置就可以直接使用,之后检查过程中遇到自己不需要的规则,加个例外就好。

Flake8支持插件,通过添加插件还可以让Flake8功能变的更为强大。

3. 代码自动格式化:Black

写代码时,我个人会尽量遵守 PEP8 ,但难保团队中有些人代码写的有些随意。为保证编码风格的统一,在代码提交前统一由Black对代码镜像格式化。自动格式化之后的代码可能会少了那么一点个性,但为了统一还是值得的。

4. Import规则检查&格式化工具:isort

Black不会对Python 的 import 语句进行排序和分段,这个工作就交给isort来做了。

5. 类型检查:Mypy

长久以来Python作为脚本语言,程序里没有类型信息,很多本可在编译阶段发现的问题被保留到运行时。Python在3.5之后开始支持 Type Hint 了。利用Mypy可以利用这些类型信息对程序进行校验。

6. 单元测试:pytest

相比 unittest ,pytest使用上更为方便。更为重要的是pytest兼容 unittest,似乎没有什么理由来拒绝pytest。 

7. 测试覆盖率:Coverage.py

代码覆盖率测试工具好像也没有第二个选择。

8. pre-commit

git commit 时调用flake8进行代码检查,调用black对代码进行格式化等操作。利用pre-commit从源头上杜绝有人把不合格的代码提交到代码库。

9. Docker、Gitlab-CI、GitHub Action、Travis CI

CI服务可根据自己的实际情况进行选择

Python多进程环境下日志模块导致死锁

近期公司的一个Python程序在启动新进程的时候总是会失败。在进程里可以看到对应的进程已经创建成功,但对应代码并未执行,且没有输入任何日志。

通过定位,发现问题源自Python的logging模块,在写文件模式下,logging模块是不支持多进程的。

问题产生原因

Python默认采用Fork方式创建新进程,在Fork新进程的时候会连同 也一同复制到新进程。

  1. 当主进程里有两个线程T1/T2,以及一个锁Lock1。
  2. 线程T2获取了锁Lock1,此时线程T1创建了一个新进程P2,此时Lock1被一同frok给了P2。
  3. P2执行时尝试等待Lock1解锁。由于线程T2不会被复制到P2,没有人给P2线程的Lock1解锁,导致P2死锁。

Python的logging模块在写文件时会加锁,由于锁被复制导致进程死锁。

注:由于创建新进程时锁会被复制,混用多进程和多线程时的加锁操作应当格外小心。

解决方案

根据Python的官方文档,logging模块不支持多进程模式下将日志保存到单一日志文件。多进程模式下日志保存方案,建议参考Python官方文档 Logging to a single file from multiple processes

参考资料:

Telegram机器人

最近重新开始玩Ingress。好多年没玩,主要玩家已由QQ转战Telegram了。还有玩家专门为Telegram做了个Bot用来做新人接待、面基统计等相关工作。稍微研究了一下Telegram的Bot实现,发现Telegram API功能非常强大,而且使用起来也很简单,可以轻易的做出自己的机器人。

如果你想更多的了解Telegram Bot可以做什么,怎么创建一个自己的Bot建议阅读Telegram的官方文档 Bots: An introduction for developers。如果你和我一样使用Python进行开发,可以使用python-telegram-bot进行开发。

让Telegram Bot主动推送消息

一般情况下Bot都是在接收到用户的命令后被动的回复信息,如果希望机器人主动推送消息可以先手动查询chat id,然后Bot发送消息时指定为该chat id。获取chat id的方法如下:

  1. 和机器人对话。如果希望获取group的id,这需要先将机器人加到group,再@bot /xxx给机器人发消息。
  2. 访问 https://api.telegram.org/bot<YourBOTToken>/getUpdates获取消息。
  3. 访问getUpdates接口后将得到一组JSON数据,里面哪个是chat id还是比较容易识别出来的。

一个简单的机器人实例

发送命令51job,这个机器人会调用jobmonitor检查51job的岗位更新情况。

招聘网站岗位信息更新监控工具

项目地址: https://github.com/vicalloy/jobmonitor/

image codecov.io

一个监控招聘网站工作岗位更新情况并发送通知的小工具。很早之前写的一个小脚本,近期重构了一下,让这个脚本可以更方便的扩展。

目前只做了前程无忧以及V2EX的支持,欢迎添加其他网站的支持。

目的

  • 招聘网站的问题
    • 招聘网站每天都会显示大量的岗位更新,但大多岗位都是常年发布,要从这些岗位里过滤出真正更新的岗位并不容易。
    • 招聘网站的搜索功能还不够完善,做不了高度个性化的定制化搜索条件。
    • 专业论坛的招聘版块,几乎没有搜索功能。
  • 这个工具可以做什么
    • 支持定制招聘网站搜索条件,并对网站提供的标准搜索功能进行少量增强。
    • 对检索到的工作岗位进行过滤,如果该岗位之前已发布过,自动忽略。
    • 可部署在服务器上,设置定时任务方式定时推送岗位更新,支持多种消息推送方式。
    • 新工作岗位通知方式支持:显示到控制台、保存到文件、发送到 Slack (强烈推荐 Slack )。注:如果想支持微信、邮件的通知,需要自行扩展。
    • 内置了 51JOB 和 V2EX 的支持。注:如需要支持其他招聘网站,需要自行进行扩展。

使用范例

  • 初始化项目
  • 创建jobs.py。使用python jobs.py执行查询。
  • 可在服务器上使用 crontab 设置定时任务,定期检查

代码导航

  • monitor.py
    • JobMonitor 工作岗位监控基础类
    • QCWYJobMonitor 51JOB岗位监控实现
    • V2exJobMonitor V2EX岗位监控实现
  • storage.py
    • JobMonitorStorage 存储区基础类
    • JobMonitorJsonStorage 将信息以json方式保存到文件的存储区实现
  • message.py
    • BaseMessageBackend 消息发送处理后端基础类
    • IMMessageBackend IM类消息的后端基础类
    • CLIMessageBackend 将消息发送到控制台
    • FileMessageBackend 将消息保存到文件
    • SlackMessageBackend 将消息发送到Slack
    • TelegramMessageBackend 将消息发送到Telegram
  • models.py
    • Job 岗位信息基础数据类
    • QCWYJob 51JOB的岗位信息解析类
    • V2exJob V2EX的岗位信息解析类

注: 还为这个工具做了一个 web 前端界面 https://github.com/vicalloy/jobmonitorweb/ 可通过 web 端查看推送信息。不过个人觉得用 slack 或 telegram 接收和查看推送信息更方便。这个项目里使用了Django Channels来做Web端的实时消息推送,如果感兴趣可以参考一下。

中心化服务问题

最初也考虑过将这个功能做成服务,用户可以通过WEB界面配置自己的订阅规则和消息的接收方式。不过一般网站都会有反爬虫的处理,如果服务器对一个网站访问过于频繁很可能会被该网站给ban掉,这个方案不可行。

当然,如果真想把这个功能做成服务还是有办法的。可以将主要功能用JS实现,用户将数据抓取的规则配置和历史记录保存在服务器。用户打开浏览器手动刷新岗位信息,数据抓取通过用户的浏览器完成。

12306刷票工具

项目地址: https://github.com/vicalloy/12306-ticket-checker

只是在刷出票后发送提醒消息,并不能自动购票。在收到消息后还是得拼手速。 脚本用 Python3 实现,可挂到服务器上 24 小时刷。

前言

总体来说火车票应当是越来越好买,因此一直没怎么太操心。哪知道今年票似乎没有很好买,最近在 12306 刷了几天一张票都没看到。广大抢票软件又都只支持 Windows 系统,作为 Mac 用起来不是太方便。
弄了一个小脚本挂到服务器上,在查询到有符合条件的车票后将通过Slack将消息推送给我。

注意事项

  • 脚本采用python3开发,请使用python3运行该脚本。
  • 在刷到票后,采用 Slack 发送通知消息,因此请先创建Slack的Team。在创建好Team后,创建一个名叫ticket的channel,并申请一个Bot用于发消息。如希望采用其他的通知途径,请自行修改12306.py中的send_message实现。

后记

一大早就刷出了一大波票,不过等我兴冲冲的打开手机客户端一查,连个票的影子都没看到。可能是经过几年的发展抢票市场也日渐成熟,所有的票都在第一时间给抢票软件给刷走了。

TODO

目前这个脚本只能实现余票的提醒功能,但理论上要实现自动购票的功能并会太难。buy.py中给出了登陆的基础实现,不过考虑到实现所有功能所要付出的时间成本因此不打算继续了。
注:也是因为票被秒光的速度太快,估计折腾完也用处不大。

自动购票最大的障碍还是来自于12306的验证码。

验证码的处理思路

手动处理验证码

手动处理验证码应当是最简单有效的处理方式,当前缺点也很明显,无法做到全自动。Slack的API非常强大且易用,通过Slack的”Real Time Messaging API”,我们可以利用Slack实现交互。在需要输入验证码的时候,通过Slack将验证码推送到用户,用户在完成验证码输入后,系统自动处理之后的业务逻辑。

自动识别

要做好验证码的自动识别时间就比手动处理要麻烦多了。如果不是想卖给黄牛我个人是觉得没必要打自动识别的主意了。

12306的验证码可以分为2部分,最顶部的文字以及下方的8张图片。

文字的变形其实并不算太大,相信以现在OCR的水平识别率还是挺高的,重点是下发的8张图片。12306的验证码图小分辨率低,不说机器,要人来识别都不容易。如果纯粹根据机器学习来做图片识别,即使学习库再大效果也不会好到哪去。

最有效的还是”笨方法”,让系统频繁的去请求12306的验证码,然后手工将所有图片打上Tag。考虑到12306的图库不会太小,给图片打Tag必然会有很大的工作量,如果没有利益驱使是百分百做不来的。
另一方面,即使前期做了非常多的准备工作也很难保证12306不会添加新的图片。在遇到不认识图片的时候最简单的方法自然是先将图片记录下来等待手工加Tag,另一方面重新刷新验证换个自己认识的。
Google有提供上传图片进行搜索的功能,可以把图片上传到Google然后得到图片的关键字(当然精确度不会太高)。在图片资料库不够完整的时候也可以利用Google来猜些图。

Python 3的type hints

近年来新出的语言Go、Rust、Swift都无疑例外的是静态类型。随着软件的越来越复杂,动态语言“太过随意”的缺点也越来越明显。此外随着IDE的发展,静态类型语言“繁琐”的缺点也得到了很好的规避。
在学习Swift的时候,Swift的类型推导给了留下了很深的印象。类型推导或许是现阶段兼顾类型检查以及动态语言简洁的最佳解决方案。
Python从3.5开始加入了type hints(类型注释)。

从名字“type hints”就可以看出,只是类型注释并不是强制要求。由于只是“注释”,“type hints”需要第三方工具的配合。你需要使用Mypy来对代码进行语法检查。另外PyCharm在新版本中也加入了对“type hints”的支持。

对我而言“type hints”无疑是一个非常棒的特性,让Python也具备了类似Swift的类型推导功能。希望日后的各类Python都能加入type hints的支持。另外就希望各类开发工具可以充分的利用type hints特性。

注:
Swift中使用“?”表示option,Python里则需要写成Optional[str]相比之下有些太过繁琐。

django-lbutils

在Github上公开了不少项目,不过其中大多都算不上“开源”。在我看来开源也应当是一件认真的事情,需要对项目持续的维护,同时也需要提供必要测试用例及文档以保证项目的质量。
最近打算将自用的Django工具组件做个简单的整理然后发布了。为了让项目不要太“随意”,开始补测试用例及文档。因为是自用,测试用例和文档几乎为零。最初只是想简单的修葺,不想真正做起来工作比预期的要高出很多。整了许久代码中的注释依旧很不全,测试的覆盖率都还未达到80%,文档更是没开始动。因为文档还没弄完,所以项目也不算正式发布,如果你感兴趣可以先去看看django-lbutils
为了这个项目,尝试了一些之前经常看到却一直没怎么使用过的工具。

Tox

Tox是一个Python的自动打包测试用具,用来测试Python库在不同环境下的兼容性。因为是自用,本机环境跑起来是没啥问题。不过在兼容性测试时部分测试用例在Python3以及Django的某些版本下跑起来会有问题。为了搞定兼容性花费了不少时间。

Travis CI

Travis CI是一个在线的持续构建平台。Travis CI会检查你在Github上项目的变化,每当有新push的时候进行自动编译。我现在是每次改动后,直接在Travis CI看测试用例的执行情况。

Coveralls

Coveralls测试覆盖率查看工具。结合Travis CI,可在每次测试完成后将测试覆盖率信息推送到Coveralls。在Coveralls可方便查看当前的测试覆盖率。

Read the Docs

Read the Docs文档托管服务。可从Github抓取文档并自动编译好生成在线文档,目前几乎所有的Python库都将文档托管在上面。

用程序生成word文档(DOC)

很多程序都支持导出PDF文档,不过如果需要对导出文档进行编辑PDF就显得不那么方便了。就国内环境而言,导出word文档对有编辑需求的文档而言更为合适。
由于我使用python,因此这里只讨论python下可用的方案。目前找到的解决方案主要有下面几种

  • python-docx
    目前能找到支持word格式的库非常的少,就python而言只找到 python-docx 算是相对可用的解决方案。python-docx 的功能非常的弱,有很多的限制,比如不支持模板等。如果想生成复杂文档那就无能为力了。
  • POD
    POD是Appy框架的一部分,可使用ODF (Open Document Format)文件做为模板,并输出ODF格式的文件,并可调用LibreOffice将生成的文件转换成DOC等格式。相比python-docx,POD用于生成更为复杂的文档。但如果你需要动态生成一些复杂表格,POD可能会有些问题。
  • unoconv
    unoconv是个文档转换工具,可调用LibreOffice对文档格式进行转换。unoconv可将HTML转换为DOC格式。因此可先生成HTML,然后再将HTML转换为DOC。生成HTML对广大的WEB开发者而言无疑是轻而易举的,这也是我最终选择的方案。不过要注意的是用unoconv将HTML转换成DOC,遍地是坑-_-!。

    • 只支持有限的HTML语法。很多CSS语法根本就不支持,看到的和转换出来的效果完全不一样。解决办法:在LibreOffice中编辑文档,然后保存成HTML,然后对保存的HTML进行编辑。
    • unoconv的-t参数可传入.ott格式的文档模板。默认情况下LibreOffice转换出的表格的行高远大于文字的高度,更糟糕的是文字还是顶对齐,非常不美观。编辑个空文档,然后保存为.ott,转换的时候指定模板文件可解决该问题。
    • 生成的文档的左右边距不一致。恩…,这个我还没有找到解决办法。

让你的python程序同时兼容python2和python3

python邮件列表里有人发表言论说“python3在10内都无法普及”。在我看来这样的观点有些过于悲观,python3和python2虽然不兼容,但他们之间差别并没很多人想像的那么大。你只需要对自己的代码稍微做些修改就可以很好的同时支持python2和python3的。下面我将简要的介绍一下如何让自己的python代码如何同时支持python2和python3。

  • 放弃python 2.6之前的python版本
    python 2.6之前的python版本缺少一些新特性,会给你的迁移工作带来不少麻烦。如果不是迫不得已还是放弃对之前版本的支持吧。
  • 使用 2to3 工具对代码检查
    2to3是python自带的一个代码转换工具,可以将python2的代码自动转换为python3的代码。当然,不幸的是转换出的代码并没有对python2的兼容做任何的处理。所以我们并不真正使用2to3转换出的代码。执行 2to3 t.py 查看输出信息,并修正相关问题。
  • 使用python -3执行python程序
    2to3 可以检查出很多python2&3的兼容性问题,但也有很多问题是2to3发现不了的。在加上 -3 参数后,程序在运行时会在控制台上将python2和python3不一致,同时2to3无法处理的问题提示出来。比如python3和python2中对除法的处理规则做过改变。使用-3参数执行4/2将提示 DeprecationWarning: classic int division 。
  • from __future__ import
    from __future__ import”后即可使使用python的未来特性了。python的完整future特性可见 __future__ 。python3中所有字符都变成了unicode。在python2中unicode字符在定义时需要在字符前面加 u,但在3中则不需要家u,而且在加u后程序会无法编译通过。为了解决该问题可以 “from future import unicode_literals” ,这样python2中字符的行为将和python3中保持一致,python2中定义普通字符将自动识别为unicode。
  • import问题
    python3中“少”了很多python2的包,在大多情况下这些包之是改了个名字而已。我们可以在import的时候对这些问题进行处理。

  • 使用python3的方式写程序
    python2中print是关键字,到了python3中print变成了函数。事实上在python2.6中已经带了print函数,所以对print你直接按照2to3中给出的提示改为新写法即可。在python3中对异常的处理做了些变化,这个和print类似,直接按照2to3中的提示修改即可。
  • 检查当前运行的python版本
    有时候你或许必须为python2和python3写不同的代码,你可以用下面的代码检查当前系统的python版本。

  • six
    six 提供了一些简单的工具用来封装 Python 2 和 Python 3 之间的差异性。我并不太推荐使用six。如果不需要支持python2.6之前的python版本,即使不用six也是比较容易处理兼容性问题的。使用six会让你的代码更像python2而不是python3。

python3的普及需要每位pythoner的推动,或许你还无法立即升级到python3,但请现在就开始写兼容python3的代码,并在条件成熟时升级到python3。

注:

编程语言们各自的哲学

曾有同事打算将ZOPE和PLONE啃下,我是不建议的。同事说我不够开放,对自己不喜欢的技术都很排斥。我承认,每个人都会有自己的偏好。但我不赞成使用ZOPE恰恰不是因为偏好问题,我也不会因个人偏好而建议采用或不采用某项技术。

在我看来ZOPE是一个很变扭的技术。ZOPE引入了接口/容器等概念,给人感觉ZOPE在很多方面都在有意的模仿JAVA。Python和JAVA在语言哲学方面有着比较大的差异,试图以Java的方式来做一个Python的WEB开发框架无疑是有些别扭的。如果ZOPE真的学的特别象,那我为什么不干脆直接使用Java?

这世界上存在各种编程语言,每种编程语言都有着自己的特点,正是这些差异满足了各类人的不同需求。这些编程语言都有着自己最核心的思想,这个核心思想就是所谓的“哲学”。没有自己“哲学”的编程语言是无法在这个世界上存活的。有些编程语言看上去问题很多,却很流行。或许它的那些问题也正是它流行的原因。

PHP

PHP的使用门槛非常的低,而且通常用PHP写出来的东西代码都不是那么的“漂亮”。于是有些人将PHP看成是业余程序员用的东西。然而就是这么一个看似不怎么专业的东西统治了大半个互联网。PHP的“哲学”是“quick and dirty”。在一定程度上beauty和quick并不容易并存。PHP将quick和简单作为第一要求,代码的漂亮退居第二。dirty的代码并不容易维护,所以通常在系统在复杂后,复杂业务逻辑功能将交由其他技术实现。PHP则安心做着自己表现层的事。

Java

有人说Java是给笨人用的语言。这话虽然很难听,不过在一定程度上这还真就是Java的哲学。Java充分利用语言特性和IDE等自动化工具来避免程序员犯错,让人海战术成为可能。对于大多Java项目只需要少数的牛人来设计系统构架和主要接口,下面的具体实现用“笨人”来做就可以了。

Python

python的哲学是“quick and clean”。在一定程度上说python确实非常clean也很quick。不过python的clean可让也让python变的有些平庸。python号称什么都能做,却又没在哪个领域特别突出。

Ruby

Ruby强调人文关怀,编程是一件有乐趣的事,你可以按照自己喜欢的方式去使用ruby。相对而言ruby可能更容易发挥个人的创造性,但在团队协作时则容易遇到麻烦。