[latexpage]
SciPy 是一个开源的算法库和数学工具包,可以处理最优化、线性代数、积分、插值、拟合、特殊函数、快速傅里叶变换、信号处理、图像处理、常微分方程求解器等。 它依赖于 NumPy, Pandas 也依赖了 NumPy。本文重点是体验它怎么处理最优化的问题。很多情形下通过 SciPy 的 optimize.minimize 方法寻求目标函数最小值的过程得到最优化的输入与输出。比如寻找二次元函数的根,求解线性/动态规则,金融行业的计算出最优投资组合的资产分配等。为什么 SciPy 没有 maximize 方法呢,因为没有必要,想要找到最大化的值,只要把目标函数的值取反,或者是模或绝对值的最小值。看到 minimize 方法名更让人觉得目标函数会有一个收敛值。
虽然 SciPy 对特定的问题有更直白的函数,如求根有 optimize.root, 线性规则 optimize.linprog(现不建议使用),但各种优化基本都可以回归到 minimize 方法调用。minimize 方法的原型是1def minimize(fun, x0, args=(), method=None, jac=None, hess=None, 2 hessp=None, bounds=None, constraints=(), tol=None, 3 callback=None, options=None):
除了必须的目标函数和初始值,还有更多参数,像常用的约束(contraints) - 满足某些特定条件的最优化, 线程或非线性约束等; 求解方法(method) - Powell, Newton-CG 等
下面用 optimize.minimize 来求解一些问题 Read More
使用 Python 的话用不着像 Java 那样是考虑用 Logback 还是 Log4J 的问题,因为它内置提供了完备功能的 logging 库。虽然 JDK 也有 java.util.logging(JUL), 它的特性其实也不差,如日志级别,输出格式,不同的输出目的地的选择,但在 Logback 和 Log4J 的光环之下几乎无人问津。相比而言 Python 的 logging 却极为受宠,非必要时基本不会去考虑引入第三方的日志库,如 Loguru, LogBook, Structlog, Picologging, 尽管它们也很出色,毕竟是庶出。logging 的最基本用法
在基本前面加是最字,是因为这一节仅仅是如何让 logging 作为 print() 的替代品,暂不涉及到参数的传递,异常的输出,以及格式定制,日志往哪里输出的问题。1import logging 2 3logging.info("hello")
运行,什么也看不到,因为 Python logging 的默认级别是 warning, 这不符合人的基本认知,一般 logging.info() 起码是用来替代 print() 的,居然直接用无法输出,不知该库的设计者是怎么个想法。 Read More
前两年用 AWS Lambda 搭配 API Gateway 使用是为了省钱,因为没有请求时不花钱。又由于是 Rest API, 所以实现部分用了 FastAPI 的装饰器,但不实际启动 FastAPI 的 Web 服务,Lambda 的 handler 方法根据 routeKey 手动映射到 FastAPI 的装饰方法。大概实现是def lambda_handler(event: dict, context):
当时也思考着能不能把 Lambda 的请求与 FastAPI 的 Web 服务桥接起来,却又不能真正启动一个 Web 服务,否则 Lambda 调用不能结束。比如说 AWS Lambda 收到请求时快速启动 FastAPI 服务,该服务绑定到 TCP 端口或 Socket 文件都行,然后 Lambda 请求代理到 FastAPI 服务,最后关闭 FastAPI 服务,但是想来都不那么容易实现。 Read More
fastapi_function = locate_fastapi_function(event['routeKey'])
return fastapi_function(<extract parameters from event>)
本文旨在测试 Python Flask 框架的默认并发能力,即同时能处理多少个请求,以及请求等待队列大致有多大; 并找到如何改变默认并发数。虽然网上或许很容易找到它们的默认并发数,但通过实验的方式可以得到更感性的认识。
本文写作时使用的环境为- 测试机器为 MacBook Pro, CPU 6 核超线程,内存 16 Gb
- JMeter 5.5 -- 连续发送请或压力测试
- Python 3.10.9
- Flask 2.2.2
从 JMeter 每半秒发送一个请求,连续发送 1000 个,程序中 API 方法接受到请求后 sleep 800 秒,保证在全部 1000 个请求送出之前一直占着连接,以此来找到同时被处理的请求数目,并且有足够的时间统计当前的 TCP 连接数。在测试极端规模的并发数时,由于在 Mac OS X 很难突破 5000 个线程的限制,这时就让 JMeter 分布到远程 Linux(Docker 或虚拟机) 上执行。
请求的 URL 是 http://localhost:8080/?id=${count}, 带一个自增序列用以识别不同的请求, JMeter 的 Thread Group 配置为 Number of Threads (users): 1000, Ramp-up period (seconds): 500 Read More
Python 3.10 于 2021-10-04 发布,至今已大半年,目前 AWS 的 Lambda 尚未直接支持,但用 Docker 镜像的方式使用 AWS Lambda 是可以使用 Python 3.10。Python 一年一发布的节奏比 Java LTS 还紧密。下一个版本 Python 3.11 预计在 2022-10-03 发布。在学习 Python 3.10 之前先回顾一下 Python 3.7, 3.8, 3.9 的特性(不想关心之前版本的变迁可直接跳跃到下方的 Python 3.10 新特性去)
Python 3.7 所带来的新特性- breakpoint()
- 数据类(@dataclass)
- 类型提示强化和延迟注解求值
- 时间精度的提高
- 保证字典的顺序
- async 和 await 成为关键字
- asyncio.run() 简化事件循环
- 上下文变量(ContextVar) - 可实现 ThreadLocal 和 SLF4J 的 MDC 功能
Python 的函数参数支持默认值,这是本人一直喜欢的特性,Python 不支持方法重载,但默认参数可起到类似的效果,还不用写多个函数。现在支持默认参数的语言普遍的,像 C++, C#, Ruby, Groovy, PHP, Scala, JavaScript 等,Java 还不行。
但是特别要小心,Python 的函数默认值与其他的语言是不同的,直接违反了最直观的常识 -- 默认参数应该是省略就每次用同样的默认值,传的话就用传入的值。
当我在 IntelliJ IDEA 中写类似如下的代码1def foo(a, b=[]): 2 b.append(1) 3 return b
我的 SonarLint 插件就要抱怨了
说是SonarLint: Change this default value to "None" and initialize this parameter inside the function/method
Read More
Default argument value is mutable
成熟的 Web API 框架总有一款 API 文档与之伴随,当前最知名的莫过于支持 Open API 的 Swagger 了。Python 的 Flask 框架支持 Swagger UI 也有几条路子- Flasgger : 好像是 flask-swagger 的 fork
- flask-swagger: 许久未更新了,不用考虑
- flask-restful-swagger: 到目前也两年未更新了
- Flask-RESTPlus 的 Swagger 特性: 真需要用到 Flask-RESTPlus 就可以用它
单纯用 Flask 构建 API 的话,细数起来也就 Flasgger 比较合适,如果甩开 Flask 而用 FastAPI 的话,就不用操心 Swagger 了,因为 FastAPI 原生的支持 Swagger。
本文中我们将体验如何使用 Flasgger, 关于使用方法,在它源码的 README.md 已经描述的很清楚了。Flasgger 提供了以下几种主要的使用方式 Read More像 Java 可发布包到 Maven 仓库,NodeJS 发布包到 NPM 一样,我们也可以创建自己的 Python 包并发布到 PyPI 仓库中去。或者内部使用的包,只须发布到私有的 Nexus 服务器上。
本文中的例子将创建一个 Python 包
bounded-executor, 并发布到 PyPI 上。为什么创建这个包呢?原因是直接用 Python 的 ThreadPoolExecutor 或 ProcessPoolExecutor 来提交任务的话,任务的等待队列是没有边界的,这就会造成因提交任务过快而使得内存爆满。本包最为合适的名称应该是bounded-pool-executor, 可是名字已被他人使用,但此外的实现有所不同,ThreadPoolExecutor 用 Queue(maxsize) 来控制,而 ProcessPoolExceutor 用 BoundedSemaphore 来控制。我们以经典的 Python 工程目录结构为例,构建的核心是执行
setup.py中的setup函数,由此来理解setup的最主要配置与关键命令做了些什么。这样有助于我们理解其他的 Python 包管理工具的底层行为,从中我们可以对比 poetry 的 build, install, 和 publish 命令。 Read More
本文主要探讨一下在 Python 各种创建 list, set, tuple 和 dictionary 的方式。首先看最常用的创建方式
1alist = [1, 2] # type(alist) <class 'list'> 2aset = {1, 2} # type(aset) <class 'set'> 3atuple = (1, 2) # type(atuple) <class 'tuple'> 4adict = {'k1': 1, 'k2': 2} # type(adict) <class 'dict'>
以上相当于是针对右边的值调用了相应的构造函数,如 list([1, 2]), set({1, 2}), tupe((1, 2)), dict({'k1': 1, 'k2': 2})
创建 set 和 dictionary 都是用大括号{}, 对于 tuple 如果是单个元素时,要附加一个逗号1atuple = (1,)
如果省略逗号,会怎样呢? Read More
前面讲过的几种排序多是以排序逻辑来命名的,例如冒泡,选择和插入排序,以及其他如归并排序,当然还有觉得自己足够牛 X 快速排序命名。而本文要学习的排序算法叫做希尔排序是以其设计者 Donlad Shell 命令的排序算法,该算法在 1959 年公布,能以作者来命名的算法应该是很不错的,令设计者引以为傲的。最初写出冒泡和选择排序的就没以作者来命名,可能不好意说,更可能是公共思维。
那么什么是希尔排序呢?它实际上是插入排序算法的增强版本,又称递减增量排序算法。它对待排序列表进行间隔式分段插入处理,从而总体上减少了元素的移动次数而达到性能的大大提升。那么理解希尔排序之前一定要先了解插入排序。那么为什么说希尔排序既是递减又是增量呢? Read More