每天编程:继续谈谈 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 实在没有必要支持了。

每天编程:用 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.

以上函数也收录在我们的 python 工具函数扩展包里。

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


每天编程:怎么校验银行卡是否有效

最近在公司有一个 fakedata 的项目,用来生成各类假数据,在各种测试环境,其中就有银行卡卡号的生成,中国这方面是有自己的标准的,详细的介绍可以参考这篇文章,写的很清楚。

简单来说,就是国内的银行卡卡号,主要都是银联标准,然后前面的4-8位称之为卡 bin,这个卡 bin 是可以区分是具体哪个银行,以及是否是借记卡还是贷记卡(信用卡),关键是最后一位校验位。

从校验卡号的角度来说,可以在应用的尽量前端进行判断,如果卡号校验错误,就不用去进行四要素判断,更不能进行实际的账户交易了。校验码对了,再根据卡 bin 判断是什么银行和什么性质的银行卡(这个下次再说)。

def get_bankcard_checkcode(card_number_str):
    """
    计算银行卡校验位;

    :param:
        * card_number_str: (string) 要查询的银行卡号
    :returns:
        checkcode: (string) 银行卡的校验位

    举例如下::

        from fishbase.fish_data import *

        print('--- fish_data get_bankcard_checkcode demo ---')

        # 不能放真的卡信息,有风险
        print(get_bankcard_checkcode('439188000699010'))

        print('---')

    输出结果::

        --- fish_data get_bankcard_checkcode demo ---
        9
        ---

    """
    total = 0
    even = True

    for item in card_number_str[-1::-1]:
        item = int(item)
        if even:
            item <<= 1
        if item > 9:
            item -= 9
        total += item
        even = not even

    checkcode = (10 - (total % 10)) % 10

    return str(checkcode)

实际算法部分不算复杂,参考了一些网上看到的资料。需要的朋友可以借鉴。

以上函数也收录在我们的 python 工具函数扩展包里。

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

Python 3.6 时代终将结束 迎接 3.7.x

今天看到

“Python 3.6.8 is planned to be the last bugfix release of Python 3.6. Per our support policy, we plan to provide security fixes for Python 3.6 as needed through 2021, five years following its initial release.”

Python 3.6.8 作为 3.6.x 的最后一个错误修订版本了,之后 Python 将全面转向 3.7.x。我觉得影响不是很大,Python 3.7 系列是完全兼容 3.6的,并且很多地方有了提升,一些新特性也不错。

每天编程:jupyter notebook 安装错误问题解决

用新的 wordpress 5.0 的编辑器进行编辑,还有点不习惯,感觉功能很强大的样子。之前还在担心 wordpress 那么多年江郎才尽,完全不需要,就如同我们在思考微信还能玩出什么花样,人家的 7.0 开始玩 vlog,整体界面的设计又更加好看,或者不同了。

jupyter notebook 是我们用来学习和教学 Python 时候非常好的工具,python 这几年的流行推广离不开 jupyter 在背后的功劳,其他语言中都没有看到过这么强大的交互工具,即便 Apple 当年推出 swift 时候的 playground 都略逊一筹。

只是不知道什么时候开始,大约是我在我的笔记本上安装过的 python 版本太多了缘故,不知道什么开始,jupyter nootbook 就不能正常启动了。

解决方法试了很多种,如果你得到的报错结果也是诸如 image not found,并且是从 zmq 这里出问题的话,删除你安装的 pyzmq 和 zmq,然后重新安装,就可以了。如果万一还没有好,所有相关 python 目录,比如你是用 anaconda 安装的话,就把不同的 python 环境下的这些包都删除,再重新安装。

从 WordPress 5.0 已经发布开始遐想

其实记不清是从 WordPress 哪个版本开始使用的,这么多年来,WordPress 也像 Office 一样,其实功能够用了,加上其无比强大的插件体系。可是 WordPress 依然在努力的改进和更新。

未必是看到自己的不足,却看到更好的未来。

这样的大局观,让我很钦佩。

最近几年一直在做一些技术创新,很艰难,很多时候选择了放弃,很多时候不得不放弃。现在想想,却不必自怨自艾。很多事情要坚持,总不会太差。

智商和情商以及种种,在职业上已经让我很难突破,但是谁都不可以阻碍我对电脑技术的热爱。2015年,学习 Python,为了考验自己的 Python 能力,先是2016 年做了一年业余的 Python 老师,然后2017年和同事一起写了 Python 的书;2017年年底开始接触阿里云的技术,顿感惊讶,仿佛看到另外一个世界,于是2018年的半年多业余时间,又是奋笔疾书,能力不够就靠勤奋来补,这本“冲上云霄”2019年年初应该可以付梓吧。而今年,在工作实践中,几方面的探索都让我倍感欣慰,DevOps 的实践探索,开发管理的精细化,Python 的进击,复杂 IT 项目的管理艺术。

这样想想,也很开心。虽然怀念在天目路上海银行的时候,并无人管我具体技术工作,一个人可以开开心心的研究各类开发项目。现在,背负的责任重了很多,很多无奈,很多无法平衡的平衡,但是,舞台却更大了。

舞台大了,心也需要更大。

明年要不要继续写作的节奏,是浸淫多年的 IT 项目管理,还是 DevOps ?哈哈!人生需要一些疯狂。

开始一个新的项目

最近公司 IT 建设速度明显加快了,上周开始业务中台的需求启动,今天 DevOps 的计划也准备开始试点。

有时候想想,学习编程已经三十多年了,小学时候的 Basic,初中时候沉浸在 LOGO 语言中,以及后来的 dbase 和 foxbase 这些。五年前的技能都被淘汰了,IT 世界发展的太快。但是这份初心却始终不变。我还是很喜欢编程。所以就继续喜欢吧。有时候很多东西难以说清,或许就是那么多年了,即便我没有太高天分,也毕竟花费了这么多时间和努力,总不至于太差。

开始一个新的项目,查了一下版本记录,最早是2016年4月就开始研究了,思考了那么久,还是要给自己一些压力。之前的 fish_base 项目功能虽然简单,但是从 sphinx 文档到之后 CI,都在努力学习中,然后这个项目也准备做的好一些,之后能够开源,让更多人收益。做一个基于半自然语言的报表生成工具、数据分析工具。