django bulk_create support post_save signal
django 默认 bulk_create
不支持 signal
,可以通过自定义 models.Manager
支持这个特性
1 | class CustomManager(models.Manager): |
使用实例:
1 | class Test(db.models): |
django orm custom mysql round function
Django.db.models
自带的 Round
的函数没有不支持小数点位数的保留,默认保留2位,通过自定义的 Func
可以实现调用 MySQL
的 ROUND
函数
functools.lru_cache 的正确用法
使用 pre-commit 配合 black、isort 自动格式化 Python 文件
为了保证 Python 的代码规范,在使用
git commit
提交代码之前,需要使用 blake、isort 工具对提交的文件进行格式化,如果提交的代码符合规法则 commit 成功,否则自动格式化文件,然后重新 commit
整个工作流大概是这样子:
其中 black
是代码格式化工具,可以通过 pip install black
后直接使用,使用方法如下:
1 | black {source_file_or_directory}... |
同时也支持配置文件自定义规则,详细内容可以参考官方文档 The uncompromising code formatter — Black
isort
则是用来规范 python 库的引入的,按字母顺序对 packages 进行排序,并自动分为不同的部分和类型,同样可以通过 pip install isort
后直接使用,使用方法如下:
1 | isort mypythonfile.py mypython file2.py |
和 black
一样也支持配置文件自定义规则,具体内容参考官网 isort (pycqa.github.io)
pre-commit
是整个工作流最重要的一环,pre-commit
是 git-hooks
中的一个重要的钩子,它在键入提交信息前运行。可以用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。 如果该钩子以非零值退出,Git 将放弃此次提交。 上面提到的机制是整个工作流可以进行的关键。
完全可以自定义 pre-commit
钩子的,但是如果只是为了检验的话,可以使用现成的方案 pre-commit/pre-commit,用 Python 构建,支持多语言的管理器。通过 pre-commit
这个库,简单地几步就可以实现自动化工作流。
- 安装 pre-commit
1 | pip install pre-commit |
然后通过 pre-commit --version
确定是否安装成功
1 | $ pre-commit --version |
- 添加
.pre-commit-config.yaml
的配置文件
可以通过 pre-commit sample-config
生成一个默认的配置文件,这里贴一下关于 black
和 isort
的配置文件
1 | repos: |
支持的配置项很多,具体参考 plugins
- 安装 git hooks 脚本
1 | $ pre-commit install |
然后就就大功告成了
类的自定注册以及应用场景
在 3.6 之前可以通过
meta class
去实现,3.6 之后可以通过__init_subclass__
实现
meta class
通过 meta class
的 __new__
方法可以实现自动注册 class
的功能,原理很简单,就是通过元类去控制类的创建,在调用 __new__
方法的时候自动将 class
注册
1 | class MetaClass(type): |
__init_subclass__
__init_subclass__
是 3.6 后引入的一个新的特性,一个 hook,可以让所有的子类在创建之后执行一些初始化的操作,通过这个特性我们就可以更简单地实现上述通过元类实现的功能。
1 | class ParentClass: |
需要注意的是 __init_subclass__
没有非关键字参数。
应用
代码存在如下的逻辑:
1 | if category == 'a': |
存在很多都处理逻辑,每个逻辑可能由不同的开发人员编写,全部放到一个代码块中,可以预见随着分支的增多,这部分代码会变得越来越庞大,不利于后期维护,所以最简单的方案,存在一个 dict 保存 category
和 处理方法的映射关系:
1 | category_to_func = { |
然后就可以将最初的代码简化为:
1 | return category_to_func.get(category)() |
实现
方法一
基于上面的思路,可以写出最简单的方法,让大家自行编写自己的处理方法,然后在 category_to_func
中统一注册自己的方法。
1 | category_to_func = { |
这样子没有任何问题,但是不够优雅,开发人员撰写了自己的处理方法之后,还要去指定的地方注册自己的方法,开发体验不太好。
如果存在一种方式,开发人员只需要撰写处理方法,代码可以自定注册就更好了。
方法二
通过上面的自注册的方法,让子类继承父类,然后将其自动注册到全局当中,这样子开发人员只需要关注自己的业务实现就可以了。
参考链接
PEP 487 – Simpler customisation of class creation
python - How to auto register a class when it’s defined - Stack Overflow
Django 自定义 SearchFilter兼容空格搜索
前言
Django 自带的 SearchFilter 是不支持空格搜索的,如果需要支持空格搜索并且保留之前的搜索功能则需要自定义 SearchFilter。
自定义 SearchFilter
继承 SeachFilter 然后实现 get_search_terms
方法,如果同时支持 space 然后又可以保持之前的搜索特性呢?
简单的做法就是,对输入的参数进行校验,如果只有含有空格就返回空格,否则就执行之前的搜索逻辑
1 | class CanSerachBothCharFilter(SearchFilter): |
pep 544 Protocols: Structural subtyping (static duck typing)
前言
在 PEP 484 中引入的 Typing hints (类型提示) 可以用来为静态类型检查器和其他第三方工具指定类型元数据。但是在,PEP 484 只指定了名义子类型的语义。在这个 PEP 544 中,指定了协议类的静态和运行时语义,这将为结构性子类型(静态鸭子类型)提供一个支持。
什么是鸭子类型(Duck Typing)?
If it walks like a duck and it quacks like a duck, then it must be a duck。
简单地说,”如果它走路像鸭子,叫起来像鸭子,那它一定是鸭子“。
在编程中这就意味着当我们编写接收特定输入的函数时,我们只需要关心该函数输入的行为、属性,而不是该函数输入的显式类型。
例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为”鸭子”的对象,并调用它的”走”和”叫”方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的”走”和”叫”方法。— 百度百科
那么如何在 Python 中实现“鸭子类型”呢?
鸭子类型
现在有这样子场景,我们需要编写一个函数去计算一个拥有边长(len_side)的物体的周长,伪代码如下:
1 | function calcute_circumference(shape): |
从伪代码不难看出,我们不关心是什么的物体,我们只需要保证这个物体可以有一个 len_side(列表) 字段就可以了。
现在我们可以借由 Protocol
去实现它。
1 | from typing import Protocol |
从上面的例子不难看出 Protocol
有点类似 Java 中的接口,只需要在函数上使用它,并不需要去关注输入的参数的具体类型。
泛型
我们还可以配合使用 typing
中的 TypeVar
实现泛型参数化,让我们的函数更加抽象化,只要符合协议,无论什么类型的输入都可以。
1 | from typing import Protocol |
延伸
上述的例子都可以借由 Python 中的 ABCs 去实现的,但是两者的侧重点有所不同,由于篇幅有限,在下一篇文章,让我们仔细对比一下两者的的区别。
git commit 规范以及校验方案
前言
在使用 Git 作为版本控制工具时,每次文件发生修改的时候提交都需要 git commit
命令去记录本次的修改,否则就不允许提交,显然 git commit
是一个重要的环节,因此制定一个 Git Commit 规范是有必要的,否则就会出现混乱的提交信息,这里腾讯某团队的规范作为例子,然后通过一定的手段去帮助我们把这个规范落到实处。
目的:
- 统一团队
Git commit
日志标准,便于后续代码 review,版本发布以及日志自动化生成等等。 - 统一团队的 Git 工作流,包括分支使用、tag 规范、issue 等
Git commit 日志参考案例
总体方案
Git commit日志基本规范
1 | <type>(<scope>): <subject> |
type 类型
type代表某次提交的类型,比如是修复一个bug还是增加一个新的feature
- feat: 新增 feature
- fix: 修复 bug
- docs: 仅仅修改了文档,比如 README, CHANGELOG, CONTRIBUTE等等
- style: 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑
- refactor: 代码重构,没有加新功能或者修复 bug
- perf: 优化相关,比如提升性能、体验
- test: 测试用例,包括单元测试、集成测试等
- chore: 改变构建流程、或者增加依赖库、工具等
- revert: 回滚到上一个版本
格式要求
1 | # 标题行:50个字符以内,描述主要变更内容 |
校验方案
node 项目已经有比较成熟的方案,这里以 Python 项目为例,考虑实际使用的便利性,希望可以可以实现以下的目的:
- 成员在本地执行 git commit 的命令时就完成校验,通过则允许执行
git push
否则则需要重新提交 commit 的信息。 - 提交到 gitlab,由服务端完成再次校验
本地校验
git hooks
Git 在执行 git init
进行初始化的时候,会在 .git/hooks
目录下生成一系列 hooks 脚本:
从上图可以看到每个脚本的后缀都是以 .sample
结尾的,在这个时候,脚本是不会自动执行的。我们需要把后缀去掉之后才会生效,即将 pre-commit.sample
变成 pre-commit
才会起作用。由于只是对 commit msg 做校验,所以只需要使用 commit-msg
脚本即可。
commit-msg 脚本
在 commit-msg
hooks 中完成对 commit 消息校验
1 | #!/bin/sh |
将上述脚本保持在项目所在对 .git/hooks
目录下命名为 commit-msg
,然后执行 chmod
命令:
1 | chmod +x .git/hooks/commit-msg |
验证结果
- 不符合规法的 commit msg
- 符合规范的 commit-msg
服务端校验
Git 在服务端也同样有一些 hooks:
pre-receive
update
post-receive
每个 hooks 的具体功能可以参考 Server-Side Hooks ,其中 pre-receive
和 update
均符合使用场景,唯一区别是用户同时推送到多个分支时, update 针对每个分支都会被触发执行,而 pre-receive
只执行一次。
pre-receive
说明
在任何文件被更新时,如果$GIT_DIR/hooks/pre-receive
存在并且是可执行的文件,则 pre-receive 会被无参数触发执行一次,正常 pre-receive 触发执行的时候会接收如下的参数:
1 | sha1-old SP sha1-new SP refname LF |
其中 sha1-old 为多次 commit 的最早一次的 commit 的 id,而 sha1-new 则是最新一次的 id。除此之外,git push 的时候还会传递其他的信息,可以参考 pre-receive-hooks 。
校验方法
通过 git log old-commit-ID new-commit-ID -pretty=format:%s
提取出俩个 commit 之间的所有 commit-msg 然后逐一校验。
考虑易用性,用 golang 构建校验脚本
1 | package main |
同时支持添加配置文件
1 | { |
服务器配置 pre-receive
参考 Server Hooks 需要将编译后 pre-receive
放到制定的 repository 的钩子目录即可,具体步骤如下:
- 找到对应 repository 的
.git
目录 - 在该目录下创建
custom_hooks
的目录 - 将编译后的
pre-receive
放到该目录下(如果有配置文件也上传到该目录下) - 通过
chmod +x pre-receive
让该文件可执行,同时将该文件的用户组切换为git:git
- 推送代码验证结果