Leetao's Blog

Talk is cheap, show me the code

0%

前言

公司有个项目使用 Django 开发的,前段时间我们把 Django 的版本升级到最新版本了,然后就出现问题了,我们发现生产环境出现了一个慢 SQL:

1
select * from user where not is_delete

似乎看着很正常的一个 SQL,唯一需要注意的是这个 is_delete 字段,在 model.py 中是使用 BooleanField

问题

上面已经说到了我们使用的字段类似是 BooleanField,而我们使用的数据库是 MySQL 数据库,MySQL 数据库原生字段类型没有 bool 类型,所以 Django 是以 tinyint 去实现的,所以问题就来了,当你把一个 tinyint 字段当成 bool 字段去使用,就会出现 类型转换 的问题,这就是索引之所以失效的原因。

那么为什么在之前的版本没有出现呢?

因为在之前的版本,上面的 SQL 在 Django 会是这样:

1
select * from user where is_delete = 0

在 Django 3.2 版本之后就变成了前言中的那个 SQL 了,也有人对此提出了疑问,但是很遗憾,官方最后并没有修复这个问题。所以后续在其他数据库中使用 BooleanField 需要尤其注意⚠️。

解决办法

官方没有修复这个问题,那么就需要用户就解决了,那么有没有解决办法呢?答案是有的,我们需要将涉及 BooleanField 的 ORM 改成如下:

1
.filter(bool_field=models.Value(0)) # models.Value(1)

参考

Performance regression in Exact lookup on BooleanField on MySQL.

前言

Python 可以通过 ctypes 模块调用 C 语言实现的函数,调用的方式很简单:

  1. 创建一个 c 文件,然后编写对应的函数
  2. 使用 c 编译器创建一个动态链接库文件(.so)
  3. 在 Python 文件中创建一个 CDLL 的实例
  4. 最后通过 {CDLL_instance}.{function_name}({function_parameters}) 格式调用 c 语言对应的函数

步骤一

1
2
3
4
5
6
//  my_functions.c.
#include <stdio.h>

int square(int i) {
return i * i;
}

步骤二

使用下述命令,创建 so 文件

1
cc -fPIC -shared -o my_functions.so my_functions.c

编译生成 so 文件

步骤三&四

调用 C 的函数

1
2
3
4
5
6
7
8
9
10
11
12
>>> from ctypes import *
>>> so_file = "/Users/leetao/workspace/python/test/my_functions.so"
>>> my_functions = CDLL(so_file)
>>>
>>> print(type(my_functions))
<class 'ctypes.CDLL'>
>>>
>>> print(my_functions.square(10))
100
>>> print(my_functions.square(8))
64
>>>

调用 GO 代码

从上面的 C 语言的调用过程,不难发现,最重要的是生成的 .so 文件,如果我们可以将 go 编辑成 .so 文件就可以得到 Python 调用 Go 的目的了。

CGO

Go 有个标准库 CGO 可以用来创建调用 C 代码的 Go 包,当然反过来也可以。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "C" //必须引入C库

import "fmt"

//加入下面注释代码,表示导出,可以被python调用

//export Sum
func Sum(a int, b int) int {
return a + b
}


func main() {
//必须加一个main函数,作为CGO编译的入口,无具体实现代码
}

需要注意的是:

  • 注释 //export Sum 在编译 动态库(dll ,so)的时候是必须的,说明了动态库需要输出的函数
  • main函数必须写,没有执行代码,作为CGO编译的入口
  • 必须引入C库(import “C”)

编译动态库

使用下面的命令进行编译

1
go build -buildmode=c-shared -o s1.so s1.go

注意📢:

  • -o表示输出,s1.so 表示编译成so文件的名字,s1.go表示编译哪个go文件
  • “-s -w” 选项用于减小生成动态链接库的体积,-s 是压缩,-w 是去掉调试信息
    go build -ldflags "-s -w" -o main.dll -buildmode=c-shared s1.go
  • 编译模式buildmode:
编译模式

使用 Python 语言调用

1
2
3
4
5
6
7
from ctypes import cdll

lib = cdll.LoadLibrary('./s1.so')

# 调用go语言的Sum
result = lib.Sum(100, 200)
print(result)

最后

Python 和 GO 之间的参数传递需要经过 C 的疏忽类型转换,所以还需要了解 python 中 ctypes 数据类型和 python 数据类型以及 C 的数据类型对应关系

三种数据类型使用场景:

  1. ctypes数据类型为指定调用函数时的传入参数和返回值的数据类型
  2. python数据类型为调用函数时传入的参数的数据类型
  3. C的数据类型为go代码中定义的函数所需的参数和返回值数据类型

对应关系如下:

对应关系

也就是说,如果写如下的 go 代码:

1
2
3
4
5
//export hello
func hello(namePtr *C.char){
name := C.GoString(namePtr)
log.Println("Hello", name)
}

也就意味着,我们的 Python 调用代码中参数类型也需要对应起来:

1
2
3
hello = library.hello
hello.argtypes = [ctypes.c_char_p]
hello("everyone".encode('utf-8'))

参考文章

前言

在过去一年,我一直使用 github action + hexo 的方式去自动化部署我的博客到 github pages,这种方式的好处就是,你只需要配置好一次将代码仓库,然后你就只需要一个浏览器然后写 hexo 格式的 markdown 文件就够了。

下面是我的构建历史记录

构建历史记录

到目前位置累计构建了 93 次,当然并不是意味着我写了 93 篇,更新也会导致文章重新构建。这一切似乎看着还不错,但是有几点一直让我觉得很痛苦:

  1. hexo markdown 文章开头的格式
  2. 上传图片
  3. 文章的更新

hexo 有很多固定的格式,有时候每次写文章,我都不得不去找之前的文章把格式复制过来,然后在重新修改一下。除此之外,就是上传图片这件事情了,尝试过各种图床的方案,有的图床也很好用,但是基本上都需要打开他们的网站,或者借助三方的软件然后将图片上传,这种中断,让我觉得很难受,所以我想有没有更好的方式去写博客。

阅读全文 »

assert (断言) 是在编码过程中常用的手段,使用方法如下:

1
2
3
4
5
6
7
# example.py
# assert_stmt ::= "assert" expression ["," expression]
def is_admin(user_roles):
assert isinstance(user_roles,list) and user_roles != [], "No user roles found"

assert 'admin' in user_roles, "No admin role found."
print("You have full access to the application.")
阅读全文 »

前言

mybatis 支持一对多的关系映射,如果需要在这种关系下,使用 pagehelper 的话,需要注意原本的 sql 以及 resultmap 的用法, 否则可能会导致 pagehelper 返回的 total 比预期的数量大。

阅读全文 »

前言

Python 的枚举也是一个在时常被被经常使用的一个特性,在 Python3.11 之前,Python 标准库支持枚举类型除了枚举基类 Enum,就只有 IntEnum 了(创建 int 子枚举类型常量的基类),Python 3.11 开始支持了 StrEnum,如果你的当前使用版本是 Python3.11 则可以略过一下内容。
接下来要介绍的内容,则是在 Python3.11 之前的版本,如何实现 StrEnum 以及一些注意事项。

阅读全文 »

前言

Field 是 MySQL 中的一个函数,其基本使用方法如下:
作用
用于返回指定值在给定值列表中的索引位置
语法

1
FIELD(value, val1, val2, val3, ...)
阅读全文 »

以下例子均的运行环境为 Python 3.9.5,不同版本的实际运行结果可能有所不同

前言

前端时间看了 Golang 的 学习了一下 数组 和 Slice,其中 Slice 相当于动态数组,其中数组的长度是固定的,而 Slice 则是不定长的。在 Python 中是没有数组和 Slice 的概念,它们可以通通归类为 List(列表),那么问题来了,定长的 List 和 不定长的 List 在表现上会有区别吗?(这里的定长的 List 是指对 List 进行初始化,也就是所谓的预分配)
接下来通过几个例子去验证这个问题。

阅读全文 »

以下例子以 mysql 为准

前言

django orm 中关于更新,有两种操作分别是:

  1. update
  2. bulk_update

对于 createbulk_create 这俩类,毫无疑问存在大量数据插入的时候,后者效率更高一些。但是对于更新则需要根据实际情况进行分析了,了解 django 是如何实现 bulk_update ,对于我们使用会有一定的好处。

bulk_update 的应用场景是,每个需要更新的数据的 value 值各不相同,如果是相同,则没有必要去使用 bulk_update

阅读全文 »

前言

在实际开发中,有时候需要用 Python 去处理一些 JSON 文件,一旦 JSON 文件过大,就有可能出现加载时间过长,内存消耗过大的问题,甚至会导致内存耗尽。所以如果正确地处理大的 JSON 文件呢?

阅读全文 »