小心 Python 函数默认参数的陷阱

Python 的函数参数支持默认值,这是本人一直喜欢的特性,Python 不支持方法重载,但默认参数可起到类似的效果,还不用写多个函数。现在支持默认参数的语言普遍的,像 C++, C#, Ruby, Groovy, PHP, Scala, JavaScript 等,Java 还不行。

但是特别要小心,Python 的函数默认值与其他的语言是不同的,直接违反了最直观的常识 -- 默认参数应该是省略就每次用同样的默认值,传的话就用传入的值。

当我在 IntelliJ IDEA 中写类似如下的代码

我的 SonarLint 插件就要抱怨了

说是

SonarLint: Change this default value to "None" and initialize this parameter inside the function/method
Default argument value is mutable

阅读全文 >>

Python 类实现的装饰器及简陋 REST API

学习了函数实现的 Python 装饰器后,关于装饰器的内容还没完。Python 装饰器还是属于元编程的范畴,一谈到元(Meta), 元编程,往往能用简单的方式实现比较神奇的效果 -- 小渣男的非死不可除外。Python 还允许用类来实现装饰器,原理上就是能让 Python 对象函数用,见之前的一篇 Python 对象当函数使用及动态添加方法。关键就是类实现 __call__ 函数,对象就变成 callable, 与函数的装饰器实现归纳起来就是:一个 Python 类型能不能用 @ 当作装饰器来用只需看它是否是 callable

而且因为有了类,带属性的装饰器也会更简单,装饰器的属性就是构造函数的参数。还是来看怎么用类重新实现前面的 my_decorator 装饰器 阅读全文 >>

Python 中带属性的装饰器

刚刚完成 由 Python 的 Ellipsis 到 *, /, *args, **kwargs 函数参数, 又回想起在 熟悉和应用 Python 的装饰器,关于带属性的装饰器一直未交代,安心不下来,Python 中带属性的装饰器用得非常普遍,如 Flask 的 @app.route('/')

我们一看到 Python 的装饰器(Decorator) 会很直截的与 Java 的注解(Annotation) 联系起来,其实除了都用 @ 符号外是存在很大区别的。正如它们被翻译成的中文名那样,Java 的注解在一定程度上就是一个注释,只要没有注解处理器处理它们就可以被忽略,Java 要用反射来处理注解。而 Python 的装饰器更象是代理,函数一旦被装饰后,调用目标函数时是无法挣脱装饰器函数的控制的,是硬核的。 阅读全文 >>

由 Python 的 Ellipsis 到 *, /, *args, **kwargs 函数参数

早先对 Python *args, **kwargs 参数有所了解,也知道参数列表中的 / 表示 Positional Only, * 很少见。然而在使用 FastAPI 时看到路由函数中表示默认值采用了 ... 的方式又重新激发起我对 Python 函数参数的 *, /, *args, 和 **kwargs 的兴趣。

如 FastAPI 官方文档 Request Forms and Files 中的

@app.post("/files/")
async def create_file(file: bytes = File(...), fileb: UploadFile = File(...), token: str = Form(...)):

默认值的 File(...), Form(...), 起初还以为 ... 只是真正意义上的省略号,使用时需传入适当的参数,后来发现 ... 居然是一个 Python 实实在在的内置对象。 阅读全文 >>

FastAPI - 一款新型的 Python Web 框架(对比 Flask)

近日曾想尽办法为 Flask 实现 Swagger UI 文档功能,找到的实现方式基本上是 Flask + Flasgger, 记录在 Flask 应用集成 Swagger UI。然而不断的 Google 过程中偶然发现了一款集成了 Swagger UI 的比 Flask 还好的 Python Web 框架 -- FastAPI 。起初想要在标题中表达的意思大概是 Flask + Swagger = FastAPI, 后来发现 FastAPI 的闪亮点不仅如此,于是乎又找了些 Flask 与 FastAPI 对比的文章读一读,在文后附有链接。

本文不对 Flask 与 FastAPI 的各个方面对进行对比,本人兴趣依然还是在 FastAPI 的 Swagger UI 功能,以及与 Flask 的 Blueprint 类似的特性。如果要拿 Flask 与 FastAPI 比较的话,应该用 Flask 2.x, 因为它开始支持类似 @app.get 的装饰器,并引入了 async 路由函数。

Flask 是在 2010 年发布的,它构建于 WSGI(Python Web Server Gateway Interface) 之上的,产品环境中运行需与 uWSGI, Gunicorn 搭配,或用 mod_wsgi 模块与 Apache 集成。因发布较早,所以目前应该有较多的使用者。Flask 2.0 需要 Python 3.6+ 的支持,如果支持 async, 需 Python 3.7+

FastAPI 发布于 2018 年,构建于 ASGI(Asynchronous Server Gateway Interface) 之上,在 IO 密集型的应用中有更优越的性能。生成环境中配合 ASGI 服务器,如 UvicornHypercorn. FastAPI 最为亮丽的特性是集成了 Swagger UI -- 外加一个福利 ReDoc。FastAPI 需 Python 3.6+ 版本。 阅读全文 >>

Python async, await 的理解与使用

关于 Python 中 async, await 关键字的一些知识在去年的一篇 探索 Flask 对 asyncio 的支持 有讲到,一直没在实际上用过它们,所以基本上也就忘干净了。随着 Flask 2 加入了 async 的特性,以及 FastAPI 从一开始支持 async, 又觉得有必要重新温习一下 Python 中如何使用 async, await 关键字了。

注:由于 Flask 支持了 async, 号称 async 化 Flask 的  Quart 项目开始变得无足轻重了。

本文主要的学习材料是在 YouTube 上的一个视频 Fear and Awaiting in Async (Screencast), 其中用 Python REPL 以 Live 的形式展示,对 async, await 关键字循序渐进的讲解。

如今不少语言都支持 async, await 关键字,如 C#, JavaScript, Kotlin, Rust 等,还有今天的主角 Python。而 Java 仍然很重视函数返回值的意义,未支持 async, 只能显式的返回 Future/CompletableFuture, 而且自己控制如何在线程池中执行。 阅读全文 >>

Flask 应用集成 Swagger UI

成熟的 Web  API 框架总有一款 API 文档与之伴随,当前最知名的莫过于支持 Open API 的 Swagger 了。Python 的 Flask 框架支持 Swagger UI 也有几条路子

  1. Flasgger : 好像是 flask-swagger 的 fork
  2. flask-swagger: 许久未更新了,不用考虑
  3. flask-restful-swagger: 到目前也两年未更新了
  4. Flask-RESTPlus 的 Swagger 特性: 真需要用到 Flask-RESTPlus 就可以用它

单纯用 Flask 构建 API 的话,细数起来也就 Flasgger 比较合适,如果甩开 Flask 而用 FastAPI 的话,就不用操心 Swagger 了,因为 FastAPI 原生的支持 Swagger。

本文中我们将体验如何使用 Flasgger, 关于使用方法,在它源码的 README.md 已经描述的很清楚了。Flasgger 提供了以下几种主要的使用方式 阅读全文 >>

逐步理解 Flask 的 Blueprint(蓝本)

Python 的 Flask 框架能让我们快速的建立一个轻量级的 Web 或 REST API。对于小应用由一个 @app 装饰一撸到底就行,当项目稍具规模或要更清晰就要考虑模块化,于是来到了我们今天的话题,首先是

为什么需要 Blueprint?

比如说我们一定超级简单的 Flask 应用 main.py 的代码如下:

阅读全文 >>

构建 AWS Lambda Python Docker 镜像

AWS 的 Lambda 在 2020-12-01 开始支持用 Docker 镜像存放代码,见 New for AWS Lambda - Container Image Support。AWS Lambda 最初的对发布包的限制是 50M, 解压后(因为执行前需要解压缩)不能超过 250M,对于压缩比小于 1/5 的包来说,要突破 50M 部署包的限制就要用 2018-11-29 推出的层(layer), 即把 Lambda 的依赖可以组织为层,每个 Lambda 可引用最多 5 个层,但最终 Lambda 加上层所解压后的大小仍然有 250 M 的限制。

对于使用了大量依赖的 Lambda,比如 Python 中用了 Pandas 之类的数学分析包,250M 的大小是不够的,所以才有了 Docker 镜像化的 Lambda, 镜像的大小限制一下蹦到 10G,要构建出一个 10G Lambda 用的 Linux 镜像, 那绝对是个巨兽,至少目前是超越我的想像力,除非往里面塞入大量的业务数据。关于 Lambda 有哪些限制,请参阅 Lambda quotas

介绍完 Lambda 引入 Docker 镜像的背景后,本文接下来只关注如何构建一个 Python Lambda 镜像,对于如何部署 Docker 化的 Lambda, 不在本文的范围之内。主要的参考文档为 AWS Lambda 官方的 Deploy Python Lambda functions with container images. 阅读全文 >>

创建和发布自己的 Python 包到 PyPI 上

像 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 命令。 阅读全文 >>