fishbase v1.1.6 发布

昨天,我的同事们发布了 fishbase v1.1.6。可能是一种习惯,将项目开发中的所有公用函数都封装起来,Python 的包是一种非常好用的格式,加上有 github 和 CI 工具,都可以提升开发这些公用函数的质量。同时对于我们整理思路、学习 Python 知识、单元测试、文档编写、项目管理等都有不少提升。写一个可以放在 github 上的项目和自己在项目组里折腾,压力还是略有不同的。

现在 fishbase 还是比较简单,坚持,用心,就会越来越好。

自 2016/3 初次发布以来,我们坚持不断更新,先后发布了 20 余个版本。近一年来,我们逐步形成每月更新 1 到 2 个版本的频率,抽象出了很多通用的方法,主要分为以下模块:

模块功能函数
fish_common基本函数包
fish_crypt加密数据函数包
fish_csvcsv 处理增强函数包
fish_data数据信息处理函数包,含银行卡、身份证等
fish_date日期处理增强函数包
fish_file文件处理增强函数包
fish_logger日志记录增强函数包
fish_projectproject 目录结构生成函数包
fish_random随机数据生成函数包
fish_system系统增强函数包

可以到通过以下单元中查找具体的函数列表和使用说明。


fishbase 扩展包 for python:
源代码:https://github.com/chinapnr/fishbase
文档:https://fishbase.readthedocs.io/en/latest/
pypi :https://pypi.org/project/fishbase/ 

计算缓存、优化算法和加速 Python 执行 第二部分

引入 Nim 语言

我们可以将计算密集型的函数移动到 C 扩展模块,来弥补 Python 这方面作为解释型脚本语言的不足,但是用 C 编写 Python 扩展模块并不容易,大多数人都选择使用Python,就是因为它不是 C。虽然为了性能提升引入一种新技术(语言)的代价其实有点大,但是如果这个语言和 Python 很接近,但是性能几乎和 C 语言一样,并且可以很方便的为 Python 提供扩展包,这样的话,这个引入还是值得一试的。

Nim 语言似乎还是比较小众,在它的官网网站,https://nim-lang.org/ 是这样介绍自己的:

Nim是一种系统和应用程序编程语言,支持 静态类型和编译,它通过优雅的方式提供无与伦比的性能。

Nim 具有以下特性:

  • 高性能,自动垃圾回收
  • 可以编译成 C、C++ 或者 JavaScript 语言
  • 生成无依赖的二进制代码
  • 可以运行在 Windows、macOS 和 Linux 等

是不是听上去很神奇,可以编译成二进制代码不算稀奇,可以编译成 JavaScript 还是很不错的,这样就可以全栈通吃了。(这一点很像 Kotlin 语言)我猜测 Nim 的工作原理可能也是将自己编译成 C 语言,然后再将 C 语言编译成二进制代码,同样,也可以将自己转换为 C++ 或者 JavaScript 的等价语言,当然通过 lex 和 yacc 的分析加上应该非常复杂的模板和优化技术。

Nim 语言的安装

Nim 的安装看上去有点繁琐,可以参考 Nim 的官方文档:https://nim-lang.org/install_unix.html

在 macOS 上,最容易的安装方法是 brew install nim

然后推荐几乎万能的 Visual Studio Code ,免费好用。可以到这里下载:https://code.visualstudio.com/。微软这几年还是做出了很多非常优秀的产品,且有开放的心态,才会有这样一个功能超强、支持很多种开发语言,并且还是开源免费的优秀开发工具。

打开 Visual Studio Code 后,安装 Nim 扩展,以及 Code Runner 扩展。

Nim 扩展使得 VS Code 具备支持 Nim 语法高亮。

而 Code Runner 扩展则功能强大,支持在 VS Code 中运行几乎所有主流语言。

按照 Nim 官方介绍,你可以运行一段最简单的 Hello World 程序来测试一下。输入 echo "Hello World!",保存为 helloworld.nim,然后点击运行按钮,就会看到 Hello World!,如果没有出来正确结果的话,可以参考 Nim 官方文档。

我们可以运行一个复杂的,其实也就是刚才那个斐波那契数列的 Nim 程序版本:

import math, strformat, times

proc fib(n: int): int =
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

when isMainModule:
    let x = 47
    let start = epochTime()
    let res = fib(x)
    let elapsed = (epochtime() - start).round(2)
    stderr.writeLine(&"Nim Computed fib({x})={res} in {elapsed} seconds")

用 Nim 制作 Python 扩展包

好,重点来了,我们通过 Nim 的扩展程序安装一个 nimpy 的包,就可以将 nim 编译后的程序作为 C 的二进制程序给 Python 程序使用了。

安装 nimpy 包:nimble install nimpy

下面 Nim 程序实现了算法:

import nimpy

proc fib(n: int): int {.exportpy.} =
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

假设这个程序存盘文件名为 fib_nimpy.nim

然后我们执行 nim c -d:release --app:lib --gc:regions --out:fib_nimpy.so fib_nimpy.nim ,

稍等片刻,看到一堆提示信息和 operation successful (29124 lines compiled; 1.862 sec total; 47.984MiB peakmem; Release Build) [SuccessX] ,编译已经成功,我们会看到一个 fib_nimpy.so 文件。so 文件是 unix 的动态连接库,是二进制文件,作用相当于windows下的 .dll 文件。我没有测试,估计在 Windows 环境下应该可以生成 dll 文件。

然后将 so 文件复制到和 Python 程序同样的路径,import 刚才生成的 so 文件。

import time
from fib_nimpy import fib

if __name__ == "__main__":
    x = 47
    start = time.time()
    res = fib(x)
    elapsed = time.time() - start
    print("Py3+Nim Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))

我们可以看到 from fib_nimpy 就是刚才的 so 文件,其中的 fib 是我们在前面 Nim 语言中具体定义的函数。

执行上述代码,也就是没有任何优化的直接用递归硬算,性能是 Python 的 1000 倍,因为最慢的计算代码已经是和 C 语言同样速度的代码了。

沿袭这个思路,我们可以将所有消耗 CPU 资源的函数,特别是涉及到复杂计算的函数,都可以用 Nim 实现函数功能,然后被 import 到主 Python 程序来获得性能的大大提升。从前面的例子可以看到 Nim 语言可比 C 要简单好学得多。不过 Nim 语言还比较小众,甚至注定一直比较小众,没问题,聪明勤奋的你,为了性能优化是值得去学习的,再说 Nim 语言的优势还远远不止这点。

计算缓存、优化算法和加速 Python 执行 第一部分

从斐波那契数列谈起

这里 https://robert-mcdermott.gitlab.io/posts/speeding-up-python-with-nim/讨论通过一种称之为 Nim 的技术框架来进行 Python 的加速(后面会对 Nim 技术详细介绍)。文章从计算斐波那契数列开始举例,用递归方式来计算,并且可以看到同样的计算方式,Python 和其他语言的速度上有不小的差异。

上述文章中提到,的确 Python 是一种优秀的编程语言,针对程序员的工作效率进行了优化;令人惊讶的是,你可以非常快速实现的从创意到最低可工作的一个解决方案。它通过其非常灵活的特性和易于编写和阅读的语法,大大缩短了代码开发时间。某种程度上,我们一直说 Python 的实现方式非常接近大脑的思考方式。(从机器学习的发展史来看,大脑的运转速度,比如计算加法的能力远远比不上现在的电脑,但是大脑在复杂推理的场景下有很大的优势。所以很多时候我觉得 Python 的直观易学要比速度快重要的多,如果必须牺牲其中一个特性的话。)

虽然 Python 具有很低的“代码开发时间”,但它具有很高的“代码执行时间”。为了解决Python非常低的执行性能,Python 的许多扩展模块都是用C / C++ 等高性能语言编写的。像 C / C++ 这样的语言与 Python 完全相反;,它们有很高的“代码开发时间”和非常低的“代码执行时间”。对于每种可能需要的计算密集型任务,不可能都有现成的扩展模块,并且在C / C++ 中编写自己的扩展模块以加速Python代码的慢速部分对于大多数Python程序员来说是遥不可及的。好在我们有不少方式可以改变这点。

为了了解 Python 如何执行 CPU 密集型任务,我们使用一个非常耗时的递归 Fibonacci 算法来确定序列中的第 47 个数字,以模拟计算密集型任务,计算复杂度是 (O(2^n)) 。

斐波那契数列: 根据高德纳(Donald Ervin Knuth)的《计算机程序设计艺术》(The Art of Computer Programming),1150年印度数学家 Gopala 和金月在研究箱子包装对象长宽刚好为1和2的可行方法数目时,首先描述这个数列。在西方,最先研究这个数列的人是比萨的列奥那多(意大利人斐波那契 Leonardo Fibonacci),他描述兔子生长的数目时用上了这数列。斐波那契数列就是这样:0, 1, 1, 2, 3, 5, 8, 13, 21……

摘自 https://zh.wikipedia.org/wiki/斐波那契数列

斐波那契数列可以表示如下:

用递归方式非常容易实现:

def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

if __name__ == "__main__":
    x = 47
    start = time.time()
    res = fib(x)
    elapsed = time.time() - start
    print("Python Computed fib(%s)=%s in %0.4f seconds" % (x, res, elapsed))

这里的 47 是指计算到斐波那契梳理的第 47 位,在我的电脑上计算结果是

Python Computed fib(47)=2971215073 in 667.7838 seconds

将近 11 分钟,电脑配置 Macbook Pro, 2.5 GHz Intel Core i7,16 GB 1600 MHz DDR3,这台电脑是2015年年中的,算是中等计算水平吧。Python 版本 3.7.2。

原文作者的机器性能要更加好一点,Ubuntu 16.04LTS,Intel Xeon E5-2667v3 CPUs 3.20GHz.

如下表对比,我们可以看到 Python 3 在速度上要比 C 语言慢了将近 100 倍,比 Java 也慢了将近 70倍。即便是 PyPy,依然和 C 、Java 语言相比不是一个数量级的。

C Computed fib(47)=2971215073 in 4.58 seconds

Java Computed fib(47)=2971215073 in 7.74 seconds

Go Computed fib(47)=2971215073 in 10.94 seconds

JavaScript Computed fib(47)=2971215073 in 21.384 seconds

PyPy Computed fib(47)=2971215073 in 93.63 seconds

Ruby Computed fib(47)=2971215073 in 191.57 seconds

Python3 Computed fib(47)=2971215073 in 504.55 seconds

Perl5 Computed fib(47)=2971215073 in 980.24 seconds

R Computed fib(47)=2971215073 in 2734.70 seconds

递归计算虽然简洁明了,实际上有包含大量的重复计算,因此称之为计算密集型,下图可以说明递归过程计算的重复性:

利用计算缓存进行优化

我们先用一个非常有效的方式来进行优化,可以让 Python 程序计算斐波那契数列立刻达到 C 语言的水平。

在 stackoverflow 上有专门讨论 python 实现 斐波那契数列的一个帖子。里面有很多实现方式,有的非常巧妙执行速度也非常快。https://stackoverflow.com/questions/494594/how-to-write-the-fibonacci-sequence

我称这个方法是计算缓存,因为递归时候有大量的都是重复计算之前计算过的步骤,我们把每一次的计算输入和输出都存储下来,形成一个缓存,这样一个(O(2^n)) 的复杂度就成了 (O(n)),比如当计算第 5 个数列中的数字时,第 3 个和 第 4 个都已经在缓存中,这样就变成了简单的加法,而不需要真正的递归计算了。并且并不失递归的优雅本质。

我们来看一下代码:

def cache_fib(n, _cache={}):

    if n in _cache:
        return _cache[n]
    elif n > 1:
        return _cache.setdefault(n, cache_fib(n-1) + cache_fib(n-2))
    return n

if __name__ == "__main__":
    x = 47
    start = time.time()
    res = cache_fib(x)
    elapsed = time.time() - start
    print("Python Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))

这样修改后的执行速度就是极速了,为此我把计算时间的代码精确到了小数点后八位,否则显示的就是 0 。

Python Computed fib(47)=2971215073 in 0.00016499 seconds

虽然我们用缓存的方式打败了所有其他语言有点胜之不武,但是在真正的业务系统开发过程中,这样做无可厚非,且应该大力推广。

“Fibonacci Numbers in Python” https://mortada.net/fibonacci-numbers-in-python.html 这篇文章也专门讨论了在 Python 中如何实现斐波那契数列,并且展示了如何使用 pandas 和 matplotlib 技术来可视化的分析执行效率。

计算缓存

缓存技术不是新技术,只是其概念在实际使用中再发生着变化。我们可以学习一下标准的缓存的定义。

Cache一词来源于1967年的一篇电子工程期刊论文。其作者将法语词“cache”赋予“safekeeping storage”的涵义,用于计算机工程领域。CPU的缓存曾经是用在超级计算机上的一种高级技术,不过现今计算机上使用的的AMD或Intel微处理器都在芯片内部集成了大小不等的数据缓存和指令缓存,通称为L1缓存(L1 Cache即Level 1 On-die Cache,第一级片上高速缓冲存储器);而比L1更大容量的L2缓存曾经被放在CPU外部(主板或者CPU接口卡上),但是现在已经成为CPU内部的标准组件;更昂贵的CPU会配备比L2缓存还要大的L3缓存(level 3 On-die Cache第三级高速缓冲存储器)。

主存容量远大于CPU缓存,磁盘容量远大于主存,因此无论是哪一层次的缓存都面临一个同样的问题:当容量有限的缓存的空闲空间全部用完后,又有新的内容需要添加进缓存时,如何挑选并舍弃原有的部分内容,从而腾出空间放入这些新的内容。解决这个问题的算法有几种,如最久未使用算法(LFU)、先进先出算法(FIFO)、最近最少使用算法(LRU)、非最近使用算法(NMRU)等,这些算法在不同层次的缓存上执行时拥有不同的效率和代价,需根据具体场合选择最合适的一种。

https://zh.wikipedia.org/wiki/缓存

在现代开发系统中,由于数据吞吐量太大了,并且重复访问的情况也非常多,为了有效的节约算力、提升响应速度、减少对系统的依赖,缓存技术大大发展。比如 Redis 就是广泛使用的一种缓存技术。刚才我们看到在递归计算中使用了缓存,对于性能可以有千万倍的提升。

下面介绍一下 Python 中的一些缓存技术。

lru_cache

Python 语言就是一个瑞士军刀,绝大多数需要的功能都已经整装待发。lru_cache 就是 Python 3.2 开始在 functools 中增加一个函数,通过装饰器的方式来缓存一个函数的执行结果。https://docs.python.org/3/library/functools.html#functools.lru_cache

在上面的文档中,我们可以看到 Python 官方同样是用斐波那契数列作为例子,可见这个斐波那契数列的递归其实是多么的不深入人心啊。

import time
from functools import lru_cache


@lru_cache(maxsize=None)
def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)


if __name__ == "__main__":
    x = 47
    start = time.time()
    res = fib(x)
    elapsed = time.time() - start
    print("Python Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))
    print(fib.cache_info())

我们在最后一行增加了显示缓存击中的情况。

Python Computed fib(47)=2971215073 in 0.00003409 seconds

CacheInfo(hits=44, misses=47, maxsize=None, currsize=47)

执行速度上可以看到比刚才我们自己实现的缓存算法还要快。

DiskCache

DiskCache 是一个纯 Python 的缓存包,http://www.grantjenks.com/docs/diskcache/

DiskCache 可以有效的只用上 G 空间用于缓存,通过利用坚如磐石的数据库和内存映射文件,缓存性能可以匹配并超越行业标准解决方案。(放在这里解决斐波那契数列问题有点杀鸡用牛刀了!)

DiskCache 的主要功能如下:

  • 纯 Python 构造
  • 完整的文档
  • 100% 单元测试覆盖
  • 数小时的压力测试
  • Django 兼容的 API
  • 线程安全和进程安全
  • 支持多种缓存算法 (包括LRU 和 LFU)
  • Keys 支持标签、元数据等
  • 基于 Python 2.7 开发,在 CPython 2.7, 3.4, 3.5, 3.6 和 PyPy 上测试
  • 支持 Linux, Mac OS X 和 Windows
  • 通过 Travis CI 和 AppVeyor CI 的集成测试

DiskCache 的功能更像是 RedisMemCached,并且性能优异,我们可以看看下面的性能对照表。

DiskCache 功能非常多,我们用文档中的一个例子修改一下,来继续刚才的斐波那契数列的 demo,前面的计算缓存是将相关缓存代码写在了函数的逻辑中,通过 DiskCache 的 FanoutCache 来沟通一个函数的装饰器,同样且更加通用的达到计算缓存的效果。

from diskcache import FanoutCache
import time

cache = FanoutCache('/tmp/diskcache/fanoutcache')


@cache.memoize(typed=True, expire=1, tag='fib')
def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)


if __name__ == "__main__":
    x = 47
    start = time.time()
    res = fib(x)
    elapsed = time.time() - start
    print("Python Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))

执行效果如下:

Python Computed fib(47)=2971215073 in 0.02766585 seconds

装饰器中的 expire 参数是多少毫秒后失效,使用 DiskCache 的话,在装饰器发挥作用前定义了磁盘缓存文件的位置。如果将参数 expire 调整到比较大的数值或者 None 的话,会发现再次执行的话,速度大大提升。

Python Computed fib(47)=2971215073 in 0.00032520 seconds

DiskCache 功能强大,值得用专门的章节来完整的介绍。

cache.py

如果想既有 lru_cache 这样简单,又暂时不想用 DiskCache 这样的大家伙,但是其文件可以实例化还是不错的一种解决方案,我们还可以尝试用一下 cache.py,出处在这里 https://github.com/bwasti/cache.py

只要 import cache 之后,就可以直接使用了。

import cache
import time


@cache.cache(timeout=20, fname="my_cache.pkl")
def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)


if __name__ == "__main__":
    x = 47
    start = time.time()
    res = fib(x)
    elapsed = time.time() - start
    print("Python Computed fib(%s)=%s in %0.8f seconds" % (x, res, elapsed))

执行结果如下:

Python Computed fib(47)=2971215073 in 0.02948809 seconds

列出四种缓存方式的执行速度:

内置缓存 0.00016499 seconds

Python 自带 lru_cache 0.00003409 seconds

DiskCache 0.00032520 seconds

cache.py 0.02948809 seconds

现在在这类计算缓存的场景下,Python 自带的 lru_cache 速度最快,而 DiskCache 包性能均衡,考虑到其强大的功能,值得一试,cache.py 的性能一般,但是代码非常简洁,可以学习。

关于角色模型

最近一年一直比较关心并行计算,一开始因为 python 的线程锁实在麻烦,然后又因为一直受到 k8s 的弹性伸缩影响,加上最近又看了不少函数计算的材料和相关的 POC。我之前曾经设想过,如果将程序都用函数计算的方式,同时通过 DSL 来连接这些函数,这样既可以应对访问压力,又可以处理复杂逻辑,加上弹性,费用也不会太高,达到最佳 ROI。

看到阿里云最新发布的类似 numpy 的 python 包用于并行计算来处理本来需要大内存的机器学习运算,其中提到了通过角色模型。发现这个角色模型或许可以满足前面设想的一些逻辑。

wiki 告诉我们:在计算机科学中,角色模型(英语:Actor model)是一种并发运算上的模型。“角色”是一种程序上的抽象概念,被视为并发运算的基本单元:当一个角色接收到一则消息,它可以做出一些决策、创建更多的角色、发送更多的消息、决定要如何回答接下来的消息。角色模型在1973年于Carl Hewitt、Peter Bishop及Richard Steiger的论文中提出。

支持角色模型的语言非常多,

角色模型推崇的哲学是“一切皆是角色”,这与面向对象编程的“一切皆是对象”类似,但是面向对象编程通常是顺序执行的,而角色模型是并行执行的。角色是一个运算实体,回应接受到的消息,同时并行的:

发送有限数量的消息给其他角色;
创建有限数量的新角色;
指定接受到下一个消息时的行为。
以上操作不含有顺序执行的假设,因此可以并行进行。

发送者与已经发送的消息解耦,是角色模型的根本优势。这允许进行异步通信,同时满足消息传递的控制结构。

消息接收者是通过地址区分的,有时也被称作“邮件地址”。因此角色只能和它拥有地址的角色通信。它可以通过接受到的信息获取地址,或者获取它创建的角色的地址。

角色模型的特征是,角色内部或之间进行并行计算,角色可以动态创建,角色地址包含在消息中,交互只有通过直接的异步消息通信,不限制消息到达的顺序。

几乎所有主流语言都支持角色模型,在 Python 里面保持更新的库是:

Pykka:https://github.com/jodal/pykka
Pulsar:http://quantmind.github.io/pulsar/

之后会继续研究角色模型的使用和实用性。

每天编程:继续谈谈 python pathlib

本文内容翻译自 python3_with_pleasure 

pathlib 现在是 python 3 的标准库,避免你来写一大堆 os.path.join。

from pathlib import Path

dataset = 'wiki_images'
datasets_root = Path('/path/to/datasets/')

train_path = datasets_root / dataset / 'train'
test_path = datasets_root / dataset / 'test'

for image_path in train_path.iterdir():
    with image_path.open() as f: # note, open is a method of Path object
        # do something with an image

我们可以看到一个在机器学习中使用到的场景,训练数据的路径和测试用路径,通过很简单的 / 符号就连接起来了。代码安全、简洁、可读性好。

pathlib.path 有一大堆的方法和属性,我们可以减少很多花费在搜索引擎上的时间。

p.exists()
p.is_dir()
p.parts
p.with_name('sibling.png') # only change the name, but keep the folder
p.with_suffix('.jpg') # only change the extension, but keep the folder and the name
p.chmod(mode)
p.rmdir()

每天编程:非常好用的 python 路径文件处理库 pathlib

的确,我之前也是习惯用 os.path.join ,倒是也没有觉得有什么不好,直到我看到了 pathlib,应该是 python 3.x 时代引入的新的专门更加优雅的处理比较麻烦的路径拼接、文件是否存在、路径是否存在等操作系统文件路径相关的Python 官方函数。

比如你可以用 path1 / path2 / filename 这样的简单直观的方法来完成路径和文件名的拼接,通过 PurePosixPath 和 PureWindowsPath 的类显式的兼容多操作系统。

原来的 os 库发展太久了,如同 python 的日期时间库一样,在长时间的进化中包和其函数的结构越来越混乱了。从下面的 os 和 pathlib 的函数比较可见一斑:

os and os.pathpathlib
os.path.abspath()Path.resolve()
os.chmod()Path.chmod()
os.mkdir()Path.mkdir()
os.rename()Path.rename()
os.replace()Path.replace()
os.rmdir()Path.rmdir()
os.remove()os.unlink()Path.unlink()
os.getcwd()Path.cwd()
os.path.exists()Path.exists()
os.path.expanduser()Path.expanduser() and Path.home()
os.path.isdir()Path.is_dir()
os.path.isfile()Path.is_file()
os.path.islink()Path.is_symlink()
os.stat()Path.stat()Path.owner(),Path.group()
os.path.isabs()PurePath.is_absolute()
os.path.join()PurePath.joinpath()
os.path.basename()PurePath.name
os.path.dirname()PurePath.parent
os.path.samefile()Path.samefile()
os.path.splitext()PurePath.suffix

Pathlib 非常完美的体现了 Python 的优雅,更多信息可以参考官方文档。打算将 fishbase 中的一些封装的文件处理函数逐步更换为对 Pathlib 的封装,Python 2.7 实在没有必要支持了。

不做世界的鸡肋

昨天,周五,很高兴收到出版社的来信,书稿终于通过了初审。

开发二部一年一度的迎新趣味知识竞赛,如期举行,又是百来号人,可惜,不少同事的名字我都叫不全。

晚上在神旺自助餐,部门的尾牙。记得第一次到这个餐厅,还是2011或者2012年某个冬天,那天下雪,回去时候在内环,车滑的厉害。那时候公司还在漕河泾,去到这个餐厅,就是觉得宜山路很堵。时间流逝,每年新春前,各类聚餐,公司年会,顿感岁月神偷。

昨天,周五,两位相处很长时间的同事 last day,多少有点伤感。记得2014年,我们聚餐的时候,和他们相谈甚欢,其中一位说等上市后,可以驾车去西藏云云,加上原来负责运维的胡老师退休,2014年之前的“老人”,只剩下我了。

不进则退,人生就是这样的不容易,也是无奈,也是自勉。没有人可以安逸,达摩克里斯之剑永悬。

今天,周六,看完了“无名之辈”,其中一位演员有同事认识,据说从2006年开始坚持表演,其貌不扬,演技精湛。

片尾曲,特别震撼,平静的旋律,极有张力的演绎:

我们不做世界的鸡肋!

Call of Duty,记录一下

很惭愧,2015年买的 Call of Duty – Advanced Wafare,之前每一年大概就玩了一小时,加上 PS4 的手柄操作很不熟练,所以一直有点畏难。一直到今天,才终于过关第一个首尔战役,拿到了两枚勋章。

游戏机大概是每个男生从小时候开始的梦想,现在恐怕早就不止男生喜欢打游戏了,我觉得我女儿最喜欢的也是各类手机游戏,这个话题太大。

我们小时候还没有什么游戏机,红白机都已经是我初中年代的产物了,那时候我已经是 Apple II 电脑的狂热 fans,当然也在电脑上花费了很多游戏时间,很难抵挡那些游戏带来的魅力。现在我们无法想象和理解当年那些看上去那么粗糙,很多画面要靠联想才知道说啥的游戏,怎么就让我们乐不思蜀了。

后来其实对很多游戏都是浅尝辄止,或许也是因为中学时代的这个原因,是有点后悔,如果当时多花费一些时间在编程上,说不定就会更好。有时候,假期里很长时间泡在学校的机房,其实大部分时候都是在玩,所以这个深深的悔意使得后来我对游戏处于一个敬而远之的态度,小游戏玩玩,但是绝不沉迷。后来,控制力好了很多,也不刻意了。甚而又购买了不少制作游戏的工具,圆一下少年时代的游戏制作梦想!

有时候,看到女儿贪玩,虽然要说她,但其实内心很理解,我们就是这样长大的,没什么好后悔的,这就是人生。

每天编程:用 Python 的类方法构造通用函数

今天花了十分钟将之前 fishbase 的身份证和银行卡的7个函数修改为类方法,这样调用基本没啥改变,调用方法优雅一些,并且用类管理之后,暴露接口等都比较好控制,类相比函数的好处就不用再说了。(很遗憾,20多年前我在用 Turbo Pascal 以及后来 Delphi 的很长一段时间内,都没有理解这一点,一直是面向过程编程,大约一直到 2005年才突然顿悟,应该用面向对象的方法,包括和自己的共享软件中的故事,以后可以专门聊聊。)

我在代码里保留了一小段 Classmethod 的例子:

class CardBin(object):

    @classmethod
    def a(cls):
        return 'hello'

    @classmethod
    def b(cls):
        return CardBin.a() + ' world'


print(CardBin.a())
print(CardBin.b())

这个例子定义了一个类方法 a,还有一个使用了类方法 a 的类方法 b,以及外部使用这两个类方法的例子。

类方法的定义和与静态方法的好处就不多说了,至少使用的时候不用实例化,这一点对我们来说在这个场景就很好。

每天编程:怎么判断身份证号码是否正确

继续分享生成测试数据库时候的一些心得,在生成假数据时,如何判断身份证号码是否正确,和银行卡一样,身份证最后一位是校验码,不过计算方法不太一样。算法这里不详细叙述,网上很多。下面的代码可以实现这一功能,输入身份证的前面17位,返回校验码。

def get_checkcode(id_number_str):
    """
    计算身份证号码的校验位;

    :param:
        * id_number_str: (string) 身份证号的前17位,比如 3201241987010100
    :returns:
        * 返回类型 (tuple)
        * flag: (bool) 如果身份证号格式正确,返回 True;格式错误,返回 False
        * checkcode: 计算身份证前17位的校验码

    举例如下::

        from fishbase.fish_data import *

        print('--- fish_data idcard_get_checkcode demo ---')

        # id number
        id1 = '32012419870101001'
        print(id1, idcard_get_checkcode(id1)[1])

        # id number
        id2 = '13052219840731647'
        print(id2, idcard_get_checkcode(id2)[1])

        print('---')

    输出结果::

        --- fish_data idcard_get_checkcode demo ---
        32012419870101001 5
        13052219840731647 1
        ---

    """

    # 判断长度,如果不是 17 位,直接返回失败
    if len(id_number_str) != 17:
        return False, -1

    id_regex = '[1-9][0-9]{14}([0-9]{2}[0-9X])?'

    if not re.match(id_regex, id_number_str):
        return False, -1

    items = [int(item) for item in id_number_str]

    # 加权因子表
    factors = (7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)

    # 计算17位数字各位数字与对应的加权因子的乘积
    copulas = sum([a * b for a, b in zip(factors, items)])

    # 校验码表
    check_codes = ('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2')

    checkcode = check_codes[copulas % 11].upper()

    return True, checkcode

这段代码已经可以基本实现网上大部分的身份证校验器的功能了,在实际生成身份证假数据的时候,问题还要复杂一些,因为我们不能随便那一串数字,去生成一个校验码,那个意义不太大。转载如下:

公民身份号码是特征组合码,由前十七位数字本体码和最后一位数字校验码组成。排列顺序从左至右依次为六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。

地址码: 表示编码对象常住户口所在县(市、旗、区)的行政区划代码。对于新生儿,该地址码为户口登记地行政区划代码。需要没说明的是,随着行政区划的调整,同一个地方进行户口登记的可能存在地址码不一致的情况。行政区划代码按GB/T2260的规定执行。

出生日期码:表示编码对象出生的年、月、日,年、月、日代码之间不用分隔符,格式为YYYYMMDD,如19880328。按GB/T 7408的规定执行。原15位身份证号码中出生日期码还有对百岁老人特定的标识,其中999、998、997、996分配给百岁老人。

顺序码: 表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配给女性。

校验码: 根据本体码,通过采用ISO 7064:1983,MOD 11-2校验码系统计算出校验码。算法可参考下文。前面有提到数字校验码,我们知道校验码也有X的,实质上为罗马字符X,相当于10.


fishbase 扩展包 for python:
源代码:https://github.com/chinapnr/fishbase
文档:https://fishbase.readthedocs.io/en/latest/
pypi :https://pypi.org/project/fishbase/